tor-browser

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

nsWebPEncoder.cpp (11057B)


      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 "ImageLogging.h"
      7 #include "nsCRT.h"
      8 #include "nsWebPEncoder.h"
      9 #include "nsStreamUtils.h"
     10 #include "nsString.h"
     11 #include "prprf.h"
     12 #include "mozilla/CheckedInt.h"
     13 #include "mozilla/UniquePtrExtensions.h"
     14 
     15 using namespace mozilla;
     16 
     17 // static LazyLogModule sWEBPEncoderLog("WEBPEncoder");
     18 
     19 NS_IMPL_ISUPPORTS(nsWebPEncoder, imgIEncoder, nsIInputStream,
     20                  nsIAsyncInputStream)
     21 
     22 nsWebPEncoder::nsWebPEncoder()
     23    : mFinished(false),
     24      mImageBuffer(nullptr),
     25      mImageBufferSize(0),
     26      mImageBufferUsed(0),
     27      mImageBufferReadPoint(0),
     28      mCallback(nullptr),
     29      mCallbackTarget(nullptr),
     30      mNotifyThreshold(0),
     31      mReentrantMonitor("nsWebPEncoder.mReentrantMonitor") {}
     32 
     33 nsWebPEncoder::~nsWebPEncoder() {
     34  if (mImageBuffer) {
     35    WebPFree(mImageBuffer);
     36    mImageBuffer = nullptr;
     37    mImageBufferSize = 0;
     38    mImageBufferUsed = 0;
     39    mImageBufferReadPoint = 0;
     40  }
     41 }
     42 
     43 // nsWebPEncoder::InitFromData
     44 //
     45 //    One output option is supported: "quality=X" where X is an integer in the
     46 //    range 0-100. Higher values for X give better quality.
     47 
     48 NS_IMETHODIMP
     49 nsWebPEncoder::InitFromData(const uint8_t* aData,
     50                            uint32_t aLength,  // (unused, req'd by JS)
     51                            uint32_t aWidth, uint32_t aHeight, uint32_t aStride,
     52                            uint32_t aInputFormat,
     53                            const nsAString& aOutputOptions,
     54                            const nsACString& aRandomizationKey) {
     55  NS_ENSURE_ARG(aData);
     56 
     57  // validate input format
     58  if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA &&
     59      aInputFormat != INPUT_FORMAT_HOSTARGB)
     60    return NS_ERROR_INVALID_ARG;
     61 
     62  // Stride is the padded width of each row, so it better be longer (I'm afraid
     63  // people will not understand what stride means, so check it well)
     64  if ((aInputFormat == INPUT_FORMAT_RGB && aStride < aWidth * 3) ||
     65      ((aInputFormat == INPUT_FORMAT_RGBA ||
     66        aInputFormat == INPUT_FORMAT_HOSTARGB) &&
     67       aStride < aWidth * 4)) {
     68    NS_WARNING("Invalid stride for InitFromData");
     69    return NS_ERROR_INVALID_ARG;
     70  }
     71 
     72  // can't initialize more than once
     73  if (mImageBuffer != nullptr) {
     74    return NS_ERROR_ALREADY_INITIALIZED;
     75  }
     76 
     77  // options: we only have one option so this is easy
     78  int quality = 92;
     79  if (aOutputOptions.Length() > 0) {
     80    // have options string
     81    const nsString qualityPrefix(u"quality="_ns);
     82    if (aOutputOptions.Length() > qualityPrefix.Length() &&
     83        StringBeginsWith(aOutputOptions, qualityPrefix)) {
     84      // have quality string
     85      nsCString value = NS_ConvertUTF16toUTF8(
     86          Substring(aOutputOptions, qualityPrefix.Length()));
     87      int newquality = -1;
     88      if (PR_sscanf(value.get(), "%d", &newquality) == 1) {
     89        if (newquality >= 0 && newquality <= 100) {
     90          quality = newquality;
     91        } else {
     92          NS_WARNING(
     93              "Quality value out of range, should be 0-100,"
     94              " using default");
     95        }
     96      } else {
     97        NS_WARNING(
     98            "Quality value invalid, should be integer 0-100,"
     99            " using default");
    100      }
    101    } else {
    102      return NS_ERROR_INVALID_ARG;
    103    }
    104  }
    105 
    106  size_t size = 0;
    107 
    108  CheckedInt32 width = CheckedInt32(aWidth);
    109  CheckedInt32 height = CheckedInt32(aHeight);
    110  CheckedInt32 stride = CheckedInt32(aStride);
    111  if (!width.isValid() || !height.isValid() || !stride.isValid() ||
    112      !(CheckedUint32(aStride) * CheckedUint32(aHeight)).isValid()) {
    113    return NS_ERROR_INVALID_ARG;
    114  }
    115 
    116  if (aInputFormat == INPUT_FORMAT_RGB) {
    117    size = quality == 100
    118               ? WebPEncodeLosslessRGB(aData, width.value(), height.value(),
    119                                       stride.value(), &mImageBuffer)
    120               : WebPEncodeRGB(aData, width.value(), height.value(),
    121                               stride.value(), quality, &mImageBuffer);
    122  } else if (aInputFormat == INPUT_FORMAT_RGBA) {
    123    size = quality == 100
    124               ? WebPEncodeLosslessRGBA(aData, width.value(), height.value(),
    125                                        stride.value(), &mImageBuffer)
    126               : WebPEncodeRGBA(aData, width.value(), height.value(),
    127                                stride.value(), quality, &mImageBuffer);
    128  } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
    129    UniquePtr<uint8_t[]> aDest =
    130        MakeUniqueFallible<uint8_t[]>(aStride * aHeight);
    131    if (NS_WARN_IF(!aDest)) {
    132      return NS_ERROR_OUT_OF_MEMORY;
    133    }
    134 
    135    for (uint32_t y = 0; y < aHeight; y++) {
    136      for (uint32_t x = 0; x < aWidth; x++) {
    137        const uint32_t& pixelIn =
    138            ((const uint32_t*)(aData))[y * aStride / 4 + x];
    139        uint8_t* pixelOut = &aDest[y * aStride + x * 4];
    140 
    141        uint8_t alpha = (pixelIn & 0xff000000) >> 24;
    142        pixelOut[3] = alpha;
    143        if (alpha == 255) {
    144          pixelOut[0] = (pixelIn & 0xff0000) >> 16;
    145          pixelOut[1] = (pixelIn & 0x00ff00) >> 8;
    146          pixelOut[2] = (pixelIn & 0x0000ff);
    147        } else if (alpha == 0) {
    148          pixelOut[0] = pixelOut[1] = pixelOut[2] = 0;
    149        } else {
    150          pixelOut[0] =
    151              (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
    152          pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
    153          pixelOut[2] = (((pixelIn & 0x0000ff)) * 255 + alpha / 2) / alpha;
    154        }
    155      }
    156    }
    157 
    158    size =
    159        quality == 100
    160            ? WebPEncodeLosslessRGBA(aDest.get(), width.value(), height.value(),
    161                                     stride.value(), &mImageBuffer)
    162            : WebPEncodeRGBA(aDest.get(), width.value(), height.value(),
    163                             stride.value(), quality, &mImageBuffer);
    164  }
    165 
    166  mFinished = true;
    167 
    168  if (size == 0) {
    169    return NS_ERROR_FAILURE;
    170  }
    171 
    172  mImageBufferUsed = size;
    173 
    174  return NS_OK;
    175 }
    176 
    177 // nsWebPEncoder::StartImageEncode
    178 //
    179 //
    180 // See ::InitFromData for other info.
    181 NS_IMETHODIMP
    182 nsWebPEncoder::StartImageEncode(uint32_t aWidth, uint32_t aHeight,
    183                                uint32_t aInputFormat,
    184                                const nsAString& aOutputOptions) {
    185  return NS_ERROR_NOT_IMPLEMENTED;
    186 }
    187 
    188 // Returns the number of bytes in the image buffer used.
    189 NS_IMETHODIMP
    190 nsWebPEncoder::GetImageBufferUsed(uint32_t* aOutputSize) {
    191  NS_ENSURE_ARG_POINTER(aOutputSize);
    192  *aOutputSize = mImageBufferUsed;
    193  return NS_OK;
    194 }
    195 
    196 // Returns a pointer to the start of the image buffer
    197 NS_IMETHODIMP
    198 nsWebPEncoder::GetImageBuffer(char** aOutputBuffer) {
    199  NS_ENSURE_ARG_POINTER(aOutputBuffer);
    200  *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
    201  return NS_OK;
    202 }
    203 
    204 NS_IMETHODIMP
    205 nsWebPEncoder::AddImageFrame(const uint8_t* aData,
    206                             uint32_t aLength,  // (unused, req'd by JS)
    207                             uint32_t aWidth, uint32_t aHeight,
    208                             uint32_t aStride, uint32_t aInputFormat,
    209                             const nsAString& aFrameOptions) {
    210  return NS_ERROR_NOT_IMPLEMENTED;
    211 }
    212 
    213 NS_IMETHODIMP
    214 nsWebPEncoder::EndImageEncode() { return NS_ERROR_NOT_IMPLEMENTED; }
    215 
    216 NS_IMETHODIMP
    217 nsWebPEncoder::Close() {
    218  if (mImageBuffer) {
    219    WebPFree(mImageBuffer);
    220    mImageBuffer = nullptr;
    221    mImageBufferSize = 0;
    222    mImageBufferUsed = 0;
    223    mImageBufferReadPoint = 0;
    224  }
    225  return NS_OK;
    226 }
    227 
    228 NS_IMETHODIMP
    229 nsWebPEncoder::Available(uint64_t* _retval) {
    230  if (!mImageBuffer) {
    231    return NS_BASE_STREAM_CLOSED;
    232  }
    233 
    234  *_retval = mImageBufferUsed - mImageBufferReadPoint;
    235  return NS_OK;
    236 }
    237 
    238 NS_IMETHODIMP
    239 nsWebPEncoder::StreamStatus() {
    240  return mImageBuffer ? NS_OK : NS_BASE_STREAM_CLOSED;
    241 }
    242 
    243 NS_IMETHODIMP
    244 nsWebPEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
    245  return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
    246 }
    247 
    248 NS_IMETHODIMP
    249 nsWebPEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
    250                            uint32_t aCount, uint32_t* _retval) {
    251  // Avoid another thread reallocing the buffer underneath us
    252  ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
    253 
    254  uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint;
    255  if (maxCount == 0) {
    256    *_retval = 0;
    257    return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
    258  }
    259 
    260  if (aCount > maxCount) {
    261    aCount = maxCount;
    262  }
    263  nsresult rv = aWriter(
    264      this, aClosure,
    265      reinterpret_cast<const char*>(mImageBuffer + mImageBufferReadPoint), 0,
    266      aCount, _retval);
    267  if (NS_SUCCEEDED(rv)) {
    268    NS_ASSERTION(*_retval <= aCount, "bad write count");
    269    mImageBufferReadPoint += *_retval;
    270  }
    271 
    272  // errors returned from the writer end here!
    273  return NS_OK;
    274 }
    275 
    276 NS_IMETHODIMP
    277 nsWebPEncoder::IsNonBlocking(bool* _retval) {
    278  *_retval = true;
    279  return NS_OK;
    280 }
    281 
    282 NS_IMETHODIMP
    283 nsWebPEncoder::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
    284                         uint32_t aRequestedCount, nsIEventTarget* aTarget) {
    285  if (aFlags != 0) {
    286    return NS_ERROR_NOT_IMPLEMENTED;
    287  }
    288 
    289  if (mCallback || mCallbackTarget) {
    290    return NS_ERROR_UNEXPECTED;
    291  }
    292 
    293  mCallbackTarget = aTarget;
    294  // 0 means "any number of bytes except 0"
    295  mNotifyThreshold = aRequestedCount;
    296  if (!aRequestedCount) {
    297    mNotifyThreshold = 1024;  // 1 KB seems good.  We don't want to
    298                              // notify incessantly
    299  }
    300 
    301  // We set the callback absolutely last, because NotifyListener uses it to
    302  // determine if someone needs to be notified.  If we don't set it last,
    303  // NotifyListener might try to fire off a notification to a null target
    304  // which will generally cause non-threadsafe objects to be used off the
    305  // main thread
    306  mCallback = aCallback;
    307 
    308  // What we are being asked for may be present already
    309  NotifyListener();
    310  return NS_OK;
    311 }
    312 
    313 NS_IMETHODIMP
    314 nsWebPEncoder::CloseWithStatus(nsresult aStatus) { return Close(); }
    315 
    316 void nsWebPEncoder::NotifyListener() {
    317  // We might call this function on multiple threads (any threads that call
    318  // AsyncWait and any that do encoding) so we lock to avoid notifying the
    319  // listener twice about the same data (which generally leads to a truncated
    320  // image).
    321  ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
    322 
    323  if (mCallback &&
    324      (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
    325       mFinished)) {
    326    nsCOMPtr<nsIInputStreamCallback> callback;
    327    if (mCallbackTarget) {
    328      callback = NS_NewInputStreamReadyEvent("nsWebPEncoder::NotifyListener",
    329                                             mCallback, mCallbackTarget);
    330    } else {
    331      callback = mCallback;
    332    }
    333 
    334    NS_ASSERTION(callback, "Shouldn't fail to make the callback");
    335    // Null the callback first because OnInputStreamReady could reenter
    336    // AsyncWait
    337    mCallback = nullptr;
    338    mCallbackTarget = nullptr;
    339    mNotifyThreshold = 0;
    340 
    341    callback->OnInputStreamReady(this);
    342  }
    343 }