/* Copyright (C) 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: gshtx.c,v 1.2.6.1.2.1 2003/01/17 00:49:02 giles Exp $ */ /* Stand-alone halftone/transfer function related code */ #include "memory_.h" #include "gx.h" #include "gserrors.h" #include "gsstruct.h" #include "gsutil.h" /* for gs_next_ids */ #include "gxfmap.h" #include "gzstate.h" #include "gzht.h" #include "gshtx.h" /* must come after g*ht.h */ /* * Procedure to free the set of components when a halftone is released. */ private void free_comps( gs_memory_t * pmem, void *pvht, client_name_t cname ) { gs_ht *pht = (gs_ht *) pvht; gs_free_object(pmem, pht->params.ht_multiple.components, cname); gs_free_object(pmem, pvht, cname); } /* * Stub transfer function, to be applied to components that are not provided * with a transfer function. */ private float null_closure_transfer( floatp val, const gx_transfer_map * pmap_dummy, /* NOTUSED */ const void *dummy /* NOTUSED */ ) { return val; } /* * Build a gs_ht halftone structure. */ int gs_ht_build( gs_ht ** ppht, uint num_comps, gs_memory_t * pmem ) { gs_ht *pht; gs_ht_component *phtc; int i; /* must have at least one component */ *ppht = 0; if (num_comps == 0) return_error(gs_error_rangecheck); /* allocate the halftone and the array of components */ rc_alloc_struct_1(pht, gs_ht, &st_gs_ht, pmem, return_error(gs_error_VMerror), "gs_ht_build" ); phtc = gs_alloc_struct_array(pmem, num_comps, gs_ht_component, &st_ht_comp_element, "gs_ht_build" ); if (phtc == 0) { gs_free_object(pmem, pht, "gs_ht_build"); return_error(gs_error_VMerror); } /* initialize the halftone */ pht->type = ht_type_multiple; pht->rc.free = free_comps; pht->params.ht_multiple.components = phtc; pht->params.ht_multiple.num_comp = num_comps; for (i = 0; i < num_comps; i++) { phtc[i].cname = gs_ht_separation_Default; phtc[i].type = ht_type_none; } *ppht = pht; return 0; } /* * Set a spot-function halftone component in a gs_ht halftone. */ int gs_ht_set_spot_comp( gs_ht * pht, int comp, gs_ht_separation_name sepname, floatp freq, floatp angle, float (*spot_func) (floatp, floatp), bool accurate, gs_ht_transfer_proc transfer, const void *client_data ) { gs_ht_component *phtc = &(pht->params.ht_multiple.components[comp]); if (comp >= pht->params.ht_multiple.num_comp) return_error(gs_error_rangecheck); if (phtc->type != ht_type_none) return_error(gs_error_invalidaccess); phtc->cname = (pht->params.ht_multiple.num_comp == 1 ? gs_ht_separation_Default : sepname); phtc->type = ht_type_spot; phtc->params.ht_spot.screen.frequency = freq; phtc->params.ht_spot.screen.angle = angle; phtc->params.ht_spot.screen.spot_function = spot_func; phtc->params.ht_spot.accurate_screens = accurate; phtc->params.ht_spot.transfer = gs_mapped_transfer; phtc->params.ht_spot.transfer_closure.proc = (transfer == 0 ? null_closure_transfer : transfer); phtc->params.ht_spot.transfer_closure.data = client_data; return 0; } /* * Set a threshold halftone component in a gs_ht halftone. Note that the * caller is responsible for releasing the threshold data. */ int gs_ht_set_threshold_comp( gs_ht * pht, int comp, gs_ht_separation_name sepname, int width, int height, const gs_const_string * thresholds, gs_ht_transfer_proc transfer, const void *client_data ) { gs_ht_component *phtc = &(pht->params.ht_multiple.components[comp]); if (comp >= pht->params.ht_multiple.num_comp) return_error(gs_error_rangecheck); if (phtc->type != ht_type_none) return_error(gs_error_invalidaccess); phtc->cname = (pht->params.ht_multiple.num_comp == 1 ? gs_ht_separation_Default : sepname); phtc->type = ht_type_threshold; phtc->params.ht_threshold.width = width; phtc->params.ht_threshold.height = height; phtc->params.ht_threshold.thresholds = *thresholds; phtc->params.ht_threshold.transfer = gs_mapped_transfer; phtc->params.ht_threshold.transfer_closure.proc = (transfer == 0 ? null_closure_transfer : transfer); phtc->params.ht_threshold.transfer_closure.data = client_data; return 0; } /* * Increase the reference count of a gs_ht structure by 1. */ void gs_ht_reference( gs_ht * pht ) { rc_increment(pht); } /* * Decrement the reference count of a gs_ht structure by 1. Free the * structure if the reference count reaches 0. */ void gs_ht_release( gs_ht * pht ) { rc_decrement_only(pht, "gs_ht_release"); } /* * Verify that a gs_ht halftone is legitimate. */ private int check_ht( gs_ht * pht ) { int i; bool have_default = false; int num_comps = pht->params.ht_multiple.num_comp; if (pht->type != ht_type_multiple) return_error(gs_error_unregistered); for (i = 0; i < num_comps; i++) { gs_ht_component *phtc = &(pht->params.ht_multiple.components[i]); if ((phtc->type != ht_type_spot) && (phtc->type != ht_type_threshold)) return_error(gs_error_unregistered); if (phtc->cname == gs_ht_separation_Default) { if (have_default) return_error(gs_error_rangecheck); else have_default = true; } } return have_default ? 0 : gs_error_rangecheck; } /* * Load a transfer map from a gs_ht_transfer_proc function. */ private void build_transfer_map( gs_ht_component * phtc, gx_transfer_map * pmap ) { gs_ht_transfer_proc proc; const void *client_info; int i; frac *values = pmap->values; if (phtc->type == ht_type_spot) { proc = phtc->params.ht_spot.transfer_closure.proc; client_info = phtc->params.ht_spot.transfer_closure.data; } else { proc = phtc->params.ht_threshold.transfer_closure.proc; client_info = phtc->params.ht_threshold.transfer_closure.data; } for (i = 0; i < transfer_map_size; i++) { float fval = proc(i * (1 / (double)(transfer_map_size - 1)), pmap, client_info); values[i] = (fval <= 0.0 ? frac_0 : fval >= 1.0 ? frac_1 : float2frac(fval)); } } /* * Allocate the order and transfer maps required by a halftone, and perform * some elementary initialization. This will also build the component index * to order index map. */ private gx_ht_order_component * alloc_ht_order( const gs_ht * pht, gs_memory_t * pmem, byte * comp2order ) { int num_comps = pht->params.ht_multiple.num_comp; gx_ht_order_component *pocs = gs_alloc_struct_array( pmem, pht->params.ht_multiple.num_comp, gx_ht_order_component, &st_ht_order_component_element, "alloc_ht_order" ); int inext = 1; int i; if (pocs == 0) return 0; pocs->corder.transfer = 0; for (i = 0; i < num_comps; i++) { gs_ht_component *phtc = &(pht->params.ht_multiple.components[i]); gx_transfer_map *pmap = gs_alloc_struct(pmem, gx_transfer_map, &st_transfer_map, "alloc_ht_order" ); if (pmap == 0) { int j; for (j = 0; j < inext; j++) gs_free_object(pmem, pocs[j].corder.transfer, "alloc_ht_order"); gs_free_object(pmem, pocs, "alloc_ht_order"); return 0; } pmap->proc = gs_mapped_transfer; pmap->id = gs_next_ids(1); if (phtc->cname == gs_ht_separation_Default) { pocs->corder.levels = 0; pocs->corder.bit_data = 0; pocs->corder.cache = 0; pocs->corder.transfer = pmap; pocs->cname = gs_ht_separation_Default; comp2order[i] = 0; } else { pocs[inext].corder.levels = 0; pocs[inext].corder.bit_data = 0; pocs[inext].corder.cache = 0; pocs[inext].corder.transfer = pmap; pocs[inext].cname = phtc->cname; comp2order[i] = inext++; } } return pocs; } /* * Build the halftone order for one component. */ private int build_component( gs_ht_component * phtc, gx_ht_order * porder, gs_state * pgs, gs_memory_t * pmem ) { if (phtc->type == ht_type_spot) { gs_screen_enum senum; int code; code = gx_ht_process_screen_memory(&senum, pgs, &phtc->params.ht_spot.screen, phtc->params.ht_spot.accurate_screens, pmem ); if (code < 0) return code; /* avoid wiping out the transfer structure pointer */ senum.order.transfer = porder->transfer; *porder = senum.order; } else { /* ht_type_threshold */ int code; gx_transfer_map *transfer = porder->transfer; porder->params.M = phtc->params.ht_threshold.width; porder->params.N = 0; porder->params.R = 1; porder->params.M1 = phtc->params.ht_threshold.height; porder->params.N1 = 0; porder->params.R1 = 1; code = gx_ht_alloc_threshold_order(porder, phtc->params.ht_threshold.width, phtc->params.ht_threshold.height, 256, pmem ); if (code < 0) return code; gx_ht_construct_threshold_order( porder, phtc->params.ht_threshold.thresholds.data ); /* * gx_ht_construct_threshold_order wipes out transfer map pointer, * restore it here. */ porder->transfer = transfer; } build_transfer_map(phtc, porder->transfer); return 0; } /* * Free an order array and all elements it points to. */ private void free_order_array( gx_ht_order_component * pocs, int num_comps, gs_memory_t * pmem ) { int i; for (i = 0; i < num_comps; i++) gx_ht_order_release(&(pocs[i].corder), pmem, true); gs_free_object(pmem, pocs, "gs_ht_install"); } /* * Install a gs_ht halftone as the current halftone in the graphic state. */ int gs_ht_install( gs_state * pgs, gs_ht * pht ) { int code = 0; gs_memory_t *pmem = pht->rc.memory; gx_device_halftone dev_ht; gx_ht_order_component *pocs; byte comp2order[32]; /* ample component to order map */ int num_comps = pht->params.ht_multiple.num_comp; int i; /* perform so sanity checks (must have one default component) */ if ((code = check_ht(pht)) != 0) return code; /* allocate the halftone order structure and transfer maps */ if ((pocs = alloc_ht_order(pht, pmem, comp2order)) == 0) return_error(gs_error_VMerror); /* build all of the order for each component */ for (i = 0; i < num_comps; i++) { int j = comp2order[i]; code = build_component(&(pht->params.ht_multiple.components[i]), &(pocs[j].corder), pgs, pmem ); if ((code >= 0) && (j != 0)) { gx_ht_cache *pcache; pcache = gx_ht_alloc_cache(pmem, 4, pocs[j].corder.raster * (pocs[j].corder.num_bits / pocs[j].corder.width) * 4 ); if (pcache == 0) code = gs_note_error(gs_error_VMerror); else { pocs[j].corder.cache = pcache; gx_ht_init_cache(pcache, &(pocs[j].corder)); } } if (code < 0) break; } if (code < 0) { free_order_array(pocs, num_comps, pmem); return code; } /* initialize the device halftone structure */ dev_ht.rc.memory = pmem; dev_ht.order = pocs[0].corder; /* Default */ if (num_comps == 1) { /* we have only a Default; we don't need components. */ gs_free_object(pmem, pocs, "gs_ht_install"); dev_ht.components = 0; } else { dev_ht.components = pocs; dev_ht.num_comp = num_comps; } /* at last, actually install the halftone in the graphic state */ return gx_ht_install(pgs, (gs_halftone *) pht, &dev_ht); } /* ---------------- Mask-defined halftones ---------------- */ /* * Create a halftone order from an array of explicit masks. This is * silly, because the rendering machinery actually wants masks, but doing * it right seems to require too many changes in existing code. */ private int create_mask_bits(const byte * mask1, const byte * mask2, int width, int height, gx_ht_bit * bits) { /* * We do this with the slowest, simplest possible algorithm.... */ int width_bytes = (width + 7) >> 3; int x, y; int count = 0; for (y = 0; y < height; ++y) for (x = 0; x < width; ++x) { int offset = y * width_bytes + (x >> 3); byte bit_mask = 0x80 >> (x & 7); if ((mask1[offset] ^ mask2[offset]) & bit_mask) { if (bits) gx_ht_construct_bit(&bits[count], width, y * width + x); ++count; } } return count; } private int create_mask_order(gx_ht_order * porder, gs_state * pgs, const gs_client_order_halftone * phcop, gs_memory_t * mem) { int width_bytes = (phcop->width + 7) >> 3; const byte *masks = (const byte *)phcop->client_data; int bytes_per_mask = width_bytes * phcop->height; const byte *prev_mask; int num_levels = phcop->num_levels; int num_bits = 0; int i; int code; /* Do a first pass to compute how many bits entries will be needed. */ for (prev_mask = masks, num_bits = 0, i = 0; i < num_levels - 1; ++i, prev_mask += bytes_per_mask ) num_bits += create_mask_bits(prev_mask, prev_mask + bytes_per_mask, phcop->width, phcop->height, NULL); code = gx_ht_alloc_client_order(porder, phcop->width, phcop->height, num_levels, num_bits, mem); if (code < 0) return code; /* Fill in the bits and levels entries. */ for (prev_mask = masks, num_bits = 0, i = 0; i < num_levels - 1; ++i, prev_mask += bytes_per_mask ) { porder->levels[i] = num_bits; num_bits += create_mask_bits(prev_mask, prev_mask + bytes_per_mask, phcop->width, phcop->height, ((gx_ht_bit *)porder->bit_data) + num_bits); } porder->levels[num_levels - 1] = num_bits; return 0; } /* Define the client-order halftone procedure structure. */ private const gs_client_order_ht_procs_t mask_order_procs = { create_mask_order }; /* * Define a halftone by an explicit set of masks. We translate these * internally into a threshold array, since that's what the halftone * rendering machinery knows how to deal with. */ int gs_ht_set_mask_comp(gs_ht * pht, int component_index, gs_ht_separation_name sepr_name, int width, int height, int num_levels, const byte * masks, /* width x height x num_levels bits */ gs_ht_transfer_proc transfer, const void *client_data) { gs_ht_component *phtc = &(pht->params.ht_multiple.components[component_index]); if (component_index >= pht->params.ht_multiple.num_comp) return_error(gs_error_rangecheck); if (phtc->type != ht_type_none) return_error(gs_error_invalidaccess); phtc->cname = (pht->params.ht_multiple.num_comp == 1 ? gs_ht_separation_Default : sepr_name); phtc->type = ht_type_client_order; phtc->params.client_order.width = width; phtc->params.client_order.height = height; phtc->params.client_order.num_levels = num_levels; phtc->params.client_order.procs = &mask_order_procs; phtc->params.client_order.client_data = masks; phtc->params.client_order.transfer_closure.proc = (transfer == 0 ? null_closure_transfer : transfer); phtc->params.client_order.transfer_closure.data = client_data; return 0; }