Coverage Report

Created: 2025-12-01 06:44

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.28M
#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.3k
#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
925
static uint8_t calcNextPower2Ex(uint16_t n) {
41
925
  uint8_t nextPow2;
42
43
2.22k
  for (nextPow2 = 0; n > (1uL << nextPow2); ++nextPow2);
44
925
  return nextPow2;
45
925
}
46
47
/* write callback. returns 0 on success or -1 on error.  */
48
39.9k
static int writecb(void* pContext, const uint8_t* pData, const size_t numBytes) {
49
39.9k
  CGIF* pGIF;
50
39.9k
  size_t r;
51
52
39.9k
  pGIF = (CGIF*)pContext;
53
39.9k
  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.9k
  } else if(pGIF->config.pWriteFn) {
58
39.9k
    return pGIF->config.pWriteFn(pGIF->config.pContext, pData, numBytes);
59
39.9k
  }
60
0
  return 0;
61
39.9k
}
62
63
/* free space allocated for CGIF struct */
64
1.90k
static void freeCGIF(CGIF* pGIF) {
65
1.90k
  if((pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
66
1.79k
    free(pGIF->config.pGlobalPalette);
67
1.79k
  }
68
1.90k
  free(pGIF);
69
1.90k
}
70
71
/* create a new GIF */
72
1.91k
CGIF* cgif_newgif(CGIF_Config* pConfig) {
73
1.91k
  FILE*          pFile;
74
1.91k
  CGIF*          pGIF;
75
1.91k
  CGIFRaw*       pGIFRaw; // raw GIF stream
76
1.91k
  CGIFRaw_Config rawConfig = {0};
77
  // width or heigth cannot be zero
78
1.91k
  if(!pConfig->width || !pConfig->height) {
79
6
    return NULL;
80
6
  }
81
1.90k
  pFile = NULL;
82
  // open output file (if necessary)
83
1.90k
  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.90k
  pGIF = malloc(sizeof(CGIF));
91
1.90k
  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.90k
  memset(pGIF, 0, sizeof(CGIF));
99
1.90k
  pGIF->pFile = pFile;
100
1.90k
  pGIF->iHEAD = 1;
101
1.90k
  memcpy(&(pGIF->config), pConfig, sizeof(CGIF_Config));
102
  // make a deep copy of global color tabele (GCT), if required.
103
1.90k
  if((pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
104
1.79k
    pGIF->config.pGlobalPalette = malloc(pConfig->numGlobalPaletteEntries * 3);
105
1.79k
    memcpy(pGIF->config.pGlobalPalette, pConfig->pGlobalPalette, pConfig->numGlobalPaletteEntries * 3);
106
1.79k
  }
107
108
1.90k
  rawConfig.pGCT      = pConfig->pGlobalPalette;
109
1.90k
  rawConfig.sizeGCT   = (pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) ? 0 : pConfig->numGlobalPaletteEntries;
110
  // translate CGIF_ATTR_* to CGIF_RAW_ATTR_* flags
111
1.90k
  rawConfig.attrFlags = (pConfig->attrFlags & CGIF_ATTR_IS_ANIMATED) ? CGIF_RAW_ATTR_IS_ANIMATED : 0;
112
1.90k
  rawConfig.attrFlags |= (pConfig->attrFlags & CGIF_ATTR_NO_LOOP) ? CGIF_RAW_ATTR_NO_LOOP : 0;
113
1.90k
  rawConfig.width     = pConfig->width;
114
1.90k
  rawConfig.height    = pConfig->height;
115
1.90k
  rawConfig.numLoops  = pConfig->numLoops;
116
1.90k
  rawConfig.pWriteFn  = writecb;
117
1.90k
  rawConfig.pContext  = (void*)pGIF;
118
  // pass config down and create a new raw GIF stream.
119
1.90k
  pGIFRaw = cgif_raw_newgif(&rawConfig);
120
  // check for errors
121
1.90k
  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.89k
  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.89k
  pGIF->curResult = CGIF_PENDING;
133
1.89k
  return pGIF;
134
1.90k
}
135
136
/* compare given pixel indices using the correct local or global color table; returns 0 if the two pixels are RGB equal */
137
720k
static int cmpPixel(const CGIF* pGIF, const CGIF_FrameConfig* pCur, const CGIF_FrameConfig* pBef, const uint8_t iCur, const uint8_t iBef) {
138
720k
  uint8_t* pBefCT; // color table to use for pBef
139
720k
  uint8_t* pCurCT; // color table to use for pCur
140
141
720k
  if((pCur->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iCur == pCur->transIndex) {
142
24.4k
    return 0; // identical
143
24.4k
  }
144
696k
  if((pBef->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iBef == pBef->transIndex) {
145
7.61k
    return 1; // done: cannot compare
146
7.61k
  }
147
  // safety bounds check
148
688k
  const uint16_t sizeCTBef = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
149
688k
  const uint16_t sizeCTCur = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
150
688k
  if((iBef >= sizeCTBef) || (iCur >= sizeCTCur)) {
151
398k
    return 1; // error: out-of-bounds - cannot compare
152
398k
  }
153
290k
  pBefCT = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
154
290k
  pCurCT = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
155
290k
  return memcmp(pBefCT + iBef * 3, pCurCT + iCur * 3, 3);
156
688k
}
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.03k
static int getDiffArea(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
160
1.03k
  const uint8_t* pCurImageData;
161
1.03k
  const uint8_t* pBefImageData;
162
1.03k
  uint16_t       i, x;
163
1.03k
  uint16_t       newHeight, newWidth, newLeft, newTop;
164
1.03k
  const uint16_t width  = pGIF->config.width;
165
1.03k
  const uint16_t height = pGIF->config.height;
166
1.03k
  uint8_t        iCur, iBef;
167
168
1.03k
  pCurImageData = pCur->pImageData;
169
1.03k
  pBefImageData = pBef->pImageData;
170
  // find top
171
1.03k
  i = 0;
172
49.1k
  while(i < height) {
173
99.4k
    for(int c = 0; c < width; ++c) {
174
51.3k
      iCur = *(pCurImageData + MULU16(i, width) + c);
175
51.3k
      iBef = *(pBefImageData + MULU16(i, width) + c);
176
51.3k
      if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
177
941
        goto FoundTop;
178
941
      }
179
51.3k
    }
180
48.1k
    ++i;
181
48.1k
  }
182
1.03k
FoundTop:
183
1.03k
  if(i == height) {
184
97
    return 0;
185
97
  }
186
941
  newTop = i;
187
188
  // find actual height
189
941
  i = height - 1;
190
3.36k
  while(i > newTop) {
191
5.82k
    for(int c = 0; c < width; ++c) {
192
3.40k
      iCur = *(pCurImageData + MULU16(i, width) + c);
193
3.40k
      iBef = *(pBefImageData + MULU16(i, width) + c);
194
3.40k
      if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
195
387
        goto FoundHeight;
196
387
      }
197
3.40k
    }
198
2.42k
    --i;
199
2.42k
  }
200
941
FoundHeight:
201
941
  newHeight = (i + 1) - newTop;
202
203
  // find left
204
941
  i = newTop;
205
941
  x = 0;
206
2.55k
  while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
207
1.61k
    ++i;
208
1.61k
    if(i > (newTop + newHeight - 1)) {
209
1.35k
      ++x; //(x==width cannot happen as return 0 is trigged in the only possible case before)
210
1.35k
      i = newTop;
211
1.35k
    }
212
1.61k
  }
213
941
  newLeft = x;
214
215
  // find actual width
216
941
  i = newTop;
217
941
  x = width - 1;
218
2.84k
  while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
219
1.90k
    ++i;
220
1.90k
    if(i > (newTop + newHeight - 1)) {
221
1.11k
      --x; //(x<newLeft cannot happen as return 0 is trigged in the only possible case before)
222
1.11k
      i = newTop;
223
1.11k
    }
224
1.90k
  }
225
941
  newWidth = (x + 1) - newLeft;
226
227
941
  pResult->width  = newWidth;
228
941
  pResult->height = newHeight;
229
941
  pResult->top    = newTop;
230
941
  pResult->left   = newLeft;
231
941
  return 1;
232
941
}
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
569
static int getDiffAreaGlobalPalette(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
236
569
  const uint8_t* pCurImageData;
237
569
  const uint8_t* pBefImageData;
238
569
  uint32_t       offset;
239
569
  uint16_t       i, x;
240
569
  uint16_t       newHeight, newWidth, newLeft, newTop;
241
569
  const uint16_t width  = pGIF->config.width;
242
569
  const uint16_t height = pGIF->config.height;
243
244
569
  pCurImageData = pCur->pImageData;
245
569
  pBefImageData = pBef->pImageData;
246
  // find top
247
569
  i = 0;
248
569
  offset = 0;
249
7.97k
  while(i < height) {
250
7.89k
    if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
251
488
      break;
252
488
    }
253
7.40k
    ++i;
254
7.40k
    offset += width;
255
7.40k
  }
256
257
569
  if(i == height) {
258
81
    return 0;
259
81
  }
260
488
  newTop = i;
261
262
  // find actual height
263
488
  i = height - 1;
264
488
  offset = MULU16(i, width);
265
1.77k
  while(i > newTop) {
266
1.50k
    if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
267
224
      break;
268
224
    }
269
1.28k
    --i;
270
1.28k
    offset -= width;
271
1.28k
  }
272
488
  newHeight = (i + 1) - newTop;
273
274
  // find left
275
488
  i = newTop;
276
488
  x = 0;
277
488
  offset = MULU16(i, width);
278
28.0k
  while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
279
27.5k
    ++i;
280
27.5k
    offset += width;
281
27.5k
    if(i > (newTop + newHeight - 1)) {
282
967
      ++x; //(x==width cannot happen as return 0 is triggered in the only possible case before)
283
967
      i = newTop;
284
967
      offset = MULU16(i, width);
285
967
    }
286
27.5k
  }
287
488
  newLeft = x;
288
289
  // find actual width
290
488
  i = newTop;
291
488
  x = width - 1;
292
488
  offset = MULU16(i, width);
293
25.0k
  while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
294
24.5k
    ++i;
295
24.5k
    offset += width;
296
24.5k
    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.5k
  }
302
488
  newWidth = (x + 1) - newLeft;
303
304
488
  pResult->width  = newWidth;
305
488
  pResult->height = newHeight;
306
488
  pResult->top    = newTop;
307
488
  pResult->left   = newLeft;
308
488
  return 1;
309
569
}
310
311
/* optimize GIF file size by only redrawing the rectangular area that differs from previous frame */
312
1.60k
static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult* pResult) {
313
1.60k
  uint16_t i;
314
1.60k
  uint8_t* pNewImageData;
315
1.60k
  const uint16_t width  = pGIF->config.width;
316
1.60k
  const uint8_t* pCurImageData = pCur->pImageData;
317
1.60k
  int diffFrame;
318
319
1.60k
  if ((pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
320
1.12k
      && (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
569
    diffFrame = getDiffAreaGlobalPalette(pGIF, pCur, pBef, pResult);
323
1.03k
  } else {
324
1.03k
    diffFrame = getDiffArea(pGIF, pCur, pBef, pResult);
325
1.03k
  }
326
327
1.60k
  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
178
    pResult->width  = 1;
330
178
    pResult->height = 1;
331
178
    pResult->left   = 0;
332
178
    pResult->top    = 0;
333
178
  }
334
335
  // create new image data
336
1.60k
  pNewImageData = malloc(MULU16(pResult->width, pResult->height)); // TBD check return value of malloc
337
1.40M
  for (i = 0; i < pResult->height; ++i) {
338
1.40M
    memcpy(pNewImageData + MULU16(i, pResult->width), pCurImageData + MULU16((i + pResult->top), width) + pResult->left, pResult->width);
339
1.40M
  }
340
341
1.60k
  return pNewImageData;
342
1.60k
}
343
344
/* move frame down to the raw GIF API */
345
4.32k
static cgif_result flushFrame(CGIF* pGIF, CGIF_Frame* pCur, CGIF_Frame* pBef) {
346
4.32k
  CGIFRaw_FrameConfig rawConfig;
347
4.32k
  DimResult           dimResult;
348
4.32k
  uint8_t*            pTmpImageData;
349
4.32k
  uint8_t*            pBefImageData;
350
4.32k
  int                 isFirstFrame, useLCT, hasAlpha, hasSetTransp;
351
4.32k
  uint16_t            numPaletteEntries;
352
4.32k
  uint16_t            imageWidth, imageHeight, width, height, top, left;
353
4.32k
  uint8_t             transIndex, disposalMethod;
354
4.32k
  cgif_result         r;
355
356
4.32k
  imageWidth     = pGIF->config.width;
357
4.32k
  imageHeight    = pGIF->config.height;
358
4.32k
  isFirstFrame   = (pBef == NULL) ? 1 : 0;
359
4.32k
  useLCT         = (pCur->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? 1 : 0; // LCT stands for "local color table"
360
4.32k
  hasAlpha       = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0;
361
4.32k
  hasSetTransp   = (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0;
362
4.32k
  disposalMethod = pCur->disposalMethod;
363
4.32k
  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.32k
  if(isFirstFrame || hasAlpha) {
368
2.02k
    pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
369
2.02k
  }
370
  // transparency setting (which areas are identical to the frame before) provided by user:
371
  // CGIF_FRAME_GEN_USE_TRANSPARENCY not possible
372
4.32k
  if(hasSetTransp) {
373
1.14k
    pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY);
374
1.14k
  }
375
4.32k
  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.32k
  if(numPaletteEntries == 256) {
378
161
    pCur->config.genFlags &= ~CGIF_FRAME_GEN_USE_TRANSPARENCY;
379
161
  }
380
381
  // purge overlap of current frame and frame before (width - height optim), if required (CGIF_FRAME_GEN_USE_DIFF_WINDOW set)
382
4.32k
  if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_DIFF_WINDOW) {
383
1.60k
    pTmpImageData = doWidthHeightOptim(pGIF, &pCur->config, &pBef->config, &dimResult);
384
1.60k
    width  = dimResult.width;
385
1.60k
    height = dimResult.height;
386
1.60k
    top    = dimResult.top;
387
1.60k
    left   = dimResult.left;
388
2.71k
  } else {
389
2.71k
    pTmpImageData = NULL;
390
2.71k
    width         = imageWidth;
391
2.71k
    height        = imageHeight;
392
2.71k
    top           = 0;
393
2.71k
    left          = 0;
394
2.71k
  }
395
396
  // mark matching areas of the previous frame as transparent, if required (CGIF_FRAME_GEN_USE_TRANSPARENCY set)
397
4.32k
  if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) {
398
    // set transIndex to next free index
399
925
    int pow2 = calcNextPower2Ex(numPaletteEntries);
400
925
    pow2 = (pow2 < 2) ? 2 : pow2; // TBD keep transparency index behavior as in V0.1.0 (for now)
401
925
    transIndex = (1 << pow2) - 1;
402
925
    if(transIndex < numPaletteEntries) {
403
92
      transIndex = (1 << (pow2 + 1)) - 1;
404
92
    }
405
925
    if(pTmpImageData == NULL) {
406
181
      pTmpImageData = malloc(MULU16(imageWidth, imageHeight)); // TBD check return value of malloc
407
181
      memcpy(pTmpImageData, pCur->config.pImageData, MULU16(imageWidth, imageHeight));
408
181
    }
409
925
    pBefImageData = pBef->config.pImageData;
410
343k
    for(int i = 0; i < height; ++i) {
411
954k
      for(int x = 0; x < width; ++x) {
412
611k
        if(cmpPixel(pGIF, &pCur->config, &pBef->config, pTmpImageData[MULU16(i, width) + x], pBefImageData[MULU16(top + i, imageWidth) + (left + x)]) == 0) {
413
115k
          pTmpImageData[MULU16(i, width) + x] = transIndex;
414
115k
        }
415
611k
      }
416
342k
    }
417
925
  }
418
419
  // move frame down to GIF raw API
420
4.32k
  rawConfig.pLCT           = pCur->config.pLocalPalette;
421
4.32k
  rawConfig.pImageData     = (pTmpImageData) ? pTmpImageData : pCur->config.pImageData;
422
4.32k
  rawConfig.attrFlags      = 0;
423
4.32k
  if(hasAlpha || (pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) || hasSetTransp) {
424
2.87k
    rawConfig.attrFlags |= CGIF_RAW_FRAME_ATTR_HAS_TRANS;
425
2.87k
  }
426
4.32k
  rawConfig.attrFlags |= (pCur->config.attrFlags & CGIF_FRAME_ATTR_INTERLACED) ? CGIF_RAW_FRAME_ATTR_INTERLACED : 0;
427
4.32k
  rawConfig.width          = width;
428
4.32k
  rawConfig.height         = height;
429
4.32k
  rawConfig.top            = top;
430
4.32k
  rawConfig.left           = left;
431
4.32k
  rawConfig.delay          = pCur->config.delay;
432
4.32k
  rawConfig.sizeLCT        = (useLCT) ? pCur->config.numLocalPaletteEntries : 0;
433
4.32k
  rawConfig.disposalMethod = disposalMethod;
434
4.32k
  rawConfig.transIndex     = transIndex;
435
4.32k
  r = cgif_raw_addframe(pGIF->pGIFRaw, &rawConfig);
436
4.32k
  free(pTmpImageData);
437
4.32k
  return r;
438
4.32k
}
439
440
7.19k
static void freeFrame(CGIF_Frame* pFrame) {
441
7.19k
  if(pFrame) {
442
4.49k
    free(pFrame->config.pImageData);
443
4.49k
    if(pFrame->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
444
907
      free(pFrame->config.pLocalPalette);
445
907
    }
446
4.49k
    free(pFrame);
447
4.49k
  }
448
7.19k
}
449
450
4.49k
static void copyFrameConfig(CGIF_FrameConfig* pDest, CGIF_FrameConfig* pSrc) {
451
4.49k
  pDest->pLocalPalette          = pSrc->pLocalPalette; // might need a deep copy
452
4.49k
  pDest->pImageData             = pSrc->pImageData;    // might need a deep copy
453
4.49k
  pDest->attrFlags              = pSrc->attrFlags;
454
4.49k
  pDest->genFlags               = pSrc->genFlags;
455
4.49k
  pDest->delay                  = pSrc->delay;
456
4.49k
  pDest->numLocalPaletteEntries = pSrc->numLocalPaletteEntries;
457
  // copy transIndex if necessary (field added with V0.2.0; avoid binary incompatibility)
458
4.49k
  if(pSrc->attrFlags & (CGIF_FRAME_ATTR_HAS_ALPHA | CGIF_FRAME_ATTR_HAS_SET_TRANS)) {
459
1.85k
    pDest->transIndex = pSrc->transIndex;
460
1.85k
  }
461
4.49k
}
462
463
/* queue a new GIF frame */
464
5.11k
int cgif_addframe(CGIF* pGIF, CGIF_FrameConfig* pConfig) {
465
5.11k
  CGIF_Frame* pNewFrame;
466
5.11k
  int         hasAlpha, hasSetTransp;
467
5.11k
  int         i;
468
5.11k
  cgif_result r;
469
470
  // check for previous errors
471
5.11k
  if(pGIF->curResult != CGIF_OK && pGIF->curResult != CGIF_PENDING) {
472
368
    return pGIF->curResult;
473
368
  }
474
4.75k
  hasAlpha     = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0; // alpha channel is present
475
4.75k
  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.75k
  if(hasAlpha && hasSetTransp) {
479
22
    pGIF->curResult = CGIF_ERROR;
480
22
    return pGIF->curResult;
481
22
  }
482
  // cannot set global and local alpha channel at the same time
483
4.72k
  if((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) {
484
6
    pGIF->curResult = CGIF_ERROR;
485
6
    return pGIF->curResult;
486
6
  }
487
  // sanity check:
488
  // at least one valid CT needed (global or local)
489
4.72k
  if(!(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) && (pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE)) {
490
4
    pGIF->curResult = CGIF_ERROR;
491
4
    return CGIF_ERROR; // invalid config
492
4
  }
493
494
  // if frame matches previous frame, drop it completely and sum the frame delay
495
4.71k
  if(pGIF->aFrames[pGIF->iHEAD] != NULL) {
496
2.98k
    const uint32_t frameDelay = pConfig->delay + pGIF->aFrames[pGIF->iHEAD]->config.delay;
497
2.98k
    if(frameDelay <= 0xFFFF && !(pGIF->config.genFlags & CGIF_GEN_KEEP_IDENT_FRAMES)) {
498
1.59k
      int sameFrame = 1;
499
1.59k
      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.06k
          && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0) {
501
628
        if (memcmp(pConfig->pImageData, pGIF->aFrames[pGIF->iHEAD]->config.pImageData, MULU16(pGIF->config.width, pGIF->config.height))) {
502
567
          sameFrame = 0;
503
567
        }
504
970
      } else {
505
48.9k
        for(i = 0; i < pGIF->config.width * pGIF->config.height; i++) {
506
48.8k
          if(cmpPixel(pGIF, pConfig, &pGIF->aFrames[pGIF->iHEAD]->config, pConfig->pImageData[i], pGIF->aFrames[pGIF->iHEAD]->config.pImageData[i])) {
507
854
            sameFrame = 0;
508
854
            break;
509
854
          }
510
48.8k
        }
511
970
      }
512
513
1.59k
      if (sameFrame) {
514
177
        pGIF->aFrames[pGIF->iHEAD]->config.delay = frameDelay;
515
177
        return CGIF_OK;
516
177
      }
517
1.59k
    }
518
2.98k
  }
519
520
  // search for free slot in frame queue
521
7.34k
  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.54k
  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
50
      pGIF->curResult = r;
531
50
      return pGIF->curResult;
532
50
    }
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.49k
  pNewFrame = malloc(sizeof(CGIF_Frame));
540
4.49k
  copyFrameConfig(&(pNewFrame->config), pConfig);
541
4.49k
  pNewFrame->config.pImageData = malloc(MULU16(pGIF->config.width, pGIF->config.height));
542
4.49k
  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.49k
  if(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
545
907
    pNewFrame->config.pLocalPalette  = malloc(pConfig->numLocalPaletteEntries * 3);
546
907
    memcpy(pNewFrame->config.pLocalPalette, pConfig->pLocalPalette, pConfig->numLocalPaletteEntries * 3);
547
907
  }
548
4.49k
  pNewFrame->disposalMethod        = DISPOSAL_METHOD_LEAVE;
549
4.49k
  pNewFrame->transIndex            = 0;
550
4.49k
  pGIF->aFrames[i]                 = pNewFrame; // add frame to queue
551
4.49k
  pGIF->iHEAD                      = i;         // update HEAD index
552
  // check whether we need to adapt the disposal method of the frame before.
553
4.49k
  if(pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) {
554
148
    pGIF->aFrames[i]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // TBD might be removed
555
148
    pGIF->aFrames[i]->transIndex     = 0;
556
148
    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
148
  }
561
  // set per-frame alpha channel (we need to adapt the disposal method of the frame before)
562
4.49k
  if(pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA) {
563
683
    pGIF->aFrames[i]->transIndex = pConfig->transIndex;
564
683
    if(pGIF->aFrames[i - 1] != NULL) {
565
216
      pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_DIFF_WINDOW); // width/height optim not possible for frame before
566
216
      pGIF->aFrames[i - 1]->disposalMethod   = DISPOSAL_METHOD_BACKGROUND; // restore to background color
567
216
    }
568
683
  }
569
  // user provided transparency setting
570
4.49k
  if(hasSetTransp) {
571
1.16k
    pGIF->aFrames[i]->transIndex = pConfig->transIndex;
572
1.16k
  }
573
4.49k
  pGIF->curResult = CGIF_OK;
574
4.49k
  return pGIF->curResult;
575
4.54k
}
576
577
/* close the GIF-file and free allocated space */
578
1.89k
int cgif_close(CGIF* pGIF) {
579
1.89k
  int         r;
580
1.89k
  cgif_result result;
581
582
  // check for previous errors
583
1.89k
  if(pGIF->curResult != CGIF_OK) {
584
226
    goto CGIF_CLOSE_Cleanup;
585
226
  }
586
587
  // flush all remaining frames in queue
588
4.09k
  for(int i = 1; i < SIZE_FRAME_QUEUE; ++i) {
589
3.18k
    if(pGIF->aFrames[i] != NULL) {
590
2.80k
      r = flushFrame(pGIF, pGIF->aFrames[i], pGIF->aFrames[i - 1]);
591
2.80k
      if(r != CGIF_OK) {
592
762
        pGIF->curResult = r;
593
762
        break;
594
762
      }
595
2.80k
    }
596
3.18k
  }
597
598
  // cleanup
599
1.89k
CGIF_CLOSE_Cleanup:
600
1.89k
  r = cgif_raw_close(pGIF->pGIFRaw); // close raw GIF stream
601
  // check for errors
602
1.89k
  if(r != CGIF_OK) {
603
976
    pGIF->curResult = r;
604
976
  }
605
606
1.89k
  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.56k
  for(int i = 0; i < SIZE_FRAME_QUEUE; ++i) {
613
5.67k
    freeFrame(pGIF->aFrames[i]);
614
5.67k
  }
615
616
1.89k
  result = pGIF->curResult;
617
1.89k
  freeCGIF(pGIF);
618
  // catch internal value CGIF_PENDING
619
1.89k
  if(result == CGIF_PENDING) {
620
164
    result = CGIF_ERROR;
621
164
  }
622
1.89k
  return result; // return previous result
623
1.66k
}