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

Rendering with gd_is_bgcolor is inheritly flawed #25

Open
marcakafoddex opened this issue May 10, 2023 · 5 comments
Open

Rendering with gd_is_bgcolor is inheritly flawed #25

marcakafoddex opened this issue May 10, 2023 · 5 comments

Comments

@marcakafoddex
Copy link

marcakafoddex commented May 10, 2023

Thank you for this lib! Appreciate the work.
However, rendering a frame to its RGB colors, and then using gd_is_bgcolor is inheritly flawed. It's not at all impossible that the bgcolor is set to a "color" that also appears elsewhere in the palette. gd_is_bgcolor will mark those non-transparent pixels as bg colors incorrectly.

gd_render_frame(gif, buffer);
    color = buffer;
    for (y = 0; y < gif->height; y++) {
        for (x = 0; x < gif->width; x++) {
            if (gd_is_bgcolor(gif, color)) // will return true for non-transparent similarly colored pixels as well
                transparent_pixel(x, y);
            else
                opaque_pixel(x, y, color);
            color += 3;
        }
    }

The solution is to fill the "render" buffer not with 3-byte RGB colors, but with 1-byte indices instead, and then check each index against the background index instead. Just pointing this out for other people, in case they run into the same problem as me.

@marcakafoddex
Copy link
Author

This is a modified version of gifdec.c and gifdec.h, that has the following changes:

  • adds support for loading from memory by specifying custom read functions (not always from disk)
  • removes gd_is_bgcolor as it's a broken concept
  • when rendering a frame with gd_render_frame it now expects FOUR bytes per pixel, not THREE, where the 4th bytes is the index, which can be compared against bgindex, like this:
auto gif = gd_open_....;
std::vector<uint8_t> gifFrame(static_cast<size_t>(gif->width * gif->height * 4));
std::vector<uint8_t> pixels(static_cast<size_t>(gif->width * gif->height * 4));
int ret;
do {
	ret = gd_get_frame(gif);
	if (ret == -1)
		return Result::Error;
	gd_render_frame(gif, gifFrame.data());

	auto color = gifFrame.data();
	auto output = pixels.data();
	for (uint32_t i = 0; i < gif->height; i++) {
		for (uint32_t j = 0; j < gif->width; j++, color += 4, output += 4) {
			if (color[3] == gif->bgindex) {
				output[0] = 0;
				output[1] = 0;
				output[2] = 0;
				output[3] = 0;
			} else {
				output[0] = color[0];
				output[1] = color[1];
				output[2] = color[2];
				output[3] = 255;
			}
		}
	}

	// do something with output pixels, ie. upload to OpenGL or whatever
} while (ret != 0);

The code below works for me, but YMMV, use at your own risk.

gifdec.h

#ifndef GIFDEC_H
#define GIFDEC_H

#include <stdint.h>
#include <sys/types.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct gd_FUNCS {
	int (*read)(void*, unsigned int, void*);		// should read up to specified amount of bytes, should return amount of read bytes, or -1 on error, last is userdata
	int (*seek)(unsigned int, int, void*);			// should seek from current position if int value is zero, from start position if not zero, return 0 if okay, otherwise non-zero, last is userdata
	unsigned int(*position)(void*);					// should return current read position, otherwise non-zero, last is userdata
	void (*close)(void*);
} gd_FUNCS;

typedef struct gd_Palette {
	int size;
	uint8_t colors[0x100 * 3];
} gd_Palette;

typedef struct gd_GCE {
	uint16_t delay;
	uint8_t tindex;
	uint8_t disposal;
	int input;
	int transparency;
} gd_GCE;

typedef struct gd_GIF {
	const gd_FUNCS* funcs;
	void* userdata;
	off_t anim_start;
	uint16_t width, height;
	uint16_t depth;
	uint16_t loop_count;
	gd_GCE gce;
	gd_Palette *palette;
	gd_Palette lct, gct;
	void (*plain_text)(
		struct gd_GIF *gif, uint16_t tx, uint16_t ty,
		uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch,
		uint8_t fg, uint8_t bg
	);
	void (*comment)(struct gd_GIF *gif);
	void (*application)(struct gd_GIF *gif, char id[8], char auth[3]);
	uint16_t fx, fy, fw, fh;
	uint8_t bgindex;
	uint8_t *canvas, *frame;
} gd_GIF;


gd_GIF *gd_open_gif(const char *fname);
gd_GIF *gd_open_gif_funcs(const gd_FUNCS* funcs, void* userdata);
int gd_get_frame(gd_GIF *gif);
void gd_render_frame(gd_GIF *gif, uint8_t *buffer);
void gd_rewind(gd_GIF *gif);
void gd_close_gif(gd_GIF *gif);

#ifdef __cplusplus
}
#endif

#endif /* GIFDEC_H */

gifdec.c

#if defined(WIN32)
#pragma warning(disable : 4244)
#pragma warning(disable : 4701)
#endif

#include "gifdec.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>

#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))

// not sure if we can depend cross-platform on SEEK_CUR and SEEK_SET being defined to
// the same thing, so let's define our own
#define SEEK_FROM_START 1
#define SEEK_FROM_CURRENT 0

static int myRead(void* target, unsigned int count, void* userdata) {
	FILE* file = userdata;
	return (int)(fread(target, 1, count, file));
}

static int mySeek(unsigned int position, int fromStart, void* userdata) {
	FILE* file = userdata;
	if (fromStart)
		return fseek(file, position, SEEK_SET);
	return fseek(file, position, SEEK_CUR);
}

static unsigned int myPosition(void* userdata) {
	FILE* file = userdata;
	return ftell(file);
}

static void myClose(void* userdata) {
	FILE* file = userdata;
	fclose(file);
}

static const gd_FUNCS kFileFuncs = { &myRead, &mySeek, &myPosition, &myClose };

typedef struct Entry {
	uint16_t length;
	uint16_t prefix;
	uint8_t  suffix;
} Entry;

typedef struct Table {
	int bulk;
	int nentries;
	Entry *entries;
} Table;

static uint16_t
read_num(const gd_FUNCS* funcs, void* userdata)
{
	uint8_t bytes[2];

	funcs->read(bytes, 2, userdata);
	return bytes[0] + (((uint16_t) bytes[1]) << 8);
}

gd_GIF*
gd_open_gif_funcs(const gd_FUNCS* funcs, void* userdata) {
	uint8_t sigver[3];
	uint16_t width, height, depth;
	uint8_t fdsz, bgidx, aspect;
	int i;
	uint8_t* bgcolor;
	int gct_sz;
	gd_GIF* gif;

	if (!funcs || !funcs->read || !funcs->seek || !funcs->position || !funcs->close)
		return NULL;

	gif = calloc(1, sizeof(*gif));
	if (!gif)
		goto fail;

	/* Header */
	funcs->read(sigver, 3, userdata);
	if (memcmp(sigver, "GIF", 3) != 0) {
		goto fail;
	}
	/* Version */
	funcs->read(sigver, 3, userdata);
	if (memcmp(sigver, "89a", 3) != 0) {
		goto fail;
	}
	/* Width x Height */
	width = read_num(funcs, userdata);
	height = read_num(funcs, userdata);
	/* FDSZ */
	funcs->read(&fdsz, 1, userdata);
	/* Presence of GCT */
	if (!(fdsz & 0x80)) {
		goto fail;
	}
	/* Color Space's Depth */
	depth = ((fdsz >> 4) & 7) + 1;
	/* Ignore Sort Flag. */
	/* GCT Size */
	gct_sz = 1 << ((fdsz & 0x07) + 1);
	/* Background Color Index */
	funcs->read(&bgidx, 1, userdata);
	/* Aspect Ratio */
	funcs->read(&aspect, 1, userdata);
	/* Create gd_GIF Structure. */
	gif = calloc(1, sizeof(*gif));
	if (!gif) 
		goto fail;
	gif->funcs = funcs;
	gif->userdata = userdata;
	gif->width = width;
	gif->height = height;
	gif->depth = depth;
	/* Read GCT */
	gif->gct.size = gct_sz;
	funcs->read(gif->gct.colors, 3 * gif->gct.size, userdata);
	gif->palette = &gif->gct;
	gif->bgindex = bgidx;
	gif->frame = calloc(5, width * height);	// 1 + (3 + 1)
	if (!gif->frame) {
		free(gif);
		goto fail;
	}
	gif->canvas = &gif->frame[width * height];
	if (gif->bgindex)
		memset(gif->frame, gif->bgindex, gif->width * gif->height);
	bgcolor = &gif->palette->colors[gif->bgindex * 3];
	if (bgcolor[0] || bgcolor[1] || bgcolor[2])
		for (i = 0; i < gif->width * gif->height; i++) {
			gif->canvas[i * 4 + 0] = bgcolor[0];
			gif->canvas[i * 4 + 1] = bgcolor[1];
			gif->canvas[i * 4 + 2] = bgcolor[2];
			gif->canvas[i * 4 + 3] = gif->bgindex;
		}
	gif->anim_start = funcs->position(userdata);
	goto ok;
fail:
	gif = NULL;
ok:
	return gif;
}

gd_GIF *
gd_open_gif(const char *fname)
{
	FILE* file = fopen(fname, "rb");
	if (!file)
		return NULL;
	gd_GIF* gif = gd_open_gif_funcs(&kFileFuncs, file);
	if (!gif) {
		fclose(file);
		return NULL;
	}
	return gif;
}

static void
discard_sub_blocks(gd_GIF *gif)
{
	uint8_t size;

	do {
		gif->funcs->read(&size, 1, gif->userdata);
		gif->funcs->seek(size, SEEK_FROM_CURRENT, gif->userdata);
	} while (size);
}

static void
read_plain_text_ext(gd_GIF *gif)
{
	if (gif->plain_text) {
		uint16_t tx, ty, tw, th;
		uint8_t cw, ch, fg, bg;
		off_t sub_block;
		gif->funcs->seek(1, SEEK_FROM_CURRENT, gif->userdata); /* block size = 12 */
		tx = read_num(gif->funcs, gif->userdata);
		ty = read_num(gif->funcs, gif->userdata);
		tw = read_num(gif->funcs, gif->userdata);
		th = read_num(gif->funcs, gif->userdata);
		gif->funcs->read(&cw, 1, gif->userdata);
		gif->funcs->read(&ch, 1, gif->userdata);
		gif->funcs->read(&fg, 1, gif->userdata);
		gif->funcs->read(&bg, 1, gif->userdata);
		sub_block = gif->funcs->position(gif->userdata);
		gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg);
		gif->funcs->seek(sub_block, SEEK_FROM_START, gif->userdata);
	} else {
		/* Discard plain text metadata. */
		gif->funcs->seek(13, SEEK_FROM_CURRENT, gif->userdata);
	}
	/* Discard plain text sub-blocks. */
	discard_sub_blocks(gif);
}

static void
read_graphic_control_ext(gd_GIF *gif)
{
	uint8_t rdit;

	/* Discard block size (always 0x04). */
	gif->funcs->seek(1, SEEK_FROM_CURRENT, gif->userdata);
	gif->funcs->read(&rdit, 1, gif->userdata);
	gif->gce.disposal = (rdit >> 2) & 3;
	gif->gce.input = rdit & 2;
	gif->gce.transparency = rdit & 1;
	gif->gce.delay = read_num(gif->funcs, gif->userdata);
	gif->funcs->read(&gif->gce.tindex, 1, gif->userdata);
	/* Skip block terminator. */
	gif->funcs->seek(1, SEEK_FROM_CURRENT, gif->userdata);
}

static void
read_comment_ext(gd_GIF *gif)
{
	if (gif->comment) {
		off_t sub_block = gif->funcs->position(gif->userdata);
		gif->comment(gif);
		gif->funcs->seek(sub_block, SEEK_FROM_START, gif->userdata);
	}
	/* Discard comment sub-blocks. */
	discard_sub_blocks(gif);
}

static void
read_application_ext(gd_GIF *gif)
{
	char app_id[8];
	char app_auth_code[3];

	/* Discard block size (always 0x0B). */
	gif->funcs->seek(1, SEEK_FROM_CURRENT, gif->userdata);
	/* Application Identifier. */
	gif->funcs->read(app_id, 8, gif->userdata);
	/* Application Authentication Code. */
	gif->funcs->read(app_auth_code, 3, gif->userdata);
	if (!strncmp(app_id, "NETSCAPE", sizeof(app_id))) {
		/* Discard block size (0x03) and constant byte (0x01). */
		gif->funcs->seek(2, SEEK_FROM_CURRENT, gif->userdata);
		gif->loop_count = read_num(gif->funcs, gif->userdata);
		/* Skip block terminator. */
		gif->funcs->seek(1, SEEK_FROM_CURRENT, gif->userdata);
	} else if (gif->application) {
		off_t sub_block = gif->funcs->position(gif->userdata);
		gif->application(gif, app_id, app_auth_code);
		gif->funcs->seek(sub_block, SEEK_FROM_START, gif->userdata);
		discard_sub_blocks(gif);
	} else {
		discard_sub_blocks(gif);
	}
}

static void
read_ext(gd_GIF *gif)
{
	uint8_t label;

	gif->funcs->read(&label, 1, gif->userdata);
	switch (label) {
	case 0x01:
		read_plain_text_ext(gif);
		break;
	case 0xF9:
		read_graphic_control_ext(gif);
		break;
	case 0xFE:
		read_comment_ext(gif);
		break;
	case 0xFF:
		read_application_ext(gif);
		break;
	default:
		fprintf(stderr, "unknown extension: %02X\n", label);
	}
}

static Table *
new_table(int key_size)
{
	int key;
	int init_bulk = MAX(1 << (key_size + 1), 0x100);
	Table *table = malloc(sizeof(*table) + sizeof(Entry) * init_bulk);
	if (table) {
		table->bulk = init_bulk;
		table->nentries = (1 << key_size) + 2;
		table->entries = (Entry *) &table[1];
		for (key = 0; key < (1 << key_size); key++)
			table->entries[key] = (Entry) {1, 0xFFF, key};
	}
	return table;
}

/* Add table entry. Return value:
 *  0 on success
 *  +1 if key size must be incremented after this addition
 *  -1 if could not realloc table */
static int
add_entry(Table **tablep, uint16_t length, uint16_t prefix, uint8_t suffix)
{
	Table *table = *tablep;
	if (table->nentries == table->bulk) {
		table->bulk *= 2;
		table = realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk);
		if (!table) return -1;
		table->entries = (Entry *) &table[1];
		*tablep = table;
	}
	table->entries[table->nentries] = (Entry) {length, prefix, suffix};
	table->nentries++;
	if ((table->nentries & (table->nentries - 1)) == 0)
		return 1;
	return 0;
}

static uint16_t
get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte)
{
	int bits_read;
	int rpad;
	int frag_size;
	uint16_t key;

	key = 0;
	for (bits_read = 0; bits_read < key_size; bits_read += frag_size) {
		rpad = (*shift + bits_read) % 8;
		if (rpad == 0) {
			/* Update byte. */
			if (*sub_len == 0) {
				gif->funcs->read(sub_len, 1, gif->userdata); /* Must be nonzero! */
				if (*sub_len == 0)
					return 0x1000;
			}
			gif->funcs->read(byte, 1, gif->userdata);
			(*sub_len)--;
		}
		frag_size = MIN(key_size - bits_read, 8 - rpad);
		key |= ((uint16_t) ((*byte) >> rpad)) << bits_read;
	}
	/* Clear extra bits to the left. */
	key &= (1 << key_size) - 1;
	*shift = (*shift + key_size) % 8;
	return key;
}

/* Compute output index of y-th input line, in frame of height h. */
static int
interlaced_line_index(int h, int y)
{
	int p; /* number of lines in current pass */

	p = (h - 1) / 8 + 1;
	if (y < p) /* pass 1 */
		return y * 8;
	y -= p;
	p = (h - 5) / 8 + 1;
	if (y < p) /* pass 2 */
		return y * 8 + 4;
	y -= p;
	p = (h - 3) / 4 + 1;
	if (y < p) /* pass 3 */
		return y * 4 + 2;
	y -= p;
	/* pass 4 */
	return y * 2 + 1;
}

/* Decompress image pixels.
 * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */
static int
read_image_data(gd_GIF *gif, int interlace)
{
	uint8_t sub_len, shift, byte;
	int init_key_size, key_size, table_is_full;
	int frm_off, frm_size, str_len, i, p, x, y;
	uint16_t key, clear, stop;
	int ret;
	Table *table;
	Entry entry;
	off_t start, end;

	gif->funcs->read(&byte, 1, gif->userdata);
	key_size = (int) byte;
	if (key_size < 2 || key_size > 8)
		return -1;
	
	start = gif->funcs->position(gif->userdata);
	discard_sub_blocks(gif);
	end = gif->funcs->position(gif->userdata);
	gif->funcs->seek(start, SEEK_FROM_START, gif->userdata);
	clear = 1 << key_size;
	stop = clear + 1;
	table = new_table(key_size);
	key_size++;
	init_key_size = key_size;
	sub_len = shift = 0;
	key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */
	frm_off = 0;
	ret = 0;
	frm_size = gif->fw*gif->fh;
	while (frm_off < frm_size) {
		if (key == clear) {
			key_size = init_key_size;
			table->nentries = (1 << (key_size - 1)) + 2;
			table_is_full = 0;
		} else if (!table_is_full) {
			ret = add_entry(&table, str_len + 1, key, entry.suffix);
			if (ret == -1) {
				free(table);
				return -1;
			}
			if (table->nentries == 0x1000) {
				ret = 0;
				table_is_full = 1;
			}
		}
		key = get_key(gif, key_size, &sub_len, &shift, &byte);
		if (key == clear) continue;
		if (key == stop || key == 0x1000) break;
		if (ret == 1) key_size++;
		entry = table->entries[key];
		str_len = entry.length;
		for (i = 0; i < str_len; i++) {
			p = frm_off + entry.length - 1;
			x = p % gif->fw;
			y = p / gif->fw;
			if (interlace)
				y = interlaced_line_index((int) gif->fh, y);
			gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix;
			if (entry.prefix == 0xFFF)
				break;
			else
				entry = table->entries[entry.prefix];
		}
		frm_off += str_len;
		if (key < table->nentries - 1 && !table_is_full)
			table->entries[table->nentries - 1].suffix = entry.suffix;
	}
	free(table);
	if (key == stop)
		gif->funcs->read(&sub_len, 1, gif->userdata); /* Must be zero! */
	gif->funcs->seek(end, SEEK_FROM_START, gif->userdata);
	return 0;
}

/* Read image.
 * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */
static int
read_image(gd_GIF *gif)
{
	uint8_t fisrz;
	int interlace;

	/* Image Descriptor. */
	gif->fx = read_num(gif->funcs, gif->userdata);
	gif->fy = read_num(gif->funcs, gif->userdata);
	
	if (gif->fx >= gif->width || gif->fy >= gif->height)
		return -1;
	
	gif->fw = read_num(gif->funcs, gif->userdata);
	gif->fh = read_num(gif->funcs, gif->userdata);
	
	gif->fw = MIN(gif->fw, gif->width - gif->fx);
	gif->fh = MIN(gif->fh, gif->height - gif->fy);
	
	gif->funcs->read(&fisrz, 1, gif->userdata);
	interlace = fisrz & 0x40;
	/* Ignore Sort Flag. */
	/* Local Color Table? */
	if (fisrz & 0x80) {
		/* Read LCT */
		gif->lct.size = 1 << ((fisrz & 0x07) + 1);
		gif->funcs->read(gif->lct.colors, 3 * gif->lct.size, gif->userdata);
		gif->palette = &gif->lct;
	} else
		gif->palette = &gif->gct;
	/* Image Data. */
	return read_image_data(gif, interlace);
}

static void
render_frame_rect(gd_GIF *gif, uint8_t *buffer)
{
	int i, j, k;
	uint8_t index, *color;
	i = gif->fy * gif->width + gif->fx;
	for (j = 0; j < gif->fh; j++) {
		for (k = 0; k < gif->fw; k++) {
			index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k];
			color = &gif->palette->colors[index*3];
			if (!gif->gce.transparency || index != gif->gce.tindex) {
				buffer[(i + k) * 4 + 0] = color[0];
				buffer[(i + k) * 4 + 1] = color[1];
				buffer[(i + k) * 4 + 2] = color[2];
				buffer[(i + k) * 4 + 3] = index;
			}
		}
		i += gif->width;
	}
}

static void
dispose(gd_GIF *gif)
{
	int i, j, k;
	uint8_t *bgcolor;
	switch (gif->gce.disposal) {
	case 2: /* Restore to background color. */
		bgcolor = &gif->palette->colors[gif->bgindex*3];
		i = gif->fy * gif->width + gif->fx;
		for (j = 0; j < gif->fh; j++) {
			for (k = 0; k < gif->fw; k++) {
				gif->canvas[(i + k) * 4 + 0] = bgcolor[0];
				gif->canvas[(i + k) * 4 + 1] = bgcolor[1];
				gif->canvas[(i + k) * 4 + 2] = bgcolor[2];
				gif->canvas[(i + k) * 4 + 3] = gif->bgindex;

			}
			i += gif->width;
		}
		break;
	case 3: /* Restore to previous, i.e., don't update canvas.*/
		break;
	default:
		/* Add frame non-transparent pixels to canvas. */
		render_frame_rect(gif, gif->canvas);
	}
}

/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */
int
gd_get_frame(gd_GIF *gif)
{
	char sep;

	dispose(gif);
	gif->funcs->read(&sep, 1, gif->userdata);
	while (sep != ',') {
		if (sep == ';')
			return 0;
		if (sep == '!')
			read_ext(gif);
		else return -1;
		gif->funcs->read(&sep, 1, gif->userdata);
	}
	if (read_image(gif) == -1)
		return -1;
	return 1;
}

void
gd_render_frame(gd_GIF *gif, uint8_t *buffer)
{
	memcpy(buffer, gif->canvas, gif->width * gif->height * 4);
	render_frame_rect(gif, buffer);
}

void
gd_rewind(gd_GIF *gif)
{
	gif->funcs->seek(gif->anim_start, SEEK_FROM_START, gif->userdata);
}

void
gd_close_gif(gd_GIF *gif)
{
	gif->funcs->close(gif->userdata);
	free(gif->frame);    
	free(gif);
}

@lukegrahamSydney
Copy link

lukegrahamSydney commented Feb 6, 2024

Hello marcakafoddex

I havn't used the original lib and went straight to yours, so i don't know if this issue is with yours or from the original lib, but it seems theres an issue with transparency. While the background colour is correctly ignored, it seems on some images (quite a lot) theres other colours being incorrectly treated as transparent. The screenshot for example:

image

My code is here. This code creates an SDL surface with the palette. I've also used the code in your example and got the same result.:

`gd_GIF* gif = gd_open_gif(getFileName().c_str());
if (gif != nullptr)
{
uint8_t* buffer = (uint8_t*)malloc(gif->width * gif->height * 4);

while (gd_get_frame(gif))
{
    gd_render_frame(gif, buffer);

    uint8_t* color = buffer;

    int pitch = 1 * gif->width;
    unsigned char* pixels = (unsigned char*)SDL_malloc(pitch * gif->height);
    uint8_t* output = pixels;

    for (uint32_t i = 0; i < gif->height; i++)
    {
        for (uint32_t j = 0; j < gif->width; j++, color += 4, output += 1)
        {
            //We only need the palette index in our output.
            output[0] = color[3];
        }
    }

    auto surface = SDL_CreateRGBSurfaceFrom(pixels, gif->width, gif->height, 1 * 8, pitch, 0, 0, 0, 1);
    if (surface != NULL)
    {
        surface->flags &= ~SDL_PREALLOC;
        int paletteLen = gif->palette->size;
        if (paletteLen > 0)
        {
            auto palette = gif->palette->colors;
            SDL_Color newPalette[256];

            paletteLen = paletteLen > 256 ? 256 : paletteLen;

            for (auto i = 0U; i < paletteLen; ++i)
            {
                newPalette[i].r = palette[i * 3 + 0];
                newPalette[i].g = palette[i * 3 + 1];
                newPalette[i].b = palette[i * 3 + 2];
                
                if (i == gif->bgindex) {
                    newPalette[i].a = 0;
                } else newPalette[i].a = 255;
            }

            SDL_SetPaletteColors(surface->format->palette, newPalette, 0, paletteLen);
        }

        auto frame = new Image(surface, false);
        addFrame(frame);
    }
}

gd_rewind(gif);

free(buffer);
gd_close_gif(gif);

}`

Previously was using stb gif decoder without issue (except no animation)

@lukegrahamSydney
Copy link

I get a better result by ignoring the bgindex altogether. Instead i use gif->gce.tindex

` gd_GIF* gif = gd_open_gif(getFileName().c_str());
if (gif != nullptr)
{
while (gd_get_frame(gif))
{
int pitch = 1 * gif->width;
unsigned char* indexes = (unsigned char*)SDL_malloc(pitch * gif->height);

     //"clear" the image by using the tindex (transparent index)
     memset(indexes, gif->gce.tindex, pitch * gif->height);

     int i, j, k;
     uint8_t * color;
     i = gif->fy * gif->width + gif->fx;
     for (j = 0; j < gif->fh; j++) {
         for (k = 0; k < gif->fw; k++) {
             indexes[i + k] = gif->frame[(gif->fy + j) * gif->width + gif->fx + k];
         }
         i += gif->width;
     }

     auto surface = SDL_CreateRGBSurfaceFrom(indexes, gif->width, gif->height, 1 * 8, pitch, 0, 0, 0, 1);
     if (surface != NULL)
     {
         surface->flags &= ~SDL_PREALLOC;
         int paletteLen = gif->palette->size;
         if (paletteLen > 0)
         {
             auto palette = gif->palette->colors;
             SDL_Color newPalette[256];

             paletteLen = paletteLen > 256 ? 256 : paletteLen;

             for (auto i = 0U; i < paletteLen; ++i)
             {
                 newPalette[i].r = palette[i * 3 + 0];
                 newPalette[i].g = palette[i * 3 + 1];
                 newPalette[i].b = palette[i * 3 + 2];
                 
                 //If this index is transparent index, ignore it
                 if ((i == gif->gce.tindex)) {
                     newPalette[i].a = 0;
                 } else newPalette[i].a = 255;
             }

             SDL_SetPaletteColors(surface->format->palette, newPalette, 0, paletteLen);
         }

         auto frame = new Image(surface, false);
         addFrame(frame);
     }
 }
 gd_close_gif(gif);

}`

@marcakafoddex
Copy link
Author

I get a better result by ignoring the bgindex altogether. Instead i use gif->gce.tindex

Interesting, i can render all my gifs with the code i showed earlier. I wonder what makes your gifs different. Kudos for sharing your experiences and improvements though!

@lukegrahamSydney
Copy link

As far as i can tell, if "gif->gce.transparency" is true, then "gif->gce.tindex" should be used as the "transparent" colour, and bgindex should be ignored. if "gif->gce.transparency" is false, then use bgindex as the transparent color.

` while (gd_get_frame(gif))
{
if (firstFrame) {
m_frameDuration = int(gif->gce.delay) * 10;
firstFrame = false;
}
int pitch = 1 * gif->width;
unsigned char* indexes = (unsigned char*)SDL_malloc(pitch * gif->height);

 //If transparency flag is true, use "gce.tindex", otherwise use gif->bgindex
 int transparentIndex = gif->gce.transparency ? gif->gce.tindex : gif->bgindex;

 //Clear the image using the transparent index
 memset(indexes, transparentIndex, pitch * gif->height);

 int i, j, k;
 i = gif->fy * gif->width + gif->fx;
 for (j = 0; j < gif->fh; j++) {
     for (k = 0; k < gif->fw; k++) {
         indexes[i + k] = gif->frame[(gif->fy + j) * gif->width + gif->fx + k];
     }
     i += gif->width;
 }

 auto surface = SDL_CreateRGBSurfaceFrom(indexes, gif->width, gif->height, 1 * 8, pitch, 0, 0, 0, 1);
 if (surface != NULL)
 {
     surface->flags &= ~SDL_PREALLOC;
     int paletteLen = gif->palette->size;
     if (paletteLen > 0)
     {
         auto palette = gif->palette->colors;
         SDL_Color newPalette[256];

         paletteLen = paletteLen > 256 ? 256 : paletteLen;

         for (auto i = 0U; i < paletteLen; ++i)
         {
             newPalette[i].r = palette[i * 3 + 0];
             newPalette[i].g = palette[i * 3 + 1];
             newPalette[i].b = palette[i * 3 + 2];
             newPalette[i].a = 255;
            
         }
         
         if(transparentIndex < paletteLen)
             newPalette[transparentIndex].a = 0;
         SDL_SetPaletteColors(surface->format->palette, newPalette, 0, paletteLen);
     }

     auto frame = new Image(surface, false);
     addFrame(frame);
 }

}`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants