image_libpng.C

00001 /*
00002  * This file is part of the "Archon" framework.
00003  * (http://files3d.sourceforge.net)
00004  *
00005  * Copyright © 2002 by Kristian Spangsege and Brian Kristiansen.
00006  *
00007  * Permission to use, copy, modify, and distribute this software and
00008  * its documentation under the terms of the GNU General Public License is
00009  * hereby granted. No representations are made about the suitability of
00010  * this software for any purpose. It is provided "as is" without express
00011  * or implied warranty. See the GNU General Public License
00012  * (http://www.gnu.org/copyleft/gpl.html) for more details.
00013  *
00014  * The characters in this file are ISO8859-1 encoded.
00015  *
00016  * The documentation in this file is in "Doxygen" style
00017  * (http://www.doxygen.org).
00018  */
00019 
00020 #include <errno.h>
00021 
00022 #include <cstdio>
00023 
00024 #include <iostream>
00025 #include <string>
00026 
00027 #include <png.h>
00028 
00029 // There is a bug in libpng which forces us to put this include below png.h
00030 #include <setjmp.h>
00031 
00032 #include <archon/util/mutex.H>
00033 #include <archon/util/text.H>
00034 #include <archon/util/charenc.H>
00035 
00036 #include <archon/util/image_png.H>
00037 
00038 using namespace std;
00039 
00040 namespace Archon
00041 {
00042   namespace Utilities
00043   {
00044 
00104     struct FormatLibpng: Image::Format
00105     {
00106       string getSpecifier() const
00107       {
00108         return "png";
00109       }
00110 
00111       bool checkSignature(Ref<Stream::Reader> r) const
00112       {
00113         char header[8];
00114         return r->readAll(header, 8)==8 &&
00115           !png_sig_cmp(reinterpret_cast<png_bytep>(header), 0, 8);
00116       }
00117 
00118       bool checkSuffix(string s) const
00119       {
00120         return s == "png";
00121       }
00122 
00130       Image load(Ref<Stream::Reader> reader, Image::ProgressTracker *tracker,
00131                  Logger *logger) const
00132         throw(Image::InvalidFormatException,
00133               IOException, UnexpectedException)
00134       {
00135         char header[8];
00136         if(reader->readAll(header, 8) != 8 ||
00137            png_sig_cmp(reinterpret_cast<png_bytep>(header), 0, 8))
00138           ARCHON_THROW1(Image::InvalidFormatException, "Not a PNG header");
00139 
00140         LoadContext c(reader, tracker, logger);
00141         LoadWrapper w;
00142 
00143         w.png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, &c,
00144                                            errorCallback, warningCallback);
00145         if(!w.png_ptr)
00146           ARCHON_THROW1(ResourceException,
00147                         "PNG library was unable to allocate "
00148                         "a 'png_struct' structure");
00149 
00150         w.info_ptr = png_create_info_struct(w.png_ptr);
00151         if(!w.info_ptr)
00152           ARCHON_THROW1(ResourceException,
00153                         "PNG library was unable to allocate "
00154                         "first 'png_info' structure");
00155 
00156         w.end_info_ptr = png_create_info_struct(w.png_ptr);
00157         if(!w.end_info_ptr)
00158           ARCHON_THROW1(ResourceException,
00159                         "PNG library was unable to allocate "
00160                         "second 'png_info' structure");
00161 
00162         png_set_read_fn(w.png_ptr, 0, readCallback);
00163         png_set_read_status_fn(w.png_ptr, progressCallback);
00164 
00165         string comment;
00166         Image image;
00167         Array<png_byte *> rowPointers;
00168 
00169         /*
00170          * Handle termination and catch errors occuring in any of the
00171          * following PNG-functions.
00172          */
00173         if(!setjmp(png_jmpbuf(w.png_ptr))) // try
00174         {
00175           png_set_sig_bytes(w.png_ptr, 8);
00176           png_read_info(w.png_ptr, w.info_ptr);
00177 
00178           // Set up PNG read transformations
00179           unsigned bitsPerComponent = png_get_bit_depth(w.png_ptr, w.info_ptr);
00180           const png_byte colorType = png_get_color_type(w.png_ptr, w.info_ptr);
00181           if(bitsPerComponent > 8) png_set_swap(w.png_ptr);
00182           if(bitsPerComponent < 8) png_set_packing(w.png_ptr);
00183           if(colorType == PNG_COLOR_TYPE_PALETTE)
00184             png_set_palette_to_rgb(w.png_ptr);
00185           /* Should we do this transformation?
00186              if(png_get_valid(w.png_ptr, w.info_ptr, PNG_INFO_tRNS))
00187              png_set_tRNS_to_alpha(w.png_ptr);
00188           */
00189 
00190 
00191           // Fetch the image geometry
00192           png_read_update_info(w.png_ptr, w.info_ptr); // Updated header information
00193           unsigned height = png_get_image_height(w.png_ptr, w.info_ptr);
00194           unsigned width  = png_get_image_width (w.png_ptr, w.info_ptr);
00195           Image::ComponentSpecifier components =
00196             static_cast<Image::ComponentSpecifier>(png_get_channels(w.png_ptr,
00197                                                            w.info_ptr));
00198           unsigned charsPerPixel = (bitsPerComponent*components+7)/8;
00199           unsigned charsPerRow   = charsPerPixel*width;
00200 
00201           if(png_get_rowbytes(w.png_ptr, w.info_ptr) != charsPerRow)
00202             ARCHON_THROW1(InternalException,
00203                           "Unexpected number of bytes per row "
00204                           "reported by the PNG library");
00205 
00206           comment = "N/Y/A";
00207 
00208           // Construct an image with an uninitialized pixel buffer
00209           image = Image(width, height, components, bitsPerComponent, comment);
00210 
00211           // Make row array
00212           png_byte *pixelBuffer =
00213             reinterpret_cast<png_byte *>(getPixelBufferNoLeak(image));
00214           rowPointers.reset(height);
00215           for(unsigned i=0; i<height; ++i)
00216             rowPointers[i] = pixelBuffer + (height-i-1)*charsPerRow;
00217 
00218           c.totalRows = height; // For the progress tracker
00219 
00220           png_read_image(w.png_ptr, rowPointers.get());
00221           png_read_end(w.png_ptr, w.end_info_ptr);
00222         }
00223         else // catch
00224         {
00225           switch(c.errorType)
00226           {
00227           case 1: throw ThreadTerminatedException(c.errorLocation);
00228           case 2: throw Stream::ReadException(c.errorLocation, c.errorMessage);
00229           case 3: throw UnexpectedException(c.errorLocation, c.errorMessage);
00230           }
00231           ARCHON_THROW1(Image::InvalidFormatException, c.errorMessage);
00232         }
00233 
00234         return image;
00235       }
00236 
00237 
00244       void save(Image image, Ref<Stream::Writer> writer,
00245                 Image::ProgressTracker *tracker, Logger *logger) const
00246       throw(IOException, UnexpectedException)
00247       {
00248         SaveContext c(writer, tracker, logger);
00249         SaveWrapper w;
00250 
00251         w.png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, &c,
00252                                             errorCallback, warningCallback);
00253         if(!w.png_ptr)
00254           ARCHON_THROW1(ResourceException,
00255                         "PNG library was unable to allocate "
00256                         "a 'png_struct' structure");
00257 
00258         w.info_ptr = png_create_info_struct(w.png_ptr);
00259         if(!w.info_ptr)
00260           ARCHON_THROW1(ResourceException,
00261                         "PNG library was unable to allocate "
00262                         "first 'png_info' structure");
00263 
00264         png_set_write_fn(w.png_ptr, 0, writeCallback, flushCallback);
00265         png_set_write_status_fn(w.png_ptr, progressCallback);
00266 
00267         string comment = image.getComment();
00268         Array<png_byte *> rowPointers;
00269 
00270         /*
00271          * Handle termination and catch errors occuring in any of the
00272          * following PNG-functions.
00273          */
00274         if(!setjmp(png_jmpbuf(w.png_ptr))) // try
00275         {
00276           int colorType;
00277           switch(image.getComponentSpecifier())
00278           {
00279           case Image::components_l:    colorType = PNG_COLOR_TYPE_GRAY;       break;
00280           case Image::components_la:   colorType = PNG_COLOR_TYPE_GRAY_ALPHA; break;
00281           case Image::components_rgb:  colorType = PNG_COLOR_TYPE_RGB;        break;
00282           case Image::components_rgba: colorType = PNG_COLOR_TYPE_RGB_ALPHA;  break;
00283           default:
00284             ARCHON_THROW1(InternalException,
00285                           "Unexpected channel specifier " +
00286                           Text::toString(image.getComponentSpecifier()));
00287           };
00288 
00289           unsigned height = image.getHeight();
00290           unsigned bitsPerComponent = image.getBitsPerComponent();
00291           png_set_IHDR(w.png_ptr, w.info_ptr, image.getWidth(), height,
00292                        bitsPerComponent, colorType, PNG_INTERLACE_NONE,
00293                        PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
00294 
00295           png_write_info(w.png_ptr, w.info_ptr);
00296 
00297           // Set PNG read transformations
00298           if(bitsPerComponent < 8) png_set_packing(w.png_ptr);
00299           if(bitsPerComponent > 8) png_set_swap(w.png_ptr);
00300 
00301           png_byte *pixelBuffer =
00302             reinterpret_cast<png_byte *>(getPixelBufferNoLeak(image));
00303           rowPointers.reset(height);
00304           for(unsigned i=0; i<height; i++)
00305             rowPointers[i] = pixelBuffer + (height-i-1)*image.getCharsPerRow();
00306 
00307           c.totalRows = height; // For the progress tracker
00308 
00309           png_write_image(w.png_ptr, rowPointers.get());
00310 
00311           // Add the comment after the pixel data
00312           if(!comment.empty())
00313           {
00314             // Get rid of NUL characters in the comment. The PNG spec does
00315             // not allow them.
00316             string::size_type i = 0;
00317             for(;;)
00318             {
00319               string::size_type p = comment.find('\0');
00320               if(p == string::npos) break;
00321               comment.replace(i, 1, "\xEF\xBF\xBD"); // Unicode replacement character
00322             }
00323 
00324             png_text text;
00325 
00326 #ifdef PNG_iTXt_SUPPORTED
00327 
00328             text.text_length = 0;
00329             text.itxt_length = comment.size();
00330             text.lang        = 0;
00331             text.lang_key    = 0;
00332             text.compression = comment.size() < 1000 ?
00333               PNG_ITXT_COMPRESSION_NONE : PNG_ITXT_COMPRESSION_zTXt;
00334 
00335 #else
00336 
00337             comment = CharEnc::transcode(comment, CharEnc::UTF_8,
00338                                          CharEnc::ISO_8859_1);
00339             text.text_length = comment.size();
00340             text.compression = comment.size() < 1000 ?
00341               PNG_TEXT_COMPRESSION_NONE : PNG_TEXT_COMPRESSION_zTXt;
00342 
00343 #endif
00344 
00345             text.key         = "Comment";
00346             text.text        = const_cast<char *>(comment.data());
00347 
00348             png_set_text(w.png_ptr, w.info_ptr, &text, 1);
00349           }
00350 
00351           png_write_end(w.png_ptr, 0);
00352         }
00353         else // catch
00354         {
00355           switch(c.errorType)
00356           {
00357           case 1: throw ThreadTerminatedException(c.errorLocation);
00358           case 2: throw Stream::ReadException(c.errorLocation, c.errorMessage);
00359           case 3: throw UnexpectedException(c.errorLocation, c.errorMessage);
00360           }
00361           ARCHON_THROW1(Image::InvalidFormatException, c.errorMessage);
00362         }
00363       }
00364 
00365     private:
00366 
00367       struct Context
00368       {
00369         double totalRows;
00370         Image::ProgressTracker *tracker;
00371         Logger *logger;       // For warnings
00372         int errorType;        // 0 for none, 1 for ThreadTerminatedException,
00373                               // 2 for IOException, 3 for UnexpectedException
00374                               // from the stream, 4 for libpng initiated
00375                               // error or invalid format detected.
00376         string errorMessage;  // For fatal errors
00377         string errorLocation; // Source file location from exception
00378         Context(Image::ProgressTracker *tracker, Logger *logger):
00379           totalRows(0), tracker(tracker), logger(logger), errorType(0)  {}
00380       };
00381 
00382       struct LoadContext: Context
00383       {
00384         Ref<Stream::Reader> reader;
00385         LoadContext(Ref<Stream::Reader> reader, Image::ProgressTracker *tracker,
00386                     Logger *logger): Context(tracker, logger), reader(reader) {}
00387       };
00388 
00389       struct SaveContext: Context
00390       {
00391         Ref<Stream::Writer> writer;
00392         SaveContext(Ref<Stream::Writer> writer, Image::ProgressTracker *tracker,
00393                     Logger *logger): Context(tracker, logger), writer(writer) {}
00394       };
00395 
00396       struct LoadWrapper
00397       {
00398         png_structp png_ptr;
00399         png_infop info_ptr;
00400         png_infop end_info_ptr;
00401         LoadWrapper():png_ptr(0), info_ptr(0), end_info_ptr(0) {}
00402         ~LoadWrapper()
00403         {
00404           if(png_ptr) png_destroy_read_struct(&png_ptr, info_ptr ? &info_ptr : 0,
00405                                               end_info_ptr ? &end_info_ptr : 0);
00406         }
00407       };
00408 
00409       struct SaveWrapper
00410       {
00411         png_structp png_ptr;
00412         png_infop info_ptr;
00413         SaveWrapper():png_ptr(0), info_ptr(0) {}
00414         ~SaveWrapper()
00415         {
00416           if(png_ptr) png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : 0);
00417         }
00418       };
00419 
00420       static void errorCallback(png_structp png_ptr, png_const_charp message) throw()
00421       {
00422         {
00423           Context *c = static_cast<Context *>(png_get_error_ptr(png_ptr));
00424           c->errorType    = 4;
00425           c->errorMessage = message;
00426         }
00427         longjmp(png_jmpbuf(png_ptr), 1);
00428       }
00429 
00430       static void warningCallback(png_structp png_ptr, png_const_charp message) throw()
00431       {
00432         Context *c = static_cast<Context *>(png_get_error_ptr(png_ptr));
00433         if(c->logger) c->logger->log(message);
00434       }
00435 
00436       static void progressCallback(png_structp png_ptr, png_uint_32 row, int pass) throw()
00437       {
00438         Context *c = static_cast<Context *>(png_get_error_ptr(png_ptr));
00439         // if(c->logger) c->logger->log("Progress " + Text::toString(row) + "/" + Text::toString(c->totalRows) + " (" + Text::toString(pass) + ")");
00440         if(c->tracker && c->totalRows) c->tracker->progress(row/c->totalRows);
00441       }
00442 
00446       static void readCallback(png_structp png_ptr, png_bytep data,
00447                         png_size_t length) throw()
00448       {
00449         LoadContext *c = static_cast<LoadContext *>(png_get_error_ptr(png_ptr));
00450         try
00451         {
00452           const int n = c->reader->readAll(reinterpret_cast<char *>(data),
00453                                            static_cast<int>(length));
00454           if(n == static_cast<int>(length)) return;
00455 
00456           c->errorType    = 4;
00457           c->errorMessage = "Premature end of PNG data";
00458         }
00459         catch(ThreadTerminatedException &e)
00460         {
00461           c->errorType     = 1;
00462           c->errorLocation = e.getLocation();
00463         }
00464         catch(IOException &e)
00465         {
00466           c->errorType     = 2;
00467           c->errorLocation = e.getLocation();
00468           c->errorMessage  = e.getMessage();
00469         }
00470         catch(UnexpectedException &e)
00471         {
00472           c->errorType     = 3;
00473           c->errorLocation = e.getLocation();
00474           c->errorMessage  = e.getMessage();
00475         }
00476 
00477         longjmp(png_jmpbuf(png_ptr), 1);
00478       }
00479 
00480       static void writeCallback(png_structp png_ptr, png_bytep data,
00481                                 png_size_t length) throw()
00482       {
00483         SaveContext *c = static_cast<SaveContext *>(png_get_error_ptr(png_ptr));
00484         try
00485         {
00486           c->writer->writeAll(reinterpret_cast<const char *>(data),
00487                               static_cast<int>(length));
00488           return;
00489         }
00490         catch(ThreadTerminatedException &e)
00491         {
00492           c->errorType     = 1;
00493           c->errorLocation = e.getLocation();
00494         }
00495         catch(IOException &e)
00496         {
00497           c->errorType     = 2;
00498           c->errorLocation = e.getLocation();
00499           c->errorMessage  = e.getMessage();
00500         }
00501         catch(UnexpectedException &e)
00502         {
00503           c->errorType     = 3;
00504           c->errorLocation = e.getLocation();
00505           c->errorMessage  = e.getMessage();
00506         }
00507 
00508         longjmp(png_jmpbuf(png_ptr), 1);
00509       }
00510 
00514       static void flushCallback(png_structp png_ptr)
00515       {
00516       }
00517     };
00518 
00519     Image::Format *getDefaultPngFormat()
00520     {
00521       static FormatLibpng f;
00522       return &f;
00523     }
00524   }
00525 }

Generated on Sun Jul 30 22:55:44 2006 for Archon by  doxygen 1.4.4