# Module:         poImgTfm
# Copyright:      Paul Obermeier 2013-2026 / obermeier@poSoft.de
# First Version:  2013 / 03 / 01
#
# Distributed under BSD license.
#
# Module with image transformation procedures for the poImg package.

namespace eval poImgUtil {
    variable ns [namespace current]

    namespace ensemble create

    namespace export Transform
    namespace export Rotate

    proc _Max { a b } {
        if { $a > $b } {
            return $a
        } else {
            return $b
        }
    }

    proc _ApplyTfm { x y } {
        variable sTfmMat

        set u [expr {$x * [lindex $sTfmMat 0] + $y * [lindex $sTfmMat 2] + [lindex $sTfmMat 4]}]
        set v [expr {$x * [lindex $sTfmMat 1] + $y * [lindex $sTfmMat 3] + [lindex $sTfmMat 5]}]
        # puts [format "_ApplyTfm (%.2f, %.2f) --> (%.2f, %.2f)" $x $y  $u $v]
        return [list $u $v]
    }

    proc _ApplyTfmDeriv { x y } {
        variable sTfmMat

        set ux [lindex $sTfmMat 0]
        set uy [lindex $sTfmMat 2]
        set vx [lindex $sTfmMat 1]
        set vy [lindex $sTfmMat 3]
        return [list $ux $uy $vx $vy]
    }

    ###########################################################################
    #[@e
    #       Name:           poImgUtil::Transform
    #
    #       Usage:          Apply a transformation matrix to an image.
    #
    #       Tcl usage:      Transform { srcImg destImg tfm fillMode }
    #
    #                       srcImg:   image
    #                       destImg:  image
    #                       tfm:      Tfm2D (2-dim. transformation matrix)
    #                       fillMode: string, optional (FILL)
    #
    #       Description:    Image "srcImg" is transformed geometrically according
    #                       to transformation matrix "tfm".
    #                       The result is stored in image "destImg":
    #                       Each pixel in destImg at position "d", is filled with
    #                       the color at position "s" in "srcImg", where
    #
    #                           s = d * tfm.
    #
    #                       If "s" is not within the boundaries of the source
    #                       image, "fillMode" determines what color is assigned
    #                       to the pixel at position "d".
    #                       Acceptable values for "fillMode" are FILL, CLIP, WRAP.
    #                       See the description of "WarpFunct" for details.
    #
    #                       The x and y coordinates in "srcImg" and "destImg"
    #                       increase from left to right and from bottom to top.
    #                       The lower left  corners are at (0.0, 0.0).
    #                       The upper right corners are at (1.0, 1.0).
    #
    #      States:          State settings influencing functionality:
    #                       Draw mask:    No
    #                       Draw mode:    No
    #                       Draw color:   No
    #                       Threading:    Yes
    #                       UByte format: All
    #                       Float format: All 
    #
    #       Return Value:   None.
    #
    #       See also:       poImgUtil::Rotate
    #                       IP_WarpFunct
    #                       IP_SetNumThreads
    #
    ###########################################################################

    proc Transform { srcImg destImg tfm { fillMode FILL } } {
        variable ns
        variable sTfmMat

        poVec TfmCopy2D $tfm sTfmMat
        $srcImg GetImageInfo w h
        set size [${ns}::_Max $w $h]
        $destImg WarpFunct $srcImg ${ns}::_ApplyTfm ${ns}::_ApplyTfmDeriv $size $fillMode
    }

    ###########################################################################
    #[@e
    #       Name:           poImgUtil::Rotate
    #
    #       Usage:          Rotate an image by an arbitray angle.
    #
    #       Tcl usage:      Rotate { srcImg destImg angle xcen ycen fillMode }
    #
    #                       srcImg:     image
    #                       destImg:    image
    #                       angle:      float
    #                       xcen, ycen: float, optional (0.5, 0.5)
    #                       fillMode:   string, optional (FILL)
    #
    #       Description:    Image "srcImg" is rotated counterclockwise by
    #                       "angle" around position (xcen, ycen).  The result
    #                       is stored in image "destImg".
    #
    #                       "fillMode" determines how pixels in "destImg",
    #                       which are not covered by the rotated version of
    #                       "srcImg" are treated.
    #                       Acceptable values for "fillMode" are FILL, CLIP, WRAP. 
    #                       See the description of "WarpFunct" for details.
    #
    #                       The x and y coordinates in "srcImg" and "destImg"
    #                       increase from left to right and from bottom to top.
    #                       The lower left  corners are at (0.0, 0.0).
    #                       The upper right corners are at (1.0, 1.0).
    #
    #                       "angle" is measured in radians. 
    #                       Use procedure "poVecUtil::DegToRad" to convert an angle
    #                       given in degrees into radians.
    #
    #      States:          State settings influencing functionality:
    #                       Draw mask:    No
    #                       Draw mode:    No
    #                       Draw color:   No
    #                       Threading:    Yes
    #                       UByte format: All
    #                       Float format: All 
    #
    #       Return Value:   None.
    #
    #       See also:       poImgUtil::Transform
    #                       IP_WarpFunct
    #                       IP_SetNumThreads
    #
    ###########################################################################

    proc Rotate { srcImg destImg angle { xcen 0.5 } { ycen 0.5 } { fillMode FILL } } {

        # Get hold of the rotation center and of the out-of-range
        # pixel treatment mode; supply default values for data not
        # specified by the user.

        set center [list [expr {-1.0 * $xcen}] [expr {-1.0 * $ycen}]]

        # Build the transformation matrix to do the rotation:
        # First shift the image so that the intended center of rotation
        # coincides with the coordinate origin.

        poVec TfmBuildTrans2D $center tfmTrans1

        # Scale the image in y to compensate for the aspect ratio of the
        # destination image.

        $destImg GetImageInfo w h aspect
        set scale [list 1.0 [expr {1.0 / $aspect}]]
        poVec TfmConcatScale2D $scale $tfmTrans1 tfmScale1

        # Rotate the image.

        set rot [expr {-1.0 * $angle}]
        poVec TfmConcatRot2D $rot $tfmScale1 tfmRot1

        # Scale the image in y a second time, to compensate for the aspect
        # ratio of the source image.

        $srcImg GetImageInfo w h aspect
        set scale [list 1.0 $aspect]
        poVec TfmConcatScale2D $scale $tfmRot1 tfmScale2

        # Shift the image so that the center of rotation moves back to its
        # original position.

        poVec VecScale2D -1 $center center2
        poVec TfmConcatTrans2D $center2 $tfmScale2 tfmTrans2

        # Finally, rotate the source image according to the transformation
        # matrix.

        Transform $srcImg $destImg $tfmTrans2 $fillMode
    }
}
