/* Copyright (C) 1998 Aladdin Enterprises.  All rights reserved.
  
  This file is part of Aladdin Ghostscript.
  
  Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
  or distributor accepts any responsibility for the consequences of using it,
  or for whether it serves any particular purpose or works at all, unless he
  or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
  License (the "License") for full details.
  
  Every copy of Aladdin Ghostscript must include a copy of the License,
  normally in a plain ASCII text file named PUBLIC.  The License grants you
  the right to copy, modify and redistribute Aladdin Ghostscript, but only
  under certain conditions described in the License.  Among other things, the
  License requires that the copyright notice and this notice be preserved on
  all copies.
*/

/*$Id: gdevbmpa.c $ */
/* .BMP file format output drivers: Demo of ASYNC rendering */

/* Initial version 2/2/98 by John Desrosiers (soho@crl.com) */
/* 7/28/98 ghost@aladdin.com - Factored out common BMP format code. */

#include "stdio_.h"
#include "gserrors.h"
#include "gdevprna.h"
#include "gdevpccm.h"
#include "gdevbmp.h"
#include "gpsync.h"

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

/* Define data type for this device based on prn_device */
typedef struct gx_device_async_s {
    gx_device_common;
    gx_prn_device_common;
    int buffered_page_exists;
    long file_offset_to_data;
    int copies_printed;
} gx_device_async;

/* Define initializer for device */
#define async_device(procs, dname, w10, h10, xdpi, ydpi, lm, bm, rm, tm, color_bits, print_page)\
{ prn_device_std_margins_body(gx_device_async, procs, dname,\
    w10, h10, xdpi, ydpi, lm, tm, lm, bm, rm, tm, color_bits, print_page),\
  0, 0L\
}

private dev_proc_open_device(bmpa_writer_open);
private prn_dev_proc_open_render_device(bmpa_reader_open_render_device);
private dev_proc_print_page_copies(bmpa_reader_print_page_copies);
private prn_dev_proc_buffer_page(bmpa_reader_buffer_page);
private dev_proc_output_page(bmpa_reader_output_page);
private dev_proc_put_params(bmpa_put_params);
private dev_proc_get_hardware_params(bmpa_get_hardware_params);
private prn_dev_proc_start_render_thread(bmpa_reader_start_render_thread);
private prn_dev_proc_get_space_params(bmpa_get_space_params);
#define default_print_page 0	/* not needed becoz print_page_copies def'd */

/* Monochrome. */

private gx_device_procs bmpamono_procs =
  prn_procs(bmpa_writer_open, gdev_prn_output_page, gdev_prn_close);
gx_device_async far_data gs_bmpamono_device =
  async_device(bmpamono_procs, "bmpamono",
	DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	X_DPI, Y_DPI,
	0,0,0,0,			/* margins */
	1, default_print_page);

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

private gx_device_procs bmpa16_procs =
  prn_color_procs(bmpa_writer_open, gdev_prn_output_page, gdev_prn_close,
    pc_4bit_map_rgb_color, pc_4bit_map_color_rgb);
gx_device_async far_data gs_bmpa16_device =
  async_device(bmpa16_procs, "bmpa16",
	DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	X_DPI, Y_DPI,
	0,0,0,0,			/* margins */
	4, default_print_page);

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

private gx_device_procs bmpa256_procs =
  prn_color_procs(bmpa_writer_open, gdev_prn_output_page, gdev_prn_close,
    pc_8bit_map_rgb_color, pc_8bit_map_color_rgb);
gx_device_async far_data gs_bmpa256_device =
  async_device(bmpa256_procs, "bmpa256",
	DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	X_DPI, Y_DPI,
	0,0,0,0,			/* margins */
	8, default_print_page);

/* 24-bit color. */

private gx_device_procs bmpa16m_procs =
  prn_color_procs(bmpa_writer_open, gdev_prn_output_page, gdev_prn_close,
    bmp_map_16m_rgb_color, bmp_map_16m_color_rgb);
gx_device_async far_data gs_bmpa16m_device =
  async_device(bmpa16m_procs, "bmpa16m",
	DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
	X_DPI, Y_DPI,
	0,0,0,0,			/* margins */
	24, default_print_page);

/* --------- Forward declarations ---------- */

private void bmpa_reader_thread(P1(void *));

/* ------------ Writer Instance procedures ---------- */

/* Writer's open procedure */
private int
bmpa_writer_open(gx_device *pdev  /* Driver instance to open */)
{
    gx_device_async * const pwdev = (gx_device_async *)pdev;
    int max_width;
    int max_raster;
    int min_band_height;
    int max_src_image_row;

    /*
     * Set up device's printer proc vector to point to this driver, since
     * there are no convenient macros for setting them up in static template.
     */
    init_async_render_procs(pwdev, bmpa_reader_start_render_thread,
			    bmpa_reader_buffer_page,
			    bmpa_reader_print_page_copies);
    set_dev_proc(pdev, put_params, bmpa_put_params);
    set_dev_proc(pdev, get_hardware_params, bmpa_get_hardware_params);
    set_dev_proc(pdev, output_page, bmpa_reader_output_page);	/* hack */
    pwdev->printer_procs.get_space_params = bmpa_get_space_params;
    pwdev->printer_procs.open_render_device =
	bmpa_reader_open_render_device;	/* Included for tutorial value */

    /*
     * Determine MAXIMUM parameters this device will have to support over
     * lifetime.  See comments for bmpa_get_space_params().
     */
    max_width = DEFAULT_WIDTH_10THS * 60;   /* figure max wid = default @ 600dpi */
    min_band_height = max(1, (DEFAULT_HEIGHT_10THS * 60) / 100);
    max_raster = (max_width * pwdev->color_info.depth) / 8;	/* doesn't need to be super accurate */
    max_src_image_row = max_width * 3 * 2;

    /* Special writer open routine for async interpretation */
    /* Starts render thread */
    return gdev_prn_async_write_open((gx_device_printer *)pdev,
				     max_raster, min_band_height,
				     max_src_image_row);
}

/* -------------- Renderer instance procedures ----------*/

/* Thread to do rendering, started by bmpa_reader_start_render_thread */
private void 
bmpa_reader_thread(void *params)
{
    gdev_prn_async_render_thread((gdev_prn_start_render_params *)params);
}

private int	/* rets 0 ok, -ve error if couldn't start thread */
bmpa_reader_start_render_thread(gdev_prn_start_render_params *params)
{
    return gp_create_thread(bmpa_reader_thread, params);
}

private int
bmpa_reader_open_render_device(gx_device_printer *ppdev)
{
    gx_device_async * const prdev = (gx_device_async *)ppdev;
	
    /* Do anything that needs to be done at open time here... */
    prdev->copies_printed = 0;

    /* Cascade down to the default handler */
    return gdev_prn_async_render_open(ppdev);
}

/* Generic routine to send the page to the printer. */
private int
bmpa_reader_output_page(gx_device *pdev, int num_copies, int flush)
{
    /*
     * HACK: open the printer page with the positionable attribute since
     * we need to seek back & forth to support partial rendering.
     */
    if ( num_copies > 0 || !flush ) {
	int code = gdev_prn_open_printer_positionable(pdev, 1, 1);

	if ( code < 0 )
	    return code;
    }
    return gdev_prn_output_page(pdev, num_copies, flush);
}

private int
bmpa_reader_print_page_copies(gx_device_printer *pdev, FILE *prn_stream,
			     int num_copies)
{
    gx_device_async * const prdev = (gx_device_async *)pdev;
    gx_device * const dev = (gx_device *)pdev;
    uint raster = gdev_prn_raster(pdev);
    /* BMP scan lines are padded to 32 bits. */
    uint bmp_raster = raster + (-raster & 3);
    int code = 0;
    int y;
    byte *row = 0;
    byte *raster_data;

    /*
     * BMP format is single page, so discard all but 1st printable page
     * This logic isn't quite right, since we can't truncate file if
     * num_pages == 0.
     */
    if (prdev->copies_printed > 0)
	return 0;

    /* If there's data in buffer, need to process w/overlays */
    if (prdev->buffered_page_exists) {
	code = bmpa_reader_buffer_page(pdev, prn_stream, num_copies);
	goto done;
    }

    row = (byte *)gs_malloc(bmp_raster, 1, "bmp file buffer");
    if (row == 0)		/* can't allocate row buffer */
	return_error(gs_error_VMerror);

    /* Write header & seek to its end */
    fseek(prn_stream, 0L, SEEK_SET);
    code = write_bmp_header(pdev, prn_stream);
    if (code < 0)
	goto done;
    /* Save the file offset where data begins */
    if ((prdev->file_offset_to_data = ftell(prn_stream)) == -1L) {
	code = gs_note_error(gs_error_ioerror);
	goto done;
    }

    /*
     * Write out the bands top to bottom.
     * Finish the job even if num_copies == 0, to avoid invalid output file.
     */
    for (y = prdev->height - 1; y >= 0; y--) {
	if ((code = dev_proc(dev, get_bits)(dev, y, row, &raster_data)) < 0)
	    goto done;
	if (fwrite((const char *)raster_data, bmp_raster, 1, prn_stream) < 1) {
	    code = gs_error_ioerror;
	    goto done;
	}
    }
done:
    gs_free((char *)row, bmp_raster, 1, "bmp file buffer");
    if (code >= 0 && prdev->copies_printed > 0)
	prdev->copies_printed = num_copies;
    prdev->buffered_page_exists = 0;
    return code;
}

/* Buffer a (partial) rasterized page & optionally print result multiple times. */
private int
bmpa_reader_buffer_page(gx_device_printer *pdev, FILE *file, int num_copies)
{
    gx_device_async * const prdev = (gx_device_async *)pdev;
    gx_device * const dev = (gx_device *)pdev;
    int code = 0;

    /* BMP format is single page, so discard all but 1st page */
    if (prdev->copies_printed > 0)
	return 0;

    /* If there's no data in buffer, no need to do any overlays */
    if (!prdev->buffered_page_exists) {
	code = bmpa_reader_print_page_copies(pdev, file, num_copies);
	goto done;
    }

    /*
     * Overlay file's bits on top of existing file There are two choices for
     * doing this: get_overlay_bits vs.  the combination of
     * locate_overlay_buffer and get_bits. If you already have a buffer in a
     * format compatible with GS's format, use get_overlay_bits. If you'd
     * rather use the buffer already in the device, use
     * locate_overlay_buffer, copy the bits into the returned buffer, then
     * get_bits.
     *
     * Either way, try to do entire bands at a shot for much greater
     * efficiency.
     */

    /* Seek to beginning of data portion of file */
    if (fseek(file, prdev->file_offset_to_data, SEEK_SET)) {
	code = gs_note_error(gs_error_ioerror);
	goto done;
    }

    {
	byte *raster_data;
	int raster = gx_device_raster(dev, 1);
	ulong bmp_raster = raster + (-raster & 3); /* BMP scan lines are padded to 32 bits. */
	int max_band_height =
	    (*pdev->printer_procs.locate_overlay_buffer)(pdev, 0, &raster_data);
	int band;
	int file_raster_good = min(raster, bmp_raster);
	long file_raster_slop = bmp_raster - file_raster_good;

	/*
	 * iterate thru bands from top to bottom.
	 * Do this an entire band at a time for efficiency.
	 */
	for (band = (pdev->height - 1) / max_band_height; band >= 0; --band) {
	    int band_base_line = max_band_height * band;
	    int band_height = (*pdev->printer_procs.locate_overlay_buffer)
		(pdev, band_base_line, &raster_data);
	    int line;

	    /* Fill in overlay buffer with a band from the BMP file. */
	    /* Need to do this backward since BMP is top to bottom */
	    for (line = band_height - 1; line >= 0; --line)
	        if (fread(raster_data + line * bmp_raster,
			  file_raster_good, 1, file) < 1 ||
		    fseek(file, file_raster_slop, SEEK_CUR)
		    ) {
		    code = gs_note_error(gs_error_ioerror);
	            goto done;
		}

	    /* Rewind & write out buffer with contents of get_bits */
	    if (fseek(file,
		      -(file_raster_good + file_raster_slop) * band_height,
		      SEEK_CUR)) {
		code = gs_note_error(gs_error_ioerror);
		goto done;
	    }
	    for (line = band_height - 1; line >= 0; --line) {
		if ((code = dev_proc(dev, get_bits)
		     (dev, line + band_base_line, 0, &raster_data)) < 0
		    )
	            goto done;
		if (fwrite(raster_data, file_raster_good, 1, file) < 1 ||
		    fseek(file, file_raster_slop, SEEK_CUR)
		    ) {
		    code = gs_note_error(gs_error_ioerror);
		    goto done;
		}
	    }
	}
    }

 done:
    if (code >= 0 && prdev->copies_printed > 0)
	prdev->copies_printed = num_copies;
    prdev->buffered_page_exists = (code >= 0);
    return code;
}

/*------------ Procedures common to writer & renderer -------- */

/* Compute space parameters */
private void
bmpa_get_space_params(const gx_device_printer *pdev,
 gdev_prn_space_params *space_params)
{
    /* Plug params into device before opening it
     *
     * You ask "How did you come up with these #'s?" You asked, so...
     *
     * To answer clearly, let me begin by recapitulating how command list
     * (clist) device memory allocation works in the non-overlapped case:
     * When the device is opened, a buffer is allocated. How big? For
     * starters, it must be >= PRN_MIN_BUFFER_SPACE, and as we'll see, must
     * be sufficient to satisfy the rest of the band params. If you don't
     * specify a size for it in space_params.band.BandBufferSpace, the open
     * routine will use a heuristic where it tries to use PRN_BUFFER_SPACE,
     * then works its way down by factors of 2 if that much memory isn't
     * available.
     *
     * The device proceeds to divide the buffer into several parts: one of
     * them is used for the same thing during writing & rasterizing; the
     * other parts are redivided and used differently writing and
     * rasterizing. The limiting factor dictating memory requirements is the
     * rasterizer's render buffer.  This buffer needs to be able to contain
     * a bitmap that covers an entire band. Memory consumption is whatever
     * is needed to hold N rows of data aligned on word boundaries, +
     * sizeof(pointer) for each of N rows. Whatever is left over in the
     * rasterized is allocated to a tile cache. You want to make sure that
     * cache is at least 50KB.
     *
     * For example, take a 600 dpi b/w device at 8.5 x 11 inches.  For the
     * whole device, that's 6600 rows @ 638 bytes = ~4.2 MB total.  If the
     * device is divided into 100 bands, each band's rasterizer buffer is
     * 62K. Add on a 50K tile cache, and you get a 112KB (+ add a little
     * slop) total device buffer size.
     *
     * Now that we've covered the rasterizer, let's switch back to the
     * writer. The writer must have a tile cache *exactly* the same size as
     * the reader. This means that the space to divide up for the writer is
     * equal is size to the rasterizer's band buffer.  This space is divided
     * into 2 sections: per-band bookeeping info and a command buffer. The
     * bookeeping info currently uses ~72 bytes for each band. The rest is
     * the command buffer.
     *
     * To continue the same 112KB example, we have 62KB to slice up.
     * We need 72 bytes * 100 bands = 7.2KB, leaving a 55K command buffer.
     *
     * A larger command buffer has some performance (see gxclmem.c comments)
     * advantages in the general case, but is critical in one special case:
     * high-level images. Whenever possible, images are transmitted across
     * the band buffer in their original resolution and bits/pixel. The
     * alternative fallback behavior can be very slow.  Here, the relevant
     * restriction is that at least one entire source image row must fit
     * into the command buffer. This means that, in our example, an RGB
     * source image would have to be <= 18K pixels wide. If the image is
     * sampled at the same resolution as the hardware (600 dpi), that means
     * the row would be limited to a very reasonable 30 inches. However, if
     * the source image is sampled at 2400 dpi, that limit is only 7.5
     * inches. The situation gets worse as bands get smaller, but the
     * implementor must decide on the tradeoff point.
     *
     * The moral of the story is that you should never make a band
     * so small that its buffer limits the command buffer excessively.
     * Again, Max image row bytes = band buffer size - # bands * 72. 
     *
     * In the overlapped case, everything is exactly as above, except that
     * two identical devices, each with an identical buffer, are allocated:
     * one for the writer, and one for the rasterizer. Because it's critical
     * to allocate identical buffers, I *strongly* recommend setting these
     * params in the writer's open routine:
     * space_params.band.BandBufferSpace, .BandWidth and .BandHeight.  If
     * you don't force these values to a known value, the memory allocation
     * heuristic may not come to the same result for both copies of the
     * device, since the first allocation will diminish the amount of free
     * memory.
     *
     * There is room for an important optimization here: allocate the
     * writer's space with enough memory for a generous command buffer, but
     * allocate the reader with only enough memory for a band rasterization
     * buffer and the tile cache.  To do this, observe that the space_params
     * struct has two sizes: BufferSpace vs. BandBufferSpace.  To start,
     * BandBufferSpace is always <= BufferSpace. On the reader side,
     * BandBufferSpace is divided between the tile cache and the rendering
     * buffer -- that's all the memory that's needed to rasterize. On the
     * writer's side, BandBufferSpace is divided the same way: the tile
     * cache (which must be identical to the reader's) is carved out, and
     * the space that would have been used for a rasterizing buffer is used
     * as a command buffer. However, you can further increase the cmd buf
     * further by setting BufferSize (not BandBufferSize) to a higher number
     * than BandBufferSize. In that case, the command buffer is increased by
     * the difference (BufferSize - BandBufferSize). There is logic in the
     * memory allocation for printers that will automatically use BufferSize
     * for writers (or non-async printers), and BandBufferSize for readers.
     *
     * Note: per the comments in gxclmem.c, the banding logic will perform
     * better with 1MB or better for the command list.
     */
    
    /* This will give us a very "ungenerous" buffer. */
    /* Here, my arbitrary rule for min image row is: twice the dest width */
    /* in full RGB. */
    int render_space;
    int writer_space;
    const int tile_cache_space = 50 * 1024;
    const int min_image_rows = 2;
    int min_row_space =
	min_image_rows * (  3 * ( pdev->width + sizeof(int) - 1 )  );
    int min_band_count = max(1, pdev->height / 100);	/* make bands >= 1% of total */

    space_params->band.BandWidth = pdev->width;
    space_params->band.BandHeight
	= (pdev->height + min_band_count - 1) / min_band_count;

    render_space = gdev_mem_data_size( (const gx_device_memory *)pdev,
				       space_params->band.BandWidth,
				       space_params->band.BandHeight );
    /* need to include minimal writer requirements to satisfy rasterizer init */
    writer_space = 	/* add 5K slop for good measure */
	5000 + (72 + 8) * ( (pdev->height / space_params->band.BandHeight) + 1 );
    space_params->band.BandBufferSpace =
	max(render_space, writer_space) + tile_cache_space;
    space_params->BufferSpace =
	max(render_space, writer_space + min_row_space) + tile_cache_space;
}

/* Put device parameters. */
/* IMPORTANT: async drivers must NOT CLOSE the device while doing put_params.*/
/* IMPORTANT: async drivers must NOT CLOSE the device while doing put_params.*/
/* IMPORTANT: async drivers must NOT CLOSE the device while doing put_params.*/
/* IMPORTANT: async drivers must NOT CLOSE the device while doing put_params.*/
private int
bmpa_put_params(gx_device *pdev, gs_param_list *plist)
{
    /*
     * This driver does nothing interesting except cascade down to
     * gdev_prn_put_params, which is something it would have to do even if
     * it did do something interesting here.
     *
     * Note that gdev_prn_put_params does not close the device.
     */
    return gdev_prn_put_params(pdev, plist);
}

/* Get hardware-detected parameters. */
/* This proc defines a only one param: a useless value for testing */
int
bmpa_get_hardware_params(gx_device *dev, gs_param_list *plist)
{
    static const char *const test_value = "Test value";
    static const char *const test_name = "TestValue";
    int code = 0;

    if ( param_requested(plist, test_name) ) {
	gs_param_string param_str;

	param_string_from_string(param_str, test_value); /* value must be persistent to use this macro */
	code = param_write_string(plist, test_name, &param_str);
    }
    return code;
}
