Back to index

lightning-sunbird  0.9+nobinonly
nsPNGEncoder.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
00002  * ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is PNG Encoding code
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Google Inc.
00019  * Portions created by the Initial Developer are Copyright (C) 2005
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Brett Wilson <brettw@gmail.com>
00024  *   Stuart Parmenter <pavlov@pavlov.net>
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 "nsPNGEncoder.h"
00041 #include "prmem.h"
00042 #include "nsString.h"
00043 #include "nsStreamUtils.h"
00044 
00045 // Bug 308126 - AIX defines jmpbuf in sys/context.h which conflicts with the
00046 // definition of jmpbuf in the png.h header file.
00047 #ifdef jmpbuf
00048 #undef jmpbuf
00049 #endif
00050 
00051 // Input streams that do not implement nsIAsyncInputStream should be threadsafe
00052 // so that they may be used with nsIInputStreamPump and nsIInputStreamChannel,
00053 // which read such a stream on a background thread.
00054 NS_IMPL_THREADSAFE_ISUPPORTS2(nsPNGEncoder, imgIEncoder, nsIInputStream)
00055 
00056 nsPNGEncoder::nsPNGEncoder() : mImageBuffer(nsnull), mImageBufferSize(0),
00057                                  mImageBufferUsed(0), mImageBufferReadPoint(0)
00058 {
00059 }
00060 
00061 nsPNGEncoder::~nsPNGEncoder()
00062 {
00063   if (mImageBuffer) {
00064     PR_Free(mImageBuffer);
00065     mImageBuffer = nsnull;
00066   }
00067 }
00068 
00069 // nsPNGEncoder::InitFromData
00070 //
00071 //    One output option is supported: "transparency=none" means that the
00072 //    output PNG will not have an alpha channel, even if the input does.
00073 //
00074 //    Based partially on gfx/cairo/cairo/src/cairo-png.c
00075 //    See also modules/libimg/png/libpng.txt
00076 
00077 NS_IMETHODIMP nsPNGEncoder::InitFromData(const PRUint8* aData,
00078                                           PRUint32 aLength, // (unused, req'd by JS)
00079                                           PRUint32 aWidth,
00080                                           PRUint32 aHeight,
00081                                           PRUint32 aStride,
00082                                           PRUint32 aInputFormat,
00083                                           const nsAString& aOutputOptions)
00084 {
00085   // validate input format
00086   if (aInputFormat != INPUT_FORMAT_RGB &&
00087       aInputFormat != INPUT_FORMAT_RGBA &&
00088       aInputFormat != INPUT_FORMAT_HOSTARGB)
00089     return NS_ERROR_INVALID_ARG;
00090 
00091   // Stride is the padded width of each row, so it better be longer (I'm afraid
00092   // people will not understand what stride means, so check it well)
00093   if ((aInputFormat == INPUT_FORMAT_RGB &&
00094        aStride < aWidth * 3) ||
00095       ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) &&
00096        aStride < aWidth * 4)) {
00097     NS_WARNING("Invalid stride for InitFromData");
00098     return NS_ERROR_INVALID_ARG;
00099   }
00100 
00101   // can't initialize more than once
00102   if (mImageBuffer != nsnull)
00103     return NS_ERROR_ALREADY_INITIALIZED;
00104 
00105   // options: we only have one option so this is easy
00106   PRBool useTransparency = PR_TRUE;
00107   if (aOutputOptions.Length() >= 17) {
00108     if (StringBeginsWith(aOutputOptions,  NS_LITERAL_STRING("transparency=none")))
00109       useTransparency = PR_FALSE;
00110   }
00111 
00112   // initialize
00113   png_struct* png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
00114                                                 png_voidp_NULL,
00115                                                 png_error_ptr_NULL,
00116                                                 png_error_ptr_NULL);
00117   if (! png_ptr)
00118     return NS_ERROR_OUT_OF_MEMORY;
00119   png_info* info_ptr = png_create_info_struct(png_ptr);
00120   if (! info_ptr)
00121   {
00122     png_destroy_write_struct(&png_ptr, nsnull);
00123     return NS_ERROR_FAILURE;
00124   }
00125   if (setjmp(png_jmpbuf(png_ptr))) {
00126     png_destroy_write_struct(&png_ptr, &info_ptr);
00127     return NS_ERROR_OUT_OF_MEMORY;
00128   }
00129 
00130   // Set up to read the data into our image buffer, start out with an 8K
00131   // estimated size. Note: we don't have to worry about freeing this data
00132   // in this function. It will be freed on object destruction.
00133   mImageBufferSize = 8192;
00134   mImageBuffer = (PRUint8*)PR_Malloc(mImageBufferSize);
00135   if (!mImageBuffer) {
00136     png_destroy_write_struct(&png_ptr, &info_ptr);
00137     return NS_ERROR_OUT_OF_MEMORY;
00138   }
00139   mImageBufferUsed = 0;
00140 
00141   // set our callback for libpng to give us the data
00142   png_set_write_fn(png_ptr, this, WriteCallback, NULL);
00143 
00144   // include alpha?
00145   int colorType;
00146   if ((aInputFormat == INPUT_FORMAT_HOSTARGB ||
00147        aInputFormat == INPUT_FORMAT_RGBA)  &&  useTransparency)
00148     colorType = PNG_COLOR_TYPE_RGB_ALPHA;
00149   else
00150     colorType = PNG_COLOR_TYPE_RGB;
00151 
00152   png_set_IHDR(png_ptr, info_ptr, aWidth, aHeight, 8, colorType,
00153                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
00154                PNG_FILTER_TYPE_DEFAULT);
00155 
00156   png_write_info(png_ptr, info_ptr);
00157 
00158   // write each row: if we add more input formats, we may want to
00159   // generalize the conversions
00160   if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
00161     // PNG requires RGBA with post-multiplied alpha, so we need to convert
00162     PRUint8* row = new PRUint8[aWidth * 4];
00163     for (PRUint32 y = 0; y < aHeight; y ++) {
00164       ConvertHostARGBRow(&aData[y * aStride], row, aWidth, useTransparency);
00165       png_write_row(png_ptr, row);
00166     }
00167     delete[] row;
00168 
00169   } else if (aInputFormat == INPUT_FORMAT_RGBA && ! useTransparency) {
00170     // RBGA, but we need to strip the alpha
00171     PRUint8* row = new PRUint8[aWidth * 4];
00172     for (PRUint32 y = 0; y < aHeight; y ++) {
00173       StripAlpha(&aData[y * aStride], row, aWidth);
00174       png_write_row(png_ptr, row);
00175     }
00176     delete[] row;
00177 
00178   } else if (aInputFormat == INPUT_FORMAT_RGB ||
00179              aInputFormat == INPUT_FORMAT_RGBA) {
00180     // simple RBG(A), no conversion needed
00181     for (PRUint32 y = 0; y < aHeight; y ++) {
00182       png_write_row(png_ptr, (PRUint8*)&aData[y * aStride]);
00183     }
00184 
00185   } else {
00186     NS_NOTREACHED("Bad format type");
00187   }
00188 
00189   png_write_end(png_ptr, info_ptr);
00190   png_destroy_write_struct(&png_ptr, &info_ptr);
00191 
00192   // if output callback can't get enough memory, it will free our buffer
00193   if (!mImageBuffer)
00194     return NS_ERROR_OUT_OF_MEMORY;
00195 
00196   return NS_OK;
00197 }
00198 
00199 
00200 /* void close (); */
00201 NS_IMETHODIMP nsPNGEncoder::Close()
00202 {
00203   if (mImageBuffer != nsnull) {
00204     PR_Free(mImageBuffer);
00205     mImageBuffer = nsnull;
00206     mImageBufferSize = 0;
00207     mImageBufferUsed = 0;
00208     mImageBufferReadPoint = 0;
00209   }
00210   return NS_OK;
00211 }
00212 
00213 /* unsigned long available (); */
00214 NS_IMETHODIMP nsPNGEncoder::Available(PRUint32 *_retval)
00215 {
00216   if (!mImageBuffer)
00217     return NS_BASE_STREAM_CLOSED;
00218 
00219   *_retval = mImageBufferUsed - mImageBufferReadPoint;
00220   return NS_OK;
00221 }
00222 
00223 /* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
00224 NS_IMETHODIMP nsPNGEncoder::Read(char * aBuf, PRUint32 aCount,
00225                                  PRUint32 *_retval)
00226 {
00227   return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
00228 }
00229 
00230 /* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */
00231 NS_IMETHODIMP nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter,
00232                                          void *aClosure, PRUint32 aCount,
00233                                          PRUint32 *_retval)
00234 {
00235   PRUint32 maxCount = mImageBufferUsed - mImageBufferReadPoint;
00236   if (maxCount == 0) {
00237     *_retval = 0;
00238     return NS_OK;
00239   }
00240 
00241   if (aCount > maxCount)
00242     aCount = maxCount;
00243   nsresult rv = aWriter(this, aClosure,
00244                         NS_REINTERPRET_CAST(const char*, mImageBuffer),
00245                         0, aCount, _retval);
00246   if (NS_SUCCEEDED(rv)) {
00247     NS_ASSERTION(*_retval <= aCount, "bad write count");
00248     mImageBufferReadPoint += *_retval;
00249   }
00250 
00251   // errors returned from the writer end here!
00252   return NS_OK;
00253 }
00254 
00255 /* boolean isNonBlocking (); */
00256 NS_IMETHODIMP nsPNGEncoder::IsNonBlocking(PRBool *_retval)
00257 {
00258   *_retval = PR_FALSE;  // We don't implement nsIAsyncInputStream
00259   return NS_OK;
00260 }
00261 
00262 
00263 // nsPNGEncoder::ConvertHostARGBRow
00264 //
00265 //    Our colors are stored with premultiplied alphas, but PNGs use
00266 //    post-multiplied alpha. This swaps to PNG-style alpha.
00267 //
00268 //    Copied from gfx/cairo/cairo/src/cairo-png.c
00269 
00270 void
00271 nsPNGEncoder::ConvertHostARGBRow(const PRUint8* aSrc, PRUint8* aDest,
00272                                   PRUint32 aPixelWidth, PRBool aUseTransparency)
00273 {
00274   PRUint32 pixelStride = aUseTransparency ? 4 : 3;
00275   for (PRUint32 x = 0; x < aPixelWidth; x ++) {
00276     const PRUint32& pixelIn = ((const PRUint32*)(aSrc))[x];
00277     PRUint8 *pixelOut = &aDest[x * pixelStride];
00278 
00279     PRUint8 alpha = (pixelIn & 0xff000000) >> 24;
00280     if (alpha == 0) {
00281       pixelOut[0] = pixelOut[1] = pixelOut[2] = pixelOut[3] = 0;
00282     } else {
00283       pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
00284       pixelOut[1] = (((pixelIn & 0x00ff00) >>  8) * 255 + alpha / 2) / alpha;
00285       pixelOut[2] = (((pixelIn & 0x0000ff) >>  0) * 255 + alpha / 2) / alpha;
00286       if (aUseTransparency)
00287         pixelOut[3] = alpha;
00288     }
00289   }
00290 }
00291 
00292 
00293 // nsPNGEncoder::StripAlpha
00294 //
00295 //    Input is RGBA, output is RGB
00296 
00297 void
00298 nsPNGEncoder::StripAlpha(const PRUint8* aSrc, PRUint8* aDest,
00299                           PRUint32 aPixelWidth)
00300 {
00301   for (PRUint32 x = 0; x < aPixelWidth; x ++) {
00302     const PRUint8* pixelIn = &aSrc[x * 4];
00303     PRUint8* pixelOut = &aDest[x * 3];
00304     pixelOut[0] = pixelIn[0];
00305     pixelOut[1] = pixelIn[1];
00306     pixelOut[2] = pixelIn[2];
00307   }
00308 }
00309 
00310 
00311 // nsPNGEncoder::WriteCallback
00312 
00313 void // static
00314 nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, png_size_t size)
00315 {
00316   nsPNGEncoder* that = NS_STATIC_CAST(nsPNGEncoder*, png_get_io_ptr(png));
00317   if (! that->mImageBuffer)
00318     return;
00319 
00320   if (that->mImageBufferUsed + size > that->mImageBufferSize) {
00321     // expand buffer, just double each time
00322     that->mImageBufferSize *= 2;
00323     PRUint8* newBuf = (PRUint8*)PR_Realloc(that->mImageBuffer,
00324                                            that->mImageBufferSize);
00325     if (! newBuf) {
00326       // can't resize, just zero (this will keep us from writing more)
00327       PR_Free(that->mImageBuffer);
00328       that->mImageBufferSize = 0;
00329       that->mImageBufferUsed = 0;
00330       return;
00331     }
00332     that->mImageBuffer = newBuf;
00333   }
00334   memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size);
00335   that->mImageBufferUsed += size;
00336 }