Bug 1586038 [wpt PR 19492] - [LayoutNG] Account for relpos offset when adding layout...
[gecko.git] / image / imgTools.cpp
blobad5e49b9ee73107d41c4f699a454d1ccb6cd3c8c
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 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());
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,
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();
347 } else {
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);
358 NS_IMETHODIMP
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();
395 if (dataSurface) {
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);
415 if (!dt) {
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);
429 NS_IMETHODIMP
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
438 // of bounds.
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.
458 if (aWidth == 0) {
459 aWidth = frameWidth;
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,
470 /* aZero = */ true);
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);
483 if (!dt) {
484 gfxWarning()
485 << "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
486 return NS_ERROR_OUT_OF_MEMORY;
488 dt->CopySurface(frame, IntRect(aOffsetX, aOffsetY, aWidth, aHeight),
489 IntPoint(0, 0));
491 return EncodeImageData(dataSurface, map, aMimeType, aOutputOptions, aStream);
494 NS_IMETHODIMP
495 imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner,
496 imgINotificationObserver** aObserver) {
497 NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner));
498 return NS_OK;
501 NS_IMETHODIMP
502 imgTools::GetImgLoaderForDocument(dom::Document* aDoc, imgILoader** aLoader) {
503 NS_IF_ADDREF(*aLoader = nsContentUtils::GetImgLoaderForDocument(aDoc));
504 return NS_OK;
507 NS_IMETHODIMP
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);
515 } // namespace image
516 } // namespace mozilla