(位图)图像与new Bitmap(image)的行为不同。

5

这里是我撰写的测试,目前会失败:

var unusableColor = Color.FromArgb(13, 19, 20, 19);
var retrievedColor = Color.Empty;
var tempFile = Path.GetTempFileName();

using (var bitmap = new Bitmap(1, 1))
{
    bitmap.SetPixel(0, 0, unusableColor);
    bitmap.Save(tempFile, ImageFormat.Png);
}

using (var image = Image.FromFile(tempFile))
// This will lead to the error
using (var bitmap = new Bitmap(image))
// But this will work
//using (var bitmap = (Bitmap)image)
{
    retrievedColor = bitmap.GetPixel(0, 0);
}

Assert.That(retrievedColor, Is.SameAs(unusableColor));

如果你查看retrievedColor,你会发现它与Color.FromArgb(13, 19, 19, 19)相同。所以不同之处在于绿色部分从20变为19。
有任何想法为什么会发生这种情况或者在哪些情况下位图的构造函数会改变像素?
更新
似乎是一个更深层次的嵌套问题。通过用图像变量的简单转换替换Bitmap构造函数,问题就消失了。这可能解决了问题,但并没有解释它。而且我还能够通过以下步骤在Paint.Net中重现这个问题:
打开Paint.Net并创建一个新图像(大小不重要) 选择全部(Ctrl+A) 删除选择(Del) 打开颜色对话框(F8) 输入RGB的上述值(19、20、19),底部输入透明度(13)。 选择填充工具(F) 将颜色填充到空图像中 选择颜色选择工具(K) 点击您的新图像的某个位置并观察颜色对话框
因此,这似乎是一个更深层次的问题,不是由位图或图像类引起的,而是由一些更深层次的功能(如GDI+或类似功能)引起的。
更新2
我刚写了一个新的测试来找出所有受影响的颜色:
for (int a = 0; a < 256; a++)
{
    for (int r = 0; r < 256; r++)
    {
        for (int g = 0; g < 256; g++)
        {
            for (int b = 0; b < 256; b++)
            {
                using (var bitmap = new Bitmap(1, 1))
                {
                    var desiredColor = Color.FromArgb(a, r, g, b);
                    bitmap.SetPixel(0, 0, desiredColor);

                    // This will fail in a lot of colors with a low alpha channel value
                    using (var copiedBitmap = new Bitmap(bitmap))
                    // This will work, cause the information is entirely copied.
                    //using (var copiedBitmap = (Bitmap)bitmap.Clone())
                    {
                        var retrievedColor = copiedBitmap.GetPixel(0, 0);

                        if (desiredColor != retrievedColor)
                        {
                            Debug.Print(desiredColor + " != " + retrievedColor);
                        }
                    }
                }
            }
        }
    }

请不要让它完全自行运行,因为这会花费很长时间来完成,并且还会发现很多差异。但是,如果你在透明度(设置为1或10)上玩耍,你会发现RGB值将其用作某种位深度。
因此,如果你从使用低透明度值的现有位图创建新的位图,则会出现问题。真正的根本原因似乎在GDI、内核或这个领域的某个地方,无法从.Net中解决。
只需注意,如果颜色具有较低的透明度值,则调用位图构造函数可能会导致颜色更改。如果你确实需要原始颜色在第二个实例中保持不变,请使用(Bitmap)myBitmap.Clone()或如果你从磁盘加载它,请使用(Bitmap)Image.FromFile(filename),因为Image只是一个抽象类,通常通过Bitmap类实例化。

可能与往返乘以预乘阿尔法引起的舍入问题有关。 - CodesInChaos
问题出在 'new Bitmap(image)'。请查看我的更新答案。 - Vano Maisuradze
2个回答

8

我使用Paint.NET检查了你的代码保存的PNG文件,像素颜色确实为unusableColor
如果你将读取代码更改为以下内容:

using (Bitmap bitmap = (Bitmap)Image.FromFile(tempFile))
{
    retrievedColor = bitmap.GetPixel(0, 0);
}

一切正常


是的,这个可以工作。但在我看来,这引发了更多问题而不是回答。为什么显式转换器的行为与位图类型的构造函数不同呢? - Oliver
@Oliver:这是一个很好的问题,我无法回答。我想象当创建一个新的位图时,某些变量(例如像素类型?颜色空间?)会以不同的方式设置,而不仅仅是将现有图像转换为位图。我真的不知道,很抱歉。我的只是一个解决方案,而不是一个答案... - Marco
4
同意。问题出在Bitmap(Image)构造函数中,它使用Graphics.DrawImage()方法来进行复制。DrawImage()方法会采用一些快捷方式,这是针对人眼设计的。 - Hans Passant

2
你可以使用Clone方法:
    using (var image = Image.FromFile(tempFile))
    {
        using (var bitmap = image.Clone() as Bitmap)
        {
            retrievedColor = bitmap.GetPixel(0, 0);
        }
    }

问题在于“new Bitmap(image)”,因为它创建了新的实例。如果你看一下位图的构造函数,它会创建一个新的透明图像并绘制源图像。图形对象有平滑模式属性,用于绘制质量,默认情况下没有抗锯齿。
这是位图的构造函数:
Graphics graphics = null;
try
{
    graphics = Graphics.FromImage(this);
    graphics.Clear(Color.Transparent);
    graphics.DrawImage(original, 0, 0, width, height);
}
finally
{
    if (graphics != null)
    {
        graphics.Dispose();
    }
}

如果你只是从文件中加载图像,或者克隆图像数据是相同的。


1
请记住,Clone会创建您图像的副本;如果它很小,这不是问题,否则您将浪费内存而没有任何必要的操作 ;) - Marco
我认为这里的诀窍并不在于 Clone(),事实上是 as Bitmap 所产生的 魔力。如果你只是删除 Clone(),它也会起作用,然后就是与Marco提供的相同解决方案。 - Oliver
1
@CodeInChaos:是的,我没有做任何魔法,但 image as Bitmap 的行为与 new Bitmap(image) 不同。问题在于,构造函数对于给定的图像所执行的操作与从磁盘加载图片时不同。 - Oliver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接