Search code examples
algorithmgraphicsdrawingantialiasingraster-graphics

Dip when joining blended antialiased lines


I have a problem when joining two antialiased lines when using a blending mode, I get a dip at the point where they join. By blending mode I mean that I draw my antialiased line by calculating the ratio of line colour vs background colour, so when the ratio for a pixel is for instance 70% the new pixel is 0.7*line colour + 0.3*background colour. My antialiasing function for lines is basically made from an error function (though I suppose the same problem arises for most antialiasing functions), like this:

0.5+0.5erf(-x)

So when two lines meet, one drawn after the other, you get a dip, the joint of the two lines dips to 75% of the intensity it should be at because at that point 50% of the background was kept for the first line and then 50% of those 50% remained after the second line was drawn when 0% should be left:

1 - (0.5erfc(-x) * 0.5erfc(x))

I can only assume that it's a common problem in drawing antialiased raster graphics with joined lines so it must have a common solution, but I have no idea what this is. Thanks!

Also: Just to be clear on how the lines are drawn, in width the lines are made with a Gaussian function (e^-x*x) and both ends are rounded off using raised error functions. You can see an example of what a 10 px long horizontal line looks like by entering '0.5erfc(-x-5) * 0.5erfc(x-5) * e^(-y*y)' in WolframAlpha.


Solution

  • Eventually I found the answer to that problem. There's no sensible way to do it by directly drawing one line after the other directly onto the main image, because you don't want the lines to be blended together, you want them to be added together then have the result of that sum of lines blended into the main image.

    However that's unwieldy if you have to draw all these lines to a separate buffer and then blend that whole buffer onto the main buffer, which is what I considered and dismissed as unsuitable before asking this question. Thankfully since then I've totally changed my approach, instead of having one buffer on which to draw element after element instead I use a per-pixel approach where each pixel is calculated directly by going through a list of elements to draw, for the sake of parallelisation (with OpenCL). So instead of having to use extra buffers I simply need a small array that can hold a few extra pixel values, and in my list of elements to draw I have elements that serve as brackets, so for instance instead of having:

    image => (blend) line1 => (blend) line2 => (blend) line3

    I can have:

    image => (blend) [0 => (add) line1 => (add) line2 => (add) line3]

    which is done by replacing the use of a single pixel value by having an array of values for each depth level of brackets so in this case at v[0] you'd have the pixel from image, then you'd start with v[1] at 0 and add each line to it, and when all the lines are added the closing of the bracket would make v[1] be blended into v[0] and the correct resulting pixel value would be there.

    So that's it, it's pretty simple, it's only a problem if you don't allow yourself to use the equivalent of a group of layers in Photoshop.