Commit 3ec049f5 authored by Teresa Kang's avatar Teresa Kang Committed by Commit Bot

Fix Incorrect Shadow Blur

When a pattern (with a transparent background) is used to fill or stroke
a rect, the ShadowBlur is incorrectly applied to the rect mask instead
of the pattern itself.

This is fixed by setting the last param of BaseRenderingContext2D::
Draw() to kNonOpaqueImage when the rect is drawn with a pattern.

Bug: 1020183
Change-Id: I62ae4e298185eb5b7699c2dbf4b9ad962939e464
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2219416Reviewed-by: default avatarFernando Serboncini <fserb@chromium.org>
Reviewed-by: default avatarAaron Krajeski <aaronhk@chromium.org>
Reviewed-by: default avatarJuanmi Huertas <juanmihd@chromium.org>
Commit-Queue: Teresa Kang <teresakang@google.com>
Cr-Commit-Position: refs/heads/master@{#775121}
parent 572d079f
...@@ -643,7 +643,10 @@ void BaseRenderingContext2D::DrawPathInternal( ...@@ -643,7 +643,10 @@ void BaseRenderingContext2D::DrawPathInternal(
{ c->drawPath(sk_path, *flags); }, { c->drawPath(sk_path, *flags); },
[](const SkIRect& rect) // overdraw test lambda [](const SkIRect& rect) // overdraw test lambda
{ return false; }, { return false; },
bounds, paint_type); bounds, paint_type,
GetState().HasPattern(paint_type)
? CanvasRenderingContext2DState::kNonOpaqueImage
: CanvasRenderingContext2DState::kNoImage);
} }
static SkPathFillType ParseWinding(const String& winding_rule_string) { static SkPathFillType ParseWinding(const String& winding_rule_string) {
...@@ -700,15 +703,20 @@ void BaseRenderingContext2D::fillRect(double x, ...@@ -700,15 +703,20 @@ void BaseRenderingContext2D::fillRect(double x,
// pattern was unaccelerated is because it was not possible to hold that image // pattern was unaccelerated is because it was not possible to hold that image
// in an accelerated texture - that is, into the GPU). That's why we disable // in an accelerated texture - that is, into the GPU). That's why we disable
// the acceleration to be sure that it will work. // the acceleration to be sure that it will work.
if (IsAccelerated() && GetState().HasPattern() && if (IsAccelerated() &&
!GetState().PatternIsAccelerated()) GetState().HasPattern(CanvasRenderingContext2DState::kFillPaintType) &&
!GetState().PatternIsAccelerated(
CanvasRenderingContext2DState::kFillPaintType))
DisableAcceleration(); DisableAcceleration();
SkRect rect = SkRect::MakeXYWH(fx, fy, fwidth, fheight); SkRect rect = SkRect::MakeXYWH(fx, fy, fwidth, fheight);
Draw([&rect](cc::PaintCanvas* c, const PaintFlags* flags) // draw lambda Draw([&rect](cc::PaintCanvas* c, const PaintFlags* flags) // draw lambda
{ c->drawRect(rect, *flags); }, { c->drawRect(rect, *flags); },
[&rect, this](const SkIRect& clip_bounds) // overdraw test lambda [&rect, this](const SkIRect& clip_bounds) // overdraw test lambda
{ return RectContainsTransformedRect(rect, clip_bounds); }, { return RectContainsTransformedRect(rect, clip_bounds); },
rect, CanvasRenderingContext2DState::kFillPaintType); rect, CanvasRenderingContext2DState::kFillPaintType,
GetState().HasPattern(CanvasRenderingContext2DState::kFillPaintType)
? CanvasRenderingContext2DState::kNonOpaqueImage
: CanvasRenderingContext2DState::kNoImage);
} }
static void StrokeRectOnCanvas(const FloatRect& rect, static void StrokeRectOnCanvas(const FloatRect& rect,
...@@ -756,7 +764,10 @@ void BaseRenderingContext2D::strokeRect(double x, ...@@ -756,7 +764,10 @@ void BaseRenderingContext2D::strokeRect(double x,
{ StrokeRectOnCanvas(rect, c, flags); }, { StrokeRectOnCanvas(rect, c, flags); },
[](const SkIRect& clip_bounds) // overdraw test lambda [](const SkIRect& clip_bounds) // overdraw test lambda
{ return false; }, { return false; },
bounds, CanvasRenderingContext2DState::kStrokePaintType); bounds, CanvasRenderingContext2DState::kStrokePaintType,
GetState().HasPattern(CanvasRenderingContext2DState::kStrokePaintType)
? CanvasRenderingContext2DState::kNonOpaqueImage
: CanvasRenderingContext2DState::kNoImage);
} }
void BaseRenderingContext2D::ClipInternal(const Path& path, void BaseRenderingContext2D::ClipInternal(const Path& path,
......
...@@ -337,8 +337,7 @@ class MODULES_EXPORT BaseRenderingContext2D : public GarbageCollectedMixin, ...@@ -337,8 +337,7 @@ class MODULES_EXPORT BaseRenderingContext2D : public GarbageCollectedMixin,
const ContainsFunc&, const ContainsFunc&,
const SkRect& bounds, const SkRect& bounds,
CanvasRenderingContext2DState::PaintType, CanvasRenderingContext2DState::PaintType,
CanvasRenderingContext2DState::ImageType = CanvasRenderingContext2DState::ImageType);
CanvasRenderingContext2DState::kNoImage);
void InflateStrokeRect(FloatRect&) const; void InflateStrokeRect(FloatRect&) const;
...@@ -392,6 +391,19 @@ class MODULES_EXPORT BaseRenderingContext2D : public GarbageCollectedMixin, ...@@ -392,6 +391,19 @@ class MODULES_EXPORT BaseRenderingContext2D : public GarbageCollectedMixin,
bool ShouldDrawImageAntialiased(const FloatRect& dest_rect) const; bool ShouldDrawImageAntialiased(const FloatRect& dest_rect) const;
// When the canvas is stroked or filled with a pattern, which is assumed to
// have a transparent background, the shadow needs to be applied with
// DropShadowPaintFilter for kNonOpaqueImageType
// Used in Draw and CompositedDraw to avoid the shadow offset being modified
// by the transformation matrix
bool ShouldUseDropShadowPaintFilter(
CanvasRenderingContext2DState::PaintType paint_type,
CanvasRenderingContext2DState::ImageType image_type) const {
return (paint_type == CanvasRenderingContext2DState::kFillPaintType ||
paint_type == CanvasRenderingContext2DState::kStrokePaintType) &&
image_type == CanvasRenderingContext2DState::kNonOpaqueImage;
}
void DrawPathInternal(const Path&, void DrawPathInternal(const Path&,
CanvasRenderingContext2DState::PaintType, CanvasRenderingContext2DState::PaintType,
SkPathFillType = SkPathFillType::kWinding); SkPathFillType = SkPathFillType::kWinding);
...@@ -466,7 +478,9 @@ void BaseRenderingContext2D::Draw( ...@@ -466,7 +478,9 @@ void BaseRenderingContext2D::Draw(
return; return;
if (IsFullCanvasCompositeMode(GetState().GlobalComposite()) || if (IsFullCanvasCompositeMode(GetState().GlobalComposite()) ||
StateHasFilter()) { StateHasFilter() ||
(GetState().ShouldDrawShadows() &&
ShouldUseDropShadowPaintFilter(paint_type, image_type))) {
CompositedDraw(draw_func, GetPaintCanvas(), paint_type, image_type); CompositedDraw(draw_func, GetPaintCanvas(), paint_type, image_type);
DidDraw(clip_bounds); DidDraw(clip_bounds);
} else if (GetState().GlobalComposite() == SkBlendMode::kSrc) { } else if (GetState().GlobalComposite() == SkBlendMode::kSrc) {
...@@ -495,8 +509,11 @@ void BaseRenderingContext2D::CompositedDraw( ...@@ -495,8 +509,11 @@ void BaseRenderingContext2D::CompositedDraw(
cc::PaintCanvas* c, cc::PaintCanvas* c,
CanvasRenderingContext2DState::PaintType paint_type, CanvasRenderingContext2DState::PaintType paint_type,
CanvasRenderingContext2DState::ImageType image_type) { CanvasRenderingContext2DState::ImageType image_type) {
sk_sp<PaintFilter> filter = StateGetFilter(); sk_sp<PaintFilter> canvas_filter = StateGetFilter();
DCHECK(IsFullCanvasCompositeMode(GetState().GlobalComposite()) || filter); DCHECK(IsFullCanvasCompositeMode(GetState().GlobalComposite()) ||
canvas_filter ||
(GetState().ShouldDrawShadows() &&
ShouldUseDropShadowPaintFilter(paint_type, image_type)));
SkMatrix ctm = c->getTotalMatrix(); SkMatrix ctm = c->getTotalMatrix();
c->setMatrix(SkMatrix::I()); c->setMatrix(SkMatrix::I());
PaintFlags composite_flags; PaintFlags composite_flags;
...@@ -507,13 +524,14 @@ void BaseRenderingContext2D::CompositedDraw( ...@@ -507,13 +524,14 @@ void BaseRenderingContext2D::CompositedDraw(
*GetState().GetFlags(paint_type, kDrawShadowOnly, image_type); *GetState().GetFlags(paint_type, kDrawShadowOnly, image_type);
int save_count = c->getSaveCount(); int save_count = c->getSaveCount();
c->save(); c->save();
if (filter) { if (canvas_filter ||
ShouldUseDropShadowPaintFilter(paint_type, image_type)) {
PaintFlags foreground_flags = PaintFlags foreground_flags =
*GetState().GetFlags(paint_type, kDrawForegroundOnly, image_type); *GetState().GetFlags(paint_type, kDrawForegroundOnly, image_type);
shadow_flags.setImageFilter(sk_make_sp<ComposePaintFilter>( shadow_flags.setImageFilter(sk_make_sp<ComposePaintFilter>(
sk_make_sp<ComposePaintFilter>(foreground_flags.getImageFilter(), sk_make_sp<ComposePaintFilter>(foreground_flags.getImageFilter(),
shadow_flags.getImageFilter()), shadow_flags.getImageFilter()),
filter)); canvas_filter));
// Saving the shadow layer before setting the matrix, so the shadow offset // Saving the shadow layer before setting the matrix, so the shadow offset
// does not get modified by the transformation matrix // does not get modified by the transformation matrix
c->saveLayer(nullptr, &shadow_flags); c->saveLayer(nullptr, &shadow_flags);
...@@ -529,7 +547,7 @@ void BaseRenderingContext2D::CompositedDraw( ...@@ -529,7 +547,7 @@ void BaseRenderingContext2D::CompositedDraw(
c->restoreToCount(save_count); c->restoreToCount(save_count);
} }
composite_flags.setImageFilter(std::move(filter)); composite_flags.setImageFilter(std::move(canvas_filter));
c->saveLayer(nullptr, &composite_flags); c->saveLayer(nullptr, &composite_flags);
PaintFlags foreground_flags = PaintFlags foreground_flags =
*GetState().GetFlags(paint_type, kDrawForegroundOnly, image_type); *GetState().GetFlags(paint_type, kDrawForegroundOnly, image_type);
......
...@@ -946,7 +946,7 @@ void CanvasRenderingContext2D::DrawTextInternal( ...@@ -946,7 +946,7 @@ void CanvasRenderingContext2D::DrawTextInternal(
}, },
[](const SkIRect& rect) // overdraw test lambda [](const SkIRect& rect) // overdraw test lambda
{ return false; }, { return false; },
bounds, paint_type); bounds, paint_type, CanvasRenderingContext2DState::kNoImage);
} }
const Font& CanvasRenderingContext2D::AccessFont() { const Font& CanvasRenderingContext2D::AccessFont() {
......
...@@ -638,15 +638,16 @@ const PaintFlags* CanvasRenderingContext2DState::GetFlags( ...@@ -638,15 +638,16 @@ const PaintFlags* CanvasRenderingContext2DState::GetFlags(
return flags; return flags;
} }
bool CanvasRenderingContext2DState::HasPattern() const { bool CanvasRenderingContext2DState::HasPattern(PaintType paint_type) const {
return FillStyle() && FillStyle()->GetCanvasPattern() && return Style(paint_type) && Style(paint_type)->GetCanvasPattern() &&
FillStyle()->GetCanvasPattern()->GetPattern(); Style(paint_type)->GetCanvasPattern()->GetPattern();
} }
// Only to be used if the CanvasRenderingContext2DState has Pattern // Only to be used if the CanvasRenderingContext2DState has Pattern
bool CanvasRenderingContext2DState::PatternIsAccelerated() const { bool CanvasRenderingContext2DState::PatternIsAccelerated(
DCHECK(HasPattern()); PaintType paint_type) const {
return FillStyle()->GetCanvasPattern()->GetPattern()->IsTextureBacked(); DCHECK(HasPattern(paint_type));
return Style(paint_type)->GetCanvasPattern()->GetPattern()->IsTextureBacked();
} }
} // namespace blink } // namespace blink
...@@ -110,12 +110,16 @@ class CanvasRenderingContext2DState final ...@@ -110,12 +110,16 @@ class CanvasRenderingContext2DState final
void SetFillStyle(CanvasStyle*); void SetFillStyle(CanvasStyle*);
CanvasStyle* FillStyle() const { return fill_style_.Get(); } CanvasStyle* FillStyle() const { return fill_style_.Get(); }
// Prefer to use Style() over StrokeStyle() and FillStyle()
// if properties of CanvasStyle are concerned
CanvasStyle* Style(PaintType) const; CanvasStyle* Style(PaintType) const;
bool HasPattern() const; // Check the pattern in StrokeStyle or FillStyle depending on the PaintType
bool HasPattern(PaintType) const;
// Only to be used if the CanvasRenderingContext2DState has Pattern // Only to be used if the CanvasRenderingContext2DState has Pattern
bool PatternIsAccelerated() const; // Pattern is in either StrokeStyle or FillStyle depending on the PaintType
bool PatternIsAccelerated(PaintType) const;
enum Direction { kDirectionInherit, kDirectionRTL, kDirectionLTR }; enum Direction { kDirectionInherit, kDirectionRTL, kDirectionLTR };
......
...@@ -583,7 +583,7 @@ void OffscreenCanvasRenderingContext2D::DrawTextInternal( ...@@ -583,7 +583,7 @@ void OffscreenCanvasRenderingContext2D::DrawTextInternal(
}, },
[](const SkIRect& rect) // overdraw test lambda [](const SkIRect& rect) // overdraw test lambda
{ return false; }, { return false; },
bounds, paint_type); bounds, paint_type, CanvasRenderingContext2DState::kNoImage);
paint_canvas->restoreToCount(save_count); paint_canvas->restoreToCount(save_count);
ValidateStateStack(); ValidateStateStack();
} }
......
...@@ -64,32 +64,32 @@ function testPixelShadow(pixel, reference, alphaApprox) { ...@@ -64,32 +64,32 @@ function testPixelShadow(pixel, reference, alphaApprox) {
testScenarios = testScenarios =
[ [
['TestAlphaShadow 1', ctx.getImageData(400, 150, 1, 1).data, [ 0, 0, 0, 0, 0], 0], ['TestAlphaShadow 1', ctx.getImageData(400, 150, 1, 1).data, [ 0, 0, 0, 0], 0],
['TestAlphaShadow 2', ctx.getImageData(400, 75, 1, 1).data, [ 255, 0, 0, 64], 15], ['TestAlphaShadow 2', ctx.getImageData(400, 75, 1, 1).data, [ 255, 0, 0, 64], 15],
['TestAlphaShadow 3', ctx.getImageData(400, 225, 1, 1).data, [ 255, 0, 0, 64], 15], ['TestAlphaShadow 3', ctx.getImageData(400, 225, 1, 1).data, [ 255, 0, 0, 64], 15],
['TestAlphaShadow 4', ctx.getImageData(325, 150, 1, 1).data, [ 255, 0, 0, 64], 15], ['TestAlphaShadow 4', ctx.getImageData(325, 150, 1, 1).data, [ 255, 0, 0, 64], 15],
['TestAlphaShadow 5', ctx.getImageData(475, 150, 1, 1).data, [ 255, 0, 0, 64], 15], ['TestAlphaShadow 5', ctx.getImageData(475, 150, 1, 1).data, [ 255, 0, 0, 64], 15],
['TestBlurryShadow 1', ctx.getImageData(400, 400, 1, 1).data, [ 0, 0, 0, 0, 0], 0], ['TestBlurryShadow 1', ctx.getImageData(400, 400, 1, 1).data, [ 0, 0, 0, 0], 0],
['TestBlurryShadow 2', ctx.getImageData(400, 300, 1, 1).data, [ 255, 0, 0, 31], 15], ['TestBlurryShadow 2', ctx.getImageData(400, 300, 1, 1).data, [ 255, 0, 0, 31], 15],
['TestBlurryShadow 3', ctx.getImageData(400, 500, 1, 1).data, [ 255, 0, 0, 31], 15], ['TestBlurryShadow 3', ctx.getImageData(400, 500, 1, 1).data, [ 255, 0, 0, 31], 15],
['TestBlurryShadow 4', ctx.getImageData(300, 400, 1, 1).data, [ 255, 0, 0, 31], 15], ['TestBlurryShadow 4', ctx.getImageData(300, 400, 1, 1).data, [ 255, 0, 0, 31], 15],
['TestBlurryShadow 5', ctx.getImageData(500, 400, 1, 1).data, [ 255, 0, 0, 31], 15], ['TestBlurryShadow 5', ctx.getImageData(500, 400, 1, 1).data, [ 255, 0, 0, 31], 15],
['TestRotatedAlphaShadow 1', ctx.getImageData(400, 650, 1, 1).data, [ 0, 0, 0, 0, 0], 0], ['TestRotatedAlphaShadow 1', ctx.getImageData(400, 650, 1, 1).data, [ 0, 0, 0, 0], 0],
['TestRotatedAlphaShadow 2', ctx.getImageData(400, 575, 1, 1).data, [ 255, 0, 0, 64], 15], ['TestRotatedAlphaShadow 2', ctx.getImageData(400, 575, 1, 1).data, [ 255, 0, 0, 64], 15],
['TestRotatedAlphaShadow 3', ctx.getImageData(400, 725, 1, 1).data, [ 255, 0, 0, 64], 15], ['TestRotatedAlphaShadow 3', ctx.getImageData(400, 725, 1, 1).data, [ 255, 0, 0, 64], 15],
['TestRotatedAlphaShadow 4', ctx.getImageData(325, 650, 1, 1).data, [ 255, 0, 0, 64], 15], ['TestRotatedAlphaShadow 4', ctx.getImageData(325, 650, 1, 1).data, [ 255, 0, 0, 64], 15],
['TestRotatedAlphaShadow 5', ctx.getImageData(475, 650, 1, 1).data, [ 255, 0, 0, 64], 15], ['TestRotatedAlphaShadow 5', ctx.getImageData(475, 650, 1, 1).data, [ 255, 0, 0, 64], 15],
['TestRotatedBlurryShadow 1', ctx.getImageData(400, 900, 1, 1).data, [ 0, 0, 0, 0, 0], 0], ['TestRotatedBlurryShadow 1', ctx.getImageData(400, 900, 1, 1).data, [ 0, 0, 0, 0], 0],
['TestRotatedBlurryShadow 2', ctx.getImageData(400, 800, 1, 1).data, [ 255, 0, 0, 31], 15], ['TestRotatedBlurryShadow 2', ctx.getImageData(400, 800, 1, 1).data, [ 255, 0, 0, 31], 15],
['TestRotatedBlurryShadow 3', ctx.getImageData(400, 1000, 1, 1).data, [ 255, 0, 0, 31], 15], ['TestRotatedBlurryShadow 3', ctx.getImageData(400, 1000, 1, 1).data, [ 255, 0, 0, 31], 15],
['TestRotatedBlurryShadow 4', ctx.getImageData(300, 900, 1, 1).data, [ 255, 0, 0, 31], 15], ['TestRotatedBlurryShadow 4', ctx.getImageData(300, 900, 1, 1).data, [ 255, 0, 0, 31], 15],
['TestRotatedBlurryShadow 5', ctx.getImageData(500, 900, 1, 1).data, [ 255, 0, 0, 31], 15], ['TestRotatedBlurryShadow 5', ctx.getImageData(500, 900, 1, 1).data, [ 255, 0, 0, 31], 15],
]; ];
generate_tests(testPixelShadow, testScenarios); generate_tests(testPixelShadow, testScenarios);
</script> </script>
</body> </body>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<body>
<script>
test(function(t) {
var SHADOW_OFFSET = 15;
var image = new Image();
image.src = "resources/html5.png";
image.onload = function() {
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.setAttribute('width', '1000');
canvas.setAttribute('height', '1000');
var ctx = canvas.getContext('2d');
var pattern = ctx.createPattern(image, 'repeat');
ctx.beginPath();
ctx.fillStyle = pattern;
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = SHADOW_OFFSET;
ctx.shadowOffsetY = SHADOW_OFFSET;
ctx.rect(0, 0, image.width, image.height);
// colored pixels of html5.png starts at 3 pixels away from the border of
// the image, they must be taken into account since the shadow is applied
// to the colored pixels.
const squareBorder = image.width + SHADOW_OFFSET + 3 * 2;
var imgDataBefore = [];
imgDataBefore.push(ctx.getImageData(25, squareBorder, 1, 1).data)
for (var i = 25; i < squareBorder; i += 10)
imgDataBefore.push(ctx.getImageData(squareBorder, i, 1, 1).data)
ctx.fill();
var imgDataAfter = [];
imgDataAfter.push(ctx.getImageData(25, squareBorder, 1, 1).data)
for (var i = 25; i < squareBorder; i += 10)
imgDataAfter.push(ctx.getImageData(squareBorder, i, 1, 1).data)
for (var i = 0; i < imgDataAfter.length; i++)
assert_array_equals(imgDataAfter[i], imgDataBefore[i]);
};
}, 'Test that the drop shadow properly falls off when a rect is drawn by filling with a pattern.');
</script>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<body>
<script>
test(function(t) {
var SHADOW_OFFSET = 15;
var image = new Image();
image.src = "resources/html5.png";
image.onload = function() {
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.setAttribute('width', '1000');
canvas.setAttribute('height', '1000');
var ctx = canvas.getContext('2d');
var pattern = ctx.createPattern(image, 'repeat');
ctx.beginPath();
ctx.fillStyle = pattern;
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = SHADOW_OFFSET;
ctx.shadowOffsetY = SHADOW_OFFSET;
// colored pixels of html5.png starts at 3 pixels away from the border of
// the image, they must be taken into account since the shadow is applied
// to the colored pixels.
const squareBorder = image.width + SHADOW_OFFSET + 3 * 2;
var imgDataBefore = [];
imgDataBefore.push(ctx.getImageData(25, squareBorder, 1, 1).data)
for (var i = 25; i < squareBorder; i += 10)
imgDataBefore.push(ctx.getImageData(squareBorder, i, 1, 1).data)
ctx.fillRect(0, 0, image.width, image.height);
var imgDataAfter = [];
imgDataAfter.push(ctx.getImageData(25, squareBorder, 1, 1).data)
for (var i = 25; i < squareBorder; i += 10)
imgDataAfter.push(ctx.getImageData(squareBorder, i, 1, 1).data)
for (var i = 0; i < imgDataAfter.length; i++)
assert_array_equals(imgDataAfter[i], imgDataBefore[i]);
};
}, 'Test that the drop shadow properly falls off when fillRect is drawn with a pattern.');
</script>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<body>
<script>
test(function(t) {
var STROKE_WIDTH = 20;
var SHADOW_OFFSET = 15;
// Do not start at (0, 0)
// otherwise half of the stroke on top left will be hidden
var POSITION = 20;
var SIDE_LENGTH = 100;
var image = new Image();
image.src = "resources/html5.png";
image.onload = function() {
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.setAttribute('width', '1000');
canvas.setAttribute('height', '1000');
var ctx = canvas.getContext('2d');
var pattern = ctx.createPattern(image, 'repeat');
ctx.beginPath();
ctx.strokeStyle = pattern;
ctx.lineWidth = STROKE_WIDTH;
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = SHADOW_OFFSET;
ctx.shadowOffsetY = SHADOW_OFFSET;
// colored pixels of html5.png starts at 3 pixels away from the border of
// the image, they must be taken into account since the shadow is applied
// to the colored pixels.
const squareBorder = POSITION + SIDE_LENGTH + STROKE_WIDTH / 2 + SHADOW_OFFSET + 3 * 2;
var imgDataBefore = [];
for (var i = 25; i < squareBorder; i += 10)
imgDataBefore.push(ctx.getImageData(squareBorder, i, 1, 1).data)
ctx.strokeRect(POSITION, POSITION, SIDE_LENGTH, SIDE_LENGTH);
var imgDataAfter = [];
for (var i = 25; i < squareBorder; i += 10)
imgDataAfter.push(ctx.getImageData(squareBorder, i, 1, 1).data)
for (var i = 0; i < imgDataAfter.length; i++)
assert_array_equals(imgDataAfter[i], imgDataBefore[i]);
};
}, 'Test that the drop shadow properly falls off when a rect is stroked with a pattern.');
</script>
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