about summary refs log tree commit diff
path: root/other/pamx
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
commit1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch)
tree64c8c96cf54d8718847339a403e5e67b922e8c3f /other/pamx
downloadnetpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'other/pamx')
-rw-r--r--other/pamx/COPYRIGHT72
-rw-r--r--other/pamx/Makefile43
-rw-r--r--other/pamx/Makefile251
-rw-r--r--other/pamx/fill.c82
-rw-r--r--other/pamx/fill.h16
-rw-r--r--other/pamx/image.c331
-rw-r--r--other/pamx/image.h90
-rw-r--r--other/pamx/pamx.c364
-rw-r--r--other/pamx/send.c872
-rw-r--r--other/pamx/send.h38
-rw-r--r--other/pamx/valtomem.h65
-rw-r--r--other/pamx/window.c1209
-rw-r--r--other/pamx/window.h38
-rw-r--r--other/pamx/ximageinfo.h25
14 files changed, 3296 insertions, 0 deletions
diff --git a/other/pamx/COPYRIGHT b/other/pamx/COPYRIGHT
new file mode 100644
index 00000000..3ad3b675
--- /dev/null
+++ b/other/pamx/COPYRIGHT
@@ -0,0 +1,72 @@
+Many of the source files for Pamx contain code written by or derived
+from code written by Jim Frost for his program Xloadimage.  The files
+in which Frost has copyright contain Frost's name at the top.  Frost
+licenses his copyright to the public as follows:
+
+    Copyright 1989, 1993 Jim Frost
+   
+    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.  The author makes no representations
+    about the suitability of this software for any purpose.  It is
+    provided "as is" without express or implied warranty.
+   
+    THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+    INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+    NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+    CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+    OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+    OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+    USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+The rest of the code was primarily written by Bryan Henderson,
+starting in 2006.  Bryan and other authors have contributed their work
+to the public domain.
+
+
+Here is a more or less complete list of people who contributed to
+the Xloadimage from which Pamx was derived.  The list is from the README
+file in the Xloadimage source code:
+
+Special thanks to the crew at the Boston University Graphics Lab for
+their assistance and sample images, and to bzs@std.com for his simple
+dithering algorithm (or what's left of it).  Real special thanks to
+Kirk L. Johnson (tuna@athena.mit.edu) for a very nice GIF loader and
+dithering routine, to Mark Snitily (zok!mark@apple.com) for 386/ix
+compatibility work, to Andreas Stolcke (stolcke@icsib12.berkeley.edu)
+for miscellaneous bug fixes, to Anthony A. Datri (datri@convex.com)
+for a number of things, to Mark Moraes (moraes@cs.toronto.edu) for
+the slideshow colormap fix, to Gregg Townsend (gmt@cs.arizona.edu) for
+a suggested dithering routine and other fixes, to Brian Frost
+(B1F5814@RIGEL.TAMU.EDU) for changes for VMS, to Chip Horstman for G3
+FAX support, to Deron Dann Johnson (dj@eng.sun.com) for fixing the
+RetainTemporary bug, to Tom Tatlow (tatlow@dash.enet.dec.com) for
+image rotation code, to Mark A. Horstman (mhorstm@sarek.sbc.com) for
+tilde expansion in .xloadimagerc files and virtual-root support in
+root.c, to Tim Roper (timr@labtam.labtam.oz.au), Graeme Gill
+(graeme@labtam.oz.au) for gamma correction and Utah RLE image support,
+Mark Majhor (uunet!sequent!markm) for FBM and MacPaint support, Ian
+MacPhedran (macphed@dvinci.usask.ca) for PGM and PPM support, Per
+Fogelstrom (pf@diab.se) for a fix to send.c, Hans J. Albertsson
+(hans@Sweden.Sun.COM) for cleaning up GIF aborting, Graham Hudspith
+(gwh@inmos.com) for a geometry patch, Glenn P. Davis
+(davis@unidata.ucar.edu) for McIDAS areafile support, Keith S. Pickens
+(maxwell.nde.swri.edu!ksp) for fixing the RLE loader to work with the
+updated zio package, Mike Douglas (douglas@wilbur.coyote.trw.com) for
+normalization, Rod Johnson (johnson@wrl.epi.com) for speedup
+suggestions, Hal Peterson (hrp@cray.com) for his Imakefile fix, Matt
+Caprile (Matthew.Caprile@ec.bull.fr) for slideshow delay code, Bob
+Deroy (rwd@bucrsb.bu.edu) for mondo 24-bit Sun Rasterfile images that
+broke everything, Christos S. Zoulas (christo@ee.cornell.edu) for a
+first-cut 24-bit implementation, Gerald James Barnes
+(gjb@oasis.icl.stc.co.uk) for a first-cut forced-visual
+implementation, Michael Campanella (campanella@cvg.enet.dec.com) for
+more VMS changes, Kee Hinckley (nazgul@alfalfa.com) for robustness
+changes to the g3 and MacPaint loaders and the ZIO package, Tim
+Northrup (tim@brspyr1.brs.com) for PC Paintbrush and GEM image
+formats, Richard Weidner (richard@elroy.jpl.nasa.gov) for lots of
+24-bit testing,  Eckhard Rueggeberg (erueg@cfgauss.uni-math.gwdg.de)
+for a better PCX loader, and any others whose names I've missed.
diff --git a/other/pamx/Makefile b/other/pamx/Makefile
new file mode 100644
index 00000000..a86a3331
--- /dev/null
+++ b/other/pamx/Makefile
@@ -0,0 +1,43 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = other/pamx
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/Makefile.config
+
+ifneq ($(X11LIB),NONE)
+  ifneq ($(X11HDR_DIR),)
+    INCLUDES += -I$(X11HDR_DIR)
+  endif
+endif
+
+ifneq ($(X11LIB),NONE)
+  BINARIES += pamx
+endif
+
+PAMX_OBJECTS = \
+	pamx.o \
+	image.o \
+	send.o \
+	window.o \
+
+MERGE_OBJECTS = \
+	pamx.o2 \
+	image.o \
+	send.o \
+	window.o \
+
+OBJECTS = $(PAMX_OBJECTS)
+
+MERGEBINARIES = $(BINARIES)
+
+all: $(BINARIES)
+
+include $(SRCDIR)/Makefile.common
+
+pamx: $(PAMX_OBJECTS) $(NETPBMLIB) $(LIBOPT)
+	$(LD) $(LDFLAGS) -o $@ $(PAMX_OBJECTS) \
+	  $(shell $(LIBOPT) $(NETPBMLIB) $(X11LIB)) \
+	  $(LDLIBS) $(MATHLIB) $(RPATH) $(LADD)
diff --git a/other/pamx/Makefile2 b/other/pamx/Makefile2
new file mode 100644
index 00000000..f69e103f
--- /dev/null
+++ b/other/pamx/Makefile2
@@ -0,0 +1,51 @@
+# C compiler to use, including special flags.
+CC=gcc
+
+WARNINGS = -Wall -Wmissing-declarations -Wundef -Wimplicit -Wwrite-strings \
+	-Winline \
+	-Wstrict-prototypes -Wmissing-prototypes \
+	-Werror
+
+CFLAGS = $(WARNINGS) -fno-common -g
+INCLUDES = -I /home/bryanh/netpbm/other/importinc
+
+# X11 include and library information.
+X11_LIB_DIR=-L/subsysx/X11R6/lib
+X11_LIB_NAME=-lX11
+NETPBMLIB = /home/bryanh/netpbm/lib/libnetpbm.so
+
+LIBS=$(X11_LIB_DIR) $(X11_LIB_NAME) -lm
+
+default: pamx
+
+# files for the image library
+IMAGE_SRCS= image.c
+IMAGE_OBJS= ${IMAGE_SRCS:.c=.o}
+
+# files for the image processing library
+PROCESS_HDRS=
+# no image processing.
+PROCESS_SRCS= fill.c
+PROCESS_OBJS= ${PROCESS_SRCS:.c=.o}
+
+X_SRCS= send.c window.c pamx.c
+X_OBJS= ${X_SRCS:.c=.o}
+
+OBJS= $(IMAGE_OBJS) $(PROCESS_OBJS) $(X_OBJS) $(NETPBMLIB)
+
+.c.o: $*.c
+	$(CC) -c $(CFLAGS) $(INCLUDES) $*.c $(CADD)
+
+pamx: $(OBJS) $(OPTIONAL_LIBS)
+	$(CC) -o $@ $(OBJS) $(OPTIONAL_LIBS) $(LIBS)
+
+clean::
+	rm -f *.o pamx
+
+dep:
+	$(CC) -MM -MG $(INCLUDES) *.c >Makefile.depend
+
+include Makefile.depend
+
+Makefile.depend:
+	>$@
diff --git a/other/pamx/fill.c b/other/pamx/fill.c
new file mode 100644
index 00000000..13a2b21e
--- /dev/null
+++ b/other/pamx/fill.c
@@ -0,0 +1,82 @@
+/* 
+   fill an image area with a particular pixel value
+ 
+   By Jim Frost 1989.10.02, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include "pm.h"
+#include "image.h"
+#include "valtomem.h"
+#include "fill.h"
+
+void
+fill(Image * const imageP,
+     unsigned int const fx,
+     unsigned int const fy,
+     unsigned int const fw,
+     unsigned int const fh,
+     Pixel        const pixval) {
+
+    assertGoodImage(imageP);
+    switch(imageP->type) {
+    case IBITMAP: {
+        unsigned int const linelen = (imageP->width +7)/ 8;
+        unsigned int const start = (fx +7) / 8;
+        unsigned char const startmask = 0x80 >> (fx % 8);
+
+        unsigned int y;
+        unsigned char * lineptr;
+
+        for (y = fy, lineptr = imageP->data + linelen * fy;
+             y < fy + fh;
+             ++y, lineptr += linelen) {
+
+            unsigned int x;
+            unsigned char mask;
+            unsigned char * pixptr;
+
+            mask = startmask;
+            pixptr = lineptr + start;
+
+            for (x = fx; x < fw; ++x) {
+                if (pixval)
+                    *pixptr |= mask;
+                else
+                    *pixptr &= ~mask;
+                if (!(mask >>= 1)) {
+                    mask = 0x80;
+                    ++pixptr;
+                }
+            }
+        }
+    } break;
+        
+  case IRGB:
+    case ITRUE: {
+        unsigned int const linelen= imageP->width * imageP->pixlen;
+        unsigned int const start = imageP->pixlen * fx;
+
+        unsigned int y;
+        unsigned char * lineptr;
+
+        for (y = fy, lineptr = imageP->data + (linelen * fy);
+             y < fy + fh;
+             ++y, lineptr += linelen) {
+
+            unsigned int x;
+            unsigned char * pixptr;
+
+            pixptr = lineptr + start;
+            for (x = fx, pixptr = lineptr + start;
+                 x < fw;
+                 ++x, pixptr += imageP->pixlen) {
+                valToMem(pixval, pixptr, imageP->pixlen);
+            }
+        }
+    } break;
+    default:
+        pm_error("INTERNAL ERROR: Impossible image type %u", imageP->type);
+    }
+}
diff --git a/other/pamx/fill.h b/other/pamx/fill.h
new file mode 100644
index 00000000..1f316d0b
--- /dev/null
+++ b/other/pamx/fill.h
@@ -0,0 +1,16 @@
+#ifndef FILL_H_INCLUDED
+#define FILL_H_INCLUDED
+
+#include "ximageinfo.h"
+
+struct Image;
+
+void
+fill(struct Image * const imageP,
+     unsigned int   const fx,
+     unsigned int   const fy,
+     unsigned int   const fw,
+     unsigned int   const fh,
+     Pixel          const pixval);
+
+#endif
diff --git a/other/pamx/image.c b/other/pamx/image.c
new file mode 100644
index 00000000..3aaa8478
--- /dev/null
+++ b/other/pamx/image.c
@@ -0,0 +1,331 @@
+/*
+   Functions to allocate and deallocate structures and structure data
+ 
+   By Jim Frost 1989.09.29, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "image.h"
+
+
+
+/* this table is useful for quick conversions between depth and ncolors */
+
+static unsigned long const DepthToColorsTable[] = {
+  /*  0 */ 1,
+  /*  1 */ 2,
+  /*  2 */ 4,
+  /*  3 */ 8,
+  /*  4 */ 16,
+  /*  5 */ 32,
+  /*  6 */ 64,
+  /*  7 */ 128,
+  /*  8 */ 256,
+  /*  9 */ 512,
+  /* 10 */ 1024,
+  /* 11 */ 2048,
+  /* 12 */ 4096,
+  /* 13 */ 8192,
+  /* 14 */ 16384,
+  /* 15 */ 32768,
+  /* 16 */ 65536,
+  /* 17 */ 131072,
+  /* 18 */ 262144,
+  /* 19 */ 524288,
+  /* 20 */ 1048576,
+  /* 21 */ 2097152,
+  /* 22 */ 4194304,
+  /* 23 */ 8388608,
+  /* 24 */ 16777216,
+  /* 25 */ 33554432,
+  /* 26 */ 67108864,
+  /* 27 */ 134217728,
+  /* 28 */ 268435456,
+  /* 29 */ 536870912,
+  /* 30 */ 1073741824,
+  /* 31 */ 2147483648u,
+  /* 32 */ 2147483648u /* bigger than unsigned int; this is good enough */
+};
+
+
+
+unsigned int
+depthToColors(unsigned int const depth) {
+    return DepthToColorsTable[MIN(depth,32)];
+}
+
+
+
+unsigned long
+colorsToDepth(unsigned long const ncolors) {
+
+    unsigned long a;
+
+    for (a = 0; (a < 32) && (DepthToColorsTable[a] < ncolors); ++a)
+        /* EMPTY */
+        ;
+    return a ;
+}
+
+
+
+void
+assertGoodImage(Image * const imageP) {
+
+    assert(imageP != NULL);
+
+    switch (imageP->type) {
+    case IBITMAP:
+    case IRGB:
+    case ITRUE:
+        break;
+    default:
+        assert(FALSE);  /* can't be */
+    }
+}
+
+
+
+static void
+newRGBMapData(RGBMap *     const rgbP,
+              unsigned int const size) {
+
+    rgbP->used = 0;
+    rgbP->size = size;
+    rgbP->compressed = FALSE;
+    MALLOCARRAY(rgbP->red, size);
+    MALLOCARRAY(rgbP->grn, size);
+    MALLOCARRAY(rgbP->blu, size);
+
+    if (rgbP->red == NULL || rgbP->grn == NULL || rgbP->blu == NULL)
+        pm_error("Out of memory allocating %u pixels", size);
+}
+
+
+
+static void
+freeRGBMapData(RGBMap * const rgbP) {
+
+    free(rgbP->red);
+    free(rgbP->grn);
+    free(rgbP->blu);
+}
+
+
+
+Image *
+newBitImage(unsigned int const width,
+            unsigned int const height) {
+
+    unsigned int const linelen = (width + 7) / 8;
+
+    Image * imageP;
+
+    MALLOCVAR_NOFAIL(imageP);
+
+    imageP->type = IBITMAP;
+    newRGBMapData(&imageP->rgb, 2);
+    imageP->rgb.red[0] = imageP->rgb.grn[0] = imageP->rgb.blu[0] = 65535;
+    imageP->rgb.red[1] = imageP->rgb.grn[1] = imageP->rgb.blu[1] = 0;
+    imageP->rgb.used = 2;
+    imageP->width = width;
+    imageP->height = height;
+    imageP->depth = 1;
+
+    if (UINT_MAX / linelen < height)
+        pm_error("Image dimensions too big to compute: %u x %u",
+                 linelen, height);
+    MALLOCARRAY(imageP->data, linelen * height);
+
+    if (imageP->data == NULL)
+        pm_error("Out of memory allocating array of %u x %u", linelen, height);
+
+    return imageP;
+}
+
+
+
+Image *
+newRGBImage(unsigned int const width,
+            unsigned int const height,
+            unsigned int const depth) {
+    
+    unsigned int const pixlen = pixlen > 0 ? (depth + 7) / 8 : 1;
+        /* Special case for "zero" depth image, which is sometimes
+           interpreted as "one color"
+        */
+    unsigned int const numcolors = depthToColors(depth);
+
+    Image * imageP;
+    
+    MALLOCVAR_NOFAIL(imageP);
+    imageP->type   = IRGB;
+    newRGBMapData(&imageP->rgb, numcolors);
+    imageP->width  = width;
+    imageP->height = height;
+    imageP->depth  = depth;
+    imageP->pixlen = pixlen;
+
+    if (UINT_MAX / width / height < pixlen)
+        pm_error("Image dimensions %u x %u x %u are too big to compute.",
+                 width, height, pixlen);
+    MALLOCARRAY(imageP->data, width * height * pixlen);
+    if (imageP->data == NULL)
+        pm_error("Unable to allocate %u x %u x %u raster array",
+                 width, height, pixlen);
+
+    return imageP;
+}
+
+
+
+Image *
+newTrueImage(unsigned int const width,
+             unsigned int const height) {
+
+    unsigned int const pixlen = 3;
+    
+    Image * imageP;
+
+    MALLOCVAR_NOFAIL(imageP);
+    imageP->type     = ITRUE;
+    imageP->rgb.used = 0;
+    imageP->rgb.size = 0;
+    imageP->width    = width;
+    imageP->height   = height;
+    imageP->depth    = 24;
+    imageP->pixlen   = 3;
+
+    if (UINT_MAX / width / height < pixlen)
+        pm_error("Image dimensions %u x %u x %u are too big to compute.",
+                 width, height, pixlen);
+    MALLOCARRAY(imageP->data, width * height * pixlen);
+    if (imageP->data == NULL)
+        pm_error("Unable to allocate %u x %u x %u raster array",
+                 width, height, pixlen);
+
+    return imageP;
+}
+
+
+
+static void
+freeImageData(Image * const imageP) {
+
+    if (!TRUEP(imageP))
+        freeRGBMapData(&imageP->rgb);
+    free(imageP->data);
+}
+
+
+
+void
+freeImage(Image * const imageP) {
+
+    assertGoodImage(imageP);
+
+    freeImageData(imageP);
+
+    imageP->type = IBAD;
+
+    free(imageP);
+}
+
+
+
+
+static void
+fillRow1(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][0], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+fillRow3(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][plane], pamP->maxval, 255);
+    }
+}
+
+
+
+Image *
+pbmLoad(const char * const fullname,
+        const char * const name,
+        bool         const verbose) {
+
+    FILE * ifP;
+    struct pam pam;
+    Image * imageP;
+    unsigned int row;
+    const char * filename;
+    tuple * tuplerow;
+    unsigned char * p;
+    enum {DEPTH_1, DEPTH_3} depth;
+
+    if (STREQ(fullname, "stdin"))
+        filename = "-";
+    else
+        filename = fullname;
+
+    ifP = pm_openr(filename);
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (strncmp(pam.tuple_type, "RGB", 3) == 0) {
+        depth = DEPTH_3;
+        if (pam.depth < 3)
+            pm_error("Invalid depth %u for RGB tuple type.", pam.depth);
+    } else
+        depth = DEPTH_1;
+
+    imageP = newTrueImage(pam.width, pam.height);
+
+    p = &imageP->data[0];  /* initial value */
+
+    tuplerow = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        pnm_readpamrow(&pam, tuplerow);
+        
+        switch (depth) {
+        case DEPTH_3:
+            fillRow3(&pam, tuplerow, &p);
+            break;
+        case DEPTH_1:
+            fillRow1(&pam, tuplerow, &p);
+            break;
+        }
+    }
+    pnm_freepamrow(tuplerow);
+    
+    pm_close(ifP);
+
+    return imageP;
+}
diff --git a/other/pamx/image.h b/other/pamx/image.h
new file mode 100644
index 00000000..9c9689ac
--- /dev/null
+++ b/other/pamx/image.h
@@ -0,0 +1,90 @@
+#ifndef IMAGE_H_INCLUDED
+#define IMAGE_H_INCLUDED
+#include <strings.h>
+
+#include "pm_c_util.h"
+
+#include "ximageinfo.h"
+
+typedef struct rgbmap {
+    unsigned int size;       /* size of RGB map */
+    unsigned int used;       /* number of colors used in RGB map */
+    bool         compressed; /* image uses colormap fully */
+    Intensity *  red;        /* color values in X style */
+    Intensity *  grn;
+    Intensity *  blu;
+} RGBMap;
+
+typedef struct Image {
+    unsigned int    type;   /* type of image */
+    RGBMap          rgb;    /* RGB map of image if IRGB type */
+    unsigned int    width;  /* width of image in pixels */
+    unsigned int    height; /* height of image in pixels */
+    unsigned int    depth;  /* depth of image in bits if IRGB type */
+    unsigned int    pixlen; /* length of pixel if IRGB type */
+    unsigned char * data;   /* data rounded to full byte for each row */
+} Image;
+
+#define IBAD    0 /* invalid image (used when freeing) */
+#define IBITMAP 1 /* image is a bitmap */
+#define IRGB    2 /* image is RGB */
+#define ITRUE   3 /* image is true color */
+
+#define BITMAPP(IMAGE) ((IMAGE)->type == IBITMAP)
+#define RGBP(IMAGE)    ((IMAGE)->type == IRGB)
+#define TRUEP(IMAGE)   ((IMAGE)->type == ITRUE)
+
+#define TRUE_RED(PIXVAL)   (((PIXVAL) & 0xff0000) >> 16)
+#define TRUE_GRN(PIXVAL) (((PIXVAL) & 0xff00) >> 8)
+#define TRUE_BLU(PIXVAL)  ((PIXVAL) & 0xff)
+#define RGB_TO_TRUE(R,G,B) \
+  (((unsigned int)((R) & 0xff00) << 8) | ((unsigned int)(G) & 0xff00) | \
+   ((unsigned int)(B) >> 8))
+
+unsigned long
+colorsToDepth(unsigned long const ncolors);
+
+void
+assertGoodImage(Image * const imageP);
+
+Image *
+newBitImage(unsigned int const width,
+            unsigned int const height);
+
+Image *
+newRGBImage(unsigned int const width,
+            unsigned int const height,
+            unsigned int const depth);
+
+Image *
+newTrueImage(unsigned int const width,
+             unsigned int const height);
+
+void
+freeImage(Image * const imageP);
+
+unsigned int
+depthToColors(unsigned int const depth);
+
+extern unsigned short RedIntensity[];
+extern unsigned short GreenIntensity[];
+extern unsigned short BlueIntensity[];
+
+static __inline__ unsigned int
+colorIntensity(unsigned int const red,
+               unsigned int const grn,
+               unsigned int const blu) {
+/*----------------------------------------------------------------------------
+  Return the (approximate) intensity of a color.
+-----------------------------------------------------------------------------*/
+    return (RedIntensity[red / 256] +
+            GreenIntensity[grn / 256] +
+            BlueIntensity[blu / 256]);
+}
+
+Image *
+pbmLoad(const char * const fullname,
+        const char * const name,
+        bool         const verbose);
+
+#endif
diff --git a/other/pamx/pamx.c b/other/pamx/pamx.c
new file mode 100644
index 00000000..8a64fec4
--- /dev/null
+++ b/other/pamx/pamx.c
@@ -0,0 +1,364 @@
+/* By Bryan Henderson 2006.03.25 
+
+   Copyright information is in the file COPYRIGHT
+*/
+
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "filename.h"
+
+#include "ximageinfo.h"
+#include "image.h"
+#include "fill.h"
+#include "window.h"
+
+
+struct geometry {
+    bool specified;
+    const char * string;
+    unsigned int width;
+    unsigned int height;
+};
+
+struct cmdlineInfo {
+    const char * inputFileName;
+    const char * displayName;  /* NULL if none */
+    const char * display;
+    const char * title;
+    unsigned int fullscreen;
+    unsigned int verbose;
+    struct geometry geometry;
+    const char * foreground;
+    const char * background;
+    const char * border;
+    unsigned int install;
+    unsigned int private;
+    unsigned int pixmap;
+    unsigned int fit;
+    unsigned int visualSpec;
+    unsigned int visual;
+};
+
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+/* --------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+--------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to optParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+  
+    unsigned int option_def_index;
+
+    unsigned int displaySpec, titleSpec, foregroundSpec, backgroundSpec,
+        borderSpec, geometrySpec;
+
+    const char * geometryOpt;
+    const char * visualOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "fullscreen", OPT_FLAG,
+            NULL, &cmdlineP->fullscreen,     0);
+    OPTENT3(0, "install",    OPT_FLAG,
+            NULL, &cmdlineP->install,     0);
+    OPTENT3(0, "private",    OPT_FLAG,
+            NULL, &cmdlineP->private,     0);
+    OPTENT3(0, "fit",        OPT_FLAG,
+            NULL, &cmdlineP->fit,     0);
+    OPTENT3(0, "pixmap",     OPT_FLAG,
+            NULL, &cmdlineP->pixmap,     0);
+    OPTENT3(0, "verbose",    OPT_FLAG,
+            NULL, &cmdlineP->verbose,     0);
+    OPTENT3(0, "display",    OPT_STRING,
+            &cmdlineP->display, &displaySpec, 0);
+    OPTENT3(0, "title",      OPT_STRING,
+            &cmdlineP->title, &titleSpec,     0);
+    OPTENT3(0, "foreground", OPT_STRING,
+            &cmdlineP->foreground, &foregroundSpec,     0);
+    OPTENT3(0, "background", OPT_STRING,
+            &cmdlineP->background, &backgroundSpec,     0);
+    OPTENT3(0, "border",     OPT_STRING,
+            &cmdlineP->border, &borderSpec,     0);
+    OPTENT3(0, "geometry",   OPT_STRING,
+            &geometryOpt, &geometrySpec,     0);
+    OPTENT3(0, "visual",     OPT_STRING,
+            &visualOpt, &cmdlineP->visualSpec,     0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
+    
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (geometrySpec) {
+        int rc;
+
+        cmdlineP->geometry.specified = true;
+        cmdlineP->geometry.string    = geometryOpt;
+
+        rc = sscanf(geometryOpt, "%ux%u",
+                    &cmdlineP->geometry.width, &cmdlineP->geometry.height);
+
+        if (rc != 2)
+            pm_error("Geometry value '%s' does not have the form WxH, "
+                     "where W and H are natural numbers", geometryOpt);
+
+        if (cmdlineP->geometry.width == 0)
+            pm_error("Width in -geometry option is zero");
+        if (cmdlineP->geometry.height == 0)
+            pm_error("Height in -geometry option is zero");
+    } else
+        cmdlineP->geometry.specified = false;
+
+    if (!displaySpec)
+        cmdlineP->display = NULL;
+
+    if (!titleSpec)
+        cmdlineP->title = NULL;
+
+    if (!foregroundSpec)
+        cmdlineP->foreground = NULL;
+
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+
+    if (!borderSpec)
+        cmdlineP->border = NULL;
+
+    if (cmdlineP->visualSpec)
+        cmdlineP->visual = visualClassFromName(visualOpt);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("Program takes at most one argument:  input file name");
+}
+
+
+
+static int
+errorHandler(Display *     const disp,
+             XErrorEvent * const error) {
+/*----------------------------------------------------------------------------
+   This is an X error handler
+-----------------------------------------------------------------------------*/
+    char errortext[BUFSIZ];
+
+    XGetErrorText(disp, error->error_code, errortext, sizeof(errortext));
+    pm_error("X Error: %s on resource 0x%x",
+             errortext, (unsigned)error->resourceid);
+    return 0;
+}
+
+
+
+static void
+fillRow1(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][0], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+fillRow3(struct pam *     const pamP,
+         tuple *          const tuplerow,
+         unsigned char ** const pP) {
+
+    unsigned int col;
+    
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < pamP->depth; ++plane)
+            *(*pP)++ =
+                pnm_scalesample(tuplerow[col][plane], pamP->maxval, 255);
+    }
+}
+
+
+
+static void
+loadPamImage(FILE *   const ifP,
+             Image ** const imagePP) {
+
+    struct pam pam;
+    Image * imageP;
+    unsigned int row;
+    tuple * tuplerow;
+    unsigned char * p;
+    enum {DEPTH_1, DEPTH_3} depth;
+
+    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
+
+    if (strncmp(pam.tuple_type, "RGB", 3) == 0) {
+        depth = DEPTH_3;
+        if (pam.depth < 3)
+            pm_error("Invalid depth %u for RGB tuple type.", pam.depth);
+    } else
+        depth = DEPTH_1;
+
+    imageP = newTrueImage(pam.width, pam.height);
+
+    p = &imageP->data[0];  /* initial value */
+
+    tuplerow = pnm_allocpamrow(&pam);
+
+    for (row = 0; row < pam.height; ++row) {
+        pnm_readpamrow(&pam, tuplerow);
+        
+        switch (depth) {
+        case DEPTH_3:
+            fillRow3(&pam, tuplerow, &p);
+            break;
+        case DEPTH_1:
+            fillRow1(&pam, tuplerow, &p);
+            break;
+        }
+    }
+    pnm_freepamrow(tuplerow);
+
+    *imagePP = imageP;
+}
+
+
+#define BACKGROUND_IDX 0
+#define FOREGROUND_IDX 1
+
+
+static void
+processImage(Image *            const imageP,
+             struct cmdlineInfo const cmdline,
+             Display *          const dispP,
+             int                const scrn) {
+/*----------------------------------------------------------------------------
+   Modify image *imageP according to various command line options.
+-----------------------------------------------------------------------------*/
+    if (imageP->depth <= 1) {
+        if (cmdline.background) {
+            XColor color;
+            XParseColor(dispP, DefaultColormap(dispP, scrn),
+                        cmdline.background, &color);
+            imageP->rgb.red[BACKGROUND_IDX] = color.red;
+            imageP->rgb.grn[BACKGROUND_IDX] = color.green;
+            imageP->rgb.blu[BACKGROUND_IDX] = color.blue;
+        }
+        if (cmdline.foreground) {
+            XColor color;
+            XParseColor(dispP, DefaultColormap(dispP, scrn),
+                        cmdline.foreground, &color);
+            imageP->rgb.red[FOREGROUND_IDX] = color.red;
+            imageP->rgb.grn[FOREGROUND_IDX] = color.green;
+            imageP->rgb.blu[FOREGROUND_IDX] = color.blue;
+        }
+    }    
+}
+
+
+
+static void
+determineTitle(struct cmdlineInfo const cmdline,
+               const char **      const titleP) {
+    
+    const char * title;
+    
+    if (cmdline.title)
+        title = strdup(cmdline.title);
+    else {
+        if (STREQ(cmdline.inputFileName, "-"))
+            title = NULL;
+        else {
+            title = pm_basename(cmdline.inputFileName);
+        }
+    }
+    *titleP = title;
+}
+
+
+
+int
+main(int     argc,
+     char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE *       ifP;
+    Image *      imageP;
+    Display *    dispP;           /* display we're sending to */
+    int          scrn;           /* screen we're sending to */
+    int          retval;
+    const char * geometryString;
+    const char * title;
+    viewer * viewerP;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+    
+    dispP = XOpenDisplay(cmdline.display);
+    if (!dispP)
+        pm_error("Cannot open display '%s'", XDisplayName(cmdline.display));
+
+    scrn = DefaultScreen(dispP);
+    XSetErrorHandler(errorHandler);
+
+    geometryString =
+        cmdline.geometry.specified ? cmdline.geometry.string : NULL;
+
+    createViewer(&viewerP, dispP, scrn, geometryString, cmdline.fullscreen);
+
+    loadPamImage(ifP, &imageP);
+
+    processImage(imageP, cmdline, dispP, scrn);
+
+    determineTitle(cmdline, &title);
+
+    displayInViewer(viewerP, imageP,
+                    cmdline.install, cmdline.private, cmdline.fit,
+                    cmdline.pixmap, cmdline.visualSpec, cmdline.visual,
+                    title, cmdline.verbose,
+                    &retval);
+
+    freeImage(imageP);
+
+    destroyViewer(viewerP);
+
+    if (title)
+        strfree(title);
+
+    XCloseDisplay(dispP);
+
+    return retval;
+}
diff --git a/other/pamx/send.c b/other/pamx/send.c
new file mode 100644
index 00000000..4b1268a5
--- /dev/null
+++ b/other/pamx/send.c
@@ -0,0 +1,872 @@
+/*
+ 
+  Send an Image to an X pixmap
+
+
+  By Jim Frost 1989.10.02, Bryan Henderson 2006.03.25.
+ 
+  Copyright 1989, 1990, 1991 Jim Frost.
+  See COPYRIGHT file for copyright information.
+*/
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "pm_c_util.h"
+#include "pm.h"
+#include "mallocvar.h"
+#include "ximageinfo.h"
+#include "valtomem.h"
+#include "image.h"
+#include "send.h"
+
+#define TRUE_TO_15BIT(PIXEL)     \
+    ((((PIXEL) & 0xf80000) >> 9) | \
+    (((PIXEL) & 0x00f800) >> 6) | \
+    (((PIXEL) & 0x0000f8) >> 3))
+
+#define RED_INTENSITY(P)   (((P) & 0x7c00) >> 10)
+#define GREEN_INTENSITY(P) (((P) & 0x03e0) >> 5)
+#define BLUE_INTENSITY(P)   ((P) & 0x001f)
+#define PM_SCALE(a, b, c) (long)((a) * (c))/(b)
+
+
+static bool GotError;
+
+static int
+pixmapErrorTrap(Display *     const disp,
+                XErrorEvent * const  pErrorEvent) {
+
+#define MAXERRORLEN 100
+    char buf[MAXERRORLEN+1];
+    GotError = 1;
+    XGetErrorText(disp, pErrorEvent->error_code, buf, MAXERRORLEN);
+    pm_message("serial #%ld (request code %d) Got Error '%s'",
+               pErrorEvent->serial,
+               pErrorEvent->request_code,
+               buf);
+    return 0;
+}
+
+
+
+Pixmap
+ximageToPixmap(Display *    const disp,
+               Window       const parent,
+               XImageInfo * const ximageinfoP) {
+
+    XErrorHandler old_handler;
+    Pixmap        pixmap;
+  
+    GotError = FALSE;
+    old_handler = XSetErrorHandler(pixmapErrorTrap);
+    XSync(disp, False);
+    pixmap= XCreatePixmap(disp, parent,
+                          ximageinfoP->ximageP->width,
+                          ximageinfoP->ximageP->height,
+                          ximageinfoP->depth);
+    XSetErrorHandler(old_handler);
+    if (GotError)
+        return None;
+    ximageinfoP->drawable = pixmap;
+    sendXImage(ximageinfoP, 0, 0, 0, 0,
+               ximageinfoP->ximageP->width, ximageinfoP->ximageP->height);
+
+    return pixmap;
+}
+
+
+
+/* find the best pixmap depth supported by the server for a particular
+ * visual and return that depth.
+ *
+ * this is complicated by R3's lack of XListPixmapFormats so we fake it
+ * by looking at the structure ourselves.
+ */
+
+static unsigned int
+bitsPerPixelAtDepth(Display *    const disp,
+                    int          const scrn,
+                    unsigned int const depth) {
+
+#if XlibSpecificationRelease < 4 /* the way things were */
+  unsigned int a;
+
+  for (a= 0; a < disp->nformats; a++)
+    if (disp->pixmap_format[a].depth == depth)
+      return(disp->pixmap_format[a].bits_per_pixel);
+
+#else /* the way things should be */
+  XPixmapFormatValues *xf;
+  unsigned int nxf, a;
+
+  xf = XListPixmapFormats(disp, (int *)&nxf);
+  for (a = 0; a < nxf; a++)
+    if (xf[a].depth == depth)
+      return(xf[a].bits_per_pixel);
+#endif
+
+  /* this should never happen; if it does, we're in trouble
+   */
+
+  fprintf(stderr, "bitsPerPixelAtDepth: Can't find pixmap depth info!\n");
+  exit(1);
+}
+     
+
+
+static Image *
+itrueToRGB(Image *      const imageP,
+           unsigned int const ddepth) {
+
+    int y, x, num_pixels, colors;
+    unsigned long pixel_counts[32786];
+    unsigned long pixel_array[32786];
+    Pixel pixval;
+    unsigned char * pixel;
+    unsigned char * dpixel;
+    Image * newImageP;
+    
+    newImageP = newRGBImage(imageP->width, imageP->height, ddepth);
+
+    colors = 1 << ddepth;
+  
+    bzero(pixel_counts, 32768 * sizeof(unsigned long));
+  
+    pixel= imageP->data;
+    for (y= 0; y < imageP->height; y++) {
+        unsigned int x;
+        for (x= 0; x < imageP->width; x++) {
+            unsigned int const z = TRUE_TO_15BIT(memToVal(pixel, 3));
+            pixel_counts[z]++;
+            pixel += 3;
+        }
+    }
+    num_pixels = 0;
+    for (x = 0; x < 32768; ++x) {
+        if (pixel_counts[x] > 0) {
+            unsigned long const red = RED_INTENSITY(x);
+            unsigned long const grn = GREEN_INTENSITY(x);
+            unsigned long const blu = BLUE_INTENSITY(x);
+            pixel_counts[x] = num_pixels;
+            *(newImageP->rgb.red + num_pixels) = red<<11;
+            *(newImageP->rgb.grn + num_pixels) = grn<<11; 
+            *(newImageP->rgb.blu + num_pixels) =  blu<<11;
+            pixel_array[num_pixels++] = (short)x;
+            if (num_pixels > colors)
+                break;
+        }
+    }    
+
+    pixel = imageP->data;
+    dpixel = newImageP->data;
+    
+    for (y = 0; y < imageP->height; ++y) {
+        unsigned int x;
+        for (x = 0; x < imageP->width; ++x) {
+            unsigned int const z = TRUE_TO_15BIT(memToVal(pixel, 3));
+            pixval = pixel_counts[z];
+            valToMem(pixval, dpixel, newImageP->pixlen);
+            pixel += 3;
+            dpixel += newImageP->pixlen;
+        }
+    }
+    newImageP->rgb.used = num_pixels;
+    newImageP->rgb.compressed = 1;
+
+    return newImageP;
+}
+
+
+
+static void
+makeUsableVisual(Image *      const origImageP,
+                 Visual *     const visualP,
+                 unsigned int const ddepth,
+                 Image **     const newImagePP) {
+
+    /* process image based on type of visual to which we're sending */
+
+    switch (origImageP->type) {
+    case ITRUE:
+        switch (visualP->class) {
+        case TrueColor:
+        case DirectColor:
+            /* goody goody */
+            *newImagePP = origImageP;
+            break;
+        case PseudoColor:
+            *newImagePP = itrueToRGB(origImageP, ddepth);
+            if (*newImagePP == NULL)
+                pm_error("Unable to convert for Pseudocolor.");
+            break;
+        default:
+            pm_error("INTERNAL ERROR: impossible visual class %u",
+                     visualP->class);
+        }
+        break;
+        
+    case IRGB:
+        switch(visualP->class) {
+        case TrueColor:
+        case DirectColor:
+            /* no problem, we handle this just fine */
+            *newImagePP = origImageP;
+            break;
+        default:
+            pm_error("INTERNAL ERROR: impossible visual class %u",
+                     visualP->class);
+        }
+        
+    case IBITMAP:
+        /* no processing ever needs to be done for bitmaps */
+        *newImagePP = origImageP;
+        break;
+    }
+}    
+
+
+
+static void
+makeColorMap1(Display *  const disp,
+              int        const scrn,
+              Visual *   const visualP,
+              Colormap * const cmapP,
+              Pixel **   const redvalueP,
+              Pixel **   const grnvalueP,
+              Pixel **   const bluvalueP) {
+    
+    Pixel * redvalue;
+    Pixel * grnvalue;
+    Pixel * bluvalue;
+    Pixel pixval;
+    unsigned int redcolors, grncolors, blucolors;
+    unsigned int redstep, grnstep, blustep;
+    unsigned int redbottom, grnbottom, blubottom;
+    unsigned int redtop, grntop, blutop;
+    unsigned int a;
+            
+    MALLOCARRAY_NOFAIL(redvalue, 256);
+    MALLOCARRAY_NOFAIL(grnvalue, 256);
+    MALLOCARRAY_NOFAIL(bluvalue, 256);
+            
+    if (visualP == DefaultVisual(disp, scrn))
+        *cmapP = DefaultColormap(disp, scrn);
+    else
+        *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                 visualP, AllocNone);
+            
+ retry_direct: /* tag we hit if a DirectColor allocation fails on
+                * default colormap */
+            
+    /* calculate number of distinct colors in each band */
+            
+    redcolors = grncolors = blucolors = 1;
+    for (pixval = 1; pixval; pixval <<= 1) {
+        if (pixval & visualP->red_mask)
+            redcolors <<= 1;
+        if (pixval & visualP->green_mask)
+            grncolors <<= 1;
+        if (pixval & visualP->blue_mask)
+            blucolors <<= 1;
+    }
+            
+    /* sanity check */
+            
+    if ((redcolors > visualP->map_entries) ||
+        (grncolors > visualP->map_entries) ||
+        (blucolors > visualP->map_entries)) {
+        pm_message("Warning: inconsistency in color information "
+                   "(this may be ugly)");
+    }
+            
+    redstep= 256 / redcolors;
+    grnstep= 256 / grncolors;
+    blustep= 256 / blucolors;
+    redbottom = grnbottom = blubottom= 0;
+    for (a = 0; a < visualP->map_entries; ++a) {
+        XColor xcolor;
+        Status rc;
+        if (redbottom < 256)
+            redtop = redbottom + redstep;
+        if (grnbottom < 256)
+            grntop = grnbottom + grnstep;
+        if (blubottom < 256)
+            blutop = blubottom + blustep;
+                
+        xcolor.flags = DoRed | DoGreen | DoBlue;
+        xcolor.red   = (redtop - 1) << 8;
+        xcolor.green = (grntop - 1) << 8;
+        xcolor.blue  = (blutop - 1) << 8;
+        rc = XAllocColor(disp, *cmapP, &xcolor);
+        if (rc == 0) {
+            /* Allocation failed.  If it's for a DirectColor default
+               visual then we should create a private colormap
+               and try again.
+            */
+
+            if ((visualP->class == DirectColor) &&
+                (visualP == DefaultVisual(disp, scrn))) {
+                *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                         visualP, AllocNone);
+                goto retry_direct;
+            }
+                    
+            /* something completely unexpected happened */
+                    
+            pm_error("INTERNAL ERROR: XAllocColor failed on a "
+                     "TrueColor/Directcolor visual");
+        }
+                
+        /* fill in pixel values for each band at this intensity */
+                
+        while ((redbottom < 256) && (redbottom < redtop))
+            redvalue[redbottom++] = xcolor.pixel & visualP->red_mask;
+        while ((grnbottom < 256) && (grnbottom < grntop))
+            grnvalue[grnbottom++] = xcolor.pixel & visualP->green_mask;
+        while ((blubottom < 256) && (blubottom < blutop))
+            bluvalue[blubottom++] = xcolor.pixel & visualP->blue_mask;
+    }
+    *redvalueP   = redvalue;
+    *grnvalueP   = grnvalue;
+    *bluvalueP   = bluvalue;
+}
+
+
+ 
+static void
+allocColorCells(Display *      const disp,
+                Colormap       const cmap,
+                Pixel *        const colorIndex,
+                unsigned int   const colorCount,
+                unsigned int * const cellCountP) {
+
+    bool outOfCells;
+    unsigned int cellCount;
+    
+    outOfCells = false;  /* initial value */
+    cellCount = 0;       /* initial value */
+    while (cellCount < colorCount && !outOfCells) {
+        Status rc;
+        rc = XAllocColorCells(disp, cmap, FALSE, NULL, 0,
+                              &colorIndex[cellCount++], 1);
+        if (rc == 0)
+            outOfCells = true;
+    }
+    *cellCountP = cellCount;
+}
+
+    
+
+
+static void
+makeColorMap2(Display *  const disp,
+              int        const scrn,
+              Visual *   const visualP,
+              RGBMap     const rgb,
+              bool       const userWantsPrivateCmap,
+              bool       const userWantsFit,
+              bool       const verbose,
+              Colormap * const cmapP,
+              Pixel **   const colorIndexP) {
+
+    bool privateCmap;
+    bool fit;
+    bool newmap;
+    Pixel * colorIndex;
+
+    MALLOCARRAY_NOFAIL(colorIndex, rgb.used);
+        
+    /* 'privateCmap' is invalid if not a dynamic visual */
+        
+    switch (visualP->class) {
+    case StaticColor:
+    case StaticGray:
+        privateCmap = TRUE;
+    default:
+        privateCmap = userWantsPrivateCmap;
+    }
+        
+    /* get the colormap to use. */
+        
+    if (privateCmap) { /* user asked us to use a private cmap */
+        newmap = TRUE;
+        fit = FALSE;
+    } else if ((visualP == DefaultVisual(disp, scrn)) ||
+               (visualP->class == StaticGray) ||
+               (visualP->class == StaticColor) ||
+               (visualP->class == TrueColor) ||
+               (visualP->class == DirectColor)) {
+            
+        unsigned int a;
+
+        fit = userWantsFit;
+
+        /* if we're using the default visual, try to alloc colors
+           shareable.  otherwise we're using a static visual and
+           should treat it accordingly.
+        */
+            
+        if (visualP == DefaultVisual(disp, scrn))
+            *cmapP = DefaultColormap(disp, scrn);
+        else
+            *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                     visualP, AllocNone);
+        newmap = FALSE;
+            
+        /* allocate colors shareable (if we can) */
+            
+        for (a = 0; a < rgb.used; ++a) {
+            Status rc;
+            XColor  xcolor;
+
+            xcolor.flags = DoRed | DoGreen | DoBlue;
+            xcolor.red   = rgb.red[a];
+            xcolor.green = rgb.grn[a];
+            xcolor.blue  = rgb.blu[a];
+            rc = XAllocColor(disp, *cmapP, &xcolor);
+            if (rc == 0) {
+                if ((visualP->class == StaticColor) ||
+                    (visualP->class == StaticGray) ||
+                    (visualP->class == TrueColor) ||
+                    (visualP->class == DirectColor)) {
+                    pm_error("XAllocColor failed on a static visual");
+                } else {
+                    /* We can't allocate the colors shareable so
+                       free all the colors we had allocated and
+                       create a private colormap (or fit into the
+                       default cmap if `fit' is true).
+                    */
+                    XFreeColors(disp, *cmapP, colorIndex, a, 0);
+                    newmap = TRUE;
+                    break;
+                }
+            }
+            colorIndex[a] = xcolor.pixel;
+        }
+    } else {
+        newmap = TRUE;
+        fit    = FALSE;
+    }
+        
+    if (newmap) {
+        /* Either create a new colormap or fit the image into the
+           one we have.  To create a new one, we create a private
+           cmap and allocate the colors writable.  Fitting the
+           colors is harder; we have to:
+
+           1. grab the server so no one can goof with the colormap.
+           2. count the available colors using XAllocColorCells.
+           3. free the colors we just allocated.
+           4. reduce the depth of the image to fit.
+           5. allocate the colors again shareable.
+           6. ungrab the server and continue on our way.
+               
+           Someone should shoot the people who designed X color allocation.
+        */
+            
+        unsigned int a;
+
+        if (fit) {
+            if (verbose)
+                pm_message("Fitting image into default colormap");
+            XGrabServer(disp);
+        } else {
+            if (verbose)
+                pm_message("Using private colormap");
+                
+            /* create new colormap */
+                
+            *cmapP = XCreateColormap(disp, RootWindow(disp, scrn),
+                                     visualP, AllocNone);
+        }
+            
+        allocColorCells(disp, *cmapP, colorIndex, rgb.used, &a);
+
+        if (fit) {
+            if (a > 0)
+                XFreeColors(disp, *cmapP, colorIndex, a, 0);
+            if (a <= 2)
+                pm_error("Cannot fit into default colormap");
+        }
+            
+        if (a == 0)
+            pm_error("Color allocation failed!");
+            
+        if (fit) {
+            unsigned int a;
+            for (a = 0; a < rgb.used; ++a) {
+                XColor xcolor;
+                xcolor.flags = DoRed | DoGreen | DoBlue;
+                xcolor.red   = rgb.red[a];
+                xcolor.green = rgb.grn[a];
+                xcolor.blue  = rgb.blu[a];
+                
+                if (!XAllocColor(disp, *cmapP, &xcolor))
+                    pm_error("XAllocColor failed while fitting colormap!");
+                colorIndex[a] = xcolor.pixel;
+            }
+            XUngrabServer(disp);
+        } else {
+            unsigned int b;
+            for (b = 0; b < a; ++b) {
+                XColor xcolor;
+                xcolor.flags = DoRed | DoGreen | DoBlue;
+                xcolor.pixel = colorIndex[b];
+                xcolor.red   = rgb.red[b];
+                xcolor.green = rgb.grn[b];
+                xcolor.blue  = rgb.blu[b];
+                XStoreColor(disp, *cmapP, &xcolor);
+            }
+        }
+    }
+    *colorIndexP = colorIndex;
+}
+
+
+
+static void
+doColorAllocation(XImageInfo * const ximageinfoP,
+                  Display *    const disp,
+                  int          const scrn,
+                  Visual *     const visualP,
+                  Image *      const imageP,
+                  bool         const userWantsPrivateCmap,
+                  bool         const userWantsFit,
+                  bool         const verbose,
+                  Pixel **     const colorIndexP,
+                  Pixel **     const redvalP,
+                  Pixel **     const grnvalP,
+                  Pixel **     const bluvalP) {
+    
+    if ((visualP->class == TrueColor || visualP->class == DirectColor) &&
+        !BITMAPP(imageP)) {
+        makeColorMap1(disp, scrn, visualP, &ximageinfoP->cmap,
+                      redvalP, grnvalP, bluvalP);
+        *colorIndexP = NULL;
+    } else {
+        makeColorMap2(disp, scrn, visualP, imageP->rgb,
+                      userWantsPrivateCmap, userWantsFit, verbose,
+                      &ximageinfoP->cmap, colorIndexP);
+        
+        *redvalP = *grnvalP = *bluvalP = NULL;
+    }
+}
+    
+
+
+
+static void
+makeXImage(XImageInfo * const ximageinfoP,
+           Display *    const disp,
+           int          const scrn,
+           Visual *     const visualP,
+           unsigned int const ddepth,
+           Image *      const imageP,
+           Pixel        const colorIndex[],
+           Pixel        const redvalue[],
+           Pixel        const grnvalue[],
+           Pixel        const bluvalue[],
+           bool         const verbose) {
+/*----------------------------------------------------------------------------
+  Create an XImage and related colormap based on the image type we
+  have.
+-----------------------------------------------------------------------------*/
+    if (verbose)
+        pm_message("Building XImage...");
+
+    switch (imageP->type) {
+    case IBITMAP: {
+        unsigned int const byteCount =
+            (imageP->width + 7) / 8 * imageP->height;
+        unsigned char * data;
+
+        /* we copy the data to be more consistent */
+
+        MALLOCARRAY(data, byteCount);
+        if (data == NULL)
+            pm_error("Can't allocate space for %u byte image", byteCount);
+        bcopy(imageP->data, data, byteCount);
+
+        ximageinfoP->ximageP =
+            XCreateImage(disp, visualP, 1, XYBitmap,
+                         0, (char*)data, imageP->width, imageP->height, 8, 0);
+        ximageinfoP->depth = ddepth;
+        ximageinfoP->foreground = colorIndex[1];
+        ximageinfoP->background = colorIndex[0];
+        ximageinfoP->ximageP->bitmap_bit_order = MSBFirst;
+        ximageinfoP->ximageP->byte_order = MSBFirst;
+    } break;
+
+    case IRGB:
+    case ITRUE: {
+        /* Modify image data to match visual and colormap */
+        
+        unsigned int const dbits = bitsPerPixelAtDepth(disp, scrn, ddepth);
+        unsigned int const dpixlen = (dbits + 7) / 8;
+
+        ximageinfoP->depth = ddepth;
+        
+        switch (visualP->class) {
+        case DirectColor:
+        case TrueColor: {
+            unsigned char * data;
+            unsigned char * destptr;
+            unsigned char * srcptr;
+        
+            ximageinfoP->ximageP =
+                XCreateImage(disp, visualP, ddepth, ZPixmap, 0,
+                             NULL, imageP->width, imageP->height, 8, 0);
+            MALLOCARRAY(data, imageP->width * imageP->height * dpixlen);
+            if (data == NULL)
+                pm_error("Unable to allocate space for %u x %u x %u image",
+                         imageP->width, imageP->height, dpixlen);
+            ximageinfoP->ximageP->data = (char*)data;
+            destptr = data;
+            srcptr = imageP->data;
+            switch (imageP->type) {
+            case ITRUE: {
+                unsigned int y;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x= 0; x < imageP->width; ++x) {
+                        Pixel const pixval = memToVal(srcptr, imageP->pixlen);
+                        Pixel const newpixval =
+                            redvalue[TRUE_RED(pixval)] |
+                            grnvalue[TRUE_GRN(pixval)] |
+                            bluvalue[TRUE_BLU(pixval)];
+                        valToMem(newpixval, destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            } break;
+            case IRGB: {
+                unsigned int y;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x = 0; x < imageP->width; ++x) {
+                        Pixel const pixval = memToVal(srcptr, imageP->pixlen);
+                        Pixel const newpixval =
+                            redvalue[imageP->rgb.red[pixval] >> 8] |
+                            grnvalue[imageP->rgb.grn[pixval] >> 8] |
+                            bluvalue[imageP->rgb.blu[pixval] >> 8];
+                        valToMem(newpixval, destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            } break;
+            default: /* something's broken */
+                pm_error("INTERNAL ERROR: Unexpected image type %u for "
+                         "DirectColor/TrueColor visual!", imageP->type);
+            }
+            ximageinfoP->ximageP->byte_order = MSBFirst;
+                /* Trust me, I know what I'm talking about */
+        } break;
+
+        default: {
+            
+            /* only IRGB images make it this far. */
+
+            /* If our XImage doesn't have modulus 8 bits per pixel,
+               it's unclear how to pack bits so we instead use an
+               XYPixmap image.  This is slower.
+            */
+
+            if (dbits % 8) {
+                unsigned int const linelen = (imageP->width + 7) / 8;
+                unsigned int const size =
+                    imageP->width * imageP->height * dpixlen;
+                unsigned char * data;
+                unsigned char * destptr;
+                unsigned char * srcptr;
+                Pixel pixval;
+                unsigned int a;
+
+                ximageinfoP->ximageP =
+                    XCreateImage(disp, visualP, ddepth, XYPixmap, 0,
+                                 NULL, imageP->width, imageP->height, 8, 0);
+
+                MALLOCARRAY(data, size);
+                if (data == NULL)
+                    pm_error("Unable to allocate space for %u x %x x %u "
+                             "image", imageP->width, imageP->height, dpixlen);
+                ximageinfoP->ximageP->data = (char*)data;
+                bzero(data, size);
+                ximageinfoP->ximageP->bitmap_bit_order = MSBFirst;
+                ximageinfoP->ximageP->byte_order = MSBFirst;
+                for (a= 0; a < dbits; ++a) {
+                    Pixel const pixmask = 1 << a;
+                    unsigned char * const destdata = 
+                        data + ((ddepth - a - 1) * imageP->height * linelen);
+
+                    unsigned int y;
+
+                    srcptr = imageP->data;
+                    for (y= 0; y < imageP->height; ++y) {
+                        unsigned int x;
+                        unsigned char mask;
+                        destptr = destdata + (y * linelen);
+                        *destptr = 0;
+                        mask = 0x80;
+                        for (x= 0; x < imageP->width; ++x) {
+                            pixval = memToVal(srcptr, imageP->pixlen);
+                            srcptr += imageP->pixlen;
+                            if (colorIndex[pixval] & pixmask)
+                                *destptr |= mask;
+                            mask >>= 1;
+                            if (mask == 0) {
+                                mask = 0x80;
+                                ++destptr;
+                            }
+                        }
+                    }
+                }
+            } else {
+                unsigned int const dpixlen =
+                    (ximageinfoP->ximageP->bits_per_pixel + 7) / 8;
+
+                unsigned char * data;
+                unsigned char * srcptr;
+                unsigned char * destptr;
+                unsigned int y;
+
+                ximageinfoP->ximageP =
+                    XCreateImage(disp, visualP, ddepth, ZPixmap, 0,
+                                 NULL, imageP->width, imageP->height, 8, 0);
+
+                MALLOCARRAY(data, imageP->width * imageP->height * dpixlen);
+                if (data == NULL)
+                    pm_error("Failed to allocate space for %u x %u x %u image",
+                             imageP->width, imageP->height, dpixlen);
+                ximageinfoP->ximageP->data = (char*)data;
+                ximageinfoP->ximageP->byte_order= MSBFirst;
+                    /* Trust me, i know what I'm talking about */
+                srcptr = imageP->data;
+                destptr = data;
+                for (y= 0; y < imageP->height; ++y) {
+                    unsigned int x;
+                    for (x= 0; x < imageP->width; x++) {
+                        valToMem(colorIndex[memToVal(srcptr, imageP->pixlen)],
+                                 destptr, dpixlen);
+                        srcptr += imageP->pixlen;
+                        destptr += dpixlen;
+                    }
+                }
+            }
+        } break;
+        }   
+    } break;
+    }
+    if (verbose)
+        pm_message("done");
+}
+
+
+
+XImageInfo *
+imageToXImage(Display *    const disp,
+              int          const scrn,
+              Visual *     const visualP, /* visual to use */
+              unsigned int const ddepth, /* depth of the visual to use */
+              Image *      const origImageP,
+              bool         const userWantsPrivateCmap,
+              bool         const userWantsFit,
+              bool         const verbose) {
+
+    XImageInfo * ximageinfoP;
+    Image * imageP;
+    Pixel * colorIndex;
+    Pixel * redvalue;
+    Pixel * grnvalue;
+    Pixel * bluvalue;
+
+    assertGoodImage(origImageP);
+  
+    MALLOCVAR_NOFAIL(ximageinfoP);
+    ximageinfoP->disp = disp;
+    ximageinfoP->scrn = scrn;
+    ximageinfoP->depth = 0;
+    ximageinfoP->drawable = None;
+    ximageinfoP->foreground = ximageinfoP->background = 0;
+    ximageinfoP->gc = NULL;
+    ximageinfoP->ximageP = NULL;
+  
+    makeUsableVisual(origImageP, visualP, ddepth, &imageP);
+
+    assertGoodImage(imageP);
+
+    doColorAllocation(ximageinfoP, disp, scrn, visualP, imageP,
+                      userWantsPrivateCmap, userWantsFit, verbose,
+                      &colorIndex, &redvalue, &grnvalue, &bluvalue);
+
+    makeXImage(ximageinfoP, disp, scrn, visualP, ddepth, imageP,
+               colorIndex, redvalue, grnvalue, bluvalue, verbose);
+
+    if (colorIndex)
+        free(colorIndex);
+    if (redvalue) {
+        free(redvalue);
+        free(grnvalue);
+        free(bluvalue);
+    }
+    if (imageP != origImageP)
+        freeImage(imageP);
+    
+    return ximageinfoP;
+}
+
+
+
+void
+sendXImage(XImageInfo * const ximageinfoP,
+           int          const src_x,
+           int          const src_y,
+           int          const dst_x,
+           int          const dst_y,
+           unsigned int const w,
+           unsigned int const h) {
+/*----------------------------------------------------------------------------
+  Given an XImage and a drawable, move a rectangle from the Ximage
+  to the drawable.
+-----------------------------------------------------------------------------*/
+    XGCValues gcv;
+
+    /* build and cache the GC */
+    
+    if (!ximageinfoP->gc) {
+        gcv.function = GXcopy;
+        if (ximageinfoP->ximageP->depth == 1) {
+            gcv.foreground = ximageinfoP->foreground;
+            gcv.background= ximageinfoP->background;
+            ximageinfoP->gc =
+                XCreateGC(ximageinfoP->disp, ximageinfoP->drawable,
+                          GCFunction | GCForeground | GCBackground,
+                          &gcv);
+        }else
+            ximageinfoP->gc =
+                XCreateGC(ximageinfoP->disp, ximageinfoP->drawable,
+                          GCFunction, &gcv);
+    }
+    
+    XPutImage(ximageinfoP->disp, ximageinfoP->drawable, ximageinfoP->gc,
+              ximageinfoP->ximageP, src_x, src_y, dst_x, dst_y, w, h);
+}
+
+
+
+void
+freeXImage(Image *      const imageP,
+           XImageInfo * const ximageinfoP) {
+/*----------------------------------------------------------------------------
+  free up anything cached in the local Ximage structure.
+-----------------------------------------------------------------------------*/
+    if (ximageinfoP->gc)
+        XFreeGC(ximageinfoP->disp, ximageinfoP->gc);
+    free(ximageinfoP->ximageP->data);
+    ximageinfoP->ximageP->data = NULL;
+    XDestroyImage(ximageinfoP->ximageP);
+
+    free(ximageinfoP);
+}
diff --git a/other/pamx/send.h b/other/pamx/send.h
new file mode 100644
index 00000000..203b99c7
--- /dev/null
+++ b/other/pamx/send.h
@@ -0,0 +1,38 @@
+#ifndef SEND_H_INCLUDED
+#define SEND_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+#include "pm_c_util.h"
+
+struct Image;
+
+void
+sendXImage(XImageInfo * const ximageinfoP,
+           int          const src_x,
+           int          const src_y,
+           int          const dst_x,
+           int          const dst_y,
+           unsigned int const w,
+           unsigned int const h);
+
+void
+freeXImage(struct Image * const imageP,
+           XImageInfo *   const ximageinfoP);
+
+XImageInfo *
+imageToXImage(Display *      const disp,
+              int            const scrn,
+              Visual *       const visualP, /* visual to use */
+              unsigned int   const ddepth, /* depth of the visual to use */
+              struct Image * const origImageP,
+              bool           const userWantsPrivateCmap,
+              bool           const userWantsFit,
+              bool           const verbose);
+
+Pixmap
+ximageToPixmap(Display *    const disp,
+               Window       const parent,
+               XImageInfo * const ximageinfo);
+
+#endif
diff --git a/other/pamx/valtomem.h b/other/pamx/valtomem.h
new file mode 100644
index 00000000..e27ce0df
--- /dev/null
+++ b/other/pamx/valtomem.h
@@ -0,0 +1,65 @@
+/*
+  By Jim Frost 1989.10.02.
+
+  Copyright 1989 Jim Frost.
+  See COPYRIGHT file for copyright information.
+*/
+#ifndef VALTOMEM_H_INCLUDED
+#define VALTOMEM_H_INCLUDED
+
+/* inline these functions for speed.  these only work for {len : 1,2,3,4}.
+ */
+
+#define memToVal(PTR,LEN) \
+  ((LEN) == 1 ? ((unsigned long)(*((unsigned char *)PTR))) : \
+   ((LEN) == 3 ? ((unsigned long) \
+          (*(unsigned char *)(PTR) << 16) | \
+          (*((unsigned char *)(PTR) + 1) << 8) | \
+          (*((unsigned char *)(PTR) + 2))) : \
+    ((LEN) == 2 ? ((unsigned long) \
+           (*(unsigned char *)(PTR) << 8) | \
+           (*((unsigned char *)(PTR) + 1))) : \
+     ((unsigned long)((*(unsigned char *)(PTR) << 24) | \
+              (*((unsigned char *)(PTR) + 1) << 16) | \
+              (*((unsigned char *)(PTR) + 2) << 8) | \
+              (*((unsigned char *)(PTR) + 3)))))))
+
+#define memToValLSB(PTR,LEN) \
+  ((LEN) == 1 ? ((unsigned long)(*(unsigned char *)(PTR))) : \
+   ((LEN) == 3 ? ((unsigned long) \
+          (*(unsigned char *)(PTR)) | \
+          (*((unsigned char *)(PTR) + 1) << 8) | \
+          (*((unsigned char *)(PTR) + 2) << 16)) : \
+    ((LEN) == 2 ? ((unsigned long) \
+           (*(unsigned char *)(PTR)) | (*((unsigned char *)(PTR) + 1) << 8)) : \
+     ((unsigned long)((*(unsigned char *)(PTR)) | \
+              (*((unsigned char *)(PTR) + 1) << 8) | \
+              (*((unsigned char *)(PTR) + 2) << 16) | \
+              (*((unsigned char *)(PTR) + 3) << 24))))))
+
+#define valToMem(VAL,PTR,LEN) \
+  ((LEN) == 1 ? (*(unsigned char *)(PTR) = ((unsigned int)(VAL) & 0xff)) : \
+   ((LEN) == 3 ? (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+          ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+          ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff))) : \
+    ((LEN) == 2 ? (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+           ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff))) : \
+     (((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff000000) >> 24), \
+      ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+      ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+      ((*((unsigned char *)(PTR) + 3)) = ((unsigned int)(VAL) & 0xff))))))
+
+#define valToMemLSB(VAL,PTR,LEN) \
+  ((LEN) == 1 ? (*(unsigned char *)(PTR) = ((unsigned int)(VAL) & 0xff)) : \
+   ((LEN) == 3 ? (((*(unsigned char *)(PTR) + 2) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+          ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+          ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff))) : \
+    ((LEN) == 2 ? (((*((unsigned char *)(PTR) + 1) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+            ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff)))) : \
+     (((*((unsigned char *)(PTR) + 3)) = ((unsigned int)(VAL) & 0xff000000) >> 24), \
+      ((*((unsigned char *)(PTR) + 2)) = ((unsigned int)(VAL) & 0xff0000) >> 16), \
+      ((*((unsigned char *)(PTR) + 1)) = ((unsigned int)(VAL) & 0xff00) >> 8), \
+      ((*(unsigned char *)(PTR)) = ((unsigned int)(VAL) & 0xff))))))
+
+
+#endif
diff --git a/other/pamx/window.c b/other/pamx/window.c
new file mode 100644
index 00000000..1a6e510b
--- /dev/null
+++ b/other/pamx/window.c
@@ -0,0 +1,1209 @@
+/*
+   Functions to allocate and deallocate structures and structure data
+ 
+   By Jim Frost 1989.10.03, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include <assert.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/Xdefs.h>  /* Needed by Xutil.h */
+#include <X11/X.h>      /* Needed by Xutil.h */
+#include <X11/Xlib.h>   /* Needed by Xutil.h */
+#include <X11/Xutil.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pm.h"
+#include "ximageinfo.h"
+#include "send.h"
+#include "image.h"
+#include "window.h"
+
+/* A viewer object is something in which you can display an image.  You
+   can display multiple images in the same viewer, sequentially.
+
+   The viewer has a permanent window, called the viewport.  When you display
+   an image, it has a second window, called the image window, which is a
+   child of the viewport.
+*/
+
+struct viewer {
+    Display *    dispP;
+    int          scrn;
+    Window       imageWin;
+    Window       viewportWin;
+    Colormap     imageColormap;
+    Cursor       cursor;
+    unsigned int xpos, ypos;
+    unsigned int width, height;
+    bool         fullscreen;
+    bool         userChoseGeometry;
+    Atom         deleteAtom;
+    bool         blank;
+        /* I'm just guessing, but it seems that a "paint" operation is
+           necessary the first time a viewport is used, but not when
+           displaying a new image in an old viewport.  I assume that's
+           because the new viewport is blank, and that's what this
+           value means.
+        */
+    Pixmap       pixmap;
+};
+
+
+
+static void
+setXloadimageClassHint(Display * const dispP,
+                       Window    const window) {
+
+    XClassHint classhint;
+
+    classhint.res_class = (char*)"Xloadimage";
+    classhint.res_name  = (char*)"xloadimage";
+
+    XSetClassHint(dispP, window, &classhint);
+}
+
+
+
+static void
+setDeleteWindow(viewer * const viewerP) {
+
+    Atom const protoAtom = XInternAtom(viewerP->dispP, "WM_PROTOCOLS", False);
+    
+    viewerP->deleteAtom = XInternAtom(viewerP->dispP,
+                                      "WM_DELETE_WINDOW", False);
+    
+    if ((protoAtom != None) && (viewerP->deleteAtom != None))
+        XChangeProperty(viewerP->dispP, viewerP->viewportWin,
+                        protoAtom, XA_ATOM, 32, PropModeReplace,
+                        (unsigned char *)&viewerP->deleteAtom, 1);
+}
+
+
+
+static void
+getInitialViewerGeometry(const char *   const geometryString,
+                         bool           const fullscreen,
+                         Display *      const dispP,
+                         int            const scrn,
+                         unsigned int * const xposP,
+                         unsigned int * const yposP,
+                         unsigned int * const widthP,
+                         unsigned int * const heightP,
+                         bool *         const userChoseP) {
+
+    unsigned int const defaultWidth  = 10;
+    unsigned int const defaultHeight = 10;
+
+    if (fullscreen) {
+        *widthP  = DisplayWidth(dispP, scrn);
+        *heightP = DisplayHeight(dispP, scrn);
+        *xposP   = 0;
+        *yposP   = 0;
+        *userChoseP = TRUE;
+    } else if (geometryString) {
+        const char * defGeom;
+        asprintfN(&defGeom, "%ux%u+0+0", defaultWidth, defaultHeight);
+        XGeometry(dispP, scrn, geometryString, defGeom, 0, 1, 1, 0, 0,
+                  (int *)xposP, (int *)yposP,
+                  (int *)widthP, (int *)heightP);
+        strfree(defGeom);
+        *userChoseP = TRUE;
+    } else {
+        *widthP     = defaultWidth;
+        *heightP    = defaultHeight;
+        *xposP      = 0;
+        *yposP      = 0;
+        *userChoseP = FALSE;
+    }
+}
+
+
+
+void
+createViewer(viewer **     const viewerPP,
+             Display *     const dispP,
+             int           const scrn,
+             const char *  const geometryString,
+             bool          const fullscreen) {
+
+    viewer * viewerP;
+    
+    XSetWindowAttributes  swa_view;
+
+    MALLOCVAR_NOFAIL(viewerP);
+
+    viewerP->dispP       = dispP;
+    viewerP->scrn        = scrn;
+    viewerP->fullscreen  = fullscreen;
+    viewerP->imageWin    = 0;
+    viewerP->viewportWin = 0;
+
+    getInitialViewerGeometry(geometryString, fullscreen, dispP, scrn,
+                             &viewerP->xpos, &viewerP->ypos,
+                             &viewerP->width, &viewerP->height,
+                             &viewerP->userChoseGeometry);
+
+    viewerP->cursor = XCreateFontCursor(dispP, XC_watch);
+
+    swa_view.background_pixel = WhitePixel(dispP, scrn);
+    swa_view.backing_store    = NotUseful;
+    swa_view.cursor           = viewerP->cursor;
+    swa_view.event_mask       =
+        ButtonPressMask | Button1MotionMask | KeyPressMask |
+        StructureNotifyMask | EnterWindowMask | LeaveWindowMask;
+    swa_view.save_under       = FALSE;
+    
+    viewerP->viewportWin =
+        XCreateWindow(dispP, RootWindow(dispP, scrn),
+                      viewerP->xpos, viewerP->ypos,
+                      viewerP->width, viewerP->height, 0,
+                      DefaultDepth(dispP, scrn), InputOutput,
+                      DefaultVisual(dispP, scrn),
+                      CWBackingStore | CWBackPixel | CWCursor |
+                      CWEventMask | CWSaveUnder,
+                      &swa_view);
+
+    setXloadimageClassHint(viewerP->dispP, viewerP->viewportWin);
+    
+    setDeleteWindow(viewerP);
+
+    viewerP->blank = TRUE;
+
+    *viewerPP = viewerP;
+}
+
+
+
+static void
+determineRepaintStrategy(viewer  *    const viewerP,
+                         bool         const userWantsPixmap,
+                         bool         const verbose,
+                         XImageInfo * const ximageinfoP,
+                         Pixmap *     const pixmapP) {
+                        
+    /* Decide how we're going to handle repaints.  We have three modes:
+       use backing-store, use background pixmap, and use exposures.
+       If the server supports backing-store, we enable it and use it.
+       This really helps servers which are memory constrained.  If the
+       server does not have backing-store, we try to send the image to
+       a pixmap and use that as backing-store.  If that fails, we use
+       exposures to blit the image (which is ugly but it works).
+       
+       'use_pixmap' forces background pixmap mode, which may improve
+       performance.
+    */
+
+    ximageinfoP->drawable = viewerP->imageWin;
+    if ((DoesBackingStore(ScreenOfDisplay(viewerP->dispP, viewerP->scrn)) ==
+         NotUseful) ||
+        userWantsPixmap) {
+        *pixmapP = ximageToPixmap(viewerP->dispP, viewerP->imageWin,
+                                  ximageinfoP);
+        if (*pixmapP == None && verbose)
+            pm_message("Cannot create image in server; "
+                       "repaints will be ugly!");
+    } else
+        *pixmapP = None;
+}
+
+
+
+static void
+setImageWindowAttr(Display * const dispP,
+                   int       const scrn,
+                   Window    const imageWindow,
+                   Colormap  const cmap,
+                   Pixmap    const pixmap) {
+
+    /* build window attributes for the image window */
+
+    XSetWindowAttributes swa_img;
+    unsigned int         wa_mask_img;
+
+    swa_img.bit_gravity  = NorthWestGravity;
+    swa_img.save_under   = FALSE;
+    swa_img.colormap     = cmap;
+    swa_img.border_pixel = 0;
+
+    wa_mask_img = 0;  /* initial value */
+
+    if (pixmap == None) {
+        /* No pixmap.  Must paint over the wire.  Ask for BackingStore
+           to cut down on the painting.  But, ask for Exposures so we can
+           paint both viewables and backingstore.
+        */
+
+        swa_img.background_pixel = WhitePixel(dispP,scrn);
+        wa_mask_img |= CWBackPixel;
+        swa_img.event_mask = ExposureMask;
+        wa_mask_img |= CWEventMask;
+        swa_img.backing_store = WhenMapped;
+        wa_mask_img |= CWBackingStore;
+    } else {
+        /* We have a pixmap so tile the window.  to move the image we only
+           have to move the window and the server should do the rest.
+         */
+
+        swa_img.background_pixmap = pixmap;
+        wa_mask_img |= CWBackPixmap;
+        swa_img.event_mask = 0; /* no exposures please */
+        wa_mask_img |= CWEventMask;
+        swa_img.backing_store = NotUseful;
+        wa_mask_img |= CWBackingStore;
+    }
+    XChangeWindowAttributes(dispP, imageWindow, wa_mask_img, &swa_img);
+}
+
+
+
+static void
+createImageWindow(viewer *      const viewerP,
+                  XImageInfo *  const ximageInfoP,
+                  Image *       const imageP,
+                  Visual *      const visualP,
+                  bool          const userWantsPixmap,
+                  bool          const verbose) {
+
+    XSetWindowAttributes  swa_img;
+
+    swa_img.bit_gravity  = NorthWestGravity;
+    swa_img.save_under   = FALSE;
+    swa_img.colormap     = ximageInfoP->cmap;
+    swa_img.border_pixel = 0;
+    viewerP->imageWin = XCreateWindow(viewerP->dispP, viewerP->viewportWin,
+                                      viewerP->xpos, viewerP->ypos,
+                                      imageP->width, imageP->height, 0,
+                                      ximageInfoP->depth, InputOutput, visualP,
+                                      CWBitGravity | CWColormap | CWSaveUnder |
+                                      CWBorderPixel, &swa_img);
+    viewerP->imageColormap = ximageInfoP->cmap;
+    setXloadimageClassHint(viewerP->dispP, viewerP->imageWin);
+
+    determineRepaintStrategy(viewerP, userWantsPixmap, verbose, ximageInfoP,
+                             &viewerP->pixmap);
+
+    setImageWindowAttr(viewerP->dispP, viewerP->scrn,
+                       viewerP->imageWin, ximageInfoP->cmap,
+                       viewerP->pixmap);
+}
+
+
+
+static void
+destroyImageWindow(viewer * const viewerP) {
+    
+    if (viewerP->imageWin) {
+        if (viewerP->imageColormap &&
+            (viewerP->imageColormap != DefaultColormap(viewerP->dispP, viewerP->scrn)))
+            XFreeColormap(viewerP->dispP, viewerP->imageColormap);
+        XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+    }
+}
+                       
+
+
+static void
+changeCursor(viewer *      const viewerP,
+             unsigned int  const imageWidth,
+             unsigned int  const imageHeight) {
+
+    Cursor cursor;
+    XSetWindowAttributes swa;
+
+    if ((viewerP->width >= imageWidth) && (viewerP->height >= imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_icon);
+    else if ((viewerP->width < imageWidth) && (viewerP->height >= imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_sb_h_double_arrow);
+    else if ((viewerP->width >= imageWidth) && (viewerP->height < imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_sb_v_double_arrow);
+    else
+        cursor = XCreateFontCursor(viewerP->dispP, XC_fleur);
+
+    swa.cursor = cursor;
+    XChangeWindowAttributes(viewerP->dispP, viewerP->viewportWin,
+                            CWCursor, &swa);
+
+    XFreeCursor(viewerP->dispP, viewerP->cursor);
+
+    viewerP->cursor = cursor;
+}
+
+
+
+static void
+placeImage(viewer * const viewerP,
+           int      const width,
+           int      const height,
+           int *    const rxP,     /* input and output */
+           int *    const ryP) {   /* input and output */
+
+    int pixx, pixy;
+    
+    pixx = *rxP;
+    pixy = *ryP;
+    
+    if (viewerP->width > width)
+        pixx = (viewerP->width - width) / 2;
+    else {
+        if ((pixx < 0) && (pixx + width < viewerP->width))
+            pixx = viewerP->width - width;
+        if (pixx > 0)
+            pixx = 0;
+    }
+    if (viewerP->height > height)
+        pixy = (viewerP->height - height) / 2;
+    else {
+        if ((pixy < 0) && (pixy + height < viewerP->height))
+            pixy = viewerP->height - viewerP->height;
+        if (pixy > 0)
+            pixy = 0;
+    }
+    XMoveWindow(viewerP->dispP, viewerP->imageWin, pixx, pixy);
+
+    *rxP = pixx;
+    *ryP = pixy;
+}
+
+
+
+static void
+blitImage(XImageInfo * const ximageinfoP,
+          unsigned int const width,
+          unsigned int const height,
+          int          const xArg,
+          int          const yArg,
+          int          const wArg,
+          int          const hArg) {
+
+    int w, h;
+    int x, y;
+
+    w = MIN(wArg, width);
+    h = MIN(hArg, height);
+
+    x = xArg;
+    y = yArg;
+
+    if (x < 0) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y, -x, h, False);
+        w -= (0 - x);
+        x = 0;
+    }
+    if (y < 0) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y, w, -y, False);
+        h -= (0 - y);
+        y = 0;
+    }
+    if (x + w > width) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x + width, y, x + w - width, h, False);
+        w -= x + w - width;
+    }
+    if (y + h > height) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y + height, w, y + h - height, False);
+        h -= y + h - height;
+    }
+    sendXImage(ximageinfoP, x, y, x, y, w, h);
+}
+
+
+
+void
+destroyViewer(viewer * const viewerP) {
+/*----------------------------------------------------------------------------
+   Clean up static window.
+-----------------------------------------------------------------------------*/
+    if (viewerP->pixmap != None)
+        XFreePixmap(viewerP->dispP, viewerP->pixmap);
+
+    if (viewerP->imageWin)
+        XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+    viewerP->imageWin = 0;
+    
+    if (viewerP->viewportWin)
+        XDestroyWindow(viewerP->dispP, viewerP->viewportWin);
+    
+    viewerP->viewportWin = 0;
+
+    XFreeCursor(viewerP->dispP, viewerP->cursor);
+
+    free(viewerP);
+}
+
+
+
+static void
+setViewportColormap(viewer *  const viewerP,
+                    Visual *  const visualP) {
+/*----------------------------------------------------------------------------
+  Set the colormap and WM_COLORMAP_WINDOWS properly for the viewport.
+-----------------------------------------------------------------------------*/
+    static Atom cmap_atom = None;
+    XSetWindowAttributes swa;
+    Window cmap_windows[2];
+
+    if (cmap_atom == None)
+        cmap_atom = XInternAtom(viewerP->dispP, "WM_COLORMAP_WINDOWS", False);
+    
+    /* If the visual we're using is the same as the default visual (used by
+       the viewport window) then we can set the viewport window to use the
+       image's colormap.  This keeps most window managers happy.
+    */
+    
+    if (visualP == DefaultVisual(viewerP->dispP, viewerP->scrn)) {
+        swa.colormap = viewerP->imageColormap;
+        XChangeWindowAttributes(viewerP->dispP, viewerP->viewportWin,
+                                CWColormap, &swa);
+        XDeleteProperty(viewerP->dispP, viewerP->viewportWin, cmap_atom);
+    } else {
+        /* Smart window managers can handle it when we use a different colormap
+           in our subwindow so long as we set the WM_COLORMAP_WINDOWS property
+           ala ICCCM.
+        */
+        cmap_windows[0] = viewerP->imageWin;
+        cmap_windows[1] = viewerP->viewportWin;
+        XChangeProperty(viewerP->dispP, viewerP->viewportWin,
+                        cmap_atom, XA_WINDOW, 32,
+                        PropModeReplace, (unsigned char *)cmap_windows, 2);
+    }
+}
+
+
+
+static const char *
+iconName(const char * const s) {
+/*----------------------------------------------------------------------------
+   Return an icon name suitable for an image titled 's'.
+
+   s == NULL means untitled.
+-----------------------------------------------------------------------------*/
+    const char * retval;
+    char buf[BUFSIZ];
+
+    if (!s)
+        STRSCPY(buf, "Unnamed");
+    else {
+        char * t;
+
+        STRSCPY(buf, s);
+        /* strip off stuff following 1st word.  This strips
+           info added by processing functions too.
+        */
+        t = index(buf, ' ');
+        if (t)
+            *t = '\0';
+    
+        /* Strip off leading path.  if you don't use unix-style paths,
+           You might want to change this.
+        */
+    
+        t= rindex(buf, '/');
+        if (t) {
+            char * p;
+            for (p = buf, ++t; *t; ++p, ++t)
+                *p = *t;
+            *p = '\0';
+        }
+        /* look for an extension and strip it off */
+        t = index(buf, '.');
+        if (t)
+            *t = '\0';
+    }
+
+    retval = strdup(buf);
+    if (retval == NULL)
+        pm_error("Out of memory");
+    return retval;
+}
+
+
+
+/* visual class to name table  */
+
+static struct visual_class_name {
+    int          class; /* numerical value of class */
+    const char * name;  /* actual name of class */
+} const VisualClassName[] = {
+    {TrueColor,   "TrueColor"   } ,
+    {DirectColor, "DirectColor" } ,
+    {PseudoColor, "PseudoColor" } ,
+    {StaticColor, "StaticColor" } ,
+    {GrayScale,   "GrayScale"   } ,
+    {StaticGray,  "StaticGray"  } ,
+    {StaticGray,  "StaticGrey"  } ,
+    {-1,          NULL          }
+};
+
+
+
+int
+visualClassFromName(const char * const name) {
+
+    unsigned int a;
+    int class;
+    bool found;
+    
+    for (a = 0, found = FALSE; VisualClassName[a].name; ++a) {
+        if (STRCASEEQ(VisualClassName[a].name, name)) {
+            /* Check for uniqueness.  We special-case StaticGray
+               because we have two spellings but they are unique if
+               we find either.
+            */
+            if (found && class != StaticGray)
+                pm_error("'%s' does not uniquely describe a visual class",
+                         name);
+
+            class = VisualClassName[a].class;
+            found = TRUE;
+        }
+    }
+    if (!found)
+        pm_error("'%s' is not a visual class name", name);
+
+    return class;
+}
+
+
+
+static const char *
+nameOfVisualClass(int const class) {
+
+    unsigned int a;
+    const char * name;
+    bool found;
+
+    for (a = 0, found = FALSE; VisualClassName[a].name; ++a) {
+        if (VisualClassName[a].class == class) {
+            name = VisualClassName[a].name;
+            found = TRUE;
+        }
+    }
+    if (found)
+        return name;
+    else
+        return "[Unknown Visual Class]";
+}
+
+
+
+/* find the best visual of a particular class with a particular depth
+ */
+
+static Visual *
+bestVisualOfClassAndDepth(Display *    const disp,
+                          int          const scrn,
+                          int          const class,
+                          unsigned int const depth) {
+
+    long const vinfoMask =
+        VisualScreenMask | VisualClassMask | VisualDepthMask;
+
+    Visual * best;
+    XVisualInfo template;
+    XVisualInfo * infoP;
+    unsigned int nvisuals;
+
+    best = NULL;  /* initial value */
+
+    template.screen = scrn;
+    template.class  = class;
+    template.depth  = depth;
+    infoP = XGetVisualInfo(disp, vinfoMask, &template, (int*)&nvisuals);
+    if (infoP) {
+        /* There are visuals with this depth */
+
+        /* Not sure what to do if this gives more than one visual of a
+           particular class and depth, so just return the first one.
+        */
+
+        best = infoP->visual;
+        XFree((char *)infoP);
+    }
+    return best;
+}
+
+
+
+static void
+bestVisual(Display *      const disp,
+           int            const scrn,
+           Image *        const imageP,
+           Visual **      const rvisualPP,
+           unsigned int * const rdepthP) {
+/*----------------------------------------------------------------------------
+  Try to determine the best available visual to use for a particular
+  image
+-----------------------------------------------------------------------------*/
+    unsigned int  depth, a;
+    Screen * screen;
+    Visual * visualP;
+    Visual * default_visualP;
+
+    /* Figure out the best depth the server supports.  note that some servers
+       (such as the HP 11.3 server) actually say they support some depths but
+       have no visuals that support that depth.  Seems silly to me ...
+    */
+
+    depth = 0;
+    screen = ScreenOfDisplay(disp, scrn);
+    for (a = 0; a < screen->ndepths; ++a) {
+        if (screen->depths[a].nvisuals &&
+            ((!depth ||
+              ((depth < imageP->depth) && (screen->depths[a].depth > depth)) ||
+              ((screen->depths[a].depth >= imageP->depth) &&
+               (screen->depths[a].depth < depth)))))
+            depth = screen->depths[a].depth;
+    }
+    if (!depth) {
+        /* this shouldn't happen */
+        pm_message("bestVisual: didn't find any depths?!?");
+        depth = DefaultDepth(disp, scrn);
+    }
+    
+    /* given this depth, find the best possible visual
+     */
+    
+    default_visualP = DefaultVisual(disp, scrn);
+    switch (imageP->type) {
+    case ITRUE: {
+        /* If the default visual is DirectColor or TrueColor prioritize such
+           that we use the default type if it exists at this depth
+        */
+
+        if (default_visualP->class == TrueColor) {
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+            if (!visualP)
+                visualP = bestVisualOfClassAndDepth(disp, scrn,
+                                                    DirectColor, depth);
+        } else {
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+            if (!visualP)
+                visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor,
+                                                    depth);
+        }
+        
+        if (!visualP || ((depth <= 8) &&
+                         bestVisualOfClassAndDepth(disp, scrn, PseudoColor,
+                                                   depth)))
+            visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+    } break;
+        
+    case IRGB: {
+        /* if it's an RGB image, we want PseudoColor if we can get it */
+
+        visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+    } break;
+
+    case IBITMAP: {
+        visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+
+        /* It seems pretty wasteful to use a TrueColor or DirectColor visual
+           to display a bitmap (2-color) image, so we look for those last
+        */
+
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+    } break;
+    }
+
+    if (!visualP) { /* this shouldn't happen */
+        pm_message("bestVisual: couldn't find one?!?");
+        depth = DefaultDepth(disp, scrn);
+        visualP = DefaultVisual(disp, scrn);
+    }
+    *rvisualPP = visualP;
+    *rdepthP   = depth;
+}
+
+
+
+static void
+bestVisualOfClass(Display *      const disp,
+                  int            const scrn,
+                  Image *        const image,
+                  int            const visual_class,
+                  Visual **      const rvisual,
+                  unsigned int * const rdepth) {
+/*----------------------------------------------------------------------------
+  Given a visual class, try to find the best visual of that class at
+  the best depth.  Return a null visual and depth if we couldn't find
+  any visual of that type at any depth.
+-----------------------------------------------------------------------------*/
+  Visual       *visual;
+  Screen       *screen;
+  unsigned int  a, b, depth;
+
+  /* loop through depths looking for a visual of a good depth which matches
+   * our visual class.
+   */
+
+  screen= ScreenOfDisplay(disp, scrn);
+  visual= (Visual *)NULL;
+  depth= 0;
+  for (a= 0; a < screen->ndepths; a++) {
+    for (b= 0; b < screen->depths[a].nvisuals; b++) {
+      if ((screen->depths[a].visuals[b].class == visual_class) &&
+      (!depth ||
+       ((depth < image->depth) && (screen->depths[a].depth > depth)) ||
+       ((screen->depths[a].depth >= image->depth) &&
+        (screen->depths[a].depth < depth)))) {
+    depth= screen->depths[a].depth;
+    visual= &(screen->depths[a].visuals[b]);
+      }
+    }
+  }
+  *rvisual= visual;
+  *rdepth= depth;
+}
+
+
+
+static void
+getImageDispDimensions(viewer *       const viewerP,
+                       Image *        const imageP,
+                       unsigned int * const widthP,
+                       unsigned int * const heightP) {
+
+    if (viewerP->userChoseGeometry) {
+        *widthP  = viewerP->width;
+        *heightP = viewerP->height;
+    } else {
+        unsigned int const displayWidth = 
+            DisplayWidth(viewerP->dispP, viewerP->scrn);
+        unsigned int const displayHeight =
+            DisplayHeight(viewerP->dispP, viewerP->scrn);
+
+        /* We don't use more than 90% of display real estate unless user
+           explicitly asked for it.
+        */
+        *widthP = MIN(imageP->width, (unsigned)(displayWidth * 0.9));
+        *heightP = MIN(imageP->height, (unsigned)(displayHeight * 0.9));
+    }
+}
+
+
+
+static void
+getVisualAndDepth(Image *        const imageP,
+                  Display *      const dispP,
+                  int            const scrn,
+                  bool           const fit,
+                  bool           const visualSpec,
+                  unsigned int   const visualClass,
+                  Visual **      const visualPP,
+                  unsigned int * const depthP) {
+
+
+    /* If the user told us to fit the colormap, we must use the default
+       visual.
+    */
+
+    if (fit) {
+        *visualPP = DefaultVisual(dispP, scrn);
+        *depthP   = DefaultDepth(dispP, scrn);
+    } else {
+        Visual * visualP;
+        unsigned int depth;
+
+        visualP = NULL;
+        if (!visualSpec) {
+            /* Try to pick the best visual for the image. */
+
+            bestVisual(dispP, scrn, imageP, &visualP, &depth);
+        } else {
+            /* Try to find a visual of the specified class */
+
+            bestVisualOfClass(dispP, scrn, imageP, visualClass,
+                              &visualP, &depth);
+            if (!visualP) {
+                bestVisual(dispP, scrn, imageP, &visualP, &depth);
+                pm_message("Server does not provide %s visual, using %s",
+                           nameOfVisualClass(visualClass),
+                           nameOfVisualClass(visualP->class));
+            }
+        }
+        *visualPP = visualP;
+        *depthP   = depth;
+    }
+}
+
+
+
+static void
+setNormalSizeHints(viewer *     const viewerP,
+                   Image *      const imageP) {
+    
+    XSizeHints sh;
+    
+    sh.width  = viewerP->width;
+    sh.height = viewerP->height;
+    if (viewerP->fullscreen) {
+        sh.min_width  = sh.max_width  = viewerP->width;
+        sh.min_height = sh.max_height = viewerP->height;
+    } else {
+        sh.min_width  = 1;
+        sh.min_height = 1;
+        sh.max_width  = imageP->width;
+        sh.max_height = imageP->height;
+    }
+    sh.width_inc  = 1;
+    sh.height_inc = 1;
+
+    sh.flags = PMinSize | PMaxSize | PResizeInc;
+    if (viewerP->userChoseGeometry)
+        sh.flags |= USSize;
+    else
+        sh.flags |= PSize;
+
+    if (viewerP->userChoseGeometry) {
+        sh.x = viewerP->xpos;
+        sh.y = viewerP->ypos;
+        sh.flags |= USPosition;
+    }
+    XSetNormalHints(viewerP->dispP, viewerP->viewportWin, &sh);
+
+    sh.min_width  = sh.max_width;
+    sh.min_height = sh.max_height;
+    XSetNormalHints(viewerP->dispP, viewerP->imageWin, &sh);
+        /* Image doesn't shrink */
+}
+
+
+
+static void
+setWMHints(viewer * const viewerP) {
+
+    XWMHints wmh;
+
+    wmh.input = TRUE;
+    wmh.flags = InputHint;
+    XSetWMHints(viewerP->dispP, viewerP->viewportWin, &wmh);
+}
+
+
+#define CTL_C '\003'
+
+typedef enum exitReason {
+    EXIT_NONE,
+    EXIT_QUIT,
+    EXIT_WM_KILL,
+    EXIT_DESTROYED
+} exitReason;
+
+
+
+static void
+run(viewer *     const viewerP,
+    Image *      const imageP,
+    XImageInfo * const ximageInfoP,
+    int          const initPixx,
+    int          const initPixy,
+    bool         const install,
+    exitReason * const exitReasonP) {
+
+    int lastx, lasty;
+    int pixx, pixy;
+    union {
+        XEvent              event;
+        XAnyEvent           any;
+        XButtonEvent        button;
+        XKeyEvent           key;
+        XConfigureEvent     configure;
+        XExposeEvent        expose;
+        XMotionEvent        motion;
+        XResizeRequestEvent resize;
+        XClientMessageEvent message;
+    } event;
+    exitReason exitReason;
+
+    lastx = lasty = -1;
+    pixx   = initPixx;
+    pixy   = initPixy;
+
+    exitReason = EXIT_NONE;  /* No reason to exit yet */
+
+    while (exitReason == EXIT_NONE) {
+        XNextEvent(viewerP->dispP, &event.event);
+
+        switch (event.any.type) {
+        case ButtonPress:
+            if (event.button.button == 1) {
+                lastx = event.button.x;
+                lasty = event.button.y;
+            }
+            break;
+
+        case KeyPress: {
+            char buf[128];
+            KeySym ks;
+            XComposeStatus status;
+            Status rc;
+            
+            rc = XLookupString(&event.key, buf, 128, &ks, &status);
+            if (rc == 1) {
+                char const ret = buf[0];
+                char const lowerRet = tolower(ret);
+
+                switch (lowerRet) {
+                case CTL_C:
+                case 'q':
+                    exitReason = EXIT_QUIT;
+                    break;
+                }
+            }
+        } break;
+
+        case MotionNotify: {
+            int mousex, mousey;
+
+            if (imageP->width  <= viewerP->width &&
+                imageP->height <= viewerP->height) {
+                /* we're AT&T */
+            } else {
+                mousex = event.button.x;
+                mousey = event.button.y;
+                while (XCheckTypedEvent(viewerP->dispP, MotionNotify,
+                                        (XEvent*)&event)) {
+                    mousex = event.button.x;
+                    mousey = event.button.y;
+                }
+                pixx -= (lastx - mousex);
+                pixy -= (lasty - mousey);
+                lastx = mousex;
+                lasty = mousey;
+                placeImage(viewerP, imageP->width, imageP->height,
+                           &pixx, &pixy);
+            }
+        } break;
+
+        case ConfigureNotify:
+            viewerP->width  = event.configure.width;
+            viewerP->height = event.configure.height;
+
+            placeImage(viewerP, imageP->width, imageP->height,
+                       &pixx, &pixy);
+
+            /* Configure the cursor to indicate which directions we can drag
+             */
+
+            changeCursor(viewerP, imageP->width, imageP->height);
+            break;
+
+        case DestroyNotify:
+            exitReason = EXIT_DESTROYED;
+            break;
+
+        case Expose:
+            blitImage(ximageInfoP, imageP->width, imageP->height,
+                      event.expose.x, event.expose.y,
+                      event.expose.width, event.expose.height);
+            break;
+
+        case EnterNotify:
+            if (install)
+                XInstallColormap(viewerP->dispP, ximageInfoP->cmap);
+            break;
+
+        case LeaveNotify:
+            if (install)
+                XUninstallColormap(viewerP->dispP, ximageInfoP->cmap);
+            break;
+
+        case ClientMessage:
+            /* If we get a client message for the viewport window
+               which has the value of the delete atom, it means the
+               window manager wants us to die.
+            */
+
+            if ((event.message.window == viewerP->viewportWin) &&
+                (event.message.data.l[0] == viewerP->deleteAtom)) {
+                exitReason = EXIT_WM_KILL;
+            }
+            break;
+        }
+    }
+    *exitReasonP = exitReason;
+}
+
+
+
+static int
+retvalueFromExitReason(exitReason const exitReason) {
+
+    int retval;
+
+    switch (exitReason) {
+    case EXIT_NONE:      assert(false); break;
+    case EXIT_QUIT:      retval =  0;     break;
+    case EXIT_WM_KILL:   retval = 10;     break;
+    case EXIT_DESTROYED: retval = 20;     break;
+    }
+    return retval;
+}
+
+
+
+static void
+resizeViewer(viewer *     const viewerP,
+             unsigned int const newWidth,
+             unsigned int const newHeight) {
+    
+    if (viewerP->width != newWidth || viewerP->height != newHeight) {
+        XResizeWindow(viewerP->dispP, viewerP->viewportWin,
+                      newWidth, newHeight);
+        viewerP->width  = newWidth;
+        viewerP->height = newHeight;
+    }
+}
+
+
+
+void
+displayInViewer(viewer *       const viewerP,
+                struct Image * const imageP,
+                bool           const install,
+                bool           const userWantsPrivateCmap,
+                bool           const fit,
+                bool           const userWantsPixmap,
+                bool           const visualSpec,
+                unsigned int   const visualClass,
+                const char *   const title,
+                bool           const verbose,
+                int *          const retvalP) {
+    
+    XImageInfo *     ximageInfoP;
+    Visual *         visual;
+    unsigned int     depth;
+    bool             privateCmap;
+    int              pixx, pixy;
+    exitReason       exitReason;
+    unsigned int     windowWidth, windowHeight;
+
+    pixx = -1; pixy = -1;  /* initial values */
+
+    getImageDispDimensions(viewerP, imageP, &windowWidth, &windowHeight);
+
+    resizeViewer(viewerP, windowWidth, windowHeight);
+
+    getVisualAndDepth(imageP, viewerP->dispP, viewerP->scrn,
+                      fit, visualSpec, visualClass,
+                      &visual, &depth);
+    
+    if (verbose && (visual != DefaultVisual(viewerP->dispP, viewerP->scrn)))
+        pm_message("Using %s visual", nameOfVisualClass(visual->class));
+
+    /* If we're in slideshow mode and the user told us to fit the colormap,
+       free it here.
+    */
+
+    if (viewerP->blank) {
+        /* For the first image we display we can use the default cmap.
+           subsequent images use a private colormap (unless they're
+           bitmaps) so we don't get color erosion when switching
+           images.
+        */
+
+        if (fit) {
+            XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+            viewerP->imageWin = 0;
+            viewerP->imageColormap = 0;
+            privateCmap = userWantsPrivateCmap;
+        } else if (!BITMAPP(imageP))
+            privateCmap = TRUE;
+    } else
+        privateCmap = userWantsPrivateCmap;
+
+    ximageInfoP = imageToXImage(viewerP->dispP, viewerP->scrn, visual, depth,
+                                imageP, privateCmap, fit, verbose);
+    if (!ximageInfoP)
+        pm_error("INTERNAL ERROR: Cannot convert Image to XImage");
+
+    destroyImageWindow(viewerP);
+
+    createImageWindow(viewerP, ximageInfoP, imageP, visual,
+                      userWantsPixmap, verbose);
+
+    if (title)
+        XStoreName(viewerP->dispP, viewerP->viewportWin, title);
+    else
+        XStoreName(viewerP->dispP, viewerP->viewportWin, "Unnamed");
+
+    {
+        const char * const name = iconName(title);
+        XSetIconName(viewerP->dispP, viewerP->viewportWin, name);
+        strfree(name);
+    }
+    setNormalSizeHints(viewerP, imageP);
+
+    setWMHints(viewerP);
+
+    setViewportColormap(viewerP, visual);
+
+    /* Map (display) windows */
+
+    XMapWindow(viewerP->dispP, viewerP->imageWin);
+    XMapWindow(viewerP->dispP, viewerP->viewportWin);
+
+    /* Start displaying image */
+
+    placeImage(viewerP, imageP->width, imageP->height, &pixx, &pixy);
+    if (!viewerP->blank) {
+        XResizeWindow(viewerP->dispP, viewerP->imageWin,
+                      imageP->width, imageP->height);
+        /* Clear the image window.  Ask for exposure if there is no tile. */
+        XClearArea(viewerP->dispP, viewerP->imageWin, 0, 0, 0, 0,
+                   (viewerP->pixmap == None));
+        viewerP->blank = FALSE;
+    }
+
+    changeCursor(viewerP, imageP->width, imageP->height);
+
+    /* Process X events, continuously */
+    run(viewerP, imageP, ximageInfoP, pixx, pixy, install, &exitReason);
+
+    freeXImage(imageP, ximageInfoP);
+
+    *retvalP = retvalueFromExitReason(exitReason);
+}
diff --git a/other/pamx/window.h b/other/pamx/window.h
new file mode 100644
index 00000000..2efc54b5
--- /dev/null
+++ b/other/pamx/window.h
@@ -0,0 +1,38 @@
+#ifndef WINDOW_H_INCLUDED
+#define WINDOW_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+#include "pm_c_util.h"
+
+struct Image;
+struct viewer;
+typedef struct viewer viewer;
+
+int
+visualClassFromName(const char * const name);
+
+void
+createViewer(viewer **     const viewerPP,
+             Display *     const dispP,
+             int           const scrn,
+             const char *  const geometryString,
+             bool          const fullscreen);
+
+void
+destroyViewer(viewer * const viewerP);
+
+void
+displayInViewer(viewer *       const viewerP,
+                struct Image * const imageP,
+                bool           const install,
+                bool           const userWantsPrivateCmap,
+                bool           const fit,
+                bool           const userWantsPixmap,
+                bool           const visualSpec,
+                unsigned int   const visualClass,
+                const char *   const title,
+                bool           const verbose,
+                int *          const retvalP);
+
+#endif
diff --git a/other/pamx/ximageinfo.h b/other/pamx/ximageinfo.h
new file mode 100644
index 00000000..b0b54ee1
--- /dev/null
+++ b/other/pamx/ximageinfo.h
@@ -0,0 +1,25 @@
+#ifndef XIMAGEINFO_H_INCLUDED
+#define XIMAGEINFO_H_INCLUDED
+
+#include <X11/Xlib.h>
+
+
+
+typedef unsigned long  Pixel;     /* what X thinks a pixel is */
+typedef unsigned short Intensity; /* what X thinks an RGB intensity is */
+
+typedef struct {
+    /* This struct holds the X-client side bits for a rendered image.  */
+    Display * disp;       /* destination display */
+    int       scrn;       /* destination screen */
+    int       depth;      /* depth of drawable we want/have */
+    Drawable  drawable;   /* drawable to send image to */
+    Pixel     foreground;
+        /* foreground and background pixels for mono images */
+    Pixel     background;
+    Colormap  cmap;       /* colormap used for image */
+    GC        gc;         /* cached gc for sending image */
+    XImage *  ximageP;     /* ximage structure */
+} XImageInfo;
+
+#endif