--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -7,19 +7,20 @@
#include "mozilla/dom/ImageBitmap.h"
#include "mozilla/dom/ImageBitmapBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/gfx/2D.h"
+#include "ImageBitmapUtils.h"
+#include "ImageUtils.h"
#include "imgTools.h"
#include "libyuv.h"
-#include "nsLayoutUtils.h"
using namespace mozilla::gfx;
using namespace mozilla::layers;
namespace mozilla {
namespace dom {
using namespace workers;
@@ -390,16 +391,17 @@ HasRasterImage(HTMLImageElement& aImageE
return false;
}
ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
bool aIsPremultipliedAlpha /* = true */)
: mParent(aGlobal)
, mData(aData)
, mSurface(nullptr)
+ , mDataWrapper(new ImageUtils(mData))
, mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height)
, mIsPremultipliedAlpha(aIsPremultipliedAlpha)
{
MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
}
ImageBitmap::~ImageBitmap()
{
@@ -476,17 +478,22 @@ ImageBitmap::PrepareForDrawTarget(gfx::D
mSurface = target->Snapshot();
// Make mCropRect match new surface we've cropped to
mPictureRect.MoveTo(0, 0);
}
// Pre-multiply alpha here.
// Apply pre-multiply alpha only if mIsPremultipliedAlpha is false.
- if (!mIsPremultipliedAlpha) {
+ // Ignore this step if the source surface does not have alpha channel; this
+ // kind of source surfaces might come form layers::PlanarYCbCrImage.
+ if (!mIsPremultipliedAlpha &&
+ mSurface->GetFormat() != SurfaceFormat::B8G8R8X8 &&
+ mSurface->GetFormat() != SurfaceFormat::R8G8B8X8 &&
+ mSurface->GetFormat() != SurfaceFormat::X8R8G8B8) {
MOZ_ASSERT(mSurface->GetFormat() == SurfaceFormat::R8G8B8A8 ||
mSurface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
mSurface->GetFormat() == SurfaceFormat::A8R8G8B8);
RefPtr<DataSourceSurface> dstSurface = mSurface->GetDataSurface();
MOZ_ASSERT(dstSurface);
RefPtr<DataSourceSurface> srcSurface;
@@ -1402,10 +1409,234 @@ ImageBitmap::ExtensionsEnabled(JSContext
return Preferences::GetBool("canvas.imagebitmap_extensions.enabled");
} else {
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(workerPrivate);
return workerPrivate->ImageBitmapExtensionsEnabled();
}
}
+// ImageBitmap extensions.
+ImageBitmapFormat
+ImageBitmap::FindOptimalFormat(const Optional<Sequence<ImageBitmapFormat>>& aPossibleFormats,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(mDataWrapper, "No ImageBitmapFormatUtils functionalities.");
+
+ ImageBitmapFormat platformFormat = mDataWrapper->GetFormat();
+
+ if (!aPossibleFormats.WasPassed() ||
+ aPossibleFormats.Value().Contains(platformFormat)) {
+ return platformFormat;
+ } else {
+ // If no matching is found, FindBestMatchingFromat() returns
+ // ImageBitmapFormat::EndGuard_ and we throw an exception.
+ ImageBitmapFormat optimalFormat =
+ FindBestMatchingFromat(platformFormat, aPossibleFormats.Value());
+
+ if (optimalFormat == ImageBitmapFormat::EndGuard_) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ return optimalFormat;
+ }
+}
+
+int32_t
+ImageBitmap::MappedDataLength(ImageBitmapFormat aFormat, ErrorResult& aRv)
+{
+ MOZ_ASSERT(mDataWrapper, "No ImageBitmapFormatUtils functionalities.");
+
+ if (aFormat == mDataWrapper->GetFormat()) {
+ return mDataWrapper->GetBufferLength();
+ } else {
+ return CalculateImageBufferSize(aFormat, Width(), Height());
+ }
+}
+
+template<typename T>
+class MapDataIntoBufferSource
+{
+protected:
+ MapDataIntoBufferSource(JSContext* aCx,
+ Promise *aPromise,
+ ImageBitmap *aImageBitmap,
+ const T& aBuffer,
+ int32_t aOffset,
+ ImageBitmapFormat aFormat)
+ : mPromise(aPromise)
+ , mImageBitmap(aImageBitmap)
+ , mBuffer(aCx, aBuffer.Obj())
+ , mOffset(aOffset)
+ , mFormat(aFormat)
+ {
+ MOZ_ASSERT(mPromise);
+ MOZ_ASSERT(JS_IsArrayBufferObject(mBuffer) ||
+ JS_IsArrayBufferViewObject(mBuffer));
+ }
+
+ virtual ~MapDataIntoBufferSource() = default;
+
+ void DoMapDataIntoBufferSource()
+ {
+ ErrorResult error;
+
+ // Prepare destination buffer.
+ uint8_t* bufferData = nullptr;
+ uint32_t bufferLength = 0;
+ bool isSharedMemory = false;
+ if (JS_IsArrayBufferObject(mBuffer)) {
+ js::GetArrayBufferLengthAndData(mBuffer, &bufferLength, &isSharedMemory, &bufferData);
+ } else if (JS_IsArrayBufferViewObject(mBuffer)) {
+ js::GetArrayBufferViewLengthAndData(mBuffer, &bufferLength, &isSharedMemory, &bufferData);
+ } else {
+ error.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ mPromise->MaybeReject(error);
+ return;
+ }
+
+ if (NS_WARN_IF(!bufferData) || NS_WARN_IF(!bufferLength)) {
+ error.Throw(NS_ERROR_NOT_AVAILABLE);
+ mPromise->MaybeReject(error);
+ return;
+ }
+
+ // Check length.
+ const int32_t neededBufferLength =
+ mImageBitmap->MappedDataLength(mFormat, error);
+
+ if (((int32_t)bufferLength - mOffset) < neededBufferLength) {
+ error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ mPromise->MaybeReject(error);
+ return;
+ }
+
+ // Call ImageBitmapFormatUtils.
+ UniquePtr<ImagePixelLayout> layout =
+ mImageBitmap->mDataWrapper->MapDataInto(bufferData,
+ mOffset,
+ bufferLength,
+ mFormat,
+ error);
+
+ if (NS_WARN_IF(!layout)) {
+ mPromise->MaybeReject(error);
+ return;
+ }
+
+ mPromise->MaybeResolve(*layout);
+ }
+
+ RefPtr<Promise> mPromise;
+ RefPtr<ImageBitmap> mImageBitmap;
+ JS::PersistentRooted<JSObject*> mBuffer;
+ int32_t mOffset;
+ ImageBitmapFormat mFormat;
+};
+
+template<typename T>
+class MapDataIntoBufferSourceTask final : public Runnable,
+ public MapDataIntoBufferSource<T>
+{
+public:
+ MapDataIntoBufferSourceTask(JSContext* aCx,
+ Promise *aPromise,
+ ImageBitmap *aImageBitmap,
+ const T& aBuffer,
+ int32_t aOffset,
+ ImageBitmapFormat aFormat)
+ : MapDataIntoBufferSource<T>(aCx, aPromise, aImageBitmap, aBuffer, aOffset, aFormat)
+ {
+ }
+
+ virtual ~MapDataIntoBufferSourceTask() = default;
+
+ NS_IMETHOD Run() override
+ {
+ MapDataIntoBufferSource<T>::DoMapDataIntoBufferSource();
+ return NS_OK;
+ }
+};
+
+template<typename T>
+class MapDataIntoBufferSourceWorkerTask final : public WorkerSameThreadRunnable,
+ public MapDataIntoBufferSource<T>
+{
+public:
+ MapDataIntoBufferSourceWorkerTask(JSContext* aCx,
+ Promise *aPromise,
+ ImageBitmap *aImageBitmap,
+ const T& aBuffer,
+ int32_t aOffset,
+ ImageBitmapFormat aFormat)
+ : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()),
+ MapDataIntoBufferSource<T>(aCx, aPromise, aImageBitmap, aBuffer, aOffset, aFormat)
+ {
+ }
+
+ virtual ~MapDataIntoBufferSourceWorkerTask() = default;
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MapDataIntoBufferSource<T>::DoMapDataIntoBufferSource();
+ return true;
+ }
+};
+
+void AsyncMapDataIntoBufferSource(JSContext* aCx,
+ Promise *aPromise,
+ ImageBitmap *aImageBitmap,
+ const ArrayBufferViewOrArrayBuffer& aBuffer,
+ int32_t aOffset,
+ ImageBitmapFormat aFormat)
+{
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(aImageBitmap);
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> task;
+
+ if (aBuffer.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
+ task = new MapDataIntoBufferSourceTask<ArrayBuffer>(aCx, aPromise, aImageBitmap, buffer, aOffset, aFormat);
+ } else if (aBuffer.IsArrayBufferView()) {
+ const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView();
+ task = new MapDataIntoBufferSourceTask<ArrayBufferView>(aCx, aPromise, aImageBitmap, bufferView, aOffset, aFormat);
+ }
+
+ NS_DispatchToCurrentThread(task); // Actually, to the main-thread.
+ } else {
+ RefPtr<WorkerSameThreadRunnable> task;
+
+ if (aBuffer.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
+ task = new MapDataIntoBufferSourceWorkerTask<ArrayBuffer>(aCx, aPromise, aImageBitmap, buffer, aOffset, aFormat);
+ } else if (aBuffer.IsArrayBufferView()) {
+ const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView();
+ task = new MapDataIntoBufferSourceWorkerTask<ArrayBufferView>(aCx, aPromise, aImageBitmap, bufferView, aOffset, aFormat);
+ }
+
+ task->Dispatch(); // Actually, to the current worker-thread.
+ }
+}
+
+already_AddRefed<Promise>
+ImageBitmap::MapDataInto(JSContext* aCx,
+ ImageBitmapFormat aFormat,
+ const ArrayBufferViewOrArrayBuffer& aBuffer,
+ int32_t aOffset, ErrorResult& aRv)
+{
+ MOZ_ASSERT(mDataWrapper, "No ImageBitmapFormatUtils functionalities.");
+ MOZ_ASSERT(aCx, "No JSContext while calling ImageBitmap::MapDataInto().");
+
+ RefPtr<Promise> promise = Promise::Create(mParent, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ AsyncMapDataIntoBufferSource(aCx, promise, this, aBuffer, aOffset, aFormat);
+ return promise.forget();
+}
+
} // namespace dom
} // namespace mozilla
--- a/dom/canvas/ImageBitmap.h
+++ b/dom/canvas/ImageBitmap.h
@@ -36,27 +36,31 @@ class Image;
namespace dom {
class OffscreenCanvas;
namespace workers {
class WorkerStructuredCloneClosure;
}
+class ArrayBufferViewOrArrayBuffer;
class CanvasRenderingContext2D;
+class CreateImageBitmapFromBlob;
+class CreateImageBitmapFromBlobTask;
+class CreateImageBitmapFromBlobWorkerTask;
class File;
class HTMLCanvasElement;
class HTMLImageElement;
class HTMLVideoElement;
+enum class ImageBitmapFormat : uint32_t;
class ImageData;
+class ImageUtils;
+template<typename T> class MapDataIntoBufferSource;
class Promise;
class PostMessageEvent; // For StructuredClone between windows.
-class CreateImageBitmapFromBlob;
-class CreateImageBitmapFromBlobTask;
-class CreateImageBitmapFromBlobWorkerTask;
struct ImageBitmapCloneData final
{
RefPtr<gfx::DataSourceSurface> mSurface;
gfx::IntRect mPictureRect;
bool mIsPremultipliedAlpha;
};
@@ -138,16 +142,33 @@ public:
// Mozilla Extensions
static bool ExtensionsEnabled(JSContext* aCx, JSObject* aObj);
friend CreateImageBitmapFromBlob;
friend CreateImageBitmapFromBlobTask;
friend CreateImageBitmapFromBlobWorkerTask;
+ template<typename T>
+ friend class MapDataIntoBufferSource;
+
+ // Mozilla Extensions
+ ImageBitmapFormat
+ FindOptimalFormat(const Optional<Sequence<ImageBitmapFormat>>& aPossibleFormats,
+ ErrorResult& aRv);
+
+ int32_t
+ MappedDataLength(ImageBitmapFormat aFormat, ErrorResult& aRv);
+
+ already_AddRefed<Promise>
+ MapDataInto(JSContext* aCx,
+ ImageBitmapFormat aFormat,
+ const ArrayBufferViewOrArrayBuffer& aBuffer,
+ int32_t aOffset, ErrorResult& aRv);
+
protected:
/*
* The default value of aIsPremultipliedAlpha is TRUE because that the
* data stored in HTMLImageElement, HTMLVideoElement, HTMLCanvasElement,
* CanvasRenderingContext2D are alpha-premultiplied in default.
*
* Actually, if one HTMLCanvasElement's rendering context is WebGLContext, it
@@ -210,16 +231,23 @@ protected:
* if the are of mPictureRect is just the same as the mData's size. Or, it is
* a independent data buffer which is copied and cropped form the mData's data
* buffer.
*/
RefPtr<layers::Image> mData;
RefPtr<gfx::SourceSurface> mSurface;
/*
+ * This is used in the ImageBitmap-Extensions implementation.
+ * ImageUtils is a wrapper to layers::Image, which add some common methods for
+ * accessing the layers::Image's data.
+ */
+ UniquePtr<ImageUtils> mDataWrapper;
+
+ /*
* The mPictureRect is the size of the source image in default, however, if
* users specify the cropping area while creating an ImageBitmap, then this
* mPictureRect is the cropping area.
*
* Note that if the CreateInternal() copies and crops data from the source
* image, then this mPictureRect is just the size of the final mData.
*
* The mPictureRect will be used at PrepareForDrawTarget() while user is going