/* Copyright (C) 1989, 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: zcolor.c,v 1.4.6.1.2.1 2003/01/17 00:49:05 giles Exp $ */
/* Color operators */
#include "ghost.h"
#include "oper.h"
#include "estack.h"
#include "ialloc.h"
#include "igstate.h"
#include "iutil.h"
#include "store.h"
#include "gxfixed.h"
#include "gxmatrix.h"
#include "gzstate.h"
#include "gxdevice.h"
#include "gxcmap.h"
#include "icolor.h"

/* Imported from gsht.c */
void gx_set_effective_transfer(P1(gs_state *));

/* Define the number of stack slots needed for zcolor_remap_one. */
const int zcolor_remap_one_ostack = 4;
const int zcolor_remap_one_estack = 3;

/* - currentgray <gray> */
private int
zcurrentgray(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    float gray;
    int code = gs_currentgray(igs, &gray);

    if (code < 0)
	return code;
    push(1);
    make_real(op, gray);
    return 0;
}

/* - currentrgbcolor <red> <green> <blue> */
private int
zcurrentrgbcolor(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    float par[3];
    int code = gs_currentrgbcolor(igs, par);

    if (code < 0)
	return code;
    push(3);
    make_floats(op - 2, par, 3);
    return 0;
}

/* - currenttransfer <proc> */
private int
zcurrenttransfer(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;

    push(1);
    *op = istate->transfer_procs.colored.gray;
    return 0;
}

/* - processcolors <int> - */
/* Note: this is an undocumented operator that is not supported */
/* in Level 2. */
private int
zprocesscolors(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;

    push(1);
    make_int(op, gs_currentdevice(igs)->color_info.num_components);
    return 0;
}

/* <gray> setgray - */
private int
zsetgray(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    double gray;
    int code;

    if (real_param(op, &gray) < 0)
	return_op_typecheck(op);
    if ((code = gs_setgray(igs, gray)) < 0)
	return code;
    make_null(&istate->colorspace.array);
    pop(1);
    return 0;
}

/* <red> <green> <blue> setrgbcolor - */
private int
zsetrgbcolor(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    double par[3];
    int code;

    if ((code = num_params(op, 3, par)) < 0 ||
	(code = gs_setrgbcolor(igs, par[0], par[1], par[2])) < 0
	)
	return code;
    make_null(&istate->colorspace.array);
    pop(3);
    return 0;
}

/* <proc> settransfer - */
private int
zsettransfer(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    int code;

    check_proc(*op);
    check_ostack(zcolor_remap_one_ostack - 1);
    check_estack(1 + zcolor_remap_one_estack);
    istate->transfer_procs.colored.red =
	istate->transfer_procs.colored.green =
	istate->transfer_procs.colored.blue =
	istate->transfer_procs.colored.gray = *op;
    code = gs_settransfer_remap(igs, gs_mapped_transfer, false);
    if (code < 0)
	return code;
    push_op_estack(zcolor_reset_transfer);
    pop(1);
    return zcolor_remap_one(i_ctx_p, &istate->transfer_procs.colored.gray,
			    igs->set_transfer.colored.gray, igs,
			    zcolor_remap_one_finish);
}

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

/* Prepare to remap one color component */
/* (also used for black generation and undercolor removal). */
/* Use the 'for' operator to gather the values. */
/* The caller must have done the necessary check_ostack and check_estack. */
int
zcolor_remap_one(i_ctx_t *i_ctx_p, const ref * pproc,
		 gx_transfer_map * pmap, const gs_state * pgs,
		 op_proc_t finish_proc)
{
    os_ptr op;

    /*
     * Detect the identity function, which is a common value for one or
     * more of these functions.
     */
    if (r_size(pproc) == 0) {
	gx_set_identity_transfer(pmap);
	/*
	 * Even though we don't actually push anything on the e-stack, all
	 * clients do, so we return o_push_estack in this case.  This is
	 * needed so that clients' finishing procedures will get run.
	 */
	return o_push_estack;
    }
    op = osp += 4;
    make_int(op - 3, 0);
    make_int(op - 2, 1);
    make_int(op - 1, transfer_map_size - 1);
    *op = *pproc;
    ++esp;
    make_struct(esp, imemory_space((gs_ref_memory_t *) pgs->memory),
		pmap);
    push_op_estack(finish_proc);
    push_op_estack(zfor_fraction);
    return o_push_estack;
}

/* Store the result of remapping a component. */
private int
zcolor_remap_one_store(i_ctx_t *i_ctx_p, floatp min_value)
{
    int i;
    gx_transfer_map *pmap = r_ptr(esp, gx_transfer_map);

    if (ref_stack_count(&o_stack) < transfer_map_size)
	return_error(e_stackunderflow);
    for (i = 0; i < transfer_map_size; i++) {
	double v;
	int code =
	    real_param(ref_stack_index(&o_stack, transfer_map_size - 1 - i),
		       &v);

	if (code < 0)
	    return code;
	pmap->values[i] =
	    (v < min_value ? float2frac(min_value) :
	     v >= 1.0 ? frac_1 :
	     float2frac(v));
    }
    ref_stack_pop(&o_stack, transfer_map_size);
    esp--;			/* pop pointer to transfer map */
    return o_pop_estack;
}
int
zcolor_remap_one_finish(i_ctx_t *i_ctx_p)
{
    return zcolor_remap_one_store(i_ctx_p, 0.0);
}
int
zcolor_remap_one_signed_finish(i_ctx_t *i_ctx_p)
{
    return zcolor_remap_one_store(i_ctx_p, -1.0);
}

/* Finally, reset the effective transfer functions and */
/* invalidate the current color. */
int
zcolor_reset_transfer(i_ctx_t *i_ctx_p)
{
    gx_set_effective_transfer(igs);
    return zcolor_remap_color(i_ctx_p);
}
int
zcolor_remap_color(i_ctx_t *i_ctx_p)
{
    gx_unset_dev_color(igs);
    return 0;
}

/* ------ Initialization procedure ------ */

const op_def zcolor_op_defs[] =
{
    {"0currentgray", zcurrentgray},
    {"0currentrgbcolor", zcurrentrgbcolor},
    {"0currenttransfer", zcurrenttransfer},
    {"0processcolors", zprocesscolors},
    {"1setgray", zsetgray},
    {"3setrgbcolor", zsetrgbcolor},
    {"1settransfer", zsettransfer},
		/* Internal operators */
    {"1%zcolor_remap_one_finish", zcolor_remap_one_finish},
    {"1%zcolor_remap_one_signed_finish", zcolor_remap_one_signed_finish},
    {"0%zcolor_reset_transfer", zcolor_reset_transfer},
    {"0%zcolor_remap_color", zcolor_remap_color},
    op_def_end(0)
};
