blob: 92aa202454fdf02f1b6aedaa012605ee126f0a0d [file] [log] [blame]
Michael Ludwigd20cf332019-01-30 14:38:59 -05001/*
2 * Copyright 2019 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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "samplecode/Sample.h"
Michael Ludwigd20cf332019-01-30 14:38:59 -05009
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "include/core/SkCanvas.h"
11#include "include/core/SkColorFilter.h"
12#include "include/core/SkFont.h"
13#include "include/core/SkImage.h"
14#include "include/core/SkPath.h"
15#include "include/core/SkSurface.h"
Michael Ludwigd20cf332019-01-30 14:38:59 -050016
17namespace skiagm {
18
19class ShapeRenderer : public SkRefCntBase {
20public:
21 static constexpr SkScalar kTileWidth = 20.f;
22 static constexpr SkScalar kTileHeight = 20.f;
23
24 virtual ~ShapeRenderer() {}
25
26 // Draw the shape, limited to kTileWidth x kTileHeight. It must apply the local subpixel (tx,
27 // ty) translation and rotation by angle. Prior to these transform adjustments, the SkCanvas
28 // will only have pixel aligned translations (these are separated to make super-sampling
29 // renderers easier).
30 virtual void draw(SkCanvas* canvas, SkPaint* paint,
31 SkScalar tx, SkScalar ty, SkScalar angle) = 0;
32
33 virtual SkString name() = 0;
34
35 virtual sk_sp<ShapeRenderer> toHairline() = 0;
36
37 void applyLocalTransform(SkCanvas* canvas, SkScalar tx, SkScalar ty, SkScalar angle) {
38 canvas->translate(tx, ty);
39 canvas->rotate(angle, kTileWidth / 2.f, kTileHeight / 2.f);
40 }
41};
42
43class RectRenderer : public ShapeRenderer {
44public:
45 static sk_sp<ShapeRenderer> Make() {
46 return sk_sp<ShapeRenderer>(new RectRenderer());
47 }
48
49 SkString name() override { return SkString("rect"); }
50
51 sk_sp<ShapeRenderer> toHairline() override {
52 // Not really available but can't return nullptr
53 return Make();
54 }
55
56 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
57 SkScalar width = paint->getStrokeWidth();
58 paint->setStyle(SkPaint::kFill_Style);
59
60 this->applyLocalTransform(canvas, tx, ty, angle);
61 canvas->drawRect(SkRect::MakeLTRB(kTileWidth / 2.f - width / 2.f, 2.f,
62 kTileWidth / 2.f + width / 2.f, kTileHeight - 2.f),
63 *paint);
64 }
65
66private:
67 RectRenderer() {}
68
69 typedef ShapeRenderer INHERITED;
70};
71
72class PathRenderer : public ShapeRenderer {
73public:
74 static sk_sp<ShapeRenderer> MakeLine(bool hairline = false) {
75 return MakeCurve(0.f, hairline);
76 }
77
78 static sk_sp<ShapeRenderer> MakeLines(SkScalar depth, bool hairline = false) {
79 return MakeCurve(-depth, hairline);
80 }
81
82 static sk_sp<ShapeRenderer> MakeCurve(SkScalar depth, bool hairline = false) {
83 return sk_sp<ShapeRenderer>(new PathRenderer(depth, hairline));
84 }
85
86 SkString name() override {
87 SkString name;
88 if (fHairline) {
89 name.append("hairline");
90 if (fDepth > 0.f) {
91 name.appendf("-curve-%.2f", fDepth);
92 }
93 } else if (fDepth > 0.f) {
94 name.appendf("curve-%.2f", fDepth);
95 } else if (fDepth < 0.f) {
96 name.appendf("line-%.2f", -fDepth);
97 } else {
98 name.append("line");
99 }
100
101 return name;
102 }
103
104 sk_sp<ShapeRenderer> toHairline() override {
105 return sk_sp<ShapeRenderer>(new PathRenderer(fDepth, true));
106 }
107
108 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
109 SkPath path;
110 path.moveTo(kTileWidth / 2.f, 2.f);
111
112 if (fDepth > 0.f) {
113 path.quadTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f,
114 kTileWidth / 2.f, kTileHeight - 2.f);
115 } else {
116 if (fDepth < 0.f) {
117 path.lineTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f);
118 }
119 path.lineTo(kTileWidth / 2.f, kTileHeight - 2.f);
120 }
121
122 if (fHairline) {
123 // Fake thinner hairlines by making it transparent, conflating coverage and alpha
124 SkColor4f color = paint->getColor4f();
125 SkScalar width = paint->getStrokeWidth();
126 if (width > 1.f) {
127 // Can't emulate width larger than a pixel
128 return;
129 }
130 paint->setColor4f({color.fR, color.fG, color.fB, width}, nullptr);
131 paint->setStrokeWidth(0.f);
132 }
133
134 // Adding round caps forces Ganesh to use the path renderer for lines instead of converting
135 // them to rectangles (which are already explicitly tested). However, when not curved, the
136 // GrShape will still find a way to turn it into a rrect draw so it doesn't hit the
137 // path renderer in that condition.
138 paint->setStrokeCap(SkPaint::kRound_Cap);
139 paint->setStrokeJoin(SkPaint::kMiter_Join);
140 paint->setStyle(SkPaint::kStroke_Style);
141
142 this->applyLocalTransform(canvas, tx, ty, angle);
143 canvas->drawPath(path, *paint);
144 }
145
146private:
147 SkScalar fDepth; // 0.f to make a line, otherwise outset of curve from end points
148 bool fHairline;
149
150 PathRenderer(SkScalar depth, bool hairline)
151 : fDepth(depth)
152 , fHairline(hairline) {}
153
154 typedef ShapeRenderer INHERITED;
155};
156
157class OffscreenShapeRenderer : public ShapeRenderer {
158public:
159 ~OffscreenShapeRenderer() override = default;
160
161 static sk_sp<OffscreenShapeRenderer> Make(sk_sp<ShapeRenderer> renderer, int supersample,
162 bool forceRaster = false) {
163 SkASSERT(supersample > 0);
164 return sk_sp<OffscreenShapeRenderer>(new OffscreenShapeRenderer(std::move(renderer),
165 supersample, forceRaster));
166 }
167
168 SkString name() override {
169 SkString name = fRenderer->name();
170 if (fSupersampleFactor != 1) {
171 name.prependf("%dx-", fSupersampleFactor * fSupersampleFactor);
172 }
173 return name;
174 }
175
176 sk_sp<ShapeRenderer> toHairline() override {
177 return Make(fRenderer->toHairline(), fSupersampleFactor, fForceRasterBackend);
178 }
179
180 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override {
181 // Subpixel translation+angle are applied in the offscreen buffer
182 this->prepareBuffer(canvas, paint, tx, ty, angle);
183 this->redraw(canvas);
184 }
185
186 // Exposed so that it's easy to fill the offscreen buffer, then draw zooms/filters of it before
187 // drawing the original scale back into the canvas.
188 void prepareBuffer(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) {
189 auto info = SkImageInfo::Make(fSupersampleFactor * kTileWidth,
190 fSupersampleFactor * kTileHeight,
191 kRGBA_8888_SkColorType, kPremul_SkAlphaType);
192 auto surface = fForceRasterBackend ? SkSurface::MakeRaster(info)
193 : canvas->makeSurface(info);
194
195 surface->getCanvas()->save();
196 // Make fully transparent so it is easy to determine pixels that are touched by partial cov.
197 surface->getCanvas()->clear(SK_ColorTRANSPARENT);
198 // Set up scaling to fit supersampling amount
199 surface->getCanvas()->scale(fSupersampleFactor, fSupersampleFactor);
200 fRenderer->draw(surface->getCanvas(), paint, tx, ty, angle);
201 surface->getCanvas()->restore();
202
203 // Save image so it can be drawn zoomed in or to visualize touched pixels; only valid until
204 // the next call to draw()
205 fLastRendered = surface->makeImageSnapshot();
206 }
207
208 void redraw(SkCanvas* canvas, SkScalar scale = 1.f, bool debugMode = false) {
209 SkASSERT(fLastRendered);
210 // Use medium quality filter to get mipmaps when drawing smaller, or use nearest filtering
211 // when upscaling
212 SkPaint blit;
213 blit.setFilterQuality(scale > 1.f ? kNone_SkFilterQuality : kMedium_SkFilterQuality);
214 if (debugMode) {
215 // Makes anything that's > 1/255 alpha fully opaque and sets color to medium green.
Mike Reede869a1e2019-04-30 12:18:54 -0400216 static constexpr float kFilter[] = {
217 0.f, 0.f, 0.f, 0.f, 16.f/255,
218 0.f, 0.f, 0.f, 0.f, 200.f/255,
219 0.f, 0.f, 0.f, 0.f, 16.f/255,
Michael Ludwigd20cf332019-01-30 14:38:59 -0500220 0.f, 0.f, 0.f, 255.f, 0.f
221 };
222
Mike Reede869a1e2019-04-30 12:18:54 -0400223 blit.setColorFilter(SkColorFilters::Matrix(kFilter));
Michael Ludwigd20cf332019-01-30 14:38:59 -0500224 }
225
226 canvas->scale(scale, scale);
227 canvas->drawImageRect(fLastRendered, SkRect::MakeWH(kTileWidth, kTileHeight), &blit);
228 }
229
230private:
231 bool fForceRasterBackend;
232 sk_sp<SkImage> fLastRendered;
233 sk_sp<ShapeRenderer> fRenderer;
234 int fSupersampleFactor;
235
236 OffscreenShapeRenderer(sk_sp<ShapeRenderer> renderer, int supersample, bool forceRaster)
237 : fForceRasterBackend(forceRaster)
238 , fLastRendered(nullptr)
239 , fRenderer(std::move(renderer))
240 , fSupersampleFactor(supersample) { }
241
242 typedef ShapeRenderer INHERITED;
243};
244
245class ThinAASample : public Sample {
246public:
247 ThinAASample() {
248 this->setBGColor(0xFFFFFFFF);
249 }
250
251protected:
252 void onOnceBeforeDraw() override {
253 // Setup all base renderers
254 fShapes.push_back(RectRenderer::Make());
255 fShapes.push_back(PathRenderer::MakeLine());
256 fShapes.push_back(PathRenderer::MakeLines(4.f)); // 2 segments
257 fShapes.push_back(PathRenderer::MakeCurve(2.f)); // Shallow curve
258 fShapes.push_back(PathRenderer::MakeCurve(8.f)); // Deep curve
259
260 for (int i = 0; i < fShapes.count(); ++i) {
261 fNative.push_back(OffscreenShapeRenderer::Make(fShapes[i], 1));
262 fRaster.push_back(OffscreenShapeRenderer::Make(fShapes[i], 1, /* raster */ true));
263 fSS4.push_back(OffscreenShapeRenderer::Make(fShapes[i], 4)); // 4x4 -> 16 samples
264 fSS16.push_back(OffscreenShapeRenderer::Make(fShapes[i], 8)); // 8x8 -> 64 samples
265
266 fHairline.push_back(OffscreenShapeRenderer::Make(fRaster[i]->toHairline(), 1));
267 }
268
269 // Start it at something subpixel
270 fStrokeWidth = 0.5f;
271
272 fSubpixelX = 0.f;
273 fSubpixelY = 0.f;
274 fAngle = 0.f;
275
276 fCurrentStage = AnimStage::kMoveLeft;
277 fLastFrameTime = -1.f;
278
279 // Don't animate in the beginning
280 fAnimTranslate = false;
281 fAnimRotate = false;
282 }
283
284 void onDrawContent(SkCanvas* canvas) override {
285 // Move away from screen edge and add instructions
286 SkPaint text;
287 SkFont font(nullptr, 12);
288 canvas->translate(60.f, 20.f);
289 canvas->drawString("Each row features a rendering command under different AA strategies. "
290 "Native refers to the current backend of the viewer, e.g. OpenGL.",
291 0, 0, font, text);
292
293 canvas->drawString(SkStringPrintf("Stroke width: %.2f ('-' to decrease, '=' to increase)",
294 fStrokeWidth), 0, 24, font, text);
295 canvas->drawString(SkStringPrintf("Rotation: %.3f ('r' to animate, 'y' sets to 90, 'u' sets"
296 " to 0, 'space' adds 15)", fAngle), 0, 36, font, text);
297 canvas->drawString(SkStringPrintf("Translation: %.3f, %.3f ('t' to animate)",
298 fSubpixelX, fSubpixelY), 0, 48, font, text);
299
300 canvas->translate(0.f, 100.f);
301
302 // Draw with surface matching current viewer surface type
303 this->drawShapes(canvas, "Native", 0, fNative);
304
305 // Draw with forced raster backend so it's easy to compare side-by-side
306 this->drawShapes(canvas, "Raster", 1, fRaster);
307
308 // Draw paths as hairlines + alpha hack
309 this->drawShapes(canvas, "Hairline", 2, fHairline);
310
311 // Draw at 4x supersampling in bottom left
312 this->drawShapes(canvas, "SSx16", 3, fSS4);
313
314 // And lastly 16x supersampling in bottom right
315 this->drawShapes(canvas, "SSx64", 4, fSS16);
316 }
317
Hal Canary41248072019-07-11 16:32:53 -0400318 bool onAnimate(double nanos) override {
319 SkScalar t = 1e-9 * nanos;
Michael Ludwigd20cf332019-01-30 14:38:59 -0500320 SkScalar dt = fLastFrameTime < 0.f ? 0.f : t - fLastFrameTime;
321 fLastFrameTime = t;
322
323 if (!fAnimRotate && !fAnimTranslate) {
324 // Keep returning true so that the last frame time is tracked
325 fLastFrameTime = -1.f;
326 return false;
327 }
328
329 switch(fCurrentStage) {
330 case AnimStage::kMoveLeft:
331 fSubpixelX += 2.f * dt;
332 if (fSubpixelX >= 1.f) {
333 fSubpixelX = 1.f;
334 fCurrentStage = AnimStage::kMoveDown;
335 }
336 break;
337 case AnimStage::kMoveDown:
338 fSubpixelY += 2.f * dt;
339 if (fSubpixelY >= 1.f) {
340 fSubpixelY = 1.f;
341 fCurrentStage = AnimStage::kMoveRight;
342 }
343 break;
344 case AnimStage::kMoveRight:
345 fSubpixelX -= 2.f * dt;
346 if (fSubpixelX <= -1.f) {
347 fSubpixelX = -1.f;
348 fCurrentStage = AnimStage::kMoveUp;
349 }
350 break;
351 case AnimStage::kMoveUp:
352 fSubpixelY -= 2.f * dt;
353 if (fSubpixelY <= -1.f) {
354 fSubpixelY = -1.f;
355 fCurrentStage = fAnimRotate ? AnimStage::kRotate : AnimStage::kMoveLeft;
356 }
357 break;
358 case AnimStage::kRotate: {
359 SkScalar newAngle = fAngle + dt * 15.f;
360 bool completed = SkScalarMod(newAngle, 15.f) < SkScalarMod(fAngle, 15.f);
361 fAngle = SkScalarMod(newAngle, 360.f);
362 if (completed) {
363 // Make sure we're on a 15 degree boundary
364 fAngle = 15.f * SkScalarRoundToScalar(fAngle / 15.f);
365 if (fAnimTranslate) {
366 fCurrentStage = this->getTranslationStage();
367 }
368 }
369 } break;
370 }
371
372 return true;
373 }
374
Hal Canary8a027312019-07-03 10:55:44 -0400375 SkString name() override { return SkString("Thin-AA"); }
Michael Ludwigd20cf332019-01-30 14:38:59 -0500376
Hal Canary6cc65e12019-07-03 15:53:04 -0400377 bool onChar(SkUnichar key) override {
Michael Ludwigd20cf332019-01-30 14:38:59 -0500378 switch(key) {
379 case 't':
380 // Toggle translation animation.
381 fAnimTranslate = !fAnimTranslate;
382 if (!fAnimTranslate && fAnimRotate && fCurrentStage != AnimStage::kRotate) {
383 // Turned off an active translation so go to rotating
384 fCurrentStage = AnimStage::kRotate;
385 } else if (fAnimTranslate && !fAnimRotate &&
386 fCurrentStage == AnimStage::kRotate) {
387 // Turned on translation, rotation had been paused too, so reset the stage
388 fCurrentStage = this->getTranslationStage();
389 }
390 return true;
391 case 'r':
392 // Toggle rotation animation.
393 fAnimRotate = !fAnimRotate;
394 if (!fAnimRotate && fAnimTranslate && fCurrentStage == AnimStage::kRotate) {
395 // Turned off an active rotation so go back to translation
396 fCurrentStage = this->getTranslationStage();
397 } else if (fAnimRotate && !fAnimTranslate &&
398 fCurrentStage != AnimStage::kRotate) {
399 // Turned on rotation, translation had been paused too, so reset to rotate
400 fCurrentStage = AnimStage::kRotate;
401 }
402 return true;
403 case 'u': fAngle = 0.f; return true;
404 case 'y': fAngle = 90.f; return true;
405 case ' ': fAngle = SkScalarMod(fAngle + 15.f, 360.f); return true;
406 case '-': fStrokeWidth = SkMaxScalar(0.1f, fStrokeWidth - 0.05f); return true;
407 case '=': fStrokeWidth = SkMinScalar(1.f, fStrokeWidth + 0.05f); return true;
408 }
Hal Canary6cc65e12019-07-03 15:53:04 -0400409 return false;
Michael Ludwigd20cf332019-01-30 14:38:59 -0500410 }
411
412private:
413 // Base renderers that get wrapped on the offscreen renderers so that they can be transformed
414 // for visualization, or supersampled.
415 SkTArray<sk_sp<ShapeRenderer>> fShapes;
416
417 SkTArray<sk_sp<OffscreenShapeRenderer>> fNative;
418 SkTArray<sk_sp<OffscreenShapeRenderer>> fRaster;
419 SkTArray<sk_sp<OffscreenShapeRenderer>> fHairline;
420 SkTArray<sk_sp<OffscreenShapeRenderer>> fSS4;
421 SkTArray<sk_sp<OffscreenShapeRenderer>> fSS16;
422
423 SkScalar fStrokeWidth;
424
425 // Animated properties to stress the AA algorithms
426 enum class AnimStage {
427 kMoveRight, kMoveDown, kMoveLeft, kMoveUp, kRotate
428 } fCurrentStage;
429 SkScalar fLastFrameTime;
430 bool fAnimRotate;
431 bool fAnimTranslate;
432
433 // Current frame's animation state
434 SkScalar fSubpixelX;
435 SkScalar fSubpixelY;
436 SkScalar fAngle;
437
438 AnimStage getTranslationStage() {
439 // For paused translations (i.e. fAnimTranslate toggled while translating), the current
440 // stage moves to kRotate, but when restarting the translation animation, we want to
441 // go back to where we were without losing any progress.
442 if (fSubpixelX > -1.f) {
443 if (fSubpixelX >= 1.f) {
444 // Can only be moving down on right edge, given our transition states
445 return AnimStage::kMoveDown;
446 } else if (fSubpixelY > 0.f) {
447 // Can only be moving right along top edge
448 return AnimStage::kMoveRight;
449 } else {
450 // Must be moving left along bottom edge
451 return AnimStage::kMoveLeft;
452 }
453 } else {
454 // Moving up along the left edge, or is at the very top so start moving left
455 return fSubpixelY > -1.f ? AnimStage::kMoveUp : AnimStage::kMoveLeft;
456 }
457 }
458
459 void drawShapes(SkCanvas* canvas, const char* name, int gridX,
460 SkTArray<sk_sp<OffscreenShapeRenderer>> shapes) {
461 SkAutoCanvasRestore autoRestore(canvas, /* save */ true);
462
463 for (int i = 0; i < shapes.count(); ++i) {
464 this->drawShape(canvas, name, gridX, shapes[i].get(), i == 0);
465 // drawShape positions the canvas properly for the next iteration
466 }
467 }
468
469 void drawShape(SkCanvas* canvas, const char* name, int gridX,
470 OffscreenShapeRenderer* shape, bool drawNameLabels) {
471 static constexpr SkScalar kZoomGridWidth = 8 * ShapeRenderer::kTileWidth + 8.f;
472 static constexpr SkRect kTile = SkRect::MakeWH(ShapeRenderer::kTileWidth,
473 ShapeRenderer::kTileHeight);
474 static constexpr SkRect kZoomTile = SkRect::MakeWH(8 * ShapeRenderer::kTileWidth,
475 8 * ShapeRenderer::kTileHeight);
476
477 // Labeling per shape and detailed labeling that isn't per-stroke
478 canvas->save();
479 SkPaint text;
480 SkFont font(nullptr, 12);
481
482 if (gridX == 0) {
483 SkString name = shape->name();
484 SkScalar centering = name.size() * 4.f; // ad-hoc
485
486 canvas->save();
487 canvas->translate(-10.f, 4 * ShapeRenderer::kTileHeight + centering);
488 canvas->rotate(-90.f);
489 canvas->drawString(shape->name(), 0.f, 0.f, font, text);
490 canvas->restore();
491 }
492 if (drawNameLabels) {
493 canvas->drawString(name, gridX * kZoomGridWidth, -10.f, font, text);
494 }
495 canvas->restore();
496
497 // Paints for outlines and actual shapes
498 SkPaint outline;
499 outline.setStyle(SkPaint::kStroke_Style);
500 SkPaint clear;
501 clear.setColor(SK_ColorWHITE);
502
503 SkPaint paint;
504 paint.setAntiAlias(true);
505 paint.setStrokeWidth(fStrokeWidth);
506
507 // Generate a saved image of the correct stroke width, but don't put it into the canvas
508 // yet since we want to draw the "original" size on top of the zoomed in version
509 shape->prepareBuffer(canvas, &paint, fSubpixelX, fSubpixelY, fAngle);
510
511 // Draw it at 8X zoom
512 SkScalar x = gridX * kZoomGridWidth;
513
514 canvas->save();
515 canvas->translate(x, 0.f);
516 canvas->drawRect(kZoomTile, outline);
517 shape->redraw(canvas, 8.0f);
518 canvas->restore();
519
520 // Draw the original
521 canvas->save();
522 canvas->translate(x + 4.f, 4.f);
523 canvas->drawRect(kTile, clear);
524 canvas->drawRect(kTile, outline);
525 shape->redraw(canvas, 1.f);
526 canvas->restore();
527
528 // Now redraw it into the coverage location (just to the right of the original scale)
529 canvas->save();
530 canvas->translate(x + ShapeRenderer::kTileWidth + 8.f, 4.f);
531 canvas->drawRect(kTile, clear);
532 canvas->drawRect(kTile, outline);
533 shape->redraw(canvas, 1.f, /* debug */ true);
534 canvas->restore();
535
536 // Lastly, shift the canvas translation down by 8 * kTH + padding for the next set of shapes
537 canvas->translate(0.f, 8.f * ShapeRenderer::kTileHeight + 20.f);
538 }
539
540 typedef Sample INHERITED;
541};
542
543//////////////////////////////////////////////////////////////////////////////
544
545DEF_SAMPLE( return new ThinAASample; )
546
547}