4.10 Spatial Transformations
This section gives the API for applying spatial transforms to a flomap, such as rotations, warps, morphs, and lens distortion effects.
To use the provided transforms, apply a function like flomap-flip-horizontal directly,
or apply something like a flomap-rotate-transform to a flomap using flomap-transform.
Here, fun is a mapping from input coordinates to output coordinates and inv is its inverse.
4.10.1 Provided Transformations
Some standard image transforms.
These are lossless, in that repeated applications do not degrade (blur or alias) the image.
Returns a flomap transform that rotates a flomap θ
radians counterclockwise around its (Real
Use flomap-rotate-transform if you need to know the bounds of the rotated flomap or need to compose a rotation with another transform using flomap-transform-compose.
Returns a flomap transform that “whirls” a flomap: rotates it counterclockwise θ radians in the center, and rotates less with more distance from the center.
This transform does not alter the size of its input.
Returns a flomap transform that simulates “fisheye” lens distortion with an α diagonal angle of view.
Returns a flomap transform that scales flomaps by x-scale horizontally and y-scale vertically.
You should generally prefer to use flomap-scale, which is faster and correctly reduces resolution before downsampling to avoid aliasing.
This is provided for composition with other transforms using flomap-transform-compose.
4.10.2 General Transformations
Applies spatial transform t to fm.
The rectangle x-start y-start x-end y-end is with respect to the fm’s transformed coordinates.
If given, points in fm are transformed only if their transformed coordinates are within that rectangle.
If not given, flomap-transform uses the rectangle returned by (flomap-transform-bounds t w h), where w and h are the size of fm.
This transform doubles a flomap’s size:
Transforms can use the width and height arguments w h
however they wish; for example, double-transform
ignores them, and flomap-rotate-transform
uses them to calculate the center coordinate.
function usually increases the size of a flomap to fit its corners in the result.
To rotate in a way that does not change the size—
i.e. to do an in-place
use 0 0 w h
as the transformed rectangle:
Using it on text-fm with a purple background:
Alternatively, we could define a new transform-producing function flomap-in-place-rotate-transform
that never transforms points outside of the orginal flomap:
To transform fm
uses only the inv
field of (t w h)
Every point new-x new-y
in the transformed bounds is given the components returned by
A value of type Flomap-Transform receives the width and height of a flomap to operate on, and returns a flomap-2d-mapping on the coordinates of flomaps of that size.
Represents an invertible mapping from Real
, or from real-valued flomap coordinates to real-valued flomap coordinates.
See Conceptual Model
for the meaning of real-valued flomap coordinates.
The forward mapping fun is used to determine the bounds of a transformed flomap.
(See flomap-transform-bounds for details.)
The inverse mapping inv is used to actually transform the flomap.
(See flomap-transform for details.)
'id: Do not transform bounds.
Use this for in-place transforms such as flomap-whirl-transform.
'corners: Return the smallest rectangle containing only the transformed corners.
Use this for linear and affine transforms (such as flomap-rotate-transform or a skew transform),
transforms that do not produce extreme points, and others for which it can be proved (or at least empirically demonstrated)
that the rectangle containing the transformed corners contains all the transformed points.
'edges: Return the smallest rectangle containing only the transformed left, top, right, and bottom edges.
Use this for transforms that are almost-everywhere continuous and invertible—which describes most interesting transforms.
'all: Return the smallest rectangle containing all the transformed points.
Use this for transforms that produce overlaps and other non-invertible results.
For good performance, define instances of flomap-2d-mapping and functions that return them (e.g. instances of Flomap-Transform), in Typed Racket.
Defining them in untyped Racket makes every application of fun and inv contract-checked when used in typed code, such as the implementation of flomap-transform.
(In the worst case, flomap-transform applies fun to every pair of coordinates in the input flomap.
It always applies inv to every pair of coordinates in the output flomap.)
Composes two flomap transforms.
Applying the result of (flomap-transform-compose t2 t1)
is the same as applying t1
and then t2
The points are transformed only once, meaning their component values are estimated only once, so the result is less degraded (blurry or aliased).
The bounds are generally tighter.
The following example “whirls” text-fm clockwise 360 degrees and back.
This is first done by applying the two transforms separately, and secondly by applying a composition of them.
Notice the heavy aliasing (a “Moiré pattern”) in the first result is not in the second.
In the next example, notice that rotating multiple times blurs the result and pads it with transparent points, but that applying composed rotation transforms doesn’t:
How the bounds for the composed transform are calculated depends on how they would have been calculated for t1
is the bounds rule for (t1 w h)
is the bounds rule for (t2 w h)
Then the bounds rule b
for (flomap-transform-compose t2 t1)
is determined by the following rules, applied in order:
If either b1 = 'all or b2 = 'all, then b = 'all.
If either b1 = 'edges or b2 = 'edges, then b = 'edges.
If either b1 = 'corners or b2 = 'corners, then b = 'corners.
Otherwise, b1 = b2 = 'id, so b = 'id.
Returns the rectangle that would contain a w×h flomap after transform by t.
How the rectangle is determined depends on the bounded-by field of (t w h).
See flomap-2d-mapping for details.
See flomap-rotate-transform and flomap-projection-transform for examples.
4.10.3 Lens Projection and Correction
The following API demonstrates a parameterized family of spatial transforms.
It also provides a physically grounded generalization of the flomap transforms returned by flomap-fisheye-transform.
Returns a flomap transform that corrects for or simulates lens distortion.
To correct for lens distortion in a flomap:
Find a projection from-proj that models the actual lens.
Find a projection to-proj that models the desired (but fictional) lens.
Apply (flomap-projection-transform to-proj from-proj) to the flomap.
This photo is in the public domain.
In the following example, a photo of the State of the Union address was taken using an “equal area” (or “equisolid angle”) fisheye lens with a 180-degree diagonal angle of view:
Notice that the straight geometry in the House chamber (especially the trim around the ceiling) is represented by straight edges in the corrected photo.
When crop? is #t, the output flomap is no larger than the input flomap.
When crop? is #f, the output flomap is large enough to contain the entire transformed flomap.
An uncropped result can be quite large, especially with angles of view at or near 180 degrees.
|> (define rectangle-fm|
| (draw-flomap (λ (fm-dc)|
| (send fm-dc set-pen "black" 4 'dot)|
| (send fm-dc set-brush "yellow" 'solid)|
| (send fm-dc set-alpha 1/2)|
| (send fm-dc draw-rectangle 0 0 32 32))|
| 32 32))|
|> (flomap->bitmap rectangle-fm)|
Given a diagonal angle of view α
, these all return a projection modeling some kind of camera lens.
See Fisheye Lens
for the defining formulas.
A value of type Projection receives the diagonal size of a flomap to operate on, and returns a projection-mapping instance.
The provided projections (such as perspective-projection) use a closed-over diagonal angle of view α and the diagonal size to calculate the focal length.
Represents an invertible function from a point’s angle ρ from the optical axis, to the distance r to the center of a photo, in flomap coordinates.
For example, given a diagonal angle of view α
and the diagonal size d
of a flomap, the perspective-projection
function calculates the focal length f
It then constructs the projection mapping as