/* Copyright (C) 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: gximage2.c,v 1.2.6.1.2.1 2003/01/17 00:49:04 giles Exp $ */
/* ImageType 2 image implementation */
#include "math_.h"
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsmatrix.h"		/* for gscoord.h */
#include "gscoord.h"
#include "gscspace.h"
#include "gscpixel.h"
#include "gsdevice.h"
#include "gsiparm2.h"
#include "gxgetbit.h"
#include "gxiparam.h"
#include "gxpath.h"

/* Forward references */
private dev_proc_begin_typed_image(gx_begin_image2);
private image_proc_source_size(gx_image2_source_size);

/* Structure descriptor */
private_st_gs_image2();

/* Define the image type for ImageType 2 images. */
const gx_image_type_t gs_image_type_2 = {
    &st_gs_image2, gx_begin_image2, gx_image2_source_size,
    gx_image_no_sput, gx_image_no_sget, gx_image_default_release, 2
};

/* Initialize an ImageType 2 image. */
void
gs_image2_t_init(gs_image2_t * pim)
{
    pim->type = &gs_image_type_2;
    pim->UnpaintedPath = 0;
    pim->PixelCopy = false;
}

/*
 * Compute the device space coordinates and source data size for an
 * ImageType 2 image.  This procedure fills in
 * image.{Width,Height,ImageMatrix}.
 */
typedef struct image2_data_s {
    gs_point origin;
    gs_int_rect bbox;
    gs_image1_t image;
} image2_data_t;
private int
image2_set_data(const gs_image2_t * pim, image2_data_t * pid)
{
    gs_state *pgs = pim->DataSource;
    gs_matrix smat;
    gs_rect sbox, dbox;

    gs_transform(pgs, pim->XOrigin, pim->YOrigin, &pid->origin);
    sbox.q.x = (sbox.p.x = pim->XOrigin) + pim->Width;
    sbox.q.y = (sbox.p.y = pim->YOrigin) + pim->Height;
    gs_currentmatrix(pgs, &smat);
    gs_bbox_transform(&sbox, &smat, &dbox);
    pid->bbox.p.x = (int)floor(dbox.p.x);
    pid->bbox.p.y = (int)floor(dbox.p.y);
    pid->bbox.q.x = (int)ceil(dbox.q.x);
    pid->bbox.q.y = (int)ceil(dbox.q.y);
    pid->image.Width = pid->bbox.q.x - pid->bbox.p.x;
    pid->image.Height = pid->bbox.q.y - pid->bbox.p.y;
    pid->image.ImageMatrix = pim->ImageMatrix;
    return 0;
}

/* Compute the source size of an ImageType 2 image. */
private int
gx_image2_source_size(const gs_imager_state * pis, const gs_image_common_t * pim,
		      gs_int_point * psize)
{
    image2_data_t idata;

    image2_set_data((const gs_image2_t *)pim, &idata);
    psize->x = idata.image.Width;
    psize->y = idata.image.Height;
    return 0;
}

/* Begin an ImageType 2 image. */
/* Note that since ImageType 2 images don't have any source data, */
/* this procedure does all the work. */
private int
gx_begin_image2(gx_device * dev,
		const gs_imager_state * pis, const gs_matrix * pmat,
		const gs_image_common_t * pic, const gs_int_rect * prect,
	      const gx_drawing_color * pdcolor, const gx_clip_path * pcpath,
		gs_memory_t * mem, gx_image_enum_common_t ** pinfo)
{
    const gs_image2_t *pim = (const gs_image2_t *)pic;
    gs_state *pgs = pim->DataSource;
    gx_device *sdev = gs_currentdevice(pgs);
    int depth = sdev->color_info.depth;

/****** ONLY HANDLE depth <= 8 FOR PixelCopy ******/
    bool pixel_copy = pim->PixelCopy && depth <= 8 &&
    !memcmp(&dev->color_info, &sdev->color_info,
	    sizeof(dev->color_info));
    bool has_alpha;
    bool direct_copy;
    image2_data_t idata;
    byte *row;
    uint row_size, source_size;
    gx_image_enum_common_t *info;
    gs_matrix smat, dmat;
    gs_color_space cs;
    const gs_color_space *pcs;
    int code;

    gs_image_t_init_rgb(&idata.image, pis);
    /* Add Decode entries for K and alpha */
    idata.image.Decode[6] = idata.image.Decode[8] = 0.0;
    idata.image.Decode[7] = idata.image.Decode[9] = 1.0;
    if (pmat == 0) {
	gs_currentmatrix((const gs_state *)pis, &dmat);
	pmat = &dmat;
    } else
	dmat = *pmat;
    gs_currentmatrix(pgs, &smat);
    code = image2_set_data(pim, &idata);
    if (code < 0)
	return code;
/****** ONLY HANDLE SIMPLE CASES FOR NOW ******/
    if (idata.bbox.p.x != floor(idata.origin.x))
	return_error(gs_error_rangecheck);
    if (!(idata.bbox.p.y == floor(idata.origin.y) ||
	  idata.bbox.q.y == ceil(idata.origin.y))
	)
	return_error(gs_error_rangecheck);
    source_size = (idata.image.Width * depth + 7) >> 3;
    row_size = max(3 * idata.image.Width, source_size);
    row = gs_alloc_bytes(mem, row_size, "gx_begin_image2");
    if (row == 0)
	return_error(gs_error_VMerror);
    if (pixel_copy &&
	(pcpath == NULL ||
	 gx_cpath_includes_rectangle(pcpath,
				     int2fixed(idata.bbox.p.x),
				     int2fixed(idata.bbox.p.y),
				     int2fixed(idata.bbox.q.x),
				     int2fixed(idata.bbox.q.y)))
	) {
	gs_matrix mat;

	idata.image.BitsPerComponent = depth;
	gs_cspace_init_DevicePixel(&cs, depth);
	pcs = &cs;
	/*
	 * Figure 7.2 of the Adobe 3010 Supplement says that we should
	 * compute CTM x ImageMatrix here, but I'm almost certain it
	 * should be the other way around.  Also see gdevx.c.
	 */
	gs_matrix_multiply(&idata.image.ImageMatrix, &smat, &mat);
	direct_copy =
	    (is_xxyy(&dmat) || is_xyyx(&dmat)) &&
#define eqe(e) mat.e == dmat.e
	    eqe(xx) && eqe(xy) && eqe(yx) && eqe(yy);
#undef eqe
	has_alpha = false;	/* no separate alpha channel */
    } else {
	pixel_copy = false;
	idata.image.BitsPerComponent = 8;
	/* Always use RGB source color for now. */
	pcs = gs_cspace_DeviceRGB(pis);
	direct_copy = false;
	/*
	 * The source device has alpha if the same RGB values with
	 * different alphas map to different pixel values.
	 ****** THIS IS NOT GOOD ENOUGH: WE WANT TO SKIP TRANSFERRING
	 ****** ALPHA IF THE SOURCE IS CAPABLE OF HAVING ALPHA BUT
	 ****** DOESN'T CURRENTLY HAVE ANY ACTUAL ALPHA VALUES DIFFERENT
	 ****** FROM 1.
	 */
	/*
	 * Since the default implementation of map_rgb_alpha_color
	 * premultiplies the color towards white, we can't just test
	 * whether changing alpha has an effect on the color.
	 */
	{
	    gx_color_index trans_black =
	    (*dev_proc(sdev, map_rgb_alpha_color))
	    (sdev, (gx_color_value) 0, (gx_color_value) 0,
	     (gx_color_value) 0, (gx_color_value) 0);

	    has_alpha =
		trans_black != (*dev_proc(sdev, map_rgb_alpha_color))
		(sdev, (gx_color_value) 0, (gx_color_value) 0,
		 (gx_color_value) 0, gx_max_color_value) &&
		trans_black != (*dev_proc(sdev, map_rgb_alpha_color))
		(sdev, gx_max_color_value, gx_max_color_value,
		 gx_max_color_value, gx_max_color_value);
	}
    }
    idata.image.ColorSpace = pcs;
    idata.image.Alpha =
	(has_alpha ? gs_image_alpha_last : gs_image_alpha_none);
    if (smat.yy < 0) {
	/*
	 * The source Y axis is reflected.  Reflect the mapping from
	 * user space to source data.
	 */
	idata.image.ImageMatrix.ty += idata.image.Height *
	    idata.image.ImageMatrix.yy;
	idata.image.ImageMatrix.xy = -idata.image.ImageMatrix.xy;
	idata.image.ImageMatrix.yy = -idata.image.ImageMatrix.yy;
    }
    if (!direct_copy)
	code = (*dev_proc(dev, begin_typed_image))
	    (dev, pis, pmat, (const gs_image_common_t *)&idata.image, NULL,
	     pdcolor, pcpath, mem, &info);
    if (code >= 0) {
	int y;
	gs_int_rect rect;
	gs_get_bits_params_t params;
	const byte *data;
	uint offset = row_size - source_size;

	rect = idata.bbox;
	for (y = 0; code >= 0 && y < idata.image.Height; ++y) {
	    gs_int_rect *unread = 0;
	    int num_unread;

/****** y COMPUTATION IS ROUNDED -- WRONG ******/
	    rect.q.y = rect.p.y + 1;
	    /* Insist on x_offset = 0 to simplify the conversion loop. */
	    params.options =
		GB_ALIGN_ANY | (GB_RETURN_COPY | GB_RETURN_POINTER) |
		GB_OFFSET_0 | (GB_RASTER_STANDARD | GB_RASTER_ANY) |
		GB_PACKING_CHUNKY;
	    if (pixel_copy) {
		params.options |= GB_COLORS_NATIVE;
		params.data[0] = row + offset;
		code = (*dev_proc(sdev, get_bits_rectangle))
		    (sdev, &rect, &params, &unread);
		if (code < 0)
		    break;
		num_unread = code;
		data = params.data[0];
		if (direct_copy) {
		    /*
		     * Copy the pixels directly to the destination.
		     * We know that the transformation is only a translation,
		     * but we must handle an inverted destination Y axis.
		     */
		    code = (*dev_proc(dev, copy_color))
			(dev, data, 0, row_size, gx_no_bitmap_id,
			 (int)(dmat.tx - idata.image.ImageMatrix.tx),
			 (int)(dmat.ty - idata.image.ImageMatrix.ty +
			       (dmat.yy < 0 ? ~y : y)),
			 idata.image.Width, 1);
		    continue;
		}
	    } else {
		/*
		 * Convert the pixels to pure colors.  This may be very
		 * slow and painful.  Eventually we will use indexed color for
		 * narrow pixels.
		 */
		/* Always use RGB source color for now. */
		params.options |=
		    GB_COLORS_RGB | GB_DEPTH_8 |
		    (has_alpha ? GB_ALPHA_LAST : GB_ALPHA_NONE);
		params.data[0] = row;
		code = (*dev_proc(sdev, get_bits_rectangle))
		    (sdev, &rect, &params, &unread);
		if (code < 0)
		    break;
		num_unread = code;
		data = params.data[0];
	    }
	    if (num_unread > 0 && pim->UnpaintedPath) {
		/* Add the rectangle(s) to the unpainted path. */
		int i;

		for (i = 0; code >= 0 && i < num_unread; ++i)
		    code = gx_path_add_rectangle(pim->UnpaintedPath,
						 int2fixed(unread[i].p.x),
						 int2fixed(unread[i].p.y),
						 int2fixed(unread[i].q.x),
						 int2fixed(unread[i].q.y));
		gs_free_object(dev->memory, unread, "UnpaintedPath unread");
	    }
	    code = gx_image_data(info, &data, 0, row_size, 1);
	    rect.p.y = rect.q.y;
	}
	if (!direct_copy) {
	    if (code >= 0)
		code = gx_image_end(info, true);
	    else
		discard(gx_image_end(info, false));
	}
    }
    gs_free_object(mem, row, "gx_begin_image2");
    return (code < 0 ? code : 1);
}
