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