X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=hacks%2Ftessellimage.c;h=b9a8bd93ee6f05d1307d7272948e2767e5615b22;hb=c85f503f5793839a6be4c818332aca4a96927bb2;hp=d361eb3fb4f4944d1c0dea114b7d3a6f16ae7cb1;hpb=aa75c7476aeaa84cf3abc192b376a8b03c325213;p=xscreensaver diff --git a/hacks/tessellimage.c b/hacks/tessellimage.c index d361eb3f..b9a8bd93 100644 --- a/hacks/tessellimage.c +++ b/hacks/tessellimage.c @@ -1,4 +1,4 @@ -/* tessellimage, Copyright (c) 2014 Jamie Zawinski +/* tessellimage, Copyright (c) 2014-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 @@ -12,9 +12,6 @@ #include "screenhack.h" #include "delaunay.h" -#undef DO_VORONOI - - #ifndef HAVE_JWXYZ # define XK_MISCELLANY # include @@ -31,7 +28,7 @@ struct state { int delay; Bool outline_p, cache_p, fill_p; double duration, duration2; - int max_depth; + int max_depth, max_resolution; double start_time, start_time2; XImage *img, *delta; @@ -43,8 +40,20 @@ struct state { async_load_state *img_loader; XRectangle geom; Bool button_down_p; + enum { DELAUNAY, VORONOI } mode; }; +typedef struct { + int npoints; + XPoint ctr; + XPoint *p; +} voronoi_polygon; + +typedef struct { + XPoint p; + double slope; +} voronoi_pa; + /* Returns the current time in seconds as a double. */ @@ -83,6 +92,10 @@ tessellimage_init (Display *dpy, Window window) st->max_depth = get_integer_resource (st->dpy, "maxDepth", "MaxDepth"); if (st->max_depth < 100) st->max_depth = 100; + st->max_resolution = get_integer_resource (st->dpy, + "maxResolution", "MaxResolution"); + if (st->max_resolution < 0) st->max_resolution = 0; + st->duration = get_float_resource (st->dpy, "duration", "Seconds"); if (st->duration < 1) st->duration = 1; @@ -116,10 +129,10 @@ decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret) static unsigned long -pixel_distance (Visual *v, unsigned long p1, unsigned long p2) +pixel_distance (Screen *s, Visual *v, unsigned long p1, unsigned long p2) { static int initted_p = 0; - static unsigned int rmsk=0, gmsk=0, bmsk=0; + static unsigned long rmsk=0, gmsk=0, bmsk=0; static unsigned int rpos=0, gpos=0, bpos=0; static unsigned int rsiz=0, gsiz=0, bsiz=0; @@ -130,9 +143,7 @@ pixel_distance (Visual *v, unsigned long p1, unsigned long p2) if (!p1 && !p2) return 0; if (! initted_p) { - rmsk = v->red_mask; - gmsk = v->green_mask; - bmsk = v->blue_mask; + visual_rgb_masks (s, v, &rmsk, &gmsk, &bmsk); decode_mask (rmsk, &rpos, &rsiz); decode_mask (gmsk, &gpos, &gsiz); decode_mask (bmsk, &bpos, &bsiz); @@ -274,6 +285,23 @@ analyze (struct state *st) unsigned int w, h, bw, d; unsigned long histo[256]; + { + char *s = get_string_resource (st->dpy, "mode", "Mode"); + if (!s || !*s || !strcasecmp(s, "random")) + st->mode = (random() & 1) ? DELAUNAY : VORONOI; + else if (!strcasecmp(s, "delaunay")) + st->mode = DELAUNAY; + else if (!strcasecmp(s, "voronoi")) + st->mode = VORONOI; + else + { + fprintf (stderr, + "%s: mode must be delaunay, voronoi or random, not \"%s\"\n", + progname, s); + exit (1); + } + } + flush_cache (st); /* Convert the loaded pixmap to an XImage. @@ -321,7 +349,8 @@ analyze (struct state *st) pixels[i++] = (x > 0 && y < h-1 ? XGetPixel (st->img, x-1, y+1) : 0); for (i = 1; i < countof(pixels); i++) - distance += pixel_distance (st->xgwa.visual, pixels[0], pixels[i]); + distance += pixel_distance (st->xgwa.screen, st->xgwa.visual, + pixels[0], pixels[i]); distance /= countof(pixels)-1; XPutPixel (st->delta, x, y, distance); } @@ -403,7 +432,6 @@ analyze (struct state *st) } -#ifndef DO_VORONOI /* True if the distance between any two corners is too small for it to make sense to draw an outline around this triangle. */ @@ -419,17 +447,47 @@ small_triangle_p (const XPoint *p) if (abs (p[2].y - p[0].y) < min) return True; return False; } -#endif /* DO_VORONOI */ -#ifdef DO_VORONOI +static Bool +small_cell_p (const voronoi_polygon *p) +{ + int min = 4; + if (abs (p->p[0].x - p->ctr.x) < min) return True; + if (abs (p->p[0].y - p->ctr.y) < min) return True; + return False; +} + + +static int +cmp_ccw (const void *v1, const void *v2) +{ + const voronoi_pa *p1,*p2; + p1 = v1; + p2 = v2; + if (p1->slope < p2->slope) return -1; + else if (p1->slope > p2->slope) return 1; + return 0; +} + +static void +sort_ccw (XPoint *ctr, XPoint *p, int npoints) +{ + voronoi_pa *pa = (void *) malloc (npoints * sizeof(*pa)); + int i; + for (i = 0; i < npoints; i++) + { + pa[i].p = p[i]; + pa[i].slope = atan2 (p[i].x - ctr->x, p[i].y - ctr->y); + } + qsort (pa, npoints, sizeof(*pa), cmp_ccw); + for (i = 0; i < npoints; i++) + p[i] = pa[i].p; + free (pa); +} -typedef struct { - int npoints; - XPoint *p; -} voronoi_polygon; static voronoi_polygon * -delaunay_to_voronoi (int np, XYZ *p, int nv, ITRIANGLE *v) +delaunay_to_voronoi (int np, XYZ *p, int nv, ITRIANGLE *v, double scale) { struct tri_list { int count, size; @@ -441,15 +499,6 @@ delaunay_to_voronoi (int np, XYZ *p, int nv, ITRIANGLE *v) calloc (np + 1, sizeof(*vert_to_tri)); voronoi_polygon *out = (voronoi_polygon *) calloc (np + 1, sizeof(*out)); -/* - for (i = 0; i < np; i++) - printf("# p %d = %d %d\n", i, (int)p[i].x, (int)p[i].y); - printf("\n"); - for (i = 0; i < nv; i++) - printf("@ t %d = %d %d %d\n", i, (int)v[i].p1, (int)v[i].p2, (int)v[i].p3); - printf("\n"); -*/ - /* Iterate the triangles to construct a map of vertices to the triangles that contain them. */ @@ -471,59 +520,47 @@ delaunay_to_voronoi (int np, XYZ *p, int nv, ITRIANGLE *v) } } -/* - for (i = 0; i < nv; i++) - { - struct tri_list *t = &vert_to_tri[i]; - printf("p %d [%d %d]:", i, (int)p[i].x, (int)p[i].y); - for (j = 0; j < t->count; j++) { - int tt = t->tri[j]; - printf(" t %d [%d(%d %d) %d(%d %d) %d(%d %d)]", - tt, - (int)v[tt].p1, - (int)p[v[tt].p1].x, (int)p[v[tt].p1].y, - (int)v[tt].p2, - (int)p[v[tt].p2].x, (int)p[v[tt].p2].y, - (int)v[tt].p3, - (int)p[v[tt].p3].x, (int)p[v[tt].p3].y - ); - if (tt < 0 || tt >= nv) abort(); - } - printf("\n"); - } -*/ - /* For every vertex, compose a polygon whose corners are the centers of each triangle using that vertex. Skip any with less than 3 points. + + This is currently omitting the voronoi cells that should touch the edges + of the outer rectangle. Not sure exactly how to include those. */ for (i = 0; i < np; i++) { + long ctr_x = 0, ctr_y = 0; struct tri_list *t = &vert_to_tri[i]; int n = t->count; if (n < 3) n = 0; out[i].npoints = n; + if (n == 0) continue; + out[i].ctr.x = out[i].ctr.y = 0; out[i].p = (n > 0 ? (XPoint *) calloc (out[i].npoints + 1, sizeof (*out[i].p)) : 0); -//printf("%d: ", i); for (j = 0; j < out[i].npoints; j++) { ITRIANGLE *tt = &v[t->tri[j]]; - out[i].p[j].x = (p[tt->p1].x + p[tt->p2].x + p[tt->p3].x) / 3; - out[i].p[j].y = (p[tt->p1].y + p[tt->p2].y + p[tt->p3].y) / 3; -//printf(" [%d: %d %d]", j, out[i].p[j].x, out[i].p[j].y); + out[i].p[j].x = scale * (p[tt->p1].x + p[tt->p2].x + p[tt->p3].x) / 3; + out[i].p[j].y = scale * (p[tt->p1].y + p[tt->p2].y + p[tt->p3].y) / 3; + ctr_x += out[i].p[j].x; + ctr_y += out[i].p[j].y; } -//printf("\n"); + out[i].ctr.x = ctr_x / out[i].npoints; /* long -> short */ + out[i].ctr.y = ctr_y / out[i].npoints; + if (out[i].ctr.x < 0) abort(); + if (out[i].ctr.y < 0) abort(); + sort_ccw (&out[i].ctr, out[i].p, out[i].npoints); } + for (i = 0; i < np+1; i++) + if (vert_to_tri[i].tri) + free (vert_to_tri[i].tri); free (vert_to_tri); + return out; } -#endif /* DO_VORONOI */ - - - static void tessellate (struct state *st) @@ -578,7 +615,7 @@ tessellate (struct state *st) XCopyArea (st->dpy, st->cache[st->thresh], st->output, st->pgc, - 0, 0, st->delta->width, st->delta->height, + 0, 0, st->xgwa.width, st->xgwa.height, 0, 0); } else if (ticked_p) @@ -590,6 +627,7 @@ tessellate (struct state *st) int nv = 0; int ntri = 0; int x, y, i; + double wscale = st->xgwa.width / (double) st->delta->width; #if 0 fprintf(stderr, "%s: thresh %d/%d = %d=%d\n", @@ -658,88 +696,97 @@ tessellate (struct state *st) if (st->output) XFreePixmap (st->dpy, st->output); st->output = XCreatePixmap (st->dpy, st->window, - st->delta->width, st->delta->height, + st->xgwa.width, st->xgwa.height, st->xgwa.depth); XFillRectangle (st->dpy, st->output, st->pgc, - 0, 0, st->delta->width, st->delta->height); - -#ifdef DO_VORONOI + 0, 0, st->xgwa.width, st->xgwa.height); - voronoi_polygon *polys = delaunay_to_voronoi (nv, p, ntri, v); - for (i = 0; i < nv; i++) + switch (st->mode) { + case VORONOI: { - if (polys[i].npoints >= 3) + voronoi_polygon *polys = + delaunay_to_voronoi (nv, p, ntri, v, wscale); + for (i = 0; i < nv; i++) { - unsigned long color = XGetPixel (st->img, p[i].x, p[i].y); - XSetForeground (st->dpy, st->pgc, color); - XFillPolygon (st->dpy, st->output, st->pgc, - polys[i].p, polys[i].npoints, - Convex, CoordModeOrigin); - - if (st->outline_p) + if (polys[i].npoints >= 3) { - XColor bd; - double scale = 0.8; - bd.pixel = color; - XQueryColor (st->dpy, st->xgwa.colormap, &bd); - bd.red *= scale; - bd.green *= scale; - bd.blue *= scale; - - /* bd.red = 0xFFFF; bd.green = 0; bd.blue = 0; */ - - XAllocColor (st->dpy, st->xgwa.colormap, &bd); - XSetForeground (st->dpy, st->pgc, bd.pixel); - XDrawLines (st->dpy, st->output, st->pgc, - polys[i].p, polys[i].npoints, - CoordModeOrigin); - XFreeColors (st->dpy, st->xgwa.colormap, &bd.pixel, 1, 0); + unsigned long color = XGetPixel (st->img, + polys[i].ctr.x / wscale, + polys[i].ctr.y / wscale); + XSetForeground (st->dpy, st->pgc, color); + XFillPolygon (st->dpy, st->output, st->pgc, + polys[i].p, polys[i].npoints, + Convex, CoordModeOrigin); + + if (st->outline_p && !small_cell_p(&polys[i])) + { + XColor bd; + double scale = 0.8; + bd.pixel = color; + XQueryColor (st->dpy, st->xgwa.colormap, &bd); + bd.red *= scale; + bd.green *= scale; + bd.blue *= scale; + + /* bd.red = 0xFFFF; bd.green = 0; bd.blue = 0; */ + + XAllocColor (st->dpy, st->xgwa.colormap, &bd); + XSetForeground (st->dpy, st->pgc, bd.pixel); + XDrawLines (st->dpy, st->output, st->pgc, + polys[i].p, polys[i].npoints, + CoordModeOrigin); + XFreeColors (st->dpy, st->xgwa.colormap, &bd.pixel, + 1, 0); + } } + if (polys[i].p) free (polys[i].p); + polys[i].p = 0; } - if (polys[i].p) free (polys[i].p); - polys[i].p = 0; + free (polys); } - free (polys); - -#else /* !DO_VORONOI */ + break; - for (i = 0; i < ntri; i++) - { - XPoint xp[3]; - unsigned long color; - xp[0].x = p[v[i].p1].x; xp[0].y = p[v[i].p1].y; - xp[1].x = p[v[i].p2].x; xp[1].y = p[v[i].p2].y; - xp[2].x = p[v[i].p3].x; xp[2].y = p[v[i].p3].y; - - /* Set the color of this triangle to the pixel at its midpoint. */ - color = XGetPixel (st->img, - (xp[0].x + xp[1].x + xp[2].x) / 3, - (xp[0].y + xp[1].y + xp[2].y) / 3); - - XSetForeground (st->dpy, st->pgc, color); - XFillPolygon (st->dpy, st->output, st->pgc, xp, countof(xp), - Convex, CoordModeOrigin); - - if (st->outline_p && !small_triangle_p(xp)) - { /* Border the triangle with a color that is darker */ - XColor bd; - double scale = 0.8; - bd.pixel = color; - XQueryColor (st->dpy, st->xgwa.colormap, &bd); - bd.red *= scale; - bd.green *= scale; - bd.blue *= scale; - - /* bd.red = 0xFFFF; bd.green = 0; bd.blue = 0; */ - - XAllocColor (st->dpy, st->xgwa.colormap, &bd); - XSetForeground (st->dpy, st->pgc, bd.pixel); - XDrawLines (st->dpy, st->output, st->pgc, - xp, countof(xp), CoordModeOrigin); - XFreeColors (st->dpy, st->xgwa.colormap, &bd.pixel, 1, 0); - } - } -#endif /* !DO_VORONOI */ + case DELAUNAY: + for (i = 0; i < ntri; i++) + { + XPoint xp[3]; + unsigned long color; + xp[0].x = p[v[i].p1].x * wscale; xp[0].y = p[v[i].p1].y * wscale; + xp[1].x = p[v[i].p2].x * wscale; xp[1].y = p[v[i].p2].y * wscale; + xp[2].x = p[v[i].p3].x * wscale; xp[2].y = p[v[i].p3].y * wscale; + + /* Set the color of this triangle to the pixel at its midpoint. */ + color = XGetPixel (st->img, + (xp[0].x + xp[1].x + xp[2].x) / (3 * wscale), + (xp[0].y + xp[1].y + xp[2].y) / (3 * wscale)); + + XSetForeground (st->dpy, st->pgc, color); + XFillPolygon (st->dpy, st->output, st->pgc, xp, countof(xp), + Convex, CoordModeOrigin); + + if (st->outline_p && !small_triangle_p(xp)) + { /* Border the triangle with a color that is darker */ + XColor bd; + double scale = 0.8; + bd.pixel = color; + XQueryColor (st->dpy, st->xgwa.colormap, &bd); + bd.red *= scale; + bd.green *= scale; + bd.blue *= scale; + + /* bd.red = 0xFFFF; bd.green = 0; bd.blue = 0; */ + + XAllocColor (st->dpy, st->xgwa.colormap, &bd); + XSetForeground (st->dpy, st->pgc, bd.pixel); + XDrawLines (st->dpy, st->output, st->pgc, + xp, countof(xp), CoordModeOrigin); + XFreeColors (st->dpy, st->xgwa.colormap, &bd.pixel, 1, 0); + } + } + break; + default: + abort(); + } free (p); free (v); @@ -748,7 +795,7 @@ tessellate (struct state *st) { st->cache[st->thresh] = XCreatePixmap (st->dpy, st->window, - st->delta->width, st->delta->height, + st->xgwa.width, st->xgwa.height, st->xgwa.depth); if (! st->cache[st->thresh]) { @@ -760,7 +807,7 @@ tessellate (struct state *st) st->output, st->cache[st->thresh], st->pgc, - 0, 0, st->delta->width, st->delta->height, + 0, 0, st->xgwa.width, st->xgwa.height, 0, 0); } } @@ -775,20 +822,19 @@ static Pixmap get_deltap (struct state *st) { int x, y; - int w = st->delta->width; - int h = st->delta->height; + int w = st->xgwa.width; + int h = st->xgwa.height; + double wscale = st->xgwa.width / (double) st->delta->width; XImage *dimg; Visual *v = st->xgwa.visual; - unsigned int rmsk=0, gmsk=0, bmsk=0; + unsigned long rmsk=0, gmsk=0, bmsk=0; unsigned int rpos=0, gpos=0, bpos=0; unsigned int rsiz=0, gsiz=0, bsiz=0; if (st->deltap) return st->deltap; - rmsk = v->red_mask; - gmsk = v->green_mask; - bmsk = v->blue_mask; + visual_rgb_masks (st->xgwa.screen, v, &rmsk, &gmsk, &bmsk); decode_mask (rmsk, &rpos, &rsiz); decode_mask (gmsk, &gpos, &gsiz); decode_mask (bmsk, &bpos, &bsiz); @@ -802,7 +848,7 @@ get_deltap (struct state *st) for (y = 0; y < h; y++) for (x = 0; x < w; x++) { - unsigned long v = XGetPixel (st->delta, x, y) << 5; + unsigned long v = XGetPixel (st->delta, x / wscale, y / wscale) << 5; unsigned long p = (((v << rpos) & rmsk) | ((v << gpos) & gmsk) | ((v << bpos) & bmsk)); @@ -835,11 +881,23 @@ tessellimage_draw (Display *dpy, Window window, void *closure) if (!st->img_loader && st->start_time + st->duration < double_time()) { + int w = st->xgwa.width; + int h = st->xgwa.height; + + /* Analysing a full-resolution image on a Retina display is too slow, + so scale down the source at image-load time. */ + if (st->max_resolution > 10) + { + if (w > h && w > st->max_resolution) + h = st->max_resolution * h / w, w = st->max_resolution; + else if (h > st->max_resolution) + w = st->max_resolution * w / h, h = st->max_resolution; + } + /* fprintf(stderr,"%s: loading %d x %d\n", progname, w, h); */ + XClearWindow (st->dpy, st->window); if (st->image) XFreePixmap (dpy, st->image); - st->image = XCreatePixmap (st->dpy, st->window, - st->xgwa.width, st->xgwa.height, - st->xgwa.depth); + st->image = XCreatePixmap (st->dpy, st->window, w, h, st->xgwa.depth); st->img_loader = load_image_async_simple (0, st->xgwa.screen, st->window, st->image, 0, &st->geom); goto DONE; @@ -854,16 +912,12 @@ tessellimage_draw (Display *dpy, Window window, void *closure) XCopyArea (st->dpy, (st->button_down_p ? get_deltap (st) : st->output), st->window, st->wgc, - 0, 0, st->delta->width, st->delta->height, - (st->xgwa.width - st->delta->width) / 2, - (st->xgwa.height - st->delta->height) / 2); + 0, 0, st->xgwa.width, st->xgwa.height, 0, 0); else if (!st->nthreshes) XCopyArea (st->dpy, st->image, st->window, st->wgc, - 0, 0, st->xgwa.width, st->xgwa.height, - 0, - 0); + 0, 0, st->xgwa.width, st->xgwa.height, 0, 0); DONE: @@ -921,12 +975,15 @@ tessellimage_free (Display *dpy, Window window, void *closure) static const char *tessellimage_defaults [] = { ".background: black", ".foreground: white", + ".lowrez: True", "*dontClearRoot: True", "*fpsSolid: true", + "*mode: random", "*delay: 30000", "*duration: 120", "*duration2: 0.4", "*maxDepth: 30000", + "*maxResolution: 1024", "*outline: True", "*fillScreen: True", "*cache: True", @@ -942,6 +999,8 @@ static XrmOptionDescRec tessellimage_options [] = { { "-duration", ".duration", XrmoptionSepArg, 0 }, { "-duration2", ".duration2", XrmoptionSepArg, 0 }, { "-max-depth", ".maxDepth", XrmoptionSepArg, 0 }, + { "-max-resolution", ".maxResolution", XrmoptionSepArg, 0 }, + { "-mode", ".mode", XrmoptionSepArg, 0 }, { "-outline", ".outline", XrmoptionNoArg, "True" }, { "-no-outline", ".outline", XrmoptionNoArg, "False" }, { "-fill-screen", ".fillScreen", XrmoptionNoArg, "True" },