| /* | 
 |  * Copyright 2019 Google Inc. | 
 |  * | 
 |  * Use of this source code is governed by a BSD-style license that can be | 
 |  * found in the LICENSE file. | 
 |  */ | 
 |  | 
 | #include "gm/gm.h" | 
 | #include "include/core/SkCanvas.h" | 
 | #include "include/core/SkColor.h" | 
 | #include "include/core/SkFilterQuality.h" | 
 | #include "include/core/SkFont.h" | 
 | #include "include/core/SkFontTypes.h" | 
 | #include "include/core/SkImage.h" | 
 | #include "include/core/SkMatrix.h" | 
 | #include "include/core/SkMatrix44.h" | 
 | #include "include/core/SkPaint.h" | 
 | #include "include/core/SkRRect.h" | 
 | #include "include/core/SkRect.h" | 
 | #include "include/core/SkRefCnt.h" | 
 | #include "include/core/SkScalar.h" | 
 | #include "include/core/SkSize.h" | 
 | #include "include/core/SkString.h" | 
 | #include "include/core/SkSurface.h" | 
 | #include "tools/timer/TimeUtils.h" | 
 |  | 
 | // Mimics https://output.jsbin.com/falefice/1/quiet?CC_POSTER_CIRCLE, which can't be captured as | 
 | // an SKP due to many 3D layers being composited post-SKP capture. | 
 | // See skbug.com/9028 | 
 | class PosterCircleGM : public skiagm::GM { | 
 | public: | 
 |     PosterCircleGM() : fTime(0.f) {} | 
 |  | 
 | protected: | 
 |  | 
 |     SkString onShortName() override { | 
 |         return SkString("poster_circle"); | 
 |     } | 
 |  | 
 |     SkISize onISize() override { | 
 |         return SkISize::Make(kStageWidth, kStageHeight + 50); | 
 |     } | 
 |  | 
 |     bool onAnimate(double nanos) override { | 
 |         fTime = TimeUtils::Scaled(1e-9 * nanos, 0.5f); | 
 |         return true; | 
 |     } | 
 |  | 
 |     void onOnceBeforeDraw() override { | 
 |         SkFont font; | 
 |         font.setEdging(SkFont::Edging::kAntiAlias); | 
 |         font.setEmbolden(true); | 
 |         font.setSize(24.f); | 
 |  | 
 |         sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(kPosterSize, kPosterSize); | 
 |         for (int i = 0; i < kNumAngles; ++i) { | 
 |             SkCanvas* canvas = surface->getCanvas(); | 
 |  | 
 |             SkPaint fillPaint; | 
 |             fillPaint.setAntiAlias(true); | 
 |             fillPaint.setColor(i % 2 == 0 ? SkColorSetRGB(0x99, 0x5C, 0x7F) | 
 |                                           : SkColorSetRGB(0x83, 0x5A, 0x99)); | 
 |             canvas->drawRRect(SkRRect::MakeRectXY(SkRect::MakeWH(kPosterSize, kPosterSize), | 
 |                                                   10.f, 10.f), fillPaint); | 
 |  | 
 |             SkString label; | 
 |             label.printf("%d", i); | 
 |             SkRect labelBounds; | 
 |             font.measureText(label.c_str(), label.size(), SkTextEncoding::kUTF8, &labelBounds); | 
 |             SkScalar labelX = 0.5f * kPosterSize - 0.5f * labelBounds.width(); | 
 |             SkScalar labelY = 0.5f * kPosterSize + 0.5f * labelBounds.height(); | 
 |  | 
 |  | 
 |             SkPaint labelPaint; | 
 |             labelPaint.setAntiAlias(true); | 
 |             canvas->drawString(label, labelX, labelY, font, labelPaint); | 
 |  | 
 |             fPosterImages[i] = surface->makeImageSnapshot(); | 
 |         } | 
 |     } | 
 |  | 
 |     void onDraw(SkCanvas* canvas) override { | 
 |         // See https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/perspective | 
 |         // for projection matrix when --webkit-perspective: 800px is used. | 
 |         SkMatrix44 proj(SkMatrix44::kIdentity_Constructor); | 
 |         proj.set(3, 2, -1.f / 800.f); | 
 |  | 
 |         for (int pass = 0; pass < 2; ++pass) { | 
 |             // Want to draw 90 to 270 first (the back), then 270 to 90 (the front), but do all 3 | 
 |             // rings backsides, then their frontsides since the front projections overlap across | 
 |             // rings. Note: we skip the poster circle's x axis rotation because that complicates the | 
 |             // back-to-front drawing order and it isn't necessary to trigger draws aligned with Z. | 
 |             bool drawFront = pass > 0; | 
 |  | 
 |             for (int y = 0; y < 3; ++y) { | 
 |                 float ringY = (y - 1) * (kPosterSize + 10.f); | 
 |                 for (int i = 0; i < kNumAngles; ++i) { | 
 |                     // Add an extra 45 degree rotation, which triggers the bug by aligning some of | 
 |                     // the posters with the z axis. | 
 |                     SkScalar yDuration = 5.f - y; | 
 |                     SkScalar yRotation = SkScalarMod(kAngleStep * i + | 
 |                             360.f * SkScalarMod(fTime / yDuration, yDuration), 360.f); | 
 |                     // These rotation limits were chosen manually to line up with current projection | 
 |                     static constexpr SkScalar kBackMinAngle = 70.f; | 
 |                     static constexpr SkScalar kBackMaxAngle = 290.f; | 
 |                     if (drawFront) { | 
 |                         if (yRotation >= kBackMinAngle && yRotation <= kBackMaxAngle) { | 
 |                             // Back portion during a front draw | 
 |                             continue; | 
 |                         } | 
 |                     } else { | 
 |                         if (yRotation < kBackMinAngle || yRotation > kBackMaxAngle) { | 
 |                             // Front portion during a back draw | 
 |                             continue; | 
 |                         } | 
 |                     } | 
 |  | 
 |                     canvas->save(); | 
 |  | 
 |                     // Matrix matches transform: rotateY(<angle>deg) translateZ(200px); nested in an | 
 |                     // element with the perspective projection matrix above. | 
 |                     SkMatrix44 model; | 
 |                     // No post/preRotate, so start with rotation matrix and adjust from there | 
 |                     model.setRotateAboutUnit(0.f, 1.f, 0.f, SkDegreesToRadians(yRotation)); | 
 |                     model.preTranslate(0.f, 0.f, kRingRadius); // *before* rotation | 
 |                     model.postTranslate(0.f, ringY, 0.f);      // *after* rotation | 
 |                     model.postConcat(proj); | 
 |                     model.postTranslate(0.5f * kStageWidth, 0.5f * kStageHeight + 25, 0.f); | 
 |  | 
 |                     // Flatten the 4x4 matrix by discarding the 3rd row and column | 
 |                     canvas->concat(SkMatrix::MakeAll( | 
 |                             model.get(0, 0), model.get(0, 1), model.get(0, 3), | 
 |                             model.get(1, 0), model.get(1, 1), model.get(1, 3), | 
 |                             model.get(3, 0), model.get(3, 1), model.get(3, 3))); | 
 |  | 
 |                     SkRect poster = SkRect::MakeLTRB(-0.5f * kPosterSize, -0.5f * kPosterSize, | 
 |                                                       0.5f * kPosterSize,  0.5f * kPosterSize); | 
 |                     SkPaint fillPaint; | 
 |                     fillPaint.setAntiAlias(true); | 
 |                     fillPaint.setAlphaf(0.7f); | 
 |                     fillPaint.setFilterQuality(kLow_SkFilterQuality); | 
 |                     canvas->drawImageRect(fPosterImages[i], poster, &fillPaint); | 
 |  | 
 |                     canvas->restore(); | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 | private: | 
 |     static const int kAngleStep = 30; | 
 |     static const int kNumAngles = 12; // 0 through 330 degrees | 
 |  | 
 |     static const int kStageWidth = 600; | 
 |     static const int kStageHeight = 400; | 
 |     static const int kRingRadius = 200; | 
 |     static const int kPosterSize = 100; | 
 |  | 
 |     sk_sp<SkImage> fPosterImages[kNumAngles]; | 
 |     SkScalar fTime; | 
 | }; | 
 |  | 
 | DEF_GM(return new PosterCircleGM();) |