/* Copyright (C) 1989, 1995 Aladdin Enterprises.  All rights reserved.
  
  This file is part of Aladdin Ghostscript.
  
  Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
  or distributor accepts any responsibility for the consequences of using it,
  or for whether it serves any particular purpose or works at all, unless he
  or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
  License (the "License") for full details.
  
  Every copy of Aladdin Ghostscript must include a copy of the License,
  normally in a plain ASCII text file named PUBLIC.  The License grants you
  the right to copy, modify and redistribute Aladdin Ghostscript, but only
  under certain conditions described in the License.  Among other things, the
  License requires that the copyright notice and this notice be preserved on
  all copies.
*/

/* zpaint.c */
/* Painting operators */
#include "ghost.h"
#include "errors.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 "gsmatrix.h"
#include "gsimage.h"
#include "gspaint.h"
#include "stream.h"
#include "ifilter.h"		/* for stream exception handling */

/* Forward references */
/* zimage_setup is used by zimage2.c */
int zimage_setup(P3(const gs_image_t *pim, ref *sources, int npop));
/* zimage_opaque_setup is used by zcolor1.c */
int zimage_opaque_setup(P4(os_ptr, bool, const gs_color_space_type _ds *, int));
private int image_setup(P4(gs_image_t *pim, os_ptr op, const gs_color_space_type _ds *pcst, int npop));
private int image_proc_continue(P1(os_ptr));
private int image_file_continue(P1(os_ptr));
private int image_string_process(P3(os_ptr, gs_image_enum *, int));
private int image_cleanup(P1(os_ptr));

/* - fill - */
private int
zfill(register os_ptr op)
{	return gs_fill(igs);
}

/* - .fillpage - */
private int
zfillpage(register os_ptr op)
{	return gs_fillpage(igs);
}

/* - eofill - */
private int
zeofill(register os_ptr op)
{	return gs_eofill(igs);
}

/* - stroke - */
private int
zstroke(register os_ptr op)
{	return gs_stroke(igs);
}

/* <width> <height> <bits/sample> <matrix> <datasrc> image - */
int
zimage(register os_ptr op)
{	return zimage_opaque_setup(op, false, &gs_color_space_type_DeviceGray, 5);
}

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

	check_type(op[-2], t_boolean);
	if ( op[-2].value.boolval )
	  image = gs_image_mask_inverted_default;
	else
	  image = gs_image_mask_default;
	return image_setup(&image, op, NULL, 5);
}

/* Common setup for image and colorimage. */
/* Fills in MultipleDataSources, BitsPerComponent. */
int
zimage_opaque_setup(register os_ptr op, bool multi,
  const gs_color_space_type _ds *pcst, int npop)
{	gs_image_t image;

	check_int_leu(op[-2], (level2_enabled ? 12 : 8));  /* bits/sample */
	image = gs_image_default;
	image.MultipleDataSources = multi;
	image.BitsPerComponent = (int)op[-2].value.intval;
	return image_setup(&image, op, pcst, npop);
}

/* Common setup for [color]image and imagemask. */
/* Fills in Width, Height, ImageMatrix, ColorSpace. */
private int
image_setup(gs_image_t *pim, register os_ptr op,
  const gs_color_space_type _ds *pcst, int npop)
{	int code;
	gs_color_space cs;

	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;
	if ( pcst != NULL )
	  { cs.type = pcst;
	    pim->ColorSpace = &cs;
	  }
	pim->Width = (int)op[-4].value.intval;
	pim->Height = (int)op[-3].value.intval;
	return zimage_setup(pim, op, npop);
}

/* Common setup for Level 1 image/imagemask/colorimage and */
/* the Level 2 dictionary form of image/imagemask. */
int
zimage_setup(const gs_image_t *pim, ref *sources, int npop)
{	int code;
	gs_image_enum *penum;
	int px;
	ref *pp;
	int num_sources =
	  (pim->MultipleDataSources ?
	   pim->ColorSpace->type->num_components : 1);

	/* We push on the estack: */
	/*	Control mark, 4 procs, last plane index, */
	/*	enumeration structure. */
#define inumpush 7
	check_estack(inumpush + 2);	/* stuff above, + continuation + proc */
	/* Note that the "procedures" might not be procedures, */
	/* but might be strings or files (Level 2 only). */
	/* (The Level 1 reference manual says that Level 1 requires */
	/* procedures, but Adobe Level 1 interpreters also accept strings.) */
	for ( px = 0, pp = sources; px < num_sources; px++, pp++ )
	{	switch ( r_type(pp) )
		{
		case t_file:
			if ( !level2_enabled )
			  return_error(e_typecheck);
			/* 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);
		}
	}
	if ( (penum = gs_image_enum_alloc(imemory, "image_setup")) == 0 )
	  return_error(e_VMerror);
	code = gs_image_init(penum, pim, igs);
	if ( code != 0 )	/* error, or empty image */
	{	ifree_object(penum, "image_setup");
		if ( code >= 0 )	/* empty image */
		  pop(npop);
		return code;
	}
	push_mark_estack(es_other, image_cleanup);
	++esp;
	for ( px = 0, pp = sources; px < 4; esp++, px++, pp++ )
	  if ( px < num_sources )
	    *esp = *pp;
	  else
	    make_null(esp);
	make_int(esp, 0);		/* current plane */
	++esp;
	make_istruct(esp, 0, penum);
	pop(npop);
	switch ( r_type(sources) )
	  {
	  case t_file:
		push_op_estack(image_file_continue);
		break;
	  case t_string:
		return image_string_process(osp, penum, num_sources);
	  default:			/* procedure */
		push_op_estack(image_proc_continue);
		*++esp = sources[0];
		break;
	  }
	return o_push_estack;
}
/* Continuation for procedure data source. */
private int
image_proc_continue(register os_ptr op)
{	gs_image_enum *penum = r_ptr(esp, gs_image_enum);
	uint size, used;
	int code;
	int px;
	const ref *pproc;

	if ( !r_has_type_attrs(op, t_string, a_read) )
	{	check_op(1);
		/* Procedure didn't return a (readable) string.  Quit. */
		esp -= inumpush;
		image_cleanup(op);
		return_error(!r_has_type(op, t_string) ? e_typecheck : e_invalidaccess);
	}
	size = r_size(op);
	if ( size == 0 )
	  code = 1;
	else
	  code = gs_image_next(penum, op->value.bytes, size, &used);
	if ( code )
	  {	/* Stop now. */
		esp -= inumpush;
		pop(1);  op = osp;
		image_cleanup(op);
		return (code < 0 ? code : o_pop_estack);
	  }
	pop(1);
	px = (int)++(esp[-1].value.intval);
	pproc = esp - 5;
	if ( px == 4 || r_has_type(pproc + px, t_null) )
	  esp[-1].value.intval = px = 0;
	push_op_estack(image_proc_continue);
	*++esp = pproc[px];
	return o_push_estack;
}
/* Continue processing data from an image with file data sources. */
private int
image_file_continue(os_ptr op)
{	gs_image_enum *penum = r_ptr(esp, gs_image_enum);
	uint size = max_uint;
	int code;
	int px, num_sources;
	const ref *pproc = esp - 5;

top:

	/* Do a first pass through the files to ensure that */
	/* they all have data available in their buffers, */
	/* and compute the min of the available amounts. */

	for ( px = 0; px < 4 && !r_has_type(pproc + px, t_null); ++px )
	  {	const ref *psref = pproc + px;
		stream *s = psref->value.pfile;
		uint avail;

		while ( (avail = sbufavailable(s)) == 0 )
		{	int next = sgetc(s);

			if ( next >= 0 )
			{	sputback(s);
				continue;
			}
			switch ( next )
			{
			case EOFC:
				break;		/* with avail = 0 */
			case INTC:
			case CALLC:
				return s_handle_read_exception(next, psref,
						NULL, 0, image_file_continue);
			default:
			/* case ERRC: */
				return_error(e_ioerror);
			}
			break;			/* for EOFC */
		}
		if ( avail < size )
		  size = avail;
	}
	num_sources = px;

	/* Now pass the min of the available buffered data to */
	/* the image processor. */

	if ( size == 0 )
	  code = 1;
	else
	  for ( px = 0, code = 0; px < num_sources && !code; ++px )
	    {	stream *s = pproc[px].value.pfile;
		uint used;

		code = gs_image_next(penum, sbufptr(s), size, &used);
		sskip(s, used);
	    }
	if ( code )
	{	esp -= inumpush;
		image_cleanup(op);
		return (code < 0 ? code : o_pop_estack);
	}

	/* Start over again with plane 0. */

	goto top;
}
/* Process data from an image with string data sources. */
/* This never requires callbacks, so it's simpler. */
private int
image_string_process(os_ptr op, gs_image_enum *penum, int num_sources)
{	int px = 0;

	for ( ; ; )
	  {	const ref *psrc = esp - 5 + px;
		uint size = r_size(psrc);
		uint used;
		int code;

		if ( size == 0 )
		  code = 1;
		else
		  code = gs_image_next(penum, psrc->value.bytes, size, &used);
		if ( code )
		  {	/* Stop now. */
			esp -= inumpush;
			image_cleanup(op);
			return (code < 0 ? code : o_pop_estack);
		  }
		if ( ++px == num_sources )
		  px = 0;
	  }
}
/* Clean up after enumerating an image */
private int
image_cleanup(os_ptr op)
{	gs_image_enum *penum = r_ptr(esp + inumpush, gs_image_enum);
	gs_image_cleanup(penum);
	ifree_object(penum, "image_cleanup");
	return 0;
}

/* ------ Non-standard operators ------ */

/* <width> <height> <data> .imagepath - */
private int
zimagepath(register os_ptr op)
{	int code;
	check_type(op[-2], t_integer);
	check_type(op[-1], t_integer);
	check_read_type(*op, t_string);
	if ( r_size(op) < ((op[-2].value.intval + 7) >> 3) * op[-1].value.intval )
		return_error(e_rangecheck);
	code = gs_imagepath(igs,
		(int)op[-2].value.intval, (int)op[-1].value.intval,
		op->value.const_bytes);
	if ( code == 0 ) pop(3);
	return code;
}

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

BEGIN_OP_DEFS(zpaint_op_defs) {
	{"0eofill", zeofill},
	{"0fill", zfill},
	{"0.fillpage", zfillpage},
	{"5image", zimage},
	{"5imagemask", zimagemask},
	{"3.imagepath", zimagepath},
	{"0stroke", zstroke},
		/* Internal operators */
	{"1%image_proc_continue", image_proc_continue},
	{"0%image_file_continue", image_file_continue},
END_OP_DEFS(0) }
