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