/* glitchpeg, Copyright (c) 2018 Jamie Zawinski * * 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. * * Insert errors into an image file, then display the corrupted result. * * This only works on X11 and MacOS because iOS and Android don't have * access to the source files of images, only the decoded image data. */ #include "screenhack.h" #include "ximage-loader.h" #ifndef HAVE_JWXYZ # include /* for XtInputId, etc */ #endif #include #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) struct state { Display *dpy; Window window; GC gc; XWindowAttributes xgwa; int delay; int count; int duration; time_t start_time; unsigned char *image_data; unsigned long image_size; XtInputId pipe_id; FILE *pipe; Bool button_down_p; }; static Bool bigendian (void) { union { int i; char c[sizeof(int)]; } u; u.i = 1; return !u.c[0]; } /* Given a bitmask, returns the position and width of the field. Duplicated from ximage-loader.c. */ static void decode_mask (unsigned long mask, unsigned long *pos_ret, unsigned long *size_ret) { int i; for (i = 0; i < 32; i++) if (mask & (1L << i)) { int j = 0; *pos_ret = i; for (; i < 32; i++, j++) if (! (mask & (1L << i))) break; *size_ret = j; return; } } /* Renders a scaled, cropped version of the RGBA XImage onto the window. */ static void draw_image (Display *dpy, Window window, Visual *v, GC gc, int w, int h, int depth, XImage *in) { XImage *out; int x, y, w2, h2, xoff, yoff; double xs, ys, s; unsigned long crpos=0, cgpos=0, cbpos=0, capos=0; /* bitfield positions */ unsigned long srpos=0, sgpos=0, sbpos=0; unsigned long srmsk=0, sgmsk=0, sbmsk=0; unsigned long srsiz=0, sgsiz=0, sbsiz=0; # ifdef HAVE_JWXYZ // BlackPixel has alpha: 0xFF000000. unsigned long black = BlackPixelOfScreen (DefaultScreenOfDisplay (dpy)); #else unsigned long black = 0; # endif xs = in->width / (double) w; ys = in->height / (double) h; s = (xs > ys ? ys : xs); w2 = in->width / s; h2 = in->height / s; xoff = (w - w2) / 2; yoff = (h - h2) / 2; /* Create a new image in the depth and bit-order of the server. */ out = XCreateImage (dpy, v, depth, ZPixmap, 0, 0, w, h, 8, 0); out->bitmap_bit_order = in->bitmap_bit_order; out->byte_order = in->byte_order; out->bitmap_bit_order = BitmapBitOrder (dpy); out->byte_order = ImageByteOrder (dpy); out->data = (char *) malloc (out->height * out->bytes_per_line); if (!out->data) abort(); /* Find the server's color masks. We could cache this and just do it once, but it's a small number of instructions compared to the per-pixel operations happening next. */ srmsk = out->red_mask; sgmsk = out->green_mask; sbmsk = out->blue_mask; if (!(srmsk && sgmsk && sbmsk)) abort(); /* No server color masks? */ decode_mask (srmsk, &srpos, &srsiz); decode_mask (sgmsk, &sgpos, &sgsiz); decode_mask (sbmsk, &sbpos, &sbsiz); /* 'in' is RGBA in client endianness. Convert to what the server wants. */ if (bigendian()) crpos = 24, cgpos = 16, cbpos = 8, capos = 0; else crpos = 0, cgpos = 8, cbpos = 16, capos = 24; /* Iterate the destination rectangle and pull in the corresponding scaled and cropped source pixel, or black. Nearest-neighbor is fine. */ for (y = 0; y < out->height; y++) { int iy = (out->height - y - yoff - 1) * s; for (x = 0; x < out->width; x++) { int ix = (x - xoff) * s; unsigned long p = (ix >= 0 && ix < in->width && iy >= 0 && iy < in->height ? XGetPixel (in, ix, iy) : black); /* unsigned char a = (p >> capos) & 0xFF; */ unsigned char b = (p >> cbpos) & 0xFF; unsigned char g = (p >> cgpos) & 0xFF; unsigned char r = (p >> crpos) & 0xFF; XPutPixel (out, x, y, ((r << srpos) | (g << sgpos) | (b << sbpos) | black)); } } XPutImage (dpy, window, gc, out, 0, 0, 0, 0, out->width, out->height); XDestroyImage (out); } # define BACKSLASH(c) \ (! ((c >= 'a' && c <= 'z') || \ (c >= 'A' && c <= 'Z') || \ (c >= '0' && c <= '9') || \ c == '.' || c == '_' || c == '-' || c == '+' || c == '/')) /* Gets the name of an image file to load by running xscreensaver-getimage-file at the end of a pipe. This can be very slow! Duplicated from utils/grabclient.c */ static FILE * open_image_name_pipe (void) { char *s; /* /bin/sh on OS X 10.10 wipes out the PATH. */ const char *path = getenv("PATH"); char *cmd = s = malloc (strlen(path) * 2 + 100); strcpy (s, "/bin/sh -c 'export PATH="); s += strlen (s); while (*path) { char c = *path++; if (BACKSLASH(c)) *s++ = '\\'; *s++ = c; } strcpy (s, "; "); s += strlen (s); strcpy (s, "xscreensaver-getimage-file --name --absolute"); s += strlen (s); strcpy (s, "'"); s += strlen (s); *s = 0; FILE *pipe = popen (cmd, "r"); free (cmd); return pipe; } /* Duplicated from utils/grabclient.c */ static void xscreensaver_getimage_file_cb (XtPointer closure, int *source, XtInputId *id) { /* This is not called from a signal handler, so doing stuff here is fine. */ struct state *st = (struct state *) closure; char buf[10240]; char *file = buf; FILE *fp; struct stat stat; int n; unsigned char *s; int L; *buf = 0; fgets (buf, sizeof(buf)-1, st->pipe); pclose (st->pipe); st->pipe = 0; XtRemoveInput (st->pipe_id); st->pipe_id = 0; /* strip trailing newline */ L = strlen(buf); while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n')) buf[--L] = 0; fp = fopen (file, "r"); if (! fp) { fprintf (stderr, "%s: unable to read %s\n", progname, file); return; } if (fstat (fileno (fp), &stat)) { fprintf (stderr, "%s: %s: stat failed\n", progname, file); return; } if (st->image_data) free (st->image_data); st->image_size = stat.st_size; st->image_data = malloc (st->image_size); s = st->image_data; do { n = fread (s, 1, st->image_data + st->image_size - s, fp); if (n > 0) s += n; } while (n > 0); fclose (fp); /* fprintf (stderr, "loaded %s (%lu)\n", file, st->image_size); */ st->start_time = time((time_t *) 0); } static void * glitchpeg_init (Display *dpy, Window window) { struct state *st = (struct state *) calloc (1, sizeof(*st)); XGCValues gcv; st->dpy = dpy; st->window = window; st->gc = XCreateGC (dpy, window, 0, &gcv); XGetWindowAttributes (st->dpy, st->window, &st->xgwa); st->delay = get_integer_resource (st->dpy, "delay", "Integer"); if (st->delay < 1) st->delay = 1; st->duration = get_integer_resource (st->dpy, "duration", "Integer"); if (st->duration < 0) st->duration = 0; st->count = get_integer_resource (st->dpy, "count", "Integer"); if (st->count < 1) st->count = 1; XClearWindow (st->dpy, st->window); return st; } static unsigned long glitchpeg_draw (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; if ((!st->image_data || time((time_t *) 0) >= st->start_time + st->duration) && !st->pipe) { /* Time to reload */ st->pipe = open_image_name_pipe(); st->pipe_id = XtAppAddInput (XtDisplayToApplicationContext (dpy), fileno (st->pipe), (XtPointer) (XtInputReadMask | XtInputExceptMask), xscreensaver_getimage_file_cb, (XtPointer) st); } if (st->image_data && !st->button_down_p) { int n; XImage *image; unsigned char *glitched = malloc (st->image_size); int nn = random() % st->count; if (nn <= 0) nn = 1; memcpy (glitched, st->image_data, st->image_size); for (n = 0; n < nn; n++) { int start = 255; int end = st->image_size - 255; int size = end - start; Bool byte_p = True; /* random() % 100; */ if (size <= 100) break; if (byte_p) { int i = start + (random() % size); if (!(random() % 10)) /* Take one random byte and randomize it. */ glitched[i] = random() % 0xFF; else /* Take one random byte and add 5% to it. */ glitched[i] += (1 + (random() % 0x0C)) * ((random() & 1) ? 1 : -1); } else { /* Take two randomly-sized chunks of the file and swap them. This tends to just destroy the image. Doesn't look good. */ int s2 = 2 + size * 0.05; char *swap = malloc (s2); int start1 = start + (random() % (size - s2)); int start2 = start + (random() % (size - s2)); memcpy (glitched + start1, swap, s2); memmove (glitched + start2, glitched + start1, s2); memcpy (swap, glitched + start2, s2); free (swap); } } image = image_data_to_ximage (dpy, st->xgwa.visual, glitched, st->image_size); free (glitched); if (image) /* Might be null if decode fails */ { draw_image (dpy, window, st->xgwa.visual, st->gc, st->xgwa.width, st->xgwa.height, st->xgwa.depth, image); XDestroyImage (image); } } return st->delay; } static void glitchpeg_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { struct state *st = (struct state *) closure; XGetWindowAttributes (st->dpy, st->window, &st->xgwa); } static Bool glitchpeg_event (Display *dpy, Window window, void *closure, XEvent *event) { struct state *st = (struct state *) closure; if (event->xany.type == ButtonPress) { st->button_down_p = True; return True; } else if (event->xany.type == ButtonRelease) { st->button_down_p = False; return True; } else if (screenhack_event_helper (dpy, window, event)) { st->start_time = 0; /* reload */ return True; } return False; } static void glitchpeg_free (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; XFreeGC (dpy, st->gc); if (st->pipe_id) XtRemoveInput (st->pipe_id); if (st->pipe) fclose (st->pipe); if (st->image_data) free (st->image_data); free (st); } static const char *glitchpeg_defaults [] = { ".background: black", ".foreground: white", ".lowrez: True", "*fpsSolid: true", "*delay: 30000", "*duration: 120", "*count: 100", "*grabDesktopImages: False", /* HAVE_JWXYZ */ "*chooseRandomImages: True", /* HAVE_JWXYZ */ #ifdef HAVE_MOBILE "*ignoreRotation: True", "*rotateImages: True", #endif 0 }; static XrmOptionDescRec glitchpeg_options [] = { { "-delay", ".delay", XrmoptionSepArg, 0 }, { "-duration", ".duration", XrmoptionSepArg, 0 }, { "-count", ".count", XrmoptionSepArg, 0 }, { 0, 0, 0, 0 } }; XSCREENSAVER_MODULE ("GlitchPEG", glitchpeg)