1 /*****************************************************************************
2 * This file is part of gfxprim library. *
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. *
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. *
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 *
19 * Copyright (C) 2009-2014 Cyril Hrubis <metan@ucw.cz> *
21 *****************************************************************************/
25 GIF image support using giflib.
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"
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
))
59 if (!memcmp(buf
, GIF_SIGNATURE2
, GIF_SIGNATURE2_LEN
))
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
)
75 case UNDEFINED_RECORD_TYPE
:
77 case SCREEN_DESC_RECORD_TYPE
:
79 case IMAGE_DESC_RECORD_TYPE
:
81 case EXTENSION_RECORD_TYPE
:
83 case TERMINATE_RECORD_TYPE
:
90 static const char *gif_err_name(int 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";
118 static int gif_err(GifFileType
*gf
)
120 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
124 return GifLastError();
128 static int read_extensions(GifFileType
*gf
)
130 uint8_t *gif_ext_ptr
;
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
));
141 GP_DEBUG(2, "Have GIF extension type %i (ignoring)", gif_ext_type
);
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
));
150 } while (gif_ext_ptr
!= NULL
);
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
);
163 return &map
->Colors
[idx
];
166 static inline GP_Pixel
get_color(GifFileType
*gf
, uint32_t idx
)
170 //TODO: no color map?
171 if (gf
->SColorMap
== NULL
)
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
)
183 if (gf
->SColorMap
== NULL
)
186 color
= get_color_from_map(gf
->SColorMap
, gf
->SBackGroundColor
);
188 *pixel
= GP_Pixel_CREATE_RGB888(color
->Red
, color
->Green
, color
->Blue
);
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
;
202 /* Pass 1: Line 0 for each strip */
208 /* Pass 2: Line 4 for each strip */
209 real_y
= 8 * (y
- (h
- 1)/8 - 1) + 4;
214 /* Pass 3: Lines 2 and 6 */
215 real_y
= 4 * (y
- (h
- 1)/4 - 1) + 2;
220 /* Pass 4: Lines 1, 3, 5, and 7 */
221 real_y
= 2 * (y
- h
/2 - h
%2) + 1;
226 GP_BUG("real_y > h");
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
)
242 GifRecordType rec_type
;
243 GP_Pixmap
*res
= NULL
;
249 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
250 gf
= DGifOpen(io
, gif_input_func
, NULL
);
252 gf
= DGifOpen(io
, gif_input_func
);
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
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);
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
));
282 GP_DEBUG(2, "Have GIF record type %s",
283 rec_type_name(rec_type
));
286 case EXTENSION_RECORD_TYPE
:
287 if ((err
= read_extensions(gf
)))
290 case IMAGE_DESC_RECORD_TYPE
:
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
));
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);
315 res
= GP_PixmapAlloc(gf
->SWidth
, gf
->SHeight
, GP_PIXEL_RGB888
);
322 /* If background color is defined, use it */
323 if (get_bg_color(gf
, &bg
)) {
324 GP_DEBUG(1, "Filling bg color %x", 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
,
348 GP_DEBUG(1, "Operation aborted");
354 //TODO: now we exit after reading first image
357 } while (rec_type
!= TERMINATE_RECORD_TYPE
);
359 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
360 DGifCloseFile(gf
, NULL
);
365 /* No Image record found :( */
378 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
379 DGifCloseFile(gf
, NULL
);
389 int GP_MatchGIF(const void GP_UNUSED(*buf
))
395 GP_Pixmap
*GP_ReadGIFEx(GP_IO
GP_UNUSED(*io
),
396 GP_ProgressCallback
GP_UNUSED(*callback
))
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
= {
422 .Read
= GP_ReadGIFEx
,
424 .Match
= GP_MatchGIF
,
426 .fmt_name
= "Graphics Interchange Format",
427 .extensions
= {"gif", NULL
},