X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fglitchpeg.c;fp=hacks%2Fglitchpeg.c;h=4f3b0520de20a279804468335c1d0c78d0a75ec2;hp=0000000000000000000000000000000000000000;hb=c85f503f5793839a6be4c818332aca4a96927bb2;hpb=78add6e627ee5f10e1fa6f3852602ea5066eee5a diff --git a/hacks/glitchpeg.c b/hacks/glitchpeg.c new file mode 100644 index 00000000..4f3b0520 --- /dev/null +++ b/hacks/glitchpeg.c @@ -0,0 +1,440 @@ +/* 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)