1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 #include <fcntl.h>
6 #include <memory>
7 #include <vector>
8 
9 #include <gif_lib.h>
10 
11 #include <OpenImageIO/filesystem.h>
12 #include <OpenImageIO/imageio.h>
13 #include <OpenImageIO/thread.h>
14 
15 // GIFLIB:
16 // http://giflib.sourceforge.net/
17 // Format description:
18 // http://giflib.sourceforge.net/whatsinagif/index.html
19 
20 // for older giflib versions
21 #ifndef GIFLIB_MAJOR
22 #    define GIFLIB_MAJOR 4
23 #endif
24 
25 #ifndef DISPOSAL_UNSPECIFIED
26 #    define DISPOSAL_UNSPECIFIED 0
27 #endif
28 
29 #ifndef DISPOSE_BACKGROUND
30 #    define DISPOSE_BACKGROUND 2
31 #endif
32 
33 OIIO_PLUGIN_NAMESPACE_BEGIN
34 
35 class GIFInput final : public ImageInput {
36 public:
GIFInput()37     GIFInput() { init(); }
~GIFInput()38     virtual ~GIFInput() { close(); }
format_name(void) const39     virtual const char* format_name(void) const override { return "gif"; }
40     virtual bool open(const std::string& name, ImageSpec& newspec) override;
41     virtual bool close(void) override;
42     virtual bool seek_subimage(int subimage, int miplevel) override;
43     virtual bool read_native_scanline(int subimage, int miplevel, int y, int z,
44                                       void* data) override;
45 
current_subimage(void) const46     virtual int current_subimage(void) const override
47     {
48         lock_guard lock(m_mutex);
49         return m_subimage;
50     }
current_miplevel(void) const51     virtual int current_miplevel(void) const override
52     {
53         // No mipmap support
54         return 0;
55     }
56 
57 private:
58     std::string m_filename;          ///< Stash the filename
59     GifFileType* m_gif_file;         ///< GIFLIB handle
60     int m_transparent_color;         ///< Transparent color index
61     int m_subimage;                  ///< Current subimage index
62     int m_disposal_method;           ///< Disposal method of current subimage.
63                                      ///  Indicates what to do with canvas
64                                      ///  before drawing the _next_ subimage.
65     int m_previous_disposal_method;  ///< Disposal method of previous subimage.
66                                      ///  Indicates what to do with canvas
67                                      ///  before drawing _current_ subimage.
68     std::vector<unsigned char> m_canvas;  ///< Image canvas in output format, on
69                                           ///  which subimages are sequentially
70                                           ///  drawn.
71 
72     /// Reset everything to initial state
73     ///
74     void init(void);
75 
76     /// Read current subimage metadata.
77     ///
78     bool read_subimage_metadata(ImageSpec& newspec);
79 
80     /// Read current subimage data (ie. draw it on canvas).
81     ///
82     bool read_subimage_data(void);
83 
84     /// Helper: read gif extension.
85     ///
86     void read_gif_extension(int ext_code, GifByteType* ext, ImageSpec& spec);
87 
88     /// Decode and return a real scanline index in the interlaced image.
89     ///
90     int decode_line_number(int line_number, int height);
91 
92     /// Print error message.
93     ///
94     void report_last_error(void);
95 };
96 
97 
98 
99 // Obligatory material to make this a recognizeable imageio plugin
100 OIIO_PLUGIN_EXPORTS_BEGIN
101 
102 OIIO_EXPORT int gif_imageio_version = OIIO_PLUGIN_VERSION;
103 OIIO_EXPORT ImageInput*
gif_input_imageio_create()104 gif_input_imageio_create()
105 {
106     return new GIFInput;
107 }
108 OIIO_EXPORT const char* gif_input_extensions[] = { "gif", NULL };
109 
110 OIIO_EXPORT const char*
gif_imageio_library_version()111 gif_imageio_library_version()
112 {
113 #define STRINGIZE2(a) #a
114 #define STRINGIZE(a) STRINGIZE2(a)
115 #if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR) && defined(GIFLIB_RELEASE)
116     return "gif_lib " STRINGIZE(GIFLIB_MAJOR) "." STRINGIZE(
117         GIFLIB_MINOR) "." STRINGIZE(GIFLIB_RELEASE);
118 #else
119     return "gif_lib unknown version";
120 #endif
121 }
122 
123 OIIO_PLUGIN_EXPORTS_END
124 
125 
126 
127 void
init(void)128 GIFInput::init(void)
129 {
130     m_gif_file = nullptr;
131 }
132 
133 
134 
135 bool
open(const std::string & name,ImageSpec & newspec)136 GIFInput::open(const std::string& name, ImageSpec& newspec)
137 {
138     m_filename = name;
139     m_subimage = -1;
140     m_canvas.clear();
141 
142     bool ok = seek_subimage(0, 0);
143     newspec = spec();
144     return ok;
145 }
146 
147 
148 
149 inline int
decode_line_number(int line_number,int height)150 GIFInput::decode_line_number(int line_number, int height)
151 {
152     if (1 < height && (height + 1) / 2 <= line_number)  // 4th tile 1/2 sized
153         return 2 * (line_number - (height + 1) / 2) + 1;
154     if (2 < height && (height + 3) / 4 <= line_number)  // 3rd tile 1/4 sized
155         return 4 * (line_number - (height + 3) / 4) + 2;
156     if (4 < height && (height + 7) / 8 <= line_number)  // 2nd tile 1/8 sized
157         return 8 * (line_number - (height + 7) / 8) + 4;
158 
159     // 1st tile 1/8 sized
160     return line_number * 8;
161 }
162 
163 
164 
165 bool
read_native_scanline(int subimage,int miplevel,int y,int,void * data)166 GIFInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
167                                void* data)
168 {
169     lock_guard lock(m_mutex);
170     if (!seek_subimage(subimage, miplevel))
171         return false;
172 
173     if (y < 0 || y > m_spec.height || !m_canvas.size())
174         return false;
175 
176     memcpy(data, &m_canvas[y * m_spec.width * m_spec.nchannels],
177            m_spec.width * m_spec.nchannels);
178 
179     return true;
180 }
181 
182 
183 
184 void
read_gif_extension(int ext_code,GifByteType * ext,ImageSpec & newspec)185 GIFInput::read_gif_extension(int ext_code, GifByteType* ext, ImageSpec& newspec)
186 {
187     if (ext_code == GRAPHICS_EXT_FUNC_CODE) {
188         // read background color index, disposal method and delay time between frames
189         // http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html#graphics_control_extension_block
190 
191         if (ext[1] & 0x01) {
192             m_transparent_color = (int)ext[4];
193         }
194 
195         m_disposal_method = (ext[1] & 0x1c) >> 2;
196 
197         int delay = (ext[3] << 8) | ext[2];
198         if (delay) {
199             int rat[2] = { 100, delay };
200             newspec.attribute("FramesPerSecond", TypeRational, &rat);
201             newspec.attribute("oiio:Movie", 1);
202         }
203 
204     } else if (ext_code == COMMENT_EXT_FUNC_CODE) {
205         // read comment data
206         // http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html#comment_extension_block
207         std::string comment = std::string((const char*)&ext[1], int(ext[0]));
208         newspec.attribute("ImageDescription", comment);
209 
210     } else if (ext_code == APPLICATION_EXT_FUNC_CODE) {
211         // read loop count
212         // http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html#application_extension_block
213         if (ext[0] == 3) {
214             newspec.attribute("gif:LoopCount", (ext[3] << 8) | ext[2]);
215             newspec.attribute("oiio:LoopCount", (ext[3] << 8) | ext[2]);
216         }
217     }
218 }
219 
220 
221 
222 bool
read_subimage_metadata(ImageSpec & newspec)223 GIFInput::read_subimage_metadata(ImageSpec& newspec)
224 {
225     newspec           = ImageSpec(TypeDesc::UINT8);
226     newspec.nchannels = 4;
227     newspec.default_channel_names();
228     newspec.alpha_channel = 4;
229     newspec.attribute("oiio:ColorSpace", "sRGB");
230 
231     m_previous_disposal_method = m_disposal_method;
232     m_disposal_method          = DISPOSAL_UNSPECIFIED;
233 
234     m_transparent_color = -1;
235 
236     GifRecordType m_gif_rec_type;
237     do {
238         if (DGifGetRecordType(m_gif_file, &m_gif_rec_type) == GIF_ERROR) {
239             report_last_error();
240             return false;
241         }
242 
243         switch (m_gif_rec_type) {
244         case IMAGE_DESC_RECORD_TYPE:
245             if (DGifGetImageDesc(m_gif_file) == GIF_ERROR) {
246                 report_last_error();
247                 return false;
248             }
249 
250             break;
251 
252         case EXTENSION_RECORD_TYPE:
253             int ext_code;
254             GifByteType* ext;
255             if (DGifGetExtension(m_gif_file, &ext_code, &ext) == GIF_ERROR) {
256                 report_last_error();
257                 return false;
258             }
259             if (ext != NULL) {
260                 read_gif_extension(ext_code, ext, newspec);
261             }
262 
263             while (ext != NULL) {
264                 if (DGifGetExtensionNext(m_gif_file, &ext) == GIF_ERROR) {
265                     report_last_error();
266                     return false;
267                 }
268 
269                 if (ext != NULL) {
270                     read_gif_extension(ext_code, ext, newspec);
271                 }
272             }
273 
274             break;
275 
276         case TERMINATE_RECORD_TYPE: return false; break;
277 
278         default: break;
279         }
280     } while (m_gif_rec_type != IMAGE_DESC_RECORD_TYPE);
281 
282     newspec.attribute("gif:Interlacing", m_gif_file->Image.Interlace ? 1 : 0);
283 
284     return true;
285 }
286 
287 
288 
289 bool
read_subimage_data()290 GIFInput::read_subimage_data()
291 {
292     GifColorType* colormap = NULL;
293     if (m_gif_file->Image.ColorMap) {  // local colormap
294         colormap = m_gif_file->Image.ColorMap->Colors;
295     } else if (m_gif_file->SColorMap) {  // global colormap
296         colormap = m_gif_file->SColorMap->Colors;
297     } else {
298         errorf("Neither local nor global colormap present.");
299         return false;
300     }
301 
302     if (m_subimage == 0 || m_previous_disposal_method == DISPOSE_BACKGROUND) {
303         // make whole canvas transparent
304         std::fill(m_canvas.begin(), m_canvas.end(), 0x00);
305     }
306 
307     // decode scanline index if image is interlaced
308     bool interlacing = m_spec.get_int_attribute("gif:Interlacing") != 0;
309 
310     // get subimage dimensions and draw it on canvas
311     int window_height = m_gif_file->Image.Height;
312     int window_width  = m_gif_file->Image.Width;
313     int window_top    = m_gif_file->Image.Top;
314     int window_left   = m_gif_file->Image.Left;
315     std::unique_ptr<unsigned char[]> fscanline(new unsigned char[window_width]);
316     for (int wy = 0; wy < window_height; wy++) {
317         if (DGifGetLine(m_gif_file, &fscanline[0], window_width) == GIF_ERROR) {
318             report_last_error();
319             return false;
320         }
321         int y = window_top
322                 + (interlacing ? decode_line_number(wy, window_height) : wy);
323         if (0 <= y && y < m_spec.height) {
324             for (int wx = 0; wx < window_width; wx++) {
325                 int x   = window_left + wx;
326                 int idx = m_spec.nchannels * (y * m_spec.width + x);
327                 if (0 <= x && x < m_spec.width
328                     && fscanline[wx] != m_transparent_color) {
329                     m_canvas[idx]     = colormap[fscanline[wx]].Red;
330                     m_canvas[idx + 1] = colormap[fscanline[wx]].Green;
331                     m_canvas[idx + 2] = colormap[fscanline[wx]].Blue;
332                     m_canvas[idx + 3] = 0xff;
333                 }
334             }
335         }
336     }
337 
338     return true;
339 }
340 
341 
342 
343 bool
seek_subimage(int subimage,int miplevel)344 GIFInput::seek_subimage(int subimage, int miplevel)
345 {
346     if (subimage < 0 || miplevel != 0)
347         return false;
348 
349     if (m_subimage == subimage) {
350         // We're already pointing to the right subimage
351         return true;
352     }
353 
354     if (m_subimage > subimage) {
355         // requested subimage is located before the current one
356         // file needs to be reopened
357         if (m_gif_file && !close()) {
358             return false;
359         }
360     }
361 
362     if (!m_gif_file) {
363 #if GIFLIB_MAJOR >= 5 && defined(_WIN32)
364         // On Windows, UTF-8 filenames won't work properly with Giflib. Jump
365         // through some hoops: get an integer file descriptor for Giflib.
366         int fd = Filesystem::open(m_filename, _O_RDONLY | _O_BINARY);
367         if (fd == -1) {
368             errorf("Error trying to open the file.");
369             return false;
370         }
371         int giflib_error;
372         if (!(m_gif_file = DGifOpenFileHandle(fd, &giflib_error))) {
373             errorf("%s", GifErrorString(giflib_error));
374             return false;
375         }
376 #elif GIFLIB_MAJOR >= 5
377         int giflib_error;
378         if (!(m_gif_file = DGifOpenFileName(m_filename.c_str(),
379                                             &giflib_error))) {
380             errorf("%s", GifErrorString(giflib_error));
381             return false;
382         }
383 #else
384         if (!(m_gif_file = DGifOpenFileName(m_filename.c_str()))) {
385             errorf("Error trying to open the file.");
386             return false;
387         }
388 #endif
389 
390         m_subimage = -1;
391         m_canvas.resize(m_gif_file->SWidth * m_gif_file->SHeight * 4);
392     }
393 
394     // skip subimages preceding the requested one
395     if (m_subimage < subimage) {
396         for (m_subimage += 1; m_subimage < subimage; m_subimage++) {
397             if (!read_subimage_metadata(m_spec) || !read_subimage_data()) {
398                 return false;
399             }
400         }
401     }
402 
403     // read metadata of current subimage
404     if (!read_subimage_metadata(m_spec)) {
405         return false;
406     }
407 
408     m_spec.width       = m_gif_file->SWidth;
409     m_spec.height      = m_gif_file->SHeight;
410     m_spec.depth       = 1;
411     m_spec.full_height = m_spec.height;
412     m_spec.full_width  = m_spec.width;
413     m_spec.full_depth  = m_spec.depth;
414 
415     m_subimage = subimage;
416 
417     // draw subimage on canvas
418     if (!read_subimage_data()) {
419         return false;
420     }
421 
422     return true;
423 }
424 
425 
426 
427 static spin_mutex gif_error_mutex;
428 
429 
430 void
report_last_error(void)431 GIFInput::report_last_error(void)
432 {
433     // N.B. Only GIFLIB_MAJOR >= 5 looks properly thread-safe, in that the
434     // error is guaranteed to be specific to this open file.  We use a  spin
435     // mutex to prevent a thread clash for older versions, but it still
436     // appears to be a global call, so we can't be absolutely sure that the
437     // error was for *this* file.  So if you're using giflib prior to
438     // version 5, beware.
439 #if GIFLIB_MAJOR >= 5
440     errorf("%s", GifErrorString(m_gif_file->Error));
441 #elif GIFLIB_MAJOR == 4 && GIFLIB_MINOR >= 2
442     spin_lock lock(gif_error_mutex);
443     errorf("%s", GifErrorString());
444 #else
445     spin_lock lock(gif_error_mutex);
446     errorf("GIF error %d", GifLastError());
447 #endif
448 }
449 
450 
451 
452 inline bool
close(void)453 GIFInput::close(void)
454 {
455     if (m_gif_file) {
456 #if GIFLIB_MAJOR > 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1)
457         if (DGifCloseFile(m_gif_file, NULL) == GIF_ERROR) {
458 #else
459         if (DGifCloseFile(m_gif_file) == GIF_ERROR) {
460 #endif
461             errorf("Error trying to close the file.");
462             return false;
463         }
464         m_gif_file = NULL;
465     }
466     m_canvas.clear();
467 
468     return true;
469 }
470 
471 OIIO_PLUGIN_NAMESPACE_END
472