benjamminf / warpjs

Warp, distort, bend, twist and smudge your SVG’s directly in the browser
https://benjamminf.github.io/warpjs
MIT License
486 stars 34 forks source link

New API #5

Closed benjamminf closed 7 years ago

benjamminf commented 7 years ago

The new API should be entirely functional, yet still allow of points to be extended. I propose the following method for distorting points:

warpObject.transform(([x, y, ox, oy]) => [
    x + 1,
    y - 1,
    isNaN(ox) ? x : ox,
    isNaN(oy) ? y : oy,
])

This example shows how to transform points and keep track of the original position. The function takes in an array of points where the first two elements can be guaranteed (the x and y positions) but allows returning an arbitrary number of points. The interpolation will also operate on all points, so extended points will still be interpolated.

This method will allow storing velocity vectors and anything else along with all points.

benjamminf commented 7 years ago

Might be worth considering this approach too: provide a vector initialiser to handle default values. This would allow the transform function to not need to check for undefined's.

warpObject.initVector(([x, y]) => [x, y, x, y])
warpObject.transform(([x, y, ox, oy]) => [x + 1, y - 1, ox, oy])
benjamminf commented 7 years ago

Normalisation steps:

  1. Convert shapes to paths
  2. Convert relative paths to absolute paths
  3. Convert path segments (arcs, horizontal/vertical lines, etc.) to lines and curves
  4. Convert path lines to curves

Each step can be performed independently, but strongly recommended to run all, in the above order. This process should also be explicit.

const warpObject = new Warp(svgElement)
warpObject.normalize() // Runs all
warpObject.normalize(Warp.TO_PATH)
warpObject.normalize(Warp.TO_ABSOLUTE)
warpObject.normalize(Warp.TO_LINE)
warpObject.normalize(Warp.TO_CURVE)
benjamminf commented 7 years ago

Interpolation and extrapolation should be explicit:

warpObject.transform(transFn)
warpObject.interpolate(threshold)
warpObject.extrapolate(threshold)

This should allow for the most amount of flexibility with when to interpolate/extrapolate points. This would allow for fine-tuning performance.

benjamminf commented 7 years ago

In reference to the initVector method above, this could actually just be replaced with a starting call to transform that does the same thing. Meaning initVector is redundant.

warpObject.transform(([x, y]) => [x, y, x, y])
warpObject.transform(([x, y, ox, oy]) => [x + 1, y - 1, ox, oy])
benjamminf commented 7 years ago

Taking inspiration from https://github.com/nfroidure/SVGPathData – create a path parsing/streaming/encoding sub-API that provides an interface to inject transforms when iterating over the path data. The linked API supports everything Warp.js needs out of the box, but it's compile size is unfortunately huge.

On second thought, a simple Array.map should suffice once the path string has been parsed. The kind of data the parser should output should be like https://github.com/hughsk/svg-path-parser – unfortunately this libraries compile size is far too big as well. A much more minimal parser should be possible.

benjamminf commented 7 years ago

This should be a good starting point: https://github.com/jkroso/parse-svg-path

Not a fan of the minimal output, and I could probably improve the code to make it more readable. Since it's tiny I'll just rewrite it for Warp.js. Transforms could just be plain functions that are passed to Array.map

benjamminf commented 7 years ago

New setting: precision – ability to set the numerical precision when generating path strings. Default to 2 (decimal places).

benjamminf commented 7 years ago

To address #4, I propose two new methods: preinterpolate and preextrapolate

They will take in both the transform function and threshold, and will interpolate points based on the position of points post-transform. The transform will not actually occur – a second call to transform will be required.

const transFn = ([x, y]) => [x + y, y / 2]
warpObject.preinterpolate(transFn, threshold)
warpObject.transform(transFn)

It should also only be single-pass, meaning the transformation is applied once, and interpolated based on that. This on it's own will not guarantee perfect quality, as even post-interpolation, those points transformed again might still require more interpolation. It'll be up to the user to call these pre methods as many times as needed.

It might also be useful to have these methods return a boolean, indicating whether any work took place. This could be exploited with a loop to preinterpolate as many times as needed, until no more interpolation takes place. Obviously, this would be extremely expensive, but some situations might call for it.

while(warpObject.preinterpolate(transFn, threshold)) {}
warpObject.transform(transFn)