/* Copyright (C) 1996, 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: gdevpdfi.c,v 1.24.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* Image handling for PDF-writing driver */
#include "gx.h"
#include "gserrors.h"
#include "gsdevice.h"
#include "gsflip.h"
#include "gdevpdfx.h"
#include "gdevpdfg.h"
#include "gdevpdfo.h"		/* for data stream */
#include "gxcspace.h"
#include "gximage3.h"
#include "gximag3x.h"
#include "gsiparm4.h"

/* Forward references */
private image_enum_proc_plane_data(pdf_image_plane_data);
private image_enum_proc_end_image(pdf_image_end_image);
private image_enum_proc_end_image(pdf_image_end_image_object);
private IMAGE3_MAKE_MID_PROC(pdf_image3_make_mid);
private IMAGE3_MAKE_MCDE_PROC(pdf_image3_make_mcde);
private IMAGE3X_MAKE_MID_PROC(pdf_image3x_make_mid);
private IMAGE3X_MAKE_MCDE_PROC(pdf_image3x_make_mcde);

private const gx_image_enum_procs_t pdf_image_enum_procs = {
    pdf_image_plane_data,
    pdf_image_end_image
};
private const gx_image_enum_procs_t pdf_image_object_enum_procs = {
    pdf_image_plane_data,
    pdf_image_end_image_object
};

/* ---------------- Driver procedures ---------------- */

/* Define the structure for keeping track of progress through an image. */
typedef struct pdf_image_enum_s {
    gx_image_enum_common;
    gs_memory_t *memory;
    int width;
    int bits_per_pixel;		/* bits per pixel (per plane) */
    int rows_left;
    pdf_image_writer writer;
    gs_matrix mat;
} pdf_image_enum;
gs_private_st_composite(st_pdf_image_enum, pdf_image_enum, "pdf_image_enum",
  pdf_image_enum_enum_ptrs, pdf_image_enum_reloc_ptrs);
/* GC procedures */
private ENUM_PTRS_WITH(pdf_image_enum_enum_ptrs, pdf_image_enum *pie)
    if (index < pdf_image_writer_max_ptrs) {
	gs_ptr_type_t ret =
	    ENUM_USING(st_pdf_image_writer, &pie->writer, sizeof(pie->writer),
		       index);

	if (ret == 0)		/* don't stop early */
	    ENUM_RETURN(0);
	return ret;
    }
    return ENUM_USING_PREFIX(st_gx_image_enum_common,
			     pdf_image_writer_max_ptrs);
ENUM_PTRS_END
private RELOC_PTRS_WITH(pdf_image_enum_reloc_ptrs, pdf_image_enum *pie)
{
    RELOC_USING(st_pdf_image_writer, &pie->writer, sizeof(pie->writer));
    RELOC_USING(st_gx_image_enum_common, vptr, size);
}
RELOC_PTRS_END

/*
 * Test whether we can write an image in-line.  Before PDF 1.2, this is only
 * allowed for masks, images in built-in color spaces, and images in Indexed
 * color spaces based on these with a string lookup table.
 */
private bool
can_write_image_in_line(const gx_device_pdf *pdev, const gs_image_t *pim)
{
    const gs_color_space *pcs;

    if (pim->ImageMask)
	return true;
    if (pdev->CompatibilityLevel >= 1.2)
	return true;
    pcs = pim->ColorSpace;
 cs:
    switch (gs_color_space_get_index(pcs)) {
    case gs_color_space_index_DeviceGray:
    case gs_color_space_index_DeviceRGB:
    case gs_color_space_index_DeviceCMYK:
	return true;
    case gs_color_space_index_Indexed:
	if (pcs->params.indexed.use_proc)
	    return false;
	pcs = (const gs_color_space *)&pcs->params.indexed.base_space;
	goto cs;
    default:
	return false;
    }
}

/*
 * Start processing an image.  This procedure takes extra arguments because
 * it has to do something slightly different for the parts of an ImageType 3
 * image.
 */
typedef enum {
    PDF_IMAGE_DEFAULT,
    PDF_IMAGE_TYPE3_MASK,	/* no in-line, don't render */
    PDF_IMAGE_TYPE3_DATA	/* no in-line */
} pdf_typed_image_context_t;
private int
pdf_begin_typed_image(gx_device_pdf *pdev, 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,
		      pdf_typed_image_context_t context)
{
    const gs_pixel_image_t *pim;
    int code;
    pdf_image_enum *pie;
    gs_image_format_t format;
    const gs_color_space *pcs;
    gs_color_space cs_gray_temp;
    cos_value_t cs_value;
    int num_components;
    bool is_mask = false, in_line = false;
    gs_int_rect rect;
    /*
     * We define this union because psdf_setup_image_filters may alter the
     * gs_pixel_image_t part, but pdf_begin_image_data must also have access
     * to the type-specific parameters.
     */
    union iu_ {
	gs_pixel_image_t pixel;	/* we may change some components */
	gs_image1_t type1;
	gs_image3_t type3;
	gs_image3x_t type3x;
	gs_image4_t type4;
    } image;
    ulong nbytes;
    int width, height;

    /* Check for the image types we can handle. */
    switch (pic->type->index) {
    case 1: {
	const gs_image_t *pim1 = (const gs_image_t *)pic;

	if (pim1->Alpha != gs_image_alpha_none)
	    goto nyi;
	is_mask = pim1->ImageMask;
	in_line = context == PDF_IMAGE_DEFAULT &&
	    can_write_image_in_line(pdev, pim1);
	image.type1 = *pim1;
	break;
    }
    case 3: {
	const gs_image3_t *pim3 = (const gs_image3_t *)pic;

	if (pdev->CompatibilityLevel < 1.3)
	    goto nyi;
	if (prect && !(prect->p.x == 0 && prect->p.y == 0 &&
		       prect->q.x == pim3->Width &&
		       prect->q.y == pim3->Height))
	    goto nyi;
	/*
	 * We handle ImageType 3 images in a completely different way:
	 * the default implementation sets up the enumerator.
	 */
	return gx_begin_image3_generic((gx_device *)pdev, pis, pmat, pic,
				       prect, pdcolor, pcpath, mem,
				       pdf_image3_make_mid,
				       pdf_image3_make_mcde, pinfo);
    }
    case IMAGE3X_IMAGETYPE: {
	/* See ImageType3 above for more information. */
	const gs_image3x_t *pim3x = (const gs_image3x_t *)pic;

	if (pdev->CompatibilityLevel < 1.4)
	    goto nyi;
	if (prect && !(prect->p.x == 0 && prect->p.y == 0 &&
		       prect->q.x == pim3x->Width &&
		       prect->q.y == pim3x->Height))
	    goto nyi;
	return gx_begin_image3x_generic((gx_device *)pdev, pis, pmat, pic,
					prect, pdcolor, pcpath, mem,
					pdf_image3x_make_mid,
					pdf_image3x_make_mcde, pinfo);
    }
    case 4:
	if (pdev->CompatibilityLevel < 1.3)
	    goto nyi;
	image.type4 = *(const gs_image4_t *)pic;
	break;
    default:
	goto nyi;
    }
    pim = (const gs_pixel_image_t *)pic;
    format = pim->format;
    switch (format) {
    case gs_image_format_chunky:
    case gs_image_format_component_planar:
	break;
    default:
	goto nyi;
    }
    pcs = pim->ColorSpace;
    num_components = (is_mask ? 1 : gs_color_space_num_components(pcs));

    code = pdf_open_page(pdev, PDF_IN_STREAM);
    if (code < 0)
	return code;
    pdf_put_clip_path(pdev, pcpath);
    if (context == PDF_IMAGE_TYPE3_MASK) {
	/*
	 * The soft mask for an ImageType 3x image uses a DevicePixel
	 * color space, which pdf_color_space() can't handle.  Patch it
	 * to DeviceGray here.
	 */
	gs_cspace_init_DeviceGray(&cs_gray_temp);
	pcs = &cs_gray_temp;
    } else if (is_mask)
	code = pdf_prepare_imagemask(pdev, pis, pdcolor);
    else
	code = pdf_prepare_image(pdev, pis);
    if (code < 0)
	goto nyi;
    if (prect)
	rect = *prect;
    else {
	rect.p.x = rect.p.y = 0;
	rect.q.x = pim->Width, rect.q.y = pim->Height;
    }
    pie = gs_alloc_struct(mem, pdf_image_enum, &st_pdf_image_enum,
			  "pdf_begin_image");
    if (pie == 0)
	return_error(gs_error_VMerror);
    *pinfo = (gx_image_enum_common_t *) pie;
    gx_image_enum_common_init(*pinfo, (const gs_data_image_t *) pim,
			      (context == PDF_IMAGE_TYPE3_MASK ?
			       &pdf_image_object_enum_procs :
			       &pdf_image_enum_procs),
			      (gx_device *)pdev, num_components, format);
    pie->memory = mem;
    width = rect.q.x - rect.p.x;
    pie->width = width;
    height = rect.q.y - rect.p.y;
    pie->bits_per_pixel =
	pim->BitsPerComponent * num_components / pie->num_planes;
    pie->rows_left = height;
    nbytes = (((ulong) pie->width * pie->bits_per_pixel + 7) >> 3) *
	pie->num_planes * pie->rows_left;
    in_line &= nbytes <= MAX_INLINE_IMAGE_BYTES;
    if (rect.p.x != 0 || rect.p.y != 0 ||
	rect.q.x != pim->Width || rect.q.y != pim->Height ||
	(is_mask && pim->CombineWithColor)
	/* Color space setup used to be done here: see SRZB comment below. */
	) {
	gs_free_object(mem, pie, "pdf_begin_image");
	goto nyi;
    }
    if (pmat == 0)
	pmat = &ctm_only(pis);
    {
	gs_matrix mat;
	gs_matrix bmat;
	int code;

	pdf_make_bitmap_matrix(&bmat, -rect.p.x, -rect.p.y,
			       pim->Width, pim->Height, height);
	if ((code = gs_matrix_invert(&pim->ImageMatrix, &mat)) < 0 ||
	    (code = gs_matrix_multiply(&bmat, &mat, &mat)) < 0 ||
	    (code = gs_matrix_multiply(&mat, pmat, &pie->mat)) < 0
	    ) {
	    gs_free_object(mem, pie, "pdf_begin_image");
	    return code;
	}
    }
    if ((code = pdf_begin_write_image(pdev, &pie->writer, gs_no_id, width,
				      height, NULL, in_line)) < 0 ||
	/*
	 * Some regrettable PostScript code (such as LanguageLevel 1 output
	 * from Microsoft's PSCRIPT.DLL driver) misuses the transfer
	 * function to accomplish the equivalent of indexed color.
	 * Downsampling (well, only averaging) or JPEG compression are not
	 * compatible with this.  Play it safe by using only lossless
	 * filters if the transfer function(s) is/are other than the
	 * identity.
	 */
	(code = (pdev->transfer_not_identity ?
		 psdf_setup_lossless_filters((gx_device_psdf *) pdev,
					     &pie->writer.binary,
					     &image.pixel) :
		 psdf_setup_image_filters((gx_device_psdf *) pdev,
					  &pie->writer.binary, &image.pixel,
					  pmat, pis))) < 0 ||
	/* SRZB 2001-04-25/Bl
	 * Since psdf_setup_image_filters may change the color space
	 * (in case of pdev->params.ConvertCMYKImagesToRGB == true),
	 * we postpone the selection of the PDF color space to here:
	 */
	(!is_mask &&
	 (code = pdf_color_space(pdev, &cs_value, image.pixel.ColorSpace,
				 (in_line ? &pdf_color_space_names_short :
				  &pdf_color_space_names), in_line)) < 0) ||
	(code = pdf_begin_image_data(pdev, &pie->writer,
				     (const gs_pixel_image_t *)&image,
				     &cs_value)) < 0
	) {
	/****** SHOULD FREE STRUCTURES AND CLEAN UP HERE ******/
	goto nyi;		/* fall back to default implementation */
    }
    return 0;
 nyi:
    return gx_default_begin_typed_image
	((gx_device *)pdev, pis, pmat, pic, prect, pdcolor, pcpath, mem,
	 pinfo);
}
int
gdev_pdf_begin_typed_image(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)
{
    return pdf_begin_typed_image((gx_device_pdf *)dev, pis, pmat, pic, prect,
				 pdcolor, pcpath, mem, pinfo,
				 PDF_IMAGE_DEFAULT);
}

/* ---------------- All images ---------------- */

/* Process the next piece of an image. */
private int
pdf_image_plane_data(gx_image_enum_common_t * info,
		     const gx_image_plane_t * planes, int height,
		     int *rows_used)
{
    gx_device_pdf *pdev = (gx_device_pdf *)info->dev;
    pdf_image_enum *pie = (pdf_image_enum *) info;
    int h = height;
    int y;
    /****** DOESN'T HANDLE IMAGES WITH VARYING WIDTH PER PLANE ******/
    uint width_bits = pie->width * pie->plane_depths[0];
    /****** DOESN'T HANDLE NON-ZERO data_x CORRECTLY ******/
    uint bcount = (width_bits + 7) >> 3;
    uint ignore;
    int nplanes = pie->num_planes;
    stream *s = pdev->streams.strm;
    long pos = stell(s);
    int code;
    int status = 0;

    if (h > pie->rows_left)
	h = pie->rows_left;
    pie->rows_left -= h;
    for (y = 0; y < h; ++y) {
	if (nplanes > 1) {
	    /*
	     * We flip images in blocks, and each block except the last one
	     * must contain an integral number of pixels.  The easiest way
	     * to meet this condition is for all blocks except the last to
	     * be a multiple of 3 source bytes (guaranteeing an integral
	     * number of 1/2/4/8/12-bit samples), i.e., 3*nplanes flipped
	     * bytes.  This requires a buffer of at least
	     * 3*GS_IMAGE_MAX_COMPONENTS bytes.
	     */
	    int pi;
	    uint count = bcount;
	    uint offset = 0;
#define ROW_BYTES max(200 /*arbitrary*/, 3 * GS_IMAGE_MAX_COMPONENTS)
	    const byte *bit_planes[GS_IMAGE_MAX_COMPONENTS];
	    int block_bytes = ROW_BYTES / (3 * nplanes) * 3;
	    byte row[ROW_BYTES];

	    for (pi = 0; pi < nplanes; ++pi)
		bit_planes[pi] = planes[pi].data + planes[pi].raster * y;
	    while (count) {
		uint flip_count;
		uint flipped_count;

		if (count >= block_bytes) {
		    flip_count = block_bytes;
		    flipped_count = block_bytes * nplanes;
		} else {
		    flip_count = count;
		    flipped_count =
			(width_bits % (block_bytes * 8) * nplanes + 7) >> 3;
		}
		image_flip_planes(row, bit_planes, offset, flip_count,
				  nplanes, pie->plane_depths[0]);
		status = sputs(pie->writer.binary.strm, row, flipped_count,
			       &ignore);
		if (status < 0)
		    break;
		offset += flip_count;
		count -= flip_count;
	    }
	} else {
	    status = sputs(pie->writer.binary.strm,
			   planes[0].data + planes[0].raster * y, bcount,
			   &ignore);
	}
	if (status < 0)
	    break;
    }
    *rows_used = h;
    if (status < 0)
	return_error(gs_error_ioerror);
    code = cos_stream_add_since(pie->writer.data, pos);
    return (code < 0 ? code : !pie->rows_left);
#undef ROW_BYTES
}

/* Clean up by releasing the buffers. */
private int
pdf_image_end_image_data(gx_image_enum_common_t * info, bool draw_last,
			 bool do_image)
{
    gx_device_pdf *pdev = (gx_device_pdf *)info->dev;
    pdf_image_enum *pie = (pdf_image_enum *)info;
    int height = pie->writer.height;
    int data_height = height - pie->rows_left;
    int code;

    if (pie->writer.pres)
	((pdf_x_object_t *)pie->writer.pres)->data_height = data_height;
    else
	pdf_put_image_matrix(pdev, &pie->mat,
			     (height == 0 || data_height == 0 ? 1.0 :
			      (double)data_height / height));
    code = pdf_end_image_binary(pdev, &pie->writer, data_height);
    if (code < 0)
	return code;
    code = pdf_end_write_image(pdev, &pie->writer);
    switch (code) {
    default:
	return code;	/* error */
    case 1:
	code = 0;
	break;
    case 0:
	if (do_image)
	    code = pdf_do_image(pdev, pie->writer.pres, &pie->mat, true);
    }
    gs_free_object(pie->memory, pie, "pdf_end_image");
    return code;
}

/* End a normal image, drawing it. */
private int
pdf_image_end_image(gx_image_enum_common_t * info, bool draw_last)
{
    return pdf_image_end_image_data(info, draw_last, true);
}

/* ---------------- Type 3/3x images ---------------- */

/*
 * For both types of masked images, we create temporary dummy (null) devices
 * that forward the begin_typed_image call to the implementation above.
 */
private int
pdf_make_mxd(gx_device **pmxdev, gx_device *tdev, gs_memory_t *mem)
{
    gx_device *fdev;
    int code = gs_copydevice(&fdev, (const gx_device *)&gs_null_device, mem);

    if (code < 0)
	return code;
    gx_device_set_target((gx_device_forward *)fdev, tdev);
    *pmxdev = fdev;
    return 0;
}

/* End the mask of an ImageType 3 image, not drawing it. */
private int
pdf_image_end_image_object(gx_image_enum_common_t * info, bool draw_last)
{
    return pdf_image_end_image_data(info, draw_last, false);
}

/* ---------------- Type 3 images ---------------- */

/* Implement the mask image device. */
private dev_proc_begin_typed_image(pdf_mid_begin_typed_image);
private int
pdf_image3_make_mid(gx_device **pmidev, gx_device *dev, int width, int height,
		    gs_memory_t *mem)
{
    int code = pdf_make_mxd(pmidev, dev, mem);

    if (code < 0)
	return code;
    set_dev_proc(*pmidev, begin_typed_image, pdf_mid_begin_typed_image);
    return 0;
}
private int
pdf_mid_begin_typed_image(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)
{
    /* The target of the null device is the pdfwrite device. */
    gx_device_pdf *const pdev = (gx_device_pdf *)
	((gx_device_null *)dev)->target;
    int code = pdf_begin_typed_image
	(pdev, pis, pmat, pic, prect, pdcolor, pcpath, mem, pinfo,
	 PDF_IMAGE_TYPE3_MASK);

    if (code < 0)
	return code;
    if ((*pinfo)->procs != &pdf_image_object_enum_procs) {
	/* We couldn't handle the mask image.  Bail out. */
	/* (This is never supposed to happen.) */
	return_error(gs_error_rangecheck);
    }
    return code;
}

/* Implement the mask clip device. */
private int
pdf_image3_make_mcde(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,
		     gx_device **pmcdev, gx_device *midev,
		     gx_image_enum_common_t *pminfo,
		     const gs_int_point *origin)
{
    int code = pdf_make_mxd(pmcdev, midev, mem);
    pdf_image_enum *pmie;
    pdf_image_enum *pmce;
    cos_stream_t *pmcs;

    if (code < 0)
	return code;
    code = pdf_begin_typed_image
	((gx_device_pdf *)dev, pis, pmat, pic, prect, pdcolor, pcpath, mem,
	 pinfo, PDF_IMAGE_TYPE3_DATA);
    if (code < 0)
	return code;
    /* Add the /Mask entry to the image dictionary. */
    if ((*pinfo)->procs != &pdf_image_enum_procs) {
	/* We couldn't handle the image.  Bail out. */
	gx_image_end(*pinfo, false);
	gs_free_object(mem, *pmcdev, "pdf_image3_make_mcde");
	return_error(gs_error_rangecheck);
    }
    pmie = (pdf_image_enum *)pminfo;
    pmce = (pdf_image_enum *)(*pinfo);
    pmcs = (cos_stream_t *)pmce->writer.pres->object;
    return cos_dict_put_c_key_object(cos_stream_dict(pmcs), "/Mask",
				     pmie->writer.pres->object);
}

/* ---------------- Type 3x images ---------------- */

/* Implement the mask image device. */
private int
pdf_image3x_make_mid(gx_device **pmidev, gx_device *dev, int width, int height,
		     int depth, gs_memory_t *mem)
{
    int code = pdf_make_mxd(pmidev, dev, mem);

    if (code < 0)
	return code;
    set_dev_proc(*pmidev, begin_typed_image, pdf_mid_begin_typed_image);
    return 0;
}

/* Implement the mask clip device. */
private int
pdf_image3x_make_mcde(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,
		      gx_device **pmcdev, gx_device *midev[2],
		      gx_image_enum_common_t *pminfo[2],
		      const gs_int_point origin[2],
		      const gs_image3x_t *pim)
{
    int code;
    pdf_image_enum *pmie;
    pdf_image_enum *pmce;
    cos_stream_t *pmcs;
    int i;
    const gs_image3x_mask_t *pixm;

    if (midev[0]) {
	if (midev[1])
	    return_error(gs_error_rangecheck);
	i = 0, pixm = &pim->Opacity;
    } else if (midev[1])
	i = 1, pixm = &pim->Shape;
    else
	return_error(gs_error_rangecheck);
    code = pdf_make_mxd(pmcdev, midev[i], mem);
    if (code < 0)
	return code;
    code = pdf_begin_typed_image
	((gx_device_pdf *)dev, pis, pmat, pic, prect, pdcolor, pcpath, mem,
	 pinfo, PDF_IMAGE_TYPE3_DATA);
    if (code < 0)
	return code;
    if ((*pinfo)->procs != &pdf_image_enum_procs) {
	/* We couldn't handle the image.  Bail out. */
	gx_image_end(*pinfo, false);
	gs_free_object(mem, *pmcdev, "pdf_image3x_make_mcde");
	return_error(gs_error_rangecheck);
    }
    pmie = (pdf_image_enum *)pminfo[i];
    pmce = (pdf_image_enum *)(*pinfo);
    pmcs = (cos_stream_t *)pmce->writer.pres->object;
    /*
     * Add the SMask entry to the image dictionary, and, if needed,
     * the Matte entry to the mask dictionary.
     */
    if (pixm->has_Matte) {
	int num_components =
	    gs_color_space_num_components(pim->ColorSpace);

	code = cos_dict_put_c_key_floats(
				(cos_dict_t *)pmie->writer.pres->object,
				"/Matte", pixm->Matte,
				num_components);
	if (code < 0)
	    return code;
    }
    return cos_dict_put_c_key_object(cos_stream_dict(pmcs), "/SMask",
				     pmie->writer.pres->object);
}
