Graphics.DrawImage creates different image data on x86 and x64

1.8k views Asked by At

Hey there!

Here is my setting:
I've got a c# application that extracts features from a series of images. Due to the size of a dataset (several thousand images) it is heavily parallelized, that's why we have a high-end machine with ssd that runs on Windows7 x64 (.NET4 runtime) to lift the hard work. I'm developing it on a Windows XP SP3 x86 machine under Visual Studio 2008 (.NET3.5) with Windows Forms - no chance to move to WPF by the way.

Edit3: It's weird but I think I finally found out what's going on. Seems to be the codec for the image format that yields different results on the two machines! I don't know exactly what is going on there but the decoder on the xp machine produces more sane results than the win7 one. Sadly the better version is still in the x86 XP system :(. I guess the only solution to this one is changing the input image format to something lossless like png or bmp (Stupid me not thinking about the file format in the first place :)).

Edit2: Thank you for your efforts. I think I will stick to implementing a converter on my own, it's not exactly what I wanted but I have to solve it somehow :). If anybody is reading this who has some ideas for me please let me know.

Edit: In the comments I was recommended to use a third party lib for this. I think I didn't made myself clear enough in that I don't really want to use the DrawImage approach anyway - it's just a flawed quickhack to get an actually working new Bitmap(tmp, ... myPixelFormat) that would hopefully use some interpolation. The thing I want to achieve is solely to convert the incoming image to a common PixelFormat with some standard interpolation.

My problem is as follows. Some of the source images are in Indexed8bpp jpg format that don't get along very well with the WinForms imaging stuff. Therefore in my image loading logic there is a check for indexed images that will convert the image to my applications default format (e.g. Format16bpp) like that:

Image GetImageByPath(string path)
{
    Image result = null;

    using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        Image tmp = Image.FromStream(fs); // Here goes the same image ...

        if (tmp.PixelFormat == PixelFormat.Format1bppIndexed ||
            tmp.PixelFormat == PixelFormat.Format4bppIndexed ||
            tmp.PixelFormat == PixelFormat.Format8bppIndexed ||
            tmp.PixelFormat == PixelFormat.Indexed)
        {
            // Creating a Bitmap container in the application's default format
            result = new Bitmap(tmp.Width, tmp.Height, DefConf.DefaultPixelFormat);
            Graphics g = Graphics.FromImage(result);
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;

            // We need not to scale anything in here
            Rectangle drawRect = new Rectangle(0, 0, tmp.Width, tmp.Height);

            // (*) Here is where the strange thing happens - I know I could use
            // DrawImageUnscaled - that isn't working either
            g.DrawImage(tmp, drawRect, drawRect, GraphicsUnit.Pixel);

            g.Dispose();
        }
        else 
        {
            result = new Bitmap(tmp); // Just copying the input stream
        }

        tmp.Dispose();
    }

    // (**) At this stage the x86 XP memory image differs from the 
    // the x64 Win7 image despite having the same settings
    // on the very same image o.O
    result.GetPixel(0, 0).B; // x86: 102, x64: 102
    result.GetPixel(1, 0).B; // x86: 104, x64: 102
    result.GetPixel(2, 0).B; // x86:  83, x64:  85
    result.GetPixel(3, 0).B; // x86: 117, x64: 121
    ...
    return result;
}

I tracked the problem down to (*). I think the InterpolationMode has something to do with it but there's no difference which of them I choose the results are different at (**) on the two systems anyway. I've been investigating test image data with some stupid copy&paste lines, to be sure it's not an issue with accessing the data in a wrong way.

The images all together look like this Electron Backscatter Diffraction Pattern. The actual color values differ subtly but they carry a lot of information - the interpolation even enhances it. It looks like the composition algorithm on the x86 machine uses the InterpolationMode property whereas the x64 thingy just spreads the palette values out without taking any interpolation into account.

I never noticed any difference between the output of the two machines until the day I implemented a histogram view feature on the data in my application. On the x86 machine it is balanced as one would expect it from watching the images. The x64 machine on the other hand would rather give some kind of sparse bar-diagram, an indication of indexed image data. It even effects the overall output data of the whole application - the output differs on both machines with the same data, that's not a good thing.

To me it looks like a bug in the x64 implementation, but that's just me :-). I just want the images on the x64 machine to have the same values as the x86 ones.

If anybody has an idea I'd be very pleased. I've been searching for similar behavior on the net for ages but resistance seems futile :)

Oh look out ... a whale!

4

There are 4 answers

1
Jim Mischel On BEST ANSWER

If you want to make sure that this is always done the same way, you'll have to write your own code to handle it. Fortunately, it's not too difficult.

Your 8bpp image has a palette that contains the actual color values. You need to read that palette and convert the color values (which, if I remember correctly, are 24 bits) to 16-bit color values. You're going to lose information in the conversion, but you're already losing information in your conversion. At least this way, you'll lost the information in a predictable way.

Put the converted color values (there won't be more than 256 of them) into an array that you can use for lookup. Then ...

Create your destination bitmap and call LockBits to get a pointer to the actual bitmap data. Call LockBits to get a pointer to the bitmap data of the source bitmap. Then, for each pixel:

read the source bitmap pixel (8 bytes)
get the color value (16 bits) from your converted color array
store the color value in the destination bitmap

You could do this with GetPixel and SetPixel, but it would be very very slow.

3
dthorpe On

I vaguely seem to recall that .NET graphics classes rely on GDI+. If that's still the case today, then there's no point in trying your app on different 64 bit systems with different video drivers. Your best bet would be to either do the interpolation using raw GDI operations (P/Invoke) or write your own pixel interpolation routine in software. Neither option is particularly attractive.

2
Smash On

You really should use OpenCV for image handling like that, it's available in C# here: OpenCVSharp.

0
Stefanos Zilellis On

I use a standard method for the graphics object, and with this settings outperforms X86. Count performance at release runs, not debug. Also check optimize code at project properties, build tab. Studio 2017, framework 4.7.1

public static Graphics CreateGraphics(Image i)
{
    Graphics g = Graphics.FromImage(i);
    g.CompositingMode = CompositingMode.SourceOver;
    g.CompositingQuality = CompositingQuality.HighSpeed;
    g.InterpolationMode = InterpolationMode.NearestNeighbor;
    g.SmoothingMode = SmoothingMode.HighSpeed;
    return g;
}