Canvas2Image

kroc 4 Jun 2003 - This proc will create an image from a canvas. Actually, it only handle few canvas objects: images, rectangles, lines and circles, but others might been supported later. The proc return image name.

 proc Canvas2Image { canvas } {
     # Start with a blank new image:
     set largeur [$canvas cget -width]
     set hauteur [$canvas cget -height]
     image create photo capture -width $largeur -height $hauteur
     capture put #FFFFFF -to 0 0 $largeur $hauteur
     # Get each canvas objects:
     foreach objet [$canvas find all] {
         if {[lsearch [$canvas type $objet] image] >= 0} {
             # Images
             set coords [$canvas bbox $objet]
             set Xcap [expr {int([lindex $coords 0])}]
             set Ycap [expr {int([lindex $coords 1])}]
             set Xfin [expr {int([lindex $coords 2])}]
             set Yfin [expr {int([lindex $coords 3])}]
             # Image could be move outside the canvas, copy only visible part:
             if { $Xcap < 0} {
                 set sX [expr {abs($Xcap)}]
                 set Xcap 0
             } else {
                 set sX 0
             }
             if { $Ycap < 0} {
                 set sY [expr {abs($Ycap)}]
                 set Ycap 0
             } else {
                 set sY 0
             }
             if { $Xfin >= 0 && $Yfin >= 0 } {
                 set nom [$canvas itemcget $objet -image]
                 if { [string equal -nocase [image type $nom] photo] } {
                     capture copy $nom -from $sX $sY -to $Xcap $Ycap $Xfin $Yfin
                 } else {
                     image delete $nom
                 }
             }
         }
         if {[lsearch [$canvas type $objet] rectangle] >= 0} {
             # Rectangles
             set width [expr {[$canvas itemcget $objet -width]/2.0}]
             if {$width <= 0.5} {
                 set width 0.5
             }
             set coords [$canvas coords $objet]
             set capcoul [$canvas itemcget $objet -outline]
             set x1a [expr {round([lindex $coords 0]-$width)}]
             set x1b [expr {round([lindex $coords 0]+$width)}]
             set y1a [expr {round([lindex $coords 1]-$width)}]
             set y1b [expr {round([lindex $coords 1]+$width)}]
             set x2a [expr {round([lindex $coords 2]-$width)}]
             set x2b [expr {round([lindex $coords 2]+$width)}]
             set y2a [expr {round([lindex $coords 3]-$width)}]
             set y2b [expr {round([lindex $coords 3]+$width)}]
             # as it could be move outside the canvas, copy only visible part:
             if {$x1a < 0} { set x1a 0 }
             if {$x1b < 0} { set x1b 0 }
             if {$y1a < 0} { set y1a 0 }
             if {$y1b < 0} { set y1b 0 }
             if {$x2a < 0} { set x2a 0 }
             if {$x2b < 0} { set x2b 0 }
             if {$y2a < 0} { set y2a 0 }
             if {$y2b < 0} { set y2b 0 }
             capture put $capcoul -to $x1a $y1a $x1b $y2b
             capture put $capcoul -to $x2a $y1a $x2b $y2b
             capture put $capcoul -to $x1a $y1a $x2a $y1b
             capture put $capcoul -to $x1a $y2a $x2a $y2b
         }
         if {[lsearch [$canvas type $objet] line] >= 0} {
             # lines
             set width [expr {[$canvas itemcget $objet -width]}]
             if {$width <= 1.0} {
                 set width 1.0
             }
             set coords [$canvas coords $objet]
             set capcoul [$canvas itemcget $objet -fill]
             set x1 [expr {int([lindex $coords 0])}]
             set y1 [expr {int([lindex $coords 1])}]
             set x2 [expr {int([lindex $coords 2])}]
             set y2 [expr {int([lindex $coords 3])}]
             if { $x1==$x2 } {
                 # as it could be move outside the canvas, copy only visible part:
                 if {$x1 < 0} { set x1 0 }
                 if {$x2 < 0} { set x2 0 }
                 if {$y1 < 0} { set y1 0 }
                 if {$y2 < 0} { set y2 0 }
                 capture put $capcoul -to [expr {round($x1-($width/2.0))}] $y1 \
                     [expr {round($x2+($width/2.0))}] $y2
             } elseif { $y1==$y2 } {
                 # as it could be move outside the canvas, copy only visible part:
                 if {$x1 < 0} { set x1 0 }
                 if {$x2 < 0} { set x2 0 }
                 if {$y1 < 0} { set y1 0 }
                 if {$y2 < 0} { set y2 0 }
                 capture put $capcoul -to $x1 [expr {round($y1-($width/2.0))}] $x2 \
                     [expr {round($y2+($width/2.0))}]
             } else {
                 set m [expr {((1.0*$y2)-$y1)/($x2-$x1)}]
                 set w1 [expr {$width/-2}]
                 set w2 [expr {$width/2}]
                 set beta [expr {2.0*atan(1.0)+atan($m)}]
                 set step [expr abs(1.0/$m)]
                 if {$step >= 0.5} { set step 0.5 }
                 if {$x1 > $x2} {
                     set x1 [expr {int([lindex $coords 2])}]
                     set y1 [expr {int([lindex $coords 3])}]
                     set x2 [expr {int([lindex $coords 0])}]
                     set y2 [expr {int([lindex $coords 1])}]
                     # as it could be move outside the canvas, copy only visible part:
                     if {$x1 < 0} { set x1 0 }
                     if {$x2 < 0} { set x2 0 }
                     if {$y1 < 0} { set y1 0 }
                     if {$y2 < 0} { set y2 0 }
                 }
                 for {set x $x1} {$x <= $x2} {set x [expr {$x+$step}]} {
                     for { set dx $w1 } { $dx <= $w2 } {set dx [expr {$dx+$step}]} {
                         set y [expr {$m*($x-$x1)+$y1}]
                         set yt [expr {round($y+($dx*sin($beta)))}]
                         set xt [expr {round($x+($dx*cos($beta)))}]
                         if {$xt <= 0} { set xt 0 }
                         if {$yt <= 0} { set yt 0 }
                         capture put $capcoul -to $xt $yt
                     }
                 }
             }
         }
         if {[lsearch [$canvas type $objet] oval] >= 0} {
             # only circles
             set width [expr {[$canvas itemcget $objet -width]}]
             if {$width <= 1.0} {
                 set width 1.0
             }
             set 2pi [expr {8.0*atan(1)}]
             set coords [$canvas bbox $objet]
             set capcoul [$canvas itemcget $objet -outline]
             set a [expr {([lindex $coords 0]+[lindex $coords 2])/2.0}]
             set b [expr {([lindex $coords 1]+[lindex $coords 3])/2.0}]
             set rmax [expr {[lindex $coords 2]-$a}]
             set rmin [expr {$rmax-$width}]
             set step [expr {1.0/$rmax}]
             for {set R $rmin} {$R <= $rmax} {set R [expr {$R+0.4}]} {
                 for {set alpha 0} {$alpha <= $2pi} {set alpha [expr {$alpha+$step}]} {
                     set x [expr {$a+($R*cos($alpha))}]
                     set y [expr {$b+($R*sin($alpha))}]
                     if {$x <= 0} { set x 0 }
                     if {$y <= 0} { set y 0 }
                     capture put $capcoul -to [expr {round($x)}] [expr {round($y)}]
                 }
             }
         }
     }
     return capture
 }

Here is a little script you could append to test it :

 pack [canvas .c]
 .c create line 10 10 100 100 -width 12 -fill blue
 .c create line -10 10 80 100 -width 12 -fill red
 .c create line 10 -10 100 80 -width 12 -fill green
 .c create rectangle 125 15 223 212 -width 8 -outline yellow
 .c create oval 100 100 200 200 -width 4 -outline  [tk_chooseColor]
 update
 Canvas2Image .c
 toplevel .i
 pack [canvas .i.c]
 .i.c create image 0 0 -image capture -anchor nw
 update

RS 2005-07-22: Alternatively, if you have the Img package,

 pack [canvas .c]
 #-- ... add canvas items here ... wait for window to be visible ...
 set im [image create photo -data .c]
 $im write myname.gif -format GIF

is enough to dump a visible canvas's contents to a file :)

KPV 2006-04-13: To capture not just the visible part of a canvas but the whole canvas with Img check out Capturing Multiple Screens.


pi31415 2012-05-06: Alternatively, if you have ghostscript and TCL 8.6

# Set the GS_PROG environment variable first.
# On Windows, you could use gswin32c.exe or gswin64c.exe

package require Tcl 8.6
package require Tk

namespace eval ::canvas2photo {
    set ghostscript undefined

    proc construct {} {
        if {[info exists ::env(GS_PROG)]} {
            set ::canvas2photo::ghostscript $::env(GS_PROG)
        } else {
            set ::canvas2photo::ghostscript "gs"
        }
    }

    proc convert {canvas} {
        set epsfile undefined
        set height [$canvas cget -height]
        set pngfile undefined
        set retval undefined
        set width [$canvas cget -width]

        # save canvas to an EPS file
        set epsfile [::canvas2photo::getTempFilename]
        $canvas postscript      \
            -colormode color    \
            -file $epsfile      \
            -height $height     \
            -pageanchor nw      \
            -pageheight $height \
            -pagewidth $width   \
            -pagex 0            \
            -pagey $height      \
            -width $width

        # convert EPS to PNG
        set pngfile [::canvas2photo::getTempFilename]
        exec $::canvas2photo::ghostscript \
            -dBATCH                       \
            -dDEVICEHEIGHTPOINTS=$height  \
            -dDEVICEWIDTHPOINTS=$width    \
            -dNOPAUSE                     \
            -dSAFER                       \
            -sDEVICE=pngalpha             \
            -sOutputFile=$pngfile         \
            $epsfile

        # load PNG to photo
        set retval [image create photo -file $pngfile -format png]

        # clean up
        file delete -force $epsfile
        file delete -force $pngfile
        return $retval
    }

    proc getTempFilename {} {
        set fh [file tempfile retval]
        close $fh
        return $retval
    }

    construct
}