/* Copyright (C) 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: gdevalph.c,v 1.2.6.1.2.1 2003/01/17 00:49:00 giles Exp $ */
/* Alpha-channel storage device */
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsdcolor.h"
#include "gxdevice.h"
#include "gxdevmem.h"
#include "gxgetbit.h"

/*
 * Very few devices (actually, none right now) actually store an alpha
 * channel.  Here we provide a device that acts as a "wrapper" around
 * another device, and adds alpha channel storage.
 */

/* Define the device. */
typedef struct gx_device_store_alpha_s {
    gx_device_forward_common;
    bool open_close_target;	/* if true, open/close target when opening/closing */
    int alpha_depth;		/* # of bits of stored alpha */
    gx_device_memory *adev;	/* stores the alpha values -- created when needed */
} gx_device_store_alpha;

gs_private_st_suffix_add1_final(st_device_store_alpha,
			     gx_device_store_alpha, "gx_device_store_alpha",
		device_store_alpha_enum_ptrs, device_store_alpha_reloc_ptrs,
				gx_device_finalize, st_device_forward, adev);
/* The device descriptor. */
private dev_proc_open_device(dsa_open);
private dev_proc_close_device(dsa_close);
private dev_proc_fill_rectangle(dsa_fill_rectangle);
private dev_proc_map_rgb_color(dsa_map_rgb_color);
private dev_proc_map_color_rgb(dsa_map_color_rgb);
private dev_proc_copy_mono(dsa_copy_mono);
private dev_proc_copy_color(dsa_copy_color);
private dev_proc_put_params(dsa_put_params);
private dev_proc_map_rgb_alpha_color(dsa_map_rgb_alpha_color);
private dev_proc_map_color_rgb_alpha(dsa_map_color_rgb_alpha);
private dev_proc_copy_alpha(dsa_copy_alpha);
private dev_proc_fill_path(dsa_fill_path);
private dev_proc_stroke_path(dsa_stroke_path);
private dev_proc_get_bits_rectangle(dsa_get_bits_rectangle);
private const gx_device_store_alpha gs_store_alpha_device =
{std_device_std_body_open(gx_device_store_alpha, 0,
			  "alpha storage", 0, 0, 1, 1),
 {dsa_open,
  gx_forward_get_initial_matrix,
  gx_forward_sync_output,
  gx_forward_output_page,
  dsa_close,
  dsa_map_rgb_color,
  dsa_map_color_rgb,
  dsa_fill_rectangle,
  gx_default_tile_rectangle,
  dsa_copy_mono,
  dsa_copy_color,
  gx_default_draw_line,
  gx_default_get_bits,
  gx_forward_get_params,
  dsa_put_params,
  gx_default_cmyk_map_cmyk_color,	/* only called for CMYK */
  gx_forward_get_xfont_procs,
  gx_forward_get_xfont_device,
  dsa_map_rgb_alpha_color,
  gx_forward_get_page_device,
  gx_forward_get_alpha_bits,
  dsa_copy_alpha,
  gx_forward_get_band,
  gx_default_copy_rop,
  dsa_fill_path,
  dsa_stroke_path,
		/*
		 * We should do the same thing for the "mid-level"
		 * drawing operations that we do for fill_path and
		 * stroke_path, but we don't bother to do this yet.
		 */
  gx_default_fill_mask,
  gx_default_fill_trapezoid,
  gx_default_fill_parallelogram,
  gx_default_fill_triangle,
  gx_default_draw_thin_line,
  gx_default_begin_image,
  gx_default_image_data,
  gx_default_end_image,
  gx_default_strip_tile_rectangle,
  gx_default_strip_copy_rop,
  gx_forward_get_clipping_box,
  gx_default_begin_typed_image,
  dsa_get_bits_rectangle,
  dsa_map_color_rgb_alpha,
  gx_default_create_compositor
 }
};

/* Forward references */
private int alloc_alpha_storage(P1(gx_device_store_alpha * sadev));

#define sadev ((gx_device_store_alpha *)dev)

/* ------ Open/close ------ */

private int
dsa_open(gx_device * dev)
{
    gx_device *tdev = sadev->target;
    int max_value =
    max(tdev->color_info.max_gray, tdev->color_info.max_color);
    int adepth = (max_value <= 3 ? 2 : max_value <= 15 ? 4 : 8);
    int depth = tdev->color_info.depth + adepth;
    int code;

    if (depth > 32)
	return_error(gs_error_rangecheck);
    code =
	(sadev->open_close_target ?
	 (*dev_proc(tdev, open_device)) (tdev) : 0);
    if (code < 0)
	return code;
    sadev->width = tdev->width;
    sadev->height = tdev->height;
    sadev->color_info = tdev->color_info;
    sadev->color_info.depth = (depth <= 4 ? 4 : ROUND_UP(depth, 8));
    sadev->alpha_depth = adepth;
    sadev->adev = 0;
    return 0;
}

private int
dsa_close(gx_device * dev)
{
    gx_device *adev = (gx_device *) sadev->adev;

    if (adev) {
	(*dev_proc(adev, close_device)) (adev);
	gs_free_object(dev->memory, adev, "dsa_close(adev)");
	sadev->adev = 0;
    }
    if (sadev->open_close_target) {
	gx_device *tdev = sadev->target;

	return (*dev_proc(tdev, close_device)) (tdev);
    }
    return 0;
}

/* ------ Color mapping ------ */

private gx_color_index
dsa_map_rgb_color(gx_device * dev,
	      gx_color_value red, gx_color_value green, gx_color_value blue)
{
    return dsa_map_rgb_alpha_color(dev, red, green, blue,
				   gx_max_color_value);
}

private int
dsa_map_color_rgb(gx_device * dev,
		  gx_color_index color, gx_color_value rgb[3])
{
    gx_device *tdev = sadev->target;

    return (*dev_proc(tdev, map_color_rgb))
	(tdev, color >> sadev->alpha_depth, rgb);
}

private gx_color_index
dsa_map_rgb_alpha_color(gx_device * dev,
	      gx_color_value red, gx_color_value green, gx_color_value blue,
			gx_color_value alpha)
{
    gx_device *tdev = sadev->target;
    int adepth = sadev->alpha_depth;

    return
	((*dev_proc(tdev, map_rgb_color)) (tdev, red, green, blue)
	 << adepth) + (alpha * ((1 << adepth) - 1) / gx_max_color_value);
}

private int
dsa_map_color_rgb_alpha(gx_device * dev,
			gx_color_index color, gx_color_value rgba[4])
{
    int code = dsa_map_color_rgb(dev, color, rgba);
    int amask = (1 << sadev->alpha_depth) - 1;

    if (code < 0)
	return code;
    rgba[3] = (color & amask) * gx_max_color_value / amask;
    return code;
}

/* ------ Low-level drawing ------ */

private int
dsa_fill_rectangle(gx_device * dev,
		   int x, int y, int width, int height, gx_color_index color)
{
    gx_device *tdev = sadev->target;
    gx_device *adev = (gx_device *) sadev->adev;
    int adepth = sadev->alpha_depth;
    uint amask = (1 << adepth) - 1;

    if ((~color & amask) && !adev) {
	int code = alloc_alpha_storage(sadev);

	if (code < 0)
	    return code;
	adev = (gx_device *) sadev->adev;
    }
    if (adev)
	(*dev_proc(adev, fill_rectangle))
	    (adev, x, y, width, height, color & amask);
    return (*dev_proc(tdev, fill_rectangle))
	(tdev, x, y, width, height, color >> adepth);
}

private int
dsa_copy_mono(gx_device * dev,
	      const byte * data, int data_x, int raster, gx_bitmap_id id,
	      int x, int y, int width, int height,
	      gx_color_index color0, gx_color_index color1)
{
    gx_device *tdev = sadev->target;
    gx_device *adev = (gx_device *) sadev->adev;
    int adepth = sadev->alpha_depth;
    uint amask = (1 << adepth) - 1;

    if ((((color0 != gx_no_color_index) && (~color0 & amask)) ||
	 ((color1 != gx_no_color_index) && (~color1 & amask))) && !adev
	) {
	int code = alloc_alpha_storage(sadev);

	if (code < 0)
	    return code;
	adev = (gx_device *) sadev->adev;
    }
    if (adev)
	(*dev_proc(adev, copy_mono))
	    (adev, data, data_x, raster, id, x, y, width, height,
	     (color0 == gx_no_color_index ? color0 : color0 & amask),
	     (color1 == gx_no_color_index ? color1 : color1 & amask));
    return (*dev_proc(tdev, copy_mono))
	(tdev, data, data_x, raster, id, x, y, width, height,
	 (color0 == gx_no_color_index ? color0 : color0 >> adepth),
	 (color1 == gx_no_color_index ? color1 : color1 >> adepth));
}

private int
dsa_copy_color(gx_device * dev,
	       const byte * data, int data_x, int raster, gx_bitmap_id id,
	       int x, int y, int width, int height)
{
    int sdepth = dev->color_info.depth;
    gx_device *tdev = sadev->target;
    int tdepth = tdev->color_info.depth;
    gx_device *adev = (gx_device *) sadev->adev;
    int adepth = sadev->alpha_depth;	/* = adev->color_info.depth */
    uint amask = (1 << adepth) - 1;
    gs_memory_t *mem = dev->memory;
    byte *color_row;
    byte *alpha_row;
    int code = 0;
    int xbit, yi;

    fit_copy(dev, data, data_x, raster, id, x, y, width, height);
    color_row =
	gs_alloc_bytes(mem, bitmap_raster(width * tdepth),
		       "dsa_copy_color(color)");
    /*
     * If we aren't already storing alpha, alpha_row remains 0 until we
     * encounter a source value with a non-unity alpha.
     */
    alpha_row =
	(adev ?
	 gs_alloc_bytes(mem, bitmap_raster(width * adepth),
			"dsa_copy_color(color)") : 0);
    if (color_row == 0 || (adev && alpha_row == 0)) {
	code = gs_note_error(gs_error_VMerror);
	goto out;
    }
    xbit = data_x * dev->color_info.depth;
    for (yi = y; yi < y + height; ++yi) {
	const byte *source_row = data + (yi - y) * raster;

	sample_load_declare_setup(sptr, sbit, source_row + (xbit >> 3),
				  xbit & 7, sdepth);
	sample_store_declare_setup(dcptr, dcbit, dcbyte,
				   color_row, 0, tdepth);
	sample_store_declare_setup(daptr, dabit, dabyte,
				   alpha_row, 0, adepth);
	int xi;

	for (xi = 0; xi < width; ++xi) {
	    gx_color_index source;

	    sample_load_next32(source, sptr, sbit, sdepth);
	    {
		gx_color_index dest_color = source >> adepth;

		sample_store_next32(dest_color, dcptr, dcbit, tdepth, dcbyte);
	    }
	    {
		uint dest_alpha = source & amask;

		if (dest_alpha != amask && !alpha_row) {
		    /*
		     * Create an alpha row (and, if necessary, alpha storage).
		     */
		    int axbit = (xi - x) * adepth;

		    if (!adev) {
			code = alloc_alpha_storage(sadev);
			if (code < 0)
			    return code;
			adev = (gx_device *) sadev->adev;
		    }
		    alpha_row =
			gs_alloc_bytes(mem, bitmap_raster(width * adepth),
				       "dsa_copy_color(color)");
		    if (alpha_row == 0) {
			code = gs_note_error(gs_error_VMerror);
			goto out;
		    }
		    /*
		     * All the alpha values up to this point are unity, so we
		     * can fill them into the alpha row quickly.
		     */
		    memset(alpha_row, 0xff, (axbit + 7) >> 3);
		    daptr = alpha_row + (axbit >> 3);
		    sample_store_setup(dabit, axbit & 7, adepth);
		    sample_store_preload(dabyte, daptr, dabit, adepth);
		}
		if (alpha_row)
		    sample_store_next12(dest_alpha, daptr, dabit, adepth, dabyte);
	    }
	}
	if (alpha_row) {
	    sample_store_flush(daptr, dabit, adepth, dabyte);
	    (*dev_proc(adev, copy_color))
		(adev, alpha_row, 0, 0 /* irrelevant */ , gx_no_bitmap_id,
		 x, yi, width, 1);
	}
	sample_store_flush(dcptr, dcbit, adepth, dcbyte);
	(*dev_proc(tdev, copy_color))
	    (tdev, color_row, 0, 0 /* irrelevant */ , gx_no_bitmap_id,
	     x, yi, width, 1);
    }
  out:gs_free_object(mem, alpha_row, "dsa_copy_color(alpha)");
    gs_free_object(mem, color_row, "dsa_copy_color(color)");
    return code;
}

private int
dsa_copy_alpha(gx_device * dev, const byte * data, int data_x,
	   int raster, gx_bitmap_id id, int x, int y, int width, int height,
	       gx_color_index color, int depth)
{
    gx_device *tdev = sadev->target;
    gx_device *adev = (gx_device *) sadev->adev;
    int adepth = sadev->alpha_depth;
    uint amask = (1 << adepth) - 1;

    if ((~color & amask) && !adev) {
	int code = alloc_alpha_storage(sadev);

	if (code < 0)
	    return code;
	adev = (gx_device *) sadev->adev;
    }
    if (adev)
	(*dev_proc(adev, copy_alpha))
	    (adev, data, data_x, raster, id, x, y, width, height,
	     color & amask, depth);
    return (*dev_proc(tdev, copy_alpha))
	(tdev, data, data_x, raster, id, x, y, width, height,
	 color >> adepth, depth);
}

/* ------ High-level drawing ------ */

private int
dsa_fill_path(gx_device * dev, const gs_imager_state * pis, gx_path * ppath,
	    const gx_fill_params * params, const gx_drawing_color * pdcolor,
	      const gx_clip_path * pcpath)
{
    if (!color_is_pure(pdcolor))
	return gx_default_fill_path(dev, pis, ppath, params, pdcolor,
				    pcpath);

    {
	gx_color_index color = gx_dc_pure_color(pdcolor);
	gx_drawing_color dcolor;
	gx_device *tdev = sadev->target;
	gx_device *adev = (gx_device *) sadev->adev;
	int adepth = sadev->alpha_depth;
	uint amask = (1 << adepth) - 1;
	int code;

	if ((~color & amask) && !adev) {
	    code = alloc_alpha_storage(sadev);
	    if (code < 0)
		return code;
	    adev = (gx_device *) sadev->adev;
	}
	if (adev) {
	    color_set_pure(&dcolor, color & amask);
	    code = (*dev_proc(adev, fill_path))
		(adev, pis, ppath, params, &dcolor, pcpath);
	    if (code < 0)
		return code;
	}
	color_set_pure(&dcolor, color >> adepth);
	return (*dev_proc(tdev, fill_path))
	    (tdev, pis, ppath, params, &dcolor, pcpath);
    }
}

private int
dsa_stroke_path(gx_device * dev, const gs_imager_state * pis, gx_path * ppath,
	  const gx_stroke_params * params, const gx_drawing_color * pdcolor,
		const gx_clip_path * pcpath)
{
    if (!color_is_pure(pdcolor))
	return gx_default_stroke_path(dev, pis, ppath, params, pdcolor,
				      pcpath);

    {
	gx_color_index color = gx_dc_pure_color(pdcolor);
	gx_drawing_color dcolor;
	gx_device *tdev = sadev->target;
	gx_device *adev = (gx_device *) sadev->adev;
	int adepth = sadev->alpha_depth;
	uint amask = (1 << adepth) - 1;
	int code;

	if ((~color & amask) && !adev) {
	    code = alloc_alpha_storage(sadev);
	    if (code < 0)
		return code;
	    adev = (gx_device *) sadev->adev;
	}
	if (adev) {
	    color_set_pure(&dcolor, color & amask);
	    code = (*dev_proc(adev, stroke_path))
		(adev, pis, ppath, params, &dcolor, pcpath);
	    if (code < 0)
		return code;
	}
	color_set_pure(&dcolor, color >> adepth);
	return (*dev_proc(tdev, stroke_path))
	    (tdev, pis, ppath, params, &dcolor, pcpath);
    }
}

/* ------ Reading bits ------ */

private int
dsa_get_bits_rectangle(gx_device * dev, const gs_int_rect * prect,
		       gs_get_bits_params_t * params, gs_int_rect ** unread)
{
    int ddepth = dev->color_info.depth;
    gx_device *tdev = sadev->target;
    int tdepth = tdev->color_info.depth;
    gx_device *adev = (gx_device *) sadev->adev;
    int adepth = sadev->alpha_depth;	/* = adev->color_info.depth */
    uint alpha = (1 << adepth) - 1;
    gs_get_bits_options_t options = params->options;
    int x = prect->p.x, w = prect->q.x - x, y = prect->p.y, h = prect->q.y - y;
    int yi;
    uint raster =
    (options & gb_raster_standard ? bitmap_raster(w * ddepth) :
     params->raster);

    if ((~options &
	 (gb_colors_native | gb_alpha_last | gb_packing_chunky |
	  gb_return_copy | gb_offset_0)) ||
	!(options & (gb_raster_standard | gb_raster_specified))
	) {
	if (options == 0) {
	    params->options =
		gb_colors_native | gb_alpha_last | gb_packing_chunky |
		gb_return_copy | gb_offset_0 |
		(gb_raster_standard | gb_raster_specified);
	    return_error(gs_error_rangecheck);
	}
    }
    if ((w <= 0) | (h <= 0)) {
	if ((w | h) < 0)
	    return_error(gs_error_rangecheck);
	return 0;
    }
    if (x < 0 || w > dev->width - x ||
	y < 0 || h > dev->height - y
	)
	return_error(gs_error_rangecheck);
    for (yi = y; yi < y + h; ++yi) {
	sample_load_declare(scptr, scbit);
	sample_load_declare(saptr, sabit);
	sample_store_declare(dptr, dbit, dbbyte);
	byte *data = params->data[0] + (yi - y) * raster;
	gs_get_bits_params_t color_params;
	gs_int_rect rect;
	int code, xi;

	rect.p.x = x, rect.p.y = yi;
	rect.q.x = x + w, rect.q.y = yi + 1;
	color_params.options =
	    gb_colors_native | gb_alpha_none | gb_packing_chunky |
	    (gb_return_copy | gb_return_pointer) | gb_offset_specified |
	    gb_raster_all /*irrelevant */  | gb_align_all /*irrelevant */ ;
	color_params.x_offset = w;
	code = (*dev_proc(tdev, get_bits_rectangle))
	    (tdev, &rect, &color_params, NULL);
	if (code < 0)
	    return code;
	dptr = data;
	sample_store_setup(dbit, 0, ddepth);
	sample_store_preload(dbbyte, dptr, dbit, ddepth);
	{
	    int cxbit = (color_params.data[0] == data ? w * tdepth : 0);

	    scptr = data + (cxbit >> 3);
	    sample_load_setup(scbit, cxbit & 7, tdepth);
	}
	if (adev) {
	    int axbit = x * adepth;

	    saptr = data + (axbit >> 3);
	    sample_load_setup(sabit, axbit & 7, adepth);
	}
	for (xi = 0; xi < w; ++xi) {
	    gx_color_index colors;

	    sample_load_next32(colors, scptr, scbit, tdepth);
	    if (adev)
		sample_load_next12(alpha, saptr, sabit, adepth);
	    colors = (colors << adepth) + alpha;
	    sample_store_next32(colors, dptr, dbit, ddepth, dbbyte);
	}
	sample_store_flush(dptr, dbit, ddepth, dbbyte);
    }
    return 0;
}

/* ------ Setting parameters ------ */

private int
dsa_put_params(gx_device * dev, gs_param_list * plist)
{
    gx_device *tdev = sadev->target;
    int code = (*dev_proc(tdev, put_params)) (tdev, plist);

    if (code < 0)
	return code;
    gx_device_copy_params(dev, tdev);
    return code;
}

#undef sadev

/* ------ Internal utilities ------ */

/* Allocate the alpha storage memory device. */
private int
alloc_alpha_storage(gx_device_store_alpha * sadev)
{
    int adepth = sadev->alpha_depth;
    const gx_device_memory *mdproto = gdev_mem_device_for_bits(adepth);
    gs_memory_t *mem = sadev->memory;
    gx_device_memory *mdev =
    gs_alloc_struct(mem, gx_device_memory,
		    &st_device_memory, "alloc_alpha_storage(adev)");
    int code;

    if (mdev == 0)
	return_error(gs_error_VMerror);
    gs_make_mem_device(mdev, mdproto, mem, 0, NULL);
    mdev->width = sadev->width;
    mdev->height = sadev->height;
    mdev->bitmap_memory = mem;
    code = (*dev_proc(mdev, open_device)) ((gx_device *) mdev);
    if (code < 0) {
	gs_free_object(mem, mdev, "alloc_alpha_storage(adev)");
	return_error(gs_error_VMerror);
    }
    sadev->adev = mdev;
    return (*dev_proc(mdev, fill_rectangle))
	((gx_device *) mdev, 0, 0, mdev->width, mdev->height,
	 (gx_color_index) ((1 << adepth) - 1));
}
