Monday, June 22, 2009

How to: Convert DIB to Bitmap

During the weekend a friend asked for some help on code to convert a DIB to a Bitmap in .NET, he had found some code in internet and it almost did everything he needed but the image was getting cut off along the sides, we figured the issue had something to do with getting the pointer to the bitmap, so we digged more and found some more code that then we had to make a couple fixes and changes to get it working. Since I was not able to find a full working solution to the problem I decided to write this post, I saw many people asking the same question in multiple forums so hopefully this will help

First, you’ll need to add System.Drawing.dll to the references of your project, then you’ll need to add the following uses clauses

//Name spaces needed
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

the next step is to declare the BITMAPINFOHEADER structure, this can be declared outside your class:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BITMAPINFOHEADER
{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public uint biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;

public void Init()
{
biSize = (uint)Marshal.SizeOf(this);
}
}

Then you need to import a function from GdiPlus.dll
//GDI External method needed Place it within your class
[DllImport("GdiPlus.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern int GdipCreateBitmapFromGdiDib(IntPtr pBIH,
IntPtr pPix, out IntPtr pBitmap);

Now we can get to the actual code to conver the DIB to Bitmap, note the use of a helper function GetPixelInfo, this was the culprit of our issues, and the one we had a hard time finding then getting to work, the function BitmapFromDIB that you’ll find all over the web doesn’t have this code and so it doesn’t work in many cases, in fact the function that you'll find all over the place has a parameter pPix but it doesn't specify how to get this value. This code needs to be declared inside your class

//THIS METHOD SAVES THE CONTENTS OF THE DIB POINTER INTO A BITMAP OBJECT
private static Bitmap BitmapFromDIB(IntPtr pDIB)
{
//get pointer to bitmap header info
IntPtr pPix = GetPixelInfo(pDIB);

//Call external GDI method
MethodInfo mi = typeof(Bitmap).GetMethod("FromGDIplus", BindingFlags.Static | BindingFlags.NonPublic);
if (mi == null)
return null;

// Initialize memory pointer where Bitmap will be saved
IntPtr pBmp = IntPtr.Zero;

//Call external methosd that saves bitmap into pointer
int status = GdipCreateBitmapFromGdiDib(pDIB, pPix, out pBmp);

//If success return bitmap, if failed return null
if ((status == 0) && (pBmp != IntPtr.Zero))
return (Bitmap)mi.Invoke(null, new object[] { pBmp });
else
return null
;
}

//THIS METHOD GETS THE POINTER TO THE BITMAP HEADER INFO
private static IntPtr GetPixelInfo(IntPtr bmpPtr)
{
BITMAPINFOHEADER bmi = (BITMAPINFOHEADER)Marshal.PtrToStructure(bmpPtr, typeof(BITMAPINFOHEADER));

if (bmi.biSizeImage == 0)
bmi.biSizeImage = (uint)(((((bmi.biWidth * bmi.biBitCount) + 31) & ~31) >> 3) * bmi.biHeight);

int p = (int)bmi.biClrUsed;
if ((p == 0) && (bmi.biBitCount <= 8))
p = 1 << bmi.biBitCount;
p = (p * 4) + (int)bmi.biSize + (int)bmpPtr;
return (IntPtr)p;
}

finally, as a plus, what my friend actually needed was to convert from DIB to TIFF, so he wrote a function to do that, this function reuses the BitmapFromDIB function and allows you to set the image resolution

private void SavehDibToTiff(int hDIB, string fileName, int xRes, int yRes)
{
//Identify the memory pointer to the DIB Handler (hDIB)
IntPtr dibPtr = new IntPtr(hDIB);

//Save the contents of DIB pointer into bitmap object
Bitmap myBitmap = BitmapFromDIB(dibPtr);

//Set resolution if needed
if (xRes >0 && yRes>0)
myBitmap.SetResolution(xRes, yRes);

//Create an instance of the windows TIFF encoder
ImageCodecInfo ici = GetEncoderInfo("image/tiff");

//Define encoder parameters
EncoderParameters eps = new EncoderParameters(1); // only one parameter in this case (compression)

//Create an Encoder Value for TIFF compression Group 4
long ev = (long)EncoderValue.CompressionCCITT4;
eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, ev);

//Save file
myBitmap.Save(fileName, ici, eps);
}
//Helper to get Encoder from Windows for file type.
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
for (int j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}

and that is it, hoping this will help someone out there

2 comments:

Anonymous said...

THANK YOU! I had been beating my head against a wall looking several hours for a way to:

- Take an hDib
- Convert it to System.Bitmap
- Set the bitmap's resolution to the source device's resolution
- Save it as a .png file.

Your code sample did exactly what I needed.

Anonymous said...

i'm using this http://www.codeproject.com/KB/dotnet/twaindotnet.aspx
to get the DIB and yours to convert them to System.Drawing.Image

unfortunately its not working the
GdipCreateBitmapFromGdiDib call your making is returning status code 2 which is invalid parameter

and as its all pointers i'm really struggling to find out why