这就是问题:
我在.png
中保存了一个位图,其中颜色为ARGB(50,210,102,70)
,尺寸为1 x 1 pixel
。
我再次检索相同的图片并使用GetPixel(0,0)
方法,我得到的是ARGB(50,209,102,70)
。
检索到的值略有不同,RGB
值略有不同,但A
值保持不变。
但是当我使用255
获取A
值时,会返回正确的RGB
值。
因此,对255
使用小于A
的值会导致上述问题。
以下是保存位图的代码。
Bitmap bmpPut = new Bitmap(1, 1); //Also tried with 'PixelFormat.Format32bppArgb'
bmpPut.SetPixel(0, 0, Color.FromArgb(254, 220, 210, 70));
bmpPut.Save("1.png"); //Also tried with using 'ImageFormat.Png'
以下是获取像素颜色的代码
Bitmap bit = new Bitmap(Image.FromFile("1.png"));
MessageBox.Show("R:" + bit.GetPixel(0, 0).R.ToString() +
"| G: " + bit.GetPixel(0, 0).G.ToString() +
"| B: " + bit.GetPixel(0, 0).B.ToString() +
"| A: " + bit.GetPixel(0, 0).A.ToString());
我得到的是ARGB(254,219,209,70)
P.S。:我读了几个类似的问题,他们没有解决这个问题,但我还没有找到解决方案。
答案 0 :(得分:6)
mammago has found a workaround,即使用类构造函数直接从文件构造Bitmap
对象,而不是通过{返回的Bitmap
对象间接构造Image
对象{1}}。
这个答案的目的是解释为什么有效,特别是两个方法之间的实际差异导致不同的每像素颜色值获得。
差异化的一个提议是色彩管理。但是,这似乎是一个非首发,因为这两个调用都没有要求颜色管理(ICM)支持。
但是,您可以通过检查.NET BCL的源代码来说明问题。在a comment中,mammago发布了Image
和Bitmap
类实施代码的链接,但无法识别相关差异。
让我们从Bitmap
class constructor that creates a Bitmap
object directly from a file开始,因为这是最简单的:
Image.FromFile()
那里有很多东西,但大部分都不相关。代码的第一部分只是获取并验证路径。之后是重要的一点:调用本地GDI +函数public Bitmap(String filename) {
IntSecurity.DemandReadFileIO(filename);
// GDI+ will read this file multiple times. Get the fully qualified path
// so if our app changes default directory we won't get an error
filename = Path.GetFullPath(filename);
IntPtr bitmap = IntPtr.Zero;
int status = SafeNativeMethods.Gdip.GdipCreateBitmapFromFile(filename, out bitmap);
if (status != SafeNativeMethods.Gdip.Ok)
throw SafeNativeMethods.Gdip.StatusException(status);
status = SafeNativeMethods.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
if (status != SafeNativeMethods.Gdip.Ok) {
SafeNativeMethods.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
throw SafeNativeMethods.Gdip.StatusException(status);
}
SetNativeImage(bitmap);
EnsureSave(this, filename, null);
}
,the many Bitmap-related functions provided by the GDI+ flat API之一。它完全符合您的想法,它从路径到图像文件创建GdipCreateBitmapFromFile
对象,而不使用颜色匹配(ICM)。这是繁重的功能。然后.NET包装器检查错误并再次验证生成的对象。如果验证失败,它会清理并抛出异常。如果验证成功,它会将句柄保存在成员变量(对Bitmap
的调用)中,然后调用除了图像(如果是GIF)之外什么都不做的函数(SetNativeImage
)。由于这个不是,我们将完全忽略它。
好的,从概念上讲,这只是一个围绕EnsureSave
的大而昂贵的包装器,可执行大量冗余验证。
那么GdipCreateBitmapFromFile
呢?好吧,the overload you're actually calling只是一个转发到the other overload的存根,传递Image.FromFile()
表示不需要颜色匹配(ICM)。有趣的重载代码如下:
false
这看起来非常相似。它以稍微不同的方式验证文件名,但这里没有失败,所以我们可以忽略这些差异。如果未请求嵌入式颜色管理,它会委托另一个本机GDI + flat API函数来完成繁重的工作:GdipLoadImageFromFile
。
Others have speculated差异可能是这两种不同的原生功能的结果。这是一个很好的理论,但是我对这些函数进行了反汇编,虽然它们有不同的实现,但是没有明显的差异可以解释这里观察到的行为。 public static Image FromFile(String filename,
bool useEmbeddedColorManagement) {
if (!File.Exists(filename)) {
IntSecurity.DemandReadFileIO(filename);
throw new FileNotFoundException(filename);
}
// GDI+ will read this file multiple times. Get the fully qualified path
// so if our app changes default directory we won't get an error
filename = Path.GetFullPath(filename);
IntPtr image = IntPtr.Zero;
int status;
if (useEmbeddedColorManagement) {
status = SafeNativeMethods.Gdip.GdipLoadImageFromFileICM(filename, out image);
}
else {
status = SafeNativeMethods.Gdip.GdipLoadImageFromFile(filename, out image);
}
if (status != SafeNativeMethods.Gdip.Ok)
throw SafeNativeMethods.Gdip.StatusException(status);
status = SafeNativeMethods.Gdip.GdipImageForceValidation(new HandleRef(null, image));
if (status != SafeNativeMethods.Gdip.Ok) {
SafeNativeMethods.Gdip.GdipDisposeImage(new HandleRef(null, image));
throw SafeNativeMethods.Gdip.StatusException(status);
}
Image img = CreateImageObject(image);
EnsureSave(img, filename, null);
return img;
}
将执行验证,尝试加载元文件(如果可能),然后调用内部GdipCreateBitmapFromFile
类的构造函数来执行实际加载。 GpBitmap
的实现方式类似,只是它通过内部GdipLoadImageFromFile
函数间接到达GpBitmap
类构造函数。此外,我无法通过直接在C ++中调用这些本机函数来重现您描述的行为,因此将它们作为解释的候选者。
有趣的是,我也无法通过将GpImage::LoadImage
的结果转换为Image.FromFile
来重现您描述的行为,例如:
Bitmap
虽然依靠它不是一个好主意,但如果你回到Bitmap bit = (Bitmap)(Image.FromFile("1.png"));
的源代码,你会发现这实际上是合法的。它调用the internal CreateImageObject
function,根据正在加载的图像的实际类型,将Image.FromFile
委托给Bitmap.FromGDIplus
。 The Bitmap.FromGDIplus
function只构造一个Metafile.FromGDIplus
对象,调用我们已经看到的Bitmap
函数来设置其底层句柄,并返回SetNativeImage
个对象。因此,从文件加载位图图像时,Bitmap
实际上会返回Image.FromFile
个对象。此Bitmap
对象的行为与使用Bitmap
类构造函数创建的对象相同。
重现行为的关键是根据Bitmap
的结果创建新的 Bitmap
对象,这正是您的原始代码所做的:
Image.FromFile
这将调用the Bitmap
class constructor that takes an Image
object,其内部委派给one that takes explicit dimensions:
Bitmap bit = new Bitmap(Image.FromFile("1.png"));
here 是我们最终找到您在问题中描述的行为的解释!您可以看到它从指定的public Bitmap(Image original, int width, int height) : this(width, height) {
Graphics g = null;
try {
g = Graphics.FromImage(this);
g.Clear(Color.Transparent);
g.DrawImage(original, 0, 0, width, height);
}
finally {
if (g != null) {
g.Dispose();
}
}
}
对象创建一个临时Graphics
对象,用透明颜色填充Image
对象,最后绘制指定的{{{ 1}}进入Graphics
上下文。此时,不与您正在使用的图像相同,但该图像的副本。这是色彩匹配可以踢的地方in,以及可能影响图像的各种其他事物。
事实上,除了问题中描述的意外行为外,您编写的代码隐藏了一个错误:它无法处置由{{1}创建的临时Image
对象}!
神秘解决了。为长期,间接的答案道歉,但希望它已经教会了一些关于调试的东西!继续使用mammago推荐的解决方案,因为它既简单又正确。
答案 1 :(得分:1)
更换
Bitmap bit = new Bitmap(Image.FromFile("1.png"));
与
Bitmap bit = new Bitmap("1.png");
应该做的伎俩。
似乎Image.FromFile()
与Bitmap
构造函数一样精确。