/*---------------------------------------------------------------------------*
 |              PDFlib - A library for generating PDF on the fly             |
 +---------------------------------------------------------------------------+
 | Copyright (c) 1997-2007 Thomas Merz and PDFlib GmbH. All rights reserved. |
 +---------------------------------------------------------------------------+
 |                                                                           |
 |    This software is subject to the PDFlib license. It is NOT in the       |
 |    public domain. Extended versions and commercial licenses are           |
 |    available, please check http://www.pdflib.com.                         |
 |                                                                           |
 *---------------------------------------------------------------------------*/

/* $Id: p_image.c,v 1.318.2.31 2009/03/03 21:25:20 kurt Exp $
 *
 * PDFlib image routines
 *
 */

#define P_IMAGE_C

#include "p_intern.h"
#include "p_color.h"
#include "p_defopt.h"
#include "p_font.h"
#include "p_image.h"
#include "p_layer.h"
#include "p_tagged.h"

static void
pdf_init_image_struct(PDF *p, pdf_image *image)
{
    (void) p;

    /********** option variables *************/
    image->verbose      = p->debug[(int) 'i'];
    image->verbose      = pdf_get_errorpolicy(p, NULL, image->verbose);
    image->bitreverse   = pdc_false;
    image->bpc          = pdc_undef;
    image->components   = pdc_undef;
    image->height_pixel = pdc_undef;
    image->ignoremask   = pdc_false;
    image->ignoreorient = pdc_false;
    image->doinline     = pdc_false;
    image->interpolate  = pdc_false;
    image->invert       = pdc_false;
    image->jpegoptimize = pdc_true;
    image->passthrough  = pdc_undef;
    image->K            = 0;
    image->imagemask    = pdc_false;
    image->mask         = pdc_undef;
    image->ri           = AutoIntent;
    image->page         = 1;
    image->reference    = pdf_ref_direct;
    image->width_pixel  = pdc_undef;
    image->topdown_save = pdc_false;
    image->iconname     = (char *) NULL;
    /*****************************************/

    image->orientation  = 1;
    image->transparent  = pdc_false;
    image->compression  = pdf_comp_none;
    image->predictor	= pred_default;
    image->in_use       = pdc_false;
    image->corrupt      = pdc_false;
    image->fp           = (pdc_file *) NULL;
    image->filename	= (char *) NULL;
    image->params	= (char *) NULL;
    image->dpi_x	= 0;
    image->dpi_y	= 0;
    image->strips	= 1;
    image->rowsperstrip	= 1;
    image->colorspace   = pdc_undef;
    image->dochandle    = pdc_undef;        /* this means "not a PDI page" */
    image->use_raw	= pdc_false;
    image->pixelmode	= pdc_undef;
    image->type		= pdf_img_auto;
    image->transval[0]	= 0;
    image->transval[1]	= 0;
    image->transval[2]	= 0;
    image->transval[3]	= 0;


    /********* image-type specific stuff *****/
    /* This is ugly, but we must do it here since both the TIFF and JPEG
     * modules are affected.
     */
    image->info.jpeg.jpegifoffset = 0L;
}

void
pdf_init_images(PDF *p)
{
    int im;

    p->images_capacity = IMAGES_CHUNKSIZE;

    p->images = (pdf_image *)
    	pdc_malloc(p->pdc,
	    sizeof(pdf_image) * p->images_capacity, "pdf_init_images");

    for (im = 0; im < p->images_capacity; im++)
	pdf_init_image_struct(p, &(p->images[im]));
}

void
pdf_grow_images(PDF *p)
{
    int im;

    p->images = (pdf_image *) pdc_realloc(p->pdc, p->images,
	sizeof(pdf_image) * 2 * p->images_capacity, "pdf_grow_images");

    for (im = p->images_capacity; im < 2 * p->images_capacity; im++)
	pdf_init_image_struct(p, &(p->images[im]));
    for (im = 0; im < p->images_capacity; im++)
	p->images[im].src.private_data = &(p->images[im]);

    p->images_capacity *= 2;
}

void
pdf_cleanup_image(PDF *p, int im)
{
    pdf_image *image = &p->images[im];

    /* clean up parameter string if necessary */
    if (image->params)
    {
        pdc_free(p->pdc, image->params);
        image->params = NULL;
    }

    if (image->filename)
    {
        pdc_free(p->pdc, image->filename);
        image->filename = NULL;
    }

    if (image->fp)
    {
        pdc_fclose(image->fp);
        image->fp = NULL;
    }

    if (image->iconname)
    {
        pdc_free(p->pdc, image->iconname);
        image->iconname = NULL;
    }



    /* type-specific cleanups */
    if (image->type == pdf_img_gif)
	pdf_cleanup_gif(p, image);

    if (image->type == pdf_img_jpeg)
        pdf_cleanup_jpeg(p, image);

    /* free the image slot and prepare for next use */
    pdf_init_image_struct(p, image);
}

void
pdf_cleanup_images(PDF *p)
{
    int im;

    if (!p->images)
	return;

    /* Free images which the caller left open */

    /* When we think of inter-document survival of images,
    ** we MUST NOT FORGET that the current TIFF algorithm
    ** depends on contiguous image slots for the image strips!
    */
    for (im = 0; im < p->images_capacity; im++)
    {
        if (p->images[im].in_use)              /* found used slot */
	    pdf_cleanup_image(p, im);	/* free image descriptor */
    }

    pdc_free(p->pdc, p->images);
    p->images = NULL;
}

void
pdf_init_xobjects(PDF *p)
{
    int idx;

    p->xobjects_number = 0;

    if (p->xobjects == (pdf_xobject *) 0)
    {
	p->xobjects_capacity = XOBJECTS_CHUNKSIZE;

	p->xobjects = (pdf_xobject *)
	    pdc_malloc(p->pdc, sizeof(pdf_xobject) * p->xobjects_capacity,
	    "pdf_init_xobjects");
    }

    for (idx = 0; idx < p->xobjects_capacity; idx++)
	p->xobjects[idx].flags = 0;
}

int
pdf_new_xobject(PDF *p, pdf_xobj_type type, pdc_id obj_id)
{
    static const char fn[] = "pdf_new_xobject";
    int i, slot = p->xobjects_number++;

    if (slot == p->xobjects_capacity)
    {
	p->xobjects = (pdf_xobject *) pdc_realloc(p->pdc, p->xobjects,
	    sizeof(pdf_xobject) * 2 * p->xobjects_capacity, fn);

	for (i = p->xobjects_capacity; i < 2 * p->xobjects_capacity; i++)
	    p->xobjects[i].flags = 0;

	p->xobjects_capacity *= 2;
    }

    if (obj_id == PDC_NEW_ID)
	obj_id = pdc_begin_obj(p->out, PDC_NEW_ID);

    p->xobjects[slot].obj_id = obj_id;
    p->xobjects[slot].type = type;
    p->xobjects[slot].flags = xobj_flag_used;

    return slot;
}

pdc_id
pdf_get_xobject(PDF *p, int im)
{
    if (im >= 0 && im < p->images_capacity)
    {
        pdf_image *img = &p->images[im];

        if (img->in_use)
            return p->xobjects[img->no].obj_id;
    }
    return PDC_BAD_ID;
}

void
pdf_write_xobjects(PDF *p)
{
    if (p->xobjects_number > 0)
    {
	pdc_bool hit = pdc_false;
	int i;

	for (i = 0; i < p->xobjects_number; ++i)
	{
	    if (p->xobjects[i].flags & xobj_flag_write)
	    {
		if (!hit)
		{
		    pdc_puts(p->out, "/XObject");
		    pdc_begin_dict(p->out);
		    hit = pdc_true;
		}

		pdc_printf(p->out, "/I%d", i);
		pdc_objref(p->out, "", p->xobjects[i].obj_id);
		p->xobjects[i].flags &= ~xobj_flag_write;
	    }
	}

	if (hit)
	    pdc_end_dict(p->out);
    }
}

void
pdf_get_page_xobjects(PDF *p, pdf_reslist *rl)
{
    int i;

    for (i = 0; i < p->xobjects_number; i++) {
	if (p->xobjects[i].flags & xobj_flag_write) {
	    p->xobjects[i].flags &= ~xobj_flag_write;
	    pdf_add_reslist(p, rl, i);
	}
    }
}

void
pdf_mark_page_xobject(PDF *p, int n)
{
    p->xobjects[n].flags |= xobj_flag_write;
}

void
pdf_cleanup_xobjects(PDF *p)
{
    if (p->xobjects) {
	pdc_free(p->pdc, p->xobjects);
	p->xobjects = NULL;
    }
}


/* ---------------------------- put image ----------------------------------- */

void
pdf_put_inline_image(PDF *p, int im)
{
    static const char *fn = "pdf_put_inline_image";
    pdf_image	*image;
    pdc_matrix m;
    PDF_data_source *src;
    int		i;

    image = &p->images[im];

    /* Image object */

    image->no = -1;

    pdf__save(p);

    pdc_scale_matrix(image->width, image->height, &m);

    pdf_concat_raw(p, &m);

    pdc_puts(p->out, "BI");

    pdc_printf(p->out, "/W %d", (int) image->width);
    pdc_printf(p->out, "/H %d", (int) image->height);

    /* Acrobat 7 and 8 require /BPC even for image masks */
    pdc_printf(p->out, "/BPC %d", image->bpc);

    if (image->imagemask == pdc_true) {
	pdc_puts(p->out, "/IM true");

    } else if (image->colorspace != pdc_undef) {

	switch (p->colorspaces[image->colorspace].type) {
	    case DeviceGray:
		pdc_printf(p->out, "/CS/G");
		break;

	    case DeviceRGB:
		pdc_printf(p->out, "/CS/RGB");
		break;

	    case DeviceCMYK:
		pdc_printf(p->out, "/CS/CMYK");
		break;

	    default:
		pdc_error(p->pdc, PDF_E_INT_BADCS, fn,
		    pdc_errprintf(p->pdc, "%d", image->colorspace),
		    pdc_errprintf(p->pdc, "%d",
		    	(int) p->colorspaces[image->colorspace].type),
		    0);
		break;
	}
    }

    if (image->compression != pdf_comp_none) {
	pdc_printf(p->out, "/F/%s",
            pdc_get_keyword(image->compression, pdf_shortfilter_pdfkeylist));
    }

    /* prepare precompressed (raw) image data */
    if (image->use_raw &&
        (image->params ||
	 image->predictor != pred_default ||
         image->compression == pdf_comp_ccitt)) {

	pdc_printf(p->out, "/DP[<<");

        /* write EarlyChange */
        if (image->params)
            pdc_puts(p->out, image->params);

        if (image->compression == pdf_comp_ccitt) {
            if (image->K != 0)
                pdc_printf(p->out, "/K %d", image->K);
        }

        if (image->compression == pdf_comp_flate ||
            image->compression == pdf_comp_lzw) {
	    if (image->predictor != pred_default) {
		pdc_printf(p->out, "/Predictor %d", (int) image->predictor);
		pdc_printf(p->out, "/Columns %d", (int) image->width);
		if (image->bpc != 8)
		    pdc_printf(p->out, "/BitsPerComponent %d", image->bpc);

		if (image->components != 1)	/* 1 is default */
		    pdc_printf(p->out, "/Colors %d", image->components);
	    }
	}

        if (image->compression == pdf_comp_ccitt) {
	    if ((int) image->width != 1728)	/* CCITT default width */
		pdc_printf(p->out, "/Columns %d", (int) image->width);

	    /* /Rows is optional, but it helps in certain cases with damaged
	     * CCITT compressed image data
	     */
	    pdc_printf(p->out, "/Rows %d", (int) fabs(image->height));
	}
	pdc_puts(p->out, ">>]");		/* DecodeParms dict and array */
    }

    if (image->ri != AutoIntent) {
        pdc_printf(p->out, "/Intent/%s",
            pdc_get_keyword(image->ri, pdf_renderingintent_pdfkeylist));
    }

    if (image->interpolate) {
        pdc_puts(p->out, "/I true");
    }

    if (image->invert) {
	pdc_puts(p->out, "/D[1 0");
	for (i = 1; i < image->components; i++)
	    pdc_puts(p->out, " 1 0");
	pdc_puts(p->out, "]ID\n");

    } else {
	pdc_puts(p->out, " ID\n");
    }

    /* Write the actual image data to the content stream */

    src = &image->src;

    /* We can't use pdf_copy_stream() here because it automatically
     * generates a stream object, which is not correct for inline
     * image data.
     */
    if (src->init)
	src->init(p, src);

    while (src->fill(p, src))
	pdc_write(p->out, src->next_byte, src->bytes_available);

    if (src->terminate)
	src->terminate(p, src);

    /* Acrobat requires whitespace between image data and "EI" */
    pdc_puts(p->out, "\nEI\n");

    pdf__restore(p);

    /* Do the equivalent of PDF_close_image() since the image handle
     * cannot be re-used anyway.
     */
    pdf_cleanup_image(p, im);
}

void
pdf_put_image(PDF *p, int im, pdc_bool firststrip, pdc_bool checkcontentstream)
{
    static const char *fn = "pdf_put_image";
    pdc_bool logg3 = pdc_logg_is_enabled(p->pdc, 3, trc_image);
    pdc_id	length_id;
    pdf_image	*image;
    int		i;
    pdf_compression	compression;

    image = &p->images[im];

    if (logg3)
        pdc_logg(p->pdc, "\t\t\tput image %d to PDF file ...\n", im);

    /* Images may also be written to the output before the first page */
    if (checkcontentstream && PDF_GET_STATE(p) == pdf_state_page)
	pdf_end_contents_section(p);



    pdc_logg_cond(p->pdc, 2, trc_image,
                       "\tpdf_put_image:\n"
                       "\t\t\tim = %d\n"
                       "\t\t\timage->colorspace = %d\n",
		       im,
		       image->colorspace);

    if (image->colorspace != pdc_undef)
        pdc_logg_cond(p->pdc, 2, trc_image,
                      "\t\t\tcolor space type = %d\n",
		      (int) p->colorspaces[image->colorspace].type);

    /* Image object */

    image->no = pdf_new_xobject(p, image_xobject, PDC_NEW_ID);

    pdc_begin_dict(p->out); 		/* XObject */

    pdc_puts(p->out, "/Subtype/Image\n");

    pdc_printf(p->out, "/Width %d\n", (int) image->width);
    pdc_printf(p->out, "/Height %d\n", (int) fabs(image->height));

    /*
     * Transparency handling
     */

    /* Masking by color: single transparent color value */
    if (image->transparent && image->colorspace != pdc_undef) {
	pdf_colorspace *cs = &p->colorspaces[image->colorspace];

	switch (cs->type) {
	    case Indexed:
	    case DeviceGray:
	    pdc_printf(p->out,"/Mask[%d %d]\n",
		(int) image->transval[0], (int) image->transval[0]);
	    break;


	    case DeviceRGB:
	    pdc_printf(p->out,"/Mask[%d %d %d %d %d %d]\n",
		(int) image->transval[0], (int) image->transval[0],
		(int) image->transval[1], (int) image->transval[1],
		(int) image->transval[2], (int) image->transval[2]);
	    break;

	    case DeviceCMYK:
	    pdc_printf(p->out,"/Mask[%d %d %d %d %d %d %d %d]\n",
		(int) image->transval[0], (int) image->transval[0],
		(int) image->transval[1], (int) image->transval[1],
		(int) image->transval[2], (int) image->transval[2],
		(int) image->transval[3], (int) image->transval[3]);
	    break;

	    default:
	    pdc_error(p->pdc, PDF_E_INT_BADCS, fn,
		pdc_errprintf(p->pdc, "%d", image->colorspace),
		pdc_errprintf(p->pdc, "%d",
		    (int) p->colorspaces[image->colorspace].type),
		0);
	}

    /* Masking by position: separate bitmap mask */
    } else if (image->mask != pdc_undef && p->images[image->mask].bpc > 1) {
        pdc_objref(p->out, "/SMask",
            p->xobjects[p->images[image->mask].no].obj_id);

    } else if (image->mask != pdc_undef) {
        pdc_objref(p->out, "/Mask",
            p->xobjects[p->images[image->mask].no].obj_id);
    }

    /*
     * /BitsPerComponent is optional for image masks according to the
     * PDF reference, but some viewers require it nevertheless.
     * We must therefore always write it.
     */
    if (image->type != pdf_img_jpeg2000)
        pdc_printf(p->out, "/BitsPerComponent %d\n", image->bpc);

    if (image->imagemask) {
	pdc_puts(p->out, "/ImageMask true\n");
        if (image->type == pdf_img_jpeg2000)
            pdc_puts(p->out, "/SMaskInData 1\n");

    } else if (image->colorspace != pdc_undef) {

	switch (p->colorspaces[image->colorspace].type) {
	    case DeviceGray:
		break;

	    case DeviceRGB:
		break;

	    case DeviceCMYK:
		break;

	    case Indexed:
		break;



	    default:
		pdc_error(p->pdc, PDF_E_INT_BADCS, fn,
		    pdc_errprintf(p->pdc, "%d", image->colorspace),
		    pdc_errprintf(p->pdc, "%d",
		    	(int) p->colorspaces[image->colorspace].type),
		    0);
        }

        pdc_puts(p->out, "/ColorSpace");
        pdf_write_colorspace(p, image->colorspace, pdc_false);
        pdc_puts(p->out, "\n");
    }

    if (image->invert) {
        pdc_puts(p->out, "/Decode[1 0");
        for (i = 1; i < image->components; i++)
            pdc_puts(p->out, " 1 0");
	pdc_end_array(p->out);
    }

    if (image->ri != AutoIntent) {
        pdc_printf(p->out, "/Intent/%s\n",
            pdc_get_keyword(image->ri, pdf_renderingintent_pdfkeylist));
    }

    if (image->interpolate) {
        pdc_puts(p->out, "/Interpolate true\n");
    }

    /* special case: referenced image data instead of direct data */
    if (image->reference != pdf_ref_direct) {

        if (image->compression != pdf_comp_none) {
	    pdc_printf(p->out, "/FFilter[/%s]\n",
                pdc_get_keyword(image->compression, pdf_filter_pdfkeylist));
	}

        if (image->compression == pdf_comp_ccitt) {
	    pdc_puts(p->out, "/FDecodeParms[<<");

	    if ((int) image->width != 1728)	/* CCITT default width */
		pdc_printf(p->out, "/Columns %d", (int) image->width);

	    /* /Rows is optional, but it helps in certain cases with damaged
	     * CCITT compressed image data
	     */
	    pdc_printf(p->out, "/Rows %d", (int) fabs(image->height));

            if (image->K != 0)
                pdc_printf(p->out, "/K %d", image->K);

	    pdc_puts(p->out, ">>]\n");

	}

	if (image->reference == pdf_ref_file) {

	    /* LATER: make image file name platform-neutral:
	     * Change : to / on the Mac
	     * Change \ to / on Windows
	     */
	    pdc_puts(p->out, "/F");
	    pdc_put_pdfstring(p->out, image->filename, 0);
            pdc_puts(p->out, "/Length 0");

	} else if (image->reference == pdf_ref_url) {

	    pdc_puts(p->out, "/F<</FS/URL/F");
	    pdc_put_pdfstring(p->out, image->filename, 0);
	    pdc_puts(p->out, ">>/Length 0");
	}

	pdc_end_dict(p->out);		/* XObject */

	/* We must avoid pdc_begin/end_pdfstream() here in order to
	 * generate a really empty stream.
	 * Note: this doesn't work with AES encryption, but we don't
	 * bother to fix it since file references are no longer
	 * supported anyway.
	 */
	pdc_puts(p->out, "stream\n");	/* dummy image stream */
	pdc_puts(p->out, "endstream\n");

        pdc_end_obj(p->out);                    /* XObject */

	if (PDF_GET_STATE(p) == pdf_state_page)
	    pdf_begin_contents_section(p);

	return;
    }

    compression = image->compression;

    /*
     * Now the (more common) handling of actual image
     * data to be included in the PDF output.
     */

    /* force compression if not a recognized precompressed image format */
    if ((!image->use_raw || compression == pdf_comp_none) &&
	pdc_get_compresslevel(p->out))
        compression = pdf_comp_flate;

    if (compression != pdf_comp_none)
	pdc_printf(p->out, "/Filter/%s\n",
                   pdc_get_keyword(compression, pdf_filter_pdfkeylist));

    /* prepare precompressed (raw) image data; avoid empty DecodeParms */
    if (image->use_raw &&
        (image->params ||
	 image->predictor != pred_default ||
         compression == pdf_comp_ccitt)) {

	pdc_printf(p->out, "/DecodeParms<<");

        /* write EarlyChange */
        if (image->params)
            pdc_puts(p->out, image->params);

        if (compression == pdf_comp_ccitt) {
            if (image->K != 0)
                pdc_printf(p->out, "/K %d", image->K);
        }

        if (compression == pdf_comp_flate || compression == pdf_comp_lzw) {
	    if (image->predictor != pred_default) {
		pdc_printf(p->out, "/Predictor %d", (int) image->predictor);
		pdc_printf(p->out, "/Columns %d", (int) image->width);
		if (image->bpc != 8)
		    pdc_printf(p->out, "/BitsPerComponent %d", image->bpc);

		if (image->components != 1)	/* 1 is default */
		    pdc_printf(p->out, "/Colors %d", image->components);
	    }
	}

        if (compression == pdf_comp_ccitt) {
	    if ((int) image->width != 1728)	/* CCITT default width */
		pdc_printf(p->out, "/Columns %d", (int) image->width);

	    /* /Rows is optional, but it helps in certain cases with damaged
	     * CCITT compressed image data
	     */
	    pdc_printf(p->out, "/Rows %d", (int) fabs(image->height));
	}

	pdc_puts(p->out, ">>\n");		/* DecodeParms dict */
    }




    /* Write the actual image data */
    length_id = pdc_alloc_id(p->out);

    pdc_objref(p->out, "/Length", length_id);
    pdc_end_dict(p->out);		/* XObject */

    /* image data */

    /*
     * We must check "image->compression" here since this describes the
     * actual status of the input data, as opposed to "compression"
     * which describes the desired status of the output data.
     */

    pdf_copy_stream(p, &image->src,
            !image->use_raw || image->compression == pdf_comp_none);

    pdc_end_obj(p->out);	/* XObject */

    pdc_put_pdfstreamlength(p->out, length_id);

    if (p->flush & pdc_flush_content)
	pdc_flush_stream(p->out);

    /*
     * Write colormap information for indexed color spaces
     */
    if (firststrip && image->colorspace != pdc_undef &&
        p->colorspaces[image->colorspace].type == Indexed) {
	pdf_write_colormap(p, image->colorspace);
    }

    if (checkcontentstream && PDF_GET_STATE(p) == pdf_state_page)
	pdf_begin_contents_section(p);

    if (p->flush & pdc_flush_content)
	pdc_flush_stream(p->out);
}


/* ---------------------------- fit image ----------------------------------- */

void
pdf__fit_image(PDF *p, int im, pdc_scalar x, pdc_scalar y, const char *optlist)
{
    pdf_image *image;
    int legal_states;

    pdf_check_handle(p, im, pdc_imagehandle);

    image = &p->images[im];

    if (PDF_GET_STATE(p) == pdf_state_glyph && !pdf_get_t3colorized(p) &&
        image->imagemask == pdc_false)
        legal_states = pdf_state_page | pdf_state_pattern | pdf_state_template;
    else if (PDF_GET_STATE(p) == pdf_state_pattern &&
        pdf_get_shading_painttype(p) == 2 && image->imagemask == pdc_false)
        legal_states = pdf_state_page | pdf_state_glyph | pdf_state_template;
    else
        legal_states = pdf_state_content;
    PDF_CHECK_STATE(p, legal_states);

    if (PDF_GET_STATE(p) == pdf_state_template && im == p->templ)
        pdc_error(p->pdc, PDF_E_TEMPLATE_SELF,
            pdc_errprintf(p->pdc, "%d", im), 0, 0, 0);

    pdc_check_number(p->pdc, "x", x);
    pdc_check_number(p->pdc, "y", y);

    pdf_place_xobject(p, im, x, y, optlist);
}

void
pdf_init_xobject_options(PDF *p, pdf_xobject_options *xo)
{
    xo->adjustpage = pdc_false;
    xo->blind = pdc_false;
    xo->filename = NULL;
    xo->flags = 0;
    xo->imagewarning = p->debug[(int) 'i'];
    xo->ignoreorientation = pdc_false;
    xo->im = -1;
    xo->mask = 0;
    xo->dpi[0] = 0;
    xo->dpi[1] = 0;
    xo->page = 1;
    xo->scale[0] = 1;
    xo->scale[1] = 1;
}

void
pdf_get_xobject_options(PDF *p, pdf_xobject_options *xo, pdc_resopt *resopts)
{
    int inum;

    (void) p;

    if (!(xo->flags & is_block))
    {
        pdc_get_optvalues("adjustpage", resopts, &xo->adjustpage, NULL);

        pdc_get_optvalues("blind", resopts, &xo->blind, NULL);
    }

    if (xo->flags & is_image)
    {
        if (pdc_get_optvalues("ignoreorientation", resopts,
                                                &xo->ignoreorientation, NULL))
            xo->mask |= (1L << xo_ignoreorientation);


        inum = pdc_get_optvalues("dpi", resopts, xo->dpi, NULL);
        if (inum)
        {
            if (inum == 1)
                xo->dpi[1] = xo->dpi[0];
            xo->mask |= (1L << xo_dpi);
        }
    }

    if (xo->flags & is_block)
    {
        if (pdc_get_optvalues("imagewarning", resopts, &xo->imagewarning, NULL))
            xo->mask |= (1L << xo_imagewarning);
    }

    inum = pdc_get_optvalues("scale", resopts, xo->scale, NULL);
    if (inum)
    {
        if (inum == 1)
            xo->scale[1] = xo->scale[0];
        xo->mask |= (1L << xo_scale);
    }
}

/* definitions of fit xobject options */
static const pdc_defopt pdf_fit_xobject_options[] =
{
    PDF_XOBJECT_OPTIONS1
    PDF_XOBJECT_OPTIONS2
    PDF_XOBJECT_OPTIONS3
    PDF_FIT_OPTIONS1
    PDF_FIT_OPTIONS2
    PDC_OPT_TERMINATE
};

pdc_resopt *
pdf_parse_fitxobject_optlist(PDF *p, int im, pdf_xobject_options *xo,
                             pdf_fit_options *fit, const char *optlist)
{
    pdc_resopt *resopts = NULL;
    pdf_image *image = &p->images[im];

    /* initialize */
    pdf_init_xobject_options(p, xo);
    xo->im = im;
    if (p->xobjects[image->no].type == image_xobject)
    {
        xo->flags |= is_image;
        xo->dpi[0] = dpi_internal;
        xo->dpi[1] = dpi_internal;
        xo->ignoreorientation = image->ignoreorient;
    }
    pdf_init_fit_options(p, pdc_false, fit);
    fit->flags |= is_image;

    /* parsing option list */
    if (optlist && strlen(optlist))
    {
        pdc_clientdata data;

        pdf_set_clientdata(p, &data);
        resopts = pdc_parse_optionlist(p->pdc, optlist,
                      pdf_fit_xobject_options, &data, pdc_true);

        pdf_get_xobject_options(p, xo, resopts);
        pdf_get_fit_options(p, pdc_false, fit, resopts);
    }

    return resopts;
}

void
pdf_place_xobject(PDF *p, int im, pdc_scalar x, pdc_scalar y,
                  const char *optlist)
{
    pdf_xobject_options xo;
    pdf_fit_options fit;

    /* initialize */
    pdf_parse_fitxobject_optlist(p, im, &xo, &fit, optlist);
    fit.refpoint[0] = x;
    fit.refpoint[1] = y;

    /* put out xobject */
    if (!xo.blind)
    {
        pdf_end_text(p);
        pdf_begin_contents_section(p);



        pdf__save(p);
    }

    pdf_fit_xobject_internal(p, &xo, &fit, NULL);

    if (!xo.blind)
        pdf__restore(p);
}

void
pdf_fit_xobject_internal(PDF *p, pdf_xobject_options *xo, pdf_fit_options *fit,
                         pdc_matrix *immatrix)
{
    pdc_bool logg3 = pdc_logg_is_enabled(p->pdc, 3, trc_image);
    pdf_image *image = &p->images[xo->im];
    pdf_xobject_options xo_save;
    pdc_rectangle matchrect;
    pdf_fit_options fit_save;
    pdc_matrix m, mm, sm, ctm_clip, ctm_save;
    pdc_vector tmpscale, elemscale, fitscale, purescale;
    pdc_vector elemsize, mirror, shift, relpos;
    pdc_vector polyline[5];
    pdc_box fitbox, clipbox, elembox, redbox;
    pdc_scalar x, y, ss;
    pdc_scalar rowsize = 1, lastratio = 1;
    pdc_scalar dpi_x, dpi_y, tx = 0, ty = 0, boxwidth, boxheight;
    pdc_bool kclip = pdc_false, kcliptiff = pdc_false;
    int indangle, indmirror;
    int is, ip, islast;
    int imageno;

    /* initialize */
    tmpscale.x = 1;
    tmpscale.y = 1;
    if (image->mask != pdc_undef)
    {
        ctm_save = p->curr_ppt->gstate[p->curr_ppt->sl].ctm;
        xo_save = *xo;
        fit_save = *fit;
    }
    else
    {
        pdc_identity_matrix(&ctm_save);
    }

    /* box size */
    boxwidth = fit->boxsize[0];
    boxheight = fit->boxsize[1];

    /* element size */
    elemsize.x = fabs(image->width);
    elemsize.y = fabs(image->height);

    if (logg3)
        pdc_logg(p->pdc,
                 "\t\t\tfitbox size:  width=%g, height=%g\n"
                 "\t\t\telement size: width=%g, height=%g\n",
                 boxwidth, boxheight, elemsize.x, elemsize.y);

    /* clipping */
    if (!kclip)
    {
        kclip = pdf_get_mbox_clipping(p, fit->matchbox, elemsize.x, elemsize.y,
                                      &clipbox);
    }

    if (logg3 && kclip)
        pdc_logg(p->pdc,
                 "\t\t\tclip box: llx=%g, lly=%g, urx=%g, ury=%g\n",
                 clipbox.ll.x, clipbox.ll.y, clipbox.ur.x, clipbox.ur.y);

    /* TIFF image orientation */
    if (image->orientation != 1 && !xo->ignoreorientation)
    {
        /* Tag Orientation     =  1,   2,   3,   4,   5,   6,   7,   8  */
        const int addangle[8]  = {0,   0, 180, 180,  90, 270, 270,  90};
        const int rowmirror[8] = {1,  -1,   1,  -1,  -1,   1,  -1,   1};

        if (logg3)
            pdc_logg(p->pdc, "\t\t\torientation tag: %d\n", image->orientation);

        is = image->orientation - 1;

	fit->orientate += addangle[is];
	if (fit->orientate >= 360)
	    fit->orientate -= 360;
        tmpscale.x = rowmirror[is];

        if (kclip)
        {
            switch (addangle[is])
            {
                default:
                elembox = clipbox;
                break;

                case 90:
                elembox.ll.x = clipbox.ll.y;
                elembox.ll.y = elemsize.x - clipbox.ur.x;
                elembox.ur.x = clipbox.ur.y;
                elembox.ur.y = elemsize.x - clipbox.ll.x;
                break;

                case 180:
                elembox.ll.x = elemsize.x - clipbox.ur.x;
                elembox.ll.y = elemsize.y - clipbox.ur.y;
                elembox.ur.x = elemsize.x - clipbox.ll.x;
                elembox.ur.y = elemsize.y - clipbox.ll.y;
                break;

                case 270:
                elembox.ll.x = elemsize.y - clipbox.ur.y;
                elembox.ll.y = clipbox.ll.x;
                elembox.ur.x = elemsize.y - clipbox.ll.y;
                elembox.ur.y = clipbox.ur.x;
                break;
            }
            clipbox = elembox;

            if (tmpscale.x == -1)
            {
                clipbox.ll.x = elemsize.x - elembox.ur.x;
                clipbox.ur.x = elemsize.x - elembox.ll.x;
            }
        }
    }

    /* Compensate inverted direction handling in TIFFReadRGBAImageOriented() */
    if (!image->use_raw && image->pixelmode == pdc_true)
    {
        tmpscale.y = -1;
        elembox = clipbox;
        clipbox.ll.y = elemsize.y - elembox.ur.y;
        clipbox.ur.y = elemsize.y - elembox.ll.y;
    }

    if (logg3 && kclip)
        pdc_logg(p->pdc,
                 "\t\t\tclip box: llx=%g, lly=%g, urx=%g, ury=%g  "
                 "(corrected)\n",
                 clipbox.ll.x, clipbox.ll.y, clipbox.ur.x, clipbox.ur.y);

    /* image scale */
    elemscale.x = tmpscale.x * xo->scale[0];
    elemscale.y = tmpscale.y * xo->scale[1];

    if (logg3)
        pdc_logg(p->pdc,
                 "\t\t\telement scaling: sx=%g, sy=%g  "
                 "(user, correction mirroring)\n",
                 elemscale.x, elemscale.y);

    /* element size */
    elemsize.x = fabs(clipbox.ur.x - clipbox.ll.x);
    elemsize.y = fabs(clipbox.ur.y - clipbox.ll.y);

    /* calculation of image scale and size */
    if (xo->flags & is_image)
    {
        tmpscale.x = 1.0;
        tmpscale.y = 1.0;
        if (xo->dpi[0] == dpi_internal)
        {
            dpi_x = image->dpi_x;
            dpi_y = image->dpi_y;
            if (dpi_x > 0 && dpi_y > 0)
            {
                tmpscale.x = 72.0 / dpi_x;
                tmpscale.y = 72.0 / dpi_y;
            }
            else if (dpi_x < 0 && dpi_y < 0)
            {
                tmpscale.y = dpi_y / dpi_x;
            }
        }
        else if (xo->dpi[0] > 0)
        {
            tmpscale.x = 72.0 / xo->dpi[0];
            tmpscale.y = 72.0 / xo->dpi[1];
        }

        elemscale.x *= tmpscale.x;
        elemscale.y *= tmpscale.y;
        rowsize = elemscale.y * image->rowsperstrip;

        if (logg3)
            pdc_logg(p->pdc,
               "\t\t\telement scaling: sx=%g, sy=%g  "
               "(relating to 72dpi)\n",
               tmpscale.x, tmpscale.y);
    }

    /* pure scaling without mirroring */
    purescale.x = fabs(elemscale.x);
    purescale.y = fabs(elemscale.y);

    /* element size */
    elemsize.x *= purescale.x;
    elemsize.y *= purescale.y;

    if (logg3)
        pdc_logg(p->pdc,
                 "\t\t\telement size: width=%g, height=%g  (scaled)\n",
                 elemsize.x, elemsize.y);

    if (xo->flags & is_image)
    {
        elemscale.x *= image->width;
        elemscale.y *= image->height;
        lastratio = (elemscale.y / rowsize) - (image->strips - 1);
    }

    /* mirroring */
    indmirror = 0;
    if (elemscale.x < 0 && elemscale.y < 0)
        indmirror = 2;
    else if (elemscale.x < 0)
        indmirror = 1;
    else if (elemscale.y < 0)
        indmirror = 3;

    /* orientation */
    indangle = fit->orientate / 90;
    if (indangle % 2)
    {
        ss = elemsize.x;
        elemsize.x = elemsize.y;
        elemsize.y = ss;
    }

    /* box for fitting */
    fitbox.ll.x = 0;
    fitbox.ll.y = 0;
    fitbox.ur.x = boxwidth;
    fitbox.ur.y = boxheight;

    /* relative position */
    relpos.x = fit->position[0] / 100.0;
    relpos.y = fit->position[1] / 100.0;

    /* calculate image box */
    if (fit->fitmethod == pdc_tauto)
        fit->fitmethod = pdc_meet;
    pdc_place_element(fit->fitmethod, 1.0, &fitbox, &relpos,
                      &elemsize, &relpos, &elembox, &fitscale);

    if (logg3)
        pdc_logg(p->pdc,
                 "\t\t\telement scaling: sx=%g, sy=%g  "
                 "(relating to fitbox)\n",
                 fitscale.x, fitscale.y);

    /* reference point */
    x = fit->refpoint[0];
    y = fit->refpoint[1];


    /* adjust page size */
    if (!xo->blind && xo->adjustpage && PDF_GET_STATE(p) == pdf_state_page)
    {
	pdc_scalar urx, ury, width, height, theight;

        urx = 2 * x + elembox.ur.x;
        ury = 2 * y + elembox.ur.y;
	pdc_transform_point(&p->curr_ppt->gstate[p->curr_ppt->sl].ctm,
			    urx, ury, &width, &theight);
	height = (p->ydirection > 0) ? theight
			: pdf_get_pageheight(p) - p->ydirection * theight;

	if (height < p->ydirection * theight / 2.0)
	    pdc_error(p->pdc, PDF_E_IMAGE_NOADJUST, 0, 0, 0, 0);

        width = fabs(width);
        height = fabs(height);

	if ((width < PDF_ACRO_MINPAGE || width > PDF_ACRO_MAXPAGE ||
	     height < PDF_ACRO_MINPAGE || height > PDF_ACRO_MAXPAGE))
		pdc_warning(p->pdc, PDF_E_PAGE_SIZE_ACRO, 0, 0, 0, 0);

	pdf_set_pagebox(p, pdf_mediabox, 0, 0, width, height);
        pdf_set_pagebox(p, pdf_artbox, 0, 0, 0, 0);
        pdf_set_pagebox(p, pdf_bleedbox, 0, 0, 0, 0);
        pdf_set_pagebox(p, pdf_cropbox, 0, 0, 0, 0);
        pdf_set_pagebox(p, pdf_trimbox, 0, 0, 0, 0);
    }

    /* reference point */
    pdc_translation_matrix(x, y, &m);

    /* optional rotation */
    if (fabs(fit->rotate) > PDC_FLOAT_PREC)
    {
        pdc_rotation_matrix(p->ydirection * fit->rotate, &mm);
        pdc_multiply_matrix(&mm, &m);
    }

    /* output after translation and rotation */
    if (!xo->blind)
    {
        /* new CTM */
        if (fit->showborder ||
            fit->fitmethod == pdc_clip || fit->fitmethod == pdc_slice)
        {
            pdf_concat_raw(p, &m);
            pdc_identity_matrix(&m);
        }

        /* show border */
        if (fit->showborder)
        {
            pdf__rect(p, 0, 0, boxwidth, boxheight);
            pdf__stroke(p);
        }

        /* clipping */
        if (fit->fitmethod == pdc_clip || fit->fitmethod == pdc_slice)
        {
            if (boxwidth < PDC_FLOAT_PREC)
                boxwidth = PDF_ACRO_MAXPAGE;
            if (boxheight < PDC_FLOAT_PREC)
                boxheight = PDF_ACRO_MAXPAGE;
            pdf__rect(p, 0, 0, boxwidth, boxheight);
            pdf__clip(p);

            ctm_clip = p->curr_ppt->gstate[p->curr_ppt->sl].ctm;
            redbox.ll.x = 0;
            redbox.ll.y = 0;
            redbox.ur.x = boxwidth;
            redbox.ur.y = p->ydirection * boxheight;
        }
    }

    /* translation of element box */
    elembox.ll.y *= p->ydirection;
    elembox.ur.y *= p->ydirection;
    pdc_box2polyline(NULL, &elembox, polyline);
    ip = indangle + indmirror;
    if (ip >= 4) ip -= 4;
    tx = polyline[ip].x;
    ty = polyline[ip].y;
    pdc_translation_matrix(tx, ty, &mm);
    pdc_multiply_matrix(&mm, &m);
    boxwidth = elembox.ur.x - elembox.ll.x;
    boxheight = elembox.ur.y - elembox.ll.y;

    /* orientation of image */
    if (fit->orientate != 0)
    {
        pdc_rotation_matrix(p->ydirection * fit->orientate, &mm);
        pdc_multiply_matrix(&mm, &m);
        if (indangle % 2)
        {
            ss = fitscale.x;
            fitscale.x = fitscale.y;
            fitscale.y = ss;

            ss = boxwidth;
            boxwidth = p->ydirection * boxheight;
            boxheight = p->ydirection * ss;
        }
    }

    /* mirroring of image */
    mirror.x = elemscale.x * fitscale.x;
    elemscale.x = fabs(mirror.x);
    mirror.x /= elemscale.x;
    if (image->strips == 1)
        mirror.y = p->ydirection * elemscale.y * fitscale.y;
    else
        mirror.y = p->ydirection * rowsize * fitscale.y;
    elemscale.y = fabs(mirror.y);
    mirror.y /= elemscale.y;
    pdc_scale_matrix(mirror.x, mirror.y, &mm);
    pdc_multiply_matrix(&mm, &m);

    if (logg3)
        pdc_logg(p->pdc,
                 "\t\t\tcumulative mirroring: mx=%g, my=%g\n",
                 mirror.x, mirror.y);

    /* matchbox or clip path */
    if (!xo->blind && (fit->matchbox || kclip || kcliptiff))
    {
        pdf_concat_raw(p, &m);
        pdc_identity_matrix(&m);

        if (fit->matchbox)
        {
            matchrect.llx = 0;
            matchrect.lly = 0;
            matchrect.urx = boxwidth;
            matchrect.ury = p->ydirection * boxheight;

            if (fit->fitmethod == pdc_clip || fit->fitmethod == pdc_slice)
            {
                pdc_rectangle cliprect;
                pdc_matrix ctm_inv;

                pdc_invert_matrix(p->pdc, &ctm_inv,
                              &p->curr_ppt->gstate[p->curr_ppt->sl].ctm);
                pdc_multiply_matrix(&ctm_clip, &ctm_inv);
                pdc_box2polyline(&ctm_inv, &redbox, polyline);
                pdc_polyline2rect(polyline, 4, &cliprect);

                pdc_rect_intersect(&matchrect, &matchrect, &cliprect);
            }

            pdf_set_mbox_rectangle(p, fit->matchbox, &matchrect, 0);
            pdf_draw_mbox_rectangle(p, fit->matchbox, mbox_area | mbox_border);

            pdf_add_page_mbox(p, fit->matchbox);
        }

        /* displacement of image because of clipping */
        shift = clipbox.ll;
        purescale.x *= fitscale.x;
        purescale.y *= fitscale.y;
        if (kclip)
        {
            shift.x *= -purescale.x;
            shift.y *= -purescale.y;
        }

        if (kclip)
        {
            /* user clipping path */
            pdf__rect(p, 0, 0, boxwidth, boxheight);
            pdf__clip(p);
        }

        if (kclip)
        {
            pdc_translation_matrix(shift.x, shift.y, &mm);
            pdc_multiply_matrix(&mm, &m);
        }
    }

    /* scaling of image */
    pdc_scale_matrix(elemscale.x, elemscale.y, &mm);
    pdc_multiply_matrix(&mm, &m);

    /* ctm */
    if (xo->blind)
    {
        if (immatrix == NULL)
            mm = p->curr_ppt->gstate[p->curr_ppt->sl].ctm;
        else
            mm = *immatrix;
        pdc_multiply_matrix(&m, &mm);
    }
    else
    {
        pdf_concat_raw(p, &m);
        mm = p->curr_ppt->gstate[p->curr_ppt->sl].ctm;
    }

    pdc_logg_cond(p->pdc, 5, trc_image,
                       "\t\t\tCTM components of image %s\"%s\":\n"
                       "\t\t\ta = %f\n"
                       "\t\t\tb = %f\n"
                       "\t\t\tc = %f\n"
                       "\t\t\td = %f\n"
                       "\t\t\te = %f\n"
                       "\t\t\tf = %f\n",
                       image->imagemask ? "mask " : "",
                       image->filename, mm.a, mm.b, mm.c, mm.d, mm.e, mm.f);

    if (!xo->blind)
        pdf_cleanup_fit_options(p, fit);

    /* check image orientation */
    if (immatrix != NULL)
    {
        *immatrix = mm;
        return;
    }
    if (image->mask != pdc_undef)
    {
        sm = ctm_save;
        xo_save.im = image->mask;
        xo_save.blind = pdc_true;
        pdf_fit_xobject_internal(p, &xo_save, &fit_save, &sm);

        if ((sm.a * mm.a < 0) || (sm.b * mm.b < 0) ||
            (sm.c * mm.c < 0) || (sm.d * mm.d < 0))
        {
            pdc_error(p->pdc, PDF_E_IMAGE_NOMATCH,
                      p->images[image->mask].filename, image->filename, 0, 0);
        }
    }

    if (!(xo->flags & is_image) && !xo->blind)
    {
        pdf_reset_gstate(p);
        pdf_reset_tstate(p);
    }


    if (!xo->blind)
    {
        /* last strip first */
        if (image->strips > 1 && lastratio != 1.0)
        {
            pdc_scale_matrix(1, lastratio, &m);
            pdf_concat_raw(p, &m);
        }

        /* put out image strips separately if available */
        islast = image->strips - 1;
        imageno = image->no + islast;
        for (is = islast; is >= 0; is--)
        {
            pdc_printf(p->out, "/I%d Do\n", imageno);
            p->xobjects[imageno].flags |= xobj_flag_write;
            if (image->strips > 1 && is > 0)
            {
                pdc_translation_matrix(0, 1, &m);
                if (is == islast && lastratio != 1.0)
		{
		    pdc_scale_matrix(1, (1.0 / lastratio), &sm);
                    pdc_multiply_matrix(&sm, &m);
		}
                pdf_concat_raw(p, &m);
                imageno--;
            }
        }
        if (image->mask != pdc_undef)
        {
            p->xobjects[p->images[image->mask].no].flags |= xobj_flag_write;

            if (p->images[image->mask].bpc > 1)
                 pdf_set_autotgroup(p, pdc_true);
        }
    }
}


/* ---------------------------- info image --------------------------------- */

void
pdf_get_image_size(PDF *p, int im, pdc_scalar *width, pdc_scalar *height)
{
    pdf_image *image;

    pdf_check_handle(p, im, pdc_imagehandle);
    image = &p->images[im];

    if (image->orientation < 5 || image->ignoreorient)
    {
        if (width)
            *width = image->width;
        if (height)
            *height = fabs(image->height);
    }
    else
    {
        if (width)
            *width = fabs(image->height);
        if (height)
            *height = image->width;
    }
}

void
pdf_get_image_resolution(PDF *p, int im, pdc_scalar *dpi_x, pdc_scalar *dpi_y)
{
    pdf_image *image;

    pdf_check_handle(p, im, pdc_imagehandle);
    image = &p->images[im];

    if (image->orientation < 5 || image->ignoreorient)
    {
        if (dpi_x)
            *dpi_x = image->dpi_x;
        if (dpi_y)
            *dpi_y = image->dpi_y;
    }
    else
    {
        if (dpi_x)
            *dpi_x = image->dpi_y;
        if (dpi_y)
            *dpi_y = image->dpi_x;
    }
}

int
pdf_get_image_colorspace(PDF *p, int im)
{
    pdf_image *image;

    pdf_check_handle(p, im, pdc_imagehandle);
    image = &p->images[im];

    if (image->colorspace != pdc_undef)
        return  (int) p->colorspaces[image->colorspace].type;

    return (int) NoColor;
}


const char *
pdf_get_image_filename(PDF *p, pdf_image *image)
{
    return pdc_errprintf(p->pdc, "%.*s", PDC_ERR_MAXSTRLEN, image->filename);
}


/* ---------------------------- load image --------------------------------- */

static const pdc_keyconn pdf_extension_names[] =
{
    {".bmp",   pdf_img_bmp},
    {".ccitt", pdf_img_ccitt},
    {".g3",    pdf_img_ccitt},
    {".g4",    pdf_img_ccitt},
    {".fax",   pdf_img_ccitt},
    {".gif",   pdf_img_gif},
    {".jpg",   pdf_img_jpeg},
    {".jpeg",  pdf_img_jpeg},
    {".jpx",   pdf_img_jpeg2000},
    {".jp2",   pdf_img_jpeg2000},
    {".jpf",   pdf_img_jpeg2000},
    {".jpm",   pdf_img_jpeg2000},
    {".j2k",   pdf_img_jpeg2000},
    {".png",   pdf_img_png},
    {".raw",   pdf_img_raw},
    {".tif",   pdf_img_tiff},
    {".tiff",  pdf_img_tiff},
    {NULL, 0}
};

/* allowed values for options 'bpc' */
static const pdc_keyconn pdf_bpcvalues[] =
{
    {"1", 1}, {"2", 2}, {"4", 4}, {"8", 8}, {"16", 16}, {NULL, 0}
};

/* allowed values for options 'components' */
static const pdc_keyconn pdf_compvalues[] =
{
    {"1", 1}, {"3", 3}, {"4", 4}, {NULL, 0}
};

#define PDF_OPIOPT_FLAG PDC_OPT_UNSUPP
#define PDF_ICCOPT_FLAG PDC_OPT_UNSUPP
#define PDF_CLIPPATH_FLAG PDC_OPT_UNSUPP

#define PDF_METADATA_FLAG  PDC_OPT_UNSUPP

#define PDF_LAYER_FLAG PDC_OPT_UNSUPP

/* definitions of open image options */
static const pdc_defopt pdf_open_image_options[] =
{
    {"hypertextencoding", pdc_stringlist,  PDC_OPT_NONE, 1, 1,
      0.0, PDF_MAX_NAMESTRING, NULL},

    {"bitreverse", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    {"bpc", pdc_integerlist, PDC_OPT_INTLIST, 1, 1, 1.0, 16.0, pdf_bpcvalues},

    {"components", pdc_integerlist, PDC_OPT_INTLIST, 1, 1, 1.0, 4.0,
      pdf_compvalues},

    {"height", pdc_integerlist, 0, 1, 1, 1.0, PDC_INT_MAX,  NULL},

    {"width", pdc_integerlist, 0, 1, 1, 1.0, PDC_INT_MAX, NULL},

    {"honoriccprofile", pdc_booleanlist, PDF_ICCOPT_FLAG, 1, 1, 0.0, 0.0, NULL},

    /* ordering of the next three options is significant */

    {"iccprofile", pdc_iccprofilehandle, PDF_ICCOPT_FLAG, 1, 1, 1.0, 0.0, NULL},

    {"colorize", pdc_colorhandle, PDC_OPT_IGNOREIF1, 1, 1, 0.0, 0.0, NULL},

    {"mask", pdc_booleanlist, PDC_OPT_IGNOREIF2, 1, 1, 0.0, 0.0, NULL},

    {"honorclippingpath", pdc_booleanlist, PDF_CLIPPATH_FLAG, 1, 1,
     0.0, 0.0, NULL},

    {"clippingpathname", pdc_stringlist, PDF_CLIPPATH_FLAG, 1, 1,
      1.0, 255.0, NULL},

    {"ignoremask", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    {"ignoreorientation", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    /* deprecated */
    {"imagewarning", pdc_booleanlist, PDC_OPT_PDFLIB_7, 1, 1, 0.0, 0.0, NULL},

    {"inline", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    {"interpolate", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    {"invert", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    {"jpegoptimize", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    {"passthrough", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    {"K", pdc_integerlist, 0, 1, 1, -1.0, 1.0, NULL},

    {"masked", pdc_imagehandle, 0, 1, 1, 0.0, 0.0, NULL},

    {"page", pdc_integerlist, 0, 1, 1, 1.0, PDC_INT_MAX, NULL},

    {"renderingintent", pdc_keywordlist, 0, 1, 1, 0.0, 0.0,
      pdf_renderingintent_pdfkeylist},

    {"reftype", pdc_keywordlist, 0, 1, 1, 0.0, 0.0, pdf_reftype_keys},

    {"template", pdc_booleanlist, 0, 1, 1, 0.0, 0.0, NULL},

    {"iconname", pdc_stringlist, 0, 1, 1,
      1.0, PDF_MAX_NAMESTRING, NULL},

    {"OPI-1.3", pdc_stringlist, PDF_OPIOPT_FLAG, 1, 1,
      0.0, PDC_INT_MAX, NULL},

    {"OPI-2.0", pdc_stringlist, PDF_OPIOPT_FLAG | PDC_OPT_IGNOREIF1, 1, 1,
      0.0, PDC_INT_MAX, NULL},

    {"metadata", pdc_stringlist, PDF_METADATA_FLAG, 1, 1,
      0.0, PDC_INT_MAX, NULL},

    {"layer", pdc_layerhandle, PDF_LAYER_FLAG, 1, 1,
      0.0, 0.0, NULL},

    PDF_ERRORPOLICY_OPTION

    PDC_OPT_TERMINATE
};

int
pdf__load_image(
    PDF *p,
    const char *type,
    const char *filename,
    const char *optlist)
{
    const char *keyword = NULL, *stemp1 = NULL, *stemp2 = NULL, *stemp3 = NULL;
    char qualname[32], uptype[16], testfilename[PDC_FILENAMELEN + 1];
    pdc_clientdata data;
    pdc_resopt *resopts;
    pdc_encoding htenc;
    int htcp;
    pdf_image_type imgtype;
    pdc_file *fp = NULL;
    int colorize = pdc_undef;
    pdf_image *image;
    pdc_bool indjpeg = pdc_false;
    pdc_bool templ = pdc_false;
    pdc_bool verbose = p->debug[(int) 'i'];
    int legal_states = 0;
    int i, k, inum, imageslot, retval = -1, errcode = 0;

    if (type == NULL || *type == '\0')
        pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "type", 0, 0, 0);

    /* parsing image type */
    k = pdc_get_keycode_ci(type, pdf_image_keylist);
    if (k == PDC_KEY_NOTFOUND)
        pdc_error(p->pdc, PDC_E_ILLARG_STRING, "type", type, 0, 0);
    imgtype = (pdf_image_type) k;
    type = pdc_get_keyword(imgtype, pdf_image_keylist);

    /* parsing option list */
    pdf_set_clientdata(p, &data);
    resopts = pdc_parse_optionlist(p->pdc, optlist, pdf_open_image_options,
                                   &data, pdc_true);

    keyword = "imagewarning";
    pdc_get_optvalues(keyword, resopts, &verbose, NULL);
    verbose = pdf_get_errorpolicy(p, resopts, verbose);

    /* filename must be already converted to UTF-8 */
    pdc_logg_cond(p->pdc, 1, trc_image, "\tImage file: \"%s\"\n", filename);

    /* search for image file */
    fp = pdc_fsearch_fopen(p->pdc, filename, NULL, "image ", PDC_FILE_BINARY);

    /* automatic check */
    if (imgtype == pdf_img_auto)
    {
        pdf_tiff_info tiff_info;

        /* search for files with other extensions */
        if (fp == NULL)
        {
            pdc_logg_cond(p->pdc, 1, trc_image,
                "\tImage file not found; searching for files "
                "with alternative extensions\n");

            for (i = 0; i < 2; i++)
            {
                for (k = 0; k < 100; k++)
                {
                    const char *extension = pdf_extension_names[k].word;
                    if (extension)
                    {
                        strcpy(testfilename, filename);
                        strcpy(uptype, extension);
                        if (i)
                            pdc_strtoupper(uptype);
                        strcat(testfilename, uptype);

                        fp = pdc_fsearch_fopen(p->pdc, testfilename, NULL,
                                               "image ", PDC_FILE_BINARY);
                        if (fp != NULL)
                            break;
                    }
                    else
                    {
                        break;
                    }
                }
                if (fp != NULL)
                    break;
            }

            if (fp != NULL)
            {
                filename = (const char *) testfilename;
                pdc_logg_cond(p->pdc, 1, trc_image,
                                  "\tImage file \"%s\" found\n", filename);
            }
            else
            {
                fp = pdc_fsearch_fopen(p->pdc, filename, NULL, "image ",
                                       PDC_FILE_BINARY);
            }
        }

        /* automatic type check */
        if (fp != NULL)
        {
            if (pdf_is_BMP_file(p, fp))
                imgtype = pdf_img_bmp;
            else if (pdf_is_GIF_file(p, fp))
                imgtype = pdf_img_gif;
            else if (pdf_is_PNG_file(p, fp))
                imgtype = pdf_img_png;
            else if (pdf_is_TIFF_file(p, fp, &tiff_info, pdc_true))
                imgtype = pdf_img_tiff;
            else if (pdf_is_JPEG_file(p, fp))
                imgtype = pdf_img_jpeg;
            else if (pdf_is_JPX_file(p, fp))
                imgtype = pdf_img_jpeg2000;
            else
            {
                pdc_fclose(fp);

                pdc_set_errmsg(p->pdc, PDF_E_IMAGE_UNKNOWN, filename, 0, 0, 0);

                if (verbose)
                    PDC_RETHROW(p->pdc);

                return -1;
            }
            type = pdc_get_keyword(imgtype, pdf_image_keylist);
        }
    }

    if (fp == NULL)
    {
        if (verbose)
            PDC_RETHROW(p->pdc);

        return -1;
    }
    pdc_fclose(fp);

    strcpy(uptype, type);
    pdc_strtoupper(uptype);
    pdc_logg_cond(p->pdc, 1, trc_image,
        "\tImage type \"%s\" detected\n", uptype);

    if (imgtype == pdf_img_jpeg2000)
    {
        pdc_set_errmsg(p->pdc, PDF_E_UNSUPP_JPEG2000, 0, 0, 0, 0);

        if (verbose)
            PDC_RETHROW(p->pdc);

        return -1;
    }

    /* find free slot */
    for (imageslot = 0; imageslot < p->images_capacity; imageslot++)
        if (!p->images[imageslot].in_use)
            break;

    if (imageslot == p->images_capacity)
        pdf_grow_images(p);
    image = &p->images[imageslot];

    /* copy filename */
    image->filename = pdc_strdup(p->pdc, filename);

    /* inherit global flags */
    image->verbose = verbose;
    image->ri = p->rendintent;

    /* parsing optlist */
    if (optlist && strlen(optlist))
    {
        keyword = "reftype";
        if (pdc_get_optvalues(keyword, resopts, &inum, NULL))
        {
            image->reference = (pdf_ref_type) inum;
            if (image->reference != pdf_ref_direct &&
                imgtype != pdf_img_ccitt &&
                imgtype != pdf_img_jpeg &&
                imgtype != pdf_img_raw)
            {
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNSUPP, keyword, uptype,
                            0, 0);
                image->reference = pdf_ref_direct;
            }
        }
        indjpeg = (imgtype == pdf_img_jpeg &&
                   image->reference != pdf_ref_direct) ? pdc_true : pdc_false;

        keyword = "bpc";
        if (pdc_get_optvalues(keyword, resopts, &image->bpc, NULL))
        {
            if (imgtype != pdf_img_raw && !indjpeg)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
            if (image->bpc == 16)
            {
                if (p->compatibility < PDC_1_5)
                {
                    errcode = PDC_E_OPT_VERSION;
                    stemp1 = "bpc=16";
                    stemp2 = pdc_get_pdfversion(p->pdc, p->compatibility);
                    goto PDF_IMAGE_ERROR;
                }
            }
        }

        keyword = "components";
        if (pdc_get_optvalues(keyword, resopts, &image->components, NULL))
        {
            if (imgtype != pdf_img_raw && !indjpeg)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
        }

        keyword = "height";
        if (pdc_get_optvalues(keyword, resopts, &image->height_pixel, NULL))
        {
            if (imgtype != pdf_img_ccitt &&
                imgtype != pdf_img_raw && !indjpeg)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
        }

        keyword = "width";
        if (pdc_get_optvalues(keyword, resopts, &image->width_pixel, NULL))
        {
            if (imgtype != pdf_img_raw &&
                imgtype != pdf_img_ccitt && !indjpeg)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
        }

        keyword = "bitreverse";
        if (pdc_get_optvalues(keyword, resopts, &image->bitreverse, NULL))
        {
            if (image->bitreverse &&
               (imgtype != pdf_img_ccitt || image->reference != pdf_ref_direct))
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
        }

        keyword = "colorize";
        if (pdc_get_optvalues(keyword, resopts, &colorize, NULL))
        {
            if (imgtype == pdf_img_jpeg2000)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
        }


        keyword = "ignoremask";
        if (pdc_get_optvalues(keyword, resopts, &image->ignoremask, NULL))
        {
            if (imgtype == pdf_img_bmp ||
                imgtype == pdf_img_ccitt ||
                imgtype == pdf_img_raw)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNSUPP, keyword, uptype,
                            0, 0);
        }

        keyword = "ignoreorientation";
        if (pdc_get_optvalues(keyword, resopts, &image->ignoreorient, NULL))
        {
            if (imgtype == pdf_img_tiff)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNSUPP, keyword, uptype,
                            0, 0);
        }

        keyword = "inline";
        if (pdc_get_optvalues(keyword, resopts,
                              &image->doinline, NULL) && image->doinline)
        {
            if (imgtype != pdf_img_ccitt &&
                imgtype != pdf_img_jpeg &&
                imgtype != pdf_img_raw)
            {
                pdc_warning(p->pdc,
                    PDF_E_IMAGE_OPTUNSUPP, keyword, uptype, 0, 0);
                image->doinline = pdc_false;
            }
            else if (image->reference != pdf_ref_direct)
            {
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
                image->doinline = pdc_false;
            }
        }

        keyword = "interpolate";
        pdc_get_optvalues(keyword, resopts, &image->interpolate, NULL);


        keyword = "invert";
        if (pdc_get_optvalues(keyword, resopts, &image->invert, NULL))
        {
            if (imgtype == pdf_img_jpeg2000)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
        }

        keyword = "jpegoptimize";
        pdc_get_optvalues(keyword, resopts, &image->jpegoptimize, NULL);

        keyword = "passthrough";
        pdc_get_optvalues(keyword, resopts, &image->passthrough, NULL);

        keyword = "K";
        if (pdc_get_optvalues(keyword, resopts, &image->K, NULL))
        {
            if (imgtype != pdf_img_ccitt)
                pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNREAS, keyword, uptype,
                            0, 0);
        }

        keyword = "mask";
        pdc_get_optvalues(keyword, resopts, &image->imagemask, NULL);


        keyword = "masked";
        if (pdc_get_optvalues(keyword, resopts, &image->mask, NULL))
        {


            if (!p->images[image->mask].in_use ||
                 p->images[image->mask].strips != 1 ||
                (p->compatibility <= PDC_1_3 &&
                (p->images[image->mask].imagemask != pdc_true ||
                 p->images[image->mask].bpc != 1)))
            {
                errcode = PDF_E_IMAGE_OPTBADMASK;
                stemp1 = keyword;
                stemp2 = pdc_errprintf(p->pdc, "%d", image->mask);
                goto PDF_IMAGE_ERROR;
            }

	    if (p->colorspaces[p->images[image->mask].colorspace].type !=
		DeviceGray)
            {
                errcode = PDF_E_IMAGE_BADMASK;
                stemp1 = pdc_errprintf(p->pdc, "%s",
			p->images[image->mask].filename);
                goto PDF_IMAGE_ERROR;
            }
        }

        keyword = "renderingintent";
        if (pdc_get_optvalues(keyword, resopts, &inum, NULL))
            image->ri = (pdf_renderingintent) inum;

        keyword = "page";
        if (pdc_get_optvalues(keyword, resopts, &image->page, NULL))
        {
            if (imgtype != pdf_img_tiff)
            {
                if (image->page == 1)
                {
                    pdc_warning(p->pdc, PDF_E_IMAGE_OPTUNSUPP, keyword,
                                uptype, 0, 0);
                }
                else
                {
                    errcode = PDF_E_IMAGE_NOPAGE;
                    stemp1 = pdc_errprintf(p->pdc, "%d", image->page);
                    stemp2 = uptype;
                    stemp3 = pdc_errprintf(p->pdc, "%s", image->filename);
                    goto PDF_IMAGE_ERROR;
                }
            }
        }

        keyword = "template";
        if (pdc_get_optvalues(keyword, resopts, &templ, NULL))
        {
            if (templ && image->doinline)
            {
                pdc_warning(p->pdc, PDC_E_OPT_IGNORE, keyword, "inline",
                            0, 0);
                templ = pdc_false;
            }
        }

        htenc =
            pdf_get_hypertextencoding_opt(p, resopts, &htcp, pdc_true);
        keyword = "iconname";
        if (pdf_get_opt_textlist(p, keyword, resopts, htenc, htcp, pdc_true,
                                 NULL, &image->iconname, NULL))
        {
            if (image->doinline)
            {
                image->iconname = NULL;
                pdc_warning(p->pdc, PDC_E_OPT_IGNORE, keyword, "inline",
                            0, 0);
            }
            else
            {
                pdc_save_lastopt(resopts, PDC_OPT_SAVE1ELEM);
                templ = pdc_true;
            }
        }



    }

    /* precise scope diagnosis */
    if (image->doinline)
        legal_states = pdf_state_content;
    else if (templ)
        legal_states = pdf_state_document;
    else
        legal_states = pdf_state_document | pdf_state_page | pdf_state_font;
    PDF_CHECK_STATE(p, legal_states);

    /* required options */
    if (imgtype == pdf_img_raw || imgtype == pdf_img_ccitt || indjpeg)
    {
        keyword = "";
        if (image->height_pixel == pdc_undef)
            keyword = "height";
        else if (image->width_pixel == pdc_undef)
            keyword = "width";
        else
        {
            image->width = image->width_pixel;
            image->height = image->height_pixel;
        }

        if (imgtype == pdf_img_ccitt)
        {
            image->components = 1;
            image->bpc = 1;
        }
        if (image->bpc == pdc_undef)
            keyword = "bpc";
        else if (image->components == pdc_undef)
            keyword = "components";

        if (*keyword)
        {
            errcode = PDC_E_OPT_NOTFOUND;
            stemp1 = keyword;
            goto PDF_IMAGE_ERROR;
        }
    }



    /* set colorspace */
    if (colorize != pdc_undef)
    {
        image->colorspace = colorize;
    }
    else if (image->imagemask == pdc_true)
    {
        image->colorspace = (int) DeviceGray;
    }
    else
    {
        switch(image->components)
        {
            case 1:
            image->colorspace = DeviceGray;
            break;

            case 3:
            image->colorspace = DeviceRGB;
            break;

            case 4:
            image->colorspace = DeviceCMYK;
            break;

            default:
            break;
        }
    }

    /* try to open image file */
    if (image->reference == pdf_ref_direct)
    {
	strcpy(qualname, uptype);
	strcat(qualname, " ");
        image->fp = pdc_fsearch_fopen(p->pdc, image->filename, NULL, qualname,
                                      PDC_FILE_BINARY);
        if (image->fp == NULL)
            goto PDF_IMAGE_ERROR;
    }


    /* set image type */
    image->type = imgtype;

    /* call working function */
    switch (imgtype)
    {
        case pdf_img_bmp:
        retval = pdf_process_BMP_data(p, imageslot);
        break;

        case pdf_img_ccitt:
        retval = pdf_process_CCITT_data(p, imageslot);
        break;

        case pdf_img_gif:
        retval = pdf_process_GIF_data(p, imageslot);
        break;

        case pdf_img_jpeg:
        if (image->passthrough == pdc_undef)
            image->passthrough = pdc_false;
        retval = pdf_process_JPEG_data(p, imageslot);
        break;

        case pdf_img_jpeg2000:
        retval = pdf_process_JPX_data(p, imageslot);
        break;

        case pdf_img_png:
        retval = pdf_process_PNG_data(p, imageslot);
        break;

        default:
        case pdf_img_raw:
        retval = pdf_process_RAW_data(p, imageslot);
        break;

        case pdf_img_tiff:
        if (image->passthrough == pdc_undef)
            image->passthrough = pdc_true;
        retval = pdf_process_TIFF_data(p, imageslot);
        break;
    }

    /* cleanup */
    if (retval == -1)
    {
        pdf_cleanup_image(p, imageslot);

        if (verbose)
            PDC_RETHROW(p->pdc);
    }
    else
    {
        if (image->fp)
            pdc_fclose(image->fp);
        image->fp = NULL;

        /* logging protocol */
        if (image->in_use && pdc_logg_is_enabled(p->pdc, 1, trc_image))
        {
            pdc_scalar width, height, dpi_x, dpi_y;

            pdf_get_image_size(p, imageslot, &width, &height);
            pdf_get_image_resolution(p, imageslot, &dpi_x, &dpi_y);

            pdc_logg(p->pdc, "\tImage width: %g pixel\n"
                              "\tImage height: %g pixel\n"
                              "\tImage x resolution: %g dpi\n"
                              "\tImage y resolution: %g dpi\n",
                              width, height, dpi_x, dpi_y);

            if (imgtype == pdf_img_tiff)
                pdc_logg(p->pdc, "\tOrientation tag: %d\n",
                                  image->orientation);

        }

        if (templ)
        {
            retval = pdf_embed_image(p, imageslot);
            pdf_cleanup_image(p, imageslot);
        }
    }

    return retval;

    PDF_IMAGE_ERROR:

    pdf_cleanup_image(p, imageslot);

    if (errcode)
        pdc_set_errmsg(p->pdc, errcode, stemp1, stemp2, stemp3, 0);
    if (verbose)
        PDC_RETHROW(p->pdc);

    return retval;
}

void
pdf__close_image(PDF *p, int image)
{
    pdf_check_handle(p, image, pdc_imagehandle);
    pdf_cleanup_image(p, image);
}


#define MAX_THUMBNAIL_SIZE      106

void
pdf__add_thumbnail(PDF *p, int im)
{
    pdf_image *image;

    pdf_check_handle(p, im, pdc_imagehandle);

    if (pdf_get_thumb_id(p) != PDC_BAD_ID)
        pdc_error(p->pdc, PDF_E_IMAGE_THUMB, 0, 0, 0, 0);

    image = &p->images[im];

    if (image->strips > 1)
        pdc_error(p->pdc, PDF_E_IMAGE_THUMB_MULTISTRIP,
            pdc_errprintf(p->pdc, "%d", im), 0, 0, 0);

    if (image->width > MAX_THUMBNAIL_SIZE || image->height > MAX_THUMBNAIL_SIZE)
        pdc_error(p->pdc, PDF_E_IMAGE_THUMB_SIZE,
            pdc_errprintf(p->pdc, "%d", im),
            pdc_errprintf(p->pdc, "%d", MAX_THUMBNAIL_SIZE), 0, 0);

    if (image->colorspace != (int) DeviceGray &&
        image->colorspace != (int) DeviceRGB &&
        image->colorspace != (int) Indexed)
        pdc_error(p->pdc, PDF_E_IMAGE_THUMB_CS,
            pdc_errprintf(p->pdc, "%d", im), 0, 0, 0);

    /* Add the image to the thumbnail key of the current page.  */
    pdf_set_thumb_id(p, p->xobjects[image->no].obj_id);
}
