Index: Makefile.in
===================================================================
RCS file: /cvsroot/w3m/w3m/Makefile.in,v
retrieving revision 1.42
diff -u -r1.42 Makefile.in
--- Makefile.in	29 May 2007 12:01:04 -0000	1.42
+++ Makefile.in	13 Dec 2010 13:51:45 -0000
@@ -58,10 +58,12 @@
 	CONF_DIR='$(CONF_DIR)' \
 	RC_DIR='$(RC_DIR)' DESTDIR='$(DESTDIR)' KEYBIND_SRC='$(KEYBIND_SRC)'
 
-IMGCFLAGS = @IMGX11CFLAGS@ @IMGFBCFLAGS@
-IMGLDFLAGS = @IMGX11LDFLAGS@ @IMGFBLDFLAGS@
+IMGCFLAGS = @IMGX11CFLAGS@ @IMGFBCFLAGS@ @IMGWINCFLAGS@
+IMGLDFLAGS = @IMGX11LDFLAGS@ @IMGFBLDFLAGS@ @IMGWINLDFLAGS@
 
 CC0 = @CC@
+CXX = @CXX@
+IMGLINK = @IMGLINK@
 CC = @POSUBST@ $(CC0)
 CPP = @CPP@
 RANLIB=@RANLIB@
@@ -193,7 +195,7 @@
 	$(CC) $(CFLAGS) -DDUMMY -c -o $@ $?
 
 $(IMGDISPLAY): w3mimgdisplay.o $(ALIB) w3mimg/w3mimg.a
-	$(CC) $(CFLAGS) -o $(IMGDISPLAY) w3mimgdisplay.o w3mimg/w3mimg.a $(LDFLAGS) $(LIBS) $(IMGLDFLAGS)
+	$(IMGLINK) $(CFLAGS) -o $(IMGDISPLAY) w3mimgdisplay.o w3mimg/w3mimg.a $(LDFLAGS) $(LIBS) $(IMGLDFLAGS)
 
 w3mimgdisplay.o: w3mimgdisplay.c w3mimg/w3mimg.h
 	$(CC) $(CFLAGS) $(IMGCFLAGS) -o $@ -c $(srcdir)/w3mimgdisplay.c
Index: acinclude.m4
===================================================================
RCS file: /cvsroot/w3m/w3m/acinclude.m4,v
retrieving revision 1.43
diff -u -r1.43 acinclude.m4
--- acinclude.m4	26 Jul 2010 11:24:14 -0000	1.43
+++ acinclude.m4	13 Dec 2010 13:51:45 -0000
@@ -569,6 +569,8 @@
 [AC_SUBST(USE_IMAGE)
  AC_SUBST(USE_W3MIMG_X11)
  AC_SUBST(USE_W3MIMG_FB)
+ AC_SUBST(USE_W3MIMG_WIN)
+ AC_SUBST(IMGLINK)
  AC_SUBST(W3MIMGDISPLAY_SETUID)
  AC_SUBST(INSTALL_W3MIMGDISPLAY)
  INSTALL_W3MIMGDISPLAY='${INSTALL_PROGRAM}'
@@ -583,10 +585,12 @@
  AC_SUBST(IMGX11LDFLAGS)
  AC_SUBST(IMGFBCFLAGS)
  AC_SUBST(IMGFBLDFLAGS)
+ AC_SUBST(IMGWINCFLAGS)
+ AC_SUBST(IMGWINLDFLAGS)
  AC_MSG_CHECKING(if image is enabled)
  AC_ARG_ENABLE(image,
  [  --enable-image[=DEVS]		enable inline image handler for DEVS
-				 DEVS may be comma separeted: x11,fb,fb+s
+				 DEVS may be comma separeted: x11,fb,fb+s,win
 				 default: autodetected.
 				 'no' means disable inline image],,
  [enable_image="yes"])
@@ -600,10 +604,14 @@
 	if test -c /dev/fb0; then
 	  enable_image=x11,fb
         fi;;
+    CYGWIN*)
+	enable_image=x11,win;;
     esac
   fi   
   save_ifs="$IFS"; IFS=",";
-  for img in $enable_image; do
+  set x $enable_image; shift
+  IFS="$save_ifs"
+  for img in "$[]@"; do
     case $img in
       x11) x11=yes;;
       fb)  fb=yes;;
@@ -611,9 +619,9 @@
            AC_DEFINE(W3MIMGDISPLAY_SETUID)
            INSTALL_W3MIMGDISPLAY='${INSTALL} -o root -m 4755 -s'
            AC_DEFINE(INSTALL_W3MIMGDISPLAY, $INSTALL_W3MIMGDISPLAY);;
+      win) win=yes;;
     esac
   done
-  IFS="$save_ifs"
   enable_image=yes
   AC_DEFINE(USE_IMAGE)
   AC_MSG_CHECKING(image library)
@@ -659,6 +667,7 @@
    esac
   done
   IMGTARGETS=""
+  IMGLINK='$(CC)'
   if test x"$with_gtk2" = xyes; then
    AC_W3M_CHECK_VER([GdkPixbuf],
 	[`$PKG_CONFIG --modversion gdk-pixbuf-2.0 2>/dev/null`],
@@ -752,12 +761,23 @@
      AC_MSG_WARN([unable to build w3mimgdisplay with FB support])
    fi
   fi
+  if test x"$win" = xyes; then
+    AC_DEFINE(USE_W3MIMG_WIN)
+    IMGOBJS="$IMGOBJS win/win_w3mimg.o"
+    IMGTARGETS="${IMGTARGETS} win"
+    IMGWINCFLAGS="-I/usr/include/w32api"
+    IMGWINLDFLAGS="-lgdiplus -lgdi32 -luser32"
+    IMGLINK='$(CXX)'
+  fi
   AC_DEFINE(IMGTARGETS, "$IMGTARGETS")
   AC_DEFINE(IMGOBJS, "$IMGOBJS")
   AC_DEFINE(IMGX11CFLAGS, "$IMGX11CFLAGS")
   AC_DEFINE(IMGX11LDFLAGS, "$IMGX11LDFLAGS")
   AC_DEFINE(IMGFBCFLAGS, "$IMGFBCFLAGS")
-  AC_DEFINE(IMGFBLDFLAGS, "$IMGLDFLAGS")
+  AC_DEFINE(IMGFBLDFLAGS, "$IMGFBLDFLAGS")
+  AC_DEFINE(IMGLINK, "$IMGLINK")
+  AC_DEFINE(IMGWINCFLAGS, "$IMGWINCFLAGS")
+  AC_DEFINE(IMGWINLDFLAGS, "$IMGWINLDFLAGS")
  fi])
 # ----------------------------------------------------------------
 # AC_W3M_XFACE
Index: config.h.in
===================================================================
RCS file: /cvsroot/w3m/w3m/config.h.in,v
retrieving revision 1.20
diff -u -r1.20 config.h.in
--- config.h.in	10 Aug 2010 10:16:57 -0000	1.20
+++ config.h.in	13 Dec 2010 13:51:45 -0000
@@ -69,6 +69,7 @@
 #undef USE_IMAGE
 #undef USE_W3MIMG_X11
 #undef USE_W3MIMG_FB
+#undef USE_W3MIMG_WIN
 #undef W3MIMGDISPLAY_SETUID
 #undef USE_IMLIB
 #undef USE_GDKPIXBUF
Index: configure.ac
===================================================================
RCS file: /cvsroot/w3m/w3m/configure.ac,v
retrieving revision 1.10
diff -u -r1.10 configure.ac
--- configure.ac	10 Aug 2010 10:16:57 -0000	1.10
+++ configure.ac	13 Dec 2010 13:51:45 -0000
@@ -17,7 +17,7 @@
 dnl AM_INIT_AUTOMAKE(w3m, 0.4)
 AC_CONFIG_HEADER(config.h)
 dnl Checks for programs.
-dnl AC_PROG_CXX
+AC_PROG_CXX
 AC_PROG_AWK
 AC_PROG_CC
 AC_PROG_CPP
@@ -184,7 +184,7 @@
 scripts/w3mman/Makefile scripts/w3mman/w3mman scripts/w3mman/w3mman.1 \
 scripts/w3mman/w3mman2html.cgi \
 libwc/Makefile \
-w3mimg/Makefile w3mimg/fb/Makefile w3mimg/x11/Makefile \
+w3mimg/Makefile w3mimg/fb/Makefile w3mimg/x11/Makefile w3mimg/win/Makefile \
 w3mhelp-w3m_en.html w3mhelp-w3m_ja.html \
 w3mhelp-lynx_en.html w3mhelp-lynx_ja.html])
 
Index: image.c
===================================================================
RCS file: /cvsroot/w3m/w3m/image.c,v
retrieving revision 1.36
diff -u -r1.36 image.c
--- image.c	7 Jul 2003 15:49:03 -0000	1.36
+++ image.c	13 Dec 2010 13:51:45 -0000
@@ -115,10 +115,13 @@
 static void
 closeImgdisplay()
 {
-    if (Imgdisplay_rf)
-	fclose(Imgdisplay_rf);
     if (Imgdisplay_wf)
 	fclose(Imgdisplay_wf);
+    if (Imgdisplay_rf) {
+	/* sync with the child */
+	getc(Imgdisplay_rf); /* EOF expected */
+	fclose(Imgdisplay_rf);
+    }
     if (Imgdisplay_pid)
 	kill(Imgdisplay_pid, SIGKILL);
     Imgdisplay_rf = NULL;
Index: w3mimgdisplay.c
===================================================================
RCS file: /cvsroot/w3m/w3m/w3mimgdisplay.c,v
retrieving revision 1.18
diff -u -r1.18 w3mimgdisplay.c
--- w3mimgdisplay.c	13 Jul 2003 16:20:42 -0000	1.18
+++ w3mimgdisplay.c	13 Dec 2010 13:51:45 -0000
@@ -65,6 +65,7 @@
     if (defined_test) {
 	printf("%d %d\n", w_op->width - w_op->offset_x,
 	       w_op->height - w_op->offset_y);
+	w_op->close(w_op);
 	exit(0);
     }
 
@@ -75,6 +76,7 @@
 	    if (w_op->get_image_size(w_op, &img, defined_size, &w, &h))
 		printf("%d %d\n", w, h);
 	}
+	w_op->close(w_op);
 	exit(0);
     }
 
Index: w3mimg/Makefile.in
===================================================================
RCS file: /cvsroot/w3m/w3m/w3mimg/Makefile.in,v
retrieving revision 1.10
diff -u -r1.10 Makefile.in
--- w3mimg/Makefile.in	26 Apr 2004 17:00:38 -0000	1.10
+++ w3mimg/Makefile.in	13 Dec 2010 13:51:45 -0000
@@ -11,10 +11,11 @@
 RANLIB=@RANLIB@
 RM=rm
 
-IMGCFLAGS=@IMGX11CFLAGS@ @IMGFBCFLAGS@
+SUBDIRS=fb x11 win
+IMGCFLAGS=@IMGX11CFLAGS@ @IMGFBCFLAGS@ @IMGWINCFLAGS@
 IMGOBJS=@IMGOBJS@
 
-.PHONY: fb x11
+.PHONY: $(SUBDIRS)
 all: @IMGTARGETS@ w3mimg.a
 
 w3mimg.a: $(IMGOBJS)
@@ -24,18 +25,18 @@
 w3mimg.o: w3mimg.c
 	$(CC) $(CFLAGS) -c $<
 
-fb x11:
+$(SUBDIRS):
 	cd $@ && $(MAKE) CC="$(CC)" OPTS="$(OPTS)"
 
 clean:
 	@-$(RM) -f *.o
-	@for dir in fb x11; do \
+	@for dir in $(SUBDIRS); do \
 		(cd $$dir && $(MAKE) clean RM=$(RM)); \
 	 done
 	-$(RM) -f w3mimg.a
 
 distclean: clean
-	for subdir in fb x11; \
+	for subdir in $(SUBDIRS); \
 	do \
 		(cd $$subdir && $(MAKE) distclean); \
 	done
Index: w3mimg/w3mimg.c
===================================================================
RCS file: /cvsroot/w3m/w3m/w3mimg/w3mimg.c,v
retrieving revision 1.5
diff -u -r1.5 w3mimg.c
--- w3mimg/w3mimg.c	6 Nov 2002 03:50:49 -0000	1.5
+++ w3mimg/w3mimg.c	13 Dec 2010 13:51:45 -0000
@@ -14,6 +14,10 @@
     uid_t runner_uid = getuid();
     uid_t owner_uid = geteuid();
 #endif
+#ifdef USE_W3MIMG_WIN
+    if (w_op == NULL)
+	w_op = w3mimg_winopen();
+#endif
 #ifdef USE_W3MIMG_X11
 #ifdef W3MIMGDISPLAY_SETUID
     /* run in user privileges */
Index: w3mimg/w3mimg.h
===================================================================
RCS file: /cvsroot/w3m/w3m/w3mimg/w3mimg.h,v
retrieving revision 1.8
diff -u -r1.8 w3mimg.h
--- w3mimg/w3mimg.h	13 Jul 2003 16:19:10 -0000	1.8
+++ w3mimg/w3mimg.h	13 Dec 2010 13:51:45 -0000
@@ -1,6 +1,13 @@
 /* $Id: w3mimg.h,v 1.8 2003/07/13 16:19:10 ukai Exp $ */
+#ifndef W3MIMG_W3MIMG_H
+#define W3MIMG_W3MIMG_H
+
 #include "config.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #ifdef USE_W3MIMG_FB
 #include "w3mimg/fb/fb.h"
 #include "w3mimg/fb/fb_img.h"
@@ -42,5 +49,13 @@
 #ifdef USE_W3MIMG_FB
 extern w3mimg_op *w3mimg_fbopen();
 #endif
+#ifdef USE_W3MIMG_WIN
+extern w3mimg_op *w3mimg_winopen();
+#endif
 
 extern w3mimg_op *w3mimg_open();
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* W3MIMG_W3MIMG_H */
--- /dev/null	Mon Dec 13 20:59:00 2010
+++ w3mimg/win/Makefile.in	Mon Dec 13 20:54:42 2010
@@ -0,0 +1,31 @@
+#
+# w3mimg/win/Makefile
+#
+#
+@SET_MAKE@
+SHELL=@SHELL@
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = $(srcdir):.
+CFLAGS=$(OPTS) -I../.. -I$(top_srcdir) -I$(srcdir) @CFLAGS@ @CPPFLAGS@ @DEFS@ $(IMGCFLAGS)
+RM=rm
+CC=@CC@
+CXX=@CXX@
+
+IMGCFLAGS=@IMGWINCFLAGS@
+OBJS=win_w3mimg.o
+
+all: win_w3mimg.o
+
+win_w3mimg.o: win_w3mimg.cpp
+	$(CXX) $(CFLAGS) -c $<
+
+clean:
+	@-$(RM) -f *.o
+
+distclean: clean
+	-$(RM) -f Makefile
+
+#
+
+
--- /dev/null	Mon Dec 13 20:59:00 2010
+++ w3mimg/win/win_w3mimg.cpp	Mon Dec 13 20:54:42 2010
@@ -0,0 +1,1063 @@
+/* $Id: win_w3mimg.cpp,v 1.1.2.1 2010/12/13 11:54:42 shinra Exp $ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "config.h"
+#include <assert.h>
+#include <locale.h>
+
+#include <new>
+#include <algorithm>
+
+#include "w3mimg/w3mimg.h"
+#include <windows.h>
+#include <gdiplus.h>
+#include <unistd.h>
+#include <sys/cygwin.h>
+/* GDI+ can handle BMP, GIF, JPEG, PNG and TIFF by itself. */
+
+#define OFFSET_X	2
+#define OFFSET_Y	2
+#define DEBUG
+#define USE_GDIP_CACHED_BITMAP
+
+#ifdef DEBUG
+#define THROW_NONE throw()
+#else
+#define THROW_NONE
+#endif
+
+struct win_info {
+    HWND window;
+    Gdiplus::ARGB background_pixel;
+    ULONG_PTR gdiplus_token;
+    FILE *logfile;
+};
+
+struct window_list {
+    HWND *wnd;
+    size_t nwnd;
+    size_t capacity;
+};
+
+#ifdef USE_GDIP_CACHED_BITMAP
+typedef Gdiplus::CachedBitmap *cache_handle;
+#else
+typedef HBITMAP cache_handle;
+#endif
+class win_image {
+private:
+    win_image(const win_image &); // decl only
+    win_image &operator=(const win_image &); // decl only
+
+    Gdiplus::Bitmap *gpbitmap;
+    unsigned int nframe;
+    unsigned int current;
+    unsigned long tick;
+    unsigned long loopcount; // zero = infinite
+    unsigned long *delay; // unit: millisecond
+    cache_handle *cache;
+
+public:
+    win_image() THROW_NONE;
+    ~win_image() THROW_NONE;
+    int load(w3mimg_op *wop, Gdiplus::Bitmap **p_gpbitmap,
+	    int *wreturn, int *hreturn) THROW_NONE;
+    int show(w3mimg_op *wop, int sx, int sy, int sw, int sh, int x, int y) THROW_NONE;
+    int animate(w3mimg_op *wop) THROW_NONE;
+};
+
+static int win_init(w3mimg_op * wop) THROW_NONE;
+static int win_finish(w3mimg_op * wop) THROW_NONE;
+static int win_active(w3mimg_op * wop) THROW_NONE;
+static void win_set_background(w3mimg_op * wop, char *background) THROW_NONE;
+static void win_sync(w3mimg_op * wop) THROW_NONE;
+static void win_close(w3mimg_op * wop) THROW_NONE;
+
+static int win_load_image(w3mimg_op * wop, W3MImage * img, char *fname,
+	int w, int h) THROW_NONE;
+static int win_show_image(w3mimg_op * wop, W3MImage * img,
+	int sx, int sy, int sw, int sh, int x, int y) THROW_NONE;
+static void win_free_image(w3mimg_op * wop, W3MImage * img) THROW_NONE;
+static int win_get_image_size(w3mimg_op * wop, W3MImage * img,
+	char *fname, int *w, int *h) THROW_NONE;
+static int win_clear(w3mimg_op * wop, int x, int y, int w, int h) THROW_NONE;
+
+static int window_alive(w3mimg_op *wop) THROW_NONE;
+static Gdiplus::Bitmap *read_image_file(w3mimg_op *wop, const char *fname) THROW_NONE;
+static BOOL CALLBACK store_to_window_list(HWND hWnd, LPARAM wndlist) THROW_NONE;
+static void clear_window_list(struct window_list *wl) THROW_NONE;
+static const char *gdip_strerror(Gdiplus::Status status) THROW_NONE;
+static void gdip_perror(w3mimg_op *wop, Gdiplus::Status status, const char *func) THROW_NONE;
+static char *win32_strerror_alloc(DWORD status) THROW_NONE;
+static void win32_perror(w3mimg_op *wop, DWORD status, const char *func) THROW_NONE;
+#if 0 /* unused */
+static WCHAR *mb2wstr_alloc(const char *) THROW_NONE;
+static char *wstr2mb_alloc(const WCHAR *) THROW_NONE;
+#endif
+
+#define PRELUDE(wop, xi) \
+    assert(wop); \
+    struct win_info *xi = static_cast<struct win_info *>(wop->priv); \
+    assert(xi)
+
+win_image::win_image() THROW_NONE
+    : gpbitmap(NULL), nframe(0)
+{}
+
+win_image::~win_image() THROW_NONE
+{
+    if (this->cache) {
+	for (size_t i = 0; i != this->nframe; ++i) {
+#ifdef USE_GDIP_CACHED_BITMAP
+	    delete this->cache[i];
+#else
+	    if (this->cache[i])
+		DeleteObject(this->cache[i]);
+#endif
+	}
+	delete[] this->cache;
+    }
+    delete[] this->delay;
+    delete this->gpbitmap;
+}
+
+int
+win_image::load(w3mimg_op *wop, Gdiplus::Bitmap **p_gpbitmap, int *wreturn, int *hreturn) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    Gdiplus::Bitmap *gpbitmap = *p_gpbitmap;
+    assert(gpbitmap);
+    Gdiplus::Status status = Gdiplus::Ok;
+    int retval = 0;
+
+    Gdiplus::PropertyItem *loopcountbuf = NULL;
+    Gdiplus::PropertyItem *delaybuf = NULL;
+    unsigned long *delay = NULL;
+    cache_handle *cache = NULL;
+
+    if (xi->logfile) {
+	fprintf(xi->logfile, "win_image::load(%p, %p, %p, %p) start\n",
+		wop, gpbitmap, wreturn, hreturn);
+    }
+    {
+	unsigned int width = gpbitmap->GetWidth();
+	unsigned int height = gpbitmap->GetHeight();
+	unsigned int nframe = gpbitmap->GetFrameCount(&Gdiplus::FrameDimensionTime);
+	unsigned long loopcount = 0;
+
+	if (xi->logfile)
+	    fprintf(xi->logfile, "win_image::load(): size[0]=%ux%u\n", width, height);
+	if (nframe == 0) {
+	    // Not an animated picture
+	    if (xi->logfile)
+		fprintf(xi->logfile, "win_image::load(): zero frame count\n");
+	    nframe = 1;
+	    delay = new(std::nothrow) unsigned long[1];
+	    if (delay == NULL)
+		goto last;
+	    delay[0] = 0;
+	} else {
+	    unsigned int loopcountsize = gpbitmap->GetPropertyItemSize(PropertyTagLoopCount);
+	    unsigned int delaysize = gpbitmap->GetPropertyItemSize(PropertyTagFrameDelay);
+
+	    // Get loop count
+	    if (loopcountsize != 0) {
+		loopcountbuf = (Gdiplus::PropertyItem *)malloc(loopcountsize);
+		if (loopcountbuf == NULL)
+		    goto last;
+		status = gpbitmap->GetPropertyItem(PropertyTagLoopCount, loopcountsize, loopcountbuf);
+		if (status != Gdiplus::Ok)
+		    goto gdip_error;
+		if (loopcountbuf->type == PropertyTagTypeShort &&
+			loopcountbuf->length >= sizeof(unsigned short)) {
+		    loopcount = *(unsigned short *)loopcountbuf->value;
+		} else if (loopcountbuf->type == PropertyTagTypeLong &&
+			loopcountbuf->length >= sizeof(unsigned long)) {
+		    loopcount = *(unsigned long *)loopcountbuf->value;
+		}
+	    }
+	    if (xi->logfile)
+		fprintf(xi->logfile, "win_image::load(): loopcount=%lu\n", loopcount);
+	    // Get delay times
+	    if (delaysize != 0) {
+		delaybuf = (Gdiplus::PropertyItem *)malloc(delaysize);
+		if (delaybuf == NULL)
+		    goto last;
+		status = gpbitmap->GetPropertyItem(PropertyTagFrameDelay, delaysize, delaybuf);
+		if (status != Gdiplus::Ok)
+		    goto gdip_error;
+		delay = new(std::nothrow) unsigned long[nframe];
+		if (delay == NULL)
+		    goto last;
+		std::fill(delay, delay + nframe, 0);
+		if (delaybuf->type == PropertyTagTypeShort) {
+		    unsigned int count = delaybuf->length / sizeof(unsigned short);
+		    for (unsigned int i = 0; i != count; ++i)
+			delay[i] = ((unsigned short *)delaybuf->value)[i] * 10;
+		} else if (delaybuf->type == PropertyTagTypeLong) {
+		    unsigned int count = delaybuf->length / sizeof(unsigned long);
+		    for (unsigned int i = 0; i != count; ++i)
+			delay[i] = ((unsigned long *)delaybuf->value)[i] * 10;
+		}
+	    }
+	    if (xi->logfile) {
+		for (unsigned int i = 0; i != nframe; ++i)
+		    fprintf(xi->logfile, "win_image::load(): delay[%u]=%lu\n", i, delay[i]);
+	    }
+	    // Get dimensions
+	    for (unsigned int nextframe = 1; nextframe != nframe; ++nextframe) {
+		status = gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, nextframe);
+		if (status != Gdiplus::Ok) {
+		    if (xi->logfile)
+			fprintf(xi->logfile, "win_image::load(): SelectActiveFrame() to %u failed = %d: %s\n",
+				nextframe, (int)status, gdip_strerror(status));
+		    goto last;
+		}
+		unsigned int iw = gpbitmap->GetWidth();
+		unsigned int ih = gpbitmap->GetHeight();
+		if (iw > width)
+		    width = iw;
+		if (ih > height)
+		    height = ih;
+		if (xi->logfile)
+		    fprintf(xi->logfile, "win_image::load(): size[%u]=%ux%u\n", nextframe, iw, ih);
+	    }
+	    // Back to the top
+	    status = gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, 0);
+	    if (status != Gdiplus::Ok) {
+		if (xi->logfile)
+		    fprintf(xi->logfile, "win_image::load(): SelectActiveFrame() to %u frame = %d: %s\n",
+			    1U, (int)status, gdip_strerror(status));
+		goto last;
+	    }
+	}
+	// Allocate cache array
+	cache = new(std::nothrow) cache_handle[nframe];
+	if (cache == NULL)
+	    goto last;
+	std::fill(cache, cache + nframe, (cache_handle)NULL);
+	// Sanity check
+	if (width > SHRT_MAX || height > SHRT_MAX) {
+	    if (xi->logfile)
+		fprintf(xi->logfile, "win_image::load(): too big image: %ux%u\n", width, height);
+	    goto last;
+	}
+	// Store the results
+	if (wreturn)
+	    *wreturn = (int)width;
+	if (hreturn)
+	    *hreturn = (int)height;
+	this->gpbitmap = gpbitmap;
+	*p_gpbitmap = NULL; // ownership transfer
+	this->nframe = nframe;
+	this->current = 0;
+	this->tick = 0;
+	this->loopcount = loopcount;
+	this->delay = delay;
+	delay = NULL; // ownership transfer
+	this->cache = cache;
+	cache = NULL; // ownership transfer
+	retval = 1;
+    }
+    goto last;
+    
+gdip_error:
+    gdip_perror(wop, status, "win_image::load");
+    goto last;
+last:
+    delete[] cache;
+    delete[] delay;
+    free(delaybuf);
+    free(loopcountbuf);
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_image::load() = %d\n", retval);
+    return retval;
+}
+
+int
+win_image::show(w3mimg_op *wop, int sx, int sy, int sw, int sh, int x, int y) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    int retval = 0;
+    Gdiplus::Status status = Gdiplus::Ok;
+    cache_handle newcache = NULL;
+#ifndef USE_GDIP_CACHED_BITMAP
+    HDC windc = NULL;
+    HDC memdc = NULL;
+#endif
+
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_image::show(%p, %d, %d, %d, %d, %d, %d) start\n",
+		wop, sx, sy, sw, sh, x, y);
+    if (!window_alive(wop))
+	goto last;
+    {
+	int xx = x + wop->offset_x;
+	int yy = y + wop->offset_y;
+
+#ifdef USE_GDIP_CACHED_BITMAP
+	// Prepare the Graphics object for painting
+	Gdiplus::Graphics graphics(xi->window);
+	if ((status = graphics.GetLastStatus()) != Gdiplus::Ok)
+	    goto gdip_error;
+	Gdiplus::Rect clip(xx, yy, sw, sh);
+	status = graphics.SetClip(clip);
+	if (status != Gdiplus::Ok)
+	    goto gdip_error;
+
+	// Clear the area first as the picture may be transparent
+	status = graphics.Clear(Gdiplus::Color(xi->background_pixel));
+	if (status != Gdiplus::Ok)
+	    goto gdip_error;
+
+	unsigned int retry_count = 2;
+	do {
+	    if (this->cache[this->current] == NULL) {
+		// Cache the image
+		Gdiplus::CachedBitmap *newcache = new Gdiplus::CachedBitmap(this->gpbitmap, &graphics);
+		if (newcache == NULL)
+		    goto last;
+		if ((status = newcache->GetLastStatus()) != Gdiplus::Ok)
+		    goto gdip_error;
+		this->cache[this->current] = newcache;
+		newcache = NULL; // ownership transfer
+		--retry_count;
+	    }
+	    // Draw it
+	    status = graphics.DrawCachedBitmap(this->cache[this->current], xx - sx, yy - sy);
+	    if (status == Gdiplus::Ok)
+		break;
+	    // maybe the user altered the display configuration
+	    if (xi->logfile)
+		fprintf(xi->logfile, "win_image::show(): stale cache = %d: %s\n",
+			(int)status, gdip_strerror(status));
+	    delete this->cache[this->current];
+	    this->cache[this->current] = NULL;
+	    if (retry_count == 0)
+		goto last;
+	} while (1);
+#else /* !USE_GDIP_CACHED_BITMAP */
+	// Prepare the GC for painting
+	windc = GetDC(xi->window);
+	if (windc == NULL)
+	    goto win32_error;
+	// Prepare the GC for read/write access to the cached bitmap
+	memdc = CreateCompatibleDC(windc);
+	if (memdc == NULL)
+	    goto win32_error;
+	unsigned int retry_count = 2;
+	do {
+	    HGDIOBJ hbitmap_save;
+	    if (this->cache[this->current] == NULL) {
+		unsigned int ow = gpbitmap->GetWidth();
+		unsigned int oh = gpbitmap->GetHeight();
+		// Cache the image
+		newcache = CreateCompatibleBitmap(windc, ow, oh);
+		if (newcache == NULL)
+		    goto win32_error;
+		SetLastError(ERROR_SUCCESS); // SelectObject sometimes leave the error unchanged
+		hbitmap_save = SelectObject(memdc, newcache);
+		if (hbitmap_save == NULL || hbitmap_save == HGDI_ERROR)
+		    goto win32_error;
+		Gdiplus::Graphics tmp_graphics(memdc);
+		if ((status = tmp_graphics.GetLastStatus()) != Gdiplus::Ok)
+		    goto gdip_error;
+		status = tmp_graphics.Clear(Gdiplus::Color(xi->background_pixel));
+		if (status != Gdiplus::Ok)
+		    goto gdip_error;
+		status = tmp_graphics.DrawImage(this->gpbitmap, 0, 0, ow, oh);
+		if (status != Gdiplus::Ok)
+		    goto gdip_error;
+		this->cache[this->current] = newcache;
+		newcache = NULL; // ownership transfer
+		--retry_count;
+	    } else {
+		SetLastError(ERROR_SUCCESS); // SelectObject sometimes leave the error unchanged
+		hbitmap_save = SelectObject(memdc, this->cache[this->current]);
+		if (hbitmap_save == NULL || hbitmap_save == HGDI_ERROR)
+		    goto stale_cache;
+	    }
+	    // Draw it
+	    if (BitBlt(windc, xx, yy, sw, sh, memdc, sx, sy, SRCCOPY))
+		break;
+	    // maybe the user altered the display configuration
+stale_cache:
+	    if (xi->logfile) {
+		DWORD ecode = GetLastError();
+		char *msg = win32_strerror_alloc(ecode);
+		fprintf(xi->logfile, "win_image::show(): stale cache = %u: %s\n",
+			(unsigned int)ecode, msg ? msg : "(unknown)");
+		LocalFree(msg);
+	    }
+	    SetLastError(ERROR_SUCCESS); // SelectObject sometimes leave the error unchanged
+	    hbitmap_save = SelectObject(memdc, hbitmap_save);
+	    if (hbitmap_save == NULL || hbitmap_save == HGDI_ERROR)
+		goto win32_error;
+	    DeleteObject(this->cache[this->current]);
+	    this->cache[this->current] = NULL;
+	    if (retry_count == 0)
+		goto last;
+	} while (1);
+#endif /* !USE_GDIP_CACHED_BITMAP */
+
+	retval = 1;
+    }
+    goto last;
+#ifndef USE_GDIP_CACHED_BITMAP
+win32_error:
+    win32_perror(wop, GetLastError(), "win_image::show");
+    goto last;
+#endif
+gdip_error:
+    gdip_perror(wop, status, "win_image::show");
+    goto last;
+last:
+#ifdef USE_GDIP_CACHED_BITMAP
+    delete newcache;
+#else
+    if (memdc)
+	DeleteDC(memdc);
+    if (windc)
+	ReleaseDC(xi->window, windc);
+    if (newcache)
+	DeleteObject(newcache);
+#endif
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_image::show() = %d\n", retval);
+    return retval;
+}
+
+int
+win_image::animate(w3mimg_op * wop) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    int retval = 0;
+    Gdiplus::Status status = Gdiplus::Ok;
+
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_image::animate(%p) start\n", wop);
+    {
+	if (this->nframe <= 1)
+	    goto animation_end;
+#define UNIT_TICK 50
+#define MIN_DELAY (UNIT_TICK*2)
+	this->tick += UNIT_TICK;
+	if (this->tick >= MIN_DELAY && this->tick >= this->delay[this->current]) {
+	    this->tick = 0;
+	    unsigned int nextframe = this->current + 1;
+	    if (nextframe >= this->nframe) {
+		if (this->loopcount == 1) // end of the loop
+		    goto animation_end;
+		nextframe = 0;
+	    }
+	    status = this->gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, nextframe);
+	    if (status != Gdiplus::Ok)
+		goto gdip_error;
+	    this->current = nextframe;
+	    if (nextframe == 0 && this->loopcount > 1)
+		--this->loopcount;
+	}
+animation_end:
+	retval = 1;
+    }
+    goto last;
+gdip_error:
+    gdip_perror(wop, status, "win_image::animate");
+    goto last;
+last:
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_image::animate() = %d\n", retval);
+    return retval;
+}
+
+static int
+window_alive(w3mimg_op *wop) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    if (xi->window == NULL)
+	return 0;
+    if (IsWindow(xi->window))
+	return 1;
+    xi->window = NULL;
+    fputs("w3mimgdisplay: target window disappeared\n", stderr);
+    if (xi->logfile)
+	fputs("w3mimgdisplay: target window disappeared\n", xi->logfile);
+    return 0;
+}
+
+static int
+win_init(w3mimg_op *) THROW_NONE
+{
+    // nothing to do
+    return 1;
+}
+
+static int
+win_finish(w3mimg_op *) THROW_NONE
+{
+    // nothing to do
+    return 1;
+}
+
+static int
+win_clear(w3mimg_op *wop, int x, int y, int w, int h) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    Gdiplus::Status status = Gdiplus::Ok;
+    int retval = 0;
+
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_clear(%p, %d, %d, %d, %d) start\n",
+		wop, x, y, w, h);
+    if (!window_alive(wop))
+	goto last;
+    {
+	if (x < 0)
+	    x = 0;
+	if (y < 0)
+	    y = 0;
+	Gdiplus::SolidBrush brush(Gdiplus::Color(xi->background_pixel));
+	if ((status = brush.GetLastStatus()) != Gdiplus::Ok)
+	    goto gdip_error;
+	Gdiplus::Graphics graphics(xi->window);
+	if ((status = graphics.GetLastStatus()) != Gdiplus::Ok)
+	    goto gdip_error;
+	status = graphics.FillRectangle(&brush, x + wop->offset_x, y + wop->offset_y, w, h);
+	if (status != Gdiplus::Ok)
+	    goto gdip_error;
+	retval = 1;
+    }
+    goto last;
+gdip_error:
+    gdip_perror(wop, status, "win_clear");
+    goto last;
+last:
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_clear() = %d\n", retval);
+    return retval;
+}
+
+static int
+win_active(w3mimg_op * wop) THROW_NONE
+{
+    return window_alive(wop);
+}
+
+static void
+win_set_background(w3mimg_op * wop, char *background) THROW_NONE
+{
+    PRELUDE(wop, xi);
+
+    HDC windc = NULL;
+
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_set_background(%p, \"%s\")\n", wop, background ? background : "(auto)");
+    {
+	// Fallback value
+	// xi->background_pixel = Gdiplus::Color::White;
+	xi->background_pixel = Gdiplus::Color::Black;
+
+	// Explicit
+	if (background) {
+	    unsigned int r, g, b;
+	    if (sscanf(background, "#%02x%02x%02x", &r, &g, &b) == 3) {
+		xi->background_pixel = Gdiplus::Color::MakeARGB((BYTE)255, (BYTE)r, (BYTE)g, (BYTE)b);
+		goto last;
+	    }
+	}
+
+	// Auto detect
+	if (xi->window == NULL || !IsWindow(xi->window))
+	    goto last;
+	windc = GetDC(xi->window);
+	if (windc == NULL)
+	    goto win32_error;
+	COLORREF c = GetPixel(windc,
+		    (wop->offset_x >= 1) ? (wop->offset_x - 1) : 0,
+		    (wop->offset_y >= 1) ? (wop->offset_y - 1) : 0);
+	xi->background_pixel = Gdiplus::Color::MakeARGB(
+		(BYTE)255, GetRValue(c), GetGValue(c), GetBValue(c));
+    }
+    goto last;
+win32_error:
+    win32_perror(wop, GetLastError(), "win_set_background");
+    goto last;
+last:
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_set_background() result = #%06x\n",
+		(unsigned int)xi->background_pixel);
+    if (windc)
+	ReleaseDC(xi->window, windc);
+}
+
+static void
+win_sync(w3mimg_op *) THROW_NONE
+{
+    // nothing to do
+    return;
+}
+
+static void
+win_close(w3mimg_op * wop) THROW_NONE
+{
+    PRELUDE(wop, xi);
+
+    if (xi->gdiplus_token)
+	Gdiplus::GdiplusShutdown(xi->gdiplus_token);
+    if (xi->logfile) {
+	fprintf(xi->logfile, "win_close(%p)\n", wop);
+	fclose(xi->logfile);
+    }
+    delete xi;
+    delete wop;
+}
+
+static Gdiplus::Bitmap *
+read_image_file(w3mimg_op *wop, const char *fname) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    Gdiplus::Status status = Gdiplus::Ok;
+    Gdiplus::Bitmap *retval = NULL;
+
+    WCHAR *wfname = NULL;
+    Gdiplus::Bitmap *gpbitmap = NULL;
+
+    if (xi->logfile)
+	fprintf(xi->logfile, "read_image_file(%p, \"%s\") start\n", wop, fname);
+    {
+	wfname = (WCHAR *)cygwin_create_path(CCP_POSIX_TO_WIN_W, fname);
+	if (wfname == NULL)
+	    goto last;
+	gpbitmap = new Gdiplus::Bitmap(wfname);
+	if (gpbitmap == NULL)
+	    goto last;
+	status = gpbitmap->GetLastStatus();
+	switch (status) {
+	    case Gdiplus::Ok:
+		break;
+	    case Gdiplus::UnknownImageFormat:
+	    case Gdiplus::FileNotFound:
+		goto last; // fail silently
+	    default:
+		goto gdip_error;
+	}
+	retval = gpbitmap;
+	gpbitmap = NULL; // ownership transfer
+    }
+    goto last;
+gdip_error:
+    gdip_perror(wop, status, "read_image_file");
+last:
+    delete gpbitmap;
+    free(wfname);
+    if (xi->logfile)
+	fprintf(xi->logfile, "read_image_file() = %p\n", retval);
+    return retval;
+}
+
+static int
+win_load_image(w3mimg_op * wop, W3MImage * img, char *fname, int w, int h) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    int retval = 0;
+    Gdiplus::Bitmap *gpbitmap = NULL;
+    win_image *wimg = NULL;
+
+    assert(img);
+    if (xi->logfile) {
+	fprintf(xi->logfile, "win_load_image(%p, %p, \"%s\", %d, %d) start\n",
+		wop, img, fname, w, h);
+    }
+    {
+	gpbitmap = read_image_file(wop, fname);
+	if (gpbitmap == NULL)
+	    goto last;
+	int iw, ih;
+	wimg = new(std::nothrow) win_image;
+	if (!wimg->load(wop, &gpbitmap, &iw, &ih))
+	    goto last;
+	img->pixmap = wimg;
+	wimg = NULL; // ownership transfer
+	img->width = (0 <= w && w < iw) ? w : iw;
+	img->height = (0 <= h && h < ih) ? h : ih;
+	retval = 1;
+    }
+    goto last;
+last:
+    delete wimg;
+    delete gpbitmap;
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_load_image() = %d\n", retval);
+    return retval;
+}
+
+static int
+win_show_image(w3mimg_op * wop, W3MImage * img, int sx, int sy, int sw,
+	       int sh, int x, int y) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    int retval = 0;
+
+    assert(img);
+    win_image *wimg = static_cast<win_image *>(img->pixmap);
+    assert(wimg);
+    
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_show_image(%p, %p, %d, %d, %d, %d, %d, %d) start\n",
+		wop, img, sx, sy, sw, sh, x, y);
+    int sww = sw ? sw : img->width;
+    int shh = sh ? sh : img->height;
+    retval = wimg->show(wop, sx, sy, sww, shh, x, y)
+	&& wimg->animate(wop);
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_show_image() = %d\n", retval);
+    return retval;
+}
+
+static void
+win_free_image(w3mimg_op * wop, W3MImage * img) THROW_NONE
+{
+    PRELUDE(wop, xi);
+
+    assert(img);
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_free_image(%p, %p) pixmap=%p\n", wop, img, img->pixmap);
+    delete static_cast<win_image *>(img->pixmap);
+    img->pixmap = NULL;
+    img->width = 0;
+    img->height = 0;
+}
+
+static int
+win_get_image_size(w3mimg_op * wop, W3MImage *img_unused, char *fname, int *w, int *h) THROW_NONE
+{
+    PRELUDE(wop, xi);
+    int retval = 0;
+    Gdiplus::Bitmap *gpbitmap = NULL;
+    win_image *wimg = NULL;
+
+    if (xi->logfile) {
+	fprintf(xi->logfile, "win_get_image_size(%p, %p, \"%s\", %p, %p) start\n",
+		wop, img_unused, fname, w, h);
+    }
+    {
+	gpbitmap = read_image_file(wop, fname);
+	if (gpbitmap == NULL)
+	    goto last;
+	wimg = new(std::nothrow) win_image;
+	if (wimg == NULL)
+	    goto last;
+	retval = wimg->load(wop, &gpbitmap, w, h);;
+    }
+    goto last;
+last:
+    delete wimg;
+    delete gpbitmap;
+    if (xi->logfile)
+	fprintf(xi->logfile, "win_get_image_size() = %d\n", retval);
+    return retval;
+}
+
+extern "C" w3mimg_op *
+w3mimg_winopen()
+{
+    w3mimg_op *retval = NULL;
+    Gdiplus::Status status = Gdiplus::Ok;
+
+    w3mimg_op *wop = NULL;
+    struct win_info *xi = NULL;
+    struct window_list children = { NULL, 0, 0 };
+
+    {
+	// Quit if running on X
+	const char *display_name;
+	if ((display_name = getenv("DISPLAY")) != NULL &&
+		display_name[0] && strcmp(display_name, ":0") != 0)
+	    return NULL;
+
+	// Allocate the context objects
+	wop = new(std::nothrow) w3mimg_op(); // call the default ctor instead of "new w3mimg_op;"
+	if (wop == NULL)
+	    return NULL;
+	wop->priv = xi = new(std::nothrow) win_info();
+	if (xi == NULL)
+	    goto last;
+
+	// Debug logging
+	const char *logging_dir;
+	if ((logging_dir = getenv("W3MIMG_LOGDIR")) != NULL &&
+		logging_dir[0]) {
+	    size_t l = strlen(logging_dir) + sizeof "/w3mimgXXXXXXXXXX.log";
+	    char *fname = (char *)malloc(l);
+	    snprintf(fname, l, "%s/w3mimg%d.log", logging_dir, (int)getpid());
+	    xi->logfile = fopen(fname, "a");
+	    if (xi->logfile) {
+		setvbuf(xi->logfile, NULL, _IONBF, 0);
+		fprintf(xi->logfile, "\nw3mimg_winopen() start pid=%d\n", (int)getpid());
+	    }
+	}
+
+	// Look for the window to draw the image
+	xi->window = NULL;
+	const char *windowid;
+	if ((windowid = getenv("WINDOWID")) != NULL)
+	    xi->window = FindWindowA(windowid, NULL);
+	if (!xi->window)
+	    xi->window = GetForegroundWindow();
+	if (!xi->window)
+	    goto win32_error;
+
+	WINDOWINFO winfo = WINDOWINFO();
+	winfo.cbSize = sizeof winfo;
+	GetWindowInfo(xi->window, &winfo);
+	wop->width = (int)(winfo.rcClient.right - winfo.rcClient.left);
+	wop->height = (int)(winfo.rcClient.bottom - winfo.rcClient.top);
+
+	// Search decendant windows and find out which is the text window
+	while (1) {
+	    HWND p_window = xi->window;
+
+	    clear_window_list(&children);
+	    EnumChildWindows(xi->window, &store_to_window_list, (LPARAM)&children);
+	    for (unsigned int i = 0; i < children.nwnd; i++) {
+		int width, height;
+
+		GetWindowInfo(children.wnd[i], &winfo);
+		width = (int)(winfo.rcClient.right - winfo.rcClient.left);
+		height = (int)(winfo.rcClient.bottom - winfo.rcClient.top);
+		if (width > wop->width * 0.7 &&
+			height > wop->height * 0.7) {
+		    /* maybe text window */
+		    wop->width = width;
+		    wop->height = height;
+		    xi->window = children.wnd[i];
+		}
+	    }
+	    if (p_window == xi->window)
+		break;
+	}
+
+	// Terminal may leave some border pixels
+	wop->offset_x = OFFSET_X;
+	wop->offset_y = OFFSET_Y;
+
+	// Start up the GDI+
+	Gdiplus::GdiplusStartupInput startup_input; /// default ctor
+	status = Gdiplus::GdiplusStartup(&xi->gdiplus_token, &startup_input, NULL);
+	if (status != Gdiplus::Ok)
+	    goto gdip_error;
+
+	// Fill the context object
+	wop->init = win_init;
+	wop->finish = win_finish;
+	wop->active = win_active;
+	wop->set_background = win_set_background;
+	wop->sync = win_sync;
+	wop->close = win_close;
+	wop->clear = win_clear;
+
+	wop->load_image = win_load_image;
+	wop->show_image = win_show_image;
+	wop->free_image = win_free_image;
+	wop->get_image_size = win_get_image_size;
+
+	retval = wop; // take care of the object lifetime
+    }
+    goto last;
+win32_error:
+    win32_perror(wop, GetLastError(), "w3mimg_winopen");
+    goto last;
+gdip_error:
+    gdip_perror(wop, status, "w3mimg_winopen");
+    goto last;
+last:
+    if (xi && xi->logfile)
+	fprintf(xi->logfile, "w3mimg_winopen() = %p\n", retval);
+    clear_window_list(&children);
+    if (!retval) {
+	if (xi) {
+	    if (xi->gdiplus_token)
+		Gdiplus::GdiplusShutdown(xi->gdiplus_token);
+	    if (xi->logfile)
+		fclose(xi->logfile);
+	    delete xi;
+	}
+	delete wop;
+    }
+    return retval;
+}
+
+static BOOL CALLBACK
+store_to_window_list(HWND hWnd, LPARAM wndlist) THROW_NONE
+{
+    struct window_list *wl = (struct window_list *)wndlist;
+
+    if (wl->nwnd >= wl->capacity) {
+	size_t newsize = (wl->capacity < 4 ) ? 4 : (wl->capacity * 2);
+	HWND *newbuf = (HWND *)realloc(wl->wnd, newsize * sizeof newbuf[0]);
+	if (newbuf == NULL) {
+	    clear_window_list(wl);
+	    return FALSE;
+	}
+	wl->wnd = newbuf;
+	wl->capacity = newsize;
+    }
+    wl->wnd[wl->nwnd++] = hWnd;
+    return TRUE;
+}
+
+static void
+clear_window_list(struct window_list *wl) THROW_NONE
+{
+    free(wl->wnd);
+    wl->wnd = NULL;
+    wl->nwnd = 0;
+    wl->capacity = 0;
+}
+
+static const char *
+gdip_strerror(Gdiplus::Status status) THROW_NONE
+{
+    size_t i;
+    struct status_rec {
+	Gdiplus::Status code;
+	const char *str;
+    };
+    static const struct status_rec table[] = {
+#define ERRITEM(s) { Gdiplus::s, #s }
+	ERRITEM(Ok),
+	ERRITEM(GenericError),
+	ERRITEM(InvalidParameter),
+	ERRITEM(OutOfMemory),
+	ERRITEM(ObjectBusy),
+	ERRITEM(InsufficientBuffer),
+	ERRITEM(NotImplemented),
+	ERRITEM(Win32Error),
+	ERRITEM(WrongState),
+	ERRITEM(Aborted),
+	ERRITEM(FileNotFound),
+	ERRITEM(ValueOverflow),
+	ERRITEM(AccessDenied),
+	ERRITEM(UnknownImageFormat),
+	ERRITEM(FontFamilyNotFound),
+	ERRITEM(FontStyleNotFound),
+	ERRITEM(NotTrueTypeFont),
+	ERRITEM(UnsupportedGdiplusVersion),
+	ERRITEM(GdiplusNotInitialized),
+	ERRITEM(PropertyNotFound),
+	ERRITEM(PropertyNotSupported),
+	ERRITEM(ProfileNotFound),
+#undef ERRITEM
+    };
+    for (i = 0; i != sizeof table / sizeof table[0]; ++i)
+	if (table[i].code == status)
+	    return table[i].str;
+    return "unknown";
+}
+
+static void
+gdip_perror(w3mimg_op *wop, Gdiplus::Status status, const char *func) THROW_NONE
+{
+    const char *s = gdip_strerror(status);
+    fprintf(stderr, "w3mimgdisplay: GDI+ error %d: %s\n", (int)status, s);
+    if (wop && wop->priv) {
+	struct win_info *xi = (struct win_info *)wop->priv;
+	if (xi->logfile) {
+	    fprintf(xi->logfile, "%s(): GDI+ error %d: %s\n", func, (int)status, s);
+	}
+    }
+}
+
+// Don't free() the result; use LocalFree() instead
+static char *
+win32_strerror_alloc(DWORD status) THROW_NONE
+{
+    char *errbuf = NULL;
+
+    FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+	    FORMAT_MESSAGE_FROM_SYSTEM |
+	    FORMAT_MESSAGE_IGNORE_INSERTS,
+	    NULL, status, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
+	    (LPSTR)&errbuf, 0, NULL);
+    if (errbuf) {
+	size_t len = strlen(errbuf);
+	if (len > 0 && errbuf[len - 1] == '\n')
+	    errbuf[len - 1] = '\0';
+    }
+    return errbuf;
+}
+
+static void
+win32_perror(w3mimg_op *wop, DWORD status, const char *func) THROW_NONE
+{
+    char *errbuf = win32_strerror_alloc(status);
+    const char *s = errbuf ? errbuf : "(unknown)";
+
+    fprintf(stderr, "w3mimgdisplay: Win32 error %u: %s\n", (unsigned int)status, s);
+    if (wop && wop->priv) {
+	struct win_info *xi = (struct win_info *)wop->priv;
+	if (xi->logfile) {
+	    fprintf(xi->logfile, "%s(): Win32 error %u: %s\n",
+		    func, (unsigned int)status, s);
+	}
+    }
+    LocalFree(errbuf);
+}
+
+#if 0 /* unused */
+static WCHAR *
+mb2wstr_alloc(const char *s) THROW_NONE
+{
+    int len;
+    WCHAR *buf = NULL;
+
+    len = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, s, -1, NULL, 0);
+    if (len <= 0) {
+	fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
+		(unsigned int)GetLastError());
+	goto error;
+    }
+    buf = (WCHAR *)malloc(len * sizeof(WCHAR)); /* including L'\0' */
+    if (!buf)
+	goto error;
+    len = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, s, -1, buf, len);
+    if (len <= 0) {
+	fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
+		(unsigned int)GetLastError());
+	goto error;
+    }
+    return buf;
+error:
+    free(buf);
+    return NULL;
+}
+
+static char *
+wstr2mb_alloc(const WCHAR *ws) THROW_NONE
+{
+    int len;
+    char *buf = NULL;
+
+    len = WideCharToMultiByte(CP_OEMCP, WC_COMPOSITECHECK, ws, -1, NULL, 0, NULL, NULL);
+    if (len <= 0) {
+	fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
+		(unsigned int)GetLastError());
+	goto error;
+    }
+    buf = (char *)malloc(len); /* including '\0' */
+    if (!buf)
+	goto error;
+    len = WideCharToMultiByte(CP_OEMCP, WC_COMPOSITECHECK, ws, -1, buf, len, NULL, NULL);
+    if (len <= 0) {
+	fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
+		(unsigned int)GetLastError());
+	goto error;
+    }
+    return buf;
+error:
+    free(buf);
+    return NULL;
+}
+#endif /* unused */
