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.
This commit is contained in:
Ting-Yu Lin ext:(%2C%20Brad%20Werth%20%3Cbwerth%40mozilla.com%3E) 2018-01-25 14:55:18 +08:00
Родитель 6e60c098ef
Коммит 8efec59d87
4 изменённых файлов: 428 добавлений и 8 удалений

Просмотреть файл

@ -11,10 +11,12 @@
#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"
@ -175,9 +177,8 @@ nsFloatManager::GetFlowArea(WritingMode aWM, nscoord aBCoord, nscoord aBSize,
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;
}
@ -580,6 +581,13 @@ public:
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).
@ -615,6 +623,7 @@ protected:
// RoundedBoxShapeInfo
//
// Implements shape-outside: <shape-box> and shape-outside: inset().
//
class nsFloatManager::RoundedBoxShapeInfo final : public nsFloatManager::ShapeInfo
{
public:
@ -952,6 +961,246 @@ nsFloatManager::PolygonShapeInfo::XInterceptAtY(const nscoord aY,
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
@ -966,6 +1215,16 @@ nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame,
{
MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
if (IsEmpty()) {
// Per spec, a float area defined by a shape is clipped to the floats
// 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()) {
@ -977,10 +1236,20 @@ nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame,
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.
@ -1277,6 +1546,84 @@ nsFloatManager::ShapeInfo::CreatePolygon(
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,

Просмотреть файл

@ -344,6 +344,7 @@ private:
class RoundedBoxShapeInfo;
class EllipseShapeInfo;
class PolygonShapeInfo;
class ImageShapeInfo;
struct FloatInfo {
nsIFrame *const mFrame;

Просмотреть файл

@ -4,7 +4,7 @@
* 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"
@ -13,11 +13,13 @@
#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"
@ -946,10 +948,72 @@ nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext,
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.
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()
{

Просмотреть файл

@ -254,6 +254,14 @@ public:
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();