Search code examples
c++gifanimated-gifdirect2d

How to resize an animated GIF?


I am trying to add gif support to my program with Direct2D, according to the MSDN sample.

It seems that I almost deal with just rendering the animation: dispose, compose frames, etc.

But now I want a gif animation that is currently playing to be resized. The MSDN example actually just resets current frame when the image needs to be resized.


The problem is that during the resize I can't just clear current presented image and draw frame that need to be drawn because of the transparent pixels.

  • If during resize just to copy current composition, resize it, and draw, it will be fast, but there will be a quality loss.
  • If during resize literally redraw each frames from 0 to current, it works as it needs but has big performance issues even with relatively not big number of frames.

I just checked gifs resize behaviour in Chromium: it obviously does not reset frame to zero, and resize dynamically and fast.

How Chromium, for example, does it? How should I do it?


Update 1 I have one assumption, going to test it today-tomorrow: on gif loading create bitmap where compose all frames, and then draw that bitmap, when the gif is need to be resized. That will be obviously fast, I should not have loss quality (minimum), because it will not resize the original composed bitmap. But I think it should not work, because the composition for each frame can be different, i.e. composition of previous frames for frame 5 is not the same as composition for frame 20. Or maybe it actually same, or composition for higher frame is acceptable for the lower frame.


Update 2 Just tested, using precompose of all frames does not work. It works only for the last frames, obviously.


Update 3 currently in my solution, where during resize composition just erased, I noticed that horizontal transparent "holes" that now obviously appear on next frame, disappear on the next or after few next ones (can't currently understand), which means that I should not redraw all previous frames after resize. But how to determine how many should I?


Update 4 No, it was just a case, there are gifs that has frames, that use composition, that includes even first frame, so I always have to iterate from 0 to current_frame. Out of ideas currently...


Solution

  • First, Microsoft's sample code does not reset current number on resize - it restores it only during device recreation.

    The main difference between the code I wrote, the sample was that in my solution compatible target, used for storing frames composition, was resizing during the image resize. In my Microsoft sample this target creates ones during image load and it's size equals GIFs virtual screen size (or how it called).

    Do not know why for sure, but recreate each time composition target and redraw there it's resized bitmap gave huge quality loss and glitches.

    So approximate code will be:

    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
    
               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;
    
                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();
    }