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