/* Copyright (C) 1993, 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: gxpcmap.c,v 1.3.6.1.2.1 2003/01/17 00:49:04 giles 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 "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, NULL, NULL, gx_default_text_begin, gx_default_finish_copydevice }, 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; 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(P3(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) (P2(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); }