From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / hacks / recanim.c
1 /* recanim, Copyright (c) 2014-2018 Jamie Zawinski <jwz@jwz.org>
2  * Record animation frames of the running screenhack.
3  *
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 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif /* HAVE_CONFIG_H */
16
17 #ifdef USE_GL
18 # ifdef HAVE_JWXYZ
19 #  include "jwxyz.h"
20 # else /* !HAVE_JWXYZ -- real Xlib */
21 #  include <GL/glx.h>
22 #  include <GL/glu.h>
23 # endif /* !HAVE_JWXYZ */
24 # ifdef HAVE_JWZGLES
25 #  include "jwzgles.h"
26 # endif /* HAVE_JWZGLES */
27 #endif /* USE_GL */
28
29 #ifdef HAVE_GDK_PIXBUF
30 # ifdef HAVE_GTK2
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 */
36
37 #include <sys/stat.h>
38 #include <sys/types.h>
39
40 #include "screenhackI.h"
41 #include "recanim.h"
42
43 #undef gettimeofday  /* wrapped by recanim.h */
44 #undef time
45
46 struct record_anim_state {
47   Screen *screen;
48   Window window;
49   int frame_count;
50   int target_frames;
51   XWindowAttributes xgwa;
52   char *title;
53   int pct;
54   int fade_frames;
55 # ifdef USE_GL
56   char *data, *data2;
57 # else  /* !USE_GL */
58   XImage *img;
59   Pixmap p;
60   GC gc;
61 # endif /* !USE_GL */
62 };
63
64
65 static double
66 double_time (void)
67 {
68   struct timeval now;
69 # ifdef GETTIMEOFDAY_TWO_ARGS
70   struct timezone tzp;
71   gettimeofday(&now, &tzp);
72 # else
73   gettimeofday(&now);
74 # endif
75
76   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
77 }
78
79
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
83    saving the frame.
84  */
85 static double recanim_time_warp = 0;
86
87 void
88 screenhack_record_anim_gettimeofday (struct timeval *tv
89 # ifdef GETTIMEOFDAY_TWO_ARGS
90                                      , struct timezone *tz
91 # endif
92                                      )
93 {
94   gettimeofday (tv
95 # ifdef GETTIMEOFDAY_TWO_ARGS
96                 , tz
97 # endif
98                 );
99   tv->tv_sec  -= (time_t) recanim_time_warp;
100   tv->tv_usec -= 1000000 * (recanim_time_warp - (time_t) recanim_time_warp);
101 }
102
103 time_t
104 screenhack_record_anim_time (time_t *o)
105 {
106   struct timeval tv;
107 # ifdef GETTIMEOFDAY_TWO_ARGS
108   struct timezone tz;
109 # endif
110   screenhack_record_anim_gettimeofday (&tv
111 # ifdef GETTIMEOFDAY_TWO_ARGS
112                                        , &tz
113 # endif
114                                        );
115   if (o) *o = tv.tv_sec;
116   return tv.tv_sec;
117 }
118
119
120 record_anim_state *
121 screenhack_record_anim_init (Screen *screen, Window window, int target_frames)
122 {
123   Display *dpy = DisplayOfScreen (screen);
124   record_anim_state *st;
125
126 # ifndef USE_GL
127   XGCValues gcv;
128 # endif /* !USE_GL */
129
130   if (target_frames <= 0) return 0;
131
132   st = (record_anim_state *) calloc (1, sizeof(*st));
133
134   st->screen = screen;
135   st->window = window;
136   st->target_frames = target_frames;
137   st->frame_count = 0;
138   st->fade_frames = 30 * 1.5;
139
140   if (st->fade_frames >= (st->target_frames / 2) - 30)
141     st->fade_frames = 0;
142
143 # ifdef HAVE_GDK_PIXBUF
144   {
145     Window root;
146     int x, y;
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);
150
151 #  ifdef HAVE_GTK2
152 #   if !GLIB_CHECK_VERSION(2, 36 ,0)
153     g_type_init();
154 #   endif
155 #  else  /* !HAVE_GTK2 */
156     xlib_rgb_init (dpy, screen);
157 #  endif /* !HAVE_GTK2 */
158   }
159 # else  /* !HAVE_GDK_PIXBUF */
160 #  error GDK_PIXBUF is required
161 # endif /* !HAVE_GDK_PIXBUF */
162
163   XGetWindowAttributes (dpy, st->window, &st->xgwa);
164
165 # ifdef USE_GL
166
167   st->data  = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
168   st->data2 = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
169
170 # else    /* !USE_GL */
171
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 */
179
180
181 # ifndef HAVE_JWXYZ
182   XFetchName (dpy, st->window, &st->title);
183 # endif /* !HAVE_JWXYZ */
184
185   return st;
186 }
187
188
189 /* Fade to black. Assumes data is 3-byte packed.
190  */
191 static void
192 fade_frame (record_anim_state *st, unsigned char *data, double ratio)
193 {
194   int x, y, i;
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++)
201         *s++ *= ratio;
202 }
203
204
205 void
206 screenhack_record_anim (record_anim_state *st)
207 {
208   int bytes_per_line = st->xgwa.width * 3;
209   double start_time = double_time();
210
211 # ifndef USE_GL
212
213   Display *dpy = DisplayOfScreen (st->screen);
214   char *data = (char *) st->img->data;
215
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
220    */
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);
225
226   /* Convert BGRA to RGB */
227   {
228     const char *in = st->img->data;
229     char *out = st->img->data;
230     int x, y;
231     int w = st->img->width;
232     int h = st->img->height;
233     for (y = 0; y < h; y++)
234       {
235         const char *in2 = in;
236         for (x = 0; x < w; x++)
237           {
238             *out++ = in2[2];
239             *out++ = in2[1];
240             *out++ = in2[0];
241             in2 += 4;
242           }
243         in += st->img->bytes_per_line;
244       }
245   }
246 # else  /* USE_GL */
247
248   char *data = st->data2;
249   int y;
250
251 # ifdef HAVE_JWZGLES
252 #  undef glReadPixels /* Kludge -- unimplemented in the GLES compat layer */
253 # endif
254
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.
257      Leave it black. */
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);
262
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),
267             bytes_per_line);
268
269 # endif /* USE_GL */
270
271
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) /
278                 st->fade_frames);
279
280 # ifdef HAVE_GDK_PIXBUF
281   {
282     const char *type = "png";
283     char fn[1024];
284     GError *error = 0;
285     GdkPixbuf *pixbuf;
286
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,
291                                        bytes_per_line,
292                                        0, 0);
293
294     sprintf (fn, "%s-%06d.%s", progname, st->frame_count, type);
295     gdk_pixbuf_save (pixbuf, fn, type, &error, NULL);
296
297     if (error)
298       {
299         fprintf (stderr, "%s: %s: %s\n", progname, fn, error->message);
300         exit (1);
301       }
302
303     g_object_unref (pixbuf);
304   }
305 # else  /* !HAVE_GDK_PIXBUF */
306 #  error GDK_PIXBUF is required
307 # endif /* !HAVE_GDK_PIXBUF */
308
309 # ifndef HAVE_JWXYZ
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)
313       {
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);
318         XSync (dpy, 0);
319         free (t2);
320         st->pct = pct;
321       }
322   }
323 # endif /* !HAVE_JWXYZ */
324
325   if (++st->frame_count >= st->target_frames)
326     screenhack_record_anim_free (st);
327
328   recanim_time_warp += double_time() - start_time;
329 }
330
331
332 void
333 screenhack_record_anim_free (record_anim_state *st)
334 {
335 # ifndef USE_GL
336   Display *dpy = DisplayOfScreen (st->screen);
337 # endif /* !USE_GL */
338
339   struct stat s;
340   int i;
341   const char *type = "png";
342   char cmd[1024];
343   char fn[1024];
344   const char *soundtrack = 0;
345
346   fprintf (stderr, "%s: wrote %d frames\n", progname, st->frame_count);
347
348 # ifdef USE_GL
349   free (st->data);
350   free (st->data2);
351 # else  /* !USE_GL */
352   free (st->img->data);
353   st->img->data = 0;
354   XDestroyImage (st->img);
355   XFreeGC (dpy, st->gc);
356   XFreePixmap (dpy, st->p);
357 # endif /* !USE_GL */
358
359   sprintf (fn, "%s.%s", progname, "mp4");
360   unlink (fn);
361
362 # define ST "images/drives-200.mp3"
363   soundtrack = ST;
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;
369
370   sprintf (cmd,
371            "ffmpeg"
372            " -hide_banner"
373            " -v 16"
374            " -framerate 30"     /* rate of input: must be before -i */
375            " -i '%s-%%06d.%s'"
376            " -r 30",            /* rate of output: must be after -i */
377            progname, type);
378   if (soundtrack)
379     sprintf (cmd + strlen(cmd),
380              " -i '%s' -map 0:v:0 -map 1:a:0 -acodec aac",
381              soundtrack);
382   sprintf (cmd + strlen(cmd),
383            " -c:v libx264"
384            " -profile:v high"
385            " -crf 18"
386            " -pix_fmt yuv420p"
387            " '%s'"
388            " </dev/null"
389            /*" 2>&-"*/,
390            fn);
391   fprintf (stderr, "%s: exec: %s\n", progname, cmd);
392   system (cmd);
393
394   if (stat (fn, &s))
395     {
396       fprintf (stderr, "%s: %s was not created\n", progname, fn);
397       exit (1);
398     }
399
400   fprintf (stderr, "%s: wrote %s (%.1f MB)\n", progname, fn,
401            s.st_size / (float) (1024 * 1024));
402
403   for (i = 0; i < st->frame_count; i++)
404     {
405       sprintf (fn, "%s-%06d.%s", progname, i, type);
406       unlink (fn);
407     }
408
409   if (st->title)
410     free (st->title);
411   free (st);
412   exit (0);
413 }