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