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