Search code examples
c++direct2d

Transparent glitches while drawing GIF


I wrote some solution to draw gifs, using Direct2D GIF sample:

// somewhere in render loop

IWICBitmapFrameDecode* frame = nullptr;
IWICFormatConverter* converter = nullptr;
gifDecoder->GetFrame(current_frame, &frame);

if (frame)
{
    d2dWICFactory->CreateFormatConverter(&converter);

    if (converter)
    {
        ID2D1Bitmap* temp = nullptr;

        converter->Initialize(frame, GUID_WICPixelFormat32bppPBGRA,
            WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteType::WICBitmapPaletteTypeCustom);

        tar->CreateBitmapFromWicBitmap(converter, NULL, &temp);

        scale->SetValue(D2D1_SCALE_PROP_SCALE, D2D1::Vector2F(1, 1));
        scale->SetInput(0, temp);

        target->DrawImage(scale);

        SafeRelease(&temp);
    }
}
SafeRelease(&frame);
SafeRelease(&converter);

Where gifDecoder is obtained this way:

d2dWICFactory->CreateDecoderFromFilename(pathToGif, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &gifDecoder);

enter image description here

But in my program almost with all gifs, all frames, except the first one, for some reason have horizontal transparent lines. Obviously, when I view the same gif in browser or in another program there are no that holes.

I tried to change pixel format, pallet type, but that glitches are still there.

What do I do wrong?


Solution

  • Basic steps:

    1. On image load, in case of gif, for caching purposes store not all frames as ready to draw ID2D1Bitmaps but only the image's decoder. Every time decode the frame, get bitmap and draw it. At least, at first animation loop frames bitmaps and metadata (again, for each frame) can be cached.
    2. For each gif create compatible target with the size of gif screen (obtained from metadata) for composing.
    3. Compose animation on render: get metadata for current frame -> if current frame has disposal = true, clear compose target -> draw current frame to compose target (even if disposal true) -> draw compose target to render target,
    4. Note, that frames may have different sizes and even offsets.

    Approximate code:

    ID2D1DeviceContext *renderTarget;
    
    class GIF
    {
          ID2D1BitmapRenderTarget *compose;
    
          IWICBitmapDecoder *decoder; // it seems like precache all frames costs too much time, it will be faster to obtain each frame each time (maybe cache during first loop)
          float naturalWidth; // gif screen width
          float naturalHeight; // gif screen height
    
          int framesCount;
          int currentFrame;
          unsigned int lastFrameEpoch;
    
          int x;
          int y;
          int width; // image width needed to be drawn
          int height; // image height
    
          float fw; // scale factor width;
          float fh; // scale factor height
    
          GIF(const wchar_t *path, int x, int y, int width, int height)
          {
               // Init, using WIC obtain here image's decoder, naturalWidth, naturalHeight
               // framesCount and other optional stuff like loop information
               // using IWICMetadataQueryReader obtained from the whole image,
               // not from zero frame
    
               compose = nullptr;
               currentFrame = 0;
               lastFrameEpoch = epoch(); // implement Your millisecond function
    
               Resize(width, height);
          }
    
          void Resize(int width, int height)
          {
               this->width = width;
               this->height = height;
    
               // calculate scale factors
               fw = width / naturalWidth; 
               fh = height / naturalHeight;
    
               if(compose == nullptr)
               {
                     renderTarget->CreateCompatibleRenderTarget(D2D1::SizeF(naturalWidth, naturalHeight), &compose);
                     compose->Clear(D2D1::ColorF(0,0,0,0));
               }
          }
    
          void Render()
          {
               IWICBitmapFrameDecode* frame = nullptr;
               decoder->GetFrame(currentFrame, &frame);
    
               IWICFormatConverter* converter = nullptr;
               d2dWICFactory->CreateFormatConverter(&converter);
    
               converter->Initialize(frame, GUID_WICPixelFormat32bppPBGRA,
                    WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteType::WICBitmapPaletteTypeCustom);
    
                IWICMetadataQueryReader* reader = nullptr;
                frame->GetMetadataQueryReader(&reader);
    
                PROPVARIANT propValue;
                PropVariantInit(&propValue);
    
                char disposal = 0;
                float l = 0, t = 0; // frame offsets (may have)
    
                HRESULT hr = S_OK;
    
                reader->GetMetadataByName(L"/grctlext/Delay", &propValue);
                if (SUCCEEDED((propValue.vt == VT_UI2 ? S_OK : E_FAIL)))
                {
                    UINT frameDelay = 0;
                    UIntMult(propValue.uiVal, 10, &fr);
                    frameDelay = fr;
    
                    if (frameDelay < 16)
                    {
                        frameDelay = 16;
                    }
    
                    if(epoch() - lastFrameEpoch < frameDelay)
                    {
                           SafeRelease(&reader);
                           SafeRelease(&frame);
                           SafeRelease(&converter);
                           return;
                    }
                }
    
                
    
                if (SUCCEEDED(reader->GetMetadataByName(
                    L"/grctlext/Disposal",
                    &propValue)))
                {
                    hr = (propValue.vt == VT_UI1) ? S_OK : E_FAIL;
                    if (SUCCEEDED(hr))
                    {
                        disposal = propValue.bVal;
                    }
                }
    
                PropVariantClear(&propValue);
    
                {
                    hr = reader->GetMetadataByName(L"/imgdesc/Left", &propValue);
                    if (SUCCEEDED(hr))
                    {
                        hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
                        if (SUCCEEDED(hr))
                        {
                            l = static_cast<FLOAT>(propValue.uiVal);
                        }
                        PropVariantClear(&propValue);
                    }
                }
    
                {
                    hr = reader->GetMetadataByName(L"/imgdesc/Top", &propValue);
                    if (SUCCEEDED(hr))
                    {
                        hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
                        if (SUCCEEDED(hr))
                        {
                            t = static_cast<FLOAT>(propValue.uiVal);
                        }
                        PropVariantClear(&propValue);
                    }
                }
        
                renderTarget->CreateBitmapFromWicBitmap(converter, NULL, &temp);
    
    
                compose->BeginDraw();
                if (disposal == 2)
                    compose->Clear(D2D1::ColorF(0, 0, 0, 0));
            
                auto ss = temp->GetSize();
                compose->DrawBitmap(temp, D2D1::RectF(l, t, l + ss.width, t + ss.height));
                
                compose->EndDraw();
    
                ID2D1Bitmap* composition = nullptr;
                compose->GetBitmap(&composition);
    
                // You need to create scale effect to have antialised resizing
                scale->SetValue(D2D1_SCALE_PROP_SCALE, D2D1::Vector2F(fw, fh));
                scale->SetInput(0, composition);
    
                auto p = D2D1::Point2F(x, y);
    
                renderTarget->DrawImage(iscale, p); 
    
                 SafeRelease(&temp);                
                 SafeRelease(&composition);
                 SafeRelease(&reader);
                 SafeRelease(&frame);
                 SafeRelease(&converter);
          }
    }*gif[100] = {nullptr};
    
    
    inline void Render()
    {
          renderTarget->BeginDraw();
    
          for(int i = 0; i < 100 && gif[i] != nullptr; i++)
             gif[i]->Render();
    
          renderTarget->EndDraw();
    }