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

时间:2011-07-07 18:26:20

标签: gdi+ alpha

我正在使用.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 +正在对我的颜色通道做出错误的假设,我可能没有选择。

感谢您的帮助。

编辑:我解决了这个问题,但我再也不能将答案发布七个小时了。真棒。

1 个答案:

答案 0 :(得分:1)

抱歉延误。无论如何,我应该在发布之前对此进行更长时间的研究,因为我在大约五或十分钟之后找到了解决方案。为了清楚起见,我没有找到解决所述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。

这不仅效果很好,而且速度非常快。从现在开始,我将使用这种技术来满足我的所有频道交换需求。 (我已经在另一个类似的应用程序中使用它了。)

对不必要的帖子感到抱歉。我至少希望这个解决方案可以帮助别人。