michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxImageSurface.h" michael@0: #include "ImageEncoder.h" michael@0: #include "mozilla/dom/CanvasRenderingContext2D.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: class EncodingCompleteEvent : public nsRunnable michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: EncodingCompleteEvent(nsIScriptContext* aScriptContext, michael@0: nsIThread* aEncoderThread, michael@0: FileCallback& aCallback) michael@0: : mImgSize(0) michael@0: , mType() michael@0: , mImgData(nullptr) michael@0: , mScriptContext(aScriptContext) michael@0: , mEncoderThread(aEncoderThread) michael@0: , mCallback(&aCallback) michael@0: , mFailed(false) michael@0: {} michael@0: virtual ~EncodingCompleteEvent() {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mozilla::ErrorResult rv; michael@0: michael@0: if (!mFailed) { michael@0: nsRefPtr blob = michael@0: new nsDOMMemoryFile(mImgData, mImgSize, mType); michael@0: michael@0: if (mScriptContext) { michael@0: JSContext* jsContext = mScriptContext->GetNativeContext(); michael@0: if (jsContext) { michael@0: JS_updateMallocCounter(jsContext, mImgSize); michael@0: } michael@0: } michael@0: michael@0: mCallback->Call(blob, rv); michael@0: } michael@0: michael@0: // These members aren't thread-safe. We're making sure that they're being michael@0: // released on the main thread here. Otherwise, they could be getting michael@0: // released by EncodingRunnable's destructor on the encoding thread michael@0: // (bug 916128). michael@0: mScriptContext = nullptr; michael@0: mCallback = nullptr; michael@0: michael@0: mEncoderThread->Shutdown(); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType) michael@0: { michael@0: mImgData = aImgData; michael@0: mImgSize = aImgSize; michael@0: mType = aType; michael@0: } michael@0: michael@0: void SetFailed() michael@0: { michael@0: mFailed = true; michael@0: } michael@0: michael@0: private: michael@0: uint64_t mImgSize; michael@0: nsAutoString mType; michael@0: void* mImgData; michael@0: nsCOMPtr mScriptContext; michael@0: nsCOMPtr mEncoderThread; michael@0: nsRefPtr mCallback; michael@0: bool mFailed; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(EncodingCompleteEvent, nsIRunnable); michael@0: michael@0: class EncodingRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: EncodingRunnable(const nsAString& aType, michael@0: const nsAString& aOptions, michael@0: uint8_t* aImageBuffer, michael@0: imgIEncoder* aEncoder, michael@0: EncodingCompleteEvent* aEncodingCompleteEvent, michael@0: int32_t aFormat, michael@0: const nsIntSize aSize, michael@0: bool aUsePlaceholder, michael@0: bool aUsingCustomOptions) michael@0: : mType(aType) michael@0: , mOptions(aOptions) michael@0: , mImageBuffer(aImageBuffer) michael@0: , mEncoder(aEncoder) michael@0: , mEncodingCompleteEvent(aEncodingCompleteEvent) michael@0: , mFormat(aFormat) michael@0: , mSize(aSize) michael@0: , mUsePlaceholder(aUsePlaceholder) michael@0: , mUsingCustomOptions(aUsingCustomOptions) michael@0: {} michael@0: virtual ~EncodingRunnable() {} michael@0: michael@0: nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData) michael@0: { michael@0: nsCOMPtr stream; michael@0: nsresult rv = ImageEncoder::ExtractDataInternal(mType, michael@0: mOptions, michael@0: mImageBuffer, michael@0: mFormat, michael@0: mSize, michael@0: mUsePlaceholder, michael@0: nullptr, michael@0: getter_AddRefs(stream), michael@0: mEncoder); michael@0: michael@0: // If there are unrecognized custom parse options, we should fall back to michael@0: // the default values for the encoder without any options at all. michael@0: if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) { michael@0: rv = ImageEncoder::ExtractDataInternal(mType, michael@0: EmptyString(), michael@0: mImageBuffer, michael@0: mFormat, michael@0: mSize, michael@0: mUsePlaceholder, michael@0: nullptr, michael@0: getter_AddRefs(stream), michael@0: mEncoder); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stream->Available(aImgSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(*aImgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); michael@0: michael@0: rv = NS_ReadInputStreamToBuffer(stream, aImgData, *aImgSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: uint64_t imgSize; michael@0: void* imgData = nullptr; michael@0: michael@0: nsresult rv = ProcessImageData(&imgSize, &imgData); michael@0: if (NS_FAILED(rv)) { michael@0: mEncodingCompleteEvent->SetFailed(); michael@0: } else { michael@0: mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType); michael@0: } michael@0: rv = NS_DispatchToMainThread(mEncodingCompleteEvent, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: // Better to leak than to crash. michael@0: mEncodingCompleteEvent.forget(); michael@0: return rv; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: private: michael@0: nsAutoString mType; michael@0: nsAutoString mOptions; michael@0: nsAutoArrayPtr mImageBuffer; michael@0: nsCOMPtr mEncoder; michael@0: nsRefPtr mEncodingCompleteEvent; michael@0: int32_t mFormat; michael@0: const nsIntSize mSize; michael@0: bool mUsePlaceholder; michael@0: bool mUsingCustomOptions; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(EncodingRunnable, nsIRunnable) michael@0: michael@0: /* static */ michael@0: nsresult michael@0: ImageEncoder::ExtractData(nsAString& aType, michael@0: const nsAString& aOptions, michael@0: const nsIntSize aSize, michael@0: bool aUsePlaceholder, michael@0: nsICanvasRenderingContextInternal* aContext, michael@0: nsIInputStream** aStream) michael@0: { michael@0: nsCOMPtr encoder = ImageEncoder::GetImageEncoder(aType); michael@0: if (!encoder) { michael@0: return NS_IMAGELIB_ERROR_NO_ENCODER; michael@0: } michael@0: michael@0: return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, michael@0: aUsePlaceholder, aContext, aStream, encoder); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: ImageEncoder::ExtractDataAsync(nsAString& aType, michael@0: const nsAString& aOptions, michael@0: bool aUsingCustomOptions, michael@0: uint8_t* aImageBuffer, michael@0: int32_t aFormat, michael@0: const nsIntSize aSize, michael@0: bool aUsePlaceholder, michael@0: nsICanvasRenderingContextInternal* aContext, michael@0: nsIScriptContext* aScriptContext, michael@0: FileCallback& aCallback) michael@0: { michael@0: nsCOMPtr encoder = ImageEncoder::GetImageEncoder(aType); michael@0: if (!encoder) { michael@0: return NS_IMAGELIB_ERROR_NO_ENCODER; michael@0: } michael@0: michael@0: nsCOMPtr encoderThread; michael@0: nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr completeEvent = michael@0: new EncodingCompleteEvent(aScriptContext, encoderThread, aCallback); michael@0: michael@0: nsCOMPtr event = new EncodingRunnable(aType, michael@0: aOptions, michael@0: aImageBuffer, michael@0: encoder, michael@0: completeEvent, michael@0: aFormat, michael@0: aSize, michael@0: aUsePlaceholder, michael@0: aUsingCustomOptions); michael@0: return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: /*static*/ nsresult michael@0: ImageEncoder::GetInputStream(int32_t aWidth, michael@0: int32_t aHeight, michael@0: uint8_t* aImageBuffer, michael@0: int32_t aFormat, michael@0: imgIEncoder* aEncoder, michael@0: const char16_t* aEncoderOptions, michael@0: nsIInputStream** aStream) michael@0: { michael@0: nsresult rv = michael@0: aEncoder->InitFromData(aImageBuffer, michael@0: aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4, michael@0: aFormat, michael@0: nsDependentString(aEncoderOptions)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return CallQueryInterface(aEncoder, aStream); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: ImageEncoder::ExtractDataInternal(const nsAString& aType, michael@0: const nsAString& aOptions, michael@0: uint8_t* aImageBuffer, michael@0: int32_t aFormat, michael@0: const nsIntSize aSize, michael@0: bool aUsePlaceholder, michael@0: nsICanvasRenderingContextInternal* aContext, michael@0: nsIInputStream** aStream, michael@0: imgIEncoder* aEncoder) michael@0: { michael@0: nsCOMPtr imgStream; michael@0: michael@0: // get image bytes michael@0: nsresult rv; michael@0: if (aImageBuffer && !aUsePlaceholder) { michael@0: rv = ImageEncoder::GetInputStream( michael@0: aSize.width, michael@0: aSize.height, michael@0: aImageBuffer, michael@0: aFormat, michael@0: aEncoder, michael@0: nsPromiseFlatString(aOptions).get(), michael@0: getter_AddRefs(imgStream)); michael@0: } else if (aContext && !aUsePlaceholder) { michael@0: NS_ConvertUTF16toUTF8 encoderType(aType); michael@0: rv = aContext->GetInputStream(encoderType.get(), michael@0: nsPromiseFlatString(aOptions).get(), michael@0: getter_AddRefs(imgStream)); michael@0: } else { michael@0: // If placeholder data requested or no context, encode an empty image. michael@0: // note that if we didn't have a current context, the spec says we're michael@0: // supposed to just return transparent black pixels of the canvas michael@0: // dimensions. michael@0: nsRefPtr emptyCanvas = michael@0: new gfxImageSurface(gfxIntSize(aSize.width, aSize.height), michael@0: gfxImageFormat::ARGB32); michael@0: if (emptyCanvas->CairoStatus()) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: if (aUsePlaceholder) { michael@0: // If placeholder data was requested, return all-white, opaque image data. michael@0: int32_t dataSize = emptyCanvas->GetDataSize(); michael@0: memset(emptyCanvas->Data(), 0xFF, dataSize); michael@0: } michael@0: rv = aEncoder->InitFromData(emptyCanvas->Data(), michael@0: aSize.width * aSize.height * 4, michael@0: aSize.width, michael@0: aSize.height, michael@0: aSize.width * 4, michael@0: imgIEncoder::INPUT_FORMAT_HOSTARGB, michael@0: aOptions); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: imgStream = do_QueryInterface(aEncoder); michael@0: } michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: imgStream.forget(aStream); michael@0: return rv; michael@0: } michael@0: michael@0: /* static */ michael@0: already_AddRefed michael@0: ImageEncoder::GetImageEncoder(nsAString& aType) michael@0: { michael@0: // Get an image encoder for the media type. michael@0: nsCString encoderCID("@mozilla.org/image/encoder;2?type="); michael@0: NS_ConvertUTF16toUTF8 encoderType(aType); michael@0: encoderCID += encoderType; michael@0: nsCOMPtr encoder = do_CreateInstance(encoderCID.get()); michael@0: michael@0: if (!encoder && aType != NS_LITERAL_STRING("image/png")) { michael@0: // Unable to create an encoder instance of the specified type. Falling back michael@0: // to PNG. michael@0: aType.AssignLiteral("image/png"); michael@0: nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png"); michael@0: encoder = do_CreateInstance(PNGEncoderCID.get()); michael@0: } michael@0: michael@0: return encoder.forget(); michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla