CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Brendan Tompkins [MVP]

Blog First. Ask Questions Later.

Refactoring Unsafe Code: GIF Image Color Quantizer, Now with Safe Goodness

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.



Comments

Sean Carpenter said:

I'm not sure (and I don't have an x64 machine to try it on here) but will the cast of an IntPtr to an Int32 cause a problem on a 64-bit machine?  I thought the size of an IntPtr was dependent on the platform.

# June 14, 2007 12:12 PM

Brendan Tompkins said:

Sean,

Good catch, that was a copy-paste error there.  You're absolutely right, x64 IntPtrs are 64 Bit

# June 14, 2007 1:47 PM

Lionel said:

You can also use Marshal.ReadInt32 and Marshal.WriteInt32 if you want something a bit more lightweight than Marshal.PtrToStructure.

protected  void FirstPass(BitmapData sourceData, int width, int height)

{

for (int row = 0; row < height; row++)

{

for (int col = 0; col < width; col++)

{

// Now I have the pixel, call the FirstPassQuantize function...

int offset = sourceData.Stride + (_pixelSize * col);

Color color = Color.FromArgb(Marshal.ReadInt32(sourceData.Scan0, offset));

InitialQuantizePixel(color);

Marshal.WriteInt32(sourceData.Scan0, offset);

}

}

}

# June 14, 2007 2:26 PM

Brendan Tompkins said:

Lionel,

I could probably get rid of the struct altogether in that case.  Perhaps I'll look at that sometime.  Thanks!

# June 14, 2007 2:31 PM

Just Don't Know said:

I think the link to the source code is incorrect - it points to the same url as the binary link...

# June 14, 2007 4:55 PM

Brendan Tompkins said:

Ooops, the links were reversed.  Fixed now.  I'm getting sloppy in my old age.

# June 14, 2007 5:10 PM

Greg said:

Heh no credit? :)

# June 15, 2007 3:23 AM

Brendan Tompkins said:

Oh. yes... Ahem.

I'd like to thank my parents, God, and Greg Young for pointing out a very stupid arithmetic error I was having.  Greg is definitely my go-to guy for all things close to the metal.

# June 15, 2007 9:24 AM

help.net said:

I am using in different projects the nice Quantizer class, a blessing if you need to render clear GIF

# June 22, 2007 11:33 AM

Olav Junker Kjær said:

Thank you for the code, it's very valuable!

There seem to be a small bug in the source, in the SecondPass method. In the optimization you compare Marshal.ReadByte for previous and current pixel. However this will only compare the first color channel. You should use Marshal.ReadInt32.

# July 5, 2007 2:29 AM

Daniel said:

Hi,

Downloads are not working !!!

Thanks,

Daniel

# July 13, 2007 4:57 PM

Tim Down said:

Brendan,

I have discovered a problem using your OctreeQuantizer that I fixed by replacing it with the unsafe one from the MSDN article from which you copied it. I haven't got time to try and figure out exactly what triggers the problem, I'm afraid. What I found was that on certain images I'd generated I got rogue black horizontal lines appearing on my quantized image. If you wish I can send you copies of the original image and the version of the image produced by your quantizer.

# July 17, 2007 4:47 AM

Logan R said:

I was having a similar problem to Tim, also confirming the unsafe MS version worked properly.  I was just about to dig into the problem when I noticed Olav's note above about Marshall.ReadInt32.  Thanks Olav!

# September 12, 2007 4:26 PM

Nathan said:

Wow, thank you so much for your work.  This problem was killing me (literally).  Unfortunately this was the last stop after browsing every site on the web related to gif and gdi.

Thanks.

# October 24, 2007 5:56 PM

Rosbicn said:

I met the same problem where Tim wrote above. Black horizontal lines appears on some result images. And I read all comment here and found Olav and Logon's note. Their solution was working!!! on line 181, the raw code is "if (Marshal.ReadByte(pPreviousPixel) != Marshal.ReadByte(pSourcePixel))". It is a small bug. Change it to "if (Marshal.ReadInt32(pPreviousPixel) != Marshal.ReadInt32(pSourcePixel))". Because the bitwidth of a pixel is 4 as int32, not 1 as byte. Thanks Olav and Tim.

# January 27, 2008 6:58 AM

Stefan GIbney said:

Hi Brendan

Great work, except both links seem to direct you to the binary.

Thanks

Stefan

# February 1, 2008 2:52 AM

poonam said:

Nice trick..

but i am not sure how and where to use it ...

i'll definitely find out some way to use it and make my website more attractive..

Thanks a lot for this trick..

# February 15, 2008 12:46 PM

nokia said:

thanks for you

# March 23, 2008 6:32 PM

nokia tema said:

Downloads are not working

# March 23, 2008 6:33 PM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About Brendan Tompkins

Brendan has been programming with .NET since the first public beta and is owner and operator of Port Technology Services, a consultancy company providing .NET application development services to the Maritime industry. In July, 2007, he was awarded the Microsoft MVP award for ASP.NET. He's also a proud co-founder of failed .COM startup Intrinsigo, and has had a hand in the failure of numerous other businesses. He currently runs CodeBetter.Com and Devlicio.us, and lives in Norfolk, Virgina with his wife Tiara and son Ian.

View Brendan's profile on LinkedIn

Check out Devlicio.us!