Coverage Report

Created: 2026-10-28 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cgif/src/cgif.c
Line
Count
Source
1
#include <stdint.h>
2
#include <stdio.h>
3
#include <string.h>
4
#include <stdlib.h>
5
6
#include "cgif.h"
7
#include "cgif_raw.h"
8
9
24.9k
#define MULU16(a, b) (((uint32_t)a) * ((uint32_t)b)) // helper macro to correctly multiply two U16's without default signed int promotion
10
18.6k
#define SIZE_FRAME_QUEUE (3)
11
12
// CGIF_Frame type
13
// note: internal sections, subject to change in future versions
14
typedef struct {
15
  CGIF_FrameConfig config;
16
  uint8_t          disposalMethod;
17
  uint8_t          transIndex;
18
} CGIF_Frame;
19
20
// CGIF type
21
// note: internal sections, subject to change in future versions
22
struct st_gif {
23
  CGIF_Frame*        aFrames[SIZE_FRAME_QUEUE]; // (internal) we need to keep the last three frames in memory.
24
  CGIF_Config        config;                    // (internal) configuration parameters of the GIF
25
  CGIFRaw*           pGIFRaw;                   // (internal) raw GIF stream
26
  FILE*              pFile;
27
  cgif_result        curResult;
28
  int                iHEAD;                     // (internal) index to current HEAD frame in aFrames queue
29
};
30
31
// dimension result type
32
typedef struct {
33
  uint16_t width;
34
  uint16_t height;
35
  uint16_t top;
36
  uint16_t left;
37
} DimResult;
38
39
/* calculate next power of two exponent of given number (n MUST be <= 256) */
40
0
static uint8_t calcNextPower2Ex(uint16_t n) {
41
0
  uint8_t nextPow2;
42
43
0
  for (nextPow2 = 0; n > (1uL << nextPow2); ++nextPow2);
44
0
  return nextPow2;
45
0
}
46
47
/* write callback. returns 0 on success or -1 on error.  */
48
58.5k
static int writecb(void* pContext, const uint8_t* pData, const size_t numBytes) {
49
58.5k
  CGIF* pGIF;
50
58.5k
  size_t r;
51
52
58.5k
  pGIF = (CGIF*)pContext;
53
58.5k
  if(pGIF->pFile) {
54
0
    r = fwrite(pData, 1, numBytes, pGIF->pFile);
55
0
    if(r == numBytes) return 0;
56
0
    else return -1;
57
58.5k
  } else if(pGIF->config.pWriteFn) {
58
58.5k
    return pGIF->config.pWriteFn(pGIF->config.pContext, pData, numBytes);
59
58.5k
  }
60
0
  return 0;
61
58.5k
}
62
63
/* free space allocated for CGIF struct */
64
1.15k
static void freeCGIF(CGIF* pGIF) {
65
1.15k
  if((pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
66
0
    free(pGIF->config.pGlobalPalette);
67
0
  }
68
1.15k
  free(pGIF);
69
1.15k
}
70
71
/* create a new GIF */
72
1.16k
CGIF* cgif_newgif(CGIF_Config* pConfig) {
73
1.16k
  FILE*          pFile;
74
1.16k
  CGIF*          pGIF;
75
1.16k
  CGIFRaw*       pGIFRaw; // raw GIF stream
76
1.16k
  CGIFRaw_Config rawConfig = {0};
77
  // width or heigth cannot be zero
78
1.16k
  if(!pConfig->width || !pConfig->height) {
79
14
    return NULL;
80
14
  }
81
1.15k
  pFile = NULL;
82
  // open output file (if necessary)
83
1.15k
  if(pConfig->path) {
84
0
    pFile = fopen(pConfig->path, "wb");
85
0
    if(pFile == NULL) {
86
0
      return NULL; // error: fopen failed
87
0
    }
88
0
  }
89
  // allocate space for CGIF context
90
1.15k
  pGIF = malloc(sizeof(CGIF));
91
1.15k
  if(pGIF == NULL) {
92
0
    if(pFile) {
93
0
      fclose(pFile);
94
0
    }
95
0
    return NULL; // error -> malloc failed
96
0
  }
97
98
1.15k
  memset(pGIF, 0, sizeof(CGIF));
99
1.15k
  pGIF->pFile = pFile;
100
1.15k
  pGIF->iHEAD = 1;
101
1.15k
  memcpy(&(pGIF->config), pConfig, sizeof(CGIF_Config));
102
  // make a deep copy of global color tabele (GCT), if required.
103
1.15k
  if((pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
104
0
    pGIF->config.pGlobalPalette = malloc(pConfig->numGlobalPaletteEntries * 3);
105
0
    if(pGIF->config.pGlobalPalette == NULL) {
106
0
      if(pFile) {
107
0
        fclose(pFile);
108
0
      }
109
0
      free(pGIF);
110
0
      return NULL;
111
0
    }
112
0
    memcpy(pGIF->config.pGlobalPalette, pConfig->pGlobalPalette, pConfig->numGlobalPaletteEntries * 3);
113
0
  }
114
115
1.15k
  rawConfig.pGCT      = pConfig->pGlobalPalette;
116
1.15k
  rawConfig.sizeGCT   = (pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) ? 0 : pConfig->numGlobalPaletteEntries;
117
  // translate CGIF_ATTR_* to CGIF_RAW_ATTR_* flags
118
1.15k
  rawConfig.attrFlags = (pConfig->attrFlags & CGIF_ATTR_IS_ANIMATED) ? CGIF_RAW_ATTR_IS_ANIMATED : 0;
119
1.15k
  rawConfig.attrFlags |= (pConfig->attrFlags & CGIF_ATTR_NO_LOOP) ? CGIF_RAW_ATTR_NO_LOOP : 0;
120
1.15k
  rawConfig.width     = pConfig->width;
121
1.15k
  rawConfig.height    = pConfig->height;
122
1.15k
  rawConfig.numLoops  = pConfig->numLoops;
123
1.15k
  rawConfig.pWriteFn  = writecb;
124
1.15k
  rawConfig.pContext  = (void*)pGIF;
125
  // pass config down and create a new raw GIF stream.
126
1.15k
  pGIFRaw = cgif_raw_newgif(&rawConfig);
127
  // check for errors
128
1.15k
  if(pGIFRaw == NULL) {
129
0
    if(pFile) {
130
0
      fclose(pFile);
131
0
    }
132
0
    freeCGIF(pGIF);
133
0
    return NULL;
134
0
  }
135
136
1.15k
  pGIF->pGIFRaw = pGIFRaw;
137
  // assume error per default.
138
  // set to CGIF_OK by the first successful cgif_addframe() call, as a GIF without frames is invalid.
139
1.15k
  pGIF->curResult = CGIF_PENDING;
140
1.15k
  return pGIF;
141
1.15k
}
142
143
/* compare given pixel indices using the correct local or global color table; returns 0 if the two pixels are RGB equal */
144
19.9k
static int cmpPixel(const CGIF* pGIF, const CGIF_FrameConfig* pCur, const CGIF_FrameConfig* pBef, const uint8_t iCur, const uint8_t iBef) {
145
19.9k
  uint8_t* pBefCT; // color table to use for pBef
146
19.9k
  uint8_t* pCurCT; // color table to use for pCur
147
148
19.9k
  if((pCur->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iCur == pCur->transIndex) {
149
8.05k
    return 0; // identical
150
8.05k
  }
151
11.9k
  if((pBef->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iBef == pBef->transIndex) {
152
163
    return 1; // done: cannot compare
153
163
  }
154
  // safety bounds check
155
11.7k
  const uint16_t sizeCTBef = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
156
11.7k
  const uint16_t sizeCTCur = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
157
11.7k
  if((iBef >= sizeCTBef) || (iCur >= sizeCTCur)) {
158
272
    return 1; // error: out-of-bounds - cannot compare
159
272
  }
160
11.4k
  pBefCT = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
161
11.4k
  pCurCT = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
162
11.4k
  return memcmp(pBefCT + iBef * 3, pCurCT + iCur * 3, 3);
163
11.7k
}
164
165
// compare given frames; returns 0 if frames are equal and 1 if they differ. If they differ, pResult returns area of difference
166
0
static int getDiffArea(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
167
0
  const uint8_t* pCurImageData;
168
0
  const uint8_t* pBefImageData;
169
0
  uint16_t       i, x;
170
0
  uint16_t       newHeight, newWidth, newLeft, newTop;
171
0
  const uint16_t width  = pGIF->config.width;
172
0
  const uint16_t height = pGIF->config.height;
173
0
  uint8_t        iCur, iBef;
174
175
0
  pCurImageData = pCur->pImageData;
176
0
  pBefImageData = pBef->pImageData;
177
  // find top
178
0
  i = 0;
179
0
  while(i < height) {
180
0
    for(int c = 0; c < width; ++c) {
181
0
      iCur = *(pCurImageData + MULU16(i, width) + c);
182
0
      iBef = *(pBefImageData + MULU16(i, width) + c);
183
0
      if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
184
0
        goto FoundTop;
185
0
      }
186
0
    }
187
0
    ++i;
188
0
  }
189
0
FoundTop:
190
0
  if(i == height) {
191
0
    return 0;
192
0
  }
193
0
  newTop = i;
194
195
  // find actual height
196
0
  i = height - 1;
197
0
  while(i > newTop) {
198
0
    for(int c = 0; c < width; ++c) {
199
0
      iCur = *(pCurImageData + MULU16(i, width) + c);
200
0
      iBef = *(pBefImageData + MULU16(i, width) + c);
201
0
      if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
202
0
        goto FoundHeight;
203
0
      }
204
0
    }
205
0
    --i;
206
0
  }
207
0
FoundHeight:
208
0
  newHeight = (i + 1) - newTop;
209
210
  // find left
211
0
  i = newTop;
212
0
  x = 0;
213
0
  while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
214
0
    ++i;
215
0
    if(i > (newTop + newHeight - 1)) {
216
0
      ++x; //(x==width cannot happen as return 0 is trigged in the only possible case before)
217
0
      i = newTop;
218
0
    }
219
0
  }
220
0
  newLeft = x;
221
222
  // find actual width
223
0
  i = newTop;
224
0
  x = width - 1;
225
0
  while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
226
0
    ++i;
227
0
    if(i > (newTop + newHeight - 1)) {
228
0
      --x; //(x<newLeft cannot happen as return 0 is trigged in the only possible case before)
229
0
      i = newTop;
230
0
    }
231
0
  }
232
0
  newWidth = (x + 1) - newLeft;
233
234
0
  pResult->width  = newWidth;
235
0
  pResult->height = newHeight;
236
0
  pResult->top    = newTop;
237
0
  pResult->left   = newLeft;
238
0
  return 1;
239
0
}
240
241
// compare given global palette frames; returns 0 if frames are equal and 1 if they differ. If they differ, pResult returns area of difference
242
0
static int getDiffAreaGlobalPalette(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
243
0
  const uint8_t* pCurImageData;
244
0
  const uint8_t* pBefImageData;
245
0
  uint32_t       offset;
246
0
  uint16_t       i, x;
247
0
  uint16_t       newHeight, newWidth, newLeft, newTop;
248
0
  const uint16_t width  = pGIF->config.width;
249
0
  const uint16_t height = pGIF->config.height;
250
251
0
  pCurImageData = pCur->pImageData;
252
0
  pBefImageData = pBef->pImageData;
253
  // find top
254
0
  i = 0;
255
0
  offset = 0;
256
0
  while(i < height) {
257
0
    if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
258
0
      break;
259
0
    }
260
0
    ++i;
261
0
    offset += width;
262
0
  }
263
264
0
  if(i == height) {
265
0
    return 0;
266
0
  }
267
0
  newTop = i;
268
269
  // find actual height
270
0
  i = height - 1;
271
0
  offset = MULU16(i, width);
272
0
  while(i > newTop) {
273
0
    if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
274
0
      break;
275
0
    }
276
0
    --i;
277
0
    offset -= width;
278
0
  }
279
0
  newHeight = (i + 1) - newTop;
280
281
  // find left
282
0
  i = newTop;
283
0
  x = 0;
284
0
  offset = MULU16(i, width);
285
0
  while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
286
0
    ++i;
287
0
    offset += width;
288
0
    if(i > (newTop + newHeight - 1)) {
289
0
      ++x; //(x==width cannot happen as return 0 is triggered in the only possible case before)
290
0
      i = newTop;
291
0
      offset = MULU16(i, width);
292
0
    }
293
0
  }
294
0
  newLeft = x;
295
296
  // find actual width
297
0
  i = newTop;
298
0
  x = width - 1;
299
0
  offset = MULU16(i, width);
300
0
  while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
301
0
    ++i;
302
0
    offset += width;
303
0
    if(i > (newTop + newHeight - 1)) {
304
0
      --x; //(x<newLeft cannot happen as return 0 is triggered in the only possible case before)
305
0
      i = newTop;
306
0
      offset = MULU16(i, width);
307
0
    }
308
0
  }
309
0
  newWidth = (x + 1) - newLeft;
310
311
0
  pResult->width  = newWidth;
312
0
  pResult->height = newHeight;
313
0
  pResult->top    = newTop;
314
0
  pResult->left   = newLeft;
315
0
  return 1;
316
0
}
317
318
/* optimize GIF file size by only redrawing the rectangular area that differs from previous frame */
319
0
static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult* pResult) {
320
0
  uint16_t i;
321
0
  uint8_t* pNewImageData;
322
0
  const uint16_t width  = pGIF->config.width;
323
0
  const uint8_t* pCurImageData = pCur->pImageData;
324
0
  int diffFrame;
325
326
0
  if ((pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
327
0
      && (pBef->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0 && (pCur->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0) {
328
    // Both frames use global palette; use fast comparison.
329
0
    diffFrame = getDiffAreaGlobalPalette(pGIF, pCur, pBef, pResult);
330
0
  } else {
331
0
    diffFrame = getDiffArea(pGIF, pCur, pBef, pResult);
332
0
  }
333
334
0
  if (diffFrame == 0) { // need dummy pixel (frame is identical with one before)
335
    // TBD we might make it possible to merge identical frames in the future
336
0
    pResult->width  = 1;
337
0
    pResult->height = 1;
338
0
    pResult->left   = 0;
339
0
    pResult->top    = 0;
340
0
  }
341
342
  // create new image data
343
0
  pNewImageData = malloc(MULU16(pResult->width, pResult->height));
344
0
  if(pNewImageData == NULL) {
345
0
    return NULL; // allocation failed
346
0
  }
347
0
  for (i = 0; i < pResult->height; ++i) {
348
0
    memcpy(pNewImageData + MULU16(i, pResult->width), pCurImageData + MULU16((i + pResult->top), width) + pResult->left, pResult->width);
349
0
  }
350
351
0
  return pNewImageData;
352
0
}
353
354
/* move frame down to the raw GIF API */
355
2.40k
static cgif_result flushFrame(CGIF* pGIF, CGIF_Frame* pCur, CGIF_Frame* pBef) {
356
2.40k
  CGIFRaw_FrameConfig rawConfig;
357
2.40k
  DimResult           dimResult;
358
2.40k
  uint8_t*            pTmpImageData;
359
2.40k
  uint8_t*            pBefImageData;
360
2.40k
  int                 isFirstFrame, useLCT, hasAlpha, hasSetTransp;
361
2.40k
  uint16_t            numPaletteEntries;
362
2.40k
  uint16_t            imageWidth, imageHeight, width, height, top, left;
363
2.40k
  uint8_t             transIndex, disposalMethod;
364
2.40k
  cgif_result         r;
365
366
2.40k
  imageWidth     = pGIF->config.width;
367
2.40k
  imageHeight    = pGIF->config.height;
368
2.40k
  isFirstFrame   = (pBef == NULL) ? 1 : 0;
369
2.40k
  useLCT         = (pCur->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? 1 : 0; // LCT stands for "local color table"
370
2.40k
  hasAlpha       = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0;
371
2.40k
  hasSetTransp   = (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0;
372
2.40k
  disposalMethod = pCur->disposalMethod;
373
2.40k
  transIndex     = pCur->transIndex;
374
  // deactivate impossible size optimizations
375
  //  => in case alpha channel is used
376
  // CGIF_FRAME_GEN_USE_TRANSPARENCY and CGIF_FRAME_GEN_USE_DIFF_WINDOW are not possible
377
2.40k
  if(isFirstFrame || hasAlpha) {
378
1.37k
    pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
379
1.37k
  }
380
  // transparency setting (which areas are identical to the frame before) provided by user:
381
  // CGIF_FRAME_GEN_USE_TRANSPARENCY not possible
382
2.40k
  if(hasSetTransp) {
383
1.96k
    pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY);
384
1.96k
  }
385
2.40k
  numPaletteEntries = (useLCT) ? pCur->config.numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
386
  // switch off transparency optimization if color table is full (no free spot for the transparent index), TBD: count used colors, adapt table
387
2.40k
  if(numPaletteEntries == 256) {
388
0
    pCur->config.genFlags &= ~CGIF_FRAME_GEN_USE_TRANSPARENCY;
389
0
  }
390
391
  // purge overlap of current frame and frame before (width - height optim), if required (CGIF_FRAME_GEN_USE_DIFF_WINDOW set)
392
2.40k
  if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_DIFF_WINDOW) {
393
0
    pTmpImageData = doWidthHeightOptim(pGIF, &pCur->config, &pBef->config, &dimResult);
394
0
    if(pTmpImageData == NULL) {
395
0
      return CGIF_EALLOC; // allocation failed in doWidthHeightOptim
396
0
    }
397
0
    width  = dimResult.width;
398
0
    height = dimResult.height;
399
0
    top    = dimResult.top;
400
0
    left   = dimResult.left;
401
2.40k
  } else {
402
2.40k
    pTmpImageData = NULL;
403
2.40k
    width         = imageWidth;
404
2.40k
    height        = imageHeight;
405
2.40k
    top           = 0;
406
2.40k
    left          = 0;
407
2.40k
  }
408
409
  // mark matching areas of the previous frame as transparent, if required (CGIF_FRAME_GEN_USE_TRANSPARENCY set)
410
2.40k
  if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) {
411
    // set transIndex to next free index
412
0
    int pow2 = calcNextPower2Ex(numPaletteEntries);
413
0
    pow2 = (pow2 < 2) ? 2 : pow2; // TBD keep transparency index behavior as in V0.1.0 (for now)
414
0
    transIndex = (1 << pow2) - 1;
415
0
    if(transIndex < numPaletteEntries) {
416
0
      transIndex = (1 << (pow2 + 1)) - 1;
417
0
    }
418
0
    if(pTmpImageData == NULL) {
419
0
      pTmpImageData = malloc(MULU16(imageWidth, imageHeight));
420
0
      if(pTmpImageData == NULL) {
421
0
        return CGIF_EALLOC; // allocation failed
422
0
      }
423
0
      memcpy(pTmpImageData, pCur->config.pImageData, MULU16(imageWidth, imageHeight));
424
0
    }
425
0
    pBefImageData = pBef->config.pImageData;
426
0
    for(int i = 0; i < height; ++i) {
427
0
      for(int x = 0; x < width; ++x) {
428
0
        if(cmpPixel(pGIF, &pCur->config, &pBef->config, pTmpImageData[MULU16(i, width) + x], pBefImageData[MULU16(top + i, imageWidth) + (left + x)]) == 0) {
429
0
          pTmpImageData[MULU16(i, width) + x] = transIndex;
430
0
        }
431
0
      }
432
0
    }
433
0
  }
434
435
  // move frame down to GIF raw API
436
2.40k
  rawConfig.pLCT           = pCur->config.pLocalPalette;
437
2.40k
  rawConfig.pImageData     = (pTmpImageData) ? pTmpImageData : pCur->config.pImageData;
438
2.40k
  rawConfig.attrFlags      = 0;
439
2.40k
  if(hasAlpha || (pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) || hasSetTransp) {
440
2.40k
    rawConfig.attrFlags |= CGIF_RAW_FRAME_ATTR_HAS_TRANS;
441
2.40k
  }
442
2.40k
  rawConfig.attrFlags |= (pCur->config.attrFlags & CGIF_FRAME_ATTR_INTERLACED) ? CGIF_RAW_FRAME_ATTR_INTERLACED : 0;
443
2.40k
  rawConfig.width          = width;
444
2.40k
  rawConfig.height         = height;
445
2.40k
  rawConfig.top            = top;
446
2.40k
  rawConfig.left           = left;
447
2.40k
  rawConfig.delay          = pCur->config.delay;
448
2.40k
  rawConfig.sizeLCT        = (useLCT) ? pCur->config.numLocalPaletteEntries : 0;
449
2.40k
  rawConfig.disposalMethod = disposalMethod;
450
2.40k
  rawConfig.transIndex     = transIndex;
451
2.40k
  r = cgif_raw_addframe(pGIF->pGIFRaw, &rawConfig);
452
2.40k
  free(pTmpImageData);
453
2.40k
  return r;
454
2.40k
}
455
456
4.48k
static void freeFrame(CGIF_Frame* pFrame) {
457
4.48k
  if(pFrame) {
458
2.40k
    free(pFrame->config.pImageData);
459
2.40k
    if(pFrame->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
460
2.40k
      free(pFrame->config.pLocalPalette);
461
2.40k
    }
462
2.40k
    free(pFrame);
463
2.40k
  }
464
4.48k
}
465
466
2.40k
static void copyFrameConfig(CGIF_FrameConfig* pDest, CGIF_FrameConfig* pSrc) {
467
2.40k
  pDest->pLocalPalette          = pSrc->pLocalPalette; // might need a deep copy
468
2.40k
  pDest->pImageData             = pSrc->pImageData;    // might need a deep copy
469
2.40k
  pDest->attrFlags              = pSrc->attrFlags;
470
2.40k
  pDest->genFlags               = pSrc->genFlags;
471
2.40k
  pDest->delay                  = pSrc->delay;
472
2.40k
  pDest->numLocalPaletteEntries = pSrc->numLocalPaletteEntries;
473
  // copy transIndex if necessary (field added with V0.2.0; avoid binary incompatibility)
474
2.40k
  if(pSrc->attrFlags & (CGIF_FRAME_ATTR_HAS_ALPHA | CGIF_FRAME_ATTR_HAS_SET_TRANS)) {
475
2.40k
    pDest->transIndex = pSrc->transIndex;
476
2.40k
  }
477
2.40k
}
478
479
/* queue a new GIF frame */
480
2.54k
int cgif_addframe(CGIF* pGIF, CGIF_FrameConfig* pConfig) {
481
2.54k
  CGIF_Frame* pNewFrame;
482
2.54k
  int         hasAlpha, hasSetTransp;
483
2.54k
  uint32_t    i;
484
2.54k
  cgif_result r;
485
486
  // check for previous errors
487
2.54k
  if(pGIF->curResult != CGIF_OK && pGIF->curResult != CGIF_PENDING) {
488
0
    return pGIF->curResult;
489
0
  }
490
2.54k
  hasAlpha     = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0; // alpha channel is present
491
2.54k
  hasSetTransp = (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0;  // user provided transparency setting (identical areas marked by user)
492
  // check for invalid configs:
493
  // cannot set alpha channel and user-provided transparency at the same time.
494
2.54k
  if(hasAlpha && hasSetTransp) {
495
0
    pGIF->curResult = CGIF_ERROR;
496
0
    return pGIF->curResult;
497
0
  }
498
  // cannot set global and local alpha channel at the same time
499
2.54k
  if((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) {
500
0
    pGIF->curResult = CGIF_ERROR;
501
0
    return pGIF->curResult;
502
0
  }
503
  // sanity check:
504
  // at least one valid CT needed (global or local)
505
2.54k
  if(!(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) && (pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE)) {
506
0
    pGIF->curResult = CGIF_ERROR;
507
0
    return CGIF_ERROR; // invalid config
508
0
  }
509
510
  // if frame matches previous frame, drop it completely and sum the frame delay
511
2.54k
  if(pGIF->aFrames[pGIF->iHEAD] != NULL) {
512
1.52k
    const uint32_t frameDelay = pConfig->delay + pGIF->aFrames[pGIF->iHEAD]->config.delay;
513
1.52k
    if(frameDelay <= 0xFFFF && !(pGIF->config.genFlags & CGIF_GEN_KEEP_IDENT_FRAMES)) {
514
1.36k
      int sameFrame = 1;
515
1.36k
      if ((pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
516
0
          && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0) {
517
0
        if (memcmp(pConfig->pImageData, pGIF->aFrames[pGIF->iHEAD]->config.pImageData, MULU16(pGIF->config.width, pGIF->config.height))) {
518
0
          sameFrame = 0;
519
0
        }
520
1.36k
      } else {
521
20.0k
        for(i = 0; i < MULU16(pGIF->config.width, pGIF->config.height); i++) {
522
19.9k
          if(cmpPixel(pGIF, pConfig, &pGIF->aFrames[pGIF->iHEAD]->config, pConfig->pImageData[i], pGIF->aFrames[pGIF->iHEAD]->config.pImageData[i])) {
523
1.22k
            sameFrame = 0;
524
1.22k
            break;
525
1.22k
          }
526
19.9k
        }
527
1.36k
      }
528
529
1.36k
      if (sameFrame) {
530
144
        pGIF->aFrames[pGIF->iHEAD]->config.delay = frameDelay;
531
144
        return CGIF_OK;
532
144
      }
533
1.36k
    }
534
1.52k
  }
535
536
  // search for free slot in frame queue
537
3.77k
  for(i = pGIF->iHEAD; i < SIZE_FRAME_QUEUE && pGIF->aFrames[i] != NULL; ++i);
538
  // check whether the queue is full
539
  // when queue is full: we need to flush one frame.
540
2.40k
  if(i == SIZE_FRAME_QUEUE) {
541
1.01k
    r = flushFrame(pGIF, pGIF->aFrames[1], pGIF->aFrames[0]);
542
1.01k
    freeFrame(pGIF->aFrames[0]);
543
1.01k
    pGIF->aFrames[0] = NULL; // avoid potential double free in cgif_close
544
    // check for errors
545
1.01k
    if(r != CGIF_OK) {
546
0
      pGIF->curResult = r;
547
0
      return pGIF->curResult;
548
0
    }
549
1.01k
    i = SIZE_FRAME_QUEUE - 1;
550
    // keep the flushed frame in memory, as we might need it to write the next one.
551
1.01k
    pGIF->aFrames[0] = pGIF->aFrames[1];
552
1.01k
    pGIF->aFrames[1] = pGIF->aFrames[2];
553
1.01k
    pGIF->aFrames[2] = NULL;
554
1.01k
  }
555
  // create new Frame struct + make a deep copy of pConfig.
556
2.40k
  pNewFrame = malloc(sizeof(CGIF_Frame));
557
2.40k
  if(pNewFrame == NULL) {
558
0
    pGIF->curResult = CGIF_EALLOC;
559
0
    return pGIF->curResult;
560
0
  }
561
2.40k
  copyFrameConfig(&(pNewFrame->config), pConfig);
562
2.40k
  pNewFrame->config.pImageData = malloc(MULU16(pGIF->config.width, pGIF->config.height));
563
2.40k
  if(pNewFrame->config.pImageData == NULL) {
564
0
    free(pNewFrame);
565
0
    pGIF->curResult = CGIF_EALLOC;
566
0
    return pGIF->curResult;
567
0
  }
568
2.40k
  memcpy(pNewFrame->config.pImageData, pConfig->pImageData, MULU16(pGIF->config.width, pGIF->config.height));
569
  // make a deep copy of the local color table, if required.
570
2.40k
  if(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
571
2.40k
    pNewFrame->config.pLocalPalette  = malloc(pConfig->numLocalPaletteEntries * 3);
572
2.40k
    if(pNewFrame->config.pLocalPalette == NULL) {
573
0
      free(pNewFrame->config.pImageData);
574
0
      free(pNewFrame);
575
0
      pGIF->curResult = CGIF_EALLOC;
576
0
      return pGIF->curResult;
577
0
    }
578
2.40k
    memcpy(pNewFrame->config.pLocalPalette, pConfig->pLocalPalette, pConfig->numLocalPaletteEntries * 3);
579
2.40k
  }
580
2.40k
  pNewFrame->disposalMethod        = DISPOSAL_METHOD_LEAVE;
581
2.40k
  pNewFrame->transIndex            = 0;
582
2.40k
  pGIF->aFrames[i]                 = pNewFrame; // add frame to queue
583
2.40k
  pGIF->iHEAD                      = i;         // update HEAD index
584
  // check whether we need to adapt the disposal method of the frame before.
585
2.40k
  if(pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) {
586
0
    pGIF->aFrames[i]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // TBD might be removed
587
0
    pGIF->aFrames[i]->transIndex     = 0;
588
0
    if(pGIF->aFrames[i - 1] != NULL) {
589
0
      pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
590
0
      pGIF->aFrames[i - 1]->disposalMethod   = DISPOSAL_METHOD_BACKGROUND; // restore to background color
591
0
    }
592
0
  }
593
  // set per-frame alpha channel (we need to adapt the disposal method of the frame before)
594
2.40k
  if(pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA) {
595
439
    pGIF->aFrames[i]->transIndex = pConfig->transIndex;
596
439
    if(pGIF->aFrames[i - 1] != NULL) {
597
345
      pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_DIFF_WINDOW); // width/height optim not possible for frame before
598
345
      pGIF->aFrames[i - 1]->disposalMethod   = DISPOSAL_METHOD_BACKGROUND; // restore to background color
599
345
    }
600
439
  }
601
  // user provided transparency setting
602
2.40k
  if(hasSetTransp) {
603
1.96k
    pGIF->aFrames[i]->transIndex = pConfig->transIndex;
604
1.96k
  }
605
2.40k
  pGIF->curResult = CGIF_OK;
606
2.40k
  return pGIF->curResult;
607
2.40k
}
608
609
/* close the GIF-file and free allocated space */
610
1.15k
int cgif_close(CGIF* pGIF) {
611
1.15k
  int         r;
612
1.15k
  cgif_result result;
613
614
  // check for previous errors
615
1.15k
  if(pGIF->curResult != CGIF_OK) {
616
130
    goto CGIF_CLOSE_Cleanup;
617
130
  }
618
619
  // flush all remaining frames in queue
620
3.07k
  for(int i = 1; i < SIZE_FRAME_QUEUE; ++i) {
621
2.05k
    if(pGIF->aFrames[i] != NULL) {
622
1.38k
      r = flushFrame(pGIF, pGIF->aFrames[i], pGIF->aFrames[i - 1]);
623
1.38k
      if(r != CGIF_OK) {
624
0
        pGIF->curResult = r;
625
0
        break;
626
0
      }
627
1.38k
    }
628
2.05k
  }
629
630
  // cleanup
631
1.15k
CGIF_CLOSE_Cleanup:
632
1.15k
  r = cgif_raw_close(pGIF->pGIFRaw); // close raw GIF stream
633
  // check for errors
634
1.15k
  if(r != CGIF_OK) {
635
130
    pGIF->curResult = r;
636
130
  }
637
638
1.15k
  if(pGIF->pFile) {
639
0
    r = fclose(pGIF->pFile); // we are done at this point => close the file
640
0
    if(r) {
641
0
      pGIF->curResult = CGIF_ECLOSE; // error: fclose failed
642
0
    }
643
0
  }
644
4.62k
  for(int i = 0; i < SIZE_FRAME_QUEUE; ++i) {
645
3.46k
    freeFrame(pGIF->aFrames[i]);
646
3.46k
  }
647
648
1.15k
  result = pGIF->curResult;
649
1.15k
  freeCGIF(pGIF);
650
  // catch internal value CGIF_PENDING
651
1.15k
  if(result == CGIF_PENDING) {
652
130
    result = CGIF_ERROR;
653
130
  }
654
1.15k
  return result; // return previous result
655
1.02k
}