Coverage Report

Created: 2025-12-31 06:25

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
10.0M
#define MULU16(a, b) (((uint32_t)a) * ((uint32_t)b)) // helper macro to correctly multiply two U16's without default signed int promotion
10
87.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
1.82k
static uint8_t calcNextPower2Ex(uint16_t n) {
41
1.82k
  uint8_t nextPow2;
42
43
4.21k
  for (nextPow2 = 0; n > (1uL << nextPow2); ++nextPow2);
44
1.82k
  return nextPow2;
45
1.82k
}
46
47
/* write callback. returns 0 on success or -1 on error.  */
48
152k
static int writecb(void* pContext, const uint8_t* pData, const size_t numBytes) {
49
152k
  CGIF* pGIF;
50
152k
  size_t r;
51
52
152k
  pGIF = (CGIF*)pContext;
53
152k
  if(pGIF->pFile) {
54
48.6k
    r = fwrite(pData, 1, numBytes, pGIF->pFile);
55
48.6k
    if(r == numBytes) return 0;
56
0
    else return -1;
57
103k
  } else if(pGIF->config.pWriteFn) {
58
103k
    return pGIF->config.pWriteFn(pGIF->config.pContext, pData, numBytes);
59
103k
  }
60
0
  return 0;
61
152k
}
62
63
/* free space allocated for CGIF struct */
64
5.13k
static void freeCGIF(CGIF* pGIF) {
65
5.13k
  if((pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
66
3.79k
    free(pGIF->config.pGlobalPalette);
67
3.79k
  }
68
5.13k
  free(pGIF);
69
5.13k
}
70
71
/* create a new GIF */
72
5.16k
CGIF* cgif_newgif(CGIF_Config* pConfig) {
73
5.16k
  FILE*          pFile;
74
5.16k
  CGIF*          pGIF;
75
5.16k
  CGIFRaw*       pGIFRaw; // raw GIF stream
76
5.16k
  CGIFRaw_Config rawConfig = {0};
77
  // width or heigth cannot be zero
78
5.16k
  if(!pConfig->width || !pConfig->height) {
79
32
    return NULL;
80
32
  }
81
5.13k
  pFile = NULL;
82
  // open output file (if necessary)
83
5.13k
  if(pConfig->path) {
84
2.00k
    pFile = fopen(pConfig->path, "wb");
85
2.00k
    if(pFile == NULL) {
86
0
      return NULL; // error: fopen failed
87
0
    }
88
2.00k
  }
89
  // allocate space for CGIF context
90
5.13k
  pGIF = malloc(sizeof(CGIF));
91
5.13k
  if(pGIF == NULL) {
92
0
    if(pFile) {
93
0
      fclose(pFile);
94
0
    }
95
0
    return NULL; // error -> malloc failed
96
0
  }
97
98
5.13k
  memset(pGIF, 0, sizeof(CGIF));
99
5.13k
  pGIF->pFile = pFile;
100
5.13k
  pGIF->iHEAD = 1;
101
5.13k
  memcpy(&(pGIF->config), pConfig, sizeof(CGIF_Config));
102
  // make a deep copy of global color tabele (GCT), if required.
103
5.13k
  if((pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
104
3.79k
    pGIF->config.pGlobalPalette = malloc(pConfig->numGlobalPaletteEntries * 3);
105
3.79k
    memcpy(pGIF->config.pGlobalPalette, pConfig->pGlobalPalette, pConfig->numGlobalPaletteEntries * 3);
106
3.79k
  }
107
108
5.13k
  rawConfig.pGCT      = pConfig->pGlobalPalette;
109
5.13k
  rawConfig.sizeGCT   = (pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) ? 0 : pConfig->numGlobalPaletteEntries;
110
  // translate CGIF_ATTR_* to CGIF_RAW_ATTR_* flags
111
5.13k
  rawConfig.attrFlags = (pConfig->attrFlags & CGIF_ATTR_IS_ANIMATED) ? CGIF_RAW_ATTR_IS_ANIMATED : 0;
112
5.13k
  rawConfig.attrFlags |= (pConfig->attrFlags & CGIF_ATTR_NO_LOOP) ? CGIF_RAW_ATTR_NO_LOOP : 0;
113
5.13k
  rawConfig.width     = pConfig->width;
114
5.13k
  rawConfig.height    = pConfig->height;
115
5.13k
  rawConfig.numLoops  = pConfig->numLoops;
116
5.13k
  rawConfig.pWriteFn  = writecb;
117
5.13k
  rawConfig.pContext  = (void*)pGIF;
118
  // pass config down and create a new raw GIF stream.
119
5.13k
  pGIFRaw = cgif_raw_newgif(&rawConfig);
120
  // check for errors
121
5.13k
  if(pGIFRaw == NULL) {
122
24
    if(pFile) {
123
11
      fclose(pFile);
124
11
    }
125
24
    freeCGIF(pGIF);
126
24
    return NULL;
127
24
  }
128
129
5.11k
  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
5.11k
  pGIF->curResult = CGIF_PENDING;
133
5.11k
  return pGIF;
134
5.13k
}
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.91M
static int cmpPixel(const CGIF* pGIF, const CGIF_FrameConfig* pCur, const CGIF_FrameConfig* pBef, const uint8_t iCur, const uint8_t iBef) {
138
1.91M
  uint8_t* pBefCT; // color table to use for pBef
139
1.91M
  uint8_t* pCurCT; // color table to use for pCur
140
141
1.91M
  if((pCur->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iCur == pCur->transIndex) {
142
50.7k
    return 0; // identical
143
50.7k
  }
144
1.86M
  if((pBef->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iBef == pBef->transIndex) {
145
9.32k
    return 1; // done: cannot compare
146
9.32k
  }
147
  // safety bounds check
148
1.85M
  const uint16_t sizeCTBef = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
149
1.85M
  const uint16_t sizeCTCur = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
150
1.85M
  if((iBef >= sizeCTBef) || (iCur >= sizeCTCur)) {
151
892k
    return 1; // error: out-of-bounds - cannot compare
152
892k
  }
153
966k
  pBefCT = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
154
966k
  pCurCT = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
155
966k
  return memcmp(pBefCT + iBef * 3, pCurCT + iCur * 3, 3);
156
1.85M
}
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
2.16k
static int getDiffArea(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
160
2.16k
  const uint8_t* pCurImageData;
161
2.16k
  const uint8_t* pBefImageData;
162
2.16k
  uint16_t       i, x;
163
2.16k
  uint16_t       newHeight, newWidth, newLeft, newTop;
164
2.16k
  const uint16_t width  = pGIF->config.width;
165
2.16k
  const uint16_t height = pGIF->config.height;
166
2.16k
  uint8_t        iCur, iBef;
167
168
2.16k
  pCurImageData = pCur->pImageData;
169
2.16k
  pBefImageData = pBef->pImageData;
170
  // find top
171
2.16k
  i = 0;
172
78.5k
  while(i < height) {
173
158k
    for(int c = 0; c < width; ++c) {
174
81.8k
      iCur = *(pCurImageData + MULU16(i, width) + c);
175
81.8k
      iBef = *(pBefImageData + MULU16(i, width) + c);
176
81.8k
      if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
177
1.98k
        goto FoundTop;
178
1.98k
      }
179
81.8k
    }
180
76.3k
    ++i;
181
76.3k
  }
182
2.16k
FoundTop:
183
2.16k
  if(i == height) {
184
187
    return 0;
185
187
  }
186
1.98k
  newTop = i;
187
188
  // find actual height
189
1.98k
  i = height - 1;
190
13.1k
  while(i > newTop) {
191
24.3k
    for(int c = 0; c < width; ++c) {
192
13.1k
      iCur = *(pCurImageData + MULU16(i, width) + c);
193
13.1k
      iBef = *(pBefImageData + MULU16(i, width) + c);
194
13.1k
      if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
195
774
        goto FoundHeight;
196
774
      }
197
13.1k
    }
198
11.1k
    --i;
199
11.1k
  }
200
1.98k
FoundHeight:
201
1.98k
  newHeight = (i + 1) - newTop;
202
203
  // find left
204
1.98k
  i = newTop;
205
1.98k
  x = 0;
206
5.26k
  while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
207
3.27k
    ++i;
208
3.27k
    if(i > (newTop + newHeight - 1)) {
209
2.38k
      ++x; //(x==width cannot happen as return 0 is trigged in the only possible case before)
210
2.38k
      i = newTop;
211
2.38k
    }
212
3.27k
  }
213
1.98k
  newLeft = x;
214
215
  // find actual width
216
1.98k
  i = newTop;
217
1.98k
  x = width - 1;
218
4.93k
  while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
219
2.95k
    ++i;
220
2.95k
    if(i > (newTop + newHeight - 1)) {
221
2.36k
      --x; //(x<newLeft cannot happen as return 0 is trigged in the only possible case before)
222
2.36k
      i = newTop;
223
2.36k
    }
224
2.95k
  }
225
1.98k
  newWidth = (x + 1) - newLeft;
226
227
1.98k
  pResult->width  = newWidth;
228
1.98k
  pResult->height = newHeight;
229
1.98k
  pResult->top    = newTop;
230
1.98k
  pResult->left   = newLeft;
231
1.98k
  return 1;
232
1.98k
}
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
1.14k
static int getDiffAreaGlobalPalette(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult *pResult) {
236
1.14k
  const uint8_t* pCurImageData;
237
1.14k
  const uint8_t* pBefImageData;
238
1.14k
  uint32_t       offset;
239
1.14k
  uint16_t       i, x;
240
1.14k
  uint16_t       newHeight, newWidth, newLeft, newTop;
241
1.14k
  const uint16_t width  = pGIF->config.width;
242
1.14k
  const uint16_t height = pGIF->config.height;
243
244
1.14k
  pCurImageData = pCur->pImageData;
245
1.14k
  pBefImageData = pBef->pImageData;
246
  // find top
247
1.14k
  i = 0;
248
1.14k
  offset = 0;
249
26.6k
  while(i < height) {
250
26.5k
    if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
251
1.04k
      break;
252
1.04k
    }
253
25.5k
    ++i;
254
25.5k
    offset += width;
255
25.5k
  }
256
257
1.14k
  if(i == height) {
258
105
    return 0;
259
105
  }
260
1.04k
  newTop = i;
261
262
  // find actual height
263
1.04k
  i = height - 1;
264
1.04k
  offset = MULU16(i, width);
265
7.68k
  while(i > newTop) {
266
7.13k
    if (memcmp(pCurImageData + offset, pBefImageData + offset, width)) {
267
493
      break;
268
493
    }
269
6.64k
    --i;
270
6.64k
    offset -= width;
271
6.64k
  }
272
1.04k
  newHeight = (i + 1) - newTop;
273
274
  // find left
275
1.04k
  i = newTop;
276
1.04k
  x = 0;
277
1.04k
  offset = MULU16(i, width);
278
35.4k
  while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
279
34.3k
    ++i;
280
34.3k
    offset += width;
281
34.3k
    if(i > (newTop + newHeight - 1)) {
282
1.94k
      ++x; //(x==width cannot happen as return 0 is triggered in the only possible case before)
283
1.94k
      i = newTop;
284
1.94k
      offset = MULU16(i, width);
285
1.94k
    }
286
34.3k
  }
287
1.04k
  newLeft = x;
288
289
  // find actual width
290
1.04k
  i = newTop;
291
1.04k
  x = width - 1;
292
1.04k
  offset = MULU16(i, width);
293
30.5k
  while(pCurImageData[offset + x] == pBefImageData[offset + x]) {
294
29.5k
    ++i;
295
29.5k
    offset += width;
296
29.5k
    if(i > (newTop + newHeight - 1)) {
297
869
      --x; //(x<newLeft cannot happen as return 0 is triggered in the only possible case before)
298
869
      i = newTop;
299
869
      offset = MULU16(i, width);
300
869
    }
301
29.5k
  }
302
1.04k
  newWidth = (x + 1) - newLeft;
303
304
1.04k
  pResult->width  = newWidth;
305
1.04k
  pResult->height = newHeight;
306
1.04k
  pResult->top    = newTop;
307
1.04k
  pResult->left   = newLeft;
308
1.04k
  return 1;
309
1.14k
}
310
311
/* optimize GIF file size by only redrawing the rectangular area that differs from previous frame */
312
3.31k
static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult* pResult) {
313
3.31k
  uint16_t i;
314
3.31k
  uint8_t* pNewImageData;
315
3.31k
  const uint16_t width  = pGIF->config.width;
316
3.31k
  const uint8_t* pCurImageData = pCur->pImageData;
317
3.31k
  int diffFrame;
318
319
3.31k
  if ((pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
320
2.23k
      && (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
1.14k
    diffFrame = getDiffAreaGlobalPalette(pGIF, pCur, pBef, pResult);
323
2.16k
  } else {
324
2.16k
    diffFrame = getDiffArea(pGIF, pCur, pBef, pResult);
325
2.16k
  }
326
327
3.31k
  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
292
    pResult->width  = 1;
330
292
    pResult->height = 1;
331
292
    pResult->left   = 0;
332
292
    pResult->top    = 0;
333
292
  }
334
335
  // create new image data
336
3.31k
  pNewImageData = malloc(MULU16(pResult->width, pResult->height)); // TBD check return value of malloc
337
2.94M
  for (i = 0; i < pResult->height; ++i) {
338
2.94M
    memcpy(pNewImageData + MULU16(i, pResult->width), pCurImageData + MULU16((i + pResult->top), width) + pResult->left, pResult->width);
339
2.94M
  }
340
341
3.31k
  return pNewImageData;
342
3.31k
}
343
344
/* move frame down to the raw GIF API */
345
11.5k
static cgif_result flushFrame(CGIF* pGIF, CGIF_Frame* pCur, CGIF_Frame* pBef) {
346
11.5k
  CGIFRaw_FrameConfig rawConfig;
347
11.5k
  DimResult           dimResult;
348
11.5k
  uint8_t*            pTmpImageData;
349
11.5k
  uint8_t*            pBefImageData;
350
11.5k
  int                 isFirstFrame, useLCT, hasAlpha, hasSetTransp;
351
11.5k
  uint16_t            numPaletteEntries;
352
11.5k
  uint16_t            imageWidth, imageHeight, width, height, top, left;
353
11.5k
  uint8_t             transIndex, disposalMethod;
354
11.5k
  cgif_result         r;
355
356
11.5k
  imageWidth     = pGIF->config.width;
357
11.5k
  imageHeight    = pGIF->config.height;
358
11.5k
  isFirstFrame   = (pBef == NULL) ? 1 : 0;
359
11.5k
  useLCT         = (pCur->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? 1 : 0; // LCT stands for "local color table"
360
11.5k
  hasAlpha       = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0;
361
11.5k
  hasSetTransp   = (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0;
362
11.5k
  disposalMethod = pCur->disposalMethod;
363
11.5k
  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
11.5k
  if(isFirstFrame || hasAlpha) {
368
5.75k
    pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
369
5.75k
  }
370
  // transparency setting (which areas are identical to the frame before) provided by user:
371
  // CGIF_FRAME_GEN_USE_TRANSPARENCY not possible
372
11.5k
  if(hasSetTransp) {
373
4.31k
    pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY);
374
4.31k
  }
375
11.5k
  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
11.5k
  if(numPaletteEntries == 256) {
378
450
    pCur->config.genFlags &= ~CGIF_FRAME_GEN_USE_TRANSPARENCY;
379
450
  }
380
381
  // purge overlap of current frame and frame before (width - height optim), if required (CGIF_FRAME_GEN_USE_DIFF_WINDOW set)
382
11.5k
  if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_DIFF_WINDOW) {
383
3.31k
    pTmpImageData = doWidthHeightOptim(pGIF, &pCur->config, &pBef->config, &dimResult);
384
3.31k
    width  = dimResult.width;
385
3.31k
    height = dimResult.height;
386
3.31k
    top    = dimResult.top;
387
3.31k
    left   = dimResult.left;
388
8.25k
  } else {
389
8.25k
    pTmpImageData = NULL;
390
8.25k
    width         = imageWidth;
391
8.25k
    height        = imageHeight;
392
8.25k
    top           = 0;
393
8.25k
    left          = 0;
394
8.25k
  }
395
396
  // mark matching areas of the previous frame as transparent, if required (CGIF_FRAME_GEN_USE_TRANSPARENCY set)
397
11.5k
  if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) {
398
    // set transIndex to next free index
399
1.82k
    int pow2 = calcNextPower2Ex(numPaletteEntries);
400
1.82k
    pow2 = (pow2 < 2) ? 2 : pow2; // TBD keep transparency index behavior as in V0.1.0 (for now)
401
1.82k
    transIndex = (1 << pow2) - 1;
402
1.82k
    if(transIndex < numPaletteEntries) {
403
187
      transIndex = (1 << (pow2 + 1)) - 1;
404
187
    }
405
1.82k
    if(pTmpImageData == NULL) {
406
395
      pTmpImageData = malloc(MULU16(imageWidth, imageHeight)); // TBD check return value of malloc
407
395
      memcpy(pTmpImageData, pCur->config.pImageData, MULU16(imageWidth, imageHeight));
408
395
    }
409
1.82k
    pBefImageData = pBef->config.pImageData;
410
960k
    for(int i = 0; i < height; ++i) {
411
2.69M
      for(int x = 0; x < width; ++x) {
412
1.73M
        if(cmpPixel(pGIF, &pCur->config, &pBef->config, pTmpImageData[MULU16(i, width) + x], pBefImageData[MULU16(top + i, imageWidth) + (left + x)]) == 0) {
413
414k
          pTmpImageData[MULU16(i, width) + x] = transIndex;
414
414k
        }
415
1.73M
      }
416
958k
    }
417
1.82k
  }
418
419
  // move frame down to GIF raw API
420
11.5k
  rawConfig.pLCT           = pCur->config.pLocalPalette;
421
11.5k
  rawConfig.pImageData     = (pTmpImageData) ? pTmpImageData : pCur->config.pImageData;
422
11.5k
  rawConfig.attrFlags      = 0;
423
11.5k
  if(hasAlpha || (pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) || hasSetTransp) {
424
8.26k
    rawConfig.attrFlags |= CGIF_RAW_FRAME_ATTR_HAS_TRANS;
425
8.26k
  }
426
11.5k
  rawConfig.attrFlags |= (pCur->config.attrFlags & CGIF_FRAME_ATTR_INTERLACED) ? CGIF_RAW_FRAME_ATTR_INTERLACED : 0;
427
11.5k
  rawConfig.width          = width;
428
11.5k
  rawConfig.height         = height;
429
11.5k
  rawConfig.top            = top;
430
11.5k
  rawConfig.left           = left;
431
11.5k
  rawConfig.delay          = pCur->config.delay;
432
11.5k
  rawConfig.sizeLCT        = (useLCT) ? pCur->config.numLocalPaletteEntries : 0;
433
11.5k
  rawConfig.disposalMethod = disposalMethod;
434
11.5k
  rawConfig.transIndex     = transIndex;
435
11.5k
  r = cgif_raw_addframe(pGIF->pGIFRaw, &rawConfig);
436
11.5k
  free(pTmpImageData);
437
11.5k
  return r;
438
11.5k
}
439
440
19.5k
static void freeFrame(CGIF_Frame* pFrame) {
441
19.5k
  if(pFrame) {
442
11.9k
    free(pFrame->config.pImageData);
443
11.9k
    if(pFrame->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
444
4.24k
      free(pFrame->config.pLocalPalette);
445
4.24k
    }
446
11.9k
    free(pFrame);
447
11.9k
  }
448
19.5k
}
449
450
11.9k
static void copyFrameConfig(CGIF_FrameConfig* pDest, CGIF_FrameConfig* pSrc) {
451
11.9k
  pDest->pLocalPalette          = pSrc->pLocalPalette; // might need a deep copy
452
11.9k
  pDest->pImageData             = pSrc->pImageData;    // might need a deep copy
453
11.9k
  pDest->attrFlags              = pSrc->attrFlags;
454
11.9k
  pDest->genFlags               = pSrc->genFlags;
455
11.9k
  pDest->delay                  = pSrc->delay;
456
11.9k
  pDest->numLocalPaletteEntries = pSrc->numLocalPaletteEntries;
457
  // copy transIndex if necessary (field added with V0.2.0; avoid binary incompatibility)
458
11.9k
  if(pSrc->attrFlags & (CGIF_FRAME_ATTR_HAS_ALPHA | CGIF_FRAME_ATTR_HAS_SET_TRANS)) {
459
6.28k
    pDest->transIndex = pSrc->transIndex;
460
6.28k
  }
461
11.9k
}
462
463
/* queue a new GIF frame */
464
13.1k
int cgif_addframe(CGIF* pGIF, CGIF_FrameConfig* pConfig) {
465
13.1k
  CGIF_Frame* pNewFrame;
466
13.1k
  int         hasAlpha, hasSetTransp;
467
13.1k
  int         i;
468
13.1k
  cgif_result r;
469
470
  // check for previous errors
471
13.1k
  if(pGIF->curResult != CGIF_OK && pGIF->curResult != CGIF_PENDING) {
472
535
    return pGIF->curResult;
473
535
  }
474
12.5k
  hasAlpha     = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0; // alpha channel is present
475
12.5k
  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
12.5k
  if(hasAlpha && hasSetTransp) {
479
40
    pGIF->curResult = CGIF_ERROR;
480
40
    return pGIF->curResult;
481
40
  }
482
  // cannot set global and local alpha channel at the same time
483
12.5k
  if((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) {
484
9
    pGIF->curResult = CGIF_ERROR;
485
9
    return pGIF->curResult;
486
9
  }
487
  // sanity check:
488
  // at least one valid CT needed (global or local)
489
12.5k
  if(!(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) && (pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE)) {
490
14
    pGIF->curResult = CGIF_ERROR;
491
14
    return CGIF_ERROR; // invalid config
492
14
  }
493
494
  // if frame matches previous frame, drop it completely and sum the frame delay
495
12.5k
  if(pGIF->aFrames[pGIF->iHEAD] != NULL) {
496
7.83k
    const uint32_t frameDelay = pConfig->delay + pGIF->aFrames[pGIF->iHEAD]->config.delay;
497
7.83k
    if(frameDelay <= 0xFFFF && !(pGIF->config.genFlags & CGIF_GEN_KEEP_IDENT_FRAMES)) {
498
4.58k
      int sameFrame = 1;
499
4.58k
      if ((pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) == 0
500
2.10k
          && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0 && (pGIF->aFrames[pGIF->iHEAD]->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) == 0) {
501
1.27k
        if (memcmp(pConfig->pImageData, pGIF->aFrames[pGIF->iHEAD]->config.pImageData, MULU16(pGIF->config.width, pGIF->config.height))) {
502
1.15k
          sameFrame = 0;
503
1.15k
        }
504
3.31k
      } else {
505
80.1k
        for(i = 0; i < pGIF->config.width * pGIF->config.height; i++) {
506
79.7k
          if(cmpPixel(pGIF, pConfig, &pGIF->aFrames[pGIF->iHEAD]->config, pConfig->pImageData[i], pGIF->aFrames[pGIF->iHEAD]->config.pImageData[i])) {
507
2.93k
            sameFrame = 0;
508
2.93k
            break;
509
2.93k
          }
510
79.7k
        }
511
3.31k
      }
512
513
4.58k
      if (sameFrame) {
514
498
        pGIF->aFrames[pGIF->iHEAD]->config.delay = frameDelay;
515
498
        return CGIF_OK;
516
498
      }
517
4.58k
    }
518
7.83k
  }
519
520
  // search for free slot in frame queue
521
19.3k
  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
12.0k
  if(i == SIZE_FRAME_QUEUE) {
525
4.21k
    r = flushFrame(pGIF, pGIF->aFrames[1], pGIF->aFrames[0]);
526
4.21k
    freeFrame(pGIF->aFrames[0]);
527
4.21k
    pGIF->aFrames[0] = NULL; // avoid potential double free in cgif_close
528
    // check for errors
529
4.21k
    if(r != CGIF_OK) {
530
94
      pGIF->curResult = r;
531
94
      return pGIF->curResult;
532
94
    }
533
4.12k
    i = SIZE_FRAME_QUEUE - 1;
534
    // keep the flushed frame in memory, as we might need it to write the next one.
535
4.12k
    pGIF->aFrames[0] = pGIF->aFrames[1];
536
4.12k
    pGIF->aFrames[1] = pGIF->aFrames[2];
537
4.12k
  }
538
  // create new Frame struct + make a deep copy of pConfig.
539
11.9k
  pNewFrame = malloc(sizeof(CGIF_Frame));
540
11.9k
  copyFrameConfig(&(pNewFrame->config), pConfig);
541
11.9k
  pNewFrame->config.pImageData = malloc(MULU16(pGIF->config.width, pGIF->config.height));
542
11.9k
  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
11.9k
  if(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
545
4.24k
    pNewFrame->config.pLocalPalette  = malloc(pConfig->numLocalPaletteEntries * 3);
546
4.24k
    memcpy(pNewFrame->config.pLocalPalette, pConfig->pLocalPalette, pConfig->numLocalPaletteEntries * 3);
547
4.24k
  }
548
11.9k
  pNewFrame->disposalMethod        = DISPOSAL_METHOD_LEAVE;
549
11.9k
  pNewFrame->transIndex            = 0;
550
11.9k
  pGIF->aFrames[i]                 = pNewFrame; // add frame to queue
551
11.9k
  pGIF->iHEAD                      = i;         // update HEAD index
552
  // check whether we need to adapt the disposal method of the frame before.
553
11.9k
  if(pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) {
554
295
    pGIF->aFrames[i]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // TBD might be removed
555
295
    pGIF->aFrames[i]->transIndex     = 0;
556
295
    if(pGIF->aFrames[i - 1] != NULL) {
557
201
      pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
558
201
      pGIF->aFrames[i - 1]->disposalMethod   = DISPOSAL_METHOD_BACKGROUND; // restore to background color
559
201
    }
560
295
  }
561
  // set per-frame alpha channel (we need to adapt the disposal method of the frame before)
562
11.9k
  if(pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA) {
563
1.89k
    pGIF->aFrames[i]->transIndex = pConfig->transIndex;
564
1.89k
    if(pGIF->aFrames[i - 1] != NULL) {
565
947
      pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_DIFF_WINDOW); // width/height optim not possible for frame before
566
947
      pGIF->aFrames[i - 1]->disposalMethod   = DISPOSAL_METHOD_BACKGROUND; // restore to background color
567
947
    }
568
1.89k
  }
569
  // user provided transparency setting
570
11.9k
  if(hasSetTransp) {
571
4.38k
    pGIF->aFrames[i]->transIndex = pConfig->transIndex;
572
4.38k
  }
573
11.9k
  pGIF->curResult = CGIF_OK;
574
11.9k
  return pGIF->curResult;
575
12.0k
}
576
577
/* close the GIF-file and free allocated space */
578
5.11k
int cgif_close(CGIF* pGIF) {
579
5.11k
  int         r;
580
5.11k
  cgif_result result;
581
582
  // check for previous errors
583
5.11k
  if(pGIF->curResult != CGIF_OK) {
584
557
    goto CGIF_CLOSE_Cleanup;
585
557
  }
586
587
  // flush all remaining frames in queue
588
11.6k
  for(int i = 1; i < SIZE_FRAME_QUEUE; ++i) {
589
8.77k
    if(pGIF->aFrames[i] != NULL) {
590
7.35k
      r = flushFrame(pGIF, pGIF->aFrames[i], pGIF->aFrames[i - 1]);
591
7.35k
      if(r != CGIF_OK) {
592
1.64k
        pGIF->curResult = r;
593
1.64k
        break;
594
1.64k
      }
595
7.35k
    }
596
8.77k
  }
597
598
  // cleanup
599
5.11k
CGIF_CLOSE_Cleanup:
600
5.11k
  r = cgif_raw_close(pGIF->pGIFRaw); // close raw GIF stream
601
  // check for errors
602
5.11k
  if(r != CGIF_OK) {
603
2.18k
    pGIF->curResult = r;
604
2.18k
  }
605
606
5.11k
  if(pGIF->pFile) {
607
1.99k
    r = fclose(pGIF->pFile); // we are done at this point => close the file
608
1.99k
    if(r) {
609
0
      pGIF->curResult = CGIF_ECLOSE; // error: fclose failed
610
0
    }
611
1.99k
  }
612
20.4k
  for(int i = 0; i < SIZE_FRAME_QUEUE; ++i) {
613
15.3k
    freeFrame(pGIF->aFrames[i]);
614
15.3k
  }
615
616
5.11k
  result = pGIF->curResult;
617
5.11k
  freeCGIF(pGIF);
618
  // catch internal value CGIF_PENDING
619
5.11k
  if(result == CGIF_PENDING) {
620
443
    result = CGIF_ERROR;
621
443
  }
622
5.11k
  return result; // return previous result
623
4.55k
}