Back to index

lightning-sunbird  0.9+nobinonly
nsPNGDecoder.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 2001
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Stuart Parmenter <pavlov@netscape.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "nsPNGDecoder.h"
00041 
00042 #include "nsMemory.h"
00043 #include "nsRect.h"
00044 
00045 #include "nsIComponentManager.h"
00046 #include "nsIInputStream.h"
00047 
00048 #include "imgIContainerObserver.h"
00049 
00050 #include "nsColor.h"
00051 
00052 #include "nspr.h"
00053 #include "png.h"
00054 
00055 static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr);
00056 static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row,
00057                            png_uint_32 row_num, int pass);
00058 static void PNGAPI end_callback(png_structp png_ptr, png_infop info_ptr);
00059 static void PNGAPI error_callback(png_structp png_ptr, png_const_charp error_msg);
00060 static void PNGAPI warning_callback(png_structp png_ptr, png_const_charp warning_msg);
00061 
00062 #ifdef PR_LOGGING
00063 PRLogModuleInfo *gPNGLog = PR_NewLogModule("PNGDecoder");
00064 #endif
00065 
00066 NS_IMPL_ISUPPORTS1(nsPNGDecoder, imgIDecoder)
00067 
00068 nsPNGDecoder::nsPNGDecoder() :
00069   mPNG(nsnull), mInfo(nsnull),
00070   colorLine(nsnull), alphaLine(nsnull),
00071   interlacebuf(nsnull), ibpr(0),
00072   mError(PR_FALSE)
00073 {
00074 }
00075 
00076 nsPNGDecoder::~nsPNGDecoder()
00077 {
00078   if (colorLine)
00079     nsMemory::Free(colorLine);
00080   if (alphaLine)
00081     nsMemory::Free(alphaLine);
00082   if (interlacebuf)
00083     nsMemory::Free(interlacebuf);
00084 }
00085 
00086 
00089 /* void init (in imgILoad aLoad); */
00090 NS_IMETHODIMP nsPNGDecoder::Init(imgILoad *aLoad)
00091 {
00092 #if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
00093   static png_byte unused_chunks[]=
00094        { 98,  75,  71,  68, '\0',   /* bKGD */
00095          99,  72,  82,  77, '\0',   /* cHRM */
00096         104,  73,  83,  84, '\0',   /* hIST */
00097         105,  67,  67,  80, '\0',   /* iCCP */
00098         105,  84,  88, 116, '\0',   /* iTXt */
00099         111,  70,  70, 115, '\0',   /* oFFs */
00100         112,  67,  65,  76, '\0',   /* pCAL */
00101         115,  67,  65,  76, '\0',   /* sCAL */
00102         112,  72,  89, 115, '\0',   /* pHYs */
00103         115,  66,  73,  84, '\0',   /* sBIT */
00104         115,  80,  76,  84, '\0',   /* sPLT */
00105         116,  69,  88, 116, '\0',   /* tEXt */
00106         116,  73,  77,  69, '\0',   /* tIME */
00107         122,  84,  88, 116, '\0'};  /* zTXt */
00108 #endif
00109 
00110   mImageLoad = aLoad;
00111   mObserver = do_QueryInterface(aLoad);  // we're holding 2 strong refs to the request.
00112 
00113   /* do png init stuff */
00114 
00115   /* Initialize the container's source image header. */
00116   /* Always decode to 24 bit pixdepth */
00117 
00118   mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, 
00119                                 NULL, error_callback, warning_callback);
00120   if (!mPNG) {
00121     return NS_ERROR_OUT_OF_MEMORY;
00122   }
00123 
00124   mInfo = png_create_info_struct(mPNG);
00125   if (!mInfo) {
00126     png_destroy_read_struct(&mPNG, NULL, NULL);
00127     return NS_ERROR_OUT_OF_MEMORY;
00128   }
00129 
00130 #if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
00131   /* Ignore unused chunks */
00132   png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
00133      (int)sizeof(unused_chunks)/5);   
00134 #endif
00135 
00136   /* use this as libpng "progressive pointer" (retrieve in callbacks) */
00137   png_set_progressive_read_fn(mPNG, NS_STATIC_CAST(png_voidp, this),
00138                               info_callback, row_callback, end_callback);
00139 
00140   return NS_OK;
00141 }
00142 
00143 /* void close (); */
00144 NS_IMETHODIMP nsPNGDecoder::Close()
00145 {
00146   if (mPNG)
00147     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : NULL, NULL);
00148 
00149   return NS_OK;
00150 }
00151 
00152 /* void flush (); */
00153 NS_IMETHODIMP nsPNGDecoder::Flush()
00154 {
00155     return NS_ERROR_NOT_IMPLEMENTED;
00156 }
00157 
00158 
00159 static NS_METHOD ReadDataOut(nsIInputStream* in,
00160                              void* closure,
00161                              const char* fromRawSegment,
00162                              PRUint32 toOffset,
00163                              PRUint32 count,
00164                              PRUint32 *writeCount)
00165 {
00166   nsPNGDecoder *decoder = NS_STATIC_CAST(nsPNGDecoder*, closure);
00167 
00168   if (decoder->mError) {
00169     *writeCount = 0;
00170     return NS_ERROR_FAILURE;
00171   }
00172 
00173   // we need to do the setjmp here otherwise bad things will happen
00174   if (setjmp(decoder->mPNG->jmpbuf)) {
00175     png_destroy_read_struct(&decoder->mPNG, &decoder->mInfo, NULL);
00176 
00177     decoder->mError = PR_TRUE;
00178     *writeCount = 0;
00179     return NS_ERROR_FAILURE;
00180   }
00181 
00182   png_process_data(decoder->mPNG, decoder->mInfo,
00183                    NS_REINTERPRET_CAST(unsigned char *, NS_CONST_CAST(char *, fromRawSegment)), count);
00184 
00185   *writeCount = count;
00186   return NS_OK;
00187 }
00188 
00189 
00190 /* unsigned long writeFrom (in nsIInputStream inStr, in unsigned long count); */
00191 NS_IMETHODIMP nsPNGDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *_retval)
00192 {
00193   NS_ASSERTION(inStr, "Got a null input stream!");
00194 
00195   nsresult rv;
00196 
00197   if (!mError)
00198     rv = inStr->ReadSegments(ReadDataOut, this, count, _retval);
00199 
00200   if (mError) {
00201     *_retval = 0;
00202     rv = NS_ERROR_FAILURE;
00203   }
00204 
00205   return rv;
00206 }
00207 
00208 
00209 void
00210 info_callback(png_structp png_ptr, png_infop info_ptr)
00211 {
00212 /*  int number_passes;   NOT USED  */
00213   png_uint_32 width, height;
00214   int bit_depth, color_type, interlace_type, compression_type, filter_type;
00215   int channels;
00216   double aGamma;
00217 
00218   png_bytep trans=NULL;
00219   int num_trans =0;
00220 
00221   /* always decode to 24-bit RGB or 32-bit RGBA  */
00222   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
00223                &interlace_type, &compression_type, &filter_type);
00224 
00225   /* limit image dimensions (bug #251381) */
00226 #define MOZ_PNG_MAX_DIMENSION 1000000L
00227   if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) {
00228     nsPNGDecoder *decoder = NS_STATIC_CAST(nsPNGDecoder*,
00229                                            png_get_progressive_ptr(png_ptr));
00230     longjmp(decoder->mPNG->jmpbuf, 1);
00231   }
00232 #undef MOZ_PNG_MAX_DIMENSION
00233 
00234   if (color_type == PNG_COLOR_TYPE_PALETTE)
00235     png_set_expand(png_ptr);
00236 
00237   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
00238     png_set_expand(png_ptr);
00239 
00240   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
00241     png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
00242     png_set_expand(png_ptr);
00243   }
00244 
00245   if (bit_depth == 16)
00246     png_set_strip_16(png_ptr);
00247 
00248   if (color_type == PNG_COLOR_TYPE_GRAY ||
00249       color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
00250       png_set_gray_to_rgb(png_ptr);
00251 
00252 
00253 #if defined(XP_WIN) || defined(XP_OS2) || defined(XP_BEOS) || defined(MOZ_WIDGET_PHOTON)
00254   // windows likes BGR
00255   png_set_bgr(png_ptr);
00256 #endif
00257 
00258   if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) {
00259       if ((aGamma <= 0.0) || (aGamma > 21474.83)) {
00260           aGamma = 0.45455;
00261           png_set_gAMA(png_ptr, info_ptr, aGamma);
00262       }
00263       png_set_gamma(png_ptr, 2.2, aGamma);
00264   }
00265   else
00266       png_set_gamma(png_ptr, 2.2, 0.45455);
00267 
00268   /* let libpng expand interlaced images */
00269   if (interlace_type == PNG_INTERLACE_ADAM7) {
00270     /* number_passes = */
00271     png_set_interlace_handling(png_ptr);
00272   }
00273 
00274   /* now all of those things we set above are used to update various struct
00275    * members and whatnot, after which we can get channels, rowbytes, etc. */
00276   png_read_update_info(png_ptr, info_ptr);
00277   channels = png_get_channels(png_ptr, info_ptr);
00278   PR_ASSERT(channels == 3 || channels == 4);
00279 
00280   /*---------------------------------------------------------------*/
00281   /* copy PNG info into imagelib structs (formerly png_set_dims()) */
00282   /*---------------------------------------------------------------*/
00283 
00284   PRInt32 alpha_bits = 1;
00285 
00286   if (channels > 3) {
00287     /* check if alpha is coming from a tRNS chunk and is binary */
00288     if (num_trans) {
00289       /* if it's not a indexed color image, tRNS means binary */
00290       if (color_type == PNG_COLOR_TYPE_PALETTE) {
00291         for (int i=0; i<num_trans; i++) {
00292           if ((trans[i] != 0) && (trans[i] != 255)) {
00293             alpha_bits = 8;
00294             break;
00295           }
00296         }
00297       }
00298     } else {
00299       alpha_bits = 8;
00300     }
00301   }
00302 
00303   nsPNGDecoder *decoder = NS_STATIC_CAST(nsPNGDecoder*, png_get_progressive_ptr(png_ptr));
00304 
00305   if (decoder->mObserver)
00306     decoder->mObserver->OnStartDecode(nsnull);
00307 
00308   decoder->mImage = do_CreateInstance("@mozilla.org/image/container;1");
00309   if (!decoder->mImage)
00310     longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY
00311 
00312   decoder->mImageLoad->SetImage(decoder->mImage);
00313 
00314   // since the png is only 1 frame, initalize the container to the width and height of the frame
00315   decoder->mImage->Init(width, height, decoder->mObserver);
00316 
00317   if (decoder->mObserver)
00318     decoder->mObserver->OnStartContainer(nsnull, decoder->mImage);
00319 
00320   decoder->mFrame = do_CreateInstance("@mozilla.org/gfx/image/frame;2");
00321   if (!decoder->mFrame)
00322     longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY
00323 
00324   gfx_format format;
00325 
00326   if (channels == 3) {
00327     format = gfxIFormats::RGB;
00328   } else if (channels > 3) {
00329     if (alpha_bits == 8) {
00330       decoder->mImage->GetPreferredAlphaChannelFormat(&format);
00331     } else if (alpha_bits == 1) {
00332       format = gfxIFormats::RGB_A1;
00333     }
00334   }
00335 
00336 #if defined(XP_WIN) || defined(XP_OS2) || defined(XP_BEOS) || defined(MOZ_WIDGET_PHOTON)
00337   // XXX this works...
00338   format += 1; // RGB to BGR
00339 #endif
00340 
00341   // then initalize the frame and append it to the container
00342   nsresult rv = decoder->mFrame->Init(0, 0, width, height, format, 24);
00343   if (NS_FAILED(rv))
00344     longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY
00345 
00346   decoder->mImage->AppendFrame(decoder->mFrame);
00347 
00348   if (decoder->mObserver)
00349     decoder->mObserver->OnStartFrame(nsnull, decoder->mFrame);
00350 
00351   PRUint32 bpr, abpr;
00352   decoder->mFrame->GetImageBytesPerRow(&bpr);
00353   decoder->mFrame->GetAlphaBytesPerRow(&abpr);
00354   decoder->colorLine = (PRUint8 *)nsMemory::Alloc(bpr);
00355   if (channels > 3)
00356     decoder->alphaLine = (PRUint8 *)nsMemory::Alloc(abpr);
00357 
00358   if (interlace_type == PNG_INTERLACE_ADAM7) {
00359     if (channels > 3)
00360       decoder->ibpr = channels*width;
00361     else
00362       decoder->ibpr = bpr;
00363     decoder->interlacebuf = (PRUint8 *)nsMemory::Alloc(decoder->ibpr*height);
00364     if (!decoder->interlacebuf) {
00365       longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY
00366     }            
00367   }
00368 
00369   return;
00370 }
00371 
00372 
00373 
00374 
00375 
00376 void
00377 row_callback(png_structp png_ptr, png_bytep new_row,
00378              png_uint_32 row_num, int pass)
00379 {
00380   /* libpng comments:
00381    *
00382    * this function is called for every row in the image.  If the
00383    * image is interlacing, and you turned on the interlace handler,
00384    * this function will be called for every row in every pass.
00385    * Some of these rows will not be changed from the previous pass.
00386    * When the row is not changed, the new_row variable will be NULL.
00387    * The rows and passes are called in order, so you don't really
00388    * need the row_num and pass, but I'm supplying them because it
00389    * may make your life easier.
00390    *
00391    * For the non-NULL rows of interlaced images, you must call
00392    * png_progressive_combine_row() passing in the row and the
00393    * old row.  You can call this function for NULL rows (it will
00394    * just return) and for non-interlaced images (it just does the
00395    * memcpy for you) if it will make the code easier.  Thus, you
00396    * can just do this for all cases:
00397    *
00398    *    png_progressive_combine_row(png_ptr, old_row, new_row);
00399    *
00400    * where old_row is what was displayed for previous rows.  Note
00401    * that the first pass (pass == 0 really) will completely cover
00402    * the old row, so the rows do not have to be initialized.  After
00403    * the first pass (and only for interlaced images), you will have
00404    * to pass the current row, and the function will combine the
00405    * old row and the new row.
00406    */
00407   nsPNGDecoder *decoder = NS_STATIC_CAST(nsPNGDecoder*, png_get_progressive_ptr(png_ptr));
00408 
00409   PRUint32 bpr, abpr;
00410   decoder->mFrame->GetImageBytesPerRow(&bpr);
00411   decoder->mFrame->GetAlphaBytesPerRow(&abpr);
00412 
00413   png_bytep line;
00414   if (decoder->interlacebuf) {
00415     line = decoder->interlacebuf+(row_num*decoder->ibpr);
00416     png_progressive_combine_row(png_ptr, line, new_row);
00417   }
00418   else
00419     line = new_row;
00420 
00421   if (new_row) {
00422     PRInt32 width;
00423     decoder->mFrame->GetWidth(&width);
00424     PRUint32 iwidth = width;
00425 
00426     gfx_format format;
00427     decoder->mFrame->GetFormat(&format);
00428     PRUint8 *aptr, *cptr;
00429 
00430     // The mac specific ifdefs in the code below are there to make sure we
00431     // always fill in 4 byte pixels right now, which is what the mac always
00432     // allocates for its pixel buffers in true color mode. This will change
00433     // when we start storing images with color palettes when they don't need
00434     // true color support (GIFs).
00435     switch (format) {
00436     case gfxIFormats::RGB:
00437     case gfxIFormats::BGR:
00438 #if defined(XP_MAC) || defined(XP_MACOSX)
00439         cptr = decoder->colorLine;
00440         for (PRUint32 x=0; x<iwidth; x++) {
00441           *cptr++ = 0;
00442           *cptr++ = *line++;
00443           *cptr++ = *line++;
00444           *cptr++ = *line++;
00445         }
00446         decoder->mFrame->SetImageData(decoder->colorLine, bpr, row_num*bpr);
00447 #else
00448         decoder->mFrame->SetImageData((PRUint8*)line, bpr, row_num*bpr);
00449 #endif
00450       break;
00451     case gfxIFormats::RGB_A1:
00452     case gfxIFormats::BGR_A1:
00453       {
00454         cptr = decoder->colorLine;
00455         aptr = decoder->alphaLine;
00456         memset(aptr, 0, abpr);
00457         for (PRUint32 x=0; x<iwidth; x++) {
00458 #if defined(XP_MAC) || defined(XP_MACOSX)
00459           *cptr++ = 0;
00460 #endif
00461           if (line[3]) {
00462             *cptr++ = *line++;
00463             *cptr++ = *line++;
00464             *cptr++ = *line++;
00465             aptr[x>>3] |= 1<<(7-x&0x7);
00466             line++;
00467           } else {
00468             *cptr++ = 0;
00469             *cptr++ = 0;
00470             *cptr++ = 0;
00471             line += 4;
00472           }
00473         }
00474         decoder->mFrame->SetAlphaData(decoder->alphaLine, abpr, row_num*abpr);
00475         decoder->mFrame->SetImageData(decoder->colorLine, bpr, row_num*bpr);
00476       }
00477       break;
00478     case gfxIFormats::RGB_A8:
00479     case gfxIFormats::BGR_A8:
00480       {
00481         cptr = decoder->colorLine;
00482         aptr = decoder->alphaLine;
00483         for (PRUint32 x=0; x<iwidth; x++) {
00484 #if defined(XP_MAC) || defined(XP_MACOSX)
00485           *cptr++ = 0;
00486 #endif
00487           *cptr++ = *line++;
00488           *cptr++ = *line++;
00489           *cptr++ = *line++;
00490           *aptr++ = *line++;
00491         }
00492         decoder->mFrame->SetAlphaData(decoder->alphaLine, abpr, row_num*abpr);
00493         decoder->mFrame->SetImageData(decoder->colorLine, bpr, row_num*bpr);
00494       }
00495       break;
00496     case gfxIFormats::RGBA:
00497     case gfxIFormats::BGRA:
00498 #if defined(XP_MAC) || defined(XP_MACOSX)
00499       {
00500         cptr = decoder->colorLine;
00501         aptr = decoder->alphaLine;
00502         for (PRUint32 x=0; x<iwidth; x++) {
00503           *cptr++ = 0;
00504           *cptr++ = *line++;
00505           *cptr++ = *line++;
00506           *cptr++ = *line++;
00507           *aptr++ = *line++;
00508         }
00509         decoder->mFrame->SetAlphaData(decoder->alphaLine, abpr, row_num*abpr);
00510         decoder->mFrame->SetImageData(decoder->colorLine, bpr, row_num*bpr);
00511       }
00512 #else
00513       decoder->mFrame->SetImageData(line, bpr, row_num*bpr);
00514 #endif
00515       break;
00516     }
00517 
00518     nsIntRect r(0, row_num, width, 1);
00519     decoder->mObserver->OnDataAvailable(nsnull, decoder->mFrame, &r);
00520   }
00521 }
00522 
00523 
00524 
00525 void
00526 end_callback(png_structp png_ptr, png_infop info_ptr)
00527 {
00528   /* libpng comments:
00529    *
00530    * this function is called when the whole image has been read,
00531    * including any chunks after the image (up to and including
00532    * the IEND).  You will usually have the same info chunk as you
00533    * had in the header, although some data may have been added
00534    * to the comments and time fields.
00535    *
00536    * Most people won't do much here, perhaps setting a flag that
00537    * marks the image as finished.
00538    */
00539 
00540   nsPNGDecoder *decoder = NS_STATIC_CAST(nsPNGDecoder*, png_get_progressive_ptr(png_ptr));
00541 
00542   if (decoder->mObserver) {
00543     decoder->mObserver->OnStopFrame(nsnull, decoder->mFrame);
00544     decoder->mObserver->OnStopContainer(nsnull, decoder->mImage);
00545     decoder->mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
00546   }
00547 
00548   // We are never going to change the data of this frame again.  Let the OS
00549   // do what it wants with this image.
00550   decoder->mFrame->SetMutable(PR_FALSE);
00551 }
00552 
00553 
00554 void
00555 error_callback(png_structp png_ptr, png_const_charp error_msg)
00556 {
00557   PR_LOG(gPNGLog, PR_LOG_ERROR, ("libpng error: %s\n", error_msg));
00558   longjmp(png_ptr->jmpbuf, 1);
00559 }
00560 
00561 
00562 void
00563 warning_callback(png_structp png_ptr, png_const_charp warning_msg)
00564 {
00565   /* convert tRNS warning to error (bug #251381) */
00566   if (strncmp(warning_msg, "Missing PLTE before tRNS", 24) == 0)
00567     png_error(png_ptr, warning_msg);
00568   PR_LOG(gPNGLog, PR_LOG_WARNING, ("libpng warning: %s\n", warning_msg));
00569 }