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