blob: 49a06ab3117cb16120bfdfd44988ba451b42edcf [file] [log] [blame]
Michael Ludwig784184a2019-04-30 13:28:26 -04001/*
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/SkFont.h"
11#include "include/core/SkImage.h"
12#include "include/core/SkRRect.h"
13#include "include/core/SkSurface.h"
14#include "tools/timer/AnimTimer.h"
15
16// Mimics https://output.jsbin.com/falefice/1/quiet?CC_POSTER_CIRCLE, which can't be captured as
17// an SKP due to many 3D layers being composited post-SKP capture.
18// See skbug.com/9028
19class PosterCircleGM : public skiagm::GM {
20public:
21 PosterCircleGM() : fTime(0.f) {}
22
23protected:
24
25 SkString onShortName() override {
26 return SkString("poster_circle");
27 }
28
29 SkISize onISize() override {
30 return SkISize::Make(kStageWidth, kStageHeight + 50);
31 }
32
33 bool onAnimate(const AnimTimer& timer) override {
34#if 0
35 if (timer.isRunning()) {
36 // Use a fixed timestep
37 fTime += 5e-4f;
38 }
39#else
40 fTime = timer.scaled(0.5f);
41#endif
42 return true;
43 }
44
45 void onOnceBeforeDraw() override {
46 SkFont font;
47 font.setEdging(SkFont::Edging::kAntiAlias);
48 font.setEmbolden(true);
49 font.setSize(24.f);
50
51 sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(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(), kUTF8_SkTextEncoding, &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
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 SkMatrix44 proj(SkMatrix44::kIdentity_Constructor);
82 proj.set(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 SkMatrix44 model;
119 // No post/preRotate, so start with rotation matrix and adjust from there
120 model.setRotateAboutUnit(0.f, 1.f, 0.f, SkDegreesToRadians(yRotation));
121 model.preTranslate(0.f, 0.f, kRingRadius); // *before* rotation
122 model.postTranslate(0.f, ringY, 0.f); // *after* rotation
123 model.postConcat(proj);
124 model.postTranslate(0.5f * kStageWidth, 0.5f * kStageHeight + 25, 0.f);
125
126 // Flatten the 4x4 matrix by discarding the 3rd row and column
127 canvas->concat(SkMatrix::MakeAll(
128 model.get(0, 0), model.get(0, 1), model.get(0, 3),
129 model.get(1, 0), model.get(1, 1), model.get(1, 3),
130 model.get(3, 0), model.get(3, 1), model.get(3, 3)));
131
132 SkRect poster = SkRect::MakeLTRB(-0.5f * kPosterSize, -0.5f * kPosterSize,
133 0.5f * kPosterSize, 0.5f * kPosterSize);
134 SkPaint fillPaint;
135 fillPaint.setAntiAlias(true);
136 fillPaint.setAlphaf(0.7f);
137 fillPaint.setFilterQuality(kLow_SkFilterQuality);
138 canvas->drawImageRect(fPosterImages[i], poster, &fillPaint);
139
140 canvas->restore();
141 }
142 }
143 }
144 }
145
146private:
147 static const int kAngleStep = 30;
148 static const int kNumAngles = 12; // 0 through 330 degrees
149
150 static const int kStageWidth = 600;
151 static const int kStageHeight = 400;
152 static const int kRingRadius = 200;
153 static const int kPosterSize = 100;
154
155 sk_sp<SkImage> fPosterImages[kNumAngles];
156 SkScalar fTime;
157};
158
159DEF_GM(return new PosterCircleGM();)