/* Copyright (C) 1992, 1995, 1996, 1997, 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gdevwdib.c,v 1.3.2.1.2.1 2003/01/17 00:49:02 giles Exp $ */
/* MS Windows 3.n driver for Ghostscript using a DIB for buffering. */
#include "gdevmswn.h"
#include "gxdevmem.h"
#include "gsdll.h"
#include "gsdllwin.h"

#ifdef __WIN32__
#  define USE_SEGMENTS 0
#else
#  define USE_SEGMENTS 1
#endif

/* Make sure we cast to the correct structure type. */
typedef struct gx_device_win_dib_s gx_device_win_dib;

#undef wdev
#define wdev ((gx_device_win_dib *)dev)

/* Device procedures */

/* See gxdevice.h for the definitions of the procedures. */
private dev_proc_open_device(win_dib_open);
private dev_proc_get_initial_matrix(win_dib_get_initial_matrix);
private dev_proc_close_device(win_dib_close);
private dev_proc_fill_rectangle(win_dib_fill_rectangle);
private dev_proc_copy_mono(win_dib_copy_mono);
private dev_proc_copy_color(win_dib_copy_color);
private dev_proc_get_bits(win_dib_get_bits);
private dev_proc_put_params(win_dib_put_params);

/* Windows-specific procedures */
private win_proc_repaint(win_dib_repaint);
private win_proc_alloc_bitmap(win_dib_alloc_bitmap);
private win_proc_free_bitmap(win_dib_free_bitmap);

/* The device descriptor */
struct gx_device_win_dib_s {
    gx_device_common;
    gx_device_win_common;

#if USE_SEGMENTS
    /* The following help manage the division of the DIB */
    /* into 64K segments.  Each block of y_block scan lines */
    /* starting at y_base mod 64K falls in a single segment. */
    /* Since the raster is a power of 2, y_block is a power of 2. */

    int y_block;
    int y_base;
    int y_mask;			/* y_block - 1 */
#endif				/* USE_SEGMENTS */

    HGLOBAL hmdata;
#ifdef __WIN32__
    HANDLE hmtx;
#endif
    int lock_count;
    gx_device_memory mdev;
};
private const gx_device_procs win_dib_procs =
{
    win_dib_open,
    win_dib_get_initial_matrix,
    win_sync_output,
    win_output_page,
    win_dib_close,
    win_map_rgb_color,
    win_map_color_rgb,
    win_dib_fill_rectangle,
    NULL,			/* tile_rectangle */
    win_dib_copy_mono,
    win_dib_copy_color,
    NULL,			/* draw_line */
    win_dib_get_bits /* NULL */ ,	/* get_bits */
    win_get_params,
    win_dib_put_params,
    NULL,			/* map_cmyk_color */
    win_get_xfont_procs,
    NULL,			/* get_xfont_device */
    NULL,			/* map_rgb_alpha_color */
    gx_page_device_get_page_device
};
gx_device_win_dib far_data gs_mswindll_device =
{
    std_device_std_body(gx_device_win_dib, &win_dib_procs, "mswindll",
			INITIAL_WIDTH, INITIAL_HEIGHT,	/* win_open() fills these in later */
			INITIAL_RESOLUTION, INITIAL_RESOLUTION	/* win_open() fills these in later */
    ),
    {0},			/* std_procs */
    0,				/* BitsPerPixel */
    2,				/* nColors */
    0,				/* mapped_color_flags */
    win_dib_alloc_bitmap,
    win_dib_free_bitmap
};

/* forward declarations */
private HGLOBAL win_dib_make_dib(gx_device_win * dev, int orgx, int orgy, int wx, int wy);
private int win_dib_lock_device(unsigned char *device, int flag);


/* Open the win_dib driver */
private int
win_dib_open(gx_device * dev)
{
    int code = win_open(dev);

    if (code < 0)
	return code;

#ifdef __WIN32__
    if (!is_win32s)
	wdev->hmtx = CreateMutex(NULL, FALSE, NULL);	/* unnamed mutex, initially unowned */
#endif
    if (gdev_mem_device_for_bits(dev->color_info.depth) == 0) {
	win_close(dev);
	return gs_error_rangecheck;
    }
    code = win_dib_alloc_bitmap((gx_device_win *) dev, dev);
    if (code < 0) {
	win_close(dev);
	return code;
    }
    /* notify caller about new device */
    if (pgsdll_callback) {
	(*pgsdll_callback) (GSDLL_DEVICE, (unsigned char *)dev, 1);
	(*pgsdll_callback) (GSDLL_SIZE, (unsigned char *)dev,
			(dev->width & 0xffff) +
			((ulong) (dev->height & 0xffff) << 16));
    }
    return code;
}

/* Get the initial matrix.  DIBs, unlike most displays, */
/* put (0,0) in the lower left corner. */
private void
win_dib_get_initial_matrix(gx_device * dev, gs_matrix * pmat)
{
    pmat->xx = dev->x_pixels_per_inch / 72.0;
    pmat->xy = 0.0;
    pmat->yx = 0.0;
    pmat->yy = dev->y_pixels_per_inch / 72.0;
    pmat->tx = 0.0;
    pmat->ty = 0.0;
}

/* Close the win_dib driver */
private int
win_dib_close(gx_device * dev)
{
    int code;

    /* wait until bitmap is not being used by caller */
    win_dib_lock_device((unsigned char *)dev, 1);
    if (pgsdll_callback)
	(*pgsdll_callback) (GSDLL_DEVICE, (unsigned char *)dev, 0);
    win_dib_lock_device((unsigned char *)dev, 0);
    win_dib_free_bitmap((gx_device_win *) dev);
#ifdef __WIN32__
    if (!is_win32s)
	CloseHandle(wdev->hmtx);
#endif
    code = win_close(dev);
    return code;
}

#define wmdev ((gx_device *)&wdev->mdev)
#define wmproc(proc) (*dev_proc(&wdev->mdev, proc))

#if USE_SEGMENTS

/* The drawing routines must all be careful not to cross */
/* a segment boundary. */

#define single_block(y, h)\
  !(((y - wdev->y_base) ^ (y - wdev->y_base + h - 1)) & ~wdev->y_mask)

#define BEGIN_BLOCKS\
{	int by, bh, left = h;\
	for ( by = y; left > 0; by += bh, left -= bh )\
	{	bh = wdev->y_block - (by & wdev->y_mask);\
		if ( bh > left ) bh = left;
#define END_BLOCKS\
	}\
}

#endif /* (!)USE_SEGMENTS */

/* Fill a rectangle. */
private int
win_dib_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
		       gx_color_index color)
{
#if USE_SEGMENTS
    if (single_block(y, h)) {
	wmproc(fill_rectangle) (wmdev, x, y, w, h, color);
    } else {			/* Divide the transfer into blocks. */
	BEGIN_BLOCKS
	    wmproc(fill_rectangle) (wmdev, x, by, w, bh, color);
	END_BLOCKS
    }
#else
    wmproc(fill_rectangle) (wmdev, x, y, w, h, color);
#endif
    return 0;
}

/* Copy a monochrome bitmap.  The colors are given explicitly. */
/* Color = gx_no_color_index means transparent (no effect on the image). */
private int
win_dib_copy_mono(gx_device * dev,
		const byte * base, int sourcex, int raster, gx_bitmap_id id,
		  int x, int y, int w, int h,
		  gx_color_index zero, gx_color_index one)
{
#if USE_SEGMENTS
    if (single_block(y, h)) {
	wmproc(copy_mono) (wmdev, base, sourcex, raster, id,
			   x, y, w, h, zero, one);
    } else {			/* Divide the transfer into blocks. */
	const byte *source = base;

	BEGIN_BLOCKS
	    wmproc(copy_mono) (wmdev, source, sourcex, raster,
			       gx_no_bitmap_id, x, by, w, bh,
			       zero, one);
	source += bh * raster;
	END_BLOCKS
    }
#else
    wmproc(copy_mono) (wmdev, base, sourcex, raster, id,
		       x, y, w, h, zero, one);
#endif
    return 0;
}

/* Copy a color pixel map.  This is just like a bitmap, except that */
/* each pixel takes 8 or 4 bits instead of 1 when device driver has color. */
private int
win_dib_copy_color(gx_device * dev,
		const byte * base, int sourcex, int raster, gx_bitmap_id id,
		   int x, int y, int w, int h)
{
#if USE_SEGMENTS
    if (single_block(y, h)) {
	wmproc(copy_color) (wmdev, base, sourcex, raster, id,
			    x, y, w, h);
    } else {			/* Divide the transfer into blocks. */
	const byte *source = base;

	BEGIN_BLOCKS
	    wmproc(copy_color) (wmdev, source, sourcex, raster,
				gx_no_bitmap_id, x, by, w, bh);
	source += by * raster;
	END_BLOCKS
    }
#else
    wmproc(copy_color) (wmdev, base, sourcex, raster, id,
			x, y, w, h);
#endif
    return 0;
}

int
win_dib_get_bits(gx_device * dev, int y, byte * str, byte ** actual_data)
{
    return wmproc(get_bits) (wmdev, y, str, actual_data);
}

int
win_dib_put_params(gx_device * dev, gs_param_list * plist)
{
    int code;

    win_dib_lock_device((unsigned char *)dev, 1);
    code = win_put_params(dev, plist);
    win_dib_lock_device((unsigned char *)dev, 0);
    return code;
}

/* ------ DLL device procedures ------ */

/* make a copy of the device bitmap and return shared memory handle to it */
/* device is a pointer to Ghostscript device from GSDLL_DEVICE message */
HGLOBAL GSDLLAPI
gsdll_copy_dib(unsigned char *device)
{
    gx_device_win_dib *dev = (gx_device_win_dib *) device;

    if (!dev || !dev->is_open || dev->mdev.width == 0 || dev->mdev.height == 0)
	return (HGLOBAL) NULL;
    return win_dib_make_dib((gx_device_win *) dev, 0, 0, dev->width, dev->height);
}

/* make a copy of the device palette and return a handle to it */
/* device is a pointer to Ghostscript device from GSDLL_DEVICE message */
HPALETTE GSDLLAPI
gsdll_copy_palette(unsigned char *device)
{
    gx_device_win_dib *dev = (gx_device_win_dib *) device;

    if (!dev || !dev->is_open || dev->mdev.width == 0 || dev->mdev.height == 0)
	return (HPALETTE) NULL;
    if (wdev->nColors > 0)
	return CreatePalette(dev->limgpalette);
    return (HPALETTE) NULL;
}

/* copy the rectangle src from the device bitmap */
/* to the rectangle dest on the device given by hdc */
/* hdc must be a device context for a device (NOT a bitmap) */
/* device is a pointer to Ghostscript device from GSDLL_DEVICE message */
void GSDLLAPI
gsdll_draw(unsigned char *device, HDC hdc, LPRECT dest, LPRECT src)
{
    gx_device_win_dib *dev = (gx_device_win_dib *) device;
    HPALETTE oldpalette;

    if (!dev || !dev->is_open || dev->mdev.width == 0 || dev->mdev.height == 0)
	return;
    if (dev->nColors > 0) {
	oldpalette = SelectPalette(hdc, dev->himgpalette, FALSE);
	RealizePalette(hdc);
    }
    win_dib_repaint((gx_device_win *) dev, hdc, dest->left, dest->top,
		    dest->right - dest->left, dest->bottom - dest->top,
		    src->left, src->top);
    if (dev->nColors > 0) {
	SelectPalette(hdc, oldpalette, FALSE);
    }
    return;
}

/* ------ Windows-specific device procedures ------ */


/* Repaint a section of the window. */
private void
win_dib_repaint(gx_device_win * dev, HDC hdc, int dx, int dy, int wx, int wy,
		int sx, int sy)
{
    struct bmi_s {
	BITMAPINFOHEADER h;
	ushort pal_index[256];
    } bmi;
    int i;
    UINT which_colors;

    memset(&bmi.h, 0, sizeof(bmi.h));
    
    bmi.h.biSize = sizeof(bmi.h);
    bmi.h.biWidth = wdev->mdev.width;
    bmi.h.biHeight = wy;
    bmi.h.biPlanes = 1;
    bmi.h.biBitCount = dev->color_info.depth;
    bmi.h.biCompression = 0;
    bmi.h.biSizeImage = 0;	/* default */
    bmi.h.biXPelsPerMeter = 0;	/* default */
    bmi.h.biYPelsPerMeter = 0;	/* default */
    
    if (dev->BitsPerPixel <= 8) {
	bmi.h.biClrUsed = wdev->nColors;
	bmi.h.biClrImportant = wdev->nColors;
	for (i = 0; i < wdev->nColors; i++)
	    bmi.pal_index[i] = i;
	which_colors = DIB_PAL_COLORS;
    } else if (dev->BitsPerPixel == 15) { /* 5-5-5 RGB mode */
	DWORD* bmi_colors = (DWORD*)(&bmi.pal_index[0]);
	bmi.h.biCompression = BI_BITFIELDS;
	bmi_colors[0] = 0x7c00;
	bmi_colors[1] = 0x03e0;
	bmi_colors[2] = 0x001f;
	which_colors = DIB_RGB_COLORS;
    } else if (dev->BitsPerPixel == 16) { /* 5-6-5 RGB mode */
	DWORD* bmi_colors = (DWORD*)(&bmi.pal_index[0]);
	bmi.h.biCompression = BI_BITFIELDS;
	bmi_colors[0] = 0xf800;
	bmi_colors[1] = 0x07e0;
	bmi_colors[2] = 0x001f;
	which_colors = DIB_RGB_COLORS;
    } else {
	bmi.h.biClrUsed = 0;
	bmi.h.biClrImportant = 0;
	which_colors = DIB_RGB_COLORS;
    }
    /*
     * Windows apparently limits the size of a single transfer
     * to 2 Mb, which can be exceeded on 24-bit displays.
     * Deal with this here.
     */
#define max_transfer 2000000
    if (wdev->mdev.raster > 0) {	/* just in case! */
	long ny = max_transfer / wdev->mdev.raster;

	for (; wy > ny; dy += ny, wy -= ny, sy += ny)
	    SetDIBitsToDevice(hdc, dx, dy, wx, ny,
			      sx, 0, 0, ny,
			      wdev->mdev.line_ptrs[wdev->height - (sy + ny)],
			      (BITMAPINFO FAR *) & bmi, which_colors);
    }
#undef max_transfer
    SetDIBitsToDevice(hdc, dx, dy, wx, wy,
		      sx, 0, 0, wy,
		      wdev->mdev.line_ptrs[wdev->height - (sy + wy)],
		      (BITMAPINFO FAR *) & bmi, which_colors);
}

/* This makes a DIB that contains all or part of the bitmap. */
/* The bitmap pixel orgx must start on a byte boundary. */
private HGLOBAL
win_dib_make_dib(gx_device_win * dev, int orgx, int orgy, int wx, int wy)
{
#define xwdev ((gx_device_win_dib *)dev)
    gx_color_value prgb[3];
    HGLOBAL hglobal;
    BYTE FAR *pDIB;
    BITMAPINFOHEADER FAR *pbmih;
    RGBQUAD FAR *pColors;
    BYTE FAR *pBits;
    BYTE FAR *pLine;
    ulong bitmapsize;
    int palcount;
    int i;
    UINT lwidth;		/* line width in bytes rounded up to multiple of 4 bytes */

#ifdef USE_SEGMENTS
    int loffset;		/* byte offset to start of line */
    UINT lseg;			/* bytes remaining in this segment */

#endif

    if (orgx + wx > wdev->width)
	wx = wdev->width - orgx;
    if (orgy + wy > wdev->height)
	wy = wdev->height - orgy;

    loffset = orgx * wdev->color_info.depth / 8;
    lwidth = ((wx * wdev->color_info.depth + 31) & ~31) >> 3;
    bitmapsize = (long)lwidth *wy;

    if (wdev->color_info.depth > 16)
	palcount = 0;
    else if (wdev->color_info.depth > 8)
	palcount = 3;		// 16-bit BI_BITFIELDS
    else
	palcount = wdev->nColors;

    hglobal = GlobalAlloc(GHND | GMEM_SHARE, sizeof(BITMAPINFOHEADER)
			  + sizeof(RGBQUAD) * palcount + bitmapsize);
    if (hglobal == (HGLOBAL) NULL) {
	MessageBeep(-1);
	return (HGLOBAL) NULL;
    }
    pDIB = (BYTE FAR *) GlobalLock(hglobal);
    if (pDIB == (BYTE FAR *) NULL) {
	MessageBeep(-1);
	return (HGLOBAL) NULL;
    }
    pbmih = (BITMAPINFOHEADER FAR *) (pDIB);
    pColors = (RGBQUAD FAR *) (pDIB + sizeof(BITMAPINFOHEADER));
    pBits = (BYTE FAR *) (pDIB + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * palcount);

    pbmih->biSize = sizeof(BITMAPINFOHEADER);
    pbmih->biWidth = wx;
    pbmih->biHeight = wy;
    pbmih->biPlanes = 1;
    pbmih->biBitCount = wdev->color_info.depth;
    pbmih->biCompression = 0;
    pbmih->biSizeImage = 0;	/* default */
    pbmih->biXPelsPerMeter = (DWORD) (dev->x_pixels_per_inch / 25.4 * 1000);
    pbmih->biYPelsPerMeter = (DWORD) (dev->y_pixels_per_inch / 25.4 * 1000);
    pbmih->biClrUsed = palcount;
    pbmih->biClrImportant = palcount;

    if (dev->BitsPerPixel == 15) { /* 5-5-5 RGB mode */
	DWORD* bmi_colors = (DWORD*)(pColors);
        pbmih->biCompression = BI_BITFIELDS;
	bmi_colors[0] = 0x7c00;
	bmi_colors[1] = 0x03e0;
	bmi_colors[2] = 0x001f;
    } 
    else if (dev->BitsPerPixel == 16) { /* 5-6-5 RGB mode */
	DWORD* bmi_colors = (DWORD*)(pColors);
        pbmih->biCompression = BI_BITFIELDS;
	bmi_colors[0] = 0xf800;
	bmi_colors[1] = 0x07e0;
	bmi_colors[2] = 0x001f;
    } 
    else {
    for (i = 0; i < palcount; i++) {
	win_map_color_rgb((gx_device *) wdev, (gx_color_index) i, prgb);
	pColors[i].rgbRed = win_color_value(prgb[0]);
	pColors[i].rgbGreen = win_color_value(prgb[1]);
	pColors[i].rgbBlue = win_color_value(prgb[2]);
	pColors[i].rgbReserved = 0;
    }
    }

    pLine = pBits;
    for (i = orgy; i < orgy + wy; i++) {
#if USE_SEGMENTS
	/* Window 3.1 has hmemcpy, but 3.0 doesn't */
	lseg = (UINT) (-OFFSETOF(pLine));	/* remaining bytes in this segment */
	if (lseg >= lwidth) {
	    _fmemcpy(pLine, xwdev->mdev.line_ptrs[i] + loffset, lwidth);
	} else {		/* break up transfer to avoid crossing segment boundary */
	    _fmemcpy(pLine, xwdev->mdev.line_ptrs[i] + loffset, lseg);
	    _fmemcpy(pLine + lseg, xwdev->mdev.line_ptrs[i] + loffset + lseg, lwidth - lseg);
	}
#else
	memcpy(pLine, xwdev->mdev.line_ptrs[i], lwidth);
#endif
	pLine += lwidth;
    }

    GlobalUnlock(hglobal);
    return hglobal;
}


/* Allocate the backing bitmap. */
private int
win_dib_alloc_bitmap(gx_device_win * dev, gx_device * param_dev)
{
    int width;
    gx_device_memory mdev;
    HGLOBAL hmdata;
    byte FAR *base;
    byte FAR *ptr_base;
    uint ptr_size;
    uint raster;

#ifdef USE_SEGMENTS
    ulong data_size;

#endif

#ifdef __WIN32__
    if (is_win32s) {
#endif
	/* Round up the width so that the scan line size is a power of 2. */
	if (dev->color_info.depth == 24) {
	    width = param_dev->width * 3 - 1;
	    while (width & (width + 1))
		width |= width >> 1;
	    width = (width + 1) / 3;
	} else {
	    width = param_dev->width - 1;
	    while (width & (width + 1))
		width |= width >> 1;
	    width++;
	}
#ifdef __WIN32__
    } else {			/* don't have to worry about segments so use less memory */
	width = param_dev->width;
    }
#endif

    /* Finish initializing the DIB. */

    gs_make_mem_device(&mdev, gdev_mem_device_for_bits(dev->color_info.depth), 0, 0, (gx_device *) dev);
    mdev.width = width;
    mdev.height = param_dev->height;
    raster = gdev_mem_raster(&mdev);
    data_size = (ulong) raster *mdev.height;

    ptr_size = sizeof(byte **) * mdev.height;
    hmdata = GlobalAlloc(0, raster + data_size + ptr_size * 2);
    if (hmdata == 0) {
	return win_nomemory();
    }
    /* Nothing can go wrong now.... */

    wdev->hmdata = hmdata;
    base = GlobalLock(hmdata);
#if USE_SEGMENTS
    /* Adjust base so scan lines, and the pointer table, */
    /* don't cross a segment boundary. */
    base += (-PTR_OFF(base) & (raster - 1));
    ptr_base = base + data_size;
    if (PTR_OFF(ptr_base + ptr_size) < ptr_size)
	base += (uint) - PTR_OFF(ptr_base);
    wdev->y_block = 0x10000L / raster;
    wdev->y_mask = wdev->y_block - 1;
    if ((wdev->y_base = PTR_OFF(base)) != 0)
	wdev->y_base = -(PTR_OFF(base) / raster);
#endif
    wdev->mdev = mdev;
    wdev->mdev.base = (byte *) base;
    wmproc(open_device) ((gx_device *) & wdev->mdev);

    if (wdev->is_open && pgsdll_callback)
	(*pgsdll_callback) (GSDLL_SIZE, (unsigned char *)dev,
			    (dev->width & 0xffff) +
			    ((ulong) (dev->height & 0xffff) << 16));

    return 0;
}


/* Free the backing bitmap. */
private void
win_dib_free_bitmap(gx_device_win * dev)
{
    HGLOBAL hmdata = wdev->hmdata;

    GlobalUnlock(hmdata);
    GlobalFree(hmdata);
}

/* Lock the device (so it's size cannot be changed) if flag = TRUE */
/* or unlock the device if flag = FALSE */
/* device is a pointer to Ghostscript device from GSDLL_DEVICE message */
private int
win_dib_lock_device(unsigned char *device, int flag)
{
    gx_device *dev = (gx_device *) device;

#ifdef __WIN32__
    if (!is_win32s) {
	if (flag) {
	    if (WaitForSingleObject(wdev->hmtx, 60000) == WAIT_TIMEOUT)
		return 2;
	    return 1;
	}
	ReleaseMutex(wdev->hmtx);
	return 0;
    }
#endif
    if (flag)
	wdev->lock_count++;
    else
	wdev->lock_count--;
    if (wdev->lock_count < 0)
	wdev->lock_count = 0;
    return wdev->lock_count;
}

int GSDLLAPI _export
gsdll_lock_device(unsigned char *device, int flag)
{
    return win_dib_lock_device(device, flag);
}


/* Copy bitmap
 * If pbmih nonzero, copy the BITMAPINFOHEADER.
 * If prgbquad nonzero, copy the palette.
 *   number of entries copied is given by pbmih->biClrUsed
 * If ppbyte nonzero, return pointer to row.
 *   pointer is only valid while device is locked
 * GS can change the palette while the device is locked.
 * Do not call this function while GS is busy.
 * If all pbmih and prgbquad and ppbyte are all NULL,
 * return value is byte count needed for BITMAPINFOHEADER
 * and palette and one bitmap row.
 * Otherwise return value is 0;
 *
 * This function exists to allow the bitmap to be copied to a file
 * or structured storage, without the overhead of having two copies
 * of the bitmap in memory at the same time.
 */
int GSDLLAPI _export
gsdll_get_bitmap_row(unsigned char *device, LPBITMAPINFOHEADER pbmih,
		     LPRGBQUAD prgbquad, LPBYTE * ppbyte, unsigned int row)
{
    int palcount;
    gx_device_win_dib *dev = (gx_device_win_dib *) device;

    palcount = (dev->color_info.depth == 24) ? 0 : dev->nColors;

    if (pbmih) {
	pbmih->biSize = sizeof(BITMAPINFOHEADER);
	pbmih->biWidth = dev->width;
	pbmih->biHeight = dev->mdev.height;
	pbmih->biPlanes = 1;
	pbmih->biBitCount = dev->color_info.depth;
	if ((dev->BitsPerPixel == 15) || (dev->BitsPerPixel == 16))
            pbmih->biCompression = BI_BITFIELDS;
	else
	pbmih->biCompression = 0;
	pbmih->biSizeImage = 0;	/* default */
	pbmih->biXPelsPerMeter = (DWORD) (dev->x_pixels_per_inch / 25.4 * 1000);
	pbmih->biYPelsPerMeter = (DWORD) (dev->y_pixels_per_inch / 25.4 * 1000);
	pbmih->biClrUsed = palcount;
	pbmih->biClrImportant = palcount;
    }
    if (prgbquad) {
	int i;
	gx_color_value prgb[3];

	if (dev->BitsPerPixel == 15) { /* 5-5-5 RGB mode */
	    DWORD* bmi_colors = (DWORD*)(prgbquad);
	    pbmih->biCompression = BI_BITFIELDS;
	    bmi_colors[0] = 0x7c00;
	    bmi_colors[1] = 0x03e0;
	    bmi_colors[2] = 0x001f;
	} 
	else if (dev->BitsPerPixel == 16) { /* 5-6-5 RGB mode */
	    DWORD* bmi_colors = (DWORD*)(prgbquad);
	    pbmih->biCompression = BI_BITFIELDS;
	    bmi_colors[0] = 0xf800;
	    bmi_colors[1] = 0x07e0;
	    bmi_colors[2] = 0x001f;
	} 
	else {
	for (i = 0; i < palcount; i++) {
	    win_map_color_rgb((gx_device *) wdev, (gx_color_index) i, prgb);
	    prgbquad[i].rgbRed = win_color_value(prgb[0]);
	    prgbquad[i].rgbGreen = win_color_value(prgb[1]);
	    prgbquad[i].rgbBlue = win_color_value(prgb[2]);
	    prgbquad[i].rgbReserved = 0;
	    }
	}
    }
    if (ppbyte) {
	if (row < dev->mdev.height)
	    *ppbyte = dev->mdev.line_ptrs[row];
	else
	    *ppbyte = NULL;
    }
    if ((pbmih == NULL) && (prgbquad == NULL) && (ppbyte == NULL))
	return sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)
	    + gdev_mem_raster(&(dev->mdev));
    return 0;
}
