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