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