skia2/gm/drawatlas.cpp
Ben Wagner fcfd0af9fd Notify when SkFontMetrics bounds are bogus.
OpenType and many other font formats have the concept of pre-computed
metrics for the union of all glyph bounding boxes. This allows for fast
though course quick rejecting of bounds, since the glyphs themselves may
potentially be quite a bit larger than the EM. With the introduction of
variable fonts OpenType does not vary these bounds, so the bounds are
only valid for the default non-varied font.

As a result the fTop, fBottom, fXMax, and fXMin reported in
SkFontMetrics may be bogus. Since simply always setting them to empty
zeros may be disruptive, provide a way forward for new users to check if
the bounds are valid.

This exposed an issue where SkTextBlobBuilder::TightRunBounds does not
handle SkTextBlob::kRSXform_Positioning, so add a test for that.

Change-Id: I872729e0f16e2a196229f9902addf4b07b461590
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/301455
Reviewed-by: Herb Derby <herb@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
2020-07-09 19:26:40 +00:00

358 lines
12 KiB
C++

/*
* Copyright 2015 Google Inc.
*
* 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/SkBlendMode.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkFilterQuality.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathMeasure.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRSXform.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypeface.h"
#include "include/core/SkTypes.h"
#include "include/core/SkVertices.h"
#include "include/effects/SkGradientShader.h"
#include "include/private/SkTemplates.h"
#include "src/core/SkAutoMalloc.h"
#include "src/core/SkFontPriv.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include <initializer_list>
class DrawAtlasGM : public skiagm::GM {
static sk_sp<SkImage> MakeAtlas(SkCanvas* caller, const SkRect& target) {
SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
auto surface(ToolUtils::makeSurface(caller, info));
SkCanvas* canvas = surface->getCanvas();
// draw red everywhere, but we don't expect to see it in the draw, testing the notion
// that drawAtlas draws a subset-region of the atlas.
canvas->clear(SK_ColorRED);
SkPaint paint;
paint.setBlendMode(SkBlendMode::kClear);
SkRect r(target);
r.inset(-1, -1);
// zero out a place (with a 1-pixel border) to land our drawing.
canvas->drawRect(r, paint);
paint.setBlendMode(SkBlendMode::kSrcOver);
paint.setColor(SK_ColorBLUE);
paint.setAntiAlias(true);
canvas->drawOval(target, paint);
return surface->makeImageSnapshot();
}
public:
DrawAtlasGM() {}
protected:
SkString onShortName() override {
return SkString("draw-atlas");
}
SkISize onISize() override {
return SkISize::Make(640, 480);
}
void onDraw(SkCanvas* canvas) override {
const SkRect target = { 50, 50, 80, 90 };
auto atlas = MakeAtlas(canvas, target);
const struct {
SkScalar fScale;
SkScalar fDegrees;
SkScalar fTx;
SkScalar fTy;
void apply(SkRSXform* xform) const {
const SkScalar rad = SkDegreesToRadians(fDegrees);
xform->fSCos = fScale * SkScalarCos(rad);
xform->fSSin = fScale * SkScalarSin(rad);
xform->fTx = fTx;
xform->fTy = fTy;
}
} rec[] = {
{ 1, 0, 10, 10 }, // just translate
{ 2, 0, 110, 10 }, // scale + translate
{ 1, 30, 210, 10 }, // rotate + translate
{ 2, -30, 310, 30 }, // scale + rotate + translate
};
const int N = SK_ARRAY_COUNT(rec);
SkRSXform xform[N];
SkRect tex[N];
SkColor colors[N];
for (int i = 0; i < N; ++i) {
rec[i].apply(&xform[i]);
tex[i] = target;
colors[i] = 0x80FF0000 + (i * 40 * 256);
}
SkPaint paint;
paint.setFilterQuality(kLow_SkFilterQuality);
paint.setAntiAlias(true);
canvas->drawAtlas(atlas.get(), xform, tex, N, nullptr, &paint);
canvas->translate(0, 100);
canvas->drawAtlas(atlas.get(), xform, tex, colors, N, SkBlendMode::kSrcIn, nullptr, &paint);
}
private:
typedef GM INHERITED;
};
DEF_GM( return new DrawAtlasGM; )
///////////////////////////////////////////////////////////////////////////////////////////////////
static void draw_text_on_path(SkCanvas* canvas, const void* text, size_t length,
const SkPoint xy[], const SkPath& path, const SkFont& font, const SkPaint& paint,
float baseline_offset) {
SkPathMeasure meas(path, false);
int count = font.countText(text, length, SkTextEncoding::kUTF8);
size_t size = count * (sizeof(SkRSXform) + sizeof(SkScalar));
SkAutoSMalloc<512> storage(size);
SkRSXform* xform = (SkRSXform*)storage.get();
SkScalar* widths = (SkScalar*)(xform + count);
// Compute a conservative bounds so we can cull the draw
const SkRect fontb = SkFontPriv::GetFontBounds(font);
const SkScalar max = std::max(std::max(SkScalarAbs(fontb.fLeft), SkScalarAbs(fontb.fRight)),
std::max(SkScalarAbs(fontb.fTop), SkScalarAbs(fontb.fBottom)));
const SkRect bounds = path.getBounds().makeOutset(max, max);
SkAutoTArray<SkGlyphID> glyphs(count);
font.textToGlyphs(text, length, SkTextEncoding::kUTF8, glyphs.get(), count);
font.getWidths(glyphs.get(), count, widths);
for (int i = 0; i < count; ++i) {
// we want to position each character on the center of its advance
const SkScalar offset = SkScalarHalf(widths[i]);
SkPoint pos;
SkVector tan;
if (!meas.getPosTan(xy[i].x() + offset, &pos, &tan)) {
pos = xy[i];
tan.set(1, 0);
}
pos += SkVector::Make(-tan.fY, tan.fX) * baseline_offset;
xform[i].fSCos = tan.x();
xform[i].fSSin = tan.y();
xform[i].fTx = pos.x() - tan.y() * xy[i].y() - tan.x() * offset;
xform[i].fTy = pos.y() + tan.x() * xy[i].y() - tan.y() * offset;
}
canvas->drawTextBlob(SkTextBlob::MakeFromRSXform(glyphs.get(), count * sizeof(SkGlyphID),
&xform[0], font, SkTextEncoding::kGlyphID),
0, 0, paint);
if (true) {
SkPaint p;
p.setStyle(SkPaint::kStroke_Style);
canvas->drawRect(bounds, p);
}
}
static sk_sp<SkShader> make_shader() {
SkPoint pts[2] = {{0, 0}, {220, 0}};
SkColor colors[2] = {SK_ColorRED, SK_ColorBLUE};
return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kMirror);
}
static void drawTextPath(SkCanvas* canvas, bool doStroke) {
const char text0[] = "ABCDFGHJKLMNOPQRSTUVWXYZ";
const int N = sizeof(text0) - 1;
SkPoint pos[N];
SkFont font;
font.setSize(100);
SkPaint paint;
paint.setShader(make_shader());
paint.setAntiAlias(true);
if (doStroke) {
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(2.25f);
paint.setStrokeJoin(SkPaint::kRound_Join);
}
SkScalar x = 0;
for (int i = 0; i < N; ++i) {
pos[i].set(x, 0);
x += font.measureText(&text0[i], 1, SkTextEncoding::kUTF8, nullptr, &paint);
}
SkPath path;
const float baseline_offset = -5;
const SkPathDirection dirs[] = {
SkPathDirection::kCW, SkPathDirection::kCCW,
};
for (auto d : dirs) {
path.reset();
path.addOval(SkRect::MakeXYWH(160, 160, 540, 540), d);
draw_text_on_path(canvas, text0, N, pos, path, font, paint, baseline_offset);
}
paint.reset();
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawPath(path, paint);
}
DEF_SIMPLE_GM(drawTextRSXform, canvas, 430, 860) {
canvas->scale(0.5f, 0.5f);
const bool doStroke[] = { false, true };
for (auto st : doStroke) {
drawTextPath(canvas, st);
canvas->translate(0, 860);
}
}
// Exercise xform blob and its bounds
DEF_SIMPLE_GM(blob_rsxform, canvas, 500, 100) {
SkFont font;
font.setTypeface(ToolUtils::create_portable_typeface());
font.setSize(50);
const char text[] = "CrazyXform";
constexpr size_t len = sizeof(text) - 1;
SkRSXform xforms[len];
SkScalar scale = 1;
SkScalar x = 0, y = 0;
for (size_t i = 0; i < len; ++i) {
scale = SkScalarSin(i * SK_ScalarPI / (len-1)) * 0.75f + 0.5f;
xforms[i] = SkRSXform::Make(scale, 0, x, y);
x += 50 * scale;
}
auto blob = SkTextBlob::MakeFromRSXform(text, len, xforms, font);
SkPoint offset = { 20, 70 };
SkPaint paint;
paint.setColor(0xFFCCCCCC);
canvas->drawRect(blob->bounds().makeOffset(offset.fX, offset.fY), paint);
paint.setColor(SK_ColorBLACK);
canvas->drawTextBlob(blob, offset.fX, offset.fY, paint);
}
// Exercise xform blob and its tight bounds
DEF_SIMPLE_GM(blob_rsxform_distortable, canvas, 500, 100) {
sk_sp<SkTypeface> typeface;
std::unique_ptr<SkStreamAsset> distortable(GetResourceAsStream("fonts/Distortable.ttf"));
if (distortable) {
sk_sp<SkFontMgr> fm = SkFontMgr::RefDefault();
const SkFontArguments::VariationPosition::Coordinate position[] = {
{ SkSetFourByteTag('w','g','h','t'), 1.618033988749895f }
};
SkFontArguments params;
params.setVariationDesignPosition({position, SK_ARRAY_COUNT(position)});
typeface = fm->makeFromStream(std::move(distortable), params);
}
SkFont font(typeface, 50);
const char text[] = "abcabcabc";
constexpr size_t len = sizeof(text) - 1;
SkRSXform xforms[len];
SkScalar scale = 1;
SkScalar x = 0, y = 0;
for (size_t i = 0; i < len; ++i) {
scale = SkScalarSin(i * SK_ScalarPI / (len-1)) * 0.75f + 0.5f;
xforms[i] = SkRSXform::Make(scale, 0, x, y);
x += 50 * scale;
}
auto blob = SkTextBlob::MakeFromRSXform(text, len, xforms, font);
SkPoint offset = { 20, 70 };
SkPaint paint;
paint.setColor(0xFFCCCCCC);
canvas->drawRect(blob->bounds().makeOffset(offset.fX, offset.fY), paint);
paint.setColor(SK_ColorBLACK);
canvas->drawTextBlob(blob, offset.fX, offset.fY, paint);
}
static sk_sp<SkVertices> make_vertices(sk_sp<SkImage> image, const SkRect& r,
SkColor color) {
SkPoint pos[4];
r.toQuad(pos);
SkColor colors[4] = { color, color, color, color };
return SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode, 4,
pos, pos, colors);
}
/*
* drawAtlas and drawVertices have several things in common:
* - can create compound "shaders", combining texture and colors
* - these are combined via an explicit blendmode
* - like drawImage, they only respect parts of the paint
* - colorfilter, imagefilter, blendmode, alpha
*
* This GM produces a series of pairs of images (atlas | vertices).
* Each pair should look the same, and each set shows a different combination
* of alpha | colorFilter | mode
*/
DEF_SIMPLE_GM(compare_atlas_vertices, canvas, 560, 585) {
const SkRect tex = SkRect::MakeWH(128, 128);
const SkRSXform xform = SkRSXform::Make(1, 0, 0, 0);
const SkColor color = 0x884488CC;
auto image = GetResourceAsImage("images/mandrill_128.png");
auto verts = make_vertices(image, tex, color);
const sk_sp<SkColorFilter> filters[] = {
nullptr,
SkColorFilters::Blend(0xFF00FF88, SkBlendMode::kModulate),
};
const SkBlendMode modes[] = {
SkBlendMode::kSrcOver,
SkBlendMode::kPlus,
};
canvas->translate(10, 10);
SkPaint paint;
for (SkBlendMode mode : modes) {
for (float alpha : { 1.0f, 0.5f }) {
paint.setAlphaf(alpha);
canvas->save();
for (auto cf : filters) {
paint.setColorFilter(cf);
canvas->drawAtlas(image, &xform, &tex, &color, 1,
mode, &tex, &paint);
canvas->translate(128, 0);
paint.setShader(image->makeShader());
canvas->drawVertices(verts, mode, paint);
paint.setShader(nullptr);
canvas->translate(145, 0);
}
canvas->restore();
canvas->translate(0, 145);
}
}
}