From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / blitspin.c
1 /* xscreensaver, Copyright (c) 1992-2018 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 "ximage-loader.h"
30 #include <stdio.h>
31 #include <time.h>
32
33 #include "images/gen/som_png.h"
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       Pixmap mask = 0;
269       Pixmap pixmap = image_data_to_pixmap (st->dpy, st->window,
270                                             som_png, sizeof(som_png),
271                                             &st->width, &st->height, &mask);
272       XGCValues gcv;
273       GC gc;
274       gcv.foreground = st->bg;
275       gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
276       st->bitmap = XCreatePixmap (st->dpy, st->window, 
277                                   st->xgwa.width, st->xgwa.height,
278                                   st->xgwa.depth);
279       XFillRectangle (st->dpy, st->bitmap, gc, 0, 0, st->width, st->height);
280       XSetClipMask (st->dpy, gc, mask);
281       XCopyArea (st->dpy, pixmap, st->bitmap, gc, 0, 0, st->width, st->height,
282                  0, 0);
283       XFreeGC (st->dpy, gc);
284       XFreePixmap (st->dpy, pixmap);
285       XFreePixmap (st->dpy, mask);
286
287       st->scale_up = True; /* definitely. */
288       st->loaded_p = True;
289       blitspin_init_2 (st);
290     }
291   else if (!strcasecmp (bitmap_name, "(screen)") ||
292            !strcasecmp (bitmap_name, "screen"))
293     {
294       st->bitmap = XCreatePixmap (st->dpy, st->window, 
295                                   st->xgwa.width, st->xgwa.height,
296                                   st->xgwa.depth);
297       st->width = st->xgwa.width;
298       st->height = st->xgwa.height;
299       st->scale_up = True; /* maybe? */
300       st->load_ext_p = True;
301       st->img_loader = load_image_async_simple (0, st->xgwa.screen, st->window,
302                                             st->bitmap, 0, 0);
303     }
304   else
305     {
306       st->bitmap = file_to_pixmap (st->dpy, st->window, bitmap_name,
307                                    &st->width, &st->height, 0);
308       st->scale_up = True; /* probably? */
309       blitspin_init_2 (st);
310     }
311
312   return st;
313 }
314
315
316 static void
317 blitspin_init_2 (struct state *st)
318 {
319   XGCValues gcv;
320
321   /* make it square */
322   st->size = (st->width < st->height) ? st->height : st->width;
323   /* round up to power of 2 */
324   st->size = blitspin_to_pow2(st->size, st->scale_up);
325   {                                             /* don't exceed screen size */
326     int s = XScreenNumberOfScreen(st->xgwa.screen);
327     int w = blitspin_to_pow2(XDisplayWidth(st->dpy, s), False);
328     int h = blitspin_to_pow2(XDisplayHeight(st->dpy, s), False);
329     if (st->size > w) st->size = w;
330     if (st->size > h) st->size = h;
331   }
332
333   if (st->self) XFreePixmap (st->dpy, st->self);
334   if (st->temp) XFreePixmap (st->dpy, st->temp);
335   if (st->mask) XFreePixmap (st->dpy, st->mask);
336
337   st->self = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
338                             st->xgwa.depth);
339   st->temp = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
340                             st->xgwa.depth);
341   st->mask = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
342                             st->xgwa.depth);
343   gcv.foreground = (st->xgwa.depth == 1 ? 1 : (~0));
344
345 # ifdef USE_XCOPYAREA
346 #  define make_gc(op) \
347     gcv.function=GX##op; \
348     if (st->gc_##op) XFreeGC (st->dpy, st->gc_##op); \
349     st->gc_##op = XCreateGC (st->dpy, st->self, GCFunction|GCForeground, &gcv)
350   make_gc(set);
351   make_gc(clear);
352   make_gc(copy);
353   make_gc(and);
354   make_gc(or);
355   make_gc(xor);
356 # endif /* USE_XCOPYAREA */
357
358   gcv.foreground = gcv.background = st->bg;
359   if (st->gc) XFreeGC (st->dpy, st->gc);
360   st->gc = XCreateGC (st->dpy, st->window, GCForeground|GCBackground, &gcv);
361   /* Clear st->self to the background color (not to 0, which 'clear' does.) */
362   XFillRectangle (st->dpy, st->self, st->gc, 0, 0, st->size, st->size);
363   XSetForeground (st->dpy, st->gc, st->fg);
364
365   XCopyArea (st->dpy, st->bitmap, st->self, st->gc, 0, 0, 
366              st->width, st->height,
367              (st->size - st->width)  >> 1,
368              (st->size - st->height) >> 1);
369
370   st->qwad = -1;
371   st->first_time = 1;
372 }
373
374 static void
375 display (struct state *st, Pixmap pixmap)
376 {
377   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
378
379   if (st->xgwa.width != st->last_w || 
380       st->xgwa.height != st->last_h)
381     {
382       XClearWindow (st->dpy, st->window);
383       st->last_w = st->xgwa.width;
384       st->last_h = st->xgwa.height;
385     }
386   if (st->xgwa.depth != 1)
387     XCopyArea (st->dpy, pixmap, st->window, st->gc, 0, 0, st->size, st->size,
388                (st->xgwa.width - st->size) >> 1,
389                (st->xgwa.height - st->size) >> 1);
390   else
391     XCopyPlane (st->dpy, pixmap, st->window, st->gc, 0, 0, st->size, st->size,
392                 (st->xgwa.width - st->size) >> 1,
393                 (st->xgwa.height - st->size) >> 1,
394                 1);
395 /*
396   XDrawRectangle (st->dpy, st->window, st->gc,
397                   ((st->xgwa.width - st->size) >> 1) - 1,
398                   ((st->xgwa.height - st->size) >> 1) - 1,
399                   st->size+2, st->size+2);
400 */
401 }
402
403 static void
404 blitspin_reshape (Display *dpy, Window window, void *closure, 
405                   unsigned int w, unsigned int h)
406 {
407 }
408
409 static Bool
410 blitspin_event (Display *dpy, Window window, void *closure, XEvent *event)
411 {
412   struct state *st = (struct state *) closure;
413   if (screenhack_event_helper (dpy, window, event))
414     {
415       st->start_time = 0;
416       return True;
417     }
418   return False;
419 }
420
421 static void
422 blitspin_free (Display *dpy, Window window, void *closure)
423 {
424 }
425
426 \f
427 static const char *blitspin_defaults [] = {
428   ".background: black",
429   ".foreground: white",
430   ".fpsSolid:   true",
431   "*delay:      500000",
432   "*delay2:     500000",
433   "*duration:   120",
434   "*bitmap:     (default)",
435   "*geometry:   1080x1080",
436 #ifdef HAVE_MOBILE
437   "*ignoreRotation: True",
438 #endif
439   0
440 };
441
442 static XrmOptionDescRec blitspin_options [] = {
443   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
444   { "-delay2",          ".delay2",      XrmoptionSepArg, 0 },
445   { "-duration",        ".duration",    XrmoptionSepArg, 0 },
446   { "-bitmap",          ".bitmap",      XrmoptionSepArg, 0 },
447   { 0, 0, 0, 0 }
448 };
449
450
451 XSCREENSAVER_MODULE ("BlitSpin", blitspin)