http://ftp.x.org/contrib/applications/xscreensaver-2.34.tar.gz
[xscreensaver] / hacks / distort.c
1 /* -*- mode: C; tab-width: 4 -*-
2  * xscreensaver, Copyright (c) 1992, 1993, 1994, 1996, 1997, 1998
3  * Jamie Zawinski <jwz@jwz.org>
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  */
13
14 /* distort
15  * by Jonas Munsin (jmunsin@iki.fi) and Jamie Zawinski <jwz@jwz.org>
16  * TODO:
17  *      -check the allocations in init_round_lense again, maybe make it possible again
18  *       to use swamp without pre-allocating/calculating (although that
19  *       makes it slower) - -swamp is memory hungry
20  *      -more distortion matrices (fortunately, I'm out of ideas :)
21  * Stuff that would be cool but probably too much of a resource hog:
22  *      -some kind of interpolation to avoid jaggies
23  * program idea borrowed from a screensaver on a non-*NIX OS,
24  */
25
26 #include <math.h>
27 #include "screenhack.h"
28 #include <X11/Xutil.h>
29
30 #ifdef HAVE_XSHM_EXTENSION
31 # include "xshm.h"
32 static Bool use_shm;
33 static XShmSegmentInfo shm_info;
34 #endif /* HAVE_XSHM_EXTENSION */
35
36 struct coo {
37         int x;
38         int y;
39         int r, r_change;
40         int xmove, ymove;
41 };
42 static struct coo xy_coo[10];
43
44 static int delay, radius, speed, number, blackhole, vortex, magnify, reflect;
45 static XWindowAttributes xgwa;
46 static GC gc;
47 static Window g_window;
48 static Display *g_dpy;
49 static unsigned long black_pixel;
50
51 static XImage *orig_map, *buffer_map;
52
53 static int ***from;
54 static int ****from_array;
55 static void (*effect) (int) = NULL;
56 static void move_lense(int);
57 static void swamp_thing(int);
58 static void new_rnd_coo(int);
59 static void init_round_lense(void);
60
61 static void init_distort(Display *dpy, Window window) 
62 {
63         XGCValues gcv;
64         long gcflags;
65         int i;
66
67         g_window=window;
68         g_dpy=dpy;
69
70         delay = get_integer_resource ("delay", "Integer");
71         radius = get_integer_resource ("radius", "Integer");
72         speed = get_integer_resource ("speed", "Integer");
73         number = get_integer_resource ("number", "Integer");
74
75 #ifdef HAVE_XSHM_EXTENSION
76         use_shm = get_boolean_resource("useSHM", "Boolean");
77 #endif /* HAVE_XSHM_EXTENSION */
78         
79         blackhole = get_boolean_resource("blackhole", "Boolean");
80         vortex = get_boolean_resource("vortex", "Boolean");
81         magnify = get_boolean_resource("magnify", "Boolean");
82         reflect = get_boolean_resource("reflect", "Boolean");
83         
84         if (get_boolean_resource ("swamp", "Boolean"))
85                 effect = &swamp_thing;
86         if (get_boolean_resource ("bounce", "Boolean") || reflect)
87                 effect = &move_lense;
88
89         if (effect == NULL && radius == 0 && speed == 0 && number == 0
90                 && !blackhole && !vortex && !magnify && !reflect) {
91 /* if no cmdline options are given, randomly choose one of:
92  * -radius 50 -number 4 -speed 1 -bounce
93  * -radius 50 -number 4 -speed 1 -blackhole
94  * -radius 50 -number 4 -speed 1 -vortex
95  * -radius 50 -number 4 -speed 1 -vortex -magnify
96  * -radius 50 -number 4 -speed 1 -vortex -magnify -blackhole
97  * -radius 100 -number 1 -speed 2 -bounce
98  * -radius 100 -number 1 -speed 2 -blackhole
99  * -radius 100 -number 1 -speed 2 -vortex
100  * -radius 100 -number 1 -speed 2 -vortex -magnify
101  * -radius 100 -number 1 -speed 2 -vortex -magnify -blackhole
102  * -radius 50 -number 4 -speed 2 -swamp
103  * -radius 50 -number 4 -speed 2 -swamp -blackhole
104  * -radius 50 -number 4 -speed 2 -swamp -vortex
105  * -radius 50 -number 4 -speed 2 -swamp -vortex -magnify
106  * -radius 50 -number 4 -speed 2 -swamp -vortex -magnify -blackhole
107  */
108                 
109                 i = (random() % 15);
110
111                 switch (i) {
112                         case 0:
113                                 radius=50;number=4;speed=1;
114                                 effect=&move_lense;break;
115                         case 1:
116                                 radius=50;number=4;speed=1;blackhole=1;
117                                 effect=&move_lense;break;
118                         case 2:
119                                 radius=50;number=4;speed=1;vortex=1;
120                                 effect=&move_lense;break;
121                         case 3:
122                                 radius=50;number=4;speed=1;vortex=1;magnify=1;
123                                 effect=&move_lense;break;
124                         case 4:
125                                 radius=50;number=4;speed=1;vortex=1;magnify=1;blackhole=1;
126                                 effect=&move_lense;break;
127                         case 5:
128                                 radius=100;number=1;speed=2;
129                                 effect=&move_lense;break;
130                         case 6:
131                                 radius=100;number=1;speed=2;blackhole=1;
132                                 effect=&move_lense;break;
133                         case 7:
134                                 radius=100;number=1;speed=2;vortex=1;
135                                 effect=&move_lense;break;
136                         case 8:
137                                 radius=100;number=1;speed=2;vortex=1;magnify=1;
138                                 effect=&move_lense;break;
139                         case 9:
140                                 radius=100;number=1;speed=2;vortex=1;magnify=1;blackhole=1;
141                                 effect=&move_lense;break;
142                         case 10:
143                                 radius=50;number=4;speed=2;
144                                 effect=&swamp_thing;break;
145                         case 11:
146                                 radius=50;number=4;speed=2;blackhole=1;
147                                 effect=&swamp_thing;break;
148                         case 12:
149                                 radius=50;number=4;speed=2;vortex=1;
150                                 effect=&swamp_thing;break;
151                         case 13:
152                                 radius=50;number=4;speed=2;vortex=1;magnify=1;
153                                 effect=&swamp_thing;break;
154                         case 14: default:
155                                 radius=50;number=4;speed=2;vortex=1;magnify=1;blackhole=1;
156                                 effect=&swamp_thing;break;
157                 }
158
159         }
160
161         if (delay < 0)
162                 delay = 0;
163         if (radius <= 0)
164                 radius = 60;
165         if (speed <= 0) 
166                 speed = 2;
167         if (number <= 0)
168                 number=1;
169         if (number >= 10)
170                 number=1;
171         if (effect == NULL)
172                 effect = &move_lense;
173
174         XGetWindowAttributes (dpy, window, &xgwa);
175         black_pixel = BlackPixelOfScreen( xgwa.screen );
176
177         gcv.function = GXcopy;
178         gcv.subwindow_mode = IncludeInferiors;
179         gcflags = GCForeground |GCFunction;
180         if (use_subwindow_mode_p(xgwa.screen, window)) /* see grabscreen.c */
181                 gcflags |= GCSubwindowMode;
182         gc = XCreateGC (dpy, window, gcflags, &gcv);
183
184         grab_screen_image (xgwa.screen, window);
185
186         buffer_map = 0;
187         orig_map = XGetImage(dpy, window, 0, 0, xgwa.width, xgwa.height,
188                                                  ~0L, ZPixmap);
189
190 # ifdef HAVE_XSHM_EXTENSION
191
192         if (use_shm)
193           {
194                 buffer_map = create_xshm_image(dpy, xgwa.visual, orig_map->depth,
195                                                                            ZPixmap, 0, &shm_info,
196                                                                            2*radius + speed + 2,
197                                                                            2*radius + speed + 2);
198                 if (!buffer_map)
199                   use_shm = False;
200           }
201 # endif /* HAVE_XSHM_EXTENSION */
202
203         if (!buffer_map)
204           {
205                 buffer_map = XCreateImage(dpy, xgwa.visual,
206                                                                   orig_map->depth, ZPixmap, 0, 0,
207                                                                   2*radius + speed + 2, 2*radius + speed + 2,
208                                                                   8, 0);
209                 buffer_map->data = (char *)
210                   calloc(buffer_map->height, buffer_map->bytes_per_line);
211         }
212
213         init_round_lense();
214
215         for (i = 0; i < number; i++) {
216                 new_rnd_coo(i);
217                 if (number != 1)
218                         xy_coo[i].r = (i*radius)/(number-1); /* "randomize" initial */
219                 else
220                          xy_coo[i].r = 0;
221                 xy_coo[i].r_change = speed + (i%2)*2*(-speed);  /* values a bit */
222                 xy_coo[i].xmove = speed + (i%2)*2*(-speed);
223                 xy_coo[i].ymove = speed + (i%2)*2*(-speed);
224         }
225 }
226
227 /* example: initializes a "see-trough" matrix */
228 /* static void make_null_lense(void)
229 {
230         int i, j;
231         for (i = 0; i < 2*radius+speed+2; i++) {
232                 for (j = 0 ; j < 2*radius+speed+2 ; j++) {
233                         from[i][j][0]=i;
234                         from[i][j][1]=j;
235                 }
236         } 
237 }
238 */
239
240 /* makes a lense with the Radius=loop and centred in
241  * the point (radius, radius)
242  */
243 static void make_round_lense(int radius, int loop)
244 {
245         int i, j;
246         double theta;
247
248         for (i = 0; i < 2*radius+speed+2; i++) {
249                 for(j = 0; j < 2*radius+speed+2; j++) {
250                         double r, d;
251                         r = sqrt ((i-radius)*(i-radius)+(j-radius)*(j-radius));
252                         if (loop == 0)
253                           d=0.0;
254                         else
255                           d=r/loop;
256
257                         if (r < loop-1) {
258
259                                 if (vortex) { /* vortex-twist effect */
260                                         double angle;
261                 /* this one-line formula for getting a nice rotation angle is borrowed
262                  * (with permission) from the whirl plugin for gimp,
263                  * Copyright (C) 1996 Federico Mena Quintero
264                  */
265                 /* 2.5 is just a constant used because it looks good :) */
266                                         angle = 2.5*(1-d)*(1-d);
267
268
269         /* Avoid atan2: DOMAIN error message */
270         if ((radius-j) == 0.0 && (radius-i) == 0.0)
271             theta = 0.0;
272         else
273             theta = atan2(radius-j, radius-i);
274         from[i][j][0] = radius +
275                         cos(angle - theta)*r;
276         from[i][j][1] = radius +
277                         sin(angle - theta)*r;
278
279                                         if (magnify) {
280                                                 r = sin(d*M_PI_2);
281                                                 if (blackhole && r != 0) /* blackhole effect */
282                                                         r = 1/r;
283                                                 from[i][j][0] = radius + (from[i][j][0]-radius)*r;
284                                                 from[i][j][1] = radius + (from[i][j][1]-radius)*r;
285                                         }
286                                 } else { /* default is to magnify */
287                                         r = sin(d*M_PI_2);
288                                 
289         /* raising r to different power here gives different amounts of
290          * distortion, a negative value sucks everything into a black hole
291          */
292                                 /*      r = r*r; */
293                                         if (blackhole) /* blackhole effect */
294                                                 r = 1/r;
295                                                                         /* bubble effect (and blackhole) */
296                                         from[i][j][0] = radius + (i-radius)*r;
297                                         from[i][j][1] = radius + (j-radius)*r;
298                                 }
299                         } else { /* not inside loop */
300                                 from[i][j][0] = i;
301                                 from[i][j][1] = j;
302                         }
303                 }
304         }
305 }
306
307 #ifndef EXIT_FAILURE
308 # define EXIT_FAILURE -1
309 #endif
310
311 static void allocate_lense(void)
312 {
313         int i, j;
314         /* maybe this should be redone so that from[][][] is in one block;
315          * then pointers could be used instead of arrays in some places (and
316          * maybe give a speedup - maybe also consume less memory)
317          */
318
319         from = (int ***)malloc((2*radius+speed+2) * sizeof(int **));
320         if (from == NULL) {
321                 perror("distort");
322                 exit(EXIT_FAILURE);
323         }
324         for (i = 0; i < 2*radius+speed+2; i++) {
325                 from[i] = (int **)malloc((2*radius+speed+2) * sizeof(int *));
326                 if (from[i] == NULL) {
327                         perror("distort");
328                         exit(EXIT_FAILURE);
329                 }
330                 for (j = 0; j < 2*radius+speed+2; j++) {
331                         from[i][j] = (int *)malloc(2 * sizeof(int));
332                         if (from[i][j] == NULL) {
333                                 perror("distort");
334                                 exit(EXIT_FAILURE);
335                         }
336                 }
337         }
338 }
339
340 /* from_array in an array containing precalculated from matrices,
341  * this is a double faced mem vs speed trade, it's faster, but eats
342  * _a lot_ of mem for large radius (is there a bug here? I can't see it)
343  */
344 static void init_round_lense(void)
345 {
346         int k;
347
348         if (effect == &swamp_thing) {
349                 from_array = (int ****)malloc((radius+1)*sizeof(int ***));
350                 for (k=0; k <= radius; k++) {
351                         allocate_lense();
352                         make_round_lense(radius, k);
353                         from_array[k] = from;
354                 }
355         } else { /* just allocate one from[][][] */
356                 allocate_lense();
357                 make_round_lense(radius,radius);
358         }
359 }
360
361
362 /* generate an XImage of from[][][] and draw it on the screen */
363 void draw(int k)
364 {
365         int i, j;
366         int     cx, cy;
367         int     ly, lysq, lx, ny, dist, rsq = radius * radius;
368         if (reflect) {
369                 cx = cy = radius;
370                 if (xy_coo[k].ymove > 0)
371                         cy += speed;
372                 if (xy_coo[k].xmove > 0)
373                         cx += speed;
374         }
375         for(i = 0 ; i < 2*radius+speed+2; i++) {
376                 if (reflect) {
377                         ly = i - cy;
378                         lysq = ly * ly;
379                         ny = xy_coo[k].y + i;
380                 }
381                 for(j = 0 ; j < 2*radius+speed+2 ; j++) {
382                         if (reflect) {
383                                 lx = j - cx;
384                                 dist = lx * lx + lysq;
385                                 if (dist > rsq ||
386                                         ly < -radius || ly > radius ||
387                                         lx < -radius || lx > radius)
388                                         XPutPixel( buffer_map, j, i,
389                                                            XGetPixel( orig_map, xy_coo[k].x + j, ny ));
390                                 else if (dist == 0)
391                                         XPutPixel( buffer_map, j, i, black_pixel );
392                                 else {
393                                         int     x = xy_coo[k].x + cx + (lx * rsq / dist);
394                                         int     y = xy_coo[k].y + cy + (ly * rsq / dist);
395                                         if (x < 0 || x > xgwa.width ||
396                                                 y < 0 || y > xgwa.height)
397                                                 XPutPixel( buffer_map, j, i, black_pixel );
398                                         else
399                                                 XPutPixel( buffer_map, j, i,
400                                                                    XGetPixel( orig_map, x, y ));
401                                 }
402                         } else if (xy_coo[k].x+from[i][j][0] >= 0 &&
403                                            xy_coo[k].x+from[i][j][0] < xgwa.width &&
404                                            xy_coo[k].y+from[i][j][1] >= 0 &&
405                                            xy_coo[k].y+from[i][j][1] < xgwa.height) {
406                                 XPutPixel(buffer_map, i, j,
407                                                   XGetPixel(orig_map,
408                                                                         xy_coo[k].x+from[i][j][0],
409                                                                         xy_coo[k].y+from[i][j][1]));
410                         }
411                 }
412         }
413
414         XPutImage(g_dpy, g_window, gc, buffer_map, 0, 0, xy_coo[k].x, xy_coo[k].y,
415                         2*radius+speed+2, 2*radius+speed+2);
416 }
417
418 /* create a new, random coordinate, that won't interfer with any other
419  * coordinates, as the drawing routines would be significantly slowed
420  * down if they were to handle serveral layers of distortions
421  */
422 static void new_rnd_coo(int k)
423 {
424         int i;
425
426         xy_coo[k].x = (random() % (xgwa.width-2*radius));
427         xy_coo[k].y = (random() % (xgwa.height-2*radius));
428         
429         for (i = 0; i < number; i++) {
430                 if (i != k) {
431                         if ((abs(xy_coo[k].x - xy_coo[i].x) <= 2*radius+speed+2)
432                          && (abs(xy_coo[k].y - xy_coo[i].y) <= 2*radius+speed+2)) {
433                                 xy_coo[k].x = (random() % (xgwa.width-2*radius));
434                                 xy_coo[k].y = (random() % (xgwa.height-2*radius));
435                                 i=-1; /* ugly */
436                         } 
437                 }
438         }
439 }
440
441 /* move lens and handle bounces with walls and other lenses */
442 static void move_lense(int k)
443 {
444         int i;
445
446         if (xy_coo[k].x + 2*radius + speed + 2 >= xgwa.width)
447                 xy_coo[k].xmove = -abs(xy_coo[k].xmove);
448         if (xy_coo[k].x <= speed) 
449                 xy_coo[k].xmove = abs(xy_coo[k].xmove);
450         if (xy_coo[k].y + 2*radius + speed + 2 >= xgwa.height)
451                 xy_coo[k].ymove = -abs(xy_coo[k].ymove);
452         if (xy_coo[k].y <= speed)
453                 xy_coo[k].ymove = abs(xy_coo[k].ymove);
454
455         xy_coo[k].x = xy_coo[k].x + xy_coo[k].xmove;
456         xy_coo[k].y = xy_coo[k].y + xy_coo[k].ymove;
457
458         for (i = 0; i < number; i++) {
459                 if ((i != k)
460                 
461 /* This commented test is for rectangular lenses (not presently used) and
462  * the one used is for circular ones
463                 && (abs(xy_coo[k].x - xy_coo[i].x) <= 2*radius)
464                 && (abs(xy_coo[k].y - xy_coo[i].y) <= 2*radius)) { */
465
466                 && ((xy_coo[k].x - xy_coo[i].x)*(xy_coo[k].x - xy_coo[i].x)
467                   + (xy_coo[k].y - xy_coo[i].y)*(xy_coo[k].y - xy_coo[i].y)
468                         <= 2*radius*2*radius)) {
469
470                         int x, y;
471                         x = xy_coo[k].xmove;
472                         y = xy_coo[k].ymove;
473                         xy_coo[k].xmove = xy_coo[i].xmove;
474                         xy_coo[k].ymove = xy_coo[i].ymove;
475                         xy_coo[i].xmove = x;
476                         xy_coo[i].ymove = y;
477                 }
478         }
479
480 }
481
482 /* make xy_coo[k] grow/shrink */
483 void swamp_thing(int k)
484 {
485         if (xy_coo[k].r >= radius)
486                 xy_coo[k].r_change = -abs(xy_coo[k].r_change);
487         
488         if (xy_coo[k].r <= 0) {
489                 from = from_array[0];
490                 draw(k); 
491                 xy_coo[k].r_change = abs(xy_coo[k].r_change);
492                 new_rnd_coo(k);
493                 xy_coo[k].r=xy_coo[k].r_change;
494                 return;
495         }
496
497         xy_coo[k].r = xy_coo[k].r + xy_coo[k].r_change;
498
499         if (xy_coo[k].r >= radius)
500                 xy_coo[k].r = radius;
501         if (xy_coo[k].r <= 0)
502                 xy_coo[k].r=0;
503
504         from = from_array[xy_coo[k].r];
505 }
506
507
508 \f
509
510 char *progclass = "Distort";
511
512 char *defaults [] = {
513         "*dontClearRoot:                True",
514 #ifdef __sgi    /* really, HAVE_READ_DISPLAY_EXTENSION */
515         "*visualID:                     Best",
516 #endif
517
518         "*delay:                        10000",
519         "*radius:                       0",
520         "*speed:                        0",
521         "*number:                       0",
522         "*vortex:                       False",
523         "*magnify:                      False",
524         "*swamp:                        False",
525         "*bounce:                       False",
526         "*reflect:                      False",
527         "*blackhole:            False",
528 #ifdef HAVE_XSHM_EXTENSION
529         "*useSHM:                       False",         /* xshm turns out not to help. */
530 #endif /* HAVE_XSHM_EXTENSION */
531         0
532 };
533
534 XrmOptionDescRec options [] = {
535         { "-delay",     ".delay",       XrmoptionSepArg, 0 },
536         { "-radius",    ".radius",      XrmoptionSepArg, 0 },
537         { "-speed",     ".speed",       XrmoptionSepArg, 0 },
538         { "-number",    ".number",      XrmoptionSepArg, 0 },
539         { "-swamp",     ".swamp",       XrmoptionNoArg, "True" },
540         { "-bounce",    ".bounce",      XrmoptionNoArg, "True" },
541         { "-reflect",   ".reflect",     XrmoptionNoArg, "True" },
542         { "-vortex",    ".vortex",      XrmoptionNoArg, "True" },
543         { "-magnify",   ".magnify",     XrmoptionNoArg, "True" },
544         { "-blackhole", ".blackhole",   XrmoptionNoArg, "True" },
545 #ifdef HAVE_XSHM_EXTENSION
546         { "-shm",               ".useSHM",      XrmoptionNoArg, "True" },
547         { "-no-shm",    ".useSHM",      XrmoptionNoArg, "False" },
548 #endif /* HAVE_XSHM_EXTENSION */
549         { 0, 0, 0, 0 }
550 };
551
552
553 void screenhack(Display *dpy, Window window)
554 {
555         int k;
556
557         init_distort (dpy, window);
558         while (1) {
559                 for (k = 0; k < number; k++) {
560                         effect(k);
561                         draw(k);
562                 }
563
564                 XSync(dpy, True);
565                 if (delay) usleep(delay);
566         }
567
568 }