| #include "rive/artboard.hpp" |
| #include "rive/factory.hpp" |
| #include "rive/layout/axis.hpp" |
| #include "rive/layout/n_sliced_node.hpp" |
| #include "rive/math/math_types.hpp" |
| #include "rive/math/n_slicer_helpers.hpp" |
| |
| using namespace rive; |
| |
| std::vector<float> NSlicerHelpers::uvStops(const std::vector<Axis*>& axes, |
| float size) |
| { |
| std::vector<float> uvs = {0.0}; |
| for (const Axis* axis : axes) |
| { |
| if (axis == nullptr) |
| { |
| continue; |
| } |
| if (axis->normalized()) |
| { |
| uvs.emplace_back(math::clamp(axis->offset(), 0, 1)); |
| } |
| else |
| { |
| uvs.emplace_back(math::clamp(axis->offset() / size, 0, 1)); |
| } |
| } |
| uvs.emplace_back(1.0); |
| std::sort(uvs.begin(), uvs.end()); |
| return uvs; |
| } |
| |
| std::vector<float> NSlicerHelpers::pxStops(const std::vector<Axis*>& axes, |
| float size) |
| { |
| std::vector<float> result = {0.0}; |
| for (const Axis* axis : axes) |
| { |
| if (axis == nullptr) |
| { |
| continue; |
| } |
| if (axis->normalized()) |
| { |
| result.emplace_back(math::clamp(axis->offset(), 0, 1) * size); |
| } |
| else |
| { |
| result.emplace_back(math::clamp(axis->offset(), 0, size)); |
| } |
| } |
| result.emplace_back(size); |
| std::sort(result.begin(), result.end()); |
| return result; |
| } |
| |
| ScaleInfo NSlicerHelpers::analyzeUVStops(const std::vector<float>& uvs, |
| float size, |
| float scale) |
| { |
| assert(size >= 0 && scale >= 0); |
| // Find the percentage of the dimension that should be fixed / not scaled. |
| float fixedPct = 0.0; |
| int numEmptyPatch = 0; |
| for (int i = 0; i < (int)uvs.size() - 1; i++) |
| { |
| float range = uvs[i + 1] - uvs[i]; |
| if (isFixedSegment(i)) |
| { |
| fixedPct += range; |
| } |
| else |
| { |
| numEmptyPatch += (range == 0 ? 1 : 0); |
| } |
| } |
| |
| // Find the scale that the scalable segments are bearing. |
| // If the unscaled image is 300px, how to divide it up between scaled and |
| // unscaled patches. |
| float fixedSize = fixedPct * size; |
| float scalableSize = size - fixedSize; |
| |
| // Considering the image's scale, how much should the scalable patches scale |
| // by. And if all scalable patches were empty, how much whitespace to leave |
| // in those patches. |
| bool useScale = scalableSize != 0; |
| float scaleFactor = |
| useScale ? (size * scale - fixedSize) / scalableSize : 0; |
| |
| float fallbackSize = 0; |
| if (!useScale && numEmptyPatch != 0) |
| { |
| fallbackSize = (size - fixedSize / scale) / numEmptyPatch; |
| } |
| |
| return {useScale, scaleFactor, fallbackSize}; |
| } |
| |
| float NSlicerHelpers::mapValue(const std::vector<float>& stops, |
| const ScaleInfo& scaleInfo, |
| float size, |
| float value) |
| { |
| if (value < (stops.front() - 0.01)) |
| { |
| return value; |
| } |
| |
| if (value > (stops.back() + 0.01)) |
| { |
| return value - stops.back() + size; |
| } |
| |
| float result = 0; |
| for (int i = 0; i < (int)stops.size() - 1; i++) |
| { |
| bool found = value <= stops[i + 1]; |
| float span = found ? value - stops[i] : stops[i + 1] - stops[i]; |
| if (isFixedSegment(i)) |
| { |
| result += span; |
| } |
| else |
| { |
| result += scaleInfo.useScale ? scaleInfo.scaleFactor * span : 0; |
| } |
| if (found) |
| { |
| break; |
| } |
| } |
| |
| return result; |
| } |
| |
| void NSlicerHelpers::deformLocalRenderPathWithNSlicer( |
| const NSlicedNode& nslicedNode, |
| RawPath& localPath, |
| const Mat2D& world, |
| const Mat2D& inverseWorld) |
| { |
| RawPath tempWorld = localPath.transform(world); |
| deformWorldRenderPathWithNSlicer(nslicedNode, tempWorld); |
| localPath.rewind(); |
| localPath.addPath(tempWorld, &inverseWorld); |
| } |
| |
| void NSlicerHelpers::deformWorldRenderPathWithNSlicer( |
| const NSlicedNode& nslicedNode, |
| RawPath& worldPath) |
| { |
| for (Vec2D& point : worldPath.points()) |
| { |
| nslicedNode.mapWorldPoint(point); |
| } |
| } |