/* Copyright (C) 1989, 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: gxht.c,v 1.2.6.2.2.1 2003/01/17 00:49:03 giles Exp $ */
/* Halftone rendering for imaging library */
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsstruct.h"
#include "gsbitops.h"
#include "gsutil.h"		/* for gs_next_ids */
#include "gxdcolor.h"
#include "gxfixed.h"
#include "gxdevice.h"		/* for gzht.h */
#include "gxistate.h"
#include "gzht.h"

/* Define the sizes of the halftone cache. */
#define max_cached_tiles_HUGE 5000	/* not used */
#define max_ht_bits_HUGE 1000000	/* not used */
#define max_cached_tiles_LARGE 577
#define max_ht_bits_LARGE 100000
#define max_cached_tiles_SMALL 25
#define max_ht_bits_SMALL 1000

/* Define the binary halftone device color type. */
/* The type descriptor must be public for Pattern types. */
gs_public_st_composite(st_dc_ht_binary, gx_device_color, "dc_ht_binary",
		       dc_ht_binary_enum_ptrs, dc_ht_binary_reloc_ptrs);
private dev_color_proc_load(gx_dc_ht_binary_load);
private dev_color_proc_fill_rectangle(gx_dc_ht_binary_fill_rectangle);
private dev_color_proc_equal(gx_dc_ht_binary_equal);
const gx_device_color_type_t
      gx_dc_type_data_ht_binary =
{&st_dc_ht_binary,
 gx_dc_ht_binary_load, gx_dc_ht_binary_fill_rectangle,
 gx_dc_default_fill_masked, gx_dc_ht_binary_equal
};

#undef gx_dc_type_ht_binary
const gx_device_color_type_t *const gx_dc_type_ht_binary =
&gx_dc_type_data_ht_binary;

#define gx_dc_type_ht_binary (&gx_dc_type_data_ht_binary)
/* GC procedures */
private 
ENUM_PTRS_WITH(dc_ht_binary_enum_ptrs, gx_device_color *cptr) return 0;
ENUM_PTR(0, gx_device_color, colors.binary.b_ht);
case 1:
{
    gx_ht_tile *tile = cptr->colors.binary.b_tile;

    ENUM_RETURN(tile - tile->index);
}
ENUM_PTRS_END
private RELOC_PTRS_WITH(dc_ht_binary_reloc_ptrs, gx_device_color *cptr)
{
    uint index = cptr->colors.binary.b_tile->index;

    RELOC_PTR(gx_device_color, colors.binary.b_ht);
    RELOC_TYPED_OFFSET_PTR(gx_device_color, colors.binary.b_tile, index);
}
RELOC_PTRS_END
#undef cptr

/* Other GC procedures */
private_st_ht_tiles();
private 
ENUM_PTRS_BEGIN_PROC(ht_tiles_enum_ptrs)
{
    return 0;
}
ENUM_PTRS_END_PROC
private RELOC_PTRS_BEGIN(ht_tiles_reloc_ptrs)
{
    /* Reset the bitmap pointers in the tiles. */
    /* We know the first tile points to the base of the bits. */
    gx_ht_tile *ht_tiles = vptr;
    byte *bits = ht_tiles->tiles.data;
    uint diff;

    if (bits == 0)
	return;
    RELOC_VAR(bits);
    if (size == size_of(gx_ht_tile)) {	/* only 1 tile */
	ht_tiles->tiles.data = bits;
	return;
    }
    diff = ht_tiles[1].tiles.data - ht_tiles[0].tiles.data;
    for (; size; ht_tiles++, size -= size_of(gx_ht_tile), bits += diff) {
	ht_tiles->tiles.data = bits;
    }
}
RELOC_PTRS_END
private_st_ht_cache();

/* Return the default sizes of the halftone cache. */
uint
gx_ht_cache_default_tiles(void)
{
#if arch_small_memory
    return max_cached_tiles_SMALL;
#else
    return (gs_debug_c('.') ? max_cached_tiles_SMALL :
	    max_cached_tiles_LARGE);
#endif
}
uint
gx_ht_cache_default_bits(void)
{
#if arch_small_memory
    return max_ht_bits_SMALL;
#else
    return (gs_debug_c('.') ? max_ht_bits_SMALL :
	    max_ht_bits_LARGE);
#endif
}

/* Allocate a halftone cache. */
gx_ht_cache *
gx_ht_alloc_cache(gs_memory_t * mem, uint max_tiles, uint max_bits)
{
    gx_ht_cache *pcache =
    gs_alloc_struct(mem, gx_ht_cache, &st_ht_cache,
		    "alloc_ht_cache(struct)");
    byte *tbits =
	gs_alloc_bytes(mem, max_bits, "alloc_ht_cache(bits)");
    gx_ht_tile *ht_tiles =
	gs_alloc_struct_array(mem, max_tiles, gx_ht_tile, &st_ht_tiles,
			      "alloc_ht_cache(ht_tiles)");

    if (pcache == 0 || tbits == 0 || ht_tiles == 0) {
	gs_free_object(mem, ht_tiles, "alloc_ht_cache(ht_tiles)");
	gs_free_object(mem, tbits, "alloc_ht_cache(bits)");
	gs_free_object(mem, pcache, "alloc_ht_cache(struct)");
	return 0;
    }
    pcache->bits = tbits;
    pcache->bits_size = max_bits;
    pcache->order.data_memory = 0;
    pcache->ht_tiles = ht_tiles;
    pcache->num_tiles = max_tiles;
    pcache->order.cache = pcache;
    pcache->order.transfer = 0;
    gx_ht_clear_cache(pcache);
    return pcache;
}

/* Free a halftone cache. */
void
gx_ht_free_cache(gs_memory_t * mem, gx_ht_cache * pcache)
{
    gs_free_object(mem, pcache->ht_tiles, "free_ht_cache(ht_tiles)");
    gs_free_object(mem, pcache->bits, "free_ht_cache(bits)");
    gs_free_object(mem, pcache, "free_ht_cache(struct)");
}

/* Make the cache order current, and return whether */
/* there is room for all possible tiles in the cache. */
bool
gx_check_tile_cache(const gs_imager_state * pis)
{
    const gx_ht_order *porder = &pis->dev_ht->order;
    gx_ht_cache *pcache = pis->ht_cache;

    if (pcache == 0 || pis->dev_ht == 0)
	return false;		/* no halftone or cache */
    if (pcache->order.bit_data != porder->bit_data)
	gx_ht_init_cache(pcache, porder);
    if (pcache->tiles_fit >= 0)
	return (bool)pcache->tiles_fit;
    {
	bool fit = false;
	int bits_per_level;

	if (pcache->num_cached < porder->num_levels)
	    DO_NOTHING;
	else if (pcache->levels_per_tile == 1)
	    fit = true;
	/*
	 * All the tiles fit iff bits and levels are exactly N-for-1, where
	 * N is a multiple of levels_per_tile.
	 */
	else if (porder->num_bits % porder->num_levels == 0 &&
		 (bits_per_level = porder->num_bits / porder->num_levels) %
		   pcache->levels_per_tile == 0) {
	    /* Check the N-for-1 property. */
	    const uint *level = porder->levels;
	    int i = porder->num_levels, j = 0;

	    for (; i > 0; --i, j += bits_per_level, ++level)
		if (*level != j)
		    break;
	    fit = i == 0;
	}
	pcache->tiles_fit = (int)fit;
	return fit;
    }
}

/*
 * Determine whether a given (width, y, height) might fit into a single
 * (non-strip) tile. If so, return the byte offset of the appropriate row
 * from the beginning of the tile, and set *ppx to the x phase offset
 * within the tile; if not, return -1.
 */
int
gx_check_tile_size(const gs_imager_state * pis, int w, int y, int h,
		   gs_color_select_t select, int *ppx)
{
    int tsy;
    const gx_strip_bitmap *ptile0;

    if (pis->ht_cache == 0)
	return -1;		/* no halftone cache */
    ptile0 = &pis->ht_cache->ht_tiles[0].tiles;		/* a typical tile */
    if (h > ptile0->rep_height || w > ptile0->rep_width ||
	ptile0->shift != 0
	)
	return -1;
    tsy = (y + imod(-pis->screen_phase[select].y, ptile0->rep_height)) %
	ptile0->rep_height;
    if (tsy + h > ptile0->size.y)
	return -1;
    /* Tile fits in Y, might fit in X. */
    *ppx = imod(-pis->screen_phase[select].x, ptile0->rep_width);
    return tsy * ptile0->raster;
}

/* Render a given level into a halftone cache. */
private int render_ht(P4(gx_ht_tile *, int, const gx_ht_order *,
			 gx_bitmap_id));
private gx_ht_tile *
gx_render_ht_default(gx_ht_cache * pcache, int b_level)
{
    const gx_ht_order *porder = &pcache->order;
    int level = porder->levels[b_level];
    gx_ht_tile *bt = &pcache->ht_tiles[level / pcache->levels_per_tile];

    if (bt->level != level) {
	int code = render_ht(bt, level, porder, pcache->base_id + b_level);

	if (code < 0)
	    return 0;
    }
    return bt;
}
/* Faster code if num_tiles == 1. */
private gx_ht_tile *
gx_render_ht_1_tile(gx_ht_cache * pcache, int b_level)
{
    const gx_ht_order *porder = &pcache->order;
    int level = porder->levels[b_level];
    gx_ht_tile *bt = &pcache->ht_tiles[0];

    if (bt->level != level) {
	int code = render_ht(bt, level, porder, pcache->base_id + b_level);

	if (code < 0)
	    return 0;
    }
    return bt;
}
/* Faster code if levels_per_tile == 1. */
private gx_ht_tile *
gx_render_ht_1_level(gx_ht_cache * pcache, int b_level)
{
    const gx_ht_order *porder = &pcache->order;
    int level = porder->levels[b_level];
    gx_ht_tile *bt = &pcache->ht_tiles[level];

    if (bt->level != level) {
	int code = render_ht(bt, level, porder, pcache->base_id + b_level);

	if (code < 0)
	    return 0;
    }
    return bt;
}

/* Load the device color into the halftone cache if needed. */
private int
gx_dc_ht_binary_load(gx_device_color * pdevc, const gs_imager_state * pis,
		     gx_device * dev, gs_color_select_t select)
{
    int component_index = pdevc->colors.binary.b_index;
    const gx_ht_order *porder =
	(component_index < 0 ?
	 &pdevc->colors.binary.b_ht->order :
	 &pdevc->colors.binary.b_ht->components[component_index].corder);
    gx_ht_cache *pcache =
	(porder->cache == 0 ? pis->ht_cache : porder->cache);

    if (pcache->order.bit_data != porder->bit_data)
	gx_ht_init_cache(pcache, porder);
    /* Expand gx_render_ht inline for speed. */
    {
	int b_level = pdevc->colors.binary.b_level;
	int level = porder->levels[b_level];
	gx_ht_tile *bt = &pcache->ht_tiles[level / pcache->levels_per_tile];

	if (bt->level != level) {
	    int code = render_ht(bt, level, porder,
				 pcache->base_id + b_level);

	    if (code < 0)
		return_error(gs_error_Fatal);
	}
	pdevc->colors.binary.b_tile = bt;
    }
    return 0;
}

/* Fill a rectangle with a binary halftone. */
/* Note that we treat this as "texture" for RasterOp. */
private int
gx_dc_ht_binary_fill_rectangle(const gx_device_color * pdevc, int x, int y,
		  int w, int h, gx_device * dev, gs_logical_operation_t lop,
			       const gx_rop_source_t * source)
{
    gx_rop_source_t no_source;

    /*
     * Observation of H-P devices and documentation yields confusing
     * evidence about whether white pixels in halftones are always
     * opaque.  It appears that for black-and-white devices, these
     * pixels are *not* opaque.
     */
    if (dev->color_info.depth > 1)
	lop &= ~lop_T_transparent;
    if (source == NULL && lop_no_S_is_T(lop))
	return (*dev_proc(dev, strip_tile_rectangle)) (dev,
					&pdevc->colors.binary.b_tile->tiles,
				  x, y, w, h, pdevc->colors.binary.color[0],
					      pdevc->colors.binary.color[1],
					    pdevc->phase.x, pdevc->phase.y);
    /* Adjust the logical operation per transparent colors. */
    if (pdevc->colors.binary.color[0] == gx_no_color_index)
	lop = rop3_use_D_when_T_0(lop);
    if (pdevc->colors.binary.color[1] == gx_no_color_index)
	lop = rop3_use_D_when_T_1(lop);
    if (source == NULL)
	set_rop_no_source(source, no_source, dev);
    return (*dev_proc(dev, strip_copy_rop)) (dev, source->sdata,
			       source->sourcex, source->sraster, source->id,
			     (source->use_scolors ? source->scolors : NULL),
					&pdevc->colors.binary.b_tile->tiles,
					     pdevc->colors.binary.color,
				 x, y, w, h, pdevc->phase.x, pdevc->phase.y,
					     lop);
}

/* Compare two binary halftones for equality. */
private bool
gx_dc_ht_binary_equal(const gx_device_color * pdevc1,
		      const gx_device_color * pdevc2)
{
    return pdevc2->type == pdevc1->type &&
	pdevc1->phase.x == pdevc2->phase.x &&
	pdevc1->phase.y == pdevc2->phase.y &&
	gx_dc_binary_color0(pdevc1) == gx_dc_binary_color0(pdevc2) &&
	gx_dc_binary_color1(pdevc1) == gx_dc_binary_color1(pdevc2) &&
	pdevc1->colors.binary.b_level == pdevc2->colors.binary.b_level;
}

/* Initialize the tile cache for a given screen. */
/* Cache as many different levels as will fit. */
void
gx_ht_init_cache(gx_ht_cache * pcache, const gx_ht_order * porder)
{
    uint width = porder->width;
    uint height = porder->height;
    uint size = width * height + 1;
    int width_unit =
    (width <= ht_mask_bits / 2 ? ht_mask_bits / width * width :
     width);
    int height_unit = height;
    uint raster = porder->raster;
    uint tile_bytes = raster * height;
    uint shift = porder->shift;
    int num_cached;
    int i;
    byte *tbits = pcache->bits;

    /* Non-monotonic halftones may have more bits than size. */
    if (porder->num_bits >= size)
	size = porder->num_bits + 1;
    /* Make sure num_cached is within bounds */
    num_cached = pcache->bits_size / tile_bytes;
    if (num_cached > size)
	num_cached = size;
    if (num_cached > pcache->num_tiles)
	num_cached = pcache->num_tiles;
    if (num_cached == size &&
	tile_bytes * num_cached <= pcache->bits_size / 2
	) {
	/*
	 * We can afford to replicate every tile in the cache,
	 * which will reduce breakage when tiling.  Since
	 * horizontal breakage is more expensive than vertical,
	 * and since wide shallow fills are more common than
	 * narrow deep fills, we replicate the tile horizontally.
	 * We do have to be careful not to replicate the tile
	 * to an absurdly large size, however.
	 */
	uint rep_raster =
	((pcache->bits_size / num_cached) / height) &
	~(align_bitmap_mod - 1);
	uint rep_count = rep_raster * 8 / width;

	/*
	 * There's no real value in replicating the tile
	 * beyond the point where the byte width of the replicated
	 * tile is a multiple of a long.
	 */
	if (rep_count > sizeof(ulong) * 8)
	    rep_count = sizeof(ulong) * 8;
	width_unit = width * rep_count;
	raster = bitmap_raster(width_unit);
	tile_bytes = raster * height;
    }
    pcache->base_id = gs_next_ids(porder->num_levels + 1);
    pcache->order = *porder;
    /* The transfer function is irrelevant, and might become dangling. */
    pcache->order.transfer = 0;
    pcache->num_cached = num_cached;
    pcache->levels_per_tile = (size + num_cached - 1) / num_cached;
    pcache->tiles_fit = -1;
    memset(tbits, 0, pcache->bits_size);
    for (i = 0; i < num_cached; i++, tbits += tile_bytes) {
	register gx_ht_tile *bt = &pcache->ht_tiles[i];

	bt->level = 0;
	bt->index = i;
	bt->tiles.data = tbits;
	bt->tiles.raster = raster;
	bt->tiles.size.x = width_unit;
	bt->tiles.size.y = height_unit;
	bt->tiles.rep_width = width;
	bt->tiles.rep_height = height;
	bt->tiles.shift = bt->tiles.rep_shift = shift;
    }
    pcache->render_ht =
	(pcache->num_tiles == 1 ? gx_render_ht_1_tile :
	 pcache->levels_per_tile == 1 ? gx_render_ht_1_level :
	 gx_render_ht_default);
}

/*
 * Compute and save the rendering of a given gray level
 * with the current halftone.  The cache holds multiple tiles,
 * where each tile covers a range of possible levels.
 * We adjust the tile whose range includes the desired level incrementally;
 * this saves a lot of time for the average image, where gray levels
 * don't change abruptly.  Note that the "level" is the number of bits,
 * not the index in the levels vector.
 */
private int
render_ht(gx_ht_tile * pbt, int level /* [1..num_bits-1] */ ,
	  const gx_ht_order * porder, gx_bitmap_id new_id)
{
    byte *data = pbt->tiles.data;
    int code;

    if_debug7('H', "[H]Halftone cache slot 0x%lx: old=%d, new=%d, w=%d(%d), h=%d(%d):\n",
	      (ulong) data, pbt->level, level,
	      pbt->tiles.size.x, porder->width,
	      pbt->tiles.size.y, porder->num_bits / porder->width);
#ifdef DEBUG
    if (level < 0 || level > porder->num_bits) {
	lprintf3("Error in render_ht: level=%d, old level=%d, num_bits=%d\n",
		 level, pbt->level, porder->num_bits);
	return_error(gs_error_Fatal);
    }
#endif
    code = porder->procs->render(pbt, level, porder);
    if (code < 0)
	return code;
    pbt->level = level;
    pbt->tiles.id = new_id;
    /*
     * Check whether we want to replicate the tile in the cache.
     * Since we only do this when all the renderings will fit
     * in the cache, we only do it once per level, and it doesn't
     * have to be very efficient.
     */
	/****** TEST IS WRONG if width > rep_width but tile.raster ==
	 ****** order raster.
	 ******/
    if (pbt->tiles.raster > porder->raster)
	bits_replicate_horizontally(data, pbt->tiles.rep_width,
				    pbt->tiles.rep_height, porder->raster,
				    pbt->tiles.size.x, pbt->tiles.raster);
    if (pbt->tiles.size.y > pbt->tiles.rep_height &&
	pbt->tiles.shift == 0
	)
	bits_replicate_vertically(data, pbt->tiles.rep_height,
				  pbt->tiles.raster, pbt->tiles.size.y);
#ifdef DEBUG
    if (gs_debug_c('H')) {
	const byte *p = pbt->tiles.data;
	int wb = pbt->tiles.raster;
	const byte *ptr = p + wb * pbt->tiles.size.y;

	while (p < ptr) {
	    dprintf8(" %d%d%d%d%d%d%d%d",
		     *p >> 7, (*p >> 6) & 1, (*p >> 5) & 1,
		     (*p >> 4) & 1, (*p >> 3) & 1, (*p >> 2) & 1,
		     (*p >> 1) & 1, *p & 1);
	    if ((++p - data) % wb == 0)
		dputc('\n');
	}
    }
#endif
    return 0;
}
