LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Drawing arcs in cairo (https://www.linuxquestions.org/questions/programming-9/drawing-arcs-in-cairo-4175734908/)

hazel 03-14-2024 01:18 PM

Drawing arcs in cairo
 
I'm trying to teach myself basic cairo programming but I have got stuck on arcs. The official cairo manual says:
Quote:

Angles are measured in radians. An angle of 0.0 is in the direction of the positive X axis (in user space). An angle of M_PI/2.0 radians (90 degrees) is in the direction of the positive Y axis (in user space). Angles increase in the direction from the positive X axis toward the positive Y axis. So with the default transformation matrix, angles increase in a clockwise direction.
This seems to me contradictory, both in itself and with my experiments. If (+)M_PI/2 is the positive Y-axis, then an arc from 0 (the positive X axis) to M_PI/2 should go anti-clockwise, i.e. above the X-axis. But it does in fact go clockwise underneath the X-axis. What am I missing?

Michael Uplawski 03-14-2024 03:01 PM

Quote:

Originally Posted by hazel (Post 6489726)
I'm trying to teach myself basic cairo programming but I have got stuck on arcs. The official cairo manual says:

This seems to me contradictory, both in itself and with my experiments. If (+)M_PI/2 is the positive Y-axis, then an arc from 0 (the positive X axis) to M_PI/2 should go anti-clockwise, i.e. above the X-axis. But it does in fact go clockwise underneath the X-axis. What am I missing?

I am not sure, Hazel, but the direction of the Y-axis may be downwards.

boughtonp 03-14-2024 03:36 PM


 
Taking "positive X axis" = east/right and "positive Y axis" = south/down you get...
Quote:

Angles are measured in radians. An angle of 0.0 is [east]. An angle of M_PI/2.0 radians (90 degrees) is [south]. Angles increase in the direction from the [east] toward the [south]. So with the default transformation matrix, angles increase in a clockwise direction.
...which seems consistent to me.


astrogeek 03-14-2024 04:41 PM

Indeed, as already said, on most (all?) computer displays, the origin (0,0) is the upper left corner and positive Y-axis is downwards.

I have wrestled with that off and on over the years as it does reverse the conventional meaning of polar rotations.

teckk 03-14-2024 08:36 PM

Quote:

I'm trying to teach myself basic cairo programming but I have got stuck on arcs
Did that a while back hazel. Here is a working example that you can pick apart. I updated to current gtk3 syntax, and compiled with no errors.

clock.c
Code:

//gcc -lm clock.c -o clock $(pkg-config --cflags --libs gtk+-3.0)

#include <math.h>
#include <cairo.h>
#include <gtk/gtk.h>
#include <time.h>

#define WINDOW_WIDTH  800
#define WINDOW_HEIGHT 800

#define WHITE  255, 255, 255
#define RED    255, 0, 0
#define CYAN    0, 255, 255
#define WIDTH  3

static char buffer[256];
static time_t curtime;
static struct tm *loctime;
static int seconds;
static int minutes;
static int hours;
static int radius;
static int movingSecondsEffect = FALSE;

static void quit_application(gpointer app) {
    g_application_quit (G_APPLICATION (app));
}

static void clicked(GtkWindow *window, GdkEventButton *event, gpointer user_data) {
    gtk_window_set_decorated(window, !gtk_window_get_decorated(window));
}

//Hours ticks
void HourTick (GtkWidget *widget, cairo_t *cr, int nHour, int cx, int cy) {
    double dRadians = nHour * 3.14 / 6.0;
    int x1 = cx+(int) ((0.9 * radius * sin (dRadians)));
    int y1 = cy-(int) ((0.9 * radius * cos (dRadians)));
    int x2 = cx+(int) ((1.0 * radius * sin (dRadians)));
    int y2 = cy-(int) ((1.0 * radius * cos (dRadians)));

    cairo_set_source_rgb(cr, CYAN);
    cairo_set_line_width(cr, WIDTH);
    cairo_move_to(cr, x1, y1);
    cairo_line_to(cr, x2, y2);
    cairo_stroke(cr);
}

//Minute ticks
void MinTick (GtkWidget *widget, cairo_t *cr, int nHour, int cx, int cy) { 
    double dRadians2 = nHour * 3.14 / 30.0;
    int a1 = cx+(int) ((0.97 * radius * sin (dRadians2)));
    int b1 = cy-(int) ((0.97 * radius * cos (dRadians2)));
    int a2 = cx+(int) ((1.0 * radius * sin (dRadians2)));
    int b2 = cy-(int) ((1.0 * radius * cos (dRadians2)));

    cairo_set_source_rgb(cr, CYAN);
    cairo_set_line_width(cr, WIDTH);
    cairo_move_to(cr, a1, b1);
    cairo_line_to(cr, a2, b2);
    cairo_stroke(cr);


void DrawSeconds (GtkWidget *widget, cairo_t *cr, int cx, int cy) {
    /* --- Get radians from seconds --- */
    float dRadians = seconds * 3.14 / 30.0;

    /* --- Draw seconds --- */
    cairo_set_source_rgb(cr, RED);
    cairo_set_line_width(cr, WIDTH);
    cairo_move_to(cr, cx, cy);
    cairo_line_to(cr, cx + (0.9 * radius * sin (dRadians)),
                  cy - (0.9 * radius * cos (dRadians)));
    cairo_stroke(cr);
}

void DrawMinutes (GtkWidget *widget, cairo_t *cr, int cx, int cy) {
    /* --- Get radians from seconds --- */
    float dRadians = (minutes * 3.14 / 30.0) + (seconds * 3.14 / 1800.0);

    /* --- Draw seconds --- */
    cairo_set_source_rgb(cr, CYAN);
    cairo_set_line_width(cr, WIDTH * 2);
    cairo_move_to(cr, cx, cy);
    cairo_line_to(cr, cx + (0.8 * radius * sin (dRadians)),
                  cy - (0.8 * radius * cos (dRadians)));
    cairo_stroke(cr);
}

void DrawHours (GtkWidget *widget, cairo_t *cr, int cx, int cy) {
  /* --- Get radians from seconds --- */
  float dRadians = ((hours % 12) * 3.14 / 6.0) + (minutes * 3.14 / 360.0);
   
  /* --- Draw seconds --- */
  cairo_set_source_rgb(cr, CYAN);
  cairo_set_line_width(cr, WIDTH * 3);
  cairo_move_to(cr, cx, cy);
  cairo_line_to(cr, cx + (0.6 * radius * sin (dRadians)),
                  cy - (0.6 * radius * cos (dRadians)));
  cairo_stroke(cr);
}

static gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data) {
    int midx = gtk_widget_get_allocated_width(widget)/2;
    int midy = gtk_widget_get_allocated_height(widget)/2;
    int nHour;
   
    /* --- Draw Tickmarks at each hour/ minute --- */
    for (nHour = 1; nHour <= 60; nHour++) {
        MinTick(widget, cr, nHour, midx, midy);
        if(nHour <= 12) {
            HourTick(widget, cr, nHour, midx, midy);
        }
    }
   
    cairo_set_source_rgb(cr, WHITE);
    cairo_set_font_size (cr, 50);
    cairo_move_to(cr, midx/1.4, midy/2);
    cairo_show_text(cr, buffer);
    radius = MIN (midx, midy) -2 ;
    cairo_move_to(cr, midx , 0);

    DrawSeconds (widget, cr, midx, midy);
    DrawMinutes (widget, cr, midx, midy);
    DrawHours (widget, cr, midx, midy);

    return FALSE;
}

static gint time_handler (GtkWidget *widget) {
    curtime = time(NULL);
    loctime = localtime(&curtime);
    seconds = loctime->tm_sec;
    minutes = loctime->tm_min;
    hours  = loctime->tm_hour;

    strftime(buffer, 256, "%T", loctime);

    gtk_widget_queue_draw(widget);

    return TRUE;
}

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

    window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "Hazels' Cairo Clock");
    gtk_window_set_default_size (GTK_WINDOW (window), WINDOW_WIDTH, WINDOW_HEIGHT);
    gtk_container_set_border_width (GTK_CONTAINER (window), 0);
    gtk_widget_set_app_paintable(window, TRUE);

    GdkScreen *screen = gtk_widget_get_screen(window);
    GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
    gtk_widget_set_visual(window, visual);

    ebox = gtk_event_box_new();
    gtk_container_add(GTK_CONTAINER(window), ebox);

    drawing_area = gtk_drawing_area_new();
    gtk_widget_set_size_request (drawing_area, WINDOW_WIDTH - 20 , WINDOW_HEIGHT - 20);
    gtk_container_add (GTK_CONTAINER (ebox), drawing_area);

    g_signal_connect (drawing_area, "draw", G_CALLBACK (draw_cb), NULL);
    g_signal_connect (drawing_area, "delete-event", G_CALLBACK (gtk_main_quit), NULL);

    gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
    g_signal_connect(window, "button-press-event", G_CALLBACK(clicked), NULL);
    g_signal_connect_swapped(G_OBJECT(window), "destroy",
      G_CALLBACK(quit_application), app);
    g_timeout_add (1000, (GSourceFunc) time_handler, drawing_area);
    gtk_widget_show_all (window);
}

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

    app = gtk_application_new("org.cairo.clock", 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;
}

Edit: Removed 3 functions that werent needed.

hazel 03-15-2024 01:21 AM

Yes, but why on earth should the "positive" Y-axis be defined as the one that points downwards? The origin represents Y=0, and the Y-values plotted on the axis that points downward are negative. Positive values are on the upward-pointing Y-axis.

@astrogeek: Oh, I get a glimmer now after rereading your post. It's because of the longstanding convention about screen (and window) coordinates starting at the top left-hand corner, where down and right are the only available directions. But the cairo people really should have stated this explicitly as a parallel since it's far from obvious to the naive reader and their explanation makes no sense without it. Or do the words "(in user space)" do precisely that for those familiar with the terminology? (see appendix below)

@teckk I shall copy your program and examine it at leisure.

Appendix: So I googled "user space" and got some Java stuff like:
Quote:

Originally Posted by O'Reilly, Java Media APIs
User space is the coordinate space in which the user operates. At instantiation, the origin of user space is at the top-left corner of the screen with the x coordinate increasing to the right and the y coordinate increasing downward.


Michael Uplawski 03-15-2024 01:49 AM

Quote:

Originally Posted by hazel (Post 6489804)
Yes, but why on earth should the "positive" Y-axis be defined as the one that points downwards? The origin represents Y=0, and the Y-values plotted on the axis that points downward are negative. Positive values are on the upward-pointing Y-axis.

:party: Writing Web-Applications in Europe, we oftentimes had to wonder how on earth Sunday can be considered the first day of the week. In the end, you seal your brain and adapt.

hazel 03-15-2024 05:16 AM

@teckk I can't get your program to compile. ftr I am using:
Code:

gcc `pkg-config --cflags cairo,gtk+-3.0  --libs cairo,gtk+-3.0` teckks_clock.c
It complains about not finding G_APPLICATION_DEFAULT_FLAGS. What do I need to add?

teckk 03-15-2024 08:08 AM

How old is your version of gtk3? I'm on 3.24.41

If it is older then Line 182:
Code:

app = gtk_application_new("org.cairo.clock", G_APPLICATION_DEFAULT_FLAGS);
Needs to be:
Code:

app = gtk_application_new("org.cairo.clock", G_APPLICATION_FLAGS_NONE);
"They" change the syntax on those tools kits every now and then.

hazel 03-15-2024 08:44 AM

Quote:

Originally Posted by teckk (Post 6489878)
How old is your version of gtk3? I'm on 3.24.41

Mine is 3.24.31. So I made the change and now gcc works OK but then ld spits out some undefined symbol in libm.
Code:

/usr/bin/ld: /tmp/ccpTOwHV.o: undefined reference to symbol 'sin@@GLIBC_2.2.5'
/usr/bin/ld: /lib64/libm.so.6: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

Never mind. I can still study the source, which I assume was the point of your post.

NevemTeve 03-15-2024 08:56 AM

Add `-lm` to the end of linkage.

hazel 03-15-2024 10:15 AM

1 Attachment(s)
That is absolutely amazing! Thank you Teckk and NevemTeve too. I'll add the program to my fluxbox startup file so that I can use it as active wallpaper. But I also intend to study the code; I'm sure there's a lot that I can learn from it.


Just for interest, here is my version of a tenor clef, completed today based on what I learned from this thread.

pan64 03-15-2024 12:21 PM

Quote:

Originally Posted by hazel (Post 6489804)
Yes, but why on earth should the "positive" Y-axis be defined as the one that points downwards?

Historical reasons. But think about a regular book, the first line is at the top, and the Y axis (line numbers) are counted from top to bottom. In a text editor the characters are counted from top left corner. It is exactly the same.

teckk 03-15-2024 01:44 PM

Yup.

alternor.c
Code:

//gcc -lm alternor.c -o alternor $(pkg-config --cflags --libs gtk+-3.0)

#include <cairo.h>
#include <math.h>

#define NOTEWIDTH 5
#define STEMLENGTH 25

int main (int argc, char *argv[])
{
        cairo_surface_t *surface;
        cairo_t *cr;
        int x, y;
    surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 240, 80);
    cr = cairo_create (surface);

    /* Make surface white */
        cairo_set_source_rgb (cr, 1, 1, 1);
        cairo_paint (cr);
    /* Drawing code goes here */
        x = 120;
        y = 40;

        cairo_set_line_width (cr, 2);
    cairo_set_source_rgb (cr, 0, 0, 0);

    /*Body of clef...  */
        cairo_rectangle (cr, x-1,y-4*NOTEWIDTH, 2,8*NOTEWIDTH);
        cairo_fill (cr);
       
        cairo_move_to (cr, x+3,y-4*NOTEWIDTH);
        cairo_line_to (cr, x+3,y+4*NOTEWIDTH);
        cairo_stroke (cr);
       
        cairo_arc (cr, x+3*NOTEWIDTH,y-2*NOTEWIDTH, 2*NOTEWIDTH,
                        -7*M_PI/12,3*M_PI/4);
        cairo_line_to (cr, x+8,y+5);
       
    /*        cairo_arc (cr, x+3*NOTEWIDTH,y+2*NOTEWIDTH, 2*NOTEWIDTH,
        3*M_PI/4, 3*M_PI/12);  */
           
    cairo_arc (cr, x+3*NOTEWIDTH,y+2*NOTEWIDTH, 2*NOTEWIDTH, -7*M_PI/12,3*M_PI/4);       
        cairo_stroke (cr);

    /* Write output and clean up */
    cairo_surface_write_to_png (surface, "clef.png");
    cairo_surface_destroy (surface);
    cairo_destroy (cr);
        return 0;
}

And that writes to clef.png
Code:

curl -F 'file=@'clef.png'' https://0x0.st
https://0x0.st/HFO1.png


hazel 03-16-2024 01:11 AM

Thank you, Teckk. I didn't know about that site. This particular symbol btw can be used either as an alto or a tenor clef depending how high it is on the stave, hence the name.

In case anyone is wondering why I want to draw clefs, it is part of a program I wrote years ago as a personal front end to Philip's Music Writer (pmw), which is a brilliantly simple way of producing script music using a text code. pmwScribe gives you a screen stave on which you can pin notes and other musical glyphs and it translates them into pmw code. I wrote it using gtk2/gdk2 and I'm now trying to convert it into gtk3/cairo.


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