/* Copyright (C) 1991, 1995, 1996, 1997, 1998, 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: gdevbit.c,v 1.2.6.1.2.1 2003/01/17 00:49:00 giles Exp $ */
/* "Plain bits" devices to measure rendering time. */
#include "math_.h"
#include "gdevprn.h"
#include "gsparam.h"
#include "gscrd.h"
#include "gscrdp.h"
#include "gxlum.h"
#include "gdevdcrd.h"

/* Define the device parameters. */
#ifndef X_DPI
#  define X_DPI 72
#endif
#ifndef Y_DPI
#  define Y_DPI 72
#endif

/* The device descriptor */
private dev_proc_map_rgb_color(bit_mono_map_rgb_color);
private dev_proc_map_rgb_color(bit_forcemono_map_rgb_color);
private dev_proc_map_color_rgb(bit_map_color_rgb);
private dev_proc_map_cmyk_color(bit_map_cmyk_color);
private dev_proc_get_params(bit_get_params);
private dev_proc_put_params(bit_put_params);
private dev_proc_print_page(bit_print_page);

#define bit_procs(map_rgb_color, map_cmyk_color)\
{	gdev_prn_open,\
	gx_default_get_initial_matrix,\
	NULL,	/* sync_output */\
	gdev_prn_output_page,\
	gdev_prn_close,\
	map_rgb_color,\
	bit_map_color_rgb,\
	NULL,	/* fill_rectangle */\
	NULL,	/* tile_rectangle */\
	NULL,	/* copy_mono */\
	NULL,	/* copy_color */\
	NULL,	/* draw_line */\
	NULL,	/* get_bits */\
	bit_get_params,\
	bit_put_params,\
	map_cmyk_color,\
	NULL,	/* get_xfont_procs */\
	NULL,	/* get_xfont_device */\
	NULL,	/* map_rgb_alpha_color */\
	gx_page_device_get_page_device	/* get_page_device */\
}

/*
 * The following macro is used in get_params and put_params to determine the
 * num_components for the current device. It works using the device name
 * character after "bit" which is either '\0', 'r', or 'c'. Any new devices
 * that are added to this module must modify this macro to return the
 * correct num_components. This is needed to support the ForceMono
 * parameter, which alters dev->num_components.
 */
#define REAL_NUM_COMPONENTS(dev) (dev->dname[3] == 'c' ? 4 : \
				  dev->dname[3] == 'r' ? 3 : 1)

private const gx_device_procs bitmono_procs =
bit_procs(bit_mono_map_rgb_color, NULL);
const gx_device_printer gs_bit_device =
{prn_device_body(gx_device_printer, bitmono_procs, "bit",
		 DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
		 X_DPI, Y_DPI,
		 0, 0, 0, 0,    /* margins */
		 1, 1, 1, 0, 2, 1, bit_print_page)
};

private const gx_device_procs bitrgb_procs =
bit_procs(gx_default_rgb_map_rgb_color, NULL);
const gx_device_printer gs_bitrgb_device =
{prn_device_body(gx_device_printer, bitrgb_procs, "bitrgb",
		 DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
		 X_DPI, Y_DPI,
		 0, 0, 0, 0,	/* margins */
		 3, 4, 1, 1, 2, 2, bit_print_page)
};

private const gx_device_procs bitcmyk_procs =
bit_procs(bit_forcemono_map_rgb_color, bit_map_cmyk_color);
const gx_device_printer gs_bitcmyk_device =
{prn_device_body(gx_device_printer, bitcmyk_procs, "bitcmyk",
		 DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
		 X_DPI, Y_DPI,
		 0, 0, 0, 0,	/* margins */
		 4, 4, 1, 1, 2, 2, bit_print_page)
};

/* Map gray to color. */
/* Note that 1-bit monochrome is a special case. */
private gx_color_index
bit_mono_map_rgb_color(gx_device * dev, gx_color_value red,
		       gx_color_value green, gx_color_value blue)
{
    int bpc = dev->color_info.depth;
    int drop = sizeof(gx_color_value) * 8 - bpc;
    gx_color_value gray =
    (red * (unsigned long)lum_red_weight +
     green * (unsigned long)lum_green_weight +
     blue * (unsigned long)lum_blue_weight +
     (lum_all_weights / 2))
    / lum_all_weights;

    return (bpc == 1 ? gx_max_color_value - gray : gray) >> drop;
}

/* Map RGB to gray shade. */
/* Only used in CMYK mode when put_params has set ForceMono=1 */
private gx_color_index
bit_forcemono_map_rgb_color(gx_device * dev, gx_color_value red,
		  gx_color_value green, gx_color_value blue)
{
    gx_color_value color;
    int bpc = dev->color_info.depth / 4;	/* This function is used in CMYK mode */
    int drop = sizeof(gx_color_value) * 8 - bpc;
    gx_color_value gray = red;

    if ((red != green) || (green != blue))
	gray = (red * (unsigned long)lum_red_weight +
	     green * (unsigned long)lum_green_weight +
	     blue * (unsigned long)lum_blue_weight +
	     (lum_all_weights / 2))
		/ lum_all_weights;

    color = (gx_max_color_value - gray) >> drop;	/* color is in K channel */
    return color;
}

/* Map color to RGB.  This has 3 separate cases, but since it is rarely */
/* used, we do a case test rather than providing 3 separate routines. */
private int
bit_map_color_rgb(gx_device * dev, gx_color_index color, gx_color_value rgb[3])
{
    int depth = dev->color_info.depth;
    int ncomp = REAL_NUM_COMPONENTS(dev);
    int bpc = depth / ncomp;
    uint mask = (1 << bpc) - 1;

#define cvalue(c) ((gx_color_value)((ulong)(c) * gx_max_color_value / mask))

    switch (ncomp) {
	case 1:		/* gray */
	    rgb[0] = rgb[1] = rgb[2] =
		(depth == 1 ? (color ? 0 : gx_max_color_value) :
		 cvalue(color));
	    break;
	case 3:		/* RGB */
	    {
		gx_color_index cshift = color;

		rgb[2] = cvalue(cshift & mask);
		cshift >>= bpc;
		rgb[1] = cvalue(cshift & mask);
		rgb[0] = cvalue(cshift >> bpc);
	    }
	    break;
	case 4:		/* CMYK */
	    /* Map CMYK back to RGB. */
	    {
		gx_color_index cshift = color;
		uint c, m, y, k;

		k = cshift & mask;
		cshift >>= bpc;
		y = cshift & mask;
		cshift >>= bpc;
		m = cshift & mask;
		c = cshift >> bpc;
		/* We use our improved conversion rule.... */
		rgb[0] = cvalue((mask - c) * (mask - k) / mask);
		rgb[1] = cvalue((mask - m) * (mask - k) / mask);
		rgb[2] = cvalue((mask - y) * (mask - k) / mask);
	    }
	    break;
    }
    return 0;
#undef cvalue
}

/* Map CMYK to color. */
private gx_color_index
bit_map_cmyk_color(gx_device * dev, gx_color_value cyan,
	gx_color_value magenta, gx_color_value yellow, gx_color_value black)
{
    int bpc = dev->color_info.depth / 4;
    int drop = sizeof(gx_color_value) * 8 - bpc;
    gx_color_index color =
    ((((((cyan >> drop) << bpc) +
	(magenta >> drop)) << bpc) +
      (yellow >> drop)) << bpc) +
    (black >> drop);

    return (color == gx_no_color_index ? color ^ 1 : color);
}

/* Get parameters.  We provide a default CRD. */
private int
bit_get_params(gx_device * pdev, gs_param_list * plist)
{
    int code, ecode;
    /*
     * The following is a hack to get the original num_components.
     * See comment above.
     */
    int real_ncomps = REAL_NUM_COMPONENTS(pdev);
    int ncomps = pdev->color_info.num_components;
    int forcemono = (ncomps == real_ncomps ? 0 : 1);

    /*
     * Temporarily set num_components back to the "real" value to avoid
     * confusing those that rely on it.
     */
    pdev->color_info.num_components = real_ncomps;

    ecode = gdev_prn_get_params(pdev, plist);
    code = sample_device_crd_get_params(pdev, plist, "CRDDefault");
	if (code < 0)
	    ecode = code;
    if ((code = param_write_int(plist, "ForceMono", &forcemono)) < 0) {
	ecode = code;
    }

    /* Restore the working num_components */
    pdev->color_info.num_components = ncomps;

    return ecode;
}

/* Set parameters.  We allow setting the number of bits per component. */
/* Also, ForceMono=1 forces monochrome output from RGB/CMYK devices. */
private int
bit_put_params(gx_device * pdev, gs_param_list * plist)
{
    gx_device_color_info save_info;
    int ncomps = pdev->color_info.num_components;
    int real_ncomps = REAL_NUM_COMPONENTS(pdev);
    int bpc = pdev->color_info.depth / real_ncomps;
    int v;
    int ecode = 0;
    int code;
    static const byte depths[4][8] = {
	{1, 2, 0, 4, 8, 0, 0, 8},
	{0},
	{4, 8, 0, 16, 16, 0, 0, 24},
	{4, 8, 0, 16, 32, 0, 0, 32}
    };
    const char *vname;

    /*
     * Temporarily set num_components back to the "real" value to avoid
     * confusing those that rely on it.
     */
    pdev->color_info.num_components = real_ncomps;

    if ((code = param_read_int(plist, (vname = "GrayValues"), &v)) != 1 ||
	(code = param_read_int(plist, (vname = "RedValues"), &v)) != 1 ||
	(code = param_read_int(plist, (vname = "GreenValues"), &v)) != 1 ||
	(code = param_read_int(plist, (vname = "BlueValues"), &v)) != 1
	) {
	if (code < 0)
	    ecode = code;
	else
	    switch (v) {
		case   2: bpc = 1; break;
		case   4: bpc = 2; break;
		case  16: bpc = 4; break;
		case  32: bpc = 5; break;
		case 256: bpc = 8; break;
		default:
		    param_signal_error(plist, vname,
				       ecode = gs_error_rangecheck);
	    }
    }

    switch (code = param_read_int(plist, (vname = "ForceMono"), &v)) {
    case 0:
	if (v == 1) {
	    ncomps = 1;
	    break;
	}
	else if (v == 0) {
	    ncomps = real_ncomps;
	    break;
	}
	code = gs_error_rangecheck;
    default:
	ecode = code;
	param_signal_error(plist, vname, ecode);
    case 1:
	break;
    }
    if (ecode < 0)
	return ecode;

    /*
     * Save the color_info in case gdev_prn_put_params fails, and for
     * comparison.  Note that depth is computed from real_ncomps.
     */
    save_info = pdev->color_info;
    pdev->color_info.depth = depths[real_ncomps - 1][bpc - 1];
    pdev->color_info.max_gray = pdev->color_info.max_color =
	(pdev->color_info.dither_grays =
	 pdev->color_info.dither_colors =
	 (1 << bpc)) - 1;
    ecode = gdev_prn_put_params(pdev, plist);
    if (ecode < 0) {
	pdev->color_info = save_info;
	return ecode;
    }
    /* Now restore/change num_components. This is done after other	*/
    /* processing since it is used in gx_default_put_params		*/
    pdev->color_info.num_components = ncomps;
    if (pdev->color_info.depth != save_info.depth ||
	pdev->color_info.num_components != save_info.num_components
	) {
	gs_closedevice(pdev);
    }
    /* Reset the map_cmyk_color procedure if appropriate. */
    if (dev_proc(pdev, map_cmyk_color) == cmyk_1bit_map_cmyk_color ||
	dev_proc(pdev, map_cmyk_color) == cmyk_8bit_map_cmyk_color ||
	dev_proc(pdev, map_cmyk_color) == bit_map_cmyk_color) {
	set_dev_proc(pdev, map_cmyk_color,
		     pdev->color_info.depth == 4 ? cmyk_1bit_map_cmyk_color :
		     pdev->color_info.depth == 32 ? cmyk_8bit_map_cmyk_color :
		     bit_map_cmyk_color);
    }
    return 0;
}

/* Send the page to the printer. */
private int
bit_print_page(gx_device_printer * pdev, FILE * prn_stream)
{				/* Just dump the bits on the file. */
    /* If the file is 'nul', don't even do the writes. */
    int line_size = gdev_mem_bytes_per_scan_line((gx_device *) pdev);
    byte *in = gs_alloc_bytes(pdev->memory, line_size, "bit_print_page(in)");
    byte *data;
    int nul = !strcmp(pdev->fname, "nul");
    int lnum = 0, bottom = pdev->height;

    if (in == 0)
	return_error(gs_error_VMerror);
    for (; lnum < bottom; ++lnum) {
	gdev_prn_get_bits(pdev, lnum, in, &data);
	if (!nul)
	    fwrite(data, 1, line_size, prn_stream);
    }
    gs_free_object(pdev->memory, in, "bit_print_page(in)");
    return 0;
}
