From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / blitspin.c
index 0be3c96d2a5648d325391edb6789e4e797b02b61..af3ea752e76a51ab35cb9863b6f04d673b1d8582 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1992-2008 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1992-2014 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
  */
 
 #include "screenhack.h"
+#include "pow2.h"
 #include "xpm-pixmap.h"
 #include <stdio.h>
+#include <time.h>
 
 #include "images/som.xbm"
 
+/* Implementing this using XCopyArea doesn't work with color images on OSX.
+   This means that the Cocoa implementation of XCopyArea in jwxyz.m is 
+   broken with the GXor, GXand, and/or the GXxor GC operations.  This
+   probably means that (e.g.) "kCGBlendModeDarken" is not close enough 
+   to being "GXand" to use for that.  (It works with monochrome images,
+   just not color ones).
+
+   So, on OSX, we implement the blitter by hand.  It is correct, but
+   orders of magnitude slower.
+ */
+#ifndef HAVE_JWXYZ
+# define USE_XCOPYAREA
+#endif
+
 struct state {
   Display *dpy;
   Window window;
@@ -37,7 +53,9 @@ struct state {
   int width, height, size;
   Bool scale_up;
   Pixmap self, temp, mask;
-  GC SET, CLR, CPY, IOR, AND, XOR;
+# ifdef USE_XCOPYAREA
+  GC gc_set, gc_clear, gc_copy, gc_and, gc_or, gc_xor;
+# endif
   GC gc;
   int delay, delay2;
   int duration;
@@ -50,25 +68,85 @@ struct state {
 
   time_t start_time;
   Bool loaded_p;
+  Bool load_ext_p;
   async_load_state *img_loader;
 };
 
 static void display (struct state *, Pixmap);
 static void blitspin_init_2 (struct state *);
 
-#define copy_all_to(from, xoff, yoff, to, gc)                  \
-  XCopyArea (st->dpy, (from), (to), (gc), 0, 0,                        \
-            st->size-(xoff), st->size-(yoff), (xoff), (yoff))
+#define copy_to(from, xoff, yoff, to, op)                              \
+  bitblt (st, st->from, st->to, op, 0, 0,                              \
+         st->size-(xoff), st->size-(yoff), (xoff), (yoff))
+
+#define copy_from(to, xoff, yoff, from, op)                            \
+  bitblt (st, st->from, st->to, op, (xoff), (yoff),                    \
+         st->size-(xoff), st->size-(yoff), 0, 0)
+
+
+#ifdef USE_XCOPYAREA
+# define bitblt(st, from, to, op, src_x, src_y, w, h, dst_x, dst_y)    \
+         XCopyArea((st)->dpy, (from), (to), (st)->gc_##op,             \
+                  (src_x), (src_y), (w), (h), (dst_x), (dst_y))
+#else /* !USE_XCOPYAREA */
+
+# define bitblt(st, from, to, op, src_x, src_y, w, h, dst_x, dst_y)    \
+         do_bitblt((st)->dpy, (from), (to), st->gc, GX##op,            \
+                  (src_x), (src_y), (w), (h), (dst_x), (dst_y))
+
+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)
+{
+  if (op == GXclear)
+    {
+      XSetForeground (dpy, gc, 0xFF000000);  /* ARGB black for Cocoa */
+      XFillRectangle (dpy, dst, gc, dst_x, dst_y, width, height);
+    }
+  else if (op == GXset)
+    {
+      XSetForeground (dpy, gc, ~0L);
+      XFillRectangle (dpy, dst, gc, dst_x, dst_y, width, height);
+    }
+  else if (op == GXcopy)
+    {
+      XCopyArea (dpy, src, dst, gc, src_x, src_y, width, height, dst_x, dst_y);
+    }
+  else
+    {
+      XImage *srci = XGetImage (dpy, src, src_x, src_y, width, height,
+                                ~0L, ZPixmap);
+      XImage *dsti = XGetImage (dpy, dst, dst_x, dst_y, width, height,
+                                ~0L, ZPixmap);
+      unsigned long *out = (unsigned long *) dsti->data;
+      unsigned long *in  = (unsigned long *) srci->data;
+      unsigned long *end = (in + (height * srci->bytes_per_line
+                                  / sizeof(unsigned long)));
+      switch (op)
+        {
+        case GXor:  while (in < end) { *out++ |= *in++; } break;
+        case GXand: while (in < end) { *out++ &= *in++; } break;
+        case GXxor: while (in < end) { *out++ ^= *in++; } break;
+        default: abort();
+        }
+      XPutImage (dpy, dst, gc, dsti, 0, 0, dst_x, dst_y, width, height);
+      XDestroyImage (srci);
+      XDestroyImage (dsti);
+    }
+}
+
+#endif /* !USE_XCOPYAREA */
+
 
-#define copy_all_from(to, xoff, yoff, from, gc)                        \
-  XCopyArea (st->dpy, (from), (to), (gc), (xoff), (yoff),      \
-            st->size-(xoff), st->size-(yoff), 0, 0)
 
 static unsigned long
 blitspin_draw (Display *dpy, Window window, void *closure)
 {
   struct state *st = (struct state *) closure;
   int this_delay = st->delay;
+  int qwad;
 
   if (st->img_loader)   /* still loading */
     {
@@ -76,30 +154,30 @@ blitspin_draw (Display *dpy, Window window, void *closure)
 
       if (!st->img_loader) { /* just finished */
         st->first_time = 0;
-        st->loaded_p = False;
+        st->loaded_p = True;
         st->qwad = -1;
-        st->start_time = time ((time_t) 0);
+        st->start_time = time ((time_t *) 0);
+        blitspin_init_2 (st);
       }
 
-      return this_delay;
+      /* Rotate nothing if the very first image is not yet loaded */
+      if (! st->loaded_p)
+        return this_delay;
     }
 
   if (!st->img_loader &&
-      st->start_time + st->duration < time ((time_t) 0)) {
-    /* Start it loading, but keep rotating the old image until it arrives. */
+      st->load_ext_p &&
+      st->start_time + st->duration < time ((time_t *) 0)) {
+    /* Start a new image loading, but keep rotating the old image 
+       until the new one arrives. */
     st->img_loader = load_image_async_simple (0, st->xgwa.screen, st->window,
                                               st->bitmap, 0, 0);
   }
 
-  if (! st->loaded_p) {
-    blitspin_init_2 (st);
-    st->loaded_p = True;
-  }
-
   if (st->qwad == -1) 
     {
-      XFillRectangle (st->dpy, st->mask, st->CLR, 0, 0, st->size, st->size);
-      XFillRectangle (st->dpy, st->mask, st->SET, 0, 0, st->size>>1, st->size>>1);
+      bitblt(st, st->mask, st->mask, clear,0,0, st->size,    st->size,    0,0);
+      bitblt(st, st->mask, st->mask, set,  0,0, st->size>>1, st->size>>1, 0,0);
       st->qwad = st->size>>1;
     }
 
@@ -112,22 +190,24 @@ blitspin_draw (Display *dpy, Window window, void *closure)
 
   /* for (st->qwad = st->size>>1; st->qwad > 0; st->qwad>>=1) */
 
-  copy_all_to  (st->mask, 0,        0,        st->temp, st->CPY);  /* 1 */
-  copy_all_to  (st->mask, 0,        st->qwad, st->temp, st->IOR);  /* 2 */
-  copy_all_to  (st->self, 0,        0,        st->temp, st->AND);  /* 3 */
-  copy_all_to  (st->temp, 0,        0,        st->self, st->XOR);  /* 4 */
-  copy_all_from(st->temp, st->qwad, 0,        st->self, st->XOR);  /* 5 */
-  copy_all_from(st->self, st->qwad, 0,        st->self, st->IOR);  /* 6 */
-  copy_all_to  (st->temp, st->qwad, 0,        st->self, st->XOR);  /* 7 */
-  copy_all_to  (st->self, 0,        0,        st->temp, st->CPY);  /* 8 */
-  copy_all_from(st->temp, st->qwad,           st->qwad, st->self,st->XOR);/*9*/
-  copy_all_to  (st->mask, 0,        0,        st->temp, st->AND);  /* A */
-  copy_all_to  (st->temp, 0,        0,        st->self, st->XOR);  /* B */
-  copy_all_to  (st->temp, st->qwad, st->qwad, st->self, st->XOR);  /* C */
-  copy_all_from(st->mask, st->qwad>>1,st->qwad>>1,st->mask,st->AND); /* D */
-  copy_all_to  (st->mask, st->qwad, 0,        st->mask, st->IOR);  /* E */
-  copy_all_to  (st->mask, 0,        st->qwad, st->mask, st->IOR);  /* F */
-  display (st, st->self);
+  qwad = st->qwad;
+
+  copy_to   (mask, 0,       0,       temp, copy);   /* 1 */
+  copy_to   (mask, 0,       qwad,    temp, or);     /* 2 */
+  copy_to   (self, 0,       0,       temp, and);    /* 3 */
+  copy_to   (temp, 0,       0,       self, xor);    /* 4 */
+  copy_from (temp, qwad,    0,       self, xor);    /* 5 */
+  copy_from (self, qwad,    0,       self, or);     /* 6 */
+  copy_to   (temp, qwad,    0,       self, xor);    /* 7 */
+  copy_to   (self, 0,       0,       temp, copy);   /* 8 */
+  copy_from (temp, qwad,    qwad,    self, xor);    /* 9 */
+  copy_to   (mask, 0,       0,       temp, and);    /* A */
+  copy_to   (temp, 0,       0,       self, xor);    /* B */
+  copy_to   (temp, qwad,    qwad,    self, xor);    /* C */
+  copy_from (mask, qwad>>1, qwad>>1, mask, and);    /* D */
+  copy_to   (mask, qwad,    0,       mask, or);     /* E */
+  copy_to   (mask, 0,       qwad,    mask, or);     /* F */
+  display   (st, st->self);
 
   st->qwad >>= 1;
   if (st->qwad == 0)  /* done with this round */
@@ -141,18 +221,13 @@ blitspin_draw (Display *dpy, Window window, void *closure)
 
 
 static int 
-to_pow2(struct state *st, int n, Bool up)
+blitspin_to_pow2(int n, Bool up)
 {
-  /* sizeof(Dimension) == 2. */
-  int powers_of_2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
-                       2048, 4096, 8192, 16384, 32768, 65536 };
-  int i = 0;
-  if (n > 65536) st->size = 65536;
-  while (n >= powers_of_2[i]) i++;
-  if (n == powers_of_2[i-1])
+  int pow2 = to_pow2 (n);
+  if (n == pow2)
     return n;
   else
-    return powers_of_2[up ? i : i-1];
+    return up ? pow2 : pow2 >> 1;
 }
 
 static void *
@@ -177,7 +252,7 @@ blitspin_init (Display *d_arg, Window w_arg)
   if (st->delay2 < 0) st->delay2 = 0;
   if (st->duration < 1) st->duration = 1;
 
-  st->start_time = time ((time_t) 0);
+  st->start_time = time ((time_t *) 0);
 
   bitmap_name = get_string_resource (st->dpy, "bitmap", "Bitmap");
   if (! bitmap_name || !*bitmap_name)
@@ -185,14 +260,7 @@ blitspin_init (Display *d_arg, Window w_arg)
 
   if (!strcasecmp (bitmap_name, "(default)") ||
       !strcasecmp (bitmap_name, "default"))
-# ifdef HAVE_COCOA
-    /* I don't understand why it doesn't work with color images on OSX.
-       I guess "kCGBlendModeDarken" is not close enough to being "GXand".
-     */
-    bitmap_name = "(builtin)";
-# else
     bitmap_name = "(screen)";
-# endif
 
   if (!strcasecmp (bitmap_name, "(builtin)") ||
       !strcasecmp (bitmap_name, "builtin"))
@@ -205,6 +273,8 @@ blitspin_init (Display *d_arg, Window w_arg)
                                                 st->fg, st->bg, 
                                                 st->xgwa.depth);
       st->scale_up = True; /* definitely. */
+      st->loaded_p = True;
+      blitspin_init_2 (st);
     }
   else if (!strcasecmp (bitmap_name, "(screen)") ||
            !strcasecmp (bitmap_name, "screen"))
@@ -215,6 +285,7 @@ blitspin_init (Display *d_arg, Window w_arg)
       st->width = st->xgwa.width;
       st->height = st->xgwa.height;
       st->scale_up = True; /* maybe? */
+      st->load_ext_p = True;
       st->img_loader = load_image_async_simple (0, st->xgwa.screen, st->window,
                                             st->bitmap, 0, 0);
     }
@@ -223,6 +294,7 @@ blitspin_init (Display *d_arg, Window w_arg)
       st->bitmap = xpm_file_to_pixmap (st->dpy, st->window, bitmap_name,
                                    &st->width, &st->height, 0);
       st->scale_up = True; /* probably? */
+      blitspin_init_2 (st);
     }
 
   return st;
@@ -236,49 +308,52 @@ blitspin_init_2 (struct state *st)
 
   /* make it square */
   st->size = (st->width < st->height) ? st->height : st->width;
-  st->size = to_pow2(st, st->size, st->scale_up); /* round up to power of 2 */
+  /* round up to power of 2 */
+  st->size = blitspin_to_pow2(st->size, st->scale_up);
   {                                            /* don't exceed screen size */
     int s = XScreenNumberOfScreen(st->xgwa.screen);
-    int w = to_pow2(st, XDisplayWidth(st->dpy, s), False);
-    int h = to_pow2(st, XDisplayHeight(st->dpy, s), False);
+    int w = blitspin_to_pow2(XDisplayWidth(st->dpy, s), False);
+    int h = blitspin_to_pow2(XDisplayHeight(st->dpy, s), False);
     if (st->size > w) st->size = w;
     if (st->size > h) st->size = h;
   }
 
-  st->self = XCreatePixmap (st->dpy, st->window, st->size, st->size, st->xgwa.depth);
-  st->temp = XCreatePixmap (st->dpy, st->window, st->size, st->size, st->xgwa.depth);
-  st->mask = XCreatePixmap (st->dpy, st->window, st->size, st->size, st->xgwa.depth);
+  if (st->self) XFreePixmap (st->dpy, st->self);
+  if (st->temp) XFreePixmap (st->dpy, st->temp);
+  if (st->mask) XFreePixmap (st->dpy, st->mask);
+
+  st->self = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
+                            st->xgwa.depth);
+  st->temp = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
+                            st->xgwa.depth);
+  st->mask = XCreatePixmap (st->dpy, st->window, st->size, st->size, 
+                            st->xgwa.depth);
   gcv.foreground = (st->xgwa.depth == 1 ? 1 : (~0));
-  gcv.function=GXset;  st->SET = XCreateGC(st->dpy,st->self,GCFunction|GCForeground,&gcv);
-  gcv.function=GXclear;st->CLR = XCreateGC(st->dpy,st->self,GCFunction|GCForeground,&gcv);
-  gcv.function=GXcopy; st->CPY = XCreateGC(st->dpy,st->self,GCFunction|GCForeground,&gcv);
-  gcv.function=GXor;   st->IOR = XCreateGC(st->dpy,st->self,GCFunction|GCForeground,&gcv);
-  gcv.function=GXand;  st->AND = XCreateGC(st->dpy,st->self,GCFunction|GCForeground,&gcv);
-  gcv.function=GXxor;  st->XOR = XCreateGC(st->dpy,st->self,GCFunction|GCForeground,&gcv);
+
+# ifdef USE_XCOPYAREA
+#  define make_gc(op) \
+    gcv.function=GX##op; \
+    if (st->gc_##op) XFreeGC (st->dpy, st->gc_##op); \
+    st->gc_##op = XCreateGC (st->dpy, st->self, GCFunction|GCForeground, &gcv)
+  make_gc(set);
+  make_gc(clear);
+  make_gc(copy);
+  make_gc(and);
+  make_gc(or);
+  make_gc(xor);
+# endif /* USE_XCOPYAREA */
 
   gcv.foreground = gcv.background = st->bg;
+  if (st->gc) XFreeGC (st->dpy, st->gc);
   st->gc = XCreateGC (st->dpy, st->window, GCForeground|GCBackground, &gcv);
-  /* Clear st->self to the background color (not to 0, which st->CLR does.) */
+  /* Clear st->self to the background color (not to 0, which 'clear' does.) */
   XFillRectangle (st->dpy, st->self, st->gc, 0, 0, st->size, st->size);
   XSetForeground (st->dpy, st->gc, st->fg);
 
-#if 0
-#ifdef HAVE_COCOA
-  jwxyz_XSetAntiAliasing (st->dpy, st->gc,  False);
-  jwxyz_XSetAntiAliasing (st->dpy, st->SET, False);
-  jwxyz_XSetAntiAliasing (st->dpy, st->CLR, False);
-  jwxyz_XSetAntiAliasing (st->dpy, st->CPY, False);
-  jwxyz_XSetAntiAliasing (st->dpy, st->IOR, False);
-  jwxyz_XSetAntiAliasing (st->dpy, st->AND, False);
-  jwxyz_XSetAntiAliasing (st->dpy, st->XOR, False);
-#endif /* HAVE_COCOA */
-#endif
-
-  XCopyArea (st->dpy, st->bitmap, st->self, st->CPY, 0, 0, 
+  XCopyArea (st->dpy, st->bitmap, st->self, st->gc, 0, 0, 
              st->width, st->height,
             (st->size - st->width)  >> 1,
              (st->size - st->height) >> 1);
-/*  XFreePixmap(st->dpy, st->bitmap);*/
 
   st->qwad = -1;
   st->first_time = 1;
@@ -322,6 +397,12 @@ blitspin_reshape (Display *dpy, Window window, void *closure,
 static Bool
 blitspin_event (Display *dpy, Window window, void *closure, XEvent *event)
 {
+  struct state *st = (struct state *) closure;
+  if (screenhack_event_helper (dpy, window, event))
+    {
+      st->start_time = 0;
+      return True;
+    }
   return False;
 }
 
@@ -334,11 +415,15 @@ blitspin_free (Display *dpy, Window window, void *closure)
 static const char *blitspin_defaults [] = {
   ".background:        black",
   ".foreground:        white",
+  ".fpsSolid:  true",
   "*delay:     500000",
   "*delay2:    500000",
   "*duration:  120",
   "*bitmap:    (default)",
-  "*geometry:  512x512",
+  "*geometry:  1080x1080",
+#ifdef HAVE_MOBILE
+  "*ignoreRotation: True",
+#endif
   0
 };