Skip to content

Commit

Permalink
fix: Report frame-count, dispose-method, and background-color from GI…
Browse files Browse the repository at this point in the history
…F for use with animated WebP (#200)
  • Loading branch information
skidder authored Nov 12, 2024
1 parent 2a8aa7e commit acdc956
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 40 deletions.
110 changes: 85 additions & 25 deletions giflib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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));
Expand All @@ -1131,28 +1147,46 @@ 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: {
GifByteType* ExtData;
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;
}
}

Expand All @@ -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:
Expand All @@ -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;
}
50 changes: 37 additions & 13 deletions giflib.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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++
Expand Down
16 changes: 14 additions & 2 deletions giflib.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit acdc956

Please sign in to comment.