Bug 1516095 [wpt PR 14643] - [Resource-Timing] Flakiness reduction, a=testonly
[gecko.git] / image / imgTools.cpp
blob8819731ca40105f9dd75884c81ee6ee7eefb55a4
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/. */
7 #include "imgTools.h"
9 #include "DecodePool.h"
10 #include "gfxUtils.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/Logging.h"
13 #include "mozilla/RefPtr.h"
14 #include "nsCOMPtr.h"
15 #include "mozilla/dom/Document.h"
16 #include "nsError.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"
27 #include "Image.h"
28 #include "ScriptedNotificationObserver.h"
29 #include "imgIScriptedNotificationObserver.h"
30 #include "gfxPlatform.h"
31 #include "jsfriendapi.h"
33 using namespace mozilla::gfx;
35 namespace mozilla {
36 namespace image {
38 namespace {
40 class ImageDecoderHelper final : public Runnable,
41 public nsIInputStreamCallback {
42 public:
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),
54 mCallback(aCallback),
55 mCallbackEventTarget(aCallbackEventTarget),
56 mStatus(NS_OK) {
57 MOZ_ASSERT(NS_IsMainThread());
60 NS_IMETHOD
61 Run() override {
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
64 // operation.
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)) {
74 container = mImage;
77 mCallback->OnImageReady(container, mStatus);
78 return NS_OK;
81 uint64_t length;
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.
92 if (length == 0) {
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);
100 return NS_OK;
103 // We really have nothing else to read.
104 if (length == 0) {
105 return OperationCompleted(NS_OK);
109 // Send the source data to the Image.
110 rv = mImage->OnImageDataAvailable(nullptr, nullptr, mInputStream, 0,
111 uint32_t(length));
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);
121 return NS_OK;
124 NS_IMETHOD
125 OnInputStreamReady(nsIAsyncInputStream* aAsyncInputStream) override {
126 MOZ_ASSERT(!NS_IsMainThread());
127 return Run();
130 nsresult OperationCompleted(nsresult aStatus) {
131 MOZ_ASSERT(!NS_IsMainThread());
133 mStatus = aStatus;
134 mCallbackEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
135 return NS_OK;
138 private:
139 ~ImageDecoderHelper() {
140 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mImage",
141 mImage.forget());
142 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mCallback",
143 mCallback.forget());
146 RefPtr<image::Image> mImage;
148 nsCOMPtr<nsIInputStream> mInputStream;
149 nsCOMPtr<nsIEventTarget> mEventTarget;
150 nsCOMPtr<imgIContainerCallback> mCallback;
151 nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
153 nsresult mStatus;
156 NS_IMPL_ISUPPORTS_INHERITED(ImageDecoderHelper, Runnable,
157 nsIInputStreamCallback)
159 } // namespace
161 /* ========== imgITools implementation ========== */
163 NS_IMPL_ISUPPORTS(imgTools, imgITools)
165 imgTools::imgTools() { /* member initializers and constructor code */
168 imgTools::~imgTools() { /* destructor code */
171 NS_IMETHODIMP
172 imgTools::DecodeImageFromArrayBuffer(JS::HandleValue aArrayBuffer,
173 const nsACString& aMimeType,
174 JSContext* aCx,
175 imgIContainer** aContainer) {
176 if (!aArrayBuffer.isObject()) {
177 return NS_ERROR_FAILURE;
180 JS::Rooted<JSObject*> obj(aCx,
181 js::UnwrapArrayBuffer(&aArrayBuffer.toObject()));
182 if (!obj) {
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,
191 &bufferData);
192 return DecodeImageFromBuffer((char*)bufferData, bufferLength, aMimeType,
193 aContainer);
196 NS_IMETHODIMP
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);
219 MOZ_ASSERT(stream);
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);
230 // All done.
231 image.forget(aContainer);
232 return NS_OK;
235 NS_IMETHODIMP
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);
245 nsresult rv;
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(),
259 1024);
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);
268 // Already an error?
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);
278 return NS_OK;
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());
301 if (!encoder) {
302 return NS_IMAGELIB_ERROR_NO_ENCODER;
305 IntSize size = aDataSurface->GetSize();
306 uint32_t dataLength = aMap.GetStride() * size.height;
308 // Encode the bitmap
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);
315 return NS_OK;
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);
330 NS_IMETHODIMP
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();
344 } else {
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);
355 NS_IMETHODIMP
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();
392 if (dataSurface) {
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);
412 if (!dt) {
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);
426 NS_IMETHODIMP
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
435 // of bounds.
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.
454 if (aWidth == 0) {
455 aWidth = frameWidth;
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,
466 /* aZero = */ true);
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);
479 if (!dt) {
480 gfxWarning()
481 << "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
482 return NS_ERROR_OUT_OF_MEMORY;
484 dt->CopySurface(frame, IntRect(aOffsetX, aOffsetY, aWidth, aHeight),
485 IntPoint(0, 0));
487 return EncodeImageData(dataSurface, map, aMimeType, aOutputOptions, aStream);
490 NS_IMETHODIMP
491 imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner,
492 imgINotificationObserver** aObserver) {
493 NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner));
494 return NS_OK;
497 NS_IMETHODIMP
498 imgTools::GetImgLoaderForDocument(dom::Document* aDoc, imgILoader** aLoader) {
499 NS_IF_ADDREF(*aLoader = nsContentUtils::GetImgLoaderForDocument(aDoc));
500 return NS_OK;
503 NS_IMETHODIMP
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);
511 } // namespace image
512 } // namespace mozilla