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 "jsfriendapi.h"
33 using namespace mozilla::gfx
;
40 class ImageDecoderHelper final
: public Runnable
,
41 public nsIInputStreamCallback
{
43 NS_DECL_ISUPPORTS_INHERITED
45 ImageDecoderHelper(already_AddRefed
<image::Image
> aImage
,
46 already_AddRefed
<nsIInputStream
> aInputStream
,
47 nsIEventTarget
* aEventTarget
,
48 imgIContainerCallback
* aCallback
,
49 nsIEventTarget
* aCallbackEventTarget
)
50 : Runnable("ImageDecoderHelper"),
51 mImage(std::move(aImage
)),
52 mInputStream(std::move(aInputStream
)),
53 mEventTarget(aEventTarget
),
55 mCallbackEventTarget(aCallbackEventTarget
),
57 MOZ_ASSERT(NS_IsMainThread());
62 // This runnable is dispatched on the Image thread when reading data, but
63 // at the end, it goes back to the main-thread in order to complete the
65 if (NS_IsMainThread()) {
66 // Let the Image know we've sent all the data.
67 mImage
->OnImageDataComplete(nullptr, nullptr, mStatus
, true);
69 RefPtr
<ProgressTracker
> tracker
= mImage
->GetProgressTracker();
70 tracker
->SyncNotifyProgress(FLAG_LOAD_COMPLETE
);
72 nsCOMPtr
<imgIContainer
> container
;
73 if (NS_SUCCEEDED(mStatus
)) {
77 mCallback
->OnImageReady(container
, mStatus
);
82 nsresult rv
= mInputStream
->Available(&length
);
83 if (rv
== NS_BASE_STREAM_CLOSED
) {
84 return OperationCompleted(NS_OK
);
87 if (NS_WARN_IF(NS_FAILED(rv
))) {
88 return OperationCompleted(rv
);
91 // Nothing else to read, but maybe we just need to wait.
93 nsCOMPtr
<nsIAsyncInputStream
> asyncInputStream
=
94 do_QueryInterface(mInputStream
);
95 if (asyncInputStream
) {
96 rv
= asyncInputStream
->AsyncWait(this, 0, 0, mEventTarget
);
97 if (NS_WARN_IF(NS_FAILED(rv
))) {
98 return OperationCompleted(rv
);
103 // We really have nothing else to read.
105 return OperationCompleted(NS_OK
);
109 // Send the source data to the Image.
110 rv
= mImage
->OnImageDataAvailable(nullptr, nullptr, mInputStream
, 0,
112 if (NS_WARN_IF(NS_FAILED(rv
))) {
113 return OperationCompleted(rv
);
116 rv
= mEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
117 if (NS_WARN_IF(NS_FAILED(rv
))) {
118 return OperationCompleted(rv
);
125 OnInputStreamReady(nsIAsyncInputStream
* aAsyncInputStream
) override
{
126 MOZ_ASSERT(!NS_IsMainThread());
130 nsresult
OperationCompleted(nsresult aStatus
) {
131 MOZ_ASSERT(!NS_IsMainThread());
134 mCallbackEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
139 ~ImageDecoderHelper() {
140 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mImage",
142 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mCallback",
146 RefPtr
<image::Image
> mImage
;
148 nsCOMPtr
<nsIInputStream
> mInputStream
;
149 nsCOMPtr
<nsIEventTarget
> mEventTarget
;
150 nsCOMPtr
<imgIContainerCallback
> mCallback
;
151 nsCOMPtr
<nsIEventTarget
> mCallbackEventTarget
;
156 NS_IMPL_ISUPPORTS_INHERITED(ImageDecoderHelper
, Runnable
,
157 nsIInputStreamCallback
)
161 /* ========== imgITools implementation ========== */
163 NS_IMPL_ISUPPORTS(imgTools
, imgITools
)
165 imgTools::imgTools() { /* member initializers and constructor code */
168 imgTools::~imgTools() { /* destructor code */
172 imgTools::DecodeImageFromArrayBuffer(JS::HandleValue aArrayBuffer
,
173 const nsACString
& aMimeType
,
175 imgIContainer
** aContainer
) {
176 if (!aArrayBuffer
.isObject()) {
177 return NS_ERROR_FAILURE
;
180 JS::Rooted
<JSObject
*> obj(aCx
,
181 js::UnwrapArrayBuffer(&aArrayBuffer
.toObject()));
183 return NS_ERROR_FAILURE
;
186 uint8_t* bufferData
= nullptr;
187 uint32_t bufferLength
= 0;
188 bool isSharedMemory
= false;
190 js::GetArrayBufferLengthAndData(obj
, &bufferLength
, &isSharedMemory
,
192 return DecodeImageFromBuffer((char*)bufferData
, bufferLength
, aMimeType
,
197 imgTools::DecodeImageFromBuffer(const char* aBuffer
, uint32_t aSize
,
198 const nsACString
& aMimeType
,
199 imgIContainer
** aContainer
) {
200 MOZ_ASSERT(NS_IsMainThread());
202 NS_ENSURE_ARG_POINTER(aBuffer
);
204 // Create a new image container to hold the decoded data.
205 nsAutoCString
mimeType(aMimeType
);
206 RefPtr
<image::Image
> image
=
207 ImageFactory::CreateAnonymousImage(mimeType
, aSize
);
208 RefPtr
<ProgressTracker
> tracker
= image
->GetProgressTracker();
210 if (image
->HasError()) {
211 return NS_ERROR_FAILURE
;
214 // Let's create a temporary inputStream.
215 nsCOMPtr
<nsIInputStream
> stream
;
216 nsresult rv
= NS_NewByteInputStream(getter_AddRefs(stream
), aBuffer
, aSize
,
217 NS_ASSIGNMENT_DEPEND
);
218 NS_ENSURE_SUCCESS(rv
, rv
);
220 MOZ_ASSERT(NS_InputStreamIsBuffered(stream
));
222 rv
= image
->OnImageDataAvailable(nullptr, nullptr, stream
, 0, aSize
);
223 NS_ENSURE_SUCCESS(rv
, rv
);
225 // Let the Image know we've sent all the data.
226 rv
= image
->OnImageDataComplete(nullptr, nullptr, NS_OK
, true);
227 tracker
->SyncNotifyProgress(FLAG_LOAD_COMPLETE
);
228 NS_ENSURE_SUCCESS(rv
, rv
);
231 image
.forget(aContainer
);
236 imgTools::DecodeImageAsync(nsIInputStream
* aInStr
, const nsACString
& aMimeType
,
237 imgIContainerCallback
* aCallback
,
238 nsIEventTarget
* aEventTarget
) {
239 MOZ_ASSERT(NS_IsMainThread());
241 NS_ENSURE_ARG_POINTER(aInStr
);
242 NS_ENSURE_ARG_POINTER(aCallback
);
243 NS_ENSURE_ARG_POINTER(aEventTarget
);
247 // Let's continuing the reading on a separate thread.
248 DecodePool
* decodePool
= DecodePool::Singleton();
249 MOZ_ASSERT(decodePool
);
251 RefPtr
<nsIEventTarget
> target
= decodePool
->GetIOEventTarget();
252 NS_ENSURE_TRUE(target
, NS_ERROR_FAILURE
);
254 // Prepare the input stream.
255 nsCOMPtr
<nsIInputStream
> stream
= aInStr
;
256 if (!NS_InputStreamIsBuffered(aInStr
)) {
257 nsCOMPtr
<nsIInputStream
> bufStream
;
258 rv
= NS_NewBufferedInputStream(getter_AddRefs(bufStream
), stream
.forget(),
260 NS_ENSURE_SUCCESS(rv
, rv
);
261 stream
= bufStream
.forget();
264 // Create a new image container to hold the decoded data.
265 nsAutoCString
mimeType(aMimeType
);
266 RefPtr
<image::Image
> image
= ImageFactory::CreateAnonymousImage(mimeType
, 0);
269 if (image
->HasError()) {
270 return NS_ERROR_FAILURE
;
273 RefPtr
<ImageDecoderHelper
> helper
= new ImageDecoderHelper(
274 image
.forget(), stream
.forget(), target
, aCallback
, aEventTarget
);
275 rv
= target
->Dispatch(helper
.forget(), NS_DISPATCH_NORMAL
);
276 NS_ENSURE_SUCCESS(rv
, rv
);
282 * This takes a DataSourceSurface rather than a SourceSurface because some
283 * of the callers have a DataSourceSurface and we don't want to call
284 * GetDataSurface on such surfaces since that may incure a conversion to
285 * SurfaceType::DATA which we don't need.
287 static nsresult
EncodeImageData(DataSourceSurface
* aDataSurface
,
288 DataSourceSurface::ScopedMap
& aMap
,
289 const nsACString
& aMimeType
,
290 const nsAString
& aOutputOptions
,
291 nsIInputStream
** aStream
) {
292 MOZ_ASSERT(aDataSurface
->GetFormat() == SurfaceFormat::B8G8R8A8
||
293 aDataSurface
->GetFormat() == SurfaceFormat::B8G8R8X8
,
294 "We're assuming B8G8R8A8/X8");
296 // Get an image encoder for the media type
297 nsAutoCString
encoderCID(
298 NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType
);
300 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(encoderCID
.get());
302 return NS_IMAGELIB_ERROR_NO_ENCODER
;
305 IntSize size
= aDataSurface
->GetSize();
306 uint32_t dataLength
= aMap
.GetStride() * size
.height
;
309 nsresult rv
= encoder
->InitFromData(
310 aMap
.GetData(), dataLength
, size
.width
, size
.height
, aMap
.GetStride(),
311 imgIEncoder::INPUT_FORMAT_HOSTARGB
, aOutputOptions
);
312 NS_ENSURE_SUCCESS(rv
, rv
);
314 encoder
.forget(aStream
);
318 static nsresult
EncodeImageData(DataSourceSurface
* aDataSurface
,
319 const nsACString
& aMimeType
,
320 const nsAString
& aOutputOptions
,
321 nsIInputStream
** aStream
) {
322 DataSourceSurface::ScopedMap
map(aDataSurface
, DataSourceSurface::READ
);
323 if (!map
.IsMapped()) {
324 return NS_ERROR_FAILURE
;
327 return EncodeImageData(aDataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
331 imgTools::EncodeImage(imgIContainer
* aContainer
, const nsACString
& aMimeType
,
332 const nsAString
& aOutputOptions
,
333 nsIInputStream
** aStream
) {
334 // Use frame 0 from the image container.
335 RefPtr
<SourceSurface
> frame
= aContainer
->GetFrame(
336 imgIContainer::FRAME_FIRST
, imgIContainer::FLAG_SYNC_DECODE
);
337 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
339 RefPtr
<DataSourceSurface
> dataSurface
;
341 if (frame
->GetFormat() == SurfaceFormat::B8G8R8A8
||
342 frame
->GetFormat() == SurfaceFormat::B8G8R8X8
) {
343 dataSurface
= frame
->GetDataSurface();
345 // Convert format to SurfaceFormat::B8G8R8A8
346 dataSurface
= gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
347 frame
, SurfaceFormat::B8G8R8A8
);
350 NS_ENSURE_TRUE(dataSurface
, NS_ERROR_FAILURE
);
352 return EncodeImageData(dataSurface
, aMimeType
, aOutputOptions
, aStream
);
356 imgTools::EncodeScaledImage(imgIContainer
* aContainer
,
357 const nsACString
& aMimeType
, int32_t aScaledWidth
,
358 int32_t aScaledHeight
,
359 const nsAString
& aOutputOptions
,
360 nsIInputStream
** aStream
) {
361 NS_ENSURE_ARG(aScaledWidth
>= 0 && aScaledHeight
>= 0);
363 // If no scaled size is specified, we'll just encode the image at its
364 // original size (no scaling).
365 if (aScaledWidth
== 0 && aScaledHeight
== 0) {
366 return EncodeImage(aContainer
, aMimeType
, aOutputOptions
, aStream
);
369 // Retrieve the image's size.
370 int32_t imageWidth
= 0;
371 int32_t imageHeight
= 0;
372 aContainer
->GetWidth(&imageWidth
);
373 aContainer
->GetHeight(&imageHeight
);
375 // If the given width or height is zero we'll replace it with the image's
376 // original dimensions.
377 IntSize
scaledSize(aScaledWidth
== 0 ? imageWidth
: aScaledWidth
,
378 aScaledHeight
== 0 ? imageHeight
: aScaledHeight
);
380 // Use frame 0 from the image container.
381 RefPtr
<SourceSurface
> frame
=
382 aContainer
->GetFrameAtSize(scaledSize
, imgIContainer::FRAME_FIRST
,
383 imgIContainer::FLAG_HIGH_QUALITY_SCALING
|
384 imgIContainer::FLAG_SYNC_DECODE
);
385 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
387 // If the given surface is the right size/format, we can encode it directly.
388 if (scaledSize
== frame
->GetSize() &&
389 (frame
->GetFormat() == SurfaceFormat::B8G8R8A8
||
390 frame
->GetFormat() == SurfaceFormat::B8G8R8X8
)) {
391 RefPtr
<DataSourceSurface
> dataSurface
= frame
->GetDataSurface();
393 return EncodeImageData(dataSurface
, aMimeType
, aOutputOptions
, aStream
);
397 // Otherwise we need to scale it using a draw target.
398 RefPtr
<DataSourceSurface
> dataSurface
=
399 Factory::CreateDataSourceSurface(scaledSize
, SurfaceFormat::B8G8R8A8
);
400 if (NS_WARN_IF(!dataSurface
)) {
401 return NS_ERROR_FAILURE
;
404 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ_WRITE
);
405 if (!map
.IsMapped()) {
406 return NS_ERROR_FAILURE
;
409 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
410 BackendType::SKIA
, map
.GetData(), dataSurface
->GetSize(), map
.GetStride(),
411 SurfaceFormat::B8G8R8A8
);
413 gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
414 return NS_ERROR_OUT_OF_MEMORY
;
417 IntSize frameSize
= frame
->GetSize();
418 dt
->DrawSurface(frame
, Rect(0, 0, scaledSize
.width
, scaledSize
.height
),
419 Rect(0, 0, frameSize
.width
, frameSize
.height
),
420 DrawSurfaceOptions(),
421 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
423 return EncodeImageData(dataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
427 imgTools::EncodeCroppedImage(imgIContainer
* aContainer
,
428 const nsACString
& aMimeType
, int32_t aOffsetX
,
429 int32_t aOffsetY
, int32_t aWidth
, int32_t aHeight
,
430 const nsAString
& aOutputOptions
,
431 nsIInputStream
** aStream
) {
432 NS_ENSURE_ARG(aOffsetX
>= 0 && aOffsetY
>= 0 && aWidth
>= 0 && aHeight
>= 0);
434 // Offsets must be zero when no width and height are given or else we're out
436 NS_ENSURE_ARG(aWidth
+ aHeight
> 0 || aOffsetX
+ aOffsetY
== 0);
438 // If no size is specified then we'll preserve the image's original dimensions
439 // and don't need to crop.
440 if (aWidth
== 0 && aHeight
== 0) {
441 return EncodeImage(aContainer
, aMimeType
, aOutputOptions
, aStream
);
444 // Use frame 0 from the image container.
445 RefPtr
<SourceSurface
> frame
= aContainer
->GetFrame(
446 imgIContainer::FRAME_FIRST
, imgIContainer::FLAG_SYNC_DECODE
);
447 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
449 int32_t frameWidth
= frame
->GetSize().width
;
450 int32_t frameHeight
= frame
->GetSize().height
;
452 // If the given width or height is zero we'll replace it with the image's
453 // original dimensions.
456 } else if (aHeight
== 0) {
457 aHeight
= frameHeight
;
460 // Check that the given crop rectangle is within image bounds.
461 NS_ENSURE_ARG(frameWidth
>= aOffsetX
+ aWidth
&&
462 frameHeight
>= aOffsetY
+ aHeight
);
464 RefPtr
<DataSourceSurface
> dataSurface
= Factory::CreateDataSourceSurface(
465 IntSize(aWidth
, aHeight
), SurfaceFormat::B8G8R8A8
,
467 if (NS_WARN_IF(!dataSurface
)) {
468 return NS_ERROR_FAILURE
;
471 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ_WRITE
);
472 if (!map
.IsMapped()) {
473 return NS_ERROR_FAILURE
;
476 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
477 BackendType::SKIA
, map
.GetData(), dataSurface
->GetSize(), map
.GetStride(),
478 SurfaceFormat::B8G8R8A8
);
481 << "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
482 return NS_ERROR_OUT_OF_MEMORY
;
484 dt
->CopySurface(frame
, IntRect(aOffsetX
, aOffsetY
, aWidth
, aHeight
),
487 return EncodeImageData(dataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
491 imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver
* aInner
,
492 imgINotificationObserver
** aObserver
) {
493 NS_ADDREF(*aObserver
= new ScriptedNotificationObserver(aInner
));
498 imgTools::GetImgLoaderForDocument(dom::Document
* aDoc
, imgILoader
** aLoader
) {
499 NS_IF_ADDREF(*aLoader
= nsContentUtils::GetImgLoaderForDocument(aDoc
));
504 imgTools::GetImgCacheForDocument(dom::Document
* aDoc
, imgICache
** aCache
) {
505 nsCOMPtr
<imgILoader
> loader
;
506 nsresult rv
= GetImgLoaderForDocument(aDoc
, getter_AddRefs(loader
));
507 NS_ENSURE_SUCCESS(rv
, rv
);
508 return CallQueryInterface(loader
, aCache
);
512 } // namespace mozilla