1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/canvas/src/ImageEncoder.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,344 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "gfxImageSurface.h" 1.10 +#include "ImageEncoder.h" 1.11 +#include "mozilla/dom/CanvasRenderingContext2D.h" 1.12 + 1.13 +namespace mozilla { 1.14 +namespace dom { 1.15 + 1.16 +class EncodingCompleteEvent : public nsRunnable 1.17 +{ 1.18 +public: 1.19 + NS_DECL_THREADSAFE_ISUPPORTS 1.20 + 1.21 + EncodingCompleteEvent(nsIScriptContext* aScriptContext, 1.22 + nsIThread* aEncoderThread, 1.23 + FileCallback& aCallback) 1.24 + : mImgSize(0) 1.25 + , mType() 1.26 + , mImgData(nullptr) 1.27 + , mScriptContext(aScriptContext) 1.28 + , mEncoderThread(aEncoderThread) 1.29 + , mCallback(&aCallback) 1.30 + , mFailed(false) 1.31 + {} 1.32 + virtual ~EncodingCompleteEvent() {} 1.33 + 1.34 + NS_IMETHOD Run() 1.35 + { 1.36 + MOZ_ASSERT(NS_IsMainThread()); 1.37 + 1.38 + mozilla::ErrorResult rv; 1.39 + 1.40 + if (!mFailed) { 1.41 + nsRefPtr<nsDOMMemoryFile> blob = 1.42 + new nsDOMMemoryFile(mImgData, mImgSize, mType); 1.43 + 1.44 + if (mScriptContext) { 1.45 + JSContext* jsContext = mScriptContext->GetNativeContext(); 1.46 + if (jsContext) { 1.47 + JS_updateMallocCounter(jsContext, mImgSize); 1.48 + } 1.49 + } 1.50 + 1.51 + mCallback->Call(blob, rv); 1.52 + } 1.53 + 1.54 + // These members aren't thread-safe. We're making sure that they're being 1.55 + // released on the main thread here. Otherwise, they could be getting 1.56 + // released by EncodingRunnable's destructor on the encoding thread 1.57 + // (bug 916128). 1.58 + mScriptContext = nullptr; 1.59 + mCallback = nullptr; 1.60 + 1.61 + mEncoderThread->Shutdown(); 1.62 + return rv.ErrorCode(); 1.63 + } 1.64 + 1.65 + void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType) 1.66 + { 1.67 + mImgData = aImgData; 1.68 + mImgSize = aImgSize; 1.69 + mType = aType; 1.70 + } 1.71 + 1.72 + void SetFailed() 1.73 + { 1.74 + mFailed = true; 1.75 + } 1.76 + 1.77 +private: 1.78 + uint64_t mImgSize; 1.79 + nsAutoString mType; 1.80 + void* mImgData; 1.81 + nsCOMPtr<nsIScriptContext> mScriptContext; 1.82 + nsCOMPtr<nsIThread> mEncoderThread; 1.83 + nsRefPtr<FileCallback> mCallback; 1.84 + bool mFailed; 1.85 +}; 1.86 + 1.87 +NS_IMPL_ISUPPORTS(EncodingCompleteEvent, nsIRunnable); 1.88 + 1.89 +class EncodingRunnable : public nsRunnable 1.90 +{ 1.91 +public: 1.92 + NS_DECL_THREADSAFE_ISUPPORTS 1.93 + 1.94 + EncodingRunnable(const nsAString& aType, 1.95 + const nsAString& aOptions, 1.96 + uint8_t* aImageBuffer, 1.97 + imgIEncoder* aEncoder, 1.98 + EncodingCompleteEvent* aEncodingCompleteEvent, 1.99 + int32_t aFormat, 1.100 + const nsIntSize aSize, 1.101 + bool aUsePlaceholder, 1.102 + bool aUsingCustomOptions) 1.103 + : mType(aType) 1.104 + , mOptions(aOptions) 1.105 + , mImageBuffer(aImageBuffer) 1.106 + , mEncoder(aEncoder) 1.107 + , mEncodingCompleteEvent(aEncodingCompleteEvent) 1.108 + , mFormat(aFormat) 1.109 + , mSize(aSize) 1.110 + , mUsePlaceholder(aUsePlaceholder) 1.111 + , mUsingCustomOptions(aUsingCustomOptions) 1.112 + {} 1.113 + virtual ~EncodingRunnable() {} 1.114 + 1.115 + nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData) 1.116 + { 1.117 + nsCOMPtr<nsIInputStream> stream; 1.118 + nsresult rv = ImageEncoder::ExtractDataInternal(mType, 1.119 + mOptions, 1.120 + mImageBuffer, 1.121 + mFormat, 1.122 + mSize, 1.123 + mUsePlaceholder, 1.124 + nullptr, 1.125 + getter_AddRefs(stream), 1.126 + mEncoder); 1.127 + 1.128 + // If there are unrecognized custom parse options, we should fall back to 1.129 + // the default values for the encoder without any options at all. 1.130 + if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) { 1.131 + rv = ImageEncoder::ExtractDataInternal(mType, 1.132 + EmptyString(), 1.133 + mImageBuffer, 1.134 + mFormat, 1.135 + mSize, 1.136 + mUsePlaceholder, 1.137 + nullptr, 1.138 + getter_AddRefs(stream), 1.139 + mEncoder); 1.140 + } 1.141 + NS_ENSURE_SUCCESS(rv, rv); 1.142 + 1.143 + rv = stream->Available(aImgSize); 1.144 + NS_ENSURE_SUCCESS(rv, rv); 1.145 + NS_ENSURE_TRUE(*aImgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); 1.146 + 1.147 + rv = NS_ReadInputStreamToBuffer(stream, aImgData, *aImgSize); 1.148 + NS_ENSURE_SUCCESS(rv, rv); 1.149 + 1.150 + return rv; 1.151 + } 1.152 + 1.153 + NS_IMETHOD Run() 1.154 + { 1.155 + uint64_t imgSize; 1.156 + void* imgData = nullptr; 1.157 + 1.158 + nsresult rv = ProcessImageData(&imgSize, &imgData); 1.159 + if (NS_FAILED(rv)) { 1.160 + mEncodingCompleteEvent->SetFailed(); 1.161 + } else { 1.162 + mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType); 1.163 + } 1.164 + rv = NS_DispatchToMainThread(mEncodingCompleteEvent, NS_DISPATCH_NORMAL); 1.165 + if (NS_FAILED(rv)) { 1.166 + // Better to leak than to crash. 1.167 + mEncodingCompleteEvent.forget(); 1.168 + return rv; 1.169 + } 1.170 + 1.171 + return rv; 1.172 + } 1.173 + 1.174 +private: 1.175 + nsAutoString mType; 1.176 + nsAutoString mOptions; 1.177 + nsAutoArrayPtr<uint8_t> mImageBuffer; 1.178 + nsCOMPtr<imgIEncoder> mEncoder; 1.179 + nsRefPtr<EncodingCompleteEvent> mEncodingCompleteEvent; 1.180 + int32_t mFormat; 1.181 + const nsIntSize mSize; 1.182 + bool mUsePlaceholder; 1.183 + bool mUsingCustomOptions; 1.184 +}; 1.185 + 1.186 +NS_IMPL_ISUPPORTS(EncodingRunnable, nsIRunnable) 1.187 + 1.188 +/* static */ 1.189 +nsresult 1.190 +ImageEncoder::ExtractData(nsAString& aType, 1.191 + const nsAString& aOptions, 1.192 + const nsIntSize aSize, 1.193 + bool aUsePlaceholder, 1.194 + nsICanvasRenderingContextInternal* aContext, 1.195 + nsIInputStream** aStream) 1.196 +{ 1.197 + nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType); 1.198 + if (!encoder) { 1.199 + return NS_IMAGELIB_ERROR_NO_ENCODER; 1.200 + } 1.201 + 1.202 + return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, 1.203 + aUsePlaceholder, aContext, aStream, encoder); 1.204 +} 1.205 + 1.206 +/* static */ 1.207 +nsresult 1.208 +ImageEncoder::ExtractDataAsync(nsAString& aType, 1.209 + const nsAString& aOptions, 1.210 + bool aUsingCustomOptions, 1.211 + uint8_t* aImageBuffer, 1.212 + int32_t aFormat, 1.213 + const nsIntSize aSize, 1.214 + bool aUsePlaceholder, 1.215 + nsICanvasRenderingContextInternal* aContext, 1.216 + nsIScriptContext* aScriptContext, 1.217 + FileCallback& aCallback) 1.218 +{ 1.219 + nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType); 1.220 + if (!encoder) { 1.221 + return NS_IMAGELIB_ERROR_NO_ENCODER; 1.222 + } 1.223 + 1.224 + nsCOMPtr<nsIThread> encoderThread; 1.225 + nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr); 1.226 + NS_ENSURE_SUCCESS(rv, rv); 1.227 + 1.228 + nsRefPtr<EncodingCompleteEvent> completeEvent = 1.229 + new EncodingCompleteEvent(aScriptContext, encoderThread, aCallback); 1.230 + 1.231 + nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType, 1.232 + aOptions, 1.233 + aImageBuffer, 1.234 + encoder, 1.235 + completeEvent, 1.236 + aFormat, 1.237 + aSize, 1.238 + aUsePlaceholder, 1.239 + aUsingCustomOptions); 1.240 + return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.241 +} 1.242 + 1.243 +/*static*/ nsresult 1.244 +ImageEncoder::GetInputStream(int32_t aWidth, 1.245 + int32_t aHeight, 1.246 + uint8_t* aImageBuffer, 1.247 + int32_t aFormat, 1.248 + imgIEncoder* aEncoder, 1.249 + const char16_t* aEncoderOptions, 1.250 + nsIInputStream** aStream) 1.251 +{ 1.252 + nsresult rv = 1.253 + aEncoder->InitFromData(aImageBuffer, 1.254 + aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4, 1.255 + aFormat, 1.256 + nsDependentString(aEncoderOptions)); 1.257 + NS_ENSURE_SUCCESS(rv, rv); 1.258 + 1.259 + return CallQueryInterface(aEncoder, aStream); 1.260 +} 1.261 + 1.262 +/* static */ 1.263 +nsresult 1.264 +ImageEncoder::ExtractDataInternal(const nsAString& aType, 1.265 + const nsAString& aOptions, 1.266 + uint8_t* aImageBuffer, 1.267 + int32_t aFormat, 1.268 + const nsIntSize aSize, 1.269 + bool aUsePlaceholder, 1.270 + nsICanvasRenderingContextInternal* aContext, 1.271 + nsIInputStream** aStream, 1.272 + imgIEncoder* aEncoder) 1.273 +{ 1.274 + nsCOMPtr<nsIInputStream> imgStream; 1.275 + 1.276 + // get image bytes 1.277 + nsresult rv; 1.278 + if (aImageBuffer && !aUsePlaceholder) { 1.279 + rv = ImageEncoder::GetInputStream( 1.280 + aSize.width, 1.281 + aSize.height, 1.282 + aImageBuffer, 1.283 + aFormat, 1.284 + aEncoder, 1.285 + nsPromiseFlatString(aOptions).get(), 1.286 + getter_AddRefs(imgStream)); 1.287 + } else if (aContext && !aUsePlaceholder) { 1.288 + NS_ConvertUTF16toUTF8 encoderType(aType); 1.289 + rv = aContext->GetInputStream(encoderType.get(), 1.290 + nsPromiseFlatString(aOptions).get(), 1.291 + getter_AddRefs(imgStream)); 1.292 + } else { 1.293 + // If placeholder data requested or no context, encode an empty image. 1.294 + // note that if we didn't have a current context, the spec says we're 1.295 + // supposed to just return transparent black pixels of the canvas 1.296 + // dimensions. 1.297 + nsRefPtr<gfxImageSurface> emptyCanvas = 1.298 + new gfxImageSurface(gfxIntSize(aSize.width, aSize.height), 1.299 + gfxImageFormat::ARGB32); 1.300 + if (emptyCanvas->CairoStatus()) { 1.301 + return NS_ERROR_INVALID_ARG; 1.302 + } 1.303 + if (aUsePlaceholder) { 1.304 + // If placeholder data was requested, return all-white, opaque image data. 1.305 + int32_t dataSize = emptyCanvas->GetDataSize(); 1.306 + memset(emptyCanvas->Data(), 0xFF, dataSize); 1.307 + } 1.308 + rv = aEncoder->InitFromData(emptyCanvas->Data(), 1.309 + aSize.width * aSize.height * 4, 1.310 + aSize.width, 1.311 + aSize.height, 1.312 + aSize.width * 4, 1.313 + imgIEncoder::INPUT_FORMAT_HOSTARGB, 1.314 + aOptions); 1.315 + if (NS_SUCCEEDED(rv)) { 1.316 + imgStream = do_QueryInterface(aEncoder); 1.317 + } 1.318 + } 1.319 + NS_ENSURE_SUCCESS(rv, rv); 1.320 + 1.321 + imgStream.forget(aStream); 1.322 + return rv; 1.323 +} 1.324 + 1.325 +/* static */ 1.326 +already_AddRefed<imgIEncoder> 1.327 +ImageEncoder::GetImageEncoder(nsAString& aType) 1.328 +{ 1.329 + // Get an image encoder for the media type. 1.330 + nsCString encoderCID("@mozilla.org/image/encoder;2?type="); 1.331 + NS_ConvertUTF16toUTF8 encoderType(aType); 1.332 + encoderCID += encoderType; 1.333 + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get()); 1.334 + 1.335 + if (!encoder && aType != NS_LITERAL_STRING("image/png")) { 1.336 + // Unable to create an encoder instance of the specified type. Falling back 1.337 + // to PNG. 1.338 + aType.AssignLiteral("image/png"); 1.339 + nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png"); 1.340 + encoder = do_CreateInstance(PNGEncoderCID.get()); 1.341 + } 1.342 + 1.343 + return encoder.forget(); 1.344 +} 1.345 + 1.346 +} // namespace dom 1.347 +} // namespace mozilla