Commit f9aa305a authored by Fredrik Söderqvist's avatar Fredrik Söderqvist Committed by Chromium LUCI CQ

Use floats for image slices in NinePieceImageGrid

When we get fractional slice values, either directly or by zooming, the
source rectangles can end up shifting and distorting the result when the
<image> is not a raster image.

Compute, and store, Edge::slice as float rather than int to preserve the
fractional component of a slice, letting the lower level rendering
primitives handle it.

Bug: 596075, 1134145
Change-Id: I0669d0af9131c7937b984e47adf00a37991fcefa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2584805Reviewed-by: default avatarStephen Chenney <schenney@chromium.org>
Commit-Queue: Fredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/master@{#836183}
parent 5fa4b36e
...@@ -22,21 +22,19 @@ static int ComputeEdgeWidth(const BorderImageLength& border_slice, ...@@ -22,21 +22,19 @@ static int ComputeEdgeWidth(const BorderImageLength& border_slice,
return ValueForLength(border_slice.length(), LayoutUnit(box_extent)).Floor(); return ValueForLength(border_slice.length(), LayoutUnit(box_extent)).Floor();
} }
static int ComputeEdgeSlice(const Length& slice, static float ComputeEdgeSlice(const Length& slice,
float slice_scale, float slice_scale,
int maximum) { float maximum) {
int resolved; float resolved;
// If the slice is a <number> (stored as a fixed Length), scale it by the // If the slice is a <number> (stored as a fixed Length), scale it by the
// slice scale to get to the same space as the image. // slice scale to get to the same space as the image.
if (slice.IsFixed()) { if (slice.IsFixed()) {
LayoutUnit slice_value(slice.Value()); resolved = slice.Value() * slice_scale;
slice_value *= slice_scale;
resolved = slice_value.Round();
} else { } else {
DCHECK(slice.IsPercent()); DCHECK(slice.IsPercent());
resolved = ValueForLength(slice, LayoutUnit(maximum)).Floor(); resolved = FloatValueForLength(slice, maximum);
} }
return std::min<int>(maximum, resolved); return std::min(maximum, resolved);
} }
// Scale the width of the |start| and |end| edges using |scale_factor|. // Scale the width of the |start| and |end| edges using |scale_factor|.
...@@ -59,7 +57,7 @@ static void ScaleEdgeWidths(NinePieceImageGrid::Edge& start, ...@@ -59,7 +57,7 @@ static void ScaleEdgeWidths(NinePieceImageGrid::Edge& start,
} }
NinePieceImageGrid::NinePieceImageGrid(const NinePieceImage& nine_piece_image, NinePieceImageGrid::NinePieceImageGrid(const NinePieceImage& nine_piece_image,
IntSize image_size, FloatSize image_size,
const FloatSize& slice_scale, const FloatSize& slice_scale,
float zoom, float zoom,
IntRect border_image_area, IntRect border_image_area,
...@@ -133,7 +131,7 @@ NinePieceImageGrid::NinePieceImageGrid(const NinePieceImage& nine_piece_image, ...@@ -133,7 +131,7 @@ NinePieceImageGrid::NinePieceImageGrid(const NinePieceImage& nine_piece_image,
// Given a rectangle, construct a subrectangle using offset, width and height. // Given a rectangle, construct a subrectangle using offset, width and height.
// Negative offsets are relative to the extent of the given rectangle. // Negative offsets are relative to the extent of the given rectangle.
static FloatRect Subrect(IntRect rect, static FloatRect Subrect(FloatRect rect,
float offset_x, float offset_x,
float offset_y, float offset_y,
float width, float width,
...@@ -149,12 +147,21 @@ static FloatRect Subrect(IntRect rect, ...@@ -149,12 +147,21 @@ static FloatRect Subrect(IntRect rect,
return FloatRect(base_x + offset_x, base_y + offset_y, width, height); return FloatRect(base_x + offset_x, base_y + offset_y, width, height);
} }
static FloatRect Subrect(IntSize size, static FloatRect Subrect(IntRect rect,
float offset_x, float offset_x,
float offset_y, float offset_y,
float width, float width,
float height) { float height) {
return Subrect(IntRect(IntPoint(), size), offset_x, offset_y, width, height); return Subrect(FloatRect(rect), offset_x, offset_y, width, height);
}
static FloatRect Subrect(FloatSize size,
float offset_x,
float offset_y,
float width,
float height) {
return Subrect(FloatRect(FloatPoint(), size), offset_x, offset_y, width,
height);
} }
static inline void SetCornerPiece( static inline void SetCornerPiece(
...@@ -237,8 +244,9 @@ static inline void SetVerticalEdge( ...@@ -237,8 +244,9 @@ static inline void SetVerticalEdge(
void NinePieceImageGrid::SetDrawInfoEdge(NinePieceDrawInfo& draw_info, void NinePieceImageGrid::SetDrawInfoEdge(NinePieceDrawInfo& draw_info,
NinePiece piece) const { NinePiece piece) const {
IntSize edge_source_size = image_size_ - IntSize(left_.slice + right_.slice, FloatSize edge_source_size =
top_.slice + bottom_.slice); image_size_ -
FloatSize(left_.slice + right_.slice, top_.slice + bottom_.slice);
IntSize edge_destination_size = IntSize edge_destination_size =
border_image_area_.Size() - border_image_area_.Size() -
IntSize(left_.width + right_.width, top_.width + bottom_.width); IntSize(left_.width + right_.width, top_.width + bottom_.width);
...@@ -283,8 +291,8 @@ void NinePieceImageGrid::SetDrawInfoEdge(NinePieceDrawInfo& draw_info, ...@@ -283,8 +291,8 @@ void NinePieceImageGrid::SetDrawInfoEdge(NinePieceDrawInfo& draw_info,
} }
void NinePieceImageGrid::SetDrawInfoMiddle(NinePieceDrawInfo& draw_info) const { void NinePieceImageGrid::SetDrawInfoMiddle(NinePieceDrawInfo& draw_info) const {
IntSize source_size = image_size_ - IntSize(left_.slice + right_.slice, FloatSize source_size = image_size_ - FloatSize(left_.slice + right_.slice,
top_.slice + bottom_.slice); top_.slice + bottom_.slice);
IntSize destination_size = IntSize destination_size =
border_image_area_.Size() - border_image_area_.Size() -
IntSize(left_.width + right_.width, top_.width + bottom_.width); IntSize(left_.width + right_.width, top_.width + bottom_.width);
...@@ -318,13 +326,14 @@ void NinePieceImageGrid::SetDrawInfoMiddle(NinePieceDrawInfo& draw_info) const { ...@@ -318,13 +326,14 @@ void NinePieceImageGrid::SetDrawInfoMiddle(NinePieceDrawInfo& draw_info) const {
// factor unless they have a rule other than "stretch". The middle however // factor unless they have a rule other than "stretch". The middle however
// can have "stretch" specified in one axis but not the other, so we have to // can have "stretch" specified in one axis but not the other, so we have to
// correct the scale here. // correct the scale here.
if (horizontal_tile_rule_ == kStretchImageRule) if (horizontal_tile_rule_ == kStretchImageRule) {
middle_scale_factor.SetWidth((float)destination_size.Width() / middle_scale_factor.SetWidth(destination_size.Width() /
source_size.Width()); source_size.Width());
}
if (vertical_tile_rule_ == kStretchImageRule) if (vertical_tile_rule_ == kStretchImageRule) {
middle_scale_factor.SetHeight((float)destination_size.Height() / middle_scale_factor.SetHeight(destination_size.Height() /
source_size.Height()); source_size.Height());
}
} }
draw_info.tile_scale = middle_scale_factor; draw_info.tile_scale = middle_scale_factor;
......
...@@ -63,7 +63,7 @@ class CORE_EXPORT NinePieceImageGrid { ...@@ -63,7 +63,7 @@ class CORE_EXPORT NinePieceImageGrid {
public: public:
NinePieceImageGrid(const NinePieceImage&, NinePieceImageGrid(const NinePieceImage&,
IntSize image_size, FloatSize image_size,
const FloatSize& slice_scale, const FloatSize& slice_scale,
float zoom, float zoom,
IntRect border_image_area, IntRect border_image_area,
...@@ -92,8 +92,8 @@ class CORE_EXPORT NinePieceImageGrid { ...@@ -92,8 +92,8 @@ class CORE_EXPORT NinePieceImageGrid {
struct Edge { struct Edge {
DISALLOW_NEW(); DISALLOW_NEW();
bool IsDrawable() const { return slice > 0 && width > 0; } bool IsDrawable() const { return slice > 0 && width > 0; }
float Scale() const { return IsDrawable() ? (float)width / slice : 1; } float Scale() const { return IsDrawable() ? width / slice : 1; }
int slice; float slice;
int width; int width;
}; };
...@@ -103,7 +103,7 @@ class CORE_EXPORT NinePieceImageGrid { ...@@ -103,7 +103,7 @@ class CORE_EXPORT NinePieceImageGrid {
void SetDrawInfoMiddle(NinePieceDrawInfo&) const; void SetDrawInfoMiddle(NinePieceDrawInfo&) const;
IntRect border_image_area_; IntRect border_image_area_;
IntSize image_size_; FloatSize image_size_;
ENinePieceImageRule horizontal_tile_rule_; ENinePieceImageRule horizontal_tile_rule_;
ENinePieceImageRule vertical_tile_rule_; ENinePieceImageRule vertical_tile_rule_;
bool fill_; bool fill_;
......
...@@ -30,7 +30,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_NoDrawables) { ...@@ -30,7 +30,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_NoDrawables) {
NinePieceImage nine_piece; NinePieceImage nine_piece;
nine_piece.SetImage(GeneratedImage()); nine_piece.SetImage(GeneratedImage());
IntSize image_size(100, 100); FloatSize image_size(100, 100);
IntRect border_image_area(0, 0, 100, 100); IntRect border_image_area(0, 0, 100, 100);
IntRectOutsets border_widths(0, 0, 0, 0); IntRectOutsets border_widths(0, 0, 0, 0);
...@@ -50,7 +50,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_AllDrawable) { ...@@ -50,7 +50,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_AllDrawable) {
nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10)); nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10));
nine_piece.SetFill(true); nine_piece.SetFill(true);
IntSize image_size(100, 100); FloatSize image_size(100, 100);
IntRect border_image_area(0, 0, 100, 100); IntRect border_image_area(0, 0, 100, 100);
IntRectOutsets border_widths(10, 10, 10, 10); IntRectOutsets border_widths(10, 10, 10, 10);
...@@ -70,7 +70,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_NoFillMiddleNotDrawable) { ...@@ -70,7 +70,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_NoFillMiddleNotDrawable) {
nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10)); nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10));
nine_piece.SetFill(false); // default nine_piece.SetFill(false); // default
IntSize image_size(100, 100); FloatSize image_size(100, 100);
IntRect border_image_area(0, 0, 100, 100); IntRect border_image_area(0, 0, 100, 100);
IntRectOutsets border_widths(10, 10, 10, 10); IntRectOutsets border_widths(10, 10, 10, 10);
...@@ -92,7 +92,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_TopLeftDrawable) { ...@@ -92,7 +92,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_TopLeftDrawable) {
nine_piece.SetImage(GeneratedImage()); nine_piece.SetImage(GeneratedImage());
nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10)); nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10));
IntSize image_size(100, 100); FloatSize image_size(100, 100);
IntRect border_image_area(0, 0, 100, 100); IntRect border_image_area(0, 0, 100, 100);
const struct { const struct {
...@@ -123,7 +123,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_ScaleDownBorder) { ...@@ -123,7 +123,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_ScaleDownBorder) {
nine_piece.SetImage(GeneratedImage()); nine_piece.SetImage(GeneratedImage());
nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10)); nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10));
IntSize image_size(100, 100); FloatSize image_size(100, 100);
IntRect border_image_area(0, 0, 100, 100); IntRect border_image_area(0, 0, 100, 100);
IntRectOutsets border_widths(10, 10, 10, 10); IntRectOutsets border_widths(10, 10, 10, 10);
...@@ -185,7 +185,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_ScaleDownBorder) { ...@@ -185,7 +185,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_ScaleDownBorder) {
TEST_F(NinePieceImageGridTest, NinePieceImagePainting) { TEST_F(NinePieceImageGridTest, NinePieceImagePainting) {
const struct { const struct {
IntSize image_size; FloatSize image_size;
IntRect border_image_area; IntRect border_image_area;
IntRectOutsets border_widths; IntRectOutsets border_widths;
bool fill; bool fill;
...@@ -204,7 +204,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) { ...@@ -204,7 +204,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) {
} pieces[9]; } pieces[9];
} test_cases[] = { } test_cases[] = {
{// Empty border and slices but with fill {// Empty border and slices but with fill
IntSize(100, 100), FloatSize(100, 100),
IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100),
IntRectOutsets(0, 0, 0, 0), IntRectOutsets(0, 0, 0, 0),
true, true,
...@@ -233,7 +233,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) { ...@@ -233,7 +233,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) {
1, 1, kStretchImageRule, kStretchImageRule}, 1, 1, kStretchImageRule, kStretchImageRule},
}}, }},
{// Single border and fill {// Single border and fill
IntSize(100, 100), FloatSize(100, 100),
IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100),
IntRectOutsets(0, 0, 10, 0), IntRectOutsets(0, 0, 10, 0),
true, true,
...@@ -262,7 +262,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) { ...@@ -262,7 +262,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) {
1.666667, 1.5, kStretchImageRule, kStretchImageRule}, 1.666667, 1.5, kStretchImageRule, kStretchImageRule},
}}, }},
{// All borders, no fill {// All borders, no fill
IntSize(100, 100), FloatSize(100, 100),
IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100),
IntRectOutsets(10, 10, 10, 10), IntRectOutsets(10, 10, 10, 10),
false, false,
...@@ -291,7 +291,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) { ...@@ -291,7 +291,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) {
kStretchImageRule, kStretchImageRule}, kStretchImageRule, kStretchImageRule},
}}, }},
{// Single border, no fill {// Single border, no fill
IntSize(100, 100), FloatSize(100, 100),
IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100),
IntRectOutsets(0, 0, 0, 10), IntRectOutsets(0, 0, 0, 10),
false, false,
...@@ -321,7 +321,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) { ...@@ -321,7 +321,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting) {
}}, }},
{// All borders but no slices, with fill (stretch horizontally, space {// All borders but no slices, with fill (stretch horizontally, space
// vertically) // vertically)
IntSize(100, 100), FloatSize(100, 100),
IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100),
IntRectOutsets(10, 10, 10, 10), IntRectOutsets(10, 10, 10, 10),
true, true,
...@@ -407,7 +407,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_Zoomed) { ...@@ -407,7 +407,7 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_Zoomed) {
nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10)); nine_piece.SetImageSlices(LengthBox(10, 10, 10, 10));
nine_piece.SetFill(true); nine_piece.SetFill(true);
IntSize image_size(50, 50); FloatSize image_size(50, 50);
IntRect border_image_area(0, 0, 200, 200); IntRect border_image_area(0, 0, 200, 200);
IntRectOutsets border_widths(20, 20, 20, 20); IntRectOutsets border_widths(20, 20, 20, 20);
...@@ -465,5 +465,83 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_Zoomed) { ...@@ -465,5 +465,83 @@ TEST_F(NinePieceImageGridTest, NinePieceImagePainting_Zoomed) {
} }
} }
TEST_F(NinePieceImageGridTest, NinePieceImagePainting_ZoomedNarrowSlices) {
NinePieceImage nine_piece;
nine_piece.SetImage(GeneratedImage());
// Image slices are specified in CSS pixels.
nine_piece.SetImageSlices(LengthBox(1, 1, 1, 1));
nine_piece.SetFill(true);
constexpr float zoom = 2.2f;
FloatSize image_size(3 * zoom, 3 * zoom);
IntRect border_image_area(0, 0, 220, 220);
IntRectOutsets border_widths(33, 33, 33, 33);
NinePieceImageGrid grid =
NinePieceImageGrid(nine_piece, image_size, FloatSize(zoom, zoom), zoom,
border_image_area, border_widths);
struct {
bool is_drawable;
bool is_corner_piece;
FloatRect destination;
FloatRect source;
float tile_scale_horizontal;
float tile_scale_vertical;
ENinePieceImageRule horizontal_rule;
ENinePieceImageRule vertical_rule;
} expected_pieces[kMaxPiece] = {
{true, true, FloatRect(0, 0, 33, 33), FloatRect(0, 0, 2.2f, 2.2f), 0, 0,
kStretchImageRule, kStretchImageRule},
{true, true, FloatRect(0, 187, 33, 33), FloatRect(0, 4.4f, 2.2f, 2.2f), 0,
0, kStretchImageRule, kStretchImageRule},
{true, false, FloatRect(0, 33, 33, 154), FloatRect(0, 2.2f, 2.2f, 2.2f),
15, 15, kStretchImageRule, kStretchImageRule},
{true, true, FloatRect(187, 0, 33, 33), FloatRect(4.4f, 0, 2.2f, 2.2f), 0,
0, kStretchImageRule, kStretchImageRule},
{true, true, FloatRect(187, 187, 33, 33),
FloatRect(4.4f, 4.4f, 2.2f, 2.2f), 0, 0, kStretchImageRule,
kStretchImageRule},
{true, false, FloatRect(187, 33, 33, 154),
FloatRect(4.4f, 2.2f, 2.2f, 2.2f), 15, 15, kStretchImageRule,
kStretchImageRule},
{true, false, FloatRect(33, 0, 154, 33), FloatRect(2.2f, 0, 2.2f, 2.2f),
15, 15, kStretchImageRule, kStretchImageRule},
{true, false, FloatRect(33, 187, 154, 33),
FloatRect(2.2f, 4.4f, 2.2f, 2.2f), 15, 15, kStretchImageRule,
kStretchImageRule},
{true, false, FloatRect(33, 33, 154, 154),
FloatRect(2.2f, 2.2f, 2.2f, 2.2f), 70, 70, kStretchImageRule,
kStretchImageRule},
};
for (NinePiece piece = kMinPiece; piece < kMaxPiece; ++piece) {
NinePieceImageGrid::NinePieceDrawInfo draw_info =
grid.GetNinePieceDrawInfo(piece);
EXPECT_TRUE(draw_info.is_drawable);
const auto& expected = expected_pieces[piece];
EXPECT_FLOAT_EQ(draw_info.destination.X(), expected.destination.X());
EXPECT_FLOAT_EQ(draw_info.destination.Y(), expected.destination.Y());
EXPECT_FLOAT_EQ(draw_info.destination.Width(),
expected.destination.Width());
EXPECT_FLOAT_EQ(draw_info.destination.Height(),
expected.destination.Height());
EXPECT_FLOAT_EQ(draw_info.source.X(), expected.source.X());
EXPECT_FLOAT_EQ(draw_info.source.Y(), expected.source.Y());
EXPECT_FLOAT_EQ(draw_info.source.Width(), expected.source.Width());
EXPECT_FLOAT_EQ(draw_info.source.Height(), expected.source.Height());
if (expected.is_corner_piece)
continue;
EXPECT_FLOAT_EQ(draw_info.tile_scale.Width(),
expected.tile_scale_horizontal);
EXPECT_FLOAT_EQ(draw_info.tile_scale.Height(),
expected.tile_scale_vertical);
EXPECT_EQ(draw_info.tile_rule.vertical, expected.vertical_rule);
EXPECT_EQ(draw_info.tile_rule.horizontal, expected.horizontal_rule);
}
}
} // namespace } // namespace
} // namespace blink } // namespace blink
...@@ -93,15 +93,11 @@ void PaintPieces(GraphicsContext& context, ...@@ -93,15 +93,11 @@ void PaintPieces(GraphicsContext& context,
FloatSize slice_scale(image_size.Width() / unzoomed_image_size.Width(), FloatSize slice_scale(image_size.Width() / unzoomed_image_size.Width(),
image_size.Height() / unzoomed_image_size.Height()); image_size.Height() / unzoomed_image_size.Height());
// TODO(fs): Use FloatSize here to avoid additional rounding (leave that to
// NinePieceImageGrid if needed). For narrow slices the rounding can introduce
// large errors (fairly visible in the TC in crbug.com/596075 when zooming).
IntSize rounded_image_size = RoundedIntSize(image_size);
IntRectOutsets border_widths(style.BorderTopWidth(), style.BorderRightWidth(), IntRectOutsets border_widths(style.BorderTopWidth(), style.BorderRightWidth(),
style.BorderBottomWidth(), style.BorderBottomWidth(),
style.BorderLeftWidth()); style.BorderLeftWidth());
NinePieceImageGrid grid( NinePieceImageGrid grid(
nine_piece_image, rounded_image_size, slice_scale, style.EffectiveZoom(), nine_piece_image, image_size, slice_scale, style.EffectiveZoom(),
PixelSnappedIntRect(border_image_rect), border_widths, sides_to_include); PixelSnappedIntRect(border_image_rect), border_widths, sides_to_include);
ScopedInterpolationQuality interpolation_quality_scope( ScopedInterpolationQuality interpolation_quality_scope(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment