From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / hacks / blitspin.c
1 /* xscreensaver, Copyright (c) 1992-2012 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       blitspin_init_2 (st);
300     }
301
302   return st;
303 }
304
305
306 static void
307 blitspin_init_2 (struct state *st)
308 {
309   XGCValues gcv;
310
311   /* make it square */
312   st->size = (st->width < st->height) ? st->height : st->width;
313   st->size = to_pow2(st, st->size, st->scale_up); /* round up to power of 2 */
314   {                                             /* don't exceed screen size */
315     int s = XScreenNumberOfScreen(st->xgwa.screen);
316     int w = to_pow2(st, XDisplayWidth(st->dpy, s), False);
317     int h = to_pow2(st, XDisplayHeight(st->dpy, s), False);
318     if (st->size > w) st->size = w;
319     if (st->size > h) st->size = h;
320   }
321
322   if (st->self) XFreePixmap (st->dpy, st->self);
323   if (st->temp) XFreePixmap (st->dpy, st->temp);
324   if (st->mask) XFreePixmap (st->dpy, st->mask);
325
326   st->self = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
327                             st->xgwa.depth);
328   st->temp = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
329                             st->xgwa.depth);
330   st->mask = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
331                             st->xgwa.depth);
332   gcv.foreground = (st->xgwa.depth == 1 ? 1 : (~0));
333
334 # ifdef USE_XCOPYAREA
335 #  define make_gc(op) \
336     gcv.function=GX##op; \
337     if (st->gc_##op) XFreeGC (st->dpy, st->gc_##op); \
338     st->gc_##op = XCreateGC (st->dpy, st->self, GCFunction|GCForeground, &gcv)
339   make_gc(set);
340   make_gc(clear);
341   make_gc(copy);
342   make_gc(and);
343   make_gc(or);
344   make_gc(xor);
345 # endif /* USE_XCOPYAREA */
346
347   gcv.foreground = gcv.background = st->bg;
348   if (st->gc) XFreeGC (st->dpy, st->gc);
349   st->gc = XCreateGC (st->dpy, st->window, GCForeground|GCBackground, &gcv);
350   /* Clear st->self to the background color (not to 0, which 'clear' does.) */
351   XFillRectangle (st->dpy, st->self, st->gc, 0, 0, st->size, st->size);
352   XSetForeground (st->dpy, st->gc, st->fg);
353
354   XCopyArea (st->dpy, st->bitmap, st->self, st->gc, 0, 0, 
355              st->width, st->height,
356              (st->size - st->width)  >> 1,
357              (st->size - st->height) >> 1);
358
359   st->qwad = -1;
360   st->first_time = 1;
361 }
362
363 static void
364 display (struct state *st, Pixmap pixmap)
365 {
366   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
367
368   if (st->xgwa.width != st->last_w || 
369       st->xgwa.height != st->last_h)
370     {
371       XClearWindow (st->dpy, st->window);
372       st->last_w = st->xgwa.width;
373       st->last_h = st->xgwa.height;
374     }
375   if (st->xgwa.depth != 1)
376     XCopyArea (st->dpy, pixmap, st->window, st->gc, 0, 0, st->size, st->size,
377                (st->xgwa.width - st->size) >> 1,
378                (st->xgwa.height - st->size) >> 1);
379   else
380     XCopyPlane (st->dpy, pixmap, st->window, st->gc, 0, 0, st->size, st->size,
381                 (st->xgwa.width - st->size) >> 1,
382                 (st->xgwa.height - st->size) >> 1,
383                 1);
384 /*
385   XDrawRectangle (st->dpy, st->window, st->gc,
386                   ((st->xgwa.width - st->size) >> 1) - 1,
387                   ((st->xgwa.height - st->size) >> 1) - 1,
388                   st->size+2, st->size+2);
389 */
390 }
391
392 static void
393 blitspin_reshape (Display *dpy, Window window, void *closure, 
394                   unsigned int w, unsigned int h)
395 {
396 }
397
398 static Bool
399 blitspin_event (Display *dpy, Window window, void *closure, XEvent *event)
400 {
401   return False;
402 }
403
404 static void
405 blitspin_free (Display *dpy, Window window, void *closure)
406 {
407 }
408
409 \f
410 static const char *blitspin_defaults [] = {
411   ".background: black",
412   ".foreground: white",
413   ".fpsSolid:   true",
414   "*delay:      500000",
415   "*delay2:     500000",
416   "*duration:   120",
417   "*bitmap:     (default)",
418   "*geometry:   512x512",
419 #ifdef USE_IPHONE
420   "*ignoreRotation: True",
421 #endif
422   0
423 };
424
425 static XrmOptionDescRec blitspin_options [] = {
426   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
427   { "-delay2",          ".delay2",      XrmoptionSepArg, 0 },
428   { "-duration",        ".duration",    XrmoptionSepArg, 0 },
429   { "-bitmap",          ".bitmap",      XrmoptionSepArg, 0 },
430   { 0, 0, 0, 0 }
431 };
432
433
434 XSCREENSAVER_MODULE ("BlitSpin", blitspin)