/* Copyright (C) 1989, 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: gsht.c,v 1.2.6.1.2.1 2003/01/17 00:49:02 giles Exp $ */
/* setscreen operator for Ghostscript library */
#include "memory_.h"
#include <stdlib.h>		/* for qsort */
#include "gx.h"
#include "gserrors.h"
#include "gsstruct.h"
#include "gsutil.h"		/* for gs_next_ids */
#include "gxarith.h"		/* for igcd */
#include "gzstate.h"
#include "gxdevice.h"		/* for gzht.h */
#include "gzht.h"

/* Forward declarations */
void gx_set_effective_transfer(P1(gs_state *));

/* Structure types */
public_st_ht_order();
private_st_ht_order_component();
public_st_ht_order_comp_element();
public_st_halftone();
public_st_device_halftone();

/* GC procedures */

private
ENUM_PTRS_WITH(ht_order_enum_ptrs, gx_ht_order *porder) return 0;
case 0: ENUM_RETURN((porder->data_memory ? porder->levels : 0));
case 1: ENUM_RETURN((porder->data_memory ? porder->bit_data : 0));
case 2: ENUM_RETURN(porder->cache);
case 3: ENUM_RETURN(porder->transfer);
ENUM_PTRS_END
private
RELOC_PTRS_WITH(ht_order_reloc_ptrs, gx_ht_order *porder)
{
    if (porder->data_memory) {
	RELOC_VAR(porder->levels);
	RELOC_VAR(porder->bit_data);
    }
    RELOC_VAR(porder->cache);
    RELOC_VAR(porder->transfer);
}
RELOC_PTRS_END

private 
ENUM_PTRS_WITH(halftone_enum_ptrs, gs_halftone *hptr) return 0;
case 0:
switch (hptr->type)
{
    case ht_type_spot:
	ENUM_RETURN((hptr->params.spot.transfer == 0 ?
		     hptr->params.spot.transfer_closure.data :
		     0));
    case ht_type_threshold:
	ENUM_RETURN_CONST_STRING_PTR(gs_halftone, params.threshold.thresholds);
    case ht_type_threshold2:
	return ENUM_CONST_BYTESTRING(&hptr->params.threshold2.thresholds);
    case ht_type_client_order:
	ENUM_RETURN(hptr->params.client_order.client_data);
    case ht_type_multiple:
    case ht_type_multiple_colorscreen:
	ENUM_RETURN(hptr->params.multiple.components);
    case ht_type_none:
    case ht_type_screen:
    case ht_type_colorscreen:
	return 0;
}
case 1:
switch (hptr->type) {
    case ht_type_threshold:
	ENUM_RETURN((hptr->params.threshold.transfer == 0 ?
		     hptr->params.threshold.transfer_closure.data :
		     0));
    case ht_type_threshold2:
	ENUM_RETURN(hptr->params.threshold2.transfer_closure.data);
    case ht_type_client_order:
	ENUM_RETURN(hptr->params.client_order.transfer_closure.data);
    default:
	return 0;
}
ENUM_PTRS_END

private RELOC_PTRS_WITH(halftone_reloc_ptrs, gs_halftone *hptr)
{
    switch (hptr->type) {
	case ht_type_spot:
	    if (hptr->params.spot.transfer == 0)
		RELOC_PTR(gs_halftone, params.spot.transfer_closure.data);
	    break;
	case ht_type_threshold:
	    RELOC_CONST_STRING_PTR(gs_halftone, params.threshold.thresholds);
	    if (hptr->params.threshold.transfer == 0)
		RELOC_PTR(gs_halftone, params.threshold.transfer_closure.data);
	    break;
	case ht_type_threshold2:
	    RELOC_CONST_BYTESTRING_VAR(hptr->params.threshold2.thresholds);
	    RELOC_OBJ_VAR(hptr->params.threshold2.transfer_closure.data);
	    break;
	case ht_type_client_order:
	    RELOC_PTR(gs_halftone, params.client_order.client_data);
	    RELOC_PTR(gs_halftone, params.client_order.transfer_closure.data);
	    break;
	case ht_type_multiple:
	case ht_type_multiple_colorscreen:
	    RELOC_PTR(gs_halftone, params.multiple.components);
	    break;
	case ht_type_none:
	case ht_type_screen:
	case ht_type_colorscreen:
	    break;
    }
}
RELOC_PTRS_END

/* setscreen */
int
gs_setscreen(gs_state * pgs, gs_screen_halftone * phsp)
{
    gs_screen_enum senum;
    int code = gx_ht_process_screen(&senum, pgs, phsp,
				    gs_currentaccuratescreens());

    if (code < 0)
	return code;
    return gs_screen_install(&senum);
}

/* currentscreen */
int
gs_currentscreen(const gs_state * pgs, gs_screen_halftone * phsp)
{
    switch (pgs->halftone->type) {
	case ht_type_screen:
	    *phsp = pgs->halftone->params.screen;
	    return 0;
	case ht_type_colorscreen:
	    *phsp = pgs->halftone->params.colorscreen.screens.colored.gray;
	    return 0;
	default:
	    return_error(gs_error_undefined);
    }
}

/* .currentscreenlevels */
int
gs_currentscreenlevels(const gs_state * pgs)
{
    return pgs->dev_ht->order.num_levels;
}

/* .setscreenphase */
int
gx_imager_setscreenphase(gs_imager_state * pis, int x, int y,
			 gs_color_select_t select)
{
    if (select == gs_color_select_all) {
	int i;

	for (i = 0; i < gs_color_select_count; ++i)
	    gx_imager_setscreenphase(pis, x, y, (gs_color_select_t) i);
	return 0;
    } else if (select < 0 || select >= gs_color_select_count)
	return_error(gs_error_rangecheck);
    pis->screen_phase[select].x = x;
    pis->screen_phase[select].y = y;
    return 0;
}
int
gs_setscreenphase(gs_state * pgs, int x, int y, gs_color_select_t select)
{
    int code = gx_imager_setscreenphase((gs_imager_state *) pgs, x, y,
					select);

    /*
     * If we're only setting the source phase, we don't need to do
     * unset_dev_color, because the source phase doesn't affect painting
     * with the current color.
     */
    if (code >= 0 && (select == gs_color_select_texture ||
		      select == gs_color_select_all)
	)
	gx_unset_dev_color(pgs);
    return code;
}

/* .currentscreenphase */
int
gs_currentscreenphase(const gs_state * pgs, gs_int_point * pphase,
		      gs_color_select_t select)
{
    if (select < 0 || select >= gs_color_select_count)
	return_error(gs_error_rangecheck);
    *pphase = pgs->screen_phase[select];
    return 0;
}

/* currenthalftone */
int
gs_currenthalftone(gs_state * pgs, gs_halftone * pht)
{
    *pht = *pgs->halftone;
    return 0;
}

/* ------ Internal routines ------ */

/* Process one screen plane. */
int
gx_ht_process_screen_memory(gs_screen_enum * penum, gs_state * pgs,
		gs_screen_halftone * phsp, bool accurate, gs_memory_t * mem)
{
    gs_point pt;
    int code = gs_screen_init_memory(penum, pgs, phsp, accurate, mem);

    if (code < 0)
	return code;
    while ((code = gs_screen_currentpoint(penum, &pt)) == 0)
	if ((code = gs_screen_next(penum, (*phsp->spot_function) (pt.x, pt.y))) < 0)
	    return code;
    return 0;
}

/*
 * Internal procedure to allocate and initialize either an internally
 * generated or a client-defined halftone order.  For spot halftones,
 * the client is responsible for calling gx_compute_cell_values.
 */
int
gx_ht_alloc_ht_order(gx_ht_order * porder, uint width, uint height,
		     uint num_levels, uint num_bits, uint strip_shift,
		     const gx_ht_order_procs_t *procs, gs_memory_t * mem)
{
    porder->width = width;
    porder->height = height;
    porder->raster = bitmap_raster(width);
    porder->shift = strip_shift;
    porder->orig_height = porder->height;
    porder->orig_shift = porder->shift;
    porder->full_height = ht_order_full_height(porder);
    porder->num_levels = num_levels;
    porder->num_bits = num_bits;
    porder->procs = procs;
    porder->data_memory = mem;
    porder->levels =
	(uint *)gs_alloc_byte_array(mem, porder->num_levels, sizeof(uint),
				    "alloc_ht_order_data(levels)");
    porder->bit_data =
	gs_alloc_byte_array(mem, porder->num_bits,
			    porder->procs->bit_data_elt_size,
			    "alloc_ht_order_data(bit_data)");
    if (porder->levels == 0 || porder->bit_data == 0) {
	gs_free_object(mem, porder->bit_data, "alloc_ht_order_data(bit_data)");
	porder->bit_data = 0;
	gs_free_object(mem, porder->levels, "alloc_ht_order_data(levels)");
	porder->levels = 0;
	return_error(gs_error_VMerror);
    }
    porder->cache = 0;
    porder->transfer = 0;
    return 0;
}

/* Allocate and initialize the contents of a halftone order. */
/* The client must have set the defining values in porder->params. */
int
gx_ht_alloc_order(gx_ht_order * porder, uint width, uint height,
		  uint strip_shift, uint num_levels, gs_memory_t * mem)
{
    gx_ht_order order;
    int code;

    order = *porder;
    gx_compute_cell_values(&order.params);
    code = gx_ht_alloc_ht_order(&order, width, height, num_levels,
				width * height, strip_shift,
				&ht_order_procs_default, mem);
    if (code < 0)
	return code;
    *porder = order;
    return 0;
}

/*
 * Allocate and initialize a threshold order, which may use the short
 * representation.
 */
int
gx_ht_alloc_threshold_order(gx_ht_order * porder, uint width, uint height,
			    uint num_levels, gs_memory_t * mem)
{
    gx_ht_order order;
    uint num_bits = width * height;
    const gx_ht_order_procs_t *procs =
	(num_bits > 2000 && num_bits <= max_ushort ?
	 &ht_order_procs_short : &ht_order_procs_default);
    int code;

    order = *porder;
    gx_compute_cell_values(&order.params);
    code = gx_ht_alloc_ht_order(&order, width, height, num_levels,
				width * height, 0, procs, mem);
    if (code < 0)
	return code;
    *porder = order;
    return 0;
}

/* Allocate and initialize the contents of a client-defined halftone order. */
int
gx_ht_alloc_client_order(gx_ht_order * porder, uint width, uint height,
			 uint num_levels, uint num_bits, gs_memory_t * mem)
{
    gx_ht_order order;
    int code;

    order = *porder;
    order.params.M = width, order.params.N = 0;
    order.params.R = 1;
    order.params.M1 = height, order.params.N1 = 0;
    order.params.R1 = 1;
    gx_compute_cell_values(&order.params);
    code = gx_ht_alloc_ht_order(&order, width, height, num_levels,
				num_bits, 0, &ht_order_procs_default, mem);
    if (code < 0)
	return code;
    *porder = order;
    return 0;
}

/* Compare keys ("masks", actually sample values) for qsort. */
private int
compare_samples(const void *p1, const void *p2)
{
    ht_sample_t m1 = ((const gx_ht_bit *)p1)->mask;
    ht_sample_t m2 = ((const gx_ht_bit *)p2)->mask;

    return (m1 < m2 ? -1 : m1 > m2 ? 1 : 0);
}
/* Sort the halftone order by sample value. */
void
gx_sort_ht_order(gx_ht_bit * recs, uint N)
{
    int i;

    /* Tag each sample with its index, for sorting. */
    for (i = 0; i < N; i++)
	recs[i].offset = i;
    qsort((void *)recs, N, sizeof(*recs), compare_samples);
#ifdef DEBUG
    if (gs_debug_c('H')) {
	uint i;

	dlputs("[H]Sorted samples:\n");
	for (i = 0; i < N; i++)
	    dlprintf3("%5u: %5u: %u\n",
		      i, recs[i].offset, recs[i].mask);
    }
#endif
}

/*
 * Construct the halftone order from a sampled spot function.  Only width x
 * strip samples have been filled in; we must replicate the resulting sorted
 * order vertically, shifting it by shift each time.  See gxdht.h regarding
 * the invariants that must be restored.
 */
void
gx_ht_construct_spot_order(gx_ht_order * porder)
{
    uint width = porder->width;
    uint num_levels = porder->num_levels;	/* = width x strip */
    uint strip = num_levels / width;
    gx_ht_bit *bits = (gx_ht_bit *)porder->bit_data;
    uint *levels = porder->levels;
    uint shift = porder->orig_shift;
    uint full_height = porder->full_height;
    uint num_bits = porder->num_bits;
    uint copies = num_bits / (width * strip);
    gx_ht_bit *bp = bits + num_bits - 1;
    uint i;

    gx_sort_ht_order(bits, num_levels);
    if_debug5('h',
	      "[h]spot order: num_levels=%u w=%u h=%u strip=%u shift=%u\n",
	      num_levels, width, porder->orig_height, strip, shift);
    /* Fill in the levels array, replicating the bits vertically */
    /* if needed. */
    for (i = num_levels; i > 0;) {
	uint offset = bits[--i].offset;
	uint x = offset % width;
	uint hy = offset - x;
	uint k;

	levels[i] = i * copies;
	for (k = 0; k < copies;
	     k++, bp--, hy += num_levels, x = (x + width - shift) % width
	    )
	    bp->offset = hy + x;
    }
    /* If we have a complete halftone, restore the invariant. */
    if (num_bits == width * full_height) {
	porder->height = full_height;
	porder->shift = 0;
    }
    gx_ht_construct_bits(porder);
}

/* Construct a single offset/mask. */
void
gx_ht_construct_bit(gx_ht_bit * bit, int width, int bit_num)
{
    uint padding = bitmap_raster(width) * 8 - width;
    int pix = bit_num;
    ht_mask_t mask;
    byte *pb;

    pix += pix / width * padding;
    bit->offset = (pix >> 3) & -size_of(mask);
    mask = (ht_mask_t) 1 << (~pix & (ht_mask_bits - 1));
    /* Replicate the mask bits. */
    pix = ht_mask_bits - width;
    while ((pix -= width) >= 0)
	mask |= mask >> width;
    /* Store the mask, reversing bytes if necessary. */
    bit->mask = 0;
    for (pb = (byte *) & bit->mask + (sizeof(mask) - 1);
	 mask != 0;
	 mask >>= 8, pb--
	)
	*pb = (byte) mask;
}

/* Construct offset/masks from the whitening order. */
/* porder->bits[i].offset contains the index of the bit position */
/* that is i'th in the whitening order. */
void
gx_ht_construct_bits(gx_ht_order * porder)
{
    uint i;
    gx_ht_bit *phb;

    for (i = 0, phb = (gx_ht_bit *)porder->bit_data;
	 i < porder->num_bits;
	 i++, phb++)
	gx_ht_construct_bit(phb, porder->width, phb->offset);
#ifdef DEBUG
    if (gs_debug_c('H')) {
	dlprintf1("[H]Halftone order bits 0x%lx:\n", (ulong)porder->bit_data);
	for (i = 0, phb = (gx_ht_bit *)porder->bit_data;
	     i < porder->num_bits;
	     i++, phb++)
	    dlprintf3("%4d: %u:0x%lx\n", i, phb->offset,
		      (ulong) phb->mask);
    }
#endif
}

/* Release a gx_device_halftone by freeing its components. */
/* (Don't free the gx_device_halftone itself.) */
void
gx_ht_order_release(gx_ht_order * porder, gs_memory_t * mem, bool free_cache)
{
    if (free_cache && porder->cache)
	gx_ht_free_cache(mem, porder->cache);
    gs_free_object(mem, porder->transfer, "gx_ht_order_release(transfer)");
    if (porder->data_memory) {
	gs_free_object(porder->data_memory, porder->bit_data,
		       "gx_ht_order_release(bit_data)");
	gs_free_object(porder->data_memory, porder->levels,
		       "gx_ht_order_release(levels)");
    }
}
void
gx_device_halftone_release(gx_device_halftone * pdht, gs_memory_t * mem)
{
    if (pdht->components) {
	int i;

	/* One of the components might be the same as the default */
	/* order, so check that we don't free it twice. */
	for (i = 0; i < pdht->num_comp; ++i)
	    if (pdht->components[i].corder.bit_data !=
		pdht->order.bit_data
		) {		/* Currently, all orders except the default one */
		/* own their caches. */
		gx_ht_order_release(&pdht->components[i].corder, mem, true);
	    }
	gs_free_object(mem, pdht->components,
		       "gx_dev_ht_release(components)");
	pdht->components = 0;
	pdht->num_comp = 0;
    }
    gx_ht_order_release(&pdht->order, mem, false);
}

/*
 * Install a device halftone in an imager state.  Note that this does not
 * read or update the client halftone.  There is a special check for pdht ==
 * pis->dev_ht, for the benefit of the band rendering code.
 */
int
gx_imager_dev_ht_install(gs_imager_state * pis,
			 const gx_device_halftone * pdht,
			 gs_halftone_type type, const gx_device * dev)
{
    gx_device_halftone *pgdht = pis->dev_ht;

    if ((ulong) pdht->order.raster *
	(pdht->order.num_bits / pdht->order.width) > pis->ht_cache->bits_size
	)
	return_error(gs_error_limitcheck);
    if (pdht == pgdht)
	DO_NOTHING;		/* special hack for band renderer */
    else if (pgdht != 0 && pgdht->rc.ref_count == 1 &&
	pgdht->rc.memory == pdht->rc.memory
	) {
	/* The current device halftone isn't shared. */
	/* Just release its components. */
	gx_device_halftone_release(pgdht, pgdht->rc.memory);
    } else {			/* The device halftone is shared or not yet allocated. */
	rc_unshare_struct(pis->dev_ht, gx_device_halftone,
			  &st_device_halftone, pdht->rc.memory,
			  return_error(gs_error_VMerror),
			  "gx_imager_dev_ht_install");
	pgdht = pis->dev_ht;
    }
    {
	rc_header rc;

	rc = pgdht->rc;
	*pgdht = *pdht;
	pgdht->rc = rc;
    }
    pgdht->id = gs_next_ids(1);
    pgdht->type = type;
    /* Clear the cache, to avoid confusion in case the address of */
    /* a new order vector matches that of a (deallocated) old one. */
    gx_ht_clear_cache(pis->ht_cache);
    /* Set the color_indices according to the device color_info. */
    /* Also compute the LCM of the primary color cell sizes. */
    /* Note that for strip halftones, the "cell size" is the */
    /* theoretical fully expanded size with shift = 0. */
    if (pdht->components != 0) {
	static const gs_ht_separation_name dcnames[5][4] =
	{
	    {gs_ht_separation_Default},		/* not used */
	    {gs_ht_separation_Default, gs_ht_separation_Default,
	     gs_ht_separation_Default, gs_ht_separation_Gray
	    },
	    {gs_ht_separation_Default},		/* not used */
	    {gs_ht_separation_Red, gs_ht_separation_Green,
	     gs_ht_separation_Blue, gs_ht_separation_Default
	    },
	    {gs_ht_separation_Cyan, gs_ht_separation_Magenta,
	     gs_ht_separation_Yellow, gs_ht_separation_Black
	    }
	};
	static const gs_ht_separation_name cscnames[4] =
	{gs_ht_separation_Red, gs_ht_separation_Green,
	 gs_ht_separation_Blue, gs_ht_separation_Default
	};
	int num_comps = dev->color_info.num_components;
	const gs_ht_separation_name *cnames = dcnames[num_comps];
	int lcm_width = 1, lcm_height = 1;
	uint i;

	/* Halftones set by setcolorscreen, and (we think) */
	/* Type 2 and Type 4 halftones, are supposed to work */
	/* for both RGB and CMYK, so we need a special check here. */
	if (num_comps == 4 &&
	    (type == ht_type_colorscreen ||
	     type == ht_type_multiple_colorscreen)
	    )
	    cnames = cscnames;
	if_debug4('h', "[h]dcnames=%lu,%lu,%lu,%lu\n",
		  (ulong) cnames[0], (ulong) cnames[1],
		  (ulong) cnames[2], (ulong) cnames[3]);
	memset(pgdht->color_indices, 0, sizeof(pdht->color_indices));
	for (i = 0; i < pdht->num_comp; i++) {
	    const gx_ht_order_component *pcomp =
	    &pdht->components[i];
	    int j;

	    if_debug2('h', "[h]cname[%d]=%lu\n",
		      i, (ulong) pcomp->cname);
	    for (j = 0; j < 4; j++) {
		if (pcomp->cname == cnames[j]) {
		    if_debug2('h', "[h]color_indices[%d]=%d\n",
			      j, i);
		    pgdht->color_indices[j] = i;
		}
	    }
	}
	/* Now do a second pass to compute the LCM. */
	/* We have to do it this way in case some entry in */
	/* color_indices is still 0. */
	for (i = 0; i < 4; ++i) {
	    const gx_ht_order_component *pcomp =
	    &pdht->components[pgdht->color_indices[i]];
	    uint cw = pcomp->corder.width;
	    uint ch = pcomp->corder.full_height;
	    int dw = lcm_width / igcd(lcm_width, cw);
	    int dh = lcm_height / igcd(lcm_height, ch);

	    lcm_width = (cw > max_int / dw ? max_int : cw * dw);
	    lcm_height = (ch > max_int / dh ? max_int : ch * dh);
	}
	pgdht->lcm_width = lcm_width;
	pgdht->lcm_height = lcm_height;
    } else {			/* Only one component. */
	pgdht->lcm_width = pgdht->order.width;
	pgdht->lcm_height = pgdht->order.full_height;
    }
    if_debug2('h', "[h]LCM=(%d,%d)\n",
	      pgdht->lcm_width, pgdht->lcm_height);
    gx_imager_set_effective_xfer(pis);
    return 0;
}

/*
 * Install a new halftone in the graphics state.  Note that we copy the top
 * level of the gs_halftone and the gx_device_halftone, and take ownership
 * of any substructures.
 */
int
gx_ht_install(gs_state * pgs, const gs_halftone * pht,
	      const gx_device_halftone * pdht)
{
    gs_memory_t *mem = pht->rc.memory;
    gs_halftone *old_ht = pgs->halftone;
    gs_halftone *new_ht;
    int code;

    if (old_ht != 0 && old_ht->rc.memory == mem &&
	old_ht->rc.ref_count == 1
	)
	new_ht = old_ht;
    else
	rc_alloc_struct_1(new_ht, gs_halftone, &st_halftone,
			  mem, return_error(gs_error_VMerror),
			  "gx_ht_install(new halftone)");
    code = gx_imager_dev_ht_install((gs_imager_state *) pgs,
			     pdht, pht->type, gs_currentdevice_inline(pgs));
    if (code < 0) {
	if (new_ht != old_ht)
	    gs_free_object(mem, new_ht, "gx_ht_install(new halftone)");
	return code;
    }
    if (new_ht != old_ht)
	rc_decrement(old_ht, "gx_ht_install(old halftone)");
    {
	rc_header rc;

	rc = new_ht->rc;
	*new_ht = *pht;
	new_ht->rc = rc;
    }
    pgs->halftone = new_ht;
    gx_unset_dev_color(pgs);
    return 0;
}

/* Reestablish the effective transfer functions, taking into account */
/* any overrides from halftone dictionaries. */
void
gx_imager_set_effective_xfer(gs_imager_state * pis)
{
    const gx_device_halftone *pdht = pis->dev_ht;

    pis->effective_transfer = pis->set_transfer;	/* default */
    if (pdht == 0)
	return;			/* not initialized yet */
    if (pdht->components == 0) {	/* Check for transfer function override in single halftone */
	gx_transfer_map *pmap = pdht->order.transfer;

	if (pmap != 0)
	    pis->effective_transfer.indexed[0] =
		pis->effective_transfer.indexed[1] =
		pis->effective_transfer.indexed[2] =
		pis->effective_transfer.indexed[3] = pmap;
    } else {			/* Check in all 4 standard separations */
	int i;

	for (i = 0; i < 4; ++i) {
	    gx_transfer_map *pmap =
	    pdht->components[pdht->color_indices[i]].corder.
	    transfer;

	    if (pmap != 0)
		pis->effective_transfer.indexed[i] = pmap;
	}
    }
}
void
gx_set_effective_transfer(gs_state * pgs)
{
    gx_imager_set_effective_xfer((gs_imager_state *) pgs);
}
