1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "DecodePool.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/Logging.h"
13 #include "mozilla/RefPtr.h"
15 #include "mozilla/dom/Document.h"
17 #include "imgLoader.h"
18 #include "imgICache.h"
19 #include "imgIContainer.h"
20 #include "imgIEncoder.h"
21 #include "nsNetUtil.h" // for NS_NewBufferedInputStream
22 #include "nsStreamUtils.h"
23 #include "nsStringStream.h"
24 #include "nsContentUtils.h"
25 #include "nsProxyRelease.h"
26 #include "ImageFactory.h"
28 #include "ScriptedNotificationObserver.h"
29 #include "imgIScriptedNotificationObserver.h"
30 #include "gfxPlatform.h"
31 #include "js/ArrayBuffer.h"
32 #include "js/RootingAPI.h" // JS::{Handle,Rooted}
33 #include "js/Value.h" // JS::Value
35 using namespace mozilla::gfx
;
42 class ImageDecoderHelper final
: public Runnable
,
43 public nsIInputStreamCallback
{
45 NS_DECL_ISUPPORTS_INHERITED
47 ImageDecoderHelper(already_AddRefed
<image::Image
> aImage
,
48 already_AddRefed
<nsIInputStream
> aInputStream
,
49 nsIEventTarget
* aEventTarget
,
50 imgIContainerCallback
* aCallback
,
51 nsIEventTarget
* aCallbackEventTarget
)
52 : Runnable("ImageDecoderHelper"),
53 mImage(std::move(aImage
)),
54 mInputStream(std::move(aInputStream
)),
55 mEventTarget(aEventTarget
),
57 mCallbackEventTarget(aCallbackEventTarget
),
59 MOZ_ASSERT(NS_IsMainThread());
64 // This runnable is dispatched on the Image thread when reading data, but
65 // at the end, it goes back to the main-thread in order to complete the
67 if (NS_IsMainThread()) {
68 // Let the Image know we've sent all the data.
69 mImage
->OnImageDataComplete(nullptr, nullptr, mStatus
, true);
71 RefPtr
<ProgressTracker
> tracker
= mImage
->GetProgressTracker();
72 tracker
->SyncNotifyProgress(FLAG_LOAD_COMPLETE
);
74 nsCOMPtr
<imgIContainer
> container
;
75 if (NS_SUCCEEDED(mStatus
)) {
79 mCallback
->OnImageReady(container
, mStatus
);
84 nsresult rv
= mInputStream
->Available(&length
);
85 if (rv
== NS_BASE_STREAM_CLOSED
) {
86 return OperationCompleted(NS_OK
);
89 if (NS_WARN_IF(NS_FAILED(rv
))) {
90 return OperationCompleted(rv
);
93 // Nothing else to read, but maybe we just need to wait.
95 nsCOMPtr
<nsIAsyncInputStream
> asyncInputStream
=
96 do_QueryInterface(mInputStream
);
97 if (asyncInputStream
) {
98 rv
= asyncInputStream
->AsyncWait(this, 0, 0, mEventTarget
);
99 if (NS_WARN_IF(NS_FAILED(rv
))) {
100 return OperationCompleted(rv
);
105 // We really have nothing else to read.
107 return OperationCompleted(NS_OK
);
111 // Send the source data to the Image.
112 rv
= mImage
->OnImageDataAvailable(nullptr, nullptr, mInputStream
, 0,
114 if (NS_WARN_IF(NS_FAILED(rv
))) {
115 return OperationCompleted(rv
);
118 rv
= mEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
119 if (NS_WARN_IF(NS_FAILED(rv
))) {
120 return OperationCompleted(rv
);
127 OnInputStreamReady(nsIAsyncInputStream
* aAsyncInputStream
) override
{
128 MOZ_ASSERT(!NS_IsMainThread());
132 nsresult
OperationCompleted(nsresult aStatus
) {
133 MOZ_ASSERT(!NS_IsMainThread());
136 mCallbackEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
141 ~ImageDecoderHelper() {
142 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mImage",
144 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mCallback",
148 RefPtr
<image::Image
> mImage
;
150 nsCOMPtr
<nsIInputStream
> mInputStream
;
151 nsCOMPtr
<nsIEventTarget
> mEventTarget
;
152 nsCOMPtr
<imgIContainerCallback
> mCallback
;
153 nsCOMPtr
<nsIEventTarget
> mCallbackEventTarget
;
158 NS_IMPL_ISUPPORTS_INHERITED(ImageDecoderHelper
, Runnable
,
159 nsIInputStreamCallback
)
163 /* ========== imgITools implementation ========== */
165 NS_IMPL_ISUPPORTS(imgTools
, imgITools
)
167 imgTools::imgTools() { /* member initializers and constructor code */
170 imgTools::~imgTools() { /* destructor code */
174 imgTools::DecodeImageFromArrayBuffer(JS::Handle
<JS::Value
> aArrayBuffer
,
175 const nsACString
& aMimeType
,
177 imgIContainer
** aContainer
) {
178 if (!aArrayBuffer
.isObject()) {
179 return NS_ERROR_FAILURE
;
182 JS::Rooted
<JSObject
*> obj(aCx
,
183 JS::UnwrapArrayBuffer(&aArrayBuffer
.toObject()));
185 return NS_ERROR_FAILURE
;
188 uint8_t* bufferData
= nullptr;
189 uint32_t bufferLength
= 0;
190 bool isSharedMemory
= false;
192 JS::GetArrayBufferLengthAndData(obj
, &bufferLength
, &isSharedMemory
,
194 return DecodeImageFromBuffer((char*)bufferData
, bufferLength
, aMimeType
,
199 imgTools::DecodeImageFromBuffer(const char* aBuffer
, uint32_t aSize
,
200 const nsACString
& aMimeType
,
201 imgIContainer
** aContainer
) {
202 MOZ_ASSERT(NS_IsMainThread());
204 NS_ENSURE_ARG_POINTER(aBuffer
);
206 // Create a new image container to hold the decoded data.
207 nsAutoCString
mimeType(aMimeType
);
208 RefPtr
<image::Image
> image
=
209 ImageFactory::CreateAnonymousImage(mimeType
, aSize
);
210 RefPtr
<ProgressTracker
> tracker
= image
->GetProgressTracker();
212 if (image
->HasError()) {
213 return NS_ERROR_FAILURE
;
216 // Let's create a temporary inputStream.
217 nsCOMPtr
<nsIInputStream
> stream
;
218 nsresult rv
= NS_NewByteInputStream(
219 getter_AddRefs(stream
), MakeSpan(aBuffer
, aSize
), NS_ASSIGNMENT_DEPEND
);
220 NS_ENSURE_SUCCESS(rv
, rv
);
222 MOZ_ASSERT(NS_InputStreamIsBuffered(stream
));
224 rv
= image
->OnImageDataAvailable(nullptr, nullptr, stream
, 0, aSize
);
225 NS_ENSURE_SUCCESS(rv
, rv
);
227 // Let the Image know we've sent all the data.
228 rv
= image
->OnImageDataComplete(nullptr, nullptr, NS_OK
, true);
229 tracker
->SyncNotifyProgress(FLAG_LOAD_COMPLETE
);
230 NS_ENSURE_SUCCESS(rv
, rv
);
233 image
.forget(aContainer
);
238 imgTools::DecodeImageAsync(nsIInputStream
* aInStr
, const nsACString
& aMimeType
,
239 imgIContainerCallback
* aCallback
,
240 nsIEventTarget
* aEventTarget
) {
241 MOZ_ASSERT(NS_IsMainThread());
243 NS_ENSURE_ARG_POINTER(aInStr
);
244 NS_ENSURE_ARG_POINTER(aCallback
);
245 NS_ENSURE_ARG_POINTER(aEventTarget
);
249 // Let's continuing the reading on a separate thread.
250 DecodePool
* decodePool
= DecodePool::Singleton();
251 MOZ_ASSERT(decodePool
);
253 RefPtr
<nsIEventTarget
> target
= decodePool
->GetIOEventTarget();
254 NS_ENSURE_TRUE(target
, NS_ERROR_FAILURE
);
256 // Prepare the input stream.
257 nsCOMPtr
<nsIInputStream
> stream
= aInStr
;
258 if (!NS_InputStreamIsBuffered(aInStr
)) {
259 nsCOMPtr
<nsIInputStream
> bufStream
;
260 rv
= NS_NewBufferedInputStream(getter_AddRefs(bufStream
), stream
.forget(),
262 NS_ENSURE_SUCCESS(rv
, rv
);
263 stream
= bufStream
.forget();
266 // Create a new image container to hold the decoded data.
267 nsAutoCString
mimeType(aMimeType
);
268 RefPtr
<image::Image
> image
= ImageFactory::CreateAnonymousImage(mimeType
, 0);
271 if (image
->HasError()) {
272 return NS_ERROR_FAILURE
;
275 RefPtr
<ImageDecoderHelper
> helper
= new ImageDecoderHelper(
276 image
.forget(), stream
.forget(), target
, aCallback
, aEventTarget
);
277 rv
= target
->Dispatch(helper
.forget(), NS_DISPATCH_NORMAL
);
278 NS_ENSURE_SUCCESS(rv
, rv
);
284 * This takes a DataSourceSurface rather than a SourceSurface because some
285 * of the callers have a DataSourceSurface and we don't want to call
286 * GetDataSurface on such surfaces since that may incur a conversion to
287 * SurfaceType::DATA which we don't need.
289 static nsresult
EncodeImageData(DataSourceSurface
* aDataSurface
,
290 DataSourceSurface::ScopedMap
& aMap
,
291 const nsACString
& aMimeType
,
292 const nsAString
& aOutputOptions
,
293 nsIInputStream
** aStream
) {
294 MOZ_ASSERT(aDataSurface
->GetFormat() == SurfaceFormat::B8G8R8A8
||
295 aDataSurface
->GetFormat() == SurfaceFormat::B8G8R8X8
,
296 "We're assuming B8G8R8A8/X8");
298 // Get an image encoder for the media type
299 nsAutoCString
encoderCID(
300 NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType
);
302 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(encoderCID
.get());
304 return NS_IMAGELIB_ERROR_NO_ENCODER
;
307 IntSize size
= aDataSurface
->GetSize();
308 uint32_t dataLength
= aMap
.GetStride() * size
.height
;
311 nsresult rv
= encoder
->InitFromData(
312 aMap
.GetData(), dataLength
, size
.width
, size
.height
, aMap
.GetStride(),
313 imgIEncoder::INPUT_FORMAT_HOSTARGB
, aOutputOptions
);
314 NS_ENSURE_SUCCESS(rv
, rv
);
316 encoder
.forget(aStream
);
320 static nsresult
EncodeImageData(DataSourceSurface
* aDataSurface
,
321 const nsACString
& aMimeType
,
322 const nsAString
& aOutputOptions
,
323 nsIInputStream
** aStream
) {
324 DataSourceSurface::ScopedMap
map(aDataSurface
, DataSourceSurface::READ
);
325 if (!map
.IsMapped()) {
326 return NS_ERROR_FAILURE
;
329 return EncodeImageData(aDataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
333 imgTools::EncodeImage(imgIContainer
* aContainer
, const nsACString
& aMimeType
,
334 const nsAString
& aOutputOptions
,
335 nsIInputStream
** aStream
) {
336 // Use frame 0 from the image container.
337 RefPtr
<SourceSurface
> frame
= aContainer
->GetFrame(
338 imgIContainer::FRAME_FIRST
,
339 imgIContainer::FLAG_SYNC_DECODE
| imgIContainer::FLAG_ASYNC_NOTIFY
);
340 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
342 RefPtr
<DataSourceSurface
> dataSurface
;
344 if (frame
->GetFormat() == SurfaceFormat::B8G8R8A8
||
345 frame
->GetFormat() == SurfaceFormat::B8G8R8X8
) {
346 dataSurface
= frame
->GetDataSurface();
348 // Convert format to SurfaceFormat::B8G8R8A8
349 dataSurface
= gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
350 frame
, SurfaceFormat::B8G8R8A8
);
353 NS_ENSURE_TRUE(dataSurface
, NS_ERROR_FAILURE
);
355 return EncodeImageData(dataSurface
, aMimeType
, aOutputOptions
, aStream
);
359 imgTools::EncodeScaledImage(imgIContainer
* aContainer
,
360 const nsACString
& aMimeType
, int32_t aScaledWidth
,
361 int32_t aScaledHeight
,
362 const nsAString
& aOutputOptions
,
363 nsIInputStream
** aStream
) {
364 NS_ENSURE_ARG(aScaledWidth
>= 0 && aScaledHeight
>= 0);
366 // If no scaled size is specified, we'll just encode the image at its
367 // original size (no scaling).
368 if (aScaledWidth
== 0 && aScaledHeight
== 0) {
369 return EncodeImage(aContainer
, aMimeType
, aOutputOptions
, aStream
);
372 // Retrieve the image's size.
373 int32_t imageWidth
= 0;
374 int32_t imageHeight
= 0;
375 aContainer
->GetWidth(&imageWidth
);
376 aContainer
->GetHeight(&imageHeight
);
378 // If the given width or height is zero we'll replace it with the image's
379 // original dimensions.
380 IntSize
scaledSize(aScaledWidth
== 0 ? imageWidth
: aScaledWidth
,
381 aScaledHeight
== 0 ? imageHeight
: aScaledHeight
);
383 // Use frame 0 from the image container.
384 RefPtr
<SourceSurface
> frame
= aContainer
->GetFrameAtSize(
385 scaledSize
, imgIContainer::FRAME_FIRST
,
386 imgIContainer::FLAG_HIGH_QUALITY_SCALING
|
387 imgIContainer::FLAG_SYNC_DECODE
| imgIContainer::FLAG_ASYNC_NOTIFY
);
388 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
390 // If the given surface is the right size/format, we can encode it directly.
391 if (scaledSize
== frame
->GetSize() &&
392 (frame
->GetFormat() == SurfaceFormat::B8G8R8A8
||
393 frame
->GetFormat() == SurfaceFormat::B8G8R8X8
)) {
394 RefPtr
<DataSourceSurface
> dataSurface
= frame
->GetDataSurface();
396 return EncodeImageData(dataSurface
, aMimeType
, aOutputOptions
, aStream
);
400 // Otherwise we need to scale it using a draw target.
401 RefPtr
<DataSourceSurface
> dataSurface
=
402 Factory::CreateDataSourceSurface(scaledSize
, SurfaceFormat::B8G8R8A8
);
403 if (NS_WARN_IF(!dataSurface
)) {
404 return NS_ERROR_FAILURE
;
407 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ_WRITE
);
408 if (!map
.IsMapped()) {
409 return NS_ERROR_FAILURE
;
412 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
413 BackendType::SKIA
, map
.GetData(), dataSurface
->GetSize(), map
.GetStride(),
414 SurfaceFormat::B8G8R8A8
);
416 gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
417 return NS_ERROR_OUT_OF_MEMORY
;
420 IntSize frameSize
= frame
->GetSize();
421 dt
->DrawSurface(frame
, Rect(0, 0, scaledSize
.width
, scaledSize
.height
),
422 Rect(0, 0, frameSize
.width
, frameSize
.height
),
423 DrawSurfaceOptions(),
424 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
426 return EncodeImageData(dataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
430 imgTools::EncodeCroppedImage(imgIContainer
* aContainer
,
431 const nsACString
& aMimeType
, int32_t aOffsetX
,
432 int32_t aOffsetY
, int32_t aWidth
, int32_t aHeight
,
433 const nsAString
& aOutputOptions
,
434 nsIInputStream
** aStream
) {
435 NS_ENSURE_ARG(aOffsetX
>= 0 && aOffsetY
>= 0 && aWidth
>= 0 && aHeight
>= 0);
437 // Offsets must be zero when no width and height are given or else we're out
439 NS_ENSURE_ARG(aWidth
+ aHeight
> 0 || aOffsetX
+ aOffsetY
== 0);
441 // If no size is specified then we'll preserve the image's original dimensions
442 // and don't need to crop.
443 if (aWidth
== 0 && aHeight
== 0) {
444 return EncodeImage(aContainer
, aMimeType
, aOutputOptions
, aStream
);
447 // Use frame 0 from the image container.
448 RefPtr
<SourceSurface
> frame
= aContainer
->GetFrame(
449 imgIContainer::FRAME_FIRST
,
450 imgIContainer::FLAG_SYNC_DECODE
| imgIContainer::FLAG_ASYNC_NOTIFY
);
451 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
453 int32_t frameWidth
= frame
->GetSize().width
;
454 int32_t frameHeight
= frame
->GetSize().height
;
456 // If the given width or height is zero we'll replace it with the image's
457 // original dimensions.
460 } else if (aHeight
== 0) {
461 aHeight
= frameHeight
;
464 // Check that the given crop rectangle is within image bounds.
465 NS_ENSURE_ARG(frameWidth
>= aOffsetX
+ aWidth
&&
466 frameHeight
>= aOffsetY
+ aHeight
);
468 RefPtr
<DataSourceSurface
> dataSurface
= Factory::CreateDataSourceSurface(
469 IntSize(aWidth
, aHeight
), SurfaceFormat::B8G8R8A8
,
471 if (NS_WARN_IF(!dataSurface
)) {
472 return NS_ERROR_FAILURE
;
475 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ_WRITE
);
476 if (!map
.IsMapped()) {
477 return NS_ERROR_FAILURE
;
480 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
481 BackendType::SKIA
, map
.GetData(), dataSurface
->GetSize(), map
.GetStride(),
482 SurfaceFormat::B8G8R8A8
);
485 << "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
486 return NS_ERROR_OUT_OF_MEMORY
;
488 dt
->CopySurface(frame
, IntRect(aOffsetX
, aOffsetY
, aWidth
, aHeight
),
491 return EncodeImageData(dataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
495 imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver
* aInner
,
496 imgINotificationObserver
** aObserver
) {
497 NS_ADDREF(*aObserver
= new ScriptedNotificationObserver(aInner
));
502 imgTools::GetImgLoaderForDocument(dom::Document
* aDoc
, imgILoader
** aLoader
) {
503 NS_IF_ADDREF(*aLoader
= nsContentUtils::GetImgLoaderForDocument(aDoc
));
508 imgTools::GetImgCacheForDocument(dom::Document
* aDoc
, imgICache
** aCache
) {
509 nsCOMPtr
<imgILoader
> loader
;
510 nsresult rv
= GetImgLoaderForDocument(aDoc
, getter_AddRefs(loader
));
511 NS_ENSURE_SUCCESS(rv
, rv
);
512 return CallQueryInterface(loader
, aCache
);
516 } // namespace mozilla