Coverage Report

Created: 2026-09-14 07:12

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
4.76M
#define MULU16(a, b) (((uint32_t)a) * ((uint32_t)b)) // helper macro to correctly multiply two U16's without default signed int promotion
10
35.0k
#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
990
static uint8_t calcNextPower2Ex(uint16_t n) {
41
990
  uint8_t nextPow2;
42
43
2.36k
  for (nextPow2 = 0; n > (1uL << nextPow2); ++nextPow2);
44
990
  return nextPow2;
45
990
}
46
47
/* write callback. returns 0 on success or -1 on error.  */
48
46.5k
static int writecb(void* pContext, const uint8_t* pData, const size_t numBytes) {
49
46.5k
  CGIF* pGIF;
50
46.5k
  size_t r;
51
52
46.5k
  pGIF = (CGIF*)pContext;
53
46.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
46.5k
  } else if(pGIF->config.pWriteFn) {
58
46.5k
    return pGIF->config.pWriteFn(pGIF->config.pContext, pData, numBytes);
59
46.5k
  }
60
0
  return 0;
61
46.5k
}
62
63
/* free space allocated for CGIF struct */
64
2.09k
static void freeCGIF(CGIF* pGIF) {
65
2.09k
  if((pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
66
1.97k
    free(pGIF->config.pGlobalPalette);
67
1.97k
  }
68
2.09k
  free(pGIF);
69
2.09k
}
70
71
/* create a new GIF */
72
2.09k
CGIF* cgif_newgif(CGIF_Config* pConfig) {
73
2.09k
  FILE*          pFile;
74
2.09k
  CGIF*          pGIF;
75
2.09k
  CGIFRaw*       pGIFRaw; // raw GIF stream
76
2.09k
  CGIFRaw_Config rawConfig = {0};
77
  // width or heigth cannot be zero
78
2.09k
  if(!pConfig->width || !pConfig->height) {
79
7
    return NULL;
80
7
  }
81
2.09k
  pFile = NULL;
82
  // open output file (if necessary)
83
2.09k
  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
2.09k
  pGIF = malloc(sizeof(CGIF));
91
2.09k
  if(pGIF == NULL) {
92
0
    if(pFile) {
93
0
      fclose(pFile);
94
0
    }
95
0
    return NULL; // error -> malloc failed
96
0
  }
97
98
2.09k
  memset(pGIF, 0, sizeof(CGIF));
99
2.09k
  pGIF->pFile = pFile;
100
2.09k
  pGIF->iHEAD = 1;
101
2.09k
  memcpy(&(pGIF->config), pConfig, sizeof(CGIF_Config));
102
  // make a deep copy of global color tabele (GCT), if required.
103
2.09k
  if((pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
104
1.97k
    pGIF->config.pGlobalPalette = malloc(pConfig->numGlobalPaletteEntries * 3);
105
1.97k
    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
1.97k
    memcpy(pGIF->config.pGlobalPalette, pConfig->pGlobalPalette, pConfig->numGlobalPaletteEntries * 3);
113
1.97k
  }
114
115
2.09k
  rawConfig.pGCT      = pConfig->pGlobalPalette;
116
2.09k
  rawConfig.sizeGCT   = (pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) ? 0 : pConfig->numGlobalPaletteEntries;
117
  // translate CGIF_ATTR_* to CGIF_RAW_ATTR_* flags
118
2.09k
  rawConfig.attrFlags = (pConfig->attrFlags & CGIF_ATTR_IS_ANIMATED) ? CGIF_RAW_ATTR_IS_ANIMATED : 0;
119
2.09k
  rawConfig.attrFlags |= (pConfig->attrFlags & CGIF_ATTR_NO_LOOP) ? CGIF_RAW_ATTR_NO_LOOP : 0;
120
2.09k
  rawConfig.width     = pConfig->width;
121
2.09k
  rawConfig.height    = pConfig->height;
122
2.09k
  rawConfig.numLoops  = pConfig->numLoops;
123
2.09k
  rawConfig.pWriteFn  = writecb;
124
2.09k
  rawConfig.pContext  = (void*)pGIF;
125
  // pass config down and create a new raw GIF stream.
126
2.09k
  pGIFRaw = cgif_raw_newgif(&rawConfig);
127
  // check for errors
128
2.09k
  if(pGIFRaw == NULL) {
129
13
    if(pFile) {
130
0
      fclose(pFile);
131
0
    }
132
13
    freeCGIF(pGIF);
133
13
    return NULL;
134
13
  }
135
136
2.07k
  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
2.07k
  pGIF->curResult = CGIF_PENDING;
140
2.07k
  return pGIF;
141
2.09k
}
142
143
/* compare given pixel indices using the correct local or global color table; returns 0 if the two pixels are RGB equal */
144
1.19M
static int cmpPixel(const CGIF* pGIF, const CGIF_FrameConfig* pCur, const CGIF_FrameConfig* pBef, const uint8_t iCur, const uint8_t iBef) {
145
1.19M
  uint8_t* pBefCT; // color table to use for pBef
146
1.19M
  uint8_t* pCurCT; // color table to use for pCur
147
148
1.19M
  if((pCur->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iCur == pCur->transIndex) {
149
44.6k
    return 0; // identical
150
44.6k
  }
151
1.15M
  if((pBef->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iBef == pBef->transIndex) {
152
5.33k
    return 1; // done: cannot compare
153
5.33k
  }
154
  // safety bounds check
155
1.14M
  const uint16_t sizeCTBef = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
156
1.14M
  const uint16_t sizeCTCur = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
157
1.14M
  if((iBef >= sizeCTBef) || (iCur >= sizeCTCur)) {
158
806k
    return 1; // error: out-of-bounds - cannot compare
159
806k
  }
160
338k
  pBefCT = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
161
338k
  pCurCT = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
162
338k
  return memcmp(pBefCT + iBef * 3, pCurCT + iCur * 3, 3);
163
1.14M
}
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
1.19k
static int getDiffArea(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
167
1.19k
  const uint8_t* pCurImageData;
168
1.19k
  const uint8_t* pBefImageData;
169
1.19k
  uint16_t       i, x;
170
1.19k
  uint16_t       newHeight, newWidth, newLeft, newTop;
171
1.19k
  const uint16_t width  = pGIF->config.width;
172
1.19k
  const uint16_t height = pGIF->config.height;
173
1.19k
  uint8_t        iCur, iBef;
174
175
1.19k
  pCurImageData = pCur->pImageData;
176
1.19k
  pBefImageData = pBef->pImageData;
177
  // find top
178
1.19k
  i = 0;
179
59.7k
  while(i < height) {
180
119k
    for(int c = 0; c < width; ++c) {
181
61.1k
      iCur = *(pCurImageData + MULU16(i, width) + c);
182
61.1k
      iBef = *(pBefImageData + MULU16(i, width) + c);
183
61.1k
      if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
184
1.06k
        goto FoundTop;
185
1.06k
      }
186
61.1k
    }
187
58.5k
    ++i;
188
58.5k
  }
189
1.19k
FoundTop:
190
1.19k
  if(i == height) {
191
132
    return 0;
192
132
  }
193
1.06k
  newTop = i;
194
195
  // find actual height
196
1.06k
  i = height - 1;
197
14.5k
  while(i > newTop) {
198
28.0k
    for(int c = 0; c < width; ++c) {
199
14.5k
      iCur = *(pCurImageData + MULU16(i, width) + c);
200
14.5k
      iBef = *(pBefImageData + MULU16(i, width) + c);
201
14.5k
      if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
202
401
        goto FoundHeight;
203
401
      }
204
14.5k
    }
205
13.5k
    --i;
206
13.5k
  }
207
1.06k
FoundHeight:
208
1.06k
  newHeight = (i + 1) - newTop;
209
210
  // find left
211
1.06k
  i = newTop;
212
1.06k
  x = 0;
213
2.61k
  while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
214
1.55k
    ++i;
215
1.55k
    if(i > (newTop + newHeight - 1)) {
216
1.33k
      ++x; //(x==width cannot happen as return 0 is trigged in the only possible case before)
217
1.33k
      i = newTop;
218
1.33k
    }
219
1.55k
  }
220
1.06k
  newLeft = x;
221
222
  // find actual width
223
1.06k
  i = newTop;
224
1.06k
  x = width - 1;
225
2.50k
  while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
226
1.44k
    ++i;
227
1.44k
    if(i > (newTop + newHeight - 1)) {
228
1.23k
      --x; //(x<newLeft cannot happen as return 0 is trigged in the only possible case before)
229
1.23k
      i = newTop;
230
1.23k
    }
231
1.44k
  }
232
1.06k
  newWidth = (x + 1) - newLeft;
233
234
1.06k
  pResult->width  = newWidth;
235
1.06k
  pResult->height = newHeight;
236
1.06k
  pResult->top    = newTop;
237
1.06k
  pResult->left   = newLeft;
238
1.06k
  return 1;
239
1.06k
}
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
573
static int getDiffAreaGlobalPalette(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
243
573
  const uint8_t* pCurImageData;
244
573
  const uint8_t* pBefImageData;
245
573
  uint32_t       offset;
246
573
  uint16_t       i, x;
247
573
  uint16_t       newHeight, newWidth, newLeft, newTop;
248
573
  const uint16_t width  = pGIF->config.width;
249
573
  const uint16_t height = pGIF->config.height;
250
251
573
  pCurImageData = pCur->pImageData;
252
573
  pBefImageData = pBef->pImageData;
253
  // find top
254
573
  i = 0;
255
573
  offset = 0;
256
15.0k
  while(i < height) {
257
14.9k
    if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
258
518
      break;
259
518
    }
260
14.4k
    ++i;
261
14.4k
    offset += width;
262
14.4k
  }
263
264
573
  if(i == height) {
265
55
    return 0;
266
55
  }
267
518
  newTop = i;
268
269
  // find actual height
270
518
  i = height - 1;
271
518
  offset = MULU16(i, width);
272
833
  while(i > newTop) {
273
522
    if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
274
207
      break;
275
207
    }
276
315
    --i;
277
315
    offset -= width;
278
315
  }
279
518
  newHeight = (i + 1) - newTop;
280
281
  // find left
282
518
  i = newTop;
283
518
  x = 0;
284
518
  offset = MULU16(i, width);
285
21.7k
  while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
286
21.1k
    ++i;
287
21.1k
    offset += width;
288
21.1k
    if(i > (newTop + newHeight - 1)) {
289
1.05k
      ++x; //(x==width cannot happen as return 0 is triggered in the only possible case before)
290
1.05k
      i = newTop;
291
1.05k
      offset = MULU16(i, width);
292
1.05k
    }
293
21.1k
  }
294
518
  newLeft = x;
295
296
  // find actual width
297
518
  i = newTop;
298
518
  x = width - 1;
299
518
  offset = MULU16(i, width);
300
17.8k
  while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
301
17.3k
    ++i;
302
17.3k
    offset += width;
303
17.3k
    if(i > (newTop + newHeight - 1)) {
304
332
      --x; //(x<newLeft cannot happen as return 0 is triggered in the only possible case before)
305
332
      i = newTop;
306
332
      offset = MULU16(i, width);
307
332
    }
308
17.3k
  }
309
518
  newWidth = (x + 1) - newLeft;
310
311
518
  pResult->width  = newWidth;
312
518
  pResult->height = newHeight;
313
518
  pResult->top    = newTop;
314
518
  pResult->left   = newLeft;
315
518
  return 1;
316
573
}
317
318
/* optimize GIF file size by only redrawing the rectangular area that differs from previous frame */
319
1.76k
static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult* pResult) {
320
1.76k
  uint16_t i;
321
1.76k
  uint8_t* pNewImageData;
322
1.76k
  const uint16_t width  = pGIF->config.width;
323
1.76k
  const uint8_t* pCurImageData = pCur->pImageData;
324
1.76k
  int diffFrame;
325
326
1.76k
  if ((pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
327
1.16k
      && (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
573
    diffFrame = getDiffAreaGlobalPalette(pGIF, pCur, pBef, pResult);
330
1.19k
  } else {
331
1.19k
    diffFrame = getDiffArea(pGIF, pCur, pBef, pResult);
332
1.19k
  }
333
334
1.76k
  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
187
    pResult->width  = 1;
337
187
    pResult->height = 1;
338
187
    pResult->left   = 0;
339
187
    pResult->top    = 0;
340
187
  }
341
342
  // create new image data
343
1.76k
  pNewImageData = malloc(MULU16(pResult->width, pResult->height));
344
1.76k
  if(pNewImageData == NULL) {
345
0
    return NULL; // allocation failed
346
0
  }
347
1.19M
  for (i = 0; i < pResult->height; ++i) {
348
1.18M
    memcpy(pNewImageData + MULU16(i, pResult->width), pCurImageData + MULU16((i + pResult->top), width) + pResult->left, pResult->width);
349
1.18M
  }
350
351
1.76k
  return pNewImageData;
352
1.76k
}
353
354
/* move frame down to the raw GIF API */
355
4.68k
static cgif_result flushFrame(CGIF* pGIF, CGIF_Frame* pCur, CGIF_Frame* pBef) {
356
4.68k
  CGIFRaw_FrameConfig rawConfig;
357
4.68k
  DimResult           dimResult;
358
4.68k
  uint8_t*            pTmpImageData;
359
4.68k
  uint8_t*            pBefImageData;
360
4.68k
  int                 isFirstFrame, useLCT, hasAlpha, hasSetTransp;
361
4.68k
  uint16_t            numPaletteEntries;
362
4.68k
  uint16_t            imageWidth, imageHeight, width, height, top, left;
363
4.68k
  uint8_t             transIndex, disposalMethod;
364
4.68k
  cgif_result         r;
365
366
4.68k
  imageWidth     = pGIF->config.width;
367
4.68k
  imageHeight    = pGIF->config.height;
368
4.68k
  isFirstFrame   = (pBef == NULL) ? 1 : 0;
369
4.68k
  useLCT         = (pCur->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? 1 : 0; // LCT stands for "local color table"
370
4.68k
  hasAlpha       = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0;
371
4.68k
  hasSetTransp   = (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0;
372
4.68k
  disposalMethod = pCur->disposalMethod;
373
4.68k
  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
4.68k
  if(isFirstFrame || hasAlpha) {
378
2.23k
    pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
379
2.23k
  }
380
  // transparency setting (which areas are identical to the frame before) provided by user:
381
  // CGIF_FRAME_GEN_USE_TRANSPARENCY not possible
382
4.68k
  if(hasSetTransp) {
383
1.22k
    pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY);
384
1.22k
  }
385
4.68k
  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
4.68k
  if(numPaletteEntries == 256) {
388
179
    pCur->config.genFlags &= ~CGIF_FRAME_GEN_USE_TRANSPARENCY;
389
179
  }
390
391
  // purge overlap of current frame and frame before (width - height optim), if required (CGIF_FRAME_GEN_USE_DIFF_WINDOW set)
392
4.68k
  if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_DIFF_WINDOW) {
393
1.76k
    pTmpImageData = doWidthHeightOptim(pGIF, &pCur->config, &pBef->config, &dimResult);
394
1.76k
    if(pTmpImageData == NULL) {
395
0
      return CGIF_EALLOC; // allocation failed in doWidthHeightOptim
396
0
    }
397
1.76k
    width  = dimResult.width;
398
1.76k
    height = dimResult.height;
399
1.76k
    top    = dimResult.top;
400
1.76k
    left   = dimResult.left;
401
2.91k
  } else {
402
2.91k
    pTmpImageData = NULL;
403
2.91k
    width         = imageWidth;
404
2.91k
    height        = imageHeight;
405
2.91k
    top           = 0;
406
2.91k
    left          = 0;
407
2.91k
  }
408
409
  // mark matching areas of the previous frame as transparent, if required (CGIF_FRAME_GEN_USE_TRANSPARENCY set)
410
4.68k
  if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) {
411
    // set transIndex to next free index
412
990
    int pow2 = calcNextPower2Ex(numPaletteEntries);
413
990
    pow2 = (pow2 < 2) ? 2 : pow2; // TBD keep transparency index behavior as in V0.1.0 (for now)
414
990
    transIndex = (1 << pow2) - 1;
415
990
    if(transIndex < numPaletteEntries) {
416
110
      transIndex = (1 << (pow2 + 1)) - 1;
417
110
    }
418
990
    if(pTmpImageData == NULL) {
419
214
      pTmpImageData = malloc(MULU16(imageWidth, imageHeight));
420
214
      if(pTmpImageData == NULL) {
421
0
        return CGIF_EALLOC; // allocation failed
422
0
      }
423
214
      memcpy(pTmpImageData, pCur->config.pImageData, MULU16(imageWidth, imageHeight));
424
214
    }
425
990
    pBefImageData = pBef->config.pImageData;
426
538k
    for(int i = 0; i < height; ++i) {
427
1.59M
      for(int x = 0; x < width; ++x) {
428
1.05M
        if(cmpPixel(pGIF, &pCur->config, &pBef->config, pTmpImageData[MULU16(i, width) + x], pBefImageData[MULU16(top + i, imageWidth) + (left + x)]) == 0) {
429
38.8k
          pTmpImageData[MULU16(i, width) + x] = transIndex;
430
38.8k
        }
431
1.05M
      }
432
537k
    }
433
990
  }
434
435
  // move frame down to GIF raw API
436
4.68k
  rawConfig.pLCT           = pCur->config.pLocalPalette;
437
4.68k
  rawConfig.pImageData     = (pTmpImageData) ? pTmpImageData : pCur->config.pImageData;
438
4.68k
  rawConfig.attrFlags      = 0;
439
4.68k
  if(hasAlpha || (pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) || hasSetTransp) {
440
3.03k
    rawConfig.attrFlags |= CGIF_RAW_FRAME_ATTR_HAS_TRANS;
441
3.03k
  }
442
4.68k
  rawConfig.attrFlags |= (pCur->config.attrFlags & CGIF_FRAME_ATTR_INTERLACED) ? CGIF_RAW_FRAME_ATTR_INTERLACED : 0;
443
4.68k
  rawConfig.width          = width;
444
4.68k
  rawConfig.height         = height;
445
4.68k
  rawConfig.top            = top;
446
4.68k
  rawConfig.left           = left;
447
4.68k
  rawConfig.delay          = pCur->config.delay;
448
4.68k
  rawConfig.sizeLCT        = (useLCT) ? pCur->config.numLocalPaletteEntries : 0;
449
4.68k
  rawConfig.disposalMethod = disposalMethod;
450
4.68k
  rawConfig.transIndex     = transIndex;
451
4.68k
  r = cgif_raw_addframe(pGIF->pGIFRaw, &rawConfig);
452
4.68k
  free(pTmpImageData);
453
4.68k
  return r;
454
4.68k
}
455
456
7.80k
static void freeFrame(CGIF_Frame* pFrame) {
457
7.80k
  if(pFrame) {
458
4.86k
    free(pFrame->config.pImageData);
459
4.86k
    if(pFrame->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
460
1.00k
      free(pFrame->config.pLocalPalette);
461
1.00k
    }
462
4.86k
    free(pFrame);
463
4.86k
  }
464
7.80k
}
465
466
4.86k
static void copyFrameConfig(CGIF_FrameConfig* pDest, CGIF_FrameConfig* pSrc) {
467
4.86k
  pDest->pLocalPalette          = pSrc->pLocalPalette; // might need a deep copy
468
4.86k
  pDest->pImageData             = pSrc->pImageData;    // might need a deep copy
469
4.86k
  pDest->attrFlags              = pSrc->attrFlags;
470
4.86k
  pDest->genFlags               = pSrc->genFlags;
471
4.86k
  pDest->delay                  = pSrc->delay;
472
4.86k
  pDest->numLocalPaletteEntries = pSrc->numLocalPaletteEntries;
473
  // copy transIndex if necessary (field added with V0.2.0; avoid binary incompatibility)
474
4.86k
  if(pSrc->attrFlags & (CGIF_FRAME_ATTR_HAS_ALPHA | CGIF_FRAME_ATTR_HAS_SET_TRANS)) {
475
1.94k
    pDest->transIndex = pSrc->transIndex;
476
1.94k
  }
477
4.86k
}
478
479
/* queue a new GIF frame */
480
5.50k
int cgif_addframe(CGIF* pGIF, CGIF_FrameConfig* pConfig) {
481
5.50k
  CGIF_Frame* pNewFrame;
482
5.50k
  int         hasAlpha, hasSetTransp;
483
5.50k
  uint32_t    i;
484
5.50k
  cgif_result r;
485
486
  // check for previous errors
487
5.50k
  if(pGIF->curResult != CGIF_OK && pGIF->curResult != CGIF_PENDING) {
488
343
    return pGIF->curResult;
489
343
  }
490
5.16k
  hasAlpha     = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0; // alpha channel is present
491
5.16k
  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
5.16k
  if(hasAlpha && hasSetTransp) {
495
26
    pGIF->curResult = CGIF_ERROR;
496
26
    return pGIF->curResult;
497
26
  }
498
  // cannot set global and local alpha channel at the same time
499
5.13k
  if((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) {
500
3
    pGIF->curResult = CGIF_ERROR;
501
3
    return pGIF->curResult;
502
3
  }
503
  // sanity check:
504
  // at least one valid CT needed (global or local)
505
5.13k
  if(!(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) && (pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE)) {
506
7
    pGIF->curResult = CGIF_ERROR;
507
7
    return CGIF_ERROR; // invalid config
508
7
  }
509
510
  // if frame matches previous frame, drop it completely and sum the frame delay
511
5.12k
  if(pGIF->aFrames[pGIF->iHEAD] != NULL) {
512
3.20k
    const uint32_t frameDelay = pConfig->delay + pGIF->aFrames[pGIF->iHEAD]->config.delay;
513
3.20k
    if(frameDelay <= 0xFFFF && !(pGIF->config.genFlags & CGIF_GEN_KEEP_IDENT_FRAMES)) {
514
1.83k
      int sameFrame = 1;
515
1.83k
      if ((pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
516
1.18k
          && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0) {
517
683
        if (memcmp(pConfig->pImageData, pGIF->aFrames[pGIF->iHEAD]->config.pImageData, MULU16(pGIF->config.width, pGIF->config.height))) {
518
605
          sameFrame = 0;
519
605
        }
520
1.15k
      } else {
521
61.6k
        for(i = 0; i < MULU16(pGIF->config.width, pGIF->config.height); i++) {
522
61.5k
          if(cmpPixel(pGIF, pConfig, &pGIF->aFrames[pGIF->iHEAD]->config, pConfig->pImageData[i], pGIF->aFrames[pGIF->iHEAD]->config.pImageData[i])) {
523
1.02k
            sameFrame = 0;
524
1.02k
            break;
525
1.02k
          }
526
61.5k
        }
527
1.15k
      }
528
529
1.83k
      if (sameFrame) {
530
211
        pGIF->aFrames[pGIF->iHEAD]->config.delay = frameDelay;
531
211
        return CGIF_OK;
532
211
      }
533
1.83k
    }
534
3.20k
  }
535
536
  // search for free slot in frame queue
537
7.91k
  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
4.91k
  if(i == SIZE_FRAME_QUEUE) {
541
1.56k
    r = flushFrame(pGIF, pGIF->aFrames[1], pGIF->aFrames[0]);
542
1.56k
    freeFrame(pGIF->aFrames[0]);
543
1.56k
    pGIF->aFrames[0] = NULL; // avoid potential double free in cgif_close
544
    // check for errors
545
1.56k
    if(r != CGIF_OK) {
546
53
      pGIF->curResult = r;
547
53
      return pGIF->curResult;
548
53
    }
549
1.51k
    i = SIZE_FRAME_QUEUE - 1;
550
    // keep the flushed frame in memory, as we might need it to write the next one.
551
1.51k
    pGIF->aFrames[0] = pGIF->aFrames[1];
552
1.51k
    pGIF->aFrames[1] = pGIF->aFrames[2];
553
1.51k
    pGIF->aFrames[2] = NULL;
554
1.51k
  }
555
  // create new Frame struct + make a deep copy of pConfig.
556
4.86k
  pNewFrame = malloc(sizeof(CGIF_Frame));
557
4.86k
  if(pNewFrame == NULL) {
558
0
    pGIF->curResult = CGIF_EALLOC;
559
0
    return pGIF->curResult;
560
0
  }
561
4.86k
  copyFrameConfig(&(pNewFrame->config), pConfig);
562
4.86k
  pNewFrame->config.pImageData = malloc(MULU16(pGIF->config.width, pGIF->config.height));
563
4.86k
  if(pNewFrame->config.pImageData == NULL) {
564
0
    free(pNewFrame);
565
0
    pGIF->curResult = CGIF_EALLOC;
566
0
    return pGIF->curResult;
567
0
  }
568
4.86k
  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
4.86k
  if(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
571
1.00k
    pNewFrame->config.pLocalPalette  = malloc(pConfig->numLocalPaletteEntries * 3);
572
1.00k
    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
1.00k
    memcpy(pNewFrame->config.pLocalPalette, pConfig->pLocalPalette, pConfig->numLocalPaletteEntries * 3);
579
1.00k
  }
580
4.86k
  pNewFrame->disposalMethod        = DISPOSAL_METHOD_LEAVE;
581
4.86k
  pNewFrame->transIndex            = 0;
582
4.86k
  pGIF->aFrames[i]                 = pNewFrame; // add frame to queue
583
4.86k
  pGIF->iHEAD                      = i;         // update HEAD index
584
  // check whether we need to adapt the disposal method of the frame before.
585
4.86k
  if(pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) {
586
163
    pGIF->aFrames[i]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // TBD might be removed
587
163
    pGIF->aFrames[i]->transIndex     = 0;
588
163
    if(pGIF->aFrames[i - 1] != NULL) {
589
112
      pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
590
112
      pGIF->aFrames[i - 1]->disposalMethod   = DISPOSAL_METHOD_BACKGROUND; // restore to background color
591
112
    }
592
163
  }
593
  // set per-frame alpha channel (we need to adapt the disposal method of the frame before)
594
4.86k
  if(pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA) {
595
697
    pGIF->aFrames[i]->transIndex = pConfig->transIndex;
596
697
    if(pGIF->aFrames[i - 1] != NULL) {
597
252
      pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_DIFF_WINDOW); // width/height optim not possible for frame before
598
252
      pGIF->aFrames[i - 1]->disposalMethod   = DISPOSAL_METHOD_BACKGROUND; // restore to background color
599
252
    }
600
697
  }
601
  // user provided transparency setting
602
4.86k
  if(hasSetTransp) {
603
1.25k
    pGIF->aFrames[i]->transIndex = pConfig->transIndex;
604
1.25k
  }
605
4.86k
  pGIF->curResult = CGIF_OK;
606
4.86k
  return pGIF->curResult;
607
4.86k
}
608
609
/* close the GIF-file and free allocated space */
610
2.07k
int cgif_close(CGIF* pGIF) {
611
2.07k
  int         r;
612
2.07k
  cgif_result result;
613
614
  // check for previous errors
615
2.07k
  if(pGIF->curResult != CGIF_OK) {
616
233
    goto CGIF_CLOSE_Cleanup;
617
233
  }
618
619
  // flush all remaining frames in queue
620
4.53k
  for(int i = 1; i < SIZE_FRAME_QUEUE; ++i) {
621
3.52k
    if(pGIF->aFrames[i] != NULL) {
622
3.11k
      r = flushFrame(pGIF, pGIF->aFrames[i], pGIF->aFrames[i - 1]);
623
3.11k
      if(r != CGIF_OK) {
624
837
        pGIF->curResult = r;
625
837
        break;
626
837
      }
627
3.11k
    }
628
3.52k
  }
629
630
  // cleanup
631
2.07k
CGIF_CLOSE_Cleanup:
632
2.07k
  r = cgif_raw_close(pGIF->pGIFRaw); // close raw GIF stream
633
  // check for errors
634
2.07k
  if(r != CGIF_OK) {
635
1.05k
    pGIF->curResult = r;
636
1.05k
  }
637
638
2.07k
  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
8.30k
  for(int i = 0; i < SIZE_FRAME_QUEUE; ++i) {
645
6.23k
    freeFrame(pGIF->aFrames[i]);
646
6.23k
  }
647
648
2.07k
  result = pGIF->curResult;
649
2.07k
  freeCGIF(pGIF);
650
  // catch internal value CGIF_PENDING
651
2.07k
  if(result == CGIF_PENDING) {
652
166
    result = CGIF_ERROR;
653
166
  }
654
2.07k
  return result; // return previous result
655
1.84k
}