handle perspective glyph drawing correctly
For GPU: Handling a blob that moved around in perspective reused cached information incorrectly. Always recalculate the sub run information when drawing perspective. For Bitmap: Make color emoji draw correctly. Testing: Add a GM that draws a single text blob in multiple different perspective orientations. Bug: skia:10473 Bug: skia:8914 Change-Id: I945e3326804ec47bf8cbca0e3cf4a17afc9ba5f0 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/400598 Reviewed-by: Ben Wagner <bungeman@google.com> Commit-Queue: Herb Derby <herb@google.com>
This commit is contained in:
parent
e4b8c3aa73
commit
47c88cc9d6
@ -79,7 +79,6 @@ protected:
|
||||
canvas->drawSimpleText(text, strlen(text), SkTextEncoding::kUTF8, 10, y, font, paint);
|
||||
y += metrics.fDescent + metrics.fLeading;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
@ -138,7 +137,72 @@ protected:
|
||||
|
||||
y += metrics.fDescent + metrics.fLeading;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
using INHERITED = GM;
|
||||
};
|
||||
|
||||
class ScaledEmojiPerspectiveGM : public GM {
|
||||
public:
|
||||
ScaledEmojiPerspectiveGM() {}
|
||||
|
||||
protected:
|
||||
struct EmojiFont {
|
||||
sk_sp<SkTypeface> fTypeface;
|
||||
const char* fText;
|
||||
} fEmojiFont;
|
||||
|
||||
void onOnceBeforeDraw() override {
|
||||
fEmojiFont.fTypeface = ToolUtils::emoji_typeface();
|
||||
fEmojiFont.fText = ToolUtils::emoji_sample_text();
|
||||
}
|
||||
|
||||
SkString onShortName() override {
|
||||
return SkString("scaledemojiperspective");
|
||||
}
|
||||
|
||||
SkISize onISize() override { return SkISize::Make(1200, 1200); }
|
||||
|
||||
void onDraw(SkCanvas* canvas) override {
|
||||
|
||||
canvas->drawColor(SK_ColorGRAY);
|
||||
SkMatrix taper;
|
||||
taper.setPerspY(-0.0025f);
|
||||
|
||||
SkPaint paint;
|
||||
SkFont font;
|
||||
font.setTypeface(fEmojiFont.fTypeface);
|
||||
font.setSize(40);
|
||||
const char* text = "\xF0\x9F\x98\x80"
|
||||
"\xE2\x99\xA2"; // 😀♢;
|
||||
sk_sp<SkTextBlob> blob = make_hpos_test_blob_utf8(text, font);
|
||||
|
||||
// draw text at different point sizes
|
||||
// Testing GPU bitmap path, SDF path with no scaling,
|
||||
// SDF path with scaling, path rendering with scaling
|
||||
SkFontMetrics metrics;
|
||||
font.getMetrics(&metrics);
|
||||
for (auto rotate : {0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0}) {
|
||||
canvas->save();
|
||||
SkMatrix perspective;
|
||||
perspective.postTranslate(-600, -600);
|
||||
perspective.postConcat(taper);
|
||||
perspective.postRotate(rotate);
|
||||
perspective.postTranslate(600, 600);
|
||||
canvas->concat(perspective);
|
||||
SkScalar y = 670;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
|
||||
y += -metrics.fAscent;
|
||||
|
||||
// Draw with an origin.
|
||||
canvas->drawTextBlob(blob, 565, y, paint);
|
||||
|
||||
y += metrics.fDescent + metrics.fLeading;
|
||||
}
|
||||
canvas->restore();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
@ -149,5 +213,5 @@ private:
|
||||
|
||||
DEF_GM(return new ScaledEmojiGM;)
|
||||
DEF_GM(return new ScaledEmojiPosGM;)
|
||||
|
||||
DEF_GM(return new ScaledEmojiPerspectiveGM;)
|
||||
} // namespace skiagm
|
||||
|
@ -59,7 +59,7 @@ public:
|
||||
|
||||
/* If dstOrNull is null, computes a dst by mapping the bitmap's bounds through the matrix. */
|
||||
void drawBitmap(const SkBitmap&, const SkMatrix&, const SkRect* dstOrNull,
|
||||
const SkSamplingOptions&, const SkPaint&) const;
|
||||
const SkSamplingOptions&, const SkPaint&) const override;
|
||||
void drawSprite(const SkBitmap&, int x, int y, const SkPaint&) const;
|
||||
void drawGlyphRunList(const SkGlyphRunList& glyphRunList,
|
||||
const SkPaint& paint,
|
||||
|
@ -118,7 +118,7 @@ void SkGlyphRunListPainter::drawForBitmapDevice(
|
||||
bitmapDevice->paintPaths(
|
||||
&fDrawable, strikeSpec.strikeToSourceRatio(), drawOrigin, pathPaint);
|
||||
}
|
||||
if (!fRejects.source().empty()) {
|
||||
if (!fRejects.source().empty() && !deviceMatrix.hasPerspective()) {
|
||||
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
|
||||
runFont, paint, props, fScalerContextFlags, deviceMatrix);
|
||||
|
||||
@ -127,8 +127,93 @@ void SkGlyphRunListPainter::drawForBitmapDevice(
|
||||
fDrawable.startBitmapDevice(
|
||||
fRejects.source(), drawOrigin, deviceMatrix, strike->roundingSpec());
|
||||
strike->prepareForDrawingMasksCPU(&fDrawable);
|
||||
fRejects.flipRejectsToSource();
|
||||
bitmapDevice->paintMasks(&fDrawable, paint);
|
||||
}
|
||||
if (!fRejects.source().empty()) {
|
||||
SkMatrix runMatrix = deviceMatrix;
|
||||
runMatrix.preTranslate(drawOrigin.x(), drawOrigin.y());
|
||||
std::vector<SkPoint> sourcePositions;
|
||||
|
||||
// Create a strike is source space to calculate scale information.
|
||||
SkStrikeSpec scaleStrikeSpec = SkStrikeSpec::MakeMask(
|
||||
runFont, paint, props, fScalerContextFlags, SkMatrix::I());
|
||||
SkBulkGlyphMetrics metrics{scaleStrikeSpec};
|
||||
|
||||
auto glyphIDs = fRejects.source().get<0>();
|
||||
auto positions = fRejects.source().get<1>();
|
||||
SkSpan<const SkGlyph*> glyphs = metrics.glyphs(glyphIDs);
|
||||
SkScalar maxScale = SK_ScalarMin;
|
||||
|
||||
// Calculate the scale that makes the longest edge 1:1 with its side in the cache.
|
||||
for (auto [glyph, pos] : SkMakeZip(glyphs, positions)) {
|
||||
SkPoint corners[4];
|
||||
SkPoint srcPos = pos + drawOrigin;
|
||||
// Store off the positions in device space to position the glyphs during drawing.
|
||||
sourcePositions.push_back(srcPos);
|
||||
SkRect rect = glyph->rect();
|
||||
rect.makeOffset(srcPos);
|
||||
runMatrix.mapRectToQuad(corners, rect);
|
||||
// left top -> right top
|
||||
SkScalar scale = (corners[1] - corners[0]).length() / rect.width();
|
||||
maxScale = std::max(maxScale, scale);
|
||||
// right top -> right bottom
|
||||
scale = (corners[2] - corners[1]).length() / rect.height();
|
||||
maxScale = std::max(maxScale, scale);
|
||||
// right bottom -> left bottom
|
||||
scale = (corners[3] - corners[2]).length() / rect.width();
|
||||
maxScale = std::max(maxScale, scale);
|
||||
// left bottom -> left top
|
||||
scale = (corners[0] - corners[3]).length() / rect.height();
|
||||
maxScale = std::max(maxScale, scale);
|
||||
}
|
||||
|
||||
if (maxScale * runFont.getSize() > 256) {
|
||||
maxScale = 256.0f / runFont.getSize();
|
||||
}
|
||||
|
||||
SkMatrix cacheScale = SkMatrix::Scale(maxScale, maxScale);
|
||||
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
|
||||
runFont, paint, props, fScalerContextFlags, cacheScale);
|
||||
|
||||
auto strike = strikeSpec.findOrCreateStrike();
|
||||
|
||||
// Figure out all the positions and packed glyphIDs based on the device matrix.
|
||||
fDrawable.startBitmapDevice(
|
||||
fRejects.source(), drawOrigin, deviceMatrix, strike->roundingSpec());
|
||||
|
||||
strike->prepareForDrawingMasksCPU(&fDrawable);
|
||||
auto variants = fDrawable.drawable().get<0>();
|
||||
for (auto [variant, srcPos] : SkMakeZip(variants, sourcePositions)) {
|
||||
SkGlyph* glyph = variant.glyph();
|
||||
SkMask mask = glyph->mask();
|
||||
// TODO: is this needed will A8 and BW just work?
|
||||
if (mask.fFormat != SkMask::kARGB32_Format) {
|
||||
continue;
|
||||
}
|
||||
SkBitmap bm;
|
||||
bm.installPixels(SkImageInfo::MakeN32Premul(mask.fBounds.size()),
|
||||
mask.fImage,
|
||||
mask.fRowBytes);
|
||||
|
||||
// Since the glyph in the cache is scaled by maxScale, its top left vector is too
|
||||
// long. Reduce it to find proper positions on the device.
|
||||
SkPoint realPos = srcPos
|
||||
+ SkPoint::Make(mask.fBounds.left(), mask.fBounds.top()) * (1.0f/maxScale);
|
||||
|
||||
// Calculate the preConcat matrix for drawBitmap to get the rectangle from the
|
||||
// glyph cache (which is multiplied by maxScale) to land in the right place.
|
||||
SkMatrix translate = SkMatrix::Translate(realPos);
|
||||
translate.preScale(1.0f/maxScale, 1.0f/maxScale);
|
||||
|
||||
// Draw the bitmap using the rect from the scaled cache, and not the source
|
||||
// rectangle for the glyph.
|
||||
bitmapDevice->drawBitmap(
|
||||
bm, translate, nullptr, SkSamplingOptions{SkFilterMode::kLinear},
|
||||
paint);
|
||||
}
|
||||
fRejects.flipRejectsToSource();
|
||||
}
|
||||
|
||||
// TODO: have the mask stage above reject the glyphs that are too big, and handle the
|
||||
// rejects in a more sophisticated stage.
|
||||
|
@ -74,6 +74,8 @@ public:
|
||||
const SkPaint& paint) const = 0;
|
||||
|
||||
virtual void paintMasks(SkDrawableGlyphBuffer* drawables, const SkPaint& paint) const = 0;
|
||||
virtual void drawBitmap(const SkBitmap&, const SkMatrix&, const SkRect* dstOrNull,
|
||||
const SkSamplingOptions&, const SkPaint&) const = 0;
|
||||
};
|
||||
|
||||
void drawForBitmapDevice(
|
||||
|
@ -61,6 +61,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void drawBitmap(const SkBitmap&, const SkMatrix&, const SkRect* dstOrNull,
|
||||
const SkSamplingOptions&, const SkPaint&) const override {}
|
||||
|
||||
void onDrawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint) override {
|
||||
SkASSERT(!glyphRunList.hasRSXForm());
|
||||
fPainter.drawForBitmapDevice(glyphRunList, paint, fOverdrawCanvas->getTotalMatrix(), this);
|
||||
|
@ -395,7 +395,6 @@ void GrSurfaceDrawContext::drawGlyphRunListWithCache(const GrClip* clip,
|
||||
|
||||
if (blob == nullptr || !blob->canReuse(paint, drawMatrix)) {
|
||||
if (blob != nullptr) {
|
||||
SkASSERT(!drawMatrix.hasPerspective());
|
||||
// We have to remake the blob because changes may invalidate our masks.
|
||||
// TODO we could probably get away with reuse most of the time if the pointer is unique,
|
||||
// but we'd have to clear the SubRun information
|
||||
|
@ -1528,8 +1528,11 @@ bool GrTextBlob::hasPerspective() const { return fInitialMatrix.hasPerspective()
|
||||
|
||||
bool GrTextBlob::canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const {
|
||||
// A singular matrix will create a GrTextBlob with no SubRuns, but unknown glyphs can
|
||||
// also cause empty runs. If there are no subRuns, then regenerate.
|
||||
if ((fSubRunList.isEmpty() || fSomeGlyphsExcluded) && fInitialMatrix != drawMatrix) {
|
||||
// also cause empty runs. If there are no subRuns or some glyphs were excluded or perspective,
|
||||
// then regenerate when the matrices don't match.
|
||||
if ((fSubRunList.isEmpty() || fSomeGlyphsExcluded || hasPerspective()) &&
|
||||
fInitialMatrix != drawMatrix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user