tor-browser

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

ImageEncoder.cpp (17771B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ImageEncoder.h"
      8 
      9 #include "YCbCrUtils.h"
     10 #include "gfxUtils.h"
     11 #include "mozilla/RefPtr.h"
     12 #include "mozilla/SyncRunnable.h"
     13 #include "mozilla/dom/CanvasRenderingContext2D.h"
     14 #include "mozilla/dom/GeneratePlaceholderCanvasData.h"
     15 #include "mozilla/dom/MemoryBlobImpl.h"
     16 #include "mozilla/dom/OffscreenCanvasDisplayHelper.h"
     17 #include "mozilla/dom/WorkerPrivate.h"
     18 #include "mozilla/gfx/2D.h"
     19 #include "mozilla/gfx/DataSurfaceHelpers.h"
     20 #include "mozilla/layers/CanvasRenderer.h"
     21 #include "nsComponentManagerUtils.h"
     22 #include "nsNetUtil.h"
     23 #include "nsThreadUtils.h"
     24 #include "nsXPCOMCIDInternal.h"
     25 
     26 using namespace mozilla::gfx;
     27 
     28 namespace mozilla::dom {
     29 
     30 // This class should be placed inside GetBRGADataSourceSurfaceSync(). However,
     31 // due to B2G ICS uses old complier (C++98/03) which forbids local class as
     32 // template parameter, we need to move this class outside.
     33 class SurfaceHelper : public Runnable {
     34 public:
     35  explicit SurfaceHelper(already_AddRefed<layers::Image> aImage)
     36      : Runnable("SurfaceHelper"), mImage(aImage) {}
     37 
     38  // It retrieves a SourceSurface reference and convert color format on main
     39  // thread and passes DataSourceSurface to caller thread.
     40  NS_IMETHOD Run() override {
     41    RefPtr<gfx::SourceSurface> surface = mImage->GetAsSourceSurface();
     42 
     43    if (surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8) {
     44      mDataSourceSurface = surface->GetDataSurface();
     45    } else {
     46      mDataSourceSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
     47          surface, gfx::SurfaceFormat::B8G8R8A8);
     48    }
     49 
     50    // It guarantees the reference will be released on main thread.
     51    NS_ReleaseOnMainThread("SurfaceHelper::surface", surface.forget());
     52    return NS_OK;
     53  }
     54 
     55  already_AddRefed<gfx::DataSourceSurface> GetDataSurfaceSafe() {
     56    nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
     57    MOZ_ASSERT(mainTarget);
     58    SyncRunnable::DispatchToThread(mainTarget, this, false);
     59 
     60    return mDataSourceSurface.forget();
     61  }
     62 
     63 private:
     64  RefPtr<layers::Image> mImage;
     65  RefPtr<gfx::DataSourceSurface> mDataSourceSurface;
     66 };
     67 
     68 // This function returns a DataSourceSurface in B8G8R8A8 format.
     69 // It uses SourceSurface to do format convert. Because most SourceSurface in
     70 // image formats should be referenced or dereferenced on main thread, it uses a
     71 // sync class SurfaceHelper to retrieve SourceSurface and convert to B8G8R8A8 on
     72 // main thread.
     73 already_AddRefed<DataSourceSurface> GetBRGADataSourceSurfaceSync(
     74    already_AddRefed<layers::Image> aImage) {
     75  RefPtr<SurfaceHelper> helper = new SurfaceHelper(std::move(aImage));
     76  return helper->GetDataSurfaceSafe();
     77 }
     78 
     79 class EncodingCompleteEvent final : public DiscardableRunnable {
     80  virtual ~EncodingCompleteEvent() = default;
     81 
     82 public:
     83  explicit EncodingCompleteEvent(
     84      EncodeCompleteCallback* aEncodeCompleteCallback)
     85      : DiscardableRunnable("EncodingCompleteEvent"),
     86        mImgSize(0),
     87        mImgData(nullptr),
     88        mEncodeCompleteCallback(aEncodeCompleteCallback),
     89        mFailed(false) {
     90    if (!NS_IsMainThread() && IsCurrentThreadRunningWorker()) {
     91      mCreationEventTarget = GetCurrentSerialEventTarget();
     92    } else {
     93      mCreationEventTarget = GetMainThreadSerialEventTarget();
     94    }
     95  }
     96 
     97  // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.  See
     98  // bug 1535398.
     99  MOZ_CAN_RUN_SCRIPT_BOUNDARY
    100  NS_IMETHOD Run() override {
    101    nsresult rv = NS_OK;
    102 
    103    // We want to null out mEncodeCompleteCallback no matter what.
    104    RefPtr<EncodeCompleteCallback> callback(std::move(mEncodeCompleteCallback));
    105    if (!mFailed) {
    106      RefPtr<BlobImpl> blobImpl = new MemoryBlobImpl(mImgData, mImgSize, mType);
    107      rv = callback->ReceiveBlobImpl(blobImpl.forget());
    108    } else {
    109      rv = callback->ReceiveBlobImpl(nullptr);
    110    }
    111 
    112    return rv;
    113  }
    114 
    115  void SetMembers(void* aImgData, uint64_t aImgSize,
    116                  const nsAutoString& aType) {
    117    mImgData = aImgData;
    118    mImgSize = aImgSize;
    119    mType = aType;
    120  }
    121 
    122  void SetFailed() { mFailed = true; }
    123 
    124  nsIEventTarget* GetCreationThreadEventTarget() {
    125    return mCreationEventTarget;
    126  }
    127 
    128  bool CanBeDeletedOnAnyThread() {
    129    return !mEncodeCompleteCallback ||
    130           mEncodeCompleteCallback->CanBeDeletedOnAnyThread();
    131  }
    132 
    133 private:
    134  uint64_t mImgSize;
    135  nsAutoString mType;
    136  void* mImgData;
    137  nsCOMPtr<nsIEventTarget> mCreationEventTarget;
    138  RefPtr<EncodeCompleteCallback> mEncodeCompleteCallback;
    139  bool mFailed;
    140 };
    141 
    142 class EncodingRunnable : public Runnable {
    143  virtual ~EncodingRunnable() = default;
    144 
    145 public:
    146  NS_INLINE_DECL_REFCOUNTING_INHERITED(EncodingRunnable, Runnable)
    147 
    148  EncodingRunnable(const nsAString& aType, const nsAString& aOptions,
    149                   UniquePtr<uint8_t[]> aImageBuffer, layers::Image* aImage,
    150                   imgIEncoder* aEncoder,
    151                   EncodingCompleteEvent* aEncodingCompleteEvent,
    152                   int32_t aFormat, const CSSIntSize aSize,
    153                   CanvasUtils::ImageExtraction aExtractionBehavior,
    154                   const nsCString& aRandomizationKey, bool aUsingCustomOptions)
    155      : Runnable("EncodingRunnable"),
    156        mType(aType),
    157        mOptions(aOptions),
    158        mImageBuffer(std::move(aImageBuffer)),
    159        mImage(aImage),
    160        mEncoder(aEncoder),
    161        mEncodingCompleteEvent(aEncodingCompleteEvent),
    162        mFormat(aFormat),
    163        mSize(aSize),
    164        mExtractionBehavior(aExtractionBehavior),
    165        mRandomizationKey(aRandomizationKey),
    166        mUsingCustomOptions(aUsingCustomOptions) {}
    167 
    168  nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData) {
    169    nsCOMPtr<nsIInputStream> stream;
    170    nsresult rv = ImageEncoder::ExtractDataInternal(
    171        mType, mOptions, mImageBuffer.get(), mFormat, mSize,
    172        mExtractionBehavior, mRandomizationKey, mImage, nullptr, nullptr,
    173        getter_AddRefs(stream), mEncoder);
    174 
    175    // If there are unrecognized custom parse options, we should fall back to
    176    // the default values for the encoder without any options at all.
    177    if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) {
    178      rv = ImageEncoder::ExtractDataInternal(
    179          mType, u""_ns, mImageBuffer.get(), mFormat, mSize,
    180          mExtractionBehavior, mRandomizationKey, mImage, nullptr, nullptr,
    181          getter_AddRefs(stream), mEncoder);
    182    }
    183    NS_ENSURE_SUCCESS(rv, rv);
    184 
    185    rv = NS_ReadInputStreamToBuffer(stream, aImgData, -1, aImgSize);
    186    NS_ENSURE_SUCCESS(rv, rv);
    187 
    188    return rv;
    189  }
    190 
    191  NS_IMETHOD Run() override {
    192    uint64_t imgSize;
    193    void* imgData = nullptr;
    194 
    195    nsresult rv = ProcessImageData(&imgSize, &imgData);
    196    if (NS_FAILED(rv)) {
    197      mEncodingCompleteEvent->SetFailed();
    198    } else {
    199      mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
    200    }
    201    rv = mEncodingCompleteEvent->GetCreationThreadEventTarget()->Dispatch(
    202        mEncodingCompleteEvent, nsIThread::DISPATCH_NORMAL);
    203    if (NS_FAILED(rv)) {
    204      if (!mEncodingCompleteEvent->CanBeDeletedOnAnyThread()) {
    205        // Better to leak than to crash.
    206        mEncodingCompleteEvent.forget().leak();
    207      }
    208      return rv;
    209    }
    210 
    211    return rv;
    212  }
    213 
    214 private:
    215  nsAutoString mType;
    216  nsAutoString mOptions;
    217  UniquePtr<uint8_t[]> mImageBuffer;
    218  RefPtr<layers::Image> mImage;
    219  nsCOMPtr<imgIEncoder> mEncoder;
    220  RefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
    221  int32_t mFormat;
    222  const CSSIntSize mSize;
    223  CanvasUtils::ImageExtraction mExtractionBehavior;
    224  nsCString mRandomizationKey;
    225  bool mUsingCustomOptions;
    226 };
    227 
    228 /* static */
    229 nsresult ImageEncoder::ExtractData(
    230    nsAString& aType, const nsAString& aOptions, const CSSIntSize aSize,
    231    CanvasUtils::ImageExtraction aExtractionBehavior,
    232    const nsCString& aRandomizationKey,
    233    nsICanvasRenderingContextInternal* aContext,
    234    OffscreenCanvasDisplayHelper* aOffscreenDisplay, nsIInputStream** aStream) {
    235  nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
    236  if (!encoder) {
    237    return NS_IMAGELIB_ERROR_NO_ENCODER;
    238  }
    239 
    240  return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize,
    241                             aExtractionBehavior, aRandomizationKey, nullptr,
    242                             aContext, aOffscreenDisplay, aStream, encoder);
    243 }
    244 
    245 /* static */
    246 nsresult ImageEncoder::ExtractDataFromLayersImageAsync(
    247    nsAString& aType, const nsAString& aOptions, bool aUsingCustomOptions,
    248    layers::Image* aImage, CanvasUtils::ImageExtraction aExtractionBehavior,
    249    const nsCString& aRandomizationKey,
    250    EncodeCompleteCallback* aEncodeCallback) {
    251  nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
    252  if (!encoder) {
    253    return NS_IMAGELIB_ERROR_NO_ENCODER;
    254  }
    255 
    256  RefPtr<EncodingCompleteEvent> completeEvent =
    257      new EncodingCompleteEvent(aEncodeCallback);
    258 
    259  CSSIntSize size = CSSIntSize::FromUnknownSize(aImage->GetSize());
    260  nsCOMPtr<nsIRunnable> event = new EncodingRunnable(
    261      aType, aOptions, nullptr, aImage, encoder, completeEvent,
    262      imgIEncoder::INPUT_FORMAT_HOSTARGB, size, aExtractionBehavior,
    263      VoidCString(), aUsingCustomOptions);
    264  return NS_DispatchBackgroundTask(event.forget());
    265 }
    266 
    267 /* static */
    268 nsresult ImageEncoder::ExtractDataAsync(
    269    nsAString& aType, const nsAString& aOptions, bool aUsingCustomOptions,
    270    UniquePtr<uint8_t[]> aImageBuffer, int32_t aFormat, const CSSIntSize aSize,
    271    CanvasUtils::ImageExtraction aExtractionBehavior,
    272    const nsCString& aRandomizationKey,
    273    EncodeCompleteCallback* aEncodeCallback) {
    274  nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
    275  if (!encoder) {
    276    return NS_IMAGELIB_ERROR_NO_ENCODER;
    277  }
    278 
    279  RefPtr<EncodingCompleteEvent> completeEvent =
    280      new EncodingCompleteEvent(aEncodeCallback);
    281 
    282  nsCOMPtr<nsIRunnable> event = new EncodingRunnable(
    283      aType, aOptions, std::move(aImageBuffer), nullptr, encoder, completeEvent,
    284      aFormat, aSize, aExtractionBehavior, aRandomizationKey,
    285      aUsingCustomOptions);
    286  return NS_DispatchBackgroundTask(event.forget());
    287 }
    288 
    289 /*static*/
    290 nsresult ImageEncoder::GetInputStream(int32_t aWidth, int32_t aHeight,
    291                                      uint8_t* aImageBuffer, int32_t aFormat,
    292                                      imgIEncoder* aEncoder,
    293                                      const nsAString& aEncoderOptions,
    294                                      const nsACString& aRandomizationKey,
    295                                      nsIInputStream** aStream) {
    296  nsresult rv = aEncoder->InitFromData(aImageBuffer, aWidth * aHeight * 4,
    297                                       aWidth, aHeight, aWidth * 4, aFormat,
    298                                       aEncoderOptions, aRandomizationKey);
    299  NS_ENSURE_SUCCESS(rv, rv);
    300 
    301  nsCOMPtr<imgIEncoder> encoder(aEncoder);
    302  encoder.forget(aStream);
    303  return NS_OK;
    304 }
    305 
    306 /* static */
    307 nsresult ImageEncoder::ExtractDataInternal(
    308    const nsAString& aType, const nsAString& aOptions, uint8_t* aImageBuffer,
    309    int32_t aFormat, const CSSIntSize aSize,
    310    CanvasUtils::ImageExtraction aExtractionBehavior,
    311    const nsCString& aRandomizationKey, layers::Image* aImage,
    312    nsICanvasRenderingContextInternal* aContext,
    313    OffscreenCanvasDisplayHelper* aOffscreenDisplay, nsIInputStream** aStream,
    314    imgIEncoder* aEncoder) {
    315  if (aSize.IsEmpty()) {
    316    return NS_ERROR_INVALID_ARG;
    317  }
    318 
    319  nsCOMPtr<nsIInputStream> imgStream;
    320 
    321  // get image bytes
    322  nsresult rv = NS_OK;
    323  bool hasImageSource = aImageBuffer || aContext || aOffscreenDisplay || aImage;
    324  bool usePlaceholder =
    325      aExtractionBehavior == CanvasUtils::ImageExtraction::Placeholder;
    326  if (!hasImageSource || usePlaceholder) {
    327    if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
    328      return NS_ERROR_INVALID_ARG;
    329    }
    330 
    331    // no context, so we have to encode an empty image
    332    // note that if we didn't have a current context, the spec says we're
    333    // supposed to just return transparent black pixels of the canvas
    334    // dimensions.
    335    RefPtr<DataSourceSurface> emptyCanvas =
    336        Factory::CreateDataSourceSurfaceWithStride(
    337            IntSize(aSize.width, aSize.height), SurfaceFormat::B8G8R8A8,
    338            4 * aSize.width, true);
    339    if (NS_WARN_IF(!emptyCanvas)) {
    340      return NS_ERROR_INVALID_ARG;
    341    }
    342 
    343    DataSourceSurface::MappedSurface map;
    344    if (!emptyCanvas->Map(DataSourceSurface::MapType::WRITE, &map)) {
    345      return NS_ERROR_INVALID_ARG;
    346    }
    347    if (usePlaceholder) {
    348      auto size = 4 * aSize.width * aSize.height;
    349      auto* data = map.mData;
    350      GeneratePlaceholderCanvasData(size, data);
    351    }
    352    rv = aEncoder->InitFromData(map.mData, aSize.width * aSize.height * 4,
    353                                aSize.width, aSize.height, aSize.width * 4,
    354                                imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions,
    355                                VoidCString());
    356    emptyCanvas->Unmap();
    357    if (NS_SUCCEEDED(rv)) {
    358      imgStream = aEncoder;
    359    }
    360  } else if (aImageBuffer) {
    361    if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
    362      return NS_ERROR_INVALID_ARG;
    363    }
    364 
    365    rv = ImageEncoder::GetInputStream(
    366        aSize.width, aSize.height, aImageBuffer, aFormat, aEncoder, aOptions,
    367        aRandomizationKey, getter_AddRefs(imgStream));
    368  } else if (aContext) {
    369    NS_ConvertUTF16toUTF8 encoderType(aType);
    370    rv = aContext->GetInputStream(encoderType.get(), aOptions,
    371                                  aExtractionBehavior, aRandomizationKey,
    372                                  getter_AddRefs(imgStream));
    373  } else if (aOffscreenDisplay) {
    374    const NS_ConvertUTF16toUTF8 encoderType(aType);
    375    if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
    376      return NS_ERROR_INVALID_ARG;
    377    }
    378 
    379    const RefPtr<SourceSurface> snapshot =
    380        aOffscreenDisplay->GetSurfaceSnapshot();
    381    if (!snapshot) {
    382      return NS_ERROR_OUT_OF_MEMORY;
    383    }
    384 
    385    const RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
    386    if (!data) {
    387      return NS_ERROR_OUT_OF_MEMORY;
    388    }
    389 
    390    {
    391      DataSourceSurface::MappedSurface map;
    392      if (!data->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
    393        return NS_ERROR_INVALID_ARG;
    394      }
    395      auto size = data->GetSize();
    396      rv = aEncoder->InitFromData(map.mData, size.width * size.height * 4,
    397                                  size.width, size.height, size.width * 4,
    398                                  imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions,
    399                                  VoidCString());
    400      data->Unmap();
    401    }
    402    if (NS_SUCCEEDED(rv)) {
    403      imgStream = aEncoder;
    404    }
    405  } else if (aImage) {
    406    // It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread.
    407    // Other image formats could have problem to convert format off-main-thread.
    408    // So here it uses a help function GetBRGADataSourceSurfaceSync() to convert
    409    // format on main thread.
    410    if (aImage->GetFormat() == ImageFormat::PLANAR_YCBCR) {
    411      nsTArray<uint8_t> data;
    412      layers::PlanarYCbCrImage* ycbcrImage =
    413          static_cast<layers::PlanarYCbCrImage*>(aImage);
    414      gfxImageFormat format = SurfaceFormat::A8R8G8B8_UINT32;
    415      int32_t stride = GetAlignedStride<16>(aSize.width, 4);
    416      size_t length = BufferSizeFromStrideAndHeight(stride, aSize.height);
    417      if (length == 0) {
    418        return NS_ERROR_INVALID_ARG;
    419      }
    420      data.SetCapacity(length);
    421 
    422      rv = ConvertYCbCrToRGB(*ycbcrImage->GetData(), format,
    423                             aSize.ToUnknownSize(), data.Elements(), stride);
    424      if (NS_FAILED(rv)) {
    425        MOZ_ASSERT_UNREACHABLE("Failed to convert YUV into RGB data");
    426        return rv;
    427      }
    428 
    429      rv = aEncoder->InitFromData(
    430          data.Elements(), aSize.width * aSize.height * 4, aSize.width,
    431          aSize.height, aSize.width * 4, imgIEncoder::INPUT_FORMAT_HOSTARGB,
    432          aOptions, VoidCString());
    433    } else {
    434      if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
    435        return NS_ERROR_INVALID_ARG;
    436      }
    437 
    438      RefPtr<gfx::DataSourceSurface> dataSurface;
    439      RefPtr<layers::Image> image(aImage);
    440      dataSurface = GetBRGADataSourceSurfaceSync(image.forget());
    441 
    442      DataSourceSurface::MappedSurface map;
    443      if (!dataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
    444        return NS_ERROR_INVALID_ARG;
    445      }
    446      auto size = dataSurface->GetSize();
    447      rv = aEncoder->InitFromData(map.mData, size.width * size.height * 4,
    448                                  size.width, size.height, size.width * 4,
    449                                  imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions,
    450                                  VoidCString());
    451      dataSurface->Unmap();
    452    }
    453 
    454    if (NS_SUCCEEDED(rv)) {
    455      imgStream = aEncoder;
    456    }
    457  }
    458  NS_ENSURE_SUCCESS(rv, rv);
    459 
    460  imgStream.forget(aStream);
    461  return rv;
    462 }
    463 
    464 /* static */
    465 already_AddRefed<imgIEncoder> ImageEncoder::GetImageEncoder(nsAString& aType) {
    466  // Get an image encoder for the media type.
    467  nsCString encoderCID("@mozilla.org/image/encoder;2?type=");
    468  NS_ConvertUTF16toUTF8 encoderType(aType);
    469  encoderCID += encoderType;
    470  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
    471 
    472  if (!encoder && aType != u"image/png"_ns) {
    473    // Unable to create an encoder instance of the specified type. Falling back
    474    // to PNG.
    475    aType.AssignLiteral("image/png");
    476    nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png");
    477    encoder = do_CreateInstance(PNGEncoderCID.get());
    478  }
    479 
    480  return encoder.forget();
    481 }
    482 
    483 }  // namespace mozilla::dom