Canalblog
Editer l'article Suivre ce blog Administration + Créer mon blog
Publicité
LINUX & OPEN SOURCE
17 août 2009

GTK lasso et selection rectangulaire

J'ai cherché un exemple de code capable de créer un lasso façon gimp. Pas facile! Finalement je me suis contenté d'une sélection rectangulaire assez rustique mais qui remplit bien son role, avec peu de lignes de code et sans appel à un lib particulière.  Le principe est simple, on inverse la couleur de la zone sélectionnée, et on reinverse exactement la même zone lorsque que le pointeur se déplace, et bien sûr, on inverse la nouvelle zone sélectionnée.  L'explication peut paraître obscure, il suffit de jetter un coup d'oeil sur le code, en particulier la fonction 'draw_selection' pour comprendre.

#include <gtk/gtk.h>

typedef struct _components {
      GdkPixbuf *pixbuf;
    GtkWidget *window;
    GtkWidget *mainPanel;
    GtkWidget *drawingArea;
    GdkPixmap *pixMap;
        GtkWidget *clear_button;
        gdouble    x;
        gdouble y;
        gdouble    w;
        gdouble h;       
        gboolean click;
} components;

static     components this;

static void draw_selection (GtkWidget *, gdouble, gdouble, gdouble, gdouble, gboolean *);
static gboolean configure_event( GtkWidget *, GdkEventConfigure * );
static void clear_selection (GtkWidget *button, gpointer *);


static gboolean delete_event( GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   data )
{
        fprintf(stderr,"erase & close\n");
      return FALSE;
}

static void destroy( GtkWidget *widget,
                     gpointer   data )
{
        fprintf(stderr,"bye, bye!\n");
    gtk_main_quit ();
}

/* because we need a window */
static void createWindow() {
    this.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_container_set_border_width (GTK_CONTAINER (this.window), 10);
    gtk_window_set_title (GTK_WINDOW (this.window), "Selection demo");

    g_signal_connect (G_OBJECT (this.window), "delete_event",
              G_CALLBACK (delete_event), NULL);

    g_signal_connect (G_OBJECT (this.window), "destroy",
                G_CALLBACK (destroy), NULL);

}

static void layoutWidgets() {
    this.mainPanel = gtk_vbox_new(FALSE, 0);
    gtk_container_add  (GTK_CONTAINER (this.window), this.mainPanel);
    gtk_box_pack_start (GTK_BOX(this.mainPanel), this.drawingArea, TRUE, TRUE, 0);
       
        this.clear_button = gtk_button_new_with_mnemonic ("clear");
        gtk_widget_set_tooltip_text(this.clear_button,"Clear selections on the screen");
        gtk_box_pack_start (GTK_BOX (this.mainPanel), this.clear_button, TRUE, TRUE, 0);
        /*call clear_selection to restore the picture */
        gtk_signal_connect (GTK_OBJECT(this.clear_button), "clicked",
                                G_CALLBACK (clear_selection), NULL);

}

/* show the widgets */
static void show() {
    gtk_widget_show (this.drawingArea);
    gtk_widget_show (this.mainPanel);
    gtk_widget_show (this.window);
        gtk_widget_show (this.clear_button);
}

/* retore the picture */
static void
clear_selection (GtkWidget *button, gpointer *data)
{
    configure_event( this.drawingArea, NULL );
    gdk_draw_drawable(
                        this.drawingArea->window,
            this.drawingArea->style->fg_gc[GTK_WIDGET_STATE (this.drawingArea)],
            this.pixMap,
            0, 0,
            0, 0,
            -1, -1);
}


/* Perhaps the most interesting part:
to show the selected area (a rectangle),
we just invert the colors: when the rectangle grow up there is no
problem, but when we shrink the rectangle, we have to restore the
previous area of the screen selected, it's done with a second inversion
*of the previous area */
static void
draw_selection (GtkWidget *widget, gdouble x1, gdouble y1, gdouble x2, gdouble y2, gboolean *new_click)
{
    static gdouble prev_x2=0, prev_y2=0, x, y;
   
    /* define the inversion to apply to the pixel values */
    GdkGC *invert_gc = gdk_gc_new(widget->window);
     gdk_gc_copy(invert_gc, widget->style->black_gc);                                                
    gdk_gc_set_function(invert_gc, GDK_INVERT);
   
    /*
      invert the previous inverted area:
        so two inverts restore the previous colors
         but if we start a new selection, then *new_click is TRUE
        and there is nothing to 'invert'
    */
    if (*new_click == FALSE)
    {
        gdk_draw_rectangle (this.pixMap,
                                invert_gc,
                                TRUE,
                                x1, y1,
                                prev_x2-x1,prev_y2-y1);
    }
    *new_click=FALSE; // so the next round we will invert the previous selection
   
    /* show the selection: colors are inverted */
    gdk_draw_rectangle (this.pixMap,
                invert_gc,
                TRUE,
                  x1, y1,
                  x2-x1, y2-y1);
   
    /* area to draw: we take the largest area */                           
    if (prev_x2>x2) x=prev_x2; //we decreased the width
        else x=x2;
    if (prev_y2>y2) y=prev_y2; //we decreased the height
        else y=y2;
   
  gtk_widget_queue_draw_area (widget,
                              x1, y1,
                                              x-x1, y-y1);
                                                            
    /* store the current position of the pointer (bottom-right corner)*/                                       
    prev_x2 = x2;
    prev_y2 = y2;

}

/* Redraw the screen from the backing pixmap */
static gboolean
expose_event( GtkWidget *widget, GdkEventExpose *event )
{
  gdk_draw_drawable(
                        widget->window,
            widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
            this.pixMap,
            event->area.x, event->area.y,
            event->area.x, event->area.y,
            event->area.width, event->area.height);

  return FALSE;
}


/* the window is resized : Create a new backing pixmap of the appropriate size */
static gboolean
configure_event( GtkWidget *widget, GdkEventConfigure *event )
{
   
  if (this.pixMap)
    g_object_unref(this.pixMap);

  this.pixMap = gdk_pixmap_new(widget->window,
              widget->allocation.width,
              widget->allocation.height,
              -1);

    gdk_pixbuf_render_pixmap_and_mask(this.pixbuf,
                                     &(this.pixMap),
                                      NULL,
                                      127);

return TRUE;

}

/* button is clicked: store the mouse position (top left corner of the selection) */
static gboolean
button_press_event( GtkWidget *widget, GdkEventButton *event )
{
  if (event->button == 1 && this.pixMap != NULL)
    {
            this.x=event->x;
            this.y=event->y;
            this.click=TRUE;  //new selection flag
    }   

  return TRUE;
}

/* button is released: compute the width &height of the selection */
static gboolean
button_release_event( GtkWidget *widget, GdkEventButton *event )
{
  if (event->button == 1 && this.pixMap != NULL)
    {
            this.w=event->x-this.x;
            this.h=event->y-this.y;
            /* print in the console the coordinates and the size of the selection */
            fprintf(stderr,"selection, x:%f y:%f w:%f h:%f\n",this.x,this.y,this.w,this.h);
    }   

  return TRUE;
}

/* the mouse is moving: take the current position */
static gboolean
motion_notify_event( GtkWidget *widget, GdkEventMotion *event )
{
  int x, y;
  GdkModifierType state;

  if (event->is_hint)
    gdk_window_get_pointer (event->window, &x, &y, &state);
  else
  {
     x = event->x;
     y = event->y;
     state = event->state;
   }

  if (state & GDK_BUTTON1_MASK && this.pixMap != NULL)
        draw_selection (widget, this.x, this.y, x, y, &this.click);
       
  return TRUE;
}

/* set the drawing area and the signals */
static void createDrawingArea() {
    this.drawingArea = gtk_drawing_area_new();
        gtk_widget_set_size_request(this.drawingArea, 500, 500);
       
    gtk_signal_connect (GTK_OBJECT (this.drawingArea), "expose_event",
                  (GtkSignalFunc) expose_event, NULL);
    gtk_signal_connect (GTK_OBJECT(this.drawingArea),"configure_event",
                  (GtkSignalFunc) configure_event, NULL);
    gtk_signal_connect (GTK_OBJECT (this.drawingArea), "motion_notify_event",
                  (GtkSignalFunc) motion_notify_event, NULL);
    gtk_signal_connect (GTK_OBJECT (this.drawingArea), "button_press_event",
                  (GtkSignalFunc) button_press_event, NULL);
        gtk_signal_connect (GTK_OBJECT (this.drawingArea), "button_release_event",
                  (GtkSignalFunc) button_release_event, NULL);                           

    gtk_widget_set_events (this.drawingArea, GDK_EXPOSURE_MASK
                 | GDK_LEAVE_NOTIFY_MASK
                 | GDK_BUTTON_PRESS_MASK
                                 | GDK_BUTTON_RELEASE_MASK
                 | GDK_POINTER_MOTION_MASK
                 | GDK_POINTER_MOTION_HINT_MASK);

}

/* we start here */
int main( int   argc,
          char *argv[] )
{
    gtk_init (&argc, &argv);
       
        /* load the pic in the pixbuffer - you picture here V */
        this.pixbuf = gdk_pixbuf_new_from_file("./imagemovie.png",NULL);
       
    createWindow();
    createDrawingArea();
    layoutWidgets();
    show();

    gtk_main ();

    return 0;
}


/* Compile:
*  gcc -g -Wall `pkg-config --cflags --libs gtk+-2.0` -o selection selection.c
*/

Le code source à télécharger est ici:  selection.c

Publicité
Publicité
Commentaires
LINUX & OPEN SOURCE
Publicité
Archives
Publicité