/* Copyright (C) 1993, 2000 Aladdin Enterprises.  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 version 2
  as published by the Free Software Foundation.


  This software is provided AS-IS with no warranty, either express or
  implied. That is, 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.
  
  For more information about licensing, please refer to
  http://www.ghostscript.com/licensing/. For information on
  commercial licensing, go to http://www.artifex.com/licensing/ or
  contact Artifex Software, Inc., 101 Lucas Valley Road #110,
  San Rafael, CA  94903, U.S.A., +1(415)492-9861.
*/

/* $Id: gxpcmap.c,v 1.7 2002/08/22 07:12:29 henrys Exp $ */
/* Pattern color mapping for Ghostscript library */
#include "math_.h"
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsstruct.h"
#include "gsutil.h"		/* for gs_next_ids */
#include "gxfixed.h"
#include "gxmatrix.h"
#include "gxcspace.h"		/* for gscolor2.h */
#include "gxcolor2.h"
#include "gxdcolor.h"
#include "gxdevice.h"
#include "gxdevmem.h"
#include "gxpcolor.h"
#include "gxp1impl.h"
#include "gzstate.h"

/* Define the default size of the Pattern cache. */
#define max_cached_patterns_LARGE 50
#define max_pattern_bits_LARGE 100000
#define max_cached_patterns_SMALL 5
#define max_pattern_bits_SMALL 1000
uint
gx_pat_cache_default_tiles(void)
{
#if arch_small_memory
    return max_cached_patterns_SMALL;
#else
    return (gs_debug_c('.') ? max_cached_patterns_SMALL :
	    max_cached_patterns_LARGE);
#endif
}
ulong
gx_pat_cache_default_bits(void)
{
#if arch_small_memory
    return max_pattern_bits_SMALL;
#else
    return (gs_debug_c('.') ? max_pattern_bits_SMALL :
	    max_pattern_bits_LARGE);
#endif
}

/* Define the structures for Pattern rendering and caching. */
private_st_color_tile();
private_st_color_tile_element();
private_st_pattern_cache();
private_st_device_pattern_accum();

/* ------ Pattern rendering ------ */

/* Device procedures */
private dev_proc_open_device(pattern_accum_open);
private dev_proc_close_device(pattern_accum_close);
private dev_proc_fill_rectangle(pattern_accum_fill_rectangle);
private dev_proc_copy_mono(pattern_accum_copy_mono);
private dev_proc_copy_color(pattern_accum_copy_color);
private dev_proc_get_bits_rectangle(pattern_accum_get_bits_rectangle);

/* The device descriptor */
private const gx_device_pattern_accum gs_pattern_accum_device =
{std_device_std_body_open(gx_device_pattern_accum, 0,
			  "pattern accumulator",
			  0, 0, 72, 72),
 {
     /* NOTE: all drawing procedures must be defaulted, not forwarded. */
     pattern_accum_open,
     NULL,
     NULL,
     NULL,
     pattern_accum_close,
     NULL,
     NULL,
     pattern_accum_fill_rectangle,
     gx_default_tile_rectangle,
     pattern_accum_copy_mono,
     pattern_accum_copy_color,
     NULL,
     gx_default_get_bits,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     gx_default_copy_alpha,
     NULL,
     gx_default_copy_rop,
     gx_default_fill_path,
     gx_default_stroke_path,
     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_get_largest_clipping_box,
     gx_default_begin_typed_image,
     pattern_accum_get_bits_rectangle,
     NULL,
     gx_default_create_compositor,
     NULL,
     gx_default_text_begin,
     gx_default_finish_copydevice,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     NULL
 },
 0,				/* target */
 0, 0, 0, 0			/* bitmap_memory, bits, mask, instance */
};

/* Allocate a pattern accumulator, with an initial refct of 0. */
gx_device_pattern_accum *
gx_pattern_accum_alloc(gs_memory_t * mem, client_name_t cname)
{
    gx_device_pattern_accum *adev =
	gs_alloc_struct(mem, gx_device_pattern_accum,
			&st_device_pattern_accum, cname);

    if (adev == 0)
	return 0;
    gx_device_init((gx_device *)adev,
		   (const gx_device *)&gs_pattern_accum_device,
		   mem, true);
    gx_device_forward_fill_in_procs((gx_device_forward *)adev);
    return adev;
}

/*
 * Initialize a pattern accumulator.
 * Client must already have set instance and bitmap_memory.
 *
 * Note that mask and bits accumulators are only created if necessary.
 */
private int
pattern_accum_open(gx_device * dev)
{
    gx_device_pattern_accum *const padev = (gx_device_pattern_accum *) dev;
    const gs_pattern1_instance_t *pinst = padev->instance;
    gs_memory_t *mem = padev->bitmap_memory;
    gx_device_memory *mask = 0;
    gx_device_memory *bits = 0;
    /*
     * The client should preset the target, because the device for which the
     * pattern is being rendered may not (in general, will not) be the same
     * as the one that was current when the pattern was instantiated.
     */
    gx_device *target =
	(padev->target == 0 ? gs_currentdevice(pinst->saved) :
	 padev->target);
    int width = pinst->size.x;
    int height = pinst->size.y;
    int code = 0;
    bool mask_open = false;

    /*
     * C's bizarre coercion rules force us to copy HWResolution in pieces
     * rather than using a single assignment.
     */
#define PDSET(dev)\
  ((dev)->width = width, (dev)->height = height,\
   /*(dev)->HWResolution = target->HWResolution*/\
   (dev)->HWResolution[0] = target->HWResolution[0],\
   (dev)->HWResolution[1] = target->HWResolution[1])

    PDSET(padev);
    padev->color_info = target->color_info;

    if (pinst->uses_mask) {
        mask = gs_alloc_struct( mem,
                                gx_device_memory,
                                &st_device_memory,
		                "pattern_accum_open(mask)"
                                );
        if (mask == 0)
	    return_error(gs_error_VMerror);
        gs_make_mem_mono_device(mask, mem, 0);
        PDSET(mask);
        mask->bitmap_memory = mem;
        mask->base = 0;
        code = (*dev_proc(mask, open_device)) ((gx_device *) mask);
        if (code >= 0) {
	    mask_open = true;
	    memset(mask->base, 0, mask->raster * mask->height);
	}
    }

    if (code >= 0) {
	switch (pinst->template.PaintType) {
	    case 2:		/* uncolored */
		gx_device_set_target((gx_device_forward *)padev, target);
		break;
	    case 1:		/* colored */
		bits = gs_alloc_struct(mem, gx_device_memory,
				       &st_device_memory,
				       "pattern_accum_open(bits)");
		if (bits == 0)
		    code = gs_note_error(gs_error_VMerror);
		else {
		    gs_make_mem_device(bits,
			gdev_mem_device_for_bits(target->color_info.depth),
				       mem, -1, target);
		    PDSET(bits);
#undef PDSET
		    bits->color_info = target->color_info;
		    bits->bitmap_memory = mem;
		    code = (*dev_proc(bits, open_device)) ((gx_device *) bits);
		    gx_device_set_target((gx_device_forward *)padev,
					 (gx_device *)bits);
		}
	}
    }
    if (code < 0) {
	if (bits != 0)
	    gs_free_object(mem, bits, "pattern_accum_open(bits)");
        if (mask != 0) {
	    if (mask_open)
		(*dev_proc(mask, close_device)) ((gx_device *) mask);
	    gs_free_object(mem, mask, "pattern_accum_open(mask)");
        }
	return code;
    }
    padev->mask = mask;
    padev->bits = bits;
    /* Retain the device, so it will survive anomalous grestores. */
    gx_device_retain(dev, true);
    return code;
}

/* Close an accumulator and free the bits. */
private int
pattern_accum_close(gx_device * dev)
{
    gx_device_pattern_accum *const padev = (gx_device_pattern_accum *) dev;
    gs_memory_t *mem = padev->bitmap_memory;

    /*
     * If bits != 0, it is the target of the device; reference counting
     * will close and free it.
     */
    gx_device_set_target((gx_device_forward *)padev, NULL);
    padev->bits = 0;
    if (padev->mask != 0) {
        (*dev_proc(padev->mask, close_device)) ((gx_device *) padev->mask);
        gs_free_object(mem, padev->mask, "pattern_accum_close(mask)");
        padev->mask = 0;
    }
    /* Un-retain the device now, so reference counting will free it. */
    gx_device_retain(dev, false);
    return 0;
}

/* Fill a rectangle */
private int
pattern_accum_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
			     gx_color_index color)
{
    gx_device_pattern_accum *const padev = (gx_device_pattern_accum *) dev;

    if (padev->bits)
	(*dev_proc(padev->target, fill_rectangle))
	    (padev->target, x, y, w, h, color);
    if (padev->mask)
        return (*dev_proc(padev->mask, fill_rectangle))
	    ((gx_device *) padev->mask, x, y, w, h, (gx_color_index) 1);
     else
        return 0;
}

/* Copy a monochrome bitmap. */
private int
pattern_accum_copy_mono(gx_device * dev, const byte * data, int data_x,
		    int raster, gx_bitmap_id id, int x, int y, int w, int h,
			gx_color_index color0, gx_color_index color1)
{
    gx_device_pattern_accum *const padev = (gx_device_pattern_accum *) dev;

    /* opt out early if nothing to render (some may think this a bug) */
    if (color0 == gx_no_color_index && color1 == gx_no_color_index)
        return 0;
    if (padev->bits)
	(*dev_proc(padev->target, copy_mono))
	    (padev->target, data, data_x, raster, id, x, y, w, h,
	     color0, color1);
    if (padev->mask) {
        if (color0 != gx_no_color_index)
	    color0 = 1;
        if (color1 != gx_no_color_index)
	    color1 = 1;
        if (color0 == 1 && color1 == 1)
	    return (*dev_proc(padev->mask, fill_rectangle))
	        ((gx_device *) padev->mask, x, y, w, h, (gx_color_index) 1);
        else
	    return (*dev_proc(padev->mask, copy_mono))
	        ((gx_device *) padev->mask, data, data_x, raster, id, x, y, w, h,
	         color0, color1);
    } else
        return 0;
}

/* Copy a color bitmap. */
private int
pattern_accum_copy_color(gx_device * dev, const byte * data, int data_x,
		    int raster, gx_bitmap_id id, int x, int y, int w, int h)
{
    gx_device_pattern_accum *const padev = (gx_device_pattern_accum *) dev;

    if (padev->bits)
	(*dev_proc(padev->target, copy_color))
	    (padev->target, data, data_x, raster, id, x, y, w, h);
    if (padev->mask)
        return (*dev_proc(padev->mask, fill_rectangle))
	    ((gx_device *) padev->mask, x, y, w, h, (gx_color_index) 1);
    else
        return 0;
}

/* Read back a rectangle of bits. */
/****** SHOULD USE MASK TO DEFINE UNREAD AREA *****/
private int
pattern_accum_get_bits_rectangle(gx_device * dev, const gs_int_rect * prect,
		       gs_get_bits_params_t * params, gs_int_rect ** unread)
{
    gx_device_pattern_accum *const padev = (gx_device_pattern_accum *) dev;

    if (padev->bits)
	return (*dev_proc(padev->target, get_bits_rectangle))
	    (padev->target, prect, params, unread);
    return_error(gs_error_Fatal); /* can't happen */
}

/* ------ Color space implementation ------ */

/* Free all entries in a pattern cache. */
private bool
pattern_cache_choose_all(gx_color_tile * ctile, void *proc_data)
{
    return true;
}
private void
pattern_cache_free_all(gx_pattern_cache * pcache)
{
    gx_pattern_cache_winnow(pcache, pattern_cache_choose_all, NULL);
}

/* Allocate a Pattern cache. */
gx_pattern_cache *
gx_pattern_alloc_cache(gs_memory_t * mem, uint num_tiles, ulong max_bits)
{
    gx_pattern_cache *pcache =
    gs_alloc_struct(mem, gx_pattern_cache, &st_pattern_cache,
		    "pattern_cache_alloc(struct)");
    gx_color_tile *tiles =
    gs_alloc_struct_array(mem, num_tiles, gx_color_tile,
			  &st_color_tile_element,
			  "pattern_cache_alloc(tiles)");
    uint i;

    if (pcache == 0 || tiles == 0) {
	gs_free_object(mem, tiles, "pattern_cache_alloc(tiles)");
	gs_free_object(mem, pcache, "pattern_cache_alloc(struct)");
	return 0;
    }
    pcache->memory = mem;
    pcache->tiles = tiles;
    pcache->num_tiles = num_tiles;
    pcache->tiles_used = 0;
    pcache->next = 0;
    pcache->bits_used = 0;
    pcache->max_bits = max_bits;
    pcache->free_all = pattern_cache_free_all;
    for (i = 0; i < num_tiles; tiles++, i++) {
	tiles->id = gx_no_bitmap_id;
	/* Clear the pointers to pacify the GC. */
	uid_set_invalid(&tiles->uid);
	tiles->tbits.data = 0;
	tiles->tmask.data = 0;
	tiles->index = i;
    }
    return pcache;
}
/* Ensure that an imager has a Pattern cache. */
private int
ensure_pattern_cache(gs_imager_state * pis)
{
    if (pis->pattern_cache == 0) {
	gx_pattern_cache *pcache =
	gx_pattern_alloc_cache(pis->memory,
			       gx_pat_cache_default_tiles(),
			       gx_pat_cache_default_bits());

	if (pcache == 0)
	    return_error(gs_error_VMerror);
	pis->pattern_cache = pcache;
    }
    return 0;
}

/* Get and set the Pattern cache in a gstate. */
gx_pattern_cache *
gstate_pattern_cache(gs_state * pgs)
{
    return pgs->pattern_cache;
}
void
gstate_set_pattern_cache(gs_state * pgs, gx_pattern_cache * pcache)
{
    pgs->pattern_cache = pcache;
}

/* Free a Pattern cache entry. */
private void
gx_pattern_cache_free_entry(gx_pattern_cache * pcache, gx_color_tile * ctile)
{
    if (ctile->id != gx_no_bitmap_id) {
	gs_memory_t *mem = pcache->memory;
	gx_device_memory mdev;

	/*
	 * We must initialize the memory device properly, even though
	 * we aren't using it for drawing.
	 */
	gs_make_mem_mono_device(&mdev, mem, NULL);
	if (ctile->tmask.data != 0) {
	    mdev.width = ctile->tmask.size.x;
	    mdev.height = ctile->tmask.size.y;
	    /*mdev.color_info.depth = 1;*/
	    pcache->bits_used -= gdev_mem_bitmap_size(&mdev);
	    gs_free_object(mem, ctile->tmask.data,
			   "free_pattern_cache_entry(mask data)");
	    ctile->tmask.data = 0;	/* for GC */
	}
	if (ctile->tbits.data != 0) {
	    mdev.width = ctile->tbits.size.x;
	    mdev.height = ctile->tbits.size.y;
	    mdev.color_info.depth = ctile->depth;
	    pcache->bits_used -= gdev_mem_bitmap_size(&mdev);
	    gs_free_object(mem, ctile->tbits.data,
			   "free_pattern_cache_entry(bits data)");
	    ctile->tbits.data = 0;	/* for GC */
	}
	ctile->id = gx_no_bitmap_id;
	pcache->tiles_used--;
    }
}

/*
 * Add a Pattern cache entry.  This is exported for the interpreter.
 * Note that this does not free any of the data in the accumulator
 * device, but it may zero out the bitmap_memory pointers to prevent
 * the accumulated bitmaps from being freed when the device is closed.
 */
private void make_bitmap(gx_strip_bitmap *, const gx_device_memory *, gx_bitmap_id);
int
gx_pattern_cache_add_entry(gs_imager_state * pis,
		   gx_device_pattern_accum * padev, gx_color_tile ** pctile)
{
    gx_device_memory *mbits = padev->bits;
    gx_device_memory *mmask = padev->mask;
    const gs_pattern1_instance_t *pinst = padev->instance;
    gx_pattern_cache *pcache;
    ulong used = 0;
    gx_bitmap_id id = pinst->id;
    gx_color_tile *ctile;
    int code = ensure_pattern_cache(pis);

    if (code < 0)
	return code;
    pcache = pis->pattern_cache;
    /*
     * Check whether the pattern completely fills its box.
     * If so, we can avoid the expensive masking operations
     * when using the pattern.
     */
    if (mmask != 0) {
	int y;

	for (y = 0; y < mmask->height; y++) {
	    const byte *row = scan_line_base(mmask, y);
	    int w;

	    for (w = mmask->width; w > 8; w -= 8)
		if (*row++ != 0xff)
		    goto keep;
	    if ((*row | (0xff >> w)) != 0xff)
		goto keep;
	}
	/* We don't need a mask. */
	mmask = 0;
      keep:;
    }
    if (mbits != 0)
	used += gdev_mem_bitmap_size(mbits);
    if (mmask != 0)
	used += gdev_mem_bitmap_size(mmask);
    ctile = &pcache->tiles[id % pcache->num_tiles];
    gx_pattern_cache_free_entry(pcache, ctile);
    while (pcache->bits_used + used > pcache->max_bits &&
	   pcache->bits_used != 0	/* allow 1 oversized entry (?) */
	) {
	pcache->next = (pcache->next + 1) % pcache->num_tiles;
	gx_pattern_cache_free_entry(pcache, &pcache->tiles[pcache->next]);
    }
    ctile->id = id;
    ctile->depth = padev->color_info.depth;
    ctile->uid = pinst->template.uid;
    ctile->tiling_type = pinst->template.TilingType;
    ctile->step_matrix = pinst->step_matrix;
    ctile->bbox = pinst->bbox;
    ctile->is_simple = pinst->is_simple;
    if (mbits != 0) {
	make_bitmap(&ctile->tbits, mbits, gs_next_ids(1));
	mbits->bitmap_memory = 0;	/* don't free the bits */
    } else
	ctile->tbits.data = 0;
    if (mmask != 0) {
	make_bitmap(&ctile->tmask, mmask, id);
	mmask->bitmap_memory = 0;	/* don't free the bits */
    } else
	ctile->tmask.data = 0;
    pcache->bits_used += used;
    pcache->tiles_used++;
    *pctile = ctile;
    return 0;
}
private void
make_bitmap(register gx_strip_bitmap * pbm, const gx_device_memory * mdev,
	    gx_bitmap_id id)
{
    pbm->data = mdev->base;
    pbm->raster = mdev->raster;
    pbm->rep_width = pbm->size.x = mdev->width;
    pbm->rep_height = pbm->size.y = mdev->height;
    pbm->id = id;
    pbm->rep_shift = pbm->shift = 0;
}

/* Purge selected entries from the pattern cache. */
void
gx_pattern_cache_winnow(gx_pattern_cache * pcache,
  bool(*proc) (gx_color_tile * ctile, void *proc_data), void *proc_data)
{
    uint i;

    if (pcache == 0)		/* no cache created yet */
	return;
    for (i = 0; i < pcache->num_tiles; ++i) {
	gx_color_tile *ctile = &pcache->tiles[i];

	if (ctile->id != gx_no_bitmap_id && (*proc) (ctile, proc_data))
	    gx_pattern_cache_free_entry(pcache, ctile);
    }
}

/* Reload a (non-null) Pattern color into the cache. */
/* *pdc is already set, except for colors.pattern.p_tile and mask.m_tile. */
int
gx_pattern_load(gx_device_color * pdc, const gs_imager_state * pis,
		gx_device * dev, gs_color_select_t select)
{
    gx_device_pattern_accum *adev;
    gs_pattern1_instance_t *pinst =
	(gs_pattern1_instance_t *)pdc->ccolor.pattern;
    gs_state *saved;
    gx_color_tile *ctile;
    gs_memory_t *mem = pis->memory;
    int code;

    if (gx_pattern_cache_lookup(pdc, pis, dev, select))
	return 0;
    /* We REALLY don't like the following cast.... */
    code = ensure_pattern_cache((gs_imager_state *) pis);
    if (code < 0)
	return code;
    /*
     * Note that adev is an internal device, so it will be freed when the
     * last reference to it from a graphics state is deleted.
     */
    adev = gx_pattern_accum_alloc(mem, "gx_pattern_load");
    if (adev == 0)
	return_error(gs_error_VMerror);
    gx_device_set_target((gx_device_forward *)adev, dev);
    adev->instance = pinst;
    adev->bitmap_memory = mem;
    code = dev_proc(adev, open_device)((gx_device *)adev);
    if (code < 0)
	goto fail;
    saved = gs_gstate(pinst->saved);
    if (saved == 0) {
	code = gs_note_error(gs_error_VMerror);
	goto fail;
    }
    if (saved->pattern_cache == 0)
	saved->pattern_cache = pis->pattern_cache;
    gs_setdevice_no_init(saved, (gx_device *)adev);
    code = (*pinst->template.PaintProc)(&pdc->ccolor, saved);
    if (code < 0) {
	dev_proc(adev, close_device)((gx_device *)adev);
	/* Freeing the state will free the device. */
	gs_state_free(saved);
	return code;
    }
    /* We REALLY don't like the following cast.... */
    code = gx_pattern_cache_add_entry((gs_imager_state *)pis, adev, &ctile);
    if (code >= 0) {
	if (!gx_pattern_cache_lookup(pdc, pis, dev, select)) {
	    lprintf("Pattern cache lookup failed after insertion!\n");
	    code = gs_note_error(gs_error_Fatal);
	}
    }
#ifdef DEBUG
    if (gs_debug_c('B')) {
        if (adev->mask)
	    debug_dump_bitmap(adev->mask->base, adev->mask->raster,
			      adev->mask->height, "[B]Pattern mask");
	if (adev->bits)
	    debug_dump_bitmap(((gx_device_memory *) adev->target)->base,
			      ((gx_device_memory *) adev->target)->raster,
			      adev->target->height, "[B]Pattern bits");
    }
#endif
    /* Free the bookkeeping structures, except for the bits and mask */
    /* data iff they are still needed. */
    dev_proc(adev, close_device)((gx_device *)adev);
    /* Freeing the state will free the device. */
    gs_state_free(saved);
    return code;
fail:
    gs_free_object(mem, adev, "gx_pattern_load");
    return code;
}

/* Remap a PatternType 1 color. */
cs_proc_remap_color(gx_remap_Pattern);	/* check the prototype */
int
gs_pattern1_remap_color(const gs_client_color * pc, const gs_color_space * pcs,
			gx_device_color * pdc, const gs_imager_state * pis,
			gx_device * dev, gs_color_select_t select)
{
    gs_pattern1_instance_t *pinst = (gs_pattern1_instance_t *)pc->pattern;
    int code;

    pdc->ccolor = *pc;
    if (pinst == 0) {
	/* Null pattern */
	color_set_null_pattern(pdc);
	return 0;
    }
    if (pinst->template.PaintType == 2) {	/* uncolored */
	code = (*pcs->params.pattern.base_space.type->remap_color)
	    (pc, (const gs_color_space *)&pcs->params.pattern.base_space,
	     pdc, pis, dev, select);
	if (code < 0)
	    return code;
	if (pdc->type == gx_dc_type_pure)
	    pdc->type = &gx_dc_pure_masked;
	else if (pdc->type == gx_dc_type_ht_binary)
	    pdc->type = &gx_dc_binary_masked;
	else if (pdc->type == gx_dc_type_ht_colored)
	    pdc->type = &gx_dc_colored_masked;
	else
	    return_error(gs_error_unregistered);
    } else
	color_set_null_pattern(pdc);
    pdc->mask.id = pinst->id;
    pdc->mask.m_tile = 0;
    return gx_pattern_load(pdc, pis, dev, select);
}
