gif.cpp 23.8 KB
Newer Older
André Anjos's avatar
André Anjos committed
1
2
3
4
/**
 * @file io/cxx/ImageGifFile.cc
 * @date Fri Nov 23 16:53:00 2012 +0200
 * @author Laurent El Shafey <laurent.el-shafey@idiap.ch>
Manuel Günther's avatar
Manuel Günther committed
5
 * @author Manuel Gunther <siebenkopf@googlemail.com>
André Anjos's avatar
André Anjos committed
6
7
8
9
 *
 * @brief Implements an image format reader/writer using giflib.
 *
 * Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
Manuel Günther's avatar
Manuel Günther committed
10
 * Copyright (c) 2016, Regents of the University of Colorado on behalf of the University of Colorado Colorado Springs.
André Anjos's avatar
André Anjos committed
11
12
 */

Manuel Günther's avatar
Manuel Günther committed
13
14
#ifdef HAVE_GIFLIB

André Anjos's avatar
André Anjos committed
15
16
17
18
19
20
21
22
23
#include <boost/filesystem.hpp>
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <string>

Manuel Günther's avatar
Manuel Günther committed
24
#include <bob.io.image/gif.h>
25

André Anjos's avatar
André Anjos committed
26
27
28
29
extern "C" {
#include <gif_lib.h>
}

30
31
// QuantizeBuffer function definition that was inlined (only) in giflib 4.2
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR < 5
André Anjos's avatar
André Anjos committed
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

#define ABS(x)    ((x) > 0 ? (x) : (-(x)))
#define COLOR_ARRAY_SIZE 32768
#define BITS_PER_PRIM_COLOR 5
#define MAX_PRIM_COLOR 0x1f

static int SortRGBAxis;

typedef struct QuantizedColorType {
  GifByteType RGB[3];
  GifByteType NewColorIndex;
  long Count;
  struct QuantizedColorType *Pnext;
} QuantizedColorType;

typedef struct NewColorMapType {
  GifByteType RGBMin[3], RGBWidth[3];
  unsigned int NumEntries; /* # of QuantizedColorType in linked list below */
  unsigned long Count; /* Total number of pixels in all the entries */
  QuantizedColorType *QuantizedColors;
} NewColorMapType;

// Routine called by qsort to compare two entries.
static int
SortCmpRtn(const void *Entry1, const void *Entry2)
{
  return (*((QuantizedColorType **) Entry1))->RGB[SortRGBAxis] -
    (*((QuantizedColorType **) Entry2))->RGB[SortRGBAxis];
}

// Routine to subdivide the RGB space recursively using median cut in each
// axes alternatingly until ColorMapSize different cubes exists.
// The biggest cube in one dimension is subdivide unless it has only one entry.
// Returns GIF_ERROR if failed, otherwise GIF_OK.
static int
SubdivColorMap(NewColorMapType * NewColorSubdiv,
    unsigned int ColorMapSize,
    unsigned int *NewColorMapSize) {
  int MaxSize;
  unsigned int i, j, Index = 0, NumEntries, MinColor, MaxColor;
  long Sum, Count;
  QuantizedColorType *QuantizedColor, **SortArray;
  while (ColorMapSize > *NewColorMapSize) {
    // Find candidate for subdivision:
    MaxSize = -1;
    for (i = 0; i < *NewColorMapSize; i++) {
      for (j = 0; j < 3; j++) {
        if ((((int)NewColorSubdiv[i].RGBWidth[j]) > MaxSize) &&
            (NewColorSubdiv[i].NumEntries > 1)) {
          MaxSize = NewColorSubdiv[i].RGBWidth[j];
          Index = i;
          SortRGBAxis = j;
        }
      }
    }
    if (MaxSize == -1)
      return GIF_OK;
    // Split the entry Index into two along the axis SortRGBAxis:
    // Sort all elements in that entry along the given axis and split at
    // the median.
    SortArray = (QuantizedColorType **)malloc(
        sizeof(QuantizedColorType *) *
        NewColorSubdiv[Index].NumEntries);
    if (SortArray == NULL)
      return GIF_ERROR;
    for (j = 0, QuantizedColor = NewColorSubdiv[Index].QuantizedColors;
        j < NewColorSubdiv[Index].NumEntries && QuantizedColor != NULL;
        j++, QuantizedColor = QuantizedColor->Pnext)
      SortArray[j] = QuantizedColor;
    qsort(SortArray, NewColorSubdiv[Index].NumEntries,
        sizeof(QuantizedColorType *), SortCmpRtn);
    // Relink the sorted list into one:
    for (j = 0; j < NewColorSubdiv[Index].NumEntries - 1; j++)
      SortArray[j]->Pnext = SortArray[j + 1];
    SortArray[NewColorSubdiv[Index].NumEntries - 1]->Pnext = NULL;
    NewColorSubdiv[Index].QuantizedColors = QuantizedColor = SortArray[0];
    free((char *)SortArray);
    // Now simply add the Counts until we have half of the Count:
    Sum = NewColorSubdiv[Index].Count / 2 - QuantizedColor->Count;
    NumEntries = 1;
    Count = QuantizedColor->Count;
    while (QuantizedColor->Pnext != NULL &&
        (Sum -= QuantizedColor->Pnext->Count) >= 0 &&
        QuantizedColor->Pnext->Pnext != NULL) {
      QuantizedColor = QuantizedColor->Pnext;
      NumEntries++;
      Count += QuantizedColor->Count;
    }
    // Save the values of the last color of the first half, and first
    // of the second half so we can update the Bounding Boxes later.
    // Also as the colors are quantized and the BBoxes are full 0..255,
    // they need to be rescaled.
    MaxColor = QuantizedColor->RGB[SortRGBAxis]; // Max. of first half
    // coverity[var_deref_op]
    MinColor = QuantizedColor->Pnext->RGB[SortRGBAxis]; // of second
    MaxColor <<= (8 - BITS_PER_PRIM_COLOR);
    MinColor <<= (8 - BITS_PER_PRIM_COLOR);
    // Partition right here:
    NewColorSubdiv[*NewColorMapSize].QuantizedColors =
      QuantizedColor->Pnext;
    QuantizedColor->Pnext = NULL;
    NewColorSubdiv[*NewColorMapSize].Count = Count;
    NewColorSubdiv[Index].Count -= Count;
    NewColorSubdiv[*NewColorMapSize].NumEntries =
      NewColorSubdiv[Index].NumEntries - NumEntries;
    NewColorSubdiv[Index].NumEntries = NumEntries;
    for (j = 0; j < 3; j++) {
      NewColorSubdiv[*NewColorMapSize].RGBMin[j] =
        NewColorSubdiv[Index].RGBMin[j];
      NewColorSubdiv[*NewColorMapSize].RGBWidth[j] =
        NewColorSubdiv[Index].RGBWidth[j];
    }
    NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] =
      NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] +
      NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] - MinColor;
    NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] = MinColor;
    NewColorSubdiv[Index].RGBWidth[SortRGBAxis] =
      MaxColor - NewColorSubdiv[Index].RGBMin[SortRGBAxis];
    (*NewColorMapSize)++;
  }
  return GIF_OK;
}

// Quantize high resolution image into lower one. Input image consists of a
// 2D array for each of the RGB colors with size Width by Height. There is no
// Color map for the input. Output is a quantized image with 2D array of
// indexes into the output color map.
// Note input image can be 24 bits at the most (8 for red/green/blue) and
// the output has 256 colors at the most (256 entries in the color map.).
// ColorMapSize specifies size of color map up to 256 and will be updated to
// real size before returning.
// Also non of the parameter are allocated by this routine.
// This function returns GIF_OK if succesfull, GIF_ERROR otherwise.
static int
QuantizeBuffer(unsigned int Width, unsigned int Height, int *ColorMapSize,
  GifByteType * RedInput, GifByteType * GreenInput, GifByteType * BlueInput,
  GifByteType * OutputBuffer, GifColorType * OutputColorMap)
{
  unsigned int Index, NumOfEntries;
  int i, j, MaxRGBError[3];
  unsigned int NewColorMapSize;
  long Red, Green, Blue;
  NewColorMapType NewColorSubdiv[256];
  QuantizedColorType *ColorArrayEntries, *QuantizedColor;
  ColorArrayEntries = (QuantizedColorType *)malloc(
      sizeof(QuantizedColorType) * COLOR_ARRAY_SIZE);
  if (ColorArrayEntries == NULL) {
    return GIF_ERROR;
  }
  for (i = 0; i < COLOR_ARRAY_SIZE; i++) {
    ColorArrayEntries[i].RGB[0] = i >> (2 * BITS_PER_PRIM_COLOR);
    ColorArrayEntries[i].RGB[1] = (i >> BITS_PER_PRIM_COLOR) &
      MAX_PRIM_COLOR;
    ColorArrayEntries[i].RGB[2] = i & MAX_PRIM_COLOR;
    ColorArrayEntries[i].Count = 0;
  }
  // Sample the colors and their distribution:
  for (i = 0; i < (int)(Width * Height); i++) {
    Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR)) <<
        (2 * BITS_PER_PRIM_COLOR)) +
      ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR)) <<
       BITS_PER_PRIM_COLOR) +
      (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR));
    ColorArrayEntries[Index].Count++;
  }
  // Put all the colors in the first entry of the color map, and call the
  // recursive subdivision process.
  for (i = 0; i < 256; i++) {
    NewColorSubdiv[i].QuantizedColors = NULL;
    NewColorSubdiv[i].Count = NewColorSubdiv[i].NumEntries = 0;
    for (j = 0; j < 3; j++) {
      NewColorSubdiv[i].RGBMin[j] = 0;
      NewColorSubdiv[i].RGBWidth[j] = 255;
    }
  }
  // Find the non empty entries in the color table and chain them:
  for (i = 0; i < COLOR_ARRAY_SIZE; i++)
    if (ColorArrayEntries[i].Count > 0)
      break;
  QuantizedColor = NewColorSubdiv[0].QuantizedColors = &ColorArrayEntries[i];
  NumOfEntries = 1;
  while (++i < COLOR_ARRAY_SIZE)
    if (ColorArrayEntries[i].Count > 0) {
      QuantizedColor->Pnext = &ColorArrayEntries[i];
      QuantizedColor = &ColorArrayEntries[i];
      NumOfEntries++;
    }
  QuantizedColor->Pnext = NULL;
  NewColorSubdiv[0].NumEntries = NumOfEntries; // Different sampled colors
  NewColorSubdiv[0].Count = ((long)Width) * Height; // Pixels
  NewColorMapSize = 1;
  if (SubdivColorMap(NewColorSubdiv, *ColorMapSize, &NewColorMapSize) !=
      GIF_OK) {
    free((char *)ColorArrayEntries);
    return GIF_ERROR;
  }
  if (NewColorMapSize < *ColorMapSize) {
    // And clear rest of color map:
    for (i = NewColorMapSize; i < *ColorMapSize; i++)
      OutputColorMap[i].Red = OutputColorMap[i].Green =
        OutputColorMap[i].Blue = 0;
  }
  // Average the colors in each entry to be the color to be used in the
  // output color map, and plug it into the output color map itself.
  for (i = 0; i < NewColorMapSize; i++) {
    if ((j = NewColorSubdiv[i].NumEntries) > 0) {
      QuantizedColor = NewColorSubdiv[i].QuantizedColors;
      Red = Green = Blue = 0;
      while (QuantizedColor) {
        QuantizedColor->NewColorIndex = i;
        Red += QuantizedColor->RGB[0];
        Green += QuantizedColor->RGB[1];
        Blue += QuantizedColor->RGB[2];
        QuantizedColor = QuantizedColor->Pnext;
      }
      OutputColorMap[i].Red = (Red << (8 - BITS_PER_PRIM_COLOR)) / j;
      OutputColorMap[i].Green = (Green << (8 - BITS_PER_PRIM_COLOR)) / j;
      OutputColorMap[i].Blue = (Blue << (8 - BITS_PER_PRIM_COLOR)) / j;
    } else
      fprintf(stderr,
          "\n: Null entry in quantized color map - that's weird.\n");
  }
  // Finally scan the input buffer again and put the mapped index in the
  // output buffer.
  MaxRGBError[0] = MaxRGBError[1] = MaxRGBError[2] = 0;
  for (i = 0; i < (int)(Width * Height); i++) {
    Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR)) <<
        (2 * BITS_PER_PRIM_COLOR)) +
      ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR)) <<
       BITS_PER_PRIM_COLOR) +
      (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR));
    Index = ColorArrayEntries[Index].NewColorIndex;
    OutputBuffer[i] = Index;
    if (MaxRGBError[0] < ABS(OutputColorMap[Index].Red - RedInput[i]))
      MaxRGBError[0] = ABS(OutputColorMap[Index].Red - RedInput[i]);
    if (MaxRGBError[1] < ABS(OutputColorMap[Index].Green - GreenInput[i]))
      MaxRGBError[1] = ABS(OutputColorMap[Index].Green - GreenInput[i]);
    if (MaxRGBError[2] < ABS(OutputColorMap[Index].Blue - BlueInput[i]))
      MaxRGBError[2] = ABS(OutputColorMap[Index].Blue - BlueInput[i]);
  }
  free((char *)ColorArrayEntries);
  *ColorMapSize = NewColorMapSize;
  return GIF_OK;
}

#undef ABS
#undef COLOR_ARRAY_SIZE
#undef BITS_PER_PRIM_COLOR
#undef MAX_PRIM_COLOR
#endif // End of ugly QuantizeBuffer definition for giflib 4.2

André Anjos's avatar
André Anjos committed
283
static void GifErrorHandler(const char* fname, int error) {
André Anjos's avatar
André Anjos committed
284
#if defined(GIF_LIB_VERSION) || (GIFLIB_MAJOR < 5)
André Anjos's avatar
André Anjos committed
285
  const char* error_string = GifErrorString(error);
André Anjos's avatar
André Anjos committed
286
287
288
289
290
#else
  const char* error_string = "unknown error (giflib < 5)";
#endif
  boost::format m("GIF: error in %s(): (%d) %s");
  m % fname % error;
André Anjos's avatar
André Anjos committed
291
292
293
294
  if (error_string) m % error_string;
  else m % "unknown error";
  throw std::runtime_error(m.str());
}
André Anjos's avatar
André Anjos committed
295

André Anjos's avatar
André Anjos committed
296
static int DGifDeleter (GifFileType* ptr) {
297
298
299
#if defined(GIF_LIB_VERSION) || (GIFLIB_MAJOR < 5) || (GIFLIB_MAJOR == 5) && (GIFLIB_MINOR < 1)
  return DGifCloseFile(ptr);
#else
André Anjos's avatar
André Anjos committed
300
301
302
303
304
  int error = GIF_OK;
  int retval = DGifCloseFile(ptr, &error);
  if (retval == GIF_ERROR) {
    //do not call GifErrorHandler here, or the interpreter will crash
    const char* error_string = GifErrorString(retval);
André Anjos's avatar
André Anjos committed
305
306
    boost::format m("In DGifCloseFile(): (%d) %s");
    m % error;
André Anjos's avatar
André Anjos committed
307
308
309
310
311
    if (error_string) m % error_string;
    else m % "unknown error";
    std::cerr << "ERROR: " << m.str() << std::endl;
  }
  return retval;
312
313
314
#endif
}

André Anjos's avatar
André Anjos committed
315
316
317
318
static boost::shared_ptr<GifFileType> make_dfile(const char *filename)
{
#if defined(GIF_LIB_VERSION) || (GIFLIB_MAJOR < 5)
  GifFileType* fp = DGifOpenFileName(filename);
André Anjos's avatar
André Anjos committed
319
  if (!fp) {
André Anjos's avatar
André Anjos committed
320
321
322
323
    boost::format m("cannot open file `%s'");
    m % filename;
    throw std::runtime_error(m.str());
  }
André Anjos's avatar
André Anjos committed
324
325
326
327
328
#else
  int error = GIF_OK;
  GifFileType* fp = DGifOpenFileName(filename, &error);
  if (!fp) GifErrorHandler("DGifOpenFileName", error);
#endif
329
330
331
332
  return boost::shared_ptr<GifFileType>(fp, DGifDeleter);
}


André Anjos's avatar
André Anjos committed
333
static int EGifDeleter (GifFileType* ptr) {
334
335
336
#if defined(GIF_LIB_VERSION) || (GIFLIB_MAJOR < 5) || (GIFLIB_MAJOR == 5) && (GIFLIB_MINOR < 1)
  return EGifCloseFile(ptr);
#else
André Anjos's avatar
André Anjos committed
337
338
339
340
341
  int error = GIF_OK;
  int retval = EGifCloseFile(ptr, &error);
  if (retval == GIF_ERROR) {
    //do not call GifErrorHandler here, or the interpreter will crash
    const char* error_string = GifErrorString(error);
André Anjos's avatar
André Anjos committed
342
343
    boost::format m("In EGifCloseFile(): (%d) %s");
    m % error;
André Anjos's avatar
André Anjos committed
344
345
346
347
348
    if (error_string) m % error_string;
    else m % "unknown error";
    std::cerr << "ERROR: " << m.str() << std::endl;
  }
  return retval;
349
#endif
André Anjos's avatar
André Anjos committed
350
351
352
353
354
355
}

static boost::shared_ptr<GifFileType> make_efile(const char *filename)
{
#if defined(GIF_LIB_VERSION) || (GIFLIB_MAJOR < 5)
  GifFileType* fp = EGifOpenFileName(filename, false);
André Anjos's avatar
André Anjos committed
356
  if (!fp) {
André Anjos's avatar
André Anjos committed
357
358
359
360
    boost::format m("cannot open file `%s'");
    m % filename;
    throw std::runtime_error(m.str());
  }
André Anjos's avatar
André Anjos committed
361
362
363
364
365
#else
  int error = GIF_OK;
  GifFileType* fp = EGifOpenFileName(filename, false, &error);
  if (!fp) GifErrorHandler("EGifOpenFileName", error);
#endif
366
  return boost::shared_ptr<GifFileType>(fp, EGifDeleter);
André Anjos's avatar
André Anjos committed
367
368
369
370
371
}

/**
 * LOADING
 */
372
static void im_peek(const std::string& path, bob::io::base::array::typeinfo& info)
André Anjos's avatar
André Anjos committed
373
374
375
376
377
{
  // 1. GIF file opening
  boost::shared_ptr<GifFileType> in_file = make_dfile(path.c_str());

  // 2. Set typeinfo variables
378
  info.dtype = bob::io::base::array::t_uint8;
André Anjos's avatar
André Anjos committed
379
380
381
382
383
384
385
  info.nd = 3;
  info.shape[0] = 3;
  info.shape[1] = in_file->SHeight;
  info.shape[2] = in_file->SWidth;
  info.update_strides();
}

386
static void im_load_color(boost::shared_ptr<GifFileType> in_file, bob::io::base::array::interface& b)
André Anjos's avatar
André Anjos committed
387
{
388
  const bob::io::base::array::typeinfo& info = b.type();
André Anjos's avatar
André Anjos committed
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
  const size_t height0 = info.shape[1];
  const size_t width0 = info.shape[2];
  const size_t frame_size = height0*width0;

  // The following piece of code is based on the giflib utility called gif2rgb
  // Allocate the screen as vector of column of rows. Note this
  // screen is device independent - it's the screen defined by the
  // GIF file parameters.
  std::vector<boost::shared_array<GifPixelType> > screen_buffer;

  // Size in bytes one row.
  int size = in_file->SWidth*sizeof(GifPixelType);
  // First row
  screen_buffer.push_back(boost::shared_array<GifPixelType>(new GifPixelType[in_file->SWidth]));

  // Set its color to BackGround
  for(int i=0; i<in_file->SWidth; ++i)
    screen_buffer[0][i] = in_file->SBackGroundColor;
  for(int i=1; i<in_file->SHeight; ++i) {
    // Allocate the other rows, and set their color to background too:
    screen_buffer.push_back(boost::shared_array<GifPixelType>(new GifPixelType[in_file->SWidth]));
    memcpy(screen_buffer[i].get(), screen_buffer[0].get(), size);
  }

  // Scan the content of the GIF file and load the image(s) in:
  GifRecordType record_type;
  GifByteType *extension;
  int InterlacedOffset[] = { 0, 4, 2, 1 }; // The way Interlaced image should.
  int InterlacedJumps[] = { 8, 8, 4, 2 }; // be read - offsets and jumps...
  int row, col, width, height, count, ext_code;
André Anjos's avatar
André Anjos committed
419
420
421
  int error = DGifGetRecordType(in_file.get(), &record_type);
  if(error == GIF_ERROR)
    GifErrorHandler("DGifGetRecordType", error);
André Anjos's avatar
André Anjos committed
422
423
  switch(record_type) {
    case IMAGE_DESC_RECORD_TYPE:
André Anjos's avatar
André Anjos committed
424
425
      error = DGifGetImageDesc(in_file.get());
      if (error == GIF_ERROR) GifErrorHandler("DGifGetImageDesc", error);
André Anjos's avatar
André Anjos committed
426
427
428
429
430
431
432
433
434
435
436
437
438
439
      row = in_file->Image.Top; // Image Position relative to Screen.
      col = in_file->Image.Left;
      width = in_file->Image.Width;
      height = in_file->Image.Height;
      if(in_file->Image.Left + in_file->Image.Width > in_file->SWidth ||
        in_file->Image.Top + in_file->Image.Height > in_file->SHeight)
      {
        throw std::runtime_error("GIF: the dimensions of image larger than the dimensions of the canvas.");
      }
      if(in_file->Image.Interlace) {
        // Need to perform 4 passes on the images:
        for(int i=count=0; i<4; ++i)
          for(int j=row+InterlacedOffset[i]; j<row+height; j+=InterlacedJumps[i]) {
            ++count;
André Anjos's avatar
André Anjos committed
440
441
            error = DGifGetLine(in_file.get(), &screen_buffer[j][col], width);
            if(error == GIF_ERROR) GifErrorHandler("DGifGetLine", error);
André Anjos's avatar
André Anjos committed
442
443
444
445
          }
      }
      else {
        for(int i=0; i<height; ++i) {
André Anjos's avatar
André Anjos committed
446
447
          error = DGifGetLine(in_file.get(), &screen_buffer[row++][col], width);
          if(error == GIF_ERROR) GifErrorHandler("DGifGetLine", error);
André Anjos's avatar
André Anjos committed
448
449
450
451
452
        }
      }
      break;
    case EXTENSION_RECORD_TYPE:
      // Skip any extension blocks in file:
André Anjos's avatar
André Anjos committed
453
454
      error = DGifGetExtension(in_file.get(), &ext_code, &extension);
      if (error == GIF_ERROR) GifErrorHandler("DGifGetExtension", error);
André Anjos's avatar
André Anjos committed
455
      while(extension != NULL) {
André Anjos's avatar
André Anjos committed
456
457
        error = DGifGetExtensionNext(in_file.get(), &extension);
        if(error == GIF_ERROR) GifErrorHandler("DGifGetExtensionNext", error);
André Anjos's avatar
André Anjos committed
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
      }
      break;
    case TERMINATE_RECORD_TYPE:
      break;
    default: // Should be trapped by DGifGetRecordType.
      break;
  }

  // Lets dump it - set the global variables required and do it:
  ColorMapObject *ColorMap = (in_file->Image.ColorMap ? in_file->Image.ColorMap : in_file->SColorMap);
  if(ColorMap == 0)
    throw std::runtime_error("GIF: image does not have a colormap");

  // Put data into C-style buffer
  uint8_t *element_r = reinterpret_cast<uint8_t*>(b.ptr());
  uint8_t *element_g = element_r + frame_size;
  uint8_t *element_b = element_g + frame_size;
  GifRowType gif_row;
  GifColorType *ColorMapEntry;
  for(int i=0; i<in_file->SHeight; ++i) {
    gif_row = screen_buffer[i].get();
    for(int j=0; j<in_file->SWidth; ++j) {
      ColorMapEntry = &ColorMap->Colors[gif_row[j]];
      *element_r++ = ColorMapEntry->Red;
      *element_g++ = ColorMapEntry->Green;
      *element_b++ = ColorMapEntry->Blue;
    }
  }
}

488
static void im_load(const std::string& filename, bob::io::base::array::interface& b)
André Anjos's avatar
André Anjos committed
489
490
491
492
493
{
  // 1. GIF file opening
  boost::shared_ptr<GifFileType> in_file = make_dfile(filename.c_str());

  // 2. Read content
494
  const bob::io::base::array::typeinfo& info = b.type();
André Anjos's avatar
André Anjos committed
495
496
  if (info.dtype == bob::io::base::array::t_uint8) {
    if (info.nd == 3) im_load_color(in_file, b);
André Anjos's avatar
André Anjos committed
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
    else {
      boost::format m("GIF: cannot read object of type `%s' from file `%s'");
      m % info.str() % filename;
      throw std::runtime_error(m.str());
    }
  }
  else {
    boost::format m("GIF: cannot read object of type `%s' from file `%s'");
    m % info.str() % filename;
    throw std::runtime_error(m.str());
  }
}

/**
 * SAVING
 */
513
static void im_save_color(const bob::io::base::array::interface& b, boost::shared_ptr<GifFileType> out_file)
André Anjos's avatar
André Anjos committed
514
{
515
  const bob::io::base::array::typeinfo& info = b.type();
André Anjos's avatar
André Anjos committed
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
  const int height = info.shape[1];
  const int width = info.shape[2];
  const size_t frame_size = height * width;

  // pointer to a single row (tiff_bytep is a typedef to unsigned char or char)
  const uint8_t *element_r = static_cast<const uint8_t*>(b.ptr());
  const uint8_t *element_g = element_r + frame_size;
  const uint8_t *element_b = element_g + frame_size;

  GifByteType *red_buffer = const_cast<GifByteType*>(reinterpret_cast<const GifByteType*>(element_r));
  GifByteType *green_buffer = const_cast<GifByteType*>(reinterpret_cast<const GifByteType*>(element_g));
  GifByteType *blue_buffer = const_cast<GifByteType*>(reinterpret_cast<const GifByteType*>(element_b));
  boost::shared_array<GifByteType> output_buffer(new GifByteType[width*height]);

  // The following piece of code is based on the giflib utility called gif2rgb
  const int ExpNumOfColors = 8;
  int ColorMapSize = 1 << ExpNumOfColors;
  ColorMapObject *OutputColorMap = 0;

#if defined(GIF_LIB_VERSION) || (GIFLIB_MAJOR < 5)
  if((OutputColorMap = MakeMapObject(ColorMapSize, NULL)) == 0)
#else
  if((OutputColorMap = GifMakeMapObject(ColorMapSize, NULL)) == 0)
#endif
    throw std::runtime_error("GIF: error in GifMakeMapObject().");

André Anjos's avatar
André Anjos committed
542
  int error;
André Anjos's avatar
André Anjos committed
543
#if defined(GIF_LIB_VERSION) || (GIFLIB_MAJOR < 5)
André Anjos's avatar
André Anjos committed
544
545
546
  error = QuantizeBuffer(width, height, &ColorMapSize,
      red_buffer, green_buffer, blue_buffer, output_buffer.get(),
      OutputColorMap->Colors);
André Anjos's avatar
André Anjos committed
547
#else
André Anjos's avatar
André Anjos committed
548
549
550
  error = GifQuantizeBuffer(width, height, &ColorMapSize,
      red_buffer, green_buffer, blue_buffer, output_buffer.get(),
      OutputColorMap->Colors);
André Anjos's avatar
André Anjos committed
551
#endif
André Anjos's avatar
André Anjos committed
552
  if (error == GIF_ERROR) GifErrorHandler("GifQuantizeBuffer", error);
André Anjos's avatar
André Anjos committed
553

André Anjos's avatar
André Anjos committed
554
555
556
  error = EGifPutScreenDesc(out_file.get(), width, height, ExpNumOfColors, 0,
      OutputColorMap);
  if (error == GIF_ERROR) GifErrorHandler("EGifPutScreenDesc", error);
André Anjos's avatar
André Anjos committed
557

André Anjos's avatar
André Anjos committed
558
559
  error = EGifPutImageDesc(out_file.get(), 0, 0, width, height, false, NULL);
  if (error == GIF_ERROR) GifErrorHandler("EGifPutImageDesc", error);
André Anjos's avatar
André Anjos committed
560
561
562

  GifByteType *ptr = output_buffer.get();
  for(int i=0; i<height; ++i) {
André Anjos's avatar
André Anjos committed
563
564
    error = EGifPutLine(out_file.get(), ptr, width);
    if (error == GIF_ERROR) GifErrorHandler("EGifPutImageDesc", error);
André Anjos's avatar
André Anjos committed
565
566
567
568
569
570
571
572
573
574
575
    ptr += width;
  }

  // Free map object
#if defined(GIF_LIB_VERSION) || (GIFLIB_MAJOR < 5)
  FreeMapObject(OutputColorMap);
#else
  GifFreeMapObject(OutputColorMap);
#endif
}

576
static void im_save(const std::string& filename, const bob::io::base::array::interface& array)
André Anjos's avatar
André Anjos committed
577
578
579
580
581
{
  // 1. GIF file opening
  boost::shared_ptr<GifFileType> out_file = make_efile(filename.c_str());

  // 2. Set the image information here:
582
  const bob::io::base::array::typeinfo& info = array.type();
André Anjos's avatar
André Anjos committed
583
584

  // 3. Writes content
585
  if(info.dtype == bob::io::base::array::t_uint8) {
André Anjos's avatar
André Anjos committed
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
    if(info.nd == 3) {
      if(info.shape[0] != 3)
        throw std::runtime_error("color image does not have 3 planes on 1st. dimension");
      im_save_color(array, out_file);
    }
    else {
      boost::format m("GIF: cannot save object of type `%s' to file `%s'");
      m % info.str() % filename;
      throw std::runtime_error(m.str());
    }
  }
  else {
    boost::format m("GIF: cannot save object of type `%s' to file `%s'");
    m % info.str() % filename;
    throw std::runtime_error(m.str());
  }
}


Manuel Günther's avatar
Manuel Günther committed
605
606
607
608
609
610
611
612
613
614
615
616
617
/**
 * GIF class
*/
bob::io::image::GIFFile::GIFFile(const char* path, char mode)
: m_filename(path),
  m_newfile(true) {

  //checks if file exists
  if (mode == 'r' && !boost::filesystem::exists(path)) {
    boost::format m("file '%s' is not readable");
    m % path;
    throw std::runtime_error(m.str());
  }
André Anjos's avatar
André Anjos committed
618

Manuel Günther's avatar
Manuel Günther committed
619
620
621
622
623
624
625
626
627
628
  if (mode == 'r' || (mode == 'a' && boost::filesystem::exists(path))) {
    im_peek(path, m_type);
    m_length = 1;
    m_newfile = false;
  }
  else {
    m_length = 0;
    m_newfile = true;
  }
}
André Anjos's avatar
André Anjos committed
629

Manuel Günther's avatar
Manuel Günther committed
630
631
632
void bob::io::image::GIFFile::read(bob::io::base::array::interface& buffer, size_t index) {
  if (m_newfile)
    throw std::runtime_error("uninitialized image file cannot be read");
André Anjos's avatar
André Anjos committed
633

Manuel Günther's avatar
Manuel Günther committed
634
  if (!buffer.type().is_compatible(m_type)) buffer.set(m_type);
André Anjos's avatar
André Anjos committed
635

Manuel Günther's avatar
Manuel Günther committed
636
637
  if (index != 0)
    throw std::runtime_error("cannot read image with index > 0 -- there is only one image in an image file");
André Anjos's avatar
André Anjos committed
638

Manuel Günther's avatar
Manuel Günther committed
639
640
641
  if(!buffer.type().is_compatible(m_type)) buffer.set(m_type);
  im_load(m_filename, buffer);
}
André Anjos's avatar
André Anjos committed
642

Manuel Günther's avatar
Manuel Günther committed
643
644
645
646
647
648
649
650
size_t bob::io::image::GIFFile::append(const bob::io::base::array::interface& buffer) {
  if (m_newfile) {
    im_save(m_filename, buffer);
    m_type = buffer.type();
    m_newfile = false;
    m_length = 1;
    return 0;
  }
André Anjos's avatar
André Anjos committed
651

Manuel Günther's avatar
Manuel Günther committed
652
653
  throw std::runtime_error("image files only accept a single array");
}
André Anjos's avatar
André Anjos committed
654

Manuel Günther's avatar
Manuel Günther committed
655
656
657
658
659
660
void bob::io::image::GIFFile::write (const bob::io::base::array::interface& buffer) {
  //overwriting position 0 should always work
  if (m_newfile) {
    append(buffer);
    return;
  }
André Anjos's avatar
André Anjos committed
661

Manuel Günther's avatar
Manuel Günther committed
662
663
  throw std::runtime_error("image files only accept a single array");
}
André Anjos's avatar
André Anjos committed
664

Manuel Günther's avatar
Manuel Günther committed
665
std::string bob::io::image::GIFFile::s_codecname = "bob.image_gif";
André Anjos's avatar
André Anjos committed
666

667
boost::shared_ptr<bob::io::base::File> make_gif_file (const char* path, char mode) {
Manuel Günther's avatar
Manuel Günther committed
668
  return boost::make_shared<bob::io::image::GIFFile>(path, mode);
André Anjos's avatar
André Anjos committed
669
}
Manuel Günther's avatar
Manuel Günther committed
670
671

#endif // HAVE_GIFLIB