Bug 1719886 - Support translated no-repeat patterns in canvas FillRect. r=jrmuizel

This is a bandaid designed to cope with a subset of the encountered transforms that
may be applied to a pattern inside FillRect. The existing code did not consider the
transform at all that might be applied to a pattern when it tried to manually clip
the geometry to a no-repeat pattern.

This manual clipping only seems to occur in FillRect, whereas no-repeat patterns
are not properly handled anywhere else in canvas entry-points. To fix this more
generally requires a clamp-to-transparent tile mode (like Skia's decal or Cairo's
none) which we can't currently rely upon with our D2D support. However, that is
much beyond the scope of this temporary workaround.

Differential Revision: https://phabricator.services.mozilla.com/D122186
This commit is contained in:
Lee Salzman 2021-08-10 17:37:45 +00:00
Родитель 69c0a6cb2c
Коммит 608abb2330
4 изменённых файлов: 87 добавлений и 37 удалений

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

@ -2548,49 +2548,39 @@ void CanvasRenderingContext2D::FillRect(double aX, double aY, double aW,
const ContextState* state = &CurrentState();
if (state->patternStyles[Style::FILL]) {
CanvasPattern::RepeatMode repeat =
state->patternStyles[Style::FILL]->mRepeat;
auto& style = state->patternStyles[Style::FILL];
CanvasPattern::RepeatMode repeat = style->mRepeat;
// In the FillRect case repeat modes are easy to deal with.
bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
repeat == CanvasPattern::RepeatMode::REPEATY;
bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT ||
repeat == CanvasPattern::RepeatMode::REPEATX;
IntSize patternSize =
state->patternStyles[Style::FILL]->mSurface->GetSize();
// We always need to execute painting for non-over operators, even if
// we end up with w/h = 0.
if (limitx) {
if (aX < 0) {
aW += aX;
if (aW < 0) {
aW = 0;
}
aX = 0;
if ((limitx || limity) && style->mTransform.IsRectilinear()) {
// For rectilinear transforms, we can just get the transformed pattern
// bounds and intersect them with the fill rectangle bounds.
// TODO: If the transform is not rectilinear, then we would need a fully
// general clip path to represent the X and Y clip planes bounding the
// pattern. For such cases, it would be more efficient to rely on Skia's
// Decal tiling mode rather than trying to generate a path. Until then,
// just punt to relying on the default Clamp mode.
gfx::Rect patternBounds(style->mSurface->GetRect());
patternBounds = style->mTransform.TransformBounds(patternBounds);
gfx::Rect bounds(aX, aY, aW, aH);
// We always need to execute painting for non-over operators, even if
// we end up with w/h = 0.
bounds = bounds.Intersect(patternBounds);
if (style->mTransform.HasNonAxisAlignedTransform()) {
// If there is an rotation (90 or 270 degrees), the X axis of the
// pattern projects onto the Y axis of the geometry, and vice versa.
std::swap(limitx, limity);
}
if (aX + aW > patternSize.width) {
aW = patternSize.width - aX;
if (aW < 0) {
aW = 0;
}
if (limitx) {
aX = bounds.x;
aW = bounds.width;
}
}
if (limity) {
if (aY < 0) {
aH += aY;
if (aH < 0) {
aH = 0;
}
aY = 0;
}
if (aY + aH > patternSize.height) {
aH = patternSize.height - aY;
if (aH < 0) {
aH = 0;
}
if (limity) {
aY = bounds.y;
aH = bounds.height;
}
}
}

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

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<body>
Pattern Canvas<br>
<canvas id="patternCanvas" style="border: 1px solid black" width="10" height="10"></canvas><br>
Main Canvas (red square should be in top-left corner)<br>
<canvas id="canvas" style="border: 1px solid black" width="100" height="100"></canvas>
<script>
// Draw a 10x10 red rectangle that will be used for the pattern.
const patternCanvas = document.getElementById("patternCanvas");
const patternCtx = patternCanvas.getContext("2d");
patternCtx.fillStyle = "red";
patternCtx.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pattern = ctx.createPattern(patternCanvas, "no-repeat");
ctx.fillStyle = pattern;
// Fill the entire canvas with the pattern.
ctx.fillRect(0, 0, 100, 100);
</script>
</body>
</html>

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

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<body>
Pattern Canvas<br>
<canvas id="patternCanvas" style="border: 1px solid black" width="10" height="10"></canvas><br>
Main Canvas (red square should be in top-left corner)<br>
<canvas id="canvas" style="border: 1px solid black" width="100" height="100"></canvas>
<script>
// Draw a 10x10 red rectangle that will be used for the pattern.
const patternCanvas = document.getElementById("patternCanvas");
const patternCtx = patternCanvas.getContext("2d");
patternCtx.fillStyle = "red";
patternCtx.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const height = 100;
ctx.translate(0, height);
const pattern = ctx.createPattern(patternCanvas, "no-repeat");
// Reverse translation applied to the canvas.
pattern.setTransform((new DOMMatrix()).translate(0, -height));
ctx.fillStyle = pattern;
// Fill the entire canvas with the pattern.
ctx.fillRect(0, -height, 100, 100);
</script>
</body>
</html>

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

@ -177,4 +177,4 @@ fuzzy-if(azureSkia,0-16,0-2) fuzzy-if(Android,0-3,0-40) fuzzy-if(/^Windows\x20NT
skip-if(Android) == visible-occluded.html visible-occluded-ref.html
== 1678909-1.html 1678909-1-ref.html
== 1719886-1.html 1719886-1-ref.html