diff -r 000000000000 -r 6474c204b198 content/canvas/src/ImageEncoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/canvas/src/ImageEncoder.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,344 @@ +/* -*- 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 "gfxImageSurface.h" +#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) + , mFailed(false) + {} + virtual ~EncodingCompleteEvent() {} + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + mozilla::ErrorResult rv; + + if (!mFailed) { + nsRefPtr blob = + new nsDOMMemoryFile(mImgData, mImgSize, mType); + + if (mScriptContext) { + JSContext* jsContext = mScriptContext->GetNativeContext(); + if (jsContext) { + JS_updateMallocCounter(jsContext, mImgSize); + } + } + + mCallback->Call(blob, rv); + } + + // These members aren't thread-safe. We're making sure that they're being + // released on the main thread here. Otherwise, they could be getting + // released by EncodingRunnable's destructor on the encoding thread + // (bug 916128). + mScriptContext = nullptr; + mCallback = nullptr; + + mEncoderThread->Shutdown(); + return rv.ErrorCode(); + } + + void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType) + { + mImgData = aImgData; + mImgSize = aImgSize; + mType = aType; + } + + void SetFailed() + { + mFailed = true; + } + +private: + uint64_t mImgSize; + nsAutoString mType; + void* mImgData; + nsCOMPtr mScriptContext; + nsCOMPtr mEncoderThread; + nsRefPtr mCallback; + bool mFailed; +}; + +NS_IMPL_ISUPPORTS(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 aUsePlaceholder, + bool aUsingCustomOptions) + : mType(aType) + , mOptions(aOptions) + , mImageBuffer(aImageBuffer) + , mEncoder(aEncoder) + , mEncodingCompleteEvent(aEncodingCompleteEvent) + , mFormat(aFormat) + , mSize(aSize) + , mUsePlaceholder(aUsePlaceholder) + , mUsingCustomOptions(aUsingCustomOptions) + {} + virtual ~EncodingRunnable() {} + + nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData) + { + nsCOMPtr stream; + nsresult rv = ImageEncoder::ExtractDataInternal(mType, + mOptions, + mImageBuffer, + mFormat, + mSize, + mUsePlaceholder, + 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, + mUsePlaceholder, + nullptr, + getter_AddRefs(stream), + mEncoder); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = stream->Available(aImgSize); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(*aImgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); + + rv = NS_ReadInputStreamToBuffer(stream, aImgData, *aImgSize); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; + } + + NS_IMETHOD Run() + { + uint64_t imgSize; + void* imgData = nullptr; + + nsresult rv = ProcessImageData(&imgSize, &imgData); + if (NS_FAILED(rv)) { + mEncodingCompleteEvent->SetFailed(); + } else { + mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType); + } + rv = NS_DispatchToMainThread(mEncodingCompleteEvent, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + // Better to leak than to crash. + mEncodingCompleteEvent.forget(); + return rv; + } + + return rv; + } + +private: + nsAutoString mType; + nsAutoString mOptions; + nsAutoArrayPtr mImageBuffer; + nsCOMPtr mEncoder; + nsRefPtr mEncodingCompleteEvent; + int32_t mFormat; + const nsIntSize mSize; + bool mUsePlaceholder; + bool mUsingCustomOptions; +}; + +NS_IMPL_ISUPPORTS(EncodingRunnable, nsIRunnable) + +/* static */ +nsresult +ImageEncoder::ExtractData(nsAString& aType, + const nsAString& aOptions, + const nsIntSize aSize, + bool aUsePlaceholder, + nsICanvasRenderingContextInternal* aContext, + nsIInputStream** aStream) +{ + nsCOMPtr encoder = ImageEncoder::GetImageEncoder(aType); + if (!encoder) { + return NS_IMAGELIB_ERROR_NO_ENCODER; + } + + return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, + aUsePlaceholder, aContext, aStream, encoder); +} + +/* static */ +nsresult +ImageEncoder::ExtractDataAsync(nsAString& aType, + const nsAString& aOptions, + bool aUsingCustomOptions, + uint8_t* aImageBuffer, + int32_t aFormat, + const nsIntSize aSize, + bool aUsePlaceholder, + nsICanvasRenderingContextInternal* aContext, + nsIScriptContext* aScriptContext, + FileCallback& aCallback) +{ + nsCOMPtr encoder = ImageEncoder::GetImageEncoder(aType); + if (!encoder) { + return NS_IMAGELIB_ERROR_NO_ENCODER; + } + + nsCOMPtr encoderThread; + nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr completeEvent = + new EncodingCompleteEvent(aScriptContext, encoderThread, aCallback); + + nsCOMPtr event = new EncodingRunnable(aType, + aOptions, + aImageBuffer, + encoder, + completeEvent, + aFormat, + aSize, + aUsePlaceholder, + 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 char16_t* 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, + bool aUsePlaceholder, + nsICanvasRenderingContextInternal* aContext, + nsIInputStream** aStream, + imgIEncoder* aEncoder) +{ + nsCOMPtr imgStream; + + // get image bytes + nsresult rv; + if (aImageBuffer && !aUsePlaceholder) { + rv = ImageEncoder::GetInputStream( + aSize.width, + aSize.height, + aImageBuffer, + aFormat, + aEncoder, + nsPromiseFlatString(aOptions).get(), + getter_AddRefs(imgStream)); + } else if (aContext && !aUsePlaceholder) { + NS_ConvertUTF16toUTF8 encoderType(aType); + rv = aContext->GetInputStream(encoderType.get(), + nsPromiseFlatString(aOptions).get(), + getter_AddRefs(imgStream)); + } else { + // If placeholder data requested or no context, 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 emptyCanvas = + new gfxImageSurface(gfxIntSize(aSize.width, aSize.height), + gfxImageFormat::ARGB32); + if (emptyCanvas->CairoStatus()) { + return NS_ERROR_INVALID_ARG; + } + if (aUsePlaceholder) { + // If placeholder data was requested, return all-white, opaque image data. + int32_t dataSize = emptyCanvas->GetDataSize(); + memset(emptyCanvas->Data(), 0xFF, dataSize); + } + 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 +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 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