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 | } |