/src/libwebp/examples/gifdec.c
Line | Count | Source |
1 | | // Copyright 2012 Google Inc. All Rights Reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style license |
4 | | // that can be found in the COPYING file in the root of the source |
5 | | // tree. An additional intellectual property rights grant can be found |
6 | | // in the file PATENTS. All contributing project authors may |
7 | | // be found in the AUTHORS file in the root of the source tree. |
8 | | // ----------------------------------------------------------------------------- |
9 | | // |
10 | | // GIF decode. |
11 | | |
12 | | #include "./gifdec.h" |
13 | | |
14 | | #include <stdio.h> |
15 | | |
16 | | #ifdef WEBP_HAVE_GIF |
17 | | #include <assert.h> |
18 | | #include <stdlib.h> |
19 | | #include <string.h> |
20 | | |
21 | | #include "webp/encode.h" |
22 | | #include "webp/mux_types.h" |
23 | | #include "webp/types.h" |
24 | | |
25 | 0 | #define GIF_TRANSPARENT_COLOR 0x00000000u |
26 | 0 | #define GIF_WHITE_COLOR 0xffffffffu |
27 | 0 | #define GIF_TRANSPARENT_MASK 0x01 |
28 | 0 | #define GIF_DISPOSE_MASK 0x07 |
29 | 0 | #define GIF_DISPOSE_SHIFT 2 |
30 | | |
31 | | // from utils/utils.h |
32 | | #ifdef __cplusplus |
33 | | extern "C" { |
34 | | #endif |
35 | | extern void WebPCopyPlane(const uint8_t* src, int src_stride, uint8_t* dst, |
36 | | int dst_stride, int width, int height); |
37 | | extern void WebPCopyPixels(const WebPPicture* const src, |
38 | | WebPPicture* const dst); |
39 | | #ifdef __cplusplus |
40 | | } |
41 | | #endif |
42 | | |
43 | | void GIFGetBackgroundColor(const ColorMapObject* const color_map, |
44 | | int bgcolor_index, int transparent_index, |
45 | 0 | uint32_t* const bgcolor) { |
46 | 0 | if (transparent_index != GIF_INDEX_INVALID && |
47 | 0 | bgcolor_index == transparent_index) { |
48 | 0 | *bgcolor = GIF_TRANSPARENT_COLOR; // Special case. |
49 | 0 | } else if (color_map == NULL || color_map->Colors == NULL || |
50 | 0 | bgcolor_index >= color_map->ColorCount) { |
51 | 0 | *bgcolor = GIF_WHITE_COLOR; |
52 | 0 | fprintf(stderr, |
53 | 0 | "GIF decode warning: invalid background color index. Assuming " |
54 | 0 | "white background.\n"); |
55 | 0 | } else { |
56 | 0 | const GifColorType color = color_map->Colors[bgcolor_index]; |
57 | 0 | *bgcolor = (0xffu << 24) | (color.Red << 16) | (color.Green << 8) | |
58 | 0 | (color.Blue << 0); |
59 | 0 | } |
60 | 0 | } |
61 | | |
62 | | int GIFReadGraphicsExtension(const GifByteType* const buf, int* const duration, |
63 | | GIFDisposeMethod* const dispose, |
64 | 0 | int* const transparent_index) { |
65 | 0 | const int flags = buf[1]; |
66 | 0 | const int dispose_raw = (flags >> GIF_DISPOSE_SHIFT) & GIF_DISPOSE_MASK; |
67 | 0 | const int duration_raw = buf[2] | (buf[3] << 8); // In 10 ms units. |
68 | 0 | if (buf[0] != 4) return 0; |
69 | 0 | *duration = duration_raw * 10; // Duration is in 1 ms units. |
70 | 0 | switch (dispose_raw) { |
71 | 0 | case 3: |
72 | 0 | *dispose = GIF_DISPOSE_RESTORE_PREVIOUS; |
73 | 0 | break; |
74 | 0 | case 2: |
75 | 0 | *dispose = GIF_DISPOSE_BACKGROUND; |
76 | 0 | break; |
77 | 0 | case 1: |
78 | 0 | case 0: |
79 | 0 | default: |
80 | 0 | *dispose = GIF_DISPOSE_NONE; |
81 | 0 | break; |
82 | 0 | } |
83 | 0 | *transparent_index = |
84 | 0 | (flags & GIF_TRANSPARENT_MASK) ? buf[4] : GIF_INDEX_INVALID; |
85 | 0 | return 1; |
86 | 0 | } |
87 | | |
88 | | static int Remap(const GifFileType* const gif, const uint8_t* const src, |
89 | 0 | int len, int transparent_index, uint32_t* dst) { |
90 | 0 | int i; |
91 | 0 | const GifColorType* colors; |
92 | 0 | const ColorMapObject* const cmap = |
93 | 0 | gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; |
94 | 0 | if (cmap == NULL) return 1; |
95 | 0 | if (cmap->Colors == NULL || cmap->ColorCount <= 0) return 0; |
96 | 0 | colors = cmap->Colors; |
97 | |
|
98 | 0 | for (i = 0; i < len; ++i) { |
99 | 0 | if (src[i] == transparent_index) { |
100 | 0 | dst[i] = GIF_TRANSPARENT_COLOR; |
101 | 0 | } else if (src[i] < cmap->ColorCount) { |
102 | 0 | const GifColorType c = colors[src[i]]; |
103 | 0 | dst[i] = c.Blue | (c.Green << 8) | (c.Red << 16) | (0xffu << 24); |
104 | 0 | } else { |
105 | 0 | return 0; |
106 | 0 | } |
107 | 0 | } |
108 | 0 | return 1; |
109 | 0 | } |
110 | | |
111 | | int GIFReadFrame(GifFileType* const gif, int transparent_index, |
112 | 0 | GIFFrameRect* const gif_rect, WebPPicture* const picture) { |
113 | 0 | WebPPicture sub_image; |
114 | 0 | const GifImageDesc* const image_desc = &gif->Image; |
115 | 0 | uint32_t* dst = NULL; |
116 | 0 | uint8_t* tmp = NULL; |
117 | 0 | const GIFFrameRect rect = {image_desc->Left, image_desc->Top, |
118 | 0 | image_desc->Width, image_desc->Height}; |
119 | 0 | const uint64_t memory_needed = 4 * rect.width * (uint64_t)rect.height; |
120 | 0 | int ok = 0; |
121 | 0 | *gif_rect = rect; |
122 | |
|
123 | 0 | if (memory_needed != (size_t)memory_needed || memory_needed > (4ULL << 32)) { |
124 | 0 | fprintf(stderr, "Image is too large (%d x %d).", rect.width, rect.height); |
125 | 0 | return 0; |
126 | 0 | } |
127 | | |
128 | | // Use a view for the sub-picture: |
129 | 0 | if (!WebPPictureView(picture, rect.x_offset, rect.y_offset, rect.width, |
130 | 0 | rect.height, &sub_image)) { |
131 | 0 | fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n", |
132 | 0 | rect.width, rect.height, rect.x_offset, rect.y_offset); |
133 | 0 | return 0; |
134 | 0 | } |
135 | 0 | dst = sub_image.argb; |
136 | |
|
137 | 0 | tmp = (uint8_t*)WebPMalloc(rect.width * sizeof(*tmp)); |
138 | 0 | if (tmp == NULL) goto End; |
139 | | |
140 | 0 | if (image_desc->Interlace) { // Interlaced image. |
141 | | // We need 4 passes, with the following offsets and jumps. |
142 | 0 | const int interlace_offsets[] = {0, 4, 2, 1}; |
143 | 0 | const int interlace_jumps[] = {8, 8, 4, 2}; |
144 | 0 | int pass; |
145 | 0 | for (pass = 0; pass < 4; ++pass) { |
146 | 0 | const size_t stride = (size_t)sub_image.argb_stride; |
147 | 0 | int y = interlace_offsets[pass]; |
148 | 0 | uint32_t* row = dst + y * stride; |
149 | 0 | const size_t jump = interlace_jumps[pass] * stride; |
150 | 0 | for (; y < rect.height; y += interlace_jumps[pass], row += jump) { |
151 | 0 | if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; |
152 | 0 | if (!Remap(gif, tmp, rect.width, transparent_index, row)) goto End; |
153 | 0 | } |
154 | 0 | } |
155 | 0 | } else { // Non-interlaced image. |
156 | 0 | int y; |
157 | 0 | uint32_t* ptr = dst; |
158 | 0 | for (y = 0; y < rect.height; ++y, ptr += sub_image.argb_stride) { |
159 | 0 | if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; |
160 | 0 | if (!Remap(gif, tmp, rect.width, transparent_index, ptr)) goto End; |
161 | 0 | } |
162 | 0 | } |
163 | 0 | ok = 1; |
164 | |
|
165 | 0 | End: |
166 | 0 | if (!ok) picture->error_code = sub_image.error_code; |
167 | 0 | WebPPictureFree(&sub_image); |
168 | 0 | WebPFree(tmp); |
169 | 0 | return ok; |
170 | 0 | } |
171 | | |
172 | | int GIFReadLoopCount(GifFileType* const gif, GifByteType** const buf, |
173 | 0 | int* const loop_count) { |
174 | 0 | assert(!memcmp(*buf + 1, "NETSCAPE2.0", 11) || |
175 | 0 | !memcmp(*buf + 1, "ANIMEXTS1.0", 11)); |
176 | 0 | if (DGifGetExtensionNext(gif, buf) == GIF_ERROR) { |
177 | 0 | return 0; |
178 | 0 | } |
179 | 0 | if (*buf == NULL) { |
180 | 0 | return 0; // Loop count sub-block missing. |
181 | 0 | } |
182 | 0 | if ((*buf)[0] < 3 || (*buf)[1] != 1) { |
183 | 0 | return 0; // wrong size/marker |
184 | 0 | } |
185 | 0 | *loop_count = (*buf)[2] | ((*buf)[3] << 8); |
186 | 0 | return 1; |
187 | 0 | } |
188 | | |
189 | | int GIFReadMetadata(GifFileType* const gif, GifByteType** const buf, |
190 | 0 | WebPData* const metadata) { |
191 | 0 | const int is_xmp = !memcmp(*buf + 1, "XMP DataXMP", 11); |
192 | 0 | const int is_icc = !memcmp(*buf + 1, "ICCRGBG1012", 11); |
193 | 0 | assert(is_xmp || is_icc); |
194 | 0 | (void)is_icc; // silence unused warning. |
195 | | // Construct metadata from sub-blocks. |
196 | | // Usual case (including ICC profile): In each sub-block, the |
197 | | // first byte specifies its size in bytes (0 to 255) and the |
198 | | // rest of the bytes contain the data. |
199 | | // Special case for XMP data: In each sub-block, the first byte |
200 | | // is also part of the XMP payload. XMP in GIF also has a 257 |
201 | | // byte padding data. See the XMP specification for details. |
202 | 0 | while (1) { |
203 | 0 | WebPData subblock; |
204 | 0 | const uint8_t* tmp; |
205 | 0 | if (DGifGetExtensionNext(gif, buf) == GIF_ERROR) { |
206 | 0 | return 0; |
207 | 0 | } |
208 | 0 | if (*buf == NULL) break; // Finished. |
209 | 0 | subblock.size = is_xmp ? (*buf)[0] + 1 : (*buf)[0]; |
210 | 0 | assert(subblock.size > 0); |
211 | 0 | subblock.bytes = is_xmp ? *buf : *buf + 1; |
212 | | // Note: We store returned value in 'tmp' first, to avoid |
213 | | // leaking old memory in metadata->bytes on error. |
214 | 0 | tmp = (uint8_t*)realloc((void*)metadata->bytes, |
215 | 0 | metadata->size + subblock.size); |
216 | 0 | if (tmp == NULL) { |
217 | 0 | return 0; |
218 | 0 | } |
219 | 0 | memcpy((void*)(tmp + metadata->size), subblock.bytes, subblock.size); |
220 | 0 | metadata->bytes = tmp; |
221 | 0 | metadata->size += subblock.size; |
222 | 0 | } |
223 | 0 | if (is_xmp) { |
224 | | // XMP padding data is 0x01, 0xff, 0xfe ... 0x01, 0x00. |
225 | 0 | const size_t xmp_pading_size = 257; |
226 | 0 | if (metadata->size > xmp_pading_size) { |
227 | 0 | metadata->size -= xmp_pading_size; |
228 | 0 | } |
229 | 0 | } |
230 | 0 | return 1; |
231 | 0 | } |
232 | | |
233 | | static void ClearRectangle(WebPPicture* const picture, int left, int top, |
234 | 0 | int width, int height) { |
235 | 0 | int i, j; |
236 | 0 | const size_t stride = picture->argb_stride; |
237 | 0 | uint32_t* dst = picture->argb + top * stride + left; |
238 | 0 | for (j = 0; j < height; ++j, dst += stride) { |
239 | 0 | for (i = 0; i < width; ++i) dst[i] = GIF_TRANSPARENT_COLOR; |
240 | 0 | } |
241 | 0 | } |
242 | | |
243 | 0 | void GIFClearPic(WebPPicture* const pic, const GIFFrameRect* const rect) { |
244 | 0 | if (rect != NULL) { |
245 | 0 | ClearRectangle(pic, rect->x_offset, rect->y_offset, rect->width, |
246 | 0 | rect->height); |
247 | 0 | } else { |
248 | 0 | ClearRectangle(pic, 0, 0, pic->width, pic->height); |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | 0 | void GIFCopyPixels(const WebPPicture* const src, WebPPicture* const dst) { |
253 | 0 | WebPCopyPixels(src, dst); |
254 | 0 | } |
255 | | |
256 | | void GIFDisposeFrame(GIFDisposeMethod dispose, const GIFFrameRect* const rect, |
257 | | const WebPPicture* const prev_canvas, |
258 | 0 | WebPPicture* const curr_canvas) { |
259 | 0 | assert(rect != NULL); |
260 | 0 | if (dispose == GIF_DISPOSE_BACKGROUND) { |
261 | 0 | GIFClearPic(curr_canvas, rect); |
262 | 0 | } else if (dispose == GIF_DISPOSE_RESTORE_PREVIOUS) { |
263 | 0 | const size_t src_stride = prev_canvas->argb_stride; |
264 | 0 | const uint32_t* const src = |
265 | 0 | prev_canvas->argb + rect->x_offset + rect->y_offset * src_stride; |
266 | 0 | const size_t dst_stride = curr_canvas->argb_stride; |
267 | 0 | uint32_t* const dst = |
268 | 0 | curr_canvas->argb + rect->x_offset + rect->y_offset * dst_stride; |
269 | 0 | assert(prev_canvas != NULL); |
270 | 0 | WebPCopyPlane((uint8_t*)src, (int)(4 * src_stride), (uint8_t*)dst, |
271 | 0 | (int)(4 * dst_stride), 4 * rect->width, rect->height); |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | void GIFBlendFrames(const WebPPicture* const src, |
276 | 0 | const GIFFrameRect* const rect, WebPPicture* const dst) { |
277 | 0 | int i, j; |
278 | 0 | const size_t src_stride = src->argb_stride; |
279 | 0 | const size_t dst_stride = dst->argb_stride; |
280 | 0 | assert(src->width == dst->width && src->height == dst->height); |
281 | 0 | for (j = rect->y_offset; j < rect->y_offset + rect->height; ++j) { |
282 | 0 | for (i = rect->x_offset; i < rect->x_offset + rect->width; ++i) { |
283 | 0 | const uint32_t src_pixel = src->argb[j * src_stride + i]; |
284 | 0 | const int src_alpha = src_pixel >> 24; |
285 | 0 | if (src_alpha != 0) { |
286 | 0 | dst->argb[j * dst_stride + i] = src_pixel; |
287 | 0 | } |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | 29 | void GIFDisplayError(const GifFileType* const gif, int gif_error) { |
293 | | // libgif 4.2.0 has retired PrintGifError() and added GifErrorString(). |
294 | 29 | #if LOCAL_GIF_PREREQ(4, 2) |
295 | 29 | #if LOCAL_GIF_PREREQ(5, 0) |
296 | | // Static string actually, hence the const char* cast. |
297 | 29 | const char* error_str = |
298 | 29 | (const char*)GifErrorString((gif == NULL) ? gif_error : gif->Error); |
299 | | #else |
300 | | const char* error_str = (const char*)GifErrorString(); |
301 | | (void)gif; |
302 | | #endif |
303 | 29 | if (error_str == NULL) error_str = "Unknown error"; |
304 | 29 | fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str); |
305 | | #else |
306 | | (void)gif; |
307 | | fprintf(stderr, "GIFLib Error %d: ", gif_error); |
308 | | PrintGifError(); |
309 | | fprintf(stderr, "\n"); |
310 | | #endif |
311 | 29 | } |
312 | | |
313 | | #else // !WEBP_HAVE_GIF |
314 | | |
315 | | static void ErrorGIFNotAvailable(void) { |
316 | | fprintf(stderr, |
317 | | "GIF support not compiled. Please install the libgif-dev " |
318 | | "package before building.\n"); |
319 | | } |
320 | | |
321 | | void GIFGetBackgroundColor(const struct ColorMapObject* const color_map, |
322 | | int bgcolor_index, int transparent_index, |
323 | | uint32_t* const bgcolor) { |
324 | | (void)color_map; |
325 | | (void)bgcolor_index; |
326 | | (void)transparent_index; |
327 | | (void)bgcolor; |
328 | | ErrorGIFNotAvailable(); |
329 | | } |
330 | | |
331 | | int GIFReadGraphicsExtension(const GifByteType* const data, int* const duration, |
332 | | GIFDisposeMethod* const dispose, |
333 | | int* const transparent_index) { |
334 | | (void)data; |
335 | | (void)duration; |
336 | | (void)dispose; |
337 | | (void)transparent_index; |
338 | | ErrorGIFNotAvailable(); |
339 | | return 0; |
340 | | } |
341 | | |
342 | | int GIFReadFrame(struct GifFileType* const gif, int transparent_index, |
343 | | GIFFrameRect* const gif_rect, |
344 | | struct WebPPicture* const picture) { |
345 | | (void)gif; |
346 | | (void)transparent_index; |
347 | | (void)gif_rect; |
348 | | (void)picture; |
349 | | ErrorGIFNotAvailable(); |
350 | | return 0; |
351 | | } |
352 | | |
353 | | int GIFReadLoopCount(struct GifFileType* const gif, GifByteType** const buf, |
354 | | int* const loop_count) { |
355 | | (void)gif; |
356 | | (void)buf; |
357 | | (void)loop_count; |
358 | | ErrorGIFNotAvailable(); |
359 | | return 0; |
360 | | } |
361 | | |
362 | | int GIFReadMetadata(struct GifFileType* const gif, GifByteType** const buf, |
363 | | struct WebPData* const metadata) { |
364 | | (void)gif; |
365 | | (void)buf; |
366 | | (void)metadata; |
367 | | ErrorGIFNotAvailable(); |
368 | | return 0; |
369 | | } |
370 | | |
371 | | void GIFDisposeFrame(GIFDisposeMethod dispose, const GIFFrameRect* const rect, |
372 | | const struct WebPPicture* const prev_canvas, |
373 | | struct WebPPicture* const curr_canvas) { |
374 | | (void)dispose; |
375 | | (void)rect; |
376 | | (void)prev_canvas; |
377 | | (void)curr_canvas; |
378 | | ErrorGIFNotAvailable(); |
379 | | } |
380 | | |
381 | | void GIFBlendFrames(const struct WebPPicture* const src, |
382 | | const GIFFrameRect* const rect, |
383 | | struct WebPPicture* const dst) { |
384 | | (void)src; |
385 | | (void)rect; |
386 | | (void)dst; |
387 | | ErrorGIFNotAvailable(); |
388 | | } |
389 | | |
390 | | void GIFDisplayError(const struct GifFileType* const gif, int gif_error) { |
391 | | (void)gif; |
392 | | (void)gif_error; |
393 | | ErrorGIFNotAvailable(); |
394 | | } |
395 | | |
396 | | void GIFClearPic(struct WebPPicture* const pic, |
397 | | const GIFFrameRect* const rect) { |
398 | | (void)pic; |
399 | | (void)rect; |
400 | | ErrorGIFNotAvailable(); |
401 | | } |
402 | | |
403 | | void GIFCopyPixels(const struct WebPPicture* const src, |
404 | | struct WebPPicture* const dst) { |
405 | | (void)src; |
406 | | (void)dst; |
407 | | ErrorGIFNotAvailable(); |
408 | | } |
409 | | |
410 | | #endif // WEBP_HAVE_GIF |
411 | | |
412 | | // ----------------------------------------------------------------------------- |