开发者

Color value with alpha of zero shows up as black

I'm using .NET 4.0. I don't know if this is a framework bug or if it's a GDI+ thing. I just discovered it while writing an app to swap color channels.

Let me try to 开发者_如何转开发explain the problem. I'm reading pixels from one bitmap, swapping the channels, and writing them out to another bitmap. (Specifically, I'm setting the output image's RGB values equal to the input image's alpha, and output's alpha equal to the input's green channel… or, to put it succinctly, A => RGB and G => A.) The code is as follows:

for (int y = 0; y < input.Height; y++)
{
    for (int x = 0; x < input.Width; x++)
    {
        Color srcPixel = input.GetPixel(x, y);

        int alpha = srcPixel.A;
        int green = srcPixel.G;

        Color destPixel = Color.FromArgb(green, alpha, alpha, alpha);

        output.SetPixel(x, y, destPixel);
    }
}

Similarly, I've tried this:

        int color = green << 24 | alpha << 16 | alpha << 8 | alpha;

        Color destPixel = Color.FromArgb(color);

        output.SetPixel(x, y, destPixel);

For the most part, it works.

The problem: regardless of what the RGB values are, when alpha is zero, the resultant RGB value is always pure black (R:0, G:0, B:0). I don't know if this is some sort of FromArgb() "optimization" — using .NET Reflector, I don't see FromArgb() doing anything strange — or if Bitmap.SetPixel is the culprit — more likely since it defers to native code and I can't look at it. Either way, when alpha is zero, the pixel is black. This is not the behavior I expected. I need to keep RGB channels intact.

At first I thought it was a pre-multiplied alpha issue, because I'm loading DDS files using my home-brewed DDS loader (which I built to spec and which has never given me any issues), but when I specify an explicit alpha of 255, like this:

        Color destPixel = Color.FromArgb(255, alpha, alpha, alpha);

...the RGB channels show up correctly — i.e., none of them turns out black — so it's definitely something within GDI+ that erroneously assumes RGB values can be safely ignored if the alpha is zero… which, to me, seems like a pretty stupid assumption, but, whatever.

Further exacerbating the problem is that the Color type is immutable, which makes sense for a structure, but it means I can't create a color and then assign the alpha… which, if SetPixel() is the culprit, wouldn't matter anyway. (I've tested this by geting the pixel again immediately after setting it and seeing the same results: zero alpha = zero RGB).

So, my question: has anyone dealt with this issue and come up with a relatively simple workaround? In an effort to keep my dependencies down, I am loathe to import a third-party image library, but since GDI+ is making buggy assumptions about my color channels, I may not have a choice.

Thanks for your help.

EDIT: I solved this, but I can't post the answer for another seven hours. Awesome.


Sorry for the delay. Anyway, I should have worked on this a bit longer before posting, because I found a solution about five or ten minutes later. To be clear, I didn't find a solution to the stated GDI+ issue, but I found a suitable workaround. I thought about how, in other API's, I would lock a surface and transfer bytes directly to another surface, so I took that approach. After a little help from MSDN, here's my code (sans error handling):

Bitmap input = Bitmap.FromFile(filename) as Bitmap;

int byteCount = input.Width * input.Height * 4;

var inBytes  = new byte[byteCount];
var outBytes = new byte[byteCount];

var inBmpData = input.LockBits(new Rectangle(0, 0, input.Width, input.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

Marshal.Copy(inBmpData.Scan0, inBytes, 0, byteCount);

for (int y = 0; y < input.Height; y++)
{
    for (int x = 0; x < input.Width; x++)
    {
        int offset = (input.Width * y + x) * 4;

    //  byte blue  = inBytes[offset];
        byte green = inBytes[offset + 1];
    //  byte red   = inBytes[offset + 2];
        byte alpha = inBytes[offset + 3];

        outBytes[offset]     = alpha;
        outBytes[offset + 1] = alpha;
        outBytes[offset + 2] = alpha;
        outBytes[offset + 3] = green;
    }
}

input.UnlockBits(inBmpData);

Bitmap output = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);

var outBmpData = output.LockBits(new Rectangle(0, 0, output.Width, output.Height), ImageLockMode.WriteOnly, output.PixelFormat);

Marshal.Copy(outBytes, 0, outBmpData.Scan0, outBytes.Length);

output.UnlockBits(outBmpData);

Notes: Marshal is under System.Runtime.InteropServices; BitmapData (inBmpData, outBmpData), ImageLockMode, and PixelFormat are under System.Drawing.Imaging.

Not only does this work perfectly, but it is phenomenally faster. I'll be using this technique from now on for all my channel swapping needs. (I've already used it in another, similar app.)

Sorry for the needless post. I at least hope this solution helps someone else.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜