且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Alpha为零的颜色值显示为黑色

更新时间:2022-10-18 08:40:32

抱歉,延迟了。无论如何,我应该在发布之前花一些时间进行此工作,因为大约五到十分钟后我找到了解决方案。明确地说,我没有找到解决上述GDI +问题的解决方案,但找到了合适的解决方法。我考虑过在其他API中如何锁定表面并将字节直接传输到另一个表面,因此我采用了这种方法。经过MSDN的一点帮助之后,这是我的代码(没有错误处理):

 位图输入= Bitmap.FromFile(filename)as位图

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

var inBytes =新的byte [byteCount];
var outBytes =新的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偏移=(input.Width * y + x)* 4;

//字节蓝色= inBytes [offset];
字节绿色= inBytes [偏移量+ 1];
//字节红色= inBytes [offset + 2];
个字节的alpha = inBytes [offset + 3];

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

input.UnlockBits(inBmpData);

位图输出=新位图(input.Width,input.Height,PixelFormat.Format32bppArgb);

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

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

输出。UnlockBits(outBmpData);

注意:元帅在System.Runtime.InteropServices下; BitmapData(inBmpData,outBmpData),ImageLockMode和PixelFormat在System.Drawing.Imaging下。



这不仅可以完美地工作,而且速度也非常快。从现在开始,我将使用这种技术来满足我所有的频道交换需求。 (我已经在另一个类似的应用程序中使用过它。)



很抱歉收到了不必要的帖子。我至少希望这种解决方案可以帮助其他人。


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.