1 /* recanim, Copyright (c) 2014-2018 Jamie Zawinski <jwz@jwz.org>
2 * Record animation frames of the running screenhack.
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
15 #endif /* HAVE_CONFIG_H */
20 # else /* !HAVE_JWXYZ -- real Xlib */
23 # endif /* !HAVE_JWXYZ */
26 # endif /* HAVE_JWZGLES */
29 #ifdef HAVE_GDK_PIXBUF
31 # include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
32 # else /* !HAVE_GTK2 */
33 # include <gdk-pixbuf/gdk-pixbuf-xlib.h>
34 # endif /* !HAVE_GTK2 */
35 #endif /* HAVE_GDK_PIXBUF */
38 #include <sys/types.h>
40 #include "screenhackI.h"
43 #undef gettimeofday /* wrapped by recanim.h */
46 struct record_anim_state {
51 XWindowAttributes xgwa;
69 # ifdef GETTIMEOFDAY_TWO_ARGS
71 gettimeofday(&now, &tzp);
76 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
80 /* Some of the hacks set their timing based on the real-world wall clock,
81 so to make the animations record at a sensible speed, we need to slow
82 down that clock by discounting the time taken up by snapshotting and
85 static double recanim_time_warp = 0;
88 screenhack_record_anim_gettimeofday (struct timeval *tv
89 # ifdef GETTIMEOFDAY_TWO_ARGS
95 # ifdef GETTIMEOFDAY_TWO_ARGS
99 tv->tv_sec -= (time_t) recanim_time_warp;
100 tv->tv_usec -= 1000000 * (recanim_time_warp - (time_t) recanim_time_warp);
104 screenhack_record_anim_time (time_t *o)
107 # ifdef GETTIMEOFDAY_TWO_ARGS
110 screenhack_record_anim_gettimeofday (&tv
111 # ifdef GETTIMEOFDAY_TWO_ARGS
115 if (o) *o = tv.tv_sec;
121 screenhack_record_anim_init (Screen *screen, Window window, int target_frames)
123 Display *dpy = DisplayOfScreen (screen);
124 record_anim_state *st;
128 # endif /* !USE_GL */
130 if (target_frames <= 0) return 0;
132 st = (record_anim_state *) calloc (1, sizeof(*st));
136 st->target_frames = target_frames;
138 st->fade_frames = 30 * 1.5;
140 if (st->fade_frames >= (st->target_frames / 2) - 30)
143 # ifdef HAVE_GDK_PIXBUF
147 unsigned int w, h, d, bw;
148 XGetGeometry (dpy, window, &root, &x, &y, &w, &h, &bw, &d);
149 gdk_pixbuf_xlib_init_with_depth (dpy, screen_number (screen), d);
152 # if !GLIB_CHECK_VERSION(2, 36 ,0)
155 # else /* !HAVE_GTK2 */
156 xlib_rgb_init (dpy, screen);
157 # endif /* !HAVE_GTK2 */
159 # else /* !HAVE_GDK_PIXBUF */
160 # error GDK_PIXBUF is required
161 # endif /* !HAVE_GDK_PIXBUF */
163 XGetWindowAttributes (dpy, st->window, &st->xgwa);
167 st->data = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
168 st->data2 = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
172 st->gc = XCreateGC (dpy, st->window, 0, &gcv);
173 st->p = XCreatePixmap (dpy, st->window,
174 st->xgwa.width, st->xgwa.height, st->xgwa.depth);
175 st->img = XCreateImage (dpy, st->xgwa.visual, st->xgwa.depth, ZPixmap,
176 0, 0, st->xgwa.width, st->xgwa.height, 8, 0);
177 st->img->data = (char *) calloc (st->img->height, st->img->bytes_per_line);
178 # endif /* !USE_GL */
182 XFetchName (dpy, st->window, &st->title);
183 # endif /* !HAVE_JWXYZ */
189 /* Fade to black. Assumes data is 3-byte packed.
192 fade_frame (record_anim_state *st, unsigned char *data, double ratio)
195 int w = st->xgwa.width;
196 int h = st->xgwa.height;
197 unsigned char *s = data;
198 for (y = 0; y < h; y++)
199 for (x = 0; x < w; x++)
200 for (i = 0; i < 3; i++)
206 screenhack_record_anim (record_anim_state *st)
208 int bytes_per_line = st->xgwa.width * 3;
209 double start_time = double_time();
213 Display *dpy = DisplayOfScreen (st->screen);
214 char *data = (char *) st->img->data;
216 /* Under XQuartz we can't just do XGetImage on the Window, we have to
217 go through an intermediate Pixmap first. I don't understand why.
218 Also, the fucking resize handle shows up as black. God dammit.
219 A workaround for that is to temporarily remove /opt/X11/bin/quartz-wm
221 XCopyArea (dpy, st->window, st->p, st->gc, 0, 0,
222 st->xgwa.width, st->xgwa.height, 0, 0);
223 XGetSubImage (dpy, st->p, 0, 0, st->xgwa.width, st->xgwa.height,
224 ~0L, ZPixmap, st->img, 0, 0);
226 /* Convert BGRA to RGB */
228 const char *in = st->img->data;
229 char *out = st->img->data;
231 int w = st->img->width;
232 int h = st->img->height;
233 for (y = 0; y < h; y++)
235 const char *in2 = in;
236 for (x = 0; x < w; x++)
243 in += st->img->bytes_per_line;
248 char *data = st->data2;
252 # undef glReadPixels /* Kludge -- unimplemented in the GLES compat layer */
255 /* First OpenGL frame tends to be random data like a shot of my desktop,
256 since it is the front buffer when we were drawing in the back buffer.
258 /* glDrawBuffer (GL_BACK); */
259 if (st->frame_count != 0)
260 glReadPixels (0, 0, st->xgwa.width, st->xgwa.height,
261 GL_RGB, GL_UNSIGNED_BYTE, st->data);
263 /* Flip vertically */
264 for (y = 0; y < st->xgwa.height; y++)
265 memcpy (data + bytes_per_line * y,
266 st->data + bytes_per_line * (st->xgwa.height - y - 1),
272 if (st->frame_count < st->fade_frames)
273 fade_frame (st, (unsigned char *) data,
274 (double) st->frame_count / st->fade_frames);
275 else if (st->frame_count >= st->target_frames - st->fade_frames)
276 fade_frame (st, (unsigned char *) data,
277 (double) (st->target_frames - st->frame_count - 1) /
280 # ifdef HAVE_GDK_PIXBUF
282 const char *type = "png";
287 pixbuf = gdk_pixbuf_new_from_data ((guchar *) data,
288 GDK_COLORSPACE_RGB, False, /* alpha */
289 8, /* bits per sample */
290 st->xgwa.width, st->xgwa.height,
294 sprintf (fn, "%s-%06d.%s", progname, st->frame_count, type);
295 gdk_pixbuf_save (pixbuf, fn, type, &error, NULL);
299 fprintf (stderr, "%s: %s: %s\n", progname, fn, error->message);
303 g_object_unref (pixbuf);
305 # else /* !HAVE_GDK_PIXBUF */
306 # error GDK_PIXBUF is required
307 # endif /* !HAVE_GDK_PIXBUF */
310 { /* Put percent done in window title */
311 int pct = 100 * (st->frame_count + 1) / st->target_frames;
312 if (pct != st->pct && st->title)
314 Display *dpy = DisplayOfScreen (st->screen);
315 char *t2 = (char *) malloc (strlen(st->title) + 20);
316 sprintf (t2, "%s: %d%%", st->title, pct);
317 XStoreName (dpy, st->window, t2);
323 # endif /* !HAVE_JWXYZ */
325 if (++st->frame_count >= st->target_frames)
326 screenhack_record_anim_free (st);
328 recanim_time_warp += double_time() - start_time;
333 screenhack_record_anim_free (record_anim_state *st)
336 Display *dpy = DisplayOfScreen (st->screen);
337 # endif /* !USE_GL */
341 const char *type = "png";
344 const char *soundtrack = 0;
346 fprintf (stderr, "%s: wrote %d frames\n", progname, st->frame_count);
352 free (st->img->data);
354 XDestroyImage (st->img);
355 XFreeGC (dpy, st->gc);
356 XFreePixmap (dpy, st->p);
357 # endif /* !USE_GL */
359 sprintf (fn, "%s.%s", progname, "mp4");
362 # define ST "images/drives-200.mp3"
364 if (stat (soundtrack, &s)) soundtrack = 0;
365 if (! soundtrack) soundtrack = "../" ST;
366 if (stat (soundtrack, &s)) soundtrack = 0;
367 if (! soundtrack) soundtrack = "../../" ST;
368 if (stat (soundtrack, &s)) soundtrack = 0;
374 " -framerate 30" /* rate of input: must be before -i */
376 " -r 30", /* rate of output: must be after -i */
379 sprintf (cmd + strlen(cmd),
380 " -i '%s' -map 0:v:0 -map 1:a:0 -acodec aac",
382 sprintf (cmd + strlen(cmd),
391 fprintf (stderr, "%s: exec: %s\n", progname, cmd);
396 fprintf (stderr, "%s: %s was not created\n", progname, fn);
400 fprintf (stderr, "%s: wrote %s (%.1f MB)\n", progname, fn,
401 s.st_size / (float) (1024 * 1024));
403 for (i = 0; i < st->frame_count; i++)
405 sprintf (fn, "%s-%06d.%s", progname, i, type);