aboutsummaryrefslogtreecommitdiff
path: root/host/gif.c
blob: 6474269950b29868df1ee38f04def131f42ee7b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#include "gif.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
	uint8_t *data;
	unsigned int index;
	unsigned int length;
} readBuffer_t;	

int gif_buffer_input (GifFileType *gif, GifByteType *dest, int n){
	readBuffer_t *rb = gif->UserData;
	if(rb->index+n > rb->length)
		n = rb->length - rb->index;
	memcpy(dest, rb->data, n);
	return n;
}

struct _gifAnimationState {
	int idx;
	GifFileType *gif;
	framebuffer_t *frame;
};

void color_fill(color_t *dest, color_t src, size_t size){
	/* Some magic to reduce the amount of calls to small-block memcpys */
	dest[0] = src;
	size_t i = 1;
	size_t j = 2;
	while(j < size){
		memcpy(dest+i, dest, i*sizeof(color_t));
		i = j;
		j *= 2;
	}
	memcpy(dest+i, dest, (size-i)*sizeof(color_t));
}

gifAnimationState_t *gif_read(uint8_t *buf, unsigned int buflength){
	gifAnimationState_t *st = malloc(sizeof(gifAnimationState_t));
	if(!st){
		fprintf(stderr, "Failed to allocate %lu bytes\n", sizeof(*st));
		return 0;
	}

	readBuffer_t readBuf = {buf, 0, buflength};
	int err = 0;
	GifFileType *gif = DGifOpen(&readBuf, gif_buffer_input, &err);
	if(err){
		fprintf(stderr, "Could not read GIF data: %s\n", GifErrorString(err));
		goto error;
	}

	unsigned int framesize = gif->SWidth*gif->SHeight;
	if(framesize == 0){ /* Can this actually happen? */
		fprintf(stderr, "Invalid 0*0px gif\n");
		goto error;
	}
	color_t *fb = calloc(framesize, sizeof(color_t));
	if(!fb){
		fprintf(stderr, "Failed to allocate framebuffer for GIF (%d bytes)\n", gif->SWidth*gif->SHeight);
		goto error;
	}
	if(gif->SColorMap){ /* Initially fill framebuffer with background color */
		GifColorType *col = gif->SColorMap[gif->SBackGroundColor].Colors;
		color_t c = {col->Red, col->Green, col->Blue, 255};
		color_fill(fb, c, framesize);
	}else{
		/* Set all pixels to a transparent black */
		memset(fb, 0, framesize*sizeof(color_t));
	}

	st->gif = gif;
	st->idx = 0;
	st->frame->data = fb;
	st->frame->w = gif->SWidth;
	st->frame->h = gif->SHeight;
	return st;
error:
	free(fb);
	free(st);
	return 0;
}

void gifAnimationState_free(gifAnimationState_t *st){
	if(st)
		framebuffer_free(st->frame);
	free(st);
}

/* buf ⇜ input data
 * state ⇜ internal state, initialize as NULL
 * delay ⇜ is filled with this frame's delay value in milliseconds (if it is not NULL). -1 means "No delay given".
 * Small note: I mean, rendering a GIF in python is not quite trivial, either (about 20loc). But come on, this is just ridiculous. */
framebuffer_t *gif_render(uint8_t *buf, unsigned int buflength, gifAnimationState_t **state, int *delay){
	gifAnimationState_t *st;
	if(*state == NULL){
		/* On first invocation, parse gif and store it in the state struct */
		st = gif_read(buf, buflength);
		if(!st)
			goto error;
		*state = st;
	}else{
		st = *state;
	}

	GifFileType *gif = st->gif;

	/* Find this image's color map */
	SavedImage *img = gif->SavedImages + st->idx;
	ColorMapObject *cmo = img->ImageDesc.ColorMap;
	if(!cmo){
		cmo = gif->SColorMap;
		if(!cmo){
			fprintf(stderr, "Missing color table for GIF frame %d\n", st->idx);
			goto error;
		}
	}

	/* Extract and validate image's bounds*/
	unsigned int ix = img->ImageDesc.Left;
	unsigned int iy = img->ImageDesc.Top;
	unsigned int iw = img->ImageDesc.Width;
	unsigned int ih = img->ImageDesc.Height;
	if((iy+ih)*gif->SWidth + ix+iw > st->frame->w*st->frame->h){
		fprintf(stderr, "Invalid gif: Image %d (x %d y %d w %d h %d) out of bounds (w %d h %d)\n",
				st->idx, ix, iy, iw, ih, gif->SWidth, gif->SHeight);
		goto error;
	}

	/* Find and parse this image's GCB (if it exists) */
	int transparent = -1;
	int disposal = DISPOSAL_UNSPECIFIED;
	if(delay)
		*delay = -1; /* in milliseconds */
	for(unsigned int i=0; i<img->ExtensionBlockCount; i++){
		ExtensionBlock *eb = img->ExtensionBlocks + i;
		if(eb->Function == GRAPHICS_EXT_FUNC_CODE){
			GraphicsControlBlock *gcb = (GraphicsControlBlock *)eb->Bytes;
			transparent = gcb->TransparentColor;
			disposal = gcb->DisposalMode;
			if(delay)
				*delay = gcb->DelayTime * 10; /* 10ms → 1ms */
		}
	}

	color_t *fb = st->frame->data;
	if(disposal == DISPOSE_BACKGROUND || disposal == DISPOSAL_UNSPECIFIED){
		GifColorType *gc = gif->SColorMap[gif->SBackGroundColor].Colors;
		color_t c = {gc->Red, gc->Green, gc->Blue, 255};
		color_fill(fb, c, st->frame->w*st->frame->h);
	}
	/* FIXME DISPOSE_PREVIOUS is currently unhandled. */

	/* Render gif bitmap to RGB */
	uint8_t *p = img->RasterBits;
	for(unsigned int y = iy; y < iy+ih; y++){
		for(unsigned int x = ix; x < ix+iw; x++){
			int c = *p++;
			if(c != transparent){
				GifColorType *col = cmo[c].Colors;
				color_t ct = {col->Red, col->Green, col->Blue, 255};
				fb[y*st->frame->w + x] = ct;
			}
		}
	}

	st->idx++;
	if(st->idx >= gif->ImageCount)
		st->idx = 0;
	return st->frame;
error:
	gifAnimationState_free(st);
	return 0;
}