1 | // MIT License |
2 | |
3 | // Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com |
4 | |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | // of this software and associated documentation files (the "Software"), to deal |
7 | // in the Software without restriction, including without limitation the rights |
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | // copies of the Software, and to permit persons to whom the Software is |
10 | // furnished to do so, subject to the following conditions: |
11 | |
12 | // The above copyright notice and this permission notice shall be included in all |
13 | // copies or substantial portions of the Software. |
14 | |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
21 | // SOFTWARE. |
22 | |
23 | #include <stdio.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | |
27 | #include "gif.h" |
28 | #include "gif_lib.h" |
29 | |
30 | static gif_image* readGif(GifFileType *gif) |
31 | { |
32 | gif_image* image = NULL; |
33 | |
34 | s32 error = 0; |
35 | |
36 | if(gif) |
37 | { |
38 | if(gif->SHeight > 0 && gif->SWidth > 0) |
39 | { |
40 | s32 size = gif->SWidth * gif->SHeight * sizeof(GifPixelType); |
41 | GifPixelType* screen = (GifPixelType*)malloc(size); |
42 | |
43 | if(screen) |
44 | { |
45 | memset(screen, gif->SBackGroundColor, size); |
46 | |
47 | GifRecordType record = UNDEFINED_RECORD_TYPE; |
48 | |
49 | do |
50 | { |
51 | if(DGifGetRecordType(gif, &record) == GIF_ERROR) |
52 | { |
53 | error = gif->Error; |
54 | break; |
55 | } |
56 | |
57 | switch (record) |
58 | { |
59 | case IMAGE_DESC_RECORD_TYPE: |
60 | { |
61 | if(DGifGetImageDesc(gif) == GIF_ERROR) |
62 | error = gif->Error; |
63 | |
64 | s32 row = gif->Image.Top; |
65 | s32 col = gif->Image.Left; |
66 | s32 width = gif->Image.Width; |
67 | s32 height = gif->Image.Height; |
68 | |
69 | if (gif->Image.Left + gif->Image.Width > gif->SWidth || |
70 | gif->Image.Top + gif->Image.Height > gif->SHeight) |
71 | error = E_GIF_ERR_OPEN_FAILED; |
72 | |
73 | if (gif->Image.Interlace) |
74 | { |
75 | s32 InterlacedOffset[] = { 0, 4, 2, 1 }; |
76 | s32 InterlacedJumps[] = { 8, 8, 4, 2 }; |
77 | |
78 | for (s32 i = 0; i < 4; i++) |
79 | for (s32 j = row + InterlacedOffset[i]; j < row + height; j += InterlacedJumps[i]) |
80 | { |
81 | if(DGifGetLine(gif, screen + j * gif->SWidth + col, width) == GIF_ERROR) |
82 | { |
83 | error = gif->Error; |
84 | break; |
85 | } |
86 | } |
87 | } |
88 | else |
89 | { |
90 | for (s32 i = 0; i < height; i++, row++) |
91 | { |
92 | if(DGifGetLine(gif, screen + row * gif->SWidth + col, width) == GIF_ERROR) |
93 | { |
94 | error = gif->Error; |
95 | break; |
96 | } |
97 | } |
98 | } |
99 | } |
100 | break; |
101 | |
102 | case EXTENSION_RECORD_TYPE: |
103 | { |
104 | s32 extCode = 0; |
105 | GifByteType* extension = NULL; |
106 | |
107 | if (DGifGetExtension(gif, &extCode, &extension) == GIF_ERROR) |
108 | error = gif->Error; |
109 | else |
110 | { |
111 | while (extension != NULL) |
112 | { |
113 | if(DGifGetExtensionNext(gif, &extension) == GIF_ERROR) |
114 | { |
115 | error = gif->Error; |
116 | break; |
117 | } |
118 | } |
119 | } |
120 | } |
121 | break; |
122 | case TERMINATE_RECORD_TYPE: |
123 | break; |
124 | default: break; |
125 | } |
126 | |
127 | if(error != E_GIF_SUCCEEDED) |
128 | break; |
129 | } |
130 | while(record != TERMINATE_RECORD_TYPE); |
131 | |
132 | if(error == E_GIF_SUCCEEDED) |
133 | { |
134 | |
135 | image = (gif_image*)malloc(sizeof(gif_image)); |
136 | |
137 | if(image) |
138 | { |
139 | memset(image, 0, sizeof(gif_image)); |
140 | image->buffer = screen; |
141 | image->width = gif->SWidth; |
142 | image->height = gif->SHeight; |
143 | |
144 | ColorMapObject* colorMap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; |
145 | |
146 | image->colors = colorMap->ColorCount; |
147 | |
148 | s32 size = image->colors * sizeof(gif_color); |
149 | image->palette = malloc(size); |
150 | |
151 | memcpy(image->palette, colorMap->Colors, size); |
152 | } |
153 | } |
154 | else free(screen); |
155 | } |
156 | } |
157 | |
158 | DGifCloseFile(gif, &error); |
159 | } |
160 | |
161 | return image; |
162 | } |
163 | |
164 | typedef struct |
165 | { |
166 | const void* data; |
167 | s32 pos; |
168 | } GifBuffer; |
169 | |
170 | static s32 readBuffer(GifFileType* gif, GifByteType* data, s32 size) |
171 | { |
172 | GifBuffer* buffer = (GifBuffer*)gif->UserData; |
173 | |
174 | memcpy(data, (const u8*)buffer->data + buffer->pos, size); |
175 | buffer->pos += size; |
176 | |
177 | return size; |
178 | } |
179 | |
180 | gif_image* gif_read_data(const void* data, s32 size) |
181 | { |
182 | GifBuffer buffer = {data, 0}; |
183 | GifFileType *gif = DGifOpen(&buffer, readBuffer, NULL); |
184 | |
185 | return readGif(gif); |
186 | } |
187 | |
188 | static bool writeGif(GifFileType* gif, s32 width, s32 height, const u8* data, const gif_color* palette, u8 bpp) |
189 | { |
190 | bool result = false; |
191 | s32 error = 0; |
192 | |
193 | if(gif) |
194 | { |
195 | s32 colors = 1 << bpp; |
196 | ColorMapObject* colorMap = GifMakeMapObject(colors, NULL); |
197 | |
198 | memcpy(colorMap->Colors, palette, colors * sizeof(GifColorType)); |
199 | |
200 | if(EGifPutScreenDesc(gif, width, height, bpp, 0, colorMap) != GIF_ERROR) |
201 | { |
202 | if(EGifPutImageDesc(gif, 0, 0, width, height, false, NULL) != GIF_ERROR) |
203 | { |
204 | GifByteType* ptr = (GifByteType*)data; |
205 | for (s32 i = 0; i < height; i++, ptr += width) |
206 | { |
207 | if (EGifPutLine(gif, ptr, width) == GIF_ERROR) |
208 | { |
209 | error = gif->Error; |
210 | break; |
211 | } |
212 | } |
213 | |
214 | result = error == E_GIF_SUCCEEDED; |
215 | } |
216 | } |
217 | |
218 | EGifCloseFile(gif, &error); |
219 | GifFreeMapObject(colorMap); |
220 | } |
221 | |
222 | return result; |
223 | } |
224 | |
225 | static s32 writeBuffer(GifFileType* gif, const GifByteType* data, s32 size) |
226 | { |
227 | GifBuffer* buffer = (GifBuffer*)gif->UserData; |
228 | |
229 | memcpy((u8*)buffer->data + buffer->pos, data, size); |
230 | buffer->pos += size; |
231 | |
232 | return size; |
233 | } |
234 | |
235 | bool gif_write_data(const void* buffer, s32* size, s32 width, s32 height, const u8* data, const gif_color* palette, u8 bpp) |
236 | { |
237 | s32 error = 0; |
238 | GifBuffer output = {buffer, 0}; |
239 | GifFileType* gif = EGifOpen(&output, writeBuffer, &error); |
240 | |
241 | bool result = writeGif(gif, width, height, data, palette, bpp); |
242 | |
243 | *size = output.pos; |
244 | |
245 | return result; |
246 | } |
247 | |
248 | void gif_close(gif_image* image) |
249 | { |
250 | if(image) |
251 | { |
252 | if(image->buffer) free(image->buffer); |
253 | if(image->palette) free(image->palette); |
254 | |
255 | free(image); |
256 | } |
257 | } |
258 | |
259 | static bool AddLoop(GifFileType *gif) |
260 | { |
261 | { |
262 | const char *nsle = "NETSCAPE2.0" ; |
263 | const char subblock[] = { |
264 | 1, // always 1 |
265 | 0, // little-endian loop counter: |
266 | 0 // 0 for infinite loop. |
267 | }; |
268 | |
269 | EGifPutExtensionLeader(gif, APPLICATION_EXT_FUNC_CODE); |
270 | EGifPutExtensionBlock(gif, 11, nsle); |
271 | EGifPutExtensionBlock(gif, 3, subblock); |
272 | EGifPutExtensionTrailer(gif); |
273 | } |
274 | |
275 | return true; |
276 | } |
277 | |
278 | static const u8* toColor(const u8* ptr, gif_color* color) |
279 | { |
280 | color->r = *ptr++; |
281 | color->g = *ptr++; |
282 | color->b = *ptr++; |
283 | ptr++; |
284 | |
285 | return ptr; |
286 | } |
287 | |
288 | bool gif_write_animation(const void* buffer, s32* size, s32 width, s32 height, const u8* data, s32 frames, s32 fps, s32 scale) |
289 | { |
290 | bool result = false; |
291 | |
292 | s32 swidth = width*scale, sheight = height*scale; |
293 | s32 frameSize = width * height; |
294 | |
295 | enum{Bpp = 8, PalSize = 1 << Bpp, PalStructSize = PalSize * sizeof(gif_color)}; |
296 | |
297 | s32 error = 0; |
298 | GifBuffer output = {buffer, 0}; |
299 | GifFileType* gif = EGifOpen(&output, writeBuffer, &error); |
300 | |
301 | if(gif) |
302 | { |
303 | EGifSetGifVersion(gif, true); |
304 | |
305 | if(EGifPutScreenDesc(gif, swidth, sheight, Bpp, 0, NULL) != GIF_ERROR) |
306 | { |
307 | if(AddLoop(gif)) |
308 | { |
309 | gif_color* palette = (gif_color*)malloc(PalStructSize); |
310 | u8* screen = malloc(frameSize); |
311 | u8* line = malloc(swidth); |
312 | |
313 | for(s32 f = 0; f < frames; f++) |
314 | { |
315 | enum {DelayUnits = 100, MinDelay = 2}; |
316 | |
317 | s32 frame = (f * fps * MinDelay * 2 + 1) / (2 * DelayUnits); |
318 | |
319 | if(frame >= frames) |
320 | break; |
321 | |
322 | s32 colors = 0; |
323 | const u8* ptr = data + frameSize*frame*sizeof(u32); |
324 | |
325 | { |
326 | memset(palette, 0, PalStructSize); |
327 | memset(screen, 0, frameSize); |
328 | |
329 | for(s32 i = 0; i < frameSize; i++) |
330 | { |
331 | if(colors >= PalSize) break; |
332 | |
333 | gif_color color; |
334 | toColor(ptr + i*sizeof(u32), &color); |
335 | |
336 | bool found = false; |
337 | for(s32 c = 0; c < colors; c++) |
338 | { |
339 | if(memcmp(&palette[c], &color, sizeof(gif_color)) == 0) |
340 | { |
341 | found = true; |
342 | screen[i] = c; |
343 | break; |
344 | } |
345 | } |
346 | |
347 | if(!found) |
348 | { |
349 | // TODO: check for last color in palette and try to find closest color |
350 | screen[i] = colors; |
351 | memcpy(&palette[colors], &color, sizeof(gif_color)); |
352 | colors++; |
353 | } |
354 | } |
355 | } |
356 | |
357 | { |
358 | GraphicsControlBlock gcb = |
359 | { |
360 | .DisposalMode = DISPOSE_DO_NOT, |
361 | .UserInputFlag = false, |
362 | .DelayTime = MinDelay, |
363 | .TransparentColor = -1, |
364 | }; |
365 | |
366 | u8 ext[4]; |
367 | EGifGCBToExtension(&gcb, ext); |
368 | EGifPutExtension(gif, GRAPHICS_EXT_FUNC_CODE, sizeof ext, ext); |
369 | } |
370 | |
371 | ColorMapObject* colorMap = GifMakeMapObject(PalSize, NULL); |
372 | memset(colorMap->Colors, 0, PalStructSize); |
373 | memcpy(colorMap->Colors, palette, colors * sizeof(GifColorType)); |
374 | |
375 | if(EGifPutImageDesc(gif, 0, 0, swidth, sheight, false, colorMap) != GIF_ERROR) |
376 | { |
377 | for(s32 y = 0; y < height; y++) |
378 | { |
379 | for(s32 x = 0, pos = y*width; x < width; x++, pos++) |
380 | { |
381 | u8 color = screen[pos]; |
382 | for(s32 s = 0, pos = x*scale; s < scale; s++, pos++) |
383 | line[pos] = color; |
384 | } |
385 | |
386 | for(s32 s = 0; s < scale; s++) |
387 | { |
388 | if (EGifPutLine(gif, line, swidth) == GIF_ERROR) |
389 | { |
390 | error = gif->Error; |
391 | break; |
392 | } |
393 | } |
394 | |
395 | if(error != E_GIF_SUCCEEDED) break; |
396 | } |
397 | |
398 | *size = output.pos; |
399 | |
400 | result = error == E_GIF_SUCCEEDED; |
401 | } |
402 | |
403 | GifFreeMapObject(colorMap); |
404 | |
405 | if(!result) |
406 | break; |
407 | } |
408 | |
409 | free(line); |
410 | free(screen); |
411 | free(palette); |
412 | } |
413 | } |
414 | |
415 | EGifCloseFile(gif, &error); |
416 | |
417 | *size = output.pos; |
418 | } |
419 | |
420 | return result; |
421 | } |