1 // ==========================================================
2 // GIF Loader and Writer
3 //
4 // Design and implementation by
5 // - Ryan Rubley <ryan@lostreality.org>
6 // - Rapha�l Gaquer <raphael.gaquer@alcer.com>
7 // - Aaron Shumate <aaron@shumate.us>
8 //
9 // References
10 // http://www.w3.org/Graphics/GIF/spec-gif87.txt
11 // http://www.w3.org/Graphics/GIF/spec-gif89a.txt
12 //
13 // This file is part of FreeImage 3
14 //
15 // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
16 // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
17 // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
18 // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
19 // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
20 // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
21 // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
22 // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
23 // THIS DISCLAIMER.
24 //
25 // Use at your own risk!
26 // ==========================================================
27 
28 #ifdef _MSC_VER
29 #pragma warning (disable : 4786) // identifier was truncated to 'number' characters
30 #endif
31 
32 #include "FreeImage.h"
33 #include "Utilities.h"
34 #include "../Metadata/FreeImageTag.h"
35 
36 // ==========================================================
37 //   Metadata declarations
38 // ==========================================================
39 
40 #define GIF_DISPOSAL_UNSPECIFIED	0
41 #define GIF_DISPOSAL_LEAVE			1
42 #define GIF_DISPOSAL_BACKGROUND		2
43 #define GIF_DISPOSAL_PREVIOUS		3
44 
45 // ==========================================================
46 //   Constant/Typedef declarations
47 // ==========================================================
48 
49 
50 struct GIFinfo {
51 	BOOL read;
52 	//only really used when reading
53 	size_t global_color_table_offset;
54 	int global_color_table_size;
55 	BYTE background_color;
56 	std::vector<size_t> application_extension_offsets;
57 	std::vector<size_t> comment_extension_offsets;
58 	std::vector<size_t> graphic_control_extension_offsets;
59 	std::vector<size_t> image_descriptor_offsets;
60 
GIFinfoGIFinfo61 	GIFinfo() : read(0), global_color_table_offset(0), global_color_table_size(0), background_color(0)
62 	{
63 	}
64 };
65 
66 struct PageInfo {
PageInfoPageInfo67 	PageInfo(int d, int l, int t, int w, int h) {
68 		disposal_method = d; left = (WORD)l; top = (WORD)t; width = (WORD)w; height = (WORD)h;
69 	}
70 	int disposal_method;
71 	WORD left, top, width, height;
72 };
73 
74 //GIF defines a max of 12 bits per code
75 #define MAX_LZW_CODE			4096
76 
77 class StringTable
78 {
79 public:
80 	StringTable();
81 	~StringTable();
82 	void Initialize(int minCodeSize);
83 	BYTE *FillInputBuffer(int len);
84 	void CompressStart(int bpp, int width);
85 	int CompressEnd(BYTE *buf); //0-4 bytes
86 	bool Compress(BYTE *buf, int *len);
87 	bool Decompress(BYTE *buf, int *len);
88 	void Done(void);
89 
90 protected:
91 	bool m_done;
92 
93 	int m_minCodeSize, m_clearCode, m_endCode, m_nextCode;
94 
95 	int m_bpp, m_slack; //Compressor information
96 
97 	int m_prefix; //Compressor state variable
98 	int m_codeSize, m_codeMask; //Compressor/Decompressor state variables
99 	int m_oldCode; //Decompressor state variable
100 	int m_partial, m_partialSize; //Compressor/Decompressor bit buffer
101 
102 	int firstPixelPassed; // A specific flag that indicates if the first pixel
103 	                      // of the whole image had already been read
104 
105 	std::string m_strings[MAX_LZW_CODE]; //This is what is really the "string table" data for the Decompressor
106 	int* m_strmap;
107 
108 	//input buffer
109 	BYTE *m_buffer;
110 	int m_bufferSize, m_bufferRealSize, m_bufferPos, m_bufferShift;
111 
112 	void ClearCompressorTable(void);
113 	void ClearDecompressorTable(void);
114 };
115 
116 #define GIF_PACKED_LSD_HAVEGCT		0x80
117 #define GIF_PACKED_LSD_COLORRES		0x70
118 #define GIF_PACKED_LSD_GCTSORTED	0x08
119 #define GIF_PACKED_LSD_GCTSIZE		0x07
120 #define GIF_PACKED_ID_HAVELCT		0x80
121 #define GIF_PACKED_ID_INTERLACED	0x40
122 #define GIF_PACKED_ID_LCTSORTED		0x20
123 #define GIF_PACKED_ID_RESERVED		0x18
124 #define GIF_PACKED_ID_LCTSIZE		0x07
125 #define GIF_PACKED_GCE_RESERVED		0xE0
126 #define GIF_PACKED_GCE_DISPOSAL		0x1C
127 #define GIF_PACKED_GCE_WAITINPUT	0x02
128 #define GIF_PACKED_GCE_HAVETRANS	0x01
129 
130 #define GIF_BLOCK_IMAGE_DESCRIPTOR	0x2C
131 #define GIF_BLOCK_EXTENSION			0x21
132 #define GIF_BLOCK_TRAILER			0x3B
133 
134 #define GIF_EXT_PLAINTEXT			0x01
135 #define GIF_EXT_GRAPHIC_CONTROL		0xF9
136 #define GIF_EXT_COMMENT				0xFE
137 #define GIF_EXT_APPLICATION			0xFF
138 
139 #define GIF_INTERLACE_PASSES		4
140 static int g_GifInterlaceOffset[GIF_INTERLACE_PASSES] = {0, 4, 2, 1};
141 static int g_GifInterlaceIncrement[GIF_INTERLACE_PASSES] = {8, 8, 4, 2};
142 
143 // ==========================================================
144 // Helpers Functions
145 // ==========================================================
146 
147 static BOOL
FreeImage_SetMetadataEx(FREE_IMAGE_MDMODEL model,FIBITMAP * dib,const char * key,WORD id,FREE_IMAGE_MDTYPE type,DWORD count,DWORD length,const void * value)148 FreeImage_SetMetadataEx(FREE_IMAGE_MDMODEL model, FIBITMAP *dib, const char *key, WORD id, FREE_IMAGE_MDTYPE type, DWORD count, DWORD length, const void *value)
149 {
150 	BOOL bResult = FALSE;
151 	FITAG *tag = FreeImage_CreateTag();
152 	if(tag) {
153 		FreeImage_SetTagKey(tag, key);
154 		FreeImage_SetTagID(tag, id);
155 		FreeImage_SetTagType(tag, type);
156 		FreeImage_SetTagCount(tag, count);
157 		FreeImage_SetTagLength(tag, length);
158 		FreeImage_SetTagValue(tag, value);
159 		if(model == FIMD_ANIMATION) {
160 			TagLib& s = TagLib::instance();
161 			// get the tag description
162 			const char *description = s.getTagDescription(TagLib::ANIMATION, id);
163 			FreeImage_SetTagDescription(tag, description);
164 		}
165 		// store the tag
166 		bResult = FreeImage_SetMetadata(model, dib, key, tag);
167 		FreeImage_DeleteTag(tag);
168 	}
169 	return bResult;
170 }
171 
172 static BOOL
FreeImage_GetMetadataEx(FREE_IMAGE_MDMODEL model,FIBITMAP * dib,const char * key,FREE_IMAGE_MDTYPE type,FITAG ** tag)173 FreeImage_GetMetadataEx(FREE_IMAGE_MDMODEL model, FIBITMAP *dib, const char *key, FREE_IMAGE_MDTYPE type, FITAG **tag)
174 {
175 	if( FreeImage_GetMetadata(model, dib, key, tag) ) {
176 		if( FreeImage_GetTagType(*tag) == type ) {
177 			return TRUE;
178 		}
179 	}
180 	return FALSE;
181 }
182 
StringTable()183 StringTable::StringTable()
184 {
185 	m_buffer = NULL;
186 	firstPixelPassed = 0; // Still no pixel read
187 	// Maximum number of entries in the map is MAX_LZW_CODE * 256
188 	// (aka 2**12 * 2**8 => a 20 bits key)
189 	// This Map could be optmized to only handle MAX_LZW_CODE * 2**(m_bpp)
190 	m_strmap = new(std::nothrow) int[1<<20];
191 }
192 
~StringTable()193 StringTable::~StringTable()
194 {
195 	if( m_buffer != NULL ) {
196 		delete [] m_buffer;
197 	}
198 	if( m_strmap != NULL ) {
199 		delete [] m_strmap;
200 		m_strmap = NULL;
201 	}
202 }
203 
Initialize(int minCodeSize)204 void StringTable::Initialize(int minCodeSize)
205 {
206 	m_done = false;
207 
208 	m_bpp = 8;
209 	m_minCodeSize = minCodeSize;
210 	m_clearCode = 1 << m_minCodeSize;
211 	if(m_clearCode > MAX_LZW_CODE) {
212 		m_clearCode = MAX_LZW_CODE;
213 	}
214 	m_endCode = m_clearCode + 1;
215 
216 	m_partial = 0;
217 	m_partialSize = 0;
218 
219 	m_bufferSize = 0;
220 	ClearCompressorTable();
221 	ClearDecompressorTable();
222 }
223 
FillInputBuffer(int len)224 BYTE *StringTable::FillInputBuffer(int len)
225 {
226 	if( m_buffer == NULL ) {
227 		m_buffer = new(std::nothrow) BYTE[len];
228 		m_bufferRealSize = len;
229 	} else if( len > m_bufferRealSize ) {
230 		delete [] m_buffer;
231 		m_buffer = new(std::nothrow) BYTE[len];
232 		m_bufferRealSize = len;
233 	}
234 	m_bufferSize = len;
235 	m_bufferPos = 0;
236 	m_bufferShift = 8 - m_bpp;
237 	return m_buffer;
238 }
239 
CompressStart(int bpp,int width)240 void StringTable::CompressStart(int bpp, int width)
241 {
242 	m_bpp = bpp;
243 	m_slack = (8 - ((width * bpp) % 8)) % 8;
244 
245 	m_partial |= m_clearCode << m_partialSize;
246 	m_partialSize += m_codeSize;
247 	ClearCompressorTable();
248 }
249 
CompressEnd(BYTE * buf)250 int StringTable::CompressEnd(BYTE *buf)
251 {
252 	int len = 0;
253 
254 	//output code for remaining prefix
255 	m_partial |= m_prefix << m_partialSize;
256 	m_partialSize += m_codeSize;
257 	while( m_partialSize >= 8 ) {
258 		*buf++ = (BYTE)m_partial;
259 		m_partial >>= 8;
260 		m_partialSize -= 8;
261 		len++;
262 	}
263 
264 	//add the end of information code and flush the entire buffer out
265 	m_partial |= m_endCode << m_partialSize;
266 	m_partialSize += m_codeSize;
267 	while( m_partialSize > 0 ) {
268 		*buf++ = (BYTE)m_partial;
269 		m_partial >>= 8;
270 		m_partialSize -= 8;
271 		len++;
272 	}
273 
274 	//most this can be is 4 bytes.  7 bits in m_partial to start + 12 for the
275 	//last code + 12 for the end code = 31 bits total.
276 	return len;
277 }
278 
Compress(BYTE * buf,int * len)279 bool StringTable::Compress(BYTE *buf, int *len)
280 {
281 	if( m_bufferSize == 0 || m_done ) {
282 		return false;
283 	}
284 
285 	int mask = (1 << m_bpp) - 1;
286 	BYTE *bufpos = buf;
287 	while( m_bufferPos < m_bufferSize ) {
288 		//get the current pixel value
289 		char ch = (char)((m_buffer[m_bufferPos] >> m_bufferShift) & mask);
290 
291 		// The next prefix is :
292 		// <the previous LZW code (on 12 bits << 8)> | <the code of the current pixel (on 8 bits)>
293 		int nextprefix = (((m_prefix)<<8)&0xFFF00) + (ch & 0x000FF);
294 		if(firstPixelPassed) {
295 
296 			if( m_strmap[nextprefix] > 0) {
297 				m_prefix = m_strmap[nextprefix];
298 			} else {
299 				m_partial |= m_prefix << m_partialSize;
300 				m_partialSize += m_codeSize;
301 				//grab full bytes for the output buffer
302 				while( m_partialSize >= 8 && bufpos - buf < *len ) {
303 					*bufpos++ = (BYTE)m_partial;
304 					m_partial >>= 8;
305 					m_partialSize -= 8;
306 				}
307 
308 				//add the code to the "table map"
309 				m_strmap[nextprefix] = m_nextCode;
310 
311 				//increment the next highest valid code, increase the code size
312 				if( m_nextCode == (1 << m_codeSize) ) {
313 					m_codeSize++;
314 				}
315 				m_nextCode++;
316 
317 				//if we're out of codes, restart the string table
318 				if( m_nextCode == MAX_LZW_CODE ) {
319 					m_partial |= m_clearCode << m_partialSize;
320 					m_partialSize += m_codeSize;
321 					ClearCompressorTable();
322 				}
323 
324 				// Only keep the 8 lowest bits (prevent problems with "negative chars")
325 				m_prefix = ch & 0x000FF;
326 			}
327 
328 			//increment to the next pixel
329 			if( m_bufferShift > 0 && !(m_bufferPos + 1 == m_bufferSize && m_bufferShift <= m_slack) ) {
330 				m_bufferShift -= m_bpp;
331 			} else {
332 				m_bufferPos++;
333 				m_bufferShift = 8 - m_bpp;
334 			}
335 
336 			//jump out here if the output buffer is full
337 			if( bufpos - buf == *len ) {
338 				return true;
339 			}
340 
341 		} else {
342 			// Specific behavior for the first pixel of the whole image
343 
344 			firstPixelPassed=1;
345 			// Only keep the 8 lowest bits (prevent problems with "negative chars")
346 			m_prefix = ch & 0x000FF;
347 
348 			//increment to the next pixel
349 			if( m_bufferShift > 0 && !(m_bufferPos + 1 == m_bufferSize && m_bufferShift <= m_slack) ) {
350 				m_bufferShift -= m_bpp;
351 			} else {
352 				m_bufferPos++;
353 				m_bufferShift = 8 - m_bpp;
354 			}
355 
356 			//jump out here if the output buffer is full
357 			if( bufpos - buf == *len ) {
358 				return true;
359 			}
360 		}
361 	}
362 
363 	m_bufferSize = 0;
364 	*len = (int)(bufpos - buf);
365 
366 	return true;
367 }
368 
Decompress(BYTE * buf,int * len)369 bool StringTable::Decompress(BYTE *buf, int *len)
370 {
371 	if( m_bufferSize == 0 || m_done ) {
372 		return false;
373 	}
374 
375 	BYTE *bufpos = buf;
376 	for( ; m_bufferPos < m_bufferSize; m_bufferPos++ ) {
377 		m_partial |= (int)m_buffer[m_bufferPos] << m_partialSize;
378 		m_partialSize += 8;
379 		while( m_partialSize >= m_codeSize ) {
380 			int code = m_partial & m_codeMask;
381 			m_partial >>= m_codeSize;
382 			m_partialSize -= m_codeSize;
383 
384 			if( code > m_nextCode || /*(m_nextCode == MAX_LZW_CODE && code != m_clearCode) || */code == m_endCode ) {
385 				m_done = true;
386 				*len = (int)(bufpos - buf);
387 				return true;
388 			}
389 			if( code == m_clearCode ) {
390 				ClearDecompressorTable();
391 				continue;
392 			}
393 
394 			//add new string to string table, if not the first pass since a clear code
395 			if( m_oldCode != MAX_LZW_CODE && m_nextCode < MAX_LZW_CODE) {
396 				m_strings[m_nextCode] = m_strings[m_oldCode] + m_strings[code == m_nextCode ? m_oldCode : code][0];
397 			}
398 
399 			if( (int)m_strings[code].size() > *len - (bufpos - buf) ) {
400 				//out of space, stuff the code back in for next time
401 				m_partial <<= m_codeSize;
402 				m_partialSize += m_codeSize;
403 				m_partial |= code;
404 				m_bufferPos++;
405 				*len = (int)(bufpos - buf);
406 				return true;
407 			}
408 
409 			//output the string into the buffer
410 			memcpy(bufpos, m_strings[code].data(), m_strings[code].size());
411 			bufpos += m_strings[code].size();
412 
413 			//increment the next highest valid code, add a bit to the mask if we need to increase the code size
414 			if( m_oldCode != MAX_LZW_CODE && m_nextCode < MAX_LZW_CODE ) {
415 				if( ++m_nextCode < MAX_LZW_CODE ) {
416 					if( (m_nextCode & m_codeMask) == 0 ) {
417 						m_codeSize++;
418 						m_codeMask |= m_nextCode;
419 					}
420 				}
421 			}
422 
423 			m_oldCode = code;
424 		}
425 	}
426 
427 	m_bufferSize = 0;
428 	*len = (int)(bufpos - buf);
429 
430 	return true;
431 }
432 
Done(void)433 void StringTable::Done(void)
434 {
435 	m_done = true;
436 }
437 
ClearCompressorTable(void)438 void StringTable::ClearCompressorTable(void)
439 {
440 	if(m_strmap) {
441 		memset(m_strmap, 0xFF, sizeof(unsigned int)*(1<<20));
442 	}
443 	m_nextCode = m_endCode + 1;
444 
445 	m_prefix = 0;
446 	m_codeSize = m_minCodeSize + 1;
447 }
448 
ClearDecompressorTable(void)449 void StringTable::ClearDecompressorTable(void)
450 {
451 	for( int i = 0; i < m_clearCode; i++ ) {
452 		m_strings[i].resize(1);
453 		m_strings[i][0] = (char)i;
454 	}
455 	m_nextCode = m_endCode + 1;
456 
457 	m_codeSize = m_minCodeSize + 1;
458 	m_codeMask = (1 << m_codeSize) - 1;
459 	m_oldCode = MAX_LZW_CODE;
460 }
461 
462 // ==========================================================
463 // Plugin Interface
464 // ==========================================================
465 
466 static int s_format_id;
467 
468 // ==========================================================
469 // Plugin Implementation
470 // ==========================================================
471 
472 static const char * DLL_CALLCONV
Format()473 Format() {
474 	return "GIF";
475 }
476 
477 static const char * DLL_CALLCONV
Description()478 Description() {
479 	return "Graphics Interchange Format";
480 }
481 
482 static const char * DLL_CALLCONV
Extension()483 Extension() {
484 	return "gif";
485 }
486 
487 static const char * DLL_CALLCONV
RegExpr()488 RegExpr() {
489 	return "^GIF";
490 }
491 
492 static const char * DLL_CALLCONV
MimeType()493 MimeType() {
494 	return "image/gif";
495 }
496 
497 static BOOL DLL_CALLCONV
Validate(FreeImageIO * io,fi_handle handle)498 Validate(FreeImageIO *io, fi_handle handle) {
499 	BYTE GIF89a[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };	// ASCII code for "GIF89a"
500 	BYTE GIF87a[] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 };	// ASCII code for "GIF87a"
501 	BYTE signature[6] = { 0, 0, 0, 0, 0, 0 };
502 
503 	io->read_proc(signature, 1, 6, handle);
504 
505 	if (memcmp(GIF89a, signature, 6) == 0)
506 		return TRUE;
507 	if (memcmp(GIF87a, signature, 6) == 0)
508 		return TRUE;
509 
510 	return FALSE;
511 }
512 
513 static BOOL DLL_CALLCONV
SupportsExportDepth(int depth)514 SupportsExportDepth(int depth) {
515 	return	(depth == 1) ||
516 			(depth == 4) ||
517 			(depth == 8);
518 }
519 
520 static BOOL DLL_CALLCONV
SupportsExportType(FREE_IMAGE_TYPE type)521 SupportsExportType(FREE_IMAGE_TYPE type) {
522 	return (type == FIT_BITMAP) ? TRUE : FALSE;
523 }
524 
525 // ----------------------------------------------------------
526 
527 static void *DLL_CALLCONV
Open(FreeImageIO * io,fi_handle handle,BOOL read)528 Open(FreeImageIO *io, fi_handle handle, BOOL read) {
529 	GIFinfo *info = new(std::nothrow) GIFinfo;
530 	if( info == NULL ) {
531 		return NULL;
532 	}
533 
534 	// set Read/Write mode
535 	info->read = read;
536 
537 	if( read ) {
538 		try {
539 			// read Header (6 bytes)
540 			if( !Validate(io, handle) ) {
541 				throw FI_MSG_ERROR_MAGIC_NUMBER;
542 			}
543 
544 			//Logical Screen Descriptor
545 			io->seek_proc(handle, 4, SEEK_CUR);
546 			BYTE packed;
547 			if( io->read_proc(&packed, 1, 1, handle) < 1 ) {
548 				throw "EOF reading Logical Screen Descriptor";
549 			}
550 			if( io->read_proc(&info->background_color, 1, 1, handle) < 1 ) {
551 				throw "EOF reading Logical Screen Descriptor";
552 			}
553 			io->seek_proc(handle, 1, SEEK_CUR);
554 
555 			//Global Color Table
556 			if( packed & GIF_PACKED_LSD_HAVEGCT ) {
557 				info->global_color_table_offset = io->tell_proc(handle);
558 				info->global_color_table_size = 2 << (packed & GIF_PACKED_LSD_GCTSIZE);
559 				io->seek_proc(handle, 3 * info->global_color_table_size, SEEK_CUR);
560 			}
561 
562 			//Scan through all the rest of the blocks, saving offsets
563 			size_t gce_offset = 0;
564 			BYTE block = 0;
565 			while( block != GIF_BLOCK_TRAILER ) {
566 				if( io->read_proc(&block, 1, 1, handle) < 1 ) {
567 					throw "EOF reading blocks";
568 				}
569 				if( block == GIF_BLOCK_IMAGE_DESCRIPTOR ) {
570 					info->image_descriptor_offsets.push_back(io->tell_proc(handle));
571 					//GCE may be 0, meaning no GCE preceded this ID
572 					info->graphic_control_extension_offsets.push_back(gce_offset);
573 					gce_offset = 0;
574 
575 					io->seek_proc(handle, 8, SEEK_CUR);
576 					if( io->read_proc(&packed, 1, 1, handle) < 1 ) {
577 						throw "EOF reading Image Descriptor";
578 					}
579 
580 					//Local Color Table
581 					if( packed & GIF_PACKED_ID_HAVELCT ) {
582 						io->seek_proc(handle, 3 * (2 << (packed & GIF_PACKED_ID_LCTSIZE)), SEEK_CUR);
583 					}
584 
585 					//LZW Minimum Code Size
586 					io->seek_proc(handle, 1, SEEK_CUR);
587 				} else if( block == GIF_BLOCK_EXTENSION ) {
588 					BYTE ext;
589 					if( io->read_proc(&ext, 1, 1, handle) < 1 ) {
590 						throw "EOF reading extension";
591 					}
592 
593 					if( ext == GIF_EXT_GRAPHIC_CONTROL ) {
594 						//overwrite previous offset if more than one GCE found before an ID
595 						gce_offset = io->tell_proc(handle);
596 					} else if( ext == GIF_EXT_COMMENT ) {
597 						info->comment_extension_offsets.push_back(io->tell_proc(handle));
598 					} else if( ext == GIF_EXT_APPLICATION ) {
599 						info->application_extension_offsets.push_back(io->tell_proc(handle));
600 					}
601 				} else if( block == GIF_BLOCK_TRAILER ) {
602 					continue;
603 				} else {
604 					throw "Invalid GIF block found";
605 				}
606 
607 				//Data Sub-blocks
608 				BYTE len;
609 				if( io->read_proc(&len, 1, 1, handle) < 1 ) {
610 					throw "EOF reading sub-block";
611 				}
612 				while( len != 0 ) {
613 					io->seek_proc(handle, len, SEEK_CUR);
614 					if( io->read_proc(&len, 1, 1, handle) < 1 ) {
615 						throw "EOF reading sub-block";
616 					}
617 				}
618 			}
619 		} catch (const char *msg) {
620 			FreeImage_OutputMessageProc(s_format_id, msg);
621 			delete info;
622 			return NULL;
623 		}
624 	} else {
625 		//Header
626 		io->write_proc((void *)"GIF89a", 6, 1, handle);
627 	}
628 
629 	return info;
630 }
631 
632 static void DLL_CALLCONV
Close(FreeImageIO * io,fi_handle handle,void * data)633 Close(FreeImageIO *io, fi_handle handle, void *data) {
634 	if( data == NULL ) {
635 		return;
636 	}
637 	GIFinfo *info = (GIFinfo *)data;
638 
639 	if( !info->read ) {
640 		//Trailer
641 		BYTE b = GIF_BLOCK_TRAILER;
642 		io->write_proc(&b, 1, 1, handle);
643 	}
644 
645 	delete info;
646 }
647 
648 static int DLL_CALLCONV
PageCount(FreeImageIO * io,fi_handle handle,void * data)649 PageCount(FreeImageIO *io, fi_handle handle, void *data) {
650 	if( data == NULL ) {
651 		return 0;
652 	}
653 	GIFinfo *info = (GIFinfo *)data;
654 
655 	return (int) info->image_descriptor_offsets.size();
656 }
657 
658 static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO * io,fi_handle handle,int page,int flags,void * data)659 Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
660 	if( data == NULL ) {
661 		return NULL;
662 	}
663 	GIFinfo *info = (GIFinfo *)data;
664 
665 	if( page == -1 ) {
666 		page = 0;
667 	}
668 	if( page < 0 || page >= (int)info->image_descriptor_offsets.size() ) {
669 		return NULL;
670 	}
671 
672 	FIBITMAP *dib = NULL;
673 	try {
674 		bool have_transparent = false, no_local_palette = false, interlaced = false;
675 		int disposal_method = GIF_DISPOSAL_LEAVE, delay_time = 0, transparent_color = 0;
676 		WORD left, top, width, height;
677 		BYTE packed, b;
678 		WORD w;
679 
680 		//playback pages to generate what the user would see for this frame
681 		if( (flags & GIF_PLAYBACK) == GIF_PLAYBACK ) {
682 			//Logical Screen Descriptor
683 			io->seek_proc(handle, 6, SEEK_SET);
684 			WORD logicalwidth, logicalheight;
685 			io->read_proc(&logicalwidth, 2, 1, handle);
686 			io->read_proc(&logicalheight, 2, 1, handle);
687 #ifdef FREEIMAGE_BIGENDIAN
688 			SwapShort(&logicalwidth);
689 			SwapShort(&logicalheight);
690 #endif
691 			//set the background color with 0 alpha
692 			RGBQUAD background;
693 			if( info->global_color_table_offset != 0 && info->background_color < info->global_color_table_size ) {
694 				io->seek_proc(handle, (long)(info->global_color_table_offset + (info->background_color * 3)), SEEK_SET);
695 				io->read_proc(&background.rgbRed, 1, 1, handle);
696 				io->read_proc(&background.rgbGreen, 1, 1, handle);
697 				io->read_proc(&background.rgbBlue, 1, 1, handle);
698 			} else {
699 				background.rgbRed = 0;
700 				background.rgbGreen = 0;
701 				background.rgbBlue = 0;
702 			}
703 			background.rgbReserved = 0;
704 
705 			//allocate entire logical area
706 			dib = FreeImage_Allocate(logicalwidth, logicalheight, 32);
707 			if( dib == NULL ) {
708 				throw FI_MSG_ERROR_DIB_MEMORY;
709 			}
710 
711 			//fill with background color to start
712 			int x, y;
713 			RGBQUAD *scanline;
714 			for( y = 0; y < logicalheight; y++ ) {
715 				scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, y);
716 				for( x = 0; x < logicalwidth; x++ ) {
717 					*scanline++ = background;
718 				}
719 			}
720 
721 			//cache some info about each of the pages so we can avoid decoding as many of them as possible
722 			std::vector<PageInfo> pageinfo;
723 			int start = page, end = page;
724 			while( start >= 0 ) {
725 				//Graphic Control Extension
726 				io->seek_proc(handle, (long)(info->graphic_control_extension_offsets[start] + 1), SEEK_SET);
727 				io->read_proc(&packed, 1, 1, handle);
728 				have_transparent = (packed & GIF_PACKED_GCE_HAVETRANS) ? true : false;
729 				disposal_method = (packed & GIF_PACKED_GCE_DISPOSAL) >> 2;
730 				//Image Descriptor
731 				io->seek_proc(handle, (long)(info->image_descriptor_offsets[start]), SEEK_SET);
732 				io->read_proc(&left, 2, 1, handle);
733 				io->read_proc(&top, 2, 1, handle);
734 				io->read_proc(&width, 2, 1, handle);
735 				io->read_proc(&height, 2, 1, handle);
736 #ifdef FREEIMAGE_BIGENDIAN
737 				SwapShort(&left);
738 				SwapShort(&top);
739 				SwapShort(&width);
740 				SwapShort(&height);
741 #endif
742 
743 				pageinfo.push_back(PageInfo(disposal_method, left, top, width, height));
744 
745 				if( start != end ) {
746 					if( left == 0 && top == 0 && width == logicalwidth && height == logicalheight ) {
747 						if( disposal_method == GIF_DISPOSAL_BACKGROUND ) {
748 							pageinfo.pop_back();
749 							start++;
750 							break;
751 						} else if( disposal_method != GIF_DISPOSAL_PREVIOUS ) {
752 							if( !have_transparent ) {
753 								break;
754 							}
755 						}
756 					}
757 				}
758 				start--;
759 			}
760 			if( start < 0 ) {
761 				start = 0;
762 			}
763 
764 			//draw each page into the logical area
765 			delay_time = 0;
766 			for( page = start; page <= end; page++ ) {
767 				PageInfo &info = pageinfo[end - page];
768 				//things we can skip having to decode
769 				if( page != end ) {
770 					if( info.disposal_method == GIF_DISPOSAL_PREVIOUS ) {
771 						continue;
772 					}
773 					if( info.disposal_method == GIF_DISPOSAL_BACKGROUND ) {
774 						for( y = 0; y < info.height; y++ ) {
775 							const int scanidx = logicalheight - (y + info.top) - 1;
776 							if ( scanidx < 0 ) {
777 								break;  // If data is corrupt, don't calculate in invalid scanline
778 							}
779 							scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, scanidx) + info.left;
780 							for( x = 0; x < info.width; x++ ) {
781 								*scanline++ = background;
782 							}
783 						}
784 						continue;
785 					}
786 				}
787 
788 				//decode page
789 				FIBITMAP *pagedib = Load(io, handle, page, GIF_LOAD256, data);
790 				if( pagedib != NULL ) {
791 					RGBQUAD *pal = FreeImage_GetPalette(pagedib);
792 					have_transparent = false;
793 					if( FreeImage_IsTransparent(pagedib) ) {
794 						int count = FreeImage_GetTransparencyCount(pagedib);
795 						BYTE *table = FreeImage_GetTransparencyTable(pagedib);
796 						for( int i = 0; i < count; i++ ) {
797 							if( table[i] == 0 ) {
798 								have_transparent = true;
799 								transparent_color = i;
800 								break;
801 							}
802 						}
803 					}
804 					//copy page data into logical buffer, with full alpha opaqueness
805 					for( y = 0; y < info.height; y++ ) {
806 						const int scanidx = logicalheight - (y + info.top) - 1;
807 						if ( scanidx < 0 ) {
808 							break;  // If data is corrupt, don't calculate in invalid scanline
809 						}
810 						scanline = (RGBQUAD *)FreeImage_GetScanLine(dib, scanidx) + info.left;
811 						BYTE *pageline = FreeImage_GetScanLine(pagedib, info.height - y - 1);
812 						for( x = 0; x < info.width; x++ ) {
813 							if( !have_transparent || *pageline != transparent_color ) {
814 								*scanline = pal[*pageline];
815 								scanline->rgbReserved = 255;
816 							}
817 							scanline++;
818 							pageline++;
819 						}
820 					}
821 					//copy frame time
822 					if( page == end ) {
823 						FITAG *tag;
824 						if( FreeImage_GetMetadataEx(FIMD_ANIMATION, pagedib, "FrameTime", FIDT_LONG, &tag) ) {
825 							delay_time = *(LONG *)FreeImage_GetTagValue(tag);
826 						}
827 					}
828 					FreeImage_Unload(pagedib);
829 				}
830 			}
831 
832 			//setup frame time
833 			FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "FrameTime", ANIMTAG_FRAMETIME, FIDT_LONG, 1, 4, &delay_time);
834 			return dib;
835 		}
836 
837 		//get the actual frame image data for a single frame
838 
839 		//Image Descriptor
840 		io->seek_proc(handle, (long)info->image_descriptor_offsets[page], SEEK_SET);
841 		io->read_proc(&left, 2, 1, handle);
842 		io->read_proc(&top, 2, 1, handle);
843 		io->read_proc(&width, 2, 1, handle);
844 		io->read_proc(&height, 2, 1, handle);
845 #ifdef FREEIMAGE_BIGENDIAN
846 		SwapShort(&left);
847 		SwapShort(&top);
848 		SwapShort(&width);
849 		SwapShort(&height);
850 #endif
851 		io->read_proc(&packed, 1, 1, handle);
852 		interlaced = (packed & GIF_PACKED_ID_INTERLACED) ? true : false;
853 		no_local_palette = (packed & GIF_PACKED_ID_HAVELCT) ? false : true;
854 
855 		int bpp = 8;
856 		if( (flags & GIF_LOAD256) == 0 ) {
857 			if( !no_local_palette ) {
858 				int size = 2 << (packed & GIF_PACKED_ID_LCTSIZE);
859 				if( size <= 2 ) bpp = 1;
860 				else if( size <= 16 ) bpp = 4;
861 			} else if( info->global_color_table_offset != 0 ) {
862 				if( info->global_color_table_size <= 2 ) bpp = 1;
863 				else if( info->global_color_table_size <= 16 ) bpp = 4;
864 			}
865 		}
866 		dib = FreeImage_Allocate(width, height, bpp);
867 		if( dib == NULL ) {
868 			throw FI_MSG_ERROR_DIB_MEMORY;
869 		}
870 
871 		FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "FrameLeft", ANIMTAG_FRAMELEFT, FIDT_SHORT, 1, 2, &left);
872 		FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "FrameTop", ANIMTAG_FRAMETOP, FIDT_SHORT, 1, 2, &top);
873 		b = no_local_palette ? 1 : 0;
874 		FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "NoLocalPalette", ANIMTAG_NOLOCALPALETTE, FIDT_BYTE, 1, 1, &b);
875 		b = interlaced ? 1 : 0;
876 		FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "Interlaced", ANIMTAG_INTERLACED, FIDT_BYTE, 1, 1, &b);
877 
878 		//Palette
879 		RGBQUAD *pal = FreeImage_GetPalette(dib);
880 		if( !no_local_palette ) {
881 			int size = 2 << (packed & GIF_PACKED_ID_LCTSIZE);
882 
883 			int i = 0;
884 			while( i < size ) {
885 				io->read_proc(&pal[i].rgbRed, 1, 1, handle);
886 				io->read_proc(&pal[i].rgbGreen, 1, 1, handle);
887 				io->read_proc(&pal[i].rgbBlue, 1, 1, handle);
888 				i++;
889 			}
890 		} else if( info->global_color_table_offset != 0 ) {
891 			long pos = io->tell_proc(handle);
892 			io->seek_proc(handle, (long)info->global_color_table_offset, SEEK_SET);
893 
894 			int i = 0;
895 			while( i < info->global_color_table_size ) {
896 				io->read_proc(&pal[i].rgbRed, 1, 1, handle);
897 				io->read_proc(&pal[i].rgbGreen, 1, 1, handle);
898 				io->read_proc(&pal[i].rgbBlue, 1, 1, handle);
899 				i++;
900 			}
901 
902 			io->seek_proc(handle, pos, SEEK_SET);
903 		} else {
904 			//its legal to have no palette, but we're going to generate *something*
905 			for( int i = 0; i < 256; i++ ) {
906 				pal[i].rgbRed   = (BYTE)i;
907 				pal[i].rgbGreen = (BYTE)i;
908 				pal[i].rgbBlue  = (BYTE)i;
909 			}
910 		}
911 
912 		//LZW Minimum Code Size
913 		io->read_proc(&b, 1, 1, handle);
914 		StringTable *stringtable = new(std::nothrow) StringTable;
915 		stringtable->Initialize(b);
916 
917 		//Image Data Sub-blocks
918 		int x = 0, xpos = 0, y = 0, shift = 8 - bpp, mask = (1 << bpp) - 1, interlacepass = 0;
919 		BYTE *scanline = FreeImage_GetScanLine(dib, height - 1);
920 		BYTE buf[4096];
921 		io->read_proc(&b, 1, 1, handle);
922 		while( b ) {
923 			io->read_proc(stringtable->FillInputBuffer(b), b, 1, handle);
924 			int size = sizeof(buf);
925 			while( stringtable->Decompress(buf, &size) ) {
926 				for( int i = 0; i < size; i++ ) {
927 					scanline[xpos] |= (buf[i] & mask) << shift;
928 					if( shift > 0 ) {
929 						shift -= bpp;
930 					} else {
931 						xpos++;
932 						shift = 8 - bpp;
933 					}
934 					if( ++x >= width ) {
935 						if( interlaced ) {
936 							y += g_GifInterlaceIncrement[interlacepass];
937 							if( y >= height && ++interlacepass < GIF_INTERLACE_PASSES ) {
938 								y = g_GifInterlaceOffset[interlacepass];
939 							}
940 						} else {
941 							y++;
942 						}
943 						if( y >= height ) {
944 							stringtable->Done();
945 							break;
946 						}
947 						x = xpos = 0;
948 						shift = 8 - bpp;
949 						scanline = FreeImage_GetScanLine(dib, height - y - 1);
950 					}
951 				}
952 				size = sizeof(buf);
953 			}
954 			io->read_proc(&b, 1, 1, handle);
955 		}
956 
957 		if( page == 0 ) {
958 			size_t idx;
959 
960 			//Logical Screen Descriptor
961 			io->seek_proc(handle, 6, SEEK_SET);
962 			WORD logicalwidth, logicalheight;
963 			io->read_proc(&logicalwidth, 2, 1, handle);
964 			io->read_proc(&logicalheight, 2, 1, handle);
965 #ifdef FREEIMAGE_BIGENDIAN
966 			SwapShort(&logicalwidth);
967 			SwapShort(&logicalheight);
968 #endif
969 			FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "LogicalWidth", ANIMTAG_LOGICALWIDTH, FIDT_SHORT, 1, 2, &logicalwidth);
970 			FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "LogicalHeight", ANIMTAG_LOGICALHEIGHT, FIDT_SHORT, 1, 2, &logicalheight);
971 
972 			//Global Color Table
973 			if( info->global_color_table_offset != 0 ) {
974 				RGBQUAD globalpalette[256];
975 				io->seek_proc(handle, (long)info->global_color_table_offset, SEEK_SET);
976 				int i = 0;
977 				while( i < info->global_color_table_size ) {
978 					io->read_proc(&globalpalette[i].rgbRed, 1, 1, handle);
979 					io->read_proc(&globalpalette[i].rgbGreen, 1, 1, handle);
980 					io->read_proc(&globalpalette[i].rgbBlue, 1, 1, handle);
981 					globalpalette[i].rgbReserved = 0;
982 					i++;
983 				}
984 				FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "GlobalPalette", ANIMTAG_GLOBALPALETTE, FIDT_PALETTE, info->global_color_table_size, info->global_color_table_size * 4, globalpalette);
985 				//background color
986 				if( info->background_color < info->global_color_table_size ) {
987 					FreeImage_SetBackgroundColor(dib, &globalpalette[info->background_color]);
988 				}
989 			}
990 
991 			//Application Extension
992 			LONG loop = 1; //If no AE with a loop count is found, the default must be 1
993 			for( idx = 0; idx < info->application_extension_offsets.size(); idx++ ) {
994 				io->seek_proc(handle, (long)info->application_extension_offsets[idx], SEEK_SET);
995 				io->read_proc(&b, 1, 1, handle);
996 				if( b == 11 ) { //All AEs start with an 11 byte sub-block to determine what type of AE it is
997 					char buf[11];
998 					io->read_proc(buf, 11, 1, handle);
999 					if( !memcmp(buf, "NETSCAPE2.0", 11) || !memcmp(buf, "ANIMEXTS1.0", 11) ) { //Not everybody recognizes ANIMEXTS1.0 but it is valid
1000 						io->read_proc(&b, 1, 1, handle);
1001 						if( b == 3 ) { //we're supposed to have a 3 byte sub-block now
1002 							io->read_proc(&b, 1, 1, handle); //this should be 0x01 but isn't really important
1003 							io->read_proc(&w, 2, 1, handle);
1004 #ifdef FREEIMAGE_BIGENDIAN
1005 							SwapShort(&w);
1006 #endif
1007 							loop = w;
1008 							if( loop > 0 ) loop++;
1009 							break;
1010 						}
1011 					}
1012 				}
1013 			}
1014 			FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "Loop", ANIMTAG_LOOP, FIDT_LONG, 1, 4, &loop);
1015 
1016 			//Comment Extension
1017 			for( idx = 0; idx < info->comment_extension_offsets.size(); idx++ ) {
1018 				io->seek_proc(handle, (long)info->comment_extension_offsets[idx], SEEK_SET);
1019 				std::string comment;
1020 				char buf[255];
1021 				io->read_proc(&b, 1, 1, handle);
1022 				while( b ) {
1023 					io->read_proc(buf, b, 1, handle);
1024 					comment.append(buf, b);
1025 					io->read_proc(&b, 1, 1, handle);
1026 				}
1027 				comment.append(1, '\0');
1028 				sprintf(buf, "Comment%zd", idx);
1029 				DWORD comment_size = (DWORD)comment.size();
1030 				FreeImage_SetMetadataEx(FIMD_COMMENTS, dib, buf, 1, FIDT_ASCII, comment_size, comment_size, comment.c_str());
1031 			}
1032 		}
1033 
1034 		//Graphic Control Extension
1035 		if( info->graphic_control_extension_offsets[page] != 0 ) {
1036 			io->seek_proc(handle, (long)(info->graphic_control_extension_offsets[page] + 1), SEEK_SET);
1037 			io->read_proc(&packed, 1, 1, handle);
1038 			io->read_proc(&w, 2, 1, handle);
1039 #ifdef FREEIMAGE_BIGENDIAN
1040 			SwapShort(&w);
1041 #endif
1042 			io->read_proc(&b, 1, 1, handle);
1043 			have_transparent = (packed & GIF_PACKED_GCE_HAVETRANS) ? true : false;
1044 			disposal_method = (packed & GIF_PACKED_GCE_DISPOSAL) >> 2;
1045 			delay_time = w * 10; //convert cs to ms
1046 			transparent_color = b;
1047 			if( have_transparent ) {
1048 				int size = 1 << bpp;
1049 				if( transparent_color <= size ) {
1050 					BYTE table[256];
1051 					memset(table, 0xFF, size);
1052 					table[transparent_color] = 0;
1053 					FreeImage_SetTransparencyTable(dib, table, size);
1054 				}
1055 			}
1056 		}
1057 		FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "FrameTime", ANIMTAG_FRAMETIME, FIDT_LONG, 1, 4, &delay_time);
1058 		b = (BYTE)disposal_method;
1059 		FreeImage_SetMetadataEx(FIMD_ANIMATION, dib, "DisposalMethod", ANIMTAG_DISPOSALMETHOD, FIDT_BYTE, 1, 1, &b);
1060 
1061 		delete stringtable;
1062 
1063 	} catch (const char *msg) {
1064 		if( dib != NULL ) {
1065 			FreeImage_Unload(dib);
1066 		}
1067 		FreeImage_OutputMessageProc(s_format_id, msg);
1068 		return NULL;
1069 	}
1070 
1071 	return dib;
1072 }
1073 
1074 static BOOL DLL_CALLCONV
Save(FreeImageIO * io,FIBITMAP * dib,fi_handle handle,int page,int flags,void * data)1075 Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) {
1076 	if( data == NULL ) {
1077 		return FALSE;
1078 	}
1079 	//GIFinfo *info = (GIFinfo *)data;
1080 
1081 	if( page == -1 ) {
1082 		page = 0;
1083 	}
1084 
1085 	try {
1086 		BYTE packed, b;
1087 		WORD w;
1088 		FITAG *tag;
1089 
1090 		int bpp = FreeImage_GetBPP(dib);
1091 		if( bpp != 1 && bpp != 4 && bpp != 8 ) {
1092 			throw "Only 1, 4, or 8 bpp images supported";
1093 		}
1094 
1095 		bool have_transparent = false, no_local_palette = false, interlaced = false;
1096 		int disposal_method = GIF_DISPOSAL_BACKGROUND, delay_time = 100, transparent_color = 0;
1097 		WORD left = 0, top = 0, width = (WORD)FreeImage_GetWidth(dib), height = (WORD)FreeImage_GetHeight(dib);
1098 		WORD output_height = height;
1099 		if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "FrameLeft", FIDT_SHORT, &tag) ) {
1100 			left = *(WORD *)FreeImage_GetTagValue(tag);
1101 		}
1102 		if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "FrameTop", FIDT_SHORT, &tag) ) {
1103 			top = *(WORD *)FreeImage_GetTagValue(tag);
1104 		}
1105 		if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "NoLocalPalette", FIDT_BYTE, &tag) ) {
1106 			no_local_palette = *(BYTE *)FreeImage_GetTagValue(tag) ? true : false;
1107 		}
1108 		if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "Interlaced", FIDT_BYTE, &tag) ) {
1109 			interlaced = *(BYTE *)FreeImage_GetTagValue(tag) ? true : false;
1110 		}
1111 		if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "FrameTime", FIDT_LONG, &tag) ) {
1112 			delay_time = *(LONG *)FreeImage_GetTagValue(tag);
1113 		}
1114 		if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "DisposalMethod", FIDT_BYTE, &tag) ) {
1115 			disposal_method = *(BYTE *)FreeImage_GetTagValue(tag);
1116 		}
1117 
1118 		RGBQUAD *pal = FreeImage_GetPalette(dib);
1119 #ifdef FREEIMAGE_BIGENDIAN
1120 		SwapShort(&left);
1121 		SwapShort(&top);
1122 		SwapShort(&width);
1123 		SwapShort(&height);
1124 #endif
1125 
1126 		if( page == 0 ) {
1127 			//gather some info
1128 			WORD logicalwidth = width; // width has already been swapped...
1129 			if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "LogicalWidth", FIDT_SHORT, &tag) ) {
1130 				logicalwidth = *(WORD *)FreeImage_GetTagValue(tag);
1131 #ifdef FREEIMAGE_BIGENDIAN
1132 				SwapShort(&logicalwidth);
1133 #endif
1134 			}
1135 			WORD logicalheight = height; // height has already been swapped...
1136 			if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "LogicalHeight", FIDT_SHORT, &tag) ) {
1137 				logicalheight = *(WORD *)FreeImage_GetTagValue(tag);
1138 #ifdef FREEIMAGE_BIGENDIAN
1139 				SwapShort(&logicalheight);
1140 #endif
1141 			}
1142 			RGBQUAD *globalpalette = NULL;
1143 			int globalpalette_size = 0;
1144 			if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "GlobalPalette", FIDT_PALETTE, &tag) ) {
1145 				globalpalette_size = FreeImage_GetTagCount(tag);
1146 				if( globalpalette_size >= 2 ) {
1147 					globalpalette = (RGBQUAD *)FreeImage_GetTagValue(tag);
1148 				}
1149 			}
1150 
1151 			//Logical Screen Descriptor
1152 			io->write_proc(&logicalwidth, 2, 1, handle);
1153 			io->write_proc(&logicalheight, 2, 1, handle);
1154 			packed = GIF_PACKED_LSD_COLORRES;
1155 			b = 0;
1156 			RGBQUAD background_color;
1157 			if( globalpalette != NULL ) {
1158 				packed |= GIF_PACKED_LSD_HAVEGCT;
1159 				if( globalpalette_size < 4 ) {
1160 					globalpalette_size = 2;
1161 					packed |= 0 & GIF_PACKED_LSD_GCTSIZE;
1162 				} else if( globalpalette_size < 8 ) {
1163 					globalpalette_size = 4;
1164 					packed |= 1 & GIF_PACKED_LSD_GCTSIZE;
1165 				} else if( globalpalette_size < 16 ) {
1166 					globalpalette_size = 8;
1167 					packed |= 2 & GIF_PACKED_LSD_GCTSIZE;
1168 				} else if( globalpalette_size < 32 ) {
1169 					globalpalette_size = 16;
1170 					packed |= 3 & GIF_PACKED_LSD_GCTSIZE;
1171 				} else if( globalpalette_size < 64 ) {
1172 					globalpalette_size = 32;
1173 					packed |= 4 & GIF_PACKED_LSD_GCTSIZE;
1174 				} else if( globalpalette_size < 128 ) {
1175 					globalpalette_size = 64;
1176 					packed |= 5 & GIF_PACKED_LSD_GCTSIZE;
1177 				} else if( globalpalette_size < 256 ) {
1178 					globalpalette_size = 128;
1179 					packed |= 6 & GIF_PACKED_LSD_GCTSIZE;
1180 				} else {
1181 					globalpalette_size = 256;
1182 					packed |= 7 & GIF_PACKED_LSD_GCTSIZE;
1183 				}
1184 				if( FreeImage_GetBackgroundColor(dib, &background_color) ) {
1185 					for( int i = 0; i < globalpalette_size; i++ ) {
1186 						if( background_color.rgbRed == globalpalette[i].rgbRed &&
1187 							background_color.rgbGreen == globalpalette[i].rgbGreen &&
1188 							background_color.rgbBlue == globalpalette[i].rgbBlue ) {
1189 
1190 							b = (BYTE)i;
1191 							break;
1192 						}
1193 					}
1194 				}
1195 			} else {
1196 				packed |= (bpp - 1) & GIF_PACKED_LSD_GCTSIZE;
1197 			}
1198 			io->write_proc(&packed, 1, 1, handle);
1199 			io->write_proc(&b, 1, 1, handle);
1200 			b = 0;
1201 			io->write_proc(&b, 1, 1, handle);
1202 
1203 			//Global Color Table
1204 			if( globalpalette != NULL ) {
1205 				int i = 0;
1206 				while( i < globalpalette_size ) {
1207 					io->write_proc(&globalpalette[i].rgbRed, 1, 1, handle);
1208 					io->write_proc(&globalpalette[i].rgbGreen, 1, 1, handle);
1209 					io->write_proc(&globalpalette[i].rgbBlue, 1, 1, handle);
1210 					i++;
1211 				}
1212 			}
1213 
1214 			//Application Extension
1215 			LONG loop = 0;
1216 			if( FreeImage_GetMetadataEx(FIMD_ANIMATION, dib, "Loop", FIDT_LONG, &tag) ) {
1217 				loop = *(LONG *)FreeImage_GetTagValue(tag);
1218 			}
1219 			if( loop != 1 ) {
1220 				//the Netscape extension is really "repeats" not "loops"
1221 				if( loop > 1 ) loop--;
1222 				if( loop > 0xFFFF ) loop = 0xFFFF;
1223 				w = (WORD)loop;
1224 #ifdef FREEIMAGE_BIGENDIAN
1225 				SwapShort(&w);
1226 #endif
1227 				io->write_proc((void *)"\x21\xFF\x0BNETSCAPE2.0\x03\x01", 16, 1, handle);
1228 				io->write_proc(&w, 2, 1, handle);
1229 				b = 0;
1230 				io->write_proc(&b, 1, 1, handle);
1231 			}
1232 
1233 			//Comment Extension
1234 			FIMETADATA *mdhandle = NULL;
1235 			FITAG *tag = NULL;
1236 			mdhandle = FreeImage_FindFirstMetadata(FIMD_COMMENTS, dib, &tag);
1237 			if( mdhandle ) {
1238 				do {
1239 					if( FreeImage_GetTagType(tag) == FIDT_ASCII ) {
1240 						int length = FreeImage_GetTagLength(tag) - 1;
1241 						char *value = (char *)FreeImage_GetTagValue(tag);
1242 						io->write_proc((void *)"\x21\xFE", 2, 1, handle);
1243 						while( length > 0 ) {
1244 							b = (BYTE)(length >= 255 ? 255 : length);
1245 							io->write_proc(&b, 1, 1, handle);
1246 							io->write_proc(value, b, 1, handle);
1247 							value += b;
1248 							length -= b;
1249 						}
1250 						b = 0;
1251 						io->write_proc(&b, 1, 1, handle);
1252 					}
1253 				} while(FreeImage_FindNextMetadata(mdhandle, &tag));
1254 
1255 				FreeImage_FindCloseMetadata(mdhandle);
1256 			}
1257 		}
1258 
1259 		//Graphic Control Extension
1260 		if( FreeImage_IsTransparent(dib) ) {
1261 			int count = FreeImage_GetTransparencyCount(dib);
1262 			BYTE *table = FreeImage_GetTransparencyTable(dib);
1263 			for( int i = 0; i < count; i++ ) {
1264 				if( table[i] == 0 ) {
1265 					have_transparent = true;
1266 					transparent_color = i;
1267 					break;
1268 				}
1269 			}
1270 		}
1271 		io->write_proc((void *)"\x21\xF9\x04", 3, 1, handle);
1272 		b = (BYTE)((disposal_method << 2) & GIF_PACKED_GCE_DISPOSAL);
1273 		if( have_transparent ) b |= GIF_PACKED_GCE_HAVETRANS;
1274 		io->write_proc(&b, 1, 1, handle);
1275 		//Notes about delay time for GIFs:
1276 		//IE5/IE6 have a minimum and default of 100ms
1277 		//Mozilla/Firefox/Netscape 6+/Opera have a minimum of 20ms and a default of 100ms if <20ms is specified or the GCE is absent
1278 		//Netscape 4 has a minimum of 10ms if 0ms is specified, but will use 0ms if the GCE is absent
1279 		w = (WORD)(delay_time / 10); //convert ms to cs
1280 #ifdef FREEIMAGE_BIGENDIAN
1281 		SwapShort(&w);
1282 #endif
1283 		io->write_proc(&w, 2, 1, handle);
1284 		b = (BYTE)transparent_color;
1285 		io->write_proc(&b, 1, 1, handle);
1286 		b = 0;
1287 		io->write_proc(&b, 1, 1, handle);
1288 
1289 		//Image Descriptor
1290 		b = GIF_BLOCK_IMAGE_DESCRIPTOR;
1291 		io->write_proc(&b, 1, 1, handle);
1292 		io->write_proc(&left, 2, 1, handle);
1293 		io->write_proc(&top, 2, 1, handle);
1294 		io->write_proc(&width, 2, 1, handle);
1295 		io->write_proc(&height, 2, 1, handle);
1296 		packed = 0;
1297 		if( !no_local_palette ) packed |= GIF_PACKED_ID_HAVELCT | ((bpp - 1) & GIF_PACKED_ID_LCTSIZE);
1298 		if( interlaced ) packed |= GIF_PACKED_ID_INTERLACED;
1299 		io->write_proc(&packed, 1, 1, handle);
1300 
1301 		//Local Color Table
1302 		if( !no_local_palette ) {
1303 			int palsize = 1 << bpp;
1304 			for( int i = 0; i < palsize; i++ ) {
1305 				io->write_proc(&pal[i].rgbRed, 1, 1, handle);
1306 				io->write_proc(&pal[i].rgbGreen, 1, 1, handle);
1307 				io->write_proc(&pal[i].rgbBlue, 1, 1, handle);
1308 			}
1309 		}
1310 
1311 
1312 		//LZW Minimum Code Size
1313 		b = (BYTE)(bpp == 1 ? 2 : bpp);
1314 		io->write_proc(&b, 1, 1, handle);
1315 		StringTable *stringtable = new(std::nothrow) StringTable;
1316 		stringtable->Initialize(b);
1317 		stringtable->CompressStart(bpp, width);
1318 
1319 		//Image Data Sub-blocks
1320 		int y = 0, interlacepass = 0, line = FreeImage_GetLine(dib);
1321 		BYTE buf[255], *bufptr = buf; //255 is the max sub-block length
1322 		int size = sizeof(buf);
1323 		b = sizeof(buf);
1324 		while( y < output_height ) {
1325 			memcpy(stringtable->FillInputBuffer(line), FreeImage_GetScanLine(dib, output_height - y - 1), line);
1326 			while( stringtable->Compress(bufptr, &size) ) {
1327 				bufptr += size;
1328 				if( bufptr - buf == sizeof(buf) ) {
1329 					io->write_proc(&b, 1, 1, handle);
1330 					io->write_proc(buf, sizeof(buf), 1, handle);
1331 					size = sizeof(buf);
1332 					bufptr = buf;
1333 				} else {
1334 					size = (int)(sizeof(buf) - (bufptr - buf));
1335 				}
1336 			}
1337 			if( interlaced ) {
1338 				y += g_GifInterlaceIncrement[interlacepass];
1339 				if( y >= output_height && ++interlacepass < GIF_INTERLACE_PASSES ) {
1340 					y = g_GifInterlaceOffset[interlacepass];
1341 				}
1342 			} else {
1343 				y++;
1344 			}
1345 		}
1346 		size = (int)(bufptr - buf);
1347 		BYTE last[4];
1348 		w = (WORD)stringtable->CompressEnd(last);
1349 		if( size + w >= sizeof(buf) ) {
1350 			//one last full size sub-block
1351 			io->write_proc(&b, 1, 1, handle);
1352 			io->write_proc(buf, size, 1, handle);
1353 			io->write_proc(last, sizeof(buf) - size, 1, handle);
1354 			//and possibly a tiny additional sub-block
1355 			b = (BYTE)(w - (sizeof(buf) - size));
1356 			if( b > 0 ) {
1357 				io->write_proc(&b, 1, 1, handle);
1358 				io->write_proc(last + w - b, b, 1, handle);
1359 			}
1360 		} else {
1361 			//last sub-block less than full size
1362 			b = (BYTE)(size + w);
1363 			io->write_proc(&b, 1, 1, handle);
1364 			io->write_proc(buf, size, 1, handle);
1365 			io->write_proc(last, w, 1, handle);
1366 		}
1367 
1368 		//Block Terminator
1369 		b = 0;
1370 		io->write_proc(&b, 1, 1, handle);
1371 
1372 		delete stringtable;
1373 
1374 	} catch (const char *msg) {
1375 		FreeImage_OutputMessageProc(s_format_id, msg);
1376 		return FALSE;
1377 	}
1378 
1379 	return TRUE;
1380 }
1381 
1382 // ==========================================================
1383 //   Init
1384 // ==========================================================
1385 
1386 void DLL_CALLCONV
InitGIF(Plugin * plugin,int format_id)1387 InitGIF(Plugin *plugin, int format_id) {
1388 	s_format_id = format_id;
1389 
1390 	plugin->format_proc = Format;
1391 	plugin->description_proc = Description;
1392 	plugin->extension_proc = Extension;
1393 	plugin->regexpr_proc = RegExpr;
1394 	plugin->open_proc = Open;
1395 	plugin->close_proc = Close;
1396 	plugin->pagecount_proc = PageCount;
1397 	plugin->pagecapability_proc = NULL;
1398 	plugin->load_proc = Load;
1399 	plugin->save_proc = Save;
1400 	plugin->validate_proc = Validate;
1401 	plugin->mime_proc = MimeType;
1402 	plugin->supports_export_bpp_proc = SupportsExportDepth;
1403 	plugin->supports_export_type_proc = SupportsExportType;
1404 	plugin->supports_icc_profiles_proc = NULL;
1405 }
1406