Rename GP_Context -> GP_Pixmap
[gfxprim.git] / libs / loaders / GP_GIF.c
blobca2e7f1dab625845b1174169533ec8797df49cc0
1 /*****************************************************************************
2 * This file is part of gfxprim library. *
3 * *
4 * Gfxprim is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU Lesser General Public *
6 * License as published by the Free Software Foundation; either *
7 * version 2.1 of the License, or (at your option) any later version. *
8 * *
9 * Gfxprim is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
12 * Lesser General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Lesser General Public *
15 * License along with gfxprim; if not, write to the Free Software *
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301 USA *
18 * *
19 * Copyright (C) 2009-2014 Cyril Hrubis <metan@ucw.cz> *
20 * *
21 *****************************************************************************/
25 GIF image support using giflib.
29 #include <stdint.h>
30 #include <inttypes.h>
32 #include <errno.h>
33 #include <string.h>
35 #include "../../config.h"
36 #include "core/GP_Pixel.h"
37 #include "core/GP_GetPutPixel.gen.h"
38 #include "core/GP_Fill.h"
39 #include "core/GP_Debug.h"
41 #include "loaders/GP_IO.h"
42 #include "loaders/GP_GIF.h"
44 #ifdef HAVE_GIFLIB
46 #include <gif_lib.h>
48 #define GIF_SIGNATURE1 "GIF87a"
49 #define GIF_SIGNATURE1_LEN 6
51 #define GIF_SIGNATURE2 "GIF89a"
52 #define GIF_SIGNATURE2_LEN 6
54 int GP_MatchGIF(const void *buf)
56 if (!memcmp(buf, GIF_SIGNATURE1, GIF_SIGNATURE1_LEN))
57 return 1;
59 if (!memcmp(buf, GIF_SIGNATURE2, GIF_SIGNATURE2_LEN))
60 return 1;
62 return 0;
65 static int gif_input_func(GifFileType* gif, GifByteType* bytes, int size)
67 GP_IO *io = gif->UserData;
69 return GP_IORead(io, bytes, size);
72 static const char *rec_type_name(GifRecordType rec_type)
74 switch (rec_type) {
75 case UNDEFINED_RECORD_TYPE:
76 return "Undefined";
77 case SCREEN_DESC_RECORD_TYPE:
78 return "ScreenDesc";
79 case IMAGE_DESC_RECORD_TYPE:
80 return "ImageDesc";
81 case EXTENSION_RECORD_TYPE:
82 return "Extension";
83 case TERMINATE_RECORD_TYPE:
84 return "Terminate";
85 default:
86 return "Invalid";
90 static const char *gif_err_name(int err)
92 switch (err) {
93 case E_GIF_ERR_OPEN_FAILED:
94 return "E_GIF_ERR_OPEN_FAILED";
95 case E_GIF_ERR_WRITE_FAILED:
96 return "E_GIF_ERR_WRITE_FAILED";
97 case E_GIF_ERR_HAS_SCRN_DSCR:
98 return "E_GIF_ERR_HAS_SCRN_DSCR";
99 case E_GIF_ERR_HAS_IMAG_DSCR:
100 return "E_GIF_ERR_HAS_IMAG_DSCR";
101 case E_GIF_ERR_NO_COLOR_MAP:
102 return "E_GIF_ERR_NO_COLOR_MAP";
103 case E_GIF_ERR_DATA_TOO_BIG:
104 return "E_GIF_ERR_DATA_TOO_BIG";
105 case E_GIF_ERR_NOT_ENOUGH_MEM:
106 return "E_GIF_ERR_NOT_ENOUGH_MEM";
107 case E_GIF_ERR_DISK_IS_FULL:
108 return "E_GIF_ERR_DISK_IS_FULL";
109 case E_GIF_ERR_CLOSE_FAILED:
110 return "E_GIF_ERR_CLOSE_FAILED";
111 case E_GIF_ERR_NOT_WRITEABLE:
112 return "E_GIF_ERR_NOT_WRITEABLE";
113 default:
114 return "UNKNOWN";
118 static int gif_err(GifFileType *gf)
120 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
121 return gf->Error;
122 #else
123 (void) gf;
124 return GifLastError();
125 #endif
128 static int read_extensions(GifFileType *gf)
130 uint8_t *gif_ext_ptr;
131 int gif_ext_type;
133 //TODO: Should we free them?
135 if (DGifGetExtension(gf, &gif_ext_type, &gif_ext_ptr) != GIF_OK) {
136 GP_DEBUG(1, "DGifGetExtension() error %s (%i)",
137 gif_err_name(gif_err(gf)), gif_err(gf));
138 return EIO;
141 GP_DEBUG(2, "Have GIF extension type %i (ignoring)", gif_ext_type);
143 do {
144 if (DGifGetExtensionNext(gf, &gif_ext_ptr) != GIF_OK) {
145 GP_DEBUG(1, "DGifGetExtension() error %s (%i)",
146 gif_err_name(gif_err(gf)), gif_err(gf));
147 return EIO;
150 } while (gif_ext_ptr != NULL);
152 return 0;
155 static inline GifColorType *get_color_from_map(ColorMapObject *map, int idx)
157 if (map->ColorCount <= idx) {
158 GP_DEBUG(1, "Invalid colormap index %i (%i max)",
159 map->ColorCount, idx);
160 return map->Colors;
163 return &map->Colors[idx];
166 static inline GP_Pixel get_color(GifFileType *gf, uint32_t idx)
168 GifColorType *color;
170 //TODO: no color map?
171 if (gf->SColorMap == NULL)
172 return 0;
174 color = get_color_from_map(gf->SColorMap, idx);
176 return GP_Pixel_CREATE_RGB888(color->Red, color->Green, color->Blue);
179 static int get_bg_color(GifFileType *gf, GP_Pixel *pixel)
181 GifColorType *color;
183 if (gf->SColorMap == NULL)
184 return 0;
186 color = get_color_from_map(gf->SColorMap, gf->SBackGroundColor);
188 *pixel = GP_Pixel_CREATE_RGB888(color->Red, color->Green, color->Blue);
190 return 1;
194 * The interlacing consists of 8 pixel high strips. Each pass adds some lines
195 * into each strip. This function maps y in the gif buffer to real y.
197 static inline unsigned int interlace_real_y(GifFileType *gf, unsigned int y)
199 const unsigned int h = gf->Image.Height;
200 unsigned int real_y;
202 /* Pass 1: Line 0 for each strip */
203 real_y = 8 * y;
205 if (real_y < h)
206 return real_y;
208 /* Pass 2: Line 4 for each strip */
209 real_y = 8 * (y - (h - 1)/8 - 1) + 4;
211 if (real_y < h)
212 return real_y;
214 /* Pass 3: Lines 2 and 6 */
215 real_y = 4 * (y - (h - 1)/4 - 1) + 2;
217 if (real_y < h)
218 return real_y;
220 /* Pass 4: Lines 1, 3, 5, and 7 */
221 real_y = 2 * (y - h/2 - h%2) + 1;
223 if (real_y < h)
224 return real_y;
226 GP_BUG("real_y > h");
228 return 0;
231 static void fill_metadata(GifFileType *gf, GP_DataStorage *storage)
233 GP_DataStorageAddInt(storage, NULL, "Width", gf->SWidth);
234 GP_DataStorageAddInt(storage, NULL, "Height", gf->SHeight);
235 GP_DataStorageAddInt(storage, NULL, "Interlace", gf->Image.Interlace);
238 int GP_ReadGIFEx(GP_IO *io, GP_Pixmap **img,
239 GP_DataStorage *storage, GP_ProgressCallback *callback)
241 GifFileType *gf;
242 GifRecordType rec_type;
243 GP_Pixmap *res = NULL;
244 GP_Pixel bg;
245 int32_t x, y;
246 int err;
248 errno = 0;
249 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
250 gf = DGifOpen(io, gif_input_func, NULL);
251 #else
252 gf = DGifOpen(io, gif_input_func);
253 #endif
255 if (gf == NULL) {
257 * The giflib uses open() so when we got a failure and errno
258 * is set => open() has failed.
260 * When errno is not set the file content was not valid so we
261 * set errno to EIO.
263 if (errno == 0)
264 errno = EIO;
266 return 1;
269 GP_DEBUG(1, "Have GIF image %ix%i, %i colors, %i bpp",
270 gf->SWidth, gf->SHeight, gf->SColorResolution,
271 gf->SColorMap ? gf->SColorMap->BitsPerPixel : -1);
273 do {
274 if (DGifGetRecordType(gf, &rec_type) != GIF_OK) {
275 //TODO: error handling
276 GP_DEBUG(1, "DGifGetRecordType() error %s (%i)",
277 gif_err_name(gif_err(gf)), gif_err(gf));
278 err = EIO;
279 goto err1;
282 GP_DEBUG(2, "Have GIF record type %s",
283 rec_type_name(rec_type));
285 switch (rec_type) {
286 case EXTENSION_RECORD_TYPE:
287 if ((err = read_extensions(gf)))
288 goto err1;
289 continue;
290 case IMAGE_DESC_RECORD_TYPE:
291 break;
292 default:
293 continue;
296 if (DGifGetImageDesc(gf) != GIF_OK) {
297 //TODO: error handling
298 GP_DEBUG(1, "DGifGetImageDesc() error %s (%i)",
299 gif_err_name(gif_err(gf)), gif_err(gf));
300 err = EIO;
301 goto err1;
304 if (storage)
305 fill_metadata(gf, storage);
307 GP_DEBUG(1, "Have GIF Image left-top %ix%i, width-height %ix%i,"
308 " interlace %i, bpp %i", gf->Image.Left, gf->Image.Top,
309 gf->Image.Width, gf->Image.Height, gf->Image.Interlace,
310 gf->Image.ColorMap ? gf->Image.ColorMap->BitsPerPixel : -1);
312 if (!img)
313 break;
315 res = GP_PixmapAlloc(gf->SWidth, gf->SHeight, GP_PIXEL_RGB888);
317 if (res == NULL) {
318 err = ENOMEM;
319 goto err1;
322 /* If background color is defined, use it */
323 if (get_bg_color(gf, &bg)) {
324 GP_DEBUG(1, "Filling bg color %x", bg);
325 GP_Fill(res, bg);
328 /* Now finally read gif image data */
329 for (y = gf->Image.Top; y < gf->Image.Height; y++) {
330 uint8_t line[gf->Image.Width];
332 DGifGetLine(gf, line, gf->Image.Width);
334 unsigned int real_y = y;
336 if (gf->Image.Interlace == 64) {
337 real_y = interlace_real_y(gf, y);
338 GP_DEBUG(3, "Interlace y -> real_y %u %u", y, real_y);
341 //TODO: just now we have only 8BPP
342 for (x = 0; x < gf->Image.Width; x++)
343 GP_PutPixel_Raw_24BPP(res, x + gf->Image.Left, real_y, get_color(gf, line[x]));
345 if (GP_ProgressCallbackReport(callback, y - gf->Image.Top,
346 gf->Image.Height,
347 gf->Image.Width)) {
348 GP_DEBUG(1, "Operation aborted");
349 err = ECANCELED;
350 goto err2;
354 //TODO: now we exit after reading first image
355 break;
357 } while (rec_type != TERMINATE_RECORD_TYPE);
359 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
360 DGifCloseFile(gf, NULL);
361 #else
362 DGifCloseFile(gf);
363 #endif
365 /* No Image record found :( */
366 if (img && !res) {
367 errno = EINVAL;
368 return 1;
371 if (img)
372 *img = res;
374 return 0;
375 err2:
376 GP_PixmapFree(res);
377 err1:
378 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
379 DGifCloseFile(gf, NULL);
380 #else
381 DGifCloseFile(gf);
382 #endif
383 errno = err;
384 return 1;
387 #else
389 int GP_MatchGIF(const void GP_UNUSED(*buf))
391 errno = ENOSYS;
392 return -1;
395 GP_Pixmap *GP_ReadGIFEx(GP_IO GP_UNUSED(*io),
396 GP_ProgressCallback GP_UNUSED(*callback))
398 errno = ENOSYS;
399 return NULL;
402 #endif /* HAVE_GIFLIB */
404 GP_Pixmap *GP_ReadGIF(GP_IO *io, GP_ProgressCallback *callback)
406 return GP_LoaderReadImage(&GP_GIF, io, callback);
409 GP_Pixmap *GP_LoadGIF(const char *src_path, GP_ProgressCallback *callback)
411 return GP_LoaderLoadImage(&GP_GIF, src_path, callback);
414 int GP_LoadGIFEx(const char *src_path, GP_Pixmap **img,
415 GP_DataStorage *storage, GP_ProgressCallback *callback)
417 return GP_LoaderLoadImageEx(&GP_GIF, src_path, img, storage, callback);
420 struct GP_Loader GP_GIF = {
421 #ifdef HAVE_GIFLIB
422 .Read = GP_ReadGIFEx,
423 #endif
424 .Match = GP_MatchGIF,
426 .fmt_name = "Graphics Interchange Format",
427 .extensions = {"gif", NULL},