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