зеркало из https://github.com/mozilla/gecko-dev.git
Bug 817700 - Make <canvas>.toBlob run asynchronously. r=seth,roc,bz
This commit is contained in:
Родитель
81f8c2183b
Коммит
9374fb23d2
|
@ -0,0 +1,299 @@
|
|||
/* -*- 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 "ImageEncoder.h"
|
||||
|
||||
#include "mozilla/dom/CanvasRenderingContext2D.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class EncodingCompleteEvent : public nsRunnable
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
||||
EncodingCompleteEvent(nsIScriptContext* aScriptContext,
|
||||
nsIThread* aEncoderThread,
|
||||
FileCallback& aCallback)
|
||||
: mImgSize(0)
|
||||
, mType()
|
||||
, mImgData(nullptr)
|
||||
, mScriptContext(aScriptContext)
|
||||
, mEncoderThread(aEncoderThread)
|
||||
, mCallback(&aCallback)
|
||||
{}
|
||||
virtual ~EncodingCompleteEvent() {}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsRefPtr<nsDOMMemoryFile> blob =
|
||||
new nsDOMMemoryFile(mImgData, mImgSize, mType);
|
||||
|
||||
if (mScriptContext) {
|
||||
JSContext* jsContext = mScriptContext->GetNativeContext();
|
||||
if (jsContext) {
|
||||
JS_updateMallocCounter(jsContext, mImgSize);
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::ErrorResult rv;
|
||||
mCallback->Call(blob, rv);
|
||||
NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode());
|
||||
|
||||
mEncoderThread->Shutdown();
|
||||
return rv.ErrorCode();
|
||||
}
|
||||
|
||||
void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType)
|
||||
{
|
||||
mImgData = aImgData;
|
||||
mImgSize = aImgSize;
|
||||
mType = aType;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t mImgSize;
|
||||
nsAutoString mType;
|
||||
void* mImgData;
|
||||
nsCOMPtr<nsIScriptContext> mScriptContext;
|
||||
nsCOMPtr<nsIThread> mEncoderThread;
|
||||
nsRefPtr<FileCallback> mCallback;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS1(EncodingCompleteEvent, nsIRunnable);
|
||||
|
||||
class EncodingRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
||||
EncodingRunnable(const nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
uint8_t* aImageBuffer,
|
||||
imgIEncoder* aEncoder,
|
||||
EncodingCompleteEvent* aEncodingCompleteEvent,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
bool aUsingCustomOptions)
|
||||
: mType(aType)
|
||||
, mOptions(aOptions)
|
||||
, mImageBuffer(aImageBuffer)
|
||||
, mEncoder(aEncoder)
|
||||
, mEncodingCompleteEvent(aEncodingCompleteEvent)
|
||||
, mFormat(aFormat)
|
||||
, mSize(aSize)
|
||||
, mUsingCustomOptions(aUsingCustomOptions)
|
||||
{}
|
||||
virtual ~EncodingRunnable() {}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
nsresult rv = ImageEncoder::ExtractDataInternal(mType,
|
||||
mOptions,
|
||||
mImageBuffer,
|
||||
mFormat,
|
||||
mSize,
|
||||
nullptr,
|
||||
getter_AddRefs(stream),
|
||||
mEncoder);
|
||||
|
||||
// If there are unrecognized custom parse options, we should fall back to
|
||||
// the default values for the encoder without any options at all.
|
||||
if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) {
|
||||
rv = ImageEncoder::ExtractDataInternal(mType,
|
||||
EmptyString(),
|
||||
mImageBuffer,
|
||||
mFormat,
|
||||
mSize,
|
||||
nullptr,
|
||||
getter_AddRefs(stream),
|
||||
mEncoder);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint64_t imgSize;
|
||||
rv = stream->Available(&imgSize);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
|
||||
|
||||
void* imgData = nullptr;
|
||||
rv = NS_ReadInputStreamToBuffer(stream, &imgData, imgSize);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
|
||||
rv = NS_DispatchToMainThread(mEncodingCompleteEvent, NS_DISPATCH_NORMAL);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
private:
|
||||
nsAutoString mType;
|
||||
nsAutoString mOptions;
|
||||
nsAutoArrayPtr<uint8_t> mImageBuffer;
|
||||
nsCOMPtr<imgIEncoder> mEncoder;
|
||||
nsRefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
|
||||
int32_t mFormat;
|
||||
const nsIntSize mSize;
|
||||
bool mUsingCustomOptions;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS1(EncodingRunnable, nsIRunnable)
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
ImageEncoder::ExtractData(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
nsIInputStream** aStream)
|
||||
{
|
||||
nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
|
||||
if (!encoder) {
|
||||
return NS_IMAGELIB_ERROR_NO_ENCODER;
|
||||
}
|
||||
|
||||
return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, aContext,
|
||||
aStream, encoder);
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
ImageEncoder::ExtractDataAsync(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
bool aUsingCustomOptions,
|
||||
uint8_t* aImageBuffer,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
nsIScriptContext* aScriptContext,
|
||||
FileCallback& aCallback)
|
||||
{
|
||||
nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
|
||||
if (!encoder) {
|
||||
return NS_IMAGELIB_ERROR_NO_ENCODER;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThread> encoderThread;
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsRefPtr<EncodingCompleteEvent> completeEvent =
|
||||
new EncodingCompleteEvent(aScriptContext, encoderThread, aCallback);
|
||||
|
||||
nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
|
||||
aOptions,
|
||||
aImageBuffer,
|
||||
encoder,
|
||||
completeEvent,
|
||||
aFormat,
|
||||
aSize,
|
||||
aUsingCustomOptions);
|
||||
return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
/*static*/ nsresult
|
||||
ImageEncoder::GetInputStream(int32_t aWidth,
|
||||
int32_t aHeight,
|
||||
uint8_t* aImageBuffer,
|
||||
int32_t aFormat,
|
||||
imgIEncoder* aEncoder,
|
||||
const PRUnichar* aEncoderOptions,
|
||||
nsIInputStream** aStream)
|
||||
{
|
||||
nsresult rv =
|
||||
aEncoder->InitFromData(aImageBuffer,
|
||||
aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4,
|
||||
aFormat,
|
||||
nsDependentString(aEncoderOptions));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return CallQueryInterface(aEncoder, aStream);
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
uint8_t* aImageBuffer,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
nsIInputStream** aStream,
|
||||
imgIEncoder* aEncoder)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> imgStream;
|
||||
|
||||
// get image bytes
|
||||
nsresult rv;
|
||||
if (aImageBuffer) {
|
||||
rv = ImageEncoder::GetInputStream(
|
||||
aSize.width,
|
||||
aSize.height,
|
||||
aImageBuffer,
|
||||
aFormat,
|
||||
aEncoder,
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else if (aContext) {
|
||||
NS_ConvertUTF16toUTF8 encoderType(aType);
|
||||
rv = aContext->GetInputStream(encoderType.get(),
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else {
|
||||
// no context, so we have to encode an empty image
|
||||
// note that if we didn't have a current context, the spec says we're
|
||||
// supposed to just return transparent black pixels of the canvas
|
||||
// dimensions.
|
||||
nsRefPtr<gfxImageSurface> emptyCanvas =
|
||||
new gfxImageSurface(gfxIntSize(aSize.width, aSize.height),
|
||||
gfxASurface::ImageFormatARGB32);
|
||||
if (emptyCanvas->CairoStatus()) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
rv = aEncoder->InitFromData(emptyCanvas->Data(),
|
||||
aSize.width * aSize.height * 4,
|
||||
aSize.width,
|
||||
aSize.height,
|
||||
aSize.width * 4,
|
||||
imgIEncoder::INPUT_FORMAT_HOSTARGB,
|
||||
aOptions);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
imgStream = do_QueryInterface(aEncoder);
|
||||
}
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
imgStream.forget(aStream);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<imgIEncoder>
|
||||
ImageEncoder::GetImageEncoder(nsAString& aType)
|
||||
{
|
||||
// Get an image encoder for the media type.
|
||||
nsCString encoderCID("@mozilla.org/image/encoder;2?type=");
|
||||
NS_ConvertUTF16toUTF8 encoderType(aType);
|
||||
encoderCID += encoderType;
|
||||
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
|
||||
|
||||
if (!encoder && aType != NS_LITERAL_STRING("image/png")) {
|
||||
// Unable to create an encoder instance of the specified type. Falling back
|
||||
// to PNG.
|
||||
aType.AssignLiteral("image/png");
|
||||
nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png");
|
||||
encoder = do_CreateInstance(PNGEncoderCID.get());
|
||||
}
|
||||
|
||||
return encoder.forget();
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,92 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef ImageEncoder_h
|
||||
#define ImageEncoder_h
|
||||
|
||||
#include "imgIEncoder.h"
|
||||
#include "nsDOMFile.h"
|
||||
#include "nsError.h"
|
||||
#include "mozilla/dom/HTMLCanvasElementBinding.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsSize.h"
|
||||
|
||||
class nsICanvasRenderingContextInternal;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class EncodingRunnable;
|
||||
|
||||
class ImageEncoder
|
||||
{
|
||||
public:
|
||||
// Extracts data synchronously and gives you a stream containing the image
|
||||
// represented by aContext. aType may change to "image/png" if we had to fall
|
||||
// back to a PNG encoder. A return value of NS_OK implies successful data
|
||||
// extraction. If there are any unrecognized custom parse options in
|
||||
// aOptions, NS_ERROR_INVALID_ARG will be returned. When encountering this
|
||||
// error it is usual to call this function again without any options at all.
|
||||
static nsresult ExtractData(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
nsIInputStream** aStream);
|
||||
|
||||
// Extracts data asynchronously. aType may change to "image/png" if we had to
|
||||
// fall back to a PNG encoder. aOptions are the options to be passed to the
|
||||
// encoder and aUsingCustomOptions specifies whether custom parse options were
|
||||
// used (i.e. by using -moz-parse-options). If there are any unrecognized
|
||||
// custom parse options, we fall back to the default values for the encoder
|
||||
// without any options at all. A return value of NS_OK only implies
|
||||
// successful dispatching of the extraction step to the encoding thread.
|
||||
static nsresult ExtractDataAsync(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
bool aUsingCustomOptions,
|
||||
uint8_t* aImageBuffer,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
nsIScriptContext* aScriptContext,
|
||||
FileCallback& aCallback);
|
||||
|
||||
// Gives you a stream containing the image represented by aImageBuffer.
|
||||
// The format is given in aFormat, for example
|
||||
// imgIEncoder::INPUT_FORMAT_HOSTARGB.
|
||||
static nsresult GetInputStream(int32_t aWidth,
|
||||
int32_t aHeight,
|
||||
uint8_t* aImageBuffer,
|
||||
int32_t aFormat,
|
||||
imgIEncoder* aEncoder,
|
||||
const PRUnichar* aEncoderOptions,
|
||||
nsIInputStream** aStream);
|
||||
|
||||
private:
|
||||
// When called asynchronously, aContext is null.
|
||||
static nsresult
|
||||
ExtractDataInternal(const nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
uint8_t* aImageBuffer,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
nsIInputStream** aStream,
|
||||
imgIEncoder* aEncoder);
|
||||
|
||||
// Creates and returns an encoder instance of the type specified in aType.
|
||||
// aType may change to "image/png" if no instance of the original type could
|
||||
// be created and we had to fall back to a PNG encoder. A return value of
|
||||
// NULL should be interpreted as NS_IMAGELIB_ERROR_NO_ENCODER and aType is
|
||||
// undefined in this case.
|
||||
static already_AddRefed<imgIEncoder> GetImageEncoder(nsAString& aType);
|
||||
|
||||
friend class EncodingRunnable;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // ImageEncoder_h
|
|
@ -22,6 +22,7 @@ CPP_SOURCES += [
|
|||
'DocumentRendererChild.cpp',
|
||||
'DocumentRendererParent.cpp',
|
||||
'ImageData.cpp',
|
||||
'ImageEncoder.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WEBGL']:
|
||||
|
|
Загрузка…
Ссылка в новой задаче