1

The following code will create a transparent bitmap and then draw a white ellipse on it with anti-aliasing.

using(var background = new Bitmap(500, 500))
using (var graphics = Graphics.FromImage(background))
{
    graphics.SmoothingMode = SmoothingMode.AntiAlias;

    graphics.Clear(Color.Transparent);

    graphics.DrawEllipse(new Pen(Color.White, 50), 250, 250, 150, 150);

    background.Save("test.png", ImageFormat.Png);
 }

The bug I have found is that the anti-aliased pixels around the edge of the ellipse have an unexpected color. They have RGB values of (254,254,254) instead of (255,255,255). Since GDI+ defines transparent as ARGB (0,255,255,255) and white as (255,255,255,255) why am i seeing 254 after blending?

Bill Agee
  • 3,606
  • 20
  • 17
Zach
  • 91
  • 4
  • I imagine its a precision issue...whats the backgrounds colour? – Sayse Apr 11 '13 at 19:05
  • The backgrounds color is Color.Transparent ARGB (0,255,255,255). So I'm not sure where the precision is getting lost. – Zach Apr 11 '13 at 19:25
  • Foreground colours contributing to a particular pixel are mixed together according to their sub-pixel foreground coverage, ignoring contribution from the background. http://www.w3.org/Conferences/WWW4/Papers/53/gq-trans.html – Sayse Apr 11 '13 at 19:41
  • You are asking GDI+ to do something impossible: rendering anti-aliased pixels that will blend against any background. This cannot work by design, the image edge only looks good if the background color matches the blended-to color. Best to just not try, use SmoothingMode.None and you'll get a pure white edge. – Hans Passant Apr 12 '13 at 11:02

1 Answers1

0

I had the same problem when I generated PNGs from SVGs in Mono (Mac OS X), but for some reason not on Windows.

To fix this i copied the nearest rgb values from the figure to the antialias-pixels, but keept the antialias-alpha value.

The code is based on the example from: https://msdn.microsoft.com/en-us/library/ms229672(v=vs.90).aspx

Code to run as last step before the save method:

var pxf = PixelFormat.Format32bppArgb;
var rect = new Rectangle(0, 0, image.Width, image.Height);
BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadWrite, pxf);

IntPtr ptr = bmpData.Scan0;

int numBytes = image.Width * image.Height * 4;
byte[] rgbValues = new byte[numBytes];
Marshal.Copy(ptr, rgbValues, 0, numBytes);

for (int argb = 0; argb < rgbValues.Length; argb += 4) {
    var a = rgbValues [argb + 3]; //A
    if(a > 0x00 && a < 0xFF) { //Antialiasing:
        //Scan for neares solid with 0 transparency:
        for (int i = 0; i < 3; i++) { //3 pixels scan seems to be enough

            var right = argb + i * 4;
            var left = argb - i * 4;
            var up = argb - i * image.Width * 4;
            var down = argb + i * image.Width * 4;

            if (right < rgbValues.Length && rgbValues [right + 3] == 0xFF) {
                rgbValues [argb+2] = rgbValues [right + 2]; //R
                rgbValues [argb+1] = rgbValues [right + 1]; //G
                rgbValues [argb] = rgbValues [right];       //B
                break;
            } else if (left > 0 && rgbValues [left + 3] == 0xFF) {
                rgbValues [argb+2] = rgbValues [left + 2];
                rgbValues [argb+1] = rgbValues [left + 1];
                rgbValues [argb] = rgbValues [left];
                break;
            } else if (up > 0 && rgbValues [up + 3] == 0xFF) {
                rgbValues [argb+2] = rgbValues [up + 2];
                rgbValues [argb+1] = rgbValues [up + 1];
                rgbValues [argb] = rgbValues [up];
                break;
            } else if (down < rgbValues.Length && rgbValues [down + 3] == 0xFF) {
                rgbValues [argb+2] = rgbValues [down + 2];
                rgbValues [argb+1] = rgbValues [down + 1];
                rgbValues [argb] = rgbValues [down];
                break;
            }
        }
        //rgbValues [argb+3] = a; //Keep old alpha.
    }
}

Marshal.Copy(rgbValues, 0, ptr, numBytes);

image.UnlockBits(bmpData);

`

oddbear
  • 193
  • 1
  • 8
  • The code will also need 4 more directions, upleft, upright, downleft and downright. This is easy to implement. – oddbear Feb 05 '16 at 10:22