Picture shaders support perspective better

Adds a new GM that draws text in an SkPicture, transformed by a
perspective matrix. When drawn directly with drawPicture() it looks
fine. When drawn calculating a scale factor manually from the matrix
and converting to an image (SkImage::MakeFromPicture + drawImageRect),
it looks nearly indistinguishable. However, when drawing using a picture
shader, the image resolution of the cached rasterization is far too low.

Originally, this is because SkPictureShader only relied on
SkMatrix::decomposeScale, which fails if there's perspective and
the picture shader's fallback is to switch to the identity scale. When
the source picture has relatively small local bounds compared to the
final destination, this results in severe sampling artifacts.

This CL switches the fallback to use SkMatrixPriv::DifferentialAreaScale,
which is what was used to compute the manual scale factor in the GM,
and is what image filters use when creating their device's coordinate
space when the canvas has perspective.

Change-Id: I856cd1277eb7cfcb43c766ff0d123ee1a667fe29
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/507917
Auto-Submit: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2022-02-11 14:57:20 -05:00 committed by SkCQ
parent 51a553d8c5
commit ff616b89e3
2 changed files with 80 additions and 1 deletions

View File

@ -20,6 +20,7 @@
#include "include/core/SkShader.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "tools/ToolUtils.h"
@ -236,3 +237,74 @@ DEF_SIMPLE_GM(tiled_picture_shader, canvas, 400, 400) {
SkFilterMode::kNearest));
canvas->drawPaint(p);
}
DEF_SIMPLE_GM(pictureshader_persp, canvas, 215, 110) {
enum class DrawStrategy {
kDirect,
kPictureShader,
};
auto drawPicture = [](SkCanvas* canvas, sk_sp<SkPicture> picture, DrawStrategy strategy) {
// Only want local upper 50x50 of 'picture' before we apply decal (or clip)
SkRect bounds = {0.f, 0.f, 50.f, 50.f};
switch(strategy) {
case DrawStrategy::kDirect: {
canvas->clipRect(bounds, true);
canvas->drawPicture(picture);
break; }
case DrawStrategy::kPictureShader: {
SkPaint paint;
paint.setShader(picture->makeShader(SkTileMode::kDecal, SkTileMode::kDecal,
SkFilterMode::kLinear, nullptr, &bounds));
canvas->drawRect({0.f, 0.f, 50.f, 50.f}, paint);
break; }
}
};
auto picture = []() {
sk_sp<SkTypeface> typeface = SkTypeface::MakeDefault();
if (!typeface) {
typeface = SkTypeface::MakeFromName("monospace", SkFontStyle());
}
SkFont font;
font.setTypeface(typeface);
font.setHinting(SkFontHinting::kNormal);
font.setSize(8.f);
SkPaint paint;
paint.setColor(SK_ColorGREEN);
SkPictureRecorder recorder;
SkCanvas* record_canvas = recorder.beginRecording({0, 0, 100, 100});
record_canvas->drawTextBlob(SkTextBlob::MakeFromString("Hamburgefons", font),
0, 16.f, paint);
return recorder.finishRecordingAsPicture();
}();
SkM44 m;
m.preScale(2.f, 2.f);
SkM44 persp = SkM44::Perspective(0.01f, 10.f, SK_ScalarPI / 3.f);
persp.preTranslate(0.f, 5.f, -0.1f);
persp.preConcat(SkM44::Rotate({0.f, 1.f, 0.f}, 0.008f));
m.postConcat(persp);
canvas->clear(SK_ColorBLACK);
canvas->translate(5.f, 5.f);
for (auto strategy : { DrawStrategy::kDirect,
DrawStrategy::kPictureShader }) {
canvas->save();
SkPaint outline;
outline.setColor(SK_ColorWHITE);
outline.setStyle(SkPaint::kStroke_Style);
outline.setStrokeWidth(1.f);
canvas->drawRect({-1, -1, 101, 101}, outline);
canvas->clipRect({0, 0, 100, 100});
canvas->concat(m);
drawPicture(canvas, picture, strategy);
canvas->restore();
canvas->translate(105.f, 0.f);
}
}

View File

@ -13,6 +13,7 @@
#include "include/private/SkImageInfoPriv.h"
#include "src/core/SkArenaAlloc.h"
#include "src/core/SkImagePriv.h"
#include "src/core/SkMatrixPriv.h"
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkMatrixUtils.h"
#include "src/core/SkPicturePriv.h"
@ -184,7 +185,13 @@ struct CachedImageInfo {
SkSize size;
// Use a rotation-invariant scale
if (!m.decomposeScale(&size, nullptr)) {
size = {1, 1};
SkPoint center = {bounds.centerX(), bounds.centerY()};
SkScalar area = SkMatrixPriv::DifferentialAreaScale(m, center);
if (!SkScalarIsFinite(area) || SkScalarNearlyZero(area)) {
size = {1, 1}; // ill-conditioned matrix
} else {
size.fWidth = size.fHeight = SkScalarSqrt(area);
}
}
size.fWidth *= bounds.width();
size.fHeight *= bounds.height();