Updated 2012-09-20 10:37:50 by LkpPo

The following Itcl class implements a set of commands for performing translation, scaling and rotation on a list of points. The class also supports a transform matrix stack so transforms can be chained.

See Chapter 5 "Graphic Context Attributes" in "Java 2D API Graphics" (by Vincent J. Hardy) for a good discussion of the affine transform and composing affine transforms using a transform stack.

Tom Krehbiel

Related Pages

``` #------------------------------------------------------------
# Matrix equation for 2D affine transform
#------------------------------------------------------------
#
# | x'|   | a b tx | | x |   | a*x + b*y + tx |
# | y'| = | c d ty | | y | = | c*x + d*y + ty |
# | 1 |   | 0 0 1  | | 1 |   |        1       |
#
# Translate
# ----------
#
# | 1 0 tx |     | x'|    | 1*x + 0*y + tx |
# | 0 1 ty | =>  | y'| =  | 0*x + 1*y + ty |
# | 0 0 1  |     | 1 |    |        1       |
#
# Scale
# ----------
#
# | sx 0  0 |     | x'|   | sx*x +  0*y + 0 |
# | 0  sy 0 | =>  | y'| = |  0*x + sy*y + 0 |
# | 0  0  1 |     | 1 |   |         1       |
#
# Rotation
# ----------
#
# for: r0 = cos(angle)
#      r1 = sin(angle)
#
# | r0 -r1 0 |     | x'|    | r0*x - r1*y + 0 |
# | r1  r0 0 | =>  | y'| =  | r1*x + r0*y + 0 |
# | 0   0  1 |     | 1 |    |         1       |
#
# Shear
# ----------
#
# | 1  hx 0 |     | x'|    |  1*x + hx*y + 0 |
# | hy 1  0 | =>  | y'| =  | hy*x +  1*y + 0 |
# | 0  0  1 |     | 1 |    |        1       |
#
# Reflection
# ----------
#
# for: Rx,Ry := { 1.0 , -1.0 }
#       1 = don't reflect
#      -1 = reflect
#
# | Rx 0  0 |     | x'|   | Rx*x +  0*y + 0 |
# | 0  Ry 0 | =>  | y'| = |  0*x + Ry*y + 0 |
# | 0  0  1 |     | 1 |   |         1       |
#
#------------------------------------------------------------
class Transform {

# define some constants
private common M_PI   [expr 4.0 * atan( 1.0 )]
private common M_PI_2 [expr 2.0 * atan( 1.0 )]
private common M_PI_4 [expr       atan( 1.0 )]

private variable maxStack 0   ;# max depth of stack
private variable stackSiz 0
private variable tmStack ""   ;# fifo stack of transform matrix's

# Transform Matrix (TM)
protected variable a
protected variable b
protected variable c
protected variable d
protected variable tx
protected variable ty

constructor { depth } {
set maxStack \$depth
\$this reset
}
destructor {
}

public method dump { {indent 0} }
public method clearTransform { }
public method clearStack { }
public method reset { }
public method push { }
public method getTop { }
public method pop { }
public method getTransform { }
public method translate { x y }
public method scale { sx sy }
public method rotate { deg }
public method applyTransformToStack { x y s }
public method apply { x y }
public method applyR { rec }
public method applyTo { xyList }
public method applyStackTo { xyList }

}
#------------------------------
# CLASS IMPLEMENTATION
#------------------------------
body Transform::dump { {indent 0} } {
set x [string repeat " " \$indent]
puts "\${x}+Transform: \$this"
puts "\${x} a.......: \$a"
puts "\${x} b.......: \$b"
puts "\${x} c.......: \$c"
puts "\${x} d.......: \$d"
puts "\${x} tx......: \$tx"
puts "\${x} ty......: \$ty"
puts "\${x} maxStack: \$maxStack"
puts "\${x} stackSiz: \$stackSiz"
puts "\${x} tmStack.: \$tmStack"
}
#----------
# reset TM to unit matrix values
body Transform::clearTransform { } {
set a  1.0
set b  0.0
set c  0.0
set d  1.0
set tx 0.0
set ty 0.0
}
#----------
# clear the tmStack
body Transform::clearStack { } {
set stackSiz 0
set tmStack ""
}
#----------
# initialize TM and the stack
body Transform::reset { } {
clearTransform
clearStack
}
body Transform::push { } {
if { \$stackSiz > \$maxStack } {
# remove an element from bottom of stack
set tmStack [lreplace \$tmStack end end]
incr stackSiz -1
}
if { \$stackSiz == 0 } {
lappend tmStack "\$a \$b \$c \$d \$tx \$ty"
} else {
set tmStack [linsert \$tmStack 0 "\$a \$b \$c \$d \$tx \$ty"]
}
incr stackSiz
set a  1.0
set b  0.0
set c  0.0
set d  1.0
set tx 0.0
set ty 0.0
}
body Transform::getTop { } {
set result 0
if { \$stackSiz > 0 } {
foreach {a b c d tx ty} [lindex \$tmStack 0] break
} else {
set result 1
}
return \$result
}
body Transform::pop { } {
set result 0
if { \$stackSiz > 0 } {
set tmStack [lreplace \$tmStack 0 0]
incr stackSiz -1
set a  1.0
set b  0.0
set c  0.0
set d  1.0
set tx 0.0
set ty 0.0
} else {
set result 1
}
return \$result
}
body Transform::getTransform { } {
return "\$tx \$ty \$a"
}
body Transform::applyTransformToStack { x y s } {
if { \$stackSiz == 0 } { return }
set new ""
foreach tmEntry \$tmStack {
foreach {a b c d tx ty} \$tmEntry break
# scale
set a [expr \$a*\$s]
set b [expr \$b*\$s]
set c [expr \$c*\$s]
set d [expr \$d*\$s]
# translate
set tx [expr \$a*\$x + \$b*\$y + \$tx]
set ty [expr \$c*\$x + \$d*\$y + \$ty]
lappend new "\$a \$b \$c \$d \$tx \$ty"
}
set tmStack "\$new"
clearTransform
}

body Transform::translate { x y } {
set tx [expr \$a*\$x + \$b*\$y + \$tx]
set ty [expr \$c*\$x + \$d*\$y + \$ty]
}
body Transform::scale { sx sy } {
set a [expr \$a*\$sx]
set b [expr \$b*\$sy]
set c [expr \$c*\$sx]
set d [expr \$d*\$sy]
}
body Transform::rotate { deg } {
if { \$deg == 0.0 } {
return
} elseif { \$deg == 90.0 } {
set Cos 0.0
set Sin -1.0
} elseif { \$deg == 180.0 } {
set Cos -1.0
set Sin 0.0
} elseif { \$deg == 270.0 } {
set Cos 0.0
set Sin 1.0
} else {
set Cos [expr cos(\$deg)]
set Sin [expr sin(\$deg)]
}
set A \$a
set B \$b
set C \$c
set D \$d

set a [expr \$A*\$Cos + \$B*\$Sin]
set b [expr \$B*\$Cos - \$A*\$Sin]
set c [expr \$C*\$Cos + \$D*\$Sin]
set d [expr \$D*\$Cos - \$C*\$Sin]
}

body Transform::apply { X Y } {
upvar \$X x \$Y y
set nx [expr \$a*\$x + \$c*\$y + \$tx]
set ny [expr \$b*\$x + \$d*\$y + \$ty]
set x \$nx
set y \$ny
}
body Transform::applyR { rec } {
foreach {x0 y0 x1 y1} \$rec {}
set nx0 [expr \$a*\$x0 + \$c*\$y0 + \$tx]
set ny0 [expr \$b*\$x0 + \$d*\$y0 + \$ty]
set nx1 [expr \$a*\$x1 + \$c*\$y1 + \$tx]
set ny1 [expr \$b*\$x1 + \$d*\$y1 + \$ty]
return "\$nx0 \$ny0 \$nx1 \$ny1"
}
body Transform::applyTo { xyList } {
set result ""
foreach {x y} \$xyList {
apply x y
lappend result "\$x" "\$y"
}
return \$result
}
body Transform::applyStackTo { xyList } {
for {set i 0} {\$i < \$stackSiz} {incr i} {
set new ""
foreach {a b c d tx ty} [lindex \$tmStack \$i] break
foreach {x y} \$xyList {
lappend new [expr \$a*\$x + \$c*\$y + \$tx] [expr \$b*\$x + \$d*\$y + \$ty]
}
set xyList \$new
}
return \$xyList
}```