/* Copyright (C) 1989, 1995, 1996, 1997, 1998, 1999, 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: zimage.c,v 1.5.6.1.2.1 2003/01/17 00:49:06 giles Exp $ */
/* Image operators */
#include "memory_.h"
#include "ghost.h"
#include "oper.h"
#include "estack.h"		/* for image[mask] */
#include "gsstruct.h"
#include "ialloc.h"
#include "igstate.h"
#include "ilevel.h"
#include "store.h"
#include "gscspace.h"
#include "gscssub.h"
#include "gsmatrix.h"
#include "gsimage.h"
#include "gxiparam.h"
#include "stream.h"
#include "ifilter.h"		/* for stream exception handling */
#include "iimage.h"

/* Forward references */
private int image_setup(P5(i_ctx_t *i_ctx_p, os_ptr op, gs_image_t * pim,
			   const gs_color_space * pcs, int npop));
private int image_proc_process(P1(i_ctx_t *));
private int image_file_continue(P1(i_ctx_t *));
private int image_string_continue(P1(i_ctx_t *));
private int image_cleanup(P1(i_ctx_t *));

/* <width> <height> <bits/sample> <matrix> <datasrc> image - */
int
zimage(i_ctx_t *i_ctx_p)
{
    return zimage_opaque_setup(i_ctx_p, osp, false, gs_image_alpha_none,
			       gs_current_DeviceGray_space(igs), 5);
}

/* <width> <height> <paint_1s> <matrix> <datasrc> imagemask - */
int
zimagemask(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    gs_image_t image;

    check_type(op[-2], t_boolean);
    gs_image_t_init_mask_adjust(&image, op[-2].value.boolval,
				gs_incachedevice(igs) != CACHE_DEVICE_NONE);
    return image_setup(i_ctx_p, op, &image, NULL, 5);
}

/* Setup for [color|alpha]image.  This code isn't used for Level 1, */
/* but it's simpler to include it here. */
int
zimage_multiple(i_ctx_t *i_ctx_p, bool has_alpha)
{
    os_ptr op = osp;
    int spp;			/* samples per pixel */
    int npop = 7;
    os_ptr procp = op - 2;
    const gs_color_space *pcs;
    bool multi = false;

    check_int_leu(*op, 4);	/* ncolors */
    check_type(op[-1], t_boolean);	/* multiproc */
    switch ((spp = (int)(op->value.intval))) {
	case 1:
	    pcs = gs_current_DeviceGray_space(igs);
	    break;
	case 3:
	    pcs = gs_current_DeviceRGB_space(igs);
	    goto color;
	case 4:
	    pcs = gs_current_DeviceCMYK_space(igs);
color:
	    if (op[-1].value.boolval) {	/* planar format */
		if (has_alpha)
		    ++spp;
		npop += spp - 1;
		procp -= spp - 1;
		multi = true;
	    }
	    break;
	default:
	    return_error(e_rangecheck);
    }
    return zimage_opaque_setup(i_ctx_p, procp, multi,
		    (has_alpha ? gs_image_alpha_last : gs_image_alpha_none),
			       pcs, npop);
}

/* Common setup for [color|alpha]image. */
/* Fills in format, BitsPerComponent, Alpha. */
int
zimage_opaque_setup(i_ctx_t *i_ctx_p, os_ptr op, bool multi,
		    gs_image_alpha_t alpha, const gs_color_space * pcs,
		    int npop)
{
    gs_image_t image;

    check_int_leu(op[-2], (level2_enabled ? 12 : 8));	/* bits/sample */
    gs_image_t_init(&image, pcs);
    image.BitsPerComponent = (int)op[-2].value.intval;
    image.Alpha = alpha;
    image.format =
	(multi ? gs_image_format_component_planar : gs_image_format_chunky);
    return image_setup(i_ctx_p, op, &image, pcs, npop);
}

/* Common setup for [color|alpha]image and imagemask. */
/* Fills in Width, Height, ImageMatrix, ColorSpace. */
private int
image_setup(i_ctx_t *i_ctx_p, os_ptr op, gs_image_t * pim,
	    const gs_color_space * pcs, int npop)
{
    int code;

    check_type(op[-4], t_integer);	/* width */
    check_type(op[-3], t_integer);	/* height */
    if (op[-4].value.intval < 0 || op[-3].value.intval < 0)
	return_error(e_rangecheck);
    if ((code = read_matrix(op - 1, &pim->ImageMatrix)) < 0)
	return code;
    pim->ColorSpace = pcs;
    pim->Width = (int)op[-4].value.intval;
    pim->Height = (int)op[-3].value.intval;
    return zimage_setup(i_ctx_p, (gs_pixel_image_t *) pim, op,
			pim->ImageMask | pim->CombineWithColor, npop);
}

/* Common setup for all Level 1 and 2 images, and ImageType 4 images. */
int
zimage_setup(i_ctx_t *i_ctx_p, const gs_pixel_image_t * pim,
	     const ref * sources, bool uses_color, int npop)
{
    gx_image_enum_common_t *pie;
    int code =
	gs_image_begin_typed((const gs_image_common_t *)pim, igs,
			     uses_color, &pie);

    if (code < 0)
	return code;
    return zimage_data_setup(i_ctx_p, (const gs_pixel_image_t *)pim, pie,
			     sources, npop);
}

/* Common setup for all Level 1 and 2 images, and ImageType 3 and 4 images. */
/*
 * We push the following on the estack.
 *      control mark,
 *	num_sources,
 *      for I = num_sources-1 ... 0:
 *          data source I,
 *          aliasing information:
 *              if source is not file, 1, except that the topmost value
 *		  is used for bookkeeping in the procedure case (see below);
 *              if file is referenced by a total of M different sources and
 *                this is the occurrence with the lowest I, M;
 *              otherwise, -J, where J is the lowest I of the same file as
 *                this one;
 *      current plane index,
 *      num_sources,
 *      enumeration structure.
 */
#define NUM_PUSH(nsource) ((nsource) * 2 + 5)
/*
 * We can access these values either from the bottom (esp at control mark - 1,
 * EBOT macros) or the top (esp = enumeration structure, ETOP macros).
 * Note that all macros return pointers.
 */
#define EBOT_NUM_SOURCES(ep) ((ep) + 2)
#define EBOT_SOURCE(ep, i)\
  ((ep) + 3 + (EBOT_NUM_SOURCES(ep)->value.intval - 1 - (i)) * 2)
#define ETOP_SOURCE(ep, i)\
  ((ep) - 4 - (i) * 2)
#define ETOP_PLANE_INDEX(ep) ((ep) - 2)
#define ETOP_NUM_SOURCES(ep) ((ep) - 1)
int
zimage_data_setup(i_ctx_t *i_ctx_p, const gs_pixel_image_t * pim,
		  gx_image_enum_common_t * pie, const ref * sources, int npop)
{
    int num_sources = pie->num_planes;
    int inumpush = NUM_PUSH(num_sources);
    int code;
    gs_image_enum *penum;
    int px;
    const ref *pp;

    check_estack(inumpush + 2);	/* stuff above, + continuation + proc */
    make_int(EBOT_NUM_SOURCES(esp), num_sources);
    /*
     * Note that the data sources may be procedures, strings, or (Level
     * 2 only) files.  (The Level 1 reference manual says that Level 1
     * requires procedures, but Adobe Level 1 interpreters also accept
     * strings.)  The sources must all be of the same type.
     *
     * The Adobe documentation explicitly says that if two or more of the
     * data sources are the same or inter-dependent files, the result is not
     * defined.  We don't have a problem with the bookkeeping for
     * inter-dependent files, since each one has its own buffer, but we do
     * have to be careful if two or more sources are actually the same file.
     * That is the reason for the aliasing information described above.
     */
    for (px = 0, pp = sources; px < num_sources; px++, pp++) {
	es_ptr ep = EBOT_SOURCE(esp, px);

	make_int(ep + 1, 1);	/* default is no aliasing */
	switch (r_type(pp)) {
	    case t_file:
		if (!level2_enabled)
		    return_error(e_typecheck);
		/* Check for aliasing. */
		{
		    int pi;

		    for (pi = 0; pi < px; ++pi)
			if (sources[pi].value.pfile == pp->value.pfile) {
			    /* Record aliasing */
			    make_int(ep + 1, -pi);
			    EBOT_SOURCE(esp, pi)[1].value.intval++;
			    break;
			}
		}
		/* falls through */
	    case t_string:
		if (r_type(pp) != r_type(sources))
		    return_error(e_typecheck);
		check_read(*pp);
		break;
	    default:
		if (!r_is_proc(sources))
		    return_error(e_typecheck);
		check_proc(*pp);
	}
	*ep = *pp;
    }
    if ((penum = gs_image_enum_alloc(imemory, "image_setup")) == 0)
	return_error(e_VMerror);
    code = gs_image_enum_init(penum, pie, (const gs_data_image_t *)pim, igs);
    if (code != 0) {		/* error, or empty image */
	gs_image_cleanup(penum);
	ifree_object(penum, "image_setup");
	if (code >= 0)		/* empty image */
	    pop(npop);
	return code;
    }
    push_mark_estack(es_other, image_cleanup);
    esp += inumpush - 1;
    make_int(ETOP_PLANE_INDEX(esp), 0);
    make_int(ETOP_NUM_SOURCES(esp), num_sources);
    make_istruct(esp, 0, penum);
    switch (r_type(sources)) {
	case t_file:
	    push_op_estack(image_file_continue);
	    break;
	case t_string:
	    push_op_estack(image_string_continue);
	    break;
	default:		/* procedure */
	    push_op_estack(image_proc_process);
	    break;
    }
    pop(npop);
    return o_push_estack;
}
/* Pop all the control information off the e-stack. */
private es_ptr
zimage_pop_estack(es_ptr tep)
{
    return tep - NUM_PUSH(ETOP_NUM_SOURCES(tep)->value.intval);
}

/*
 * Continuation for procedure data source.  We use the topmost aliasing slot
 * to remember whether we've just called the procedure (1) or whether we're
 * returning from a RemapColor callout (0).
 */
private int
image_proc_continue(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    gs_image_enum *penum = r_ptr(esp, gs_image_enum);
    int px = ETOP_PLANE_INDEX(esp)->value.intval;
    int num_sources = ETOP_NUM_SOURCES(esp)->value.intval;
    uint size, used[gs_image_max_planes];
    gs_const_string plane_data[gs_image_max_planes];
    const byte *wanted;
    int i, code;

    if (!r_has_type_attrs(op, t_string, a_read)) {
	check_op(1);
	/* Procedure didn't return a (readable) string.  Quit. */
	esp = zimage_pop_estack(esp);
	image_cleanup(i_ctx_p);
	return_error(!r_has_type(op, t_string) ? e_typecheck : e_invalidaccess);
    }
    size = r_size(op);
    if (size == 0 && ETOP_SOURCE(esp, 0)[1].value.intval == 0)
	code = 1;
    else {
	for (i = 0; i < num_sources; i++)
	    plane_data[i].size = 0;
	plane_data[px].data = op->value.bytes;
	plane_data[px].size = size;
	code = gs_image_next_planes(penum, plane_data, used);
	if (code == e_RemapColor) {
	    op->value.bytes += used[px]; /* skip used data */
	    r_dec_size(op, used[px]);
	    ETOP_SOURCE(esp, 0)[1].value.intval = 0; /* RemapColor callout */
	    return code;
	}
    }
    if (code) {			/* Stop now. */
	esp = zimage_pop_estack(esp);
	pop(1);
	image_cleanup(i_ctx_p);
	return (code < 0 ? code : o_pop_estack);
    }
    pop(1);
    wanted = gs_image_planes_wanted(penum);
    do {
	if (++px == num_sources)
	    px = 0;
    } while (!wanted[px]);
    ETOP_PLANE_INDEX(esp)->value.intval = px;
    return image_proc_process(i_ctx_p);
}
private int
image_proc_process(i_ctx_t *i_ctx_p)
{
    int px = ETOP_PLANE_INDEX(esp)->value.intval;
    gs_image_enum *penum = r_ptr(esp, gs_image_enum);
    const byte *wanted = gs_image_planes_wanted(penum);
    int num_sources = ETOP_NUM_SOURCES(esp)->value.intval;
    const ref *pp;

    ETOP_SOURCE(esp, 0)[1].value.intval = 0; /* procedure callout */
    while (!wanted[px]) {
	if (++px == num_sources)
	    px = 0;
	ETOP_PLANE_INDEX(esp)->value.intval = px;
    }
    pp = ETOP_SOURCE(esp, px);
    push_op_estack(image_proc_continue);
    *++esp = *pp;
    return o_push_estack;
}

/* Continue processing data from an image with file data sources. */
private int
image_file_continue(i_ctx_t *i_ctx_p)
{
    gs_image_enum *penum = r_ptr(esp, gs_image_enum);
    int num_sources = ETOP_NUM_SOURCES(esp)->value.intval;

    for (;;) {
	uint min_avail = max_int;
	gs_const_string plane_data[gs_image_max_planes];
	int code;
	int px;
	const ref *pp;
	bool at_eof = false;

	/*
	 * Do a first pass through the files to ensure that at least
	 * one has data available in its buffer.
	 */

	for (px = 0, pp = ETOP_SOURCE(esp, 0); px < num_sources;
	     ++px, pp -= 2
	    ) {
	    int num_aliases = pp[1].value.intval;
	    stream *s = pp->value.pfile;
	    int min_left;
	    uint avail;

	    if (num_aliases <= 0)
		num_aliases = ETOP_SOURCE(esp, -num_aliases)[1].value.intval;
	    while ((avail = sbufavailable(s)) <=
		   (min_left = sbuf_min_left(s)) + num_aliases - 1) {
		int next = s->end_status;

		switch (next) {
		case 0:
		    s_process_read_buf(s);
		    continue;
		case EOFC:
		    at_eof = true;
		    break;	/* with no data available */
		case INTC:
		case CALLC:
		    return
			s_handle_read_exception(i_ctx_p, next, pp,
						NULL, 0, image_file_continue);
		default:
		    /* case ERRC: */
		    return_error(e_ioerror);
		}
		break;		/* for EOFC */
	    }
	    /*
	     * Note that in the EOF case, we can get here with no data
	     * available.
	     */
	    if (avail >= min_left)
		avail = (avail - min_left) / num_aliases; /* may be 0 */
	    if (avail < min_avail)
		min_avail = avail;
	    plane_data[px].data = sbufptr(s);
	    plane_data[px].size = avail;
	}

	/*
	 * Now pass the available buffered data to the image processor.
	 * Even if there is no available data, we must call
	 * gs_image_next_planes one more time to finish processing any
	 * retained data.
	 */

	{
	    int pi;
	    uint used[gs_image_max_planes];

	    code = gs_image_next_planes(penum, plane_data, used);
	    /* Now that used has been set, update the streams. */
	    for (pi = 0, pp = ETOP_SOURCE(esp, 0); pi < num_sources;
		 ++pi, pp -= 2
		 )
		sbufskip(pp->value.pfile, used[pi]);
	    if (code == e_RemapColor)
		return code;
	}
	if (at_eof)
	    code = 1;
	if (code) {
	    esp = zimage_pop_estack(esp);
	    image_cleanup(i_ctx_p);
	    return (code < 0 ? code : o_pop_estack);
	}
    }
}

/* Process data from an image with string data sources. */
/* This may still encounter a RemapColor callback. */
private int
image_string_continue(i_ctx_t *i_ctx_p)
{
    gs_image_enum *penum = r_ptr(esp, gs_image_enum);
    int num_sources = ETOP_NUM_SOURCES(esp)->value.intval;
    gs_const_string sources[gs_image_max_planes];
    uint used[gs_image_max_planes];

    /* Pass no data initially, to find out how much is retained. */
    memset(sources, 0, sizeof(sources[0]) * num_sources);
    for (;;) {
	int px;
	int code = gs_image_next_planes(penum, sources, used);

	if (code == e_RemapColor)
	    return code;
    stop_now:
	if (code) {		/* Stop now. */
	    esp -= NUM_PUSH(num_sources);
	    image_cleanup(i_ctx_p);
	    return (code < 0 ? code : o_pop_estack);
	}
	for (px = 0; px < num_sources; ++px)
	    if (sources[px].size == 0) {
		const ref *psrc = ETOP_SOURCE(esp, px);
		uint size = r_size(psrc);

		if (size == 0) {	    /* empty source */
		    code = 1;
		    goto stop_now;
                }
		sources[px].data = psrc->value.bytes;
		sources[px].size = size;
	    }
    }
}

/* Clean up after enumerating an image */
private int
image_cleanup(i_ctx_t *i_ctx_p)
{
    es_ptr ep_top = esp + NUM_PUSH(EBOT_NUM_SOURCES(esp)->value.intval);
    gs_image_enum *penum = r_ptr(ep_top, gs_image_enum);

    gs_image_cleanup(penum);
    ifree_object(penum, "image_cleanup");
    return 0;
}

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

const op_def zimage_op_defs[] =
{
    {"5image", zimage},
    {"5imagemask", zimagemask},
		/* Internal operators */
    {"1%image_proc_continue", image_proc_continue},
    {"0%image_file_continue", image_file_continue},
    {"0%image_string_continue", image_string_continue},
    op_def_end(0)
};
