xref: /aosp_15_r20/external/skia/gm/postercircle.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkFontTypes.h"
13 #include "include/core/SkImage.h"
14 #include "include/core/SkM44.h"
15 #include "include/core/SkMatrix.h"
16 #include "include/core/SkPaint.h"
17 #include "include/core/SkRRect.h"
18 #include "include/core/SkRect.h"
19 #include "include/core/SkRefCnt.h"
20 #include "include/core/SkScalar.h"
21 #include "include/core/SkSize.h"
22 #include "include/core/SkString.h"
23 #include "include/core/SkSurface.h"
24 #include "tools/fonts/FontToolUtils.h"
25 #include "tools/timer/TimeUtils.h"
26 
27 // Mimics https://output.jsbin.com/falefice/1/quiet?CC_POSTER_CIRCLE, which can't be captured as
28 // an SKP due to many 3D layers being composited post-SKP capture.
29 // See skbug.com/9028
30 class PosterCircleGM : public skiagm::GM {
31 public:
PosterCircleGM()32     PosterCircleGM() : fTime(0.f) {}
33 
34 protected:
getName() const35     SkString getName() const override { return SkString("poster_circle"); }
36 
getISize()37     SkISize getISize() override { return SkISize::Make(kStageWidth, kStageHeight + 50); }
38 
onAnimate(double nanos)39     bool onAnimate(double nanos) override {
40         fTime = TimeUtils::Scaled(1e-9 * nanos, 0.5f);
41         return true;
42     }
43 
onOnceBeforeDraw()44     void onOnceBeforeDraw() override {
45         SkFont font = ToolUtils::DefaultPortableFont();
46         font.setEdging(SkFont::Edging::kAntiAlias);
47         font.setEmbolden(true);
48         font.setSize(24.f);
49 
50         sk_sp<SkSurface> surface =
51                 SkSurfaces::Raster(SkImageInfo::MakeN32Premul(kPosterSize, kPosterSize));
52         for (int i = 0; i < kNumAngles; ++i) {
53             SkCanvas* canvas = surface->getCanvas();
54 
55             SkPaint fillPaint;
56             fillPaint.setAntiAlias(true);
57             fillPaint.setColor(i % 2 == 0 ? SkColorSetRGB(0x99, 0x5C, 0x7F)
58                                           : SkColorSetRGB(0x83, 0x5A, 0x99));
59             canvas->drawRRect(SkRRect::MakeRectXY(SkRect::MakeWH(kPosterSize, kPosterSize),
60                                                   10.f, 10.f), fillPaint);
61 
62             SkString label;
63             label.printf("%d", i);
64             SkRect labelBounds;
65             font.measureText(label.c_str(), label.size(), SkTextEncoding::kUTF8, &labelBounds);
66             SkScalar labelX = 0.5f * kPosterSize - 0.5f * labelBounds.width();
67             SkScalar labelY = 0.5f * kPosterSize + 0.5f * labelBounds.height();
68 
69 
70             SkPaint labelPaint;
71             labelPaint.setAntiAlias(true);
72             canvas->drawString(label, labelX, labelY, font, labelPaint);
73 
74             fPosterImages[i] = surface->makeImageSnapshot();
75         }
76     }
77 
onDraw(SkCanvas * canvas)78     void onDraw(SkCanvas* canvas) override {
79         // See https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/perspective
80         // for projection matrix when --webkit-perspective: 800px is used.
81         SkM44 proj;
82         proj.setRC(3, 2, -1.f / 800.f);
83 
84         for (int pass = 0; pass < 2; ++pass) {
85             // Want to draw 90 to 270 first (the back), then 270 to 90 (the front), but do all 3
86             // rings backsides, then their frontsides since the front projections overlap across
87             // rings. Note: we skip the poster circle's x axis rotation because that complicates the
88             // back-to-front drawing order and it isn't necessary to trigger draws aligned with Z.
89             bool drawFront = pass > 0;
90 
91             for (int y = 0; y < 3; ++y) {
92                 float ringY = (y - 1) * (kPosterSize + 10.f);
93                 for (int i = 0; i < kNumAngles; ++i) {
94                     // Add an extra 45 degree rotation, which triggers the bug by aligning some of
95                     // the posters with the z axis.
96                     SkScalar yDuration = 5.f - y;
97                     SkScalar yRotation = SkScalarMod(kAngleStep * i +
98                             360.f * SkScalarMod(fTime / yDuration, yDuration), 360.f);
99                     // These rotation limits were chosen manually to line up with current projection
100                     static constexpr SkScalar kBackMinAngle = 70.f;
101                     static constexpr SkScalar kBackMaxAngle = 290.f;
102                     if (drawFront) {
103                         if (yRotation >= kBackMinAngle && yRotation <= kBackMaxAngle) {
104                             // Back portion during a front draw
105                             continue;
106                         }
107                     } else {
108                         if (yRotation < kBackMinAngle || yRotation > kBackMaxAngle) {
109                             // Front portion during a back draw
110                             continue;
111                         }
112                     }
113 
114                     canvas->save();
115 
116                     // Matrix matches transform: rotateY(<angle>deg) translateZ(200px); nested in an
117                     // element with the perspective projection matrix above.
118                     SkM44 model = SkM44::Translate(kStageWidth/2, kStageHeight/2 + 25, 0)
119                                 * proj
120                                 * SkM44::Translate(0, ringY, 0)
121                                 * SkM44::Rotate({0,1,0}, SkDegreesToRadians(yRotation))
122                                 * SkM44::Translate(0, 0, kRingRadius);
123                     canvas->concat(model);
124 
125                     SkRect poster = SkRect::MakeLTRB(-0.5f * kPosterSize, -0.5f * kPosterSize,
126                                                       0.5f * kPosterSize,  0.5f * kPosterSize);
127                     SkPaint fillPaint;
128                     fillPaint.setAntiAlias(true);
129                     fillPaint.setAlphaf(0.7f);
130                     canvas->drawImageRect(fPosterImages[i], poster,
131                                           SkSamplingOptions(SkFilterMode::kLinear), &fillPaint);
132 
133                     canvas->restore();
134                 }
135             }
136         }
137     }
138 
139 private:
140     static const int kAngleStep = 30;
141     static const int kNumAngles = 12; // 0 through 330 degrees
142 
143     static const int kStageWidth = 600;
144     static const int kStageHeight = 400;
145     static const int kRingRadius = 200;
146     static const int kPosterSize = 100;
147 
148     sk_sp<SkImage> fPosterImages[kNumAngles];
149     SkScalar fTime;
150 };
151 
152 DEF_GM(return new PosterCircleGM();)
153