diff --git a/docs/tools/Makefile.am b/docs/tools/Makefile.am index 80ddcf2114..e3b2966bad 100644 --- a/docs/tools/Makefile.am +++ b/docs/tools/Makefile.am @@ -26,6 +26,8 @@ endif doc_shooter_DEPENDENCIES = $(DEPS) doc_shooter_LDADD = $(LDADDS) doc_shooter_SOURCES= \ + shadow.c \ + shadow.h \ shooter.c \ widgets.c \ widgets.h diff --git a/docs/tools/shadow.c b/docs/tools/shadow.c new file mode 100644 index 0000000000..67c31fe709 --- /dev/null +++ b/docs/tools/shadow.c @@ -0,0 +1,149 @@ +#include "shadow.h" +#include + +#define BLUR_RADIUS 5 +#define SHADOW_OFFSET (BLUR_RADIUS * 4 / 5) +#define SHADOW_OPACITY 0.75 + +typedef struct { + int size; + double *data; +} ConvFilter; + +static double +gaussian (double x, double y, double r) +{ + return ((1 / (2 * M_PI * r)) * + exp ((- (x * x + y * y)) / (2 * r * r))); +} + +static ConvFilter * +create_blur_filter (int radius) +{ + ConvFilter *filter; + int x, y; + double sum; + + filter = g_new0 (ConvFilter, 1); + filter->size = radius * 2 + 1; + filter->data = g_new (double, filter->size * filter->size); + + sum = 0.0; + + for (y = 0 ; y < filter->size; y++) + { + for (x = 0 ; x < filter->size; x++) + { + sum += filter->data[y * filter->size + x] = gaussian (x - (filter->size >> 1), + y - (filter->size >> 1), + radius); + } + } + + for (y = 0; y < filter->size; y++) + { + for (x = 0; x < filter->size; x++) + { + filter->data[y * filter->size + x] /= sum; + } + } + + return filter; + +} + +static GdkPixbuf * +create_shadow (GdkPixbuf *src) +{ + int x, y, i, j; + int width, height; + GdkPixbuf *dest; + static ConvFilter *filter = NULL; + int src_rowstride, dest_rowstride; + int src_bpp, dest_bpp; + + guchar *src_pixels, *dest_pixels; + + if (!filter) + filter = create_blur_filter (BLUR_RADIUS); + + width = gdk_pixbuf_get_width (src) + BLUR_RADIUS * 2 + SHADOW_OFFSET; + height = gdk_pixbuf_get_height (src) + BLUR_RADIUS * 2 + SHADOW_OFFSET; + + dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src), + gdk_pixbuf_get_has_alpha (src), + gdk_pixbuf_get_bits_per_sample (src), + width, height); + gdk_pixbuf_fill (dest, 0); + src_pixels = gdk_pixbuf_get_pixels (src); + src_rowstride = gdk_pixbuf_get_rowstride (src); + src_bpp = gdk_pixbuf_get_has_alpha (src) ? 4 : 3; + + dest_pixels = gdk_pixbuf_get_pixels (dest); + dest_rowstride = gdk_pixbuf_get_rowstride (dest); + dest_bpp = gdk_pixbuf_get_has_alpha (dest) ? 4 : 3; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + int sumr = 0, sumg = 0, sumb = 0, suma = 0; + + for (i = 0; i < filter->size; i++) + { + for (j = 0; j < filter->size; j++) + { + int src_x, src_y; + + src_y = -(BLUR_RADIUS + SHADOW_OFFSET) + y - (filter->size >> 1) + i; + src_x = -(BLUR_RADIUS + SHADOW_OFFSET) + x - (filter->size >> 1) + j; + + if (src_y < 0 || src_y > gdk_pixbuf_get_height (src) || + src_x < 0 || src_x > gdk_pixbuf_get_width (src)) + continue; + + sumr += src_pixels [src_y * src_rowstride + + src_x * src_bpp + 0] * + filter->data [i * filter->size + j]; + sumg += src_pixels [src_y * src_rowstride + + src_x * src_bpp + 1] * + filter->data [i * filter->size + j]; + + sumb += src_pixels [src_y * src_rowstride + + src_x * src_bpp + 2] * + filter->data [i * filter->size + j]; + + if (src_bpp == 4) + suma += src_pixels [src_y * src_rowstride + + src_x * src_bpp + 3] * + filter->data [i * filter->size + j]; + + + } + } + + if (dest_bpp == 4) + dest_pixels [y * dest_rowstride + + x * dest_bpp + 3] = suma * SHADOW_OPACITY; + + } + } + + return dest; +} + +GdkPixbuf * +create_shadowed_pixbuf (GdkPixbuf *src) +{ + GdkPixbuf *dest; + + dest = create_shadow (src); + + gdk_pixbuf_composite (src, dest, + BLUR_RADIUS, BLUR_RADIUS, + gdk_pixbuf_get_width (src), + gdk_pixbuf_get_height (src), + BLUR_RADIUS, BLUR_RADIUS, 1.0, 1.0, + GDK_INTERP_NEAREST, 255); + return dest; +} diff --git a/docs/tools/shadow.h b/docs/tools/shadow.h new file mode 100644 index 0000000000..2f569cc349 --- /dev/null +++ b/docs/tools/shadow.h @@ -0,0 +1,8 @@ +#ifndef __SHADOW_H__ +#define __SHADOW_H__ + +#include + +GdkPixbuf *create_shadowed_pixbuf (GdkPixbuf *src); + +#endif /* __SHADOW_H__ */ diff --git a/docs/tools/shooter.c b/docs/tools/shooter.c index 84c993e25c..1d3eeb26df 100644 --- a/docs/tools/shooter.c +++ b/docs/tools/shooter.c @@ -1,101 +1,184 @@ - +#include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "widgets.h" +#include "shadow.h" -typedef enum { - SNAPSHOT_WINDOW, - SNAPSHOT_DRAW -} SnapshotMode; +#define MAXIMUM_WM_REPARENTING_DEPTH 4 +#ifndef _ +#define _(x) (x) +#endif -static gboolean -quit_when_idle (gpointer loop) +static Window +find_toplevel_window (Window xid) { - g_main_loop_quit (loop); + Window root, parent, *children; + guint nchildren; - return G_SOURCE_REMOVE; + do + { + if (XQueryTree (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xid, &root, + &parent, &children, &nchildren) == 0) + { + g_warning ("Couldn't find window manager window"); + return 0; + } + + if (root == parent) + return xid; + + xid = parent; + } + while (TRUE); } -static void -check_for_draw (GdkEvent *event, gpointer loop) +static GdkPixbuf * +add_border_to_shot (GdkPixbuf *pixbuf) { - if (event->type == GDK_EXPOSE) - { - g_idle_add (quit_when_idle, loop); - gdk_event_handler_set ((GdkEventFunc) gtk_main_do_event, NULL, NULL); - } + GdkPixbuf *retval; - gtk_main_do_event (event); + retval = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + gdk_pixbuf_get_width (pixbuf) + 2, + gdk_pixbuf_get_height (pixbuf) + 2); + + /* Fill with solid black */ + gdk_pixbuf_fill (retval, 0xFF); + gdk_pixbuf_copy_area (pixbuf, + 0, 0, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf), + retval, 1, 1); + + return retval; } -static cairo_surface_t * -snapshot_widget (GtkWidget *widget, SnapshotMode mode) +static GdkPixbuf * +remove_shaped_area (GdkPixbuf *pixbuf, + Window window) { - cairo_surface_t *surface; - cairo_pattern_t *bg; - GMainLoop *loop; - cairo_t *cr; + GdkPixbuf *retval; + XRectangle *rectangles; + int rectangle_count, rectangle_order; + int i; - g_assert (gtk_widget_get_realized (widget)); + retval = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf)); + + gdk_pixbuf_fill (retval, 0); + rectangles = XShapeGetRectangles (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), window, + ShapeBounding, &rectangle_count, &rectangle_order); - surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), - CAIRO_CONTENT_COLOR, - gtk_widget_get_allocated_width (widget), - gtk_widget_get_allocated_height (widget)); - - loop = g_main_loop_new (NULL, FALSE); - /* We wait until the widget is drawn for the first time. - * We can not wait for a GtkWidget::draw event, because that might not - * happen if the window is fully obscured by windowed child widgets. - * Alternatively, we could wait for an expose event on widget's window. - * Both of these are rather hairy, not sure what's best. */ - gdk_event_handler_set (check_for_draw, loop, NULL); - g_main_loop_run (loop); - - cr = cairo_create (surface); - - switch (mode) + for (i = 0; i < rectangle_count; i++) { - case SNAPSHOT_WINDOW: - { - GdkWindow *window = gtk_widget_get_window (widget); - if (gdk_window_get_window_type (window) == GDK_WINDOW_TOPLEVEL || - gdk_window_get_window_type (window) == GDK_WINDOW_FOREIGN) - { - /* give the WM/server some time to sync. They need it. - * Also, do use popups instead of toplevls in your tests - * whenever you can. */ - gdk_display_sync (gdk_window_get_display (window)); - g_timeout_add (500, quit_when_idle, loop); - g_main_loop_run (loop); - } - gdk_cairo_set_source_window (cr, window, 0, 0); - cairo_paint (cr); - } - break; - case SNAPSHOT_DRAW: - bg = gdk_window_get_background_pattern (gtk_widget_get_window (widget)); - if (bg) - { - cairo_set_source (cr, bg); - cairo_paint (cr); - } - gtk_widget_draw (widget, cr); - break; - default: - g_assert_not_reached(); - break; + int y, x; + + for (y = rectangles[i].y; y < rectangles[i].y + rectangles[i].height; y++) + { + guchar *src_pixels, *dest_pixels; + + src_pixels = gdk_pixbuf_get_pixels (pixbuf) + + y * gdk_pixbuf_get_rowstride (pixbuf) + + rectangles[i].x * (gdk_pixbuf_get_has_alpha (pixbuf) ? 4 : 3); + dest_pixels = gdk_pixbuf_get_pixels (retval) + + y * gdk_pixbuf_get_rowstride (retval) + + rectangles[i].x * 4; + + for (x = rectangles[i].x; x < rectangles[i].x + rectangles[i].width; x++) + { + *dest_pixels++ = *src_pixels ++; + *dest_pixels++ = *src_pixels ++; + *dest_pixels++ = *src_pixels ++; + *dest_pixels++ = 255; + + if (gdk_pixbuf_get_has_alpha (pixbuf)) + src_pixels++; + } + } } - cairo_destroy (cr); - g_main_loop_unref (loop); - gtk_widget_destroy (widget); + return retval; +} - return surface; +static GdkPixbuf * +take_window_shot (Window child, + gboolean include_decoration) +{ + GdkWindow *window; + Window xid; + gint x_orig, y_orig; + gint x = 0, y = 0; + gint width, height; + + GdkPixbuf *tmp, *tmp2; + GdkPixbuf *retval; + + if (include_decoration) + xid = find_toplevel_window (child); + else + xid = child; + + window = gdk_x11_window_foreign_new_for_display (gdk_display_get_default (), xid); + + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); + gdk_window_get_origin (window, &x_orig, &y_orig); + + if (x_orig < 0) + { + x = - x_orig; + width = width + x_orig; + x_orig = 0; + } + + if (y_orig < 0) + { + y = - y_orig; + height = height + y_orig; + y_orig = 0; + } + + if (x_orig + width > gdk_screen_width ()) + width = gdk_screen_width () - x_orig; + + if (y_orig + height > gdk_screen_height ()) + height = gdk_screen_height () - y_orig; + + tmp = gdk_pixbuf_get_from_window (window, + x, y, width, height); + + if (include_decoration) + tmp2 = remove_shaped_area (tmp, xid); + else + tmp2 = add_border_to_shot (tmp); + + retval = create_shadowed_pixbuf (tmp2); + g_object_unref (tmp); + g_object_unref (tmp2); + + return retval; } int main (int argc, char **argv) { GList *toplevels; + GdkPixbuf *screenshot = NULL; GList *node; /* If there's no DISPLAY, we silently error out. We don't want to break @@ -107,19 +190,42 @@ int main (int argc, char **argv) for (node = toplevels; node; node = g_list_next (node)) { + GtkAllocation allocation; + GdkWindow *window; WidgetInfo *info; + XID id; char *filename; - cairo_surface_t *surface; info = node->data; gtk_widget_show (info->window); - surface = snapshot_widget (info->window, - info->include_decorations ? SNAPSHOT_WINDOW : SNAPSHOT_DRAW); + window = gtk_widget_get_window (info->window); + gtk_widget_get_allocation (info->window, &allocation); + + gtk_widget_show_now (info->window); + gtk_widget_queue_draw_area (info->window, + allocation.x, allocation.y, + allocation.width, allocation.height); + gdk_window_process_updates (window, TRUE); + + while (gtk_events_pending ()) + { + gtk_main_iteration (); + } + sleep (1); + + while (gtk_events_pending ()) + { + gtk_main_iteration (); + } + + id = gdk_x11_window_get_xid (window); + screenshot = take_window_shot (id, info->include_decorations); filename = g_strdup_printf ("./%s.png", info->name); - g_assert (cairo_surface_write_to_png (surface, filename) == CAIRO_STATUS_SUCCESS); - g_free (filename); + gdk_pixbuf_save (screenshot, filename, "png", NULL, NULL); + g_free(filename); + gtk_widget_hide (info->window); } return 0;