- add support for getting a subimage of the window
authorTony Cook <tony@develop-help.com>
Tue, 16 Jan 2007 09:33:50 +0000 (09:33 +0000)
committerTony Cook <tony@develop-help.com>
Tue, 16 Jan 2007 09:33:50 +0000 (09:33 +0000)
- include the interface header in the implementations so we
  get errors when they don't match (doh!)
- return something from the X11 error handler
- add X11/Xutil.h include for XDestroyImage()
- handle the default (no id or hwnd) case correctly

Changes
Screenshot.pm
Screenshot.xs
imss.h
scwin32.c
scx11.c

diff --git a/Changes b/Changes
index 6b7a8da..27c9be7 100755 (executable)
--- a/Changes
+++ b/Changes
@@ -1,3 +1,11 @@
+0.004
+       - add support for getting a subimage of the window
+       - include the interface header in the implementations so we
+         get errors when they don't match (doh!)
+       - return something from the X11 error handler
+       - add X11/Xutil.h include for XDestroyImage()
+       - handle the default (no id or hwnd) case correctly
+
 0.003 12 Jan 2007
        - 0.003 release
        - ignore some VC++ generated junk in MANIFEST.SKIP
index fb43237..28b6e24 100644 (file)
@@ -27,24 +27,28 @@ sub screenshot {
   # lose the class if called as a method
   @_ % 2 == 1 and shift;
 
-  my %opts = (decor => 0, display => 0, @_);
+  my %opts = 
+    (
+     decor => 0, 
+     display => 0, 
+     left => 0, 
+     top => 0,
+     right => 0,
+     bottom => 0,
+     @_);
 
   my $result;
-  if (!@_) {
-    my $result =
-      defined &_win32 ? _win32(0) :
-       defined &_x11 ? _x11($opts{display}, 0) :
-          die "No drivers enabled\n";
-  }
   if (defined $opts{hwnd}) {
     defined &_win32
       or die "Win32 driver not enabled\n";
-    $result = _win32($opts{hwnd}, $opts{decor});
+    $result = _win32($opts{hwnd}, $opts{decor}, $opts{left}, $opts{top},
+                    $opts{right}, $opts{bottom});
   }
   elsif (defined $opts{id}) { # X11 window id
     defined &_x11
       or die "X11 driver not enabled\n";
-    $result = _x11($opts{display}, $opts{id});
+    $result = _x11($opts{display}, $opts{id}, $opts{left}, $opts{top},
+                  $opts{right}, $opts{bottom});
   }
   elsif ($opts{widget}) {
     # Perl/Tk widget
@@ -55,7 +59,8 @@ sub screenshot {
         Imager->_set_error("Win32 Tk and Win32 support not built");
         return;
       }
-      $result = _win32(hex($opts{widget}->id));
+      $result = _win32(hex($opts{widget}->id), $opts{decor}, 
+                      $opts{left}, $opts{top}, $opts{right}, $opts{bottom});
     }
     elsif ($sys eq 'x11') {
       unless (defined &_x11) {
@@ -68,13 +73,22 @@ sub screenshot {
         and $id_hex = $opts{widget}->frame;
       
       # is there a way to get the display pointer from Tk?
-      $result = _x11(0, hex($id_hex));
+      $result = _x11($opts{display}, hex($id_hex), $opts{left}, $opts{top},
+                    $opts{right}, $opts{bottom});
     }
     else {
       Imager->_set_error("Unsupported windowing system '$sys'");
       return;
     }
   }
+  else {
+    $result =
+      defined &_win32 ? _win32(0, $opts{decor}, $opts{left}, $opts{top},
+                              $opts{right}, $opts{bottom}) :
+       defined &_x11 ? _x11($opts{display}, 0, $opts{left}, $opts{top},
+                            $opts{right}, $opts{bottom}) :
+          die "No drivers enabled\n";
+  }
 
   unless ($result) {
     Imager->_set_error(Imager->_error_as_msg());
@@ -170,7 +184,7 @@ Note: taking a screenshot of a remote display is slow.
 
 =item screenshot
 
-If no parameters are supplied:
+If no C<id> or C<hwnd> parameter is supplied:
 
 =over
 
@@ -188,6 +202,46 @@ otherwise, die.
 
 =back
 
+You can also supply the following parameters to retrieve a subset of
+the window:
+
+=over
+
+=item *
+
+left
+
+=item *
+
+top
+
+=item *
+
+right
+
+=item *
+
+bottom
+
+=back
+
+If left or top is negative, then treat that as from the right/bottom
+edge of the window.
+
+If right ot bottom is zero or negative then treat as from the
+right/bottom edge of the window.
+
+So setting all 4 values to 0 retrieves the whole window.
+
+  # a 10-pixel wide right edge of the window
+  my $right_10 = screenshot(left => -10, ...);
+
+  # the top-left 100x100 portion of the window
+  my $topleft_100 = screenshot(right => 100, bottom => 100, ...);
+
+  # 10x10 pixel at the bottom right corner
+  my $bott_right_10 = screenshot(left => -10, top => -10, ...);
+
 =item have_win32
 
 Returns true if Win32 support is available.
@@ -210,6 +264,19 @@ Closes a display returned by Imager::Screenshot::x11_open().
 
 =back
 
+=head1 CAVEATS
+
+It's possible to have more than one grab driver available, for
+example, Win32 and X11, and which is used can have an effect on the
+result.
+
+Under Win32, if there's a screesaver running, then you grab the
+results of the screensaver.
+
+Grabbing the root window on a rootless server (eg. Cygwin/X) may not
+grab the background.  In fact, when I tested under Cygwin/X I got the
+xterm window contents even when the Windows screensaver was running.
+
 =head1 LICENSE
 
 Imager::Screenshot is licensed under the same terms as Perl itself.
index 6d56685..e21cca2 100644 (file)
@@ -16,18 +16,26 @@ PROTOTYPES: DISABLE
 #ifdef SS_WIN32
 
 Imager
-imss_win32(hwnd, include_decor = 0)
+imss_win32(hwnd, include_decor = 0, left = 0, top = 0, right = 0, bottom = 0)
        unsigned hwnd
        int include_decor
+       int left
+       int top
+       int right
+       int bottom
 
 #endif
 
 #ifdef SS_X11
 
 Imager
-imss_x11(display, window_id)
+imss_x11(display, window_id, left = 0, top = 0, right = 0, bottom = 0)
         unsigned long display
        int window_id
+       int left
+       int top
+       int right
+       int bottom
 
 unsigned long
 imss_x11_open(display_name = NULL)
diff --git a/imss.h b/imss.h
index 8a0ced2..1ecb490 100644 (file)
--- a/imss.h
+++ b/imss.h
@@ -2,10 +2,10 @@
 #define IMSS_H
 
 extern i_img *
-imss_win32(unsigned hwnd, int include_decor);
+imss_win32(unsigned hwnd, int include_decor, int left, int top, int right, int bottom);
 
 extern i_img *
-imss_x11(unsigned long display, int window_id);
+imss_x11(unsigned long display, int window_id, int left, int top, int right, int bottom);
 
 extern unsigned long
 imss_x11_open(char const *display_name);
index dad6436..13c726d 100644 (file)
--- a/scwin32.c
+++ b/scwin32.c
@@ -1,17 +1,20 @@
 #include "imext.h"
 #include <windows.h>
 #include <string.h>
+#include "imss.h"
 
 i_img *
-imss_win32(unsigned hwnd_u, int include_decor) {
+imss_win32(unsigned hwnd_u, int include_decor, int left, int top, 
+          int right, int bottom) {
   HWND hwnd = (HWND)hwnd_u;
   HDC wdc, bmdc;
   RECT rect;
   HBITMAP work_bmp, old_dc_bmp;
-  int width, height;
+  int window_width, window_height;
   BITMAPINFO bmi;
   unsigned char *di_bits;
   i_img *result = NULL;
+  int width, height;
 
   i_clear_error();
 
@@ -31,12 +34,41 @@ imss_win32(unsigned hwnd_u, int include_decor) {
     return NULL;
   }
 
-  width = rect.right - rect.left;
-  height = rect.bottom - rect.top;
+  window_width = rect.right - rect.left;
+  window_height = rect.bottom - rect.top;
+
+  /* adjust negative/zero values to window size */
+  if (left < 0)
+    left += window_width;
+  if (top < 0)
+    top += window_height;
+  if (right <= 0)
+    right += window_width;
+  if (bottom <= 0)
+    bottom += window_height;
+  
+  /* clamp */
+  if (left < 0)
+    left = 0;
+  if (right > window_width)
+    right = window_width;
+  if (top < 0)
+    top = 0;
+  if (bottom > window_height)
+    bottom = window_height;
+
+  /* validate */
+  if (right <= left || bottom <= top) {
+    i_push_error(0, "image would be empty");
+    return NULL;
+  }
+  width = right - left;
+  height = bottom - top;
+
   work_bmp = CreateCompatibleBitmap(wdc, width, height);
   bmdc = CreateCompatibleDC(wdc);
   old_dc_bmp = SelectObject(bmdc, work_bmp);
-  BitBlt(bmdc, 0, 0, width, height, wdc, 0, 0, SRCCOPY);
+  BitBlt(bmdc, 0, 0, width, height, wdc, left, top, SRCCOPY);
 
   /* make a dib */
   memset(&bmi, 0, sizeof(bmi));
diff --git a/scx11.c b/scx11.c
index bdea60f..64df505 100644 (file)
--- a/scx11.c
+++ b/scx11.c
@@ -1,5 +1,7 @@
 #include "imext.h"
 #include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include "imss.h"
 
 static
 int
@@ -8,10 +10,13 @@ my_handler(Display *display, XErrorEvent *error) {
 
   XGetErrorText(display, error->error_code, buffer, sizeof(buffer));
   i_push_error(error->error_code, buffer);
+
+  return 0;
 }
 
 i_img *
-imss_x11(unsigned long display_ul, unsigned long window_id) {
+imss_x11(unsigned long display_ul, int window_id,
+        int left, int top, int right, int bottom) {
   Display *display = (Display *)display_ul;
   int own_display = 0; /* non-zero if we connect */
   GC gc;
@@ -22,6 +27,7 @@ imss_x11(unsigned long display_ul, unsigned long window_id) {
   int x, y;
   XColor *colors;
   XErrorHandler old_handler;
+  int width, height;
 
   i_clear_error();
 
@@ -51,7 +57,37 @@ imss_x11(unsigned long display_ul, unsigned long window_id) {
     return NULL;
   }
 
-  image = XGetImage(display, window_id, 0, 0, attr.width, attr.height,
+  /* adjust negative/zero values to window size */
+  if (left < 0)
+    left += attr.width;
+  if (top < 0)
+    top += attr.height;
+  if (right <= 0)
+    right += attr.width;
+  if (bottom <= 0)
+    bottom += attr.height;
+  
+  /* clamp */
+  if (left < 0)
+    left = 0;
+  if (right > attr.width)
+    right = attr.width;
+  if (top < 0)
+    top = 0;
+  if (bottom > attr.height)
+    bottom = attr.height;
+
+  /* validate */
+  if (right <= left || bottom <= top) {
+    XSetErrorHandler(old_handler);
+    if (own_display)
+      XCloseDisplay(display);
+    i_push_error(0, "image would be empty");
+    return NULL;
+  }
+  width = right - left;
+  height = bottom - top;
+  image = XGetImage(display, window_id, left, top, width, height,
                     -1, ZPixmap);
   if (!image) {
     XSetErrorHandler(old_handler);
@@ -61,24 +97,24 @@ imss_x11(unsigned long display_ul, unsigned long window_id) {
     return NULL;
   }
 
-  result = i_img_8_new(attr.width, attr.height, 3);
-  line = mymalloc(sizeof(i_color) * attr.width);
-  colors = mymalloc(sizeof(XColor) * attr.width);
-  for (y = 0; y < attr.height; ++y) {
+  result = i_img_8_new(width, height, 3);
+  line = mymalloc(sizeof(i_color) * width);
+  colors = mymalloc(sizeof(XColor) * width);
+  for (y = 0; y < height; ++y) {
     cp = line;
     /* XQueryColors seems to be a round-trip, so do one big request
        instead of one per pixel */
-    for (x = 0; x < attr.width; ++x) {
+    for (x = 0; x < width; ++x) {
       colors[x].pixel = XGetPixel(image, x, y);
     }
-    XQueryColors(display, attr.colormap, colors, attr.width);
-    for (x = 0; x < attr.width; ++x) {
+    XQueryColors(display, attr.colormap, colors, width);
+    for (x = 0; x < width; ++x) {
       cp->rgb.r = colors[x].red >> 8;
       cp->rgb.g = colors[x].green >> 8;
       cp->rgb.b = colors[x].blue >> 8;
       ++cp;
     }
-    i_plin(result, 0, attr.width, y, line);
+    i_plin(result, 0, width, y, line);
   }
   myfree(line);
   myfree(colors);