| /* | |
| * Copyright 2013 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/effects/SkGradient.h" | |
| #include "src/core/SkRandom.h" | |
| #include "tools/Resources.h" | |
| #include "tools/gpu/YUVUtils.h" | |
| #include "tools/viewer/Slide.h" | |
| #include <array> | |
| #include <vector> | |
| // Implementation in C++ of some WebKit MotionMark tests | |
| // Tests implemented so far: | |
| // * Canvas Lines | |
| // * Canvas Arcs | |
| // * Paths | |
| // Based on https://github.com/WebKit/MotionMark/blob/main/MotionMark/ | |
| class MMObject { | |
| public: | |
| virtual ~MMObject() = default; | |
| virtual void draw(SkCanvas* canvas) = 0; | |
| virtual void animate(double /*nanos*/) = 0; | |
| }; | |
| class Stage { | |
| public: | |
| Stage(SkSize size, int startingObjectCount, int objectIncrement) | |
| : fSize(size) | |
| , fStartingObjectCount(startingObjectCount) | |
| , fObjectIncrement(objectIncrement) {} | |
| virtual ~Stage() = default; | |
| // The default impls of draw() and animate() simply iterate over fObjects and call the | |
| // MMObject function. | |
| virtual void draw(SkCanvas* canvas) { | |
| for (size_t i = 0; i < fObjects.size(); ++i) { | |
| fObjects[i]->draw(canvas); | |
| } | |
| } | |
| virtual bool animate(double nanos) { | |
| for (size_t i = 0; i < fObjects.size(); ++i) { | |
| fObjects[i]->animate(nanos); | |
| } | |
| return true; | |
| } | |
| // The default impl handles +/- to add or remove N objects from the scene | |
| virtual bool onChar(SkUnichar uni) { | |
| bool handled = false; | |
| switch (uni) { | |
| case '+': | |
| case '=': | |
| for (int i = 0; i < fObjectIncrement; ++i) { | |
| fObjects.push_back(this->createObject()); | |
| } | |
| handled = true; | |
| break; | |
| case '-': | |
| case '_': | |
| if (fObjects.size() > (size_t) fObjectIncrement) { | |
| fObjects.resize(fObjects.size() - (size_t) fObjectIncrement); | |
| } | |
| handled = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| return handled; | |
| } | |
| protected: | |
| virtual std::unique_ptr<MMObject> createObject() = 0; | |
| void initializeObjects() { | |
| for (int i = 0; i < fStartingObjectCount; ++i) { | |
| fObjects.push_back(this->createObject()); | |
| } | |
| } | |
| [[maybe_unused]] SkSize fSize; | |
| int fStartingObjectCount; | |
| int fObjectIncrement; | |
| std::vector<std::unique_ptr<MMObject>> fObjects; | |
| SkRandom fRandom; | |
| }; | |
| class MotionMarkSlide : public Slide { | |
| public: | |
| MotionMarkSlide() = default; | |
| bool onChar(SkUnichar uni) override { | |
| return fStage->onChar(uni); | |
| } | |
| void draw(SkCanvas* canvas) override { | |
| fStage->draw(canvas); | |
| } | |
| bool animate(double nanos) override { | |
| return fStage->animate(nanos); | |
| } | |
| protected: | |
| std::unique_ptr<Stage> fStage; | |
| }; | |
| namespace { | |
| float time_counter_value(double nanos, float factor) { | |
| constexpr double kMillisPerNano = 0.0000001; | |
| return static_cast<float>(nanos*kMillisPerNano)/factor; | |
| } | |
| float time_fractional_value(double nanos, float cycleLengthMs) { | |
| return SkScalarFraction(time_counter_value(nanos, cycleLengthMs)); | |
| } | |
| // The following functions match the input processing that Chrome's canvas2d layer performs before | |
| // calling into Skia. | |
| // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc;drc=572074cb06425797e7e110511db405134cf67e2f;l=299 | |
| void canonicalize_angle(float* startAngle, float* endAngle) { | |
| float newStartAngle = SkScalarMod(*startAngle, 360.f); | |
| float delta = newStartAngle - *startAngle; | |
| *startAngle = newStartAngle; | |
| *endAngle = *endAngle + delta; | |
| } | |
| // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc;drc=572074cb06425797e7e110511db405134cf67e2f;l=245 | |
| float adjust_end_angle(float startAngle, float endAngle, bool ccw) { | |
| float newEndAngle = endAngle; | |
| if (!ccw && endAngle - startAngle >= 360.f) { | |
| newEndAngle = startAngle + 360.f; | |
| } else if (ccw && startAngle - endAngle >= 360.f) { | |
| newEndAngle = startAngle - 360.f; | |
| } else if (!ccw && startAngle > endAngle) { | |
| newEndAngle = startAngle + (360.f - SkScalarMod(startAngle - endAngle, 360.f)); | |
| } else if (ccw && startAngle < endAngle) { | |
| newEndAngle = startAngle - (360.f - SkScalarMod(endAngle - startAngle, 360.f)); | |
| } | |
| return newEndAngle; | |
| } | |
| } // namespace | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| // Canvas Lines | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| struct LineSegmentParams { | |
| float fCircleRadius; | |
| std::array<SkPoint, 4> fCircleCenters; | |
| float fLineLengthMaximum; | |
| float fLineMinimum; | |
| }; | |
| class CanvasLineSegment : public MMObject { | |
| public: | |
| CanvasLineSegment(SkRandom* random, const LineSegmentParams& params) { | |
| int circle = random->nextRangeU(0, 3); | |
| static constexpr auto kColors = std::to_array<SkColor>({ | |
| 0xffe01040, | |
| 0xff10c030, | |
| 0xff744cba, | |
| 0xffe05010, | |
| }); | |
| fColor = kColors[circle]; | |
| fLineWidth = std::pow(random->nextF(), 12) * 20 + 3; | |
| fOmega = random->nextF() * 3 + 0.2f; | |
| float theta = random->nextRangeF(0, 2*SK_ScalarPI); | |
| fCosTheta = std::cos(theta); | |
| fSinTheta = std::sin(theta); | |
| fStart = params.fCircleCenters[circle] + SkPoint::Make(params.fCircleRadius * fCosTheta, | |
| params.fCircleRadius * fSinTheta); | |
| fLength = params.fLineMinimum; | |
| fLength += std::pow(random->nextF(), 8) * params.fLineLengthMaximum; | |
| fSegmentDirection = random->nextF() > 0.5 ? -1 : 1; | |
| } | |
| ~CanvasLineSegment() override = default; | |
| void draw(SkCanvas* canvas) override { | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setColor(fColor); | |
| paint.setStrokeWidth(fLineWidth); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| SkPoint end = { | |
| fStart.fX + fSegmentDirection * fLength * fCosTheta, | |
| fStart.fY + fSegmentDirection * fLength * fSinTheta | |
| }; | |
| canvas->drawLine(fStart, end, paint); | |
| } | |
| void animate(double nanos) override { | |
| fLength += std::sin(time_counter_value(nanos, 100) * fOmega); | |
| } | |
| private: | |
| SkColor fColor; | |
| float fLineWidth; | |
| float fOmega; | |
| float fCosTheta; | |
| float fSinTheta; | |
| SkPoint fStart; | |
| float fLength; | |
| float fSegmentDirection; | |
| }; | |
| class CanvasLineSegmentStage : public Stage { | |
| public: | |
| CanvasLineSegmentStage(SkSize size) | |
| : Stage(size, /*startingObjectCount=*/5000, /*objectIncrement*/1000) { | |
| fParams.fLineMinimum = 20; | |
| fParams.fLineLengthMaximum = 40; | |
| fParams.fCircleRadius = fSize.fWidth/8 - .4 * (fParams.fLineMinimum + | |
| fParams.fLineLengthMaximum); | |
| fParams.fCircleCenters[0] = SkPoint::Make(5.5 / 32 * fSize.fWidth, 2.1 / 3*fSize.fHeight); | |
| fParams.fCircleCenters[1] = SkPoint::Make(12.5 / 32 * fSize.fWidth, .9 / 3*fSize.fHeight); | |
| fParams.fCircleCenters[2] = SkPoint::Make(19.5 / 32 * fSize.fWidth, 2.1 / 3*fSize.fHeight); | |
| fParams.fCircleCenters[3] = SkPoint::Make(26.5 / 32 * fSize.fWidth, .9 / 3*fSize.fHeight); | |
| fHalfSize = SkSize::Make(fSize.fWidth * 0.5f, fSize.fHeight * 0.5f); | |
| fTwoFifthsSizeX = fSize.fWidth * .4; | |
| this->initializeObjects(); | |
| } | |
| ~CanvasLineSegmentStage() override = default; | |
| void draw(SkCanvas* canvas) override { | |
| canvas->clear(SkColors::kWhite); | |
| float dx = fTwoFifthsSizeX * std::cos(fCurrentAngle); | |
| float dy = fTwoFifthsSizeX * std::sin(fCurrentAngle); | |
| float colorStopStep = SkScalarInterp(-.1f, .1f, fCurrentGradientStep); | |
| int brightnessStep = SkScalarRoundToInt(SkScalarInterp(32, 64, fCurrentGradientStep)); | |
| SkColor4f color1Step = SkColor4f::FromColor(SkColorSetARGB(brightnessStep, | |
| brightnessStep, | |
| (brightnessStep << 1), | |
| 102)), | |
| color2Step = SkColor4f::FromColor(SkColorSetARGB((brightnessStep << 1), | |
| (brightnessStep << 1), | |
| brightnessStep, | |
| 102)); | |
| SkPoint pts[2] = { | |
| {fHalfSize.fWidth + dx, fHalfSize.fHeight + dy}, | |
| {fHalfSize.fWidth - dx, fHalfSize.fHeight - dy} | |
| }; | |
| const SkColor4f colors[] = { | |
| color1Step, | |
| color1Step, | |
| color2Step, | |
| color2Step | |
| }; | |
| const float pos[] = { | |
| 0, | |
| 0.2f + colorStopStep, | |
| 0.8f - colorStopStep, | |
| 1 | |
| }; | |
| sk_sp<SkShader> gradientShader = SkShaders::LinearGradient(pts, | |
| {{colors, pos, SkTileMode::kClamp}, {}}); | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setStrokeWidth(15); | |
| for (int i = 0; i < 4; i++) { | |
| static constexpr auto strokeColors = std::to_array<SkColor>({ | |
| 0xffe01040, | |
| 0xff10c030, | |
| 0xff744cba, | |
| 0xffe05010, | |
| }); | |
| static constexpr auto fillColors = std::to_array<SkColor>({ | |
| 0xff70051d, | |
| 0xff016112, | |
| 0xff2F0C6E, | |
| 0xff702701, | |
| }); | |
| paint.setColor(strokeColors[i]); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| SkRect arcRect = SkRect::MakeXYWH(fParams.fCircleCenters[i].fX - fParams.fCircleRadius, | |
| fParams.fCircleCenters[i].fY- fParams.fCircleRadius, | |
| 2*fParams.fCircleRadius, | |
| 2*fParams.fCircleRadius); | |
| canvas->drawArc(arcRect, 0, 360, false, paint); | |
| paint.setColor(fillColors[i]); | |
| paint.setStyle(SkPaint::kFill_Style); | |
| canvas->drawArc(arcRect, 0, 360, false, paint); | |
| paint.setShader(gradientShader); | |
| canvas->drawArc(arcRect, 0, 360, false, paint); | |
| paint.setShader(nullptr); | |
| } | |
| this->Stage::draw(canvas); | |
| } | |
| bool animate(double nanos) override { | |
| fCurrentAngle = time_fractional_value(nanos, 3000) * SK_ScalarPI * 2; | |
| fCurrentGradientStep = 0.5f + 0.5f * std::sin( | |
| time_fractional_value(nanos, 5000) * SK_ScalarPI * 2); | |
| this->Stage::animate(nanos); | |
| return true; | |
| } | |
| std::unique_ptr<MMObject> createObject() override { | |
| return std::make_unique<CanvasLineSegment>(&fRandom,fParams); | |
| } | |
| private: | |
| LineSegmentParams fParams; | |
| SkSize fHalfSize; | |
| float fTwoFifthsSizeX; | |
| float fCurrentAngle = 0; | |
| float fCurrentGradientStep = 0.5f; | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| // Canvas Arcs | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| class CanvasArc : public MMObject { | |
| public: | |
| CanvasArc(SkRandom* random, SkSize size) { | |
| constexpr float kMaxX = 6; | |
| constexpr float kMaxY = 3; | |
| static constexpr std::array<SkColor, 3> baseColors = {0xff101010, 0xff808080, 0xffc0c0c0}; | |
| static constexpr std::array<SkColor, 3> bonusColors = {0xffe01040, 0xff10c030, 0xffe05010}; | |
| float distanceX = size.fWidth / kMaxX; | |
| float distanceY = size.fHeight / (kMaxY + 1); | |
| int randY = random->nextRangeU(0, kMaxY); | |
| int randX = random->nextRangeU(0, kMaxX - 1 * (randY % 2)); | |
| fPoint = SkPoint::Make(distanceX * (randX + (randY % 2) / 2), distanceY * (randY + 0.5f)); | |
| fRadius = 20 + std::pow(random->nextF(), 5) * (std::min(distanceX, distanceY) / 1.8f); | |
| fStartAngle = random->nextRangeF(0, 2*SK_ScalarPI); | |
| fEndAngle = random->nextRangeF(0, 2*SK_ScalarPI); | |
| fOmega = (random->nextF() - 0.5f) * 0.3f; | |
| fCounterclockwise = random->nextBool(); | |
| // The MotionMark code appends a random element from an array and appends it to the color | |
| // array, then randomly picks from that. We'll just pick that random element and use it | |
| // if the index is out of bounds for the base color array. | |
| SkColor bonusColor = bonusColors[(randX + sk_float_ceil2int(randY * 0.5f)) % 3]; | |
| int colorIndex = random->nextRangeU(0, 3); | |
| fColor = colorIndex == 3 ? bonusColor : baseColors[colorIndex]; | |
| fLineWidth = 1 + std::pow(random->nextF(), 5) * 30; | |
| fDoStroke = random->nextRangeU(0, 3) != 0; | |
| } | |
| ~CanvasArc() override = default; | |
| void draw(SkCanvas* canvas) override { | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setColor(fColor); | |
| SkRect arcRect = SkRect::MakeXYWH(fPoint.fX - fRadius, fPoint.fY - fRadius, | |
| 2*fRadius, 2*fRadius); | |
| float startAngleDeg = fStartAngle * 180.f / SK_ScalarPI; | |
| float endAngleDeg = fEndAngle * 180.f / SK_ScalarPI; | |
| canonicalize_angle(&startAngleDeg, &endAngleDeg); | |
| endAngleDeg = adjust_end_angle(startAngleDeg, endAngleDeg, fCounterclockwise); | |
| float sweepAngle = startAngleDeg - endAngleDeg; | |
| if (fDoStroke) { | |
| paint.setStrokeWidth(fLineWidth); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| canvas->drawArc(arcRect, startAngleDeg, sweepAngle, false, paint); | |
| } else { | |
| paint.setStyle(SkPaint::kFill_Style); | |
| // The MotionMark code creates a path for fills via lineTo(point), arc(), lineTo(point). | |
| // For now we'll just use drawArc for both but might need to revisit. | |
| canvas->drawArc(arcRect, startAngleDeg, sweepAngle, true, paint); | |
| } | |
| } | |
| void animate(double /*nanos*/) override { | |
| fStartAngle += fOmega; | |
| fEndAngle += fOmega / 2; | |
| } | |
| private: | |
| SkPoint fPoint; | |
| float fRadius; | |
| float fStartAngle; // in radians | |
| float fEndAngle; // in radians | |
| SkColor fColor; | |
| float fOmega; // in radians | |
| bool fDoStroke; | |
| bool fCounterclockwise; | |
| float fLineWidth; | |
| }; | |
| class CanvasArcStage : public Stage { | |
| public: | |
| CanvasArcStage(SkSize size) | |
| : Stage(size, /*startingObjectCount=*/1000, /*objectIncrement=*/200) { | |
| this->initializeObjects(); | |
| } | |
| ~CanvasArcStage() override = default; | |
| void draw(SkCanvas* canvas) override { | |
| canvas->clear(SK_ColorWHITE); | |
| this->Stage::draw(canvas); | |
| } | |
| std::unique_ptr<MMObject> createObject() override { | |
| return std::make_unique<CanvasArc>(&fRandom, fSize); | |
| } | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| // Paths | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| class CanvasLinePoint : public MMObject { | |
| protected: | |
| void setEndPoint(SkRandom* random, SkSize size, SkPoint* prevCoord) { | |
| const SkSize kGridSize = { 80, 40 }; | |
| const SkPoint kGridCenter = { 40, 20 }; | |
| static constexpr std::array<SkPoint, 4> kOffsets = {{ | |
| {-4, 0}, | |
| {2, 0}, | |
| {1, -2}, | |
| {1, 2}, | |
| }}; | |
| SkPoint coordinate = prevCoord ? *prevCoord : kGridCenter; | |
| if (prevCoord) { | |
| SkPoint offset = kOffsets[random->nextRangeU(0, 3)]; | |
| coordinate += offset; | |
| if (coordinate.fX < 0 || coordinate.fX > kGridSize.width()) | |
| coordinate.fX -= offset.fX * 2; | |
| if (coordinate.fY < 0 || coordinate.fY > kGridSize.height()) | |
| coordinate.fY -= offset.fY * 2; | |
| } | |
| fPoint = SkPoint::Make((coordinate.fX + 0.5f) * size.width() / (kGridSize.width() + 1), | |
| (coordinate.fY + 0.5f) * size.height() / (kGridSize.height() + 1)); | |
| fCoordinate = coordinate; | |
| } | |
| public: | |
| CanvasLinePoint(SkRandom* random, SkSize size, SkPoint* prev) { | |
| static constexpr std::array<SkColor, 7> kColors = { | |
| 0xff101010, 0xff808080, 0xffc0c0c0, 0xff101010, 0xff808080, 0xffc0c0c0, 0xffe01040}; | |
| fColor = kColors[random->nextRangeU(0, 6)]; | |
| fWidth = std::pow(random->nextF(), 5) * 20 + 1; | |
| fIsSplit = random->nextBool(); | |
| this->setEndPoint(random, size, prev); | |
| } | |
| ~CanvasLinePoint() override = default; | |
| virtual void append(SkPathBuilder* path) { | |
| path->lineTo(fPoint); | |
| } | |
| // unused, all the work is done by append | |
| void draw(SkCanvas*) override {} | |
| void animate(double) override {} | |
| SkColor getColor() { return fColor; } | |
| float getWidth() { return fWidth; } | |
| SkPoint getPoint() { return fPoint; } | |
| SkPoint getCoord() { return fCoordinate; } | |
| bool isSplit() { return fIsSplit; } | |
| void toggleIsSplit() { fIsSplit = !fIsSplit; } | |
| private: | |
| SkPoint fPoint; | |
| SkPoint fCoordinate; | |
| SkColor fColor; | |
| float fWidth; | |
| bool fIsSplit; | |
| }; | |
| class CanvasQuadraticSegment : public CanvasLinePoint { | |
| public: | |
| CanvasQuadraticSegment(SkRandom* random, SkSize size, SkPoint* prev) | |
| : CanvasLinePoint(random, size, prev) { | |
| // Note: The construction of these points is odd but mirrors the Javascript code. | |
| // The chosen point from the base constructor is instead the control point. | |
| fPoint2 = this->getPoint(); | |
| // Get another random point for the actual end point of the segment. | |
| this->setEndPoint(random, size, prev); | |
| } | |
| void append(SkPathBuilder* path) override { | |
| path->quadTo(fPoint2, this->getPoint()); | |
| } | |
| private: | |
| SkPoint fPoint2; | |
| }; | |
| class CanvasBezierSegment : public CanvasLinePoint { | |
| public: | |
| CanvasBezierSegment(SkRandom* random, SkSize size, SkPoint* prev) | |
| : CanvasLinePoint(random, size, prev) { | |
| // Note: The construction of these points is odd but mirrors the Javascript code. | |
| // The chosen point from the base constructor is instead the control point. | |
| fPoint2 = this->getPoint(); | |
| // Get the second control point. | |
| this->setEndPoint(random, size, prev); | |
| fPoint3 = this->getPoint(); | |
| // Get third random point for the actual end point of the segment. | |
| this->setEndPoint(random, size, prev); | |
| } | |
| void append(SkPathBuilder* path) override { | |
| path->cubicTo(fPoint2, fPoint3, this->getPoint()); | |
| } | |
| private: | |
| SkPoint fPoint2; | |
| SkPoint fPoint3; | |
| }; | |
| std::unique_ptr<CanvasLinePoint> make_line_path(SkRandom* random, SkSize size, SkPoint* prev) { | |
| int choice = random->nextRangeU(0, 3); | |
| switch (choice) { | |
| case 0: return std::make_unique<CanvasQuadraticSegment>(random, size, prev); | |
| case 1: return std::make_unique<CanvasBezierSegment>(random, size, prev); | |
| case 2: [[fallthrough]]; | |
| case 3: [[fallthrough]]; | |
| default: return std::make_unique<CanvasLinePoint>(random, size, prev); | |
| } | |
| SkUNREACHABLE; | |
| } | |
| class CanvasLinePathStage : public Stage { | |
| public: | |
| CanvasLinePathStage(SkSize size) | |
| : Stage(size, /*startingObjectCount=*/5000, /*objectIncrement=*/1000) { | |
| this->initializeObjects(); | |
| } | |
| ~CanvasLinePathStage() override = default; | |
| void draw(SkCanvas* canvas) override { | |
| canvas->clear(SK_ColorWHITE); | |
| SkPathBuilder currentPath; | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| for (size_t i = 0; i < fObjects.size(); ++i) { | |
| CanvasLinePoint* object = reinterpret_cast<CanvasLinePoint*>(fObjects[i].get()); | |
| if (i == 0) { | |
| paint.setStrokeWidth(object->getWidth()); | |
| paint.setColor(object->getColor()); | |
| currentPath.moveTo(object->getPoint()); | |
| } else { | |
| object->append(¤tPath); | |
| if (object->isSplit()) { | |
| canvas->drawPath(currentPath.detach(), paint); | |
| paint.setStrokeWidth(object->getWidth()); | |
| paint.setColor(object->getColor()); | |
| currentPath.moveTo(object->getPoint()); | |
| } | |
| if (fRandom.nextF() > 0.995) { | |
| object->toggleIsSplit(); | |
| } | |
| } | |
| } | |
| canvas->drawPath(currentPath.detach(), paint); | |
| } | |
| bool animate(double /*nanos*/) override { | |
| // Nothing to do, but return true so we redraw. | |
| return true; | |
| } | |
| std::unique_ptr<MMObject> createObject() override { | |
| if (fObjects.empty()) { | |
| return make_line_path(&fRandom, fSize, nullptr); | |
| } else { | |
| CanvasLinePoint* prevObject = reinterpret_cast<CanvasLinePoint*>(fObjects.back().get()); | |
| SkPoint coord = prevObject->getCoord(); | |
| return make_line_path(&fRandom, fSize, &coord); | |
| } | |
| } | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| // Bouncing Particles | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| SkPoint random_position(SkRandom* random, SkSize maxPosition) { | |
| return {(float)random->nextRangeU(0, maxPosition.width()), | |
| (float)random->nextRangeU(0, maxPosition.height())}; | |
| } | |
| float random_angle(SkRandom* random) { | |
| return random->nextRangeF(0, 2*SK_FloatPI); | |
| } | |
| float random_velocity(SkRandom* random, float maxVelocity) { | |
| return random->nextRangeF(maxVelocity/8, maxVelocity); | |
| } | |
| class Rotater { | |
| public: | |
| Rotater(float rotateInterval) | |
| : fTimeDelta(0) | |
| , fRotateInterval(rotateInterval) {} | |
| void next(float timeDelta) { | |
| fTimeDelta = SkScalarMod(fTimeDelta + timeDelta, fRotateInterval); | |
| } | |
| float degrees() { | |
| return (360 * fTimeDelta) / fRotateInterval; | |
| } | |
| private: | |
| float fTimeDelta; | |
| float fRotateInterval; | |
| }; | |
| Rotater random_rotater(SkRandom* random) { | |
| return Rotater(random->nextRangeF(10, 100)); | |
| } | |
| SkPoint point_on_circle(float angle, float radius) { | |
| return {radius * SkScalarCos(angle), radius * SkScalarSin(angle)}; | |
| } | |
| class BouncingParticle : public MMObject { | |
| public: | |
| BouncingParticle(SkRandom* random, SkSize stageSize, SkSize particleSize, float maxVelocity) | |
| : fStageSize(stageSize) | |
| , fSize(particleSize) | |
| , fPosition(random_position(random, | |
| {stageSize.fWidth - particleSize.fWidth, | |
| stageSize.fHeight - particleSize.fHeight})) | |
| , fAngle(random_angle(random)) | |
| , fVelocity(random_velocity(random, maxVelocity)) | |
| , fRotater(random_rotater(random)) { | |
| } | |
| void animate(double deltaNanos) override { | |
| // TODO: consolidate and pass in millis to the Stages | |
| constexpr double kMillisPerNano = 0.0000001; | |
| fPosition += point_on_circle(fAngle, fVelocity * (deltaNanos * kMillisPerNano)); | |
| fRotater.next(deltaNanos * kMillisPerNano); | |
| // If particle is going to move off right side | |
| if (fPosition.fX + fSize.width() > fStageSize.width()) { | |
| // If direction is East-South, go West-South. | |
| if (fAngle >= 0 && fAngle < SK_FloatPI / 2) | |
| fAngle = SK_FloatPI - fAngle; | |
| // If angle is East-North, go West-North. | |
| else if (fAngle > SK_FloatPI / 2 * 3) | |
| fAngle = fAngle - (fAngle - SK_FloatPI / 2 * 3) * 2; | |
| // Make sure the particle does not go outside the stage boundaries. | |
| fPosition.fX = fStageSize.width() - fSize.width(); | |
| } | |
| // If particle is going to move off left side | |
| if (fPosition.fX < 0) { | |
| // If angle is West-South, go East-South. | |
| if (fAngle > SK_FloatPI / 2 && fAngle < SK_FloatPI) | |
| fAngle = SK_FloatPI - fAngle; | |
| // If angle is West-North, go East-North. | |
| else if (fAngle > SK_FloatPI && fAngle < SK_FloatPI / 2 * 3) | |
| fAngle = fAngle + (SK_FloatPI / 2 * 3 - fAngle) * 2; | |
| // Make sure the particle does not go outside the stage boundaries. | |
| fPosition.fX = 0; | |
| } | |
| // If particle is going to move off bottom side | |
| if (fPosition.fY + fSize.height() > fStageSize.height()) { | |
| // If direction is South, go North. | |
| if (fAngle > 0 && fAngle < SK_FloatPI) | |
| fAngle = SK_FloatPI * 2 - fAngle; | |
| // Make sure the particle does not go outside the stage boundaries. | |
| fPosition.fY = fStageSize.height() - fSize.height(); | |
| } | |
| // If particle is going to move off top side | |
| if (fPosition.fY < 0) { | |
| // If direction is North, go South. | |
| if (fAngle > SK_FloatPI && fAngle < SK_FloatPI * 2) | |
| fAngle = fAngle - (fAngle - SK_FloatPI) * 2; | |
| // Make sure the particle does not go outside the stage boundaries. | |
| fPosition.fY = 0; | |
| } | |
| } | |
| protected: | |
| SkSize fStageSize; | |
| SkSize fSize; | |
| SkPoint fPosition; | |
| float fAngle; | |
| float fVelocity; | |
| Rotater fRotater; | |
| }; | |
| class BouncingParticlesStage : public Stage { | |
| public: | |
| BouncingParticlesStage(SkSize size) | |
| : Stage(size, /*startingObjectCount=*/3000, /*objectIncrement=*/500) | |
| , fParticleSize({150, 150}) | |
| , fMaxVelocity(10){ | |
| } | |
| bool animate(double nanos) override { | |
| // The particles take delta time | |
| if (fLastTime < 0) { | |
| fLastTime = nanos; | |
| } | |
| for (size_t i = 0; i < fObjects.size(); ++i) { | |
| fObjects[i]->animate(nanos - fLastTime); | |
| } | |
| fLastTime = nanos; | |
| return true; | |
| } | |
| protected: | |
| SkSize fParticleSize; | |
| float fMaxVelocity; | |
| double fLastTime = -1; | |
| }; | |
| class BouncingTaggedImage : public BouncingParticle { | |
| public: | |
| BouncingTaggedImage(SkRandom* random, SkSize stageSize, SkSize particleSize, float maxVelocity) | |
| : BouncingParticle(random, stageSize, particleSize, maxVelocity) { | |
| this->move(); | |
| fRect = SkRect::MakeSize(fSize); | |
| fRect.offset(-fSize.width()/2, -fSize.height()/2); | |
| } | |
| void move() { | |
| fTransform = SkMatrix::RotateDeg(std::floor(fRotater.degrees())); | |
| fTransform.setTranslateX(fPosition.fX); | |
| fTransform.setTranslateY(fPosition.fY); | |
| } | |
| void animate(double deltaNanos) override { | |
| BouncingParticle::animate(deltaNanos); | |
| this->move(); | |
| } | |
| void draw(SkCanvas* canvas) override { | |
| // handled by the Stage | |
| } | |
| const SkMatrix& transform() { return fTransform; } | |
| SkRect rect() { return fRect; } | |
| private: | |
| SkMatrix fTransform; | |
| SkRect fRect; | |
| }; | |
| class BouncingTaggedImagesStage : public BouncingParticlesStage { | |
| public: | |
| BouncingTaggedImagesStage(SkSize size) : BouncingParticlesStage(size) { | |
| this->initializeObjects(); | |
| } | |
| ~BouncingTaggedImagesStage() override = default; | |
| void initImages(SkCanvas* canvas) { | |
| std::array<const char*, kImageCount> kImageSrcs = { | |
| "images/brickwork-texture.jpg", | |
| "images/dog.jpg", | |
| "images/color_wheel.jpg", | |
| "images/mandrill_512_q075.jpg", | |
| "images/flutter_logo.jpg", | |
| }; | |
| #if defined(SK_GRAPHITE) | |
| skgpu::graphite::Recorder* recorder = nullptr; | |
| recorder = canvas->recorder(); | |
| #endif | |
| for (int i = 0; i < kImageCount; ++i) { | |
| auto lazyYUV = sk_gpu_test::LazyYUVImage::Make(GetResourceAsData(kImageSrcs[i]), | |
| skgpu::Mipmapped::kYes); | |
| SkASSERT(lazyYUV); | |
| #if defined(SK_GRAPHITE) | |
| if (recorder) { | |
| fImages[i] = lazyYUV->refImage(recorder, | |
| sk_gpu_test::LazyYUVImage::Type::kFromPixmaps); | |
| } | |
| #endif | |
| #if defined(SK_GANESH) | |
| if (auto rContext = canvas->recordingContext()) { | |
| fImages[i] = lazyYUV->refImage(rContext, | |
| sk_gpu_test::LazyYUVImage::Type::kFromPixmaps); | |
| } | |
| #endif | |
| } | |
| } | |
| void draw(SkCanvas* canvas) override { | |
| if (fNeedToInitImages) { | |
| this->initImages(canvas); | |
| fNeedToInitImages = false; | |
| } | |
| canvas->clear(SK_ColorWHITE); | |
| SkPaint paint; | |
| SkSamplingOptions sampling(SkFilterMode::kLinear, | |
| SkMipmapMode::kNearest); | |
| for (size_t i = 0; i < fObjects.size(); ++i) { | |
| BouncingTaggedImage* object = reinterpret_cast<BouncingTaggedImage*>(fObjects[i].get()); | |
| canvas->save(); | |
| canvas->concat(object->transform()); | |
| canvas->drawImageRect(fImages[i % kImageCount], object->rect(), sampling, nullptr); | |
| canvas->restore(); | |
| } | |
| } | |
| std::unique_ptr<MMObject> createObject() override { | |
| return std::make_unique<BouncingTaggedImage>(&fRandom, fSize, fParticleSize, fMaxVelocity); | |
| } | |
| void reset() { | |
| fNeedToInitImages = true; | |
| } | |
| private: | |
| static constexpr int kImageCount = 5; | |
| bool fNeedToInitImages = true; | |
| std::array<sk_sp<SkImage>, kImageCount> fImages; | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| class CanvasLinesSlide : public MotionMarkSlide { | |
| public: | |
| CanvasLinesSlide() {fName = "MotionMarkCanvasLines"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fStage = std::make_unique<CanvasLineSegmentStage>(SkSize::Make(w, h)); | |
| } | |
| }; | |
| class CanvasArcsSlide : public MotionMarkSlide { | |
| public: | |
| CanvasArcsSlide() {fName = "MotionMarkCanvasArcs"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fStage = std::make_unique<CanvasArcStage>(SkSize::Make(w, h)); | |
| } | |
| }; | |
| class PathsSlide : public MotionMarkSlide { | |
| public: | |
| PathsSlide() {fName = "MotionMarkPaths"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fStage = std::make_unique<CanvasLinePathStage>(SkSize::Make(w, h)); | |
| } | |
| }; | |
| class BouncingTaggedImagesSlide : public MotionMarkSlide { | |
| public: | |
| BouncingTaggedImagesSlide() {fName = "MotionMarkBouncingTaggedImages"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fStage = std::make_unique<BouncingTaggedImagesStage>(SkSize::Make(w, h)); | |
| } | |
| void gpuTeardown() override { | |
| if (fStage) { | |
| reinterpret_cast<BouncingTaggedImagesStage*>(fStage.get())->reset(); | |
| } | |
| } | |
| }; | |
| DEF_SLIDE( return new CanvasLinesSlide(); ) | |
| DEF_SLIDE( return new CanvasArcsSlide(); ) | |
| DEF_SLIDE( return new PathsSlide(); ) | |
| DEF_SLIDE( return new BouncingTaggedImagesSlide(); ) |