AKTIVE

Artifact [e47fb51b5d]
Login

Artifact [e47fb51b5d]

Artifact e47fb51b5d33c24e25929eafe4cd2eb4c706e97ef30bf3f27216d8394e6851c5:


## -*- mode: tcl ; fill-column: 90 -*-
# # ## ### ##### ######## ############# #####################
## Generators -- Affine and projective transformations.

# https://medium.com/@junfeng142857/
#                    affine-transformation-why-3d-matrix-for-a-2d-transformation-8922b08bce75
#
# For the supporting Tcl code (MulVec, etc) see `op/transform.tcl`.

operator transform::point {
    section generator virtual warp

    example {
	aktive transform quad unit2 a {1 2} b {6 1} c {7 6} d {2 7} | -matrix
	aktive transform point @1 at {0 0} | -text -label at 0 0
	aktive transform point @1 at {0 1} | -text -label at 0 1
	aktive transform point @1 at {1 1} | -text -label at 1 1
	aktive transform point @1 at {1 0} | -text -label at 1 0
    }

    example {
	aktive transform quad 2quad   a {1 2} b {6 1} c {7 6} d {2 7}   e {0 3} f {7 1} g {8 7} h {1 7} | -matrix
	aktive transform point @1 at {1 2} | -text -label at 1 2 => 0 3
	aktive transform point @1 at {6 1} | -text -label at 6 1 => 7 1
	aktive transform point @1 at {7 6} | -text -label at 7 6 => 8 7
	aktive transform point @1 at {2 7} | -text -label at 2 7 => 1 7
    }

    note Returns the point generated by the application of the \
	transformation to the input point.

    point at Point to transform.

    input

    body {
	# get matrix and transform
	MulVec [UNBOX src $src] $at
    }
}

operator transform::points {
    section generator virtual warp

    example {
	aktive transform quad unit2 a {1 2} b {6 1} c {7 6} d {2 7} | -matrix
	aktive transform points @1 series {0 0} {0 1} {1 1} {1 0} | -text
    }

    example {
	aktive transform quad 2quad a {1 2} b {6 1} c {7 6} d {2 7} e {0 3} f {7 1} g {8 7} h {1 7} | -matrix
	aktive transform points @1 series {1 2} {6 1} {7 6} {2 7} | -text
    }

    note Returns the list of points generated by the application of the \
	transformation to the input points.

    input

    point... series Points to transform.

    body {
	# get matrix and transform
	set trafo [UNBOX src $src]
	lmap at $series { MulVec $trafo $at }
    }
}

operator transform::domain {
    section generator virtual warp

    example {
	butterfly
	aktive transform rotate by 30 | -matrix -label rotate by 30
	@2 @1 | -text
    }

    note Returns the domain generated by applying the transformation (`src0`) \
	to the domain of the image (`src1`). \
	The domain is returned in the same form at as generated by \
	"<!xref: aktive query domain>," i.e. a 4-element Tcl list in \
	the format __\{x y w h\}__.

    note Fractions are rounded to integers such that the actual domain \
	is kept enclosed.

    strict 1st \
	The projective matrix is materialized for the calculation of \
	the domain.

    input
    input

    body {
	# get transform matrix
	set m [UNBOX src0 $src0]

	# get old bounding box ...
	lassign [aktive query domain $src1] xmin ymin w h
	set xmax [expr {$xmin + $w - 1}]
	set ymax [expr {$ymin + $h - 1}]

	# puts "ZZ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
	# puts "ZZ inbox $xmin $ymin $w $h"
	# puts "ZZ inbox $xmin $ymin -- $xmax $ymax"
	# puts "ZZ transform ($m)"

	# ... as corner points
	set a [list $xmin $ymin]	;# a--b
	set b [list $xmax $ymin]	;# |  |
	set c [list $xmax $ymax]	;# d--c
	set d [list $xmin $ymax]

	# transform points forward
	lassign [MulVec $m $a] ax ay
	lassign [MulVec $m $b] bx by
	lassign [MulVec $m $c] cx cy
	lassign [MulVec $m $d] dx dy

	# puts "ZZ a ($a) -> ($ax $ay)"
	# puts "ZZ b ($b) -> ($bx $by)"
	# puts "ZZ c ($c) -> ($cx $cy)"
	# puts "ZZ d ($d) -> ($dx $dy)"

	# new bounding box as points
	set xmin [expr {min ($ax,$bx,$cx,$dx)}]
	set xmax [expr {max ($ax,$bx,$cx,$dx)}]
	set ymin [expr {min ($ay,$by,$cy,$dy)}]
	set ymax [expr {max ($ay,$by,$cy,$dy)}]

	# puts "ZZ outbox $xmin $ymin -- $xmax $ymax"

	# round points to integers while keeping the exact box enclosed
	set xmini [expr {int($xmin)}]
	set ymini [expr {int($ymin)}]
	set xmaxi [expr {int(ceil($xmax))}] ; if {$xmaxi < $xmax} { incr xmaxi }
	set ymaxi [expr {int(ceil($ymax))}] ; if {$ymaxi < $ymax} { incr ymaxi }

	# puts "ZZ outbox $xmini $ymini -- $xmaxi $ymaxi"

	# new bounding box
	set w [expr {$xmaxi-$xmini+1}]
	set h [expr {$ymaxi-$ymini+1}]

	# puts "ZZ outbox $xmini $ymini $w $h"

	list $xmini $ymini $w $h
    }
}

operator transform::invert {
    section generator virtual warp

    example {
	aktive transform translate x -5 y -6 | -matrix -label translate x -5 y -6
	@1                                   | -matrix
    }
    example {
	aktive transform translate x -5 y -6 | -matrix -label translate x -5 y -6
	aktive transform invert @1           | -matrix -label invert
	aktive transform compose @1 @2       | -matrix
    }

    note Takes a single 3x3 projective transformation matrix and returns the \
	matrix of the inverted transformation. This is used to turn forward \
	into backward transformations, and vice versa.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    strict 1st \
	The projective matrix is materialized and \
	immediately used to compute the inversion.

    input

    body {
	# https://www.cuemath.com/algebra/inverse-of-3x3-matrix/
	return [BOX [Invert [UNBOX input $src]]]
    }
}

operator transform::compose {
    section generator virtual warp

    # from right to left: translate, then rotate, then translate back
    # => rotation around the non-origin point (5,6)
    example {
	aktive transform translate x -5 y -6 | -matrix -label translate x -5 y -6
	aktive transform rotate by 45        | -matrix -label rotate by 45
	aktive transform translate x 5 y 6   | -matrix -label translate x 5 y 6
	@1 @2 @3                             | -matrix -label rotate 45 around (5,6)
    }

    note Takes any number of 3x3 projective transformation matrices and returns their \
	composition.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    note A single matrix is passed through unchanged. And not materialized either.

    strict all \
	All projective matrices are materialized and \
	immediately used to compute the composition.

    input...

    body {
	aktive::aggregate-or-pass {
	    aktive transform compose-core
	} $args
    }
}

operator transform::compose-core {
    section generator virtual warp

    # from right to left: translate, then rotate
    example {
	aktive transform rotate by 45      | -matrix -label rotate
	aktive transform translate x 5 y 6 | -matrix -label translate
	@1 @2                              | -matrix -label rotate after translate
    }

    # from right to left: rotate, then translate
    example {
	aktive transform translate x 5 y 6 | -matrix -label rotate
	aktive transform rotate by 45      | -matrix -label translate
	@1 @2                              | -matrix -label translate after rotate
    }

    note Takes two 3x3 projective transformation matrices and \
	returns their composition.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    strict both \
	The two projective matrices are materialized \
	and immediately used to compute the composition A*B.

    input
    input

    body {
	BOX \
	    [Mul \
		 [UNBOX a $src0] \
		 [UNBOX b $src1]]
    }
}

operator transform::identity {
    section generator virtual warp

    example { | -matrix}

    note Returns a single-band 3x3 image containing the identity transform.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    # | 1 0 0 |
    # | 0 1 0 |
    # | 0 0 1 |
    body {
	BOXany \
	    1 0 0 \
	    0 1 0 \
	    0 0 1
    }
}

operator transform::scale {
    section generator virtual warp

    example {x 3 y 0.5 | -matrix}
    example {
	aktive transform scale x 3 y 0.5  | -matrix -label scale x 3 y 1/2
	aktive transform invert @1        | -matrix -label invert
    	aktive transform compose @1 @2    | -matrix
    }

    note Returns a single-band 3x3 image specifying a scaling by x- and y factors.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    double? 1 x Scaling factor for x-axis
    double? 1 y Scaling factor for y-axis

    # | x 0 0 |
    # | 0 y 0 |
    # | 0 0 1 |
    body {
	BOXany \
	    $x 0  0 \
	    0  $y 0 \
	    0  0  1
    }
}

operator transform::translate {
    section generator virtual warp

    example {x 3 y 1 | -matrix}
    example {
	aktive transform translate x 3 y 1 | -matrix -label translate x 3 y 1
	aktive transform invert @1           | -matrix -label invert
	aktive transform compose @1 @2       | -matrix
    }

    note Returns a single-band 3x3 image specifying a translation by x- and y offsets.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    double? 0 x Translation offset for x-axis
    double? 0 y Translation offset for y-axis

    # | 1 0 x |
    # | 0 1 y |
    # | 0 0 1 |
    body {
	BOXany \
	    1 0 $x \
	    0 1 $y \
	    0 0 1
    }
}

operator transform::shear {
    section generator virtual warp

    example {x 10    | -matrix}
    example {y 10    | -matrix}
    example {x 5 y 3 | -matrix}
    example {
	aktive transform shear x 5 y 3 | -matrix -label shear x 5 y 3
	aktive transform invert @1     | -matrix -label invert
	aktive transform compose @1 @2 | -matrix
    }

    note Returns a single-band 3x3 image specifying a shearing along the axes. \
	When both X and Y angles are specified the result \
	will shear X first, \
	then shear Y.

    note __Beware__ that angles at +/- 90 degrees are poles of infinity.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    double? 0 x	Angle for shearing away from the x-axis. \
	__Beware__ that +/- 90 degrees are poles of infinity.

    double? 0 y	Angle for shearing away from the y-axis. \
	__Beware__ that +/- 90 degrees are poles of infinity.

    body {
	# note: atan(1)/45 is pi/180, given pi = 4*atan(1)
	# convert angles to shear factors -> tan (angle in radians)
	if {($x != 0) && ($y != 0)} {
	    set m [compose [shear y $y] [shear x $x]]
	} elseif {$x != 0} {
	    set x [tan [* $x [/ [atan 1] 45]]]
	    set m [BOXany \
		       1 $x 0 \
		       0 1  0 \
		       0 0  1 ]
	} elseif {$y != 0} {
	    set y [tan [* $y [/ [atan 1] 45]]]
	    set m [BOXany \
		       1  0 0 \
		       $y 1 0 \
		       0  0 1 ]
	} else {
	    # no shear at all
	    set m [identity]
	}
	#puts [PRINT ZZ\t[info level 0] [UNBOX _ $m]]
	return $m
    }
}

operator transform::rotate {
    section generator virtual warp

    example {by 45 | -matrix}
    example {
	aktive transform rotate by 45  | -matrix -label rotate by 45
	aktive transform invert @1     | -matrix -label invert
    	aktive transform compose @1 @2 | -matrix
    }

    note Returns a single-band 3x3 image specifying a rotation around the coordinate \
	origin, by the given angle (in degrees).

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    double      by     In degrees, angle to rotate
    point? {{}} around Rotation center. Default is the origin

    # | c -s 0 | where c = cos angle,
    # | s  c 0 | and   s = sin angle
    # | 0  0 1 |
    body {
	if {![llength $around]} {
	    # note: atan(1)/45 is pi/180, given pi = 4*atan(1)
	    set rad [/ [atan 1] 45]
	    set c   [cos [* $by $rad]]
	    set s   [sin [* $by $rad]]
	    return [BOXany \
			$c [- $s] 0 \
			$s $c     0 \
			0  0      1]
	}
	# around a non-origin center a composition is required
	lassign $around x y
	compose \
	    [translate x $x y $y] \
	    [rotate by $by] \
	    [translate x [- $x] y [- $y]]
    }
}

operator transform::quad::unit2 {
    section generator virtual warp

    example {
	a {1 2} b {6 1} c {7 6} d {2 7} | -matrix
    }

    example {
	aktive transform quad unit2 a {1 2} b {6 1} c {7 6} d {2 7} | -matrix
	aktive transform point @1 at {0 0}                          | -text -label at 0 0
	aktive transform point @1 at {0 1}                          | -text -label at 0 1
	aktive transform point @1 at {1 1}                          | -text -label at 1 1
	aktive transform point @1 at {1 0}                          | -text -label at 1 0
    }

    example {
	aktive transform quad unit2 a {1 2} b {6 1} c {7 6} d {2 7} | -matrix
	aktive transform invert @1                                  | -matrix -label invert
    	aktive transform compose @1 @2                              | -matrix
    }

    note Returns a single-band 3x3 image transforming the unit square \
	to the specified quadrilateral.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    note The quadrilateral is specified as 4 points A-B-C-D in \
	counter clockwise order. The returned transform maps the \
	origin of the unit square to A and then the other points in \
	counter clockwise order.

    note To map between two arbitrary quadrilaterals A and B a composition \
	of two transforms is necessary and sufficient, i.e. mapping A to \
	the unit square (as inversion of the map from unit square to A), \
	followed by mapping the unit square to B. This is what \
	"<!xref: aktive transform quad 2quad>" does.

    point a Point A of the quadrilateral
    point b Point B of the quadrilateral
    point c Point C of the quadrilateral
    point d Point D of the quadrilateral

    ref https://raw.githubusercontent.com/JohnHardy/wiituio/refs/heads/master/WiiTUIO/WiiProvider/Warper.cs
    ref http://portal.acm.org/citation.cfm?id=884607
    ref http://www.decew.net/OSS/References/Quadrilateral%20mapping.pdf

    body {
	# Calculate the transform from the unit rectangle to the specified quad.

	# Derived from the paper:
	#	A Planar Perspective Image Matching using Point Correspondences
	#	and Rectangle-to-Quadrilateral Mapping
	# By
	#	Dong-Keun Kim, Byung-Tae Jang, Chi-Jung Hwang
	#
	# [1][OK]   http://portal.acm.org/citation.cfm?id=884607
	# [2][OK]   http://www.decew.net/OSS/References/Quadrilateral%20mapping.pdf
	# [3][OK]   https://raw.githubusercontent.com/JohnHardy/wiituio/refs/heads/master/WiiTUIO/WiiProvider/Warper.cs
	# [4][GONE] http://www.informatik.uni-trier.de/~ley/db/conf/ssiai/ssiai2002.html
	#
	# Errata [2]:
	# (a) Figure 1 in the paper has p2, p3 (and p2', p3') swapped.
	# (b) The transform matrix A is transposed (well, written for left-multiplication).
	# (c) Eq a6 is wrong. See [3] for the correct equation, modulo var names.

	# Map from top-left clock-wise for our y-axis.
	# Flipped relative to regular.
	#
	# *---*   *-------* <== Figure 1
	# |d c|   |p3' p2'|
	# |   | = |       |
	# |a b|   |p0' p1'|
	# O---*   *-------* O = origin = (0,0)

	lassign $a ax ay ; # x0, y0
	lassign $b bx by ; # x1, y1
	lassign $c cx cy ; # x2, y2
	lassign $d dx dy ; # x3, y3

	set dxd [expr {$ax - $bx + $cx - $dx}] ; # \delta x3
	set dyd [expr {$ay - $by + $cy - $dy}] ; # \delta y3

	set dxb [expr {$bx - $cx}]             ; # \delta x1
	set dxc [expr {$dx - $cx}]             ; # \delta x2

	set dyb [expr {$by - $cy}]             ; # \delta y1
	set dyc [expr {$dy - $cy}]             ; # \delta y2

	# det | dxb dyb |
	#     | dxc dyc |
	set D [expr {($dxb*$dyc - $dyb*$dxc)}]

	set g [expr {($dyc*$dxd - $dxc*$dyd)/double($D)}] ; # a6
	set h [expr {($dxb*$dyd - $dyb*$dxd)/double($D)}] ; # a7

	# note: a-d writes over the parameters now

	set a [expr {$bx * (1+$g) - $ax}] ; # a0
	set b [expr {$dx * (1+$h) - $ax}] ; # a1
	set c $ax                         ; # a2

	set d [expr {$by * (1+$g) - $ay}] ; # a3
	set e [expr {$dy * (1+$h) - $ay}] ; # a4
	set f $ay                         ; # a5

	# | a0 a3 a6 |   | a d g | => Errata (b), transpose.
	# | a1 a4 a7 | = | b e h |
	# | a2 a5 1  |   | c f 1 |
	BOXany \
	    $a $b $c \
	    $d $e $f \
	    $g $h 1
    }
}

operator transform::quad::2quad {
    section generator virtual warp

    example {
	a {1 2} b {6 1} c {7 6} d {2 7}   e {0 3} f {7 1} g {8 7} h {1 7} | -matrix
    }

    example {
	aktive transform quad 2quad a {1 2} b {6 1} c {7 6} d {2 7}   e {0 3} f {7 1} g {8 7} h {1 7} | -matrix
	aktive transform invert @1     | -matrix -label invert
    	aktive transform compose @1 @2 | -matrix
    }

    example {
	aktive transform quad 2quad a {1 2} b {6 1} c {7 6} d {2 7}   e {0 3} f {7 1} g {8 7} h {1 7} | -matrix
	aktive transform point @1 at {1 2}                         | -text -label at 1 2 => 0 3
	aktive transform point @1 at {6 1}                         | -text -label at 6 1 => 7 1
	aktive transform point @1 at {7 6}                         | -text -label at 7 6 => 8 7
	aktive transform point @1 at {2 7}                         | -text -label at 2 7 => 1 7
    }

    example {
	aktive transform quad 2quad a {1 2} b {6 1} c {7 6} d {2 7}   e {0 3} f {7 1} g {8 7} h {1 7} | -matrix
	aktive transform invert @1                                                                    | -matrix
	aktive transform point @2 at {0 3}                         | -text -label at 0 3 => 1 2
	aktive transform point @2 at {7 1}                         | -text -label at 7 1 => 6 1
	aktive transform point @2 at {8 7}                         | -text -label at 8 7 => 7 6
	aktive transform point @2 at {1 7}                         | -text -label at 1 7 => 2 7
    }

    example {
	aktive transform quad 2quad   a {47 62} b {190 10} c {210 80} d {100 125}   e {0 0} f {100 0} g {100 100} h {0 100} | -matrix -label quadrilateral
	aktive transform point @1 at { 47  62} | -text -label  47  62 =>   0   0
	aktive transform point @1 at {100 125} | -text -label 100 125 =>   0 100
	aktive transform point @1 at {210  80} | -text -label 210  80 => 100 100
	aktive transform point @1 at {190  10} | -text -label 190  10 => 100   0
    }

    example {
	aktive transform quad 2quad   a {47 62} b {190 10} c {210 80} d {100 125}   e {0 0} f {100 0} g {100 100} h {0 100} | -matrix -label quadrilateral
	aktive transform invert @1                                                                                          | -matrix -label inverted
	aktive transform point @2 at {0     0} | -text -label   0   0 =>  47  62
	aktive transform point @2 at {0   100} | -text -label   0 100 => 100 125
	aktive transform point @2 at {100 100} | -text -label 100 100 => 210  80
	aktive transform point @2 at {100   0} | -text -label 100   0 => 190  10
    }

    note Returns a single-band 3x3 image transforming the specified \
	quadrilateral A to the second quadrilateral B.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    note The quadrilaterals are specified as 4 points A-B-C-D and \
	E-F-G-H in counter clockwise order. The returned transform \
	maps A to E and then the other points in counter clockwise \
	order.

    note It is implemented by chaining a regular and an inverted \
	"<!xref: aktive transform quad unit2>" \
	to transform A-B-C-D to a unit square \
	and from there then to E-F-G-H.

    point a Point A of the quadrilateral A
    point b Point B of the quadrilateral A
    point c Point C of the quadrilateral A
    point d Point D of the quadrilateral A

    point e Point A of the quadrilateral B
    point f Point B of the quadrilateral B
    point g Point C of the quadrilateral B
    point h Point D of the quadrilateral B

    body {
	set ua [unit2 a $a b $b c $c d $d] ;# unit to ABCD
	set ub [unit2 a $e b $f c $g d $h] ;# unit to EFGH
	set au [invert $ua]                ;# ABCD to unit
	compose $ub $au ;# ABCD to unit, unit to EFGH
    }
}

operator transform::reflect::x {
    section generator virtual warp

    example { | -matrix}
    example {
	aktive transform reflect x     | -matrix -label reflect x
	aktive transform invert @1     | -matrix -label invert
    	aktive transform compose @1 @2 | -matrix
    }

    note Returns a single-band 3x3 image specifying a reflection along the x-axis.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    note When not used as part of a chain of transformations then this is \
	better done using "<!xref: aktive op flip x>"

    body {
	BOXany \
	   -1 0 0 \
	    0 1 0 \
	    0 0 1
    }
}

operator transform::reflect::y {
    section generator virtual warp

    example { | -matrix}
    example {
	aktive transform reflect y        | -matrix -label reflect y
	aktive transform invert @1        | -matrix -label invert
    	aktive transform compose @1 @2    | -matrix
    }

    note Returns a single-band 3x3 image specifying a reflection along the y-axis.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    note When not used as part of a chain of transformations then this is \
	better done using "<!xref: aktive op flip y>"

    body {
	BOXany \
	    1  0 0 \
	    0 -1 0 \
	    0  0 1
    }
}

operator transform::reflect::line {
    section generator virtual warp

    example { a {5 3}           | -matrix}
    example {
	aktive transform reflect line a {5 3} | -matrix -label reflect line 0--A
	aktive transform invert @1            | -matrix -label invert
    	aktive transform compose @1 @2        | -matrix
    }

    example { a {5 3} b {-2 -2} | -matrix}
    example {
	aktive transform reflect line a {5 3} b {-2 -2} | -matrix -label reflect line A--B
	aktive transform invert @1                      | -matrix -label invert
    	aktive transform compose @1 @2                  | -matrix
    }

    note Returns a single-band 3x3 image specifying a reflection along \
	either the line through point A and the origin, \
	or the line through points A and B.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    point       a Point A of the line to reflect over
    point? {{}} b Point B of the line to reflect over. If not specified, the origin is used

    body {
	if {![llength $b]} {
	    # line through A and origin (0,0).
	    # this is handled as a chain of three transformations
	    # (1) rotate around the origin to map the line 0-A on the X-axis.
	    # (2) reflect along the Y!-axis
	    # (3) rotate revers around the origin, to get line 0-A back.
	    #
	    # note: 45/atan(1) is 180/pi, given pi = 4*atan(1)
	    lassign $a x y
	    set angle [expr {atan2($y, $x) * 45 / atan(1)}]
	    return [compose \
			[rotate by $angle] \
			[y] \
			[rotate by [- $angle]]]
	}

	# a line through two points A and B is handled by ta chain of three
	# transformations
	# (1) translating A to the origin 0.
	# (2) reflection through the line 0--(B-A)	[recursion]
	# (3) translating 0 back to A

	lassign $a ax ay
	lassign $b bx by
	set ba [list [- $bx $ax] [- $by $ay]]

	return [compose \
		    [translate x $ax y $ay] \
		    [line a $ba] \
		    [translate x [- $ax] y [- $ay]]]
    }
}

operator transform::affine {
    section generator virtual warp

    example {a 1 b 2 c 3 d 4 e 5 f 6 | -int -matrix}

    note Returns a single-band 3x3 image holding the affine \
	transformation specifed by the 6 parameters a to f.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    double a Parameter a of the affine transform
    double b Parameter b of the affine transform
    double c Parameter c of the affine transform
    double d Parameter d of the affine transform
    double e Parameter e of the affine transform
    double f Parameter f of the affine transform

    # | a b c |
    # | d e f |
    # | 0 0 1 |
    body {
	BOXany \
	    $a $b $c \
	    $d $e $f \
	    0  0  1
    }
}

operator transform::projective {
    section generator virtual warp

    example {a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 | -int -matrix}

    note Returns a single-band 3x3 image holding the projective \
	transformation specifed by the 8 parameters a to h.

    note The result is suitable for use with "<!xref: aktive warp matrix>"

    double a Parameter a of the projective transform
    double b Parameter b of the projective transform
    double c Parameter c of the projective transform
    double d Parameter d of the projective transform
    double e Parameter e of the projective transform
    double f Parameter f of the projective transform
    double g Parameter g of the projective transform
    double h Parameter h of the projective transform

    # | a b c |
    # | d e f |
    # | g h 1 |
    body {
	BOXany \
	    $a $b $c \
	    $d $e $f \
	    $g $h 1
    }
}

##
# # ## ### ##### ######## ############# #####################
::return