X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Frecanim.c;fp=hacks%2Frecanim.c;h=f8a3dd5948a59cbcff1ffdd892216d059d54f70a;hp=0000000000000000000000000000000000000000;hb=d5186197bc394e10a4402f7f6d23fbb14103bc50;hpb=6afd6db0ae9396cd7ff897ade597cd5483f49b0e diff --git a/hacks/recanim.c b/hacks/recanim.c new file mode 100644 index 00000000..f8a3dd59 --- /dev/null +++ b/hacks/recanim.c @@ -0,0 +1,319 @@ +/* recanim, Copyright (c) 2014 Jamie Zawinski + * Record animation frames of the running screenhack. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifdef USE_GL +# ifdef HAVE_COCOA +# include "jwxyz.h" +# else /* !HAVE_COCOA -- real Xlib */ +# include +# include +# endif /* !HAVE_COCOA */ +# ifdef HAVE_JWZGLES +# include "jwzgles.h" +# endif /* HAVE_JWZGLES */ +#endif /* USE_GL */ + +#ifdef HAVE_GDK_PIXBUF +# ifdef HAVE_GTK2 +# include +# else /* !HAVE_GTK2 */ +# include +# endif /* !HAVE_GTK2 */ +#endif /* HAVE_GDK_PIXBUF */ + +#include +#include + +#include "screenhackI.h" +#include "recanim.h" + +struct record_anim_state { + Screen *screen; + Window window; + int frame_count; + int target_frames; + XWindowAttributes xgwa; + char *title; + int pct; +# ifdef USE_GL + char *data, *data2; +# else /* !USE_GL */ + XImage *img; + Pixmap p; + GC gc; +# endif /* !USE_GL */ +}; + +record_anim_state * +screenhack_record_anim_init (Screen *screen, Window window, int target_frames) +{ + Display *dpy = DisplayOfScreen (screen); + record_anim_state *st; + +# ifndef USE_GL + XGCValues gcv; +# endif /* !USE_GL */ + + if (target_frames <= 0) return 0; + + st = (record_anim_state *) calloc (1, sizeof(*st)); + + st->screen = screen; + st->window = window; + st->target_frames = target_frames; + st->frame_count = 0; + +# ifdef HAVE_GDK_PIXBUF + { + Window root; + int x, y; + unsigned int w, h, d, bw; + XGetGeometry (dpy, window, &root, &x, &y, &w, &h, &bw, &d); + gdk_pixbuf_xlib_init_with_depth (dpy, screen_number (screen), d); + +# ifdef HAVE_GTK2 +# if !GLIB_CHECK_VERSION(2, 36 ,0) + g_type_init(); +# endif +# else /* !HAVE_GTK2 */ + xlib_rgb_init (dpy, screen); +# endif /* !HAVE_GTK2 */ + } +# else /* !HAVE_GDK_PIXBUF */ +# error GDK_PIXBUF is required +# endif /* !HAVE_GDK_PIXBUF */ + + XGetWindowAttributes (dpy, st->window, &st->xgwa); + +# ifdef USE_GL + + st->data = (char *) calloc (st->xgwa.width, st->xgwa.height * 3); + st->data2 = (char *) calloc (st->xgwa.width, st->xgwa.height * 3); + +# else /* !USE_GL */ + + st->gc = XCreateGC (dpy, st->window, 0, &gcv); + st->p = XCreatePixmap (dpy, st->window, + st->xgwa.width, st->xgwa.height, st->xgwa.depth); + st->img = XCreateImage (dpy, st->xgwa.visual, st->xgwa.depth, ZPixmap, + 0, 0, st->xgwa.width, st->xgwa.height, 8, 0); + st->img->data = (char *) calloc (st->img->height, st->img->bytes_per_line); +# endif /* !USE_GL */ + + +# ifndef HAVE_COCOA + XFetchName (dpy, st->window, &st->title); +# endif /* !HAVE_COCOA */ + + return st; +} + + +void +screenhack_record_anim (record_anim_state *st) +{ + int bytes_per_line = st->xgwa.width * 3; + +# ifndef USE_GL + + Display *dpy = DisplayOfScreen (st->screen); + char *data = (char *) st->img->data; + + /* Under XQuartz we can't just do XGetImage on the Window, we have to + go through an intermediate Pixmap first. I don't understand why. + Also, the fucking resize handle shows up as black. God dammit. + A workaround for that is to temporarily remove /opt/X11/bin/quartz-wm + */ + XCopyArea (dpy, st->window, st->p, st->gc, 0, 0, + st->xgwa.width, st->xgwa.height, 0, 0); + XGetSubImage (dpy, st->p, 0, 0, st->xgwa.width, st->xgwa.height, + ~0L, ZPixmap, st->img, 0, 0); + + /* Convert BGRA to RGB */ + { + const char *in = st->img->data; + char *out = st->img->data; + int x, y; + int w = st->img->width; + int h = st->img->height; + for (y = 0; y < h; y++) + { + const char *in2 = in; + for (x = 0; x < w; x++) + { + *out++ = in2[2]; + *out++ = in2[1]; + *out++ = in2[0]; + in2 += 4; + } + in += st->img->bytes_per_line; + } + } +# else /* USE_GL */ + + char *data = st->data2; + int y; + +# ifdef HAVE_JWZGLES +# undef glReadPixels /* Kludge -- unimplemented in the GLES compat layer */ +# endif + + /* First OpenGL frame tends to be random data like a shot of my desktop, + since it is the front buffer when we were drawing in the back buffer. + Leave it black. */ + /* glDrawBuffer (GL_BACK); */ + if (st->frame_count != 0) + glReadPixels (0, 0, st->xgwa.width, st->xgwa.height, + GL_RGB, GL_UNSIGNED_BYTE, st->data); + + /* Flip vertically */ + for (y = 0; y < st->xgwa.height; y++) + memcpy (data + bytes_per_line * y, + st->data + bytes_per_line * (st->xgwa.height - y - 1), + bytes_per_line); + +# endif /* USE_GL */ + + +# ifdef HAVE_GDK_PIXBUF + { + const char *type = "png"; + char fn[1024]; + GError *error = 0; + GdkPixbuf *pixbuf; + + pixbuf = gdk_pixbuf_new_from_data ((guchar *) data, + GDK_COLORSPACE_RGB, False, /* alpha */ + 8, /* bits per sample */ + st->xgwa.width, st->xgwa.height, + bytes_per_line, + 0, 0); + + sprintf (fn, "%s-%06d.%s", progname, st->frame_count, type); + gdk_pixbuf_save (pixbuf, fn, type, &error, NULL); + + if (error) + { + fprintf (stderr, "%s: %s: %s\n", progname, fn, error->message); + exit (1); + } + + g_object_unref (pixbuf); + } +# else /* !HAVE_GDK_PIXBUF */ +# error GDK_PIXBUF is required +# endif /* !HAVE_GDK_PIXBUF */ + +# ifndef HAVE_COCOA + { /* Put percent done in window title */ + int pct = 100 * (st->frame_count + 1) / st->target_frames; + if (pct != st->pct && st->title) + { + Display *dpy = DisplayOfScreen (st->screen); + char *t2 = (char *) malloc (strlen(st->title) + 20); + sprintf (t2, "%s: %d%%", st->title, pct); + XStoreName (dpy, st->window, t2); + XSync (dpy, 0); + free (t2); + st->pct = pct; + } + } +# endif /* !HAVE_COCOA */ + + if (++st->frame_count >= st->target_frames) + screenhack_record_anim_free (st); +} + + +void +screenhack_record_anim_free (record_anim_state *st) +{ +# ifndef USE_GL + Display *dpy = DisplayOfScreen (st->screen); +# endif /* !USE_GL */ + + struct stat s; + int i; + const char *type = "png"; + char cmd[1024]; + char fn[1024]; + const char *soundtrack = 0; + + fprintf (stderr, "%s: wrote %d frames\n", progname, st->frame_count); + +# ifdef USE_GL + free (st->data); + free (st->data2); +# else /* !USE_GL */ + free (st->img->data); + st->img->data = 0; + XDestroyImage (st->img); + XFreeGC (dpy, st->gc); + XFreePixmap (dpy, st->p); +# endif /* !USE_GL */ + + sprintf (fn, "%s.%s", progname, "mp4"); + unlink (fn); + +# define ST "images/drives-200.mp3" + soundtrack = ST; + if (stat (soundtrack, &s)) soundtrack = 0; + if (! soundtrack) soundtrack = "../" ST; + if (stat (soundtrack, &s)) soundtrack = 0; + if (! soundtrack) soundtrack = "../../" ST; + if (stat (soundtrack, &s)) soundtrack = 0; + + sprintf (cmd, + "ffmpeg" + " -r 30" /* Must be before -i */ + " -framerate 30" + " -i '%s-%%06d.%s'", + progname, type); + if (soundtrack) + sprintf (cmd + strlen(cmd), + " -i '%s' -map 0:v:0 -map 1:a:0 -acodec libfaac", + soundtrack); + sprintf (cmd + strlen(cmd), + " -c:v libx264" + " -profile:v high" + " -crf 18" + " -pix_fmt yuv420p" + " '%s'" + " 2>&-", + fn); + fprintf (stderr, "%s: exec: %s\n", progname, cmd); + system (cmd); + + if (stat (fn, &s)) + { + fprintf (stderr, "%s: %s was not created\n", progname, fn); + exit (1); + } + + fprintf (stderr, "%s: wrote %s (%.1f MB)\n", progname, fn, + s.st_size / (float) (1024 * 1024)); + + for (i = 0; i < st->frame_count; i++) + { + sprintf (fn, "%s-%06d.%s", progname, i, type); + unlink (fn); + } + + if (st->title) + free (st->title); + free (st); + exit (0); +}