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

发布于 2024-11-18 17:51:48 字数 1700 浏览 1 评论 0原文

我正在使用.NET 4.0。我不知道这是一个框架错误还是 GDI+ 的问题。我刚刚在编写一个交换颜色通道的应用程序时发现了它。

让我尝试解释一下这个问题。我正在从一个位图读取像素,交换通道,然后将它们写入另一个位图。 (具体来说,我将输出图像的 RGB 值设置为等于输入图像的 alpha,输出的 alpha 等于输入的绿色通道……或者,简单地说,A => RGB 和 G => A。)代码如下:

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);
    }
}

同样,我已经尝试过这个:

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

        Color destPixel = Color.FromArgb(color);

        output.SetPixel(x, y, destPixel);

在大多数情况下,它是有效的。

问题是:无论 RGB 值是什么,当 alpha 为零时,生成的 RGB 值始终是纯黑色(R:0、G:0、B:0)。我不知道这是否是某种 FromArgb()“优化”——使用 .NET Reflector,我没有看到 FromArgb() 做任何奇怪的事情——或者 Bitmap.SetPixel 是否是罪魁祸首——更有可能,因为它遵循本机代码,我无法查看它。无论哪种方式,当 alpha 为零时,像素都是黑色的。这不是我期望的行为。我需要保持 RGB 通道完好无损。

起初我认为这是一个预乘的 alpha 问题,因为我正在使用自制的 DDS 加载器(我按照规范构建并且从未给我带来任何问题)加载 DDS 文件,但是当我指定显式 alpha 时255 个,如下所示:

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

...RGB 通道正确显示 - 即,没有一个显示为黑色 - 所以这绝对是 GDI+ 中的某些内容,错误地假设如果 alpha 为零,则可以安全地忽略 RGB 值......对我来说,这似乎是一个非常愚蠢的假设,但是,无论如何。

进一步加剧问题的是 Color 类型是不可变的,这对于结构来说是有意义的,但这意味着我无法创建颜色然后分配 alpha...如果 SetPixel() 是罪魁祸首,那么无论如何都没关系。 (我通过在设置像素后立即再次获取像素并看到相同的结果来测试这一点:零 alpha = 零 RGB)。

所以,我的问题是:有没有人处理过这个问题并提出一个相对简单的解决方法?为了降低我的依赖性,我不愿意导入第三方图像库,但由于 GDI+ 对我的颜色通道做出了错误的假设,我可能别无选择。

感谢您的帮助。

编辑:我解决了这个问题,但我不能在另外七个小时内发布答案。惊人的。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

治碍 2024-11-25 17:51:48

抱歉耽搁了。不管怎样,我应该在发布之前花更长的时间来解决这个问题,因为大约五到十分钟后我找到了解决方案。需要明确的是,我没有找到上述 GDI+ 问题的解决方案,但我找到了合适的解决方法。我考虑过如何在其他 API 中锁定一个表面并将字节直接传输到另一个表面,所以我采用了这种方法。在 MSDN 的帮助下,这是我的代码(无错误处理):

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);

注意:Marshal 位于 System.Runtime.InteropServices 下; BitmapData(inBmpData、outBmpData)、ImageLockMode 和 PixelFormat 位于 System.Drawing.Imaging 下。

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

对于不必要的帖子感到抱歉。我至少希望这个解决方案对其他人有帮助。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文