Re: porting gdk -> cairo



I took the bait and coded my solution that I (in lack of anything better
called) dovtk-lasso . I'm including it below including a test program.

I think this solution is even simpler than your old xor solution. All you
need to do is to create the draw_callback fuction that uses whatever cairo
painting you want to draw your overlay. Note that this function is called
twice, once to extract where you are drawing, and once to do the drawing.
During the first time the do_mask variable is on. In this case you should
choose a thicker pen and draw in black, to make sure that everything is
included.

Note that there is one constraint that I have not been able to solve. The
expose-event callback that is doing the actual drawing of your graphics have
to return FALSE. I would be happy to receive a solution for it.

I promise to put this code into git-hub as soon as possible.

//======================================================================

//  test-gtk-lasso.c - This example is in the public domain

//

//  Dov Grobgeld <dov grobgeld gmail com>

//  Mon Aug 16 09:09:56 2010

//----------------------------------------------------------------------

 #include <stdlib.h>

#include <gtk/gtk.h>

#include <math.h>

#include "dovtk-lasso.h"

 DovtkLasso *lasso = NULL;

int start_x, start_y, end_x, end_y;

 int cb_expose(GtkWidget      *widget,

              GdkEventExpose *event,

              gpointer        user_data)

{

    cairo_t *cr;

    cr = gdk_cairo_create(widget->window);

    cairo_rectangle(cr, event->area.x, event->area.y,

                    event->area.width, event->area.height);

    cairo_clip(cr);

     // Just draw anything in the widget

    double x, y;

    x = widget->allocation.x + widget->allocation.width / 2;

    y = widget->allocation.y + widget->allocation.height / 2;

    double radius;

    radius = MIN (widget->allocation.width / 2,

                  widget->allocation.height / 2) - 5;

     cairo_set_source_rgb(cr, 0,0,0);

    cairo_arc (cr, x, y, radius, 0, 2 * M_PI);

    cairo_stroke(cr);

    cairo_destroy(cr);

     return FALSE;

}

 /**

 * Draw  whatever overlay you want on the image. If the do_mask

 * is on, then you should paint in black and with a pen that

 * is thicker than the drawing.

 */

void my_lasso_draw(cairo_t *cr,

                   gboolean do_mask,

                   // output

                   DovtkLassoRectangleList **rect_list)

{

    int min_x = MIN(start_x, end_x);

    int min_y = MIN(start_y, end_y);

     if (!do_mask) {

        cairo_set_source_rgb(cr, 1,0,0);

        cairo_set_line_width(cr,1);

    }

    else

        cairo_set_line_width(cr, 5);

     cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);

     // Draw a rectangle

    cairo_rectangle(cr, min_x, min_y, abs(end_x-start_x), abs(end_y-start_y));

     cairo_stroke(cr);

}

 int cb_button_press(GtkWidget      *widget,

                    GdkEventButton *event,

                    gpointer        user_data)

{

    lasso = dovtk_lasso_create(widget,

                               &my_lasso_draw,

                               TRUE);

    start_x = event->x;

    start_y = event->y;

     return FALSE;

}

 int cb_button_release(GtkWidget      *widget,

                      GdkEventButton *event,

                      gpointer        user_data)

{

    dovtk_lasso_destroy(lasso);

    lasso = NULL;

    return FALSE;

}

 int cb_motion_notify(GtkWidget      *widget,

                     GdkEventMotion *event,

                     gpointer        user_data)

{

    //    printf("button motion\n");

    end_x = event->x;

    end_y = event->y;

     dovtk_lasso_update(lasso);

     return FALSE;

}

 int main(int argc, char *argv[])

{

    gtk_init(&argc, &argv);

    GtkWidget *w_top = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    g_signal_connect(G_OBJECT(w_top), "delete-event",

                     G_CALLBACK(gtk_main_quit), NULL);

     GtkWidget *w_draw = gtk_drawing_area_new();

    gtk_container_add(GTK_CONTAINER(w_top),

                      w_draw);

    gtk_widget_set_size_request(w_draw, 500,500);

    g_signal_connect(G_OBJECT(w_draw), "expose-event",

                     G_CALLBACK(cb_expose), NULL);

     // TBD - set up events for lasso

    gtk_widget_add_events(w_draw,

                          GDK_BUTTON_MOTION_MASK

                          | GDK_BUTTON_PRESS_MASK

                          | GDK_BUTTON_RELEASE_MASK);

    g_signal_connect(G_OBJECT(w_draw), "button-press-event",

                     G_CALLBACK(cb_button_press), NULL);

    g_signal_connect(G_OBJECT(w_draw), "button-release-event",

                     G_CALLBACK(cb_button_release), NULL);

    g_signal_connect(G_OBJECT(w_draw), "motion-notify-event",

                     G_CALLBACK(cb_motion_notify), NULL);

     gtk_widget_show_all(w_top);

    gtk_main();

     return 0;

}

--------------------

/**

 * dovtk-lasso.h

 *

 * A solution for drawing overlays on a gtk widget.

 *

 * This code is relased under the LGPL v2.0.

 *

 * Copyright Dov Grobgeld <dov grobgeld gmail com> 2010

 *

 */

#ifndef DOVTK_H

#define DOVTK_H

 #include <gtk/gtk.h>

 typedef struct {

} DovtkLasso;

 typedef struct {

    int num_rectangles;

    cairo_rectangle_t *rectangles;

} DovtkLassoRectangleList;

 typedef void (*DovtkLassoDrawing)(cairo_t *cr,

                                  gboolean do_mask,

                                  // output

                                  DovtkLassoRectangleList **rect_list);

 DovtkLasso *dovtk_lasso_create(GtkWidget *widget,

                               DovtkLassoDrawing drawing_cb,

                               gboolean do_calc_expose_from_cairo);

 /**

 * Called when the coordinates of the lasso were changed.

 *

 * @param lasso

 */

void dovtk_lasso_update(DovtkLasso *lasso);

 void dovtk_lasso_destroy(DovtkLasso *lasso);

 DovtkLassoRectangleList *dovtk_lasso_rectangle_list_new(int num_rectangles);

void dovtk_lasso_rectangle_list_destroy(DovtkLassoRectangleList
*rectangcle_list);

#endif /* DOVTK */

 /**

 * dovtk-lasso.c

 *

 * A solution for drawing overlays on a gtk widget.

 *

 * This code is relased under the LGPL v2.0.

 *

 * Copyright Dov Grobgeld <dov grobgeld gmail com> 2010

 *

 */

#include "dovtk-lasso.h"

 typedef struct {

    DovtkLasso parent;

    gulong expose_handler_id;

    GtkWidget *widget;

    DovtkLassoDrawing drawing_cb;

    gboolean do_calc_expose_from_cairo;

    DovtkLassoRectangleList *old_rect_list;

} DovtkLassoPrivate ;

 static int lasso_cb_expose(GtkWidget      *widget,

                           GdkEventExpose *event,

                           gpointer        user_data);

 DovtkLasso *dovtk_lasso_create(GtkWidget *widget,

                               DovtkLassoDrawing drawing_cb,

                               gboolean do_calc_expose_from_cairo)

{

    DovtkLassoPrivate *selfp = g_new0(DovtkLassoPrivate, 1);

     // This binding doesn't work if the default expose handler

    // returns TRUE!

    selfp->expose_handler_id

        = g_signal_connect(widget,

                           "expose-event",

                           G_CALLBACK(lasso_cb_expose),

                           selfp);

    selfp->widget = widget;

    selfp->drawing_cb = drawing_cb;

    selfp->do_calc_expose_from_cairo = do_calc_expose_from_cairo;

    // Create an empty list so that we can free it

    selfp->old_rect_list = dovtk_lasso_rectangle_list_new(0);

    return (DovtkLasso*)selfp;

}

 void dovtk_lasso_destroy(DovtkLasso *lasso)

{

    DovtkLassoPrivate *selfp = (DovtkLassoPrivate*)lasso;

    g_signal_handler_disconnect(selfp->widget,

                                selfp->expose_handler_id);

    // This gets rid of the overlay. Is this always needed?

    dovtk_lasso_update(lasso);

     dovtk_lasso_rectangle_list_destroy(selfp->old_rect_list);

     g_free(lasso);

}

 static int lasso_cb_expose(GtkWidget      *widget,

                           GdkEventExpose *event,

                           gpointer        user_data)

{

    DovtkLassoPrivate *selfp = (DovtkLassoPrivate*)user_data;

    //    printf("dovtk-lasso.c: expose\n");

     g_signal_handler_block(widget, selfp->expose_handler_id);

    int retval;

    g_signal_emit_by_name (widget, "expose-event", event, &retval);

    g_signal_handler_unblock(widget, selfp->expose_handler_id);

     cairo_t *cr;

    cr = gdk_cairo_create(widget->window);

    cairo_rectangle(cr, event->area.x, event->area.y,

                    event->area.width, event->area.height);

    cairo_clip(cr);

     DovtkLassoRectangleList *rect_list = NULL;

    selfp->drawing_cb(cr, FALSE, &rect_list);

     cairo_destroy(cr);

     return TRUE;

}

 int a8_idx=0;

 void dovtk_lasso_update(DovtkLasso *lasso)

{

    DovtkLassoPrivate *selfp = (DovtkLassoPrivate*)lasso;

     // Call drawing_cb to and use it to generate the rectangle list

    DovtkLassoRectangleList *rect_list = NULL;

    int scale_factor = 32;

    int low_res_width =
(selfp->widget->allocation.width+scale_factor-1) / scale_factor;

    int low_res_height =
(selfp->widget->allocation.height+scale_factor-1) / scale_factor;

     int i;

     // This should be created in the creation of DovtkLasso

    cairo_t *cr = NULL;

    cairo_surface_t *surf = NULL;

     if (selfp->do_calc_expose_from_cairo) {

        surf=cairo_image_surface_create(CAIRO_FORMAT_ARGB32,

                                        low_res_width,

                                        low_res_height);

        cr = cairo_create(surf);

        cairo_set_source_rgba(cr,0,0,0,0);

        cairo_rectangle(cr, 0,0,low_res_height,low_res_width);

        cairo_fill(cr);

        cairo_set_source_rgba(cr,0,0,0,1);

    }

    cairo_scale(cr,1.0/scale_factor,1.0/scale_factor);

    selfp->drawing_cb(cr, TRUE, &rect_list);

#if 0

    char filename[64];

    sprintf(filename, "/tmp/a8-%04d.png", a8_idx++);

    cairo_surface_write_to_png(surf, filename);

#endif

     // TBD - Turn surf into a list of rectangles

    if (selfp->do_calc_expose_from_cairo) {

        int row_idx, col_idx;

         // Allocate a lot of space

        rect_list =
dovtk_lasso_rectangle_list_new(low_res_width*low_res_height);

         guint8 *buf = cairo_image_surface_get_data(surf);

        int rect_idx = 0;

        int row_stride = cairo_image_surface_get_stride(surf);

        for (row_idx=0; row_idx<low_res_height; row_idx++) {

            for (col_idx=0; col_idx<low_res_width; col_idx++) {

                if (*(buf + row_stride * row_idx + col_idx * 4+3) > 0) {

                    cairo_rectangle_t *rect =
&rect_list->rectangles[rect_idx++];

                    rect->x = col_idx*scale_factor;

                    rect->y = row_idx*scale_factor;

                    rect->width = scale_factor;

                    rect->height = scale_factor;

                }

            }

        }

        rect_list->num_rectangles = rect_idx;

         cairo_destroy(cr);

        cairo_surface_destroy(surf);

    }

    //    printf("num_rectangles = %d\n", rect_list->num_rectangles);

     // Build a list of expose rectangles from the old and the new lists.

    // Better done as a linked list.

    DovtkLassoRectangleList *expose_rect_list

        = dovtk_lasso_rectangle_list_new(selfp->old_rect_list->num_rectangles

                                         + rect_list->num_rectangles);

    int num_old_rects = selfp->old_rect_list->num_rectangles;

    for (i=0; i<num_old_rects; i++)

        expose_rect_list->rectangles[i] = selfp->old_rect_list->rectangles[i];

    for (i=0; i<rect_list->num_rectangles; i++)

        expose_rect_list->rectangles[num_old_rects + i] =
rect_list->rectangles[i];

     // Expose the old and the new list of rectangles!

    for (i=0; i<expose_rect_list->num_rectangles; i++) {

        // Shortcut

        cairo_rectangle_t *lasso_rect = &expose_rect_list->rectangles[i];

         GdkRectangle rect;

        rect.x = lasso_rect->x;

        rect.y = lasso_rect->y;

        rect.width = lasso_rect->width;

        rect.height = lasso_rect->height;


        gdk_window_invalidate_rect(selfp->widget->window,

                                   &rect,

                                   TRUE);

    }

    dovtk_lasso_rectangle_list_destroy(expose_rect_list);

     dovtk_lasso_rectangle_list_destroy(selfp->old_rect_list);

    selfp->old_rect_list = rect_list;

}

 DovtkLassoRectangleList *dovtk_lasso_rectangle_list_new(int num_rectangles)

{

    DovtkLassoRectangleList *rectangle_list =
g_new0(DovtkLassoRectangleList, 1);

    rectangle_list->num_rectangles = num_rectangles;

    rectangle_list->rectangles = g_new0(cairo_rectangle_t, num_rectangles);

    return rectangle_list;

}

 void dovtk_lasso_rectangle_list_destroy(DovtkLassoRectangleList
*rectangle_list)

{

    g_free(rectangle_list->rectangles);

    g_free(rectangle_list);

}



On Wed, Aug 11, 2010 at 20:12, Allin Cottrell <cottrell wfu edu> wrote:

On Wed, 11 Aug 2010 jcupitt gmail com wrote:


On 11 August 2010 02:14, Allin Cottrell <cottrell wfu edu> wrote:
rid of GdkGC.  I'd imagine that the effect I'm after is something
that many GTP apps have need of, and it's trivial to achieve with
the GDK API.

I've found that XOR rubber banding is quite hard to do reliably in
gdk.  The problem I think is that you are using the screen pixels to
store part of the state, and that gets hard to coordinate between the
various things that can affect the display.

I had mouse movements triggering rect moves, mouse moves with a button
held down causing background scrolling of the canvas, and mouse moves
over other screen objects triggering highlight effects. With all these
things going on at once it became very difficult to get XOR rubber
bands to not leave annoying trails as they moved.

I switched to an update-model / invalidate-widget / redraw-on-idle
scheme as Dov suggests and I got prettier updates with less
complication. Since input and output are decoupled, it'll scale more
gracefully between slower and faster machines as well, which is nice.

My drawing case may be simpler than yours -- there's nothing
scrollable in the vicinity -- but I've found that rubber-banding
using GDK_INVERT to "undraw" the last box works flawlessly at
low programming cost.

But can you suggest a good example to look at for the alternative
approach? Thanks.

Allin Cottrell
_______________________________________________
gtk-app-devel-list mailing list
gtk-app-devel-list gnome org
http://mail.gnome.org/mailman/listinfo/gtk-app-devel-list




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]