4

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!

mfeineis
  • 2,607
  • 19
  • 22
  • Yes, Graphics.DrawImage() takes some shortcuts that cause subtle variation in the pixel color values. Too small to be perceived by the human eye. The 64-bit algorithm is going to do this slightly differently. One possible way to deal with this is to declare the x86 version to be wrong :) – Hans Passant Nov 29 '11 at 23:17
  • Wow that's fast ... I see your point but it doesn't solve my problem since it's the x86 version that yields "better" results :) – mfeineis Nov 29 '11 at 23:19
  • I wouldn't recommend relying on numeric stability of the Graphics DrawImage method, as its purpose is to display an image, not to preserve information. It's possible for its implementation to change in the future, for instance. – Dan Bryant Nov 29 '11 at 23:22
  • That's just what I thought, but a call to `tmp.Clone()` only creates a shadow copy and the `new Bitmap(tmp)` approach doesn't change the PixelFormat even when provided with a different format ... I'm not in love with my solution either, so if maybe someone got a better approach I'm open to it – mfeineis Nov 29 '11 at 23:26
  • 3
    There are quite a few image manipulation libraries out there. If the one built-in to .NET is not adequate due to the inconsistencies, you could use a third party library. – Eric J. Nov 29 '11 at 23:30
  • @EricJ. I will think about that but it kind of bothers me to include another library just to convert an image in another .NET PixelFormat ... – mfeineis Nov 29 '11 at 23:37

4 Answers4

1

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.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • Thanks for the advice I will consider it. That's actually what I wanted to avoid by using builtin functionality of the framework so I didn't have to care about these things. Not to mention that this function is the #1 bottleneck of the application. – mfeineis Nov 30 '11 at 00:04
1

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.

dthorpe
  • 35,318
  • 5
  • 75
  • 119
  • System.Drawing is indeed based on GDI+, not GDI (the main exception being TextRenderer, which isn't in play here). So the part about video drivers is probably not relevant. – Joe White Nov 29 '11 at 23:48
  • Deleted nostalgic backgrounder. :P – dthorpe Nov 29 '11 at 23:49
  • +1 for history overkill, I'm mesmerized :) ... I didn't think of something that lowlevel when writing C# ... I also read about .NET using GDI+ – mfeineis Nov 29 '11 at 23:55
0

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

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

Smash
  • 3,722
  • 4
  • 34
  • 54
  • Thank you for the link, I didn't know about OpenCV in the first place. I will investigate this further :) – mfeineis Nov 29 '11 at 23:59
  • I reviewed OpenCV now, it seems to be very powerful. Unfortunatly it's not this kind of feature detection that I do :). Also LGPL is not an option for this project but nevertheless thank you for pointing me to this one! – mfeineis Nov 30 '11 at 11:55