.NET Bitmap.Load方法在不同的计算机上产生不同的结果

时间:2016-02-14 09:43:49

标签: c# .net windows bitmap

我尝试加载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种独特颜色
  • Win7 Ultimate上的43168种独特颜色,带有.NEt版本4.5

您可以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

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

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

在VS 2015 Commumity Edition中使用OS Windows 7的机器上编译了.NET 4 Client profile的项目。

2 个答案:

答案 0 :(得分:3)

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以来,它经历了更大的变化。

答案 1 :(得分:0)

我通过将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;
        }

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