Back to index

lightning-sunbird  0.9+nobinonly
nsJPEGEncoder.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 JPEG 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  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsJPEGEncoder.h"
00040 #include "prmem.h"
00041 #include "prprf.h"
00042 #include "nsString.h"
00043 #include "nsStreamUtils.h"
00044 
00045 #include <setjmp.h>
00046 #include "jerror.h"
00047 
00048 // Input streams that do not implement nsIAsyncInputStream should be threadsafe
00049 // so that they may be used with nsIInputStreamPump and nsIInputStreamChannel,
00050 // which read such a stream on a background thread.
00051 NS_IMPL_THREADSAFE_ISUPPORTS2(nsJPEGEncoder, imgIEncoder, nsIInputStream)
00052 
00053 // used to pass error info through the JPEG library
00054 struct encoder_error_mgr {
00055   jpeg_error_mgr pub;
00056   jmp_buf setjmp_buffer;
00057 };
00058 
00059 nsJPEGEncoder::nsJPEGEncoder() : mImageBuffer(nsnull), mImageBufferSize(0),
00060                                  mImageBufferUsed(0), mImageBufferReadPoint(0)
00061 {
00062 }
00063 
00064 nsJPEGEncoder::~nsJPEGEncoder()
00065 {
00066   if (mImageBuffer) {
00067     PR_Free(mImageBuffer);
00068     mImageBuffer = nsnull;
00069   }
00070 }
00071 
00072 
00073 // nsJPEGEncoder::InitFromData
00074 //
00075 //    One output option is supported: "quality=X" where X is an integer in the
00076 //    range 0-100. Higher values for X give better quality.
00077 //
00078 //    Transparency is always discarded.
00079 
00080 NS_IMETHODIMP nsJPEGEncoder::InitFromData(const PRUint8* aData,
00081                                           PRUint32 aLength, // (unused, req'd by JS)
00082                                           PRUint32 aWidth,
00083                                           PRUint32 aHeight,
00084                                           PRUint32 aStride,
00085                                           PRUint32 aInputFormat,
00086                                           const nsAString& aOutputOptions)
00087 {
00088   // validate input format
00089   if (aInputFormat != INPUT_FORMAT_RGB &&
00090       aInputFormat != INPUT_FORMAT_RGBA &&
00091       aInputFormat != INPUT_FORMAT_HOSTARGB)
00092     return NS_ERROR_INVALID_ARG;
00093 
00094   // Stride is the padded width of each row, so it better be longer (I'm afraid
00095   // people will not understand what stride means, so check it well)
00096   if ((aInputFormat == INPUT_FORMAT_RGB &&
00097        aStride < aWidth * 3) ||
00098       ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) &&
00099        aStride < aWidth * 4)) {
00100     NS_WARNING("Invalid stride for InitFromData");
00101     return NS_ERROR_INVALID_ARG;
00102   }
00103 
00104   // can't initialize more than once
00105   if (mImageBuffer != nsnull)
00106     return NS_ERROR_ALREADY_INITIALIZED;
00107 
00108   // options: we only have one option so this is easy
00109   int quality = 50;
00110   if (aOutputOptions.Length() > 0) {
00111     // have options string
00112     const nsString qualityPrefix(NS_LITERAL_STRING("quality="));
00113     if (aOutputOptions.Length() > qualityPrefix.Length()  &&
00114         StringBeginsWith(aOutputOptions, qualityPrefix)) {
00115       // have quality string
00116       nsCString value = NS_ConvertUTF16toUTF8(Substring(aOutputOptions,
00117                                                         qualityPrefix.Length()));
00118       int newquality = -1;
00119       if (PR_sscanf(PromiseFlatCString(value).get(), "%d", &newquality) == 1) {
00120         if (newquality >= 0 && newquality <= 100) {
00121           quality = newquality;
00122         } else {
00123           NS_WARNING("Quality value out of range, should be 0-100, using default");
00124         }
00125       } else {
00126         NS_WARNING("Quality value invalid, should be integer 0-100, using default");
00127       }
00128     }
00129   }
00130 
00131   jpeg_compress_struct cinfo;
00132 
00133   // We set up the normal JPEG error routines, then override error_exit.
00134   // This must be done before the call to create_compress
00135   encoder_error_mgr errmgr;
00136   cinfo.err = jpeg_std_error(&errmgr.pub);
00137   errmgr.pub.error_exit = errorExit;
00138   // Establish the setjmp return context for my_error_exit to use.
00139   if (setjmp(errmgr.setjmp_buffer)) {
00140     // If we get here, the JPEG code has signaled an error.
00141     // We need to clean up the JPEG object, close the input file, and return.
00142     return NS_ERROR_FAILURE;
00143   }
00144 
00145   jpeg_create_compress(&cinfo);
00146   cinfo.image_width = aWidth;
00147   cinfo.image_height = aHeight;
00148   cinfo.input_components = 3;
00149   cinfo.in_color_space = JCS_RGB;
00150   cinfo.data_precision = 8;
00151 
00152   jpeg_set_defaults(&cinfo);
00153   jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100
00154 
00155   // set up the destination manager
00156   jpeg_destination_mgr destmgr;
00157   destmgr.init_destination = initDestination;
00158   destmgr.empty_output_buffer = emptyOutputBuffer;
00159   destmgr.term_destination = termDestination;
00160   cinfo.dest = &destmgr;
00161   cinfo.client_data = this;
00162 
00163   jpeg_start_compress(&cinfo, 1);
00164 
00165   // feed it the rows
00166   if (aInputFormat == INPUT_FORMAT_RGB) {
00167     while (cinfo.next_scanline < cinfo.image_height) {
00168       const PRUint8* row = &aData[cinfo.next_scanline * aStride];
00169       jpeg_write_scanlines(&cinfo, NS_CONST_CAST(PRUint8**, &row), 1);
00170     }
00171   } else if (aInputFormat == INPUT_FORMAT_RGBA) {
00172     PRUint8* row = new PRUint8[aWidth * 3];
00173     while (cinfo.next_scanline < cinfo.image_height) {
00174       StripAlpha(&aData[cinfo.next_scanline * aStride], row, aWidth);
00175       jpeg_write_scanlines(&cinfo, &row, 1);
00176     }
00177     delete[] row;
00178   } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
00179     PRUint8* row = new PRUint8[aWidth * 3];
00180     while (cinfo.next_scanline < cinfo.image_height) {
00181       ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth);
00182       jpeg_write_scanlines(&cinfo, &row, 1);
00183     }
00184     delete[] row;
00185   }
00186 
00187   jpeg_finish_compress(&cinfo);
00188   jpeg_destroy_compress(&cinfo);
00189 
00190   // if output callback can't get enough memory, it will free our buffer
00191   if (!mImageBuffer)
00192     return NS_ERROR_OUT_OF_MEMORY;
00193 
00194   return NS_OK;
00195 }
00196 
00197 
00198 /* void close (); */
00199 NS_IMETHODIMP nsJPEGEncoder::Close()
00200 {
00201   if (mImageBuffer != nsnull) {
00202     PR_Free(mImageBuffer);
00203     mImageBuffer = nsnull;
00204     mImageBufferSize = 0;
00205     mImageBufferUsed = 0;
00206     mImageBufferReadPoint = 0;
00207   }
00208   return NS_OK;
00209 }
00210 
00211 /* unsigned long available (); */
00212 NS_IMETHODIMP nsJPEGEncoder::Available(PRUint32 *_retval)
00213 {
00214   if (!mImageBuffer)
00215     return NS_BASE_STREAM_CLOSED;
00216 
00217   *_retval = mImageBufferUsed - mImageBufferReadPoint;
00218   return NS_OK;
00219 }
00220 
00221 /* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
00222 NS_IMETHODIMP nsJPEGEncoder::Read(char * aBuf, PRUint32 aCount,
00223                                   PRUint32 *_retval)
00224 {
00225   return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
00226 }
00227 
00228 /* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */
00229 NS_IMETHODIMP nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, PRUint32 aCount, PRUint32 *_retval)
00230 {
00231   PRUint32 maxCount = mImageBufferUsed - mImageBufferReadPoint;
00232   if (maxCount == 0) {
00233     *_retval = 0;
00234     return NS_OK;
00235   }
00236 
00237   if (aCount > maxCount)
00238     aCount = maxCount;
00239   nsresult rv = aWriter(this, aClosure,
00240                         NS_REINTERPRET_CAST(const char*, mImageBuffer),
00241                         0, aCount, _retval);
00242   if (NS_SUCCEEDED(rv)) {
00243     NS_ASSERTION(*_retval <= aCount, "bad write count");
00244     mImageBufferReadPoint += *_retval;
00245   }
00246 
00247   // errors returned from the writer end here!
00248   return NS_OK;
00249 }
00250 
00251 /* boolean isNonBlocking (); */
00252 NS_IMETHODIMP nsJPEGEncoder::IsNonBlocking(PRBool *_retval)
00253 {
00254   *_retval = PR_FALSE;  // We don't implement nsIAsyncInputStream
00255   return NS_OK;
00256 }
00257 
00258 
00259 // nsJPEGEncoder::ConvertHostARGBRow
00260 //
00261 //    Our colors are stored with premultiplied alphas, but we need
00262 //    an output with no alpha in machine-independent byte order.
00263 //
00264 //    See gfx/cairo/cairo/src/cairo-png.c
00265 
00266 void
00267 nsJPEGEncoder::ConvertHostARGBRow(const PRUint8* aSrc, PRUint8* aDest,
00268                                  PRUint32 aPixelWidth)
00269 {
00270   for (PRUint32 x = 0; x < aPixelWidth; x ++) {
00271     const PRUint32& pixelIn = ((const PRUint32*)(aSrc))[x];
00272     PRUint8 *pixelOut = &aDest[x * 3];
00273 
00274     PRUint8 alpha = (pixelIn & 0xff000000) >> 24;
00275     if (alpha == 0) {
00276       pixelOut[0] = pixelOut[1] = pixelOut[2] = 0;
00277     } else {
00278       pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
00279       pixelOut[1] = (((pixelIn & 0x00ff00) >>  8) * 255 + alpha / 2) / alpha;
00280       pixelOut[2] = (((pixelIn & 0x0000ff) >>  0) * 255 + alpha / 2) / alpha;
00281     }
00282   }
00283 }
00284 
00285 
00286 // nsJPEGEncoder::StripAlpha
00287 //
00288 //    Input is RGBA, output is RGB
00289 
00290 void
00291 nsJPEGEncoder::StripAlpha(const PRUint8* aSrc, PRUint8* aDest,
00292                           PRUint32 aPixelWidth)
00293 {
00294   for (PRUint32 x = 0; x < aPixelWidth; x ++) {
00295     const PRUint8* pixelIn = &aSrc[x * 4];
00296     PRUint8* pixelOut = &aDest[x * 3];
00297     pixelOut[0] = pixelIn[0];
00298     pixelOut[1] = pixelIn[1];
00299     pixelOut[2] = pixelIn[2];
00300   }
00301 }
00302 
00303 
00304 // nsJPEGEncoder::initDestination
00305 //
00306 //    Initialize destination. This is called by jpeg_start_compress() before
00307 //    any data is actually written. It must initialize next_output_byte and
00308 //    free_in_buffer. free_in_buffer must be initialized to a positive value.
00309 
00310 void // static
00311 nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo)
00312 {
00313   nsJPEGEncoder* that = NS_STATIC_CAST(nsJPEGEncoder*, cinfo->client_data);
00314   NS_ASSERTION(! that->mImageBuffer, "Image buffer already initialized");
00315 
00316   that->mImageBufferSize = 8192;
00317   that->mImageBuffer = (PRUint8*)PR_Malloc(that->mImageBufferSize);
00318   that->mImageBufferUsed = 0;
00319 
00320   cinfo->dest->next_output_byte = that->mImageBuffer;
00321   cinfo->dest->free_in_buffer = that->mImageBufferSize;
00322 }
00323 
00324 
00325 // nsJPEGEncoder::emptyOutputBuffer
00326 //
00327 //    This is called whenever the buffer has filled (free_in_buffer reaches
00328 //    zero).  In typical applications, it should write out the *entire* buffer
00329 //    (use the saved start address and buffer length; ignore the current state
00330 //    of next_output_byte and free_in_buffer).  Then reset the pointer & count
00331 //    to the start of the buffer, and return TRUE indicating that the buffer
00332 //    has been dumped.  free_in_buffer must be set to a positive value when
00333 //    TRUE is returned.  A FALSE return should only be used when I/O suspension
00334 //    is desired (this operating mode is discussed in the next section).
00335 
00336 boolean // static
00337 nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo)
00338 {
00339   nsJPEGEncoder* that = NS_STATIC_CAST(nsJPEGEncoder*, cinfo->client_data);
00340   NS_ASSERTION(that->mImageBuffer, "No buffer to empty!");
00341 
00342   that->mImageBufferUsed = that->mImageBufferSize;
00343 
00344   // expand buffer, just double size each time
00345   that->mImageBufferSize *= 2;
00346 
00347   PRUint8* newBuf = (PRUint8*)PR_Realloc(that->mImageBuffer,
00348                                          that->mImageBufferSize);
00349   if (! newBuf) {
00350     // can't resize, just zero (this will keep us from writing more)
00351     PR_Free(that->mImageBuffer);
00352     that->mImageBuffer = nsnull;
00353     that->mImageBufferSize = 0;
00354     that->mImageBufferUsed = 0;
00355 
00356     // this seems to be the only way to do errors through the JPEG library
00357     longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer,
00358             NS_ERROR_OUT_OF_MEMORY);
00359   }
00360   that->mImageBuffer = newBuf;
00361 
00362   cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed];
00363   cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed;
00364   return 1;
00365 }
00366 
00367 
00368 // nsJPEGEncoder::termDestination
00369 //
00370 //    Terminate destination --- called by jpeg_finish_compress() after all data
00371 //    has been written.  In most applications, this must flush any data
00372 //    remaining in the buffer.  Use either next_output_byte or free_in_buffer
00373 //    to determine how much data is in the buffer.
00374 
00375 void // static
00376 nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo)
00377 {
00378   nsJPEGEncoder* that = NS_STATIC_CAST(nsJPEGEncoder*, cinfo->client_data);
00379   if (! that->mImageBuffer)
00380     return;
00381   that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer;
00382   NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize,
00383                "JPEG library busted, got a bad image buffer size");
00384 }
00385 
00386 
00387 // nsJPEGEncoder::errorExit
00388 //
00389 //    Override the standard error method in the IJG JPEG decoder code. This
00390 //    was mostly copied from nsJPEGDecoder.cpp
00391 
00392 void // static
00393 nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo)
00394 {
00395   nsresult error_code;
00396   encoder_error_mgr *err = (encoder_error_mgr *) cinfo->err;
00397 
00398   // Convert error to a browser error code
00399   switch (cinfo->err->msg_code) {
00400     case JERR_OUT_OF_MEMORY:
00401       error_code = NS_ERROR_OUT_OF_MEMORY;
00402     default:
00403       error_code = NS_ERROR_FAILURE;
00404   }
00405 
00406   // Return control to the setjmp point.
00407   longjmp(err->setjmp_buffer, error_code);
00408 }