tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsJPEGEncoder.cpp (17022B)


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