summaryrefslogtreecommitdiffstats
path: root/hacks/boxfit.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/boxfit.c')
-rw-r--r--hacks/boxfit.c561
1 files changed, 561 insertions, 0 deletions
diff --git a/hacks/boxfit.c b/hacks/boxfit.c
new file mode 100644
index 0000000..1b1d0d7
--- /dev/null
+++ b/hacks/boxfit.c
@@ -0,0 +1,561 @@
+/* xscreensaver, Copyright (c) 2005-2014 Jamie Zawinski <jwz@jwz.org>
+ *
+ * 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.
+ *
+ * Boxfit -- fills space with a gradient of growing boxes or circles.
+ *
+ * Written by jwz, 21-Feb-2005.
+ *
+ * Inspired by http://www.levitated.net/daily/levBoxFitting.html
+ */
+
+#include "screenhack.h"
+#include <stdio.h>
+#include "ximage-loader.h"
+
+#define ALIVE 1
+#define CHANGED 2
+#define UNDEAD 4
+
+typedef struct {
+ unsigned long fill_color;
+ short x, y, w, h;
+ char flags;
+} box;
+
+typedef struct {
+ Display *dpy;
+ Window window;
+ XWindowAttributes xgwa;
+ GC gc;
+ unsigned long fg_color, bg_color;
+ int border_size;
+ int spacing;
+ int inc;
+
+ int mode;
+ Bool circles_p;
+ Bool growing_p;
+ Bool color_horiz_p;
+
+ int box_count;
+ int boxes_size;
+ int nboxes;
+ box *boxes;
+
+ XImage *image;
+ int ncolors;
+ XColor *colors;
+ int delay;
+ int countdown;
+
+ Bool done_once;
+ async_load_state *img_loader;
+ Pixmap loading_pixmap;
+
+} state;
+
+
+static void
+reset_boxes (state *st)
+{
+ st->nboxes = 0;
+ st->growing_p = True;
+ st->color_horiz_p = random() & 1;
+
+ if (st->done_once && st->colors)
+ free_colors (st->xgwa.screen, st->xgwa.colormap, st->colors, st->ncolors);
+
+ if (!st->done_once)
+ {
+ char *s = get_string_resource (st->dpy, "mode", "Mode");
+ if (!s || !*s || !strcasecmp (s, "random"))
+ st->mode = -1;
+ else if (!strcasecmp (s, "squares") || !strcasecmp (s, "square"))
+ st->mode = 0;
+ else if (!strcasecmp (s, "circles") || !strcasecmp (s, "circle"))
+ st->mode = 1;
+ else
+ {
+ fprintf (stderr,
+ "%s: mode must be random, squares, or circles, not '%s'\n",
+ progname, s);
+ exit (1);
+ }
+ }
+
+ if (st->mode == -1)
+ st->circles_p = random() & 1;
+ else
+ st->circles_p = (st->mode == 1);
+
+ st->done_once = True;
+
+ if (st->image || get_boolean_resource (st->dpy, "grab", "Boolean"))
+ {
+ if (st->image) XDestroyImage (st->image);
+ st->image = 0;
+
+ if (st->loading_pixmap) abort();
+ if (st->img_loader) abort();
+ if (!get_boolean_resource (st->dpy, "peek", "Boolean"))
+ st->loading_pixmap = XCreatePixmap (st->dpy, st->window,
+ st->xgwa.width, st->xgwa.height,
+ st->xgwa.depth);
+
+ XClearWindow (st->dpy, st->window);
+ st->img_loader = load_image_async_simple (0, st->xgwa.screen,
+ st->window,
+ (st->loading_pixmap
+ ? st->loading_pixmap
+ : st->window),
+ 0, 0);
+ }
+ else
+ {
+ st->ncolors = get_integer_resource (st->dpy, "colors", "Colors"); /* re-get */
+ if (st->ncolors < 1) st->ncolors = 1;
+ make_smooth_colormap (st->xgwa.screen, st->xgwa.visual, st->xgwa.colormap,
+ st->colors, &st->ncolors, True, 0, False);
+ if (st->ncolors < 1) abort();
+ XClearWindow (st->dpy, st->window);
+ }
+}
+
+
+static void
+reshape_boxes (state *st)
+{
+ int i;
+ XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
+ for (i = 0; i < st->nboxes; i++)
+ {
+ box *b = &st->boxes[i];
+ b->flags |= CHANGED;
+ }
+}
+
+static void *
+boxfit_init (Display *dpy, Window window)
+{
+ XGCValues gcv;
+ state *st = (state *) calloc (1, sizeof (*st));
+
+ st->dpy = dpy;
+ st->window = window;
+ st->delay = get_integer_resource (dpy, "delay", "Integer");
+
+ XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
+/* XSelectInput (dpy, window, st->xgwa.your_event_mask | ExposureMask);*/
+
+ if (! get_boolean_resource (dpy, "grab", "Boolean"))
+ {
+ st->ncolors = get_integer_resource (dpy, "colors", "Colors");
+ if (st->ncolors < 1) st->ncolors = 1;
+ st->colors = (XColor *) malloc (sizeof(XColor) * st->ncolors);
+ }
+
+ st->inc = get_integer_resource (dpy, "growBy", "GrowBy");
+ st->spacing = get_integer_resource (dpy, "spacing", "Spacing");
+ st->border_size = get_integer_resource (dpy, "borderSize", "BorderSize");
+ st->fg_color = get_pixel_resource (st->dpy, st->xgwa.colormap,
+ "foreground", "Foreground");
+ st->bg_color = get_pixel_resource (st->dpy, st->xgwa.colormap,
+ "background", "Background");
+ if (st->inc < 1) st->inc = 1;
+ if (st->border_size < 0) st->border_size = 0;
+
+ gcv.line_width = st->border_size;
+ gcv.background = st->bg_color;
+ st->gc = XCreateGC (st->dpy, st->window, GCBackground|GCLineWidth, &gcv);
+
+ st->box_count = get_integer_resource (dpy, "boxCount", "BoxCount");
+ if (st->box_count < 1) st->box_count = 1;
+
+ st->nboxes = 0;
+ st->boxes_size = st->box_count * 2;
+ st->boxes = (box *) calloc (st->boxes_size, sizeof(*st->boxes));
+
+ reset_boxes (st);
+
+ reshape_boxes (st);
+ return st;
+}
+
+
+
+static Bool
+boxes_overlap_p (box *a, box *b, int pad)
+{
+ /* Two rectangles overlap if the max of the tops is less than the
+ min of the bottoms and the max of the lefts is less than the min
+ of the rights.
+ */
+# undef MAX
+# undef MIN
+# define MAX(A,B) ((A)>(B)?(A):(B))
+# define MIN(A,B) ((A)<(B)?(A):(B))
+
+ int maxleft = MAX(a->x - pad, b->x);
+ int maxtop = MAX(a->y - pad, b->y);
+ int minright = MIN(a->x + a->w + pad + pad - 1, b->x + b->w);
+ int minbot = MIN(a->y + a->h + pad + pad - 1, b->y + b->h);
+ return (maxtop < minbot && maxleft < minright);
+}
+
+
+static Bool
+circles_overlap_p (box *a, box *b, int pad)
+{
+ int ar = a->w/2; /* radius */
+ int br = b->w/2;
+ int ax = a->x + ar; /* center */
+ int ay = a->y + ar;
+ int bx = b->x + br;
+ int by = b->y + br;
+ int d2 = (((bx - ax) * (bx - ax)) + /* distance between centers squared */
+ ((by - ay) * (by - ay)));
+ int r2 = ((ar + br + pad) * /* sum of radii squared */
+ (ar + br + pad));
+ return (d2 < r2);
+}
+
+
+static Bool
+box_collides_p (state *st, box *a, int pad)
+{
+ int i;
+
+ /* collide with wall */
+ if (a->x - pad < 0 ||
+ a->y - pad < 0 ||
+ a->x + a->w + pad + pad >= st->xgwa.width ||
+ a->y + a->h + pad + pad >= st->xgwa.height)
+ return True;
+
+ /* collide with another box */
+ for (i = 0; i < st->nboxes; i++)
+ {
+ box *b = &st->boxes[i];
+ if (a != b &&
+ (st->circles_p
+ ? circles_overlap_p (a, b, pad)
+ : boxes_overlap_p (a, b, pad)))
+ return True;
+ }
+
+ return False;
+}
+
+
+static unsigned int
+grow_boxes (state *st)
+{
+ int inc2 = st->inc + st->spacing + st->border_size;
+ int i;
+ int live_count = 0;
+
+ /* check box collisions, and grow if none.
+ */
+ for (i = 0; i < st->nboxes; i++)
+ {
+ box *a = &st->boxes[i];
+ if (!(a->flags & ALIVE)) continue;
+
+ if (box_collides_p (st, a, inc2))
+ {
+ a->flags &= ~ALIVE;
+ continue;
+ }
+
+ live_count++;
+ a->x -= st->inc;
+ a->y -= st->inc;
+ a->w += st->inc + st->inc;
+ a->h += st->inc + st->inc;
+ a->flags |= CHANGED;
+ }
+
+ /* Add more boxes.
+ */
+ while (live_count < st->box_count)
+ {
+ box *a;
+ st->nboxes++;
+ if (st->boxes_size <= st->nboxes)
+ {
+ st->boxes_size = (st->boxes_size * 1.2) + st->nboxes;
+ st->boxes = (box *)
+ realloc (st->boxes, st->boxes_size * sizeof(*st->boxes));
+ if (! st->boxes)
+ {
+ fprintf (stderr, "%s: out of memory (%d boxes)\n",
+ progname, st->boxes_size);
+ exit (1);
+ }
+ }
+
+ a = &st->boxes[st->nboxes-1];
+ a->flags = CHANGED;
+
+ for (i = 0; i < 100; i++)
+ {
+ a->x = inc2 + (random() % (st->xgwa.width - inc2));
+ a->y = inc2 + (random() % (st->xgwa.height - inc2));
+ a->w = 0;
+ a->h = 0;
+
+ if (! box_collides_p (st, a, inc2))
+ {
+ a->flags |= ALIVE;
+ live_count++;
+ break;
+ }
+ }
+
+ if (! (a->flags & ALIVE) || /* too many retries; */
+ st->nboxes > 65535) /* that's about 1MB of box structs. */
+ {
+ st->nboxes--; /* go into "fade out" mode now. */
+ st->growing_p = False;
+ return 2000000; /* make customizable... */
+ }
+
+ /* Pick colors for this box */
+ if (st->image)
+ {
+ int w = st->image->width;
+ int h = st->image->height;
+ a->fill_color = XGetPixel (st->image, a->x % w, a->y % h);
+ }
+ else
+ {
+ int n = (st->color_horiz_p
+ ? (a->x * st->ncolors / st->xgwa.width)
+ : (a->y * st->ncolors / st->xgwa.height));
+ a->fill_color = st->colors [n % st->ncolors].pixel;
+ }
+ }
+
+ return st->delay;
+}
+
+
+static unsigned int
+shrink_boxes (state *st)
+{
+ int i;
+ int remaining = 0;
+
+ for (i = 0; i < st->nboxes; i++)
+ {
+ box *a = &st->boxes[i];
+
+ if (a->w <= 0 || a->h <= 0) continue;
+
+ a->x += st->inc;
+ a->y += st->inc;
+ a->w -= st->inc + st->inc;
+ a->h -= st->inc + st->inc;
+ a->flags |= CHANGED;
+ if (a->w < 0) a->w = 0;
+ if (a->h < 0) a->h = 0;
+
+ if (a->w > 0 && a->h > 0)
+ remaining++;
+ }
+
+ if (remaining == 0) {
+ reset_boxes (st);
+ return 1000000;
+ } else {
+ return st->delay;
+ }
+}
+
+
+static void
+draw_boxes (state *st)
+{
+ int i;
+ for (i = 0; i < st->nboxes; i++)
+ {
+ box *b = &st->boxes[i];
+
+ if (b->flags & UNDEAD) continue;
+ if (! (b->flags & CHANGED)) continue;
+ b->flags &= ~CHANGED;
+
+ if (!st->growing_p)
+ {
+ /* When shrinking, black out an area outside of the border
+ before re-drawing the box.
+ */
+ int margin = st->inc + st->border_size;
+
+ XSetForeground (st->dpy, st->gc, st->bg_color);
+ if (st->circles_p)
+ XFillArc (st->dpy, st->window, st->gc,
+ b->x - margin, b->y - margin,
+ b->w + (margin*2), b->h + (margin*2),
+ 0, 360*64);
+ else
+ XFillRectangle (st->dpy, st->window, st->gc,
+ b->x - margin, b->y - margin,
+ b->w + (margin*2), b->h + (margin*2));
+
+ if (b->w <= 0 || b->h <= 0)
+ b->flags |= UNDEAD; /* really very dead now */
+ }
+
+ if (b->w <= 0 || b->h <= 0) continue;
+
+ XSetForeground (st->dpy, st->gc, b->fill_color);
+
+ if (st->circles_p)
+ XFillArc (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h,
+ 0, 360*64);
+ else
+ XFillRectangle (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h);
+
+ if (st->border_size > 0)
+ {
+ unsigned int bd = (st->image
+ ? st->fg_color
+ : st->colors [(b->fill_color + st->ncolors/2)
+ % st->ncolors].pixel);
+ XSetForeground (st->dpy, st->gc, bd);
+ if (st->circles_p)
+ XDrawArc (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h,
+ 0, 360*64);
+ else
+ XDrawRectangle (st->dpy, st->window, st->gc,
+ b->x, b->y, b->w, b->h);
+ }
+ }
+}
+
+
+static unsigned long
+boxfit_draw (Display *dpy, Window window, void *closure)
+{
+ state *st = (state *) closure;
+ int delay;
+
+ if (st->img_loader) /* still loading */
+ {
+ st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
+ if (! st->img_loader) /* just finished */
+ {
+ st->image = XGetImage (st->dpy,
+ (st->loading_pixmap ? st->loading_pixmap :
+ st->window),
+ 0, 0,
+ st->xgwa.width, st->xgwa.height, ~0L,
+ ZPixmap);
+ if (st->loading_pixmap) XFreePixmap (st->dpy, st->loading_pixmap);
+ XSetWindowBackground (st->dpy, st->window, st->bg_color);
+ if (st->loading_pixmap)
+ XClearWindow (st->dpy, st->window);
+ else
+ st->countdown = 2000000;
+ st->loading_pixmap = 0;
+ }
+ return st->delay;
+ }
+
+ if (st->countdown > 0)
+ {
+ st->countdown -= st->delay;
+ if (st->countdown <= 0)
+ {
+ st->countdown = 0;
+ XClearWindow (st->dpy, st->window);
+ }
+ return st->delay;
+ }
+
+ if (st->growing_p) {
+ draw_boxes (st);
+ delay = grow_boxes (st);
+ } else {
+ delay = shrink_boxes (st);
+ draw_boxes (st);
+ }
+ return delay;
+}
+
+static void
+boxfit_reshape (Display *dpy, Window window, void *closure,
+ unsigned int w, unsigned int h)
+{
+ state *st = (state *) closure;
+ reshape_boxes (st);
+}
+
+static Bool
+boxfit_event (Display *dpy, Window window, void *closure, XEvent *event)
+{
+ state *st = (state *) closure;
+ if (screenhack_event_helper (dpy, window, event))
+ {
+ st->growing_p = !st->growing_p;
+ return True;
+ }
+ return False;
+}
+
+static void
+boxfit_free (Display *dpy, Window window, void *closure)
+{
+}
+
+
+static const char *boxfit_defaults [] = {
+ ".background: black",
+ ".foreground: #444444",
+ "*fpsSolid: true",
+ "*delay: 20000",
+ "*mode: random",
+ "*colors: 64",
+ "*boxCount: 50",
+ "*growBy: 1",
+ "*spacing: 1",
+ "*borderSize: 1",
+ "*grab: False",
+ "*peek: False",
+ "*grabDesktopImages: False", /* HAVE_JWXYZ */
+ "*chooseRandomImages: True", /* HAVE_JWXYZ */
+#ifdef HAVE_MOBILE
+ "*ignoreRotation: True",
+ "*rotateImages: True",
+#endif
+ 0
+};
+
+static XrmOptionDescRec boxfit_options [] = {
+ { "-delay", ".delay", XrmoptionSepArg, 0 },
+ { "-colors", ".colors", XrmoptionSepArg, 0 },
+ { "-count", ".boxCount", XrmoptionSepArg, 0 },
+ { "-growby", ".growBy", XrmoptionSepArg, 0 },
+ { "-spacing", ".spacing", XrmoptionSepArg, 0 },
+ { "-border", ".borderSize", XrmoptionSepArg, 0 },
+ { "-mode", ".mode", XrmoptionSepArg, 0 },
+ { "-circles", ".mode", XrmoptionNoArg, "circles" },
+ { "-squares", ".mode", XrmoptionNoArg, "squares" },
+ { "-random", ".mode", XrmoptionNoArg, "random" },
+ { "-grab", ".grab", XrmoptionNoArg, "True" },
+ { "-no-grab", ".grab", XrmoptionNoArg, "False" },
+ { "-peek", ".peek", XrmoptionNoArg, "True" },
+ { "-no-peek", ".peek", XrmoptionNoArg, "False" },
+ { 0, 0, 0, 0 }
+};
+
+
+XSCREENSAVER_MODULE ("BoxFit", boxfit)