|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "gfxImageSurface.h" |
|
7 #include "ImageEncoder.h" |
|
8 #include "mozilla/dom/CanvasRenderingContext2D.h" |
|
9 |
|
10 namespace mozilla { |
|
11 namespace dom { |
|
12 |
|
13 class EncodingCompleteEvent : public nsRunnable |
|
14 { |
|
15 public: |
|
16 NS_DECL_THREADSAFE_ISUPPORTS |
|
17 |
|
18 EncodingCompleteEvent(nsIScriptContext* aScriptContext, |
|
19 nsIThread* aEncoderThread, |
|
20 FileCallback& aCallback) |
|
21 : mImgSize(0) |
|
22 , mType() |
|
23 , mImgData(nullptr) |
|
24 , mScriptContext(aScriptContext) |
|
25 , mEncoderThread(aEncoderThread) |
|
26 , mCallback(&aCallback) |
|
27 , mFailed(false) |
|
28 {} |
|
29 virtual ~EncodingCompleteEvent() {} |
|
30 |
|
31 NS_IMETHOD Run() |
|
32 { |
|
33 MOZ_ASSERT(NS_IsMainThread()); |
|
34 |
|
35 mozilla::ErrorResult rv; |
|
36 |
|
37 if (!mFailed) { |
|
38 nsRefPtr<nsDOMMemoryFile> blob = |
|
39 new nsDOMMemoryFile(mImgData, mImgSize, mType); |
|
40 |
|
41 if (mScriptContext) { |
|
42 JSContext* jsContext = mScriptContext->GetNativeContext(); |
|
43 if (jsContext) { |
|
44 JS_updateMallocCounter(jsContext, mImgSize); |
|
45 } |
|
46 } |
|
47 |
|
48 mCallback->Call(blob, rv); |
|
49 } |
|
50 |
|
51 // These members aren't thread-safe. We're making sure that they're being |
|
52 // released on the main thread here. Otherwise, they could be getting |
|
53 // released by EncodingRunnable's destructor on the encoding thread |
|
54 // (bug 916128). |
|
55 mScriptContext = nullptr; |
|
56 mCallback = nullptr; |
|
57 |
|
58 mEncoderThread->Shutdown(); |
|
59 return rv.ErrorCode(); |
|
60 } |
|
61 |
|
62 void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType) |
|
63 { |
|
64 mImgData = aImgData; |
|
65 mImgSize = aImgSize; |
|
66 mType = aType; |
|
67 } |
|
68 |
|
69 void SetFailed() |
|
70 { |
|
71 mFailed = true; |
|
72 } |
|
73 |
|
74 private: |
|
75 uint64_t mImgSize; |
|
76 nsAutoString mType; |
|
77 void* mImgData; |
|
78 nsCOMPtr<nsIScriptContext> mScriptContext; |
|
79 nsCOMPtr<nsIThread> mEncoderThread; |
|
80 nsRefPtr<FileCallback> mCallback; |
|
81 bool mFailed; |
|
82 }; |
|
83 |
|
84 NS_IMPL_ISUPPORTS(EncodingCompleteEvent, nsIRunnable); |
|
85 |
|
86 class EncodingRunnable : public nsRunnable |
|
87 { |
|
88 public: |
|
89 NS_DECL_THREADSAFE_ISUPPORTS |
|
90 |
|
91 EncodingRunnable(const nsAString& aType, |
|
92 const nsAString& aOptions, |
|
93 uint8_t* aImageBuffer, |
|
94 imgIEncoder* aEncoder, |
|
95 EncodingCompleteEvent* aEncodingCompleteEvent, |
|
96 int32_t aFormat, |
|
97 const nsIntSize aSize, |
|
98 bool aUsePlaceholder, |
|
99 bool aUsingCustomOptions) |
|
100 : mType(aType) |
|
101 , mOptions(aOptions) |
|
102 , mImageBuffer(aImageBuffer) |
|
103 , mEncoder(aEncoder) |
|
104 , mEncodingCompleteEvent(aEncodingCompleteEvent) |
|
105 , mFormat(aFormat) |
|
106 , mSize(aSize) |
|
107 , mUsePlaceholder(aUsePlaceholder) |
|
108 , mUsingCustomOptions(aUsingCustomOptions) |
|
109 {} |
|
110 virtual ~EncodingRunnable() {} |
|
111 |
|
112 nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData) |
|
113 { |
|
114 nsCOMPtr<nsIInputStream> stream; |
|
115 nsresult rv = ImageEncoder::ExtractDataInternal(mType, |
|
116 mOptions, |
|
117 mImageBuffer, |
|
118 mFormat, |
|
119 mSize, |
|
120 mUsePlaceholder, |
|
121 nullptr, |
|
122 getter_AddRefs(stream), |
|
123 mEncoder); |
|
124 |
|
125 // If there are unrecognized custom parse options, we should fall back to |
|
126 // the default values for the encoder without any options at all. |
|
127 if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) { |
|
128 rv = ImageEncoder::ExtractDataInternal(mType, |
|
129 EmptyString(), |
|
130 mImageBuffer, |
|
131 mFormat, |
|
132 mSize, |
|
133 mUsePlaceholder, |
|
134 nullptr, |
|
135 getter_AddRefs(stream), |
|
136 mEncoder); |
|
137 } |
|
138 NS_ENSURE_SUCCESS(rv, rv); |
|
139 |
|
140 rv = stream->Available(aImgSize); |
|
141 NS_ENSURE_SUCCESS(rv, rv); |
|
142 NS_ENSURE_TRUE(*aImgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); |
|
143 |
|
144 rv = NS_ReadInputStreamToBuffer(stream, aImgData, *aImgSize); |
|
145 NS_ENSURE_SUCCESS(rv, rv); |
|
146 |
|
147 return rv; |
|
148 } |
|
149 |
|
150 NS_IMETHOD Run() |
|
151 { |
|
152 uint64_t imgSize; |
|
153 void* imgData = nullptr; |
|
154 |
|
155 nsresult rv = ProcessImageData(&imgSize, &imgData); |
|
156 if (NS_FAILED(rv)) { |
|
157 mEncodingCompleteEvent->SetFailed(); |
|
158 } else { |
|
159 mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType); |
|
160 } |
|
161 rv = NS_DispatchToMainThread(mEncodingCompleteEvent, NS_DISPATCH_NORMAL); |
|
162 if (NS_FAILED(rv)) { |
|
163 // Better to leak than to crash. |
|
164 mEncodingCompleteEvent.forget(); |
|
165 return rv; |
|
166 } |
|
167 |
|
168 return rv; |
|
169 } |
|
170 |
|
171 private: |
|
172 nsAutoString mType; |
|
173 nsAutoString mOptions; |
|
174 nsAutoArrayPtr<uint8_t> mImageBuffer; |
|
175 nsCOMPtr<imgIEncoder> mEncoder; |
|
176 nsRefPtr<EncodingCompleteEvent> mEncodingCompleteEvent; |
|
177 int32_t mFormat; |
|
178 const nsIntSize mSize; |
|
179 bool mUsePlaceholder; |
|
180 bool mUsingCustomOptions; |
|
181 }; |
|
182 |
|
183 NS_IMPL_ISUPPORTS(EncodingRunnable, nsIRunnable) |
|
184 |
|
185 /* static */ |
|
186 nsresult |
|
187 ImageEncoder::ExtractData(nsAString& aType, |
|
188 const nsAString& aOptions, |
|
189 const nsIntSize aSize, |
|
190 bool aUsePlaceholder, |
|
191 nsICanvasRenderingContextInternal* aContext, |
|
192 nsIInputStream** aStream) |
|
193 { |
|
194 nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType); |
|
195 if (!encoder) { |
|
196 return NS_IMAGELIB_ERROR_NO_ENCODER; |
|
197 } |
|
198 |
|
199 return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, |
|
200 aUsePlaceholder, aContext, aStream, encoder); |
|
201 } |
|
202 |
|
203 /* static */ |
|
204 nsresult |
|
205 ImageEncoder::ExtractDataAsync(nsAString& aType, |
|
206 const nsAString& aOptions, |
|
207 bool aUsingCustomOptions, |
|
208 uint8_t* aImageBuffer, |
|
209 int32_t aFormat, |
|
210 const nsIntSize aSize, |
|
211 bool aUsePlaceholder, |
|
212 nsICanvasRenderingContextInternal* aContext, |
|
213 nsIScriptContext* aScriptContext, |
|
214 FileCallback& aCallback) |
|
215 { |
|
216 nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType); |
|
217 if (!encoder) { |
|
218 return NS_IMAGELIB_ERROR_NO_ENCODER; |
|
219 } |
|
220 |
|
221 nsCOMPtr<nsIThread> encoderThread; |
|
222 nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr); |
|
223 NS_ENSURE_SUCCESS(rv, rv); |
|
224 |
|
225 nsRefPtr<EncodingCompleteEvent> completeEvent = |
|
226 new EncodingCompleteEvent(aScriptContext, encoderThread, aCallback); |
|
227 |
|
228 nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType, |
|
229 aOptions, |
|
230 aImageBuffer, |
|
231 encoder, |
|
232 completeEvent, |
|
233 aFormat, |
|
234 aSize, |
|
235 aUsePlaceholder, |
|
236 aUsingCustomOptions); |
|
237 return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL); |
|
238 } |
|
239 |
|
240 /*static*/ nsresult |
|
241 ImageEncoder::GetInputStream(int32_t aWidth, |
|
242 int32_t aHeight, |
|
243 uint8_t* aImageBuffer, |
|
244 int32_t aFormat, |
|
245 imgIEncoder* aEncoder, |
|
246 const char16_t* aEncoderOptions, |
|
247 nsIInputStream** aStream) |
|
248 { |
|
249 nsresult rv = |
|
250 aEncoder->InitFromData(aImageBuffer, |
|
251 aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4, |
|
252 aFormat, |
|
253 nsDependentString(aEncoderOptions)); |
|
254 NS_ENSURE_SUCCESS(rv, rv); |
|
255 |
|
256 return CallQueryInterface(aEncoder, aStream); |
|
257 } |
|
258 |
|
259 /* static */ |
|
260 nsresult |
|
261 ImageEncoder::ExtractDataInternal(const nsAString& aType, |
|
262 const nsAString& aOptions, |
|
263 uint8_t* aImageBuffer, |
|
264 int32_t aFormat, |
|
265 const nsIntSize aSize, |
|
266 bool aUsePlaceholder, |
|
267 nsICanvasRenderingContextInternal* aContext, |
|
268 nsIInputStream** aStream, |
|
269 imgIEncoder* aEncoder) |
|
270 { |
|
271 nsCOMPtr<nsIInputStream> imgStream; |
|
272 |
|
273 // get image bytes |
|
274 nsresult rv; |
|
275 if (aImageBuffer && !aUsePlaceholder) { |
|
276 rv = ImageEncoder::GetInputStream( |
|
277 aSize.width, |
|
278 aSize.height, |
|
279 aImageBuffer, |
|
280 aFormat, |
|
281 aEncoder, |
|
282 nsPromiseFlatString(aOptions).get(), |
|
283 getter_AddRefs(imgStream)); |
|
284 } else if (aContext && !aUsePlaceholder) { |
|
285 NS_ConvertUTF16toUTF8 encoderType(aType); |
|
286 rv = aContext->GetInputStream(encoderType.get(), |
|
287 nsPromiseFlatString(aOptions).get(), |
|
288 getter_AddRefs(imgStream)); |
|
289 } else { |
|
290 // If placeholder data requested or no context, encode an empty image. |
|
291 // note that if we didn't have a current context, the spec says we're |
|
292 // supposed to just return transparent black pixels of the canvas |
|
293 // dimensions. |
|
294 nsRefPtr<gfxImageSurface> emptyCanvas = |
|
295 new gfxImageSurface(gfxIntSize(aSize.width, aSize.height), |
|
296 gfxImageFormat::ARGB32); |
|
297 if (emptyCanvas->CairoStatus()) { |
|
298 return NS_ERROR_INVALID_ARG; |
|
299 } |
|
300 if (aUsePlaceholder) { |
|
301 // If placeholder data was requested, return all-white, opaque image data. |
|
302 int32_t dataSize = emptyCanvas->GetDataSize(); |
|
303 memset(emptyCanvas->Data(), 0xFF, dataSize); |
|
304 } |
|
305 rv = aEncoder->InitFromData(emptyCanvas->Data(), |
|
306 aSize.width * aSize.height * 4, |
|
307 aSize.width, |
|
308 aSize.height, |
|
309 aSize.width * 4, |
|
310 imgIEncoder::INPUT_FORMAT_HOSTARGB, |
|
311 aOptions); |
|
312 if (NS_SUCCEEDED(rv)) { |
|
313 imgStream = do_QueryInterface(aEncoder); |
|
314 } |
|
315 } |
|
316 NS_ENSURE_SUCCESS(rv, rv); |
|
317 |
|
318 imgStream.forget(aStream); |
|
319 return rv; |
|
320 } |
|
321 |
|
322 /* static */ |
|
323 already_AddRefed<imgIEncoder> |
|
324 ImageEncoder::GetImageEncoder(nsAString& aType) |
|
325 { |
|
326 // Get an image encoder for the media type. |
|
327 nsCString encoderCID("@mozilla.org/image/encoder;2?type="); |
|
328 NS_ConvertUTF16toUTF8 encoderType(aType); |
|
329 encoderCID += encoderType; |
|
330 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get()); |
|
331 |
|
332 if (!encoder && aType != NS_LITERAL_STRING("image/png")) { |
|
333 // Unable to create an encoder instance of the specified type. Falling back |
|
334 // to PNG. |
|
335 aType.AssignLiteral("image/png"); |
|
336 nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png"); |
|
337 encoder = do_CreateInstance(PNGEncoderCID.get()); |
|
338 } |
|
339 |
|
340 return encoder.forget(); |
|
341 } |
|
342 |
|
343 } // namespace dom |
|
344 } // namespace mozilla |