Mar 25 2019
While working on 2d games I would scale something up or down I would find these really odd gray lines around my graphics on the edges.
Turns out these lines are caused by alpha pixel having fully transparent black color. Normally it does nothing - because the area is fully transparent.
But when you do any kind of scaling on it, the blacks from the transparent region bleed into your image. This causes these gray outlines:
Here is a great article about this problem:
http://www.adriancourreges.com/blog/2017/05/09/beware-of-transparent-pixels/
The article suggests that artists should take good care to provide color where you can’t see it. I don’t think that is easy to do or even possible with vector graphics.
But there is a simple way to fix it called Alpha Bleeding.
The solution is pretty simple just bleed normal color into black fully transparent pixels. Replace the black pixels with similar to the colors around them so that when you scale color on the edges is not affected much.
Turns out others have solved this problem before: https://github.com/urraka/alpha-bleeding
That solution turns this tree:
Into this:
It does it by filling everything black with something close to the original color. Urraka’s algorithm looked kind of complicated so I made my own using using the powers of mip-maps:
What I do is create a series of mipmaps with a special blending functions which discards fully transparent pixels:
Each mip-map is more blurry and has less alpha pixels then the last, the last mip-map is basically just the average color of the picture.
Then what I do is trace back through the original picture and find transparent pixels, then I just walk up the mip-maps until I find a solid color and I use it.
# scale image down in layers, only using opaque pixels
var layers: seq[Image]
var min = image.minifyBy2Alpha()
while min.width >= 1 and min.height >= 1:
layers.add min
min = min.minifyBy2Alpha()
# walk over all transparent pixels
# going up layers to find best colors
for x in 0 ..< image.width:
for y in 0 ..< image.height:
var rgba = image.getRgba(x, y)
if rgba.a == 0:
var
xs = x
ys = y
for l in layers:
xs = min(xs div 2, l.width - 1)
ys = min(ys div 2, l.height - 1)
rgba = l.getRgba(xs, ys)
if rgba.a > 0.uint8:
break
rgba.a = 0
image.putRgba(x, y, rgba)
Instead of black transparent, you get same color transparent, which can scale without causing gray artifacts: