LinuxQuestions.org
Latest LQ Deal: Latest LQ Deals
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 03-28-2024, 10:04 AM   #1
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 7,575
Blog Entries: 19

Rep: Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453
Another cairo problem: transferring data between gtk drawing area and backup cairo surface


So I have managed to draw all the musical glyphs that I need (except for crotchet rest, which still looks all wrong). Now I have a more fundamental problem.

The way pmwScribe works is that you pin glyphs on a musical stave which is actually a gtk drawing area. This has a backing pixbuf on which the actual drawing is done by the appropriate glyph subroutine. The rectangular area that is to contain the glyph is then refreshed from the pixbuf so that the glyphs become visible to the user. At the same time, the corresponding pmw code is written into the .pmw text file that will generate the score when processed by Philp's Music Writer.

In the old (GTK2) version of this program, the backup was a GdkPixbuf. With GTK3, you have to do it with a cairo surface. I can create such a surface with the properties of the widget's window by using gdk_window_create_similar_surface(). And I can create a cairo context for this surface and write my glyphs using it (I don't actually know if all that works but the individual bits of drawing code do work and the whole thing compiles).

But no amount of poring over cairo manuals has yet explained to me how to do the transfers from that surface to the window. It surely has to involve some kind of expose event/draw signal handler containing a cairo_paint() operation, but I can't find simple code for painting from an offscreen cairo surface onto a window.

Last edited by hazel; 03-28-2024 at 10:14 AM.
 
Old 03-28-2024, 10:20 AM   #2
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 998

Rep: Reputation: 470Reputation: 470Reputation: 470Reputation: 470Reputation: 470
To copy from a surface to a device, use cairo_set_source_surface () to select the surface into the device, and then call cairo_paint ().

If you want to read from the surface, create an image surface rather than a similar surface as the device. Cairo supports many types of surfaces, most of which are write-only. The image surface can be both read and written.
Ed
 
1 members found this post helpful.
Old 03-28-2024, 10:43 AM   #3
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 7,575

Original Poster
Blog Entries: 19

Rep: Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453
Thanks. Presumably I'll need a separate context for the transfer from the one I've been using for drawing on the pixbuf. Can I get the surface (now the source) from the old context (which I assume has an internal pointer to it)? I have that context permanently saved so that it is accessible from any function.

Last edited by hazel; 03-28-2024 at 10:44 AM.
 
Old 03-28-2024, 11:07 AM   #4
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 998

Rep: Reputation: 470Reputation: 470Reputation: 470Reputation: 470Reputation: 470
The best approach is to create the image surface and then create a context (cairo_t) from the surface to draw on. The image surface is kept. Contexts are created and destroyed as needed.

Does the program really need an intermediate (GdkPixbuf in GTK2 and cairo_image_surface in GTK3)? The reason I ask is that GTK3 automatically double-buffers drawing.
Ed
 
Old 03-28-2024, 12:55 PM   #5
teckk
LQ Guru
 
Registered: Oct 2004
Distribution: Arch
Posts: 5,138
Blog Entries: 6

Rep: Reputation: 1827Reputation: 1827Reputation: 1827Reputation: 1827Reputation: 1827Reputation: 1827Reputation: 1827Reputation: 1827Reputation: 1827Reputation: 1827Reputation: 1827
You can draw on this with your mouse. Also look at line 155. Just like last time.

I compiled this a fresh, it works.

cairo_gtk.c
Code:
//gcc $(pkg-config --cflags gtk+-3.0) cairo_gtk.c -o cairo_gtk $(pkg-config --libs gtk+-3.0)

#include <gtk/gtk.h>

static cairo_surface_t *surface = NULL;

static void clear_surface (void) {
    cairo_t *cr;

    cr = cairo_create (surface);

    cairo_set_source_rgb (cr, 1, 1, 1);
    cairo_paint (cr);

    cairo_destroy (cr);
}

// Create a new surface of the appropriate size to store paint
static gboolean configure_event_cb (GtkWidget *widget,
                                    GdkEventConfigure *event, gpointer data) {
    if (surface)
        cairo_surface_destroy (surface);

    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));

    //Initialize the surface to white
    clear_surface ();
    
    return TRUE;
}

/* Redraw the screen from the surface. The ::draw
  signal receives a ready-to-be-used cairo_t that is already
  clipped to only draw the exposed areas of the widget */
 
static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, gpointer   data) {
    cairo_set_source_surface (cr, surface, 0, 0);
    cairo_paint (cr);

    return FALSE;
}

//Draw a rectangle on the surface at position
static void draw_brush (GtkWidget *widget, gdouble x, gdouble y) {
    cairo_t *cr;

    // Paint to the surface, store state
    cr = cairo_create (surface);

    cairo_rectangle (cr, x - 3, y - 3, 6, 6);
    cairo_fill (cr);

    cairo_destroy (cr);

    // Invalidate the affected region of the drawing area.
    gtk_widget_queue_draw_area (widget, x - 3, y - 3, 6, 6);
}

/* Handle button press events by either drawing a rectangle
  or clearing the surface, depending on which button was pressed.
  The ::button-press signal handler receives a GdkEventButton
  struct which contains this information. */

static gboolean button_press_event_cb (GtkWidget *widget,
                       GdkEventButton *event, gpointer data) {
    // In case no configure event
    if (surface == NULL)
        return FALSE;

    if (event->button == GDK_BUTTON_PRIMARY) {
        draw_brush (widget, event->x, event->y);
    } else if (event->button == GDK_BUTTON_SECONDARY) {
        clear_surface ();
        gtk_widget_queue_draw (widget);
    }

    return TRUE;
}

/* Handle motion events by continuing to draw if button 1 is
  still held down. The ::motion-notify signal handler receives
  a GdkEventMotion struct which contains this information. */
 
static gboolean motion_notify_event_cb (GtkWidget *widget,
                        GdkEventMotion *event, gpointer data) {
    // In case no configure event
    if (surface == NULL)
        return FALSE;

    if (event->state & GDK_BUTTON1_MASK)
        draw_brush (widget, event->x, event->y);
        
    return TRUE;
}

static void close_window (void) {
    if (surface)
        cairo_surface_destroy (surface);
}

static void activate (GtkApplication *app, gpointer user_data) {
    GtkWidget *window;
    GtkWidget *frame;
    GtkWidget *drawing_area;

    window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "Drawing On Me with mouse Hazel");

    g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);

    gtk_container_set_border_width (GTK_CONTAINER (window), 8);

    frame = gtk_frame_new (NULL);
    gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
    gtk_container_add (GTK_CONTAINER (window), frame);

    drawing_area = gtk_drawing_area_new ();
    // Min size
    gtk_widget_set_size_request (drawing_area, 500, 500);

    gtk_container_add (GTK_CONTAINER (frame), drawing_area);

    // Signals used to handle the backing surface
    g_signal_connect (drawing_area, "draw",
                    G_CALLBACK (draw_cb), NULL);
    g_signal_connect (drawing_area,"configure-event",
                    G_CALLBACK (configure_event_cb), NULL);

    /* Event signals */
    g_signal_connect (drawing_area, "motion-notify-event",
                    G_CALLBACK (motion_notify_event_cb), NULL);
    g_signal_connect (drawing_area, "button-press-event",
                    G_CALLBACK (button_press_event_cb), NULL);

    /* Receive events the drawing area doesn't normally
     subscribe to. In particular, we need to ask for the
     button press and motion notify events that want to handle. */
    
    gtk_widget_set_events (drawing_area, gtk_widget_get_events (drawing_area)
                                     | GDK_BUTTON_PRESS_MASK
                                     | GDK_POINTER_MOTION_MASK);

    gtk_widget_show_all (window);
}

int main (int argc, char **argv)
{
    GtkApplication *app;
    int status;

    //Old or New Syntax for your gtk version.
    //app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
    app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    
    g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);

    return status;
}

Last edited by teckk; 03-28-2024 at 01:00 PM. Reason: Code formatting in code window
 
Old 03-28-2024, 01:05 PM   #6
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 7,575

Original Poster
Blog Entries: 19

Rep: Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453
Quote:
Originally Posted by EdGr View Post
Does the program really need an intermediate (GdkPixbuf in GTK2 and cairo_image_surface in GTK3)? The reason I ask is that GTK3 automatically double-buffers drawing.
Ed
Ah! That's something I never considered. As I said before, this program was originally written using gtk2 with gdk2 graphics. I found, in some online source or other, a way of writing to a gtk_drawing_area using a GdkPixbuf as an intermediary surface and I used that. The program I was copying from didn't write to the widget directly so I assumed that was how it had to be done.

So what you are saying, if I understand you, is that gtk3 can do it all in a much simpler and more logical way. Just use the gdk window itself as a cairo surface like here and write the stave and the glyphs onto it and they'll be buffered automatically. And will this automatically handle expose events too so that I don't have to write a handler for them? If so, it's a great improvement in the API. I can understand how that works whereas before I was copying someone else's work blindly.

Last edited by hazel; 03-28-2024 at 01:07 PM.
 
Old 03-28-2024, 01:26 PM   #7
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 7,575

Original Poster
Blog Entries: 19

Rep: Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453
Thank you Tekk. That will take a bit of digesting. Actually I don't really draw on this widget with my mouse. I just select notes and rests from a musical toolbox and click on the stave where I want them to go. All the actual drawing is done by background functions. There's also a backspace/edit function incorporated in case of need, but the whole thing is really just a visual guide, so I don't have to memorise all the pmw code. The real output is the pmw text file which is being written in the background, which I can then convert into sheet music. I've been using this for years now to write out my cello parts.

I have a suggestion. I will spend the next few days seeing if I can apply your advice and Ed's to the program and get a fully compilable version. If so, we go on to debugging! If not, I'll come back for more help.

Last edited by hazel; 03-28-2024 at 01:27 PM.
 
Old 03-28-2024, 01:39 PM   #8
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 998

Rep: Reputation: 470Reputation: 470Reputation: 470Reputation: 470Reputation: 470
hazel - You need to write a draw () handler. GTK3 passes the device context on which to draw. teckk's example shows how to do this. It is using a similar surface rather than an image surface which is okay for a program that does not need to access pixel data.
Ed
 
1 members found this post helpful.
Old 03-31-2024, 11:19 AM   #9
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 7,575

Original Poster
Blog Entries: 19

Rep: Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453
Ok. I've rejigged everything so that all primary drawing is now on the drawing area itself and not the pixbuf. I found some code on Stackoverflow for getting a cairo context for the widget (you get a GdkContext first and then convert it), and all my glyph writing functions now use this context. I don't remember any more where I borrowed the original code from, but I suspect now that their use of an offscreen pixbuf as the primary drawing surface was a concession to the flicker effect that apparently occurred in gtk2 when you wrote directly to the screen.

Now about handlers: in the original gtk2 version I had handlers for a configure event and an expose event. The handler for the configure event created the backup surface if it did not already exist, and the one for the expose event copied from the backup to the exposed area of the window. There was always an expose event after each configure event and then there were additional ones due to the movement of other windows.

In the new version, the configure event can still work in the same way: the backup surface is created only if it does not already exist. This is different from the way Tekk does it in his example because his is a scribble program, whereas my stave and the notes drawn on it are serious work that has to be preserved for the duration of the program (or until the user calls for a new stave) and not casually overwritten. Also I have protected the stave area from changing shape even when the window is maximised so a one-off configuration should be OK (and hasn't caused any problems to date).

I notice that Tekk's little program does not contain an expose event handler but I think I shall need one, again because the stave data in my program are essential and must be redrawn from backup as and when required. I think this will require turning the pixbuf surface into a temporary source for the window's stored cairo context, somehow clipping the area I want to refresh and then calling cairo paint for that area.

And I shall need to copy newly drawn glyphs onto the pixbuf for safe storage as they are written. Is that what the draw signal handler does?

Just tell me if I've understood things correctly.

Last edited by hazel; 03-31-2024 at 11:38 AM.
 
Old 03-31-2024, 11:37 AM   #10
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 7,575

Original Poster
Blog Entries: 19

Rep: Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453
I think I have found what I'm going to need:

gdk_cairo_set_source_window and gdk_cairo_set_source_pixbuf should handle the copying between the two images: the window becomes the source during the configure event (using the pixbuf's cairo context) and the pixbuf during the expose event (using the window's cairo context).
 
Old 03-31-2024, 12:30 PM   #11
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 998

Rep: Reputation: 470Reputation: 470Reputation: 470Reputation: 470Reputation: 470
The GTK3 draw handler replaces the expose handler in GTK2. The draw handler should do the actual drawing to the screen. The configure handler works in the same way in both GTK2 and GTK3.

Reading pixels from the framebuffer is too slow for general use. This functionality is intended for screen capture programs.

I know you don't want to hear suggestions for major changes, but IMO, the program should maintain a data structure of the musical score. All editing, drawing, saving, and printing are done from the data structure. Very little code should deal with drawing, and none should deal with pixels (pixels don't exist if the surface is a SVG or Postscript file). Written this way, the same code can draw to both raster and vector devices.
Ed
 
1 members found this post helpful.
Old 04-01-2024, 01:35 AM   #12
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 7,575

Original Poster
Blog Entries: 19

Rep: Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453
I think there's a slight misunderstanding here. pmwScribe is not a complete music-writing program. Such a thing would be far beyond my capacity. As its name suggests, pmwScribe is just a convenient adjunct to the wonderful pmw (Phillip's Music Writer) program, a quick-and-dirty way of creating pmw text files.

Think of pmw as a language. You write files in it, using your favourite text editor, and then the pmw program translates those files into printable music scores. The problem is that, as an occasional user, I could never remember the codes! For me, a stave interface is friendlier than an editor for this sort of thing. The big, well-known music-writing programs do of course have a stave writing interface but pmw doesn't. It's just a coding system plus a rather unusual postscript font and a translation program. So I decided to make things easier for myself by creating a visual aid in the form of an onscreen stave, where I could pin notes and the program would turn them into pmw code in the background. In addition it would be fun to write and I'd learn a bit more about programmig.

That was a few years ago now. Like all my graphical programs, pmwScribe used gtk2 and it still works very well. But last year I decided to rewrite all my graphical programs in gtk3. Think of it as a translation project. pmwScribe is the last one I need to do. I don't want to do more code rearrangement than is necessary. Remember I'm nearly eighty!

OK, so the screenwriting part of the program has four tasks:
1) Create a backup surface (done; the original configuration event handler does that);
2) Do the visual backup each time a new glyph is written onscreen (still to do and I'm not quite sure how);
3) Handle expose events (effectively done; I know now that I just need to convert the old expose event handler into a draw handler);
4) write the corresponding pmw code into the file (done because this part of the program doesn't need any re-coding).

PS: The reason why stage 2 is a problem for me is that there is nothing corresponding to it in the gtk2 version, where the glyphs were written directly to the backing pixbuf. Then they could be transferred to the screen on a one-by-one basis by using gtk_widget_queue_draw_area. Now I need to perform that particular operation the other way up.

Last edited by hazel; 04-01-2024 at 02:39 AM. Reason: Added postscript
 
Old 04-01-2024, 07:03 AM   #13
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 998

Rep: Reputation: 470Reputation: 470Reputation: 470Reputation: 470Reputation: 470
Thanks for the explanation! Your program is a simple editor.

I found that drag-drop had to be implemented differently on GTK3 compared to GTK2 because the Cairo->GDK->X11 pipeline operates in the forward direction only.

The draw handler should first draw the existing notes and then draw the dragged note on top. Every time the dragged note changes position, the entire window is redrawn. Incremental drawing is no longer advantageous since modern hardware can do a complete redraw at the hardware's frame rate.

The dragged note is dropped by drawing it on the surface containing the existing notes. Called in this way, Cairo writes to the framebuffer and never reads from it. I think you will find that the new code is much cleaner.

Good luck!
Ed
 
Old 04-01-2024, 08:29 AM   #14
hazel
LQ Guru
 
Registered: Mar 2016
Location: Harrow, UK
Distribution: LFS, AntiX, Slackware
Posts: 7,575

Original Poster
Blog Entries: 19

Rep: Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453Reputation: 4453
No, you've lost me again! Here's how it works at present:

You select a glyph type (say a crotchet) and click on the stave where you want it to go. Two button-press handlers are activated: the first causes the appropriate drawing function to draw the glyph on whatever surface you are using for that purpose, and the second writes the corresponding pmw code to the pmw code file. The visible notes aren't actually dragged over from the toolbox. I know there are interfaces that work like that but I have/had no idea how to do such a thing, so I have drawing routines that create the glyphs in situ. That means of course that we don't have to worry about
Quote:
the dragged note changing position.
Nothing ever changes position except during an edit operation.

Under the old gtk2 regime, the drawing surface was the backup pixbuf, so all data transfers were from the pixbuf to the drawing_area window. There was no traffic in the opposite direction. Individual notes were transferred to the window by exactly the same handler as was used for expose events; in this case it was invoked by a gtk_widget_queue_draw call at the end of the drawing operation.

Under the new regime, the drawing surface is the window itself, which I really like because it is far more logical than the old way. But it means that we now need an extra handler to copy individual notes to the backup pixbuf as they are written. Logically this means a third button-press handler.

Last edited by hazel; 04-01-2024 at 08:38 AM.
 
Old 04-01-2024, 09:30 AM   #15
RobertoCellis
LQ Newbie
 
Registered: Apr 2024
Posts: 4

Rep: Reputation: 0
Quote:
Originally Posted by EdGr View Post
Thanks for the explanation! Your program is a simple editor.

I found that drag-drop had to be implemented differently on GTK3 compared to GTK2 because the Cairo->GDK->X11 pipeline operates in the forward direction only.

The draw handler should first draw the existing notes and then draw the dragged note on top. Every time the dragged note changes position, the entire window is redrawn. Incremental drawing is no longer advantageous since modern hardware can do a complete redraw at the hardware's frame rate.

The dragged note is dropped by drawing it on the surface containing the existing notes. Called in this way, Cairo writes to the framebuffer and never reads from it. I think you will find that the new code is much cleaner.

Good luck!
Ed
thanks
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] Drawing arcs in cairo hazel Programming 15 03-19-2024 07:39 AM
Cairo build fails at cairo-analysis-surface.lo CME2 Linux - Newbie 2 12-15-2017 08:10 PM
[SOLVED] cairo's image surface backend feature could be enable ... no lee.colbert Linux - Software 3 10-21-2012 10:43 PM
pckage "cairo" required by cairo" not found barunparichha Linux - Software 4 06-25-2008 08:29 AM
GTK Drawing Area gsrikanth Programming 0 10-06-2006 07:42 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 09:04 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration