1 /*  $Id: image_io_gif.cpp 618367 2020-10-19 14:54:50Z grichenk $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors:  Mike DiCuccio
27  *
28  * File Description:
29  *    CImageIOGif -- interface class for reading/writing CompuServ GIF files
30  */
31 
32 //
33 // we include gif_lib.h first because of a conflict with windows.h
34 // (DrawText() is both a giflib function and a Win32 GDI function)
35 //
36 #include <ncbi_pch.hpp>
37 #include <ncbiconf.h>
38 #ifdef HAVE_LIBGIF
39 // alas, poor giflib... it isn't extern'ed
40 extern "C" {
41 #  include <gif_lib.h>
42 
43     /// !@#$%^ libunfig mis-spelled the prototype in their header,
44     /// so we must add it here
45     GifFileType *EGifOpen(void *userPtr, OutputFunc writeFunc);
46 
47 };
48 #endif
49 
50 #include "image_io_gif.hpp"
51 #include <util/image/image.hpp>
52 #include <util/image/image_exception.hpp>
53 #include <util/error_codes.hpp>
54 
55 #define NCBI_USE_ERRCODE_X   Util_Image
56 
57 #ifdef HAVE_LIBGIF
58 
59 //
60 //
61 // LIBGIF support
62 //
63 //
64 
65 BEGIN_NCBI_SCOPE
66 
67 
68 
s_GifRead(GifFileType * file,GifByteType * data,int len)69 static int s_GifRead(GifFileType* file, GifByteType* data, int len)
70 {
71     CNcbiIstream* istr = reinterpret_cast<CNcbiIstream*>(file->UserData);
72     if (istr) {
73         istr->read(reinterpret_cast<char*>(data), len);
74         return (int)istr->gcount();
75     }
76     return -1;
77 }
78 
79 
s_GifWrite(GifFileType * file,const GifByteType * data,int len)80 static int s_GifWrite(GifFileType* file, const GifByteType* data, int len)
81 {
82     CNcbiOstream* ostr = reinterpret_cast<CNcbiOstream*>(file->UserData);
83     if (ostr) {
84         ostr->write(reinterpret_cast<const char*>(data), len);
85         if ( *ostr ) {
86             return len;
87         }
88     }
89     return -1;
90 }
91 
92 
93 //
94 // ReadImage()
95 // read an entire GIF image into memory.  This will read only the first image
96 // in an image set.
97 //
ReadImage(CNcbiIstream & istr)98 CImage* CImageIOGif::ReadImage(CNcbiIstream& istr)
99 {
100     GifFileType* fp = NULL;
101     CRef<CImage> image;
102 
103     try {
104         // open our file for reading
105         fp = DGifOpen(&istr, s_GifRead);
106         if ( !fp ) {
107             NCBI_THROW(CImageException, eReadError,
108                        "CImageIOGif::ReadImage(): "
109                        "cannot open file for reading");
110         }
111 
112         // allocate an image
113         image.Reset(new CImage(fp->SWidth, fp->SHeight, 3));
114         memset(image->SetData(), fp->SBackGroundColor,
115                image->GetWidth() * image->GetHeight() * image->GetDepth());
116 
117         // we also allocate a single row
118         // this row is a color indexed row, and will be decoded row-by-row into the
119         // image
120         vector<unsigned char> row_data(image->GetWidth());
121         unsigned char* row_ptr = &row_data[0];
122 
123         bool done = false;
124         while ( !done ) {
125             // determine what sort of record type we have
126             // these can be image, extension, or termination
127             GifRecordType type;
128             if (DGifGetRecordType(fp, &type) == GIF_ERROR) {
129                 NCBI_THROW(CImageException, eReadError,
130                     "CImageIOGif::ReadImage(): error reading file");
131             }
132 
133             switch (type) {
134             case IMAGE_DESC_RECORD_TYPE:
135                 //
136                 // we only support the first image in a gif
137                 //
138                 if (DGifGetImageDesc(fp) == GIF_ERROR) {
139                     NCBI_THROW(CImageException, eReadError,
140                         "CImageIOGif::ReadImage(): error reading file");
141                 }
142 
143                 if (fp->Image.Interlace) {
144                     // interlaced images are a bit more complex
145                     size_t row = fp->Image.Top;
146                     size_t col = fp->Image.Left;
147                     size_t wid = fp->Image.Width;
148                     size_t ht  = fp->Image.Height;
149 
150                     static int interlaced_offs[4] = { 0, 4, 2, 1 };
151                     static int interlaced_jump[4] = { 8, 8, 4, 2 };
152                     for (size_t i = 0;  i < 4;  ++i) {
153                         for (size_t j = row + interlaced_offs[i];
154                              j < row + ht;  j += interlaced_jump[i]) {
155                             x_ReadLine(fp, row_ptr);
156                             x_UnpackData(fp, row_ptr,
157                                          image->SetData() +
158                                          (j * wid + col) * image->GetDepth());
159                         }
160                     }
161                 } else {
162                     size_t col = fp->Image.Left;
163                     size_t wid = fp->Image.Width;
164                     size_t ht  = fp->Image.Height;
165 
166                     for (size_t i = 0;  i < ht;  ++i) {
167                         x_ReadLine(fp, row_ptr);
168                         x_UnpackData(fp, row_ptr,
169                                      image->SetData() +
170                                      (i * wid + col) * image->GetDepth());
171                     }
172                 }
173                 break;
174 
175             case EXTENSION_RECORD_TYPE:
176                 {{
177                      int ext_code;
178                      GifByteType* extension;
179 
180                      // we ignore extension blocks
181                      if (DGifGetExtension(fp, &ext_code, &extension) == GIF_ERROR) {
182                          NCBI_THROW(CImageException, eReadError,
183                                     "CImageIOGif::ReadImage(): "
184                                     "error reading file");
185                      }
186                      while (extension != NULL) {
187                          if (DGifGetExtensionNext(fp, &extension) == GIF_OK) {
188                              continue;
189                          }
190 
191                          NCBI_THROW(CImageException, eReadError,
192                                     "CImageIOGif::ReadImage(): "
193                                     "error reading file");
194                      }
195                  }}
196                 break;
197 
198             default:
199                 // terminate record - break our of our while()
200                 done = true;
201                 break;
202             }
203         }
204 
205         // close up and exit
206         DGifCloseFile(fp);
207     }
208     catch (...) {
209         DGifCloseFile(fp);
210         fp = NULL;
211         throw;
212     }
213 
214     return image.Release();
215 }
216 
217 
218 //
219 // ReadImage
220 // this version returns a sub-image from the desired image.
221 //
ReadImage(CNcbiIstream & istr,size_t x,size_t y,size_t w,size_t h)222 CImage* CImageIOGif::ReadImage(CNcbiIstream& istr,
223                                size_t x, size_t y, size_t w, size_t h)
224 {
225     // we use a brain-dead implementation here - this can be done in a more
226     // memory-efficient manner...
227     CRef<CImage> image(ReadImage(istr));
228     return image->GetSubImage(x, y, w, h);
229 }
230 
231 
ReadImageInfo(CNcbiIstream & istr,size_t * width,size_t * height,size_t * depth)232 bool CImageIOGif::ReadImageInfo(CNcbiIstream& istr,
233                                 size_t* width, size_t* height, size_t* depth)
234 {
235     GifFileType* fp = NULL;
236     try {
237         // open our file for reading
238         fp = DGifOpen(&istr, s_GifRead);
239         if ( !fp ) {
240             NCBI_THROW(CImageException, eReadError,
241                        "CImageIOGif::ReadImageInfo(): "
242                        "cannot open file for reading");
243         }
244 
245         // allocate an image
246         if (width) {
247             *width = fp->SWidth;
248         }
249         if (height) {
250             *height = fp->SHeight;
251         }
252         if (depth) {
253             *depth = 3;
254         }
255 
256         // close up and exit
257         DGifCloseFile(fp);
258         return true;
259     }
260     catch (...) {
261         DGifCloseFile(fp);
262         fp = NULL;
263     }
264 
265     return false;
266 }
267 
268 //
269 // WriteImage()
270 // this writes out a GIF image.
271 //
WriteImage(const CImage & image,CNcbiOstream & ostr,CImageIO::ECompress)272 void CImageIOGif::WriteImage(const CImage& image, CNcbiOstream& ostr,
273                              CImageIO::ECompress)
274 {
275     if ( !image.GetData() ) {
276         NCBI_THROW(CImageException, eWriteError,
277                    "CImageIOGif::WriteImage(): "
278                    "cannot write empty image to file");
279     }
280 
281     ColorMapObject* cmap = NULL;
282     GifFileType* fp = NULL;
283 
284     try {
285         // first, we need to split our image into red/green/blue channels
286         // we do this to get proper GIF quantization
287         size_t size = image.GetWidth() * image.GetHeight();
288         vector<unsigned char> red  (size);
289         vector<unsigned char> green(size);
290         vector<unsigned char> blue (size);
291 
292         unsigned char* red_ptr   = &red[0];
293         unsigned char* green_ptr = &green[0];
294         unsigned char* blue_ptr  = &blue[0];
295         const unsigned char* from_data = image.GetData();
296         const unsigned char* end_data  = image.GetData() + size * image.GetDepth();
297 
298         switch (image.GetDepth()) {
299         case 3:
300             {{
301                  for ( ;  from_data != end_data;  ) {
302                      *red_ptr++   = *from_data++;
303                      *green_ptr++ = *from_data++;
304                      *blue_ptr++  = *from_data++;
305                  }
306              }}
307             break;
308 
309         case 4:
310             {{
311                  ERR_POST_X(10, Warning <<
312                                 "CImageIOGif::WriteImage(): "
313                                 "ignoring alpha channel");
314                  for ( ;  from_data != end_data;  ) {
315                      *red_ptr++   = *from_data++;
316                      *green_ptr++ = *from_data++;
317                      *blue_ptr++  = *from_data++;
318 
319                      // alpha channel ignored - should we use this to compute a
320                      // scaled rgb?
321                      ++from_data;
322                  }
323              }}
324             break;
325 
326         default:
327             NCBI_THROW(CImageException, eWriteError,
328                        "CImageIOGif::WriteImage(): unsupported image depth");
329         }
330 
331         // reset the color channel pointers!
332         red_ptr   = &red[0];
333         green_ptr = &green[0];
334         blue_ptr  = &blue[0];
335 
336         // now, create a GIF color map object
337         int cmap_size = 256;
338         cmap = MakeMapObject(cmap_size, NULL);
339         if ( !cmap ) {
340             NCBI_THROW(CImageException, eWriteError,
341                        "CImageIOGif::WriteImage(): failed to allocate color map");
342         }
343 
344         // we also allocate a strip of data to hold the indexed colors
345         vector<unsigned char> qdata(size);
346         unsigned char* qdata_ptr = &qdata[0];
347 
348         // quantize our colors
349         if (QuantizeBuffer((unsigned int)image.GetWidth(),
350                            (unsigned int)image.GetHeight(), &cmap_size,
351                            red_ptr, green_ptr, blue_ptr,
352                            qdata_ptr, cmap->Colors) == GIF_ERROR) {
353             free(cmap);
354             NCBI_THROW(CImageException, eWriteError,
355                        "CImageIOGif::WriteImage(): failed to quantize image");
356         }
357 
358         //
359         // we are now ready to write our file
360         //
361 
362         // open our file
363         fp = EGifOpen(&ostr, s_GifWrite);
364         if ( !fp ) {
365             NCBI_THROW(CImageException, eWriteError,
366                        "CImageIOGif::WriteImage(): failed to open file");
367         }
368 
369         // write the GIF screen description
370         if (EGifPutScreenDesc(fp, (int)image.GetWidth(), (int)image.GetHeight(),
371                               8, 0, cmap) == GIF_ERROR) {
372             NCBI_THROW(CImageException, eWriteError,
373                 "CImageIOGif::WriteImage(): failed to write GIF screen "
374                 "description");
375         }
376 
377         // write the GIF image description
378         if (EGifPutImageDesc(fp, 0, 0, (int)image.GetWidth(), (int)image.GetHeight(),
379                              false, NULL) == GIF_ERROR) {
380             NCBI_THROW(CImageException, eWriteError,
381                        "CImageIOGif::WriteImage(): failed to write GIF image "
382                        "description");
383         }
384 
385         // put our data
386         for (size_t i = 0;  i < image.GetHeight();  ++i) {
387             if (EGifPutLine(fp, qdata_ptr, (int)image.GetWidth()) == GIF_ERROR) {
388                 string msg("CImageIOGif::WriteImage(): error writing line ");
389                 msg += NStr::NumericToString(i);
390                 NCBI_THROW(CImageException, eWriteError, msg);
391             }
392 
393             qdata_ptr += image.GetWidth();
394         }
395 
396         // clean-up and close
397         if (EGifCloseFile(fp) == GIF_ERROR) {
398             fp = NULL;
399             NCBI_THROW(CImageException, eWriteError,
400                        "CImageIOGif::WriteImage(): error closing file");
401         }
402 
403         free(cmap);
404     }
405     catch (...) {
406         if (fp) {
407             if (EGifCloseFile(fp) == GIF_ERROR) {
408                 ERR_POST_X(11, Error
409                     << "CImageIOGif::WriteImage(): error closing file");
410             }
411             fp = NULL;
412         }
413 
414         if (cmap) {
415             free(cmap);
416             cmap = NULL;
417         }
418 
419         throw;
420     }
421 }
422 
423 
424 //
425 // WriteImage()
426 // this writes out a sub-image as a GIF file.  We use a brain-dead
427 // implementation currently - subset the image and write it out, rather than
428 // simply quantizing just the sub-image's data
429 //
WriteImage(const CImage & image,CNcbiOstream & ostr,size_t x,size_t y,size_t w,size_t h,CImageIO::ECompress compress)430 void CImageIOGif::WriteImage(const CImage& image, CNcbiOstream& ostr,
431                              size_t x, size_t y, size_t w, size_t h,
432                              CImageIO::ECompress compress)
433 {
434     CRef<CImage> subimage(image.GetSubImage(x, y, w, h));
435     WriteImage(*subimage, ostr, compress);
436 }
437 
438 
439 //
440 // x_UnpackData()
441 // this function de-indexes a GIF image's color table, expanding the values
442 // into RGB tuples
443 //
x_UnpackData(GifFileType * fp,const unsigned char * from_data,unsigned char * to_data)444 void CImageIOGif::x_UnpackData(GifFileType* fp,
445                                const unsigned char* from_data,
446                                unsigned char* to_data)
447 {
448     struct ColorMapObject* cmap =
449         (fp->Image.ColorMap ? fp->Image.ColorMap : fp->SColorMap);
450 
451     for (int i = 0;  i < fp->Image.Width;  ++i) {
452         *to_data++ = cmap->Colors[ from_data[i] ].Red;
453         *to_data++ = cmap->Colors[ from_data[i] ].Green;
454         *to_data++ = cmap->Colors[ from_data[i] ].Blue;
455     }
456 }
457 
458 
459 //
460 // x_ReadLine()
461 // read a single line from a GIF file
462 //
x_ReadLine(GifFileType * fp,unsigned char * data)463 void CImageIOGif::x_ReadLine(GifFileType* fp, unsigned char* data)
464 {
465     if ( DGifGetLine(fp, data, fp->Image.Width) == GIF_ERROR) {
466         DGifCloseFile(fp);
467         string msg("CImageIOGif::ReadImage(): error reading file");
468         NCBI_THROW(CImageException, eReadError, msg);
469     }
470 }
471 
472 
473 END_NCBI_SCOPE
474 
475 
476 
477 #else   // HAVE_LIBGIF
478 
479 //
480 // LIBGIF not supported
481 //
482 
483 BEGIN_NCBI_SCOPE
484 
485 CImage* CImageIOGif::ReadImage(CNcbiIstream&)
486 {
487     NCBI_THROW(CImageException, eUnsupported,
488                "CImageIOGif::ReadImage(): GIF format not supported");
489 }
490 
491 
492 CImage* CImageIOGif::ReadImage(CNcbiIstream&,
493                                size_t, size_t, size_t, size_t)
494 {
495     NCBI_THROW(CImageException, eUnsupported,
496                "CImageIOGif::ReadImage(): GIF format not supported");
497 }
498 
499 
500 bool CImageIOGif::ReadImageInfo(CNcbiIstream&,
501                                 size_t*, size_t*, size_t*)
502 {
503     NCBI_THROW(CImageException, eUnsupported,
504                "CImageIOGif::ReadImageInfo(): GIF format not supported");
505 }
506 
507 void CImageIOGif::WriteImage(const CImage&, CNcbiOstream&,
508                              CImageIO::ECompress)
509 {
510     NCBI_THROW(CImageException, eUnsupported,
511                "CImageIOGif::WriteImage(): GIF format not supported");
512 }
513 
514 
515 void CImageIOGif::WriteImage(const CImage&, CNcbiOstream&,
516                              size_t, size_t, size_t, size_t,
517                              CImageIO::ECompress)
518 {
519     NCBI_THROW(CImageException, eUnsupported,
520                "CImageIOGif::WriteImage(): GIF format not supported");
521 }
522 
523 
524 END_NCBI_SCOPE
525 
526 #endif  // !HAVE_LIBGIF
527