Bug 1141979 - part9 - implement ImageBitmapFactories extensions; r=jrmuizel draft
authorKaku Kuo <tkuo@mozilla.com>
Wed, 27 Apr 2016 11:46:51 +0800
changeset 373848 97eebbbbecd48b9071c68f95f2a8d151dd70a1f8
parent 373847 89e951fd34edfdec35707171afc690e079a28812
child 373849 dbb63b5c5215c6edf92cc954b2cb3fc79a5d6331
push id19853
push usertkuo@mozilla.com
push dateWed, 01 Jun 2016 09:17:41 +0000
reviewersjrmuizel
bugs1141979
milestone49.0a1
Bug 1141979 - part9 - implement ImageBitmapFactories extensions; r=jrmuizel MozReview-Commit-ID: 6Hm58nffAuV
dom/canvas/ImageBitmap.cpp
dom/canvas/ImageBitmap.h
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -1633,10 +1633,296 @@ ImageBitmap::MapDataInto(JSContext* aCx,
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   AsyncMapDataIntoBufferSource(aCx, promise, this, aBuffer, aOffset, aFormat);
   return promise.forget();
 }
 
+// ImageBitmapFactories extensions.
+static SurfaceFormat
+ImageFormatToSurfaceFromat(mozilla::dom::ImageBitmapFormat aFormat)
+{
+  switch(aFormat) {
+  case ImageBitmapFormat::RGBA32:
+    return SurfaceFormat::R8G8B8A8;
+  case ImageBitmapFormat::BGRA32:
+    return SurfaceFormat::B8G8R8A8;
+  case ImageBitmapFormat::RGB24:
+    return SurfaceFormat::R8G8B8;
+  case ImageBitmapFormat::BGR24:
+    return SurfaceFormat::B8G8R8;
+  case ImageBitmapFormat::GRAY8:
+    return SurfaceFormat::A8;
+  case ImageBitmapFormat::HSV:
+    return SurfaceFormat::HSV;
+  case ImageBitmapFormat::Lab:
+    return SurfaceFormat::Lab;
+  case ImageBitmapFormat::DEPTH:
+    return SurfaceFormat::Depth;
+  default:
+    return SurfaceFormat::UNKNOWN;
+  }
+}
+
+static already_AddRefed<layers::Image>
+CreateImageFromBufferSourceRawData(const uint8_t*aBufferData,
+                                   uint32_t aBufferLength,
+                                   mozilla::dom::ImageBitmapFormat aFormat,
+                                   const Sequence<ChannelPixelLayout>& aLayout)
+{
+  MOZ_ASSERT(aBufferData);
+  MOZ_ASSERT(aBufferLength > 0);
+
+  switch(aFormat) {
+  case ImageBitmapFormat::RGBA32:
+  case ImageBitmapFormat::BGRA32:
+  case ImageBitmapFormat::RGB24:
+  case ImageBitmapFormat::BGR24:
+  case ImageBitmapFormat::GRAY8:
+  case ImageBitmapFormat::HSV:
+  case ImageBitmapFormat::Lab:
+  case ImageBitmapFormat::DEPTH:
+  {
+    const nsTArray<ChannelPixelLayout>& channels = aLayout;
+    MOZ_ASSERT(channels.Length() != 0, "Empty Channels.");
+
+    const SurfaceFormat srcFormat = ImageFormatToSurfaceFromat(aFormat);
+    const uint32_t srcStride = channels[0].mStride;
+    const IntSize srcSize(channels[0].mWidth, channels[0].mHeight);
+
+    RefPtr<DataSourceSurface> dstDataSurface =
+      Factory::CreateDataSourceSurfaceWithStride(srcSize, srcFormat, srcStride);
+
+    if (NS_WARN_IF(!dstDataSurface)) {
+      return nullptr;
+    }
+
+    // Copy the raw data into the newly created DataSourceSurface.
+    DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE);
+    if (NS_WARN_IF(!dstMap.IsMapped())) {
+      return nullptr;
+    }
+
+    const uint8_t* srcBufferPtr = aBufferData;
+    uint8_t* dstBufferPtr = dstMap.GetData();
+
+    for (int i = 0; i < srcSize.height; ++i) {
+      memcpy(dstBufferPtr, srcBufferPtr, srcStride);
+      srcBufferPtr += srcStride;
+      dstBufferPtr += dstMap.GetStride();
+    }
+
+    // Create an Image from the BGRA SourceSurface.
+    RefPtr<SourceSurface> surface = dstDataSurface;
+    RefPtr<layers::Image> image = CreateImageFromSurface(surface);
+
+    if (NS_WARN_IF(!image)) {
+      return nullptr;
+    }
+
+    return image.forget();
+  }
+  case ImageBitmapFormat::YUV444P:
+  case ImageBitmapFormat::YUV422P:
+  case ImageBitmapFormat::YUV420P:
+  case ImageBitmapFormat::YUV420SP_NV12:
+  case ImageBitmapFormat::YUV420SP_NV21:
+  {
+    // Prepare the PlanarYCbCrData.
+    const ChannelPixelLayout& yLayout = aLayout[0];
+    const ChannelPixelLayout& uLayout = aFormat != ImageBitmapFormat::YUV420SP_NV21 ? aLayout[1] : aLayout[2];
+    const ChannelPixelLayout& vLayout = aFormat != ImageBitmapFormat::YUV420SP_NV21 ? aLayout[2] : aLayout[1];
+
+    layers::PlanarYCbCrData data;
+
+    // Luminance buffer
+    data.mYChannel = const_cast<uint8_t*>(aBufferData + yLayout.mOffset);
+    data.mYStride = yLayout.mStride;
+    data.mYSize = gfx::IntSize(yLayout.mWidth, yLayout.mHeight);
+    data.mYSkip = yLayout.mSkip;
+
+    // Chroma buffers
+    data.mCbChannel = const_cast<uint8_t*>(aBufferData + uLayout.mOffset);
+    data.mCrChannel = const_cast<uint8_t*>(aBufferData + vLayout.mOffset);
+    data.mCbCrStride = uLayout.mStride;
+    data.mCbCrSize = gfx::IntSize(uLayout.mWidth, uLayout.mHeight);
+    data.mCbSkip = uLayout.mSkip;
+    data.mCrSkip = vLayout.mSkip;
+
+    // Picture rectangle.
+    // We set the picture rectangle to exactly the size of the source image to
+    // keep the full original data.
+    data.mPicX = 0;
+    data.mPicY = 0;
+    data.mPicSize = data.mYSize;
+
+    // Create a layers::Image and set data.
+    if (aFormat == ImageBitmapFormat::YUV444P ||
+        aFormat == ImageBitmapFormat::YUV422P ||
+        aFormat == ImageBitmapFormat::YUV420P) {
+      RefPtr<layers::PlanarYCbCrImage> image =
+        new layers::RecyclingPlanarYCbCrImage(new layers::BufferRecycleBin());
+
+      if (NS_WARN_IF(!image)) {
+        return nullptr;
+      }
+
+      // Set Data.
+      if (NS_WARN_IF(!image->CopyData(data))) {
+        return nullptr;
+      }
+
+      return image.forget();
+    } else {
+      RefPtr<layers::NVImage>image = new layers::NVImage();
+
+      if (NS_WARN_IF(!image)) {
+        return nullptr;
+      }
+
+      // Set Data.
+      if (NS_WARN_IF(!image->SetData(data))) {
+        return nullptr;
+      }
+
+      return image.forget();
+    }
+  }
+  default:
+    return nullptr;
+  }
+}
+
+/*
+ * This is a synchronous task.
+ * This class is used to create a layers::CairoImage from raw data in the main
+ * thread. While creating an ImageBitmap from an BufferSource, we need to create
+ * a SouceSurface from the BufferSource raw data and then set the SourceSurface
+ * into a layers::CairoImage. However, the layers::CairoImage asserts the
+ * setting operation in the main thread, so if we are going to create an
+ * ImageBitmap from an BufferSource off the main thread, we post an event to the
+ * main thread to create a layers::CairoImage from an BufferSource raw data.
+ *
+ * TODO: Once the layers::CairoImage is constructible off the main thread, which
+ *       means the SouceSurface could be released anywhere, we do not need this
+ *       task anymore.
+ */
+class CreateImageFromBufferSourceRawDataInMainThreadSyncTask final :
+  public WorkerMainThreadRunnable
+{
+public:
+  CreateImageFromBufferSourceRawDataInMainThreadSyncTask(const uint8_t* aBuffer,
+                                                         uint32_t aBufferLength,
+                                                         mozilla::dom::ImageBitmapFormat aFormat,
+                                                         const Sequence<ChannelPixelLayout>& aLayout,
+                                                         /*output*/ layers::Image** aImage)
+  : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
+                             NS_LITERAL_CSTRING("ImageBitmap-extensions :: Create Image from BufferSource Raw Data"))
+  , mImage(aImage)
+  , mBuffer(aBuffer)
+  , mBufferLength(aBufferLength)
+  , mFormat(aFormat)
+  , mLayout(aLayout)
+  {
+    MOZ_ASSERT(!(*aImage), "Don't pass an existing Image into CreateImageFromBufferSourceRawDataInMainThreadSyncTask.");
+  }
+
+  bool MainThreadRun() override
+  {
+    RefPtr<layers::Image> image =
+      CreateImageFromBufferSourceRawData(mBuffer, mBufferLength, mFormat, mLayout);
+
+    if (NS_WARN_IF(!image)) {
+      return true;
+    }
+
+    image.forget(mImage);
+
+    return true;
+  }
+
+private:
+  layers::Image** mImage;
+  const uint8_t* mBuffer;
+  uint32_t mBufferLength;
+  mozilla::dom::ImageBitmapFormat mFormat;
+  const Sequence<ChannelPixelLayout>& mLayout;
+};
+
+/*static*/ already_AddRefed<Promise>
+ImageBitmap::Create(nsIGlobalObject* aGlobal,
+                    const ImageBitmapSource& aBuffer,
+                    int32_t aOffset, int32_t aLength,
+                    mozilla::dom::ImageBitmapFormat aFormat,
+                    const Sequence<ChannelPixelLayout>& aLayout,
+                    ErrorResult& aRv)
+{
+  MOZ_ASSERT(aGlobal);
+
+  RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  uint8_t* bufferData = nullptr;
+  uint32_t bufferLength = 0;
+
+  if (aBuffer.IsArrayBuffer()) {
+    const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
+    buffer.ComputeLengthAndData();
+    bufferData = buffer.Data();
+    bufferLength = buffer.Length();
+  } else if (aBuffer.IsArrayBufferView()) {
+    const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView();
+    bufferView.ComputeLengthAndData();
+    bufferData = bufferView.Data();
+    bufferLength = bufferView.Length();
+  } else {
+    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+    return promise.forget();
+  }
+
+  MOZ_ASSERT(bufferData && bufferLength > 0, "Cannot read data from BufferSource.");
+
+  // Check the buffer.
+  if (((uint32_t)(aOffset + aLength) > bufferLength)) {
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return promise.forget();
+  }
+
+  // Create and Crop the raw data into a layers::Image
+  RefPtr<layers::Image> data;
+  if (NS_IsMainThread()) {
+    data = CreateImageFromBufferSourceRawData(bufferData + aOffset, bufferLength,
+                                              aFormat, aLayout);
+  } else {
+    RefPtr<CreateImageFromBufferSourceRawDataInMainThreadSyncTask> task =
+      new CreateImageFromBufferSourceRawDataInMainThreadSyncTask(bufferData + aOffset,
+                                                                 bufferLength,
+                                                                 aFormat,
+                                                                 aLayout,
+                                                                 getter_AddRefs(data));
+    task->Dispatch(aRv);
+  }
+
+  if (NS_WARN_IF(!data)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return promise.forget();
+  }
+
+  // Create an ImageBimtap.
+  // Assume the data from an external buffer is not alpha-premultiplied.
+  RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data, false);
+
+  // We don't need to call SetPictureRect() here because there is no cropping
+  // supported and the ImageBitmap's mPictureRect is the size of the source
+  // image in default
+
+  AsyncFulfillImageBitmapPromise(promise, imageBitmap);
+
+  return promise.forget();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/canvas/ImageBitmap.h
+++ b/dom/canvas/ImageBitmap.h
@@ -38,16 +38,17 @@ namespace dom {
 class OffscreenCanvas;
 
 namespace workers {
 class WorkerStructuredCloneClosure;
 }
 
 class ArrayBufferViewOrArrayBuffer;
 class CanvasRenderingContext2D;
+struct ChannelPixelLayout;
 class CreateImageBitmapFromBlob;
 class CreateImageBitmapFromBlobTask;
 class CreateImageBitmapFromBlobWorkerTask;
 class File;
 class HTMLCanvasElement;
 class HTMLImageElement;
 class HTMLVideoElement;
 enum class ImageBitmapFormat : uint32_t;
@@ -123,16 +124,24 @@ public:
   CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
                             OffscreenCanvas& aOffscreenCanvas,
                             ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
          const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
 
+  static already_AddRefed<Promise>
+  Create(nsIGlobalObject* aGlobal,
+         const ImageBitmapSource& aBuffer,
+         int32_t aOffset, int32_t aLength,
+         mozilla::dom::ImageBitmapFormat aFormat,
+         const Sequence<mozilla::dom::ChannelPixelLayout>& aLayout,
+         ErrorResult& aRv);
+
   static JSObject*
   ReadStructuredClone(JSContext* aCx,
                       JSStructuredCloneReader* aReader,
                       nsIGlobalObject* aParent,
                       const nsTArray<RefPtr<gfx::DataSourceSurface>>& aClonedSurfaces,
                       uint32_t aIndex);
 
   static bool