locked
System.Drawing.Image GDI+ TIFF Question RRS feed

  • Question

  • GDI+ experts, I beseech your advice.

    I have created an image processing application in C# that deals entirely with 16-bpp GrayScale images.  Currently I'm handling a few simple file formats (proprietary) that have a header followed by the pixel values (in each case, as unsigned words (ushort)).  I store the pixels in an array ushort[width, height] and I apply all my filters to this array, then I call a static method to redraw the displayed image in a picturebox (in 8-bit grayscale form).  I need the 16bpp values - I allow the user to stretch contrast over a smaller range of pixel values so they can see more of the dynamic range.

    I've recently learned that I need to handle TIFF images, also 16bpp.  Some of these files are multi-frame.  What course of action would you recommend?  I would like to separate the frames as individual TIFF files, then use the header information in the TIFF to get the width and height of the image, then extract the pixel data to a ushort[width,height].  That way I won't need to change any of my existing image processing code.

    Is this even going to be possible?  Are there existing libraries written for this purpose that are widely available?  I've exhausted google.

    Friday, January 26, 2007 3:36 PM

Answers

  • I'd load the TIFF into a regular GDI+ Bitmap class instance.  Get access to the bitmap words with the LockBits() method.  The BitmapData instance you get back has the Scan0 member to give you the address of the first scanline in the bitmap.  Assign that to a unsafe uint16 pointer to copy the scanline.  Increment the pointer by the value of Stride property.  Step through the frames with SelectActiveFrame().

    Here's some sample code to get you started:

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Drawing.Imaging;

    namespace WindowsApplication1 {
      public partial class Form1 : Form {
        public Form1() {
          InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e) {
          // I need to make a 16bpp image
          Bitmap srce = new Bitmap(@"c:\temp\test.bmp");
          Bitmap bmp16 = new Bitmap(srce.Width, srce.Height, PixelFormat.Format16bppRgb565);
          Graphics gr = Graphics.FromImage(bmp16);
          gr.DrawImage(srce, 0, 0);
          gr.Dispose();
          srce.Dispose();
          // Get access to the bitmap bits
          BitmapData bd = bmp16.LockBits(new Rectangle(0, 0, bmp16.Width, bmp16.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppRgb565);
          ushort[,] bmpwords = new ushort[bmp16.Height, bmp16.Width];
          unsafe {
            ushort* ptr = (ushort*)bd.Scan0;
            for (int iy = 0; iy < bmp16.Height; ++iy) {
              for (int ix = 0; ix < bmp16.Width; ++ix) {
                bmpwords[iy, ix] = *(ptr + ix);
              }
              ptr += bd.Stride / 2;   // NOTE: /2 because we're accessing words!
            }
          }
        }
      }
    }

    To run: Project + properties, Build tab, turn on "Allow unsafe code".
    Friday, January 26, 2007 7:53 PM

All replies

  • I'd load the TIFF into a regular GDI+ Bitmap class instance.  Get access to the bitmap words with the LockBits() method.  The BitmapData instance you get back has the Scan0 member to give you the address of the first scanline in the bitmap.  Assign that to a unsafe uint16 pointer to copy the scanline.  Increment the pointer by the value of Stride property.  Step through the frames with SelectActiveFrame().

    Here's some sample code to get you started:

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Drawing.Imaging;

    namespace WindowsApplication1 {
      public partial class Form1 : Form {
        public Form1() {
          InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e) {
          // I need to make a 16bpp image
          Bitmap srce = new Bitmap(@"c:\temp\test.bmp");
          Bitmap bmp16 = new Bitmap(srce.Width, srce.Height, PixelFormat.Format16bppRgb565);
          Graphics gr = Graphics.FromImage(bmp16);
          gr.DrawImage(srce, 0, 0);
          gr.Dispose();
          srce.Dispose();
          // Get access to the bitmap bits
          BitmapData bd = bmp16.LockBits(new Rectangle(0, 0, bmp16.Width, bmp16.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppRgb565);
          ushort[,] bmpwords = new ushort[bmp16.Height, bmp16.Width];
          unsafe {
            ushort* ptr = (ushort*)bd.Scan0;
            for (int iy = 0; iy < bmp16.Height; ++iy) {
              for (int ix = 0; ix < bmp16.Width; ++ix) {
                bmpwords[iy, ix] = *(ptr + ix);
              }
              ptr += bd.Stride / 2;   // NOTE: /2 because we're accessing words!
            }
          }
        }
      }
    }

    To run: Project + properties, Build tab, turn on "Allow unsafe code".
    Friday, January 26, 2007 7:53 PM
  • So I can load a 16bpp grayscale TIFF into a GDI+ bitmap object?  I know PixelFormat.Format16bppGrayScale does not work.  Currently I am drawing my bitmaps from a ushort[,] containing the pixel values as follows:

                    public static Image imageFromArray(byte[,] imageData)
            {
                // The width of the image is equal to the length of the first dimension of the imageData array
                int width = imageData.GetLength(0);
                // The height is equal to the length of the second dimension
                int height = imageData.GetLength(1);

                // Create a byte array with 3 times as many elements as the image data array
                // We do this because we will display the image as a 24bpp RGB image with equal R, G, and B channels
                byte[] displayBytes = new byte[imageData.Length * 3];

                Bitmap b = new Bitmap(width, height, PixelFormat.Format24bppRgb);           // Create the new bitmap

                // We convert the array of shorts to 8-bit values to be displayed on screen

                byte pixelVal;                                                              // Represents the displayed pixel
                int arrayLoc = 0;                                                           // Counter used for RGB index

                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        // Set pixel value by casting the short to byte and dividing by 256
                        pixelVal = (imageData[i, j]);
                        displayBytes[arrayLoc] = displayBytes[arrayLoc + 1] =
                            displayBytes[arrayLoc + 2] = pixelVal;                          // Set the R, G, B channels equal

                        arrayLoc += 3;                                                      // Move to next RGB index
                    }
                }

                BitmapData bmData = b.LockBits(new Rectangle(0, 0, width, height),
                        ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);               // Lock bitmap data into memory

                int stride = bmData.Stride;                                                 // Set stride
                System.IntPtr scan0 = bmData.Scan0;                                         // Set first scanline

                Marshal.Copy(displayBytes, 0, scan0, displayBytes.Length);                  // Copy

                b.UnlockBits(bmData);                                                       // Unlock the bitmap data

                // The function returns an image that can be set directly to the picturebox
                // Since the bitmap class inhertis from System.Drawing.Image, we can set it as an image object
                Image image = b;

                return image;

     And I would like to continue drawing it in this way if possible.  Basically I am keeping the raw pixel values in the array for manipulation, and drawing them in a supported format every time an image processing function is performed (the user can see the full dynamic range of the image by performing contrast stretch operations).  I don't need to display the bitmap object as 16bpp grayscale, I only need the pixel values.

    My question is, I suppose, if I load the TIFF into the GDI+ bitmap object, will I throw an exception because the TIFF is 16bpp grayscale format?  Should I be able to continue using my existing code?

    Friday, January 26, 2007 8:23 PM
  • I'm pretty sure GDI+ can load 16bpp TIFFs.  Don't control the format, let the TIFF decoder figure it out. 

    Your code isn't correct.  You need to worry about Stride.  A 24bpp image uses 3 bytes per pixel.  Each scanline of a bitmap starts at an address that is a multiple of 4 bytes.  If your bitmap's width * 3 isn't divisible by 4, you'll get a skewed image from Marshal.Copy().
    Friday, January 26, 2007 8:36 PM
  • Thanks for your help!  You have saved me several hours (days?) of work that it might have taken to decode the TIFF images manually.  I knew there must be an efficient way to extract the pixels using GDI+.

    I am not sure I understand what you said about my code, though.  How can I correct it to take Stride into account?

    Friday, January 26, 2007 10:25 PM
  • Look at my code and notice how I use the Stride property...
    Friday, January 26, 2007 11:09 PM
  •  

    >> I'm pretty sure GDI+ can load 16bpp TIFFs.  Don't control the format, let the TIFF decoder figure it out. 

     

    This is incorrect.  It appears that no version of the .Net Bitmap class will successfully load a 16BPP GreyScale TIFF image.  They all throw an Out of Memory exception.

    Monday, October 8, 2007 12:34 PM
  • Out of memory might come from Height/width <=0, I managed to LOAD (but not save) 16bpp images to Bitmap files.
    Monday, February 18, 2019 9:59 AM