Fix mask/image filter bounds calculation for inverse-filled paths

Bug: skia:12587, skia:10959
Change-Id: I43dea5f2d2f54c7db47e778ac9c7bac53ae4998d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/509176
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2022-02-15 12:29:58 -05:00 committed by SkCQ
parent d6245fc4aa
commit 0b124c444e
8 changed files with 100 additions and 29 deletions

View File

@ -7,6 +7,7 @@
#include "gm/gm.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathEffect.h"
@ -15,6 +16,7 @@
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkDashPathEffect.h"
#include "include/effects/SkImageFilters.h"
#include <utility>
@ -135,3 +137,64 @@ DEF_SIMPLE_GM(inverse_paths, canvas, 800, 1200) {
}
}
}
DEF_SIMPLE_GM(inverse_fill_filters, canvas, 384, 128) {
auto draw = [canvas](const SkPaint& paint) {
SkPath path = SkPath::Circle(65.f, 65.f, 30.f);
path.setFillType(SkPathFillType::kInverseWinding);
canvas->save();
canvas->clipRect({0, 0, 128, 128});
canvas->drawPath(path, paint);
canvas->restore();
SkPaint stroke;
stroke.setStyle(SkPaint::kStroke_Style);
stroke.setColor(SK_ColorWHITE);
canvas->drawRect({0, 0, 128, 128}, stroke);
};
SkPaint paint;
paint.setAntiAlias(true);
draw(paint);
canvas->translate(128, 0);
paint.setImageFilter(SkImageFilters::Blur(5.f, 5.f, nullptr));
draw(paint);
canvas->translate(128, 0);
paint.setImageFilter(nullptr);
paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5));
draw(paint);
}
DEF_SIMPLE_GM(inverse_windingmode_filters, canvas, 256, 100) {
SkPath path;
path.addRect({10, 10, 30, 30}, SkPathDirection::kCW);
path.addRect({20, 20, 40, 40}, SkPathDirection::kCW);
path.addRect({10, 60, 30, 80}, SkPathDirection::kCW);
path.addRect({20, 70, 40, 90}, SkPathDirection::kCCW);
SkPaint strokePaint;
strokePaint.setStyle(SkPaint::kStroke_Style);
SkRect clipRect = {0, 0, 51, 99};
canvas->drawPath(path, strokePaint);
SkPaint fillPaint;
fillPaint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 1.0f));
for (auto fillType : { SkPathFillType::kWinding,
SkPathFillType::kEvenOdd,
SkPathFillType::kInverseWinding,
SkPathFillType::kInverseEvenOdd } ) {
canvas->translate(51, 0);
canvas->save();
canvas->clipRect(clipRect);
path.setFillType(fillType);
canvas->drawPath(path, fillPaint);
canvas->restore();
SkPaint clipPaint;
clipPaint.setColor(SK_ColorRED);
clipPaint.setStyle(SkPaint::kStroke_Style);
clipPaint.setStrokeWidth(1.f);
canvas->drawRect(clipRect, clipPaint);
}
}

View File

@ -2129,7 +2129,7 @@ void SkCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {
return;
}
auto layer = this->aboutToDraw(this, paint, &pathBounds);
auto layer = this->aboutToDraw(this, paint, path.isInverseFillType() ? nullptr : &pathBounds);
if (layer) {
this->topDevice()->drawPath(path, layer->paint());
}

View File

@ -1082,7 +1082,7 @@ void SkDraw::validate() const {
#include "src/core/SkBlitter.h"
#include "src/core/SkDraw.h"
bool SkDraw::ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect* clipBounds,
bool SkDraw::ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect& clipBounds,
const SkMaskFilter* filter, const SkMatrix* filterMatrix,
SkIRect* bounds) {
// init our bounds from the path
@ -1101,21 +1101,18 @@ bool SkDraw::ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect* clipB
}
}
// (possibly) trim the bounds to reflect the clip
// (plus whatever slop the filter needs)
if (clipBounds) {
// trim the bounds to reflect the clip (plus whatever slop the filter needs)
// Ugh. Guard against gigantic margins from wacky filters. Without this
// check we can request arbitrary amounts of slop beyond our visible
// clip, and bring down the renderer (at least on finite RAM machines
// like handsets, etc.). Need to balance this invented value between
// quality of large filters like blurs, and the corresponding memory
// requests.
static const int MAX_MARGIN = 128;
if (!bounds->intersect(clipBounds->makeOutset(std::min(margin.fX, MAX_MARGIN),
std::min(margin.fY, MAX_MARGIN)))) {
static constexpr int kMaxMargin = 128;
if (!bounds->intersect(clipBounds.makeOutset(std::min(margin.fX, kMaxMargin),
std::min(margin.fY, kMaxMargin)))) {
return false;
}
}
return true;
}
@ -1152,7 +1149,7 @@ static void draw_into_mask(const SkMask& mask, const SkPath& devPath,
draw.drawPath(devPath, paint);
}
bool SkDraw::DrawToMask(const SkPath& devPath, const SkIRect* clipBounds,
bool SkDraw::DrawToMask(const SkPath& devPath, const SkIRect& clipBounds,
const SkMaskFilter* filter, const SkMatrix* filterMatrix,
SkMask* mask, SkMask::CreateMode mode,
SkStrokeRec::InitStyle style) {
@ -1161,7 +1158,13 @@ bool SkDraw::DrawToMask(const SkPath& devPath, const SkIRect* clipBounds,
}
if (SkMask::kJustRenderImage_CreateMode != mode) {
if (!ComputeMaskBounds(devPath.getBounds(), clipBounds, filter,
// By using infinite bounds for inverse fills, ComputeMaskBounds is able to clip it to
// 'clipBounds' outset by whatever extra margin the mask filter requires.
static const SkRect kInverseBounds = { SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity,
SK_ScalarInfinity, SK_ScalarInfinity};
SkRect pathBounds = devPath.isInverseFillType() ? kInverseBounds
: devPath.getBounds();
if (!ComputeMaskBounds(pathBounds, clipBounds, filter,
filterMatrix, &mask->fBounds))
return false;
}

View File

@ -84,7 +84,7 @@ public:
void paintMasks(SkDrawableGlyphBuffer* accepted, const SkPaint& paint) const override;
static bool ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect* clipBounds,
static bool ComputeMaskBounds(const SkRect& devPathBounds, const SkIRect& clipBounds,
const SkMaskFilter* filter, const SkMatrix* filterMatrix,
SkIRect* bounds);
@ -93,7 +93,7 @@ public:
that must be done afterwards (by calling filterMask). The maskfilter is provided
solely to assist in computing the mask's bounds (if the mode requests that).
*/
static bool DrawToMask(const SkPath& devPath, const SkIRect* clipBounds,
static bool DrawToMask(const SkPath& devPath, const SkIRect& clipBounds,
const SkMaskFilter*, const SkMatrix* filterMatrix,
SkMask* mask, SkMask::CreateMode mode,
SkStrokeRec::InitStyle style);

View File

@ -265,7 +265,7 @@ bool SkMaskFilterBase::filterPath(const SkPath& devPath, const SkMatrix& matrix,
return false;
}
#endif
if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), this, &matrix, &srcM,
if (!SkDraw::DrawToMask(devPath, clip.getBounds(), this, &matrix, &srcM,
SkMask::kComputeBoundsAndRenderImage_CreateMode,
style)) {
return false;

View File

@ -133,7 +133,7 @@ static GrSurfaceProxyView sw_create_filtered_mask(GrRecordingContext* rContext,
devPath.transform(viewMatrix);
SkMask srcM, dstM;
if (!SkDraw::DrawToMask(devPath, &clipBounds, filter, &viewMatrix, &srcM,
if (!SkDraw::DrawToMask(devPath, clipBounds, filter, &viewMatrix, &srcM,
SkMask::kComputeBoundsAndRenderImage_CreateMode, fillOrHairline)) {
return {};
}
@ -237,12 +237,17 @@ static std::unique_ptr<skgpu::v1::SurfaceDrawContext> create_mask_GPU(
static bool get_unclipped_shape_dev_bounds(const GrStyledShape& shape, const SkMatrix& matrix,
SkIRect* devBounds) {
SkRect shapeDevBounds;
if (shape.inverseFilled()) {
shapeDevBounds = {SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity,
SK_ScalarInfinity, SK_ScalarInfinity};
} else {
SkRect shapeBounds = shape.styledBounds();
if (shapeBounds.isEmpty()) {
return false;
}
SkRect shapeDevBounds;
matrix.mapRect(&shapeDevBounds, shapeBounds);
}
// Even though these are "unclipped" bounds we still clip to the int32_t range.
// This is the largest int32_t that is representable exactly as a float. The next 63 larger ints
// would round down to this value when cast to a float, but who really cares.

View File

@ -504,7 +504,7 @@ void SkPDFDevice::internalDrawPathWithFilter(const SkClipStack& clipStack,
SkIRect bounds = clipStack.bounds(this->bounds()).roundOut();
SkMask sourceMask;
if (!SkDraw::DrawToMask(path, &bounds, paint->getMaskFilter(), &SkMatrix::I(),
if (!SkDraw::DrawToMask(path, bounds, paint->getMaskFilter(), &SkMatrix::I(),
&sourceMask, SkMask::kComputeBoundsAndRenderImage_CreateMode,
initStyle)) {
return;

View File

@ -1590,7 +1590,7 @@ void SkXPSDevice::drawPath(const SkPath& platonicPath,
SkMask rasteredMask;
if (SkDraw::DrawToMask(
*pixelPath,
&clipIRect,
clipIRect,
filter, //just to compute how much to draw.
&matrix,
&rasteredMask,