Rotating a Tk Photo Image

BLT provides a command that allows rotation and scaling of images.

The img_rotate package does simple rotation (90,180, etc): [L1 ]

GPS: Below is a simple working prototype for image rotation by n degrees. It's slow because it's written in Tcl, but it seems to work properly.


#!/bin/wish8.3
  
proc - {num1 num2} {
    return [expr {$num1 - $num2}]
}
proc + {num1 num2} {
    return [expr {$num1 + $num2}]
}
proc * {num1 num2} {
    return [expr {$num1 * $num2}]
}
proc / {num1 num2} {
    return [expr {$num1 / $num2}]
}
proc cos {angle} {
    return [expr cos($angle)]
}
proc sin {angle} {
    return [expr sin($angle)]
}
proc toInt {num} {
    return [expr int($num)]
}
  
proc main {} {
    set img [image create photo -file ../Experimental/hand.gif]
  
    set width [image width $img]
    set height [image height $img]
  
    pack [label .l -image $img]
    
    set imgMod [image create photo -width $width -height $height]
  
    set twoPi [expr {acos(-1) * 2}]
    set deg 15
    
    set radian [* $deg [/ $twoPi 360]]
    set cosTheta [cos $radian]
    set sinTheta [sin $radian]
  
    for {set y 0} {$y < $height} {incr y} {
    
        for {set x 0} {$x < $width} {incr x} {
            set col [$img get $x $y]
            foreach {red green blue} $col break
            set hexCol [format "#%2.2x%2.2x%2.2x" $red $green $blue]
            set newX [toInt [* $x $cosTheta]]
            set newY [toInt [+ $y [* $x $sinTheta]]]
  
            if {$newX < 0 || $newY < 0 || $newX > $width || $newY > $height} {
                continue
            }
            $imgMod put $hexCol -to $newX $newY
        }
    }
  
    pack [label .l2 -image $imgMod]
}
main

RS notes that rotation by non-multiples of 90 degrees makes the target image bigger - it seems to follow these formulas (w and h being original width and height, a the rotation angle):

w′ = abs(w cos a) + abs(h cos a)
h′ = abs(w sin a) + abs(h sin a) ;# corrected 2002-09-09 RS

DKF - The problem with the above is that pixels are not point entities. Rather, they are rectangles. Which means you need an anti-aliasing algorithm. Which makes things complex. If you assume a pixel is square (which is, alas, not a safe assumption) then a pixel will typically map to components of four pixels in its rotation. (In the rectangular case - which is what really happens, as the pixels are almost but not quite square, which can lead to distortion of things like circles if they are drawn naively - this is increased still further; with nearly square pixels, you get six pixels contributing to each rotated pixel.)

Hmm. Writing down code to handle this (even for the square case) is non-trivial. Another time maybe.

 ...

Further discussion indicates that there is a fairly easy way to calculate the weightings for contributions to a pixel from four rotated pixels. Think of each pixel as being represented by a point (in its centre, say). Now compute the distance between the point which you are trying to colour and each of the points that you are taking colour from (let these be d(1), d(2), d(3) and d(4).) Now, the weight of the contribution from pixel n should be the ratio between the sums of the other three distances and the total distance: (d(total)-d(n)) / d(total)

This has the good properties that

  1. When the point is much closer to one pixel than to others, the weight given to that pixel is much greater, and when the pixels are roughly equidistant, the weights are the same.
  2. The total of the weights is 1, so no fancy rescaling is necessary.

RS has implemented that "bi-linear interpolation" over a weekend at Photo image rotation.