| /* | |
| * Copyright 2019 Google LLC | |
| * | |
| * 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/SkFont.h" | |
| #include "include/core/SkFontTypes.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkM44.h" | |
| #include "include/core/SkMatrix.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/fonts/FontToolUtils.h" | |
| #include "tools/timer/TimeUtils.h" | |
| #include <array> | |
| // 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/40040313 | |
| class PosterCircleGM : public skiagm::GM { | |
| public: | |
| PosterCircleGM() : fTime(0.f) {} | |
| protected: | |
| SkString getName() const override { return SkString("poster_circle"); } | |
| SkISize getISize() 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 = ToolUtils::DefaultPortableFont(); | |
| font.setEdging(SkFont::Edging::kAntiAlias); | |
| font.setEmbolden(true); | |
| font.setSize(24.f); | |
| sk_sp<SkSurface> surface = | |
| SkSurfaces::Raster(SkImageInfo::MakeN32Premul(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. | |
| SkM44 proj; | |
| proj.setRC(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. | |
| SkM44 model = SkM44::Translate(kStageWidth/2, kStageHeight/2 + 25, 0) | |
| * proj | |
| * SkM44::Translate(0, ringY, 0) | |
| * SkM44::Rotate({0,1,0}, SkDegreesToRadians(yRotation)) | |
| * SkM44::Translate(0, 0, kRingRadius); | |
| canvas->concat(model); | |
| 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); | |
| canvas->drawImageRect(fPosterImages[i], poster, | |
| SkSamplingOptions(SkFilterMode::kLinear), &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; | |
| std::array<sk_sp<SkImage>, kNumAngles> fPosterImages; | |
| SkScalar fTime; | |
| }; | |
| DEF_GM(return new PosterCircleGM();) |