HRESULT LoadAnimatedGif()

in Texassemble/AnimatedGif.cpp [115:411]


HRESULT LoadAnimatedGif(const wchar_t* szFile, std::vector<std::unique_ptr<ScratchImage>>& loadedImages, bool usebgcolor)
{
    bool iswic2;
    auto pWIC = GetWICFactory(iswic2);
    if (!pWIC)
        return E_NOINTERFACE;

    ComPtr<IWICBitmapDecoder> decoder;
    HRESULT hr = pWIC->CreateDecoderFromFilename(szFile, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.GetAddressOf());
    if (FAILED(hr))
        return hr;

    {
        GUID containerFormat;
        hr = decoder->GetContainerFormat(&containerFormat);
        if (FAILED(hr))
            return hr;

        if (memcmp(&containerFormat, &GUID_ContainerFormatGif, sizeof(GUID)) != 0)
        {
            // This function only works for GIF
            return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
        }
    }

    ComPtr<IWICMetadataQueryReader> metareader;
    hr = decoder->GetMetadataQueryReader(metareader.GetAddressOf());
    if (FAILED(hr))
        return hr;

    PROPVARIANT propValue;
    PropVariantInit(&propValue);

    // Get palette
    WICColor rgbColors[256] = {};
    UINT actualColors = 0;
    {
        ComPtr<IWICPalette> palette;
        hr = pWIC->CreatePalette(palette.GetAddressOf());
        if (FAILED(hr))
            return hr;

        hr = decoder->CopyPalette(palette.Get());
        if (FAILED(hr))
            return hr;

        hr = palette->GetColors(static_cast<UINT>(std::size(rgbColors)), rgbColors, &actualColors);
        if (FAILED(hr))
            return hr;
    }

    // Get background color
    UINT bgColor = 0;
    if (usebgcolor)
    {
        // Most browsers just ignore the background color metadata and always use transparency
        hr = metareader->GetMetadataByName(L"/logscrdesc/GlobalColorTableFlag", &propValue);
        if (SUCCEEDED(hr))
        {
            bool hasTable = (propValue.vt == VT_BOOL && propValue.boolVal);
            PropVariantClear(&propValue);

            if (hasTable)
            {
                hr = metareader->GetMetadataByName(L"/logscrdesc/BackgroundColorIndex", &propValue);
                if (SUCCEEDED(hr))
                {
                    if (propValue.vt == VT_UI1)
                    {
                        uint8_t index = propValue.bVal;

                        if (index < actualColors)
                        {
                            bgColor = rgbColors[index];
                        }
                    }
                    PropVariantClear(&propValue);
                }
            }
        }
    }

    // Get global frame size
    UINT width = 0;
    UINT height = 0;

    hr = metareader->GetMetadataByName(L"/logscrdesc/Width", &propValue);
    if (FAILED(hr))
        return hr;

    if (propValue.vt != VT_UI2)
        return E_FAIL;

    width = propValue.uiVal;
    PropVariantClear(&propValue);

    hr = metareader->GetMetadataByName(L"/logscrdesc/Height", &propValue);
    if (FAILED(hr))
        return hr;

    if (propValue.vt != VT_UI2)
        return E_FAIL;

    height = propValue.uiVal;
    PropVariantClear(&propValue);

    UINT fcount;
    hr = decoder->GetFrameCount(&fcount);
    if (FAILED(hr))
        return hr;

    UINT disposal = DM_UNDEFINED;
    RECT rct = {};

    UINT previousFrame = 0;
    for (UINT iframe = 0; iframe < fcount; ++iframe)
    {
        int transparentIndex = -1;

        std::unique_ptr<ScratchImage> frameImage(new (std::nothrow) ScratchImage);
        if (!frameImage)
            return E_OUTOFMEMORY;

        if (disposal == DM_PREVIOUS)
        {
            hr = frameImage->InitializeFromImage(*loadedImages[previousFrame]->GetImage(0, 0, 0));
        }
        else if (iframe > 0)
        {
            hr = frameImage->InitializeFromImage(*loadedImages[iframe - 1]->GetImage(0, 0, 0));
        }
        else
        {
            hr = frameImage->Initialize2D(DXGI_FORMAT_B8G8R8A8_UNORM, width, height, 1, 1);
        }
        if (FAILED(hr))
            return hr;

        auto composedImage = frameImage->GetImage(0, 0, 0);

        if (!iframe)
        {
            RECT fullRct = { 0, 0, static_cast<long>(width), static_cast<long>(height) };
            FillRectangle(*composedImage, fullRct, bgColor);
        }
        else if (disposal == DM_BACKGROUND)
        {
            FillRectangle(*composedImage, rct, bgColor);
        }

        ComPtr<IWICBitmapFrameDecode> frame;
        hr = decoder->GetFrame(iframe, frame.GetAddressOf());
        if (FAILED(hr))
            return hr;

        WICPixelFormatGUID pixelFormat;
        hr = frame->GetPixelFormat(&pixelFormat);
        if (FAILED(hr))
            return hr;

        if (memcmp(&pixelFormat, &GUID_WICPixelFormat8bppIndexed, sizeof(GUID)) != 0)
        {
            // GIF is always loaded as this format
            return E_UNEXPECTED;
        }

        ComPtr<IWICMetadataQueryReader> frameMeta;
        hr = frame->GetMetadataQueryReader(frameMeta.GetAddressOf());
        if (SUCCEEDED(hr))
        {
            hr = frameMeta->GetMetadataByName(L"/imgdesc/Left", &propValue);
            if (SUCCEEDED(hr))
            {
                hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
                if (SUCCEEDED(hr))
                {
                    rct.left = static_cast<long>(propValue.uiVal);
                }
                PropVariantClear(&propValue);
            }

            hr = frameMeta->GetMetadataByName(L"/imgdesc/Top", &propValue);
            if (SUCCEEDED(hr))
            {
                hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
                if (SUCCEEDED(hr))
                {
                    rct.top = static_cast<long>(propValue.uiVal);
                }
                PropVariantClear(&propValue);
            }

            hr = frameMeta->GetMetadataByName(L"/imgdesc/Width", &propValue);
            if (SUCCEEDED(hr))
            {
                hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
                if (SUCCEEDED(hr))
                {
                    rct.right = static_cast<long>(propValue.uiVal) + rct.left;
                }
                PropVariantClear(&propValue);
            }

            hr = frameMeta->GetMetadataByName(L"/imgdesc/Height", &propValue);
            if (SUCCEEDED(hr))
            {
                hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
                if (SUCCEEDED(hr))
                {
                    rct.bottom = static_cast<long>(propValue.uiVal) + rct.top;
                }
                PropVariantClear(&propValue);
            }

            disposal = DM_UNDEFINED;
            hr = frameMeta->GetMetadataByName(L"/grctlext/Disposal", &propValue);
            if (SUCCEEDED(hr))
            {
                hr = (propValue.vt == VT_UI1 ? S_OK : E_FAIL);
                if (SUCCEEDED(hr))
                {
                    disposal = propValue.bVal;
                }
                PropVariantClear(&propValue);
            }

            hr = frameMeta->GetMetadataByName(L"/grctlext/TransparencyFlag", &propValue);
            if (SUCCEEDED(hr))
            {
                hr = (propValue.vt == VT_BOOL ? S_OK : E_FAIL);
                if (SUCCEEDED(hr) && propValue.boolVal)
                {
                    PropVariantClear(&propValue);
                    hr = frameMeta->GetMetadataByName(L"/grctlext/TransparentColorIndex", &propValue);
                    if (SUCCEEDED(hr))
                    {
                        hr = (propValue.vt == VT_UI1 ? S_OK : E_FAIL);
                        if (SUCCEEDED(hr) && propValue.uiVal < actualColors)
                        {
                            transparentIndex = static_cast<int>(propValue.uiVal);
                        }
                    }
                }
                PropVariantClear(&propValue);
            }

        }

        UINT w, h;
        hr = frame->GetSize(&w, &h);
        if (FAILED(hr))
            return hr;

        ScratchImage rawFrame;
        hr = rawFrame.Initialize2D(DXGI_FORMAT_B8G8R8A8_UNORM, w, h, 1, 1);
        if (FAILED(hr))
            return hr;

        ComPtr<IWICFormatConverter> FC;
        hr = pWIC->CreateFormatConverter(FC.GetAddressOf());
        if (FAILED(hr))
            return hr;

        hr = FC->Initialize(frame.Get(), GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeMedianCut);
        if (FAILED(hr))
            return hr;

        auto img = rawFrame.GetImage(0, 0, 0);

        hr = FC->CopyPixels(nullptr, static_cast<UINT>(img->rowPitch), static_cast<UINT>(img->slicePitch), img->pixels);
        if (FAILED(hr))
            return hr;

        if (!iframe || transparentIndex == -1)
        {
            Rect fullRect(0, 0, img->width, img->height);
            hr = CopyRectangle(*img, fullRect, *composedImage, TEX_FILTER_DEFAULT, size_t(rct.left), size_t(rct.top));
            if (FAILED(hr))
                return hr;
        }
        else
        {
            BlendRectangle(*composedImage, *img, rct, rgbColors[transparentIndex]);
        }

        if (disposal == DM_UNDEFINED || disposal == DM_NONE)
        {
            previousFrame = iframe;
        }

        loadedImages.emplace_back(std::move(frameImage));
    }

    PropVariantClear(&propValue);

    return S_OK;
}