/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- eel-debug-drawing.c: Eel drawing debugging aids. Copyright (C) 2000 Eazel, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. Author: Ramiro Estrugo <ramiro@eazel.com> */ #include <config.h> #include "eel-debug-drawing.h" #include "eel-art-gtk-extensions.h" #include "eel-debug.h" #include "eel-gdk-extensions.h" #include "eel-gdk-extensions.h" #include "eel-gdk-pixbuf-extensions.h" #include "eel-gtk-extensions.h" #include "eel-gtk-extensions.h" #include <gtk/gtk.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> /* * PixbufViewer is a very simple "private" widget that displays * a GdkPixbuf. It is used by eel_debug_show_pixbuf() to * display a pixbuf in an in process pixbuf debugging window. * * We cant use EelImage for this because part of the reason * for pixbuf debugging is to debug EelImage itself. */ #define DEBUG_TYPE_PIXBUF_VIEWER debug_pixbuf_viewer_get_type() #define DEBUG_PIXBUF_VIEWER(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), DEBUG_TYPE_PIXBUF_VIEWER, DebugPixbufViewer)) #define DEBUG_IS_PIXBUF_VIEWER(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DEBUG_TYPE_PIXBUF_VIEWER)) typedef struct DebugPixbufViewer DebugPixbufViewer; typedef struct DebugPixbufViewerClass DebugPixbufViewerClass; static GType debug_pixbuf_viewer_get_type (void); static void debug_pixbuf_viewer_set_pixbuf (DebugPixbufViewer *viewer, GdkPixbuf *pixbuf); struct DebugPixbufViewer { GtkWidget widget; GdkPixbuf *pixbuf; }; struct DebugPixbufViewerClass { GtkWidgetClass parent_class; }; G_DEFINE_TYPE (DebugPixbufViewer, debug_pixbuf_viewer, GTK_TYPE_WIDGET) static void debug_pixbuf_viewer_init (DebugPixbufViewer *viewer) { gtk_widget_set_can_focus (GTK_WIDGET (viewer), FALSE); gtk_widget_set_has_window (GTK_WIDGET (viewer), FALSE); } static void debug_pixbuf_viewer_finalize (GObject *object) { DebugPixbufViewer *viewer; viewer = DEBUG_PIXBUF_VIEWER (object); eel_gdk_pixbuf_unref_if_not_null (viewer->pixbuf); viewer->pixbuf = NULL; G_OBJECT_CLASS (debug_pixbuf_viewer_parent_class)->finalize (object); } static void debug_pixbuf_viewer_size_request (GtkWidget *widget, GtkRequisition *requisition) { DebugPixbufViewer *viewer; EelDimensions dimensions; g_assert (DEBUG_IS_PIXBUF_VIEWER (widget)); g_assert (requisition != NULL); viewer = DEBUG_PIXBUF_VIEWER (widget); if (viewer->pixbuf != NULL) { dimensions = eel_gdk_pixbuf_get_dimensions (viewer->pixbuf); } else { dimensions = eel_dimensions_empty; } requisition->width = MAX (2, dimensions.width); requisition->height = MAX (2, dimensions.height); } static int debug_pixbuf_viewer_expose_event (GtkWidget *widget, GdkEventExpose *event) { DebugPixbufViewer *viewer; EelIRect clipped_dirty_area; EelIRect dirty_area; EelIRect bounds; GtkAllocation allocation; g_assert (DEBUG_IS_PIXBUF_VIEWER (widget)); g_assert (event != NULL); g_assert (event->window == gtk_widget_get_window (widget)); g_assert (gtk_widget_get_realized (widget)); viewer = DEBUG_PIXBUF_VIEWER (widget); if (viewer->pixbuf == NULL) { return TRUE; } gtk_widget_get_allocation (widget, &allocation); bounds.x0 = allocation.x + (allocation.width - gdk_pixbuf_get_width (viewer->pixbuf)) / 2; bounds.y0 = allocation.y + (allocation.height - gdk_pixbuf_get_height (viewer->pixbuf)) / 2; bounds.x1 = bounds.x0 + gdk_pixbuf_get_width (viewer->pixbuf); bounds.y1 = bounds.y0 + gdk_pixbuf_get_height (viewer->pixbuf); /* Clip the dirty area to the screen; bail if no work to do */ dirty_area = eel_gdk_rectangle_to_eel_irect (event->area); clipped_dirty_area = eel_gdk_window_clip_dirty_area_to_screen (event->window, dirty_area); if (!eel_irect_is_empty (&clipped_dirty_area)) { EelIRect clipped_bounds; eel_irect_intersect (&clipped_bounds, &bounds, &clipped_dirty_area); if (!eel_irect_is_empty (&clipped_bounds)) { g_assert (clipped_bounds.x0 >= bounds.x0); g_assert (clipped_bounds.y0 >= bounds.y0); eel_gdk_pixbuf_draw_to_drawable (viewer->pixbuf, event->window, clipped_bounds.x0 - bounds.x0, clipped_bounds.y0 - bounds.y0, clipped_bounds); } } bounds.x0 -= 1; bounds.y0 -= 1; bounds.x1 += 1; bounds.y1 += 1; return TRUE; } static void debug_pixbuf_viewer_class_init (DebugPixbufViewerClass *pixbuf_viewer_class) { GObjectClass *object_class = G_OBJECT_CLASS (pixbuf_viewer_class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (pixbuf_viewer_class); object_class->finalize = debug_pixbuf_viewer_finalize; widget_class->size_request = debug_pixbuf_viewer_size_request; widget_class->expose_event = debug_pixbuf_viewer_expose_event; } static void debug_pixbuf_viewer_set_pixbuf (DebugPixbufViewer *viewer, GdkPixbuf *pixbuf) { g_assert (DEBUG_IS_PIXBUF_VIEWER (viewer)); if (pixbuf != viewer->pixbuf) { eel_gdk_pixbuf_unref_if_not_null (viewer->pixbuf); eel_gdk_pixbuf_ref_if_not_null (pixbuf); viewer->pixbuf = pixbuf; gtk_widget_queue_resize (GTK_WIDGET (viewer)); } } /** * eel_debug_draw_rectangle_and_cross: * @rectangle: Rectangle bounding the rectangle. * @color: Color to use for the rectangle and cross. * * Draw a rectangle and cross. Useful for debugging exposure events. */ void eel_debug_draw_rectangle_and_cross (GdkDrawable *drawable, EelIRect rectangle, guint32 color, gboolean draw_cross) { cairo_t *cr; GdkColor color_gdk = { 0 }; int width; int height; g_return_if_fail (drawable != NULL); g_return_if_fail (!eel_irect_is_empty (&rectangle)); width = rectangle.x1 - rectangle.x0; height = rectangle.y1 - rectangle.y0; cr = gdk_cairo_create (drawable); color_gdk.red = ((color >> 16) & 0xff) << 8; color_gdk.green = ((color >> 8) & 0xff) << 8; color_gdk.blue = ((color ) & 0xff) << 8; gdk_cairo_set_source_color (cr, &color_gdk); cairo_set_line_width (cr, 1.0); cairo_rectangle (cr, rectangle.x0 + 0.5, rectangle.y0 + 0.5, width, height); if (draw_cross) { cairo_move_to (cr, rectangle.x0, rectangle.y0); cairo_line_to (cr, rectangle.x0 + width, rectangle.y0 + height); cairo_move_to (cr, rectangle.x0 + width, rectangle.y0); cairo_line_to (cr, rectangle.x0, rectangle.y0 + height); } cairo_stroke (cr); cairo_destroy (cr); } /** * eel_debug_show_pixbuf_in_external_viewer: * @pixbuf: Pixbuf to show. * @viewer_name: Viewer name. * * Show the given pixbuf in an external out of process viewer. * This is very useful for debugging pixbuf stuff. * * Perhaps this function should be #ifdef DEBUG or something like that. */ void eel_debug_show_pixbuf_in_external_viewer (const GdkPixbuf *pixbuf, const char *viewer_name) { char *command; char *file_name; gboolean save_result; int ignore; int fd; g_return_if_fail (pixbuf != NULL); g_return_if_fail (viewer_name != NULL); file_name = g_strdup ("/tmp/eel-debug-png-file-XXXXXX"); fd = g_mkstemp (file_name); if (fd == -1) { g_free (file_name); file_name = g_strdup_printf ("/tmp/isis-debug-png-file-%d", getpid ()); } else { close (fd); } save_result = eel_gdk_pixbuf_save_to_file (pixbuf, file_name); if (save_result == FALSE) { g_warning ("Failed to save '%s'", file_name); g_free (file_name); return; } command = g_strdup_printf ("%s %s", viewer_name, file_name); ignore = system (command); g_free (command); remove (file_name); g_free (file_name); } static GtkWidget *debug_window = NULL; static GtkWidget *debug_image = NULL; static void debug_delete_event (GtkWidget *widget, GdkEvent *event, gpointer callback_data) { gtk_widget_hide (widget); } static void destroy_debug_window (void) { if (debug_window != NULL) { gtk_widget_destroy (debug_window); debug_window = NULL; } } /** * eel_debug_show_pixbuf_in: * @pixbuf: Pixbuf to show. * * Show the given pixbuf in an in process window. */ void eel_debug_show_pixbuf (GdkPixbuf *pixbuf) { if (debug_window == NULL) { GtkWidget *vbox; debug_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (debug_window), vbox); gtk_window_set_title (GTK_WINDOW (debug_window), "Pixbuf debugging"); gtk_window_set_resizable (GTK_WINDOW (debug_window), TRUE); gtk_container_set_border_width (GTK_CONTAINER (debug_window), 10); g_signal_connect (debug_window, "delete_event", G_CALLBACK (debug_delete_event), NULL); debug_image = gtk_widget_new (debug_pixbuf_viewer_get_type (), NULL); gtk_box_pack_start (GTK_BOX (vbox), debug_image, TRUE, TRUE, 0); eel_debug_call_at_shutdown (destroy_debug_window); gtk_widget_show (debug_image); gtk_widget_show (vbox); } gtk_widget_show (debug_window); debug_pixbuf_viewer_set_pixbuf (DEBUG_PIXBUF_VIEWER (debug_image), pixbuf); gdk_window_clear_area_e (gtk_widget_get_window (debug_window), 0, 0, -1, -1); } void eel_debug_pixbuf_draw_point (GdkPixbuf *pixbuf, int x, int y, guint32 color, int opacity) { EelDimensions dimensions; guchar *pixels; gboolean has_alpha; guint pixel_offset; guint rowstride; guchar red; guchar green; guchar blue; guchar alpha; guchar *offset; g_return_if_fail (eel_gdk_pixbuf_is_valid (pixbuf)); g_return_if_fail (opacity >= EEL_OPACITY_FULLY_TRANSPARENT); g_return_if_fail (opacity <= EEL_OPACITY_FULLY_OPAQUE); dimensions = eel_gdk_pixbuf_get_dimensions (pixbuf); g_return_if_fail (x >= 0 && x < dimensions.width); g_return_if_fail (y >= 0 && y < dimensions.height); pixels = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); pixel_offset = has_alpha ? 4 : 3; red = EEL_RGBA_COLOR_GET_R (color); green = EEL_RGBA_COLOR_GET_G (color); blue = EEL_RGBA_COLOR_GET_B (color); alpha = (guchar) opacity; offset = pixels + y * rowstride + x * pixel_offset; *(offset + 0) = red; *(offset + 1) = green; *(offset + 2) = blue; if (has_alpha) { *(offset + 3) = alpha; } } void eel_debug_pixbuf_draw_rectangle (GdkPixbuf *pixbuf, gboolean filled, int x0, int y0, int x1, int y1, guint32 color, int opacity) { EelDimensions dimensions; int x; int y; g_return_if_fail (eel_gdk_pixbuf_is_valid (pixbuf)); g_return_if_fail (opacity >= EEL_OPACITY_FULLY_TRANSPARENT); g_return_if_fail (opacity <= EEL_OPACITY_FULLY_OPAQUE); dimensions = eel_gdk_pixbuf_get_dimensions (pixbuf); if (x0 == -1) { x0 = 0; } if (y0 == -1) { y0 = 0; } if (x1 == -1) { x1 = dimensions.width - 1; } if (y1 == -1) { y1 = dimensions.height - 1; } g_return_if_fail (x1 > x0); g_return_if_fail (y1 > y0); g_return_if_fail (x0 >= 0 && x0 < dimensions.width); g_return_if_fail (y0 >= 0 && y0 < dimensions.height); g_return_if_fail (x1 >= 0 && x1 < dimensions.width); g_return_if_fail (y1 >= 0 && y1 < dimensions.height); if (filled) { for (y = y0; y <= y1; y++) { for (x = x0; x <= x1; x++) { eel_debug_pixbuf_draw_point (pixbuf, x, y, color, opacity); } } } else { /* Top / Bottom */ for (x = x0; x <= x1; x++) { eel_debug_pixbuf_draw_point (pixbuf, x, y0, color, opacity); eel_debug_pixbuf_draw_point (pixbuf, x, y1, color, opacity); } /* Left / Right */ for (y = y0; y <= y1; y++) { eel_debug_pixbuf_draw_point (pixbuf, x0, y, color, opacity); eel_debug_pixbuf_draw_point (pixbuf, x1, y, color, opacity); } } } void eel_debug_pixbuf_draw_rectangle_inset (GdkPixbuf *pixbuf, gboolean filled, int x0, int y0, int x1, int y1, guint32 color, int opacity, int inset) { EelDimensions dimensions; g_return_if_fail (eel_gdk_pixbuf_is_valid (pixbuf)); g_return_if_fail (opacity >= EEL_OPACITY_FULLY_TRANSPARENT); g_return_if_fail (opacity <= EEL_OPACITY_FULLY_OPAQUE); dimensions = eel_gdk_pixbuf_get_dimensions (pixbuf); if (x0 == -1) { x0 = 0; } if (y0 == -1) { y0 = 0; } if (x1 == -1) { x1 = dimensions.width - 1; } if (y1 == -1) { y1 = dimensions.height - 1; } x0 += inset; y0 += inset; x1 -= inset; y1 -= inset; g_return_if_fail (x1 > x0); g_return_if_fail (y1 > y0); eel_debug_pixbuf_draw_rectangle (pixbuf, filled, x0, y0, x1, y1, color, opacity); }