Utility functions that allow you to move, resize, stretch, flip, rotate and reverse a curve, without changing the points you defined it with.
| To... | Use |
|---|---|
| move it | set_position/2, inc_position/2, inc_x/2, inc_y/2 |
| resize or stretch it | set_scale/3, inc_scale/3 (a number, or {x, y} for each direction) |
| mirror it | flip_x/2, flip_y/2 |
| rotate it | set_rotation/3, inc_rotation/3 (degrees, counter-clockwise) |
| travel it the other way | reverse/1 |
The curve is first scaled and then rotated around its own {0, 0}, and finally moved to its
position (the :origin option, set_position/2 and the like). This is the usual
scale-rotate-translate order (like SVG's transform="translate(…) rotate(…) scale(…)"), so
scaling or rotating a curve never moves it away from its position. To scale, flip or rotate around
some other point, such as the curve's middle, pass around: :center or around: {x, y}.
Positions are {x, y} tuples of numbers, and amounts are numbers.
Anything else raises an ArgumentError.
Drawings
Every function here also takes a Curves.Drawing (from Curves.from_svg/2), or a group or shape
in one, and changes it as a whole, the same way it changes a curve. Everything in it moves
together, text and other kept markup too. around: :center is the middle of its shapes: text and
other markup can't be measured, so they don't count.
Curves.Drawing.update/3 and update_in/3 change one part, found by its id:
drawing
|> Curves.Transform.set_scale(2)
|> Curves.Drawing.update("wheel", &Curves.Transform.inc_rotation(&1, 45, around: :center))The change is kept on the drawing, group or shape (in its :origin, :scale and :rotation),
so it can be set again later, like a curve's. Curves.Drawing.shapes/1 and curves/1 give the
curves where they end up, and Curves.Svg draws it there. A Curves.Drawing.Subpath changes its
curve.
Summary
Types
The point to scale, flip or rotate around: :center (the middle of the curve's points) or an
{x, y} point, in the curve's own coordinates. nil (the default) is the curve's own {0, 0}.
Anything these functions change: a curve, or a Curves.Drawing, or a group, shape or subpath
in one. A drawing, group or shape changes as a whole (everything in it, text too); a subpath
changes its curve.
Functions
Mirror the curve left to right (its x scale turns negative, or back again).
Mirror the curve top to bottom (its y scale turns negative, or back again).
Using the current position increase both the x and y coordinates. It is also possible to decrease by passing a negative number
Increase the rotation (counter-clockwise) by the given amount, in degrees.
Resize the curve, adding to the existing scale. With an {x, y} pair, each direction is added to
separately; a single number adds to both. A flipped direction has a negative scale, so adding to
it brings it towards 0, making the curve smaller that way.
Using the current position increase the x coordinates. It is also possible to decrease by passing a negative number
Using the current position increase the y coordinates. It is also possible to decrease by passing a negative number
The same curve, travelled the other way: t (or u) = 0.0 is now its old end. Its shape,
position and transforms don't change. Handy to play an easing backwards, or follow a path in reverse.
Ignore current position and set a new one.
Rotate the curve counter-clockwise, in degrees, replacing the existing rotation. It turns around its
position (the :origin option or set_position/2, {0.0, 0.0} by default).
Resize the curve, replacing the existing scale. The default scale is 1.0: 2.0 doubles the curve and
0.5 halves it. A negative scale flips it.
Types
@type around() :: nil | :center | Curves.Types.point_tuple()
The point to scale, flip or rotate around: :center (the middle of the curve's points) or an
{x, y} point, in the curve's own coordinates. nil (the default) is the curve's own {0, 0}.
@type transformable() :: Curves.Types.curve_struct() | Curves.Drawing.t() | Curves.Drawing.Group.t() | Curves.Drawing.Shape.t() | Curves.Drawing.Subpath.t()
Anything these functions change: a curve, or a Curves.Drawing, or a group, shape or subpath
in one. A drawing, group or shape changes as a whole (everything in it, text too); a subpath
changes its curve.
Functions
@spec flip_x(transformable(), opts :: [{:around, around()}]) :: transformable()
Mirror the curve left to right (its x scale turns negative, or back again).
The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a
different point instead, which then stays exactly where it is: either :center (the middle of the
curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.
around: keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.
Examples
iex> curve = Curves.define_bezier([{1, 0}, {3, 2}])
iex> Curves.solve!(Transform.flip_x(curve), 1.0)
{-3.0, 2.0}
iex> Curves.solve!(Transform.flip_x(curve, around: :center), 1.0)
{1.0, 2.0}
@spec flip_y(transformable(), opts :: [{:around, around()}]) :: transformable()
Mirror the curve top to bottom (its y scale turns negative, or back again).
The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a
different point instead, which then stays exactly where it is: either :center (the middle of the
curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.
around: keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.
Examples
iex> curve = Curves.define_bezier([{1, 0}, {3, 2}])
iex> Curves.solve!(Transform.flip_y(curve), 1.0)
{3.0, -2.0}
iex> Curves.solve!(Transform.flip_y(curve, around: :center), 1.0)
{3.0, 0.0}
@spec inc_position(curve :: transformable(), Curves.Types.point_tuple()) :: transformable()
Using the current position increase both the x and y coordinates. It is also possible to decrease by passing a negative number
Examples
iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
10.5
iex> curve = Transform.inc_position(curve, {10, 10})
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
20.5
@spec inc_rotation(transformable(), number(), opts :: [{:around, around()}]) :: transformable()
Increase the rotation (counter-clockwise) by the given amount, in degrees.
The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a
different point instead, which then stays exactly where it is: either :center (the middle of the
curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.
around: keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.
Examples
iex> curve = Curves.define_bezier(:linear)
iex> curve = Transform.inc_rotation(curve, 45.0)
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> {Float.round(x, 5), Float.round(y, 5)}
{0.0, 0.70711}
@spec inc_scale( transformable(), amount :: number() | {number(), number()}, opts :: [{:around, around()}] ) :: transformable()
Resize the curve, adding to the existing scale. With an {x, y} pair, each direction is added to
separately; a single number adds to both. A flipped direction has a negative scale, so adding to
it brings it towards 0, making the curve smaller that way.
The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a
different point instead, which then stays exactly where it is: either :center (the middle of the
curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.
around: keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.
Examples
iex> curve = Curves.define_bezier(:linear)
iex> curve = Transform.inc_scale(curve, 10.0)
iex> Curves.solve!(curve, 0.5)
{5.5, 5.5}
iex> curve = Transform.inc_scale(curve, {0, -10})
iex> Curves.solve!(curve, 0.5)
{5.5, 0.5}
@spec inc_x(curve :: transformable(), xinc :: Curves.Types.coord()) :: transformable()
Using the current position increase the x coordinates. It is also possible to decrease by passing a negative number
Examples
iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
10.5
iex> curve = Transform.inc_x(curve, 10)
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> x
20.5
iex> y
10.5
@spec inc_y(curve :: transformable(), yinc :: Curves.Types.coord()) :: transformable()
Using the current position increase the y coordinates. It is also possible to decrease by passing a negative number
Examples
iex> curve = Curves.define_bezier(:linear, origin: {10, 10})
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> x
10.5
iex> y
10.5
iex> curve = Transform.inc_y(curve, -10)
iex> {x, y} = Curves.solve!(curve, 0.5)
iex> x
10.5
iex> y
0.5
@spec reverse(transformable()) :: transformable()
The same curve, travelled the other way: t (or u) = 0.0 is now its old end. Its shape,
position and transforms don't change. Handy to play an easing backwards, or follow a path in reverse.
Examples
iex> curve = Curves.define_bezier([{0, 0}, {1, 2}])
iex> Curves.solve!(Transform.reverse(curve), 0.0)
{1.0, 2.0}
iex> spline = Curves.define_catmull_rom([{0, 0}, {1, 1}, {2, 0}])
iex> Curves.solve!(Transform.reverse(spline), 2.0)
{0.0, 0.0}
@spec set_position(curve :: transformable(), Curves.Types.point_tuple()) :: transformable()
Ignore current position and set a new one.
Examples
iex> curve = Curves.define_bezier(:linear)
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
0.5
iex> curve = Curves.Transform.set_position(curve, {10, 10})
iex> {x, _y} = Curves.solve!(curve, 0.5)
iex> x
10.5
@spec set_rotation(transformable(), number(), opts :: [{:around, around()}]) :: transformable()
Rotate the curve counter-clockwise, in degrees, replacing the existing rotation. It turns around its
position (the :origin option or set_position/2, {0.0, 0.0} by default).
The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a
different point instead, which then stays exactly where it is: either :center (the middle of the
curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.
around: keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.
Examples
iex> curve = Curves.define_bezier(:linear_up_right)
iex> curve = Transform.set_rotation(curve, 90)
iex> {x, y} = Curves.solve!(curve, 1.0)
iex> {Float.round(x, 5), Float.round(y, 5)}
{-1.0, 1.0}Turned around its middle, the line stays centred on the same spot:
iex> curve = Curves.define_bezier([{0, 0}, {2, 0}]) |> Transform.set_rotation(90, around: :center)
iex> for t <- [0.0, 1.0], {x, y} = Curves.solve!(curve, t), do: {Float.round(x, 5), Float.round(y, 5)}
[{1.0, -1.0}, {1.0, 1.0}]
@spec set_scale( transformable(), scale :: number() | {number(), number()}, opts :: [{:around, around()}] ) :: transformable()
Resize the curve, replacing the existing scale. The default scale is 1.0: 2.0 doubles the curve and
0.5 halves it. A negative scale flips it.
Give an {x, y} pair to scale the two directions separately, e.g. {2, 0.5} makes it twice as wide
and half as tall.
The curve is scaled and rotated around its own {0, 0}. Pass around: to turn around a
different point instead, which then stays exactly where it is: either :center (the middle of the
curve's points) or an {x, y} point. Both are in the curve's own coordinates: the points you defined
it with, before any transforms.
around: keeps that point still by moving the curve's position, and the move stays: setting the
rotation back to 0 (or the scale back to 1) later, without around:, leaves the curve moved.
Examples
iex> curve = Curves.define_bezier(:linear)
iex> Curves.solve!(curve, 0.5)
{0.5, 0.5}
iex> curve = Transform.set_scale(curve, 10.0)
iex> Curves.solve!(curve, 0.5)
{5.0, 5.0}Separate x and y scales:
iex> curve = Curves.define_bezier([{0, 0}, {2, 2}]) |> Transform.set_scale({2, 0.5})
iex> Curves.solve!(curve, 1.0)
{4.0, 1.0}The curve is scaled around its own {0, 0}, so a moved curve stays where it was put:
iex> curve = Curves.define_bezier([{0, 0}, {4, 0}]) |> Transform.set_position({10, 10})
iex> curve = Transform.set_scale(curve, 2)
iex> Curves.solve!(curve, 0.0)
{10.0, 10.0}
iex> Curves.solve!(curve, 1.0)
{18.0, 10.0}Scaled around its middle instead, it grows equally in every direction:
iex> curve = Curves.define_bezier([{0, 0}, {2, 2}]) |> Transform.set_scale(2, around: :center)
iex> {Curves.solve!(curve, 0.0), Curves.solve!(curve, 1.0)}
{{-1.0, -1.0}, {3.0, 3.0}}