[PATCH v2 0/1] MR11149: windowscodecs: Decode GIF frames on demand.
The GIF decoder currently calls DGifSlurp() during initialization, which decodes and stores pixel data for every frame in the image. For animated GIFs with a large number of frames this can result in significant startup time and memory consumption, even when only a single frame is requested by the application. Change the decoder to scan GIF headers during initialization and defer frame pixel decoding until CopyPixels() is called for a specific frame. This avoids eagerly decoding unused frames while preserving existing decoder behavior. Signed-off-by: chenzhengyong chenzhengyong@uniontech.com tested on deepin v25 & UOS V20. -- v2: windowscodecs: Decode GIF frames on demand. https://gitlab.winehq.org/wine/wine/-/merge_requests/11149
From: chenzhengyong <chenzhengyong@uniontech.com> The GIF decoder currently calls DGifSlurp() during initialization, which decodes and stores pixel data for every frame in the image. For animated GIFs with a large number of frames this can result in significant startup time and memory consumption, even when only a single frame is requested by the application. Change the decoder to scan GIF headers during initialization and defer frame pixel decoding until CopyPixels() is called for a specific frame. This avoids eagerly decoding unused frames while preserving existing decoder behavior. Signed-off-by: chenzhengyong <chenzhengyong@uniontech.com> --- dlls/windowscodecs/gifformat.c | 38 +++++++- dlls/windowscodecs/ungif.c | 171 +++++++++++++++++++++++++++++++++ dlls/windowscodecs/ungif.h | 8 ++ 3 files changed, 213 insertions(+), 4 deletions(-) diff --git a/dlls/windowscodecs/gifformat.c b/dlls/windowscodecs/gifformat.c index 287a49f83e9..8a33e87dceb 100644 --- a/dlls/windowscodecs/gifformat.c +++ b/dlls/windowscodecs/gifformat.c @@ -37,6 +37,7 @@ struct gif_decoder { struct decoder decoder; GifFileType *gif; + IStream *stream; /* kept to allow on-demand frame decoding */ }; static inline struct gif_decoder *impl_from_decoder(struct decoder *iface) @@ -646,6 +647,13 @@ static int _gif_inputfunc(GifFileType *gif, GifByteType *data, int len) { return bytesread; } +static void gif_seek_stream(GifFileType *gif, int offset) +{ + LARGE_INTEGER seek; + seek.QuadPart = offset; + IStream_Seek((IStream *)gif->UserData, seek, STREAM_SEEK_SET, NULL); +} + static HRESULT CDECL gif_decoder_initialize(struct decoder *iface, IStream *stream, struct decoder_stat *st) { struct gif_decoder *decoder = impl_from_decoder(iface); @@ -656,17 +664,29 @@ static HRESULT CDECL gif_decoder_initialize(struct decoder *iface, IStream *stre seek.QuadPart = 0; IStream_Seek(stream, seek, STREAM_SEEK_SET, NULL); + /* keep stream for on-demand frame decoding */ + IStream_AddRef(stream); + decoder->stream = stream; + /* read all data from the stream */ decoder->gif = DGifOpen((void *)stream, _gif_inputfunc); if (!decoder->gif) + { + IStream_Release(stream); return E_FAIL; + } + + /* Set up seek function for on-demand frame decoding */ + decoder->gif->seekFunc = gif_seek_stream; - ret = DGifSlurp(decoder->gif); + /* Only scan frame headers, skip pixel data for on-demand decoding */ + ret = DGifSlurpHeaders(decoder->gif); if (ret == GIF_ERROR) + { + DGifCloseFile(decoder->gif); + IStream_Release(stream); return E_FAIL; - - /* make sure we don't use the stream after this method returns */ - decoder->gif->UserData = NULL; + } st->flags = WICBitmapDecoderCapabilityCanDecodeAllImages | WICBitmapDecoderCapabilityCanDecodeSomeImages | @@ -759,6 +779,14 @@ static HRESULT CDECL gif_decoder_copy_pixels(struct decoder *iface, UINT frame, image = &decoder->gif->SavedImages[frame]; + /* Decode this frame's pixel data on demand */ + if (!image->RasterBits) + { + decoder->gif->UserData = (void *)decoder->stream; + if (DGifDecodeFrame(decoder->gif, frame) == GIF_ERROR) + return E_FAIL; + } + if (image->ImageDesc.Interlace) return copy_interlaced_pixels(image->RasterBits, image->ImageDesc.Width, image->ImageDesc.Height, image->ImageDesc.Width, prc, stride, buffersize, buffer); @@ -871,6 +899,8 @@ static void CDECL gif_decoder_destroy(struct decoder *iface) { struct gif_decoder *decoder = impl_from_decoder(iface); + if (decoder->stream) + IStream_Release(decoder->stream); DGifCloseFile(decoder->gif); free(decoder); } diff --git a/dlls/windowscodecs/ungif.c b/dlls/windowscodecs/ungif.c index aa0552c04ce..98584e016ba 100644 --- a/dlls/windowscodecs/ungif.c +++ b/dlls/windowscodecs/ungif.c @@ -590,6 +590,24 @@ DGifGetCodeNext(GifFileType * GifFile, return GIF_OK; } +/****************************************************************************** + * Skip the LZW image sub-blocks without decoding any pixels. + * Called after DGifSetupDecompress has read the code size byte. + *****************************************************************************/ +static int +DGifSkipImageData(GifFileType *GifFile) +{ + GifByteType *CodeBlock; + do { + if (DGifGetCodeNext(GifFile, &CodeBlock) == GIF_ERROR) + { + WARN("GIF is not properly terminated\n"); + break; + } + } while (CodeBlock != NULL); + return GIF_OK; +} + /****************************************************************************** * Setup the LZ decompression for this image: *****************************************************************************/ @@ -975,6 +993,159 @@ DGifSlurp(GifFileType * GifFile) { return (GIF_OK); } +/****************************************************************************** + * Like DGifSlurp() but skips pixel data of each frame. + * Only reads frame headers (ImageDesc, extension blocks) so it is fast. + * Use DGifDecodeFrame() to decode individual frames on demand. + *****************************************************************************/ +int +DGifSlurpHeaders(GifFileType *GifFile) +{ + GifRecordType RecordType; + SavedImage *sp; + GifByteType *ExtData; + Extensions temp_save; + + temp_save.ExtensionBlocks = NULL; + temp_save.ExtensionBlockCount = 0; + + do { + if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) + return GIF_ERROR; + + switch (RecordType) { + case IMAGE_DESC_RECORD_TYPE: + if (DGifGetImageDesc(GifFile) == GIF_ERROR) + return GIF_ERROR; + + sp = &GifFile->SavedImages[GifFile->ImageCount - 1]; + /* GetFileOffset is past the code size byte read by DGifSetupDecompress + * inside DGifGetImageDesc, so subtract 1 to point at the code size byte + * itself, so DGifDecodeFrame can re-read it correctly after seeking. */ + sp->LZWDataOffset = GetFileOffset(GifFile) - 1; + + /* Skip LZW image sub-blocks without decoding */ + if (DGifSkipImageData(GifFile) == GIF_ERROR) + return GIF_ERROR; + + /* Attach pending extension to this frame */ + if (temp_save.ExtensionBlocks) { + sp->Extensions.ExtensionBlocks = temp_save.ExtensionBlocks; + sp->Extensions.ExtensionBlockCount = temp_save.ExtensionBlockCount; + temp_save.ExtensionBlocks = NULL; + temp_save.ExtensionBlockCount = 0; + sp->Extensions.Function = sp->Extensions.ExtensionBlocks[0].Function; + } + break; + + case EXTENSION_RECORD_TYPE: + { + int Function; + Extensions *Extensions; + + if (DGifGetExtension(GifFile, &Function, &ExtData) == GIF_ERROR) + return GIF_ERROR; + + if (GifFile->ImageCount || Function == GRAPHICS_EXT_FUNC_CODE) + Extensions = &temp_save; + else + Extensions = &GifFile->Extensions; + + Extensions->Function = Function; + + if (ExtData) + { + if (AddExtensionBlock(Extensions, ExtData[0], &ExtData[1]) == GIF_ERROR) + return GIF_ERROR; + } + else + { + if (AddExtensionBlock(Extensions, 0, NULL) == GIF_ERROR) + return GIF_ERROR; + } + + while (ExtData != NULL) { + int Len; + GifByteType *Data; + + if (DGifGetExtensionNext(GifFile, &ExtData) == GIF_ERROR) + return GIF_ERROR; + + if (ExtData) + { + Len = ExtData[0]; + Data = &ExtData[1]; + } + else + { + Len = 0; + Data = NULL; + } + + if (AppendExtensionBlock(Extensions, Len, Data) == GIF_ERROR) + return GIF_ERROR; + } + break; + } + + case TERMINATE_RECORD_TYPE: + break; + + default: + break; + } + } while (RecordType != TERMINATE_RECORD_TYPE); + + if (temp_save.ExtensionBlocks) + FreeExtension(&temp_save); + + return GIF_OK; +} + +/****************************************************************************** + * Decode a specific frame on demand. + * Requires DGifSlurpHeaders() to have been called first to populate SavedImages. + * On success the frame's RasterBits are allocated and filled; subsequent calls + * return immediately if the frame is already decoded. + *****************************************************************************/ +int +DGifDecodeFrame(GifFileType *GifFile, int frame) +{ + GifFilePrivateType *Private = GifFile->Private; + SavedImage *sp; + int ImageSize; + + if (frame < 0 || frame >= GifFile->ImageCount) + return GIF_ERROR; + + sp = &GifFile->SavedImages[frame]; + + /* Already decoded */ + if (sp->RasterBits) + return GIF_OK; + + /* Seek to this frame's LZW data in the stream */ + if (GifFile->seekFunc) + { + ((SeekFunc)GifFile->seekFunc)(GifFile, sp->LZWDataOffset); + /* Reset the decoder's byte offset counter so subsequent READ() calls + * start from the correct position */ + Private->Offset = sp->LZWDataOffset; + } + + ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height; + + /* Re-initialize LZW decompress state (reads the code size byte) */ + Private->PixelCount = ImageSize; + DGifSetupDecompress(GifFile); + + sp->RasterBits = malloc(ImageSize * sizeof(GifPixelType)); + if (!sp->RasterBits) + return GIF_ERROR; + + return DGifGetLine(GifFile, sp->RasterBits, ImageSize); +} + /****************************************************************************** * GifFileType constructor with user supplied input function (TVT) *****************************************************************************/ diff --git a/dlls/windowscodecs/ungif.h b/dlls/windowscodecs/ungif.h index c402109bc67..4c91c4c4881 100644 --- a/dlls/windowscodecs/ungif.h +++ b/dlls/windowscodecs/ungif.h @@ -124,6 +124,7 @@ typedef struct GifFileType { struct SavedImage *SavedImages; /* Use this to accumulate file state */ void *UserData; /* hook to attach user data (TVT) */ void *Private; /* Don't mess with this! */ + void *seekFunc; /* SeekFunc: seek to absolute byte offset for on-demand decoding */ } GifFileType; typedef enum { @@ -137,6 +138,9 @@ typedef enum { /* func type to read gif data from arbitrary sources (TVT) */ typedef int (*InputFunc) (GifFileType *, GifByteType *, int); +/* func type to seek to an absolute byte offset in the GIF data source */ +typedef void (*SeekFunc) (GifFileType *, int offset); + /* GIF89 extension function codes */ #define COMMENT_EXT_FUNC_CODE 0xfe /* comment */ @@ -171,8 +175,12 @@ int DGifCloseFile(GifFileType * GifFile); typedef struct SavedImage { GifImageDesc ImageDesc; int ImageDescOffset; + int LZWDataOffset; /* byte offset to this frame's LZW sub-blocks in the stream */ unsigned char *RasterBits; Extensions Extensions; } SavedImage; +int DGifSlurpHeaders(GifFileType *GifFile); /* scan frame headers only, skip pixel data */ +int DGifDecodeFrame(GifFileType *GifFile, int frame); /* on-demand decode of a specific frame */ + #endif /* _UNGIF_H_ */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11149
Esme Povirk (@madewokherd) commented about dlls/windowscodecs/gifformat.c:
seek.QuadPart = 0; IStream_Seek(stream, seek, STREAM_SEEK_SET, NULL);
+ /* keep stream for on-demand frame decoding */ + IStream_AddRef(stream); + decoder->stream = stream; + /* read all data from the stream */ decoder->gif = DGifOpen((void *)stream, _gif_inputfunc); if (!decoder->gif) + { + IStream_Release(stream);
We should set `decoder->stream` to NULL when releasing it, so we don't release it twice when the object is freed. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11149#note_143158
Esme Povirk (@madewokherd) commented about dlls/windowscodecs/ungif.c:
return GIF_OK; }
+/****************************************************************************** + * Skip the LZW image sub-blocks without decoding any pixels. + * Called after DGifSetupDecompress has read the code size byte. + *****************************************************************************/ +static int +DGifSkipImageData(GifFileType *GifFile) +{ + GifByteType *CodeBlock; + do { + if (DGifGetCodeNext(GifFile, &CodeBlock) == GIF_ERROR)
So if I understand this, we're still reading all the image data, but then we're discarding it? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11149#note_143159
participants (3)
-
chenzhengyong -
Esme Povirk (@madewokherd) -
zhengyong chen (@chenzhengyong)