Windows
Imaging Component and Animated GIF FilesLast update: 27 May 2019:
If you've reached this page, you are interested in the Windows Imaging Component libraries (WIC ) and will have been working with them to some extent.
WIC supports reading and writing BMP, TIFF, JPEG, PNG and GIF files, as well as importing raw high resolution image files from a number of cameras (Nikon .NEF, Canon .CR2, Sony .ARW, Olympus .ORF).
This article describes how to read and write animated GIF files using WIC methods, and the issues that arise. The GIF format specification is reasonably complex, but fortunately there's little need to delve into the depths of it to make use of GIFs.
There's sample code on MSDN that shows how to read animated GIFs, but almost none on how to create them.
The most common developer issue is how to read and more importantly, write the metadata.
What is an animated GIF exactly?
An animated GIF is a 256 colour paletted image file that plays in a browser a sequence of frames to simulate movement. The "headbanger" image above is an example. Internally, there is a master frame (#0) and a set of content frames (#1 to #n). The time each frame can be displayed, or Delay, can be set for each frame, and is a multiple of 10ms. In many cases, the master and content frames have the same dimensions, but they don't have to.
In fact, content frames can be smaller than the master frame, and the displayed position in the master frame can be set. The transparency palette colour index (commonly 0) can be set for each frame.
This 1990 Compuserve text document is not the easiest to read. Most readers will find the Wikipedia article easier to understand.
What's the problem?
There's an MSDN article that describes how to use WIC to read and write image metadata that is concise to the point of obscurity, and the code fragments are disjointed and incomplete. Our aim is to rectify that.
Metadata can be considered a binary directory structure, though you won't find the names listed in the GIF header area in a binary listing. There's two types of metadata: global and frame.
Animated GIF metadata directory names as implemented by WIC are listed and helpfully provide the metadata names and the PROPVARIANT types required for each one.
The code fragments listed below are to indicate the processes needed to write GIF metadata successfully. The development PC was running Windows 7 32-bit.
Starting WIC
<windows.h> int imageWidth, imageHeight;
// Create the class factory, make the bitmap encoder hr =
CoCreateInstance(CLSID_WICImagingFactory,NULL,
hr = pIWICFactory->CreateStream(&pIGIFstream); hr = pIGIFstream->InitializeFromFilename(pOutputFilePath,GENERIC_WRITE); |
Writing Metadata with IWICMetadataQueryWriter
This is the one that causes the most problems. It's simple emough to set up the frame encoder and pass metadata settings to it. It's then necessary to create a second query writer to write the first according to the MSDN article. Well, actually, no, it's not necessary at all.
|
hr = pBitmapEncoder->GetMetadataQueryWriter (&pEncoderMetadataQueryWriter); (L &propValue); delete[] propValue.caub.pElems; propValue.vt = VT_LPSTR;
propValue.pszVal = comment_string_pointer;
// use new-delete[] calls hr =
pEncoderMetadataQueryWriter->SetMetadataByName(L"/commentext/TextEntry",&propValue);
delete[] propValue.pszVal; PropVariantClear(&propValue); // Get the first frame width and height. Code not shown.
(L (L
if (SUCCEEDED(hr))
{
}
pConverter->Release();
}//
endif
// has to be HERE !
// Other "/grctlext/*" values written here. Writing to
the root
// Other "/imgdesc" values written here
do
{ result = pConverter->Release();}
while(result > 0);
do
{ result =
pBitmapFrameEncode->Release();}
while(result > 0);
|
Fully releasing WIC objects is important
When you get a WIC object, such as when calling QueryInterface, the object's reference count is incremented, and decremented when you call Release() on that object. The Release() call returns the new reference count, and for the object to be fully released, the reference count must be 0.
When WIC is accessing image objects, there are often internal and undocumented increments to an object's reference count. Hence, in pseudocode...
does not necessarily mean that pOtherWICObject is fully released from memory. It may have no effect, but can also lock the object, preventing it from access again. An example is writing "/grctlext/Delay" data to the global metadata region. The data is written successfully, and the image will display in a browser, but trying to access the same image file again in code result in an "object locked by another process" error. This internal WIC process does not appear in the process list and cannot be stopped except by logging out or rebooting.
Hence, the correct and robust code is
Writing frame metadata to "/grctlext"
A WIC Query Reader can read data from this tree, and the most common is the "/grctlext/Delay" value. If a GIF created with other software is queried, a non-zero number will be returned.
We more or less followed an MSDN article on writing metadata, in sequence creating a frame, writing the metadata, then committing (i.e. saving) the frame. We found that "/imgdesc" data could be written, and verified in a binary edit of the created GIF file.
A WIC Query Writer writes to "/grctlext" and returns an S_OK value, but it seems that the data is never written. When a Query Reader checks it, all "/grctlext" values return 0. There's no other error that provides any clue. We might conclude that the WIC libraries have a bug that prevents writing to the "/grctlext" tree.
This is not actually a real problem, as the Delay value, if zero. results in a default frame Duration of 100ms.
Digging a bit deeper in the "/grctlext" bug
Using the Visual Studio binary editor, we can get a listing of the GIF file contents. Refering to the GIF Specification, Section 23, Graphic Control Extension (grctlext), we see that a grctlext starts with hex 21 F9 04 Only frames have grctlext blocks, and all contain 4 bytes plus a terminator byte.
Hence, for a sample GIF created with WIC, and having six frame grctlext blocks, we can expect to find six hex 21 F9 04 entries. Decoding ...
01 Packed Fields =
Reserved 3 Bits
Disposal Method 3
Bits
User Input
Flag 1
Bit
Transparent Color Flag 1 Bit set
00 00 Frame Delay LSB, MSB no delay, default to 100ms
FF Transparent colour index == 255
What happens if we manually edit the metadata blocks, setting the LSB to 15 (0x0F) ? A GIF reader app successfully reads the correct value.
Finally we discovered the cause of the problem. When the frame has been loaded or created it must first be written to the file by the encoder, i.e. by calling ...
hr = pBitmapFrameEncode->Commit();
This is what we think happens:
A related issue is the supposed need to write the root metadata encoder as a VT_UNKNOWN. In testing we found
We suspect that the MSDN article was written using an early WIC version, and the current version does not require VT_UNKNOWN entries, or handles them internally.
It's rather unclear what the point of writing the VT_UNKNOWN value, which is the QueryWriter interface, might be. Possibly its intent is a signal to the browser that WIC should be used. In any case, it seems to make no difference when not present.
Even though writing "/grctlext" values now works, there's another bug. We expected that the "frameIndex" code loop above would write the Delay value into frames #0 to #n. In fact, it writes the Delay values to frames #1 to #n-1. This was verified with the binary editor.
The question now is, why? Is this a classic "1-off" pointer bug, or is the Commit() incrementing some internal frame pointer? Either way, it sure looks like a bug.
We fixed it with this work-around. From the GIF spec, the "/grctlext" block starts with hex 21 F9.
|
// In the frame creation loop, write the Delay values into the // correct frame metadata entry. Our example has 6 frames.
|
If you have a more complete explanation or a code variation that does not have the bug, do please let us know.
Finally, to verify animated GIFs created using WIC, look on MSDN for the Microsoft GIFAnimator.exe. This dates from 1996, and still works on Windows 10, 8 and 7. The UI is understandably a bit dated, but it works well and allows all the metadata regions to be inspected and adjusted.
Got a question on this code? Email Technical Support
Since you are here, do please have a look around the rest of our website. Tell us what you think.
Copyright © 2012-2019 PiXCL Automation Technologies Inc, Canada. All Rights Reserved.