/* * 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/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 class DrawAtlasGM : public skiagm::GM { static sk_sp 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.setAntiAlias(true); SkSamplingOptions sampling(SkFilterMode::kLinear); canvas->drawAtlas(atlas.get(), xform, tex, nullptr, N, SkBlendMode::kDst, sampling, nullptr, &paint); canvas->translate(0, 100); canvas->drawAtlas(atlas.get(), xform, tex, colors, N, SkBlendMode::kSrcIn, sampling, nullptr, &paint); } private: using INHERITED = GM; }; 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 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 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 typeface; std::unique_ptr distortable(GetResourceAsStream("fonts/Distortable.ttf")); if (distortable) { sk_sp 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 make_vertices(sk_sp 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 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 (const sk_sp& cf : filters) { paint.setColorFilter(cf); canvas->drawAtlas(image.get(), &xform, &tex, &color, 1, mode, SkSamplingOptions(), &tex, &paint); canvas->translate(128, 0); paint.setShader(image->makeShader(SkSamplingOptions())); canvas->drawVertices(verts, mode, paint); paint.setShader(nullptr); canvas->translate(145, 0); } canvas->restore(); canvas->translate(0, 145); } } }