4

I try to load JPEG file and delete all black and white pixels from image

C# Code:

    ...
    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));
        }
    }
    ...

After that I try to find unique colors in List by this code

    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);

And this code produces different results on different machines on same image!

For example, on this image it produces:

  • 43198 unique colors on XP SP3 with .NET version 4
  • 43168 unique colors on Win7 Ultimate with .NEt version 4.5

Minimum test project you can download here. It just opens selected image and produces txt-file with unique colors.

One more fact. Some pixels are read differently on different machines. I compare txt-files with notepad++ and it shows that some pixels have different RGB components. The difference is 1 for each component, e.g.

  • Win7 pixel: 255 200 100
  • WinXP pixel: 254 199 99

I have read this post

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

(sorry, I haven't enough raiting for normal link).

...but there wasn't information how to fix it.


Project was compiled for .NET 4 Client profile on machine with OS Windows 7 in VS 2015 Commumity Edition.

Pavel.Zh
  • 437
  • 3
  • 15
  • To upload images you should chose a free upload service that does not require us to log-in – TaW Feb 14 '16 at 10:15
  • @TaW Move image to imgur.com and move test project to github – Pavel.Zh Feb 14 '16 at 10:37
  • Thanks. However I guess Lasse's answer is correct: JPeg is not meant to reproduce image with absolute accuracy. Do move to PNG if you really need that! – TaW Feb 14 '16 at 10:51
  • The JPEG codec was changed in gdiplus.dll version 1.10. XP has version 1.00. Yet more loud and compelling evidence that XP is truly, truly over and done with. Otherwise excessively few practical reasons to pursue pixel-perfect results in a lossy image format. It is just different, move on. – Hans Passant Feb 14 '16 at 12:03
  • @HansPassant I can't use another format. I write it for other person and he needs JPEG))) – Pavel.Zh Feb 14 '16 at 12:59
  • Didn't you get the message yet? It is XP that is the problem here. Just stop pretending that you can support XP, you can't, and you don't have a problem. – Hans Passant Feb 14 '16 at 13:13

2 Answers2

2

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

The encoding description in the JPEG standard does not fix the precision needed for the output compressed image. However, the JPEG standard (and the similar MPEG standards) includes some precision requirements for the decoding, including all parts of the decoding process (variable length decoding, inverse DCT, dequantization, renormalization of outputs); the output from the reference algorithm must not exceed:

  • a maximum of one bit of difference for each pixel component
  • low mean square error over each 8×8-pixel block
  • very low mean error over each 8×8-pixel block
  • very low mean square error over the whole image
  • extremely low mean error over the whole image

(my emphasis)

In short, there is simply two different decoder implementations at play here, and they produce different images, within the accuracy requirement (1 bit = +/- 1 in the component values, as you observed).

So short of using the same (non-built-in) jpeg decoder, this is to be expected. If you need to have the exact same output then you probably need to switch to a different decoder, one that will be the same no matter which .NET version or Windows you're running this on. I'm guessing that GDI+ is the culprit here as this has undergone larger changes since Windows XP.

Community
  • 1
  • 1
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • It's sad news... Thanx for answer. Do you know library which can work with PNG, BMP, JPEG and TIFF formats together? – Pavel.Zh Feb 14 '16 at 10:45
  • I have tried to add Bitmiracle Libjpeg.NET library to project and result the same... – Pavel.Zh Feb 14 '16 at 12:11
  • 1
    Why do you need this to produce the same pixels across platforms anyway? You're using a lossy compression image format, you don't have real control over the pixels anyway. – Lasse V. Karlsen Feb 14 '16 at 12:13
  • I have to use JPEG. Person who wants this information has images in JPEG format and it is hard to change his mind. But I solve the problem. I don't use JpegImage.ToBitmap() method. I wrote my self implementation of converstion by using JpegImage.GetRow().ToBytes. – Pavel.Zh Feb 14 '16 at 13:03
0

I solve my problem by adding Libjpeg.NET to project and write this code:

        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;
        }

So, that's enough for me.

Pavel.Zh
  • 437
  • 3
  • 15