/* Copyright (C) 2001 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/* $Id: gsicc.c,v 1.5.2.1.2.1 2003/01/17 00:49:02 giles Exp $ */
/* Implementation of the ICCBased color space family */

#include "math_.h"
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsstruct.h"
#include "stream.h"
#include "gxcspace.h"		/* for gxcie.c */
#include "gxarith.h"
#include "gxcie.h"
#include "gzstate.h"
#include "stream.h"
#include "icc.h"		/* must precede icc.h */
#include "gsicc.h"


typedef struct _icmFileGs icmFileGs;

struct _icmFileGs {
    ICM_FILE_BASE

    /* Private: */
    stream *strp;
};

/* Garbage collection code */

/*
 * Discard a gs_cie_icc_s structure. This requires that we call the
 * destructor for ICC profile, lookup, and file objects (which are
 * stored in "foreign" memory).
 *
 * No special action is taken with respect to the stream pointer; that is
 * the responsibility of the client.  */
private void
cie_icc_finalize(void * pvicc_info)
{
    gs_cie_icc *    picc_info = (gs_cie_icc *)pvicc_info;

    if (picc_info->plu != NULL) {
        picc_info->plu->del(picc_info->plu);
        picc_info->plu = NULL;
    }
    if (picc_info->picc != NULL) {
        picc_info->picc->del(picc_info->picc);
        picc_info->picc = NULL;
    }
    if (picc_info->pfile != NULL) {
        picc_info->pfile->del(picc_info->pfile);
        picc_info->pfile = NULL;
    }
}

private_st_cie_icc();

/*
 * Because the color space structure stores alternative color space in-line,
 * we must enumerate and relocate pointers in these space explicity.
 */
gs_private_st_composite( st_color_space_CIEICC,
                         gs_paint_color_space,
                         "gs_color_space_CIEICC",
                         cs_CIEICC_enum_ptrs,
                         cs_CIEICC_reloc_ptrs );

/* pointer enumeration routine */
private
ENUM_PTRS_WITH(cs_CIEICC_enum_ptrs, gs_color_space * pcs)
        return ENUM_USING( *pcs->params.icc.alt_space.type->stype,
                           &pcs->params.icc.alt_space,
                           sizeof(pcs->params.separation.alt_space),
                           index - 1 );

        ENUM_PTR(0, gs_color_space, params.icc.picc_info);
ENUM_PTRS_END

/* pointer relocation routine */
private
RELOC_PTRS_WITH(cs_CIEICC_reloc_ptrs, gs_color_space * pcs)
    RELOC_PTR(gs_color_space, params.icc.picc_info);
    RELOC_USING( *pcs->params.icc.alt_space.type->stype,
                 &pcs->params.icc.alt_space,
                 sizeof(pcs->params.separation.alt_space) );
RELOC_PTRS_END


/*
 * Color space methods for ICCBased color spaces.
 *
 * As documented, ICCBased color spaces may be used as both base and
 * alternative color spaces. Futhermore,, they can themselves contain paint
 * color spaces as alternative color space. In this implementation we allow
 * them to be used as base and alternative color spaces, but only to contain
 * "small" base color spaces (CIEBased or smaller). This arrangement avoids
 * breaking the color space heirarchy. Providing a more correct arrangement
 * requires a major change in the color space mechanism.
 *
 * Several of the methods used by ICCBased color space apply as well to
 * DeviceN color spaces, in that they are generic to color spaces having
 * a variable number of components. We have elected not to attempt to 
 * extract and combine these operations, because this would save only a
 * small amount of code, and much more could be saved by intorducing certain
 * common elements (ranges, number of components, etc.) into the color space
 * root class.
 */
private cs_proc_num_components(gx_num_components_CIEICC);
private cs_proc_base_space(gx_alt_space_CIEICC);
private cs_proc_equal(gx_equal_CIEICC);
private cs_proc_init_color(gx_init_CIEICC);
private cs_proc_restrict_color(gx_restrict_CIEICC);
private cs_proc_concrete_space(gx_concrete_space_CIEICC);
private cs_proc_concretize_color(gx_concretize_CIEICC);
private cs_proc_adjust_cspace_count(gx_adjust_cspace_CIEICC);

private const gs_color_space_type gs_color_space_type_CIEICC = {
    gs_color_space_index_CIEICC,    /* index */
    true,                           /* can_be_base_space */
    true,                           /* can_be_alt_space */
    &st_color_space_CIEICC,         /* stype - structure descriptor */
    gx_num_components_CIEICC,       /* num_components */
    gx_alt_space_CIEICC,            /* base_space */
    gx_equal_CIEICC,                /* equal */
    gx_init_CIEICC,                 /* init_color */
    gx_restrict_CIEICC,             /* restrict_color */
    gx_concrete_space_CIEICC,       /* concrete_space */
    gx_concretize_CIEICC,           /* concreteize_color */
    NULL,                           /* remap_concrete_color */
    gx_default_remap_color,         /* remap_color */
    gx_install_CIE,                 /* install_cpsace */
    gx_adjust_cspace_CIEICC,        /* adjust_cspace_count */
    gx_no_adjust_color_count        /* adjust_color_count */
};


/*
 * Return the number of components used by a ICCBased color space - 1, 3, or 4
 */
private int
gx_num_components_CIEICC(const gs_color_space * pcs)
{
    return pcs->params.icc.picc_info->num_components;
}

/*
 * Return the alternative space for an ICCBasee color space, but only if
 * that space is being used.
 */
private const gs_color_space *
gx_alt_space_CIEICC(const gs_color_space * pcs)
{
    return (pcs->params.icc.picc_info->picc == NULL)
                ? (const gs_color_space *)&pcs->params.icc.alt_space
                : NULL;
}

/*
 * Return true if two ICCBased color spaces are equal. This routine is allowed
 * to return false even if the color spaces are equal (but not the converse),
 * so the following simple algorithm is used:
 *
 *   1. If one color space uses its alternative space, but the other does not,
 *      the two spaces are not the same.
 *
 *   2. If both color spaces use the alternative space, we recursively apply
 *      the question of equality to the base spaces.
 *
 *   3. If neither color space uses the alternative color space, the two
 *      spaces are considered the same only if they reference the same 
 *      data stream (which implies they must have the same number of
 *      components), and make use of the same ranges. No attempt is made to
 *      look into the stream (profile) contents.
 */
private bool
gx_equal_CIEICC(const gs_color_space * pcs0, const gs_color_space * pcs1)
{
    const gs_icc_params *   picc_params0 = &pcs0->params.icc;
    const gs_icc_params *   picc_params1 = &pcs1->params.icc;
    const gs_cie_icc *      picc_info0 = picc_params0->picc_info;
    const gs_cie_icc *      picc_info1 = picc_params1->picc_info;

    if (picc_info0->picc == NULL) {
        if (picc_info1->picc != NULL)
            return false;
        return picc_params0->alt_space.type->equal(
                        (const gs_color_space *)&picc_params0->alt_space,
                        (const gs_color_space *)&picc_params1->alt_space );
    } else if (picc_info1->picc == NULL)
        return false;
    else {
        const gs_range *    pranges0 = picc_info0->Range.ranges;
        const gs_range *    pranges1 = picc_info1->Range.ranges;
        int                 i, ncomps = picc_info0->num_components;

        if   ( picc_info0->instrp != picc_info1->instrp  ||
               picc_info0->file_id != picc_info1->file_id  )
            return false;

        for ( i = 0;
               i < ncomps &&
               pranges0[i].rmin == pranges1[i].rmin &&
               pranges0[i].rmax == pranges1[i].rmax;
               i++ )
            ;
        return i == ncomps;
    }
}

/*
 * Set the initial client color for an ICCBased color space. The convention
 * suggested by the ICC specification is to set all components to 0.
 */
private void
gx_init_CIEICC(gs_client_color * pcc, const gs_color_space * pcs)
{
    int     i, ncomps = pcs->params.icc.picc_info->num_components;

    for (i = 0; i < ncomps; ++i)
	pcc->paint.values[i] = 0.0;

    /* make sure that [ 0, ... 0] is in range */
    gx_restrict_CIEICC(pcc, pcs);
}

/*
 * Restrict an color to the range specified for an ICCBased color space.
 */
private void
gx_restrict_CIEICC(gs_client_color * pcc, const gs_color_space * pcs)
{
    int                 i, ncomps = pcs->params.icc.picc_info->num_components;
    const gs_range *    ranges = pcs->params.icc.picc_info->Range.ranges;

    for (i = 0; i < ncomps; ++i) {
        floatp  v = pcc->paint.values[i];
        floatp  rmin = ranges[i].rmin, rmax = ranges[i].rmax;

        if (v < rmin)
            pcc->paint.values[i] = rmin;
        else if (v > rmax)
            pcc->paint.values[i] = rmax;
    }
}

/*
 * Return the conrecte space to which this color space will map. If the
 * ICCBased color space is being used in native mode, the concrete space
 * will be dependent on the current color rendering dictionary, as it is
 * for all CIE bases. If the alternate color space is being used, then
 * this question is passed on the the appropriate method of that space.
 */
private const gs_color_space *
gx_concrete_space_CIEICC(const gs_color_space * pcs, const gs_imager_state * pis)
{
    if (pcs->params.icc.picc_info->picc == NULL) {
        const gs_color_space *  pacs = (const gs_color_space *)
                                        &pcs->params.icc.alt_space;

        return cs_concrete_space(pacs, pis);
    } else
        return gx_concrete_space_CIE(NULL, pis);
}

/*
 * Convert an ICCBased color space to a concrete color space.
 */
private int
gx_concretize_CIEICC(
    const gs_client_color * pcc,
    const gs_color_space *  pcs,
    frac *                  pconc,
    const gs_imager_state * pis )
{
    const gs_icc_params *   picc_params = &pcs->params.icc;
    const gs_cie_icc *      picc_info = picc_params->picc_info;
    stream *                instrp = picc_info->instrp;
    icc *                   picc = picc_info->picc;
    double                  inv[4], outv[3];
    cie_cached_vector3      vlmn;
    gs_client_color         lcc = *pcc;
    int                     i, ncomps = picc_info->num_components;

    /* use the altenate space concretize if appropriate */
    if (picc == NULL)
        return picc_params->alt_space.type->concretize_color(
                            pcc,
                            (const gs_color_space *)&picc_params->alt_space,
                            pconc,
                            pis );

    /* set up joint cache as required */
    CIE_CHECK_RENDERING(pcs, pconc, pis, return 0);

    /* verify and update the stream pointer */
    if (picc_info->file_id != (instrp->read_id | instrp->write_id))
        return_error(gs_error_ioerror);
    ((icmFileGs *)picc->fp)->strp = instrp;

    /* translate the input components */
    gx_restrict_CIEICC(&lcc, pcs);
    for (i = 0; i < ncomps; i++)
        inv[i] = lcc.paint.values[i];

    /*
     * Perform the lookup operation. A return value of 1 indicates that
     * clipping occurred somewhere in the operation, but the result is
     * legitimate. Other non-zero return values indicate an error, which
     * should not occur in practice.
     */
    if (picc_info->plu->lookup(picc_info->plu, outv, inv) > 1)
        return_error(gs_error_unregistered);

    /* if the output is in the CIE L*a*b* space, convert to XYZ */
    if (picc_info->pcs_is_cielab) {
        floatp              f[3];
        const gs_vector3 *  pwhtpt = &picc_info->common.points.WhitePoint;


        f[1] = (outv[0] + 16.0) / 116.0;
        f[0] = f[1] + outv[1] / 500.0;
        f[2] = f[1] - outv[2] / 200;

        for (i = 0; i < 3; i++) {
            if (f[i] >= 6.0 / 29.0)
                outv[i] = f[i] * f[i] * f[i];
            else
                outv[i] = 108.0 * (f[i] - 4.0 / 29.0) / 841.0;
        }

        /*
         * The connection space white-point is known to be D50, but we
         * use the more general form in case of future revisions.
         */
        outv[0] *= pwhtpt->u;
        outv[1] *= pwhtpt->v;
        outv[2] *= pwhtpt->w;
    }

    /* translate the output */
    vlmn.u = float2cie_cached(outv[0]);
    vlmn.v = float2cie_cached(outv[1]);
    vlmn.w = float2cie_cached(outv[2]);

    gx_cie_remap_finish(vlmn, pconc, pis, pcs);
    return 0;
}

/*
 * Handle a reference or de-reference of the prameter structure of an
 * ICCBased color space. For the purposes of this routine, the color space
 * is considered a reference rather than an object, and is not itself
 * reference counted (an unintuitive but otherwise legitimate state of
 * affairs).
 *
 * Because color spaces store alternative/base color space inline, these
 * need to have their reference count adjusted explicitly.
 */
private void
gx_adjust_cspace_CIEICC(const gs_color_space * pcs, int delta)
{
    const gs_icc_params *   picc_params = &pcs->params.icc;

    rc_adjust_const(picc_params->picc_info, delta, "gx_adjust_cspace_CIEICC");
    picc_params->alt_space.type->adjust_cspace_count(
                (const gs_color_space *)&picc_params->alt_space, delta );
}

private int
icmFileGs_seek(icmFile *pp, long int offset)
{
    icmFileGs *p = (icmFileGs *)pp;

    return spseek(p->strp, offset);
}

private size_t
icmFileGs_read(icmFile *pp, void *buffer, size_t size, size_t count)
{
    icmFileGs *p = (icmFileGs *)pp;
    uint    tot;
    int     status = sgets(p->strp, buffer, size * count, &tot);

    return (status < 0) ? status : tot;
}

private size_t
icmFileGs_write(icmFile *pp, void *buffer, size_t size, size_t count)
{
    icmFileGs *p = (icmFileGs *)pp;
    uint    tot;
    int     status = sputs(p->strp, buffer, size * count, &tot);

    return (status < 0) ? status : tot;
}

private int
icmFileGs_flush(icmFile *pp)
{
    icmFileGs *p = (icmFileGs *)pp;

    return s_std_write_flush(p->strp);
}

private int
icmFileGs_delete(icmFile *pp)
{
    free(pp);
    return 0;
}

/**
 * gx_wrap_icc_stream: Wrap a Ghostscript stream as an icclib file.
 * @strp: The Ghostscript stream.
 *
 * Creates an icmFile object that wraps @stream.
 *
 * Note: the memory for this object is allocated using malloc, and the
 * relocation of the stream pointer is done lazily, before an icclu
 * operation. It would probably be cleaner to allocate the icmFile in
 * garbage collected memory, and have the relocation happen there, but
 * I wanted to minimally modify Jan's working code.
 *
 * Return value: the stream wrapped as an icmFile object, or NULL on
 * error.
 **/
private icmFile *
gx_wrap_icc_stream(stream *strp)
{
    icmFileGs *p;

    if ((p = (icmFileGs *) calloc(1,sizeof(icmFileGs))) == NULL)
	return NULL;
    p->seek  = icmFileGs_seek;
    p->read  = icmFileGs_read;
    p->write = icmFileGs_write;
    p->flush = icmFileGs_flush;
    p->del   = icmFileGs_delete;

    p->strp = strp;

    return (icmFile *)p;
}

int
gx_load_icc_profile(gs_cie_icc *picc_info)
{
    stream *        instrp = picc_info->instrp;
    icc *           picc;
    icmLuBase * plu = NULL;
    icmFile *pfile = NULL;
	
    /* verify that the file is legitimate */
    if (picc_info->file_id != (instrp->read_id | instrp->write_id))
	return_error(gs_error_ioerror);
    /*
     * Load the top-level ICC profile.
     *
     * If an ICC profile fails to load, generate an error.
     * 
     * Testing demonstrates, however, Acrobat Reader silently
     * ignores the error and uses the alternate color space.
     * This behaviour is implemented by catching the error using
     * a stopped context from within the interpreter (gs_icc.ps).
     *
     * Failure to allocate the top-level profile object is considered
     * a limitcheck rather than a VMerror, as profile data structures
     * are stored in "foreign" memory.
     */
    if ((picc = new_icc()) == NULL)
	return_error(gs_error_limitcheck);
    {
	icProfileClassSignature profile_class;
	icColorSpaceSignature   cspace_type;
	gs_vector3 *            ppt;

	pfile = gx_wrap_icc_stream (instrp);
      
	if ((picc->read(picc, pfile, 0)) != 0)
	    goto return_rangecheck;
            
	/* verify the profile type */
	profile_class = picc->header->deviceClass;
	if ( profile_class != icSigInputClass     &&
	     profile_class != icSigDisplayClass   &&
	     profile_class != icSigOutputClass    &&
	     profile_class != icSigColorSpaceClass  )
	    goto return_rangecheck;

	/* verify the profile connection space */
	cspace_type = picc->header->pcs;
	if (cspace_type == icSigLabData)
	    picc_info->pcs_is_cielab = true;
	else if (cspace_type == icSigXYZData)
	    picc_info->pcs_is_cielab = false;
	else
	    goto return_rangecheck;

	/* verify the source color space */
	cspace_type = picc->header->colorSpace;
	if (cspace_type == icSigCmykData) {
	    if (picc_info->num_components != 4)
		goto return_rangecheck;
	} else if ( cspace_type == icSigRgbData ||
		    cspace_type == icSigLabData   ) {
	    if (picc_info->num_components != 3)
		goto return_rangecheck;
	} else if (cspace_type == icSigGrayData) {
	    if (picc_info->num_components != 1)
		goto return_rangecheck;
	}

	/*
	 * Fetch the lookup object.
	 *
	 * PostScript and PDF deal with rendering intent as strictly a
	 * rendering dictionary facility. ICC profiles allow a rendering
	 * intent to be specified for both the input (device ==> pcs) and
	 * output (pcs ==> device) operations. Hence, when using ICCBased
	 * color spaces with PDF, no clue is provided as to which source
	 * mapping to select.
	 *
	 * In the absence of other information, there are two possible
	 * selections. If our understanding is correct, when relative
	 * colorimetry is specified, the icclib code will map source
	 * color values to XYZ or L*a*b* values such that the relationship
	 * of the source color, relative to the source white and black
	 * points, will be the same as the output colors and the
	 * profile connection space illuminant (currently always D50)
	 * and pure black ([0, 0, 0]). In this case, the white and black
	 * points that should be listed in the color space are the
	 * profile connection space illuminant (D50) and pure black.
	 *
	 * If absolute colorimetry is employed, the XYZ or L*a*b* values
	 * generated will be absolute in the chromatic sense (they are
	 * not literally "absolute", as we still must have overall
	 * intensity information inorder to determine weighted spectral
	 * power levels). To achieve relative colorimetry for the output,
	 * these colors must be evaluated relative to the source white
	 * and black points. Hence, in this case, the appropriate white
	 * and black points to list in the color space are the source
	 * white and black points provided in the profile tag array.
	 *
	 * In this implementation, we will always request relative
	 * colorimetry from the icclib, and so will use the profile
	 * connection space illuminant and pure black as the white and
	 * black points of the color space. This approach is somewhat
	 * simpler, as it allows the color space white point to also
	 * be used for L*a*b* to XYZ conversion (otherwise we would
	 * need to store the profile connection space illuminant
	 * separately for that purpose). The approach does reduce to
	 * to some extent the range of mappings that can be achieved
	 * via the color rendering dictionary, but for now we believe
	 * this loss is not significant.
	 *
	 * For reasons that are not clear to us, the icclib code does
	 * not support relative colorimetry for all color profiles. For
	 * this reason, we specify icmDefaultIntent rather than
	 * icRelativeColormetric.
	 *
	 * NB: We are not color experts; our understanding of this area
	 *     may well be incorrect.
	 */
	plu = picc->get_luobj( picc,
			       icmFwd,
			       icmDefaultIntent,
			       0, /* PCS override */
			       icmLuOrdNorm );
	if (plu == NULL)
	    goto return_rangecheck;

	/* 
	 * Get the appropriate white and black points. See the note on
	 * rendering intent above for a discussion of why we are using
	 * the profile space illuminant and pure black. (Pure black need
	 * not be set explicitly, as it is the default.)
	 */
	ppt = &picc_info->common.points.WhitePoint;
	ppt->u = picc->header->illuminant.X;
	ppt->v = picc->header->illuminant.Y;
	ppt->w = picc->header->illuminant.Z;

	picc_info->picc = picc;
	picc_info->plu = plu;
	picc_info->pfile = pfile;
    }

    return 0;

 return_rangecheck:
    if (plu != NULL)
	plu->del(plu);
    if (picc != NULL)
	picc->del(picc);
    if (pfile != NULL)
	pfile->del(pfile);
    return_error(gs_error_rangecheck);
}

/*
 * Install an ICCBased color space.
 *
 * Note that an ICCBased color space must be installed before it is known if
 * the ICC profile or the alternate color space is to be used.
 */
private int
gx_install_CIEICC(const gs_color_space * pcs, gs_state * pgs)
{
    const gs_icc_params * picc_params = (const gs_icc_params *)&pcs->params.icc;
    gs_cie_icc *    picc_info = picc_params->picc_info;

    /* update the stub information used by the joint caches */
    gx_cie_load_common_cache(&picc_info->common, pgs);
    gx_cie_common_complete(&picc_info->common);
    return gs_cie_cs_complete(pgs, true);
}


/*
 * Constructor for ICCBased color space. As with the other color space
 * constructors, this provides only minimal initialization.
 */
int
gs_cspace_build_CIEICC(
    gs_color_space **   ppcspace,
    void *              client_data,
    gs_memory_t *       pmem )
{
    gs_cie_icc *        picc_info;
    gs_color_space *    pcs;

    /*
     * The gs_cie_icc_s structure is the only CIE-based color space structure
     * which accesses additional memory for which it is responsible. We make
     * use of the finalization procedure to handle this task, so we can use
     * the generic CIE space build routine (otherwise we would need a
     * separate build routine that provided its own reference count freeing
     * procedure).
     */
    picc_info = gx_build_cie_space( ppcspace,
                                    &gs_color_space_type_CIEICC,
                                    &st_cie_icc,
                                    pmem );

    if (picc_info == NULL)
        return_error(gs_error_VMerror);

    gx_set_common_cie_defaults(&picc_info->common, client_data);
    /*
     * Now set the D50 WhitePoint. The above function does not set any
     * valid WhitepPoint since PostScript always requires this, but ICC
     * assumes a D50 WhitePoint as a default
     */
    picc_info->common.points.WhitePoint.u = 0.9642;		/* Profile illuminant - D50 */
    picc_info->common.points.WhitePoint.v = 1.0000;
    picc_info->common.points.WhitePoint.w = 0.8249;
    picc_info->common.install_cspace = gx_install_CIEICC;
    picc_info->num_components = 0;
    picc_info->Range = Range4_default;
    picc_info->instrp = NULL;
    picc_info->pcs_is_cielab = false;
    picc_info->picc = NULL;
    picc_info->plu = NULL;
    picc_info->pfile = NULL;

    pcs = *ppcspace;
    pcs->params.icc.picc_info = picc_info;
    return 0;
}
