blob: ae2aa9e26b9f20f126938ed07437b5f54e9b48e6 [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* 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/SkColorPriv.h"
#include "include/core/SkData.h"
#include "include/core/SkFont.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkTypeface.h"
#include "include/ports/SkTypeface_fontations.h"
#include "modules/skshaper/include/SkShaper.h"
#include "src/ports/SkTypeface_FreeType.h"
#include "tools/Resources.h"
#include "tools/TestFontDataProvider.h"
namespace skiagm {
namespace {
constexpr int kGmWidth = 1000;
constexpr int kMargin = 15;
constexpr float kFontSize = 24;
constexpr float kLangYIncrementScale = 1.9;
/** Compare bitmap A and B, in this case originating from text rendering results with FreeType and
* Fontations + Skia path rendering, compute individual pixel differences for the rectangles that
* must match in size. Produce a highlighted difference bitmap, in which any pixel becomes white for
* which a difference was determined. */
void comparePixels(const SkBitmap& bitmapA,
const SkBitmap& bitmapB,
SkBitmap* outPixelDiffBitmap,
SkBitmap* outHighlightDiffBitmap) {
SkASSERT(bitmapA.colorType() == bitmapB.colorType() &&
bitmapA.colorType() == outPixelDiffBitmap->colorType() &&
bitmapA.dimensions() == bitmapB.dimensions() &&
bitmapA.dimensions() == outPixelDiffBitmap->dimensions());
SkASSERT(bitmapA.bytesPerPixel() == 4);
SkISize dimensions = bitmapA.dimensions();
for (int32_t x = 0; x < dimensions.fWidth; x++) {
for (int32_t y = 0; y < dimensions.fHeight; y++) {
SkPMColor c0 = *bitmapA.getAddr32(x, y);
SkPMColor c1 = *bitmapB.getAddr32(x, y);
int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
*(outPixelDiffBitmap->getAddr32(x, y)) =
SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
if (dr != 0 || dg != 0 || db != 0) {
*(outHighlightDiffBitmap->getAddr32(x, y)) = SK_ColorWHITE;
} else {
*(outHighlightDiffBitmap->getAddr32(x, y)) = SK_ColorBLACK;
}
}
}
}
} // namespace
class FontationsFtCompareGM : public GM {
public:
FontationsFtCompareGM(std::string testName,
std::string fontNameFilterRegexp,
std::string langFilterRegexp)
: fTestDataIterator(fontNameFilterRegexp, langFilterRegexp)
, fTestName(testName.c_str()) {
this->setBGColor(SK_ColorWHITE);
}
protected:
SkString getName() const override {
return SkStringPrintf("fontations_compare_ft_%s", fTestName.c_str());
}
SkISize getISize() override {
TestFontDataProvider::TestSet testSet;
fTestDataIterator.rewind();
fTestDataIterator.next(&testSet);
return SkISize::Make(kGmWidth,
testSet.langSamples.size() * kFontSize * kLangYIncrementScale + 100);
}
DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
SkPaint paint;
paint.setColor(SK_ColorBLACK);
fTestDataIterator.rewind();
TestFontDataProvider::TestSet testSet;
while (fTestDataIterator.next(&testSet)) {
sk_sp<SkTypeface> testTypeface = SkTypeface_Make_Fontations(
SkStream::MakeFromFile(testSet.fontFilename.c_str()), SkFontArguments());
sk_sp<SkTypeface> ftTypeface = SkTypeface_FreeType::MakeFromStream(
SkStream::MakeFromFile(testSet.fontFilename.c_str()), SkFontArguments());
if (!testTypeface || !ftTypeface) {
*errorMsg = "Unable to initialize typeface.";
return DrawResult::kSkip;
}
auto configureFont = [](SkFont& font) {
font.setSize(kFontSize);
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
font.setSubpixel(true);
font.setHinting(SkFontHinting::kNone);
};
SkFont font(testTypeface);
configureFont(font);
SkFont ftFont(ftTypeface);
configureFont(ftFont);
enum class DrawPhase { Fontations, FreeType, Comparison };
SkRect maxBounds = SkRect::MakeEmpty();
for (auto phase : {DrawPhase::Fontations, DrawPhase::FreeType, DrawPhase::Comparison}) {
SkScalar yCoord = kFontSize * 1.5f;
for (auto& langEntry : testSet.langSamples) {
auto shapeAndDrawToCanvas = [canvas, paint, langEntry](const SkFont& font,
SkPoint coord) {
std::string testString(langEntry.sampleShort.c_str(),
langEntry.sampleShort.size());
SkTextBlobBuilderRunHandler textBlobBuilder(testString.c_str(), {0, 0});
std::unique_ptr<SkShaper> shaper = SkShaper::Make();
shaper->shape(testString.c_str(),
testString.size(),
font,
true,
999999, /* Don't linebreak. */
&textBlobBuilder);
sk_sp<const SkTextBlob> blob = textBlobBuilder.makeBlob();
canvas->drawTextBlob(blob.get(), coord.x(), coord.y(), paint);
return blob->bounds();
};
auto roundToDevicePixels = [canvas](SkPoint& point) {
SkMatrix ctm = canvas->getLocalToDeviceAs3x3();
SkPoint mapped = ctm.mapPoint(point);
SkPoint mappedRounded =
SkPoint::Make(roundf(mapped.x()), roundf(mapped.y()));
SkMatrix inverse;
bool inverseExists = ctm.invert(&inverse);
SkASSERT(inverseExists);
if (inverseExists) {
point = inverse.mapPoint(mappedRounded);
}
};
auto fontationsCoord = [yCoord, roundToDevicePixels]() {
SkPoint fontationsCoord = SkPoint::Make(kMargin, yCoord);
roundToDevicePixels(fontationsCoord);
return fontationsCoord;
};
auto freetypeCoord = [yCoord, maxBounds, roundToDevicePixels]() {
SkPoint freetypeCoord = SkPoint::Make(
2 * kMargin + maxBounds.left() + maxBounds.width(), yCoord);
roundToDevicePixels(freetypeCoord);
return freetypeCoord;
};
switch (phase) {
case DrawPhase::Fontations: {
SkRect boundsFontations = shapeAndDrawToCanvas(font, fontationsCoord());
/* Determine maximum of column width across all language samples. */
boundsFontations.roundOut();
maxBounds.join(boundsFontations);
break;
}
case DrawPhase::FreeType: {
shapeAndDrawToCanvas(ftFont, freetypeCoord());
break;
}
case DrawPhase::Comparison: {
/* Read back pixels from equally sized rectangles from the space in
* SkCanvas where Fontations and FreeType sample texts were drawn,
* compare them using pixel comparisons similar to SkDiff, draw a
* comparison as faint pixel differences, and as an amplified
* visualization in which each differing pixel is drawn as white. */
SkPoint copyBoxFontationsCoord = fontationsCoord();
SkPoint copyBoxFreetypeCoord = freetypeCoord();
SkRect copyBoxFontations(maxBounds);
copyBoxFontations.roundOut(&copyBoxFontations);
SkRect copyBoxFreetype(copyBoxFontations);
copyBoxFontations.offset(copyBoxFontationsCoord.x(),
copyBoxFontationsCoord.y());
copyBoxFreetype.offset(copyBoxFreetypeCoord.x(),
copyBoxFreetypeCoord.y());
SkMatrix ctm = canvas->getLocalToDeviceAs3x3();
ctm.mapRect(&copyBoxFontations, copyBoxFontations);
ctm.mapRect(&copyBoxFreetype, copyBoxFreetype);
SkISize pixelDimensions(copyBoxFontations.roundOut().size());
SkImageInfo canvasImageInfo = canvas->imageInfo();
SkImageInfo copyImageInfo =
SkImageInfo::Make(pixelDimensions,
canvasImageInfo.colorType(),
canvasImageInfo.alphaType());
SkBitmap fontationsBitmap, freetypeBitmap, diffBitmap,
highlightDiffBitmap;
fontationsBitmap.allocPixels(copyImageInfo, 0);
freetypeBitmap.allocPixels(copyImageInfo, 0);
diffBitmap.allocPixels(copyImageInfo, 0);
highlightDiffBitmap.allocPixels(copyImageInfo, 0);
// comparePixels only handles 4 bpp
if (fontationsBitmap.bytesPerPixel() != 4 ||
freetypeBitmap.bytesPerPixel() != 4)
{
break;
}
const bool fontationsRead = canvas->readPixels(
fontationsBitmap, copyBoxFontations.x(), copyBoxFontations.y());
const bool freetypeRead = canvas->readPixels(
freetypeBitmap, copyBoxFreetype.x(), copyBoxFreetype.y());
// readPixels may fail
if (!fontationsRead || !freetypeRead) {
break;
}
comparePixels(fontationsBitmap,
freetypeBitmap,
&diffBitmap,
&highlightDiffBitmap);
/* Place comparison results as two extra columns, shift up to account
for placement of rectangles vs. SkTextBlobs (baseline shift). */
SkPoint comparisonCoord = ctm.mapPoint(SkPoint::Make(
3 * kMargin + maxBounds.width() * 2, yCoord + maxBounds.top()));
SkPoint whiteCoord = ctm.mapPoint(SkPoint::Make(
4 * kMargin + maxBounds.width() * 3, yCoord + maxBounds.top()));
canvas->writePixels(
diffBitmap, comparisonCoord.x(), comparisonCoord.y());
canvas->writePixels(
highlightDiffBitmap, whiteCoord.x(), whiteCoord.y());
break;
}
}
yCoord += font.getSize() * kLangYIncrementScale;
}
}
}
return DrawResult::kOk;
}
private:
using INHERITED = GM;
TestFontDataProvider fTestDataIterator;
SkString fTestName;
sk_sp<SkTypeface> fReportTypeface;
std::unique_ptr<SkFontArguments::VariationPosition::Coordinate[]> fCoordinates;
};
DEF_GM(return new FontationsFtCompareGM(
"NotoSans",
"Noto Sans",
"en_Latn|es_Latn|pt_Latn|id_Latn|ru_Cyrl|fr_Latn|tr_Latn|vi_Latn|de_"
"Latn|it_Latn|pl_Latn|nl_Latn|uk_Cyrl|gl_Latn|ro_Latn|cs_Latn|hu_Latn|"
"el_Grek|se_Latn|da_Latn|bg_Latn|sk_Latn|fi_Latn|bs_Latn|ca_Latn|no_"
"Latn|sr_Latn|sr_Cyrl|lt_Latn|hr_Latn|sl_Latn|uz_Latn|uz_Cyrl|lv_Latn|"
"et_Latn|az_Latn|az_Cyrl|la_Latn|tg_Latn|tg_Cyrl|sw_Latn|mn_Cyrl|kk_"
"Latn|kk_Cyrl|sq_Latn|af_Latn|ha_Latn|ky_Cyrl"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Deva",
"Noto Sans Devanagari",
"hi_Deva|mr_Deva"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_ar_Arab",
"Noto Sans Arabic",
"ar_Arab|uz_Arab|kk_Arab|ky_Arab"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Beng", "Noto Sans Bengali", "bn_Beng"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Jpan", "Noto Sans JP", "ja_Jpan"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Thai", "Noto Sans Thai", "th_Thai"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Hans", "Noto Sans SC", "zh_Hans"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Hant", "Noto Sans TC", "zh_Hant"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Kore", "Noto Sans KR", "ko_Kore"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Taml", "Noto Sans Tamil", "ta_Taml"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Newa", "Noto Sans Newa", "new_Newa"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Knda", "Noto Sans Kannada", "kn_Knda"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Tglg", "Noto Sans Tagalog", "fil_Tglg"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Telu", "Noto Sans Telugu", "te_Telu"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Gujr", "Noto Sans Gujarati", "gu_Gujr"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Geor", "Noto Sans Georgian", "ka_Geor"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Mlym", "Noto Sans Malayalam", "ml_Mlym"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Khmr", "Noto Sans Khmer", "km_Khmr"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Sinh", "Noto Sans Sinhala", "si_Sinh"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Mymr", "Noto Sans Myanmar", "my_Mymr"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Java", "Noto Sans Javanese", "jv_Java"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Mong", "Noto Sans Mongolian", "mn_Mong"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Armn", "Noto Sans Armenian", "hy_Armn"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Elba", "Noto Sans Elbasan", "sq_Elba"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Vith", "Noto Sans Vithkuqi", "sq_Vith"));
DEF_GM(return new FontationsFtCompareGM("NotoSans_Guru", "Noto Sans Gurmukhi", "pa_Guru"));
} // namespace skiagm