• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "gm/gm.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkColorPriv.h"
12 #include "include/core/SkData.h"
13 #include "include/core/SkFont.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkStream.h"
16 #include "include/core/SkString.h"
17 #include "include/core/SkSurface.h"
18 #include "include/core/SkTypeface.h"
19 #include "include/ports/SkTypeface_fontations.h"
20 #include "modules/skshaper/include/SkShaper.h"
21 #include "src/ports/SkTypeface_FreeType.h"
22 #include "tools/Resources.h"
23 #include "tools/TestFontDataProvider.h"
24 
25 namespace skiagm {
26 
27 namespace {
28 
29 constexpr int kGmWidth = 1000;
30 constexpr int kMargin = 30;
31 constexpr float kFontSize = 24;
32 constexpr float kLangYIncrementScale = 1.9;
33 
34 /** Compare bitmap A and B, in this case originating from text rendering results with FreeType and
35  * Fontations + Skia path rendering, compute individual pixel differences for the rectangles that
36  * must match in size. Produce a highlighted difference bitmap, in which any pixel becomes white for
37  * which a difference was determined. */
comparePixels(const SkPixmap & pixmapA,const SkPixmap & pixmapB,SkBitmap * outPixelDiffBitmap,SkBitmap * outHighlightDiffBitmap)38 void comparePixels(const SkPixmap& pixmapA,
39                    const SkPixmap& pixmapB,
40                    SkBitmap* outPixelDiffBitmap,
41                    SkBitmap* outHighlightDiffBitmap) {
42     if (pixmapA.dimensions() != pixmapB.dimensions()) {
43         return;
44     }
45     if (pixmapA.dimensions() != outPixelDiffBitmap->dimensions()) {
46         return;
47     }
48 
49     SkISize dimensions = pixmapA.dimensions();
50     for (int32_t x = 0; x < dimensions.fWidth; x++) {
51         for (int32_t y = 0; y < dimensions.fHeight; y++) {
52             SkColor c0 = pixmapA.getColor(x, y);
53             SkColor c1 = pixmapB.getColor(x, y);
54             int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
55             int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
56             int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
57 
58             *(outPixelDiffBitmap->getAddr32(x, y)) =
59                     SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
60 
61             if (dr != 0 || dg != 0 || db != 0) {
62                 *(outHighlightDiffBitmap->getAddr32(x, y)) = SK_ColorWHITE;
63             } else {
64                 *(outHighlightDiffBitmap->getAddr32(x, y)) = SK_ColorBLACK;
65             }
66         }
67     }
68 }
69 
70 }  // namespace
71 
72 class FontationsFtCompareGM : public GM {
73 public:
FontationsFtCompareGM(std::string testName,std::string fontNameFilterRegexp,std::string langFilterRegexp)74     FontationsFtCompareGM(std::string testName,
75                           std::string fontNameFilterRegexp,
76                           std::string langFilterRegexp)
77             : fTestDataIterator(fontNameFilterRegexp, langFilterRegexp)
78             , fTestName(testName.c_str()) {
79         this->setBGColor(SK_ColorWHITE);
80     }
81 
82 protected:
getName() const83     SkString getName() const override {
84         return SkStringPrintf("fontations_compare_ft_%s", fTestName.c_str());
85     }
86 
getISize()87     SkISize getISize() override {
88         TestFontDataProvider::TestSet testSet;
89         fTestDataIterator.rewind();
90         fTestDataIterator.next(&testSet);
91 
92         return SkISize::Make(kGmWidth,
93                              testSet.langSamples.size() * kFontSize * kLangYIncrementScale + 100);
94     }
95 
onDraw(SkCanvas * canvas,SkString * errorMsg)96     DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
97         SkPaint paint;
98         paint.setColor(SK_ColorBLACK);
99 
100         fTestDataIterator.rewind();
101         TestFontDataProvider::TestSet testSet;
102 
103         while (fTestDataIterator.next(&testSet)) {
104             sk_sp<SkTypeface> testTypeface = SkTypeface_Make_Fontations(
105                     SkStream::MakeFromFile(testSet.fontFilename.c_str()), SkFontArguments());
106             sk_sp<SkTypeface> ftTypeface = SkTypeface_FreeType::MakeFromStream(
107                     SkStream::MakeFromFile(testSet.fontFilename.c_str()), SkFontArguments());
108 
109             if (!testTypeface || !ftTypeface) {
110                 *errorMsg = "Unable to initialize typeface.";
111                 return DrawResult::kSkip;
112             }
113 
114             auto configureFont = [](SkFont& font) {
115                 font.setSize(kFontSize);
116                 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
117                 font.setSubpixel(true);
118                 font.setHinting(SkFontHinting::kNone);
119             };
120 
121             SkFont font(testTypeface);
122             configureFont(font);
123 
124             SkFont ftFont(ftTypeface);
125             configureFont(ftFont);
126             enum class DrawPhase { Fontations, FreeType, Comparison };
127 
128             SkRect maxBounds = SkRect::MakeEmpty();
129             for (auto phase : {DrawPhase::Fontations, DrawPhase::FreeType, DrawPhase::Comparison}) {
130                 SkScalar yCoord = kFontSize * 1.5f;
131 
132                 for (auto& langEntry : testSet.langSamples) {
133                     auto shapeAndDrawToCanvas = [canvas, paint, langEntry](const SkFont& font,
134                                                                            SkPoint coord) {
135                         std::string testString(langEntry.sampleShort.c_str(),
136                                                langEntry.sampleShort.size());
137                         SkTextBlobBuilderRunHandler textBlobBuilder(testString.c_str(), {0, 0});
138                         std::unique_ptr<SkShaper> shaper = SkShaper::Make();
139                         shaper->shape(testString.c_str(),
140                                       testString.size(),
141                                       font,
142                                       true,
143                                       999999, /* Don't linebreak. */
144                                       &textBlobBuilder);
145                         sk_sp<const SkTextBlob> blob = textBlobBuilder.makeBlob();
146                         canvas->drawTextBlob(blob.get(), coord.x(), coord.y(), paint);
147                         return blob->bounds();
148                     };
149 
150                     auto roundToDevicePixels = [canvas](SkPoint& point) {
151                         SkMatrix ctm = canvas->getLocalToDeviceAs3x3();
152                         SkPoint mapped = ctm.mapPoint(point);
153                         SkPoint mappedRounded =
154                                 SkPoint::Make(roundf(mapped.x()), roundf(mapped.y()));
155                         SkMatrix inverse;
156                         bool inverseExists = ctm.invert(&inverse);
157                         SkASSERT(inverseExists);
158                         if (inverseExists) {
159                             point = inverse.mapPoint(mappedRounded);
160                         }
161                     };
162 
163                     auto fontationsCoord = [yCoord, roundToDevicePixels]() {
164                         SkPoint fontationsCoord = SkPoint::Make(kMargin, yCoord);
165                         roundToDevicePixels(fontationsCoord);
166                         return fontationsCoord;
167                     };
168 
169                     auto freetypeCoord = [yCoord, maxBounds, roundToDevicePixels]() {
170                         SkPoint freetypeCoord = SkPoint::Make(
171                                 2 * kMargin + maxBounds.left() + maxBounds.width(), yCoord);
172                         roundToDevicePixels(freetypeCoord);
173                         return freetypeCoord;
174                     };
175 
176                     switch (phase) {
177                         case DrawPhase::Fontations: {
178                             SkRect boundsFontations = shapeAndDrawToCanvas(font, fontationsCoord());
179                             /* Determine maximum of column width across all language samples. */
180                             boundsFontations.roundOut();
181                             maxBounds.join(boundsFontations);
182                             break;
183                         }
184                         case DrawPhase::FreeType: {
185                             shapeAndDrawToCanvas(ftFont, freetypeCoord());
186                             break;
187                         }
188                         case DrawPhase::Comparison: {
189                             /* Read back pixels from equally sized rectangles from the space in
190                              * SkCanvas where Fontations and FreeType sample texts were drawn,
191                              * compare them using pixel comparisons similar to SkDiff, draw a
192                              * comparison as faint pixel differences, and as an amplified
193                              * visualization in which each differing pixel is drawn as white. */
194                             SkPoint fontationsOrigin = fontationsCoord();
195                             SkPoint freetypeOrigin = freetypeCoord();
196                             SkRect fontationsBBox(maxBounds.makeOffset(fontationsOrigin));
197                             SkRect freetypeBBox(maxBounds.makeOffset(freetypeOrigin));
198 
199                             SkMatrix ctm = canvas->getLocalToDeviceAs3x3();
200                             ctm.mapRect(&fontationsBBox, fontationsBBox);
201                             ctm.mapRect(&freetypeBBox, freetypeBBox);
202 
203                             SkIRect fontationsIBox(fontationsBBox.roundOut());
204                             SkIRect freetypeIBox(freetypeBBox.roundOut());
205 
206                             SkISize pixelDimensions(fontationsIBox.size());
207                             SkImageInfo canvasImageInfo = canvas->imageInfo();
208                             SkImageInfo diffImageInfo =
209                                     SkImageInfo::Make(pixelDimensions,
210                                                       SkColorType::kN32_SkColorType,
211                                                       SkAlphaType::kUnpremul_SkAlphaType);
212 
213                             SkBitmap diffBitmap, highlightDiffBitmap;
214                             diffBitmap.allocPixels(diffImageInfo, 0);
215                             highlightDiffBitmap.allocPixels(diffImageInfo, 0);
216 
217                             // Workaround OveridePaintFilterCanvas limitations
218                             // by getting pixel access through peekPixels()
219                             // instead of readPixels(). Then use same pixmap to
220                             // later write back the comparison results.
221                             SkPixmap canvasPixmap;
222                             if (!canvas->peekPixels(&canvasPixmap)) {
223                                 break;
224                             }
225 
226                             SkPixmap fontationsPixmap, freetypePixmap;
227                             if (!canvasPixmap.extractSubset(&fontationsPixmap, fontationsIBox) ||
228                                 !canvasPixmap.extractSubset(&freetypePixmap, freetypeIBox))
229                             {
230                                 break;
231                             }
232 
233                             comparePixels(fontationsPixmap, freetypePixmap,
234                                           &diffBitmap, &highlightDiffBitmap);
235 
236                             /* Place comparison results as two extra columns, shift up to account
237                                for placement of rectangles vs. SkTextBlobs (baseline shift). */
238                             SkPoint comparisonCoord = ctm.mapPoint(SkPoint::Make(
239                                     3 * kMargin + maxBounds.width() * 2, yCoord + maxBounds.top()));
240                             SkPoint whiteCoord = ctm.mapPoint(SkPoint::Make(
241                                     4 * kMargin + maxBounds.width() * 3, yCoord + maxBounds.top()));
242 
243                             SkSurfaceProps canvasSurfaceProps = canvas->getBaseProps();
244                             sk_sp<SkSurface> writeBackSurface =
245                                     SkSurfaces::WrapPixels(canvasPixmap, &canvasSurfaceProps);
246 
247                             writeBackSurface->writePixels(
248                                     diffBitmap, comparisonCoord.x(), comparisonCoord.y());
249                             writeBackSurface->writePixels(
250                                     highlightDiffBitmap, whiteCoord.x(), whiteCoord.y());
251                             break;
252                         }
253                     }
254 
255                     yCoord += font.getSize() * kLangYIncrementScale;
256                 }
257             }
258         }
259 
260         return DrawResult::kOk;
261     }
262 
263 private:
264     using INHERITED = GM;
265 
266     TestFontDataProvider fTestDataIterator;
267     SkString fTestName;
268     sk_sp<SkTypeface> fReportTypeface;
269     std::unique_ptr<SkFontArguments::VariationPosition::Coordinate[]> fCoordinates;
270 };
271 
272 DEF_GM(return new FontationsFtCompareGM(
273         "NotoSans",
274         "Noto Sans",
275         "en_Latn|es_Latn|pt_Latn|id_Latn|ru_Cyrl|fr_Latn|tr_Latn|vi_Latn|de_"
276         "Latn|it_Latn|pl_Latn|nl_Latn|uk_Cyrl|gl_Latn|ro_Latn|cs_Latn|hu_Latn|"
277         "el_Grek|se_Latn|da_Latn|bg_Latn|sk_Latn|fi_Latn|bs_Latn|ca_Latn|no_"
278         "Latn|sr_Latn|sr_Cyrl|lt_Latn|hr_Latn|sl_Latn|uz_Latn|uz_Cyrl|lv_Latn|"
279         "et_Latn|az_Latn|az_Cyrl|la_Latn|tg_Latn|tg_Cyrl|sw_Latn|mn_Cyrl|kk_"
280         "Latn|kk_Cyrl|sq_Latn|af_Latn|ha_Latn|ky_Cyrl"));
281 
282 DEF_GM(return new FontationsFtCompareGM("NotoSans_Deva",
283                                         "Noto Sans Devanagari",
284                                         "hi_Deva|mr_Deva"));
285 
286 DEF_GM(return new FontationsFtCompareGM("NotoSans_ar_Arab",
287                                         "Noto Sans Arabic",
288                                         "ar_Arab|uz_Arab|kk_Arab|ky_Arab"));
289 
290 DEF_GM(return new FontationsFtCompareGM("NotoSans_Beng", "Noto Sans Bengali", "bn_Beng"));
291 
292 DEF_GM(return new FontationsFtCompareGM("NotoSans_Jpan", "Noto Sans JP", "ja_Jpan"));
293 
294 DEF_GM(return new FontationsFtCompareGM("NotoSans_Thai", "Noto Sans Thai", "th_Thai"));
295 
296 DEF_GM(return new FontationsFtCompareGM("NotoSans_Hans", "Noto Sans SC", "zh_Hans"));
297 
298 DEF_GM(return new FontationsFtCompareGM("NotoSans_Hant", "Noto Sans TC", "zh_Hant"));
299 
300 DEF_GM(return new FontationsFtCompareGM("NotoSans_Kore", "Noto Sans KR", "ko_Kore"));
301 
302 DEF_GM(return new FontationsFtCompareGM("NotoSans_Taml", "Noto Sans Tamil", "ta_Taml"));
303 
304 DEF_GM(return new FontationsFtCompareGM("NotoSans_Newa", "Noto Sans Newa", "new_Newa"));
305 
306 DEF_GM(return new FontationsFtCompareGM("NotoSans_Knda", "Noto Sans Kannada", "kn_Knda"));
307 
308 DEF_GM(return new FontationsFtCompareGM("NotoSans_Tglg", "Noto Sans Tagalog", "fil_Tglg"));
309 
310 DEF_GM(return new FontationsFtCompareGM("NotoSans_Telu", "Noto Sans Telugu", "te_Telu"));
311 
312 DEF_GM(return new FontationsFtCompareGM("NotoSans_Gujr", "Noto Sans Gujarati", "gu_Gujr"));
313 
314 DEF_GM(return new FontationsFtCompareGM("NotoSans_Geor", "Noto Sans Georgian", "ka_Geor"));
315 
316 DEF_GM(return new FontationsFtCompareGM("NotoSans_Mlym", "Noto Sans Malayalam", "ml_Mlym"));
317 
318 DEF_GM(return new FontationsFtCompareGM("NotoSans_Khmr", "Noto Sans Khmer", "km_Khmr"));
319 
320 DEF_GM(return new FontationsFtCompareGM("NotoSans_Sinh", "Noto Sans Sinhala", "si_Sinh"));
321 
322 DEF_GM(return new FontationsFtCompareGM("NotoSans_Mymr", "Noto Sans Myanmar", "my_Mymr"));
323 
324 DEF_GM(return new FontationsFtCompareGM("NotoSans_Java", "Noto Sans Javanese", "jv_Java"));
325 
326 DEF_GM(return new FontationsFtCompareGM("NotoSans_Mong", "Noto Sans Mongolian", "mn_Mong"));
327 
328 DEF_GM(return new FontationsFtCompareGM("NotoSans_Armn", "Noto Sans Armenian", "hy_Armn"));
329 
330 DEF_GM(return new FontationsFtCompareGM("NotoSans_Elba", "Noto Sans Elbasan", "sq_Elba"));
331 
332 DEF_GM(return new FontationsFtCompareGM("NotoSans_Vith", "Noto Sans Vithkuqi", "sq_Vith"));
333 
334 DEF_GM(return new FontationsFtCompareGM("NotoSans_Guru", "Noto Sans Gurmukhi", "pa_Guru"));
335 
336 }  // namespace skiagm
337