Coverage Report

Created: 2025-06-13 06:50

/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
}