/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/
/*
 * $Header: /home/gda/dxcvs/dx/src/exec/dxmods/overlay.c,v 1.3 1999/05/10 15:45:28 gda Exp $
 */

#include <dxconfig.h>


/***
MODULE:
    Overlay
SHORTDESCRIPTION:
    overlays one image on top of another using either blending or keying.
CATEGORY:
    Import and Export
INPUTS:
    overlay; image; NULL; overlay image 
    base;    image; NULL; base image 
    blend;   scalar or field or string or vector ; .5;  0 for base image only; 1 for overlay image only
OUTPUTS:
    combined; image; NULL; combined image
FLAGS:
BUGS:
AUTHOR: David Wood 
END:
***/

#include <dx/dx.h>
#include <_helper_jea.h>
#include  <math.h>		/* for fabs() */
#include <stdlib.h>

#define MAX_SHAPE	64

/* 
 * Default allowable difference in color values when doing chromakeying.
 * This number is derived from visual inspection. 
 */
#define DELTA_COLOR	.001	

/* 
 * Determine whether two colors are the same 
 */
#define COLORMATCH(rgb, red,green,blue) \
	( (fabs((rgb)->r - (red)) <= DELTA_COLOR) && \
	  (fabs((rgb)->g - (green)) <= DELTA_COLOR) && \
	  (fabs((rgb)->b - (blue)) <= DELTA_COLOR) ) 

/* 
 * Supported operations. 
 */
#define  BLENDING 	0x1	/* Constant blend over images */
#define  MATTE		0x2	/* Per pixel blend over images */
#define  CHROMAKEY	0x3	/* Only overlay when base==color */

/*
 * Structure used to pass arguments to tasks.
 * If you add a new structure member, make sure it gets copied to
 * largs (as appropriate) in create_overlay_tasks().
 */
struct overlay_args {			/* Input args to overlay_task */
	Field base, overlay;		/* The 2 images to overlay */
	Field combined;			/* The output image to build */
	int	op;			/* How do we do the overlay */
	union arg3 {			/* 3rd argument to overlay */
		float	 blend;		/* Field blending coefficient */
		Field	 matte;		/* Blend matte */ 
		RGBColor chromakey;	/* Color to key on in overlay */
	} u;
};

typedef enum pixtype
{
    const_ubyte_rgb,
    const_float_rgb,
    const_mapped,
    ubyte_rgb,
    float_rgb,
    mapped
} Pixtype;

static Error blend_images(Pixtype, Pointer, RGBColor*,
			  Pixtype, Pointer, RGBColor*,
			  Pixtype, Pointer,
			  int, int, float);
static Error matte_images(Pixtype, Pointer, RGBColor*,
			  Pixtype, Pointer, RGBColor*,
			  Pixtype, Pointer,
			  int, int, float *);
static Error key_images(Pixtype, Pointer, RGBColor*,
			  Pixtype, Pointer, RGBColor*,
			  Pixtype, Pointer,
			  int, int, RGBColor *);
static Error valid_image_object(Object image);
static Error create_overlay_tasks(struct overlay_args *);
static Error overlay_task(Pointer);
static Error valid_blend_field(Field matte);
static Error compatible_grids(Field, Field, char *, char *);
static Error getImagePixelData(Field, Pixtype *, Pointer *, RGBColor **);

/*
 * Overlay two images using either of 2 methods depending on the type of 
 * the 3rd argument.  If the argument is of type...
 *
 *   scalar or Field ) 	Blend the two images such that
 *				o = base * (1-blend) + overlay * blend
 *		       	where 'blend' is
 *				scalar ) a float constant	
 *				field )  a field of float "data" constants 
 *					that map 1:1 onto both images.
 * 
 *   vector or string) specifies a color to use as the key when doing
 *			chromakeying, so that    
 *				if (base[i] == colorkey) 
 *					o[i] = overlay[i]
 *				else
 *					o[i] = base[i]
 *			This is comparable to creating a blend field that
 *			has value 1 where base[i] = colorkey and value 0
 *			elsewhere. 
 *
 * The default (when the 3rd argument is not present) is to overlay
 * the two images as if a single float blend  value of .5 had been
 * provided.
 *
 * Partitioned fields are handled properly.
 *
 * Currently, the input images (and the blend field) must have the
 * same positions.
 * 
 * 
 */
int
m_Overlay ( Object *in, Object *out )
{

#define  I_overlay	in[0]
#define  I_base		in[1]
#define  I_blend	in[2]


    Object	combined = NULL;
    char 	*colorstr;
    struct overlay_args args;

    if ( !I_overlay )
        ErrorGotoPlus1 ( ERROR_MISSING_DATA, "#10000", "'overlay'" );

    if ( !I_base )
        ErrorGotoPlus1 ( ERROR_MISSING_DATA, "#10000", "'base'" );

    if (!I_blend)   {
	args.u.blend = 0.5;	/* Default is to do a 50/50 blend */
	args.op = BLENDING;
    } 
    /*  If it's a string, then it is a color to key on */
    else if (DXExtractString(I_blend, &colorstr)) {
        /*  convert from string to DXRGB */
        if (!(DXColorNameToRGB(colorstr, &args.u.chromakey)))
            ErrorGotoPlus1( ERROR_INVALID_DATA, "#11760",colorstr );
        args.op = CHROMAKEY;
    }
    /*  If it's an DXRGB color */
    else  if (DXExtractParameter(I_blend,TYPE_FLOAT,3,1,
					(Pointer)&args.u.chromakey)) {
        args.op = CHROMAKEY;
    }
    /* See if it is a float blending value */
    else if (DXExtractFloat ( I_blend, &args.u.blend ) )  {
        if ( ( args.u.blend < 0.0 ) || ( args.u.blend > 1.0 ) ) 
            ErrorGotoPlus3( ERROR_INVALID_DATA, "#10110", "'blend'", 0, 1 );
        args.op = BLENDING;
    } 
    /* See if it is a matte/field */
    else if (valid_blend_field((Field)I_blend)) {
	args.u.matte = (Field)I_blend;
        args.op = MATTE;
    }
    /* Bad data type */
    else {
        ErrorGotoPlus1(ERROR_INVALID_DATA, "#10232","blend");
    }

    /*
     * Create the structure for the outgoing image and then build
     * a task group to do the overlay work. 
     */
    if (combined = DXCopy(I_base, COPY_STRUCTURE)) {
    	DXCreateTaskGroup();
	args.base = (Field)I_base;
	args.overlay = (Field)I_overlay;
	args.combined = (Field)combined;
    	if (!create_overlay_tasks(&args)) {
	    DXAbortTaskGroup();
	    goto error;
	}
	else if (!DXExecuteTaskGroup())
	    goto error;
    } 

    out[0] = combined;
    return OK;

error:
    if (combined != I_base) 
	DXDelete(combined);
    return ERROR;
}


/* 
 * Create one task for each object with a class of CLASS_FIELD in the
 * base image (args->base).   Called recursively for CLASS_COMPOSITEFIELD
 * objects.
 */
static Error 
create_overlay_tasks(struct overlay_args *args)
{
    Object o, b;
    int i;
    struct overlay_args largs; 

    switch (DXGetObjectClass((Object)args->base)) {
	case CLASS_FIELD:
	    /* 
	     * Make sure the overlay image is compatible with the base. 
	     */
	    if (DXGetObjectClass((Object)args->overlay) != CLASS_FIELD) {
		ErrorGotoPlus2(ERROR_INVALID_DATA, "#12310", "base", "overlay");
	    }
	    else if (!compatible_grids(args->base, args->overlay, 
					"'base'","'overlay'")) {
		goto error;
	    } 
	    /* 
	     * Check to be sure the matte (if being used) is compatible. 
	     */
	    if (args->op == MATTE) { 
		if (DXGetObjectClass((Object)args->u.matte) != CLASS_FIELD) {
		    ErrorGotoPlus2(ERROR_INVALID_DATA,"#12315","blend","matte");
		}
	        else if (!compatible_grids(args->base, args->u.matte,
					"'base'","'blend'")) 
		    goto error;
	    }
	    /* 
	     * Queue up the work. 
	     */
	    if (!DXAddTask(overlay_task, (Pointer)args, sizeof(*args), 0.0))
		goto error;
	    break;
	case CLASS_GROUP:	/* Image must be a composite field */
	    /* 
	     * Make sure the input base image is luking good.
	     */
            if (DXGetGroupClass((Group)args->base) != CLASS_COMPOSITEFIELD)
	    	ErrorGotoPlus1(ERROR_INVALID_DATA, "#12320", "base");
	    /* 
	     * Check to be sure that the overlay is the same type. 
	     */
    	    if ((DXGetObjectClass((Object)args->overlay) != CLASS_GROUP) || 
                (DXGetGroupClass((Group)args->overlay) != CLASS_COMPOSITEFIELD))
	    	ErrorGotoPlus2(ERROR_INVALID_DATA, "#12315","overlay","image"); 
	    /* 
	     * Check to be sure the matte (if being used) is compatible. 
	     */
    	    if ((args->op == MATTE) &&
		((DXGetObjectClass((Object)args->u.matte) != CLASS_GROUP) || 
                 (DXGetGroupClass((Group)args->u.matte)!=CLASS_COMPOSITEFIELD)))
	    	ErrorGotoPlus2(ERROR_INVALID_DATA, "#12315", "blend","matte");
	    /* 
	     * Load largs with the constants over all tasks. 
	     */
	    largs.op = args->op;
	    if (largs.op == BLENDING)
		largs.u.blend = args->u.blend;
	    else if (largs.op == CHROMAKEY)
		largs.u.chromakey = args->u.chromakey;
	    /* 
	     * Create one task for each member of the inputs.
	     */
	    for (i=0; (b = DXGetEnumeratedMember((Group)args->base,i,NULL)) &&
	    	      (o = DXGetEnumeratedMember((Group)args->overlay,i,NULL)) ;
		 i++) {

		largs.overlay = (Field)o; 
		largs.base = (Field)b; 
		largs.combined = (Field)DXGetEnumeratedMember(
					(Group)args->combined,i,NULL); 
		if (largs.op == MATTE) {
		    largs.u.matte = (Field)DXGetEnumeratedMember(
					(Group)args->u.matte, i,NULL); 
		    if (!largs.u.matte)
			goto error;
		}
		if (!create_overlay_tasks(&largs)) 
		    goto error;
	    }
	    break;
	default:
	    ErrorGotoPlus1(ERROR_INVALID_DATA, "#12320", "base");
    }
    return OK;
error:
    return ERROR;
}

/*
 *  Overlay two images
 *	args->combined = foo(args->base, args->overlay) 
 *  The supplied images (args->[base,overlay,combined]) are all assumed to be
 *  simple images (i.e. not composite fields).
 */
static Error 
overlay_task(Pointer ptr)
{
    struct overlay_args *args = (struct overlay_args *)ptr;
    Field combined = args->combined;
    Field base = args->base;
    Field overlay = args->overlay;
    Array a = NULL, blend;
    Pointer  	c_ptr, o_ptr, b_ptr;
    float 	*m_ptr;
    RGBColor	*o_map = NULL, *b_map = NULL;
    Pixtype     o_type, b_type, c_type;
    Type	type;
    Category	cat;
    int       	height1, width1, height2, width2;
    int 	nitems, rank, shape[MAX_SHAPE]; 

    /*
     * Be sure that the base object is an image
     */ 
    if (!valid_image_object((Object)base))
	ErrorGotoPlus1 ( ERROR_INVALID_DATA, "#12320", "base" );

    /*
     * Be sure that the overlay object is an image
     */ 
    if (!valid_image_object((Object)overlay))
	ErrorGotoPlus1 ( ERROR_INVALID_DATA, "#12320", "overlay" );
    
    /*
     * Get overlay, base and output image info
     */
    if (! getImagePixelData(overlay, &o_type, &o_ptr, &o_map))
	goto error;
	
    if (! getImagePixelData(base, &b_type, &b_ptr, &b_map))
	goto error;
	
    /*
     * Get and compare sizes of base and overlay
     */
    if (!DXGetImageSize(overlay, &width1, &height1) ||
        !DXGetImageSize(base, &width2, &height2))
        DXErrorGoto( ERROR_INVALID_DATA, "#12325");

    /* 
     * Make sure the images are the same size 
     */
    if ( (height1 != height2) || (width1 != width2) ) { 
        DXSetError ( ERROR_INVALID_DATA, "#12330",
              height1, width1, height2, width2 );
	goto error;
    }

    /*
     * Create a new array of pixels to hold the combined image.
     */
    if (! getenv("DXPIXELTYPE") || !strcmp(getenv("DXPIXELTYPE"), "DXFloat"))
	a = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 3);
    else
	a = DXNewArray(TYPE_UBYTE, CATEGORY_REAL, 1, 3);

    if (!a || !DXAddArrayData(a, 0, width1*height1, NULL))
	goto error;

    /* 
     * Install the new image (colors) in the output field 
     */
    if (!DXSetComponentValue(combined, "colors", (Object)a))
	goto error;

    if (! getImagePixelData(combined, &c_type, &c_ptr, NULL))
	goto error;
    
    /* 
     * Do the work to overlay the images 
     */
    if (args->op == BLENDING) {
	if (! blend_images(b_type, b_ptr, b_map,
		     o_type, o_ptr, o_map,
		     c_type, c_ptr,
		     height1, width1, args->u.blend)) goto error;
    } 
    else if (args->op == MATTE) {
	/*
	 * Make sure the field has the correct structure
	 */
        if (!valid_blend_field(args->u.matte))
	    ErrorGotoPlus1 ( ERROR_INVALID_DATA, "#12335", "blend" );

	/*
	 * DXExtract the array of blend values
	 */
	blend = (Array)DXGetComponentValue(args->u.matte,"data");
	if (!blend)
	    ErrorGotoPlus2 ( ERROR_INVALID_DATA, "#10250", "'blend'", "data");

	/*
	 * Check the number, type, category, rank and shape of data provided
	 */ 
	DXGetArrayInfo(blend, &nitems, &type, &cat, &rank, shape);
	if (nitems != (width1 * height1))
	    ErrorGotoPlus1 ( ERROR_INVALID_DATA, "#12340","blend" );
	if (type != TYPE_FLOAT) 
	    ErrorGotoPlus1 ( ERROR_INVALID_DATA, "#10330", "'blend' data" );
	if (cat != CATEGORY_REAL) 
	    ErrorGotoPlus1(ERROR_INVALID_DATA,"#11150","'blend' data"); 
	if ((rank != 0) && !(rank == 1 && shape[0] == 1)) 
	    ErrorGotoPlus1 ( ERROR_INVALID_DATA,  "#10081", "'blend' data");

	/*
	 * Get a pointer to the real float values
	 */
	m_ptr = (float*)DXGetArrayData(blend);
	if (!m_ptr)
	    ErrorGotoPlus2 ( ERROR_INTERNAL, "#10250", "'blend'","data" );

	if (! matte_images(b_type, b_ptr, b_map,
		     o_type, o_ptr, o_map,
		     c_type, c_ptr,
		     height1, width1, m_ptr)) goto error;
    } 
    else if (args->op == CHROMAKEY) {
	if (! key_images(b_type, b_ptr, b_map,
		   o_type, o_ptr, o_map,
		   c_type, c_ptr,
		   height1, width1, &args->u.chromakey)) goto error;
    } else 
	DXErrorGoto ( ERROR_UNEXPECTED, "bad operation" );


    /* Need to remove any "direct color map" attribute if present, 
     * since colors are expanded, this attribute does not make sense. 
     * Similarly, colors should not ref a colormap, 
     * and the color map component should be removed. DLG 4/30/97 
     */
    if (!DXSetAttribute((Object)combined,"direct color map", NULL))
       goto error;	
    if (!DXSetComponentAttribute((Field)combined,"colors","ref",NULL))
       goto error;
    if (!DXSetComponentValue((Field)combined,"color map",NULL))
       goto error;
    return OK;		

error:
    if (a) DXDelete((Object)a);
    return ERROR;
}

#define BYTE_TO_FLOAT(a) ((a)*(1.0/255.0))
#define FLOAT_TO_BYTE(a) ((a)*255.0)

#define GET_PIXEL(dst, tmp, type, u_p, f_p, map)		\
{								\
    if (type == ubyte_rgb)					\
    {								\
	tmp.r = BYTE_TO_FLOAT(*u_p++);				\
	tmp.g = BYTE_TO_FLOAT(*u_p++);				\
	tmp.b = BYTE_TO_FLOAT(*u_p++);				\
	dst = &tmp;						\
    }								\
    else if (type == float_rgb)					\
    {								\
	dst = f_p++;						\
    }								\
    else if (type == mapped)					\
    {								\
	dst = map + *u_p++;					\
    }								\
}

#define PUT_PIXEL(type, u_p, f_p, res)				\
{								\
    if (type == ubyte_rgb)					\
    {								\
	*u_p++ = FLOAT_TO_BYTE((res)->r);				\
	*u_p++ = FLOAT_TO_BYTE((res)->g);				\
	*u_p++ = FLOAT_TO_BYTE((res)->b);				\
    }								\
    else if (type == float_rgb)					\
    {								\
	f_p->r = (res)->r;					\
	f_p->g = (res)->g;					\
	f_p->b = (res)->b;					\
	f_p++;							\
    }								\
}
	
	 
/*
 * Blend two images such that
 * 	dest = (1-blend)*base + blend*overlay;
 */
static Error
blend_images(Pixtype b_type, Pointer b_ptr, RGBColor *b_map,
	     Pixtype o_type, Pointer o_ptr, RGBColor *o_map,
	     Pixtype c_type, Pointer c_ptr,
	     int height, int width, float blend)
{
    float 	bbar = 1.0 - blend;
    int		i,j,n;
    RGBColor	*fb = (RGBColor *)b_ptr, b_tmp, *b;
    RGBColor	*fo = (RGBColor *)o_ptr, o_tmp, *o;
    RGBColor	*fc = (RGBColor *)c_ptr;
    ubyte	*ub = (ubyte *)b_ptr;
    ubyte	*uo = (ubyte *)o_ptr;
    ubyte	*uc = (ubyte *)c_ptr;

    if (b_type == const_ubyte_rgb)
    {
	b_tmp.r = BYTE_TO_FLOAT(*ub++);
	b_tmp.g = BYTE_TO_FLOAT(*ub++);
	b_tmp.b = BYTE_TO_FLOAT(*ub++);
	b = &b_tmp;
    }
    else if (b_type == const_float_rgb)
    {
	b = (RGBColor *)b_ptr;
    }
    else if (b_type == const_mapped)
    {
	b = b_map + *ub;
    }

    if (o_type == const_ubyte_rgb)
    {
	o_tmp.r = BYTE_TO_FLOAT(*uo++);
	o_tmp.g = BYTE_TO_FLOAT(*uo++);
	o_tmp.b = BYTE_TO_FLOAT(*uo++);
	o = &o_tmp;
    }
    else if (o_type == const_float_rgb)
    {
	o = (RGBColor *)o_ptr;
    }
    else if (o_type == const_mapped)
    {
	o = o_map + *uo;
    }

    n = height*width;
    for (i=0; i<n; i++ )
    {
	RGBColor result;
	GET_PIXEL(b, b_tmp, b_type, ub, fb, b_map);
	GET_PIXEL(o, o_tmp, o_type, uo, fo, o_map);
	result.r = (b->r * bbar) + (o->r * blend);
	result.g = (b->g * bbar) + (o->g * blend);
	result.b = (b->b * bbar) + (o->b * blend);
	PUT_PIXEL(c_type, uc, fc, &result);
    }

    return OK;
}

/*
 * Chromakey two images such that
 * 	dest->pixel = (base->pixel == key ? overlay->pixel : base->pixel) 
 * for all pixels in base.
 */
static Error
key_images(Pixtype b_type, Pointer b_ptr, RGBColor *b_map,
	     Pixtype o_type, Pointer o_ptr, RGBColor *o_map,
	     Pixtype c_type, Pointer c_ptr,
	     int height, int width, RGBColor *key)
{
    int		i,j,n;
    RGBColor	*fb = (RGBColor *)b_ptr, b_tmp, *b;
    RGBColor	*fo = (RGBColor *)o_ptr, o_tmp, *o;
    RGBColor	*fc   = (RGBColor *)c_ptr;
    ubyte	*ub   = (ubyte *)b_ptr;
    ubyte	*uo   = (ubyte *)o_ptr;
    ubyte	*uc   = (ubyte *)c_ptr;
    float	red   = key->r;
    float	green = key->g;
    float	blue  = key->b;

    if (b_type == const_ubyte_rgb)
    {
	b_tmp.r = BYTE_TO_FLOAT(*ub++);
	b_tmp.g = BYTE_TO_FLOAT(*ub++);
	b_tmp.b = BYTE_TO_FLOAT(*ub++);
	b = &b_tmp;
    }
    else if (b_type == const_float_rgb)
    {
	b = (RGBColor *)b_ptr;
    }
    else if (b_type == const_mapped)
    {
	b = b_map + *ub;
    }

    if (o_type == const_ubyte_rgb)
    {
	o_tmp.r = BYTE_TO_FLOAT(*uo++);
	o_tmp.g = BYTE_TO_FLOAT(*uo++);
	o_tmp.b = BYTE_TO_FLOAT(*uo++);
	o = &o_tmp;
    }
    else if (o_type == const_float_rgb)
    {
	o = (RGBColor *)o_ptr;
    }
    else if (o_type == const_mapped)
    {
	o = o_map + *uo;
    }

    n = height*width;
    for (i=0; i<n; i++ )
    {
	GET_PIXEL(b, b_tmp, b_type, ub, fb, b_map);
	GET_PIXEL(o, o_tmp, o_type, uo, fo, o_map);
	if (COLORMATCH(((RGBColor *)b), red, green, blue))
	    PUT_PIXEL(c_type, uc, fc, o)
	else
	    PUT_PIXEL(c_type, uc, fc, b);
    }

    return OK;
}

/*
 * Blend two images with a matte of blends such that
 *   dest->pixel = base->pixel *(1-matte->data) + overlay->pixel*matte->data)
 * for all pixels in base.
 */
static Error
matte_images(Pixtype b_type, Pointer b_ptr, RGBColor *b_map,
	     Pixtype o_type, Pointer o_ptr, RGBColor *o_map,
	     Pixtype c_type, Pointer c_ptr,
	     int height, int width, float *blend)
{
    int		i,j,n;
    RGBColor	*fb = (RGBColor *)b_ptr, b_tmp, *b;
    RGBColor	*fo = (RGBColor *)o_ptr, o_tmp, *o;
    RGBColor	*fc   = (RGBColor *)c_ptr;
    ubyte	*ub   = (ubyte *)b_ptr;
    ubyte	*uo   = (ubyte *)o_ptr;
    ubyte	*uc   = (ubyte *)c_ptr;

    if (b_type == const_ubyte_rgb)
    {
	b_tmp.r = BYTE_TO_FLOAT(*ub++);
	b_tmp.g = BYTE_TO_FLOAT(*ub++);
	b_tmp.b = BYTE_TO_FLOAT(*ub++);
	b = &b_tmp;
    }
    else if (b_type == const_float_rgb)
    {
	b = (RGBColor *)b_ptr;
    }
    else if (b_type == const_mapped)
    {
	b = b_map + *ub;
    }

    if (o_type == const_ubyte_rgb)
    {
	o_tmp.r = BYTE_TO_FLOAT(*uo++);
	o_tmp.g = BYTE_TO_FLOAT(*uo++);
	o_tmp.b = BYTE_TO_FLOAT(*uo++);
	o = &o_tmp;
    }
    else if (o_type == const_float_rgb)
    {
	o = (RGBColor *)o_ptr;
    }
    else if (o_type == const_mapped)
    {
	o = o_map + *uo;
    }

    n = height*width;
    for (i=0; i<n; i++, blend++)
    {
	RGBColor result;
	float blnd = *blend;
	float obar = 1.0 - *blend;

	if ( blnd < 0 || blnd > 1.0)	/* Must check blends */
	    ErrorGotoPlus3( ERROR_INVALID_DATA, "#10110", 
		    "'blend' field datum", 0, 1 );

	GET_PIXEL(b, b_tmp, b_type, ub, fb, b_map);
	GET_PIXEL(o, o_tmp, o_type, uo, fo, o_map);

	result.r = (b->r * obar) + (o->r * blnd);
	result.g = (b->g * obar) + (o->g * blnd);
	result.b = (b->b * obar) + (o->b * blnd);

	PUT_PIXEL(c_type, uc, fc, &result);
    }

    return OK;

error:
    return ERROR;
}

/*
 * Determine if the given object has top level structure that is consistent
 * with an image. 
 */
static Error 
valid_image_object(Object image)
{
    switch ( DXGetObjectClass (image) )
    {
        case CLASS_FIELD:
            return(_dxf_ValidImageField ((Field) image));
        case CLASS_GROUP:
            switch ( DXGetGroupClass ( (Group) image)) {
                case CLASS_COMPOSITEFIELD:
		    return(OK);
            }
    }
    return(ERROR); 
}
/*
 * Determine whether the given object represents a valid blending field.
 */
static Error 
valid_blend_field(Field matte)
{
    switch ( DXGetObjectClass ((Object)matte) )
    {
        case CLASS_FIELD:
            return OK;
        case CLASS_GROUP:
            switch ( DXGetGroupClass ( (Group) matte)) {
                case CLASS_COMPOSITEFIELD:
		    return OK;
            }
    }
    return ERROR; 
}
/*
 * Check two images to be sure they have similar positions.
 *
 * Compatible images must have grids that...
 *	1) are 2 dimensional. 
 *	2) are dependendent on positions (not connections)
 *	3) have the same number of positions in each dimension
 *	4) have the same origin. 
 *	5) have the save deltas 
 *
 * Note: We assume (for error messages) that f1 is the base image and f2 
 * 	is the overlay image.
 *
 * Returns OK/ERROR and sets error code.
 */
static Error
compatible_grids(Field f1, Field f2, char *f1name, char *f2name)
{
    Array 	p1, p2;
    int		n1,n2,i;
    /* Make these larger than they have to be in case the grid is > 2 */
    int		cnt1[2], cnt2[2];
    float	origin1[2], origin2[2];
    float	delta1[2][2], delta2[2][2];

    p1 = (Array)DXGetComponentValue(f1,	"positions");
    if (!p1)   {
	DXSetError(ERROR_BAD_PARAMETER,"#10250",f1name, "positions");
	goto error;
    }
    

    p2 = (Array)DXGetComponentValue(f2,	"positions");
    if (!p2)   {
	DXSetError(ERROR_BAD_PARAMETER,"#10250",f2name, "positions");
	goto error;
    }
    

    if (!DXQueryGridPositions(p1, &n1, NULL, NULL, NULL))
	ErrorGotoPlus1(ERROR_BAD_PARAMETER, "#10612",f1name);

    if (!DXQueryGridPositions(p2, &n2, NULL, NULL, NULL))
	ErrorGotoPlus1(ERROR_BAD_PARAMETER, "#10612",f2name);

    if (n1 != 2) 
	ErrorGotoPlus1(ERROR_BAD_PARAMETER,"#10333",f1name);

    if (n2 != 2) 
	ErrorGotoPlus1(ERROR_BAD_PARAMETER,"#10333",f2name);

    /* Now that we know n1=n2=2, fill the arrays of information */
    if (!DXQueryGridPositions(p1, NULL, cnt1, origin1, delta1[0]) ||
	!DXQueryGridPositions(p2, NULL, cnt2, origin2, delta2[0])) 
	DXErrorGoto(ERROR_UNEXPECTED, "Cannot re-query grid positions");

    for (i=0 ; i<2 ; i++) {
	if (cnt1[i] != cnt2[i]) {
	    DXSetError(ERROR_BAD_PARAMETER,"#12345",
		i,f2name,cnt2[i],f1name,cnt1[i]);
	    goto error;
	}
	if (origin1[i] != origin2[i])  {
	    DXSetError(ERROR_BAD_PARAMETER,"#12345",
		i,f2name, origin2[i],f1name, origin1[i]);
	    goto error;
	}
	if ((delta1[i][0] != delta2[i][0]) || (delta1[i][1] != delta2[i][1])) {
	    DXSetError(ERROR_BAD_PARAMETER, "#12355",
		i,f2name,delta2[i][0],delta2[i][1],
		  f1name,delta1[i][0],delta1[i][1]);
	    goto error;
	}
    }
 
    return OK;
error:
    return ERROR;
}


static Error
getImagePixelData(Field image, Pixtype *ptype, Pointer *pixels, RGBColor **map)
{
    Type type;
    Category cat;
    int rank, shape[32];

    Array a = (Array)DXGetComponentValue(image, "colors");
    if (! a)
	DXErrorGoto(ERROR_INVALID_DATA, "#12325");
    
    DXGetArrayInfo(a, NULL, &type, &cat, &rank, shape);

    if (type == TYPE_FLOAT)
    {
	if (cat != CATEGORY_REAL || rank != 1 || shape[0] != 3)
	    DXErrorGoto(ERROR_INVALID_DATA, "pixel type is invalid");
	
	*ptype = float_rgb;
	if (map)
	    *map  = NULL;
    }
    else if (type == TYPE_UBYTE && rank == 1 && shape[0] == 3)
    {
	if (cat != CATEGORY_REAL)
	    DXErrorGoto(ERROR_INVALID_DATA, "pixel type is invalid");
	
	*ptype = ubyte_rgb;
	if (map)
	    *map  = NULL;
    }
    else if (type == TYPE_UBYTE && (rank == 0 || (rank == 1 && shape[0] == 1)))
    {
	Array mapa = (Array)DXGetComponentValue(image, "color map");

	if (!map)
	    DXErrorGoto(ERROR_INVALID_DATA, "illegal delayed-color image");

	if (cat != CATEGORY_REAL)
	    DXErrorGoto(ERROR_INVALID_DATA, "pixel type is invalid");
	
	if (!mapa)
	    DXErrorGoto(ERROR_INVALID_DATA, "invalid delayed-color image");

	*ptype = mapped;
	if (map)
	    *map  = (RGBColor *)DXGetArrayData(mapa);

    }
    else
	DXErrorGoto(ERROR_INVALID_DATA, "pixel type is invalid");
    
    if (DXGetArrayClass(a) == CLASS_CONSTANTARRAY)
    {
	*pixels = DXGetConstantArrayData(a);
	if      (*ptype == float_rgb) *ptype = const_float_rgb;
	else if (*ptype == ubyte_rgb) *ptype = const_ubyte_rgb;
	else if (*ptype == mapped)    *ptype = const_mapped;
    }
    else
	*pixels = DXGetArrayData(a);

    return OK;

error:
    return ERROR;
}

