PageRenderTime 55ms CodeModel.GetById 15ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 1ms

/image/encoders/jpeg/nsJPEGEncoder.cpp

http://github.com/zpao/v8monkey
C++ | 538 lines | 337 code | 78 blank | 123 comment | 56 complexity | a27009f1e92606bf2430a7ce3c89cac0 MD5 | raw file
  1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2 * ***** BEGIN LICENSE BLOCK *****
  3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4 *
  5 * The contents of this file are subject to the Mozilla Public License Version
  6 * 1.1 (the "License"); you may not use this file except in compliance with
  7 * the License. You may obtain a copy of the License at
  8 * http://www.mozilla.org/MPL/
  9 *
 10 * Software distributed under the License is distributed on an "AS IS" basis,
 11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 12 * for the specific language governing rights and limitations under the
 13 * License.
 14 *
 15 * The Original Code is JPEG Encoding code
 16 *
 17 * The Initial Developer of the Original Code is
 18 * Google Inc.
 19 * Portions created by the Initial Developer are Copyright (C) 2005
 20 * the Initial Developer. All Rights Reserved.
 21 *
 22 * Contributor(s):
 23 *   Brett Wilson <brettw@gmail.com>
 24 *
 25 * Alternatively, the contents of this file may be used under the terms of
 26 * either the GNU General Public License Version 2 or later (the "GPL"), or
 27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 28 * in which case the provisions of the GPL or the LGPL are applicable instead
 29 * of those above. If you wish to allow use of your version of this file only
 30 * under the terms of either the GPL or the LGPL, and not to allow others to
 31 * use your version of this file under the terms of the MPL, indicate your
 32 * decision by deleting the provisions above and replace them with the notice
 33 * and other provisions required by the GPL or the LGPL. If you do not delete
 34 * the provisions above, a recipient may use your version of this file under
 35 * the terms of any one of the MPL, the GPL or the LGPL.
 36 *
 37 * ***** END LICENSE BLOCK ***** */
 38
 39#include "nsJPEGEncoder.h"
 40#include "prmem.h"
 41#include "prprf.h"
 42#include "nsString.h"
 43#include "nsStreamUtils.h"
 44#include "gfxColor.h"
 45
 46#include <setjmp.h>
 47#include "jerror.h"
 48
 49using namespace mozilla;
 50
 51NS_IMPL_THREADSAFE_ISUPPORTS3(nsJPEGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream)
 52
 53// used to pass error info through the JPEG library
 54struct encoder_error_mgr {
 55  jpeg_error_mgr pub;
 56  jmp_buf setjmp_buffer;
 57};
 58
 59nsJPEGEncoder::nsJPEGEncoder() : mFinished(false),
 60                                 mImageBuffer(nsnull), mImageBufferSize(0),
 61                                 mImageBufferUsed(0), mImageBufferReadPoint(0),
 62                                 mCallback(nsnull),
 63                                 mCallbackTarget(nsnull), mNotifyThreshold(0),
 64                                 mReentrantMonitor("nsJPEGEncoder.mReentrantMonitor")
 65{
 66}
 67
 68nsJPEGEncoder::~nsJPEGEncoder()
 69{
 70  if (mImageBuffer) {
 71    PR_Free(mImageBuffer);
 72    mImageBuffer = nsnull;
 73  }
 74}
 75
 76
 77// nsJPEGEncoder::InitFromData
 78//
 79//    One output option is supported: "quality=X" where X is an integer in the
 80//    range 0-100. Higher values for X give better quality.
 81//
 82//    Transparency is always discarded.
 83
 84NS_IMETHODIMP nsJPEGEncoder::InitFromData(const PRUint8* aData,
 85                                          PRUint32 aLength, // (unused, req'd by JS)
 86                                          PRUint32 aWidth,
 87                                          PRUint32 aHeight,
 88                                          PRUint32 aStride,
 89                                          PRUint32 aInputFormat,
 90                                          const nsAString& aOutputOptions)
 91{
 92  NS_ENSURE_ARG(aData);
 93
 94  // validate input format
 95  if (aInputFormat != INPUT_FORMAT_RGB &&
 96      aInputFormat != INPUT_FORMAT_RGBA &&
 97      aInputFormat != INPUT_FORMAT_HOSTARGB)
 98    return NS_ERROR_INVALID_ARG;
 99
100  // Stride is the padded width of each row, so it better be longer (I'm afraid
101  // people will not understand what stride means, so check it well)
102  if ((aInputFormat == INPUT_FORMAT_RGB &&
103       aStride < aWidth * 3) ||
104      ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) &&
105       aStride < aWidth * 4)) {
106    NS_WARNING("Invalid stride for InitFromData");
107    return NS_ERROR_INVALID_ARG;
108  }
109
110  // can't initialize more than once
111  if (mImageBuffer != nsnull)
112    return NS_ERROR_ALREADY_INITIALIZED;
113
114  // options: we only have one option so this is easy
115  int quality = 92;
116  if (aOutputOptions.Length() > 0) {
117    // have options string
118    const nsString qualityPrefix(NS_LITERAL_STRING("quality="));
119    if (aOutputOptions.Length() > qualityPrefix.Length()  &&
120        StringBeginsWith(aOutputOptions, qualityPrefix)) {
121      // have quality string
122      nsCString value = NS_ConvertUTF16toUTF8(Substring(aOutputOptions,
123                                                        qualityPrefix.Length()));
124      int newquality = -1;
125      if (PR_sscanf(value.get(), "%d", &newquality) == 1) {
126        if (newquality >= 0 && newquality <= 100) {
127          quality = newquality;
128        } else {
129          NS_WARNING("Quality value out of range, should be 0-100, using default");
130        }
131      } else {
132        NS_WARNING("Quality value invalid, should be integer 0-100, using default");
133      }
134    }
135    else {
136      return NS_ERROR_INVALID_ARG;
137    }
138  }
139
140  jpeg_compress_struct cinfo;
141
142  // We set up the normal JPEG error routines, then override error_exit.
143  // This must be done before the call to create_compress
144  encoder_error_mgr errmgr;
145  cinfo.err = jpeg_std_error(&errmgr.pub);
146  errmgr.pub.error_exit = errorExit;
147  // Establish the setjmp return context for my_error_exit to use.
148  if (setjmp(errmgr.setjmp_buffer)) {
149    // If we get here, the JPEG code has signaled an error.
150    // We need to clean up the JPEG object, close the input file, and return.
151    return NS_ERROR_FAILURE;
152  }
153
154  jpeg_create_compress(&cinfo);
155  cinfo.image_width = aWidth;
156  cinfo.image_height = aHeight;
157  cinfo.input_components = 3;
158  cinfo.in_color_space = JCS_RGB;
159  cinfo.data_precision = 8;
160
161  jpeg_set_defaults(&cinfo);
162  jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100
163  if (quality >= 90) {
164    int i;
165    for (i=0; i < MAX_COMPONENTS; i++) {
166      cinfo.comp_info[i].h_samp_factor=1;
167      cinfo.comp_info[i].v_samp_factor=1;
168    }
169  }
170
171  // set up the destination manager
172  jpeg_destination_mgr destmgr;
173  destmgr.init_destination = initDestination;
174  destmgr.empty_output_buffer = emptyOutputBuffer;
175  destmgr.term_destination = termDestination;
176  cinfo.dest = &destmgr;
177  cinfo.client_data = this;
178
179  jpeg_start_compress(&cinfo, 1);
180
181  // feed it the rows
182  if (aInputFormat == INPUT_FORMAT_RGB) {
183    while (cinfo.next_scanline < cinfo.image_height) {
184      const PRUint8* row = &aData[cinfo.next_scanline * aStride];
185      jpeg_write_scanlines(&cinfo, const_cast<PRUint8**>(&row), 1);
186    }
187  } else if (aInputFormat == INPUT_FORMAT_RGBA) {
188    PRUint8* row = new PRUint8[aWidth * 3];
189    while (cinfo.next_scanline < cinfo.image_height) {
190      ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth);
191      jpeg_write_scanlines(&cinfo, &row, 1);
192    }
193    delete[] row;
194  } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
195    PRUint8* row = new PRUint8[aWidth * 3];
196    while (cinfo.next_scanline < cinfo.image_height) {
197      ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth);
198      jpeg_write_scanlines(&cinfo, &row, 1);
199    }
200    delete[] row;
201  }
202
203  jpeg_finish_compress(&cinfo);
204  jpeg_destroy_compress(&cinfo);
205
206  mFinished = true;
207  NotifyListener();
208
209  // if output callback can't get enough memory, it will free our buffer
210  if (!mImageBuffer)
211    return NS_ERROR_OUT_OF_MEMORY;
212
213  return NS_OK;
214}
215
216
217NS_IMETHODIMP nsJPEGEncoder::StartImageEncode(PRUint32 aWidth,
218                                              PRUint32 aHeight,
219                                              PRUint32 aInputFormat,
220                                              const nsAString& aOutputOptions)
221{
222  return NS_ERROR_NOT_IMPLEMENTED;
223}
224
225// Returns the number of bytes in the image buffer used.
226NS_IMETHODIMP nsJPEGEncoder::GetImageBufferUsed(PRUint32 *aOutputSize)
227{
228  NS_ENSURE_ARG_POINTER(aOutputSize);
229  *aOutputSize = mImageBufferUsed;
230  return NS_OK;
231}
232
233// Returns a pointer to the start of the image buffer
234NS_IMETHODIMP nsJPEGEncoder::GetImageBuffer(char **aOutputBuffer)
235{
236  NS_ENSURE_ARG_POINTER(aOutputBuffer);
237  *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
238  return NS_OK;
239}
240
241NS_IMETHODIMP nsJPEGEncoder::AddImageFrame(const PRUint8* aData,
242                                           PRUint32 aLength,
243                                           PRUint32 aWidth,
244                                           PRUint32 aHeight,
245                                           PRUint32 aStride,
246                                           PRUint32 aFrameFormat,
247                                           const nsAString& aFrameOptions)
248{
249  return NS_ERROR_NOT_IMPLEMENTED;
250}
251
252NS_IMETHODIMP nsJPEGEncoder::EndImageEncode()
253{
254  return NS_ERROR_NOT_IMPLEMENTED;
255}
256
257
258/* void close (); */
259NS_IMETHODIMP nsJPEGEncoder::Close()
260{
261  if (mImageBuffer != nsnull) {
262    PR_Free(mImageBuffer);
263    mImageBuffer = nsnull;
264    mImageBufferSize = 0;
265    mImageBufferUsed = 0;
266    mImageBufferReadPoint = 0;
267  }
268  return NS_OK;
269}
270
271/* unsigned long available (); */
272NS_IMETHODIMP nsJPEGEncoder::Available(PRUint32 *_retval)
273{
274  if (!mImageBuffer)
275    return NS_BASE_STREAM_CLOSED;
276
277  *_retval = mImageBufferUsed - mImageBufferReadPoint;
278  return NS_OK;
279}
280
281/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
282NS_IMETHODIMP nsJPEGEncoder::Read(char * aBuf, PRUint32 aCount,
283                                  PRUint32 *_retval)
284{
285  return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
286}
287
288/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */
289NS_IMETHODIMP nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, PRUint32 aCount, PRUint32 *_retval)
290{
291  // Avoid another thread reallocing the buffer underneath us
292  ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
293
294  PRUint32 maxCount = mImageBufferUsed - mImageBufferReadPoint;
295  if (maxCount == 0) {
296    *_retval = 0;
297    return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
298  }
299
300  if (aCount > maxCount)
301    aCount = maxCount;
302  nsresult rv = aWriter(this, aClosure,
303                        reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint),
304                        0, aCount, _retval);
305  if (NS_SUCCEEDED(rv)) {
306    NS_ASSERTION(*_retval <= aCount, "bad write count");
307    mImageBufferReadPoint += *_retval;
308  }
309
310  // errors returned from the writer end here!
311  return NS_OK;
312}
313
314/* boolean isNonBlocking (); */
315NS_IMETHODIMP nsJPEGEncoder::IsNonBlocking(bool *_retval)
316{
317  *_retval = true;
318  return NS_OK;
319}
320
321NS_IMETHODIMP nsJPEGEncoder::AsyncWait(nsIInputStreamCallback *aCallback,
322                                       PRUint32 aFlags,
323                                       PRUint32 aRequestedCount,
324                                       nsIEventTarget *aTarget)
325{
326  if (aFlags != 0)
327    return NS_ERROR_NOT_IMPLEMENTED;
328
329  if (mCallback || mCallbackTarget)
330    return NS_ERROR_UNEXPECTED;
331
332  mCallbackTarget = aTarget;
333  // 0 means "any number of bytes except 0"
334  mNotifyThreshold = aRequestedCount;
335  if (!aRequestedCount)
336    mNotifyThreshold = 1024; // 1 KB seems good.  We don't want to notify incessantly
337
338  // We set the callback absolutely last, because NotifyListener uses it to
339  // determine if someone needs to be notified.  If we don't set it last,
340  // NotifyListener might try to fire off a notification to a null target
341  // which will generally cause non-threadsafe objects to be used off the main thread
342  mCallback = aCallback;
343
344  // What we are being asked for may be present already
345  NotifyListener();
346  return NS_OK;
347}
348
349NS_IMETHODIMP nsJPEGEncoder::CloseWithStatus(nsresult aStatus)
350{
351  return Close();
352}
353
354
355
356// nsJPEGEncoder::ConvertHostARGBRow
357//
358//    Our colors are stored with premultiplied alphas, but we need
359//    an output with no alpha in machine-independent byte order.
360//
361//    See gfx/cairo/cairo/src/cairo-png.c
362void
363nsJPEGEncoder::ConvertHostARGBRow(const PRUint8* aSrc, PRUint8* aDest,
364                                  PRUint32 aPixelWidth)
365{
366  for (PRUint32 x = 0; x < aPixelWidth; x++) {
367    const PRUint32& pixelIn = ((const PRUint32*)(aSrc))[x];
368    PRUint8 *pixelOut = &aDest[x * 3];
369
370    pixelOut[0] = (pixelIn & 0xff0000) >> 16;
371    pixelOut[1] = (pixelIn & 0x00ff00) >>  8;
372    pixelOut[2] = (pixelIn & 0x0000ff) >>  0;
373  }
374}
375
376/**
377 * nsJPEGEncoder::ConvertRGBARow
378 *
379 * Input is RGBA, output is RGB, so we should alpha-premultiply.
380 */
381void
382nsJPEGEncoder::ConvertRGBARow(const PRUint8* aSrc, PRUint8* aDest,
383                              PRUint32 aPixelWidth)
384{
385  for (PRUint32 x = 0; x < aPixelWidth; x++) {
386    const PRUint8* pixelIn = &aSrc[x * 4];
387    PRUint8* pixelOut = &aDest[x * 3];
388
389    PRUint8 alpha = pixelIn[3];
390    pixelOut[0] = GFX_PREMULTIPLY(pixelIn[0], alpha);
391    pixelOut[1] = GFX_PREMULTIPLY(pixelIn[1], alpha);
392    pixelOut[2] = GFX_PREMULTIPLY(pixelIn[2], alpha);
393  }
394}
395
396// nsJPEGEncoder::initDestination
397//
398//    Initialize destination. This is called by jpeg_start_compress() before
399//    any data is actually written. It must initialize next_output_byte and
400//    free_in_buffer. free_in_buffer must be initialized to a positive value.
401
402void // static
403nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo)
404{
405  nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
406  NS_ASSERTION(! that->mImageBuffer, "Image buffer already initialized");
407
408  that->mImageBufferSize = 8192;
409  that->mImageBuffer = (PRUint8*)PR_Malloc(that->mImageBufferSize);
410  that->mImageBufferUsed = 0;
411
412  cinfo->dest->next_output_byte = that->mImageBuffer;
413  cinfo->dest->free_in_buffer = that->mImageBufferSize;
414}
415
416
417// nsJPEGEncoder::emptyOutputBuffer
418//
419//    This is called whenever the buffer has filled (free_in_buffer reaches
420//    zero).  In typical applications, it should write out the *entire* buffer
421//    (use the saved start address and buffer length; ignore the current state
422//    of next_output_byte and free_in_buffer).  Then reset the pointer & count
423//    to the start of the buffer, and return TRUE indicating that the buffer
424//    has been dumped.  free_in_buffer must be set to a positive value when
425//    TRUE is returned.  A FALSE return should only be used when I/O suspension
426//    is desired (this operating mode is discussed in the next section).
427
428boolean // static
429nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo)
430{
431  nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
432  NS_ASSERTION(that->mImageBuffer, "No buffer to empty!");
433
434  // When we're reallocing the buffer we need to take the lock to ensure
435  // that nobody is trying to read from the buffer we are destroying
436  ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
437
438  that->mImageBufferUsed = that->mImageBufferSize;
439
440  // expand buffer, just double size each time
441  that->mImageBufferSize *= 2;
442
443  PRUint8* newBuf = (PRUint8*)PR_Realloc(that->mImageBuffer,
444                                         that->mImageBufferSize);
445  if (! newBuf) {
446    // can't resize, just zero (this will keep us from writing more)
447    PR_Free(that->mImageBuffer);
448    that->mImageBuffer = nsnull;
449    that->mImageBufferSize = 0;
450    that->mImageBufferUsed = 0;
451
452    // this seems to be the only way to do errors through the JPEG library
453    longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer,
454            NS_ERROR_OUT_OF_MEMORY);
455  }
456  that->mImageBuffer = newBuf;
457
458  cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed];
459  cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed;
460  return 1;
461}
462
463
464// nsJPEGEncoder::termDestination
465//
466//    Terminate destination --- called by jpeg_finish_compress() after all data
467//    has been written.  In most applications, this must flush any data
468//    remaining in the buffer.  Use either next_output_byte or free_in_buffer
469//    to determine how much data is in the buffer.
470
471void // static
472nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo)
473{
474  nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
475  if (! that->mImageBuffer)
476    return;
477  that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer;
478  NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize,
479               "JPEG library busted, got a bad image buffer size");
480  that->NotifyListener();
481}
482
483
484// nsJPEGEncoder::errorExit
485//
486//    Override the standard error method in the IJG JPEG decoder code. This
487//    was mostly copied from nsJPEGDecoder.cpp
488
489void // static
490nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo)
491{
492  nsresult error_code;
493  encoder_error_mgr *err = (encoder_error_mgr *) cinfo->err;
494
495  // Convert error to a browser error code
496  switch (cinfo->err->msg_code) {
497    case JERR_OUT_OF_MEMORY:
498      error_code = NS_ERROR_OUT_OF_MEMORY;
499      break;
500    default:
501      error_code = NS_ERROR_FAILURE;
502  }
503
504  // Return control to the setjmp point.
505  longjmp(err->setjmp_buffer, error_code);
506}
507
508void
509nsJPEGEncoder::NotifyListener()
510{
511  // We might call this function on multiple threads (any threads that call
512  // AsyncWait and any that do encoding) so we lock to avoid notifying the
513  // listener twice about the same data (which generally leads to a truncated
514  // image).
515  ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
516
517  if (mCallback &&
518      (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
519       mFinished)) {
520    nsCOMPtr<nsIInputStreamCallback> callback;
521    if (mCallbackTarget) {
522      NS_NewInputStreamReadyEvent(getter_AddRefs(callback),
523                                  mCallback,
524                                  mCallbackTarget);
525    } else {
526      callback = mCallback;
527    }
528
529    NS_ASSERTION(callback, "Shouldn't fail to make the callback");
530    // Null the callback first because OnInputStreamReady could reenter
531    // AsyncWait
532    mCallback = nsnull;
533    mCallbackTarget = nsnull;
534    mNotifyThreshold = 0;
535
536    callback->OnInputStreamReady(this);
537  }
538}