skia2/gm/nested.cpp
Michael Ludwig 144926d968 Allow subpixel nested rects to have nearly equal x/y half widths
When transforming to device space, equal local-space half widths in X
and Y might end up being fractionally different, which would prevent
optimizing the nested rect path to use the stroke aa rect op. Visually,
these very small differences are not going to be noticeable and the op
can just arbitrarily pick one of the two widths to use for coverage.

Also fixed an issue where if the aa stroke op returned null from
MakeNested(), drawSimplifiedShape() assumed it meant nothing needed to
be drawn. After https://skia-review.googlesource.com/c/skia/+/427976,
this actually isn't the case and it should attempt path rendering for
the unbalanced subpixel nested rects.

In the linked bug's case, the square rects would not transform to
identical x and y half widths depending on the UI scale of the browser
(differing by a negligible amount). This would then return a null op
and skip falling back to the path renderer so nothing would draw.

Bug: chromium:1234194
Change-Id: Ibd4254cb37e0fc01a5da342e06c4d82cb6223fe8
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/447462
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
2021-09-10 21:05:27 +00:00

187 lines
5.7 KiB
C++

/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm/gm.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/utils/SkRandom.h"
namespace skiagm {
// Test out various combinations of nested rects, ovals and rrects.
class NestedGM : public GM {
public:
NestedGM(bool doAA, bool flipped) : fDoAA(doAA), fFlipped(flipped) {
this->setBGColor(0xFFDDDDDD);
}
protected:
SkString onShortName() override {
SkString name("nested");
if (fFlipped) {
name.append("_flipY");
}
if (fDoAA) {
name.append("_aa");
} else {
name.append("_bw");
}
return name;
}
SkISize onISize() override {
return SkISize::Make(kImageWidth, kImageHeight);
}
enum Shapes {
kRect_Shape = 0,
kRRect_Shape,
kOval_Shape,
kShapeCount
};
static void AddShape(SkPathBuilder* b, const SkRect& rect, Shapes shape, SkPathDirection dir) {
switch (shape) {
case kRect_Shape:
b->addRect(rect, dir);
break;
case kRRect_Shape: {
SkRRect rr;
rr.setRectXY(rect, 5, 5);
b->addRRect(rr, dir);
break;
}
case kOval_Shape:
b->addOval(rect, dir);
break;
default:
break;
}
}
void onDraw(SkCanvas* canvas) override {
SkPaint shapePaint;
shapePaint.setColor(SK_ColorBLACK);
shapePaint.setAntiAlias(fDoAA);
SkRect outerRect = SkRect::MakeWH(40, 40);
SkRect innerRects[] = {
{ 10, 10, 30, 30 }, // small
{ .5f, 18, 4.5f, 22 } // smaller and offset to left
};
// draw a background pattern to make transparency errors more apparent
SkRandom rand;
for (int y = 0; y < kImageHeight; y += 10) {
for (int x = 0; x < kImageWidth; x += 10) {
SkRect r = SkRect::MakeXYWH(SkIntToScalar(x),
SkIntToScalar(y),
10, 10);
SkPaint p;
p.setColor(rand.nextU() | 0xFF000000);
canvas->drawRect(r, p);
}
}
SkScalar xOff = 2, yOff = 2;
for (int outerShape = 0; outerShape < kShapeCount; ++outerShape) {
for (int innerShape = 0; innerShape < kShapeCount; ++innerShape) {
for (size_t innerRect = 0; innerRect < SK_ARRAY_COUNT(innerRects); ++innerRect) {
SkPathBuilder builder;
AddShape(&builder, outerRect, (Shapes) outerShape, SkPathDirection::kCW);
AddShape(&builder, innerRects[innerRect], (Shapes) innerShape,
SkPathDirection::kCCW);
canvas->save();
if (fFlipped) {
canvas->scale(1.0f, -1.0f);
canvas->translate(xOff, -yOff - 40.0f);
} else {
canvas->translate(xOff, yOff);
}
canvas->drawPath(builder.detach(), shapePaint);
canvas->restore();
xOff += 45;
}
}
xOff = 2;
yOff += 45;
}
}
private:
static constexpr int kImageWidth = 269;
static constexpr int kImageHeight = 134;
bool fDoAA;
bool fFlipped;
using INHERITED = GM;
};
///////////////////////////////////////////////////////////////////////////////
DEF_GM( return new NestedGM(/* doAA = */ true, /* flipped = */ false); )
DEF_GM( return new NestedGM(/* doAA = */ false, /* flipped = */ false); )
DEF_GM( return new NestedGM(/* doAA = */ true, /* flipped = */ true); )
DEF_GM( return new NestedGM(/* doAA = */ false, /* flipped = */ true); )
DEF_SIMPLE_GM(nested_hairline_square, canvas, 64, 64) {
// See crbug.com/1234194 - This should draw 1 row of 3 stroked squares, with a second 0.5px
// shifted row of squares below it.
auto drawEllipses = [&]() {
canvas->save();
// Originally the SVG string "M5,14H0V9h5V14Z M1,13h3v-3H1V13Z" but that just specifies a
// 5px wide square outside a 3px wide square.
SkPath square;
square.addRect(SkRect::MakeLTRB(0.f, 9.f, 5.f, 14.f));
square.addRect(SkRect::MakeLTRB(1.f, 10.f, 4.f, 13.f), SkPathDirection::kCCW);
// From the bug, SVG viewbox was (0, 0, 24, 24), so the above coordinates are relative to
// that, but the svg was then the child of a div that was 16x16, so it's scaled down. This
// converts the 1px wide nested rects into subpixel nested rects.
canvas->scale(16.f / 24.f, 16.f / 24.f);
SkPaint paint;
paint.setColor(SkColorSetARGB(255, 70, 70, 70));
paint.setAntiAlias(true);
// The original SVG drew 3 separate paths, but these were just translations of the original
// path baked into a path string.
canvas->drawPath(square, paint);
canvas->translate(10.f, 0.f);
canvas->drawPath(square, paint);
canvas->translate(10.f, 0.f);
canvas->drawPath(square, paint);
canvas->restore();
};
drawEllipses();
canvas->translate(0.5f, 16.f);
drawEllipses();
}
} // namespace skiagm