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:

  1. Create a WIC bitmap from your BGRA buffer.

  2. Convert it to 8-bit indexed via an IWICFormatConverter.

  3. Set the frame’s pixel format to GUID_WICPixelFormat8bppIndexed.

  4. 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(...), not GUID_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.