mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 23:02:20 +00:00
7ced96212e
We'll probably want to do something more accurate in the future with a custom clang static analysis pass which validates that XPIDL interfaces have the expected vtable and struct layout, however doing so would be more involved than the string matching done in this patch. In addition to checking for extra virtual methods, we'll likely also want to check for data members on interfaces, and reject them unless the class is marked as `[builtinclass]` in addition to some other attribute which we'll need to add to prevent them from being implemented in Rust (as c++ data members will not be reflected by the rust macro). There were 2 instances of a comment which contained the word 'virtual' within a CDATA block. These comments were moved out of the CDATA block to avoid triggering the error. Differential Revision: https://phabricator.services.mozilla.com/D151068
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 "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
|