【问题标题】:.NET Bitmap.Load method produce different result on different computers.NET Bitmap.Load 方法在不同的计算机上产生不同的结果
【发布时间】:2016-02-14 09:43:48
【问题描述】:

我尝试加载 JPEG 文件并从图像中删除所有黑白像素

C#代码:

    ...
    m_SrcImage = new Bitmap(imagePath);

    Rectangle r = new Rectangle(0, 0, m_SrcImage.Width, m_SrcImage.Height);
    BitmapData bd = m_SrcImage.LockBits(r, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

    //Load Colors
    int[] colours = new int[m_SrcImage.Width * m_SrcImage.Height];
    Marshal.Copy(bd.Scan0, colours, 0, colours.Length);
    m_SrcImage.UnlockBits(bd);

    int len = colours.Length;

    List<Color> result = new List<Color>(len);

    for (int i = 0; i < len; ++i)
    {
        uint w = ((uint)colours[i]) & 0x00FFFFFF; //Delete alpha-channel
        if (w != 0x00000000 && w != 0x00FFFFFF)   //Check pixel is not black or white
        {
            w |= 0xFF000000;                      //Return alpha channel
            result.Add(Color.FromArgb((int)w));
        }
    }
    ...

之后我尝试通过这段代码在 List 中找到唯一的颜色

    result.Sort((a, b) =>
    {
        return  a.R != b.R ? a.R - b.R :
                a.G != b.G ? a.G - b.G :
                a.B != b.B ? a.B - b.B :
                0;
    });


    List<Color> uniqueColors = new List<Color>( result.Count);   

    Color rgbTemp = result[0];

    for (int i = 0; i < len; ++i)
    {
         if (rgbTemp == result[i])
         {       
              continue;
         }

         uniqueColors.Add(rgbTemp);
         rgbTemp = result[i];
    }
    uniqueColors.Add(rgbTemp);

而且这段代码在不同机器上对同一图像产生不同的结果!

例如,在this image 上,它会产生:

  • .NET 版本 4 的 XP SP3 上的 43198 种独特颜色
  • .NET 4.5 版 Win7 Ultimate 上的 43168 种独特颜色

你可以download here的最小测试项目。它只是打开选定的图像并生成具有独特颜色的 txt 文件。

还有一个事实。某些像素在不同机器上的读取方式不同。我将 txt 文件与 notepad++ 进行比较,结果显示某些像素具有不同的 RGB 分量。每个组件的差为 1,例如

  • Win7像素:255 200 100
  • WinXP 像素:254 199 99

我看过这篇文章

stackoverflow.com/questions/2419598/why-might-different-computers-calculate-different-arithmetic-results-in-vb-net

(对不起,我没有足够的链接正常链接)。

...但是没有关于如何修复它的信息。


项目是在 VS 2015 社区版中使用 OS Windows 7 的机器上为 .NET 4 客户端配置文件编译的。

【问题讨论】:

  • 要上传图片,您应该选择不需要我们登录的免费上传服务
  • @TaW 将图片移至 imgur.com 并将测试项目移至 github
  • 谢谢。但是我猜 Lasse 的回答是正确的: JPeg 并不是要以绝对准确度再现图像。如果您真的需要,请转到 PNG!
  • 在 gdiplus.dll 版本 1.10 中更改了 JPEG 编解码器。 XP 有 1.00 版。还有更响亮和令人信服的证据表明 XP 确实、真正地结束了。否则,追求像素完美的实际理由太少会导致有损图像格式。它只是不同,继续前进。
  • @HansPassant 我不能使用其他格式。我为其他人写的,他需要JPEG)))

标签: c# .net windows bitmap


【解决方案1】:

Wikipedia has this to say about the accuracy requirements for JPEG Decoders:

JPEG 标准中的编码描述不固定输出压缩图像所需的精度。但是,JPEG 标准(和类似的 MPEG 标准)包括对解码的一些精度要求,包括解码过程的所有部分(可变长度解码、逆 DCT、去量化、输出重新归一化);参考算法的输出不得超过:

  • 每个像素分量最多相差一位
  • 每个 8×8 像素块的均方误差低
  • 每个 8×8 像素块的平均误差非常低
  • 整个图像的均方误差非常低
  • 整个图像的平均误差极低

(我的重点)

简而言之,这里只有两种不同的解码器实现,它们在精度要求范围内生成不同的图像(如您所见,组件值中的 1 位 = +/- 1)。

没有使用相同的(非内置)jpeg 解码器,这是意料之中的。如果您需要完全相同的输出,那么您可能需要切换到不同的解码器,无论您在哪个 .NET 版本或 Windows 上运行它,它都将是相同的。我猜 GDI+ 是这里的罪魁祸首,因为它自 Windows XP 以来发生了较大的变化。

【讨论】:

  • 这是个不幸的消息...谢谢您的回答。你知道可以同时处理 PNG、BMP、JPEG 和 TIFF 格式的库吗?
  • 我尝试将 Bitmiracle Libjpeg.NET 库添加到项目中,结果相同...
  • 为什么你需要它来跨平台生成相同的像素?您使用的是有损压缩图像格式,无论如何您都无法真正控制像素。
  • 我必须使用 JPEG。想要此信息的人拥有 JPEG 格式的图像,很难改变主意。但我解决了这个问题。我不使用 JpegImage.ToBitmap() 方法。我使用 JpegImage.GetRow().ToBytes 编写了自己的转换实现。
【解决方案2】:

我通过将 Libjpeg.NET 添加到项目并编写以下代码来解决我的问题:

        private Bitmap JpegToBitmap(JpegImage jpeg)
        {
            int width  = jpeg.Width;
            int height = jpeg.Height;

            // Read the image into the memory buffer 
            int[] raster = new int[height * width];

            for(int i = 0; i < height; ++i)
            {
                byte[] temp = jpeg.GetRow(i).ToBytes();

                for (int j = 0; j < temp.Length; j += 3)
                {
                    int offset = i*width + j / 3;
                    raster[offset] = 0;
                    raster[offset] |= (((int)temp[j+2])   << 16);
                    raster[offset] |= (((int)temp[j+1]) <<  8);
                    raster[offset] |= (int)temp[j];
                }
            }

            Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            byte[] bits = new byte[bmpdata.Stride * bmpdata.Height];

            for (int y = 0; y < bmp.Height; y++)
            {
                int rasterOffset = y * bmp.Width;
                int bitsOffset = (bmp.Height - y - 1) * bmpdata.Stride;

                for (int x = 0; x < bmp.Width; x++)
                {
                    int rgba = raster[rasterOffset++];
                    bits[bitsOffset++] = (byte)((rgba >> 16) & 0xff);
                    bits[bitsOffset++] = (byte)((rgba >> 8) & 0xff);
                    bits[bitsOffset++] = (byte)(rgba & 0xff);
                }
            }
            System.Runtime.InteropServices.Marshal.Copy(bits, 0, bmpdata.Scan0, bits.Length);
            bmp.UnlockBits(bmpdata);

            return bmp;
        }

所以,这对我来说已经足够了。

【讨论】:

    猜你喜欢
    • 2018-02-13
    • 1970-01-01
    • 2018-03-27
    • 2020-09-17
    • 1970-01-01
    • 2021-05-04
    • 2015-03-11
    • 2016-10-05
    • 2016-05-05
    相关资源
    最近更新 更多