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 "nsIDocument.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 "jsfriendapi.h"
33 using namespace mozilla::gfx
;
40 class ImageDecoderHelper final
: public Runnable
41 , public nsIInputStreamCallback
44 NS_DECL_ISUPPORTS_INHERITED
46 ImageDecoderHelper(already_AddRefed
<image::Image
> aImage
,
47 already_AddRefed
<nsIInputStream
> aInputStream
,
48 nsIEventTarget
* aEventTarget
,
49 imgIContainerCallback
* aCallback
,
50 nsIEventTarget
* aCallbackEventTarget
)
51 : Runnable("ImageDecoderHelper")
52 , mImage(std::move(aImage
))
53 , mInputStream(std::move(aInputStream
))
54 , mEventTarget(aEventTarget
)
55 , mCallback(aCallback
)
56 , mCallbackEventTarget(aCallbackEventTarget
)
59 MOZ_ASSERT(NS_IsMainThread());
65 // This runnable is dispatched on the Image thread when reading data, but
66 // at the end, it goes back to the main-thread in order to complete the
68 if (NS_IsMainThread()) {
69 // Let the Image know we've sent all the data.
70 mImage
->OnImageDataComplete(nullptr, nullptr, mStatus
, true);
72 RefPtr
<ProgressTracker
> tracker
= mImage
->GetProgressTracker();
73 tracker
->SyncNotifyProgress(FLAG_LOAD_COMPLETE
);
75 nsCOMPtr
<imgIContainer
> container
;
76 if (NS_SUCCEEDED(mStatus
)) {
77 container
= do_QueryInterface(mImage
);
80 mCallback
->OnImageReady(container
, mStatus
);
85 nsresult rv
= mInputStream
->Available(&length
);
86 if (rv
== NS_BASE_STREAM_CLOSED
) {
87 return OperationCompleted(NS_OK
);
90 if (NS_WARN_IF(NS_FAILED(rv
))) {
91 return OperationCompleted(rv
);
94 // Nothing else to read, but maybe we just need to wait.
96 nsCOMPtr
<nsIAsyncInputStream
> asyncInputStream
=
97 do_QueryInterface(mInputStream
);
98 if (asyncInputStream
) {
99 rv
= asyncInputStream
->AsyncWait(this, 0, 0, mEventTarget
);
100 if (NS_WARN_IF(NS_FAILED(rv
))) {
101 return OperationCompleted(rv
);
106 // We really have nothing else to read.
108 return OperationCompleted(NS_OK
);
112 // Send the source data to the Image.
113 rv
= mImage
->OnImageDataAvailable(nullptr, nullptr, mInputStream
, 0,
115 if (NS_WARN_IF(NS_FAILED(rv
))) {
116 return OperationCompleted(rv
);
119 rv
= mEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
120 if (NS_WARN_IF(NS_FAILED(rv
))) {
121 return OperationCompleted(rv
);
128 OnInputStreamReady(nsIAsyncInputStream
* aAsyncInputStream
) override
130 MOZ_ASSERT(!NS_IsMainThread());
135 OperationCompleted(nsresult aStatus
)
137 MOZ_ASSERT(!NS_IsMainThread());
140 mCallbackEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
145 ~ImageDecoderHelper()
147 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mImage",
149 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mCallback",
153 RefPtr
<image::Image
> mImage
;
155 nsCOMPtr
<nsIInputStream
> mInputStream
;
156 nsCOMPtr
<nsIEventTarget
> mEventTarget
;
157 nsCOMPtr
<imgIContainerCallback
> mCallback
;
158 nsCOMPtr
<nsIEventTarget
> mCallbackEventTarget
;
163 NS_IMPL_ISUPPORTS_INHERITED(ImageDecoderHelper
, Runnable
,
164 nsIInputStreamCallback
)
168 /* ========== imgITools implementation ========== */
172 NS_IMPL_ISUPPORTS(imgTools
, imgITools
)
176 /* member initializers and constructor code */
179 imgTools::~imgTools()
181 /* destructor code */
185 imgTools::DecodeImageFromArrayBuffer(JS::HandleValue aArrayBuffer
,
186 const nsACString
& aMimeType
,
188 imgIContainer
** aContainer
)
190 if (!aArrayBuffer
.isObject()) {
191 return NS_ERROR_FAILURE
;
194 JS::Rooted
<JSObject
*> obj(aCx
,
195 js::UnwrapArrayBuffer(&aArrayBuffer
.toObject()));
197 return NS_ERROR_FAILURE
;
200 uint8_t* bufferData
= nullptr;
201 uint32_t bufferLength
= 0;
202 bool isSharedMemory
= false;
204 js::GetArrayBufferLengthAndData(obj
, &bufferLength
, &isSharedMemory
,
206 return DecodeImageFromBuffer((char*)bufferData
, bufferLength
, aMimeType
,
211 imgTools::DecodeImageFromBuffer(const char* aBuffer
, uint32_t aSize
,
212 const nsACString
& aMimeType
,
213 imgIContainer
** aContainer
)
215 MOZ_ASSERT(NS_IsMainThread());
217 NS_ENSURE_ARG_POINTER(aBuffer
);
219 // Create a new image container to hold the decoded data.
220 nsAutoCString
mimeType(aMimeType
);
221 RefPtr
<image::Image
> image
=
222 ImageFactory::CreateAnonymousImage(mimeType
, aSize
);
223 RefPtr
<ProgressTracker
> tracker
= image
->GetProgressTracker();
225 if (image
->HasError()) {
226 return NS_ERROR_FAILURE
;
229 // Let's create a temporary inputStream.
230 nsCOMPtr
<nsIInputStream
> stream
;
231 nsresult rv
= NS_NewByteInputStream(getter_AddRefs(stream
),
233 NS_ASSIGNMENT_DEPEND
);
234 NS_ENSURE_SUCCESS(rv
, rv
);
236 MOZ_ASSERT(NS_InputStreamIsBuffered(stream
));
238 rv
= image
->OnImageDataAvailable(nullptr, nullptr, stream
, 0,
240 NS_ENSURE_SUCCESS(rv
, rv
);
242 // Let the Image know we've sent all the data.
243 rv
= image
->OnImageDataComplete(nullptr, nullptr, NS_OK
, true);
244 tracker
->SyncNotifyProgress(FLAG_LOAD_COMPLETE
);
245 NS_ENSURE_SUCCESS(rv
, rv
);
248 image
.forget(aContainer
);
253 imgTools::DecodeImageAsync(nsIInputStream
* aInStr
,
254 const nsACString
& aMimeType
,
255 imgIContainerCallback
* aCallback
,
256 nsIEventTarget
* aEventTarget
)
258 MOZ_ASSERT(NS_IsMainThread());
260 NS_ENSURE_ARG_POINTER(aInStr
);
261 NS_ENSURE_ARG_POINTER(aCallback
);
262 NS_ENSURE_ARG_POINTER(aEventTarget
);
266 // Let's continuing the reading on a separate thread.
267 DecodePool
* decodePool
= DecodePool::Singleton();
268 MOZ_ASSERT(decodePool
);
270 RefPtr
<nsIEventTarget
> target
= decodePool
->GetIOEventTarget();
271 NS_ENSURE_TRUE(target
, NS_ERROR_FAILURE
);
273 // Prepare the input stream.
274 nsCOMPtr
<nsIInputStream
> stream
= aInStr
;
275 if (!NS_InputStreamIsBuffered(aInStr
)) {
276 nsCOMPtr
<nsIInputStream
> bufStream
;
277 rv
= NS_NewBufferedInputStream(getter_AddRefs(bufStream
),
278 stream
.forget(), 1024);
279 NS_ENSURE_SUCCESS(rv
, rv
);
280 stream
= bufStream
.forget();
283 // Create a new image container to hold the decoded data.
284 nsAutoCString
mimeType(aMimeType
);
285 RefPtr
<image::Image
> image
= ImageFactory::CreateAnonymousImage(mimeType
, 0);
288 if (image
->HasError()) {
289 return NS_ERROR_FAILURE
;
292 RefPtr
<ImageDecoderHelper
> helper
=
293 new ImageDecoderHelper(image
.forget(), stream
.forget(), target
, aCallback
,
295 rv
= target
->Dispatch(helper
.forget(), NS_DISPATCH_NORMAL
);
296 NS_ENSURE_SUCCESS(rv
, rv
);
302 * This takes a DataSourceSurface rather than a SourceSurface because some
303 * of the callers have a DataSourceSurface and we don't want to call
304 * GetDataSurface on such surfaces since that may incure a conversion to
305 * SurfaceType::DATA which we don't need.
308 EncodeImageData(DataSourceSurface
* aDataSurface
,
309 DataSourceSurface::ScopedMap
& aMap
,
310 const nsACString
& aMimeType
,
311 const nsAString
& aOutputOptions
,
312 nsIInputStream
** aStream
)
314 MOZ_ASSERT(aDataSurface
->GetFormat() == SurfaceFormat::B8G8R8A8
||
315 aDataSurface
->GetFormat() == SurfaceFormat::B8G8R8X8
,
316 "We're assuming B8G8R8A8/X8");
318 // Get an image encoder for the media type
319 nsAutoCString
encoderCID(
320 NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType
);
322 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(encoderCID
.get());
324 return NS_IMAGELIB_ERROR_NO_ENCODER
;
327 IntSize size
= aDataSurface
->GetSize();
328 uint32_t dataLength
= aMap
.GetStride() * size
.height
;
331 nsresult rv
= encoder
->InitFromData(aMap
.GetData(),
336 imgIEncoder::INPUT_FORMAT_HOSTARGB
,
338 NS_ENSURE_SUCCESS(rv
, rv
);
340 encoder
.forget(aStream
);
345 EncodeImageData(DataSourceSurface
* aDataSurface
,
346 const nsACString
& aMimeType
,
347 const nsAString
& aOutputOptions
,
348 nsIInputStream
** aStream
)
350 DataSourceSurface::ScopedMap
map(aDataSurface
, DataSourceSurface::READ
);
351 if (!map
.IsMapped()) {
352 return NS_ERROR_FAILURE
;
355 return EncodeImageData(aDataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
359 imgTools::EncodeImage(imgIContainer
* aContainer
,
360 const nsACString
& aMimeType
,
361 const nsAString
& aOutputOptions
,
362 nsIInputStream
** aStream
)
364 // Use frame 0 from the image container.
365 RefPtr
<SourceSurface
> frame
=
366 aContainer
->GetFrame(imgIContainer::FRAME_FIRST
,
367 imgIContainer::FLAG_SYNC_DECODE
);
368 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
370 RefPtr
<DataSourceSurface
> dataSurface
;
372 if (frame
->GetFormat() == SurfaceFormat::B8G8R8A8
||
373 frame
->GetFormat() == SurfaceFormat::B8G8R8X8
) {
374 dataSurface
= frame
->GetDataSurface();
376 // Convert format to SurfaceFormat::B8G8R8A8
377 dataSurface
= gfxUtils::
378 CopySurfaceToDataSourceSurfaceWithFormat(frame
,
379 SurfaceFormat::B8G8R8A8
);
382 NS_ENSURE_TRUE(dataSurface
, NS_ERROR_FAILURE
);
384 return EncodeImageData(dataSurface
, aMimeType
, aOutputOptions
, aStream
);
388 imgTools::EncodeScaledImage(imgIContainer
* aContainer
,
389 const nsACString
& aMimeType
,
390 int32_t aScaledWidth
,
391 int32_t aScaledHeight
,
392 const nsAString
& aOutputOptions
,
393 nsIInputStream
** aStream
)
395 NS_ENSURE_ARG(aScaledWidth
>= 0 && aScaledHeight
>= 0);
397 // If no scaled size is specified, we'll just encode the image at its
398 // original size (no scaling).
399 if (aScaledWidth
== 0 && aScaledHeight
== 0) {
400 return EncodeImage(aContainer
, aMimeType
, aOutputOptions
, aStream
);
403 // Retrieve the image's size.
404 int32_t imageWidth
= 0;
405 int32_t imageHeight
= 0;
406 aContainer
->GetWidth(&imageWidth
);
407 aContainer
->GetHeight(&imageHeight
);
409 // If the given width or height is zero we'll replace it with the image's
410 // original dimensions.
411 IntSize
scaledSize(aScaledWidth
== 0 ? imageWidth
: aScaledWidth
,
412 aScaledHeight
== 0 ? imageHeight
: aScaledHeight
);
414 // Use frame 0 from the image container.
415 RefPtr
<SourceSurface
> frame
=
416 aContainer
->GetFrameAtSize(scaledSize
,
417 imgIContainer::FRAME_FIRST
,
418 imgIContainer::FLAG_HIGH_QUALITY_SCALING
|
419 imgIContainer::FLAG_SYNC_DECODE
);
420 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
422 // If the given surface is the right size/format, we can encode it directly.
423 if (scaledSize
== frame
->GetSize() &&
424 (frame
->GetFormat() == SurfaceFormat::B8G8R8A8
||
425 frame
->GetFormat() == SurfaceFormat::B8G8R8X8
)) {
426 RefPtr
<DataSourceSurface
> dataSurface
= frame
->GetDataSurface();
428 return EncodeImageData(dataSurface
, aMimeType
, aOutputOptions
, aStream
);
432 // Otherwise we need to scale it using a draw target.
433 RefPtr
<DataSourceSurface
> dataSurface
=
434 Factory::CreateDataSourceSurface(scaledSize
, SurfaceFormat::B8G8R8A8
);
435 if (NS_WARN_IF(!dataSurface
)) {
436 return NS_ERROR_FAILURE
;
439 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ_WRITE
);
440 if (!map
.IsMapped()) {
441 return NS_ERROR_FAILURE
;
444 RefPtr
<DrawTarget
> dt
=
445 Factory::CreateDrawTargetForData(BackendType::SKIA
,
447 dataSurface
->GetSize(),
449 SurfaceFormat::B8G8R8A8
);
451 gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
452 return NS_ERROR_OUT_OF_MEMORY
;
455 IntSize frameSize
= frame
->GetSize();
456 dt
->DrawSurface(frame
,
457 Rect(0, 0, scaledSize
.width
, scaledSize
.height
),
458 Rect(0, 0, frameSize
.width
, frameSize
.height
),
459 DrawSurfaceOptions(),
460 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
462 return EncodeImageData(dataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
466 imgTools::EncodeCroppedImage(imgIContainer
* aContainer
,
467 const nsACString
& aMimeType
,
472 const nsAString
& aOutputOptions
,
473 nsIInputStream
** aStream
)
475 NS_ENSURE_ARG(aOffsetX
>= 0 && aOffsetY
>= 0 && aWidth
>= 0 && aHeight
>= 0);
477 // Offsets must be zero when no width and height are given or else we're out
479 NS_ENSURE_ARG(aWidth
+ aHeight
> 0 || aOffsetX
+ aOffsetY
== 0);
481 // If no size is specified then we'll preserve the image's original dimensions
482 // and don't need to crop.
483 if (aWidth
== 0 && aHeight
== 0) {
484 return EncodeImage(aContainer
, aMimeType
, aOutputOptions
, aStream
);
487 // Use frame 0 from the image container.
488 RefPtr
<SourceSurface
> frame
=
489 aContainer
->GetFrame(imgIContainer::FRAME_FIRST
,
490 imgIContainer::FLAG_SYNC_DECODE
);
491 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
493 int32_t frameWidth
= frame
->GetSize().width
;
494 int32_t frameHeight
= frame
->GetSize().height
;
496 // If the given width or height is zero we'll replace it with the image's
497 // original dimensions.
500 } else if (aHeight
== 0) {
501 aHeight
= frameHeight
;
504 // Check that the given crop rectangle is within image bounds.
505 NS_ENSURE_ARG(frameWidth
>= aOffsetX
+ aWidth
&&
506 frameHeight
>= aOffsetY
+ aHeight
);
508 RefPtr
<DataSourceSurface
> dataSurface
=
509 Factory::CreateDataSourceSurface(IntSize(aWidth
, aHeight
),
510 SurfaceFormat::B8G8R8A8
,
512 if (NS_WARN_IF(!dataSurface
)) {
513 return NS_ERROR_FAILURE
;
516 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ_WRITE
);
517 if (!map
.IsMapped()) {
518 return NS_ERROR_FAILURE
;
521 RefPtr
<DrawTarget
> dt
=
522 Factory::CreateDrawTargetForData(BackendType::SKIA
,
524 dataSurface
->GetSize(),
526 SurfaceFormat::B8G8R8A8
);
529 "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
530 return NS_ERROR_OUT_OF_MEMORY
;
532 dt
->CopySurface(frame
,
533 IntRect(aOffsetX
, aOffsetY
, aWidth
, aHeight
),
536 return EncodeImageData(dataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
540 imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver
* aInner
,
541 imgINotificationObserver
** aObserver
)
543 NS_ADDREF(*aObserver
= new ScriptedNotificationObserver(aInner
));
548 imgTools::GetImgLoaderForDocument(nsIDocument
* aDoc
, imgILoader
** aLoader
)
550 NS_IF_ADDREF(*aLoader
= nsContentUtils::GetImgLoaderForDocument(aDoc
));
555 imgTools::GetImgCacheForDocument(nsIDocument
* aDoc
, imgICache
** aCache
)
557 nsCOMPtr
<imgILoader
> loader
;
558 nsresult rv
= GetImgLoaderForDocument(aDoc
, getter_AddRefs(loader
));
559 NS_ENSURE_SUCCESS(rv
, rv
);
560 return CallQueryInterface(loader
, aCache
);
564 } // namespace mozilla