Android Q 字体毛刺,锯齿

这篇博客介绍了修复Skia图形库中GPU文本渲染时因1/2像素位置导致的锯齿字符问题,以及平滑滚动测试的实现。通过对文本blob的更新和调整,确保了在不同驱动上文本的高质量渲染,同时测试了慢速滚动文本时与新构造的文本blob的一致性。
摘要由CSDN通过智能技术生成

https://skia-review.googlesource.com/c/skia/+/241038

 

From 2ada5eca6454cdeae8d7dbb05a12844e9268bf86 Mon Sep 17 00:00:00 2001

From: Herb Derby <herb@google.com>

Date: Thu, 12 Sep 2019 10:58:26 -0400

Subject: [PATCH] A fix and unit test for jagged characters

 

Test 1 - half pixel rendering

Adjust the positions of the glyph quads so they never land on a 1/2

pixel position. Having a 1/2 pixel position causes some drivers to

point sample the glyphs poorly because the round differently for

different pixels.

 

Test 2 - smooth scrolling

Move a textblob very slowly in y to make sure in matches a freshly

constructed textblob.

 

Fix:

Regen the GrTextBlob if the translation is not by whole pixels.

 

Bug:

https://buganizer.corp.google.com/issues/139472690

 

Change-Id: I161bae18693c93c5492c798b288939d0f7e33730

Reviewed-on: https://skia-review.googlesource.com/c/skia/+/241038

Commit-Queue: Ben Wagner <bungeman@google.com>

Reviewed-by: Jim Van Verth <jvanverth@google.com>

Reviewed-by: Ben Wagner <bungeman@google.com>

---

 

diff --git a/src/gpu/text/GrTextBlob.cpp b/src/gpu/text/GrTextBlob.cpp

index 0ab8f6d..0c52feb 100644

--- a/src/gpu/text/GrTextBlob.cpp

+++ b/src/gpu/text/GrTextBlob.cpp

@@ -127,24 +127,25 @@

return true;

}

- // If the text blob only has full pixel glyphs, then fractional part of the position does

- // not affect the SkGlyphs used.

- if (anyRunHasSubpixelPosition) {

- // We can update the positions in the text blob without regenerating the whole

- // blob, but only for integer translations.

- // This cool bit of math will determine the necessary translation to apply to the

- // already generated vertex coordinates to move them to the correct position.

- SkScalar transX = viewMatrix.getTranslateX() +

- viewMatrix.getScaleX() * (x - fInitialX) +

- viewMatrix.getSkewX() * (y - fInitialY) -

- fInitialViewMatrix.getTranslateX();

- SkScalar transY = viewMatrix.getTranslateY() +

- viewMatrix.getSkewY() * (x - fInitialX) +

- viewMatrix.getScaleY() * (y - fInitialY) -

- fInitialViewMatrix.getTranslateY();

- if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY)) {

- return true;

- }

+ // TODO(herb): this is not needed for full pixel glyph choice, but is needed to adjust

+ // the quads properly. Devise a system that regenerates the quads from original data

+ // using the transform to allow this to be used in general.

+

+ // We can update the positions in the text blob without regenerating the whole

+ // blob, but only for integer translations.

+ // This cool bit of math will determine the necessary translation to apply to the

+ // already generated vertex coordinates to move them to the correct position.

+ // Figure out the translation in view space given a translation in source space.

+ SkScalar transX = viewMatrix.getTranslateX() +

+ viewMatrix.getScaleX() * (x - fInitialX) +

+ viewMatrix.getSkewX() * (y - fInitialY) -

+ fInitialViewMatrix.getTranslateX();

+ SkScalar transY = viewMatrix.getTranslateY() +

+ viewMatrix.getSkewY() * (x - fInitialX) +

+ viewMatrix.getScaleY() * (y - fInitialY) -

+ fInitialViewMatrix.getTranslateY();

+ if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY)) {

+ return true;

}

} else if (this->hasDistanceField()) {

// A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different

diff --git a/tests/TextBlobCacheTest.cpp b/tests/TextBlobCacheTest.cpp

index 5523a9d..6cae4c2 100644

--- a/tests/TextBlobCacheTest.cpp

+++ b/tests/TextBlobCacheTest.cpp

@@ -7,6 +7,8 @@

#include "sk_tool_utils.h"

+#include <string>

+

#include "SkCanvas.h"

#include "SkFontMgr.h"

#include "SkGlyphRun.h"

@@ -170,3 +172,155 @@

DEF_GPUTEST_FOR_NULLGL_CONTEXT(TextBlobStressAbnormal, reporter, ctxInfo) {

text_blob_cache_inner(reporter, ctxInfo.grContext(), 256, 256, 10, false, true);

}

+

+static const int kScreenDim = 160;

+

+static SkBitmap draw_blob(SkTextBlob* blob, SkSurface* surface, SkPoint offset) {

+

+ SkPaint paint;

+

+ SkCanvas* canvas = surface->getCanvas();

+ canvas->save();

+ canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);

+ canvas->translate(offset.fX, offset.fY);

+ canvas->drawTextBlob(blob, 0, 0, paint);

+ SkBitmap bitmap;

+ bitmap.allocN32Pixels(kScreenDim, kScreenDim);

+ surface->readPixels(bitmap, 0, 0);

+ canvas->restore();

+ return bitmap;

+}

+

+static bool compare_bitmaps(const SkBitmap& expected, const SkBitmap& actual) {

+ SkASSERT(expected.width() == actual.width());

+ SkASSERT(expected.height() == actual.height());

+ for (int i = 0; i < expected.width(); ++i) {

+ for (int j = 0; j < expected.height(); ++j) {

+ SkColor expectedColor = expected.getColor(i, j);

+ SkColor actualColor = actual.getColor(i, j);

+ if (expectedColor != actualColor) {

+ return false;

+ }

+ }

+ }

+ return true;

+}

+

+static sk_sp<SkTextBlob> make_blob() {

+ auto tf = SkTypeface::MakeFromName("Roboto2-Regular", SkFontStyle());

+ SkFont font;

+ font.setTypeface(tf);

+ font.setSubpixel(false);

+ font.setEdging(SkFont::Edging::kAlias);

+ font.setSize(24);

+

+ static char text[] = "HekpqB";

+ static const int maxGlyphLen = sizeof(text) * 4;

+ SkGlyphID glyphs[maxGlyphLen];

+ int glyphCount =

+ tf->charsToGlyphs(text, SkTypeface::Encoding::kUTF8_Encoding, glyphs, sizeof(text)-1);

+

+ SkTextBlobBuilder builder;

+ const auto& runBuffer = builder.allocRun(font, glyphCount, 0, 0);

+ for (int i = 0; i < glyphCount; i++) {

+ runBuffer.glyphs[i] = glyphs[i];

+ }

+ return builder.make();

+}

+

+static const bool kDumpPngs = false;

+// dump pngs needs a "good" and a "bad" directory to put the results in. This allows the use of the

+// skdiff tool to visualize the differences.

+

+void write_png(const std::string& filename, const SkBitmap& bitmap) {

+ auto data = SkEncodeBitmap(bitmap, SkEncodedImageFormat::kPNG, 0);

+ SkFILEWStream w{filename.c_str()};

+ w.write(data->data(), data->size());

+ w.fsync();

+}

+

+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobJaggedGlyph, reporter, ctxInfo) {

+ auto grContext = ctxInfo.grContext();

+ const SkImageInfo info =

+ SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);

+ auto surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info);

+

+ auto blob = make_blob();

+

+ for (int y = 40; y < kScreenDim - 40; y++) {

+ SkBitmap base = draw_blob(blob.get(), surface.get(), {40, y + 0.0f});

+ SkBitmap half = draw_blob(blob.get(), surface.get(), {40, y + 0.5f});

+ SkBitmap unit = draw_blob(blob.get(), surface.get(), {40, y + 1.0f});

+ bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);

+ REPORTER_ASSERT(reporter, isOk);

+ if (!isOk) {

+ if (kDumpPngs) {

+ {

+ std::string filename = "bad/half-y" + std::to_string(y) + ".png";

+ write_png(filename, half);

+ }

+ {

+ std::string filename = "good/half-y" + std::to_string(y) + ".png";

+ write_png(filename, base);

+ }

+ }

+ break;

+ }

+ }

+

+ // This is not as useful as Y because advances are not integer, which causes different letter

+ // spacing.

+#if 0

+ blob = make_blob();

+ for (int x = 40; x < kScreenDim - 40; x++) {

+ SkBitmap base = draw_blob(blob.get(), surface.get(), {x + 0.0f, 40});

+ SkBitmap half = draw_blob(blob.get(), surface.get(), {x + 0.5f, 40});

+ SkBitmap unit = draw_blob(blob.get(), surface.get(), {x + 1.0f, 40});

+ bool isOk = compare_bitmaps(base, half) || compare_bitmaps(unit, half);

+ REPORTER_ASSERT(reporter, isOk);

+ if (!isOk) {

+ if (kDumpPngs) {

+ {

+ std::string filename = "bad/half-x" + std::to_string(x) + ".png";

+ write_png(filename, half);

+ }

+ {

+ std::string filename = "good/half-x" + std::to_string(x) + ".png";

+ write_png(filename, base);

+ }

+ }

+ break;

+ }

+ }

+#endif

+}

+

+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextBlobSmoothScroll, reporter, ctxInfo) {

+ auto grContext = ctxInfo.grContext();

+ const SkImageInfo info =

+ SkImageInfo::Make(kScreenDim, kScreenDim, kN32_SkColorType, kPremul_SkAlphaType);

+ auto surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info);

+

+ auto movingBlob = make_blob();

+

+ for (SkScalar y = 40; y < 50; y += 1.0/8.0) {

+ auto expectedBlob = make_blob();

+ auto expectedBitMap = draw_blob(expectedBlob.get(), surface.get(), {40, y});

+ auto movingBitmap = draw_blob(movingBlob.get(), surface.get(), {40, y});

+ bool isOk = compare_bitmaps(expectedBitMap, movingBitmap);

+ REPORTER_ASSERT(reporter, isOk);

+ if (!isOk) {

+ if (kDumpPngs) {

+ {

+ std::string filename = "bad/scroll-y" + std::to_string(y) + ".png";

+ write_png(filename, movingBitmap);

+ }

+ {

+ std::string filename = "good/scroll-y" + std::to_string(y) + ".png";

+ write_png(filename, expectedBitMap);

+ }

+ }

+ break;

+ }

+ }

+}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值