I've had a few requests to create a safe version of the Image Quantization library I've blogged about quite a few times: see Use GDI+ to Save Crystal-Clear GIF Images with .NET. It turns out this is quite useful for a lot of people, but the old version contained unsafe code, which wouldn't run under medium trust with .NET 2.0.
I spent a few hours yesterday creating a safe version of this library. The re-factoring process was fun, and I found a few cool things you can do with Marshal class with .NET.
Incrementing IntPtrs
I didn't know you could do this, and there's probably going to be situations where this doesn't work, but provided you have used something like the Bitmap's LockBits method to lock the bits into system memory, you can increment an IntPtr like so:
IntPtr pSourcePixel;
pSourcePixel = (IntPtr)((long)pSourcePixel + _pixelSize);
Pointers To Structures
Never knew about this either, but if you can map a structure to memory by using the Marshal.PtrToStructure method.
(Color32) Marshal.PtrToStructure(pSourcePixel, typeof(Color32));
... where Color32 is a structure that looks like this specifying FieldOffset, and pSourcePixel points to a 32-Bit color value
[StructLayout(LayoutKind.Explicit)]
public struct Color32
{
[FieldOffset(0)]
public byte Blue;
[FieldOffset(1)]
public byte Green;
[FieldOffset(2)]
public byte Red;
[FieldOffset(3)]
public byte Alpha;
}
So, in the end, a method that looked like this (unsafe)
/// <summary>
/// Execute the first pass through the pixels in the image
/// </summary>
/// <param name="sourceData">The source data</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
protected unsafe virtual void FirstPass ( BitmapData sourceData , int width , int height )
{
// Define the source data pointers. The source row is a byte to
// keep addition of the stride value easier (as this is in bytes)
byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer ( ) ;
Int32* pSourcePixel ;
// Loop through each row
for ( int row = 0 ; row < height ; row++ )
{
// Set the source pixel to the first pixel in this row
pSourcePixel = (Int32*) pSourceRow ;
// And loop through each column
for ( int col = 0 ; col < width ; col++ , pSourcePixel++ )
// Now I have the pixel, call the FirstPassQuantize function...
InitialQuantizePixel ( (Color32*)pSourcePixel ) ;
// Add the stride to the source row
pSourceRow += sourceData.Stride ;
}
}
Was refactored to this:
/// <summary>
/// Execute the first pass through the pixels in the image
/// </summary>
/// <param name="sourceData">The source data</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
protected virtual void FirstPass(BitmapData sourceData, int width, int height)
{
// Define the source data pointers. The source row is a byte to
// keep addition of the stride value easier (as this is in bytes)
IntPtr pSourceRow = sourceData.Scan0;
// Loop through each row
for (int row = 0; row < height; row++)
{
// Set the source pixel to the first pixel in this row
IntPtr pSourcePixel = pSourceRow;
// And loop through each column
for (int col = 0; col < width; col++)
{
Color32 color = (Color32) Marshal.PtrToStructure(pSourcePixel, typeof(Color32));
// Now I have the pixel, call the FirstPassQuantize function...
InitialQuantizePixel(color);
pSourcePixel = (IntPtr)((long)pSourcePixel + _pixelSize);
}
// Add the stride to the source row
pSourceRow = (IntPtr)((long)pSourceRow + sourceData.Stride);
}
}
So, get the new improved, safe version of the image quantizer here, and the source code here.