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