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