w3m

Unnamed repository; edit this file to name it for gitweb.
git clone https://logand.com/git/w3m.git/
Log | Files | Refs | README

win_w3mimg.cpp (27128B)


      1 /* $Id$ */
      2 #include <stdio.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 #include <ctype.h>
      6 #include "config.h"
      7 #include <assert.h>
      8 #include <locale.h>
      9 
     10 #include <new>
     11 #include <algorithm>
     12 
     13 #include "w3mimg/w3mimg.h"
     14 #include <windows.h>
     15 #include <gdiplus.h>
     16 #include <unistd.h>
     17 #include <sys/cygwin.h>
     18 /* GDI+ can handle BMP, GIF, JPEG, PNG and TIFF by itself. */
     19 
     20 #define OFFSET_X	2
     21 #define OFFSET_Y	2
     22 #define DEBUG
     23 
     24 #ifdef DEBUG
     25 #define THROW_NONE throw()
     26 #else
     27 #define THROW_NONE
     28 #endif
     29 
     30 struct win_info {
     31     HWND window;
     32     Gdiplus::ARGB background_pixel;
     33     ULONG_PTR gdiplus_token;
     34     FILE *logfile;
     35 };
     36 
     37 struct window_list {
     38     HWND *wnd;
     39     size_t nwnd;
     40     size_t capacity;
     41 };
     42 
     43 typedef Gdiplus::CachedBitmap *cache_handle;
     44 class win_image {
     45 private:
     46     win_image(const win_image &); // decl only
     47     win_image &operator=(const win_image &); // decl only
     48 
     49     Gdiplus::Bitmap *gpbitmap;
     50     unsigned int nframe;
     51     unsigned int current;
     52     unsigned long tick;
     53     unsigned long loopcount; // zero = infinite
     54     unsigned long *delay; // unit: millisecond
     55     cache_handle *cache;
     56 
     57 public:
     58     win_image() THROW_NONE;
     59     ~win_image() THROW_NONE;
     60     int load(w3mimg_op *wop, Gdiplus::Bitmap **p_gpbitmap,
     61 	    int *wreturn, int *hreturn) THROW_NONE;
     62     int show(w3mimg_op *wop, int sx, int sy, int sw, int sh, int x, int y) THROW_NONE;
     63     int animate(w3mimg_op *wop) THROW_NONE;
     64 };
     65 
     66 static int win_init(w3mimg_op * wop) THROW_NONE;
     67 static int win_finish(w3mimg_op * wop) THROW_NONE;
     68 static int win_active(w3mimg_op * wop) THROW_NONE;
     69 static void win_set_background(w3mimg_op * wop, char *background) THROW_NONE;
     70 static void win_sync(w3mimg_op * wop) THROW_NONE;
     71 static void win_close(w3mimg_op * wop) THROW_NONE;
     72 
     73 static int win_load_image(w3mimg_op * wop, W3MImage * img, char *fname,
     74 	int w, int h) THROW_NONE;
     75 static int win_show_image(w3mimg_op * wop, W3MImage * img,
     76 	int sx, int sy, int sw, int sh, int x, int y) THROW_NONE;
     77 static void win_free_image(w3mimg_op * wop, W3MImage * img) THROW_NONE;
     78 static int win_get_image_size(w3mimg_op * wop, W3MImage * img,
     79 	char *fname, int *w, int *h) THROW_NONE;
     80 static int win_clear(w3mimg_op * wop, int x, int y, int w, int h) THROW_NONE;
     81 
     82 static int window_alive(w3mimg_op *wop) THROW_NONE;
     83 static Gdiplus::Bitmap *read_image_file(w3mimg_op *wop, const char *fname) THROW_NONE;
     84 static BOOL CALLBACK store_to_window_list(HWND hWnd, LPARAM wndlist) THROW_NONE;
     85 static void clear_window_list(struct window_list *wl) THROW_NONE;
     86 static const char *gdip_strerror(Gdiplus::Status status) THROW_NONE;
     87 static void gdip_perror(w3mimg_op *wop, Gdiplus::Status status, const char *func) THROW_NONE;
     88 static char *win32_strerror_alloc(DWORD status) THROW_NONE;
     89 static void win32_perror(w3mimg_op *wop, DWORD status, const char *func) THROW_NONE;
     90 #if 0 /* unused */
     91 static WCHAR *mb2wstr_alloc(const char *) THROW_NONE;
     92 static char *wstr2mb_alloc(const WCHAR *) THROW_NONE;
     93 #endif
     94 
     95 #define PRELUDE(wop, xi) \
     96     assert(wop); \
     97     struct win_info *xi = static_cast<struct win_info *>(wop->priv); \
     98     assert(xi)
     99 
    100 win_image::win_image() THROW_NONE
    101     : gpbitmap(NULL), nframe(0)
    102 {}
    103 
    104 win_image::~win_image() THROW_NONE
    105 {
    106     if (this->cache) {
    107 	for (size_t i = 0; i != this->nframe; ++i) {
    108 	    delete this->cache[i];
    109 	}
    110 	delete[] this->cache;
    111     }
    112     delete[] this->delay;
    113     delete this->gpbitmap;
    114 }
    115 
    116 int
    117 win_image::load(w3mimg_op *wop, Gdiplus::Bitmap **p_gpbitmap, int *wreturn, int *hreturn) THROW_NONE
    118 {
    119     PRELUDE(wop, xi);
    120     Gdiplus::Bitmap *gpbitmap = *p_gpbitmap;
    121     assert(gpbitmap);
    122     Gdiplus::Status status = Gdiplus::Ok;
    123     int retval = 0;
    124 
    125     Gdiplus::PropertyItem *loopcountbuf = NULL;
    126     Gdiplus::PropertyItem *delaybuf = NULL;
    127     unsigned long *delay = NULL;
    128     cache_handle *cache = NULL;
    129 
    130     if (xi->logfile) {
    131 	fprintf(xi->logfile, "win_image::load(%p, %p, %p, %p) start\n",
    132 		wop, gpbitmap, wreturn, hreturn);
    133     }
    134     {
    135 	unsigned int width = gpbitmap->GetWidth();
    136 	unsigned int height = gpbitmap->GetHeight();
    137 	unsigned int nframe = gpbitmap->GetFrameCount(&Gdiplus::FrameDimensionTime);
    138 	unsigned long loopcount = 0;
    139 	unsigned int first_frame = 0;
    140 
    141 	if (xi->logfile)
    142 	    fprintf(xi->logfile, "win_image::load(): size[0]=%ux%u\n", width, height);
    143 	if (nframe == 0) {
    144 	    // Not an animated picture
    145 	    if (xi->logfile)
    146 		fprintf(xi->logfile, "win_image::load(): zero frame count\n");
    147 	    nframe = 1;
    148 	    delay = new(std::nothrow) unsigned long[1];
    149 	    if (delay == NULL)
    150 		goto last;
    151 	    delay[0] = 0;
    152 	} else {
    153 	    unsigned int loopcountsize = gpbitmap->GetPropertyItemSize(PropertyTagLoopCount);
    154 	    unsigned int delaysize = gpbitmap->GetPropertyItemSize(PropertyTagFrameDelay);
    155 
    156 	    // Get loop count
    157 	    if (loopcountsize != 0) {
    158 		loopcountbuf = (Gdiplus::PropertyItem *)malloc(loopcountsize);
    159 		if (loopcountbuf == NULL)
    160 		    goto last;
    161 		status = gpbitmap->GetPropertyItem(PropertyTagLoopCount, loopcountsize, loopcountbuf);
    162 		if (status != Gdiplus::Ok)
    163 		    goto gdip_error;
    164 		if (loopcountbuf->type == PropertyTagTypeShort &&
    165 			loopcountbuf->length >= sizeof(unsigned short)) {
    166 		    loopcount = *(unsigned short *)loopcountbuf->value;
    167 		} else if (loopcountbuf->type == PropertyTagTypeLong &&
    168 			loopcountbuf->length >= sizeof(unsigned long)) {
    169 		    loopcount = *(unsigned long *)loopcountbuf->value;
    170 		}
    171 	    }
    172 	    if (xi->logfile)
    173 		fprintf(xi->logfile, "win_image::load(): loopcount=%lu\n", loopcount);
    174 	    // Get delay times
    175 	    if (delaysize != 0) {
    176 		delaybuf = (Gdiplus::PropertyItem *)malloc(delaysize);
    177 		if (delaybuf == NULL)
    178 		    goto last;
    179 		status = gpbitmap->GetPropertyItem(PropertyTagFrameDelay, delaysize, delaybuf);
    180 		if (status != Gdiplus::Ok)
    181 		    goto gdip_error;
    182 		delay = new(std::nothrow) unsigned long[nframe];
    183 		if (delay == NULL)
    184 		    goto last;
    185 		std::fill(delay, delay + nframe, 0);
    186 		if (delaybuf->type == PropertyTagTypeShort) {
    187 		    unsigned int count = delaybuf->length / sizeof(unsigned short);
    188 		    for (unsigned int i = 0; i != count; ++i)
    189 			delay[i] = ((unsigned short *)delaybuf->value)[i] * 10;
    190 		} else if (delaybuf->type == PropertyTagTypeLong) {
    191 		    unsigned int count = delaybuf->length / sizeof(unsigned long);
    192 		    for (unsigned int i = 0; i != count; ++i)
    193 			delay[i] = ((unsigned long *)delaybuf->value)[i] * 10;
    194 		}
    195 	    }
    196 	    if (xi->logfile) {
    197 		for (unsigned int i = 0; i != nframe; ++i)
    198 		    fprintf(xi->logfile, "win_image::load(): delay[%u]=%lu\n", i, delay[i]);
    199 	    }
    200 	    // Get dimensions
    201 	    for (unsigned int nextframe = 1; nextframe != nframe; ++nextframe) {
    202 		status = gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, nextframe);
    203 		if (status != Gdiplus::Ok) {
    204 		    if (xi->logfile)
    205 			fprintf(xi->logfile, "win_image::load(): SelectActiveFrame() to %u failed = %d: %s\n",
    206 				nextframe, (int)status, gdip_strerror(status));
    207 		    goto last;
    208 		}
    209 		unsigned int iw = gpbitmap->GetWidth();
    210 		unsigned int ih = gpbitmap->GetHeight();
    211 		if (iw > width)
    212 		    width = iw;
    213 		if (ih > height)
    214 		    height = ih;
    215 		if (xi->logfile)
    216 		    fprintf(xi->logfile, "win_image::load(): size[%u]=%ux%u\n", nextframe, iw, ih);
    217 	    }
    218 	    // Go to the first frame
    219 	    first_frame = (0 < -wop->max_anim && -wop->max_anim < nframe) ? (nframe + wop->max_anim) : 0;
    220 	    status = gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, first_frame);
    221 	    if (status != Gdiplus::Ok) {
    222 		if (xi->logfile)
    223 		    fprintf(xi->logfile, "win_image::load(): SelectActiveFrame() to %u frame = %d: %s\n",
    224 			    first_frame, (int)status, gdip_strerror(status));
    225 		goto last;
    226 	    }
    227 	}
    228 	// Allocate cache array
    229 	cache = new(std::nothrow) cache_handle[nframe];
    230 	if (cache == NULL)
    231 	    goto last;
    232 	std::fill(cache, cache + nframe, (cache_handle)NULL);
    233 	// Sanity check
    234 	if (width > SHRT_MAX || height > SHRT_MAX) {
    235 	    if (xi->logfile)
    236 		fprintf(xi->logfile, "win_image::load(): too big image: %ux%u\n", width, height);
    237 	    goto last;
    238 	}
    239 	// Store the results
    240 	if (wreturn)
    241 	    *wreturn = (int)width;
    242 	if (hreturn)
    243 	    *hreturn = (int)height;
    244 	this->gpbitmap = gpbitmap;
    245 	*p_gpbitmap = NULL; // ownership transfer
    246 	this->nframe = nframe;
    247 	this->current = first_frame;
    248 	this->tick = 0;
    249 	this->loopcount = loopcount;
    250 	this->delay = delay;
    251 	delay = NULL; // ownership transfer
    252 	this->cache = cache;
    253 	cache = NULL; // ownership transfer
    254 	retval = 1;
    255     }
    256     goto last;
    257     
    258 gdip_error:
    259     gdip_perror(wop, status, "win_image::load");
    260     goto last;
    261 last:
    262     delete[] cache;
    263     delete[] delay;
    264     free(delaybuf);
    265     free(loopcountbuf);
    266     if (xi->logfile)
    267 	fprintf(xi->logfile, "win_image::load() = %d\n", retval);
    268     return retval;
    269 }
    270 
    271 int
    272 win_image::show(w3mimg_op *wop, int sx, int sy, int sw, int sh, int x, int y) THROW_NONE
    273 {
    274     PRELUDE(wop, xi);
    275     int retval = 0;
    276     Gdiplus::Status status = Gdiplus::Ok;
    277     cache_handle newcache = NULL;
    278 
    279     if (xi->logfile)
    280 	fprintf(xi->logfile, "win_image::show(%p, %d, %d, %d, %d, %d, %d) start current=%u\n",
    281 		wop, sx, sy, sw, sh, x, y, this->current);
    282     if (!window_alive(wop))
    283 	goto last;
    284     {
    285 	int xx = x + wop->offset_x;
    286 	int yy = y + wop->offset_y;
    287 
    288 	// Prepare the Graphics object for painting
    289 	Gdiplus::Graphics graphics(xi->window);
    290 	if ((status = graphics.GetLastStatus()) != Gdiplus::Ok)
    291 	    goto gdip_error;
    292 	Gdiplus::Rect clip(xx, yy, sw, sh);
    293 	status = graphics.SetClip(clip);
    294 	if (status != Gdiplus::Ok)
    295 	    goto gdip_error;
    296 
    297 	unsigned int retry_count = 2;
    298 	do {
    299 	    if (this->cache[this->current] == NULL) {
    300 		// Cache the image
    301 		Gdiplus::Bitmap tmp_bitmap(sw, sh, &graphics);
    302 		if ((status = tmp_bitmap.GetLastStatus()) != Gdiplus::Ok)
    303 		    goto gdip_error;
    304 		Gdiplus::Graphics tmp_graphics(&tmp_bitmap);
    305 		if ((status = tmp_graphics.GetLastStatus()) != Gdiplus::Ok)
    306 		    goto gdip_error;
    307 		status = tmp_graphics.Clear(Gdiplus::Color(xi->background_pixel));
    308 		if (status != Gdiplus::Ok)
    309 		    goto gdip_error;
    310 		status = tmp_graphics.DrawImage(this->gpbitmap, 0, 0, sw, sh);
    311 		if (status != Gdiplus::Ok)
    312 		    goto gdip_error;
    313 		Gdiplus::CachedBitmap *newcache = new Gdiplus::CachedBitmap(&tmp_bitmap, &graphics);
    314 		if (newcache == NULL)
    315 		    goto last;
    316 		if ((status = newcache->GetLastStatus()) != Gdiplus::Ok)
    317 		    goto gdip_error;
    318 		this->cache[this->current] = newcache;
    319 		newcache = NULL; // ownership transfer
    320 		--retry_count;
    321 	    }
    322 	    // Draw it
    323 	    status = graphics.DrawCachedBitmap(this->cache[this->current], xx - sx, yy - sy);
    324 	    if (status == Gdiplus::Ok)
    325 		break;
    326 	    // maybe the user altered the display configuration
    327 	    if (xi->logfile)
    328 		fprintf(xi->logfile, "win_image::show(): stale cache = %d: %s\n",
    329 			(int)status, gdip_strerror(status));
    330 	    delete this->cache[this->current];
    331 	    this->cache[this->current] = NULL;
    332 	    if (retry_count == 0)
    333 		goto last;
    334 	} while (1);
    335 
    336 	retval = 1;
    337     }
    338     goto last;
    339 gdip_error:
    340     gdip_perror(wop, status, "win_image::show");
    341     goto last;
    342 last:
    343     delete newcache;
    344     if (xi->logfile)
    345 	fprintf(xi->logfile, "win_image::show() = %d\n", retval);
    346     return retval;
    347 }
    348 
    349 int
    350 win_image::animate(w3mimg_op * wop) THROW_NONE
    351 {
    352     PRELUDE(wop, xi);
    353     int retval = 0;
    354     Gdiplus::Status status = Gdiplus::Ok;
    355 
    356     if (xi->logfile)
    357 	fprintf(xi->logfile, "win_image::animate(%p) start\n", wop);
    358     {
    359 	if (this->nframe <= 1)
    360 	    goto animation_end;
    361 #define UNIT_TICK 50
    362 #define MIN_DELAY (UNIT_TICK*2)
    363 	this->tick += UNIT_TICK;
    364 	if (this->tick >= MIN_DELAY && this->tick >= this->delay[this->current]) {
    365 	    this->tick = 0;
    366 	    unsigned int nextframe = this->current + 1;
    367 	    if (wop->max_anim == nextframe)
    368 		goto animation_end;
    369 	    if (nextframe >= this->nframe) {
    370 		if (this->loopcount == 1 || wop->max_anim < 0) // end of the loop
    371 		    goto animation_end;
    372 		nextframe = 0;
    373 	    }
    374 	    status = this->gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, nextframe);
    375 	    if (status != Gdiplus::Ok)
    376 		goto gdip_error;
    377 	    this->current = nextframe;
    378 	    if (nextframe == 0 && this->loopcount > 1)
    379 		--this->loopcount;
    380 	}
    381 animation_end:
    382 	retval = 1;
    383     }
    384     goto last;
    385 gdip_error:
    386     gdip_perror(wop, status, "win_image::animate");
    387     goto last;
    388 last:
    389     if (xi->logfile)
    390 	fprintf(xi->logfile, "win_image::animate() = %d\n", retval);
    391     return retval;
    392 }
    393 
    394 static int
    395 window_alive(w3mimg_op *wop) THROW_NONE
    396 {
    397     PRELUDE(wop, xi);
    398     if (xi->window == NULL)
    399 	return 0;
    400     if (IsWindow(xi->window))
    401 	return 1;
    402     xi->window = NULL;
    403     fputs("w3mimgdisplay: target window disappeared\n", stderr);
    404     if (xi->logfile)
    405 	fputs("w3mimgdisplay: target window disappeared\n", xi->logfile);
    406     return 0;
    407 }
    408 
    409 static int
    410 win_init(w3mimg_op *) THROW_NONE
    411 {
    412     // nothing to do
    413     return 1;
    414 }
    415 
    416 static int
    417 win_finish(w3mimg_op *) THROW_NONE
    418 {
    419     // nothing to do
    420     return 1;
    421 }
    422 
    423 static int
    424 win_clear(w3mimg_op *wop, int x, int y, int w, int h) THROW_NONE
    425 {
    426     PRELUDE(wop, xi);
    427     Gdiplus::Status status = Gdiplus::Ok;
    428     int retval = 0;
    429 
    430     if (xi->logfile)
    431 	fprintf(xi->logfile, "win_clear(%p, %d, %d, %d, %d) start\n",
    432 		wop, x, y, w, h);
    433     if (!window_alive(wop))
    434 	goto last;
    435     {
    436 	if (x < 0)
    437 	    x = 0;
    438 	if (y < 0)
    439 	    y = 0;
    440 	Gdiplus::SolidBrush brush(Gdiplus::Color(xi->background_pixel));
    441 	if ((status = brush.GetLastStatus()) != Gdiplus::Ok)
    442 	    goto gdip_error;
    443 	Gdiplus::Graphics graphics(xi->window);
    444 	if ((status = graphics.GetLastStatus()) != Gdiplus::Ok)
    445 	    goto gdip_error;
    446 	status = graphics.FillRectangle(&brush, x + wop->offset_x, y + wop->offset_y, w, h);
    447 	if (status != Gdiplus::Ok)
    448 	    goto gdip_error;
    449 	retval = 1;
    450     }
    451     goto last;
    452 gdip_error:
    453     gdip_perror(wop, status, "win_clear");
    454     goto last;
    455 last:
    456     if (xi->logfile)
    457 	fprintf(xi->logfile, "win_clear() = %d\n", retval);
    458     return retval;
    459 }
    460 
    461 static int
    462 win_active(w3mimg_op * wop) THROW_NONE
    463 {
    464     return window_alive(wop);
    465 }
    466 
    467 static void
    468 win_set_background(w3mimg_op * wop, char *background) THROW_NONE
    469 {
    470     PRELUDE(wop, xi);
    471 
    472     HDC windc = NULL;
    473 
    474     if (xi->logfile)
    475 	fprintf(xi->logfile, "win_set_background(%p, \"%s\")\n", wop, background ? background : "(auto)");
    476     {
    477 	// Fallback value
    478 	// xi->background_pixel = Gdiplus::Color::White;
    479 	xi->background_pixel = Gdiplus::Color::Black;
    480 
    481 	// Explicit
    482 	if (background) {
    483 	    unsigned int r, g, b;
    484 	    if (sscanf(background, "#%02x%02x%02x", &r, &g, &b) == 3) {
    485 		xi->background_pixel = Gdiplus::Color::MakeARGB((BYTE)255, (BYTE)r, (BYTE)g, (BYTE)b);
    486 		goto last;
    487 	    }
    488 	}
    489 
    490 	// Auto detect
    491 	if (xi->window == NULL || !IsWindow(xi->window))
    492 	    goto last;
    493 	windc = GetDC(xi->window);
    494 	if (windc == NULL)
    495 	    goto win32_error;
    496 	COLORREF c = GetPixel(windc,
    497 		    (wop->offset_x >= 1) ? (wop->offset_x - 1) : 0,
    498 		    (wop->offset_y >= 1) ? (wop->offset_y - 1) : 0);
    499 	xi->background_pixel = Gdiplus::Color::MakeARGB(
    500 		(BYTE)255, GetRValue(c), GetGValue(c), GetBValue(c));
    501     }
    502     goto last;
    503 win32_error:
    504     win32_perror(wop, GetLastError(), "win_set_background");
    505     goto last;
    506 last:
    507     if (xi->logfile)
    508 	fprintf(xi->logfile, "win_set_background() result = #%06x\n",
    509 		(unsigned int)xi->background_pixel);
    510     if (windc)
    511 	ReleaseDC(xi->window, windc);
    512 }
    513 
    514 static void
    515 win_sync(w3mimg_op *) THROW_NONE
    516 {
    517     // nothing to do
    518     return;
    519 }
    520 
    521 static void
    522 win_close(w3mimg_op * wop) THROW_NONE
    523 {
    524     PRELUDE(wop, xi);
    525 
    526     if (xi->gdiplus_token)
    527 	Gdiplus::GdiplusShutdown(xi->gdiplus_token);
    528     if (xi->logfile) {
    529 	fprintf(xi->logfile, "win_close(%p)\n", wop);
    530 	fclose(xi->logfile);
    531     }
    532     delete xi;
    533     delete wop;
    534 }
    535 
    536 static Gdiplus::Bitmap *
    537 read_image_file(w3mimg_op *wop, const char *fname) THROW_NONE
    538 {
    539     PRELUDE(wop, xi);
    540     Gdiplus::Status status = Gdiplus::Ok;
    541     Gdiplus::Bitmap *retval = NULL;
    542 
    543     WCHAR *wfname = NULL;
    544     Gdiplus::Bitmap *gpbitmap = NULL;
    545 
    546     if (xi->logfile)
    547 	fprintf(xi->logfile, "read_image_file(%p, \"%s\") start\n", wop, fname);
    548     {
    549 	wfname = (WCHAR *)cygwin_create_path(CCP_POSIX_TO_WIN_W, fname);
    550 	if (wfname == NULL)
    551 	    goto last;
    552 	gpbitmap = new Gdiplus::Bitmap(wfname);
    553 	if (gpbitmap == NULL)
    554 	    goto last;
    555 	status = gpbitmap->GetLastStatus();
    556 	switch (status) {
    557 	    case Gdiplus::Ok:
    558 		break;
    559 	    case Gdiplus::UnknownImageFormat:
    560 	    case Gdiplus::FileNotFound:
    561 		goto last; // fail silently
    562 	    default:
    563 		goto gdip_error;
    564 	}
    565 	retval = gpbitmap;
    566 	gpbitmap = NULL; // ownership transfer
    567     }
    568     goto last;
    569 gdip_error:
    570     gdip_perror(wop, status, "read_image_file");
    571 last:
    572     delete gpbitmap;
    573     free(wfname);
    574     if (xi->logfile)
    575 	fprintf(xi->logfile, "read_image_file() = %p\n", retval);
    576     return retval;
    577 }
    578 
    579 static int
    580 win_load_image(w3mimg_op * wop, W3MImage * img, char *fname, int w, int h) THROW_NONE
    581 {
    582     PRELUDE(wop, xi);
    583     int retval = 0;
    584     Gdiplus::Bitmap *gpbitmap = NULL;
    585     win_image *wimg = NULL;
    586 
    587     assert(img);
    588     if (xi->logfile) {
    589 	fprintf(xi->logfile, "win_load_image(%p, %p, \"%s\", %d, %d) start\n",
    590 		wop, img, fname, w, h);
    591     }
    592     {
    593 	gpbitmap = read_image_file(wop, fname);
    594 	if (gpbitmap == NULL)
    595 	    goto last;
    596 	int iw, ih;
    597 	wimg = new(std::nothrow) win_image;
    598 	if (!wimg->load(wop, &gpbitmap, &iw, &ih))
    599 	    goto last;
    600 	img->pixmap = wimg;
    601 	wimg = NULL; // ownership transfer
    602 	img->width = (0 <= w && w < iw) ? w : iw;
    603 	img->height = (0 <= h && h < ih) ? h : ih;
    604 	retval = 1;
    605     }
    606     goto last;
    607 last:
    608     delete wimg;
    609     delete gpbitmap;
    610     if (xi->logfile)
    611 	fprintf(xi->logfile, "win_load_image() = %d\n", retval);
    612     return retval;
    613 }
    614 
    615 static int
    616 win_show_image(w3mimg_op * wop, W3MImage * img, int sx, int sy, int sw,
    617 	       int sh, int x, int y) THROW_NONE
    618 {
    619     PRELUDE(wop, xi);
    620     int retval = 0;
    621 
    622     assert(img);
    623     win_image *wimg = static_cast<win_image *>(img->pixmap);
    624     assert(wimg);
    625     
    626     if (xi->logfile)
    627 	fprintf(xi->logfile, "win_show_image(%p, %p, %d, %d, %d, %d, %d, %d) start\n",
    628 		wop, img, sx, sy, sw, sh, x, y);
    629     int sww = sw ? sw : img->width;
    630     int shh = sh ? sh : img->height;
    631     retval = wimg->show(wop, sx, sy, sww, shh, x, y)
    632 	&& wimg->animate(wop);
    633     if (xi->logfile)
    634 	fprintf(xi->logfile, "win_show_image() = %d\n", retval);
    635     return retval;
    636 }
    637 
    638 static void
    639 win_free_image(w3mimg_op * wop, W3MImage * img) THROW_NONE
    640 {
    641     PRELUDE(wop, xi);
    642 
    643     assert(img);
    644     if (xi->logfile)
    645 	fprintf(xi->logfile, "win_free_image(%p, %p) pixmap=%p\n", wop, img, img->pixmap);
    646     delete static_cast<win_image *>(img->pixmap);
    647     img->pixmap = NULL;
    648     img->width = 0;
    649     img->height = 0;
    650 }
    651 
    652 static int
    653 win_get_image_size(w3mimg_op * wop, W3MImage *img_unused, char *fname, int *w, int *h) THROW_NONE
    654 {
    655     PRELUDE(wop, xi);
    656     int retval = 0;
    657     Gdiplus::Bitmap *gpbitmap = NULL;
    658     win_image *wimg = NULL;
    659 
    660     if (xi->logfile) {
    661 	fprintf(xi->logfile, "win_get_image_size(%p, %p, \"%s\", %p, %p) start\n",
    662 		wop, img_unused, fname, w, h);
    663     }
    664     {
    665 	gpbitmap = read_image_file(wop, fname);
    666 	if (gpbitmap == NULL)
    667 	    goto last;
    668 	wimg = new(std::nothrow) win_image;
    669 	if (wimg == NULL)
    670 	    goto last;
    671 	retval = wimg->load(wop, &gpbitmap, w, h);;
    672     }
    673     goto last;
    674 last:
    675     delete wimg;
    676     delete gpbitmap;
    677     if (xi->logfile)
    678 	fprintf(xi->logfile, "win_get_image_size() = %d\n", retval);
    679     return retval;
    680 }
    681 
    682 extern "C" w3mimg_op *
    683 w3mimg_winopen()
    684 {
    685     w3mimg_op *retval = NULL;
    686     Gdiplus::Status status = Gdiplus::Ok;
    687 
    688     w3mimg_op *wop = NULL;
    689     struct win_info *xi = NULL;
    690     struct window_list children = { NULL, 0, 0 };
    691 
    692     {
    693 	// Quit if running on X
    694 	const char *display_name;
    695 	if ((display_name = getenv("DISPLAY")) != NULL &&
    696 		display_name[0] && strcmp(display_name, ":0") != 0)
    697 	    return NULL;
    698 
    699 	// Allocate the context objects
    700 	wop = new(std::nothrow) w3mimg_op(); // call the default ctor instead of "new w3mimg_op;"
    701 	if (wop == NULL)
    702 	    return NULL;
    703 	wop->priv = xi = new(std::nothrow) win_info();
    704 	if (xi == NULL)
    705 	    goto last;
    706 
    707 	// Debug logging
    708 	const char *logging_dir;
    709 	if ((logging_dir = getenv("W3MIMG_LOGDIR")) != NULL &&
    710 		logging_dir[0]) {
    711 	    size_t l = strlen(logging_dir) + sizeof "/w3mimgXXXXXXXXXX.log";
    712 	    char *fname = (char *)malloc(l);
    713 	    snprintf(fname, l, "%s/w3mimg%d.log", logging_dir, (int)getpid());
    714 	    xi->logfile = fopen(fname, "a");
    715 	    if (xi->logfile) {
    716 		setvbuf(xi->logfile, NULL, _IONBF, 0);
    717 		fprintf(xi->logfile, "\nw3mimg_winopen() start pid=%d\n", (int)getpid());
    718 	    }
    719 	}
    720 
    721 	// Look for the window to draw the image
    722 	xi->window = NULL;
    723 	const char *windowid;
    724 	if ((windowid = getenv("WINDOWID")) != NULL)
    725 	    xi->window = FindWindowA(windowid, NULL);
    726 	if (!xi->window)
    727 	    xi->window = GetForegroundWindow();
    728 	if (!xi->window)
    729 	    goto win32_error;
    730 
    731 	WINDOWINFO winfo = WINDOWINFO();
    732 	winfo.cbSize = sizeof winfo;
    733 	GetWindowInfo(xi->window, &winfo);
    734 	wop->width = (int)(winfo.rcClient.right - winfo.rcClient.left);
    735 	wop->height = (int)(winfo.rcClient.bottom - winfo.rcClient.top);
    736 
    737 	// Search decendant windows and find out which is the text window
    738 	while (1) {
    739 	    HWND p_window = xi->window;
    740 
    741 	    clear_window_list(&children);
    742 	    EnumChildWindows(xi->window, &store_to_window_list, (LPARAM)&children);
    743 	    for (unsigned int i = 0; i < children.nwnd; i++) {
    744 		int width, height;
    745 
    746 		GetWindowInfo(children.wnd[i], &winfo);
    747 		width = (int)(winfo.rcClient.right - winfo.rcClient.left);
    748 		height = (int)(winfo.rcClient.bottom - winfo.rcClient.top);
    749 		if (width > wop->width * 0.7 &&
    750 			height > wop->height * 0.7) {
    751 		    /* maybe text window */
    752 		    wop->width = width;
    753 		    wop->height = height;
    754 		    xi->window = children.wnd[i];
    755 		}
    756 	    }
    757 	    if (p_window == xi->window)
    758 		break;
    759 	}
    760 
    761 	// Terminal may leave some border pixels
    762 	wop->offset_x = OFFSET_X;
    763 	wop->offset_y = OFFSET_Y;
    764 
    765 	// Start up the GDI+
    766 	Gdiplus::GdiplusStartupInput startup_input; /// default ctor
    767 	status = Gdiplus::GdiplusStartup(&xi->gdiplus_token, &startup_input, NULL);
    768 	if (status != Gdiplus::Ok)
    769 	    goto gdip_error;
    770 
    771 	// Fill the context object
    772 	wop->init = win_init;
    773 	wop->finish = win_finish;
    774 	wop->active = win_active;
    775 	wop->set_background = win_set_background;
    776 	wop->sync = win_sync;
    777 	wop->close = win_close;
    778 	wop->clear = win_clear;
    779 
    780 	wop->load_image = win_load_image;
    781 	wop->show_image = win_show_image;
    782 	wop->free_image = win_free_image;
    783 	wop->get_image_size = win_get_image_size;
    784 
    785 	retval = wop; // take care of the object lifetime
    786     }
    787     goto last;
    788 win32_error:
    789     win32_perror(wop, GetLastError(), "w3mimg_winopen");
    790     goto last;
    791 gdip_error:
    792     gdip_perror(wop, status, "w3mimg_winopen");
    793     goto last;
    794 last:
    795     if (xi && xi->logfile)
    796 	fprintf(xi->logfile, "w3mimg_winopen() = %p\n", retval);
    797     clear_window_list(&children);
    798     if (!retval) {
    799 	if (xi) {
    800 	    if (xi->gdiplus_token)
    801 		Gdiplus::GdiplusShutdown(xi->gdiplus_token);
    802 	    if (xi->logfile)
    803 		fclose(xi->logfile);
    804 	    delete xi;
    805 	}
    806 	delete wop;
    807     }
    808     return retval;
    809 }
    810 
    811 static BOOL CALLBACK
    812 store_to_window_list(HWND hWnd, LPARAM wndlist) THROW_NONE
    813 {
    814     struct window_list *wl = (struct window_list *)wndlist;
    815 
    816     if (wl->nwnd >= wl->capacity) {
    817 	size_t newsize = (wl->capacity < 4 ) ? 4 : (wl->capacity * 2);
    818 	HWND *newbuf = (HWND *)realloc(wl->wnd, newsize * sizeof newbuf[0]);
    819 	if (newbuf == NULL) {
    820 	    clear_window_list(wl);
    821 	    return FALSE;
    822 	}
    823 	wl->wnd = newbuf;
    824 	wl->capacity = newsize;
    825     }
    826     wl->wnd[wl->nwnd++] = hWnd;
    827     return TRUE;
    828 }
    829 
    830 static void
    831 clear_window_list(struct window_list *wl) THROW_NONE
    832 {
    833     free(wl->wnd);
    834     wl->wnd = NULL;
    835     wl->nwnd = 0;
    836     wl->capacity = 0;
    837 }
    838 
    839 static const char *
    840 gdip_strerror(Gdiplus::Status status) THROW_NONE
    841 {
    842     size_t i;
    843     struct status_rec {
    844 	Gdiplus::Status code;
    845 	const char *str;
    846     };
    847     static const struct status_rec table[] = {
    848 #define ERRITEM(s) { Gdiplus::s, #s }
    849 	ERRITEM(Ok),
    850 	ERRITEM(GenericError),
    851 	ERRITEM(InvalidParameter),
    852 	ERRITEM(OutOfMemory),
    853 	ERRITEM(ObjectBusy),
    854 	ERRITEM(InsufficientBuffer),
    855 	ERRITEM(NotImplemented),
    856 	ERRITEM(Win32Error),
    857 	ERRITEM(WrongState),
    858 	ERRITEM(Aborted),
    859 	ERRITEM(FileNotFound),
    860 	ERRITEM(ValueOverflow),
    861 	ERRITEM(AccessDenied),
    862 	ERRITEM(UnknownImageFormat),
    863 	ERRITEM(FontFamilyNotFound),
    864 	ERRITEM(FontStyleNotFound),
    865 	ERRITEM(NotTrueTypeFont),
    866 	ERRITEM(UnsupportedGdiplusVersion),
    867 	ERRITEM(GdiplusNotInitialized),
    868 	ERRITEM(PropertyNotFound),
    869 	ERRITEM(PropertyNotSupported),
    870 	ERRITEM(ProfileNotFound),
    871 #undef ERRITEM
    872     };
    873     for (i = 0; i != sizeof table / sizeof table[0]; ++i)
    874 	if (table[i].code == status)
    875 	    return table[i].str;
    876     return "unknown";
    877 }
    878 
    879 static void
    880 gdip_perror(w3mimg_op *wop, Gdiplus::Status status, const char *func) THROW_NONE
    881 {
    882     const char *s = gdip_strerror(status);
    883     fprintf(stderr, "w3mimgdisplay: GDI+ error %d: %s\n", (int)status, s);
    884     if (wop && wop->priv) {
    885 	struct win_info *xi = (struct win_info *)wop->priv;
    886 	if (xi->logfile) {
    887 	    fprintf(xi->logfile, "%s(): GDI+ error %d: %s\n", func, (int)status, s);
    888 	}
    889     }
    890 }
    891 
    892 // Don't free() the result; use LocalFree() instead
    893 static char *
    894 win32_strerror_alloc(DWORD status) THROW_NONE
    895 {
    896     char *errbuf = NULL;
    897 
    898     FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
    899 	    FORMAT_MESSAGE_FROM_SYSTEM |
    900 	    FORMAT_MESSAGE_IGNORE_INSERTS,
    901 	    NULL, status, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
    902 	    (LPSTR)&errbuf, 0, NULL);
    903     if (errbuf) {
    904 	size_t len = strlen(errbuf);
    905 	if (len > 0 && errbuf[len - 1] == '\n')
    906 	    errbuf[len - 1] = '\0';
    907     }
    908     return errbuf;
    909 }
    910 
    911 static void
    912 win32_perror(w3mimg_op *wop, DWORD status, const char *func) THROW_NONE
    913 {
    914     char *errbuf = win32_strerror_alloc(status);
    915     const char *s = errbuf ? errbuf : "(unknown)";
    916 
    917     fprintf(stderr, "w3mimgdisplay: Win32 error %u: %s\n", (unsigned int)status, s);
    918     if (wop && wop->priv) {
    919 	struct win_info *xi = (struct win_info *)wop->priv;
    920 	if (xi->logfile) {
    921 	    fprintf(xi->logfile, "%s(): Win32 error %u: %s\n",
    922 		    func, (unsigned int)status, s);
    923 	}
    924     }
    925     LocalFree(errbuf);
    926 }
    927 
    928 #if 0 /* unused */
    929 static WCHAR *
    930 mb2wstr_alloc(const char *s) THROW_NONE
    931 {
    932     int len;
    933     WCHAR *buf = NULL;
    934 
    935     len = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, s, -1, NULL, 0);
    936     if (len <= 0) {
    937 	fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
    938 		(unsigned int)GetLastError());
    939 	goto error;
    940     }
    941     buf = (WCHAR *)malloc(len * sizeof(WCHAR)); /* including L'\0' */
    942     if (!buf)
    943 	goto error;
    944     len = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, s, -1, buf, len);
    945     if (len <= 0) {
    946 	fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
    947 		(unsigned int)GetLastError());
    948 	goto error;
    949     }
    950     return buf;
    951 error:
    952     free(buf);
    953     return NULL;
    954 }
    955 
    956 static char *
    957 wstr2mb_alloc(const WCHAR *ws) THROW_NONE
    958 {
    959     int len;
    960     char *buf = NULL;
    961 
    962     len = WideCharToMultiByte(CP_OEMCP, WC_COMPOSITECHECK, ws, -1, NULL, 0, NULL, NULL);
    963     if (len <= 0) {
    964 	fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
    965 		(unsigned int)GetLastError());
    966 	goto error;
    967     }
    968     buf = (char *)malloc(len); /* including '\0' */
    969     if (!buf)
    970 	goto error;
    971     len = WideCharToMultiByte(CP_OEMCP, WC_COMPOSITECHECK, ws, -1, buf, len, NULL, NULL);
    972     if (len <= 0) {
    973 	fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
    974 		(unsigned int)GetLastError());
    975 	goto error;
    976     }
    977     return buf;
    978 error:
    979     free(buf);
    980     return NULL;
    981 }
    982 #endif /* unused */