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