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