From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / hacks / glitchpeg.c
1 /* glitchpeg, Copyright (c) 2018 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Insert errors into an image file, then display the corrupted result.
12  *
13  * This only works on X11 and MacOS because iOS and Android don't have
14  * access to the source files of images, only the decoded image data.
15  */
16
17 #include "screenhack.h"
18 #include "ximage-loader.h"
19
20 #ifndef HAVE_JWXYZ
21 # include <X11/Intrinsic.h>   /* for XtInputId, etc */
22 #endif
23
24 #include <sys/stat.h>
25
26 #undef countof
27 #define countof(x) (sizeof((x))/sizeof((*x)))
28
29 struct state {
30   Display *dpy;
31   Window window;
32   GC gc;
33   XWindowAttributes xgwa;
34   int delay;
35   int count;
36   int duration;
37   time_t start_time;
38   unsigned char *image_data; unsigned long image_size;
39   XtInputId pipe_id;
40   FILE *pipe;
41   Bool button_down_p;
42 };
43
44
45 static Bool
46 bigendian (void)
47 {
48   union { int i; char c[sizeof(int)]; } u;
49   u.i = 1;
50   return !u.c[0];
51 }
52
53
54 /* Given a bitmask, returns the position and width of the field.
55    Duplicated from ximage-loader.c.
56  */
57 static void
58 decode_mask (unsigned long mask, unsigned long *pos_ret,
59              unsigned long *size_ret)
60 {
61   int i;
62   for (i = 0; i < 32; i++)
63     if (mask & (1L << i))
64       {
65         int j = 0;
66         *pos_ret = i;
67         for (; i < 32; i++, j++)
68           if (! (mask & (1L << i)))
69             break;
70         *size_ret = j;
71         return;
72       }
73 }
74
75
76 /* Renders a scaled, cropped version of the RGBA XImage onto the window.
77  */
78 static void
79 draw_image (Display *dpy, Window window, Visual *v, GC gc, 
80             int w, int h, int depth, XImage *in)
81 {
82   XImage *out;
83   int x, y, w2, h2, xoff, yoff;
84   double xs, ys, s;
85
86   unsigned long crpos=0, cgpos=0, cbpos=0, capos=0; /* bitfield positions */
87   unsigned long srpos=0, sgpos=0, sbpos=0;
88   unsigned long srmsk=0, sgmsk=0, sbmsk=0;
89   unsigned long srsiz=0, sgsiz=0, sbsiz=0;
90
91 # ifdef HAVE_JWXYZ
92   // BlackPixel has alpha: 0xFF000000.
93   unsigned long black = BlackPixelOfScreen (DefaultScreenOfDisplay (dpy));
94 #else
95   unsigned long black = 0;
96 # endif
97
98   xs = in->width  / (double) w;
99   ys = in->height / (double) h;
100   s = (xs > ys ? ys : xs);
101   w2 = in->width  / s;
102   h2 = in->height / s;
103   xoff = (w - w2) / 2;
104   yoff = (h - h2) / 2;
105
106   /* Create a new image in the depth and bit-order of the server. */
107   out = XCreateImage (dpy, v, depth, ZPixmap, 0, 0, w, h, 8, 0);
108   out->bitmap_bit_order = in->bitmap_bit_order;
109   out->byte_order = in->byte_order;
110
111   out->bitmap_bit_order = BitmapBitOrder (dpy);
112   out->byte_order = ImageByteOrder (dpy);
113
114   out->data = (char *) malloc (out->height * out->bytes_per_line);
115   if (!out->data) abort();
116
117   /* Find the server's color masks.
118      We could cache this and just do it once, but it's a small number
119      of instructions compared to the per-pixel operations happening next.
120    */
121   srmsk = out->red_mask;
122   sgmsk = out->green_mask;
123   sbmsk = out->blue_mask;
124
125   if (!(srmsk && sgmsk && sbmsk)) abort();  /* No server color masks? */
126
127   decode_mask (srmsk, &srpos, &srsiz);
128   decode_mask (sgmsk, &sgpos, &sgsiz);
129   decode_mask (sbmsk, &sbpos, &sbsiz);
130
131   /* 'in' is RGBA in client endianness.  Convert to what the server wants. */
132   if (bigendian())
133     crpos = 24, cgpos = 16, cbpos =  8, capos =  0;
134   else
135     crpos =  0, cgpos =  8, cbpos = 16, capos = 24;
136
137   /* Iterate the destination rectangle and pull in the corresponding
138      scaled and cropped source pixel, or black. Nearest-neighbor is fine.
139    */
140   for (y = 0; y < out->height; y++)
141     {
142       int iy = (out->height - y - yoff - 1) * s;
143       for (x = 0; x < out->width; x++)
144         {
145           int ix = (x - xoff) * s;
146           unsigned long p = (ix >= 0 && ix < in->width &&
147                              iy >= 0 && iy < in->height
148                              ? XGetPixel (in, ix, iy)
149                              : black);
150        /* unsigned char a = (p >> capos) & 0xFF; */
151           unsigned char b = (p >> cbpos) & 0xFF;
152           unsigned char g = (p >> cgpos) & 0xFF;
153           unsigned char r = (p >> crpos) & 0xFF;
154           XPutPixel (out, x, y, ((r << srpos) |
155                                  (g << sgpos) |
156                                  (b << sbpos) |
157                                  black));
158         }
159     }
160
161   XPutImage (dpy, window, gc, out, 0, 0, 0, 0, out->width, out->height);
162   XDestroyImage (out);
163 }
164
165
166 # define BACKSLASH(c) \
167   (! ((c >= 'a' && c <= 'z') || \
168       (c >= 'A' && c <= 'Z') || \
169       (c >= '0' && c <= '9') || \
170       c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
171
172 /* Gets the name of an image file to load by running xscreensaver-getimage-file
173    at the end of a pipe.  This can be very slow!
174    Duplicated from utils/grabclient.c
175  */
176 static FILE *
177 open_image_name_pipe (void)
178 {
179   char *s;
180
181   /* /bin/sh on OS X 10.10 wipes out the PATH. */
182   const char *path = getenv("PATH");
183   char *cmd = s = malloc (strlen(path) * 2 + 100);
184   strcpy (s, "/bin/sh -c 'export PATH=");
185   s += strlen (s);
186   while (*path) {
187     char c = *path++;
188     if (BACKSLASH(c)) *s++ = '\\';
189     *s++ = c;
190   }
191   strcpy (s, "; ");
192   s += strlen (s);
193
194   strcpy (s, "xscreensaver-getimage-file --name --absolute");
195   s += strlen (s);
196
197   strcpy (s, "'");
198   s += strlen (s);
199
200   *s = 0;
201
202   FILE *pipe = popen (cmd, "r");
203   free (cmd);
204   return pipe;
205 }
206
207
208 /* Duplicated from utils/grabclient.c */
209 static void
210 xscreensaver_getimage_file_cb (XtPointer closure, int *source, XtInputId *id)
211 {
212   /* This is not called from a signal handler, so doing stuff here is fine.
213    */
214   struct state *st = (struct state *) closure;
215   char buf[10240];
216   char *file = buf;
217   FILE *fp;
218   struct stat stat;
219   int n;
220   unsigned char *s;
221   int L;
222
223   *buf = 0;
224   fgets (buf, sizeof(buf)-1, st->pipe);
225   pclose (st->pipe);
226   st->pipe = 0;
227   XtRemoveInput (st->pipe_id);
228   st->pipe_id = 0;
229
230   /* strip trailing newline */
231   L = strlen(buf);
232   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
233     buf[--L] = 0;
234
235   fp = fopen (file, "r");
236   if (! fp)
237     {
238       fprintf (stderr, "%s: unable to read %s\n", progname, file);
239       return;
240     }
241
242   if (fstat (fileno (fp), &stat))
243     {
244       fprintf (stderr, "%s: %s: stat failed\n", progname, file);
245       return;
246     }
247
248   if (st->image_data) free (st->image_data);
249   st->image_size = stat.st_size;
250   st->image_data = malloc (st->image_size);
251   
252   s = st->image_data;
253   do {
254     n = fread (s, 1, st->image_data + st->image_size - s, fp);
255     if (n > 0) s += n;
256   } while (n > 0);
257
258   fclose (fp);
259
260   /* fprintf (stderr, "loaded %s (%lu)\n", file, st->image_size); */
261
262   st->start_time = time((time_t *) 0);
263 }
264
265
266 static void *
267 glitchpeg_init (Display *dpy, Window window)
268 {
269   struct state *st = (struct state *) calloc (1, sizeof(*st));
270   XGCValues gcv;
271
272   st->dpy = dpy;
273   st->window = window;
274   st->gc = XCreateGC (dpy, window, 0, &gcv);
275
276   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
277
278   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
279   if (st->delay < 1) st->delay = 1;
280
281   st->duration = get_integer_resource (st->dpy, "duration", "Integer");
282   if (st->duration < 0) st->duration = 0;
283
284   st->count = get_integer_resource (st->dpy, "count", "Integer");
285   if (st->count < 1) st->count = 1;
286
287   XClearWindow (st->dpy, st->window);
288
289   return st;
290 }
291
292
293 static unsigned long
294 glitchpeg_draw (Display *dpy, Window window, void *closure)
295 {
296   struct state *st = (struct state *) closure;
297
298   if ((!st->image_data ||
299        time((time_t *) 0) >= st->start_time + st->duration) &&
300       !st->pipe)
301     {
302       /* Time to reload */
303       st->pipe = open_image_name_pipe();
304       st->pipe_id =
305         XtAppAddInput (XtDisplayToApplicationContext (dpy), 
306                        fileno (st->pipe),
307                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
308                        xscreensaver_getimage_file_cb, (XtPointer) st);
309     }
310
311   if (st->image_data && !st->button_down_p)
312     {
313       int n;
314       XImage *image;
315       unsigned char *glitched = malloc (st->image_size);
316       int nn = random() % st->count;
317       if (nn <= 0) nn = 1;
318
319       memcpy (glitched, st->image_data, st->image_size);
320
321       for (n = 0; n < nn; n++)
322         {
323           int start = 255;
324           int end = st->image_size - 255;
325           int size = end - start;
326           Bool byte_p = True;  /* random() % 100; */
327           if (size <= 100) break;
328           if (byte_p)
329             {
330               int i = start + (random() % size);
331               if (!(random() % 10))
332                 /* Take one random byte and randomize it. */
333                 glitched[i] = random() % 0xFF; 
334               else
335                 /* Take one random byte and add 5% to it. */
336                 glitched[i] += 
337                   (1 + (random() % 0x0C)) * ((random() & 1) ? 1 : -1);
338             }
339           else
340             {
341               /* Take two randomly-sized chunks of the file and swap them.
342                  This tends to just destroy the image.  Doesn't look good. */
343               int s2 = 2 + size * 0.05;
344               char *swap = malloc (s2);
345               int start1 = start + (random() % (size - s2));
346               int start2 = start + (random() % (size - s2));
347               memcpy (glitched + start1, swap, s2);
348               memmove (glitched + start2, glitched + start1, s2);
349               memcpy (swap, glitched + start2, s2);
350               free (swap);
351             }
352         }
353
354       image = image_data_to_ximage (dpy, st->xgwa.visual,
355                                     glitched, st->image_size);
356       free (glitched);
357
358       if (image)  /* Might be null if decode fails */
359         {
360           draw_image (dpy, window, st->xgwa.visual, st->gc,
361                       st->xgwa.width, st->xgwa.height, st->xgwa.depth,
362                       image);
363           XDestroyImage (image);
364         }
365     }
366
367   return st->delay;
368 }
369
370
371 static void
372 glitchpeg_reshape (Display *dpy, Window window, void *closure, 
373                  unsigned int w, unsigned int h)
374 {
375   struct state *st = (struct state *) closure;
376   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
377 }
378
379
380 static Bool
381 glitchpeg_event (Display *dpy, Window window, void *closure, XEvent *event)
382 {
383   struct state *st = (struct state *) closure;
384   if (event->xany.type == ButtonPress)
385     {
386       st->button_down_p = True;
387       return True;
388     }
389   else if (event->xany.type == ButtonRelease)
390     {
391       st->button_down_p = False;
392       return True;
393     }
394   else if (screenhack_event_helper (dpy, window, event))
395     {
396       st->start_time = 0;  /* reload */
397       return True;
398     }
399
400   return False;
401 }
402
403
404 static void
405 glitchpeg_free (Display *dpy, Window window, void *closure)
406 {
407   struct state *st = (struct state *) closure;
408   XFreeGC (dpy, st->gc);
409   if (st->pipe_id) XtRemoveInput (st->pipe_id);
410   if (st->pipe) fclose (st->pipe);
411   if (st->image_data) free (st->image_data);
412   free (st);
413 }
414
415
416 static const char *glitchpeg_defaults [] = {
417   ".background:                 black",
418   ".foreground:                 white",
419   ".lowrez:                     True",
420   "*fpsSolid:                   true",
421   "*delay:                      30000",
422   "*duration:                   120",
423   "*count:                      100",
424   "*grabDesktopImages:          False",   /* HAVE_JWXYZ */
425   "*chooseRandomImages:         True",    /* HAVE_JWXYZ */
426 #ifdef HAVE_MOBILE
427   "*ignoreRotation:             True",
428   "*rotateImages:               True",
429 #endif
430   0
431 };
432
433 static XrmOptionDescRec glitchpeg_options [] = {
434   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
435   { "-duration",        ".duration",            XrmoptionSepArg, 0 },
436   { "-count",           ".count",               XrmoptionSepArg, 0 },
437   { 0, 0, 0, 0 }
438 };
439
440 XSCREENSAVER_MODULE ("GlitchPEG", glitchpeg)