/* Copyright (C) 2001, Ghostgum Software Pty Ltd.  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.

 */

/* gdevdsp.c */
/* $Id: gdevdsp.c,v 1.5.2.5.2.3 2003/05/01 10:06:26 ghostgum Exp $ */

/*
 * DLL based display device driver.
 *
 * by Russell Lang, Ghostgum Software Pty Ltd
 *
 * This device is intended to be used for displays when
 * Ghostscript is loaded as a DLL/shared library/static library.  
 * It is intended to work for Windows, OS/2, Linux, Mac OS 9 and 
 * hopefully others.
 *
 * Before this device is opened, the address of a structure must 
 * be provided using gsapi_set_display_callback(minst, callback);
 * This structure contains callback functions to notify the 
 * caller when the device is opened, closed, resized, showpage etc.
 * The structure is defined in gdevdsp.h.
 *
 * Not all combinatinos of display formats have been tested.
 * At the end of this file is some example code showing which
 * formats have been tested.
 */

#include "string_.h"
#include "gx.h"
#include "gserrors.h"
#include "gxdevice.h"

#include "gp.h"
#include "gpcheck.h"
#include "gsparam.h"

#include "gdevpccm.h"		/* 4-bit PC color */
#include "gxdevmem.h"
#include "gdevdsp.h"
#include "gdevdsp2.h"

/* Initial values for width and height */
#define INITIAL_RESOLUTION 96
#define INITIAL_WIDTH (INITIAL_RESOLUTION * 85 / 10 + 1)
#define INITIAL_HEIGHT (INITIAL_RESOLUTION * 11 + 1)

/* Device procedures */

/* See gxdevice.h for the definitions of the procedures. */
private dev_proc_open_device(display_open);
private dev_proc_get_initial_matrix(display_get_initial_matrix);
private dev_proc_sync_output(display_sync_output);
private dev_proc_output_page(display_output_page);
private dev_proc_close_device(display_close);

private dev_proc_map_rgb_color(display_map_rgb_color_device4);
private dev_proc_map_color_rgb(display_map_color_rgb_device4);
private dev_proc_map_rgb_color(display_map_rgb_color_device8);
private dev_proc_map_color_rgb(display_map_color_rgb_device8);
private dev_proc_map_rgb_color(display_map_rgb_color_device16);
private dev_proc_map_color_rgb(display_map_color_rgb_device16);
private dev_proc_map_rgb_color(display_map_rgb_color_rgb);
private dev_proc_map_color_rgb(display_map_color_rgb_rgb);
private dev_proc_map_rgb_color(display_map_rgb_color_bgr24);
private dev_proc_map_color_rgb(display_map_color_rgb_bgr24);
private dev_proc_map_rgb_color(display_map_rgb_color_cmyk1);
private dev_proc_map_rgb_color(display_map_rgb_color_cmyk8);

private dev_proc_fill_rectangle(display_fill_rectangle);
private dev_proc_copy_mono(display_copy_mono);
private dev_proc_copy_color(display_copy_color);
private dev_proc_get_bits(display_get_bits);
private dev_proc_get_params(display_get_params);
private dev_proc_put_params(display_put_params);
private dev_proc_map_rgb_alpha_color(display_map_rgb_alpha_color);
private dev_proc_map_color_rgb_alpha(display_map_color_rgb_alpha);
private dev_proc_finish_copydevice(display_finish_copydevice);


private const gx_device_procs display_procs =
{
    display_open,
    display_get_initial_matrix,
    display_sync_output,
    display_output_page,
    display_close,
    gx_default_w_b_map_rgb_color,
    gx_default_w_b_map_color_rgb,
    display_fill_rectangle,
    NULL,				/* tile rectangle */
    display_copy_mono,
    display_copy_color,
    NULL,				/* draw line */
    display_get_bits,
    display_get_params,
    display_put_params,
    gx_default_cmyk_map_cmyk_color, 	/* map_cmyk_color */
    gx_default_get_xfont_procs,
    NULL,				/* get_xfont_device */
    NULL,				/* map_rgb_alpha_color */
    gx_page_device_get_page_device,
    /* extra entries */
    NULL,				/* get_alpha_bits */
    NULL,				/* copy_alpha */
    NULL,				/* get_band */
    NULL,				/* copy_rop */
    NULL,				/* fill_path */
    NULL,				/* stroke_path */
    NULL,				/* fill_mask */
    NULL,				/* fill_trapezoid */
    NULL,				/* fill_parallelogram */
    NULL,				/* fill_triangle */
    NULL,				/* draw_thin_line */
    NULL,				/* begin_image */
    NULL,				/* image_data */
    NULL,				/* end_image */
    NULL,				/* strip_tile_rectangle */
    NULL,				/* strip_copy_rop */
    NULL,				/* get_clipping_box */
    NULL,				/* begin_typed_image */
    NULL,				/* get_bits_rectangle */
    NULL,				/* map_color_rgb_alpha */
    NULL,				/* create_compositor */
    NULL,				/* get_hardware_params */
    NULL,				/* text_begin */
    display_finish_copydevice		/* finish_copydevice */
};

/* GC descriptor */
public_st_device_display();

private 
ENUM_PTRS_WITH(display_enum_ptrs, gx_device_display *ddev) return 0;
    case 0: 
	if (ddev->mdev) {
	    return ENUM_OBJ(gx_device_enum_ptr((gx_device *)ddev->mdev));
	}
	return 0;
ENUM_PTRS_END

private 
RELOC_PTRS_WITH(display_reloc_ptrs, gx_device_display *ddev)
    if (ddev->mdev) {
	ddev->mdev = (gx_device_memory *)
	    gx_device_reloc_ptr((gx_device *)ddev->mdev, gcst);
    }
RELOC_PTRS_END


const gx_device_display gs_display_device =
{
    std_device_std_body_type(gx_device_display, &display_procs, "display",
			&st_device_display,
			INITIAL_WIDTH, INITIAL_HEIGHT,
			INITIAL_RESOLUTION, INITIAL_RESOLUTION),
    {0},			/* std_procs */
    NULL,			/* mdev */
    NULL,			/* callback */
    NULL,			/* pHandle */
    0,				/* nFormat */
    NULL,			/* pBitmap */
    0, 				/* ulBitmapSize */
};



/* prototypes for internal procedures */
private int display_check_structure(gx_device_display *dev);
private void display_free_bitmap(gx_device_display * dev);
private int display_alloc_bitmap(gx_device_display *, gx_device *);
private int display_set_color_format(gx_device_display *dev, int nFormat);

/* Open the display driver. */
private int
display_open(gx_device * dev)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    int ccode;

    /* Erase these, in case we are opening a copied device. */
    ddev->mdev = NULL;
    ddev->pBitmap = NULL;
    ddev->ulBitmapSize = 0;

    /* Allow device to be opened "disabled" without a callback. */
    /* The callback will be set later and the device re-opened. */
    if (ddev->callback == NULL)
	return 0;

    /* Make sure we have been passed a valid callback structure. */
    if ((ccode = display_check_structure(ddev)) < 0)
	return_error(ccode);

    /* set color info */
    if ((ccode = display_set_color_format(ddev, ddev->nFormat)) < 0)
	return_error(ccode);

    /* Tell caller that the device is open. */
    /* This is always the first callback */
    ccode = (*(ddev->callback->display_open))(ddev->pHandle, dev);
    if (ccode < 0)
	return_error(ccode);

    /* Tell caller the proposed device parameters */
    ccode = (*(ddev->callback->display_presize)) (ddev->pHandle, dev,
	dev->width, dev->height, gdev_mem_raster(dev), ddev->nFormat);
    if (ccode < 0) {
	(*(ddev->callback->display_close))(ddev->pHandle, dev);
	return_error(ccode);
    }

    /* allocate the image */
    ccode = display_alloc_bitmap(ddev, dev);
    if (ccode < 0) {
	(*(ddev->callback->display_close))(ddev->pHandle, dev);
	return_error(ccode);
    }

    /* Tell caller the device parameters */
    ccode = (*(ddev->callback->display_size)) (ddev->pHandle, dev,
	dev->width, dev->height, gdev_mem_raster(dev), ddev->nFormat, 
	ddev->mdev->base);
    if (ccode < 0) {
	display_free_bitmap(ddev);
	(*(ddev->callback->display_close))(ddev->pHandle, dev);
	return_error(ccode);
    }

    return 0;
}

private void
display_get_initial_matrix(gx_device * dev, gs_matrix * pmat)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    if ((ddev->nFormat & DISPLAY_FIRSTROW_MASK) == DISPLAY_TOPFIRST)
	gx_default_get_initial_matrix(dev, pmat);
    else
	gx_upright_get_initial_matrix(dev, pmat);  /* Windows / OS/2 */
}

/* Update the display. */
int
display_sync_output(gx_device * dev)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    if (ddev->callback == NULL)
	return 0;
    (*(ddev->callback->display_sync))(ddev->pHandle, dev);
    return (0);
}

/* Update the display, bring to foreground. */
/* If you want to pause on showpage, delay your return from callback */
int
display_output_page(gx_device * dev, int copies, int flush)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    int code;
    if (ddev->callback == NULL)
	return 0;

    code = (*(ddev->callback->display_page))
			(ddev->pHandle, dev, copies, flush);

    if (code >= 0)
	code = gx_finish_output_page(dev, copies, flush);
    return code;
}

/* Close the display driver */
private int
display_close(gx_device * dev)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    if (ddev->callback == NULL)
	return 0;

    /* Tell caller that device is about to be closed. */
    (*(ddev->callback->display_preclose))(ddev->pHandle, dev);

    /* Release memory. */
    display_free_bitmap(ddev);

    /* Tell caller that device is closed. */
    /* This is always the last callback */
    (*(ddev->callback->display_close))(ddev->pHandle, dev);

    return 0;
}


/* DISPLAY_COLORS_NATIVE, 4bit/pixel */
/* Map a r-g-b color to a color code */
private gx_color_index
display_map_rgb_color_device4(gx_device * dev, 
    gx_color_value r, gx_color_value g, gx_color_value b)
{
    return pc_4bit_map_rgb_color(dev, r, g, b);
}

/* Map a color code to r-g-b. */
private int
display_map_color_rgb_device4(gx_device * dev, gx_color_index color,
		 gx_color_value prgb[3])
{
    pc_4bit_map_color_rgb(dev, color, prgb);
    return 0;
}

/* DISPLAY_COLORS_NATIVE, 8bit/pixel */
/* Map a r-g-b color to a color code */
private gx_color_index
display_map_rgb_color_device8(gx_device * dev, 
    gx_color_value r, gx_color_value g, gx_color_value b)
{
    /* palette of 96 colors */
    /* 0->63 = 00RRGGBB, 64->95 = 010YYYYY */
    if ((r == g) && (g == b))
	return ((r >> (gx_color_value_bits - 5)) + 0x40);
    return ((r >> (gx_color_value_bits - 2)) << 4) +
	((g >> (gx_color_value_bits - 2)) << 2) +
	((b >> (gx_color_value_bits - 2)));
}

/* Map a color code to r-g-b. */
private int
display_map_color_rgb_device8(gx_device * dev, gx_color_index color,
		 gx_color_value prgb[3])
{
    gx_color_value one;
    /* palette of 96 colors */
    /* 0->63 = 00RRGGBB, 64->95 = 010YYYYY */
    if (color < 64) {
	one = (gx_color_value) (gx_max_color_value / 3);
	prgb[0] = ((color >> 4) & 3) * one;
	prgb[1] = ((color >> 2) & 3) * one;
	prgb[2] = ((color) & 3) * one;
    }
    else if (color < 96) {
	one = (gx_color_value) (gx_max_color_value / 31);
	prgb[0] = prgb[1] = prgb[2] = (color & 0x1f) * one;
    }
    else {
	prgb[0] = prgb[1] = prgb[2] = 0;
    }
    return 0;
}


/* DISPLAY_COLORS_NATIVE, 16bit/pixel */
/* Map a r-g-b color to a color code */
private gx_color_index
display_map_rgb_color_device16(gx_device * dev, 
    gx_color_value r, gx_color_value g, gx_color_value b)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    if ((ddev->nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN) {
	if ((ddev->nFormat & DISPLAY_555_MASK) == DISPLAY_NATIVE_555)
	    /* byte0=0RRRRRGG byte1=GGGBBBBB */
	    return ((r >> (gx_color_value_bits - 5)) << 10) +
		((g >> (gx_color_value_bits - 5)) << 5) +
		(b >> (gx_color_value_bits - 5));
	else
	    /* byte0=RRRRRGGG byte1=GGGBBBBB */
	    return ((r >> (gx_color_value_bits - 5)) << 11) +
		((g >> (gx_color_value_bits - 6)) << 5) +
		(b >> (gx_color_value_bits - 5));
    }

    if ((ddev->nFormat & DISPLAY_555_MASK) == DISPLAY_NATIVE_555)
	/* byte0=GGGBBBBB byte1=0RRRRRGG */
	return ((r >> (gx_color_value_bits - 5)) << 2) +
	    (((g >> (gx_color_value_bits - 5)) & 0x7) << 13) +
	    (((g >> (gx_color_value_bits - 5)) & 0x18) >> 3) +
	    ((b >> (gx_color_value_bits - 5)) << 8);

    /* byte0=GGGBBBBB byte1=RRRRRGGG */
    return ((r >> (gx_color_value_bits - 5)) << 3) +
	(((g >> (gx_color_value_bits - 6)) & 0x7) << 13) +
	(((g >> (gx_color_value_bits - 6)) & 0x38) >> 3) +
	((b >> (gx_color_value_bits - 5)) << 8);
}



/* Map a color code to r-g-b. */
private int
display_map_color_rgb_device16(gx_device * dev, gx_color_index color,
		 gx_color_value prgb[3])
{
    gx_device_display *ddev = (gx_device_display *) dev;
    gx_color_value one;
    ushort value;

    if ((ddev->nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN) {
	if ((ddev->nFormat & DISPLAY_555_MASK) == DISPLAY_NATIVE_555) {
	    /* byte0=0RRRRRGG byte1=GGGBBBBB */
	    value = color >> 10;
	    prgb[0] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	    value = (color >> 5) & 0x1f;
	    prgb[1] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	    value = color & 0x1f;
	    prgb[2] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	}
	else {
	    /* byte0=RRRRRGGG byte1=GGGBBBBB */
	    value = color >> 11;
	    prgb[0] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	    value = (color >> 5) & 0x3f;
	    prgb[1] = ((value << 10) + (value << 4) + (value >> 2))
		      >> (16 - gx_color_value_bits);
	    value = color & 0x1f;
	    prgb[2] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	}
    }
    else {
	if ((ddev->nFormat & DISPLAY_555_MASK) == DISPLAY_NATIVE_555) {
	    /* byte0=GGGBBBBB byte1=0RRRRRGG */
	    value = (color >> 2) & 0x1f;
	    prgb[0] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	    value = ((color << 3) & 0x18) +  ((color >> 13) & 0x7);
	    prgb[1] = ((value << 11) + (value << 6) + (value << 1) + 
	    (value >> 4)) >> (16 - gx_color_value_bits);
	    value = (color >> 8) & 0x1f;
	    prgb[2] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	}
	else {
	    /* byte0=GGGBBBBB byte1=RRRRRGGG */
	    value = (color >> 3) & 0x1f;
	    prgb[0] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	    value = ((color << 3) & 0x38) +  ((color >> 13) & 0x7);
	    prgb[1] = ((value << 10) + (value << 4) + (value >> 2))
		      >> (16 - gx_color_value_bits);
	    value = (color >> 8) & 0x1f;
	    prgb[2] = ((value << 11) + (value << 6) + (value << 1) + 
		(value >> 4)) >> (16 - gx_color_value_bits);
	}
    }
    return 0;
}


/* Map a r-g-b color to a color code */
private gx_color_index
display_map_rgb_color_rgb(gx_device * dev, gx_color_value r, gx_color_value g,
		 gx_color_value b)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    int drop;
    gx_color_value red, green, blue;

    if ((ddev->nFormat & DISPLAY_ALPHA_MASK) == DISPLAY_ALPHA_NONE)
	drop = gx_color_value_bits - (dev->color_info.depth / 3); 
    else
	drop = gx_color_value_bits - (dev->color_info.depth / 4); 
    red  = r >> drop;
    green = g >> drop;
    blue = b >> drop;

    switch (ddev->nFormat & DISPLAY_ALPHA_MASK) {
	case DISPLAY_ALPHA_NONE:
	    if ((ddev->nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN)
		return gx_default_rgb_map_rgb_color(dev, r, g, b); /* RGB */
	    else
		return (blue<<16) + (green<<8) + red;		/* BGR */
	case DISPLAY_ALPHA_FIRST:
	case DISPLAY_UNUSED_FIRST:
	    if ((ddev->nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN)
		return (red<<16) + (green<<8) + blue;		/* xRGB */
	    else
		return (blue<<16) + (green<<8) + red;		/* xBGR */
	case DISPLAY_ALPHA_LAST:
	case DISPLAY_UNUSED_LAST:
	    if ((ddev->nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN)
		return (red<<24) + (green<<16) + (blue<<8);	/* RGBx */
	    else
		return (blue<<24) + (green<<16) + (red<<8);	/* BGRx */
    }
    return 0;
}

/* Map a color code to r-g-b. */
private int
display_map_color_rgb_rgb(gx_device * dev, gx_color_index color,
		 gx_color_value prgb[3])
{
    gx_device_display *ddev = (gx_device_display *) dev;
    uint bits_per_color;
    uint color_mask;
    
    if ((ddev->nFormat & DISPLAY_ALPHA_MASK) == DISPLAY_ALPHA_NONE)
	bits_per_color = dev->color_info.depth / 3; 
    else
	bits_per_color = dev->color_info.depth / 4; 
    color_mask = (1 << bits_per_color) - 1;

    switch (ddev->nFormat & DISPLAY_ALPHA_MASK) {
	case DISPLAY_ALPHA_NONE:
	    if ((ddev->nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN)
		return gx_default_rgb_map_color_rgb(dev, color, prgb); /* RGB */
	    else {
		/* BGR */
		prgb[0] = ((color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[1] = ((color >> bits_per_color)   & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[2] = ((color >> 2*bits_per_color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
	    }
	    break;
	case DISPLAY_ALPHA_FIRST:
	case DISPLAY_UNUSED_FIRST:
	    if ((ddev->nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN) {
		/* xRGB */
		prgb[0] = ((color >> 2*bits_per_color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[1] = ((color >> bits_per_color)   & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[2] = ((color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
	    }
	    else {
		/* xBGR */
		prgb[0] = ((color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[1] = ((color >> bits_per_color)   & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[2] = ((color >> 2*bits_per_color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
	    }
	    break;
	case DISPLAY_ALPHA_LAST:
	case DISPLAY_UNUSED_LAST:
	    if ((ddev->nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN) {
		/* RGBx */
		prgb[0] = ((color >> 3*bits_per_color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[1] = ((color >> 2*bits_per_color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[2] = ((color >> bits_per_color)   & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
	    }
	    else {
		/* BGRx */
		prgb[0] = ((color >> bits_per_color)   & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[1] = ((color >> 2*bits_per_color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
		prgb[2] = ((color >> 3*bits_per_color) & color_mask) * 
			(ulong) gx_max_color_value / color_mask;
	    }
    }
    return 0;
}

/* Map a r-g-b color to a color code */
private gx_color_index
display_map_rgb_color_bgr24(gx_device * dev, gx_color_value r, gx_color_value g,
		 gx_color_value b)
{
    return (gx_color_value_to_byte(b)<<16) +
           (gx_color_value_to_byte(g)<<8) +
            gx_color_value_to_byte(r);
}

/* Map a color code to r-g-b. */
private int
display_map_color_rgb_bgr24(gx_device * dev, gx_color_index color,
		 gx_color_value prgb[3])
{
    prgb[0] = gx_color_value_from_byte(color & 0xff);
    prgb[1] = gx_color_value_from_byte((color >> 8) & 0xff);
    prgb[2] = gx_color_value_from_byte((color >> 16) & 0xff);
    return 0;
}

/* 8-bit CMYK
 * This is currently used for anti-aliasing. A better solution would be to 
 * replace gx_default_copy_alpha with a version that works in CMYK, not RGB.
 */
/* Map a r-g-b color to a color code */
private gx_color_index
display_map_rgb_color_cmyk8(gx_device * dev, gx_color_value r, 
		gx_color_value g, gx_color_value b)
{
    gx_color_value c, m, y, k;
    c = gx_max_color_value - r;
    m = gx_max_color_value - g;
    y = gx_max_color_value - b;
    k = min(c, min(m,y));
    if (k > 0) {
	c -= k;
	m -= k;
	y -= k;
    }
    return cmyk_8bit_map_cmyk_color(dev, c, m, y, k);
}

/* 1-bit CMYK */
/* Map a r-g-b color to a color code */
private gx_color_index
display_map_rgb_color_cmyk1(gx_device * dev, gx_color_value r, 
		gx_color_value g, gx_color_value b)
{
    gx_color_value c, m, y, k;
    c = gx_max_color_value - r;
    m = gx_max_color_value - g;
    y = gx_max_color_value - b;
    k = min(c, min(m,y));
    if (k > 0) {
	c -= k;
	m -= k;
	y -= k;
    }
    return cmyk_1bit_map_cmyk_color(dev, c, m, y, k);
}


/* Fill a rectangle */
private int
display_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
		  gx_color_index color)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    if (ddev->callback == NULL)
	return 0;
    dev_proc(ddev->mdev, fill_rectangle)((gx_device *)ddev->mdev, 
	x, y, w, h, color);
    if (ddev->callback->display_update)
	(*(ddev->callback->display_update))(ddev->pHandle, dev, x, y, w, h);
    return 0;
}

/* Copy a monochrome bitmap */
private int
display_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)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    if (ddev->callback == NULL)
	return 0;
    dev_proc(ddev->mdev, copy_mono)((gx_device *)ddev->mdev, 
	base, sourcex, raster, id, x, y, w, h, zero, one);
    if (ddev->callback->display_update)
	(*(ddev->callback->display_update))(ddev->pHandle, dev, x, y, w, h);
    return 0;
}

/* Copy a color pixel map  */
private int
display_copy_color(gx_device * dev,
	      const byte * base, int sourcex, int raster, gx_bitmap_id id,
	      int x, int y, int w, int h)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    if (ddev->callback == NULL)
	return 0;
    dev_proc(ddev->mdev, copy_color)((gx_device *)ddev->mdev, 
	base, sourcex, raster, id, x, y, w, h);
    if (ddev->callback->display_update)
	(*(ddev->callback->display_update))(ddev->pHandle, dev, x, y, w, h);
    return 0;
}

private int
display_get_bits(gx_device * dev, int y, byte * str, byte ** actual_data)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    if (ddev->callback == NULL)
	return 0;
    return dev_proc(ddev->mdev, get_bits)((gx_device *)ddev->mdev, 
	y, str, actual_data);
}

private int
display_get_params(gx_device * dev, gs_param_list * plist)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    int code = gx_default_get_params(dev, plist);
    (void)(code < 0 ||
	(code = param_write_long(plist, 
	    "DisplayHandle", (long *)(&ddev->pHandle))) < 0 ||
	(code = param_write_int(plist, 
	    "DisplayFormat", &ddev->nFormat)) < 0 );
    return code;
}

/* Put parameters. */
/* The parameters "DisplayHandle" and "DisplayFormat"
 * can be changed when the device is closed, but not when open.
 * The device width and height can be changed when open.
 */
private int
display_put_params(gx_device * dev, gs_param_list * plist)
{
    gx_device_display *ddev = (gx_device_display *) dev;
    int ecode = 0, code;
    bool is_open = dev->is_open;

    int old_width = dev->width;
    int old_height = dev->height;
    int old_format = ddev->nFormat;
    void *old_handle = ddev->pHandle;

    int format;
    void *handle;

    /* Handle extra parameters */

    switch (code = param_read_int(plist, "DisplayFormat", &format)) {
	case 0:
	    if (dev->is_open) {
 		if (ddev->nFormat != format)
		    ecode = gs_error_rangecheck;
		else
		    break;
	    }
	    else {
		code = display_set_color_format(ddev, format);
		if (code < 0)
		    ecode = code;
		else
		    break;
	    }
	    goto cfe;
	default:
	    ecode = code;
	  cfe:param_signal_error(plist, "DisplayFormat", ecode);
	case 1:
	    break;
    }

    switch (code = param_read_long(plist, "DisplayHandle", (long *)(&handle))) {
	case 0:
	    if (dev->is_open) {
		if (ddev->pHandle != handle)
		    ecode = gs_error_rangecheck;
		else
		    break;
	    }
	    else
		ddev->pHandle = handle;
		break;
	    goto hdle;
	default:
	    ecode = code;
	  hdle:param_signal_error(plist, "DisplayHandle", ecode);
	case 1:
	    break;
    }

    if (ecode >= 0) {
	/* Prevent gx_default_put_params from closing the device. */
	dev->is_open = false;
	ecode = gx_default_put_params(dev, plist);
	dev->is_open = is_open;
    }
    if (ecode < 0) {
	if (format != old_format)
	    display_set_color_format(ddev, old_format);
	ddev->pHandle = old_handle;
	dev->width = old_width;
	dev->height = old_height;
	return ecode;
    }


    if ( is_open && ddev->callback &&
	((old_width != dev->width) || (old_height != dev->height)) ) {
	/* We can resize this device while it is open, but we cannot
	 * change the color format or handle.
	 */
	/* Tell caller we are about to change the device parameters */
	if ((*ddev->callback->display_presize)(ddev->pHandle, dev,
	    dev->width, dev->height, gdev_mem_raster(dev),
	    ddev->nFormat) < 0) {
	    /* caller won't let us change the size */
	    /* restore parameters then return an error */
	    display_set_color_format(ddev, old_format);
	    ddev->nFormat = old_format;
	    ddev->pHandle = old_handle;
	    dev->width = old_width;
	    dev->height = old_height;
	    return_error(gs_error_rangecheck);
	}

	display_free_bitmap(ddev);

	code = display_alloc_bitmap(ddev, dev);
	if (code < 0) {
	    /* No bitmap, so tell the caller it is zero size */
	    (*ddev->callback->display_size)(ddev->pHandle, dev, 
	        0, 0, 0, ddev->nFormat, NULL);
	    return_error(code);
        }
    
	/* tell caller about the new size */
	if ((*ddev->callback->display_size)(ddev->pHandle, dev, 
	    dev->width, dev->height, gdev_mem_raster(dev),
	    ddev->nFormat, ddev->mdev->base) < 0)
	    return_error(gs_error_rangecheck);
    }

    return 0;
}

/* Clean up the instance after making a copy. */
int
display_finish_copydevice(gx_device *dev, const gx_device *from_dev)
{
    gx_device_display *ddev = (gx_device_display *) dev;

    /* Mark the new instance as closed. */
    ddev->is_open = false;

    /* Clear pointers */
    ddev->mdev = NULL;
    ddev->pBitmap = NULL;
    ddev->ulBitmapSize = 0;

    return 0;
}

/* ------ Internal routines ------ */

/* Make sure we have been given a valid structure */
/* Return 0 on success, gs_error_rangecheck on failure */
private int display_check_structure(gx_device_display *ddev)
{
    if (ddev->callback == 0)
	return_error(gs_error_rangecheck);

    if (ddev->callback->size != sizeof(display_callback))
	return_error(gs_error_rangecheck);

    if (ddev->callback->version_major != DISPLAY_VERSION_MAJOR)
	return_error(gs_error_rangecheck);

    /* complain if caller asks for newer features */
    if (ddev->callback->version_minor > DISPLAY_VERSION_MINOR)
	return_error(gs_error_rangecheck);

    if ((ddev->callback->display_open == NULL) ||
	(ddev->callback->display_close == NULL) ||
	(ddev->callback->display_presize == NULL) ||
	(ddev->callback->display_size == NULL) ||
	(ddev->callback->display_sync == NULL) ||
	(ddev->callback->display_page == NULL))
	return_error(gs_error_rangecheck);

    /* don't test display_update, display_memalloc or display_memfree
     * since these may be NULL if not provided
     */

    return 0;
}

private void
display_free_bitmap(gx_device_display * ddev)
{
    if (ddev->callback == NULL)
	return;
    if (ddev->pBitmap) {
	if (ddev->callback->display_memalloc 
	    && ddev->callback->display_memfree
	    && ddev->pBitmap) {
	    (*ddev->callback->display_memfree)(ddev->pHandle, ddev, 
		ddev->pBitmap);
	}
	else {
	    gs_free_object(&gs_memory_default,
		ddev->pBitmap, "display_free_bitmap");
	}
	ddev->pBitmap = NULL;
	if (ddev->mdev)
	    ddev->mdev->base = NULL;
    }
    if (ddev->mdev) {
	dev_proc(ddev->mdev, close_device)((gx_device *)ddev->mdev);
        gx_device_retain((gx_device *)(ddev->mdev), false);
	ddev->mdev = NULL;
    }
}

/* Allocate the backing bitmap. */
private int
display_alloc_bitmap(gx_device_display * ddev, gx_device * param_dev)
{
    int ccode;
    const gx_device_memory *mdproto;
    if (ddev->callback == NULL)
	return 0;

    /* free old bitmap (if any) */
    display_free_bitmap(ddev);

    /* allocate a memory device for rendering */
    mdproto = gdev_mem_device_for_bits(ddev->color_info.depth);
    if (mdproto == 0)
	return_error(gs_error_rangecheck);

    ddev->mdev = gs_alloc_struct(gs_memory_stable(ddev->memory), 
    	gx_device_memory, &st_device_memory, "display_memory_device");
    if (ddev->mdev == 0)
    	return_error(gs_error_VMerror);
        
    gs_make_mem_device(ddev->mdev, mdproto, gs_memory_stable(ddev->memory), 
	0, (gx_device *) NULL);
    gx_device_fill_in_procs((gx_device *)(ddev->mdev));
    /* Mark the memory device as retained.  When the bitmap is closed,
     * we will clear this and the memory device will be then be freed.
     */
    gx_device_retain((gx_device *)(ddev->mdev), true);
    
    ddev->mdev->width = param_dev->width;
    ddev->mdev->height = param_dev->height;
    /* Tell the memory device to allocate the line pointers separately
     * so we can place the bitmap in special memory.
     */
    ddev->mdev->line_pointer_memory = ddev->mdev->memory;
    ddev->ulBitmapSize = gdev_mem_bits_size(ddev->mdev,
	ddev->mdev->width, ddev->mdev->height);

    /* allocate bitmap using an allocator not subject to GC */
    if (ddev->callback->display_memalloc 
	&& ddev->callback->display_memfree) {
        ddev->pBitmap = (*ddev->callback->display_memalloc)(ddev->pHandle, 
	    ddev, ddev->ulBitmapSize);
    }
    else {
	ddev->pBitmap = gs_alloc_byte_array_immovable(&gs_memory_default,
		(uint)ddev->ulBitmapSize, 1, "display_alloc_bitmap");
    }

    if (ddev->pBitmap == NULL) {
	ddev->mdev->width = 0;
	ddev->mdev->height = 0;
	return_error(gs_error_VMerror);
    }

    ddev->mdev->base = (byte *) ddev->pBitmap;
    ddev->mdev->foreign_bits = true;

    ccode = dev_proc(ddev->mdev, open_device)((gx_device *)ddev->mdev);
    if (ccode < 0)
	display_free_bitmap(ddev);

    /* erase bitmap - before display gets redrawn */
    if (ccode == 0)
	dev_proc(ddev, fill_rectangle)((gx_device *)ddev,
	    0, 0, ddev->width, ddev->height,
	    ddev->procs.map_rgb_color((gx_device *)ddev, 
	    gx_max_color_value, gx_max_color_value, gx_max_color_value));

    return ccode;
}

/* Set the color_info and mapping functions for this instance of the device */
private int
display_set_color_format(gx_device_display *ddev, int nFormat)
{
    gx_device_color_info dci = ddev->color_info;
    int bpc;	/* bits per component */
    int bpp;	/* bits per pixel */
    int maxvalue;

    switch (nFormat & DISPLAY_DEPTH_MASK) {
	case DISPLAY_DEPTH_1:
	    bpc = 1;
	    break;
	case DISPLAY_DEPTH_2:
	    bpc = 2;
	    break;
	case DISPLAY_DEPTH_4:
	    bpc = 4;
	    break;
	case DISPLAY_DEPTH_8:
	    bpc = 8;
	    break;
	case DISPLAY_DEPTH_12:
	    bpc = 12;
	    break;
	case DISPLAY_DEPTH_16:
	    bpc = 16;
	    break;
	default:
	    return_error(gs_error_rangecheck);
    }
    maxvalue = (1 << bpc) - 1;

    switch (nFormat & DISPLAY_COLORS_MASK) {
	case DISPLAY_COLORS_NATIVE:
	    switch (nFormat & DISPLAY_DEPTH_MASK) {
		case DISPLAY_DEPTH_1: 
		    /* 1bit/pixel, black is 1, white is 0 */
		    dci.num_components = 1;
		    dci.depth = 1;
		    dci.max_gray = 1;
		    dci.max_color = 0;
		    dci.dither_grays = 2;
		    dci.dither_colors = 0;
		    ddev->procs.map_rgb_color = gx_default_b_w_map_rgb_color;
		    ddev->procs.map_color_rgb = gx_default_b_w_map_color_rgb;
		    break;
		case DISPLAY_DEPTH_4:
		    /* 4bit/pixel VGA color */
		    dci.num_components = 3;
		    dci.depth = 4;
		    dci.max_gray = 3;
		    dci.max_color = 2;
		    dci.dither_grays = 4;
		    dci.dither_colors = 3;
		    ddev->procs.map_rgb_color = display_map_rgb_color_device4;
		    ddev->procs.map_color_rgb = display_map_color_rgb_device4;
		    break;
		case DISPLAY_DEPTH_8:
		    /* 8bit/pixel 96 color palette */
		    dci.num_components = 3;
		    dci.depth = 8;
		    dci.max_gray = 31;
		    dci.max_color = 3;
		    dci.dither_grays = 32;
		    dci.dither_colors = 4;
		    ddev->procs.map_rgb_color = display_map_rgb_color_device8;
		    ddev->procs.map_color_rgb = display_map_color_rgb_device8;
		    break;
		case DISPLAY_DEPTH_16:
		    /* Windows 16-bit display */
		    dci.num_components = 3;
		    dci.depth = 16;
		    dci.max_gray = 63;
		    dci.max_color = 63;
		    dci.dither_grays = 64;
		    dci.dither_colors = 64;
		    ddev->procs.map_rgb_color = display_map_rgb_color_device16;
		    ddev->procs.map_color_rgb = display_map_color_rgb_device16;
		    break;
		default:
		    return_error(gs_error_rangecheck);
	    }
	    break;
	case DISPLAY_COLORS_GRAY:
	    dci.num_components = 1;
	    dci.depth = bpc;
	    dci.max_gray = maxvalue;
	    dci.max_color = 0;
	    dci.dither_grays = maxvalue+1;
	    dci.dither_colors = 0;
	    if (bpc == 1) {
		ddev->procs.map_rgb_color = gx_default_w_b_map_rgb_color;
		ddev->procs.map_color_rgb = gx_default_w_b_map_color_rgb;
	    }
	    else {
		ddev->procs.map_rgb_color = gx_default_gray_map_rgb_color;
		ddev->procs.map_color_rgb = gx_default_gray_map_color_rgb;
	    }
	    break;
	case DISPLAY_COLORS_RGB:
	    if ((nFormat & DISPLAY_ALPHA_MASK) == DISPLAY_ALPHA_NONE)
		bpp = bpc * 3;
	    else
		bpp = bpc * 4;
	    dci.num_components = 3;
	    dci.depth = bpp;
	    dci.max_gray = maxvalue;
	    dci.max_color = maxvalue;
	    dci.dither_grays = maxvalue+1;
	    dci.dither_colors = maxvalue+1;
	    if (((nFormat & DISPLAY_DEPTH_MASK) == DISPLAY_DEPTH_8) &&
	 	((nFormat & DISPLAY_ALPHA_MASK) == DISPLAY_ALPHA_NONE)) {
		if ((nFormat & DISPLAY_ENDIAN_MASK) == DISPLAY_BIGENDIAN) {
		    ddev->procs.map_rgb_color = gx_default_rgb_map_rgb_color;
		    ddev->procs.map_color_rgb = gx_default_rgb_map_color_rgb;
		}
		else {
		    ddev->procs.map_rgb_color = display_map_rgb_color_bgr24;
		    ddev->procs.map_color_rgb = display_map_color_rgb_bgr24;
		}
	    }
	    else {
		/* slower flexible functions for alpha/unused component */
		ddev->procs.map_rgb_color = display_map_rgb_color_rgb;
		ddev->procs.map_color_rgb = display_map_color_rgb_rgb;
	    }
	    break;
	case DISPLAY_COLORS_CMYK:
	    dci.num_components = 4;
	    dci.depth = bpc * 4;
	    dci.max_gray = maxvalue;
	    dci.max_color = maxvalue;
	    dci.dither_grays = maxvalue+1;
	    dci.dither_colors = maxvalue+1;
	    if ((nFormat & DISPLAY_ALPHA_MASK) != DISPLAY_ALPHA_NONE)
		return_error(gs_error_rangecheck);
	    if ((nFormat & DISPLAY_ENDIAN_MASK) != DISPLAY_BIGENDIAN)
		return_error(gs_error_rangecheck);

	    if ((nFormat & DISPLAY_DEPTH_MASK) == DISPLAY_DEPTH_1) {
		ddev->procs.map_cmyk_color = cmyk_1bit_map_cmyk_color;
		ddev->procs.map_color_rgb = cmyk_1bit_map_color_rgb;
		ddev->procs.map_rgb_color = display_map_rgb_color_cmyk1;
	    }
	    else if ((nFormat & DISPLAY_DEPTH_MASK) == DISPLAY_DEPTH_8) {
		ddev->procs.map_cmyk_color = cmyk_8bit_map_cmyk_color;
		ddev->procs.map_color_rgb = cmyk_8bit_map_color_rgb;
		ddev->procs.map_rgb_color = display_map_rgb_color_cmyk8;
	    }
	    else
		return_error(gs_error_rangecheck);
	    break;
	default:
	    return_error(gs_error_rangecheck);
    }

    /* restore old anti_alias info */
    dci.anti_alias = ddev->color_info.anti_alias;

    ddev->color_info = dci;
    ddev->nFormat = nFormat;

    return 0;
}

/* ------ Begin Test Code ------ */

/*********************************************************************
typedef struct test_mode_s test_mode;
struct test_mode_s {
    char *name;
    unsigned int format;
};

test_mode test_modes[] = {
    {"1bit/pixel native, black is 1, Windows",
     DISPLAY_COLORS_NATIVE | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_1 | 
     DISPLAY_LITTLEENDIAN | DISPLAY_BOTTOMFIRST},
    {"4bit/pixel native, Windows VGA 16 color palette",
     DISPLAY_COLORS_NATIVE | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_4 | 
     DISPLAY_LITTLEENDIAN | DISPLAY_BOTTOMFIRST},
    {"8bit/pixel native, Windows SVGA 96 color palette",
     DISPLAY_COLORS_NATIVE | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_8 | 
     DISPLAY_LITTLEENDIAN | DISPLAY_BOTTOMFIRST},
    {"16bit/pixel native, Windows BGR555",
     DISPLAY_COLORS_NATIVE | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_16 | 
     DISPLAY_LITTLEENDIAN | DISPLAY_BOTTOMFIRST | DISPLAY_NATIVE_555},
    {"16bit/pixel native, Windows BGR565",
     DISPLAY_COLORS_NATIVE | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_16 | 
     DISPLAY_LITTLEENDIAN | DISPLAY_BOTTOMFIRST | DISPLAY_NATIVE_565},
    {"1bit/pixel gray, black is 0, topfirst",
     DISPLAY_COLORS_GRAY | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_1 | 
     DISPLAY_BIGENDIAN | DISPLAY_TOPFIRST},
    {"4bit/pixel gray, bottom first",
     DISPLAY_COLORS_GRAY | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_4 | 
     DISPLAY_BIGENDIAN | DISPLAY_BOTTOMFIRST},
    {"8bit/pixel gray, bottom first",
     DISPLAY_COLORS_GRAY | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_8 | 
     DISPLAY_BIGENDIAN | DISPLAY_BOTTOMFIRST},
    {"24bit/pixel color, bottom first, Windows BGR24",
     DISPLAY_COLORS_RGB | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_8 | 
     DISPLAY_LITTLEENDIAN | DISPLAY_BOTTOMFIRST},
    {"24bit/pixel color, bottom first, RGB24",
     DISPLAY_COLORS_RGB | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_8 | 
     DISPLAY_BIGENDIAN | DISPLAY_BOTTOMFIRST},
    {"24bit/pixel color, top first, GdkRgb RGB24",
     DISPLAY_COLORS_RGB | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_8 | 
     DISPLAY_BIGENDIAN | DISPLAY_TOPFIRST},
    {"32bit/pixel color, top first, Macintosh xRGB",
     DISPLAY_COLORS_RGB | DISPLAY_UNUSED_FIRST | DISPLAY_DEPTH_8 | 
     DISPLAY_BIGENDIAN | DISPLAY_TOPFIRST},
    {"32bit/pixel color, bottom first, xBGR",
     DISPLAY_COLORS_RGB | DISPLAY_UNUSED_FIRST | DISPLAY_DEPTH_8 | 
     DISPLAY_LITTLEENDIAN | DISPLAY_BOTTOMFIRST},
    {"32bit/pixel color, bottom first, Windows BGRx",
     DISPLAY_COLORS_RGB | DISPLAY_UNUSED_LAST | DISPLAY_DEPTH_8 | 
     DISPLAY_LITTLEENDIAN | DISPLAY_BOTTOMFIRST},
    {"32bit/pixel color, bottom first, RGBx",
     DISPLAY_COLORS_RGB | DISPLAY_UNUSED_LAST | DISPLAY_DEPTH_8 | 
     DISPLAY_BIGENDIAN | DISPLAY_BOTTOMFIRST},
    {"32bit/pixel CMYK, bottom first",
     DISPLAY_COLORS_CMYK | DISPLAY_ALPHA_NONE | DISPLAY_DEPTH_8 | 
     DISPLAY_BIGENDIAN | DISPLAY_BOTTOMFIRST}
};

void
test(int index)
{
    char buf[1024];
    sprintf(buf, "gs -dDisplayFormat=16#%x examples/colorcir.ps -c quit", test_modes[index].format);
    system(buf);
}

int main(int argc, char *argv[]) 
{
    int i;
    int dotest = 0;
    if (argc >=2) {
	if (strcmp(argv[1], "-t") == 0)
	    dotest = 1;
	else {
	    fprintf(stdout, "To show modes: disp\nTo run test: disp -t\n");
	    return 1;
	}
    }
    for (i=0; i < sizeof(test_modes)/sizeof(test_mode); i++) {
	fprintf(stdout, "16#%x or %d: %s\n", test_modes[i].format,
		test_modes[i].format, test_modes[i].name);
	if (dotest)
	    test(i);
    }
    return 0;
}
*********************************************************************/

/* ------ End Test Code ------ */
