/* my version of https://twitter.com/tszetela/status/1330287937808904194 */ /* to compile: gcc -Wall spirals.c -lX11 -lcairo */ /* parameters */ /* fps and rotations_per_second don't really work. The code is too slow to * be realtime, plus the time handling is completely wrong. Just make their * product = 100 for the animation to loop every 100 frames (actually 50, * because after half a turn a horizontal bar becomes horizontal again). * * The most important parameters are n_loops and n_bars. * If you change them, you will need to adjust start_x, deltax_1 and delta_x2 * for the resulting image to "look nice", that is: * - the image is more or less centered (start_x) * - the 2 columns of a group are to the "right" distance (deltax_1) * - the 3 groups of columns are "correctly" spaced (deltax_2) * You can experiment if you are not satisfied with what you see. */ double fps = 20; /* frames per second * (doesn't really work) */ double rotations_per_second = 1/5.; /* speed of rotation (since fps doesn't * really work, this one also doesn't * work) */ double n_loops = 4.3; /* number of loops in one column */ int n_bars = 120; /* number of bars to plot for one column */ int start_x = 100; /* x position in the image for 1st column */ int deltax_1 = 60; /* distance between the 2 columns of a group */ int deltax_2 = 61*2; /* distance between each group of 2 columns */ /* don't change anything below */ #include #include #include #include #include #include #include #include #include #include #ifndef M_PI #define M_PI (3.14159265358979323846) #endif #define WIDTH (512) #define HEIGHT (512) cairo_surface_t *cs; cairo_surface_t *csout; void draw_bar(cairo_t *c, int x, int y, int length, double angle, int last) { double w = length; double h = length/8.; cairo_save(c); cairo_translate(c, x, y); cairo_rotate(c, angle); cairo_translate(c, -w/2, -h/2); cairo_rectangle(c, 0, 0, w, h); if (last) cairo_set_source_rgb(c, 0.8, 0.4, 0.4); else cairo_set_source_rgb(c, 0.8, 0.8, 0.7); cairo_fill_preserve(c); cairo_set_source_rgb(c, 0.4, 0.3, 0.3); cairo_stroke(c); cairo_restore(c); } void paint(int curtime, int gif_mode, int step_mode) { cairo_t *c; int i, j; c=cairo_create(cs); /* clear screen */ cairo_rectangle(c, 0, 0, WIDTH, HEIGHT); cairo_set_source_rgb(c, 0.8, 0.8, 0.7); cairo_fill(c); int start_y = 80; double angle = 2 * M_PI * curtime / fps * rotations_per_second; double yinc = 360. / n_bars; double anginc = M_PI * n_loops / n_bars; double angdelta = -M_PI/8; int loop_max = step_mode ? curtime : n_bars; int loctime = 0; if (step_mode) angle = 0; for (i = 0; i < loop_max; i++) { int len = 100; int p = 0; double a = 0; /* draw the 6 bars of the current line */ for (j = 0; j < 3; j++) { /* bar of first column of a group */ if (step_mode && loctime >= curtime) break; draw_bar(c, p+start_x, start_y + i*yinc, len, angle + a, step_mode && loctime == curtime-1); loctime++; /* bar of second column of a group */ if (step_mode && loctime >= curtime) break; draw_bar(c, p+start_x+deltax_1, start_y + i*yinc, len, -(angle + a + angdelta) - M_PI/2, step_mode && loctime == curtime-1); loctime++; p += deltax_2; a += angdelta; } angle += anginc; } cairo_destroy(c); if (gif_mode) return; /* and put it all to the xpixmap */ c=cairo_create(csout); cairo_set_source_surface(c, cs, 0, 0); cairo_rectangle(c, 0, 0, WIDTH, HEIGHT); cairo_paint(c); cairo_destroy(c); } void expose(Display *d, Window w, Pixmap p) { XCopyArea(d, p, w, DefaultGC(d, DefaultScreen(d)), 0,0, WIDTH, HEIGHT, 0,0); } void usage(void) { printf("usage:\n"); printf(" ./a.out X Window live mode\n"); printf(" ./a.out -gif > movie.raw\n"); printf(" export raw movie data\n"); printf(" ./a.out -gif-steps > movie.raw\n"); printf(" export raw movie data (step by step mode)\n"); printf("\nto convert from raw movie to gif, do:\n"); printf(" ffmpeg -f rawvideo -s 512x512 -r 20 -pix_fmt rgb24 -i movie.raw movie.gif\n"); printf("\nto convert the movie produced by -gif-steps, do (much smaller gif file):\n"); printf(" ffmpeg -f rawvideo -s 512x512 -r 20 -pix_fmt rgb24 -i movie.raw -vf palettegen palette.png\n"); printf(" ffmpeg -f rawvideo -s 512x512 -r 20 -pix_fmt rgb24 -i movie.raw -i palette.png -filter_complex paletteuse movie.gif\n"); printf("\nto make a small gif:\n"); printf(" ffmpeg -i spirals.gif -vf scale=64:64 spirals-64.gif\n"); exit(1); } int main(int n, char **v) { Display *d; Window w; Pixmap p; fd_set r; int repaint; int redraw; int i; int j; int ret; struct timeval time_out; int do_gif = 0; int do_gif_steps = 0; int curtime = 0; for (i = 1; i < n; i++) { if (!strcmp(v[i], "-h") || !strcmp(v[i], "--help")) usage(); if (!strcmp(v[i], "-gif")) { do_gif = 1; continue; } if (!strcmp(v[i], "-gif-steps")) { do_gif_steps = 1; continue; } usage(); } cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT); /* gif mode */ if (do_gif || do_gif_steps) { if (cairo_image_surface_get_width(cs) != WIDTH || cairo_image_surface_get_height(cs) != HEIGHT || cairo_image_surface_get_stride(cs) != WIDTH*4) { printf("fatal error, bad surface size\n"); exit(1); } for (i = 0; i < 50; i++) { unsigned char *c; paint(curtime, 1, do_gif_steps); curtime++; c = cairo_image_surface_get_data(cs); for (j = 0; j < 512*512; j++, c+=4) { putchar(c[2]); putchar(c[1]); putchar(c[0]); } } return 0; } /* x window mode */ d = XOpenDisplay(0); if (!d) abort(); w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, WIDTH, HEIGHT, 0, 0, 0); XSelectInput(d, w, KeyPressMask | ExposureMask); XMapWindow(d, w); p = XCreatePixmap(d, w, WIDTH,HEIGHT, DefaultDepth(d, DefaultScreen(d))); csout = cairo_xlib_surface_create(d, p, DefaultVisual(d, DefaultScreen(d)), WIDTH, HEIGHT); repaint = 1; redraw = 0; while (1) { XEvent ev; while (XPending(d)) { XNextEvent(d, &ev); switch (ev.type) { case Expose: redraw = 1; break; case KeyPress: { char buf[65]; KeySym ks; XLookupString(&ev.xkey, buf, 64, &ks, NULL); switch (ks) { case XK_Right: case 'n': printf("n"); fflush(stdout); break; case XK_Left: case 'p': printf("p"); fflush(stdout); break; case XK_Escape: case 'q': exit(1); } break; } default: break; } } if (repaint) { paint(curtime, 0, 0); redraw = 1; repaint = 0; } if (redraw) { expose(d, w, p); redraw = 0; } FD_ZERO(&r); FD_SET(ConnectionNumber(d), &r); XFlush(d); /* this time keeping is wrong, we sleep too much because we don't * take into account computation time of previous frame. Anyway, * on my computer, even at 20 fps, we are not realtime, so no big * deal... This program is too slow. */ time_out.tv_sec = 0; time_out.tv_usec = 1000000/fps; ret = select(ConnectionNumber(d)+1, &r, 0, 0, &time_out); if (ret == -1) abort(); if (ret == 0) { repaint = 1; curtime++; } } return 0; }