http://www.jwz.org/xscreensaver/xscreensaver-5.07.tar.gz
[xscreensaver] / hacks / blitspin.c
1 /* xscreensaver, Copyright (c) 1992-2008 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* Rotate a bitmap using using bitblts.
13    The bitmap must be square, and must be a power of 2 in size.
14    This was translated from SmallTalk code which appeared in the
15    August 1981 issue of Byte magazine.
16
17    The input bitmap may be non-square, it is padded and centered
18    with the background color.  Another way would be to subdivide
19    the bitmap into square components and rotate them independently
20    (and preferably in parallel), but I don't think that would be as
21    interesting looking.
22
23    It's too bad almost nothing uses blitter hardware these days,
24    or this might actually win.
25  */
26
27 #include "screenhack.h"
28 #include "xpm-pixmap.h"
29 #include <stdio.h>
30
31 #include "images/som.xbm"
32
33 /* Implementing this using XCopyArea doesn't work with color images on OSX.
34    This means that the Cocoa implementation of XCopyArea in jwxyz.m is 
35    broken with the GXor, GXand, and/or the GXxor GC operations.  This
36    probably means that (e.g.) "kCGBlendModeDarken" is not close enough 
37    to being "GXand" to use for that.  (It works with monochrome images,
38    just not color ones).
39
40    So, on OSX, we implement the blitter by hand.  It is correct, but
41    orders of magnitude slower.
42  */
43 #ifndef HAVE_COCOA
44 # define USE_XCOPYAREA
45 #endif
46
47 struct state {
48   Display *dpy;
49   Window window;
50   XWindowAttributes xgwa;
51   int width, height, size;
52   Bool scale_up;
53   Pixmap self, temp, mask;
54 # ifdef USE_XCOPYAREA
55   GC gc_set, gc_clear, gc_copy, gc_and, gc_or, gc_xor;
56 # endif
57   GC gc;
58   int delay, delay2;
59   int duration;
60   Pixmap bitmap;
61   unsigned int fg, bg;
62
63   int qwad; /* fuckin' C, man... who needs namespaces? */
64   int first_time;
65   int last_w, last_h;
66
67   time_t start_time;
68   Bool loaded_p;
69   Bool load_ext_p;
70   async_load_state *img_loader;
71 };
72
73 static void display (struct state *, Pixmap);
74 static void blitspin_init_2 (struct state *);
75
76 #define copy_to(from, xoff, yoff, to, op)                               \
77   bitblt (st, st->from, st->to, op, 0, 0,                               \
78           st->size-(xoff), st->size-(yoff), (xoff), (yoff))
79
80 #define copy_from(to, xoff, yoff, from, op)                             \
81   bitblt (st, st->from, st->to, op, (xoff), (yoff),                     \
82           st->size-(xoff), st->size-(yoff), 0, 0)
83
84
85 #ifdef USE_XCOPYAREA
86 # define bitblt(st, from, to, op, src_x, src_y, w, h, dst_x, dst_y)     \
87          XCopyArea((st)->dpy, (from), (to), (st)->gc_##op,              \
88                    (src_x), (src_y), (w), (h), (dst_x), (dst_y))
89 #else /* !USE_XCOPYAREA */
90
91 # define bitblt(st, from, to, op, src_x, src_y, w, h, dst_x, dst_y)     \
92          do_bitblt((st)->dpy, (from), (to), st->gc, GX##op,             \
93                    (src_x), (src_y), (w), (h), (dst_x), (dst_y))
94
95 static void
96 do_bitblt (Display *dpy, Drawable src, Drawable dst, GC gc, int op,
97            int src_x, int src_y,
98            unsigned int width, unsigned int height,
99            int dst_x, int dst_y)
100 {
101   if (op == GXclear)
102     {
103       XSetForeground (dpy, gc, 0xFF000000);  /* ARGB black for Cocoa */
104       XFillRectangle (dpy, dst, gc, dst_x, dst_y, width, height);
105     }
106   else if (op == GXset)
107     {
108       XSetForeground (dpy, gc, ~0L);
109       XFillRectangle (dpy, dst, gc, dst_x, dst_y, width, height);
110     }
111   else if (op == GXcopy)
112     {
113       XCopyArea (dpy, src, dst, gc, src_x, src_y, width, height, dst_x, dst_y);
114     }
115   else
116     {
117       XImage *srci = XGetImage (dpy, src, src_x, src_y, width, height,
118                                 ~0L, ZPixmap);
119       XImage *dsti = XGetImage (dpy, dst, dst_x, dst_y, width, height,
120                                 ~0L, ZPixmap);
121       unsigned long *out = (unsigned long *) dsti->data;
122       unsigned long *in  = (unsigned long *) srci->data;
123       unsigned long *end = (in + (height * srci->bytes_per_line
124                                   / sizeof(unsigned long)));
125       switch (op)
126         {
127         case GXor:  while (in < end) { *out++ |= *in++; } break;
128         case GXand: while (in < end) { *out++ &= *in++; } break;
129         case GXxor: while (in < end) { *out++ ^= *in++; } break;
130         default: abort();
131         }
132       XPutImage (dpy, dst, gc, dsti, 0, 0, dst_x, dst_y, width, height);
133       XDestroyImage (srci);
134       XDestroyImage (dsti);
135     }
136 }
137
138 #endif /* !USE_XCOPYAREA */
139
140
141
142 static unsigned long
143 blitspin_draw (Display *dpy, Window window, void *closure)
144 {
145   struct state *st = (struct state *) closure;
146   int this_delay = st->delay;
147   int qwad;
148
149   if (st->img_loader)   /* still loading */
150     {
151       st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
152
153       if (!st->img_loader) { /* just finished */
154         st->first_time = 0;
155         st->loaded_p = True;
156         st->qwad = -1;
157         st->start_time = time ((time_t) 0);
158         blitspin_init_2 (st);
159       }
160
161       /* Rotate nothing if the very first image is not yet loaded */
162       if (! st->loaded_p)
163         return this_delay;
164     }
165
166   if (!st->img_loader &&
167       st->load_ext_p &&
168       st->start_time + st->duration < time ((time_t) 0)) {
169     /* Start a new image loading, but keep rotating the old image 
170        until the new one arrives. */
171     st->img_loader = load_image_async_simple (0, st->xgwa.screen, st->window,
172                                               st->bitmap, 0, 0);
173   }
174
175   if (st->qwad == -1) 
176     {
177       bitblt(st, st->mask, st->mask, clear,0,0, st->size,    st->size,    0,0);
178       bitblt(st, st->mask, st->mask, set,  0,0, st->size>>1, st->size>>1, 0,0);
179       st->qwad = st->size>>1;
180     }
181
182   if (st->first_time)
183     {
184       st->first_time = 0;
185       display (st, st->self);
186       return st->delay2;
187     }
188
189   /* for (st->qwad = st->size>>1; st->qwad > 0; st->qwad>>=1) */
190
191   qwad = st->qwad;
192
193   copy_to   (mask, 0,       0,       temp, copy);   /* 1 */
194   copy_to   (mask, 0,       qwad,    temp, or);     /* 2 */
195   copy_to   (self, 0,       0,       temp, and);    /* 3 */
196   copy_to   (temp, 0,       0,       self, xor);    /* 4 */
197   copy_from (temp, qwad,    0,       self, xor);    /* 5 */
198   copy_from (self, qwad,    0,       self, or);     /* 6 */
199   copy_to   (temp, qwad,    0,       self, xor);    /* 7 */
200   copy_to   (self, 0,       0,       temp, copy);   /* 8 */
201   copy_from (temp, qwad,    qwad,    self, xor);    /* 9 */
202   copy_to   (mask, 0,       0,       temp, and);    /* A */
203   copy_to   (temp, 0,       0,       self, xor);    /* B */
204   copy_to   (temp, qwad,    qwad,    self, xor);    /* C */
205   copy_from (mask, qwad>>1, qwad>>1, mask, and);    /* D */
206   copy_to   (mask, qwad,    0,       mask, or);     /* E */
207   copy_to   (mask, 0,       qwad,    mask, or);     /* F */
208   display   (st, st->self);
209
210   st->qwad >>= 1;
211   if (st->qwad == 0)  /* done with this round */
212     {
213       st->qwad = -1;
214       this_delay = st->delay2;
215     }
216
217   return this_delay;
218 }
219
220
221 static int 
222 to_pow2(struct state *st, int n, Bool up)
223 {
224   int powers_of_2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
225                         2048, 4096, 8192, 16384, 32768, 65536 };
226   int i = 0;
227   if (n > 65536) st->size = 65536;
228   while (n >= powers_of_2[i]) i++;
229   if (n == powers_of_2[i-1])
230     return n;
231   else
232     return powers_of_2[up ? i : i-1];
233 }
234
235 static void *
236 blitspin_init (Display *d_arg, Window w_arg)
237 {
238   struct state *st = (struct state *) calloc (1, sizeof(*st));
239   char *bitmap_name;
240
241   st->dpy = d_arg;
242   st->window = w_arg;
243
244   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
245
246   st->fg = get_pixel_resource (st->dpy, st->xgwa.colormap,
247                                "foreground", "Foreground");
248   st->bg = get_pixel_resource (st->dpy, st->xgwa.colormap,
249                                "background", "Background");
250   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
251   st->delay2 = get_integer_resource (st->dpy, "delay2", "Integer");
252   st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
253   if (st->delay < 0) st->delay = 0;
254   if (st->delay2 < 0) st->delay2 = 0;
255   if (st->duration < 1) st->duration = 1;
256
257   st->start_time = time ((time_t) 0);
258
259   bitmap_name = get_string_resource (st->dpy, "bitmap", "Bitmap");
260   if (! bitmap_name || !*bitmap_name)
261     bitmap_name = "(default)";
262
263   if (!strcasecmp (bitmap_name, "(default)") ||
264       !strcasecmp (bitmap_name, "default"))
265     bitmap_name = "(screen)";
266
267   if (!strcasecmp (bitmap_name, "(builtin)") ||
268       !strcasecmp (bitmap_name, "builtin"))
269     {
270       st->width = som_width;
271       st->height = som_height;
272       st->bitmap = XCreatePixmapFromBitmapData (st->dpy, st->window,
273                                                 (char *) som_bits,
274                                                 st->width, st->height, 
275                                                 st->fg, st->bg, 
276                                                 st->xgwa.depth);
277       st->scale_up = True; /* definitely. */
278       st->loaded_p = True;
279       blitspin_init_2 (st);
280     }
281   else if (!strcasecmp (bitmap_name, "(screen)") ||
282            !strcasecmp (bitmap_name, "screen"))
283     {
284       st->bitmap = XCreatePixmap (st->dpy, st->window, 
285                                   st->xgwa.width, st->xgwa.height,
286                                   st->xgwa.depth);
287       st->width = st->xgwa.width;
288       st->height = st->xgwa.height;
289       st->scale_up = True; /* maybe? */
290       st->load_ext_p = True;
291       st->img_loader = load_image_async_simple (0, st->xgwa.screen, st->window,
292                                             st->bitmap, 0, 0);
293     }
294   else
295     {
296       st->bitmap = xpm_file_to_pixmap (st->dpy, st->window, bitmap_name,
297                                    &st->width, &st->height, 0);
298       st->scale_up = True; /* probably? */
299     }
300
301   return st;
302 }
303
304
305 static void
306 blitspin_init_2 (struct state *st)
307 {
308   XGCValues gcv;
309
310   /* make it square */
311   st->size = (st->width < st->height) ? st->height : st->width;
312   st->size = to_pow2(st, st->size, st->scale_up); /* round up to power of 2 */
313   {                                             /* don't exceed screen size */
314     int s = XScreenNumberOfScreen(st->xgwa.screen);
315     int w = to_pow2(st, XDisplayWidth(st->dpy, s), False);
316     int h = to_pow2(st, XDisplayHeight(st->dpy, s), False);
317     if (st->size > w) st->size = w;
318     if (st->size > h) st->size = h;
319   }
320
321   if (st->self) XFreePixmap (st->dpy, st->self);
322   if (st->temp) XFreePixmap (st->dpy, st->temp);
323   if (st->mask) XFreePixmap (st->dpy, st->mask);
324
325   st->self = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
326                             st->xgwa.depth);
327   st->temp = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
328                             st->xgwa.depth);
329   st->mask = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
330                             st->xgwa.depth);
331   gcv.foreground = (st->xgwa.depth == 1 ? 1 : (~0));
332
333 # ifdef USE_XCOPYAREA
334 #  define make_gc(op) \
335     gcv.function=GX##op; \
336     if (st->gc_##op) XFreeGC (st->dpy, st->gc_##op); \
337     st->gc_##op = XCreateGC (st->dpy, st->self, GCFunction|GCForeground, &gcv)
338   make_gc(set);
339   make_gc(clear);
340   make_gc(copy);
341   make_gc(and);
342   make_gc(or);
343   make_gc(xor);
344 # endif /* USE_XCOPYAREA */
345
346   gcv.foreground = gcv.background = st->bg;
347   if (st->gc) XFreeGC (st->dpy, st->gc);
348   st->gc = XCreateGC (st->dpy, st->window, GCForeground|GCBackground, &gcv);
349   /* Clear st->self to the background color (not to 0, which 'clear' does.) */
350   XFillRectangle (st->dpy, st->self, st->gc, 0, 0, st->size, st->size);
351   XSetForeground (st->dpy, st->gc, st->fg);
352
353   XCopyArea (st->dpy, st->bitmap, st->self, st->gc, 0, 0, 
354              st->width, st->height,
355              (st->size - st->width)  >> 1,
356              (st->size - st->height) >> 1);
357
358   st->qwad = -1;
359   st->first_time = 1;
360 }
361
362 static void
363 display (struct state *st, Pixmap pixmap)
364 {
365   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
366
367   if (st->xgwa.width != st->last_w || 
368       st->xgwa.height != st->last_h)
369     {
370       XClearWindow (st->dpy, st->window);
371       st->last_w = st->xgwa.width;
372       st->last_h = st->xgwa.height;
373     }
374   if (st->xgwa.depth != 1)
375     XCopyArea (st->dpy, pixmap, st->window, st->gc, 0, 0, st->size, st->size,
376                (st->xgwa.width - st->size) >> 1,
377                (st->xgwa.height - st->size) >> 1);
378   else
379     XCopyPlane (st->dpy, pixmap, st->window, st->gc, 0, 0, st->size, st->size,
380                 (st->xgwa.width - st->size) >> 1,
381                 (st->xgwa.height - st->size) >> 1,
382                 1);
383 /*
384   XDrawRectangle (st->dpy, st->window, st->gc,
385                   ((st->xgwa.width - st->size) >> 1) - 1,
386                   ((st->xgwa.height - st->size) >> 1) - 1,
387                   st->size+2, st->size+2);
388 */
389 }
390
391 static void
392 blitspin_reshape (Display *dpy, Window window, void *closure, 
393                   unsigned int w, unsigned int h)
394 {
395 }
396
397 static Bool
398 blitspin_event (Display *dpy, Window window, void *closure, XEvent *event)
399 {
400   return False;
401 }
402
403 static void
404 blitspin_free (Display *dpy, Window window, void *closure)
405 {
406 }
407
408 \f
409 static const char *blitspin_defaults [] = {
410   ".background: black",
411   ".foreground: white",
412   ".fpsSolid:   true",
413   "*delay:      500000",
414   "*delay2:     500000",
415   "*duration:   120",
416   "*bitmap:     (default)",
417   "*geometry:   512x512",
418   0
419 };
420
421 static XrmOptionDescRec blitspin_options [] = {
422   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
423   { "-delay2",          ".delay2",      XrmoptionSepArg, 0 },
424   { "-duration",        ".duration",    XrmoptionSepArg, 0 },
425   { "-bitmap",          ".bitmap",      XrmoptionSepArg, 0 },
426   { 0, 0, 0, 0 }
427 };
428
429
430 XSCREENSAVER_MODULE ("BlitSpin", blitspin)