Capture a window into an image

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?