Bug 1404222 Part 1: Implement shape-outside: <image>. r=dbaron,dholbert
authorTing-Yu Lin <aethanyc@gmail.com>, Brad Werth <bwerth@mozilla.com>
Thu, 25 Jan 2018 14:55:18 +0800
changeset 776863 68f21533e6db67f747ffce2d42d28526deed0cf0
parent 776862 c93a8362c8ac026f28a27f5dc88c5a4cac071a9b
child 776864 9f645937f6c00768586d5260c8575a0b9e7fd15c
push id105022
push userdholbert@mozilla.com
push dateTue, 03 Apr 2018 21:03:05 +0000
reviewersdbaron, dholbert
bugs1404222
milestone61.0a1
Bug 1404222 Part 1: Implement shape-outside: <image>. r=dbaron,dholbert When creating ImageShapeInfo, it's likely that the image is still decoding. Part 2 will add mechanism to trigger reflow after the image is ready.
layout/generic/nsFloatManager.cpp
layout/generic/nsFloatManager.h
layout/painting/nsImageRenderer.cpp
layout/painting/nsImageRenderer.h
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -6,20 +6,22 @@
 
 /* class that manages rules for positioning floats */
 
 #include "nsFloatManager.h"
 
 #include <algorithm>
 #include <initializer_list>
 
+#include "gfxContext.h"
 #include "mozilla/ReflowInput.h"
 #include "mozilla/ShapeUtils.h"
 #include "nsBlockFrame.h"
 #include "nsError.h"
+#include "nsImageRenderer.h"
 #include "nsIPresShell.h"
 #include "nsMemory.h"
 
 using namespace mozilla;
 
 int32_t nsFloatManager::sCachedFloatManagerCount = 0;
 void* nsFloatManager::sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE];
 
@@ -170,19 +172,18 @@ nsFloatManager::GetFlowArea(WritingMode 
   bool haveFloats = false;
   for (uint32_t i = floatCount; i > 0; --i) {
     const FloatInfo &fi = mFloats[i-1];
     if (fi.mLeftBEnd <= blockStart && fi.mRightBEnd <= blockStart) {
       // There aren't any more floats that could intersect this band.
       break;
     }
     if (fi.IsEmpty(aShapeType)) {
-      // For compatibility, ignore floats with empty rects, even though it
-      // disagrees with the spec.  (We might want to fix this in the
-      // future, though.)
+      // Ignore empty float areas.
+      // https://drafts.csswg.org/css-shapes/#relation-to-box-model-and-float-behavior
       continue;
     }
 
     nscoord floatBStart = fi.BStart(aShapeType);
     nscoord floatBEnd = fi.BEnd(aShapeType);
     if (blockStart < floatBStart && aBandInfoType == BandInfoType::BandFromPoint) {
       // This float is below our band.  Shrink our band's height if needed.
       if (floatBStart < blockEnd) {
@@ -575,16 +576,23 @@ public:
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreatePolygon(
     const UniquePtr<StyleBasicShape>& aBasicShape,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
+  static UniquePtr<ShapeInfo> CreateImageShape(
+    const UniquePtr<nsStyleImage>& aShapeImage,
+    float aShapeImageThreshold,
+    nsIFrame* const aFrame,
+    WritingMode aWM,
+    const nsSize& aContainerSize);
+
 protected:
   // Compute the minimum line-axis difference between the bounding shape
   // box and its rounded corner within the given band (block-axis region).
   // This is used as a helper function to compute the LineRight() and
   // LineLeft(). See the picture in the implementation for an example.
   // RadiusL and RadiusB stand for radius on the line-axis and block-axis.
   //
   // Returns radius-x diff on the line-axis, or 0 if there's no rounded
@@ -610,16 +618,17 @@ protected:
     const nscoord aRadii[8],
     WritingMode aWM);
 };
 
 /////////////////////////////////////////////////////////////////////////////
 // RoundedBoxShapeInfo
 //
 // Implements shape-outside: <shape-box> and shape-outside: inset().
+//
 class nsFloatManager::RoundedBoxShapeInfo final : public nsFloatManager::ShapeInfo
 {
 public:
   RoundedBoxShapeInfo(const nsRect& aRect,
                       UniquePtr<nscoord[]> aRadii)
     : mRect(aRect)
     , mRadii(Move(aRadii))
   {}
@@ -948,44 +957,304 @@ nsFloatManager::PolygonShapeInfo::XInter
 
   MOZ_ASSERT(aP1.y != aP2.y,
              "A horizontal line segment results in dividing by zero error!");
 
   return aP1.x + (aY - aP1.y) * (aP2.x - aP1.x) / (aP2.y - aP1.y);
 }
 
 /////////////////////////////////////////////////////////////////////////////
+// ImageShapeInfo
+//
+// Implements shape-outside: <image>
+//
+class nsFloatManager::ImageShapeInfo final : public nsFloatManager::ShapeInfo
+{
+public:
+  ImageShapeInfo(uint8_t* aAlphaPixels,
+                 int32_t aStride,
+                 const LayoutDeviceIntSize& aImageSize,
+                 int32_t aAppUnitsPerDevPixel,
+                 float aShapeImageThreshold,
+                 const nsRect& aContentRect,
+                 WritingMode aWM,
+                 const nsSize& aContainerSize);
+
+  nscoord LineLeft(const nscoord aBStart,
+                   const nscoord aBEnd) const override;
+  nscoord LineRight(const nscoord aBStart,
+                    const nscoord aBEnd) const override;
+  nscoord BStart() const override { return mBStart; }
+  nscoord BEnd() const override { return mBEnd; }
+  bool IsEmpty() const override { return mIntervals.IsEmpty(); }
+
+  void Translate(nscoord aLineLeft, nscoord aBlockStart) override;
+
+private:
+  size_t MinIntervalIndexContainingY(const nscoord aTargetY) const;
+  nscoord LineEdge(const nscoord aBStart,
+                   const nscoord aBEnd,
+                   bool aLeft) const;
+
+  // An interval is slice of the float area defined by this ImageShapeInfo.
+  // Each interval is a rectangle that is one pixel deep in the block
+  // axis. The values are stored as block edges in the y coordinates,
+  // and inline edges as the x coordinates.
+
+  // The intervals are stored in ascending order on y.
+  nsTArray<nsRect> mIntervals;
+
+  nscoord mBStart = nscoord_MAX;
+  nscoord mBEnd = nscoord_MIN;
+};
+
+nsFloatManager::ImageShapeInfo::ImageShapeInfo(
+  uint8_t* aAlphaPixels,
+  int32_t aStride,
+  const LayoutDeviceIntSize& aImageSize,
+  int32_t aAppUnitsPerDevPixel,
+  float aShapeImageThreshold,
+  const nsRect& aContentRect,
+  WritingMode aWM,
+  const nsSize& aContainerSize)
+{
+  MOZ_ASSERT(aShapeImageThreshold >=0.0 && aShapeImageThreshold <=1.0,
+             "The computed value of shape-image-threshold is wrong!");
+
+  const uint8_t threshold = NSToIntFloor(aShapeImageThreshold * 255);
+  const int32_t w = aImageSize.width;
+  const int32_t h = aImageSize.height;
+
+  // Scan the pixels in a double loop. For horizontal writing modes, we do
+  // this row by row, from top to bottom. For vertical writing modes, we do
+  // column by column, from left to right. We define the two loops
+  // generically, then figure out the rows and cols within the i loop.
+  const int32_t bSize = aWM.IsVertical() ? w : h;
+  const int32_t iSize = aWM.IsVertical() ? h : w;
+  for (int32_t b = 0; b < bSize; ++b) {
+    // iMin and iMax store the start and end of the float area for the row
+    // or column represented by this iteration of the b loop.
+    int32_t iMin = -1;
+    int32_t iMax = -1;
+
+    for (int32_t i = 0; i < iSize; ++i) {
+      const int32_t col = aWM.IsVertical() ? b : i;
+      const int32_t row = aWM.IsVertical() ? i : b;
+
+      // Determine if the alpha pixel at this row and column has a value
+      // greater than the threshold. If it does, update our iMin and iMax values
+      // to track the edges of the float area for this row or column.
+      // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
+      const uint8_t alpha = aAlphaPixels[col + row * aStride];
+      if (alpha > threshold) {
+        if (iMin == -1) {
+          iMin = i;
+        }
+        MOZ_ASSERT(iMax < i);
+        iMax = i;
+      }
+    }
+
+    // At the end of a row or column; did we find something?
+    if (iMin != -1) {
+      // Store an interval as an nsRect with our inline axis values stored in x
+      // and our block axis values stored in y. The position is dependent on
+      // the writing mode, but the size is the same for all writing modes.
+
+      // Size is the difference in inline axis edges stored as x, and one
+      // block axis pixel stored as y. For the inline axis, we add 1 to iMax
+      // because we want to capture the far edge of the last pixel.
+      nsSize size(((iMax + 1) - iMin) * aAppUnitsPerDevPixel,
+                  aAppUnitsPerDevPixel);
+
+      // Since we started our scanning of the image pixels from the top left,
+      // the interval position starts from the origin of the content rect,
+      // converted to logical coordinates.
+      nsPoint origin = ConvertToFloatLogical(aContentRect.TopLeft(), aWM,
+                                             aContainerSize);
+
+      // Depending on the writing mode, we now move the origin.
+      if (aWM.IsVerticalRL()) {
+        // vertical-rl or sideways-rl.
+        // These writing modes proceed from the top right, and each interval
+        // moves in a positive inline direction and negative block direction.
+        // That means that the intervals will be reversed after all have been
+        // constructed. We add 1 to b to capture the end of the block axis pixel.
+        origin.MoveBy(iMin * aAppUnitsPerDevPixel, (b + 1) * -aAppUnitsPerDevPixel);
+      } else if (aWM.IsVerticalLR() && aWM.IsSideways()) {
+        // sideways-lr.
+        // These writing modes proceed from the bottom left, and each interval
+        // moves in a negative inline direction and a positive block direction.
+        // We add 1 to iMax to capture the end of the inline axis pixel.
+        origin.MoveBy((iMax + 1) * -aAppUnitsPerDevPixel, b * aAppUnitsPerDevPixel);
+      } else {
+        // horizontal-tb or vertical-lr.
+        // These writing modes proceed from the top left and each interval
+        // moves in a positive step in both inline and block directions.
+        origin.MoveBy(iMin * aAppUnitsPerDevPixel, b * aAppUnitsPerDevPixel);
+      }
+
+      mIntervals.AppendElement(nsRect(origin, size));
+    }
+  }
+
+  if (aWM.IsVerticalRL()) {
+    // vertical-rl or sideways-rl.
+    // Because we scan the columns from left to right, we need to reverse
+    // the array so that it's sorted (in ascending order) on the block
+    // direction.
+    mIntervals.Reverse();
+  }
+
+  if (!mIntervals.IsEmpty()) {
+    mBStart = mIntervals[0].Y();
+    mBEnd = mIntervals.LastElement().YMost();
+  }
+}
+
+size_t
+nsFloatManager::ImageShapeInfo::MinIntervalIndexContainingY(
+  const nscoord aTargetY) const
+{
+  // Perform a binary search to find the minimum index of an interval
+  // that contains aTargetY. If no such interval exists, return a value
+  // equal to the number of intervals.
+  size_t startIdx = 0;
+  size_t endIdx = mIntervals.Length();
+  while (startIdx < endIdx) {
+    size_t midIdx = startIdx + (endIdx - startIdx) / 2;
+    if (mIntervals[midIdx].ContainsY(aTargetY)) {
+      return midIdx;
+    }
+    nscoord midY = mIntervals[midIdx].Y();
+    if (midY < aTargetY) {
+      startIdx = midIdx + 1;
+    } else {
+      endIdx = midIdx;
+    }
+  }
+
+  return endIdx;
+}
+
+nscoord
+nsFloatManager::ImageShapeInfo::LineEdge(const nscoord aBStart,
+                                         const nscoord aBEnd,
+                                         bool aLeft) const
+{
+  MOZ_ASSERT(aBStart <= aBEnd,
+             "The band's block start is greater than its block end?");
+
+  // Find all the intervals whose rects overlap the aBStart to
+  // aBEnd range, and find the most constraining inline edge
+  // depending on the value of aLeft.
+
+  // Since the intervals are stored in block-axis order, we need
+  // to find the first interval that overlaps aBStart and check
+  // succeeding intervals until we get past aBEnd.
+
+  nscoord lineEdge = aLeft ? nscoord_MAX : nscoord_MIN;
+
+  size_t intervalCount = mIntervals.Length();
+  for (size_t i = MinIntervalIndexContainingY(aBStart);
+	   i < intervalCount; ++i) {
+    // We can always get the bCoord from the intervals' mLineLeft,
+    // since the y() coordinate is duplicated in both points in the
+    // interval.
+    auto& interval = mIntervals[i];
+    nscoord bCoord = interval.Y();
+    if (bCoord > aBEnd) {
+      break;
+    }
+    // Get the edge from the interval point indicated by aLeft.
+    if (aLeft) {
+      lineEdge = std::min(lineEdge, interval.X());
+    } else {
+      lineEdge = std::max(lineEdge, interval.XMost());
+    }
+  }
+
+  return lineEdge;
+}
+
+nscoord
+nsFloatManager::ImageShapeInfo::LineLeft(const nscoord aBStart,
+                                         const nscoord aBEnd) const
+{
+  return LineEdge(aBStart, aBEnd, true);
+}
+
+nscoord
+nsFloatManager::ImageShapeInfo::LineRight(const nscoord aBStart,
+                                          const nscoord aBEnd) const
+{
+  return LineEdge(aBStart, aBEnd, false);
+}
+
+void
+nsFloatManager::ImageShapeInfo::Translate(nscoord aLineLeft,
+                                          nscoord aBlockStart)
+{
+  for (nsRect& interval : mIntervals) {
+    interval.MoveBy(aLineLeft, aBlockStart);
+  }
+
+  mBStart += aBlockStart;
+  mBEnd += aBlockStart;
+}
+
+/////////////////////////////////////////////////////////////////////////////
 // FloatInfo
 
 nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame,
                                      nscoord aLineLeft, nscoord aBlockStart,
                                      const LogicalRect& aMarginRect,
                                      WritingMode aWM,
                                      const nsSize& aContainerSize)
   : mFrame(aFrame)
   , mRect(ShapeInfo::ConvertToFloatLogical(aMarginRect, aWM, aContainerSize) +
           nsPoint(aLineLeft, aBlockStart))
 {
   MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
 
+  if (IsEmpty()) {
+    // Per spec, a float area defined by a shape is clipped to the float’s
+    // margin box. Therefore, no need to create a shape info if the float's
+    // margin box is empty, since a float area can only be smaller than the
+    // margin box.
+
+    // https://drafts.csswg.org/css-shapes/#relation-to-box-model-and-float-behavior
+    return;
+  }
+
   const StyleShapeSource& shapeOutside = mFrame->StyleDisplay()->mShapeOutside;
 
   switch (shapeOutside.GetType()) {
     case StyleShapeSourceType::None:
       // No need to create shape info.
       return;
 
     case StyleShapeSourceType::URL:
       MOZ_ASSERT_UNREACHABLE("shape-outside doesn't have URL source type!");
       return;
 
-    case StyleShapeSourceType::Image:
-      // Bug 1265343: Implement 'shape-image-threshold'
-      // Bug 1404222: Support shape-outside: <image>
-      return;
+    case StyleShapeSourceType::Image: {
+      float shapeImageThreshold = mFrame->StyleDisplay()->mShapeImageThreshold;
+      mShapeInfo = ShapeInfo::CreateImageShape(shapeOutside.GetShapeImage(),
+                                               shapeImageThreshold,
+                                               mFrame,
+                                               aWM,
+                                               aContainerSize);
+      if (!mShapeInfo) {
+        // Image is not ready, or fails to load, etc.
+        return;
+      }
+
+      break;
+    }
 
     case StyleShapeSourceType::Box: {
       // Initialize <shape-box>'s reference rect.
       LogicalRect shapeBoxRect =
         ShapeInfo::ComputeShapeBoxRect(shapeOutside, mFrame, aMarginRect, aWM);
       mShapeInfo = ShapeInfo::CreateShapeBox(mFrame, shapeBoxRect, aWM,
                                              aContainerSize);
       break;
@@ -1272,16 +1541,94 @@ nsFloatManager::ShapeInfo::CreatePolygon
   // Convert all the physical vertices to logical.
   for (nsPoint& vertex : vertices) {
     vertex = ConvertToFloatLogical(vertex, aWM, aContainerSize);
   }
 
   return MakeUnique<PolygonShapeInfo>(Move(vertices));
 }
 
+/* static */ UniquePtr<nsFloatManager::ShapeInfo>
+nsFloatManager::ShapeInfo::CreateImageShape(
+  const UniquePtr<nsStyleImage>& aShapeImage,
+  float aShapeImageThreshold,
+  nsIFrame* const aFrame,
+  WritingMode aWM,
+  const nsSize& aContainerSize)
+{
+  MOZ_ASSERT(aShapeImage ==
+             aFrame->StyleDisplay()->mShapeOutside.GetShapeImage(),
+             "aFrame should be the frame that we got aShapeImage from");
+
+  nsImageRenderer imageRenderer(aFrame, aShapeImage.get(),
+                                nsImageRenderer::FLAG_SYNC_DECODE_IMAGES);
+
+  if (!imageRenderer.PrepareImage()) {
+    // The image is not ready yet.
+    return nullptr;
+  }
+
+  nsRect contentRect = aFrame->GetContentRect();
+
+  // Create a draw target and draw shape image on it.
+  nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
+  int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
+  LayoutDeviceIntSize contentSizeInDevPixels =
+    LayoutDeviceIntSize::FromAppUnitsRounded(contentRect.Size(),
+                                             appUnitsPerDevPixel);
+
+  // Use empty CSSSizeOrRatio to force set the preferred size as the frame's
+  // content box size.
+  imageRenderer.SetPreferredSize(CSSSizeOrRatio(), contentRect.Size());
+
+  RefPtr<gfx::DrawTarget> drawTarget =
+    gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
+      contentSizeInDevPixels.ToUnknownSize(),
+      gfx::SurfaceFormat::A8);
+  if (!drawTarget) {
+    return nullptr;
+  }
+
+  RefPtr<gfxContext> context = gfxContext::CreateOrNull(drawTarget);
+  MOZ_ASSERT(context); // already checked the target above
+
+  ImgDrawResult result =
+    imageRenderer.DrawShapeImage(aFrame->PresContext(), *context);
+
+  if (result != ImgDrawResult::SUCCESS) {
+    return nullptr;
+  }
+
+  // Retrieve the pixel image buffer to create the image shape info.
+  RefPtr<SourceSurface> sourceSurface = drawTarget->Snapshot();
+  RefPtr<DataSourceSurface> dataSourceSurface = sourceSurface->GetDataSurface();
+  DataSourceSurface::ScopedMap map(dataSourceSurface, DataSourceSurface::READ);
+
+  if (!map.IsMapped()) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(sourceSurface->GetSize() == contentSizeInDevPixels.ToUnknownSize(),
+             "Who changes the size?");
+
+  uint8_t* alphaPixels = map.GetData();
+  int32_t stride = map.GetStride();
+
+  // NOTE: ImageShapeInfo constructor does not keep a persistent copy of
+  // alphaPixels; it's only used during the constructor to compute pixel ranges.
+  return MakeUnique<ImageShapeInfo>(alphaPixels,
+                                    stride,
+                                    contentSizeInDevPixels,
+                                    appUnitsPerDevPixel,
+                                    aShapeImageThreshold,
+                                    contentRect,
+                                    aWM,
+                                    aContainerSize);
+}
+
 /* static */ nscoord
 nsFloatManager::ShapeInfo::ComputeEllipseLineInterceptDiff(
   const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
   const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
   const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
   const nscoord aBandBStart, const nscoord aBandBEnd)
 {
   // An example for the band intersecting with the top right corner of an
--- a/layout/generic/nsFloatManager.h
+++ b/layout/generic/nsFloatManager.h
@@ -339,16 +339,17 @@ public:
 #endif
 
 private:
 
   class ShapeInfo;
   class RoundedBoxShapeInfo;
   class EllipseShapeInfo;
   class PolygonShapeInfo;
+  class ImageShapeInfo;
 
   struct FloatInfo {
     nsIFrame *const mFrame;
     // The lowest block-ends of left/right floats up to and including
     // this one.
     nscoord mLeftBEnd, mRightBEnd;
 
     FloatInfo(nsIFrame* aFrame, nscoord aLineLeft, nscoord aBlockStart,
--- a/layout/painting/nsImageRenderer.cpp
+++ b/layout/painting/nsImageRenderer.cpp
@@ -1,28 +1,30 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* utility functions for drawing borders and backgrounds */
+/* utility code for drawing images as CSS borders, backgrounds, and shapes. */
 
 #include "nsImageRenderer.h"
 
 #include "mozilla/webrender/WebRenderAPI.h"
 
 #include "gfxContext.h"
 #include "gfxDrawable.h"
 #include "ImageOps.h"
+#include "ImageRegion.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "nsContentUtils.h"
 #include "nsCSSRendering.h"
 #include "nsCSSRenderingGradients.h"
+#include "nsDeviceContext.h"
 #include "nsIFrame.h"
 #include "nsStyleStructInlines.h"
 #include "nsSVGDisplayableFrame.h"
 #include "SVGObserverUtils.h"
 #include "nsSVGIntegrationUtils.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 
 using namespace mozilla;
@@ -941,20 +943,83 @@ nsImageRenderer::DrawBorderImageComponen
                                               ExtendMode::CLAMP, 1.0);
   }
 
   nsSize repeatSize(aFill.Size());
   nsRect fillRect(aFill);
   nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
                   ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
                   : fillRect;
+
   return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
               fillRect, destTile.TopLeft(), repeatSize, aSrc);
 }
 
+ImgDrawResult
+nsImageRenderer::DrawShapeImage(nsPresContext* aPresContext,
+                                gfxContext& aRenderingContext)
+{
+  if (!IsReady()) {
+    NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+    return ImgDrawResult::NOT_READY;
+  }
+
+  if (mSize.width <= 0 || mSize.height <= 0) {
+    return ImgDrawResult::SUCCESS;
+  }
+
+  ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+  switch (mType) {
+    case eStyleImageType_Image: {
+      uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
+                           imgIContainer::FRAME_FIRST;
+      nsRect dest(nsPoint(0, 0), mSize);
+      // We have a tricky situation in our choice of SamplingFilter. Shape images
+      // define a float area based on the alpha values in the rendered pixels.
+      // When multiple device pixels are used for one css pixel, the sampling
+      // can change crisp edges into aliased edges. For visual pixels, that's
+      // usually the right choice. For defining a float area, it can cause problems.
+      // If a style is using a shape-image-threshold value that is less than the
+      // alpha of the edge pixels, any filtering may smear the alpha into adjacent
+      // pixels and expand the float area in a confusing way. Since the alpha
+      // threshold can be set precisely in CSS, and since a web author may be
+      // counting on that threshold to define a precise float area from an image,
+      // it is least confusing to have the rendered pixels have unfiltered alpha.
+      // We use SamplingFilter::POINT to ensure that each rendered pixel has an
+      // alpha that precisely matches the alpha of the closest pixel in the image.
+      result = nsLayoutUtils::DrawSingleImage(aRenderingContext, aPresContext,
+                                              mImageContainer,
+                                              SamplingFilter::POINT,
+                                              dest, dest, Nothing(),
+                                              drawFlags,
+                                              nullptr, nullptr);
+      break;
+    }
+
+    case eStyleImageType_Gradient: {
+      nsCSSGradientRenderer renderer =
+        nsCSSGradientRenderer::Create(aPresContext, mGradientData, mSize);
+      nsRect dest(nsPoint(0, 0), mSize);
+
+      renderer.Paint(aRenderingContext, dest, dest, mSize,
+                     CSSIntRect::FromAppUnitsRounded(dest),
+                     dest, 1.0);
+      break;
+    }
+
+    default:
+      // Unsupported image type.
+      result = ImgDrawResult::BAD_IMAGE;
+      break;
+  }
+
+  return result;
+}
+
 bool
 nsImageRenderer::IsRasterImage()
 {
   if (mType != eStyleImageType_Image || !mImageContainer)
     return false;
   return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
 }
 
--- a/layout/painting/nsImageRenderer.h
+++ b/layout/painting/nsImageRenderer.h
@@ -249,16 +249,24 @@ public:
                            const mozilla::CSSIntRect& aSrc,
                            mozilla::StyleBorderImageRepeat aHFill,
                            mozilla::StyleBorderImageRepeat aVFill,
                            const nsSize&        aUnitSize,
                            uint8_t              aIndex,
                            const mozilla::Maybe<nsSize>& aSVGViewportSize,
                            const bool           aHasIntrinsicRatio);
 
+  /**
+   * Draw the image to aRenderingContext which can be used to define the
+   * float area in the presence of "shape-outside: <image>".
+   */
+  ImgDrawResult
+  DrawShapeImage(nsPresContext* aPresContext,
+                 gfxContext& aRenderingContext);
+
   bool IsRasterImage();
   bool IsAnimatedImage();
 
   /// Retrieves the image associated with this nsImageRenderer, if there is one.
   already_AddRefed<imgIContainer> GetImage();
 
   bool IsImageContainerAvailable(layers::LayerManager* aManager, uint32_t aFlags);
   bool IsReady() const { return mPrepareResult == ImgDrawResult::SUCCESS; }