/* Copyright (C) 2001-2020 Artifex Software, Inc.
   All Rights Reserved.

   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied,
   modified or distributed except as expressly authorized under the terms
   of the license contained in the file LICENSE in this distribution.

   Refer to licensing information at http://www.artifex.com or contact
   Artifex Software, Inc.,  1305 Grant Avenue - Suite 200, Novato,
   CA 94945, U.S.A., +1(415)492-9861, for further information.
*/

/*  GS ICC Manager.  Initial stubbing of functions.  */

#include "std.h"
#include "stdpre.h"
#include "gstypes.h"
#include "gsmemory.h"
#include "gsstruct.h"
#include "scommon.h"
#include "strmio.h"
#include "gx.h"
#include "gp.h"
#include "gxgstate.h"
#include "gxcspace.h"
#include "gscms.h"
#include "gsicc_manage.h"
#include "gsicc_cache.h"
#include "gsicc_profilecache.h"
#include "gsicc_cms.h"
#include "gserrors.h"
#include "string_.h"
#include "gxclist.h"
#include "gxcldev.h"
#include "gzstate.h"
#include "gsicc_create.h"
#include "gpmisc.h"
#include "gxdevice.h"
#include "gxdevsop.h"

#define ICC_HEADER_SIZE 128
#define CREATE_V2_DATA 0

#if ICC_DUMP
unsigned int global_icc_index = 0;
#endif

/* Needed for gsicc_set_devicen_equiv_colors. */
extern const gs_color_space_type gs_color_space_type_ICC;

/* Static prototypes */

static void gsicc_set_default_cs_value(cmm_profile_t *picc_profile,
                                       gs_gstate *pgs);
static gsicc_namelist_t* gsicc_new_namelist(gs_memory_t *memory);
static gsicc_colorname_t* gsicc_new_colorname(gs_memory_t *memory);
static gsicc_namelist_t* gsicc_get_spotnames(gcmmhprofile_t profile,
                                             gs_memory_t *memory);
static void gsicc_manager_free_contents(gsicc_manager_t *icc_man,
                                        client_name_t cname);

static void rc_gsicc_manager_free(gs_memory_t * mem, void *ptr_in,
                                  client_name_t cname);
static void rc_free_icc_profile(gs_memory_t * mem, void *ptr_in,
                                client_name_t cname);
static int gsicc_load_profile_buffer(cmm_profile_t *profile, stream *s,
                                     gs_memory_t *memory);
static int64_t gsicc_search_icc_table(clist_icctable_t *icc_table,
                                      int64_t icc_hashcode, int *size);
static int gsicc_load_namedcolor_buffer(cmm_profile_t *profile, stream *s,
                          gs_memory_t *memory);
static cmm_srcgtag_profile_t* gsicc_new_srcgtag_profile(gs_memory_t *memory);
static void gsicc_free_spotnames(gsicc_namelist_t *spotnames, gs_memory_t * mem);

static void
gsicc_manager_finalize(const gs_memory_t *memory, void * vptr);

static void
gsicc_smask_finalize(const gs_memory_t *memory, void * vptr);

/* profile data structure */
/* profile_handle should NOT be garbage collected since it is allocated by the external CMS */
gs_private_st_ptrs2(st_gsicc_colorname, gsicc_colorname_t, "gsicc_colorname",
                    gsicc_colorname_enum_ptrs, gsicc_colorname_reloc_ptrs, name, next);

gs_private_st_ptrs2_final(st_gsicc_manager, gsicc_manager_t, "gsicc_manager",
                    gsicc_manager_enum_ptrs, gsicc_manager_profile_reloc_ptrs,
                    gsicc_manager_finalize, smask_profiles, device_n);

gs_private_st_simple_final(st_gsicc_smask, gsicc_smask_t, "gsicc_smask", gsicc_smask_finalize);

gs_private_st_ptrs2(st_gsicc_devicen, gsicc_devicen_t, "gsicc_devicen",
                gsicc_devicen_enum_ptrs, gsicc_devicen_reloc_ptrs, head, final);

gs_private_st_ptrs1(st_gsicc_devicen_entry, gsicc_devicen_entry_t,
                    "gsicc_devicen_entry", gsicc_devicen_entry_enum_ptrs,
                    gsicc_devicen_entry_reloc_ptrs, next);

typedef struct default_profile_def_s {
    const char *path;
    gsicc_profile_t default_type;
} default_profile_def_t;

static default_profile_def_t default_profile_params[] =
{
    {DEFAULT_GRAY_ICC, DEFAULT_GRAY},
    {DEFAULT_RGB_ICC, DEFAULT_RGB},
    {DEFAULT_CMYK_ICC, DEFAULT_CMYK},
    {LAB_ICC, LAB_TYPE}
};

void
gsicc_setcoloraccuracy(gs_memory_t *mem, uint level)
{
    gs_lib_ctx_t *ctx = gs_lib_ctx_get_interp_instance(mem);

    ctx->icc_color_accuracy = level;
}

uint
gsicc_currentcoloraccuracy(gs_memory_t *mem)
{
    gs_lib_ctx_t *ctx = gs_lib_ctx_get_interp_instance(mem);

    return ctx->icc_color_accuracy;
}

/* Get the size of the ICC profile that is in the buffer */
unsigned int
gsicc_getprofilesize(unsigned char *buffer)
{
    return ( (buffer[0] << 24) + (buffer[1] << 16) +
             (buffer[2] << 8)  +  buffer[3] );
}

/* Get major and minor ICC version number */
int
gsicc_getprofilevers(cmm_profile_t *icc_profile, unsigned char *major,
    unsigned char *minor)
{
    if (icc_profile == NULL || icc_profile->buffer == NULL)
        return -1;

    *major = icc_profile->buffer[8];
    *minor = icc_profile->buffer[9];

    return 0;
}

void
gsicc_set_icc_range(cmm_profile_t **icc_profile)
{
    int num_comp = (*icc_profile)->num_comps;
    int k;

    for ( k = 0; k < num_comp; k++) {
        (*icc_profile)->Range.ranges[k].rmin = 0.0;
        (*icc_profile)->Range.ranges[k].rmax = 1.0;
    }
}

cmm_profile_t*
gsicc_set_iccsmaskprofile(const char *pname,
                          int namelen, gsicc_manager_t *icc_manager,
                          gs_memory_t *mem)
{
    stream *str;
    int code;
    cmm_profile_t *icc_profile;

    if (icc_manager == NULL) {
        code = gsicc_open_search(pname, namelen, mem, NULL, 0, &str);
    } else {
        code = gsicc_open_search(pname, namelen, mem, mem->gs_lib_ctx->profiledir,
                                 mem->gs_lib_ctx->profiledir_len, &str);
    }
    if (code < 0 || str == NULL)
        return NULL;
    icc_profile = gsicc_profile_new(str, mem, pname, namelen);
    code = sfclose(str);
    if (icc_profile == NULL)
        return NULL;
    /* Get the profile handle */
    icc_profile->profile_handle =
            gsicc_get_profile_handle_buffer(icc_profile->buffer,
                                            icc_profile->buffer_size,
                                            mem);
    if (!icc_profile->profile_handle) {
        rc_free_icc_profile(mem, icc_profile, "gsicc_set_iccsmaskprofile");
        return NULL;
    }
    /* Compute the hash code of the profile. Everything in the
       ICC manager will have it's hash code precomputed */
    gsicc_get_icc_buff_hash(icc_profile->buffer, &(icc_profile->hashcode),
                            icc_profile->buffer_size);
    icc_profile->hash_is_valid = true;
    icc_profile->num_comps =
            gscms_get_input_channel_count(icc_profile->profile_handle, icc_profile->memory);
    icc_profile->num_comps_out =
            gscms_get_output_channel_count(icc_profile->profile_handle, icc_profile->memory);
    icc_profile->data_cs =
            gscms_get_profile_data_space(icc_profile->profile_handle, icc_profile->memory);
    gsicc_set_icc_range(&icc_profile);
    return icc_profile;
}

static void
gsicc_smask_finalize(const gs_memory_t *memory, void * vptr)
{
    gsicc_smask_t *iccsmask = (gsicc_smask_t *)vptr;

    gsicc_adjust_profile_rc(iccsmask->smask_gray, -1,
        "gsicc_smask_finalize");
    gsicc_adjust_profile_rc(iccsmask->smask_rgb, -1,
        "gsicc_smask_finalize");
    gsicc_adjust_profile_rc(iccsmask->smask_cmyk, -1,
        "gsicc_smask_finalize");
}

gsicc_smask_t*
gsicc_new_iccsmask(gs_memory_t *memory)
{
    gsicc_smask_t *result;

    result = (gsicc_smask_t *) gs_alloc_struct(memory, gsicc_smask_t, &st_gsicc_smask, "gsicc_new_iccsmask");
    if (result != NULL) {
        result->smask_gray = NULL;
        result->smask_rgb = NULL;
        result->smask_cmyk = NULL;
        result->memory = memory;
        result->swapped = false;
    }
    return result;
}

/* Allocate a new structure to hold the profiles that contains the profiles
   used when we are in a softmask group */
int
gsicc_initialize_iccsmask(gsicc_manager_t *icc_manager)
{
    gs_memory_t *stable_mem = icc_manager->memory->stable_memory;

    /* Allocations need to be done in stable memory.  We want to maintain
       the smask_profiles object */
    icc_manager->smask_profiles = gsicc_new_iccsmask(stable_mem);
    if (icc_manager->smask_profiles == NULL)
        return gs_throw(gs_error_VMerror, "insufficient memory to allocate smask profiles");
    /* Load the gray, rgb, and cmyk profiles */
    if ((icc_manager->smask_profiles->smask_gray =
        gsicc_set_iccsmaskprofile(SMASK_GRAY_ICC, strlen(SMASK_GRAY_ICC),
        icc_manager, stable_mem) ) == NULL) {
        return gs_throw(-1, "failed to load gray smask profile");
    }
    if ((icc_manager->smask_profiles->smask_rgb =
        gsicc_set_iccsmaskprofile(SMASK_RGB_ICC, strlen(SMASK_RGB_ICC),
        icc_manager, stable_mem)) == NULL) {
        return gs_throw(-1, "failed to load rgb smask profile");
    }
    if ((icc_manager->smask_profiles->smask_cmyk =
        gsicc_set_iccsmaskprofile(SMASK_CMYK_ICC, strlen(SMASK_CMYK_ICC),
        icc_manager, stable_mem)) == NULL) {
        return gs_throw(-1, "failed to load cmyk smask profile");
    }
    /* Set these as "default" so that pdfwrite or other high level devices
       will know that these are manufactured profiles, and default spaces
       should be used */
    icc_manager->smask_profiles->smask_gray->default_match = DEFAULT_GRAY;
    icc_manager->smask_profiles->smask_rgb->default_match = DEFAULT_RGB;
    icc_manager->smask_profiles->smask_cmyk->default_match = DEFAULT_CMYK;
    return 0;
}

static int
gsicc_new_devicen(gsicc_manager_t *icc_manager)
{
/* Allocate a new deviceN ICC profile entry in the deviceN list */
    gsicc_devicen_entry_t *device_n_entry =
        gs_alloc_struct(icc_manager->memory, gsicc_devicen_entry_t,
                &st_gsicc_devicen_entry, "gsicc_new_devicen");
    if (device_n_entry == NULL)
        return gs_throw(gs_error_VMerror, "insufficient memory to allocate device n profile");
    device_n_entry->next = NULL;
    device_n_entry->iccprofile = NULL;
/* Check if we already have one in the manager */
    if ( icc_manager->device_n == NULL ) {
        /* First one.  Need to allocate the DeviceN main object */
        icc_manager->device_n = gs_alloc_struct(icc_manager->memory,
            gsicc_devicen_t, &st_gsicc_devicen, "gsicc_new_devicen");

        if (icc_manager->device_n == NULL)
            return gs_throw(gs_error_VMerror, "insufficient memory to allocate device n profile");

        icc_manager->device_n->head = device_n_entry;
        icc_manager->device_n->final = device_n_entry;
        icc_manager->device_n->count = 1;
        return 0;
    } else {
        /* We have one or more in the list. */
        icc_manager->device_n->final->next = device_n_entry;
        icc_manager->device_n->final = device_n_entry;
        icc_manager->device_n->count++;
        return 0;
    }
}

cmm_profile_t*
gsicc_finddevicen(const gs_color_space *pcs, gsicc_manager_t *icc_manager)
{
    int k,j,i;
    gsicc_devicen_entry_t *curr_entry;
    int num_comps;
    char **names = pcs->params.device_n.names;
    unsigned char *pname;
    unsigned int name_size;
    gsicc_devicen_t *devicen_profiles = icc_manager->device_n;
    gsicc_colorname_t *icc_spot_entry;
    int match_count = 0;
    bool permute_needed = false;

    num_comps = gs_color_space_num_components(pcs);

    /* Go through the list looking for a match */
    curr_entry = devicen_profiles->head;
    for ( k = 0; k < devicen_profiles->count; k++ ) {
        if (curr_entry->iccprofile->num_comps == num_comps ) {

            /* Now check the names.  The order is important
               since this is supposed to be the laydown order.
               If the order is off, the ICC profile will likely
               not be accurate.  The ICC profile drives the laydown
               order here.  A permutation vector is used to
               reorganize the data prior to the transform application */
            for ( j = 0; j < num_comps; j++) {
                /* Get the character string and length for the component name. */
                pname = (unsigned char *)names[j];
                name_size = strlen(names[j]);
                /* Compare to the jth entry in the ICC profile */
                icc_spot_entry = curr_entry->iccprofile->spotnames->head;
                for ( i = 0; i < num_comps; i++) {
                    if( strncmp((const char *) pname,
                        icc_spot_entry->name, name_size) == 0 ) {
                        /* Found a match */
                        match_count++;
                        curr_entry->iccprofile->devicen_permute[j] = i;
                        if ( j != i) {
                            /* Document ink order does not match ICC
                               profile ink order */
                            permute_needed = true;
                        }
                        break;
                    } else
                        icc_spot_entry = icc_spot_entry->next;
                }
                if (match_count < j+1)
                    return(NULL);
            }
            if ( match_count == num_comps) {
                /* We have a match.  Order of components does not match laydown
                   order specified by the ICC profile.  Set a flag.  This may
                   be an issue if we are using 2 DeviceN color spaces with the
                   same colorants but with different component orders.  The problem
                   comes about since we would be sharing the profile in the
                   DeviceN entry of the icc manager. */
                curr_entry->iccprofile->devicen_permute_needed = permute_needed;
                return(curr_entry->iccprofile);
            }
            match_count = 0;
        }
    }
    return NULL;
}

/* Populate the color names entries that should
   be contained in the DeviceN ICC profile */
static gsicc_namelist_t*
gsicc_get_spotnames(gcmmhprofile_t profile, gs_memory_t *memory)
{
    int k;
    gsicc_namelist_t *list;
    gsicc_colorname_t *name;
    gsicc_colorname_t **curr_entry;
    int num_colors;
    char *clr_name;

    num_colors = gscms_get_numberclrtnames(profile, memory);
    if (num_colors == 0)
        return(NULL);
    /* Allocate structure for managing this */
    list = gsicc_new_namelist(memory);
    if (list == NULL)
        return(NULL);
    curr_entry = &(list->head);
    list->count = num_colors;
    for (k = 0; k < num_colors; k++) {
        /* Allocate a new name object */
        clr_name = gscms_get_clrtname(profile, k, memory);
        if (clr_name == NULL)
            break;
        name = gsicc_new_colorname(memory);
        if (name == NULL) {
            /* FIXME: Free clr_name */
            gs_free_object(memory, clr_name, "gsicc_get_spotnames");
            break;
        }
        /* Get the name */
        name->name = clr_name;
        name->length = strlen(clr_name);
        *curr_entry = name;
        curr_entry = &(name->next);
    }
    if (k < num_colors) {
        /* Failed allocation */
        gsicc_free_spotnames(list, memory);
        return NULL;
    }
    return list;
}

static void
gsicc_get_devicen_names(cmm_profile_t *icc_profile, gs_memory_t *memory)
{
    /* The names are contained in the
       named color tag.  We use the
       CMM to extract the data from the
       profile */
    if (icc_profile->profile_handle == NULL) {
        if (icc_profile->buffer != NULL) {
            icc_profile->profile_handle =
                gsicc_get_profile_handle_buffer(icc_profile->buffer,
                                                icc_profile->buffer_size,
                                                memory);
        } else
            return;
    }
    icc_profile->spotnames =
        gsicc_get_spotnames(icc_profile->profile_handle, memory->non_gc_memory);
    return;
}

/* Allocate new spot name list object.  */
static gsicc_namelist_t*
gsicc_new_namelist(gs_memory_t *memory)
{
    gsicc_namelist_t *result;

    result = (gsicc_namelist_t *) gs_alloc_bytes(memory->non_gc_memory, sizeof(gsicc_namelist_t),
                                                 "gsicc_new_namelist");
    if (result == NULL)
        return NULL;
    result->count = 0;
    result->head = NULL;
    result->name_str = NULL;
    result->color_map = NULL;
    return result;
}

/* Allocate new spot name.  */
static gsicc_colorname_t*
gsicc_new_colorname(gs_memory_t *memory)
{
    gsicc_colorname_t *result;

    result = gs_alloc_struct(memory,gsicc_colorname_t,
                &st_gsicc_colorname, "gsicc_new_colorname");
    if (result == NULL)
        return NULL;
    result->length = 0;
    result->name = NULL;
    result->next = NULL;
    return result;
}

/* If the profile is one of the default types that were set in the iccmanager,
   then the index for that type is returned.  Otherwise the ICC index is returned.
   This is currently used to keep us from writing out the default profiles for
   high level devices, if desired. */
gs_color_space_index
gsicc_get_default_type(cmm_profile_t *profile_data)
{
    switch (profile_data->default_match) {
        case DEFAULT_GRAY:
            return gs_color_space_index_DeviceGray;
        case DEFAULT_RGB:
            return gs_color_space_index_DeviceRGB;
        case DEFAULT_CMYK:
            return gs_color_space_index_DeviceCMYK;
        case CIE_A:
            return gs_color_space_index_CIEA;
        case CIE_ABC:
            return gs_color_space_index_CIEABC;
        case CIE_DEF:
            return gs_color_space_index_CIEDEF;
        case CIE_DEFG:
            return gs_color_space_index_CIEDEFG;
        default:
            return gs_color_space_index_ICC;
    }
}

int
gsicc_use_fast_color(cmm_profile_t* profile_data)
{
    switch (profile_data->default_match) {
    case CIE_A:
    case CIE_ABC:
    case CIE_DEF:
    case CIE_DEFG:
    case LAB_TYPE:
    case NAMED_TYPE:
    case DEVICEN_TYPE:
        return 0;
    default:
        return profile_data->num_comps;
    }
}

bool
gsicc_is_default_profile(cmm_profile_t *profile_data)
{
    switch (profile_data->default_match) {
        case DEFAULT_GRAY:
        case DEFAULT_RGB:
        case DEFAULT_CMYK:
            return true;
        default:
            return false;
    }
}

bool
gsicc_profile_from_ps(cmm_profile_t *profile_data)
{
    switch ( profile_data->default_match ) {
        case CIE_A:
        case CIE_ABC:
        case CIE_DEF:
        case CIE_DEFG:
            return true;
        default:
            return false;
    }
}

/*
 * Adjust the reference count of the profile. This is intended to support
 * applications (such as XPS) which maintain ICC profiles outside of the
 * graphic state.
 */
/* for multi-threaded use, we need to adjust the ref_count safely */
void
gsicc_adjust_profile_rc(cmm_profile_t *profile_data, int delta, const char *name_str)
{
    if (profile_data != NULL) {
        gx_monitor_enter(profile_data->lock);
        if (profile_data->rc.ref_count == 1 && delta < 0) {
            profile_data->rc.ref_count = 0;		/* while locked */
            gx_monitor_leave(profile_data->lock);
            rc_free_struct(profile_data, name_str);
        } else {
            rc_adjust(profile_data, delta, name_str);
            gx_monitor_leave(profile_data->lock);
        }
    }
}

/* Fill in the actual source structure rending information */
static int
gsicc_fill_srcgtag_item(gsicc_rendering_param_t *r_params, char **pstrlast, bool cmyk)
{
    char *curr_ptr;
    int blackptcomp;
    int or_icc, preserve_k;
    int ri;

    /* Get the intent */
    curr_ptr = gs_strtok(NULL, "\t, \n\r", pstrlast);
    if (sscanf(curr_ptr, "%d", &ri) != 1)
        return_error(gs_error_unknownerror);
    r_params->rendering_intent = ri | gsRI_OVERRIDE;
    /* Get the black point compensation setting */
    curr_ptr = gs_strtok(NULL, "\t, \n\r", pstrlast);
    if (sscanf(curr_ptr, "%d", &blackptcomp) != 1)
        return_error(gs_error_unknownerror);
    r_params->black_point_comp = blackptcomp | gsBP_OVERRIDE;
    /* Get the over-ride embedded ICC boolean */
    curr_ptr = gs_strtok(NULL, "\t, \n\r", pstrlast);
    if (sscanf(curr_ptr, "%d", &or_icc) != 1)
        return_error(gs_error_unknownerror);
    r_params->override_icc = or_icc;
    if (cmyk) {
        /* Get the preserve K control */
        curr_ptr = gs_strtok(NULL, "\t, \n\r", pstrlast);
        if (sscanf(curr_ptr, "%d", &preserve_k) < 1)
            return_error(gs_error_unknownerror);
        r_params->preserve_black = preserve_k | gsKP_OVERRIDE;
    } else {
        r_params->preserve_black = gsBKPRESNOTSPECIFIED;
    }
    return 0;
}

static int
gsicc_check_device_link(cmm_profile_t *icc_profile, gs_memory_t *memory)
{
    bool value;

    value = gscms_is_device_link(icc_profile->profile_handle, memory);
    icc_profile->isdevlink = value;

    return value;
}

int
gsicc_get_device_class(cmm_profile_t *icc_profile)
{
    return gscms_get_device_class(icc_profile->profile_handle, icc_profile->memory);
}

/* This inititializes the srcgtag structure in the ICC manager */
static int
gsicc_set_srcgtag_struct(gsicc_manager_t *icc_manager, const char* pname,
                        int namelen)
{
    gs_memory_t *mem;
    stream *str;
    int code;
    int info_size;
    char *buffer_ptr, *curr_ptr, *last;
    int num_bytes;
    int k;
    static const char *const srcgtag_keys[] = {GSICC_SRCGTAG_KEYS};
    cmm_profile_t *icc_profile;
    cmm_srcgtag_profile_t *srcgtag;
    bool start = true;
    gsicc_cmm_t cmm = gsCMM_DEFAULT;

    /* If we don't have an icc manager or if this thing is already set
       then ignore the call.  For now, I am going to allow it to
       be set one time. */
    if (icc_manager == NULL || icc_manager->srcgtag_profile != NULL) {
        return 0;
    } else {
        mem = icc_manager->memory->non_gc_memory;
        code = gsicc_open_search(pname, namelen, mem, mem->gs_lib_ctx->profiledir,
                                 mem->gs_lib_ctx->profiledir_len, &str);
        if (code < 0)
            return code;
    }
    if (str != NULL) {
        /* Get the information in the file */
        code = sfseek(str,0,SEEK_END);
        if (code < 0)
            return code;
        info_size = sftell(str);
        code = srewind(str);
        if (code < 0)
            return code;
        if (info_size > (GSICC_NUM_SRCGTAG_KEYS + 1) * FILENAME_MAX) {
            return gs_throw1(-1, "setting of %s src obj color info failed",
                               pname);
        }
        /* Allocate the buffer, stuff with the data */
        buffer_ptr = (char*) gs_alloc_bytes(mem, info_size+1,
                                            "gsicc_set_srcgtag_struct");
        if (buffer_ptr == NULL) {
            return gs_throw1(gs_error_VMerror, "setting of %s src obj color info failed",
                               pname);
        }
        num_bytes = sfread(buffer_ptr,sizeof(unsigned char), info_size, str);
        code = sfclose(str);
        if (code < 0)
            return code;
        buffer_ptr[info_size] = 0;
        if (num_bytes != info_size) {
            gs_free_object(mem, buffer_ptr, "gsicc_set_srcgtag_struct");
            return gs_throw1(-1, "setting of %s src obj color info failed",
                               pname);
        }
        /* Create the structure in which we will store this data */
        srcgtag = gsicc_new_srcgtag_profile(mem);
        /* Now parse through the data opening the profiles that are needed */
        curr_ptr = buffer_ptr;
        /* Initialize that we want color management.  Then if profile is not
           present we know we did not want anything special done with that
           source type.  Else if we have no profile and don't want color
           management we will make sure to do that */
        for (k = 0; k < NUM_SOURCE_PROFILES; k++) {
            srcgtag->rgb_rend_cond[k].cmm = gsCMM_DEFAULT;
            srcgtag->cmyk_rend_cond[k].cmm = gsCMM_DEFAULT;
            srcgtag->gray_rend_cond[k].cmm = gsCMM_DEFAULT;
        }
        while (start || strlen(curr_ptr) > 0) {
            if (start) {
                curr_ptr = gs_strtok(buffer_ptr, "\t, \n\r", &last);
                start = false;
            } else {
                curr_ptr = gs_strtok(NULL, "\t, \n\r", &last);
            }
            if (curr_ptr == NULL) break;
            /* Now go ahead and see if we have a match */
            for (k = 0; k < GSICC_NUM_SRCGTAG_KEYS; k++) {
                if (strncmp(curr_ptr, srcgtag_keys[k], strlen(srcgtag_keys[k])) == 0 ) {
                    /* Check if the curr_ptr is None which indicates that this
                       object is not to be color managed.  Also, if the
                       curr_ptr is Replace which indicates we will be doing
                       direct replacement of the colors.  */
                    curr_ptr = gs_strtok(NULL, "\t, \n\r", &last);
                    if (strncmp(curr_ptr, GSICC_SRCTAG_NOCM, strlen(GSICC_SRCTAG_NOCM)) == 0 &&
                        strlen(curr_ptr) == strlen(GSICC_SRCTAG_NOCM)) {
                        cmm = gsCMM_NONE;
                        icc_profile = NULL;
                        break;
                    } else if ((strncmp(curr_ptr, GSICC_SRCTAG_REPLACE, strlen(GSICC_SRCTAG_REPLACE)) == 0 &&
                        strlen(curr_ptr) == strlen(GSICC_SRCTAG_REPLACE))) {
                        cmm = gsCMM_REPLACE;
                        icc_profile = NULL;
                        break;
                    } else {
                        /* Try to open the file and set the profile */
                        code = gsicc_open_search(curr_ptr, strlen(curr_ptr), mem,
                                                 mem->gs_lib_ctx->profiledir,
                                                 mem->gs_lib_ctx->profiledir_len, &str);
                        if (code < 0)
                            return code;
                        if (str != NULL) {
                            icc_profile =
                                gsicc_profile_new(str, mem, curr_ptr, strlen(curr_ptr));
                            code = sfclose(str);
                            if (code < 0)
                                return code;
                        }
                        if (str != NULL && icc_profile != NULL) {
                            code = gsicc_init_profile_info(icc_profile);
                            if (code < 0)
                                return code;
                            cmm = gsCMM_DEFAULT;
                            /* Check if this object is a devicelink profile.
                               If it is then the intent, blackpoint etc. are not
                               read nor used when dealing with these profiles */
                            gsicc_check_device_link(icc_profile, icc_profile->memory);
                            break;
                        } else {
                            /* Failed to open profile file. End this now. */
                            gs_free_object(mem, buffer_ptr, "gsicc_set_srcgtag_struct");
                            rc_decrement(srcgtag, "gsicc_set_srcgtag_struct");
                            return gs_throw1(-1,
                                    "setting of %s src obj color info failed", pname);
                        }
                    }
                }
            }
            /* Get the intent now and set the profile. If GSICC_SRCGTAG_KEYS
               order changes this switch needs to change also */
            switch (k) {
                case COLOR_TUNE:
                    /* Color tune profile. No intent */
                    srcgtag->color_warp_profile = icc_profile;
                    break;
                case GRAPHIC_CMYK:
                    srcgtag->cmyk_profiles[gsSRC_GRAPPRO] = icc_profile;
                    srcgtag->cmyk_rend_cond[gsSRC_GRAPPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->cmyk_rend_cond[gsSRC_GRAPPRO]), &last, true);
                        if (code < 0)
                            return code;
                    }
                    break;
                case IMAGE_CMYK:
                    srcgtag->cmyk_profiles[gsSRC_IMAGPRO] = icc_profile;
                    srcgtag->cmyk_rend_cond[gsSRC_IMAGPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->cmyk_rend_cond[gsSRC_IMAGPRO]), &last, true);
                        if (code < 0)
                            return code;
                    }
                    break;
                case TEXT_CMYK:
                    srcgtag->cmyk_profiles[gsSRC_TEXTPRO] = icc_profile;
                    srcgtag->cmyk_rend_cond[gsSRC_TEXTPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->cmyk_rend_cond[gsSRC_TEXTPRO]), &last, true);
                        if (code < 0)
                            return code;
                    }
                    break;
                case GRAPHIC_RGB:
                    srcgtag->rgb_profiles[gsSRC_GRAPPRO] = icc_profile;
                    srcgtag->rgb_rend_cond[gsSRC_GRAPPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->rgb_rend_cond[gsSRC_GRAPPRO]), &last, false);
                        if (code < 0)
                            return code;
                    }
                   break;
                case IMAGE_RGB:
                    srcgtag->rgb_profiles[gsSRC_IMAGPRO] = icc_profile;
                    srcgtag->rgb_rend_cond[gsSRC_IMAGPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->rgb_rend_cond[gsSRC_IMAGPRO]), &last, false);
                        if (code < 0)
                            return code;
                    }
                    break;
                case TEXT_RGB:
                    srcgtag->rgb_profiles[gsSRC_TEXTPRO] = icc_profile;
                    srcgtag->rgb_rend_cond[gsSRC_TEXTPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->rgb_rend_cond[gsSRC_TEXTPRO]), &last, false);
                        if (code < 0)
                            return code;
                    }
                    break;
                case GRAPHIC_GRAY:
                    srcgtag->gray_profiles[gsSRC_GRAPPRO] = icc_profile;
                    srcgtag->gray_rend_cond[gsSRC_GRAPPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->gray_rend_cond[gsSRC_GRAPPRO]), &last, false);
                        if (code < 0)
                            return code;
                    }
                    break;
                case IMAGE_GRAY:
                    srcgtag->gray_profiles[gsSRC_IMAGPRO] = icc_profile;
                    srcgtag->gray_rend_cond[gsSRC_IMAGPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->gray_rend_cond[gsSRC_IMAGPRO]), &last, false);
                        if (code < 0)
                            return code;
                    }
                    break;
                case TEXT_GRAY:
                    srcgtag->gray_profiles[gsSRC_TEXTPRO] = icc_profile;
                    srcgtag->gray_rend_cond[gsSRC_TEXTPRO].cmm = cmm;
                    if (cmm == gsCMM_DEFAULT) {
                        code = gsicc_fill_srcgtag_item(&(srcgtag->gray_rend_cond[gsSRC_TEXTPRO]), &last, false);
                        if (code < 0)
                            return code;
                    }
                    break;
                case GSICC_NUM_SRCGTAG_KEYS:
                    /* Failed to match the key */
                    gs_free_object(mem, buffer_ptr, "gsicc_set_srcgtag_struct");
                    rc_decrement(srcgtag, "gsicc_set_srcgtag_struct");
                    return gs_throw1(-1, "failed to find key in %s", pname);
                    break;
                default:
                    /* Some issue */
                    gs_free_object(mem, buffer_ptr, "gsicc_set_srcgtag_struct");
                    rc_decrement(srcgtag, "gsicc_set_srcgtag_struct");
                    return gs_throw1(-1, "Error in srcgtag data %s", pname);
                    break;
            }
        }
    } else {
        return gs_throw1(-1, "setting of %s src obj color info failed", pname);
    }
    gs_free_object(mem, buffer_ptr, "gsicc_set_srcgtag_struct");
    srcgtag->name_length = strlen(pname);
    srcgtag->name = (char*) gs_alloc_bytes(mem, srcgtag->name_length,
                                  "gsicc_set_srcgtag_struct");
    if (srcgtag->name == NULL)
        return gs_throw(gs_error_VMerror, "Insufficient memory for tag name");
    strncpy(srcgtag->name, pname, srcgtag->name_length);
    icc_manager->srcgtag_profile = srcgtag;
    return 0;
}

/*  This computes the hash code for the ICC data and assigns the code and the
    profile to the appropriate member variable in the ICC manager */
int
gsicc_set_profile(gsicc_manager_t *icc_manager, const char* pname, int namelen,
                  gsicc_profile_t defaulttype)
{
    cmm_profile_t *icc_profile;
    cmm_profile_t **manager_default_profile = NULL; /* quite compiler */
    stream *str;
    gs_memory_t *mem_gc = icc_manager->memory;
    int code;
    int k;
    int num_comps = 0;
    gsicc_colorbuffer_t default_space; /* Used to verify that we have the correct type */

    /* We need to check for the smask swapped profile condition.  If we are in
       that state, then any requests for setting the profile will be ignored.
       This is valid, since we are in the middle of drawing right now and this
       only would occur if we are doing a vmreclaim while in the middle of
       soft mask rendering */
    default_space = gsUNDEFINED;
    if (icc_manager->smask_profiles !=NULL &&
        icc_manager->smask_profiles->swapped == true) {
            return 0;
    } else {
        switch(defaulttype) {
            case DEFAULT_GRAY:
                manager_default_profile = &(icc_manager->default_gray);
                default_space = gsGRAY;
                num_comps = 1;
                break;
            case DEFAULT_RGB:
                manager_default_profile = &(icc_manager->default_rgb);
                default_space = gsRGB;
                num_comps = 3;
                break;
            case DEFAULT_CMYK:
                 manager_default_profile = &(icc_manager->default_cmyk);
                 default_space = gsCMYK;
                 num_comps = 4;
                 break;
            case NAMED_TYPE:
                 manager_default_profile = &(icc_manager->device_named);
                 default_space = gsNAMED;
                 break;
            case LAB_TYPE:
                 manager_default_profile = &(icc_manager->lab_profile);
                 num_comps = 3;
                 default_space = gsCIELAB;
                 break;
            case DEVICEN_TYPE:
                manager_default_profile = NULL;
                default_space = gsNCHANNEL;
                break;
            case DEFAULT_NONE:
            default:
                return 0;
                break;
        }
    }
    /* If it is not NULL then it has already been set. If it is different than
       what we already have then we will want to free it.  Since other
       gs_gstates could have different default profiles, this is done via reference
       counting.  If it is the same as what we already have then we DONT
       increment, since that is done when the gs_gstate is duplicated.  It
       could be the same, due to a resetting of the user params. To avoid
       recreating the profile data, we compare the string names. */
    if (defaulttype != DEVICEN_TYPE && (*manager_default_profile) != NULL) {
        /* Check if this is what we already have.  Also check if it is the
           output intent profile.  */
        icc_profile = *manager_default_profile;
        if ( namelen == icc_profile->name_length ) {
            if( memcmp(pname, icc_profile->name, namelen) == 0)
                return 0;
        }
        if (strncmp(icc_profile->name, OI_PROFILE,
                    strlen(icc_profile->name)) == 0) {
                return 0;
        }
        gsicc_adjust_profile_rc(icc_profile, -1,"gsicc_set_profile");
        /* Icky - if the creation of the new profile fails, we end up with a dangling
           pointer, or a wrong reference count - so NULL the appropriate entry here
         */
        switch(defaulttype) {
            case DEFAULT_GRAY:
                icc_manager->default_gray = NULL;
                break;
            case DEFAULT_RGB:
                icc_manager->default_rgb = NULL;
                break;
            case DEFAULT_CMYK:
                 icc_manager->default_cmyk = NULL;
                 break;
            case NAMED_TYPE:
                 icc_manager->device_named = NULL;
                 break;
            case LAB_TYPE:
                 icc_manager->lab_profile = NULL;
                 break;
            default:
                break;
        }
    }
    /* We need to do a special check for DeviceN since we have a linked list of
       profiles and we can have multiple specifications */
    if (defaulttype == DEVICEN_TYPE) {
        if (icc_manager->device_n != NULL) {
            gsicc_devicen_entry_t *current_entry = icc_manager->device_n->head;
            for (k = 0; k < icc_manager->device_n->count; k++) {
                if (current_entry->iccprofile != NULL) {
                    icc_profile = current_entry->iccprofile;
                    if (namelen == icc_profile->name_length)
                    if (memcmp(pname, icc_profile->name, namelen) == 0)
                        return 0;
                }
                current_entry = current_entry->next;
            }
        }
        /* An entry was not found.  We need to create a new one to use */
        code = gsicc_new_devicen(icc_manager);
        if (code < 0)
            return code;
        manager_default_profile = &(icc_manager->device_n->final->iccprofile);
    }
    code = gsicc_open_search(pname, namelen, mem_gc, mem_gc->gs_lib_ctx->profiledir,
                             mem_gc->gs_lib_ctx->profiledir_len, &str);
    if (code < 0)
        return code;
    if (str != NULL) {
        icc_profile = gsicc_profile_new(str, mem_gc, pname, namelen);
        /* Add check so that we detect cases where we are loading a named
           color structure that is not a standard profile type */
        if (icc_profile == NULL && defaulttype == NAMED_TYPE) {
            /* Failed to load the named color profile.  Just load the file
               into the buffer as it is.  The profile_handle member
               variable can then be used to hold the named color
               structure that is actually search. This is created later
               when needed. */
            char *nameptr;

            icc_profile = gsicc_profile_new(NULL, mem_gc, NULL, 0);
            if (icc_profile == NULL)
                return gs_throw(gs_error_VMerror, "Creation of ICC profile failed");
            icc_profile->data_cs = gsNAMED;
            code = gsicc_load_namedcolor_buffer(icc_profile, str, mem_gc);
            if (code < 0) return gs_throw1(-1, "problems with profile %s", pname);
            *manager_default_profile = icc_profile;
            nameptr = (char*) gs_alloc_bytes(icc_profile->memory, namelen+1,
                                             "gsicc_set_profile");
            if (nameptr == NULL)
                return gs_throw(gs_error_VMerror, "Insufficient memory for profile name");
            memcpy(nameptr, pname, namelen);
            nameptr[namelen] = '\0';
            icc_profile->name = nameptr;
            icc_profile->name_length = namelen;
            return 0;  /* Done now, since this is not a standard ICC profile */
        }
        code = sfclose(str);
        if (icc_profile == NULL) {
            return gs_throw1(-1, "problems with profile %s",pname);
        }
         *manager_default_profile = icc_profile;
        icc_profile->default_match = defaulttype;
        if (defaulttype == LAB_TYPE)
            icc_profile->islab = true;
        if ( defaulttype == DEVICEN_TYPE ) {
            /* Lets get the name information out of the profile.
               The names are contained in the icSigNamedColor2Tag
               item.  The table is in the A2B0Tag item.
               The names are in the order such that the fastest
               index in the table is the first name */
            gsicc_get_devicen_names(icc_profile, icc_manager->memory);
            /* Init this profile now */
            code = gsicc_init_profile_info(icc_profile);
            if (code < 0) return gs_throw1(-1, "problems with profile %s", pname);
        } else {
            /* Delay the loading of the handle buffer until we need the profile.
               But set some basic stuff that we need. Take care of DeviceN
               profile now, since we don't know the number of components etc */
            icc_profile->num_comps = num_comps;
            icc_profile->num_comps_out = 3;
            gsicc_set_icc_range(&icc_profile);
            icc_profile->data_cs = default_space;
        }
        return 0;
    }
    return -1;
}

/* This is used ONLY for delayed initialization of the "default" ICC profiles
   that are in the ICC manager.  This way we avoid getting these profile handles
   until we actually need them. Note that defaulttype is preset.  These are
   the *only* profiles that are delayed in this manner.  All embedded profiles
   and internally generated profiles have their handles found immediately */
int
gsicc_initialize_default_profile(cmm_profile_t *icc_profile)
{
    gsicc_profile_t defaulttype = icc_profile->default_match;
    gsicc_colorbuffer_t default_space = gsUNDEFINED;
    int num_comps, num_comps_out;
    gs_memory_t *mem = icc_profile->memory;

    /* Get the profile handle if it is not already set */
    if (icc_profile->profile_handle == NULL) {
        icc_profile->profile_handle =
                        gsicc_get_profile_handle_buffer(icc_profile->buffer,
                                                        icc_profile->buffer_size,
                                                        mem);
        if (icc_profile->profile_handle == NULL) {
            return gs_rethrow1(gs_error_VMerror, "allocation of profile %s handle failed",
                               icc_profile->name);
        }
    }
    if (icc_profile->buffer != NULL && icc_profile->hash_is_valid == false) {
        /* Compute the hash code of the profile. */
        gsicc_get_icc_buff_hash(icc_profile->buffer, &(icc_profile->hashcode),
                                icc_profile->buffer_size);
        icc_profile->hash_is_valid = true;
    }
    num_comps = icc_profile->num_comps;
    icc_profile->num_comps =
        gscms_get_input_channel_count(icc_profile->profile_handle,
            icc_profile->memory);
    num_comps_out = icc_profile->num_comps_out;
    icc_profile->num_comps_out =
        gscms_get_output_channel_count(icc_profile->profile_handle,
            icc_profile->memory);
    icc_profile->data_cs =
        gscms_get_profile_data_space(icc_profile->profile_handle,
            icc_profile->memory);
    if_debug0m(gs_debug_flag_icc,mem,"[icc] Setting ICC profile in Manager\n");
    switch(defaulttype) {
        case DEFAULT_GRAY:
            if_debug0m(gs_debug_flag_icc,mem,"[icc] Default Gray\n");
            default_space = gsGRAY;
            break;
        case DEFAULT_RGB:
            if_debug0m(gs_debug_flag_icc,mem,"[icc] Default RGB\n");
            default_space = gsRGB;
            break;
        case DEFAULT_CMYK:
            if_debug0m(gs_debug_flag_icc,mem,"[icc] Default CMYK\n");
            default_space = gsCMYK;
             break;
        case NAMED_TYPE:
            if_debug0m(gs_debug_flag_icc,mem,"[icc] Named Color\n");
            break;
        case LAB_TYPE:
            if_debug0m(gs_debug_flag_icc,mem,"[icc] CIELAB Profile\n");
            break;
        case DEVICEN_TYPE:
            if_debug0m(gs_debug_flag_icc,mem,"[icc] DeviceN Profile\n");
            break;
        case DEFAULT_NONE:
        default:
            return 0;
            break;
    }
    if_debug1m(gs_debug_flag_icc,mem,"[icc] name = %s\n", icc_profile->name);
    if_debug1m(gs_debug_flag_icc,mem,"[icc] num_comps = %d\n", icc_profile->num_comps);
    /* Check that we have the proper color space for the ICC
       profiles that can be externally set */
    if (default_space != gsUNDEFINED ||
        num_comps != icc_profile->num_comps ||
        num_comps_out != icc_profile->num_comps_out) {
        if (icc_profile->data_cs != default_space) {
            return gs_rethrow(-1, "A default profile has an incorrect color space");
        }
    }
    return 0;
}

/* This is used to get the profile handle given a file name  */
cmm_profile_t*
gsicc_get_profile_handle_file(const char* pname, int namelen, gs_memory_t *mem)
{
    cmm_profile_t *result;
    stream* str;
    int code;

    /* First see if we can get the stream. */
    code = gsicc_open_search(pname, namelen, mem, mem->gs_lib_ctx->profiledir,
        mem->gs_lib_ctx->profiledir_len, &str);
    if (code < 0 || str == NULL) {
        gs_throw(gs_error_VMerror, "Creation of ICC profile failed");
        return NULL;
    }
    result = gsicc_profile_new(str, mem, pname, namelen);
    code = sfclose(str);
    if (result == NULL) {
        gs_throw(gs_error_VMerror, "Creation of ICC profile failed");
        return NULL;
    }
    code = gsicc_init_profile_info(result);
    if (code < 0) {
        gs_throw(gs_error_VMerror, "Creation of ICC profile failed");
        return NULL;
    }
    return result;
}

/* Given that we already have a profile in a buffer (e.g. generated from a PS object)
   this gets the handle and initializes the various member variables that we need */
int
gsicc_init_profile_info(cmm_profile_t *profile)
{
    int k;

    /* Get the profile handle */
    profile->profile_handle =
        gsicc_get_profile_handle_buffer(profile->buffer,
                                        profile->buffer_size,
                                        profile->memory);
    if (profile->profile_handle == NULL)
        return -1;

    /* Compute the hash code of the profile. */
    gsicc_get_icc_buff_hash(profile->buffer, &(profile->hashcode),
                            profile->buffer_size);
    profile->hash_is_valid = true;
    profile->default_match = DEFAULT_NONE;
    profile->num_comps = gscms_get_input_channel_count(profile->profile_handle,
        profile->memory);
    profile->num_comps_out = gscms_get_output_channel_count(profile->profile_handle,
        profile->memory);
    profile->data_cs = gscms_get_profile_data_space(profile->profile_handle,
        profile->memory);

    /* Initialize the range to default values */
    for ( k = 0; k < profile->num_comps; k++) {
        profile->Range.ranges[k].rmin = 0.0;
        profile->Range.ranges[k].rmax = 1.0;
    }
    return 0;
}

/* This is used to try to find the specified or default ICC profiles */
/* This is where we would enhance the directory searching to use a   */
/* list of paths separated by ':' (unix) or ';' Windows              */
int
gsicc_open_search(const char* pname, int namelen, gs_memory_t *mem_gc,
                  const char* dirname, int dirlen, stream**strp)
{
    char *buffer;
    stream* str;

    /* Check if we need to prepend the file name  */
    if ( dirname != NULL) {
        /* If this fails, we will still try the file by itself and with
           %rom% since someone may have left a space some of the spaces
           as our defaults, even if they defined the directory to use.
           This will occur only after searching the defined directory.
           A warning is noted.  */
        buffer = (char *) gs_alloc_bytes(mem_gc, namelen + dirlen + 1,
                                     "gsicc_open_search");
        if (buffer == NULL)
            return_error(gs_error_VMerror);
        strcpy(buffer, dirname);
        buffer[dirlen] = '\0';
        strcat(buffer, pname);
        /* Just to make sure we were null terminated */
        buffer[namelen + dirlen] = '\0';

        if (gs_check_file_permission(mem_gc, buffer, strlen(buffer), "r") >= 0) {
            str = sfopen(buffer, "r", mem_gc);
            gs_free_object(mem_gc, buffer, "gsicc_open_search");
            if (str != NULL) {
                *strp = str;
                return 0;
            }
        }
        else {
            gs_free_object(mem_gc, buffer, "gsicc_open_search");
        }
    }

    /* First just try it like it is */
    if (gs_check_file_permission(mem_gc, pname, namelen, "r") >= 0) {
        str = sfopen(pname, "r", mem_gc);
        if (str != NULL) {
            *strp = str;
            return 0;
        }
    }

    /* If that fails, try %rom% */ /* FIXME: Not sure this is needed or correct */
                                   /* A better approach might be to have built in defaults */
    buffer = (char *) gs_alloc_bytes(mem_gc, 1 + namelen +
                        strlen(DEFAULT_DIR_ICC),"gsicc_open_search");
    if (buffer == NULL)
        return_error(gs_error_VMerror);
    strcpy(buffer, DEFAULT_DIR_ICC);
    strcat(buffer, pname);
    /* Just to make sure we were null terminated */
    buffer[namelen + strlen(DEFAULT_DIR_ICC)] = '\0';
    str = sfopen(buffer, "r", mem_gc);
    gs_free_object(mem_gc, buffer, "gsicc_open_search");
    if (str == NULL) {
        gs_warn1("Could not find %s ",pname);
    }
    *strp = str;
    return 0;
}

/* Free source object icc array structure.  */
static void
rc_free_srcgtag_profile(gs_memory_t * mem, void *ptr_in, client_name_t cname)
{
    cmm_srcgtag_profile_t *srcgtag_profile = (cmm_srcgtag_profile_t *)ptr_in;
    int k;
    gs_memory_t *mem_nongc =  srcgtag_profile->memory;

    if (srcgtag_profile->rc.ref_count <= 1 ) {
        /* Decrement any profiles. */
        for (k = 0; k < NUM_SOURCE_PROFILES; k++) {
            if (srcgtag_profile->gray_profiles[k] != NULL) {
                gsicc_adjust_profile_rc(srcgtag_profile->gray_profiles[k], -1,
                    "rc_free_srcgtag_profile(gray)");
            }
            if (srcgtag_profile->rgb_profiles[k] != NULL) {
                gsicc_adjust_profile_rc(srcgtag_profile->rgb_profiles[k], -1,
                             "rc_free_srcgtag_profile(rgb)");
            }
            if (srcgtag_profile->cmyk_profiles[k] != NULL) {
                gsicc_adjust_profile_rc(srcgtag_profile->cmyk_profiles[k], -1,
                             "rc_free_srcgtag_profile(cmyk)");
            }
            if (srcgtag_profile->color_warp_profile != NULL) {
                gsicc_adjust_profile_rc(srcgtag_profile->color_warp_profile, -1,
                             "rc_free_srcgtag_profile(warp)");
            }
        }
        gs_free_object(mem_nongc, srcgtag_profile->name, "rc_free_srcgtag_profile");
        gs_free_object(mem_nongc, srcgtag_profile, "rc_free_srcgtag_profile");
    }
}

/* Allocate source object icc structure. */
static cmm_srcgtag_profile_t*
gsicc_new_srcgtag_profile(gs_memory_t *memory)
{
    cmm_srcgtag_profile_t *result;
    int k;

    result = (cmm_srcgtag_profile_t *) gs_alloc_bytes(memory->non_gc_memory,
                                            sizeof(cmm_srcgtag_profile_t),
                                            "gsicc_new_srcgtag_profile");
    if (result == NULL)
        return NULL;
    result->memory = memory->non_gc_memory;

    for (k = 0; k < NUM_SOURCE_PROFILES; k++) {
        result->rgb_profiles[k] = NULL;
        result->cmyk_profiles[k] = NULL;
        result->gray_profiles[k] = NULL;
        result->gray_rend_cond[k].black_point_comp = gsBPNOTSPECIFIED;
        result->gray_rend_cond[k].rendering_intent = gsRINOTSPECIFIED;
        result->gray_rend_cond[k].override_icc = false;
        result->gray_rend_cond[k].preserve_black = gsBKPRESNOTSPECIFIED;
        result->gray_rend_cond[k].cmm = gsCMM_DEFAULT;
        result->rgb_rend_cond[k].black_point_comp = gsBPNOTSPECIFIED;
        result->rgb_rend_cond[k].rendering_intent = gsRINOTSPECIFIED;
        result->rgb_rend_cond[k].override_icc = false;
        result->rgb_rend_cond[k].preserve_black = gsBKPRESNOTSPECIFIED;
        result->rgb_rend_cond[k].cmm = gsCMM_DEFAULT;
        result->cmyk_rend_cond[k].black_point_comp = gsBPNOTSPECIFIED;
        result->cmyk_rend_cond[k].rendering_intent = gsRINOTSPECIFIED;
        result->cmyk_rend_cond[k].override_icc = false;
        result->cmyk_rend_cond[k].preserve_black = gsBKPRESNOTSPECIFIED;
        result->cmyk_rend_cond[k].cmm = gsCMM_DEFAULT;
    }
    result->color_warp_profile = NULL;
    result->name = NULL;
    result->name_length = 0;
    rc_init_free(result, memory->non_gc_memory, 1, rc_free_srcgtag_profile);
    return result;
}

static void
gsicc_free_spotnames(gsicc_namelist_t *spotnames, gs_memory_t * mem)
{
    int k;
    gsicc_colorname_t *curr_name, *next_name;

    curr_name = spotnames->head;
    for (k = 0; k < spotnames->count; k++) {
        next_name = curr_name->next;
        /* Free the name */
        gs_free_object(mem, curr_name->name, "gsicc_free_spotnames");
        /* Free the name structure */
        gs_free_object(mem, curr_name, "gsicc_free_spotnames");
        curr_name = next_name;
    }
    if (spotnames->color_map != NULL) {
        gs_free_object(mem, spotnames->color_map, "gsicc_free_spotnames");
    }
    if (spotnames->name_str != NULL) {
        gs_free_object(mem, spotnames->name_str, "gsicc_free_spotnames");
    }
}

/* Free device icc array structure.  */
static void
rc_free_profile_array(gs_memory_t * mem, void *ptr_in, client_name_t cname)
{
    cmm_dev_profile_t *icc_struct = (cmm_dev_profile_t *)ptr_in;
    int k;
    gs_memory_t *mem_nongc =  icc_struct->memory;

    if (icc_struct->rc.ref_count <= 1 ) {
        /* Decrement any profiles. */
        for (k = 0; k < NUM_DEVICE_PROFILES; k++) {
            if (icc_struct->device_profile[k] != NULL) {
                if_debug1m(gs_debug_flag_icc, mem_nongc,
                           "[icc] Releasing device profile %d\n", k);
                gsicc_adjust_profile_rc(icc_struct->device_profile[k], -1,
                             "rc_free_profile_array");
            }
        }
        if (icc_struct->link_profile != NULL) {
            if_debug0m(gs_debug_flag_icc,mem_nongc,"[icc] Releasing link profile\n");
            gsicc_adjust_profile_rc(icc_struct->link_profile, -1, "rc_free_profile_array");
        }
        if (icc_struct->proof_profile != NULL) {
            if_debug0m(gs_debug_flag_icc,mem_nongc,"[icc] Releasing proof profile\n");
            gsicc_adjust_profile_rc(icc_struct->proof_profile, -1, "rc_free_profile_array");
        }
        if (icc_struct->oi_profile != NULL) {
            if_debug0m(gs_debug_flag_icc,mem_nongc,"[icc] Releasing oi profile\n");
            gsicc_adjust_profile_rc(icc_struct->oi_profile, -1, "rc_free_profile_array");
        }
        if (icc_struct->postren_profile != NULL) {
            if_debug0m(gs_debug_flag_icc, mem_nongc, "[icc] Releasing postren profile\n");
            gsicc_adjust_profile_rc(icc_struct->postren_profile, -1, "rc_free_profile_array");
        }
        if (icc_struct->blend_profile != NULL) {
            if_debug0m(gs_debug_flag_icc, mem_nongc, "[icc] Releasing blend profile\n");
            gsicc_adjust_profile_rc(icc_struct->blend_profile, -1, "rc_free_profile_array");
        }
        if (icc_struct->spotnames != NULL) {
            if_debug0m(gs_debug_flag_icc, mem_nongc, "[icc] Releasing spotnames\n");
            /* Free the linked list in this object */
            gsicc_free_spotnames(icc_struct->spotnames, mem_nongc);
            /* Free the main object */
            gs_free_object(mem_nongc, icc_struct->spotnames, "rc_free_profile_array");
        }
        if_debug0m(gs_debug_flag_icc,mem_nongc,"[icc] Releasing device profile struct\n");
        gs_free_object(mem_nongc, icc_struct, "rc_free_profile_array");
    }
}

/* Allocate device icc structure. The actual profiles are in this structure */
cmm_dev_profile_t*
gsicc_new_device_profile_array(gs_memory_t *memory)
{
    cmm_dev_profile_t *result;
    int k;

    if_debug0m(gs_debug_flag_icc,memory,"[icc] Allocating device profile struct\n");
    result = (cmm_dev_profile_t *) gs_alloc_bytes(memory->non_gc_memory,
                                            sizeof(cmm_dev_profile_t),
                                            "gsicc_new_device_profile_array");
    if (result == NULL)
        return NULL;
    result->memory = memory->non_gc_memory;

    for (k = 0; k < NUM_DEVICE_PROFILES; k++) {
        result->device_profile[k] = NULL;
        result->rendercond[k].rendering_intent = gsRINOTSPECIFIED;
        result->rendercond[k].black_point_comp = gsBPNOTSPECIFIED;
        result->rendercond[k].override_icc = false;
        result->rendercond[k].preserve_black = gsBKPRESNOTSPECIFIED;
        result->rendercond[k].graphics_type_tag = GS_UNKNOWN_TAG;
        result->rendercond[k].cmm = gsCMM_DEFAULT;
    }
    result->proof_profile = NULL;
    result->link_profile = NULL;
    result->postren_profile = NULL;
    result->blend_profile = NULL;
    result->oi_profile = NULL;
    result->spotnames = NULL;
    result->devicegraytok = true;  /* Default is to map gray to pure K */
    result->graydetection = false;
    result->pageneutralcolor = false;
    result->usefastcolor = false;  /* Default is to not use fast color */
    result->prebandthreshold = true;
    result->supports_devn = false;
    result->sim_overprint = true;  /* Default is to simulate overprint */
    rc_init_free(result, memory->non_gc_memory, 1, rc_free_profile_array);
    return result;
}

int
gsicc_set_device_blackpreserve(gx_device *dev, gsicc_blackpreserve_t blackpreserve,
                                gsicc_profile_types_t profile_type)
{
    int code;
    cmm_dev_profile_t *profile_struct;

    /* Although device methods should not be NULL, they are not completely filled in until
     * gx_device_fill_in_procs is called, and its possible for us to get here before this
     * happens, so we *must* make sure the method is not NULL before we use it.
     */
    if (dev_proc(dev, get_profile) == NULL) {
        profile_struct = dev->icc_struct;
    } else {
        code = dev_proc(dev, get_profile)(dev,  &profile_struct);
        if (code < 0)
            return code;
    }
    if (profile_struct ==  NULL)
        return 0;
    profile_struct->rendercond[profile_type].preserve_black = blackpreserve;
    return 0;
}

int
gsicc_set_device_profile_intent(gx_device *dev, gsicc_rendering_intents_t intent,
                                gsicc_profile_types_t profile_type)
{
    int code;
    cmm_dev_profile_t *profile_struct;

    /* Although device methods should not be NULL, they are not completely filled in until
     * gx_device_fill_in_procs is called, and its possible for us to get here before this
     * happens, so we *must* make sure the method is not NULL before we use it.
     */
    if (dev_proc(dev, get_profile) == NULL) {
        profile_struct = dev->icc_struct;
    } else {
        code = dev_proc(dev, get_profile)(dev,  &profile_struct);
        if (code < 0)
            return code;
    }
    if (profile_struct ==  NULL)
        return 0;
    profile_struct->rendercond[profile_type].rendering_intent = intent;
    return 0;
}

int
gsicc_set_device_blackptcomp(gx_device *dev, gsicc_blackptcomp_t blackptcomp,
                                gsicc_profile_types_t profile_type)
{
    int code = 0;
    cmm_dev_profile_t *profile_struct;

    /* Although device methods should not be NULL, they are not completely filled in until
     * gx_device_fill_in_procs is called, and its possible for us to get here before this
     * happens, so we *must* make sure the method is not NULL before we use it.
     */
    if (dev_proc(dev, get_profile) == NULL) {
        profile_struct = dev->icc_struct;
    } else {
        code = dev_proc(dev, get_profile)(dev,  &profile_struct);
    }
    if (profile_struct ==  NULL)
        return 0;
    profile_struct->rendercond[profile_type].black_point_comp = blackptcomp;
    return code;
}

/* This is used to set up the equivalent cmyk colors for the spots that may
   exist in an output DeviceN profile.  We do this by faking a new separation
   space for each one */
int
gsicc_set_devicen_equiv_colors(gx_device *dev, const gs_gstate * pgs,
                               cmm_profile_t *profile)
{
    gs_gstate temp_state = *((gs_gstate*)pgs);
    gs_color_space *pcspace = gs_cspace_alloc(pgs->memory->non_gc_memory,
                                              &gs_color_space_type_ICC);
    if (pcspace == NULL)
        return gs_throw(gs_error_VMerror, "Insufficient memory for devn equiv colors");
    pcspace->cmm_icc_profile_data = profile;
    temp_state.color[0].color_space = pcspace;
    return dev_proc(dev, update_spot_equivalent_colors)(dev, &temp_state);
}

#define DEFAULT_ICC_PROCESS "Cyan, Magenta, Yellow, Black,"
#define DEFAULT_ICC_PROCESS_LENGTH 30
#define DEFAULT_ICC_COLORANT_NAME "ICC_COLOR_"
#define DEFAULT_ICC_COLORANT_LENGTH 12
/* allow at most 16 colorants */
/* This sets the colorants structure up in the device profile for when
   we are dealing with DeviceN type output profiles.  Note
   that this feature is only used with the tiffsep and psdcmyk devices.
   If name_str is null it will use default names for the colorants */
int
gsicc_set_device_profile_colorants(gx_device *dev, char *name_str)
{
    int code;
    cmm_dev_profile_t *profile_struct;
    gsicc_colorname_t *name_entry;
    gsicc_colorname_t **curr_entry;
    gs_memory_t *mem;
    char *temp_ptr, *last = NULL;
    int done;
    gsicc_namelist_t *spot_names;
    char *pch;
    int str_len;
    int k;
    bool free_str = false;

    code = dev_proc(dev, get_profile)((gx_device *)dev, &profile_struct);
    if (profile_struct != NULL) {
        int count = 0;

        if (name_str == NULL) {
            /* Create a default name string that we can use */
            int total_len;
            int kk;
            int num_comps = profile_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE]->num_comps;
            char temp_str[DEFAULT_ICC_COLORANT_LENGTH+2];

            /* If names are already set then we do not want to set default ones */
            if (profile_struct->spotnames != NULL)
                return 0;

            free_str = true;
            /* Assume first 4 are CMYK */
            total_len = ((DEFAULT_ICC_COLORANT_LENGTH + 1) * (num_comps-4)) +
                        DEFAULT_ICC_PROCESS_LENGTH - 1;  /* -1 due to no comma at end */
            name_str = (char*) gs_alloc_bytes(dev->memory, total_len+1,
                                               "gsicc_set_device_profile_colorants");
            if (name_str == NULL)
                return gs_throw(gs_error_VMerror, "Insufficient memory for colorant name");
            gs_sprintf(name_str, DEFAULT_ICC_PROCESS);
            for (kk = 0; kk < num_comps-5; kk++) {
                gs_sprintf(temp_str,"ICC_COLOR_%d,",kk);
                strcat(name_str,temp_str);
            }
            /* Last one no comma */
            gs_sprintf(temp_str,"ICC_COLOR_%d",kk);
            strcat(name_str,temp_str);
        }
        str_len = strlen(name_str);
        if (profile_struct->spotnames != NULL &&
            profile_struct->spotnames->name_str != NULL &&
            strlen(profile_struct->spotnames->name_str) == str_len) {
            /* Here we check if the names are the same */
            if (strncmp(name_str, profile_struct->spotnames->name_str, str_len) == 0) {
                if (free_str)
                    gs_free_object(dev->memory, name_str,
                                            "gsicc_set_device_profile_colorants");
                return 0;
            }
        }
        mem = dev->memory->non_gc_memory;
        /* We need to free the existing one if there was one */
        if (profile_struct->spotnames != NULL) {
            /* Free the linked list in this object */
            gsicc_free_spotnames(profile_struct->spotnames, mem);
            /* Free the main object */
            gs_free_object(mem, profile_struct->spotnames,
                           "gsicc_set_device_profile_colorants");
        }
        /* Allocate structure for managing names */
        spot_names = gsicc_new_namelist(mem);
        profile_struct->spotnames = spot_names;
        spot_names->name_str = (char*) gs_alloc_bytes(mem, str_len+1,
                                               "gsicc_set_device_profile_colorants");
        if (spot_names->name_str == NULL)
            return gs_throw(gs_error_VMerror, "Insufficient memory for spot name");
        memcpy(spot_names->name_str, name_str, strlen(name_str));
        spot_names->name_str[str_len] = 0;
        curr_entry = &(spot_names->head);
         /* Go ahead and tokenize now */
        pch = gs_strtok(name_str, ",", &last);
        count = 0;
        while (pch != NULL) {
            temp_ptr = pch;
            done = 0;
            /* Remove any leading spaces */
            while (!done) {
                if (*temp_ptr == 0x20) {
                    temp_ptr++;
                } else {
                    done = 1;
                }
            }
            /* Allocate a new name object */
            name_entry = gsicc_new_colorname(mem);
            /* Set our current entry to this one */
            *curr_entry = name_entry;
            name_entry->length = strlen(temp_ptr);
            name_entry->name = (char *) gs_alloc_bytes(mem, name_entry->length,
                                        "gsicc_set_device_profile_colorants");
            if (spot_names->name_str == NULL)
                return gs_throw(gs_error_VMerror, "Insufficient memory for spot name");
            memcpy(name_entry->name, temp_ptr, name_entry->length);
            /* Get the next entry location */
            curr_entry = &((*curr_entry)->next);
            count += 1;
            pch = gs_strtok(NULL, ",", &last);
        }
        spot_names->count = count;
        /* Create the color map.  Query the device to find out where these
           colorants are located.   It is possible that the device may
           not be opened yet.  In which case, we need to make sure that
           when it is opened that it checks this entry and gets itself
           properly initialized if it is a separation device. */
        spot_names->color_map =
            (gs_devicen_color_map*) gs_alloc_bytes(mem,
                                                   sizeof(gs_devicen_color_map),
                                                   "gsicc_set_device_profile_colorants");
        if (spot_names->color_map == NULL)
            return gs_throw(gs_error_VMerror, "Insufficient memory for spot color map");
        spot_names->color_map->num_colorants = count;
        spot_names->color_map->num_components = count;

        name_entry = spot_names->head;
        for (k = 0; k < count; k++) {
            int colorant_number = (*dev_proc(dev, get_color_comp_index))
                    (dev, (const char *)name_entry->name, name_entry->length,
                     SEPARATION_NAME);
            name_entry = name_entry->next;
            spot_names->color_map->color_map[k] = colorant_number;
        }
        /* We need to set the equivalent CMYK color for this colorant.  This is
           done by faking out the update spot equivalent call with a special
           gs_gstate and color space that makes it seem like the
           spot color is a separation color space.  Unfortunately, we need the
           graphic state to do this so we save it for later when we try to do
           our first mapping.  We then use this flag to know if we did it yet */
        spot_names->equiv_cmyk_set = false;
        if (free_str)
            gs_free_object(dev->memory, name_str,
                           "gsicc_set_device_profile_colorants");
    }
    return code;
}

/* This sets the device profiles. If the device does not have a defined
   profile, then a default one is selected. */
int
gsicc_init_device_profile_struct(gx_device * dev,
                                 char *profile_name,
                                 gsicc_profile_types_t profile_type)
{
    int code;
    cmm_profile_t *curr_profile;
    cmm_dev_profile_t *profile_struct;

    /* See if the device has a profile structure.  If it does, then do a
       check to see if the profile that we are trying to set is already
       set and the same.  If it is not, then we need to free it and then
       reset. */
    profile_struct = dev->icc_struct;
    if (profile_struct != NULL) {
        /* Get the profile of interest */
        if (profile_type < gsPROOFPROFILE) {
            curr_profile = profile_struct->device_profile[profile_type];
        } else {
            /* The proof, link profile or post render */
            if (profile_type == gsPROOFPROFILE) {
                curr_profile = profile_struct->proof_profile;
            } else if (profile_type == gsLINKPROFILE) {
                curr_profile = profile_struct->link_profile;
            } else if (profile_type == gsPRPROFILE) {
                curr_profile = profile_struct->postren_profile;
            } else
                curr_profile = profile_struct->blend_profile;

        }
        /* See if we have the same profile in this location */
        if (curr_profile != NULL) {
            /* There is something there now.  See if what we have coming in
               is different and it is not the output intent.  In this  */
            if (profile_name != NULL && curr_profile->name != NULL) {
                if (strncmp(curr_profile->name, profile_name,
                            strlen(profile_name)) != 0 &&
                    strncmp(curr_profile->name, OI_PROFILE,
                            strlen(curr_profile->name)) != 0) {
                    /* A change in the profile.  rc decrement this one as it
                       will be replaced */
                    gsicc_adjust_profile_rc(curr_profile, -1, "gsicc_init_device_profile_struct");
                    /* Icky - if the creation of the new profile fails, we end up with a dangling
                       pointer, or a wrong reference count - so NULL the appropriate entry here
                     */
                    if (profile_type < gsPROOFPROFILE) {
                        profile_struct->device_profile[profile_type] = NULL;
                    } else {
                        /* The proof, link profile or post render */
                        if (profile_type == gsPROOFPROFILE) {
                            profile_struct->proof_profile = NULL;
                        } else if (profile_type == gsLINKPROFILE) {
                            profile_struct->link_profile = NULL;
                        } else if (profile_type == gsPRPROFILE) {
                            profile_struct->postren_profile = NULL;
                        } else
                            profile_struct->blend_profile = NULL;

                    }

                } else {
                    /* Nothing to change.  It was either the same or is the
                       output intent */
                    return 0;
                }
            }
        }
    } else {
        /* We have no profile structure at all. Allocate the structure in
           non-GC memory.  */
        dev->icc_struct = gsicc_new_device_profile_array(dev->memory);
        profile_struct = dev->icc_struct;
        if (profile_struct == NULL)
            return_error(gs_error_VMerror);
    }
    /* Either use the incoming or a default */
    if (profile_name == NULL) {
        profile_name =
            (char *) gs_alloc_bytes(dev->memory,
                                    MAX_DEFAULT_ICC_LENGTH,
                                    "gsicc_init_device_profile_struct");
        if (profile_name == NULL)
            return_error(gs_error_VMerror);
        switch(dev->color_info.num_components) {
            case 1:
                strncpy(profile_name, DEFAULT_GRAY_ICC, strlen(DEFAULT_GRAY_ICC));
                profile_name[strlen(DEFAULT_GRAY_ICC)] = 0;
                break;
            case 3:
                strncpy(profile_name, DEFAULT_RGB_ICC, strlen(DEFAULT_RGB_ICC));
                profile_name[strlen(DEFAULT_RGB_ICC)] = 0;
                break;
            case 4:
                strncpy(profile_name, DEFAULT_CMYK_ICC, strlen(DEFAULT_CMYK_ICC));
                profile_name[strlen(DEFAULT_CMYK_ICC)] = 0;
                break;
            default:
                strncpy(profile_name, DEFAULT_CMYK_ICC, strlen(DEFAULT_CMYK_ICC));
                profile_name[strlen(DEFAULT_CMYK_ICC)] = 0;
                break;
        }
        /* Go ahead and set the profile */
        code = gsicc_set_device_profile(dev, dev->memory, profile_name,
                                        profile_type);
        gs_free_object(dev->memory, profile_name,
                       "gsicc_init_device_profile_struct");
        return code;
    } else {
        /* Go ahead and set the profile */
        code = gsicc_set_device_profile(dev, dev->memory, profile_name,
                                        profile_type);
        return code;
    }
}

/* This is used in getting a list of colorant names for the intepreters
   device parameter list. */
char* gsicc_get_dev_icccolorants(cmm_dev_profile_t *dev_profile)
{
    if (dev_profile == NULL || dev_profile->spotnames == NULL ||
        dev_profile->spotnames->name_str == NULL)
        return 0;
    else
        return dev_profile->spotnames->name_str;
}

/* Check that the current state of the profiles is fine. Proof profile is of no
   concern since it is doing a CIELAB to CIELAB mapping in the mashed up
   transform. */
static int
gsicc_verify_device_profiles(gx_device * pdev)
{
    int k;
    cmm_dev_profile_t *dev_icc = pdev->icc_struct;
    bool check_components = true;
    bool can_postrender = false;
    bool objects = false;

    if (dev_proc(pdev, dev_spec_op) != NULL) {
        check_components = !(dev_proc(pdev, dev_spec_op)(pdev, gxdso_skip_icc_component_validation, NULL, 0));
        can_postrender = dev_proc(pdev, dev_spec_op)(pdev, gxdso_supports_iccpostrender, NULL, 0);
    }

    if (dev_icc->device_profile[GS_DEFAULT_DEVICE_PROFILE] == NULL)
        return 0;

    if (dev_icc->postren_profile != NULL && dev_icc->link_profile != NULL) {
        return gs_rethrow(-1, "Post render profile not allowed with device link profile");
    }

    if (dev_icc->blend_profile != NULL) {
        if (!(dev_icc->blend_profile->data_cs == gsGRAY ||
            dev_icc->blend_profile->data_cs == gsRGB ||
            dev_icc->blend_profile->data_cs == gsCMYK))
            return gs_rethrow(-1, "Blending color space must be Gray, RGB or CMYK");
    }

    if (dev_icc->postren_profile != NULL) {
        if (!can_postrender) {
            return gs_rethrow(-1, "Post render profile not supported by device");
        }
        if (check_components) {
            if (dev_icc->postren_profile->num_comps !=
                pdev->color_info.num_components) {
                return gs_rethrow(-1, "Post render profile does not match the device color model");
            }
            return 0;
        }
        return 0; /* An interesting case with sep device.  Need to do a bit of testing here */
    }

    for (k = 1; k < NUM_DEVICE_PROFILES; k++) {
        if (dev_icc->device_profile[k] != NULL) {
            objects = true;
            break;
        }
    }

    if (dev_icc->link_profile == NULL) {
        if (!objects) {
            if (check_components && dev_icc->device_profile[GS_DEFAULT_DEVICE_PROFILE]->num_comps !=
                pdev->color_info.num_components)
                return gs_rethrow(-1, "Mismatch of ICC profiles and device color model");
            else
                return 0;  /* Currently sep devices have some leeway here */
        } else {
            if (check_components) {
                for (k = 1; k < NUM_DEVICE_PROFILES; k++)
                    if (dev_icc->device_profile[k] != NULL) {
                        if (dev_icc->device_profile[k]->num_comps !=
                            pdev->color_info.num_components)
                            return gs_rethrow(-1, "Mismatch of object dependent ICC profiles and device color model");
                    }
            }
            return 0;
        }
    } else {
        /* The input of the device link must match the output of the device
           profile and the device link output must match the device color
           model */
        if (check_components && dev_icc->link_profile->num_comps_out !=
            pdev->color_info.num_components) {
            return gs_rethrow(-1, "Mismatch of device link profile and device color model");
        }
        if (check_components) {
            for (k = 0; k < NUM_DEVICE_PROFILES; k++) {
                if (dev_icc->device_profile[k] != NULL) {
                    if (dev_icc->device_profile[k]->num_comps !=
                        dev_icc->link_profile->num_comps) {
                        return gs_rethrow(-1, "Mismatch of device link profile and device ICC profile");
                    }
                }
            }
        }
        return 0;
    }
}

/*  This computes the hash code for the device profile and assigns the profile
    in the icc_struct member variable of the device.  This should
    really occur only one time, but may occur twice if a color model is
    specified or a nondefault profile is specified on the command line */
int
gsicc_set_device_profile(gx_device * pdev, gs_memory_t * mem,
                         char *file_name, gsicc_profile_types_t pro_enum)
{
    cmm_profile_t *icc_profile;
    stream *str;
    int code;

    /* This is slightly silly, we have a device method for 'get_profile' we really ought to
     * have one for 'set_profile' as well. In its absence, make sure we are setting the profile
     * of the bootm level device.
     */
    while(pdev->child)
        pdev = pdev->child;

    /* Check if device has a profile for this slot. Note that we already
       decremented for any profile that we might be replacing
       in gsicc_init_device_profile_struct */
    if (file_name != NULL) {
        /* Silent on failure if this is an output intent profile that
         * could not be found.  Bug 695042.  Multi-threaded rendering
         * set up will try to find the file for the profile during the set
         * up via put/get params. but one does not exist.  The OI profile
         * will be cloned after the put/get params */
        if (strncmp(file_name, OI_PROFILE, strlen(OI_PROFILE)) == 0)
            return -1;

        code = gsicc_open_search(file_name, strlen(file_name), mem,
                                 mem->gs_lib_ctx->profiledir,
                                 mem->gs_lib_ctx->profiledir_len, &str);
        if (code < 0)
            return code;
        if (str != NULL) {
            icc_profile =
                gsicc_profile_new(str, mem, file_name, strlen(file_name));
            code = sfclose(str);
            if (icc_profile == NULL)
                return gs_throw(gs_error_VMerror, "Creation of ICC profile failed");
            if (pro_enum < gsPROOFPROFILE) {
                if_debug1m(gs_debug_flag_icc, mem,
                           "[icc] Setting device profile %d\n", pro_enum);
                pdev->icc_struct->device_profile[pro_enum] = icc_profile;
            } else {
                /* The proof, link or post render profile. Output intent
                   profile is set in zicc.c */
                if (pro_enum == gsPROOFPROFILE) {
                    if_debug0m(gs_debug_flag_icc, mem, "[icc] Setting proof profile\n");
                    pdev->icc_struct->proof_profile = icc_profile;
                } else if (pro_enum ==  gsLINKPROFILE) {
                    if_debug0m(gs_debug_flag_icc, mem, "[icc] Setting link profile\n");
                    pdev->icc_struct->link_profile = icc_profile;
                } else if (pro_enum == gsPRPROFILE) {
                    if_debug0m(gs_debug_flag_icc, mem, "[icc] Setting postrender profile\n");
                    pdev->icc_struct->postren_profile = icc_profile;
                } else {
                    if_debug0m(gs_debug_flag_icc, mem, "[icc] Setting blend profile\n");
                    pdev->icc_struct->blend_profile = icc_profile;
                }
            }

            /* Get the profile handle */
            icc_profile->profile_handle =
                gsicc_get_profile_handle_buffer(icc_profile->buffer,
                                                icc_profile->buffer_size,
                                                mem);
            if (icc_profile->profile_handle == NULL)
                return_error(gs_error_unknownerror);

            /* Compute the hash code of the profile. Everything in the
               ICC manager will have it's hash code precomputed */
            gsicc_get_icc_buff_hash(icc_profile->buffer,
                                    &(icc_profile->hashcode),
                                    icc_profile->buffer_size);
            icc_profile->hash_is_valid = true;

            /* Get the number of channels in the output profile */
            icc_profile->num_comps =
                gscms_get_input_channel_count(icc_profile->profile_handle,
                    icc_profile->memory);
            if_debug1m(gs_debug_flag_icc, mem, "[icc] Profile has %d components\n",
                       icc_profile->num_comps);
            icc_profile->num_comps_out =
                gscms_get_output_channel_count(icc_profile->profile_handle,
                    icc_profile->memory);
            icc_profile->data_cs =
                gscms_get_profile_data_space(icc_profile->profile_handle,
                    icc_profile->memory);

            /* Check that everything is OK with regard to the number of
               components. */
            if (gsicc_verify_device_profiles(pdev) < 0)
                return gs_rethrow(-1, "Error in device profiles");

            /* We need to know if this is one of the "default" profiles or
               if someone has externally set it.  The reason is that if there
               is an output intent in the file, and someone wants to use the
               output intent our handling of the output intent profile is
               different depending upon if someone specified a particular
               output profile */
            switch (icc_profile->num_comps) {
                case 1:
                    if (strncmp(icc_profile->name, DEFAULT_GRAY_ICC,
                    strlen(icc_profile->name)) == 0) {
                        icc_profile->default_match = DEFAULT_GRAY;
                    }
                    break;
                case 3:
                    if (strncmp(icc_profile->name, DEFAULT_RGB_ICC,
                    strlen(icc_profile->name)) == 0) {
                        icc_profile->default_match = DEFAULT_RGB;
                    }
                    break;
                case 4:
                    if (strncmp(icc_profile->name, DEFAULT_CMYK_ICC,
                    strlen(icc_profile->name)) == 0) {
                        icc_profile->default_match = DEFAULT_CMYK;
                    }
                    break;
                default:
                    /* NCLR Profile.  Set up default colorant names */
                    gsicc_set_device_profile_colorants(pdev, NULL);
                    break;
            }
            if_debug1m(gs_debug_flag_icc, mem, "[icc] Profile data CS is %d\n",
                       icc_profile->data_cs);
        } else
            return gs_rethrow(-1, "cannot find device profile");
    }
    return 0;
}

/* Set the icc profile in the gs_color_space object */
int
gsicc_set_gscs_profile(gs_color_space *pcs, cmm_profile_t *icc_profile,
                       gs_memory_t * mem)
{
    if (pcs == NULL)
        return -1;
#if ICC_DUMP
    if (icc_profile->buffer) {
        dump_icc_buffer(mem,
                        icc_profile->buffer_size, "set_gscs",
                        icc_profile->buffer);
        global_icc_index++;
    }
#endif

    gsicc_adjust_profile_rc(icc_profile, 1, "gsicc_set_gscs_profile");
    if (pcs->cmm_icc_profile_data != NULL) {
        /* There is already a profile set there */
        /* free it and then set to the new one.  */
        /* should we check the hash code and retain if the same
           or place this job on the caller?  */
        gsicc_adjust_profile_rc(pcs->cmm_icc_profile_data, -1, "gsicc_set_gscs_profile");
    }
    pcs->cmm_icc_profile_data = icc_profile;
    return 0;
}

int
gsicc_clone_profile(cmm_profile_t *source, cmm_profile_t **destination,
                    gs_memory_t *memory)
{
    cmm_profile_t *des = gsicc_profile_new(NULL, memory, source->name,
        source->name_length);

    if (des == NULL)
        return gs_throw(gs_error_VMerror, "Profile clone failed");
    des->buffer = gs_alloc_bytes(memory, source->buffer_size, "gsicc_clone_profile");
    if (des->buffer == NULL) {
        gsicc_adjust_profile_rc(des, -1, "gsicc_clone_profile");
        return gs_throw(gs_error_VMerror, "Profile clone failed");
    }
    memcpy(des->buffer, source->buffer, source->buffer_size);
    des->buffer_size = source->buffer_size;
    gsicc_init_profile_info(des);
    *destination = des;
    return 0;
}

cmm_profile_t *
gsicc_profile_new(stream *s, gs_memory_t *memory, const char* pname,
                  int namelen)
{
    cmm_profile_t *result;
    int code;
    char *nameptr = NULL;
    gs_memory_t *mem_nongc = memory->non_gc_memory;

    result = (cmm_profile_t*) gs_alloc_bytes(mem_nongc, sizeof(cmm_profile_t),
                                    "gsicc_profile_new");
    if (result == NULL)
        return result;
    memset(result, 0, GSICC_SERIALIZED_SIZE);
    if (namelen > 0) {
        nameptr = (char*) gs_alloc_bytes(mem_nongc, namelen+1,
                             "gsicc_profile_new");
        if (nameptr == NULL) {
            gs_free_object(mem_nongc, result, "gsicc_profile_new");
            return NULL;
        }
        memcpy(nameptr, pname, namelen);
        nameptr[namelen] = '\0';
        result->name = nameptr;
    } else {
        result->name = NULL;
    }
    result->name_length = namelen;

    /* We may not have a stream if we are creating this
       object from our own constructed buffer.  For
       example if we are converting CalRGB to an ICC type */
    if ( s != NULL) {
        code = gsicc_load_profile_buffer(result, s, mem_nongc);
        if (code < 0) {
            gs_free_object(mem_nongc, result, "gsicc_profile_new");
            gs_free_object(mem_nongc, nameptr, "gsicc_profile_new");
            return NULL;
        }
    } else {
        result->buffer = NULL;
        result->buffer_size = 0;
    }
    rc_init_free(result, mem_nongc, 1, rc_free_icc_profile);
    result->profile_handle = NULL;
    result->spotnames = NULL;
    result->rend_is_valid = false;
    result->isdevlink = false;  /* only used for srcgtag profiles */
    result->dev = NULL;
    result->memory = mem_nongc;
    result->vers = ICCVERS_UNKNOWN;
    result->v2_data = NULL;
    result->v2_size = 0;
    result->release = gscms_release_profile; /* Default case */

    result->lock = gx_monitor_label(gx_monitor_alloc(mem_nongc),
                                    "gsicc_manage");
    if (result->lock == NULL) {
        gs_free_object(mem_nongc, result->buffer, "gsicc_load_profile");
        gs_free_object(mem_nongc, result, "gsicc_profile_new");
        gs_free_object(mem_nongc, nameptr, "gsicc_profile_new");
        return NULL;
    }
    if_debug1m(gs_debug_flag_icc, mem_nongc,
               "[icc] allocating ICC profile = "PRI_INTPTR"\n", (intptr_t)result);
    return result;
}

static void
rc_free_icc_profile(gs_memory_t * mem, void *ptr_in, client_name_t cname)
{
    cmm_profile_t *profile = (cmm_profile_t *)ptr_in;
    gs_memory_t *mem_nongc =  profile->memory;

    if_debug2m(gs_debug_flag_icc, mem,
               "[icc] rc decrement profile = "PRI_INTPTR" rc = %ld\n",
               (intptr_t)ptr_in, profile->rc.ref_count);
    if (profile->rc.ref_count <= 1 ) {
        /* Clear out the buffer if it is full */
        if (profile->buffer != NULL) {
            gs_free_object(mem_nongc, profile->buffer, "rc_free_icc_profile(buffer)");
            profile->buffer = NULL;
        }
        if_debug0m(gs_debug_flag_icc, mem, "[icc] profile freed\n");
        /* Release this handle if it has been set */
        if (profile->profile_handle != NULL) {
            profile->release(profile->profile_handle, profile->memory);
            profile->profile_handle = NULL;
        }
        /* Release the name if it has been set */
        if (profile->name != NULL) {
            gs_free_object(mem_nongc, profile->name,"rc_free_icc_profile(name)");
            profile->name = NULL;
            profile->name_length = 0;
        }
        profile->hash_is_valid = 0;
        if (profile->lock != NULL) {
            gx_monitor_free(profile->lock);
            profile->lock = NULL;
        }
        /* If we had a DeviceN profile with names deallocate that now */
        if (profile->spotnames != NULL) {
            /* Free the linked list in this object */
            gsicc_free_spotnames(profile->spotnames, mem_nongc);
            /* Free the main object */
            gs_free_object(mem_nongc, profile->spotnames, "rc_free_icc_profile(spotnames)");
        }
        /* If we allocated a buffer to hold the v2 profile then free that */
        if (profile->v2_data != NULL) {
            gs_free_object(mem_nongc, profile->v2_data, "rc_free_icc_profile(v2_data)");
        }
        gs_free_object(mem_nongc, profile, "rc_free_icc_profile");
    }
}

/* We are just starting up.  We need to set the initial color space in the
   graphic state at this time */
int
gsicc_init_gs_colors(gs_gstate *pgs)
{
    int             code = 0;
    gs_color_space  *cs_old;
    gs_color_space  *cs_new;
    int k;

    if (pgs->in_cachedevice)
        return_error(gs_error_undefined);

    for (k = 0; k < 2; k++) {
        /* First do color space 0 */
        cs_old = pgs->color[k].color_space;
        cs_new = gs_cspace_new_DeviceGray(pgs->memory);
        if (cs_new == NULL)
            return_error(gs_error_VMerror);
        rc_increment_cs(cs_new);
        pgs->color[k].color_space = cs_new;
        if ( (code = cs_new->type->install_cspace(cs_new, pgs)) < 0 ) {
            pgs->color[k].color_space = cs_old;
            rc_decrement_only_cs(cs_new, "gsicc_init_gs_colors");
            return code;
        } else {
            rc_decrement_only_cs(cs_old, "gsicc_init_gs_colors");
        }
    }
    return code;
}

/* Only set those that have not already been set. */
int
gsicc_init_iccmanager(gs_gstate * pgs)
{
    int code = 0, k;
    const char *pname;
    int namelen;
    gsicc_manager_t *iccmanager = pgs->icc_manager;
    cmm_profile_t *profile;

    for (k = 0; k < 4; k++) {
        pname = default_profile_params[k].path;
        namelen = strlen(pname);

        switch(default_profile_params[k].default_type) {
            case DEFAULT_GRAY:
                profile = iccmanager->default_gray;
                break;
            case DEFAULT_RGB:
                profile = iccmanager->default_rgb;
                break;
            case DEFAULT_CMYK:
                 profile = iccmanager->default_cmyk;
                 break;
            default:
                profile = NULL;
        }
        if (profile == NULL)
            code = gsicc_set_profile(iccmanager, pname, namelen+1,
                                     default_profile_params[k].default_type);
        if (code < 0)
            return gs_rethrow(code, "cannot find default icc profile");
    }
#if CREATE_V2_DATA
    /* Test bed for V2 creation from V4 */
    for (int j = 2; j < 3; j++)
    {
        byte *data;
        int size;

        switch (default_profile_params[j].default_type) {
        case DEFAULT_GRAY:
            profile = iccmanager->default_gray;
            break;
        case DEFAULT_RGB:
            profile = iccmanager->default_rgb;
            break;
        case DEFAULT_CMYK:
            profile = iccmanager->default_cmyk;
            break;
        default:
            profile = NULL;
        }
        gsicc_initialize_default_profile(profile);
        data = gsicc_create_getv2buffer(pgs, profile, &size);
    }
#endif
    return 0;
}

static void
gsicc_manager_finalize(const gs_memory_t *memory, void * vptr)
{
    gsicc_manager_t *icc_man = (gsicc_manager_t *)vptr;

    gsicc_manager_free_contents(icc_man, "gsicc_manager_finalize");
}

gsicc_manager_t *
gsicc_manager_new(gs_memory_t *memory)
{
    gsicc_manager_t *result;

    /* Allocated in stable gc memory.  This done since the profiles
       may be introduced late in the process. */
    result = gs_alloc_struct(memory->stable_memory, gsicc_manager_t, &st_gsicc_manager,
                             "gsicc_manager_new");
    if ( result == NULL )
        return NULL;
    rc_init_free(result, memory->stable_memory, 1, rc_gsicc_manager_free);
    result->default_gray = NULL;
    result->default_rgb = NULL;
    result->default_cmyk = NULL;
    result->lab_profile = NULL;
    result->xyz_profile = NULL;
    result->graytok_profile = NULL;
    result->device_named = NULL;
    result->device_n = NULL;
    result->smask_profiles = NULL;
    result->memory = memory->stable_memory;
    result->srcgtag_profile = NULL;
    result->override_internal = false;
    return result;
}

static void gsicc_manager_free_contents(gsicc_manager_t *icc_manager,
                                  client_name_t cname)
{
    int k;
    gsicc_devicen_entry_t *device_n, *device_n_next;

    gsicc_adjust_profile_rc(icc_manager->default_cmyk, -1, "gsicc_manager_free_contents");
    gsicc_adjust_profile_rc(icc_manager->default_gray, -1, "gsicc_manager_free_contents");
    gsicc_adjust_profile_rc(icc_manager->default_rgb, -1, "gsicc_manager_free_contents");
    gsicc_adjust_profile_rc(icc_manager->device_named, -1, "gsicc_manager_free_contents");
    gsicc_adjust_profile_rc(icc_manager->lab_profile, -1, "gsicc_manager_free_contents");
    gsicc_adjust_profile_rc(icc_manager->graytok_profile, -1, "gsicc_manager_free_contents");
    rc_decrement(icc_manager->srcgtag_profile, "gsicc_manager_free_contents");

    /* Loop through the DeviceN profiles */
    if ( icc_manager->device_n != NULL) {
        device_n = icc_manager->device_n->head;
        for ( k = 0; k < icc_manager->device_n->count; k++) {
            gsicc_adjust_profile_rc(device_n->iccprofile, -1, "gsicc_manager_free_contents");
            device_n_next = device_n->next;
            gs_free_object(icc_manager->memory, device_n, "gsicc_manager_free_contents");
            device_n = device_n_next;
        }
        gs_free_object(icc_manager->memory, icc_manager->device_n,
                       "gsicc_manager_free_contents");
    }

    /* The soft mask profiles */
    if (icc_manager->smask_profiles != NULL) {
        gs_free_object(icc_manager->smask_profiles->memory, icc_manager->smask_profiles, "gsicc_manager_free_contents");
        icc_manager->smask_profiles = NULL;
    }
}

static void
rc_gsicc_manager_free(gs_memory_t * mem, void *ptr_in, client_name_t cname)
{
    /* Ending the manager.  Decrement the ref counts of the profiles
       and then free the structure */
    gsicc_manager_t *icc_manager = (gsicc_manager_t * ) ptr_in;

    gs_free_object(icc_manager->memory, icc_manager, "rc_gsicc_manager_free");
}

/* Allocates and loads the icc buffer from the stream. */
static int
gsicc_load_profile_buffer(cmm_profile_t *profile, stream *s,
                          gs_memory_t *memory)
{
    int                     num_bytes,profile_size;
    unsigned char           *buffer_ptr;
    int                     code;

    code = srewind(s);  /* Work around for issue with sfread return 0 bytes
                        and not doing a retry if there is an issue.  This
                        is a bug in the stream logic or strmio layer.  Occurs
                        with smask_withicc.pdf on linux 64 bit system */
    if (code < 0)
        return code;
    /* Get the size from doing a seek to the end and then a rewind instead
       of relying upon the profile size indicated in the header */
    code = sfseek(s,0,SEEK_END);
    if (code < 0)
        return code;
    profile_size = sftell(s);
    code = srewind(s);
    if (code < 0)
        return code;
    if (profile_size < ICC_HEADER_SIZE)
        return_error(gs_error_VMerror);
    /* Allocate the buffer, stuff with the profile */
   buffer_ptr = gs_alloc_bytes(memory, profile_size,
                                        "gsicc_load_profile");
   if (buffer_ptr == NULL)
        return gs_throw(gs_error_VMerror, "Insufficient memory for profile buffer");
   num_bytes = sfread(buffer_ptr,sizeof(unsigned char),profile_size,s);
   if( num_bytes != profile_size) {
       gs_free_object(memory, buffer_ptr, "gsicc_load_profile");
       return -1;
   }
   profile->buffer = buffer_ptr;
   profile->buffer_size = num_bytes;
   return 0;
}

/* Allocates and loads the named color structure from the stream. */
static int
gsicc_load_namedcolor_buffer(cmm_profile_t *profile, stream *s,
                          gs_memory_t *memory)
{
    int                     num_bytes,profile_size;
    unsigned char           *buffer_ptr;
    int                     code;

    code = srewind(s);
    if (code < 0)
        return code;
    code = sfseek(s,0,SEEK_END);
    if (code < 0)
        return code;
    profile_size = sftell(s);
    code = srewind(s);
    if (code < 0)
        return code;
    /* Allocate the buffer, stuff with the profile */
    buffer_ptr = gs_alloc_bytes(memory->non_gc_memory, profile_size,
                                        "gsicc_load_profile");
    if (buffer_ptr == NULL)
        return gs_throw(gs_error_VMerror, "Insufficient memory for profile buffer");
    num_bytes = sfread(buffer_ptr,sizeof(unsigned char),profile_size,s);
    if( num_bytes != profile_size) {
        gs_free_object(memory->non_gc_memory, buffer_ptr, "gsicc_load_profile");
        return -1;
    }
    profile->buffer = buffer_ptr;
    profile->buffer_size = num_bytes;
    return 0;
}

/* Check if the embedded profile is the same as any of the default profiles */
static void
gsicc_set_default_cs_value(cmm_profile_t *picc_profile, gs_gstate *pgs)
{
    gsicc_manager_t *icc_manager = pgs->icc_manager;
    int64_t hashcode = picc_profile->hashcode;

    if ( picc_profile->default_match == DEFAULT_NONE ) {
        switch ( picc_profile->data_cs ) {
            case gsGRAY:
                if ( hashcode == icc_manager->default_gray->hashcode )
                    picc_profile->default_match = DEFAULT_GRAY_s;
                break;
            case gsRGB:
                if ( hashcode == icc_manager->default_rgb->hashcode )
                    picc_profile->default_match = DEFAULT_RGB_s;
                break;
            case gsCMYK:
                if ( hashcode == icc_manager->default_cmyk->hashcode )
                    picc_profile->default_match = DEFAULT_CMYK_s;
                break;
            case gsCIELAB:
                if ( hashcode == icc_manager->lab_profile->hashcode )
                    picc_profile->default_match = LAB_TYPE_s;
                break;
            case gsCIEXYZ:
                return;
                break;
            case gsNCHANNEL:
                return;
                break;
            default:
                return;
        }
    }
}

/* Initialize the hash code value */
void
gsicc_init_hash_cs(cmm_profile_t *picc_profile, gs_gstate *pgs)
{
    if ( !(picc_profile->hash_is_valid) ) {
        gsicc_get_icc_buff_hash(picc_profile->buffer, &(picc_profile->hashcode),
                                picc_profile->buffer_size);
        picc_profile->hash_is_valid = true;
    }
    gsicc_set_default_cs_value(picc_profile, pgs);
}

/* Interface code to get the profile handle for data
   stored in the clist device */
gcmmhprofile_t
gsicc_get_profile_handle_clist(cmm_profile_t *picc_profile, gs_memory_t *memory)
{
    gcmmhprofile_t profile_handle = NULL;
    unsigned int profile_size;
    int size;
    gx_device_clist_reader *pcrdev = (gx_device_clist_reader*) picc_profile->dev;
    unsigned char *buffer_ptr;
    int64_t position;
    gsicc_serialized_profile_t profile_header;
    int k;

    if( pcrdev != NULL) {

        /* Check ICC table for hash code and get the whole size icc raw buffer
           plus serialized header information */
        position = gsicc_search_icc_table(pcrdev->icc_table,
                                          picc_profile->hashcode, &size);
        if ( position < 0 )
            return 0;  /* Not found. */

        /* Get the ICC buffer.  We really want to avoid this transfer.
           I need to write  an interface to the CMM to do this through
           the clist ioprocs */
        /* Allocate the buffer */
        profile_size = size - GSICC_SERIALIZED_SIZE;
        /* Profile and its members are ALL in non-gc memory */
        buffer_ptr = gs_alloc_bytes(memory->non_gc_memory, profile_size,
                                            "gsicc_get_profile_handle_clist");
        if (buffer_ptr == NULL)
            return 0;
        clist_read_chunk(pcrdev, position + GSICC_SERIALIZED_SIZE,
            profile_size, (unsigned char *) buffer_ptr);
        profile_handle = gscms_get_profile_handle_mem(buffer_ptr, profile_size, memory->non_gc_memory);
        /* We also need to get some of the serialized information */
        clist_read_chunk(pcrdev, position, GSICC_SERIALIZED_SIZE,
                        (unsigned char *) (&profile_header));
        picc_profile->buffer = NULL;
        picc_profile->buffer_size = 0;
        picc_profile->data_cs = profile_header.data_cs;
        picc_profile->default_match = profile_header.default_match;
        picc_profile->hash_is_valid = profile_header.hash_is_valid;
        picc_profile->hashcode = profile_header.hashcode;
        picc_profile->islab = profile_header.islab;
        picc_profile->num_comps = profile_header.num_comps;
        picc_profile->rend_is_valid = profile_header.rend_is_valid;
        picc_profile->rend_cond = profile_header.rend_cond;
        picc_profile->isdevlink = profile_header.isdevlink;
        for ( k = 0; k < profile_header.num_comps; k++ ) {
            picc_profile->Range.ranges[k].rmax =
                profile_header.Range.ranges[k].rmax;
            picc_profile->Range.ranges[k].rmin =
                profile_header.Range.ranges[k].rmin;
        }
        gs_free_object(memory->non_gc_memory, buffer_ptr, "gsicc_get_profile_handle_clist");
        return profile_handle;
     }
     return 0;
}

gcmmhprofile_t
gsicc_get_profile_handle_buffer(unsigned char *buffer, int profile_size, gs_memory_t *memory)
{

    gcmmhprofile_t profile_handle = NULL;

     if( buffer != NULL) {
         if (profile_size < ICC_HEADER_SIZE) {
             return 0;
         }
         profile_handle = gscms_get_profile_handle_mem(buffer, profile_size, memory->non_gc_memory);
         return profile_handle;
     }
     return 0;
}

 /*  If we have a profile for the color space already, then we use that.
     If we do not have one then we will use data from
     the ICC manager that is based upon the current color space. */
 cmm_profile_t*
 gsicc_get_gscs_profile(gs_color_space *gs_colorspace,
                        gsicc_manager_t *icc_manager)
 {
     cmm_profile_t *profile = gs_colorspace->cmm_icc_profile_data;
     gs_color_space_index color_space_index =
            gs_color_space_get_index(gs_colorspace);
     int code = 0;
     bool islab;

     if (profile != NULL )
        return profile;
     /* else, return the default types */
     switch( color_space_index ) {
        case gs_color_space_index_DeviceGray:
            return icc_manager->default_gray;
            break;
        case gs_color_space_index_DeviceRGB:
            return icc_manager->default_rgb;
            break;
        case gs_color_space_index_DeviceCMYK:
            return icc_manager->default_cmyk;
            break;
            /* Only used in 3x types */
        case gs_color_space_index_DevicePixel:
            return 0;
            break;
       case gs_color_space_index_DeviceN:
            /* If we made it to here, then we will need to use the
               alternate colorspace */
            return 0;
            break;
       case gs_color_space_index_CIEDEFG:
           /* For now just use default CMYK to avoid segfault.  MJV to fix */
           gs_colorspace->cmm_icc_profile_data = icc_manager->default_cmyk;
           gsicc_adjust_profile_rc(icc_manager->default_cmyk, 1, "gsicc_get_gscs_profile");
           return gs_colorspace->cmm_icc_profile_data;
           /* Need to convert to an ICC form */
           break;
        case gs_color_space_index_CIEDEF:
           /* For now just use default RGB to avoid segfault.  MJV to fix */
           gs_colorspace->cmm_icc_profile_data = icc_manager->default_rgb;
           gsicc_adjust_profile_rc(icc_manager->default_rgb, 1, "gsicc_get_gscs_profile");
           return gs_colorspace->cmm_icc_profile_data;
           /* Need to convert to an ICC form */
           break;
        case gs_color_space_index_CIEABC:
            gs_colorspace->cmm_icc_profile_data =
                gsicc_profile_new(NULL, icc_manager->memory, NULL, 0);
            if (gs_colorspace->cmm_icc_profile_data == NULL) {
                gs_throw(gs_error_VMerror, "Creation of ICC profile for CIEABC failed");
                return NULL;
            }
            code =
                gsicc_create_fromabc(gs_colorspace,
                        &(gs_colorspace->cmm_icc_profile_data->buffer),
                        &(gs_colorspace->cmm_icc_profile_data->buffer_size),
                        icc_manager->memory,
                        &(gs_colorspace->params.abc->caches.DecodeABC.caches[0]),
                        &(gs_colorspace->params.abc->common.caches.DecodeLMN[0]),
                        &islab);
            if (code < 0) {
                gs_warn("Failed to create ICC profile from CIEABC");
                gsicc_adjust_profile_rc(gs_colorspace->cmm_icc_profile_data, -1,
                             "gsicc_get_gscs_profile");
                return NULL;
            }

            if (islab) {
                /* Destroy the profile */
                gsicc_adjust_profile_rc(gs_colorspace->cmm_icc_profile_data, -1,
                             "gsicc_get_gscs_profile");
                /* This may be an issue for pdfwrite */
                return icc_manager->lab_profile;
            }
            gs_colorspace->cmm_icc_profile_data->default_match = CIE_ABC;
            return gs_colorspace->cmm_icc_profile_data;
            break;
        case gs_color_space_index_CIEA:
            gs_colorspace->cmm_icc_profile_data =
                gsicc_profile_new(NULL, icc_manager->memory, NULL, 0);
            if (gs_colorspace->cmm_icc_profile_data == NULL) {
                gs_throw(gs_error_VMerror, "Creation of ICC profile for CIEA failed");
                return NULL;
            }
            code =
                gsicc_create_froma(gs_colorspace,
                            &(gs_colorspace->cmm_icc_profile_data->buffer),
                            &(gs_colorspace->cmm_icc_profile_data->buffer_size),
                            icc_manager->memory,
                            &(gs_colorspace->params.a->caches.DecodeA),
                            &(gs_colorspace->params.a->common.caches.DecodeLMN[0]));
            gs_colorspace->cmm_icc_profile_data->default_match = CIE_A;
            return gs_colorspace->cmm_icc_profile_data;
            break;
        case gs_color_space_index_Separation:
            /* Caller should use named color path */
            return 0;
            break;
        case gs_color_space_index_Pattern:
        case gs_color_space_index_Indexed:
            /* Caller should use the base space for these */
            return 0;
            break;
        case gs_color_space_index_ICC:
            /* This should not occur, as the space
               should have had a populated profile handle */
            return 0;
            break;
     }
    return 0;
 }

static int64_t
gsicc_search_icc_table(clist_icctable_t *icc_table, int64_t icc_hashcode, int *size)
{
    int tablesize = icc_table->tablesize, k;
    clist_icctable_entry_t *curr_entry;

    curr_entry = icc_table->head;
    for (k = 0; k < tablesize; k++ ) {
        if ( curr_entry->serial_data.hashcode == icc_hashcode ) {
            *size = curr_entry->serial_data.size;
            return curr_entry->serial_data.file_position;
        }
        curr_entry = curr_entry->next;
    }

    /* Did not find it! */
    *size = 0;
    return -1;
}

/* This is used to get only the serial data from the clist.  We don't bother
   with the whole profile until we actually need it.  It may be that the link
   that we need is already in the link cache */
cmm_profile_t*
gsicc_read_serial_icc(gx_device *dev, int64_t icc_hashcode)
{
    cmm_profile_t *profile;
    int64_t position;
    int size;
    int code;
    gx_device_clist_reader *pcrdev = (gx_device_clist_reader*) dev;

    /* Create a new ICC profile structure */
    profile = gsicc_profile_new(NULL, pcrdev->memory, NULL, 0);
    if (profile == NULL)
        return NULL;

    /* Check ICC table for hash code and get the whole size icc raw buffer
       plus serialized header information. Make sure the icc_table has
       been intialized */
    if (pcrdev->icc_table == NULL) {
        code = clist_read_icctable(pcrdev);
        if (code<0)
            return NULL;
    }
    position = gsicc_search_icc_table(pcrdev->icc_table, icc_hashcode, &size);
    if ( position < 0 )
        return NULL;

    /* Get the serialized portion of the ICC profile information */
    clist_read_chunk(pcrdev, position, GSICC_SERIALIZED_SIZE,
                    (unsigned char *) profile);
    return profile;
}

void
gsicc_profile_serialize(gsicc_serialized_profile_t *profile_data,
                        cmm_profile_t *icc_profile)
{
    if (icc_profile == NULL)
        return;
    memcpy(profile_data, icc_profile, GSICC_SERIALIZED_SIZE);
}

/* Utility functions */

int
gsicc_getsrc_channel_count(cmm_profile_t *icc_profile)
{
    return gscms_get_input_channel_count(icc_profile->profile_handle,
        icc_profile->memory);
}

void
gsicc_extract_profile(gs_graphics_type_tag_t graphics_type_tag,
                       cmm_dev_profile_t *profile_struct,
                       cmm_profile_t **profile, gsicc_rendering_param_t *render_cond)
{
    switch (graphics_type_tag & ~GS_DEVICE_ENCODES_TAGS) {
        case GS_UNKNOWN_TAG:
        case GS_UNTOUCHED_TAG:
        default:
            (*profile) = profile_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE];
            *render_cond = profile_struct->rendercond[GS_DEFAULT_DEVICE_PROFILE];
            break;
        case GS_PATH_TAG:
            *render_cond = profile_struct->rendercond[GS_GRAPHIC_DEVICE_PROFILE];
            if (profile_struct->device_profile[GS_GRAPHIC_DEVICE_PROFILE] != NULL) {
                (*profile) = profile_struct->device_profile[GS_GRAPHIC_DEVICE_PROFILE];
            } else {
                (*profile) = profile_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE];
            }
            break;
        case GS_IMAGE_TAG:
            *render_cond = profile_struct->rendercond[GS_IMAGE_DEVICE_PROFILE];
            if (profile_struct->device_profile[GS_IMAGE_DEVICE_PROFILE] != NULL) {
                (*profile) = profile_struct->device_profile[GS_IMAGE_DEVICE_PROFILE];
            } else {
                (*profile) = profile_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE];
            }
            break;
        case GS_TEXT_TAG:
            *render_cond = profile_struct->rendercond[GS_TEXT_DEVICE_PROFILE];
            if (profile_struct->device_profile[GS_TEXT_DEVICE_PROFILE] != NULL) {
                (*profile) = profile_struct->device_profile[GS_TEXT_DEVICE_PROFILE];
            } else {
                (*profile) = profile_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE];
            }
            break;
        }
}

/* internal ICC and rendering intent override control */
void
gs_setoverrideicc(gs_gstate *pgs, bool value)
{
    if (pgs->icc_manager != NULL) {
        pgs->icc_manager->override_internal = value;
    }
}
bool
gs_currentoverrideicc(const gs_gstate *pgs)
{
    if (pgs->icc_manager != NULL) {
        return pgs->icc_manager->override_internal;
    } else {
        return false;
    }
}

void
gsicc_setrange_lab(cmm_profile_t *profile)
{
    profile->Range.ranges[0].rmin = 0.0;
    profile->Range.ranges[0].rmax = 100.0;
    profile->Range.ranges[1].rmin = -128.0;
    profile->Range.ranges[1].rmax = 127.0;
    profile->Range.ranges[2].rmin = -128.0;
    profile->Range.ranges[2].rmax = 127.0;
}

#if ICC_DUMP
/* Debug dump of ICC buffer data */
static void
dump_icc_buffer(const gs_memory_t *mem, int buffersize, char filename[],byte *Buffer)
{
    char full_file_name[50];
    gp_file *fid;

    gs_sprintf(full_file_name,"%d)%s_debug.icc",global_icc_index,filename);
    fid = gp_fopen(mem, full_file_name,"wb");
    gp_fwrite(Buffer,sizeof(unsigned char),buffersize,fid);
    fclose(fid);
}
#endif

/* The following are for setting the system/user params */
/* No default for the deviceN profile. */
void
gs_currentdevicenicc(const gs_gstate * pgs, gs_param_string * pval)
{
    static const char *const rfs = "";

    /*FIXME: This should return the entire list !!! */
    /*       Just return the first one for now      */
    if (pgs->icc_manager->device_n == NULL) {
        pval->data = (const byte *) rfs;
        pval->persistent = true;
    } else {
        pval->data =
            (const byte *) (pgs->icc_manager->device_n->head->iccprofile->name);
        pval->persistent = false;
    }
    pval->size = strlen((const char *)pval->data);
}

int
gs_setdevicenprofileicc(const gs_gstate * pgs, gs_param_string * pval)
{
    int code = 0;
    char *pname, *pstr, *pstrend, *last = NULL;
    int namelen = (pval->size)+1;
    gs_memory_t *mem = pgs->memory;

    /* Check if it was "NULL" */
    if (pval->size != 0) {
        /* The DeviceN name can have multiple files
           in it.  This way we can define all the
           DeviceN color spaces with ICC profiles.
           divide using , and ; delimeters as well as
           remove leading and ending spaces (file names
           can have internal spaces). */
        pname = (char *)gs_alloc_bytes(mem, namelen,
                                     "set_devicen_profile_icc");
        if (pname == NULL)
            return_error(gs_error_VMerror);
        memcpy(pname,pval->data,namelen-1);
        pname[namelen-1] = 0;
        pstr = gs_strtok(pname, ",;", &last);
        while (pstr != NULL) {
            namelen = strlen(pstr);
            /* Remove leading and trailing spaces from the name */
            while ( namelen > 0 && pstr[0] == 0x20) {
                pstr++;
                namelen--;
            }
            namelen = strlen(pstr);
            pstrend = &(pstr[namelen-1]);
            while ( namelen > 0 && pstrend[0] == 0x20) {
                pstrend--;
                namelen--;
            }
            code = gsicc_set_profile(pgs->icc_manager, (const char*) pstr, namelen, DEVICEN_TYPE);
            if (code < 0)
                return gs_throw(code, "cannot find devicen icc profile");
            pstr = gs_strtok(NULL, ",;", &last);
        }
        gs_free_object(mem, pname,
        "set_devicen_profile_icc");
        return code;
    }
    return 0;
}

void
gs_currentdefaultgrayicc(const gs_gstate * pgs, gs_param_string * pval)
{
    static const char *const rfs = DEFAULT_GRAY_ICC;

    if (pgs->icc_manager->default_gray == NULL) {
        pval->data = (const byte *) rfs;
        pval->persistent = true;
    } else {
        pval->data = (const byte *) (pgs->icc_manager->default_gray->name);
        pval->persistent = false;
    }
    pval->size = strlen((const char *)pval->data);
}

int
gs_setdefaultgrayicc(const gs_gstate * pgs, gs_param_string * pval)
{
    int code;
    char *pname;
    int namelen = (pval->size)+1;
    gs_memory_t *mem = pgs->memory;
    bool not_initialized;

    /* Detect if this is our first time in here.  If so, then we need to
       reset up the default gray color spaces that are in the graphic state
       to be ICC based.  It was not possible to do it until after we get
       the profile */
    not_initialized = (pgs->icc_manager->default_gray == NULL);

    pname = (char *)gs_alloc_bytes(mem, namelen,
                             "set_default_gray_icc");
    if (pname == NULL)
        return_error(gs_error_VMerror);
    memcpy(pname,pval->data,namelen-1);
    pname[namelen-1] = 0;
    code = gsicc_set_profile(pgs->icc_manager,
        (const char*) pname, namelen, DEFAULT_GRAY);
    gs_free_object(mem, pname,
        "set_default_gray_icc");
    if (code < 0)
        return gs_throw(code, "cannot find default gray icc profile");
    /* if this is our first time in here then we need to properly install the
       color spaces that were initialized in the graphic state at this time */
    if (not_initialized) {
        code = gsicc_init_gs_colors((gs_gstate*) pgs);
    }
    if (code < 0)
        return gs_throw(code, "error initializing gstate color spaces to icc");
    return code;
}

void
gs_currenticcdirectory(const gs_gstate * pgs, gs_param_string * pval)
{
    static const char *const rfs = DEFAULT_DIR_ICC;   /* as good as any other */
    const gs_lib_ctx_t *lib_ctx = pgs->memory->gs_lib_ctx;

    if (lib_ctx->profiledir == NULL) {
        pval->data = (const byte *)rfs;
        pval->size = strlen(rfs);
        pval->persistent = true;
    } else {
        pval->data = (const byte *)(lib_ctx->profiledir);
        pval->size = lib_ctx->profiledir_len - 1;
        pval->persistent = false;
    }
}

int
gs_seticcdirectory(const gs_gstate * pgs, gs_param_string * pval)
{
    char *pname;
    int namelen = (pval->size)+1;
    gs_memory_t *mem = (gs_memory_t *)pgs->memory;

    /* Check if it was "NULL" */
    if (pval->size != 0 ) {
        pname = (char *)gs_alloc_bytes(mem, namelen,
                                       "gs_seticcdirectory");
        if (pname == NULL)
            return gs_rethrow(gs_error_VMerror, "cannot allocate directory name");
        memcpy(pname,pval->data,namelen-1);
        pname[namelen-1] = 0;
        if (gs_lib_ctx_set_icc_directory(mem, (const char*) pname, namelen) < 0) {
            gs_free_object(mem, pname, "gs_seticcdirectory");
            return -1;
        }
        gs_free_object(mem, pname, "gs_seticcdirectory");
    }
    return 0;
}

void
gs_currentsrcgtagicc(const gs_gstate * pgs, gs_param_string * pval)
{
    if (pgs->icc_manager->srcgtag_profile == NULL) {
        pval->data = NULL;
        pval->size = 0;
        pval->persistent = true;
    } else {
        pval->data = (byte *)pgs->icc_manager->srcgtag_profile->name;
        pval->size = pgs->icc_manager->srcgtag_profile->name_length;
        pval->persistent = false;
    }
}

int
gs_setsrcgtagicc(const gs_gstate * pgs, gs_param_string * pval)
{
    int code;
    char *pname;
    int namelen = (pval->size)+1;
    gs_memory_t *mem = pgs->memory;

    if (pval->size == 0) return 0;
    pname = (char *)gs_alloc_bytes(mem, namelen, "set_srcgtag_icc");
    if (pname == NULL)
        return_error(gs_error_VMerror);
    memcpy(pname,pval->data,namelen-1);
    pname[namelen-1] = 0;
    code = gsicc_set_srcgtag_struct(pgs->icc_manager, (const char*) pname,
                                   namelen);
    gs_free_object(mem, pname, "set_srcgtag_icc");
    if (code < 0)
        return gs_rethrow(code, "cannot find srctag file");
    return code;
}

void
gs_currentdefaultrgbicc(const gs_gstate * pgs, gs_param_string * pval)
{
    static const char *const rfs = DEFAULT_RGB_ICC;

    if (pgs->icc_manager->default_rgb == NULL) {
        pval->data = (const byte *) rfs;
        pval->persistent = true;
    } else {
        pval->data = (const byte *) (pgs->icc_manager->default_rgb->name);
        pval->persistent = false;
    }
    pval->size = strlen((const char *)pval->data);
}

int
gs_setdefaultrgbicc(const gs_gstate * pgs, gs_param_string * pval)
{
    int code;
    char *pname;
    int namelen = (pval->size)+1;
    gs_memory_t *mem = pgs->memory;

    pname = (char *)gs_alloc_bytes(mem, namelen,
                             "set_default_rgb_icc");
    if (pname == NULL)
        return_error(gs_error_VMerror);
    memcpy(pname,pval->data,namelen-1);
    pname[namelen-1] = 0;
    code = gsicc_set_profile(pgs->icc_manager,
        (const char*) pname, namelen, DEFAULT_RGB);
    gs_free_object(mem, pname,
        "set_default_rgb_icc");
    if (code < 0)
        return gs_rethrow(code, "cannot find default rgb icc profile");
    return code;
}

void
gs_currentnamedicc(const gs_gstate * pgs, gs_param_string * pval)
{
    static const char *const rfs = "";

    if (pgs->icc_manager->device_named == NULL) {
        pval->data = (const byte *) rfs;
        pval->persistent = true;
    } else {
        pval->data = (const byte *) (pgs->icc_manager->device_named->name);
        pval->persistent = false;
    }
    pval->size = strlen((const char *)pval->data);
}

int
gs_setnamedprofileicc(const gs_gstate * pgs, gs_param_string * pval)
{
    int code;
    char* pname;
    int namelen = (pval->size)+1;
    gs_memory_t *mem = pgs->memory;

    /* Check if it was "NULL" */
    if (pval->size != 0) {
        pname = (char *)gs_alloc_bytes(mem, namelen,
                                 "set_named_profile_icc");
        if (pname == NULL)
            return_error(gs_error_VMerror);
        memcpy(pname,pval->data,namelen-1);
        pname[namelen-1] = 0;
        code = gsicc_set_profile(pgs->icc_manager,
            (const char*) pname, namelen, NAMED_TYPE);
        gs_free_object(mem, pname,
                "set_named_profile_icc");
        if (code < 0)
            return gs_rethrow(code, "cannot find named color icc profile");
        return code;
    }
    return 0;
}

void
gs_currentdefaultcmykicc(const gs_gstate * pgs, gs_param_string * pval)
{
    static const char *const rfs = DEFAULT_CMYK_ICC;

    if (pgs->icc_manager->default_cmyk == NULL) {
        pval->data = (const byte *) rfs;
        pval->persistent = true;
    } else {
        pval->data = (const byte *) (pgs->icc_manager->default_cmyk->name);
        pval->persistent = false;
    }
    pval->size = strlen((const char *)pval->data);
}

int
gs_setdefaultcmykicc(const gs_gstate * pgs, gs_param_string * pval)
{
    int code;
    char* pname;
    int namelen = (pval->size)+1;
    gs_memory_t *mem = pgs->memory;

    pname = (char *)gs_alloc_bytes(mem, namelen,
                             "set_default_cmyk_icc");
    if (pname == NULL)
        return_error(gs_error_VMerror);
    memcpy(pname,pval->data,namelen-1);
    pname[namelen-1] = 0;
    code = gsicc_set_profile(pgs->icc_manager,
        (const char*) pname, namelen, DEFAULT_CMYK);
    gs_free_object(mem, pname,
                "set_default_cmyk_icc");
    if (code < 0)
        return gs_throw(code, "cannot find default cmyk icc profile");
    return code;
}

void
gs_currentlabicc(const gs_gstate * pgs, gs_param_string * pval)
{
    static const char *const rfs = LAB_ICC;

    pval->data = (const byte *)( (pgs->icc_manager->lab_profile == NULL) ?
                        rfs : pgs->icc_manager->lab_profile->name);
    pval->size = strlen((const char *)pval->data);
    pval->persistent = true;
}

int
gs_setlabicc(const gs_gstate * pgs, gs_param_string * pval)
{
    int code;
    char* pname;
    int namelen = (pval->size)+1;
    gs_memory_t *mem = pgs->memory;

    pname = (char *)gs_alloc_bytes(mem, namelen,
                             "set_lab_icc");
    if (pname == NULL)
        return_error(gs_error_VMerror);
    memcpy(pname,pval->data,namelen-1);
    pname[namelen-1] = 0;
    code = gsicc_set_profile(pgs->icc_manager,
        (const char*) pname, namelen, LAB_TYPE);
    gs_free_object(mem, pname,
                "set_lab_icc");
    if (code < 0)
        return gs_throw(code, "cannot find default lab icc profile");
    return code;
}
