implement writing GIFs
Stefan Schuermans

Stefan Schuermans commited on 2016-12-18 19:51:24
Showing 4 changed files, with 224 additions and 1 deletions.

... ...
@@ -434,7 +434,7 @@ int main(int argCnt, char **args)
434 434
            "  -Md2\n"
435 435
            "     mirror movie diagonally (/)\n"
436 436
            "  -o <file>\n"
437
-           "     write movie to file (*.blm, *.bmm, *.bml, *.bbm" MNGEXT ")\n\n"
437
+           "     write movie to file (*.blm, *.bmm, *.bml, *.bbm" MNGEXT GIFEXT ")\n\n"
438 438
            "test_modes: black, white, gradient, dots, lines, trans\n\n"
439 439
            "old syntax: %s <input-file> [<output-file>]\n\n",
440 440
            args[0], args[0]);
... ...
@@ -4,6 +4,7 @@
4 4
    a blinkenarea.org project */
5 5
 
6 6
 #include <gif_lib.h>
7
+#include <stdlib.h>
7 8
 
8 9
 #include <BlinkenLib/config.h>
9 10
 #include <BlinkenLib/BlinkenConstants.h>
... ...
@@ -186,3 +187,210 @@ stBlinkenMovie *BlinkenGifLoad(const char *pFilename)
186 187
   return pMovie;
187 188
 }
188 189
 
190
+int BlinkenGifSave(stBlinkenMovie *pMovie, const char *pFilename)
191
+{
192
+  GifFileType *gif;
193
+  int frameCnt, height, width, channels, maxval, channels_new, maxval_new,
194
+      colors, r, g, b, i, frame, y, x, c, y1, x1, y2, x2, v, ret;
195
+  unsigned int delay;
196
+  stBlinkenMovie *pOutMovie;
197
+  stBlinkenFrame *pFrame, *pLast;
198
+  SavedImage *pImg;
199
+  GifImageDesc *pDesc;
200
+  ExtensionBlock *pEx;
201
+
202
+  if (pMovie == NULL || pFilename == NULL)
203
+    return -1; // error
204
+
205
+  frameCnt = BlinkenMovieGetFrameCnt(pMovie);
206
+  height = BlinkenMovieGetHeight(pMovie);
207
+  width = BlinkenMovieGetWidth(pMovie);
208
+  channels = BlinkenMovieGetChannels(pMovie);
209
+  maxval = BlinkenMovieGetMaxval(pMovie);
210
+
211
+  // convert movie to suitable color depth (i.e. at most 256 colors)
212
+  if (channels > 3)
213
+    channels_new = 3;
214
+  else
215
+    channels_new = channels;
216
+  switch (channels) {
217
+    case 1:  maxval_new = 255; colors = 256; break;
218
+    case 2:  maxval_new = 15;  colors = 256; break;
219
+    case 3:  maxval_new = 5;   colors = 216; break;
220
+    default: maxval_new = 255; colors = 255; break;
221
+  }
222
+  pOutMovie = BlinkenMovieClone(pMovie);
223
+  if (pOutMovie == NULL)
224
+    return -1; // error
225
+  if (channels_new != channels || maxval_new != maxval) {
226
+    BlinkenMovieResize(pOutMovie, height, width, channels_new, maxval_new);
227
+    channels = channels_new;
228
+    maxval = maxval_new;
229
+  }
230
+
231
+  // open GIF file for encoding
232
+  gif = EGifOpenFileName(pFilename, 0);
233
+  if (gif == NULL) {
234
+    BlinkenMovieFree(pOutMovie);
235
+    return -1; // error
236
+  }
237
+
238
+  // set global GIF config
239
+  gif->SWidth = width;
240
+  gif->SHeight = height;
241
+  gif->SColorResolution = colors;
242
+  gif->SBackGroundColor = 0;
243
+  gif->SColorMap = NULL;
244
+  gif->ImageCount = frameCnt;
245
+  gif->Image.Left = 0;
246
+  gif->Image.Top = 0;
247
+  gif->Image.Width = width;
248
+  gif->Image.Height = height;
249
+  gif->Image.ColorMap = NULL;
250
+  gif->SavedImages = NULL;
251
+  gif->UserData = NULL;
252
+
253
+  // create color map
254
+  gif->SColorMap = MakeMapObject(256, NULL);
255
+  if (gif->SColorMap == NULL) {
256
+    EGifCloseFile(gif);
257
+    BlinkenMovieFree(pOutMovie);
258
+    return -1; // error
259
+  }
260
+  for (b = 0, i = 0; b <= maxval && i < colors; ++b) {
261
+    for (g = 0; g <= maxval && i < colors; ++g) {
262
+      for (r = 0; r <= maxval && i < colors; ++r, ++i) {
263
+        gif->SColorMap->Colors[i].Red   = ((int)r * 255 + maxval / 2) / maxval;
264
+        gif->SColorMap->Colors[i].Green = ((int)g * 255 + maxval / 2) / maxval;
265
+        gif->SColorMap->Colors[i].Blue  = ((int)b * 255 + maxval / 2) / maxval;
266
+      }
267
+    }
268
+  }
269
+  for (; i < 256; ++i) {
270
+    gif->SColorMap->Colors[i].Red   = 0;
271
+    gif->SColorMap->Colors[i].Green = 0;
272
+    gif->SColorMap->Colors[i].Blue  = 0;
273
+  }
274
+
275
+  // create space for saved images
276
+  gif->SavedImages = calloc(frameCnt, sizeof(SavedImage));
277
+  if (gif->SavedImages == NULL) {
278
+    EGifCloseFile(gif);
279
+    BlinkenMovieFree(pOutMovie);
280
+    return -1; // error
281
+  }
282
+
283
+  // put frames into GIF structure
284
+  pLast = NULL;
285
+  for (frame = 0; frame < frameCnt; ++frame) {
286
+    pFrame = BlinkenMovieGetFrame(pOutMovie, frame);
287
+    pImg = &gif->SavedImages[frame];
288
+    pDesc = &pImg->ImageDesc;
289
+
290
+    // find smallest rectangle with changes
291
+    if (pLast != NULL) {
292
+      y1 = height - 1;
293
+      x1 = width - 1;
294
+      y2 = 0;
295
+      x2 = 0;
296
+      for (y = 0; y < height; ++y) {
297
+        for (x = 0; x < width; ++x) {
298
+          for (c = 0; c < channels; ++c) {
299
+           if (BlinkenFrameGetPixel(pFrame, y, x, c) !=
300
+               BlinkenFrameGetPixel(pLast, y, x, c))
301
+             break;
302
+          }
303
+          if (c < channels) { // change detected at x,y
304
+            if (y < y1) y1 = y;
305
+            if (x < x1) x1 = x;
306
+            if (y > y2) y2 = y;
307
+            if (x > x2) x2 = x;
308
+          }
309
+        } // for x
310
+      } // for y
311
+      if (y1 <= y2 && x1 <= x2) {
312
+        // changes detected -> encode changed rectangle
313
+        pDesc->Left = x1;
314
+        pDesc->Top = y1;
315
+        pDesc->Width = x2 - x1 + 1;
316
+        pDesc->Height = y2 - y1 + 1;
317
+      } else {
318
+        // no changes detected -> encode top left pixel (empty does not work)
319
+        pDesc->Left = 0;
320
+        pDesc->Top = 0;
321
+        pDesc->Width = 1;
322
+        pDesc->Height = 1;
323
+      }
324
+    } else { // if pLast
325
+      // no last frame -> encode full frame
326
+      pDesc->Left = 0;
327
+      pDesc->Top = 0;
328
+      pDesc->Width = width;
329
+      pDesc->Height = height;
330
+    } // if pLast ... else
331
+    pDesc->Interlace = 0;
332
+    pDesc->ColorMap = NULL;
333
+
334
+    // allocate pixel buffer
335
+    pImg->RasterBits = calloc(pDesc->Width * pDesc->Height,
336
+                              sizeof(unsigned char));
337
+    if (pImg->RasterBits == NULL) {
338
+      EGifCloseFile(gif);
339
+      BlinkenMovieFree(pOutMovie);
340
+      return -1; // error
341
+    }
342
+
343
+    // fill pixel buffer
344
+    for (y = 0, y1 = pDesc->Top, i = 0; y < pDesc->Height; ++y, ++y1) {
345
+      for (x = 0, x1 = pDesc->Left; x < pDesc->Width; ++x, ++x1, ++i) {
346
+        v = 0;
347
+        for (c = channels - 1; c >= 0; --c) {
348
+          v *= (maxval + 1);
349
+          v += BlinkenFrameGetPixel(pFrame, y1, x1, c);
350
+        }
351
+        pImg->RasterBits[i] = v;
352
+      } // for x
353
+    } // for y
354
+
355
+    // allocate and fill extension block with delay and disposal mode
356
+    pImg->Function = 0;
357
+    pImg->ExtensionBlockCount = 1;
358
+    pImg->ExtensionBlocks = calloc(1, sizeof(ExtensionBlock));
359
+    if (pImg->ExtensionBlocks == NULL) {
360
+      EGifCloseFile(gif);
361
+      BlinkenMovieFree(pOutMovie);
362
+      return -1; // error
363
+    }
364
+    pEx = pImg->ExtensionBlocks;
365
+    pEx->Function = GRAPHICS_EXT_FUNC_CODE;
366
+    pEx->ByteCount = 4;
367
+    pEx->Bytes = calloc(pEx->ByteCount, sizeof (unsigned char));
368
+    if (pEx->Bytes == NULL) {
369
+      EGifCloseFile(gif);
370
+      BlinkenMovieFree(pOutMovie);
371
+      return -1; // error
372
+    }
373
+    pEx->Bytes[0] = 1 << 2; // disposal mode: do not dispose
374
+    delay = (BlinkenFrameGetDuration(pFrame) + 5) / 10;
375
+    if (delay <= 0)
376
+      delay = 1;
377
+    pEx->Bytes[1] = delay & 0xFF;
378
+    pEx->Bytes[2] = delay >> 8 & 0xFF;
379
+    pEx->Bytes[3] = 0xFF;
380
+    // transparent color (Bytes[3]) not used, flag 0x01 in Bytes[0] not set
381
+
382
+    pLast = pFrame;
383
+  } // for frame
384
+
385
+  // encode GIF file
386
+  ret = EGifSpew(gif) == GIF_OK ? 0 : -1;
387
+
388
+  // close GIF file
389
+  EGifCloseFile(gif);
390
+
391
+  // free copy of movie
392
+  BlinkenMovieFree(pOutMovie);
393
+
394
+  return ret;
395
+}
396
+
... ...
@@ -20,6 +20,8 @@ extern "C" {
20 20
 
21 21
 stBlinkenMovie *BlinkenGifLoad(const char *pFilename);
22 22
 
23
+int BlinkenGifSave(stBlinkenMovie *pMovie, const char *pFilename);
24
+
23 25
 #ifdef __cplusplus
24 26
 } // extern "C"
25 27
 #endif
... ...
@@ -1851,6 +1851,15 @@ int BlinkenMovieSaveMng(stBlinkenMovie *pMovie, const char *pFilename)
1851 1851
 
1852 1852
 #endif // #ifdef BLINKENLIB_CFG_MNG
1853 1853
 
1854
+#ifdef BLINKENLIB_CFG_GIF
1855
+
1856
+int BlinkenMovieSaveGif(stBlinkenMovie *pMovie, const char *pFilename)
1857
+{
1858
+  return BlinkenGifSave(pMovie, pFilename);
1859
+}
1860
+
1861
+#endif // #ifdef BLINKENLIB_CFG_GIF
1862
+
1854 1863
 int BlinkenMovieSave(stBlinkenMovie *pMovie, const char *pFilename)
1855 1864
 {
1856 1865
   int len;
... ...
@@ -1871,6 +1880,10 @@ int BlinkenMovieSave(stBlinkenMovie *pMovie, const char *pFilename)
1871 1880
   if (len > 4 && strcmp(pFilename + len - 4, ".mng") == 0)
1872 1881
     return BlinkenMovieSaveMng(pMovie, pFilename);
1873 1882
 #endif // #ifdef BLINKENLIB_CFG_MNG
1883
+#ifdef BLINKENLIB_CFG_GIF
1884
+  if (len > 4 && strcmp(pFilename + len - 4, ".gif") == 0)
1885
+    return BlinkenMovieSaveGif(pMovie, pFilename);
1886
+#endif // #ifdef BLINKENLIB_CFG_GIF
1874 1887
   return -1;
1875 1888
 }
1876 1889
 
1877 1890