Bug 1141979 - part8 - implement ImageBitmap extensions; r=jrmuizel draft
authorKaku Kuo <tkuo@mozilla.com>
Wed, 27 Apr 2016 13:03:01 +0800
changeset 373847 89e951fd34edfdec35707171afc690e079a28812
parent 373846 a29bb0c3de585bdc577d94c1416f044132e669fd
child 373848 97eebbbbecd48b9071c68f95f2a8d151dd70a1f8
push id19853
push usertkuo@mozilla.com
push dateWed, 01 Jun 2016 09:17:41 +0000
reviewersjrmuizel
bugs1141979
milestone49.0a1
Bug 1141979 - part8 - implement ImageBitmap extensions; r=jrmuizel MozReview-Commit-ID: Bc6HoqRLMNB
dom/bindings/Bindings.conf
dom/canvas/ImageBitmap.cpp
dom/canvas/ImageBitmap.h
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -640,16 +640,20 @@ DOMInterfaces = {
     'headerFile': 'IDBEvents.h',
 },
 
 'IID': {
     'nativeType': 'nsIJSID',
     'headerFile': 'xpcjsid.h',
 },
 
+'ImageBitmap': {
+    'implicitJSContext': [ 'mapDataInto' ],
+},
+
 'ImageCapture': {
     'binaryNames': { 'videoStreamTrack': 'GetVideoStreamTrack' }
 },
 
 'ImageData': {
     'wrapperCache': False,
 },
 
--- 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