Bug 1568151 - Replace `target.getInspector()` by `target.getFront("inspector")`....
[gecko.git] / image / imgTools.cpp
blobf415be660964867c1fe13eb013d8f0c23a9a2c95
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 "js/ArrayBuffer.h"
32 #include "js/RootingAPI.h" // JS::{Handle,Rooted}
33 #include "js/Value.h" // JS::Value
35 using namespace mozilla::gfx;
37 namespace mozilla {
38 namespace image {
40 namespace {
42 class ImageDecoderHelper final : public Runnable,
43 public nsIInputStreamCallback {
44 public:
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),
56 mCallback(aCallback),
57 mCallbackEventTarget(aCallbackEventTarget),
58 mStatus(NS_OK) {
59 MOZ_ASSERT(NS_IsMainThread());
62 NS_IMETHOD
63 Run() override {
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
66 // operation.
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)) {
76 container = mImage;
79 mCallback->OnImageReady(container, mStatus);
80 return NS_OK;
83 uint64_t length;
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.
94 if (length == 0) {
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);
102 return NS_OK;
105 // We really have nothing else to read.
106 if (length == 0) {
107 return OperationCompleted(NS_OK);
111 // Send the source data to the Image.
112 rv = mImage->OnImageDataAvailable(nullptr, nullptr, mInputStream, 0,
113 uint32_t(length));
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);
123 return NS_OK;
126 NS_IMETHOD
127 OnInputStreamReady(nsIAsyncInputStream* aAsyncInputStream) override {
128 MOZ_ASSERT(!NS_IsMainThread());
129 return Run();
132 nsresult OperationCompleted(nsresult aStatus) {
133 MOZ_ASSERT(!NS_IsMainThread());
135 mStatus = aStatus;
136 mCallbackEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
137 return NS_OK;
140 private:
141 ~ImageDecoderHelper() {
142 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mImage",
143 mImage.forget());
144 NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mCallback",
145 mCallback.forget());
148 RefPtr<image::Image> mImage;
150 nsCOMPtr<nsIInputStream> mInputStream;
151 nsCOMPtr<nsIEventTarget> mEventTarget;
152 nsCOMPtr<imgIContainerCallback> mCallback;
153 nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
155 nsresult mStatus;
158 NS_IMPL_ISUPPORTS_INHERITED(ImageDecoderHelper, Runnable,
159 nsIInputStreamCallback)
161 } // namespace
163 /* ========== imgITools implementation ========== */
165 NS_IMPL_ISUPPORTS(imgTools, imgITools)
167 imgTools::imgTools() { /* member initializers and constructor code */
170 imgTools::~imgTools() { /* destructor code */
173 NS_IMETHODIMP
174 imgTools::DecodeImageFromArrayBuffer(JS::Handle<JS::Value> aArrayBuffer,
175 const nsACString& aMimeType,
176 JSContext* aCx,
177 imgIContainer** aContainer) {
178 if (!aArrayBuffer.isObject()) {
179 return NS_ERROR_FAILURE;
182 JS::Rooted<JSObject*> obj(aCx,
183 JS::UnwrapArrayBuffer(&aArrayBuffer.toObject()));
184 if (!obj) {
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,
193 &bufferData);
194 return DecodeImageFromBuffer((char*)bufferData, bufferLength, aMimeType,
195 aContainer);
198 NS_IMETHODIMP
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);
221 MOZ_ASSERT(stream);
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);
232 // All done.
233 image.forget(aContainer);
234 return NS_OK;
237 NS_IMETHODIMP
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);
247 nsresult rv;
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(),
261 1024);
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);
270 // Already an error?
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);
280 return NS_OK;
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 incure 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());
303 if (!encoder) {
304 return NS_IMAGELIB_ERROR_NO_ENCODER;
307 IntSize size = aDataSurface->GetSize();
308 uint32_t dataLength = aMap.GetStride() * size.height;
310 // Encode the bitmap
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);
317 return NS_OK;
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);
332 NS_IMETHODIMP
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, imgIContainer::FLAG_SYNC_DECODE);
339 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
341 RefPtr<DataSourceSurface> dataSurface;
343 if (frame->GetFormat() == SurfaceFormat::B8G8R8A8 ||
344 frame->GetFormat() == SurfaceFormat::B8G8R8X8) {
345 dataSurface = frame->GetDataSurface();
346 } else {
347 // Convert format to SurfaceFormat::B8G8R8A8
348 dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
349 frame, SurfaceFormat::B8G8R8A8);
352 NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
354 return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
357 NS_IMETHODIMP
358 imgTools::EncodeScaledImage(imgIContainer* aContainer,
359 const nsACString& aMimeType, int32_t aScaledWidth,
360 int32_t aScaledHeight,
361 const nsAString& aOutputOptions,
362 nsIInputStream** aStream) {
363 NS_ENSURE_ARG(aScaledWidth >= 0 && aScaledHeight >= 0);
365 // If no scaled size is specified, we'll just encode the image at its
366 // original size (no scaling).
367 if (aScaledWidth == 0 && aScaledHeight == 0) {
368 return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
371 // Retrieve the image's size.
372 int32_t imageWidth = 0;
373 int32_t imageHeight = 0;
374 aContainer->GetWidth(&imageWidth);
375 aContainer->GetHeight(&imageHeight);
377 // If the given width or height is zero we'll replace it with the image's
378 // original dimensions.
379 IntSize scaledSize(aScaledWidth == 0 ? imageWidth : aScaledWidth,
380 aScaledHeight == 0 ? imageHeight : aScaledHeight);
382 // Use frame 0 from the image container.
383 RefPtr<SourceSurface> frame =
384 aContainer->GetFrameAtSize(scaledSize, imgIContainer::FRAME_FIRST,
385 imgIContainer::FLAG_HIGH_QUALITY_SCALING |
386 imgIContainer::FLAG_SYNC_DECODE);
387 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
389 // If the given surface is the right size/format, we can encode it directly.
390 if (scaledSize == frame->GetSize() &&
391 (frame->GetFormat() == SurfaceFormat::B8G8R8A8 ||
392 frame->GetFormat() == SurfaceFormat::B8G8R8X8)) {
393 RefPtr<DataSourceSurface> dataSurface = frame->GetDataSurface();
394 if (dataSurface) {
395 return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
399 // Otherwise we need to scale it using a draw target.
400 RefPtr<DataSourceSurface> dataSurface =
401 Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8);
402 if (NS_WARN_IF(!dataSurface)) {
403 return NS_ERROR_FAILURE;
406 DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE);
407 if (!map.IsMapped()) {
408 return NS_ERROR_FAILURE;
411 RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
412 BackendType::SKIA, map.GetData(), dataSurface->GetSize(), map.GetStride(),
413 SurfaceFormat::B8G8R8A8);
414 if (!dt) {
415 gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
416 return NS_ERROR_OUT_OF_MEMORY;
419 IntSize frameSize = frame->GetSize();
420 dt->DrawSurface(frame, Rect(0, 0, scaledSize.width, scaledSize.height),
421 Rect(0, 0, frameSize.width, frameSize.height),
422 DrawSurfaceOptions(),
423 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
425 return EncodeImageData(dataSurface, map, aMimeType, aOutputOptions, aStream);
428 NS_IMETHODIMP
429 imgTools::EncodeCroppedImage(imgIContainer* aContainer,
430 const nsACString& aMimeType, int32_t aOffsetX,
431 int32_t aOffsetY, int32_t aWidth, int32_t aHeight,
432 const nsAString& aOutputOptions,
433 nsIInputStream** aStream) {
434 NS_ENSURE_ARG(aOffsetX >= 0 && aOffsetY >= 0 && aWidth >= 0 && aHeight >= 0);
436 // Offsets must be zero when no width and height are given or else we're out
437 // of bounds.
438 NS_ENSURE_ARG(aWidth + aHeight > 0 || aOffsetX + aOffsetY == 0);
440 // If no size is specified then we'll preserve the image's original dimensions
441 // and don't need to crop.
442 if (aWidth == 0 && aHeight == 0) {
443 return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
446 // Use frame 0 from the image container.
447 RefPtr<SourceSurface> frame = aContainer->GetFrame(
448 imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE);
449 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
451 int32_t frameWidth = frame->GetSize().width;
452 int32_t frameHeight = frame->GetSize().height;
454 // If the given width or height is zero we'll replace it with the image's
455 // original dimensions.
456 if (aWidth == 0) {
457 aWidth = frameWidth;
458 } else if (aHeight == 0) {
459 aHeight = frameHeight;
462 // Check that the given crop rectangle is within image bounds.
463 NS_ENSURE_ARG(frameWidth >= aOffsetX + aWidth &&
464 frameHeight >= aOffsetY + aHeight);
466 RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
467 IntSize(aWidth, aHeight), SurfaceFormat::B8G8R8A8,
468 /* aZero = */ true);
469 if (NS_WARN_IF(!dataSurface)) {
470 return NS_ERROR_FAILURE;
473 DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE);
474 if (!map.IsMapped()) {
475 return NS_ERROR_FAILURE;
478 RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
479 BackendType::SKIA, map.GetData(), dataSurface->GetSize(), map.GetStride(),
480 SurfaceFormat::B8G8R8A8);
481 if (!dt) {
482 gfxWarning()
483 << "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
484 return NS_ERROR_OUT_OF_MEMORY;
486 dt->CopySurface(frame, IntRect(aOffsetX, aOffsetY, aWidth, aHeight),
487 IntPoint(0, 0));
489 return EncodeImageData(dataSurface, map, aMimeType, aOutputOptions, aStream);
492 NS_IMETHODIMP
493 imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner,
494 imgINotificationObserver** aObserver) {
495 NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner));
496 return NS_OK;
499 NS_IMETHODIMP
500 imgTools::GetImgLoaderForDocument(dom::Document* aDoc, imgILoader** aLoader) {
501 NS_IF_ADDREF(*aLoader = nsContentUtils::GetImgLoaderForDocument(aDoc));
502 return NS_OK;
505 NS_IMETHODIMP
506 imgTools::GetImgCacheForDocument(dom::Document* aDoc, imgICache** aCache) {
507 nsCOMPtr<imgILoader> loader;
508 nsresult rv = GetImgLoaderForDocument(aDoc, getter_AddRefs(loader));
509 NS_ENSURE_SUCCESS(rv, rv);
510 return CallQueryInterface(loader, aCache);
513 } // namespace image
514 } // namespace mozilla