/* Copyright (C) 1992, 1993, 1997, 1998, 1999, 2000 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: gdevbmp.c,v 1.4.2.1.2.1 2003/01/17 00:49:00 giles Exp $ */
/* .BMP file format output drivers */
#include "gdevprn.h"
#include "gdevpccm.h"
#include "gdevbmp.h"

/* ------ The device descriptors ------ */

private dev_proc_print_page(bmp_print_page);
private dev_proc_print_page(bmp_cmyk_print_page);

/* Monochrome. */

const gx_device_printer gs_bmpmono_device =
prn_device(prn_std_procs, "bmpmono",
	   DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	   X_DPI, Y_DPI,
	   0, 0, 0, 0,		/* margins */
	   1, bmp_print_page);

/* 8-bit (SuperVGA-style) grayscale . */
/* (Uses a fixed palette of 256 gray levels.) */

private const gx_device_procs bmpgray_procs =
prn_color_procs(gdev_prn_open, gdev_prn_output_page, gdev_prn_close,
		gx_default_gray_map_rgb_color, gx_default_gray_map_color_rgb);
const gx_device_printer gs_bmpgray_device =
prn_device(bmpgray_procs, "bmpgray",
	   DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	   X_DPI, Y_DPI,
	   0, 0, 0, 0,		/* margins */
	   8, bmp_print_page);

/* 1-bit-per-plane separated CMYK color. */

#define bmp_cmyk_procs(p_map_color_rgb, p_map_cmyk_color)\
    gdev_prn_open, NULL, NULL, gdev_prn_output_page, gdev_prn_close,\
    NULL, p_map_color_rgb, NULL, NULL, NULL, NULL, NULL, NULL,\
    gdev_prn_get_params, gdev_prn_put_params,\
    p_map_cmyk_color, NULL, NULL, NULL, gx_page_device_get_page_device

private const gx_device_procs bmpsep1_procs = {
    bmp_cmyk_procs(cmyk_1bit_map_color_rgb, cmyk_1bit_map_cmyk_color)
};
const gx_device_printer gs_bmpsep1_device = {
  prn_device_body(gx_device_printer, bmpsep1_procs, "bmpsep1",
	DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	X_DPI, Y_DPI,
	0,0,0,0,			/* margins */
	4, 4, 1, 1, 2, 2, bmp_cmyk_print_page)
};

/* 8-bit-per-plane separated CMYK color. */

private const gx_device_procs bmpsep8_procs = {
    bmp_cmyk_procs(cmyk_8bit_map_color_rgb, cmyk_8bit_map_cmyk_color)
};
const gx_device_printer gs_bmpsep8_device = {
  prn_device_body(gx_device_printer, bmpsep8_procs, "bmpsep8",
	DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	X_DPI, Y_DPI,
	0,0,0,0,			/* margins */
	4, 32, 255, 255, 256, 256, bmp_cmyk_print_page)
};

/* 4-bit planar (EGA/VGA-style) color. */

private const gx_device_procs bmp16_procs =
prn_color_procs(gdev_prn_open, gdev_prn_output_page, gdev_prn_close,
		pc_4bit_map_rgb_color, pc_4bit_map_color_rgb);
const gx_device_printer gs_bmp16_device = {
  prn_device_body(gx_device_printer, bmp16_procs, "bmp16",
	   DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	   X_DPI, Y_DPI,
	   0, 0, 0, 0,		/* margins */
	   3, 4, 3, 2, 4, 3, bmp_print_page)
};

/* 8-bit (SuperVGA-style) color. */
/* (Uses a fixed palette of 3,3,2 bits.) */

private const gx_device_procs bmp256_procs =
prn_color_procs(gdev_prn_open, gdev_prn_output_page, gdev_prn_close,
		pc_8bit_map_rgb_color, pc_8bit_map_color_rgb);
const gx_device_printer gs_bmp256_device = {
  prn_device_body(gx_device_printer, bmp256_procs, "bmp256", 
	   DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	   X_DPI, Y_DPI,
	   0, 0, 0, 0,		/* margins */
	   3, 8, 6, 6, 7, 7, bmp_print_page)
};

/* 24-bit color. */

private const gx_device_procs bmp16m_procs =
prn_color_procs(gdev_prn_open, gdev_prn_output_page, gdev_prn_close,
		bmp_map_16m_rgb_color, bmp_map_16m_color_rgb);
const gx_device_printer gs_bmp16m_device =
prn_device(bmp16m_procs, "bmp16m",
	   DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	   X_DPI, Y_DPI,
	   0, 0, 0, 0,		/* margins */
	   24, bmp_print_page);

/* 32-bit CMYK color (outside the BMP specification). */

private const gx_device_procs bmp32b_procs = {
    bmp_cmyk_procs(cmyk_8bit_map_color_rgb, gx_default_cmyk_map_cmyk_color)
};
const gx_device_printer gs_bmp32b_device =
prn_device(bmp32b_procs, "bmp32b",
	   DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	   X_DPI, Y_DPI,
	   0, 0, 0, 0,		/* margins */
	   32, bmp_print_page);

/* ------ Private definitions ------ */

/* Write out a page in BMP format. */
/* This routine is used for all non-separated formats. */
private int
bmp_print_page(gx_device_printer * pdev, FILE * file)
{
    uint raster = gdev_prn_raster(pdev);
    /* BMP scan lines are padded to 32 bits. */
    uint bmp_raster = raster + (-raster & 3);
    byte *row = gs_alloc_bytes(pdev->memory, bmp_raster, "bmp file buffer");
    int y;
    int code;		/* return code */

    if (row == 0)		/* can't allocate row buffer */
	return_error(gs_error_VMerror);

    /* Write the file header. */

    code = write_bmp_header(pdev, file);
    if (code < 0)
	goto done;

    /* Write the contents of the image. */
    /* BMP files want the image in bottom-to-top order! */

    for (y = pdev->height - 1; y >= 0; y--) {
	gdev_prn_copy_scan_lines(pdev, y, row, raster);
	fwrite((const char *)row, bmp_raster, 1, file);
    }

done:
    gs_free_object(pdev->memory, row, "bmp file buffer");

    return code;
}

/* Write out a page in separated CMYK format. */
/* This routine is used for all formats. */
private int
bmp_cmyk_print_page(gx_device_printer * pdev, FILE * file)
{
    int plane_depth = pdev->color_info.depth / 4;
    uint raster = bitmap_raster(pdev->width * plane_depth);
    /* BMP scan lines are padded to 32 bits. */
    uint bmp_raster = raster + (-raster & 3);
    byte *row = gs_alloc_bytes(pdev->memory, bmp_raster, "bmp file buffer");
    int y;
    int code = 0;		/* return code */
    int plane;

    if (row == 0)		/* can't allocate row buffer */
	return_error(gs_error_VMerror);

    for (plane = 0; plane <= 3; ++plane) {
	gx_render_plane_t render_plane;

	/* Write the page header. */

	code = write_bmp_separated_header(pdev, file);
	if (code < 0)
	    break;

	/* Write the contents of the image. */
	/* BMP files want the image in bottom-to-top order! */

	gx_render_plane_init(&render_plane, (gx_device *)pdev, plane);
	for (y = pdev->height - 1; y >= 0; y--) {
	    byte *actual_data;
	    uint actual_raster;

	    code = gdev_prn_get_lines(pdev, y, 1, row, bmp_raster,
				      &actual_data, &actual_raster,
				      &render_plane);
	    if (code < 0)
		goto done;
	    fwrite((const char *)actual_data, bmp_raster, 1, file);
	}
    }

done:
    gs_free_object(pdev->memory, row, "bmp file buffer");

    return code;
}
