Bug 1404222 Part 1 - Implement shape-outside: <image>.
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.
MozReview-Commit-ID: FpWE7g7NK70
--- 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];
@@ -575,16 +577,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 +619,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 +958,258 @@ 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* aAlphaBitmaps,
+ const CSSIntSize& aSize,
+ 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:
+ // An Interval stores line-left and line-right at a given block coordinate
+ // in the float manager's coordinate space. The Y() of mLineLeft and
+ // mLineRight should be the same.
+ struct Interval {
+ nsPoint mLineLeft;
+ nsPoint mLineRight;
+ };
+
+ // The intervals should be sorted in ascending order on mLineLeft.Y().
+ nsTArray<Interval> mIntervals;
+
+ nscoord mBStart = nscoord_MAX;
+ nscoord mBEnd = nscoord_MIN;
+};
+
+nsFloatManager::ImageShapeInfo::ImageShapeInfo(
+ uint8_t* aAlphaPixels,
+ const CSSIntSize& aImageSize,
+ 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 = NSToIntRound(aShapeImageThreshold * 255);
+ const int32_t w = aImageSize.width;
+ const int32_t h = aImageSize.height;
+
+ int32_t min = -1;
+ int32_t max = -1;
+
+ // Scan the pixels row by row, from top to bottom (if vertical, column by
+ // column, from left to right).
+ for (int32_t index = 0; index < w * h; ++index) {
+ const int32_t col = aWM.IsVertical() ? index / h : index % w;
+ const int32_t row = aWM.IsVertical() ? index % h : index / w;
+ const int32_t curr = aWM.IsVertical() ? row : col;
+
+ // At the beginning of a row (or column if vertical).
+ if (curr == 0) {
+ min = -1;
+ max = -1;
+ }
+
+ // Find the min and max columns (rows if vertical) in those pixels whose
+ // alpha channel is greater than the threshold.
+ // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
+ const uint8_t alpha = aAlphaPixels[col + row * w];
+ if (alpha > threshold) {
+ if (min == -1) {
+ min = curr;
+ }
+ if (max < curr) {
+ max = curr;
+ }
+ }
+
+ // At the end of a row (or column if vertical), and found something.
+ if (curr == (aWM.IsVertical() ? h - 1 : w - 1) && (min != -1)) {
+ // Make the position be relative to the container.
+ nsPoint lineLeft(aContentRect.TopLeft());
+ nsPoint lineRight(aContentRect.TopLeft());
+
+ nscoord colAppUnits = CSSPixel::ToAppUnits(col);
+ nscoord rowAppUnits = CSSPixel::ToAppUnits(row);
+ nscoord minAppUnits = CSSPixel::ToAppUnits(min);
+ // Add one to max because we need the position of the pixels's right
+ // edge (or bottom edge if vertical).
+ nscoord maxAppUnits = CSSPixel::ToAppUnits(max + 1);
+
+ if (aWM.IsVerticalLR() && aWM.IsSideways()) {
+ // sideways-lr: its physical directions of line-left and line-right
+ // are bottom and top, which are the opposite of other vertical
+ // writing modes.
+ lineLeft.MoveBy(colAppUnits, maxAppUnits);
+ lineRight.MoveBy(colAppUnits, minAppUnits);
+ } else if (aWM.IsVertical()) {
+ lineLeft.MoveBy(colAppUnits, minAppUnits);
+ lineRight.MoveBy(colAppUnits, maxAppUnits);
+ } else {
+ // horizontal-tb
+ lineLeft.MoveBy(minAppUnits, rowAppUnits);
+ lineRight.MoveBy(maxAppUnits, rowAppUnits);
+ }
+
+ mIntervals.AppendElement(
+ Interval{ ConvertToFloatLogical(lineLeft, aWM, aContainerSize),
+ ConvertToFloatLogical(lineRight, aWM, aContainerSize) });
+ }
+ }
+
+ if (aWM.IsVerticalRL()) {
+ // 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].mLineLeft.Y();
+ mBEnd = mIntervals[mIntervals.Length() - 1].mLineLeft.Y();
+ }
+}
+
+nscoord
+nsFloatManager::ImageShapeInfo::LineLeft(const nscoord aBStart,
+ const nscoord aBEnd) const
+{
+ MOZ_ASSERT(aBStart <= aBEnd,
+ "The band's block start is greater than its block end?");
+
+ nscoord lineLeft = nscoord_MAX;
+
+ for (const Interval& interval : mIntervals) {
+ nscoord bCoord = interval.mLineLeft.Y();
+
+ if (bCoord < aBStart) {
+ continue;
+ }
+
+ if (bCoord > aBEnd) {
+ break;
+ }
+
+ lineLeft = std::min(lineLeft, interval.mLineLeft.X());
+ }
+
+ return lineLeft;
+}
+
+nscoord
+nsFloatManager::ImageShapeInfo::LineRight(const nscoord aBStart,
+ const nscoord aBEnd) const
+{
+ MOZ_ASSERT(aBStart <= aBEnd,
+ "The band's block start is greater than its block end?");
+
+ nscoord lineRight = nscoord_MIN;
+
+ for (const Interval& interval : mIntervals) {
+ nscoord bCoord = interval.mLineRight.Y();
+
+ if (bCoord < aBStart) {
+ continue;
+ }
+
+ if (bCoord > aBEnd) {
+ break;
+ }
+
+ lineRight = std::max(lineRight, interval.mLineRight.X());
+ }
+
+ return lineRight;
+}
+
+void
+nsFloatManager::ImageShapeInfo::Translate(nscoord aLineLeft,
+ nscoord aBlockStart)
+{
+ for (Interval& interval : mIntervals) {
+ interval.mLineLeft.MoveBy(aLineLeft, aBlockStart);
+ interval.mLineRight.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 rect is empty.
+ 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 +1496,75 @@ 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();
+
+ // Use empty CSSSizeOrRatio to force set the preferred size as the frame's
+ // content box size.
+ imageRenderer.SetPreferredSize(CSSSizeOrRatio(), contentRect.Size());
+
+ // Create a draw target and draw shape image on it.
+ CSSIntSize imageIntSize = CSSIntSize::FromAppUnitsRounded(contentRect.Size());
+ RefPtr<gfx::DrawTarget> drawTarget =
+ gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA,
+ imageIntSize.ToUnknownSize(),
+ gfx::SurfaceFormat::A8);
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(drawTarget);
+
+ 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() == imageIntSize.ToUnknownSize(),
+ "Who changes the size?");
+
+ uint8_t* alphaPixels = map.GetData();
+ return MakeUnique<ImageShapeInfo>(alphaPixels, imageIntSize,
+ 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,31 @@
/* -*- 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 */
+/* This file contains 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;
@@ -942,16 +945,74 @@ nsImageRenderer::DrawBorderImageComponen
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 (GetType()) {
+ case eStyleImageType_Image: {
+ nsIntSize imageIntSize = CSSIntSize::FromAppUnitsRounded(mSize).ToUnknownSize();
+ result = mImageContainer->Draw(&aRenderingContext, imageIntSize,
+ ImageRegion::Create(imageIntSize),
+ imgIContainer::FRAME_FIRST,
+ SamplingFilter::GOOD, Nothing(), 0, 1.0);
+ break;
+ }
+
+ case eStyleImageType_Gradient: {
+ // Set DPI scale and full zoom to 1.0 so that nsCSSGradientRenderer
+ // won't take device pixels into consideration.
+ nsDeviceContext* dc = aPresContext->DeviceContext();
+ double targetDPI = 1.0;
+ float oldFullZoom = dc->GetFullZoom();
+
+ dc->CheckDPIChange(&targetDPI);
+ dc->SetFullZoom(1.0f);
+
+ 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);
+
+ // Restore DPI and full zoom.
+ dc->CheckDPIChange();
+ dc->SetFullZoom(oldFullZoom);
+ 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; }