1 /*
2 * Copyright 2013 Google Inc.
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/SkBlendMode.h"
10 #include "include/core/SkBlurTypes.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkColorFilter.h"
14 #include "include/core/SkDrawLooper.h"
15 #include "include/core/SkFont.h"
16 #include "include/core/SkFontTypes.h"
17 #include "include/core/SkMaskFilter.h"
18 #include "include/core/SkMatrix.h"
19 #include "include/core/SkPaint.h"
20 #include "include/core/SkPath.h"
21 #include "include/core/SkPathEffect.h"
22 #include "include/core/SkPoint.h"
23 #include "include/core/SkRect.h"
24 #include "include/core/SkRefCnt.h"
25 #include "include/core/SkScalar.h"
26 #include "include/core/SkShader.h"
27 #include "include/core/SkSize.h"
28 #include "include/core/SkString.h"
29 #include "include/core/SkTextBlob.h"
30 #include "include/core/SkTileMode.h"
31 #include "include/core/SkTypeface.h"
32 #include "include/core/SkTypes.h"
33 #include "include/effects/Sk2DPathEffect.h"
34 #include "include/effects/SkColorMatrixFilter.h"
35 #include "include/effects/SkGradientShader.h"
36 #include "include/effects/SkLayerDrawLooper.h"
37 #include "include/private/SkTArray.h"
38 #include "include/private/SkTDArray.h"
39 #include "src/core/SkBlurMask.h"
40 #include "tools/ToolUtils.h"
41
42 #include <string.h>
43
44 namespace skiagm {
45
46 constexpr int kWidth = 1250;
47 constexpr int kHeight = 700;
48
49 // Unlike the variant in ToolUtils, this version positions the glyphs on a diagonal
add_to_text_blob(SkTextBlobBuilder * builder,const char * text,const SkFont & font,SkScalar x,SkScalar y)50 static void add_to_text_blob(SkTextBlobBuilder* builder, const char* text, const SkFont& font,
51 SkScalar x, SkScalar y) {
52 SkTDArray<uint16_t> glyphs;
53
54 size_t len = strlen(text);
55 glyphs.append(font.countText(text, len, SkTextEncoding::kUTF8));
56 font.textToGlyphs(text, len, SkTextEncoding::kUTF8, glyphs.begin(), glyphs.count());
57
58 const SkScalar advanceX = font.getSize() * 0.85f;
59 const SkScalar advanceY = font.getSize() * 1.5f;
60
61 SkTDArray<SkScalar> pos;
62 for (unsigned i = 0; i < len; ++i) {
63 *pos.append() = x + i * advanceX;
64 *pos.append() = y + i * (advanceY / len);
65 }
66 const SkTextBlobBuilder::RunBuffer& run = builder->allocRunPos(font, glyphs.count());
67 memcpy(run.glyphs, glyphs.begin(), glyphs.count() * sizeof(uint16_t));
68 memcpy(run.pos, pos.begin(), len * sizeof(SkScalar) * 2);
69 }
70
71 typedef void (*LooperProc)(SkPaint*);
72
73 struct LooperSettings {
74 SkBlendMode fMode;
75 SkColor fColor;
76 SkPaint::Style fStyle;
77 SkScalar fWidth;
78 SkScalar fOffset;
79 SkScalar fSkewX;
80 bool fEffect;
81 };
82
mask_filter(SkPaint * paint)83 static void mask_filter(SkPaint* paint) {
84 paint->setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
85 SkBlurMask::ConvertRadiusToSigma(3.f)));
86 }
87
make_tile_effect()88 static sk_sp<SkPathEffect> make_tile_effect() {
89 SkMatrix m;
90 m.setScale(1.f, 1.f);
91
92 SkPath path;
93 path.addCircle(0, 0, SkIntToScalar(5));
94
95 return SkPath2DPathEffect::Make(m, path);
96 }
97
path_effect(SkPaint * paint)98 static void path_effect(SkPaint* paint) {
99 paint->setPathEffect(make_tile_effect());
100 }
101
make_shader(const SkRect & bounds)102 static sk_sp<SkShader> make_shader(const SkRect& bounds) {
103 const SkPoint pts[] = {
104 { bounds.left(), bounds.top() },
105 { bounds.right(), bounds.bottom() },
106 };
107 const SkColor colors[] = {
108 SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorBLACK,
109 SK_ColorCYAN, SK_ColorMAGENTA, SK_ColorYELLOW,
110 };
111 return SkGradientShader::MakeLinear(pts, colors, nullptr, SK_ARRAY_COUNT(colors),
112 SkTileMode::kClamp);
113 }
114
color_filter(SkPaint * paint)115 static void color_filter(SkPaint* paint) {
116 SkRect r;
117 r.setWH(SkIntToScalar(kWidth), 50);
118 paint->setShader(make_shader(r));
119 paint->setColorFilter(SkColorMatrixFilter::MakeLightingFilter(0xF0F0F0, 0));
120 }
121
kitchen_sink(SkPaint * paint)122 static void kitchen_sink(SkPaint* paint) {
123 color_filter(paint);
124 path_effect(paint);
125 mask_filter(paint);
126
127 }
128
setupLooper(SkLayerDrawLooper::BitFlags bits,LooperProc proc,const LooperSettings settings[],size_t size)129 static sk_sp<SkDrawLooper> setupLooper(SkLayerDrawLooper::BitFlags bits,
130 LooperProc proc,
131 const LooperSettings settings[],
132 size_t size) {
133 SkLayerDrawLooper::Builder looperBuilder;
134
135 SkLayerDrawLooper::LayerInfo info;
136 info.fPaintBits = bits;
137
138 info.fColorMode = SkBlendMode::kSrc;
139
140 for (size_t i = 0; i < size; i++) {
141 info.fOffset.set(settings[i].fOffset, settings[i].fOffset);
142 SkPaint* paint = looperBuilder.addLayer(info);
143 paint->setBlendMode(settings[i].fMode);
144 paint->setColor(settings[i].fColor);
145 paint->setStyle(settings[i].fStyle);
146 paint->setStrokeWidth(settings[i].fWidth);
147 if (settings[i].fEffect) {
148 (*proc)(paint);
149 }
150 }
151 return looperBuilder.detach();
152 }
153
154 class TextBlobLooperGM : public GM {
155 public:
TextBlobLooperGM()156 TextBlobLooperGM() {}
157
158 protected:
onOnceBeforeDraw()159 void onOnceBeforeDraw() override {
160 SkTextBlobBuilder builder;
161
162 // LCD
163 SkFont font;
164 font.setSize(32);
165 const char* text = "The quick brown fox jumps over the lazy dog";
166 font.setSubpixel(true);
167 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
168 font.setTypeface(ToolUtils::create_portable_typeface());
169 add_to_text_blob(&builder, text, font, 0, 0);
170 fBlob = builder.make();
171
172 // create a looper which sandwhiches an effect in two normal draws
173 LooperSettings looperSandwhich[] = {
174 { SkBlendMode::kSrc, SK_ColorMAGENTA, SkPaint::kFill_Style, 0, 0, 0, false },
175 { SkBlendMode::kSrcOver, 0x88000000, SkPaint::kFill_Style, 0, 10.f, 0, true },
176 { SkBlendMode::kSrcOver, 0x50FF00FF, SkPaint::kFill_Style, 0, 20.f, 0, false },
177 };
178
179 LooperSettings compound[] = {
180 { SkBlendMode::kSrc, SK_ColorWHITE, SkPaint::kStroke_Style, 1.f * 3/4, 0, 0, false },
181 { SkBlendMode::kSrc, SK_ColorRED, SkPaint::kStroke_Style, 4.f, 0, 0, false },
182 { SkBlendMode::kSrc, SK_ColorBLUE, SkPaint::kFill_Style, 0, 0, 0, false },
183 { SkBlendMode::kSrcOver, 0x88000000, SkPaint::kFill_Style, 0, 10.f, 0, true }
184 };
185
186 LooperSettings xfermode[] = {
187 { SkBlendMode::kDifference, SK_ColorWHITE, SkPaint::kFill_Style, 0, 0, 0, false },
188 { SkBlendMode::kSrcOver, 0xFF000000, SkPaint::kFill_Style, 0, 1.f, 0, true },
189 { SkBlendMode::kSrcOver, 0x50FF00FF, SkPaint::kFill_Style, 0, 2.f, 0, false },
190 };
191
192 // NOTE, this should be ignored by textblobs
193 LooperSettings skew[] = {
194 { SkBlendMode::kSrc, SK_ColorRED, SkPaint::kFill_Style, 0, 0, -1.f, false },
195 { SkBlendMode::kSrc, SK_ColorGREEN, SkPaint::kFill_Style, 0, 10.f, -1.f, false },
196 { SkBlendMode::kSrc, SK_ColorBLUE, SkPaint::kFill_Style, 0, 20.f, -1.f, false },
197 };
198
199 LooperSettings kitchenSink[] = {
200 { SkBlendMode::kSrc, SK_ColorWHITE, SkPaint::kStroke_Style, 1.f * 3/4, 0, 0, false },
201 { SkBlendMode::kSrc, SK_ColorBLACK, SkPaint::kFill_Style, 0, 0, 0, false },
202 { SkBlendMode::kDifference, SK_ColorWHITE, SkPaint::kFill_Style, 1.f, 10.f, 0, false },
203 { SkBlendMode::kSrc, SK_ColorWHITE, SkPaint::kFill_Style, 0, 10.f, 0, true },
204 { SkBlendMode::kSrcOver, 0x50FF00FF, SkPaint::kFill_Style, 0, 20.f, 0, false },
205 };
206
207 fLoopers.push_back(setupLooper(SkLayerDrawLooper::kMaskFilter_Bit |
208 SkLayerDrawLooper::kXfermode_Bit |
209 SkLayerDrawLooper::kStyle_Bit, &mask_filter,
210 compound, SK_ARRAY_COUNT(compound)));
211 fLoopers.push_back(setupLooper(SkLayerDrawLooper::kPathEffect_Bit |
212 SkLayerDrawLooper::kXfermode_Bit, &path_effect,
213 looperSandwhich, SK_ARRAY_COUNT(looperSandwhich)));
214 fLoopers.push_back(setupLooper(SkLayerDrawLooper::kShader_Bit |
215 SkLayerDrawLooper::kColorFilter_Bit |
216 SkLayerDrawLooper::kXfermode_Bit, &color_filter,
217 looperSandwhich, SK_ARRAY_COUNT(looperSandwhich)));
218 fLoopers.push_back(setupLooper(SkLayerDrawLooper::kShader_Bit |
219 SkLayerDrawLooper::kColorFilter_Bit |
220 SkLayerDrawLooper::kXfermode_Bit, &color_filter,
221 xfermode, SK_ARRAY_COUNT(xfermode)));
222 fLoopers.push_back(setupLooper(0, nullptr, skew, SK_ARRAY_COUNT(skew)));
223 fLoopers.push_back(setupLooper(SkLayerDrawLooper::kMaskFilter_Bit |
224 SkLayerDrawLooper::kShader_Bit |
225 SkLayerDrawLooper::kColorFilter_Bit |
226 SkLayerDrawLooper::kPathEffect_Bit |
227 SkLayerDrawLooper::kStyle_Bit |
228 SkLayerDrawLooper::kXfermode_Bit, &kitchen_sink,
229 kitchenSink, SK_ARRAY_COUNT(kitchenSink)));
230
231 // Test we respect overrides
232 fLoopers.push_back(setupLooper(0, &kitchen_sink,
233 kitchenSink, SK_ARRAY_COUNT(kitchenSink)));
234 }
235
onShortName()236 SkString onShortName() override {
237 return SkString("textbloblooper");
238 }
239
onISize()240 SkISize onISize() override {
241 return SkISize::Make(kWidth, kHeight);
242 }
243
onDraw(SkCanvas * canvas)244 void onDraw(SkCanvas* canvas) override {
245
246 canvas->drawColor(SK_ColorGRAY);
247
248 SkPaint paint;
249 canvas->translate(10, 40);
250
251 SkRect bounds = fBlob->bounds();
252
253 int y = 0;
254 for (int looper = 0; looper < fLoopers.count(); looper++) {
255 SkTextBlob* b = fBlob.get();
256 canvas->save();
257 canvas->translate(0, SkIntToScalar(y));
258 fLoopers[looper]->apply(canvas, paint, [b](SkCanvas* c, const SkPaint& p) {
259 c->drawTextBlob(b, 0, 0, p);
260 });
261 canvas->restore();
262 y += SkScalarFloorToInt(bounds.height());
263 }
264 }
265
266 private:
267 sk_sp<SkTextBlob> fBlob;
268 SkTArray<sk_sp<SkDrawLooper>> fLoopers;
269
270 typedef GM INHERITED;
271 };
272
273 //////////////////////////////////////////////////////////////////////////////
274
275 DEF_GM(return new TextBlobLooperGM;)
276 }
277