• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/SkMatrix.h"
16  #include "include/core/SkMatrix44.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          SkMatrix44 proj(SkMatrix44::kIdentity_Constructor);
86          proj.set(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                      SkMatrix44 model;
123                      // No post/preRotate, so start with rotation matrix and adjust from there
124                      model.setRotateAboutUnit(0.f, 1.f, 0.f, SkDegreesToRadians(yRotation));
125                      model.preTranslate(0.f, 0.f, kRingRadius); // *before* rotation
126                      model.postTranslate(0.f, ringY, 0.f);      // *after* rotation
127                      model.postConcat(proj);
128                      model.postTranslate(0.5f * kStageWidth, 0.5f * kStageHeight + 25, 0.f);
129  
130                      // Flatten the 4x4 matrix by discarding the 3rd row and column
131                      canvas->concat(SkMatrix::MakeAll(
132                              model.get(0, 0), model.get(0, 1), model.get(0, 3),
133                              model.get(1, 0), model.get(1, 1), model.get(1, 3),
134                              model.get(3, 0), model.get(3, 1), model.get(3, 3)));
135  
136                      SkRect poster = SkRect::MakeLTRB(-0.5f * kPosterSize, -0.5f * kPosterSize,
137                                                        0.5f * kPosterSize,  0.5f * kPosterSize);
138                      SkPaint fillPaint;
139                      fillPaint.setAntiAlias(true);
140                      fillPaint.setAlphaf(0.7f);
141                      fillPaint.setFilterQuality(kLow_SkFilterQuality);
142                      canvas->drawImageRect(fPosterImages[i], poster, &fillPaint);
143  
144                      canvas->restore();
145                  }
146              }
147          }
148      }
149  
150  private:
151      static const int kAngleStep = 30;
152      static const int kNumAngles = 12; // 0 through 330 degrees
153  
154      static const int kStageWidth = 600;
155      static const int kStageHeight = 400;
156      static const int kRingRadius = 200;
157      static const int kPosterSize = 100;
158  
159      sk_sp<SkImage> fPosterImages[kNumAngles];
160      SkScalar fTime;
161  };
162  
163  DEF_GM(return new PosterCircleGM();)
164