/* Copyright (C) 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: gdevpdfj.c,v 1.7.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* Image-writing utilities for pdfwrite driver */
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gserrors.h"
#include "gdevpdfx.h"
#include "gdevpdfg.h"
#include "gdevpdfo.h"
#include "gxcspace.h"
#include "gsiparm4.h"

#define CHECK(expr)\
  BEGIN if ((code = (expr)) < 0) return code; END

/* GC descriptors */
public_st_pdf_image_writer();

/* ---------------- Image stream dictionaries ---------------- */

const pdf_image_names_t pdf_image_names_full = {
    { PDF_COLOR_SPACE_NAMES },
    { PDF_FILTER_NAMES },
    PDF_IMAGE_PARAM_NAMES
};
const pdf_image_names_t pdf_image_names_short = {
    { PDF_COLOR_SPACE_NAMES_SHORT },
    { PDF_FILTER_NAMES_SHORT },
    PDF_IMAGE_PARAM_NAMES_SHORT
};

/* Store the values of image parameters other than filters. */
/* pdev is used only for updating procsets. */
/* pcsvalue is not used for masks. */
private int
pdf_put_pixel_image_values(cos_dict_t *pcd, gx_device_pdf *pdev,
			   const gs_pixel_image_t *pim,
			   const gs_color_space *pcs,
			   const pdf_image_names_t *pin,
			   const cos_value_t *pcsvalue)
{
    int num_components;
    float indexed_decode[2];
    const float *default_decode = NULL;
    int code;

    if (pcs) {
	CHECK(cos_dict_put_c_key(pcd, pin->ColorSpace, pcsvalue));
	pdf_color_space_procsets(pdev, pcs);
	num_components = gs_color_space_num_components(pcs);
	if (gs_color_space_get_index(pcs) == gs_color_space_index_Indexed) {
	    indexed_decode[0] = 0;
	    indexed_decode[1] = (1 << pim->BitsPerComponent) - 1;
	    default_decode = indexed_decode;
	}
    } else
	num_components = 1;
    CHECK(cos_dict_put_c_key_int(pcd, pin->Width, pim->Width));
    CHECK(cos_dict_put_c_key_int(pcd, pin->Height, pim->Height));
    CHECK(cos_dict_put_c_key_int(pcd, pin->BitsPerComponent,
				 pim->BitsPerComponent));
    {
	int i;

	for (i = 0; i < num_components * 2; ++i)
	    if (pim->Decode[i] !=
		(default_decode ? default_decode[i] : i & 1)
		)
		break;
	if (i < num_components * 2) {
	    cos_array_t *pca =
		cos_array_alloc(pdev, "pdf_put_pixel_image_values(decode)");

	    if (pca == 0)
		return_error(gs_error_VMerror);
	    for (i = 0; i < num_components * 2; ++i)
		CHECK(cos_array_add_real(pca, pim->Decode[i]));
	    CHECK(cos_dict_put_c_key_object(pcd, pin->Decode,
					    COS_OBJECT(pca)));
	}
    }
    if (pim->Interpolate)
	CHECK(cos_dict_put_c_strings(pcd, pin->Interpolate, "true"));
    return 0;
}
int
pdf_put_image_values(cos_dict_t *pcd, gx_device_pdf *pdev,
		     const gs_pixel_image_t *pic,
		     const pdf_image_names_t *pin,
		     const cos_value_t *pcsvalue)
{
    const gs_color_space *pcs = pic->ColorSpace;
    int code;

    switch (pic->type->index) {
    case 1: {
	const gs_image1_t *pim = (const gs_image1_t *)pic;

	if (pim->ImageMask) {
	    CHECK(cos_dict_put_c_strings(pcd, pin->ImageMask, "true"));
	    pdev->procsets |= ImageB;
	    pcs = NULL;
	}
    }
	break;
    case 3: {
	/*
	 * Clients must treat this as a special case: they must call
	 * pdf_put_image_values for the MaskDict separately, and must
	 * add the Mask entry to the main image stream (dictionary).
	 */
	/*const gs_image3_t *pim = (const gs_image3_t *)pic;*/

	/* Masked images are only supported starting in PDF 1.3. */
	if (pdev->CompatibilityLevel < 1.3)
	    return_error(gs_error_rangecheck);
    }
	break;
    case 4: {
	const gs_image4_t *pim = (const gs_image4_t *)pic;
	int num_components = gs_color_space_num_components(pcs);
	cos_array_t *pca;
	int i;
    
	/* Masked images are only supported starting in PDF 1.3. */
	if (pdev->CompatibilityLevel < 1.3)
	    return_error(gs_error_rangecheck);
	pca = cos_array_alloc(pdev, "pdf_put_image_values(mask)");
	if (pca == 0)
	    return_error(gs_error_VMerror);
	for (i = 0; i < num_components; ++i) {
	    int lo, hi;

	    if (pim->MaskColor_is_range)
		lo = pim->MaskColor[i * 2], hi = pim->MaskColor[i * 2 + 1];
	    else
		lo = hi = pim->MaskColor[i];
	    CHECK(cos_array_add_int(pca, lo));
	    CHECK(cos_array_add_int(pca, hi));
	}
	CHECK(cos_dict_put_c_key_object(pcd, "/Mask", COS_OBJECT(pca)));
    }
	break;
    default:
	return_error(gs_error_rangecheck);
    }
    return pdf_put_pixel_image_values(pcd, pdev, pic, pcs, pin, pcsvalue);
}

/* Store filters for an image. */
/* Currently this only saves parameters for CCITTFaxDecode. */
int
pdf_put_image_filters(cos_dict_t *pcd, gx_device_pdf *pdev,
		      const psdf_binary_writer * pbw,
		      const pdf_image_names_t *pin)
{
    return pdf_put_filters(pcd, pdev, pbw->strm, &pin->filter_names);
}

/* ---------------- Image writing ---------------- */

/*
 * Fill in the image parameters for a device space bitmap.
 * PDF images are always specified top-to-bottom.
 * data_h is the actual number of data rows, which may be less than h.
 */
void
pdf_make_bitmap_matrix(gs_matrix * pmat, int x, int y, int w, int h,
		       int h_actual)
{
    pmat->xx = w;
    pmat->xy = 0;
    pmat->yx = 0;
    pmat->yy = -h_actual;
    pmat->tx = x;
    pmat->ty = y + h;
}

/*
 * Put out the gsave and matrix for an image.  y_scale adjusts the matrix
 * for images that end prematurely.
 */
void
pdf_put_image_matrix(gx_device_pdf * pdev, const gs_matrix * pmat,
		     floatp y_scale)
{
    gs_matrix imat;

    gs_matrix_translate(pmat, 0.0, 1.0 - y_scale, &imat);
    gs_matrix_scale(&imat, 1.0, y_scale, &imat);
    pdf_put_matrix(pdev, "q ", &imat, "cm\n");
}

/* Put out a reference to an image resource. */
int
pdf_do_image(gx_device_pdf * pdev, const pdf_resource_t * pres,
	     const gs_matrix * pimat, bool in_contents)
{
    if (in_contents) {
	int code = pdf_open_contents(pdev, PDF_IN_STREAM);

	if (code < 0)
	    return code;
    }
    if (pimat) {
	/* Adjust the matrix to account for short images. */
	const pdf_x_object_t *const pxo = (const pdf_x_object_t *)pres;
	double scale = (double)pxo->data_height / pxo->height;

	pdf_put_image_matrix(pdev, pimat, scale);
    }
    pprintld1(pdev->strm, "/R%ld Do\nQ\n", pdf_resource_id(pres));
    return 0;
}

/* ------ Begin / finish ------ */

/*
 * Begin writing an image, creating the resource if not in-line and
 * pres == 0, and setting up the binary writer.
 */
int
pdf_begin_write_image(gx_device_pdf * pdev, pdf_image_writer * piw,
		      gx_bitmap_id id, int w, int h, pdf_resource_t *pres,
		      bool in_line)
{
    /* Patch pdev->strm so the right stream gets into the writer. */
    stream *save_strm = pdev->strm;
    int code;

    if (in_line) {
	piw->pres = 0;
	piw->pin = &pdf_image_names_short;
	piw->data = cos_stream_alloc(pdev, "pdf_begin_image_data");
	if (piw->data == 0)
	    return_error(gs_error_VMerror);
	piw->end_string = " Q";
    } else {
	pdf_x_object_t *pxo;
	cos_stream_t *pcos;

	if (pres == 0) {
	    code = pdf_alloc_resource(pdev, resourceXObject, id, &piw->pres,
				      0L);
	    if (code < 0)
		return code;
	    cos_become(piw->pres->object, cos_type_stream);
	} else {
	    /* Resource already allocated (Mask for ImageType 3 image). */
	    piw->pres = pres;
	}
	piw->pres->rid = id;
	piw->pin = &pdf_image_names_full;
	pxo = (pdf_x_object_t *)piw->pres;
	pcos = (cos_stream_t *)pxo->object;
	CHECK(cos_dict_put_c_strings(cos_stream_dict(pcos), "/Subtype",
				     "/Image"));
	pxo->width = w;
	pxo->height = h;
	/* Initialize data_height for the benefit of copy_{mono,color}. */
	pxo->data_height = h;
	piw->data = pcos;
    }
    piw->height = h;
    pdev->strm = pdev->streams.strm;
    code = psdf_begin_binary((gx_device_psdf *) pdev, &piw->binary);
    pdev->strm = save_strm;
    return code;
}

/* Begin writing the image data, setting up the dictionary and filters. */
int
pdf_begin_image_data(gx_device_pdf * pdev, pdf_image_writer * piw,
		     const gs_pixel_image_t * pim, const cos_value_t *pcsvalue)
{
    cos_dict_t *pcd = cos_stream_dict(piw->data);
    int code = pdf_put_image_values(pcd, pdev, pim, piw->pin, pcsvalue);

    if (code >= 0)
	code = pdf_put_image_filters(pcd, pdev, &piw->binary, piw->pin);
    if (code < 0) {
	if (!piw->pres)
	    COS_FREE(piw->data, "pdf_begin_image_data");
	piw->data = 0;
    }
    return code;
}

/* Finish writing the binary image data. */
int
pdf_end_image_binary(gx_device_pdf *pdev, pdf_image_writer *piw, int data_h)
{
    long pos = stell(pdev->streams.strm);  /* piw->binary.target */
    int code;

    psdf_end_binary(&piw->binary);
    code = cos_stream_add_since(piw->data, pos);
    if (code < 0)
	return code;
    /* If the image ended prematurely, update the Height. */
    if (data_h != piw->height)
	code = cos_dict_put_c_key_int(cos_stream_dict(piw->data),
				      piw->pin->Height, data_h);
    return code;
}

/*
 * Finish writing an image.  If in-line, write the BI/dict/ID/data/EI and
 * return 1; if a resource, write the resource definition and return 0.
 */
int
pdf_end_write_image(gx_device_pdf * pdev, pdf_image_writer * piw)
{
    pdf_resource_t *pres = piw->pres;

    if (pres) {			/* image resource */
	if (!pres->named) {	/* named objects are written at the end */
	    cos_write_object(pres->object, pdev);
	    cos_release(pres->object, "pdf_end_write_image");
	}
	return 0;
    } else {			/* in-line image */
	stream *s = pdev->strm;

	stream_puts(s, "BI\n");
	cos_stream_elements_write(piw->data, pdev);
	stream_puts(s, (pdev->binary_ok ? "ID " : "ID\n"));
	cos_stream_contents_write(piw->data, pdev);
	pprints1(s, "\nEI%s\n", piw->end_string);
	COS_FREE(piw->data, "pdf_end_write_image");
	return 1;
    }
}

/* ------ Copy data ------ */

/* Copy the data for a mask or monobit bitmap. */
int
pdf_copy_mask_bits(stream *s, const byte *base, int sourcex, int raster,
		   int w, int h, byte invert)
{
    int yi;

    for (yi = 0; yi < h; ++yi) {
	const byte *data = base + yi * raster + (sourcex >> 3);
	int sbit = sourcex & 7;

	if (sbit == 0) {
	    int nbytes = (w + 7) >> 3;
	    int i;

	    for (i = 0; i < nbytes; ++data, ++i)
		sputc(s, *data ^ invert);
	} else {
	    int wleft = w;
	    int rbit = 8 - sbit;

	    for (; wleft + sbit > 8; ++data, wleft -= 8)
		sputc(s, ((*data << sbit) + (data[1] >> rbit)) ^ invert);
	    if (wleft > 0)
		sputc(s, ((*data << sbit) ^ invert) &
		      (byte) (0xff00 >> wleft));
	}
    }
    return 0;
}

/* Copy the data for a colored image (device pixels). */
int
pdf_copy_color_bits(stream *s, const byte *base, int sourcex, int raster,
		    int w, int h, int bytes_per_pixel)
{
    int yi;

    for (yi = 0; yi < h; ++yi) {
	uint ignore;

	sputs(s, base + sourcex * bytes_per_pixel + yi * raster,
	      w * bytes_per_pixel, &ignore);
    }
    return 0;
}
