Pixel drawing with Cairo and GtkDrawingArea

Hello

I now have one additional question.

Example: I want to move an Object (an rectangle) across the window. I dont want an finished Picture. It looks like cairo can only handle fully calculated graphics, which it then displays in one operation.

Better Example: There are “cellular automata”; for example “Langton’s ant”.

I want to watch “Langton’s ant” moving and I would like to be able to change the speed at will during this process.

But it seems, “cairo” doesn’t work well here.

I would like to implement this as simply as possible without contortions.

Is there nothing comparable to SetPixel, BitBlt etc. like in windows ?
(In terms of directly visible response.)

I’ve done a CA using Cairo. I don’t have the code at hand, but IIRC I used CairoImageSurface, in a manner like this. So you could maintain an image surface, ‘incrementally’ update that however you want, and your draw function would only render (blit) that image surface as-is to the widget’s Cairo context whenever the widget needs redrawn.

Whether that’s the best and/or fastest way to do it, I don’t know. Perhaps GTKGLArea could be better, depending on your precise case (or there might be other options).

If you can implement your solution with BitBlt, then you can implement it with Cairo too. Do you need to show a bitmap at different positions on the screen (GtkDrawingArea)?

Then make a cairo pattern from pixbuf (gdk_cairo_surface_create_from_pixbuf, cairo_pattern_create_for_surface; this must be done only once), set it as source in the “draw” signal callback function (cairo_set_source) and determine the position within the GtkDrawingArea (cairo_matrix_init_translate, cairo_set_matrix).

Timing (how often redraw must be done) is another task. AFAIK, neither Windows GDI nor Cairo provide any functions for that. For simple animations on Windows, I use WaitForSingleObject in order to set delays between frames. So you need something similar that works with Cairo on your platform.

Timing of redraw should be done via GTK, not Cairo directly; Cairo should only care about what is drawn, not when. You would probably need to add a GTK Widget tick callback and have that update the surface and queue a draw.

First of all, thanks for all the advice. I will experiment a bit as soon as I have more time and maybe write a small demonstrative program if I get stuck.

It looks like the technology under Linux isn’t really comparable to that under Windows.

Under Windows, I achieved success more quickly and intuitively. I am still a beginner when it comes to Linux.

Here is a brief overview of my approach:

I tried it with an loop via g_idle_add in combination with the DrawingArea-Callback and gtk_widget_queue_draw resp. gtk_widget_queue_draw_area. (If it helps to understand my Idea: In Windows you can achieve it via Timer and/or with the PeekMessage-Loop)

I work with an “Doublebuffer”-cairo_image_surface and copy its content in the idle-function via gtk_widget_queue_draw resp. gtk_widget_queue_draw_area over the DrawingArea-Callback into the Program-Window. (Like BitBlt in Windows).

But it doesn’t work.

Here is a short example, that contents only the relevant stuff:

#include <stdbool.h>
#include <gtk/gtk.h>

/*
Structure:

GtkWindow
    GtkFixed
        GtkDrawingArea
        GtkButton


( Commented out lines are for test purposes. )


What i want:

I want do draw into an "hidden" Bitmap (as example for a double-buffer-system). If needed or at an
interval its content should be transferred into the Program-Window.




You can comment-in "direct_test" in the function "callback__drawingarea".

This works. (But I have to mouse-click for every new picture)

But if I try to draw via the function "testdraw" ("direct_test" is commented out), all what I get is an
black Screen, instead of a sort of "animation".




User "dboles" suggested "GtkTickCallback" ... But I don't understand the mechanism. The DrawingArea
needs its callback-function. So I has to use the GtkTickCallback-function to fire in it "gtk_widget_queue_draw" ?


Is my combination of an idle-function (with included draw-operations) and gtk_widget_queue_draw wrong ?

*/

GtkWidget *Window,*Area;

cairo_surface_t *CairoSurface;
cairo_t *Cairo;

guint IdleID;

bool Order_executed;
bool Startbutton_clicked;

bool callback__button_start(void);
gboolean callback__drawingarea(GtkWidget *widget, cairo_t *cr,gpointer data);
gboolean idle_function(gpointer user_data);
void testdraw(void);

int main(void)
{
GtkWidget *fixed,*button_start;

gtk_init(NULL,NULL);

Window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(Window),1000,1000);
g_signal_connect(Window,"delete-event",(GCallback)gtk_main_quit,NULL);
g_signal_connect(Window,"destroy",(GCallback)gtk_main_quit,NULL);

fixed = gtk_fixed_new();
gtk_container_add(GTK_CONTAINER(Window),fixed);

Area = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER(fixed),Area);
g_signal_connect((GtkWidget *)Area,"draw",(GCallback)callback__drawingarea,NULL);
gtk_widget_set_size_request(Area,1000,1000);


button_start = gtk_button_new_with_label("Start");
gtk_container_add(GTK_CONTAINER(fixed),button_start);
g_signal_connect((GtkWidget *)button_start,"clicked",(GCallback)callback__button_start,NULL);

// gtk_fixed_move((GtkFixed*)fixed,button_start, 900,900);

// Creating the "Background-Videomemory":

// Formats CAIRO_FORMAT_RGB30 and CAIRO_FORMAT_RGB16_565 results as well in black-screen. All others shows no effect
CairoSurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,1000,1000);
            
Cairo = cairo_create(CairoSurface);

IdleID = g_idle_add(idle_function,NULL);


gtk_widget_show_all(Window);
gtk_main();

}

bool callback__button_start(void)
{
Startbutton_clicked = true; // See function “callback__drawingarea”
Order_executed = true; // See function “idle_function”
gtk_widget_queue_draw(Area);
//gtk_widget_queue_draw_area(Area,0,0,1000,1000);

return true;

}

gboolean callback__drawingarea(GtkWidget *widget,cairo_t *cr,gpointer data)
{
unsigned long x,y,i; // for direct_test

if(Startbutton_clicked == false)
{
    // This also catches the mandatory calling of the function at program start.
    return false;
} 

//#define direct_test

#ifdef direct_test
cairo_set_source_rgb(cr,1.0,0.0,0.0);

for(i=0;i!=100;i++)
{
    x = rand() % 400;
    y = rand() % 400;
    cairo_rectangle(cr,x,y,10,10);
}

cairo_fill(cr);

return false;

#endif

//cairo_surface_flush(CairoSurface);
cairo_set_source_surface(cr,CairoSurface,0,0); // This should do, what BitBlt under Windows do
       
cairo_paint(cr);
//cairo_fill(cr); // Does not work at all
        
Order_executed = true;         


return false;

}

gboolean idle_function(gpointer user_data)
{
testdraw();

if(Order_executed == true)
{
    Order_executed = false;
       gtk_widget_queue_draw_area(Area,0,0,1000,1000);
    //gtk_widget_queue_draw(Area);
}

return true;
}

// This function draws into the Background-Bitmap
void testdraw(void)
{
unsigned long x,y;

x = rand() % 400;
y = rand() % 400;

cairo_set_source_rgb(Cairo,1.0,0.0,0.0);
cairo_rectangle(Cairo,x,y,10,10);

}

1 Like

You have to call cairo_fill() after cairo_rectangle() in testdraw.

2 Likes

Thank you. I am still thinking in “windows-terms”.

For interested people, who are like me still rather beginners on this topic “cairo”: I have changed testdraw a little and now you can see movement, instead of just an red rectangle: (And this is, what I wanted)

void testdraw(void)
{
    unsigned long x,y;

double r;

    x = rand() % 400;
    y = rand() % 400;
    
    r = 1.0 / (double)(rand() % 256);
    
    cairo_set_source_rgb(Cairo,r,0.0,0.0);
    cairo_rectangle(Cairo,x,y,10,10);
    
    cairo_fill(Cairo);    
}

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.