Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Report frame-count, dispose-method, and background-color from GIF for use with animated WebP #200

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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