Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MonoMac System.Drawing.Image.GetPropertyItem(0x5100)

Recently, I was trying to answer another SO question about loading the frames (Bitmap and duration) of animated GIFs. The code can be found on pastenbin.

While doing additional tests on this code before moving it into my dev library, I noticed that there is a problem with this line of code:

//Get the times stored in the gif
//PropertyTagFrameDelay ((PROPID) 0x5100) comes from gdiplusimaging.h
//More info on http://msdn.microsoft.com/en-us/library/windows/desktop/ms534416(v=vs.85).aspx
var times = img.GetPropertyItem(0x5100).Value;

When running this on Windows .Net using this (example GIF), the array is of the same size as the amount of frames in the animated GIF and filled with the durations of the frames. In this case a byte[20] which converts to (BitConverter.ToInt32()) 5 durations:

[75,0,0,0,125,0,0,0,125,0,0,0,125,0,0,0,250,0,0,0]

On MonoMac however, this line of code for the same example GIF returns a byte[4] which converts to only one duration (the first):

[75,0,0,0]

I tested this for 10 different GIF's and the result is always the same. On Windows all durations are in the byte[], while MonoMac only lists the first duration:

[x,0,0,0]
[75,0,0,0]
[50,0,0,0]
[125,0,0,0]

Looking at the Mono System.Drawing.Image source code, the length seem to be set in this method, which is a GDI wrapper:

status = GDIPlus.GdipGetPropertyItemSize (nativeObject, propid,out propSize);

However, I don't really see any problems, not with the source as with my implementation. Am I missing something or is this a bug?

like image 930
dsfgsho Avatar asked Jul 21 '13 13:07

dsfgsho


2 Answers

I don't see anything wrong in the mono source either. It would have been helpful if you would have posted one of the sample images you tried. One quirk about the GIF image format is that the Graphics Control Extension block that contains the frame time is optional and may be omitted before an image descriptor. Non-zero odds therefore that you have GIF files that just have one GCE that applies to all the frames, you are supposed to apply the same frame time to every frame.

Do note that you didn't get 4 values, the frame time is encoded as a 32-bit value and you are seeing the little endian encoding for it in a byte[]. You should use BitConverter.ToInt32(), as you correctly did in your sample code.

I therefore think you should probably use this instead:

//convert 4 bit value to integer
var duration = BitConverter.ToInt32(times, 4*i % times.Length);

Do note that there's another nasty implementation detail about GIF frames, frames #2 and up do not have to be the same size as the frame #1. And each frame has a metadata field that describes what should be done with the previous frame to merge it with the next one. There are no property IDs that I know of to obtain the frame offset, size and undraw method for each frame. I think you need to render each frame into a bitmap yourself to get a proper sequence of images. Very ugly details, GIF needs to die.

like image 173
Hans Passant Avatar answered Nov 01 '22 08:11

Hans Passant


If you look into libgdiplus you will see that the properties are always read from the active bitmap:

if (gdip_bitmapdata_property_find_id(image->active_bitmap, propID, &index) != Ok) {

You can set the active bitmap by calling Image.SelectActiveFrame and then mono will return the correct durations, one by one. Since this is an incompatibility with windows, I'd call it a mono bug. As a simple workaround, you can of course just check the array length and handle both cases. This will be better than a check for mono, because if mono gets fixed this will continue to work.

like image 33
Jester Avatar answered Nov 01 '22 10:11

Jester