/* xscreensaver, Copyright (c) 1992-1997 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
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 */

/* Rotate a bitmap using using bitblts.
   The bitmap must be square, and must be a power of 2 in size.
   This was translated from SmallTalk code which appeared in the
   August 1981 issue of Byte magazine.

   The input bitmap may be non-square, it is padded and centered
   with the background color.  Another way would be to subdivide
   the bitmap into square components and rotate them independently
   (and preferably in parallel), but I don't think that would be as
   interesting looking.

   It's too bad almost nothing uses blitter hardware these days,
   or this might actually win.
 */

#include "screenhack.h"
#include <stdio.h>

#ifdef HAVE_XPM
# include <X11/xpm.h>
# ifndef PIXEL_ALREADY_TYPEDEFED
#  define PIXEL_ALREADY_TYPEDEFED /* Sigh, Xmu/Drawing.h needs this... */
# endif
#endif

#ifdef HAVE_XMU
# ifndef VMS
#  include <X11/Xmu/Drawing.h>
#else  /* VMS */
#  include <Xmu/Drawing.h>
# endif /* VMS */
#endif

#include "images/som.xbm"

static Display *dpy;
static Window window;
static unsigned int size;
static Pixmap self, temp, mask;
static GC SET, CLR, CPY, IOR, AND, XOR;
static GC gc;
static int delay, delay2;
static Pixmap bitmap;
static int depth;
static unsigned int fg, bg;

static void display (Pixmap);

#define copy_all_to(from, xoff, yoff, to, gc)		\
  XCopyArea (dpy, (from), (to), (gc), 0, 0,		\
	     size-(xoff), size-(yoff), (xoff), (yoff))

#define copy_all_from(to, xoff, yoff, from, gc)		\
  XCopyArea (dpy, (from), (to), (gc), (xoff), (yoff),	\
	     size-(xoff), size-(yoff), 0, 0)

static void
rotate (void)
{
  int qwad; /* fuckin' C, man... who needs namespaces? */
  XFillRectangle (dpy, mask, CLR, 0, 0, size, size);
  XFillRectangle (dpy, mask, SET, 0, 0, size>>1, size>>1);
  for (qwad = size>>1; qwad > 0; qwad>>=1)
    {
      if (delay) usleep (delay);
      copy_all_to   (mask,       0,       0, temp, CPY);  /* 1 */
      copy_all_to   (mask,       0,    qwad, temp, IOR);  /* 2 */
      copy_all_to   (self,       0,       0, temp, AND);  /* 3 */
      copy_all_to   (temp,       0,       0, self, XOR);  /* 4 */
      copy_all_from (temp,    qwad,       0, self, XOR);  /* 5 */
      copy_all_from (self,    qwad,       0, self, IOR);  /* 6 */
      copy_all_to   (temp,    qwad,       0, self, XOR);  /* 7 */
      copy_all_to   (self,       0,       0, temp, CPY);  /* 8 */
      copy_all_from (temp,    qwad,    qwad, self, XOR);  /* 9 */
      copy_all_to   (mask,       0,       0, temp, AND);  /* A */
      copy_all_to   (temp,       0,       0, self, XOR);  /* B */
      copy_all_to   (temp,    qwad,    qwad, self, XOR);  /* C */
      copy_all_from (mask, qwad>>1, qwad>>1, mask, AND);  /* D */
      copy_all_to   (mask,    qwad,       0, mask, IOR);  /* E */
      copy_all_to   (mask,       0,    qwad, mask, IOR);  /* F */
      display (self);
    }
}

static void
read_bitmap (char *bitmap_name, int *widthP, int *heightP)
{
#ifdef HAVE_XPM
  XWindowAttributes xgwa;
  XpmAttributes xpmattrs;
  int result;
  xpmattrs.valuemask = 0;
  bitmap = 0;

  XGetWindowAttributes (dpy, window, &xgwa);

# ifdef XpmCloseness
  xpmattrs.valuemask |= XpmCloseness;
  xpmattrs.closeness = 40000;
# endif
# ifdef XpmVisual
  xpmattrs.valuemask |= XpmVisual;
  xpmattrs.visual = xgwa.visual;
# endif
# ifdef XpmDepth
  xpmattrs.valuemask |= XpmDepth;
  xpmattrs.depth = xgwa.depth;
# endif
# ifdef XpmColormap
  xpmattrs.valuemask |= XpmColormap;
  xpmattrs.colormap = xgwa.colormap;
# endif

  result = XpmReadFileToPixmap (dpy, window, bitmap_name, &bitmap, 0,
				&xpmattrs);
  switch (result)
    {
    case XpmColorError:
      fprintf (stderr, "%s: warning: xpm color substitution performed\n",
	       progname);
      /* fall through */
    case XpmSuccess:
      *widthP = xpmattrs.width;
      *heightP = xpmattrs.height;
      break;
    case XpmFileInvalid:
    case XpmOpenFailed:
      bitmap = 0;
      break;
    case XpmColorFailed:
      fprintf (stderr, "%s: xpm: color allocation failed\n", progname);
      exit (-1);
    case XpmNoMemory:
      fprintf (stderr, "%s: xpm: out of memory\n", progname);
      exit (-1);
    default:
      fprintf (stderr, "%s: xpm: unknown error code %d\n", progname, result);
      exit (-1);
    }
  if (! bitmap)
#endif

#ifdef HAVE_XMU
    {
      int xh, yh;
      Pixmap b2;
      bitmap = XmuLocateBitmapFile (DefaultScreenOfDisplay (dpy), bitmap_name,
				    0, 0, widthP, heightP, &xh, &yh);
      if (! bitmap)
	{
	  fprintf (stderr, "%s: couldn't find bitmap %s\n", progname,
		   bitmap_name);
	  exit (1);
	}
      b2 = XmuCreatePixmapFromBitmap (dpy, window, bitmap, *widthP, *heightP,
				      depth, fg, bg);
      XFreePixmap (dpy, bitmap);
      bitmap = b2;
    }
#else  /* !XMU */
    {
      fprintf (stderr,
	       "%s: your vendor doesn't ship the standard Xmu library.\n",
	       progname);
      fprintf (stderr, "\tWe can't load XBM files without it.\n");
      exit (1);
    }
#endif /* !XMU */
}


static Pixmap
read_screen (Display *dpy, Window window, int *widthP, int *heightP)
{
  Pixmap p;
  XWindowAttributes xgwa;
  XGCValues gcv;
  GC gc;
  XGetWindowAttributes (dpy, window, &xgwa);
  *widthP = xgwa.width;
  *heightP = xgwa.height;

  grab_screen_image(xgwa.screen, window);
  p = XCreatePixmap(dpy, window, *widthP, *heightP, xgwa.depth);
  gcv.function = GXcopy;
  gc = XCreateGC (dpy, window, GCFunction, &gcv);
  XCopyArea (dpy, window, p, gc, 0, 0, *widthP, *heightP, 0, 0);

  /* Reset the window's background color... */
  XSetWindowBackground (dpy, window,
			get_pixel_resource ("background", "Background",
					    dpy, xgwa.colormap));
  XCopyArea (dpy, p, window, gc, 0, 0, *widthP, *heightP, 0, 0);
  XFreeGC (dpy, gc);

  return p;
}


static int 
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) size = 65536;
  while (n >= powers_of_2[i]) i++;
  if (n == powers_of_2[i-1])
    return n;
  else
    return powers_of_2[up ? i : i-1];
}

static void
init (void)
{
  XWindowAttributes xgwa;
  Colormap cmap;
  XGCValues gcv;
  int width, height;
  char *bitmap_name;
  Bool scale_up;

  XGetWindowAttributes (dpy, window, &xgwa);
  cmap = xgwa.colormap;
  depth = xgwa.depth;

  fg = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
  bg = get_pixel_resource ("background", "Background", dpy, cmap);
  delay = get_integer_resource ("delay", "Integer");
  delay2 = get_integer_resource ("delay2", "Integer");
  if (delay < 0) delay = 0;
  if (delay2 < 0) delay2 = 0;
  bitmap_name = get_string_resource ("bitmap", "Bitmap");
  if (! bitmap_name || !*bitmap_name)
    bitmap_name = "(default)";

  if (!strcmp (bitmap_name, "(default)"))
    {
      width = som_width;
      height = som_height;
      bitmap = XCreatePixmapFromBitmapData (dpy, window, (char *) som_bits,
					    width, height, fg, bg, depth);
      scale_up = True; /* definitely. */
    }
  else if (!strcmp (bitmap_name, "(screen)"))
    {
      bitmap = read_screen (dpy, window, &width, &height);
      scale_up = True; /* maybe? */
    }
  else
    {
      read_bitmap (bitmap_name, &width, &height);
      scale_up = True; /* probably? */
    }

  size = (width < height) ? height : width;	/* make it square */
  size = to_pow2(size, scale_up);		/* round up to power of 2 */
  {						/* don't exceed screen size */
    int s = XScreenNumberOfScreen(xgwa.screen);
    int w = to_pow2(XDisplayWidth(dpy, s), False);
    int h = to_pow2(XDisplayHeight(dpy, s), False);
    if (size > w) size = w;
    if (size > h) size = h;
  }

  self = XCreatePixmap (dpy, window, size, size, depth);
  temp = XCreatePixmap (dpy, window, size, size, depth);
  mask = XCreatePixmap (dpy, window, size, size, depth);
  gcv.foreground = (depth == 1 ? 1 : (~0));
  gcv.function=GXset;  SET = XCreateGC(dpy,self,GCFunction|GCForeground,&gcv);
  gcv.function=GXclear;CLR = XCreateGC(dpy,self,GCFunction|GCForeground,&gcv);
  gcv.function=GXcopy; CPY = XCreateGC(dpy,self,GCFunction|GCForeground,&gcv);
  gcv.function=GXor;   IOR = XCreateGC(dpy,self,GCFunction|GCForeground,&gcv);
  gcv.function=GXand;  AND = XCreateGC(dpy,self,GCFunction|GCForeground,&gcv);
  gcv.function=GXxor;  XOR = XCreateGC(dpy,self,GCFunction|GCForeground,&gcv);

  gcv.foreground = gcv.background = bg;
  gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
  /* Clear self to the background color (not to 0, which CLR does.) */
  XFillRectangle (dpy, self, gc, 0, 0, size, size);
  XSetForeground (dpy, gc, fg);

  XCopyArea (dpy, bitmap, self, CPY, 0, 0, width, height,
	     (size - width)>>1, (size - height)>>1);
  XFreePixmap(dpy, bitmap);

  display (self);
  XSync(dpy, False);
}

static void
display (Pixmap pixmap)
{
  XWindowAttributes xgwa;
  static int last_w = 0, last_h = 0;
  XGetWindowAttributes (dpy, window, &xgwa);
  if (xgwa.width != last_w || xgwa.height != last_h)
    {
      XClearWindow (dpy, window);
      last_w = xgwa.width;
      last_h = xgwa.height;
    }
#ifdef HAVE_XPM
  if (depth != 1)
    XCopyArea (dpy, pixmap, window, gc, 0, 0, size, size,
	       (xgwa.width-size)>>1, (xgwa.height-size)>>1);
  else
#endif
    XCopyPlane (dpy, pixmap, window, gc, 0, 0, size, size,
		(xgwa.width-size)>>1, (xgwa.height-size)>>1, 1);
/*
  XDrawRectangle (dpy, window, gc,
		  ((xgwa.width-size)>>1)-1, ((xgwa.height-size)>>1)-1,
		  size+2, size+2);
*/
  XSync (dpy, True);
}


char *progclass = "BlitSpin";

char *defaults [] = {
  ".background:	black",
  ".foreground:	white",
  "*delay:	500000",
  "*delay2:	500000",
  "*bitmap:	(default)",
  "*geometry:	512x512",
  0
};

XrmOptionDescRec options [] = {
  { "-delay",		".delay",	XrmoptionSepArg, 0 },
  { "-delay2",		".delay2",	XrmoptionSepArg, 0 },
  { "-bitmap",		".bitmap",	XrmoptionSepArg, 0 },
  { "-grab-screen",	".bitmap",	XrmoptionNoArg, "(screen)" },
  { 0, 0, 0, 0 }
};

void
screenhack (Display *d, Window w)
{
  dpy = d;
  window = w;
  init ();
  if (delay2) usleep (delay2 * 2);
  while (1)
    {
      rotate ();
      if (delay2) usleep (delay2);
    }
}
