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