David Easton 17 Mar 2003 This uses the Img package to capture a screenshot of a widget hierarchy or toplevel window into a photo image. It is an extension of the canvas2photo techniques from the Img page.
The 'captureWindow' function can be passed any widget path, including that of a toplevel window. The image of the window/widget will contain white areas if the display is obscured by any other window (including transient windows).
Feel free to use, correct, improve, comment etc.
KPV See Capturing Multiple Screens for a way to capture more than one screenful.
# # Capture a window into an image # Author: David Easton # proc captureWindow { win } { package require Img regexp {([0-9]*)x([0-9]*)\+([0-9]*)\+([0-9]*)} [winfo geometry $win] - w h x y # Make the base image based on the window set image [image create photo -format window -data $win] foreach child [winfo children $win] { captureWindowSub $child $image 0 0 } return $image } proc captureWindowSub { win image px py } { if {![winfo ismapped $win]} { return } regexp {([0-9]*)x([0-9]*)\+([0-9]*)\+([0-9]*)} [winfo geometry $win] - w h x y incr px $x incr py $y # Make an image from this widget set tempImage [image create photo -format window -data $win] # Copy this image into place on the main image $image copy $tempImage -to $px $py image delete $tempImage foreach child [winfo children $win] { captureWindowSub $child $image $px $py } }
LH 24 Feb 2018 Quite a useful piece of code, David. Here is a shorter version that combines captureWindow and captureWindowSub into one proc, and removes some other redundant code.
package require Img proc CaptureWindow {win {baseImg ""} {px 0} {py 0}} { # create the base image of win (the root of capturing process) if {$baseImg eq ""} { set baseImg [image create photo -format window -data $win] } # paste images of win's children on the base image foreach child [winfo children $win] { if {![winfo ismapped $child]} continue set childImg [image create photo -format window -data $child] regexp {\+(\d*)\+(\d*)} [winfo geometry $child] -> x y $baseImg copy $childImg -to [incr x $px] [incr y $py] image delete $childImg CaptureWindow $child $baseImg $x $y } return $baseImg }
David Easton 17 Mar 2003 Here is a demo for above the above that creates a window and saves the screenshot to a file, when the user presses the 'x' key in the window.
proc windowToFile { win } { set image [captureWindow $win] set types {{"Image Files" {.gif}}} set filename [tk_getSaveFile -filetypes $types \ -initialfile capture.gif \ -defaultextension .gif] if {[llength $filename]} { $image write -format gif $filename puts "Written to file: $filename" } else { puts "Write cancelled" } image delete $image } proc demo { } { package require Tk wm withdraw . set top .t toplevel $top wm title $top "Demo" frame $top.f pack $top.f -fill both -expand 1 label $top.f.hello -text "Press x to capture window" pack $top.f.hello -s top -e 0 -f none -padx 10 -pady 10 checkbutton $top.f.b1 -text "CheckButton 1" checkbutton $top.f.b2 -text "CheckButton 2" radiobutton $top.f.r1 -text "RadioButton 1" -variable num -value 1 radiobutton $top.f.r2 -text "RadioButton 2" -variable num -value 2 pack $top.f.b1 $top.f.b2 $top.f.r1 $top.f.r2 \ -side top -expand 0 -fill none update bind $top <Key-x> [list windowToFile $top] } demo
TV Well, eehh, this is nice for making tk documentation for instance and probably interesting implementationwise, but isn't it possible to capture any window in some way? I do remember having tried and extension package which does this.
David Easton 17 Mar 2003 After a little research: BLT also provides a mechanism for taking a snapshot of a window using the command 'winop snap <window> <photoName>". Thus, the above gives a way of doing it using Img rather than BLT. BLT will show the contents of an overlapping window, whereas the above method blanks out any overlapping window. An example of taking a snapshot using BLT is:
proc bltCaptureWindow { win } { package require BLT # Make an empty photo image set image [image create photo] # Snapshot of window/widget winop snap $win $image return $image }
David Easton 2 Nov 2006 The following code will capture a whole screen except for the desktop which will appear black. This has been tested on Windows. This requires the BLT package.
proc captureScreenToImage {} { package require BLT # Try to make a unique window name set win ".tmp[clock seconds]" toplevel $win # Use frame as BLT crashed interpreter when trying winop on toplevel window pack [frame $win.fr -bg black -border 0] -expand true -fill both wm state $win zoomed wm overrideredirect $win 1 lower $win update idletasks set image [image create photo] blt::winop snap $win.fr $image destroy $win return $image } set image [captureScreenToImage] package require Img $image write Screenshot.gif -format gif ;# Only if 256 colours or less $image write Screenshot.png -format png $image write Screenshot.jpg -format jpeg $image write Screenshot.bmp -format bmp
The combination of photo image zooming and the Img extension let us code A little magnifying glass in just a few lines.
I added a proc to record window snapshots of an app with an animated image.
proc capture_snapshot { count } { set img [image create photo -format window -data .] set name [ format "./output/%05d.ppm" $count ] $img write $name -format ppm image delete $img }
This is called from the proc that updates each frame like:
update if { $make_movie == 1 } { capture_snapshot $count } incr count
On Linux this works just dandy. I get a bunch of ppm images, that I post process to jpeg, and then to an avi. On Windows, many (10-15) frames are skipped. Can anyone explain why? Can I fix this for Windows?