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
30static 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
164typedef struct
165{
166 const void* data;
167 s32 pos;
168} GifBuffer;
169
170static 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
180gif_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
188static 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
225static 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
235bool 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
248void 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
259static 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
278static 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
288bool 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}