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.
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 }