| /* | |
| * Copyright 2025 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkSpan.h" | |
| #include <optional> | |
| #include <vector> | |
| namespace { | |
| template <typename T> bool span_equal(SkSpan<T> a, SkSpan<T> b) { | |
| if (a.size() != b.size()) { | |
| return false; | |
| } | |
| if (a.empty()) { | |
| return true; | |
| } | |
| return (a.data() == b.data()) || std::equal(a.begin(), a.end(), b.begin()); | |
| } | |
| SkPoint operator*(float s, SkPoint p) { | |
| return { | |
| p.fX * s, | |
| p.fY * s | |
| }; | |
| } | |
| SkPoint lerp(SkPoint from, SkPoint to, float t) { | |
| return from + t * (to - from); | |
| } | |
| // These can be copy/pasted into Chrome / Android | |
| // - CanInterpolate() | |
| // - Interpolate() | |
| // | |
| // That would allow us to remove the associated SkPath methods. | |
| // | |
| // Note: they require no private access to SkPath internals, and are not used/called by | |
| // anthing in Skia. | |
| /** | |
| * Returns true if the two paths can be interpolated. | |
| * | |
| * Returns true iff the paths have the same verbs and conicWeights, | |
| * and the same number of points. | |
| */ | |
| bool CanInterpolate(const SkPath& from, const SkPath& to) { | |
| return from.points().size() == to.points().size() && | |
| span_equal(from.verbs(), to.verbs()) && | |
| span_equal(from.conicWeights(), to.conicWeights()); | |
| } | |
| /** | |
| * Return a path whose points are lineraly interpolated between from and to, | |
| * using t (typically between 0 and 1). | |
| * | |
| * (logically) newPoint = (1 - t) * fromPoint + t * toPoint | |
| * | |
| * The result has the same fillType as the 'from' path. | |
| * | |
| * If the two paths are not interpolatable, this returns {}. | |
| */ | |
| std::optional<SkPath> Interpolate(const SkPath& from, const SkPath& to, float t) { | |
| if (!CanInterpolate(from, to)) { | |
| return {}; | |
| } | |
| // note, if from and to have different fill-types, that's up to the client to deal with. | |
| const SkPathFillType fillType = from.getFillType(); | |
| const SkSpan<const SkPoint> fromPts = from.points(), | |
| toPts = to.points(); | |
| std::vector<SkPoint> dst(fromPts.size()); | |
| for (size_t i = 0; i < fromPts.size(); ++i) { | |
| dst[i] = lerp(fromPts[i], toPts[i], t); | |
| } | |
| return SkPath::Raw({dst.data(), dst.size()}, from.verbs(), from.conicWeights(), fillType); | |
| } | |
| } // namespace | |
| /////////////////////////////////////////////////////////////////////////////////////////////// | |
| bool SkPath::isInterpolatable(const SkPath& compare) const { | |
| return CanInterpolate(*this, compare); | |
| } | |
| SkPath SkPath::makeInterpolate(const SkPath& ending, SkScalar weight) const { | |
| return Interpolate(*this, ending, 1 - weight).value_or(SkPath()); | |
| } | |
| bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const { | |
| if (auto result = Interpolate(*this, ending, 1 - weight)) { | |
| *out = *result; | |
| return true; | |
| } | |
| return false; | |
| } |