mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 10:17:02 -06:00
A chain of geometry effects makes at most 2 million anchors of a path (of a type, symbol or image art's paths together) and about 8 million lines for the renderer to draw at 1 px/pt, or what the path costs when that is more. Roughen spaces its points further apart, Zig Zag makes fewer ridges a segment, Free Distort, Twist and the warps split segments into longer pieces (map_nonlinear_within) and Transform makes fewer copies where they would pass it; a result drawn with too many lines is built again with fewer points, and an effect that still doesn't fit is left out. Three Roughens of a rectangle in a 25 KB file needed gigabytes of geometry, and 2 million anchors of them flattened into 141 million lines (7 GB, past vello's 2^27 lines a path); the file now opens in 0.4 s at about 450 MB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
35 lines
1.4 KiB
Rust
35 lines
1.4 KiB
Rust
//! Warp effects: 15 envelope styles plus horizontal/vertical perspective distortion.
|
|
//!
|
|
//! Each style is a map on normalised box coordinates (x, y) ∈ [-1, 1]² (y down). The path is
|
|
//! split into short pieces whose control points are mapped (see `map_nonlinear`).
|
|
|
|
use serde_json::Value;
|
|
use vectorcraft_geom::{PathData, Point, Rect};
|
|
|
|
use crate::util::*;
|
|
|
|
pub use vectorcraft_doc::live::{WarpStyle, warp_point};
|
|
|
|
/// Apply a warp to `path` using box `b`, making at most `max` anchors ([`map_nonlinear_within`]).
|
|
pub fn warp(path: &PathData, b: Rect, style: WarpStyle, p: &Value, max: usize) -> PathData {
|
|
let bend = num(p, "bend", 50.0).clamp(-100.0, 100.0) / 100.0;
|
|
let dh = num(p, "horizontal", 0.0).clamp(-100.0, 100.0) / 100.0;
|
|
let dv = num(p, "vertical", 0.0).clamp(-100.0, 100.0) / 100.0;
|
|
let vertical = text(p, "orientation", "horizontal").eq_ignore_ascii_case("vertical");
|
|
let c = b.center();
|
|
let hw = (b.width() / 2.0).max(1e-9);
|
|
let hh = (b.height() / 2.0).max(1e-9);
|
|
let f = |q: Point| {
|
|
let (x, y) = ((q.x - c.x) / hw, (q.y - c.y) / hh);
|
|
let (x2, y2) = if vertical {
|
|
let (a, b2) = warp_point(style, bend, dh, dv, hw / hh, y, x);
|
|
(b2, a)
|
|
} else {
|
|
warp_point(style, bend, dh, dv, hh / hw, x, y)
|
|
};
|
|
Point::new(c.x + x2 * hw, c.y + y2 * hh)
|
|
};
|
|
let piece = b.width().hypot(b.height()) / 24.0;
|
|
map_nonlinear_within(path, piece, max, f)
|
|
}
|