Your GIF routines are almost there, but the culprit is that GIF only supports 8-bit indexed palettes, not 32-bit BGRA. By feeding the encoder raw 32-bit frames you’re effectively giving it an unsupported format, so it spits out an almost‐empty file. Here’s how to fix both your single‐frame and multi‐frame paths:
1. Single‐frame GIF (in SaveToWICFile)
You already correctly create a format converter, but double-check that you set the frame’s pixel format to 8bppIndexed before calling WriteSource. In your code you have:
// … after formatConverter->Initialize(…, GUID_WICPixelFormat8bppIndexed, …) WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat8bppIndexed; hr = frame->SetPixelFormat(&pixelFormat); if (FAILED(hr)) return hr; // write using the converter (which carries the palette) hr = frame->WriteSource(formatConverter.Get(), nullptr); if (FAILED(hr)) return hr;
That is exactly what you need —just make sure no other code path overwrote that pixelFormat.
2. Animated (multi‐frame) GIF (CaptureMultiFrameGIF)
Here you’re doing:
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA; hr = frame->SetPixelFormat(&pixelFormat); // … hr = frame->WritePixels( … ); // raw BGRA data
That won’t work for GIF. Instead, for each frame:
-
Create a WIC bitmap from your BGRA buffer.
-
Convert it to 8-bit indexed via an
IWICFormatConverter. -
Set the frame’s pixel format to
GUID_WICPixelFormat8bppIndexed. -
Write with
frame->WriteSource(converter.Get(), nullptr).
Sketch of the replacement loop
// inside your for(frameIndex…) std::vector<uint8_t> bgraData(img->width * img->height * 4); ConvertRGBAToBGRA(img, bgraData.data()); // 1) Wrap BGRA in a WIC bitmap Microsoft::WRL::ComPtr<IWICBitmap> srcBitmap; hr = wicFactory->CreateBitmapFromMemory( static_cast<UINT>(img->width), static_cast<UINT>(img->height), GUID_WICPixelFormat32bppBGRA, static_cast<UINT>(img->width * 4), static_cast<UINT>(bgraData.size()), bgraData.data(), &srcBitmap ); if (FAILED(hr)) continue; // 2) Convert to 8-bit indexed Microsoft::WRL::ComPtr<IWICFormatConverter> converter; hr = wicFactory->CreateFormatConverter(&converter); if (SUCCEEDED(hr)) { hr = converter->Initialize( srcBitmap.Get(), GUID_WICPixelFormat8bppIndexed, WICBitmapDitherTypeOrdered8x8, // or None nullptr, 0.0, WICBitmapPaletteTypeMedianCut ); if (FAILED(hr)) continue; } // 3) Tell the frame encoder to expect indexed pixels WICPixelFormatGUID pfIndexed = GUID_WICPixelFormat8bppIndexed; hr = frame->SetPixelFormat(&pfIndexed); if (FAILED(hr)) continue; // 4) Write via the converter (this also writes the palette) hr = frame->WriteSource(converter.Get(), nullptr); if (FAILED(hr)) continue; hr = frame->Commit();
This will produce a valid 8-bit GIF frame every iteration, with minimal memory overhead since each converter/bitmap is released at the end of the loop.
3. Metadata tweaks for looping & delays
You already add the NETSCAPE loop extension via:
metadataWriter->SetMetadataByName(L"/appext/data", &propValue); metadataWriter->SetMetadataByName(L"/appext/application", &propValue);
Make sure the case matches what WIC expects (e.g. "/appext/Application" and "/appext/Data").
For per-frame delays you can stick with your IPropertyBag2 approach, but if that still misbehaves you can instead do:
Microsoft::WRL::ComPtr<IWICMetadataQueryWriter> frameMeta; if (SUCCEEDED(frame->GetMetadataQueryWriter(&frameMeta))) { PROPVARIANT var; PropVariantInit(&var); var.vt = VT_UI2; var.uiVal = delayTime; // in centiseconds frameMeta->SetMetadataByName(L"/grctlext/Delay", &var); PropVariantClear(&var); }
Summary of changes
-
Always convert 32-bit RGBA → 8-bit indexed for GIF (single‐ and multi‐frame).
-
Use
GUID_WICPixelFormat8bppIndexed+WriteSource(...), notGUID_WICPixelFormat32bppBGRA+WritePixels. -
Verify metadata property names’ casing for looping (
/appext/Application,/appext/Data) and frame delays (/grctlext/Delay).
With these adjustments your GIF encoder will actually write out full, valid files instead of just a stub header.