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