diff --git a/giflib.cpp b/giflib.cpp index 64504af9..2f29c41f 100644 --- a/giflib.cpp +++ b/giflib.cpp @@ -123,6 +123,16 @@ int giflib_decoder_get_prev_frame_delay(const giflib_decoder d) return d->prev_frame_delay_time; } +int giflib_decoder_get_prev_frame_disposal(const giflib_decoder d) +{ + switch (d->prev_frame_disposal) { + case DISPOSE_DO_NOT: + return GIF_DISPOSE_NONE; + default: + return GIF_DISPOSE_BACKGROUND; + } +} + void giflib_decoder_release(giflib_decoder d) { if (d->pixels) { @@ -474,6 +484,23 @@ giflib_decoder_frame_state giflib_decoder_skip_frame(giflib_decoder d) static int interlace_offset[] = {0, 4, 2, 1}; static int interlace_jumps[] = {8, 8, 4, 2}; +static void extract_background_color(GifFileType* gif, GraphicsControlBlock* gcb, + uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) { + bool have_transparency = (gcb->TransparentColor != NO_TRANSPARENT_COLOR); + if (have_transparency) { + *r = *g = *b = *a = 0; + } + else if (gif->SColorMap && gif->SColorMap->Colors) { + *r = gif->SColorMap->Colors[gif->SBackGroundColor].Red; + *g = gif->SColorMap->Colors[gif->SBackGroundColor].Green; + *b = gif->SColorMap->Colors[gif->SBackGroundColor].Blue; + *a = 255; + } + else { + *r = *g = *b = *a = 255; + } +} + // decode the full frame and write it into mat // decode_frame_header *must* be called before this function bool giflib_decoder_decode_frame(giflib_decoder d, opencv_mat mat) @@ -541,19 +568,9 @@ bool giflib_decoder_decode_frame(giflib_decoder d, opencv_mat mat) giflib_get_frame_gcb(d->gif, &gcb); if (!d->have_read_first_frame) { - bool have_transparency = (gcb.TransparentColor != NO_TRANSPARENT_COLOR); - if (have_transparency) { - d->bg_red = d->bg_green = d->bg_blue = d->bg_alpha = 0; - } - else if (d->gif->SColorMap && d->gif->SColorMap->Colors) { - d->bg_red = d->gif->SColorMap->Colors[d->gif->SBackGroundColor].Red; - d->bg_green = d->gif->SColorMap->Colors[d->gif->SBackGroundColor].Green; - d->bg_blue = d->gif->SColorMap->Colors[d->gif->SBackGroundColor].Blue; - d->bg_alpha = 255; - } - else { - d->bg_red = d->bg_green = d->bg_blue = d->bg_alpha = 255; - } + GraphicsControlBlock gcb; + giflib_get_frame_gcb(d->gif, &gcb); + extract_background_color(d->gif, &gcb, &d->bg_red, &d->bg_green, &d->bg_blue, &d->bg_alpha); } if (!giflib_decoder_render_frame(d, &gcb, mat)) { @@ -1113,15 +1130,14 @@ int giflib_encoder_get_output_length(giflib_encoder e) return e->dst_offset; } -int giflib_decoder_get_loop_count(const giflib_decoder d) { +struct GifAnimationInfo giflib_decoder_get_animation_info(const giflib_decoder d) { // Default to 1 loop (play once) if no NETSCAPE2.0 extension is found - int loop_count = 1; + GifAnimationInfo info = {1, 0, 255, 255, 255, 0}; // loop_count, frame_count, bg_r, bg_g, bg_b, bg_a // Create a temporary decoder to read extension blocks - // We need a separate decoder because reading extension blocks modifies decoder state giflib_decoder loopReader = new struct giflib_decoder_struct(); if (!loopReader) { - return loop_count; // Return default on allocation failure + return info; // Return default on allocation failure } memset(loopReader, 0, sizeof(struct giflib_decoder_struct)); @@ -1131,11 +1147,15 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) { GifFileType* gif = DGifOpen(loopReader, decode_func, &error); if (error) { delete loopReader; - return loop_count; + return info; } - // Read all blocks until we find NETSCAPE2.0 or hit end + bool found_loop_count = false; + bool found_gcb = false; + GraphicsControlBlock gcb = {}; GifRecordType recordType; + + // Read all blocks until we hit end while (DGifGetRecordType(gif, &recordType) == GIF_OK) { switch (recordType) { case EXTENSION_RECORD_TYPE: { @@ -1143,16 +1163,30 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) { int ExtFunction; if (DGifGetExtension(gif, &ExtFunction, &ExtData) == GIF_OK && ExtData != NULL) { + // Look for GraphicsControlBlock if we haven't found it yet + if (!found_gcb && ExtFunction == GRAPHICS_EXT_FUNC_CODE) { + found_gcb = true; + DGifExtensionToGCB(ExtData[0], &ExtData[1], &gcb); + // Get background color as soon as we have the GCB + uint8_t bg_red, bg_green, bg_blue, bg_alpha; + extract_background_color(gif, &gcb, &bg_red, &bg_green, + &bg_blue, &bg_alpha); + info.bg_red = bg_red; + info.bg_green = bg_green; + info.bg_blue = bg_blue; + info.bg_alpha = bg_alpha; + } // Look for NETSCAPE2.0 extension - if (ExtFunction == APPLICATION_EXT_FUNC_CODE && ExtData[0] >= 11 && + else if (!found_loop_count && + ExtFunction == APPLICATION_EXT_FUNC_CODE && + ExtData[0] >= 11 && memcmp(ExtData + 1, "NETSCAPE2.0", 11) == 0) { - // Get the next block with loop count if (DGifGetExtensionNext(gif, &ExtData) == GIF_OK && ExtData != NULL && ExtData[0] >= 3 && ExtData[1] == 1) { - loop_count = ExtData[2] | (ExtData[3] << 8); - goto cleanup; // Found what we need + info.loop_count = ExtData[2] | (ExtData[3] << 8); + found_loop_count = true; } } @@ -1167,10 +1201,23 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) { } case IMAGE_DESC_RECORD_TYPE: - // Skip image data + // Count frame and skip image data + info.frame_count++; if (DGifGetImageDesc(gif) != GIF_OK) { goto cleanup; } + // Skip the image data + { + GifByteType* CodeBlock; + if (DGifGetCode(gif, &error, &CodeBlock) == GIF_ERROR) { + goto cleanup; + } + while (CodeBlock != NULL) { + if (DGifGetCodeNext(gif, &CodeBlock) == GIF_ERROR) { + goto cleanup; + } + } + } break; case TERMINATE_RECORD_TYPE: @@ -1181,8 +1228,21 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) { } } + // If we never found a GCB, still need to set background color + if (!found_gcb) { + uint8_t bg_red, bg_green, bg_blue, bg_alpha; + extract_background_color(gif, &gcb, &bg_red, &bg_green, + &bg_blue, &bg_alpha); + + // convert to int to handle uint limitations in rust FFI + info.bg_red = bg_red; + info.bg_green = bg_green; + info.bg_blue = bg_blue; + info.bg_alpha = bg_alpha; + } + cleanup: DGifCloseFile(gif, &error); delete loopReader; - return loop_count; + return info; } diff --git a/giflib.go b/giflib.go index 655fbcdc..38cdd3e4 100644 --- a/giflib.go +++ b/giflib.go @@ -12,12 +12,17 @@ import ( ) type gifDecoder struct { - decoder C.giflib_decoder - mat C.opencv_mat - buf []byte - frameIndex int - loopCount int - loopCountRead bool + decoder C.giflib_decoder + mat C.opencv_mat + buf []byte + frameIndex int + loopCount int + frameCount int + animationInfoRead bool + bgRed uint8 + bgGreen uint8 + bgBlue uint8 + bgAlpha uint8 } type gifEncoder struct { @@ -69,7 +74,7 @@ func (d *gifDecoder) Header() (*ImageHeader, error) { height: int(C.giflib_decoder_get_height(d.decoder)), pixelType: PixelType(C.CV_8UC4), orientation: OrientationTopLeft, - numFrames: int(C.giflib_decoder_get_num_frames(d.decoder)), + numFrames: d.FrameCount(), contentLength: len(d.buf), }, nil } @@ -112,17 +117,36 @@ func (d *gifDecoder) Duration() time.Duration { } func (d *gifDecoder) BackgroundColor() uint32 { - return 0x00FFFFFF // use a non-opaque alpha value so we can see the background if needed + d.readAnimationInfo() + return uint32(d.bgRed)<<16 | uint32(d.bgGreen)<<8 | uint32(d.bgBlue) | uint32(d.bgAlpha)<<24 } -func (d *gifDecoder) LoopCount() int { - if !d.loopCountRead { - d.loopCount = int(C.giflib_decoder_get_loop_count(d.decoder)) - d.loopCountRead = true +// readAnimationInfo reads the GIF info from the decoder and caches it +// this involves reading extension blocks and is relatively expensive +// so we only do it once when we need it +func (d *gifDecoder) readAnimationInfo() { + if !d.animationInfoRead { + info := C.giflib_decoder_get_animation_info(d.decoder) + d.loopCount = int(info.loop_count) + d.frameCount = int(info.frame_count) + d.animationInfoRead = true + d.bgRed = uint8(info.bg_red) + d.bgGreen = uint8(info.bg_green) + d.bgBlue = uint8(info.bg_blue) + d.bgAlpha = uint8(info.bg_alpha) } +} + +func (d *gifDecoder) LoopCount() int { + d.readAnimationInfo() return d.loopCount } +func (d *gifDecoder) FrameCount() int { + d.readAnimationInfo() + return d.frameCount +} + func (d *gifDecoder) DecodeTo(f *Framebuffer) error { h, err := d.Header() if err != nil { @@ -157,7 +181,7 @@ func (d *gifDecoder) DecodeTo(f *Framebuffer) error { } f.duration = time.Duration(C.giflib_decoder_get_prev_frame_delay(d.decoder)) * 10 * time.Millisecond f.blend = NoBlend - f.dispose = DisposeToBackgroundColor + f.dispose = DisposeMethod(C.giflib_decoder_get_prev_frame_disposal(d.decoder)) f.xOffset = 0 f.yOffset = 0 d.frameIndex++ diff --git a/giflib.hpp b/giflib.hpp index 30e12bbc..da973901 100644 --- a/giflib.hpp +++ b/giflib.hpp @@ -7,6 +7,18 @@ extern "C" { #endif +struct GifAnimationInfo { + int loop_count; + int frame_count; + int bg_red; + int bg_green; + int bg_blue; + int bg_alpha; +}; + +#define GIF_DISPOSE_NONE 0 +#define GIF_DISPOSE_BACKGROUND 1 + typedef struct giflib_decoder_struct* giflib_decoder; typedef struct giflib_encoder_struct* giflib_encoder; @@ -34,8 +46,8 @@ bool giflib_encoder_encode_frame(giflib_encoder e, const giflib_decoder d, const bool giflib_encoder_flush(giflib_encoder e, const giflib_decoder d); void giflib_encoder_release(giflib_encoder e); int giflib_encoder_get_output_length(giflib_encoder e); -int giflib_decoder_get_loop_count(const giflib_decoder d); - +struct GifAnimationInfo giflib_decoder_get_animation_info(const giflib_decoder d); +int giflib_decoder_get_prev_frame_disposal(const giflib_decoder d); #ifdef __cplusplus } #endif