Coverage Report

Created: 2025-11-17 06:55

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