mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 00:32:11 +00:00
80f17b60f8
Current gecko dispatches tasks of releasing image::Image to main thread at some places. The task was dispatched individually. Then there were cases that the releasing took long time. It increased peak memory usage and caused a problem like Bug 1639280. When main thread is very busy like WebGL, it takes longer time until the Image is released on main thread. If Images are released quickly on main thread, we could reduce peak memory usage. When SurfaceCache::ReleaseImageOnMainThread() is called, there could be already an ongoing task for releasing Image. It could reduce a duration until release on main thread. Differential Revision: https://phabricator.services.mozilla.com/D82864
650 lines
21 KiB
C++
650 lines
21 KiB
C++
/* -*- 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 "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
|
|
|
|
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, nullptr, 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, nullptr, 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->)
|
|
|
|
nsresult GetNativeSizes(nsTArray<nsIntSize>& aNativeSizes) const override {
|
|
return mImage->GetNativeSizes(aNativeSizes);
|
|
}
|
|
|
|
size_t GetNativeSizesLength() const override {
|
|
return mImage->GetNativeSizesLength();
|
|
}
|
|
|
|
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, 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, 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;
|
|
uint32_t bufferLength = 0;
|
|
bool isSharedMemory = false;
|
|
|
|
JS::GetArrayBufferLengthAndData(obj, &bufferLength, &isSharedMemory,
|
|
&bufferData);
|
|
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), MakeSpan(aBuffer, aSize), NS_ASSIGNMENT_DEPEND);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
MOZ_ASSERT(stream);
|
|
MOZ_ASSERT(NS_InputStreamIsBuffered(stream));
|
|
|
|
rv = image->OnImageDataAvailable(nullptr, nullptr, stream, 0, aSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Let the Image know we've sent all the data.
|
|
rv = image->OnImageDataComplete(nullptr, 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
|