| /* |
| * 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 <vector> |
| |
| #include "include/core/SkCanvas.h" |
| #include "include/core/SkPath.h" |
| #include "include/effects/SkGradientShader.h" |
| #include "src/base/SkRandom.h" |
| #include "tools/Resources.h" |
| #include "tools/gpu/YUVUtils.h" |
| #include "tools/viewer/Slide.h" |
| |
| // 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; |
| SkPoint fCircleCenters[4]; |
| float fLineLengthMaximum; |
| float fLineMinimum; |
| }; |
| |
| class CanvasLineSegment : public MMObject { |
| public: |
| CanvasLineSegment(SkRandom* random, const LineSegmentParams& params) { |
| int circle = random->nextRangeU(0, 3); |
| |
| static constexpr SkColor kColors[] = { |
| 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(SK_ColorWHITE); |
| |
| 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)); |
| |
| SkColor color1Step = SkColorSetARGB(brightnessStep, |
| brightnessStep, |
| (brightnessStep << 1), |
| 102); |
| SkColor color2Step = SkColorSetARGB((brightnessStep << 1), |
| (brightnessStep << 1), |
| brightnessStep, |
| 102); |
| SkPoint pts[2] = { |
| {fHalfSize.fWidth + dx, fHalfSize.fHeight + dy}, |
| {fHalfSize.fWidth - dx, fHalfSize.fHeight - dy} |
| }; |
| SkColor colors[] = { |
| color1Step, |
| color1Step, |
| color2Step, |
| color2Step |
| }; |
| float pos[] = { |
| 0, |
| 0.2f + colorStopStep, |
| 0.8f - colorStopStep, |
| 1 |
| }; |
| sk_sp<SkShader> gradientShader = SkGradientShader::MakeLinear(pts, colors, pos, 4, |
| SkTileMode::kClamp, 0); |
| |
| SkPaint paint; |
| paint.setAntiAlias(true); |
| paint.setStrokeWidth(15); |
| for (int i = 0; i < 4; i++) { |
| const SkColor strokeColors[] = { |
| 0xffe01040, 0xff10c030, 0xff744cba, 0xffe05010 |
| }; |
| const SkColor fillColors[] = { |
| 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; |
| |
| const SkColor baseColors[3] = { |
| 0xff101010, 0xff808080, 0xffc0c0c0 |
| }; |
| const SkColor bonusColors[3] = { |
| 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 }; |
| const SkPoint kOffsets[4] = { |
| {-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) { |
| const SkColor kColors[7] = { |
| 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(SkPath* 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(SkPath* 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(SkPath* 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); |
| break; |
| case 1: |
| return std::make_unique<CanvasBezierSegment>(random, size, prev); |
| break; |
| case 2: |
| case 3: |
| default: |
| return std::make_unique<CanvasLinePoint>(random, size, prev); |
| break; |
| } |
| } |
| |
| 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); |
| |
| SkPath 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, paint); |
| |
| paint.setStrokeWidth(object->getWidth()); |
| paint.setColor(object->getColor()); |
| currentPath.reset(); |
| currentPath.moveTo(object->getPoint()); |
| } |
| |
| if (fRandom.nextF() > 0.995) { |
| object->toggleIsSplit(); |
| } |
| } |
| } |
| canvas->drawPath(currentPath, 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) { |
| const char* kImageSrcs[kImageCount] = { |
| "images/ducky.jpg", |
| "images/dog.jpg", |
| "images/color_wheel.jpg", |
| "images/mandrill_512_q075.jpg", |
| "images/gainmap_iso21496_1.jpg", |
| }; |
| |
| auto rContext = canvas->recordingContext(); |
| #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); |
| } else |
| #endif |
| { |
| fImages[i] = lazyYUV->refImage(rContext, |
| sk_gpu_test::LazyYUVImage::Type::kFromPixmaps); |
| } |
| } |
| } |
| |
| 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; |
| sk_sp<SkImage> fImages[kImageCount]; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| 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 { |
| 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(); ) |