Bug 1141979 - part7 - implement ImageBitmapFactories extensions; r=roc draft
authorKaku Kuo <tkuo@mozilla.com>
Fri, 11 Mar 2016 11:48:06 +0800
changeset 341992 4ed4976a4a7a1926fb0f03514cdb897cca9b6395
parent 341991 c25b6cf47b65920cffdaeda8989ae00b18b11518
child 341993 7f055cb16bc9c30a347484aeddf951318e8fccb5
push id13340
push usertkuo@mozilla.com
push dateFri, 18 Mar 2016 09:53:30 +0000
reviewersroc
bugs1141979
milestone48.0a1
Bug 1141979 - part7 - implement ImageBitmapFactories extensions; r=roc MozReview-Commit-ID: 6Hm58nffAuV
dom/canvas/ImageBitmap.cpp
dom/canvas/ImageBitmap.h
gfx/2d/Tools.h
gfx/2d/Types.h
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -1639,10 +1639,399 @@ ImageBitmap::MapDataInto(JSContext* aCx,
     aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
     return promise.forget();
   }
 
   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::YUV444P:
+  case ImageBitmapFormat::YUV422P:
+  case ImageBitmapFormat::YUV420P:
+  case ImageBitmapFormat::YUV420SP_NV12:
+  case ImageBitmapFormat::YUV420SP_NV21:
+  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,
+                                   const Maybe<gfx::IntRect>& aCropRect,
+                                   /*output*/ Maybe<gfx::IntRect>& aPictureRect)
+{
+  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 int32_t srcStride = channels[0].mStride;
+    const IntSize srcSize(channels[0].mWidth, channels[0].mHeight);
+
+    // Wrap the source buffer into a SourceSurface.
+    RefPtr<DataSourceSurface> srcDataSurface =
+      Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aBufferData),
+                                               srcStride, srcSize, srcFormat);
+    if (NS_WARN_IF(!srcDataSurface)) {
+      return nullptr;
+    }
+
+    // The temporary cropRect variable is equal to the size of source buffer if we
+    // do not need to crop, or it equals to the given cropping size.
+    const IntRect cropRect = aCropRect.valueOr(IntRect(0, 0, srcSize.width, srcSize.height));
+
+    // Copy the source buffer in the _cropRect_ area into a new SourceSurface.
+
+    // Check the aCropRect
+    ErrorResult error;
+    const IntRect positiveCropRect = FixUpNegativeDimension(cropRect, error);
+    if (NS_WARN_IF(error.Failed())) {
+      return nullptr;
+    }
+
+    // Create a new SourceSurface.
+    RefPtr<DataSourceSurface> dstDataSurface;
+
+    // Only do copying and cropping when the positiveCropRect intersects with
+    // the size of srcDataSurface.
+    const IntRect surfRect(IntPoint(0, 0), srcDataSurface->GetSize());
+    if (surfRect.Intersects(positiveCropRect)) {
+      const IntRect surfPortion = surfRect.Intersect(positiveCropRect);
+
+      // Calculate the size of the new SourceSurface.
+      const int bytesPerPixel = BytesPerPixel(srcFormat);
+      const IntSize dstSize = IntSize(surfPortion.width, surfPortion.height);
+      const uint32_t dstStride = dstSize.width * bytesPerPixel;
+
+      dstDataSurface =
+        Factory::CreateDataSourceSurfaceWithStride(dstSize, srcFormat, dstStride);
+
+      if (NS_WARN_IF(!dstDataSurface)) {
+        return nullptr;
+      }
+
+      // Copy the raw data into the newly created DataSourceSurface.
+      DataSourceSurface::ScopedMap srcMap(srcDataSurface, DataSourceSurface::READ);
+      DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE);
+      if (NS_WARN_IF(!srcMap.IsMapped()) || NS_WARN_IF(!dstMap.IsMapped())) {
+        return nullptr;
+      }
+
+      uint8_t* srcBufferPtr = srcMap.GetData() + surfPortion.y * srcMap.GetStride()
+                                               + surfPortion.x * bytesPerPixel;
+      uint8_t* dstBufferPtr = dstMap.GetData();
+
+      const uint32_t copiedBytesPerRaw = surfPortion.width * bytesPerPixel;
+
+      for (int i = 0; i < surfPortion.height; ++i) {
+        memcpy(dstBufferPtr, srcBufferPtr, copiedBytesPerRaw);
+        srcBufferPtr += srcMap.GetStride();
+        dstBufferPtr += dstMap.GetStride();
+      }
+
+      // Fixup the aPictureRect so that the ImageBitmap::Create() will call
+      // setPictureRect correctly.
+      aPictureRect = Some(positiveCropRect);
+      aPictureRect->MoveBy(-surfPortion.x, -surfPortion.y);
+
+    } else {
+      // The cropping area has no intersetion with the source image. Create a
+      // new surface with size of cropping area and fill it with transparent
+      // black.
+      const SurfaceFormat format = SurfaceFormat::B8G8R8A8;
+      const int bytesPerPixel = BytesPerPixel(format);
+      const IntSize dstSize = IntSize(positiveCropRect.width,
+                                      positiveCropRect.height);
+      const uint32_t dstStride = dstSize.width * bytesPerPixel;
+
+      dstDataSurface =
+        Factory::CreateDataSourceSurfaceWithStride(dstSize, format, dstStride, true);
+    }
+
+    // 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.
+    // We defer the cropping operation to the moment when this ImageBitmap is
+    // going to be drawn onto a canvas element (a.k.a. in the
+    // ImageBitmap::PrepareForDrawTarget() method.) because it is not trivial
+    // how to crop YUV422/YUV420P data which needs re-sampling and the cropping
+    // operation is done by first convert the YUV data into RGB space and then
+    // do the cropping there.
+    data.mPicX = 0;
+    data.mPicY = 0;
+    data.mPicSize = data.mYSize;
+
+    // Set the aPictureRect so that the ImageBitmap::Create() will call
+    // setPictureRect().
+    if (aCropRect.isSome()) {
+      aPictureRect = aCropRect;
+    }
+
+    // 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->SetData(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,
+                                                         const Maybe<gfx::IntRect>& aCropRect,
+                                                         /*output*/ layers::Image** aImage,
+                                                         /*output*/ Maybe<gfx::IntRect>& aPictureRect)
+  : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate())
+  , mImage(aImage)
+  , mBuffer(aBuffer)
+  , mBufferLength(aBufferLength)
+  , mFormat(aFormat)
+  , mLayout(aLayout)
+  , mCropRect(aCropRect)
+  , mPictureRect(aPictureRect)
+  {
+    MOZ_ASSERT(!(*aImage), "Don't pass an existing Image into CreateImageFromBufferSourceRawDataInMainThreadSyncTask.");
+  }
+
+  bool MainThreadRun() override
+  {
+    RefPtr<layers::Image> image =
+      CreateImageFromBufferSourceRawData(mBuffer, mBufferLength, mFormat,
+                                         mLayout, mCropRect, mPictureRect);
+
+    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;
+  const Maybe<IntRect>& mCropRect;
+  Maybe<IntRect>& mPictureRect;
+};
+
+/*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,
+                    const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv)
+{
+  MOZ_ASSERT(aGlobal);
+
+  RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  if (!IsSupportedFormat(aFormat)) {
+    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+    return promise.forget();
+  }
+
+  if (aCropRect.isSome() && (aCropRect->Width() == 0 || aCropRect->Height() == 0)) {
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return promise.forget();
+  }
+
+  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;
+  Maybe<IntRect> pictureRect = Nothing();
+  if (NS_IsMainThread()) {
+    data = CreateImageFromBufferSourceRawData(bufferData + aOffset, bufferLength,
+                                              aFormat, aLayout, aCropRect,
+                                              pictureRect);
+  } else {
+    RefPtr<CreateImageFromBufferSourceRawDataInMainThreadSyncTask> task =
+      new CreateImageFromBufferSourceRawDataInMainThreadSyncTask(bufferData + aOffset,
+                                                                 bufferLength,
+                                                                 aFormat,
+                                                                 aLayout,
+                                                                 aCropRect,
+                                                                 getter_AddRefs(data),
+                                                                 pictureRect);
+    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);
+  if (pictureRect) {
+    imageBitmap->SetPictureRect(pictureRect.ref(), aRv);
+
+    if (aRv.Failed()) {
+      return promise.forget();
+    }
+  }
+
+  // The cropping information has been handled in the CreateImageFromRawData()
+  // function.
+
+  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,
+         const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
+
   static JSObject*
   ReadStructuredClone(JSContext* aCx,
                       JSStructuredCloneReader* aReader,
                       nsIGlobalObject* aParent,
                       const nsTArray<RefPtr<gfx::DataSourceSurface>>& aClonedSurfaces,
                       uint32_t aIndex);
 
   static bool
--- a/gfx/2d/Tools.h
+++ b/gfx/2d/Tools.h
@@ -83,16 +83,19 @@ Distance(Point aA, Point aB)
 static inline int
 BytesPerPixel(SurfaceFormat aFormat)
 {
   switch (aFormat) {
   case SurfaceFormat::A8:
     return 1;
   case SurfaceFormat::R5G6B5_UINT16:
     return 2;
+  case SurfaceFormat::R8G8B8:
+  case SurfaceFormat::B8G8R8:
+    return 3;
   case SurfaceFormat::HSV:
   case SurfaceFormat::Lab:
     return 3 * sizeof(float);
   case SurfaceFormat::Depth:
     return sizeof(uint16_t);
   default:
     return 4;
   }
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -38,16 +38,19 @@ enum class SurfaceFormat : int8_t {
   //               in-memory            32-bit LE value   32-bit BE value
   B8G8R8A8,     // [BB, GG, RR, AA]     0xAARRGGBB        0xBBGGRRAA
   B8G8R8X8,     // [BB, GG, RR, 00]     0x00RRGGBB        0xBBGGRR00
   R8G8B8A8,     // [RR, GG, BB, AA]     0xAABBGGRR        0xRRGGBBAA
   R8G8B8X8,     // [RR, GG, BB, 00]     0x00BBGGRR        0xRRGGBB00
   A8R8G8B8,     // [AA, RR, GG, BB]     0xBBGGRRAA        0xAARRGGBB
   X8R8G8B8,     // [00, RR, GG, BB]     0xBBGGRR00        0x00RRGGBB
 
+  R8G8B8,
+  B8G8R8,
+
   // The _UINT16 suffix here indicates that the name reflects the layout when
   // viewed as a uint16_t value. In memory these values are stored using native
   // endianness.
   R5G6B5_UINT16,                    // 0bRRRRRGGGGGGBBBBB
 
   // This one is a single-byte, so endianness isn't an issue.
   A8,