ff616b89e3
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>
311 lines
11 KiB
C++
311 lines
11 KiB
C++
/*
|
|
* Copyright 2014 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/SkBitmap.h"
|
|
#include "include/core/SkCanvas.h"
|
|
#include "include/core/SkColor.h"
|
|
#include "include/core/SkMatrix.h"
|
|
#include "include/core/SkPaint.h"
|
|
#include "include/core/SkPicture.h"
|
|
#include "include/core/SkPictureRecorder.h"
|
|
#include "include/core/SkPoint.h"
|
|
#include "include/core/SkRect.h"
|
|
#include "include/core/SkRefCnt.h"
|
|
#include "include/core/SkScalar.h"
|
|
#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"
|
|
|
|
static struct {
|
|
SkTileMode tmx;
|
|
SkTileMode tmy;
|
|
} kTileConfigs[] = {
|
|
{ SkTileMode::kRepeat, SkTileMode::kRepeat },
|
|
{ SkTileMode::kRepeat, SkTileMode::kClamp },
|
|
{ SkTileMode::kMirror, SkTileMode::kRepeat },
|
|
};
|
|
|
|
class PictureShaderGM : public skiagm::GM {
|
|
public:
|
|
PictureShaderGM(SkScalar tileSize, SkScalar sceneSize, bool useLocalMatrixWrapper = false,
|
|
float alpha = 1)
|
|
: fTileSize(tileSize)
|
|
, fSceneSize(sceneSize)
|
|
, fAlpha(alpha)
|
|
, fUseLocalMatrixWrapper(useLocalMatrixWrapper)
|
|
{}
|
|
|
|
protected:
|
|
void onOnceBeforeDraw() override {
|
|
// Build the picture.
|
|
SkPictureRecorder recorder;
|
|
SkCanvas* pictureCanvas = recorder.beginRecording(fTileSize, fTileSize);
|
|
this->drawTile(pictureCanvas);
|
|
fPicture = recorder.finishRecordingAsPicture();
|
|
|
|
// Build a reference bitmap.
|
|
fBitmap.allocN32Pixels(SkScalarCeilToInt(fTileSize), SkScalarCeilToInt(fTileSize));
|
|
fBitmap.eraseColor(SK_ColorTRANSPARENT);
|
|
SkCanvas bitmapCanvas(fBitmap);
|
|
this->drawTile(&bitmapCanvas);
|
|
}
|
|
|
|
|
|
SkString onShortName() override {
|
|
return SkStringPrintf("pictureshader%s%s",
|
|
fUseLocalMatrixWrapper ? "_localwrapper" : "",
|
|
fAlpha < 1 ? "_alpha" : "");
|
|
}
|
|
|
|
SkISize onISize() override {
|
|
return SkISize::Make(1400, 1450);
|
|
}
|
|
|
|
void onDraw(SkCanvas* canvas) override {
|
|
this->drawSceneColumn(canvas, SkPoint::Make(0, 0), 1, 1, 0);
|
|
this->drawSceneColumn(canvas, SkPoint::Make(0, fSceneSize * 6.4f), 1, 2, 0);
|
|
this->drawSceneColumn(canvas, SkPoint::Make(fSceneSize * 2.4f, 0), 1, 1, 1);
|
|
this->drawSceneColumn(canvas, SkPoint::Make(fSceneSize * 2.4f, fSceneSize * 6.4f), 1, 1, 2);
|
|
this->drawSceneColumn(canvas, SkPoint::Make(fSceneSize * 4.8f, 0), 2, 1, 0);
|
|
this->drawSceneColumn(canvas, SkPoint::Make(fSceneSize * 9.6f, 0), 2, 2, 0);
|
|
|
|
// One last custom row to exercise negative scaling
|
|
SkMatrix ctm, localMatrix;
|
|
ctm.setTranslate(fSceneSize * 2.1f, fSceneSize * 13.8f);
|
|
ctm.preScale(-1, -1);
|
|
localMatrix.setScale(2, 2);
|
|
this->drawScene(canvas, ctm, localMatrix, 0);
|
|
|
|
ctm.setTranslate(fSceneSize * 2.4f, fSceneSize * 12.8f);
|
|
localMatrix.setScale(-1, -1);
|
|
this->drawScene(canvas, ctm, localMatrix, 0);
|
|
|
|
ctm.setTranslate(fSceneSize * 4.8f, fSceneSize * 12.3f);
|
|
ctm.preScale(2, 2);
|
|
this->drawScene(canvas, ctm, localMatrix, 0);
|
|
|
|
ctm.setTranslate(fSceneSize * 13.8f, fSceneSize * 14.3f);
|
|
ctm.preScale(-2, -2);
|
|
localMatrix.setTranslate(fTileSize / 4, fTileSize / 4);
|
|
localMatrix.preRotate(45);
|
|
localMatrix.preScale(-2, -2);
|
|
this->drawScene(canvas, ctm, localMatrix, 0);
|
|
}
|
|
|
|
private:
|
|
void drawSceneColumn(SkCanvas* canvas, const SkPoint& pos, SkScalar scale, SkScalar localScale,
|
|
unsigned tileMode) {
|
|
SkMatrix ctm, localMatrix;
|
|
|
|
ctm.setTranslate(pos.x(), pos.y());
|
|
ctm.preScale(scale, scale);
|
|
localMatrix.setScale(localScale, localScale);
|
|
this->drawScene(canvas, ctm, localMatrix, tileMode);
|
|
|
|
ctm.setTranslate(pos.x(), pos.y() + fSceneSize * 1.2f * scale);
|
|
ctm.preScale(scale, scale);
|
|
localMatrix.setTranslate(fTileSize / 4, fTileSize / 4);
|
|
localMatrix.preScale(localScale, localScale);
|
|
this->drawScene(canvas, ctm, localMatrix, tileMode);
|
|
|
|
ctm.setTranslate(pos.x(), pos.y() + fSceneSize * 2.4f * scale);
|
|
ctm.preScale(scale, scale);
|
|
localMatrix.setRotate(45);
|
|
localMatrix.preScale(localScale, localScale);
|
|
this->drawScene(canvas, ctm, localMatrix, tileMode);
|
|
|
|
ctm.setTranslate(pos.x(), pos.y() + fSceneSize * 3.6f * scale);
|
|
ctm.preScale(scale, scale);
|
|
localMatrix.setSkew(1, 0);
|
|
localMatrix.preScale(localScale, localScale);
|
|
this->drawScene(canvas, ctm, localMatrix, tileMode);
|
|
|
|
ctm.setTranslate(pos.x(), pos.y() + fSceneSize * 4.8f * scale);
|
|
ctm.preScale(scale, scale);
|
|
localMatrix.setTranslate(fTileSize / 4, fTileSize / 4);
|
|
localMatrix.preRotate(45);
|
|
localMatrix.preScale(localScale, localScale);
|
|
this->drawScene(canvas, ctm, localMatrix, tileMode);
|
|
}
|
|
|
|
void drawTile(SkCanvas* canvas) {
|
|
SkPaint paint;
|
|
paint.setColor(SK_ColorGREEN);
|
|
paint.setStyle(SkPaint::kFill_Style);
|
|
paint.setAntiAlias(true);
|
|
|
|
canvas->drawCircle(fTileSize / 4, fTileSize / 4, fTileSize / 4, paint);
|
|
canvas->drawRect(SkRect::MakeXYWH(fTileSize / 2, fTileSize / 2,
|
|
fTileSize / 2, fTileSize / 2), paint);
|
|
|
|
paint.setColor(SK_ColorRED);
|
|
canvas->drawLine(fTileSize / 2, fTileSize * 1 / 3,
|
|
fTileSize / 2, fTileSize * 2 / 3, paint);
|
|
canvas->drawLine(fTileSize * 1 / 3, fTileSize / 2,
|
|
fTileSize * 2 / 3, fTileSize / 2, paint);
|
|
}
|
|
|
|
void drawScene(SkCanvas* canvas, const SkMatrix& matrix, const SkMatrix& localMatrix,
|
|
unsigned tileMode) {
|
|
SkASSERT(tileMode < SK_ARRAY_COUNT(kTileConfigs));
|
|
|
|
SkPaint paint;
|
|
paint.setStyle(SkPaint::kFill_Style);
|
|
paint.setColor(SK_ColorLTGRAY);
|
|
|
|
canvas->save();
|
|
canvas->concat(matrix);
|
|
canvas->drawRect(SkRect::MakeWH(fSceneSize, fSceneSize), paint);
|
|
canvas->drawRect(SkRect::MakeXYWH(fSceneSize * 1.1f, 0, fSceneSize, fSceneSize), paint);
|
|
|
|
paint.setAlphaf(fAlpha);
|
|
|
|
auto pictureShader = fPicture->makeShader(kTileConfigs[tileMode].tmx,
|
|
kTileConfigs[tileMode].tmy,
|
|
SkFilterMode::kNearest,
|
|
fUseLocalMatrixWrapper ? nullptr : &localMatrix,
|
|
nullptr);
|
|
paint.setShader(fUseLocalMatrixWrapper
|
|
? pictureShader->makeWithLocalMatrix(localMatrix)
|
|
: pictureShader);
|
|
canvas->drawRect(SkRect::MakeWH(fSceneSize, fSceneSize), paint);
|
|
|
|
canvas->translate(fSceneSize * 1.1f, 0);
|
|
|
|
auto bitmapShader = fBitmap.makeShader(kTileConfigs[tileMode].tmx,
|
|
kTileConfigs[tileMode].tmy,
|
|
SkSamplingOptions(),
|
|
fUseLocalMatrixWrapper ? nullptr : &localMatrix);
|
|
paint.setShader(fUseLocalMatrixWrapper
|
|
? bitmapShader->makeWithLocalMatrix(localMatrix)
|
|
: bitmapShader);
|
|
canvas->drawRect(SkRect::MakeWH(fSceneSize, fSceneSize), paint);
|
|
|
|
canvas->restore();
|
|
}
|
|
|
|
const SkScalar fTileSize;
|
|
const SkScalar fSceneSize;
|
|
const float fAlpha;
|
|
const bool fUseLocalMatrixWrapper;
|
|
|
|
sk_sp<SkPicture> fPicture;
|
|
SkBitmap fBitmap;
|
|
|
|
using INHERITED = GM;
|
|
};
|
|
|
|
DEF_GM(return new PictureShaderGM(50, 100);)
|
|
DEF_GM(return new PictureShaderGM(50, 100, true);)
|
|
DEF_GM(return new PictureShaderGM(50, 100, false, 0.25f);)
|
|
|
|
DEF_SIMPLE_GM(tiled_picture_shader, canvas, 400, 400) {
|
|
// https://code.google.com/p/skia/issues/detail?id=3398
|
|
SkRect tile = SkRect::MakeWH(100, 100);
|
|
|
|
SkPictureRecorder recorder;
|
|
SkCanvas* c = recorder.beginRecording(tile);
|
|
|
|
SkRect r = tile;
|
|
r.inset(4, 4);
|
|
SkPaint p;
|
|
p.setColor(ToolUtils::color_to_565(0xFF303F9F)); // dark blue
|
|
c->drawRect(r, p);
|
|
p.setColor(ToolUtils::color_to_565(0xFFC5CAE9)); // light blue
|
|
p.setStrokeWidth(10);
|
|
c->drawLine(20, 20, 80, 80, p);
|
|
|
|
sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
|
|
|
|
p.setColor(ToolUtils::color_to_565(0xFF8BC34A)); // green
|
|
canvas->drawPaint(p);
|
|
|
|
canvas->clipRect(SkRect::MakeXYWH(0, 0, 400, 350));
|
|
p.setColor(0xFFB6B6B6); // gray
|
|
canvas->drawPaint(p);
|
|
|
|
p.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
|
|
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);
|
|
}
|
|
}
|