Skip to content

Commit

Permalink
improved GIF writer, beta31
Browse files Browse the repository at this point in the history
  • Loading branch information
asiekierka committed Feb 27, 2021
1 parent 9ed5ca5 commit 1255487
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 27 deletions.
112 changes: 90 additions & 22 deletions src/gif_writer.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ typedef struct s_gif_writer_state {

char *vbuf;
bool optimize;
bool optimize_lcts; // Enables using LCTs as optimization. Requires pad_palette.
bool pad_palette; // Enables 5-bit output and transparency color.

int screen_width;
int screen_height;
Expand Down Expand Up @@ -166,15 +168,18 @@ static void lzw_finish(lzw_encode_state *lzw, FILE *file) {
#define GIF_IMAGE_INTERLACED 0x40
#define GIF_IMAGE_LCM_DEPTH(bits) ((bits) - 1)

static void gif_write_palette(gif_writer_state *s, u32 *palette) {
for (int i = 0; i < 16; i++) {
static void gif_write_palette(gif_writer_state *s, u32 *palette, int colors) {
for (int i = 0; i < (colors < 16 ? colors : 16); i++) {
fputc(palette[i] >> 16, s->file);
fputc(palette[i] >> 8, s->file);
fputc(palette[i], s->file);
}
if (colors >= 32) {
fwrite("\x00\x00\x00Support broken thumbnailers - 48 byte filler.", 48, 1, s->file);
}
}

gif_writer_state *gif_writer_start(const char *filename, bool optimize) {
gif_writer_state *gif_writer_start(const char *filename, bool optimize, bool pad_palette) {
FILE *file = fopen(filename, "wb");
if (file == NULL) return NULL;

Expand All @@ -187,18 +192,28 @@ gif_writer_state *gif_writer_start(const char *filename, bool optimize) {
s->file = file;
setvbuf(s->file, s->vbuf, _IOFBF, 65536);

// prepare internal flags
s->force_full_redraw = true;
s->optimize = optimize;
s->pad_palette = pad_palette;
s->optimize_lcts = true;

// write header
fwrite("GIF89a", 6, 1, s->file);
fput16le(s->file, s->screen_width * s->char_width * (s->screen_width <= 40 ? 2 : 1));
fput16le(s->file, s->screen_height * s->char_height);
fputc(GIF_FLAG_GLOBAL_COLOR_MAP | GIF_FLAG_COLOR_RESOLUTION(8) | GIF_FLAG_GCM_DEPTH(4), s->file);
/* if (s->optimize_lcts) {
fputc(GIF_FLAG_COLOR_RESOLUTION(8), s->file);
} */ // Uncomment if going back to always emitting LCTs
fputc(GIF_FLAG_GLOBAL_COLOR_MAP | GIF_FLAG_COLOR_RESOLUTION(8) | GIF_FLAG_GCM_DEPTH(s->pad_palette ? 5 : 4), s->file);
fputc(0, s->file); // background color
fputc(0, s->file); // aspect ratio

// write global color table
// if (!s->optimize_lcts) { // Uncomment if going back to always emitting LCTs
u32 *palette = zzt_get_palette();
memcpy(s->global_palette, palette, 16 * sizeof(u32));
gif_write_palette(s, palette);
gif_write_palette(s, palette, s->pad_palette ? 32 : 16);

// write netscape looping extension
fputc('!', s->file); // extension
Expand All @@ -210,10 +225,6 @@ gif_writer_state *gif_writer_start(const char *filename, bool optimize) {
fput16le(s->file, 0xFFFF); // unlimited repetitions
fputc(0x00, s->file); // block terminator

// prepare internal flags
s->force_full_redraw = true;
s->optimize = optimize;

return s;
}

Expand Down Expand Up @@ -249,8 +260,15 @@ static u8* gif_alloc_draw_buffer(gif_writer_state *s, int pixel_count) {
return s->draw_buffer;
}

static u8 vram_difference[2000 >> 3];

static bool can_draw_char_vram_difference(int x, int y) {
return (vram_difference[y * 10 + (x >> 3)] & (1 << (x & 7))) != 0;
}

void gif_writer_frame(gif_writer_state *s) {
s->gif_delay_buffer += 11;
u32 palette_optimized[16];

int new_screen_w, new_screen_h, new_char_w, new_char_h;
zzt_get_screen_size(&new_screen_w, &new_screen_h);
Expand All @@ -277,9 +295,20 @@ void gif_writer_frame(gif_writer_state *s) {
int cx2 = 0;
int cy2 = 0;

bool optimize_lcts = s->optimize_lcts && requires_lct && !s->force_full_redraw;
bool use_transparency = s->optimize && s->pad_palette && !s->force_full_redraw;
int bit_depth = s->pad_palette ? 5 : 4;

// calculate cx/cy/cw/ch boundaries and new prev_video state
u8 *old_vram = s->prev_video;
u8 *new_vram = video;
u8 *vram_diff_ptr = vram_difference;
if (use_transparency) {
memset(vram_difference, 0, sizeof(vram_difference));
}
u16 used_palette_colors = 0;
u8 used_palette_map[17];

for (int cy = 0; cy < s->screen_height; cy++) {
for (int cx = 0; cx < s->screen_width; cx++, old_vram += 2, new_vram += 2) {
u8 nv_chr = new_vram[0];
Expand All @@ -298,16 +327,23 @@ void gif_writer_frame(gif_writer_state *s) {
if (cx2 < cx) cx2 = cx;
if (cy1 > cy) cy1 = cy;
if (cy2 < cy) cy2 = cy;
if (optimize_lcts) {
used_palette_colors |= (1 << (nv_col & 0x0F));
used_palette_colors |= (1 << (nv_col >> 4));
}
if (use_transparency) {
vram_diff_ptr[cx >> 3] |= (1 << (cx & 7));
}
}
}
vram_diff_ptr += 80 >> 3;
}

if (s->force_full_redraw) {
cx1 = 0;
cy1 = 0;
cx2 = s->screen_width - 1;
cy2 = s->screen_height - 1;
s->force_full_redraw = false;
}

int px = cx1 * s->char_width * (s->screen_width <= 40 ? 2 : 1);
Expand All @@ -327,42 +363,74 @@ void gif_writer_frame(gif_writer_state *s) {
}
}

if (optimize_lcts) {
int colors_used = 0;
int i = 0;
while (used_palette_colors > 0) {
if (used_palette_colors & 1) {
used_palette_map[i] = colors_used;
palette_optimized[colors_used++] = palette[i];
}
used_palette_colors >>= 1;
i++;
}
if (colors_used < 16) {
memset(palette_optimized + colors_used, 0, (16 - colors_used) * 4);
}
used_palette_map[0x10] = colors_used++;
bit_depth = highest_bit_index(colors_used) + 1;
if (bit_depth < 3) bit_depth = 3;
}

u8 *pixels = gif_alloc_draw_buffer(s, pw * ph);
render_software_paletted_range(pixels, s->screen_width, -1, 0, s->prev_video, charset, s->char_width, s->char_height, cx1, cy1, cx2, cy2);
if (use_transparency) {
memset(pixels, 0x10, pw * ph);
}
render_software_paletted_range(pixels, s->screen_width, -1, RENDER_BLINK_OFF, s->prev_video, charset, s->char_width, s->char_height,
cx1, cy1, cx2, cy2, use_transparency ? can_draw_char_vram_difference : NULL);

gif_writer_write_delay(s);

// write GCE
fputc('!', s->file); // extension
fputc(0xF9, s->file); // graphic control extension
fputc(4, s->file); // size
fputc(GIF_GCE_DISPOSE_IGNORE, s->file); // flags
fputc(GIF_GCE_DISPOSE_IGNORE | (use_transparency ? GIF_GCE_TRANSPARENCY_INDEX : 0), s->file); // flags
s->file_delay_loc = ftell(s->file);
fput16le(s->file, 11); // delay time (temporary)
fputc(0x00, s->file); // transparent color index
fputc(0x00, s->file); // block terminator
fputc(optimize_lcts ? used_palette_map[0x10] : ((bit_depth >= 5) ? 0x10 : 0), s->file); // transparent color index
fputc(0x00, s->file); // block terminatorcd

// write image descriptor
fputc(',', s->file); // extension
fput16le(s->file, px); // X pos
fput16le(s->file, py); // X pos
fput16le(s->file, py); // Y pos
fput16le(s->file, pw); // width
fput16le(s->file, ph); // height
fputc(requires_lct ? GIF_IMAGE_LOCAL_COLOR_MAP | GIF_IMAGE_LCM_DEPTH(4) : 0x00, s->file); // flags
fputc(requires_lct ? GIF_IMAGE_LOCAL_COLOR_MAP | GIF_IMAGE_LCM_DEPTH(bit_depth) : 0x00, s->file); // flags

if (requires_lct) {
gif_write_palette(s, palette);
gif_write_palette(s, optimize_lcts ? palette_optimized : palette, 1 << bit_depth);
}

lzw_init(&s->lzw, 16, s->file);
for (int iy = 0; iy < ph; iy++) {
for (int ix = 0; ix < pw; ix++, pixels++) {
lzw_emit(&s->lzw, *pixels & 0x0F, s->file);
lzw_init(&s->lzw, 1 << bit_depth, s->file);
if (optimize_lcts) {
for (int iy = 0; iy < ph; iy++) {
for (int ix = 0; ix < pw; ix++, pixels++) {
lzw_emit(&s->lzw, used_palette_map[*pixels], s->file);
}
}
} else {
for (int iy = 0; iy < ph; iy++) {
for (int ix = 0; ix < pw; ix++, pixels++) {
lzw_emit(&s->lzw, *pixels, s->file);
}
}
}
lzw_finish(&s->lzw, s->file);

fputc(0x00, s->file); // block terminator
s->force_full_redraw = false;
}

void gif_writer_on_charset_change(gif_writer_state *s) {
Expand All @@ -371,4 +439,4 @@ void gif_writer_on_charset_change(gif_writer_state *s) {

void gif_writer_on_palette_change(gif_writer_state *s) {
s->force_full_redraw = true;
}
}
2 changes: 1 addition & 1 deletion src/gif_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

typedef struct s_gif_writer_state gif_writer_state;

gif_writer_state *gif_writer_start(const char *filename, bool optimize);
gif_writer_state *gif_writer_start(const char *filename, bool optimize, bool pad_palette);
void gif_writer_stop(gif_writer_state *s);
void gif_writer_frame(gif_writer_state *s);
void gif_writer_on_charset_change(gif_writer_state *s);
Expand Down
11 changes: 9 additions & 2 deletions src/render_software.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ void render_software_rgb(u32 *buffer, int scr_width, int row_length, int flags,
}
}

void render_software_paletted_range(u8 *buffer, int scr_width, int row_length, int flags, u8 *video, u8 *charset, int char_width, int char_height, int x1, int y1, int x2, int y2) {
void render_software_paletted_range(u8 *buffer, int scr_width, int row_length, int flags, u8 *video, u8 *charset, int char_width, int char_height,
int x1, int y1, int x2, int y2, render_software_char_draw_check_func char_draw_check_func)
{
int pos_mul = POS_MUL;
int x_pitch = (scr_width - (x2 - x1 + 1)) << 1;

Expand All @@ -75,6 +77,10 @@ void render_software_paletted_range(u8 *buffer, int scr_width, int row_length, i

for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++, pos += 2) {
if (char_draw_check_func != NULL && !char_draw_check_func(x, y)) {
continue;
}

u8 chr = video[pos];
u8 col = video[pos + 1];

Expand Down Expand Up @@ -105,5 +111,6 @@ void render_software_paletted_range(u8 *buffer, int scr_width, int row_length, i
}

void render_software_paletted(u8 *buffer, int scr_width, int row_length, int flags, u8 *video, u8 *charset, int char_width, int char_height) {
render_software_paletted_range(buffer, scr_width, row_length, flags, video, charset, char_width, char_height, 0, 0, scr_width - 1, 24);
render_software_paletted_range(buffer, scr_width, row_length, flags, video, charset, char_width, char_height,
0, 0, scr_width - 1, 24, NULL);
}
5 changes: 4 additions & 1 deletion src/render_software.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@
#define RENDER_BLINK_OFF 1
#define RENDER_BLINK_PHASE 2

typedef bool (*render_software_char_draw_check_func)(int, int);

USER_FUNCTION
void render_software_rgb(u32 *buffer, int scr_width, int row_length, int flags, u8 *video, u8 *charset, int char_width, int char_height, u32 *palette);
USER_FUNCTION
void render_software_paletted(u8 *buffer, int scr_width, int row_length, int flags, u8 *video, u8 *charset, int char_width, int char_height);
USER_FUNCTION
void render_software_paletted_range(u8 *buffer, int scr_width, int row_length, int flags, u8 *video, u8 *charset, int char_width, int char_height, int x1, int y1, int x2, int y2);
void render_software_paletted_range(u8 *buffer, int scr_width, int row_length, int flags, u8 *video, u8 *charset, int char_width, int char_height,
int x1, int y1, int x2, int y2, render_software_char_draw_check_func char_draw_check_func);

#endif /* __RENDER_SOFTWARE_H__ */
2 changes: 1 addition & 1 deletion src/sdl/frontend.c
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ int main(int argc, char **argv) {
if (file != NULL) {
fclose(file);
bool optimized = !KEYMOD_SHIFT(event.key.keysym.mod);
if ((gif_writer_s = gif_writer_start(filename, optimized)) != NULL) {
if ((gif_writer_s = gif_writer_start(filename, optimized, true)) != NULL) {
fprintf(stderr, "GIF writing started [%s, %s].\n", filename, optimized ? "optimized" : "unoptimized");
} else {
fprintf(stderr, "Could not start GIF writing - internal error!\n");
Expand Down

0 comments on commit 1255487

Please sign in to comment.