1

I'd like to access color info of my 64bpp image with BitmapData. I've succesfully done this for 32bpp images, but it doesn't seem to be as easy for 64bpp images...

static void Main(string[] args)
{
    Bitmap bmp;
    using (Stream imageStreamSource = new FileStream(bitmapPath, FileMode.Open, FileAccess.Read, FileShare.Read))   //Should I use "using" here? or do I need to keep the stream open for as long as I use the Bitmap?
    {
        PngBitmapDecoder decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
        BitmapSource bitmapSource = decoder.Frames[0];
        bmp = _bitmapFromSource(bitmapSource);
    }

    unsafe
    {
        int xIncr = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;      //8
        xIncr /= 2;

        BitmapData bData1 = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);

        ushort* bData1Scan0Ptr = (ushort*)bData1.Scan0.ToPointer();
        ushort* nextBase = bData1Scan0Ptr + bData1.Stride;

        for (int y = 0; y < bData1.Height; ++y)
        {
            for (int x = 0; x < bData1.Width; ++x)
            {
                var red = bData1Scan0Ptr[2];
                var green = bData1Scan0Ptr[1];
                var blue = bData1Scan0Ptr[0];

                bData1Scan0Ptr += xIncr;      //xIncr = 4
            }

            bData1Scan0Ptr = nextBase;
            nextBase += bData1.Stride;
        }
    }
}

//http://stackoverflow.com/questions/7276212/reading-preserving-a-pixelformat-format48bpprgb-png-bitmap-in-net
private static Bitmap _bitmapFromSource(BitmapSource bitmapsource)
{
    Bitmap bitmap;
    using (MemoryStream outStream = new MemoryStream())     //Should I use "using" here?
    {
        BitmapEncoder enc = new BmpBitmapEncoder();
        enc.Frames.Add(BitmapFrame.Create(bitmapsource));
        enc.Save(outStream);
        bitmap = new System.Drawing.Bitmap(outStream);
    }
    return bitmap;
}

For a white image (255, 255, 255) I get 0 and 32 back as the 2 values...? I don't know if this is correct and I'm just interpreting them wrong or if they're just completely wrong. (not true anymore with updated code)

EDIT: With the updated code I'm getting 8192 for a 255 value and 1768 for a 128 value. This still doesn't make much sense to me...

I was also wondering if I should be using the "using" statement when loading the image, because http://msdn.microsoft.com/en-us/library/z7ha67kw says I need to keep the stream open for the lifetime of the bitmap. It seems to work fine the way it is now, or is that because I copy the bitmap to another bitmap declared outside the "using" statement?

VincentC
  • 245
  • 4
  • 14

2 Answers2

2

Normally, 64bpp formats will have 16 bits for each of RGBA, so you should really be using ushort rather than byte. For example:

BitmapData bData1 = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);

byte* bData1Scan0Ptr = (byte*)bData1.Scan0.ToPointer();
byte* nextBase = bData1Scan0Ptr + bData1.Stride;

for (int y = 0; y < bData1.Height; ++y)
{
    ushort* pRow = (ushort*)bData1Scan0Ptr;

    for (int x = 0; x < bData1.Width; ++x)
    {
        var red = pRow[2];
        var green = pRow[1];
        var blue = pRow[0];

        pRow += 4;
    }

    bData1Scan0Ptr = nextBase;
    nextBase += bData1.Stride;
}

Perhaps try this modification and comment with the result if it doesn't solve your problem, then I will update my answer if necessary.

Roger Rowland
  • 25,885
  • 11
  • 72
  • 113
  • Thanks! I'm getting different results now, but I'm still not sure if they are correct.. My image is orange (255,128,0) and the values I get are: red: 8192, green: 1768 and blue:0 – VincentC Dec 26 '13 at 09:19
  • 2
    I know this is an old question, but for anyone like me who stumbled on this, 8192 is the maximum value (equal to 255 for a 24bpp image's white). – Dax Pandhi Oct 26 '15 at 19:38
0

This is an old question but I bumped into the same issue during the development of my libraries. See the TL;DR answer at the bottom of the remarks section, at the notes of this help page.

And here is the more detailed answer: The transformation from 8 bit to 16 bit color channels (which is actually 13 bit as you already noticed) is not linear:

  • The 24bpp format, and also the others formats up to 32 bits (as well as the Color structure) represent colors with gamma correction γ = 2.2
  • Whereas 48/64bpp formats represent colors with linear gamma, ie. no gamma correction (γ = 1.0)

So if you want to transform colors from 8 to 16 bits, then you need to adjust the 8-bit RGB values with γ ~ 0.45. Just use the formula:

outputLevel = 8192 * Math.Pow(inputLevel / 255d, 1d / gamma)

which gives you 1770 for inputLevel = 128 and gamma = 0.45

That's how the orange color is (255,128,0) in 24bpp and (8192,1768,0) in 48bpp pixel formats.

Note: Use the formula only for RGB channels but not for A. Alpha has no "gamma correction".

Actually Windows is cheating a bit as you can notice for low inputLevel values: the formula above gives you 0 for input levels < 5, though try SetPixel with 48/64 bpp format and low RGB color values and you can see that the raw RGB components are never zero and they are always different. Maybe it would be quite ridiculous if converting an image from 32 to 64 bit would lose some information...

This can vary from implementation to implementation though. On Linux, libgdiplus does not support 48/64 bit formats at all. On the other hand, ReactOS (a free Windows clone) uses a simple linear transformation between 8->16 bit conversions, using the full 16 bit range as you can see from its SetPixel implementation.

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65