summaryrefslogtreecommitdiffstats
path: root/image/imgTools.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /image/imgTools.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'image/imgTools.cpp')
-rw-r--r--image/imgTools.cpp649
1 files changed, 649 insertions, 0 deletions
diff --git a/image/imgTools.cpp b/image/imgTools.cpp
new file mode 100644
index 0000000000..864efc6f44
--- /dev/null
+++ b/image/imgTools.cpp
@@ -0,0 +1,649 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "imgTools.h"
+
+#include "DecodePool.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Document.h"
+#include "nsError.h"
+#include "imgLoader.h"
+#include "imgICache.h"
+#include "imgIContainer.h"
+#include "imgIEncoder.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNetUtil.h" // for NS_NewBufferedInputStream
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsContentUtils.h"
+#include "nsProxyRelease.h"
+#include "nsIStreamListener.h"
+#include "ImageFactory.h"
+#include "Image.h"
+#include "IProgressObserver.h"
+#include "ScriptedNotificationObserver.h"
+#include "imgIScriptedNotificationObserver.h"
+#include "gfxPlatform.h"
+#include "js/ArrayBuffer.h"
+#include "js/RootingAPI.h" // JS::{Handle,Rooted}
+#include "js/Value.h" // JS::Value
+#include "Orientation.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace image {
+
+namespace {
+
+static nsresult sniff_mimetype_callback(nsIInputStream* in, void* data,
+ const char* fromRawSegment,
+ uint32_t toOffset, uint32_t count,
+ uint32_t* writeCount) {
+ nsCString* mimeType = static_cast<nsCString*>(data);
+ MOZ_ASSERT(mimeType, "mimeType is null!");
+
+ if (count > 0) {
+ imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *mimeType);
+ }
+
+ *writeCount = 0;
+ return NS_ERROR_FAILURE;
+}
+
+class ImageDecoderListener final : public nsIStreamListener,
+ public IProgressObserver,
+ public imgIContainer {
+ public:
+ NS_DECL_ISUPPORTS
+
+ ImageDecoderListener(nsIURI* aURI, imgIContainerCallback* aCallback,
+ imgINotificationObserver* aObserver)
+ : mURI(aURI),
+ mImage(nullptr),
+ mCallback(aCallback),
+ mObserver(aObserver) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD
+ OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) override {
+ if (!mImage) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ nsCString mimeType;
+ channel->GetContentType(mimeType);
+
+ if (aInputStream) {
+ // Look at the first few bytes and see if we can tell what the data is
+ // from that since servers tend to lie. :(
+ uint32_t unused;
+ aInputStream->ReadSegments(sniff_mimetype_callback, &mimeType, aCount,
+ &unused);
+ }
+
+ RefPtr<ProgressTracker> tracker = new ProgressTracker();
+ if (mObserver) {
+ tracker->AddObserver(this);
+ }
+
+ mImage = ImageFactory::CreateImage(channel, tracker, mimeType, mURI,
+ /* aIsMultiPart */ false, 0);
+
+ if (mImage->HasError()) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return mImage->OnImageDataAvailable(aRequest, aInputStream, aOffset,
+ aCount);
+ }
+
+ NS_IMETHOD
+ OnStartRequest(nsIRequest* aRequest) override { return NS_OK; }
+
+ NS_IMETHOD
+ OnStopRequest(nsIRequest* aRequest, nsresult aStatus) override {
+ // Encouter a fetch error, or no data could be fetched.
+ if (!mImage || NS_FAILED(aStatus)) {
+ mCallback->OnImageReady(nullptr, mImage ? aStatus : NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ mImage->OnImageDataComplete(aRequest, aStatus, true);
+ nsCOMPtr<imgIContainer> container = this;
+ mCallback->OnImageReady(container, aStatus);
+ return NS_OK;
+ }
+
+ virtual void Notify(int32_t aType,
+ const nsIntRect* aRect = nullptr) override {
+ if (mObserver) {
+ mObserver->Notify(nullptr, aType, aRect);
+ }
+ }
+
+ virtual void OnLoadComplete(bool aLastPart) override {}
+
+ // Other notifications are ignored.
+ virtual void SetHasImage() override {}
+ virtual bool NotificationsDeferred() const override { return false; }
+ virtual void MarkPendingNotify() override {}
+ virtual void ClearPendingNotify() override {}
+
+ // imgIContainer
+ NS_FORWARD_IMGICONTAINER(mImage->)
+
+ private:
+ virtual ~ImageDecoderListener() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<image::Image> mImage;
+ nsCOMPtr<imgIContainerCallback> mCallback;
+ nsCOMPtr<imgINotificationObserver> mObserver;
+};
+
+NS_IMPL_ISUPPORTS(ImageDecoderListener, nsIStreamListener, imgIContainer)
+
+class ImageDecoderHelper final : public Runnable,
+ public nsIInputStreamCallback {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ ImageDecoderHelper(already_AddRefed<image::Image> aImage,
+ already_AddRefed<nsIInputStream> aInputStream,
+ nsIEventTarget* aEventTarget,
+ imgIContainerCallback* aCallback,
+ nsIEventTarget* aCallbackEventTarget)
+ : Runnable("ImageDecoderHelper"),
+ mImage(std::move(aImage)),
+ mInputStream(std::move(aInputStream)),
+ mEventTarget(aEventTarget),
+ mCallback(aCallback),
+ mCallbackEventTarget(aCallbackEventTarget),
+ mStatus(NS_OK) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD
+ Run() override {
+ // This runnable is dispatched on the Image thread when reading data, but
+ // at the end, it goes back to the main-thread in order to complete the
+ // operation.
+ if (NS_IsMainThread()) {
+ // Let the Image know we've sent all the data.
+ mImage->OnImageDataComplete(nullptr, mStatus, true);
+
+ RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+ tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
+
+ nsCOMPtr<imgIContainer> container;
+ if (NS_SUCCEEDED(mStatus)) {
+ container = mImage;
+ }
+
+ mCallback->OnImageReady(container, mStatus);
+ return NS_OK;
+ }
+
+ uint64_t length;
+ nsresult rv = mInputStream->Available(&length);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ return OperationCompleted(NS_OK);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return OperationCompleted(rv);
+ }
+
+ // Nothing else to read, but maybe we just need to wait.
+ if (length == 0) {
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+ do_QueryInterface(mInputStream);
+ if (asyncInputStream) {
+ rv = asyncInputStream->AsyncWait(this, 0, 0, mEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return OperationCompleted(rv);
+ }
+ return NS_OK;
+ }
+
+ // We really have nothing else to read.
+ if (length == 0) {
+ return OperationCompleted(NS_OK);
+ }
+ }
+
+ // Send the source data to the Image.
+ rv = mImage->OnImageDataAvailable(nullptr, mInputStream, 0,
+ uint32_t(length));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return OperationCompleted(rv);
+ }
+
+ rv = mEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return OperationCompleted(rv);
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnInputStreamReady(nsIAsyncInputStream* aAsyncInputStream) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ return Run();
+ }
+
+ nsresult OperationCompleted(nsresult aStatus) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mStatus = aStatus;
+ mCallbackEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ private:
+ ~ImageDecoderHelper() {
+ SurfaceCache::ReleaseImageOnMainThread(mImage.forget());
+ NS_ReleaseOnMainThread("ImageDecoderHelper::mCallback", mCallback.forget());
+ }
+
+ RefPtr<image::Image> mImage;
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+ nsCOMPtr<imgIContainerCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
+
+ nsresult mStatus;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(ImageDecoderHelper, Runnable,
+ nsIInputStreamCallback)
+
+} // namespace
+
+/* ========== imgITools implementation ========== */
+
+NS_IMPL_ISUPPORTS(imgTools, imgITools)
+
+imgTools::imgTools() { /* member initializers and constructor code */
+}
+
+imgTools::~imgTools() { /* destructor code */
+}
+
+NS_IMETHODIMP
+imgTools::DecodeImageFromArrayBuffer(JS::Handle<JS::Value> aArrayBuffer,
+ const nsACString& aMimeType,
+ JSContext* aCx,
+ imgIContainer** aContainer) {
+ if (!aArrayBuffer.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx,
+ JS::UnwrapArrayBuffer(&aArrayBuffer.toObject()));
+ if (!obj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint8_t* bufferData = nullptr;
+ size_t bufferLength = 0;
+ bool isSharedMemory = false;
+
+ JS::GetArrayBufferLengthAndData(obj, &bufferLength, &isSharedMemory,
+ &bufferData);
+
+ // Throw for large ArrayBuffers to prevent truncation.
+ if (bufferLength > INT32_MAX) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ return DecodeImageFromBuffer((char*)bufferData, bufferLength, aMimeType,
+ aContainer);
+}
+
+NS_IMETHODIMP
+imgTools::DecodeImageFromBuffer(const char* aBuffer, uint32_t aSize,
+ const nsACString& aMimeType,
+ imgIContainer** aContainer) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_ARG_POINTER(aBuffer);
+
+ // Create a new image container to hold the decoded data.
+ nsAutoCString mimeType(aMimeType);
+ RefPtr<image::Image> image =
+ ImageFactory::CreateAnonymousImage(mimeType, aSize);
+ RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
+
+ if (image->HasError()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Let's create a temporary inputStream.
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(
+ getter_AddRefs(stream), Span(aBuffer, aSize), NS_ASSIGNMENT_DEPEND);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(stream);
+ MOZ_ASSERT(NS_InputStreamIsBuffered(stream));
+
+ rv = image->OnImageDataAvailable(nullptr, stream, 0, aSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Let the Image know we've sent all the data.
+ rv = image->OnImageDataComplete(nullptr, NS_OK, true);
+ tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // All done.
+ image.forget(aContainer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgTools::DecodeImageFromChannelAsync(nsIURI* aURI, nsIChannel* aChannel,
+ imgIContainerCallback* aCallback,
+ imgINotificationObserver* aObserver) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ RefPtr<ImageDecoderListener> listener =
+ new ImageDecoderListener(aURI, aCallback, aObserver);
+
+ return aChannel->AsyncOpen(listener);
+}
+
+NS_IMETHODIMP
+imgTools::DecodeImageAsync(nsIInputStream* aInStr, const nsACString& aMimeType,
+ imgIContainerCallback* aCallback,
+ nsIEventTarget* aEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_ARG_POINTER(aInStr);
+ NS_ENSURE_ARG_POINTER(aCallback);
+ NS_ENSURE_ARG_POINTER(aEventTarget);
+
+ nsresult rv;
+
+ // Let's continuing the reading on a separate thread.
+ DecodePool* decodePool = DecodePool::Singleton();
+ MOZ_ASSERT(decodePool);
+
+ RefPtr<nsIEventTarget> target = decodePool->GetIOEventTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+
+ // Prepare the input stream.
+ nsCOMPtr<nsIInputStream> stream = aInStr;
+ if (!NS_InputStreamIsBuffered(aInStr)) {
+ nsCOMPtr<nsIInputStream> bufStream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), stream.forget(),
+ 1024);
+ NS_ENSURE_SUCCESS(rv, rv);
+ stream = std::move(bufStream);
+ }
+
+ // Create a new image container to hold the decoded data.
+ nsAutoCString mimeType(aMimeType);
+ RefPtr<image::Image> image = ImageFactory::CreateAnonymousImage(mimeType, 0);
+
+ // Already an error?
+ if (image->HasError()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ImageDecoderHelper> helper = new ImageDecoderHelper(
+ image.forget(), stream.forget(), target, aCallback, aEventTarget);
+ rv = target->Dispatch(helper.forget(), NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/**
+ * This takes a DataSourceSurface rather than a SourceSurface because some
+ * of the callers have a DataSourceSurface and we don't want to call
+ * GetDataSurface on such surfaces since that may incur a conversion to
+ * SurfaceType::DATA which we don't need.
+ */
+static nsresult EncodeImageData(DataSourceSurface* aDataSurface,
+ DataSourceSurface::ScopedMap& aMap,
+ const nsACString& aMimeType,
+ const nsAString& aOutputOptions,
+ nsIInputStream** aStream) {
+ MOZ_ASSERT(aDataSurface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ aDataSurface->GetFormat() == SurfaceFormat::B8G8R8X8,
+ "We're assuming B8G8R8A8/X8");
+
+ // Get an image encoder for the media type
+ nsAutoCString encoderCID("@mozilla.org/image/encoder;2?type="_ns + aMimeType);
+
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
+ if (!encoder) {
+ return NS_IMAGELIB_ERROR_NO_ENCODER;
+ }
+
+ IntSize size = aDataSurface->GetSize();
+ uint32_t dataLength = aMap.GetStride() * size.height;
+
+ // Encode the bitmap
+ nsresult rv = encoder->InitFromData(
+ aMap.GetData(), dataLength, size.width, size.height, aMap.GetStride(),
+ imgIEncoder::INPUT_FORMAT_HOSTARGB, aOutputOptions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ encoder.forget(aStream);
+ return NS_OK;
+}
+
+static nsresult EncodeImageData(DataSourceSurface* aDataSurface,
+ const nsACString& aMimeType,
+ const nsAString& aOutputOptions,
+ nsIInputStream** aStream) {
+ DataSourceSurface::ScopedMap map(aDataSurface, DataSourceSurface::READ);
+ if (!map.IsMapped()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return EncodeImageData(aDataSurface, map, aMimeType, aOutputOptions, aStream);
+}
+
+NS_IMETHODIMP
+imgTools::EncodeImage(imgIContainer* aContainer, const nsACString& aMimeType,
+ const nsAString& aOutputOptions,
+ nsIInputStream** aStream) {
+ // Use frame 0 from the image container.
+ RefPtr<SourceSurface> frame = aContainer->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ RefPtr<DataSourceSurface> dataSurface;
+
+ if (frame->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ frame->GetFormat() == SurfaceFormat::B8G8R8X8) {
+ dataSurface = frame->GetDataSurface();
+ } else {
+ // Convert format to SurfaceFormat::B8G8R8A8
+ dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
+ frame, SurfaceFormat::B8G8R8A8);
+ }
+
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
+}
+
+NS_IMETHODIMP
+imgTools::EncodeScaledImage(imgIContainer* aContainer,
+ const nsACString& aMimeType, int32_t aScaledWidth,
+ int32_t aScaledHeight,
+ const nsAString& aOutputOptions,
+ nsIInputStream** aStream) {
+ NS_ENSURE_ARG(aScaledWidth >= 0 && aScaledHeight >= 0);
+
+ // If no scaled size is specified, we'll just encode the image at its
+ // original size (no scaling).
+ if (aScaledWidth == 0 && aScaledHeight == 0) {
+ return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
+ }
+
+ // Retrieve the image's size.
+ int32_t imageWidth = 0;
+ int32_t imageHeight = 0;
+ aContainer->GetWidth(&imageWidth);
+ aContainer->GetHeight(&imageHeight);
+
+ // If the given width or height is zero we'll replace it with the image's
+ // original dimensions.
+ IntSize scaledSize(aScaledWidth == 0 ? imageWidth : aScaledWidth,
+ aScaledHeight == 0 ? imageHeight : aScaledHeight);
+
+ // Use frame 0 from the image container.
+ RefPtr<SourceSurface> frame = aContainer->GetFrameAtSize(
+ scaledSize, imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_HIGH_QUALITY_SCALING |
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ // If the given surface is the right size/format, we can encode it directly.
+ if (scaledSize == frame->GetSize() &&
+ (frame->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ frame->GetFormat() == SurfaceFormat::B8G8R8X8)) {
+ RefPtr<DataSourceSurface> dataSurface = frame->GetDataSurface();
+ if (dataSurface) {
+ return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
+ }
+ }
+
+ // Otherwise we need to scale it using a draw target.
+ RefPtr<DataSourceSurface> dataSurface =
+ Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8);
+ if (NS_WARN_IF(!dataSurface)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE);
+ if (!map.IsMapped()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::SKIA, map.GetData(), dataSurface->GetSize(), map.GetStride(),
+ SurfaceFormat::B8G8R8A8);
+ if (!dt) {
+ gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ IntSize frameSize = frame->GetSize();
+ dt->DrawSurface(frame, Rect(0, 0, scaledSize.width, scaledSize.height),
+ Rect(0, 0, frameSize.width, frameSize.height),
+ DrawSurfaceOptions(),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+
+ return EncodeImageData(dataSurface, map, aMimeType, aOutputOptions, aStream);
+}
+
+NS_IMETHODIMP
+imgTools::EncodeCroppedImage(imgIContainer* aContainer,
+ const nsACString& aMimeType, int32_t aOffsetX,
+ int32_t aOffsetY, int32_t aWidth, int32_t aHeight,
+ const nsAString& aOutputOptions,
+ nsIInputStream** aStream) {
+ NS_ENSURE_ARG(aOffsetX >= 0 && aOffsetY >= 0 && aWidth >= 0 && aHeight >= 0);
+
+ // Offsets must be zero when no width and height are given or else we're out
+ // of bounds.
+ NS_ENSURE_ARG(aWidth + aHeight > 0 || aOffsetX + aOffsetY == 0);
+
+ // If no size is specified then we'll preserve the image's original dimensions
+ // and don't need to crop.
+ if (aWidth == 0 && aHeight == 0) {
+ return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
+ }
+
+ // Use frame 0 from the image container.
+ RefPtr<SourceSurface> frame = aContainer->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ int32_t frameWidth = frame->GetSize().width;
+ int32_t frameHeight = frame->GetSize().height;
+
+ // If the given width or height is zero we'll replace it with the image's
+ // original dimensions.
+ if (aWidth == 0) {
+ aWidth = frameWidth;
+ } else if (aHeight == 0) {
+ aHeight = frameHeight;
+ }
+
+ // Check that the given crop rectangle is within image bounds.
+ NS_ENSURE_ARG(frameWidth >= aOffsetX + aWidth &&
+ frameHeight >= aOffsetY + aHeight);
+
+ RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
+ IntSize(aWidth, aHeight), SurfaceFormat::B8G8R8A8,
+ /* aZero = */ true);
+ if (NS_WARN_IF(!dataSurface)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE);
+ if (!map.IsMapped()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::SKIA, map.GetData(), dataSurface->GetSize(), map.GetStride(),
+ SurfaceFormat::B8G8R8A8);
+ if (!dt) {
+ gfxWarning()
+ << "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ dt->CopySurface(frame, IntRect(aOffsetX, aOffsetY, aWidth, aHeight),
+ IntPoint(0, 0));
+
+ return EncodeImageData(dataSurface, map, aMimeType, aOutputOptions, aStream);
+}
+
+NS_IMETHODIMP
+imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner,
+ imgINotificationObserver** aObserver) {
+ NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgTools::GetImgLoaderForDocument(dom::Document* aDoc, imgILoader** aLoader) {
+ NS_IF_ADDREF(*aLoader = nsContentUtils::GetImgLoaderForDocument(aDoc));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgTools::GetImgCacheForDocument(dom::Document* aDoc, imgICache** aCache) {
+ nsCOMPtr<imgILoader> loader;
+ nsresult rv = GetImgLoaderForDocument(aDoc, getter_AddRefs(loader));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return CallQueryInterface(loader, aCache);
+}
+
+} // namespace image
+} // namespace mozilla