/* Copyright (C) 2001-2018 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.
*/


/* Gimp (XCF) export device, supporting DeviceN color models. */

#include "math_.h"
#include "gdevprn.h"
#include "gsparam.h"
#include "gscrd.h"
#include "gscrdp.h"
#include "gxlum.h"
#include "gdevdcrd.h"
#include "gstypes.h"
#include "gxdcconv.h"
#include "gscms.h"
#include "gsicc_cache.h"
#include "gsicc_manage.h"

#ifndef cmm_gcmmhlink_DEFINED
    #define cmm_gcmmhlink_DEFINED
    typedef void* gcmmhlink_t;
#endif

#ifndef cmm_gcmmhprofile_DEFINED
    #define cmm_gcmmhprofile_DEFINED
    typedef void* gcmmhprofile_t;
#endif

#ifndef MAX_CHAN
#   define MAX_CHAN 8
#endif

/* Define the device parameters. */
#ifndef X_DPI
#  define X_DPI 72
#endif
#ifndef Y_DPI
#  define Y_DPI 72
#endif

/* The device descriptor */
static dev_proc_get_params(xcf_get_params);
static dev_proc_close_device(xcf_prn_close);
static dev_proc_put_params(xcf_put_params);
static dev_proc_print_page(xcf_print_page);
static dev_proc_map_color_rgb(xcf_map_color_rgb);
static dev_proc_get_color_mapping_procs(get_spotrgb_color_mapping_procs);
#if 0
static dev_proc_get_color_mapping_procs(get_spotcmyk_color_mapping_procs);
#endif
static dev_proc_get_color_mapping_procs(get_xcf_color_mapping_procs);
static dev_proc_get_color_comp_index(xcf_get_color_comp_index);
static dev_proc_encode_color(xcf_encode_color);
static dev_proc_decode_color(xcf_decode_color);

/*
 * Type definitions associated with the fixed color model names.
 */
typedef const char * fixed_colorant_name;
typedef fixed_colorant_name fixed_colorant_names_list[];

/*
 * Structure for holding SeparationNames and SeparationOrder elements.
 */
typedef struct gs_separation_names_s {
    int num_names;
    const gs_param_string * names[GX_DEVICE_COLOR_MAX_COMPONENTS];
} gs_separation_names;

/* This is redundant with color_info.cm_name. We may eliminate this
   typedef and use the latter string for everything. */
typedef enum {
    XCF_DEVICE_GRAY,
    XCF_DEVICE_RGB,
    XCF_DEVICE_CMYK,
    XCF_DEVICE_N
} xcf_color_model;

/*
 * A structure definition for a DeviceN type device
 */
typedef struct xcf_device_s {
    gx_device_common;
    gx_prn_device_common;

    /*        ... device-specific parameters ... */

    xcf_color_model color_model;

    /*
     * Bits per component (device colorant).  Currently only 1 and 8 are
     * supported.
     */
    int bitspercomponent;

    /*
     * Pointer to the colorant names for the color model.  This will be
     * null if we have DeviceN type device.  The actual possible colorant
     * names are those in this list plus those in the separation_names
     * list (below).
     */
    const fixed_colorant_names_list * std_colorant_names;
    int num_std_colorant_names;	/* Number of names in list */

    /*
    * Separation names (if any).
    */
    gs_separation_names separation_names;

    /*
     * Separation Order (if specified).
     */
    gs_separation_names separation_order;

    /* ICC color profile objects, for color conversion.
       These are all device link profiles.  At least that
       is how it appears looking at how this code
       was written to work with the old icclib.  Just
       doing minimal updates here so that it works
       with the new CMM API.  I would be interested
       to hear how people are using this. */

    char profile_rgb_fn[256];
    cmm_profile_t *rgb_profile;
    gcmmhlink_t rgb_icc_link;

    char profile_cmyk_fn[256];
    cmm_profile_t *cmyk_profile;
    gcmmhlink_t cmyk_icc_link;

    char profile_out_fn[256];
    cmm_profile_t *output_profile;
    gcmmhlink_t output_icc_link;

} xcf_device;

/*
 * Macro definition for DeviceN procedures
 */
#define device_procs(get_color_mapping_procs)\
{	gdev_prn_open,\
        gx_default_get_initial_matrix,\
        NULL,				/* sync_output */\
        /* Since the print_page doesn't alter the device, this device can print in the background */\
        gdev_prn_bg_output_page,		/* output_page */\
        xcf_prn_close,			/* close */\
        NULL,				/* map_rgb_color - not used */\
        xcf_map_color_rgb,		/* map_color_rgb */\
        NULL,				/* fill_rectangle */\
        NULL,				/* tile_rectangle */\
        NULL,				/* copy_mono */\
        NULL,				/* copy_color */\
        NULL,				/* draw_line */\
        NULL,				/* get_bits */\
        xcf_get_params,		/* get_params */\
        xcf_put_params,		/* put_params */\
        NULL,				/* map_cmyk_color - not used */\
        NULL,				/* get_xfont_procs */\
        NULL,				/* get_xfont_device */\
        NULL,				/* map_rgb_alpha_color */\
        gx_page_device_get_page_device,	/* get_page_device */\
        NULL,				/* get_alpha_bits */\
        NULL,				/* copy_alpha */\
        NULL,				/* get_band */\
        NULL,				/* copy_rop */\
        NULL,				/* fill_path */\
        NULL,				/* stroke_path */\
        NULL,				/* fill_mask */\
        NULL,				/* fill_trapezoid */\
        NULL,				/* fill_parallelogram */\
        NULL,				/* fill_triangle */\
        NULL,				/* draw_thin_line */\
        NULL,				/* begin_image */\
        NULL,				/* image_data */\
        NULL,				/* end_image */\
        NULL,				/* strip_tile_rectangle */\
        NULL,				/* strip_copy_rop */\
        NULL,				/* get_clipping_box */\
        NULL,				/* begin_typed_image */\
        NULL,				/* get_bits_rectangle */\
        NULL,				/* map_color_rgb_alpha */\
        NULL,				/* create_compositor */\
        NULL,				/* get_hardware_params */\
        NULL,				/* text_begin */\
        NULL,				/* finish_copydevice */\
        NULL,				/* begin_transparency_group */\
        NULL,				/* end_transparency_group */\
        NULL,				/* begin_transparency_mask */\
        NULL,				/* end_transparency_mask */\
        NULL,				/* discard_transparency_layer */\
        get_color_mapping_procs,	/* get_color_mapping_procs */\
        xcf_get_color_comp_index,	/* get_color_comp_index */\
        xcf_encode_color,		/* encode_color */\
        xcf_decode_color		/* decode_color */\
}

static const fixed_colorant_names_list DeviceGrayComponents = {
        "Gray",
        0		/* List terminator */
};

static const fixed_colorant_names_list DeviceRGBComponents = {
        "Red",
        "Green",
        "Blue",
        0		/* List terminator */
};

static const fixed_colorant_names_list DeviceCMYKComponents = {
        "Cyan",
        "Magenta",
        "Yellow",
        "Black",
        0		/* List terminator */
};

/*
 * Example device with RGB and spot color support
 */
static const gx_device_procs spot_rgb_procs = device_procs(get_spotrgb_color_mapping_procs);

const xcf_device gs_xcf_device =
{
    prn_device_body_extended(xcf_device, spot_rgb_procs, "xcf",
         DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
         X_DPI, Y_DPI,		/* X and Y hardware resolution */
         0, 0, 0, 0,		/* margins */
         GX_DEVICE_COLOR_MAX_COMPONENTS, 3,	/* MaxComponents, NumComp */
         GX_CINFO_POLARITY_ADDITIVE,		/* Polarity */
         24, 0,			/* Depth, Gray_index, */
         255, 255, 256, 256,	/* MaxGray, MaxColor, DitherGray, DitherColor */
         GX_CINFO_UNKNOWN_SEP_LIN, /* Let check_device_separable set up values */
         "DeviceN",		/* Process color model name */
         xcf_print_page),	/* Printer page print routine */
    /* DeviceN device specific parameters */
    XCF_DEVICE_RGB,		/* Color model */
    8,				/* Bits per color - must match ncomp, depth, etc. above */
    (&DeviceRGBComponents),	/* Names of color model colorants */
    3,				/* Number colorants for RGB */
    {0},			/* SeparationNames */
    {0}				/* SeparationOrder names */
};

static const gx_device_procs spot_cmyk_procs = device_procs(get_xcf_color_mapping_procs);

const xcf_device gs_xcfcmyk_device =
{
    prn_device_body_extended(xcf_device, spot_cmyk_procs, "xcfcmyk",
         DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
         X_DPI, Y_DPI,		/* X and Y hardware resolution */
         0, 0, 0, 0,		/* margins */
         GX_DEVICE_COLOR_MAX_COMPONENTS, 4,	/* MaxComponents, NumComp */
         GX_CINFO_POLARITY_SUBTRACTIVE,		/* Polarity */
         32, 0,			/* Depth, Gray_index, */
         255, 255, 256, 256,	/* MaxGray, MaxColor, DitherGray, DitherColor */
         GX_CINFO_UNKNOWN_SEP_LIN, /* Let check_device_separable set up values */
         "DeviceN",		/* Process color model name */
         xcf_print_page),	/* Printer page print routine */
    /* DeviceN device specific parameters */
    XCF_DEVICE_CMYK,		/* Color model */
    8,				/* Bits per color - must match ncomp, depth, etc. above */
    (&DeviceCMYKComponents),	/* Names of color model colorants */
    4,				/* Number colorants for RGB */
    {0},			/* SeparationNames */
    {0}				/* SeparationOrder names */
};

/*
 * The following procedures are used to map the standard color spaces into
 * the color components for the spotrgb device.
 */
static void
gray_cs_to_spotrgb_cm(gx_device * dev, frac gray, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    int i = ((xcf_device *)dev)->separation_names.num_names;

    out[0] = out[1] = out[2] = gray;
    for(; i>0; i--)			/* Clear spot colors */
        out[2 + i] = 0;
}

static void
rgb_cs_to_spotrgb_cm(gx_device * dev, const gs_gstate *pgs,
                                  frac r, frac g, frac b, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    int i = ((xcf_device *)dev)->separation_names.num_names;

    out[0] = r;
    out[1] = g;
    out[2] = b;
    for(; i>0; i--)			/* Clear spot colors */
        out[2 + i] = 0;
}

static void
cmyk_cs_to_spotrgb_cm(gx_device * dev, frac c, frac m, frac y, frac k, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    int i = ((xcf_device *)dev)->separation_names.num_names;

    color_cmyk_to_rgb(c, m, y, k, NULL, out, dev->memory);
    for(; i>0; i--)			/* Clear spot colors */
        out[2 + i] = 0;
}

static void
gray_cs_to_spotcmyk_cm(gx_device * dev, frac gray, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    int i = ((xcf_device *)dev)->separation_names.num_names;

    out[0] = out[1] = out[2] = 0;
    out[3] = frac_1 - gray;
    for(; i>0; i--)			/* Clear spot colors */
        out[3 + i] = 0;
}

static void
rgb_cs_to_spotcmyk_cm(gx_device * dev, const gs_gstate *pgs,
                                   frac r, frac g, frac b, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    xcf_device *xdev = (xcf_device *)dev;
    int n = xdev->separation_names.num_names;
    int i;

    color_rgb_to_cmyk(r, g, b, pgs, out, dev->memory);
    for(i = 0; i < n; i++)			/* Clear spot colors */
        out[4 + i] = 0;
}

static void
cmyk_cs_to_spotcmyk_cm(gx_device * dev, frac c, frac m, frac y, frac k, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    xcf_device *xdev = (xcf_device *)dev;
    int n = xdev->separation_names.num_names;
    int i;

    out[0] = c;
    out[1] = m;
    out[2] = y;
    out[3] = k;
    for(i = 0; i < n; i++)			/* Clear spot colors */
        out[4 + i] = 0;
}

static void
cmyk_cs_to_spotn_cm(gx_device * dev, frac c, frac m, frac y, frac k, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    xcf_device *xdev = (xcf_device *)dev;
    int n = xdev->separation_names.num_names;

    gcmmhlink_t link = xdev->cmyk_icc_link;
    int i;

    if (link != NULL) {
        unsigned short in[4];
        unsigned short tmp[MAX_CHAN];
        int outn = xdev->cmyk_profile->num_comps_out;

        in[0] = frac2ushort(c);
        in[1] = frac2ushort(m);
        in[2] = frac2ushort(y);
        in[3] = frac2ushort(k);

        gscms_transform_color(dev, link, &(in[0]), &(tmp[0]), 2);
        for (i = 0; i < outn; i++)
            out[i] = ushort2frac(tmp[i]);
        for (; i < n + 4; i++)
            out[i] = 0;

    } else {
        /* If no profile given, assume CMYK */
        out[0] = c;
        out[1] = m;
        out[2] = y;
        out[3] = k;
        for(i = 0; i < n; i++)			/* Clear spot colors */
            out[4 + i] = 0;
    }
}

static void
gray_cs_to_spotn_cm(gx_device * dev, frac gray, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */

    cmyk_cs_to_spotn_cm(dev, 0, 0, 0, frac_1 - gray, out);
}

static void
rgb_cs_to_spotn_cm(gx_device * dev, const gs_gstate *pgs,
                                   frac r, frac g, frac b, frac out[])
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    xcf_device *xdev = (xcf_device *)dev;
    int n = xdev->separation_names.num_names;
    gcmmhlink_t link = xdev->rgb_icc_link;
    int i;

    if (link != NULL) {
        unsigned short in[3];
        unsigned short tmp[MAX_CHAN];
        int outn = xdev->rgb_profile->num_comps_out;

        in[0] = frac2ushort(r);
        in[1] = frac2ushort(g);
        in[2] = frac2ushort(b);

        gscms_transform_color(dev, link, &(in[0]), &(tmp[0]), 2);

        for (i = 0; i < outn; i++)
            out[i] = ushort2frac(tmp[i]);
        for (; i < n + 4; i++)
            out[i] = 0;
    } else {
        frac cmyk[4];

        color_rgb_to_cmyk(r, g, b, pgs, cmyk, dev->memory);
        cmyk_cs_to_spotn_cm(dev, cmyk[0], cmyk[1], cmyk[2], cmyk[3],
                            out);
    }
}

static const gx_cm_color_map_procs spotRGB_procs = {
    gray_cs_to_spotrgb_cm, rgb_cs_to_spotrgb_cm, cmyk_cs_to_spotrgb_cm
};

static const gx_cm_color_map_procs spotCMYK_procs = {
    gray_cs_to_spotcmyk_cm, rgb_cs_to_spotcmyk_cm, cmyk_cs_to_spotcmyk_cm
};

static const gx_cm_color_map_procs spotN_procs = {
    gray_cs_to_spotn_cm, rgb_cs_to_spotn_cm, cmyk_cs_to_spotn_cm
};

/*
 * These are the handlers for returning the list of color space
 * to color model conversion routines.
 */
static const gx_cm_color_map_procs *
get_spotrgb_color_mapping_procs(const gx_device * dev)
{
    return &spotRGB_procs;
}

#if 0
static const gx_cm_color_map_procs *
get_spotcmyk_color_mapping_procs(const gx_device * dev)
{
    return &spotCMYK_procs;
}
#endif

static const gx_cm_color_map_procs *
get_xcf_color_mapping_procs(const gx_device * dev)
{
    const xcf_device *xdev = (const xcf_device *)dev;

    if (xdev->color_model == XCF_DEVICE_RGB)
        return &spotRGB_procs;
    else if (xdev->color_model == XCF_DEVICE_CMYK)
        return &spotCMYK_procs;
    else if (xdev->color_model == XCF_DEVICE_N)
        return &spotN_procs;
    else
        return NULL;
}

/*
 * Encode a list of colorant values into a gx_color_index_value.
 */
static gx_color_index
xcf_encode_color(gx_device *dev, const gx_color_value colors[])
{
    int bpc = ((xcf_device *)dev)->bitspercomponent;
    gx_color_index color = 0;
    int i = 0;
    int ncomp = dev->color_info.num_components;
    COLROUND_VARS;

    COLROUND_SETUP(bpc);
    for (; i<ncomp; i++) {
        color <<= bpc;
        color |= COLROUND_ROUND(colors[i]);
    }
    return (color == gx_no_color_index ? color ^ 1 : color);
}

/*
 * Decode a gx_color_index value back to a list of colorant values.
 */
static int
xcf_decode_color(gx_device * dev, gx_color_index color, gx_color_value * out)
{
    int bpc = ((xcf_device *)dev)->bitspercomponent;
    int mask = (1 << bpc) - 1;
    int i = 0;
    int ncomp = dev->color_info.num_components;
    COLDUP_VARS;

    COLDUP_SETUP(bpc);
    for (; i<ncomp; i++) {
        out[ncomp - i - 1] = COLDUP_DUP(color & mask);
        color >>= bpc;
    }
    return 0;
}

/*
 * Convert a gx_color_index to RGB.
 */
static int
xcf_map_color_rgb(gx_device *dev, gx_color_index color, gx_color_value rgb[3])
{
    xcf_device *xdev = (xcf_device *)dev;

    if (xdev->color_model == XCF_DEVICE_RGB)
        return xcf_decode_color(dev, color, rgb);
    /* TODO: return reasonable values. */
    rgb[0] = 0;
    rgb[1] = 0;
    rgb[2] = 0;
    return 0;
}

/*
 * This routine will extract a specified set of bits from a buffer and pack
 * them into a given buffer.
 *
 * Parameters:
 *   source - The source of the data
 *   dest - The destination for the data
 *   depth - The size of the bits per pixel - must be a multiple of 8
 *   first_bit - The location of the first data bit (LSB).
 *   bit_width - The number of bits to be extracted.
 *   npixel - The number of pixels.
 *
 * Returns:
 *   Length of the output line (in bytes)
 *   Data in dest.
 */
#if 0
static int
repack_data(byte * source, byte * dest, int depth, int first_bit,
                int bit_width, int npixel)
{
    int in_nbyte = depth >> 3;		/* Number of bytes per input pixel */
    int out_nbyte = bit_width >> 3;	/* Number of bytes per output pixel */
    gx_color_index mask = 1;
    gx_color_index data;
    int i, j, length = 0;
    int in_byte_loc = 0, out_byte_loc = 0;
    byte temp;
    byte * out = dest;
    int max_bit_byte = 8 - bit_width;

    mask = (mask << bit_width) - 1;
    for (i=0; i<npixel; i++) {
        /* Get the pixel data */
        if (!in_nbyte) {		/* Multiple pixels per byte */
            data = *source;
            data >>= in_byte_loc;
            in_byte_loc += depth;
            if (in_byte_loc >= 8) {	/* If finished with byte */
                in_byte_loc = 0;
                source++;
            }
        }
        else {				/* One or more bytes per pixel */
            data = *source++;
            for (j=1; j<in_nbyte; j++)
                data = (data << 8) + *source++;
        }
        data >>= first_bit;
        data &= mask;

        /* Put the output data */
        if (!out_nbyte) {		/* Multiple pixels per byte */
            temp = *out & ~(mask << out_byte_loc);
            *out = temp | (data << out_byte_loc);
            out_byte_loc += bit_width;
            if (out_byte_loc > max_bit_byte) {	/* If finished with byte */
                out_byte_loc = 0;
                out++;
            }
        }
        else {				/* One or more bytes per pixel */
            *out++ = data >> ((out_nbyte - 1) * 8);
            for (j=1; j<out_nbyte; j++) {
                *out++ = data >> ((out_nbyte - 1 - j) * 8);
            }
        }
    }
    /* Return the number of bytes in the destination buffer. */
    length = out - dest;
    if (out_byte_loc)		 	/* If partially filled last byte */
        length++;
    return length;
}
#endif /* 0 */

static int
xcf_open_profile(const char *profile_out_fn, cmm_profile_t *icc_profile, gcmmhlink_t icc_link, gs_memory_t *memory)
{

    gsicc_rendering_param_t rendering_params;

    icc_profile = gsicc_get_profile_handle_file(profile_out_fn,
                    strlen(profile_out_fn), memory);

    if (icc_profile == NULL)
        return gs_throw(-1, "Could not create profile for xcf device");

    /* Set up the rendering parameters */

    rendering_params.black_point_comp = gsBPNOTSPECIFIED;
    rendering_params.graphics_type_tag = GS_UNKNOWN_TAG;  /* Already rendered */
    rendering_params.rendering_intent = gsPERCEPTUAL;

    /* Call with a NULL destination profile since we are using a device link profile here */
    icc_link = gscms_get_link(icc_profile,
                              NULL, &rendering_params, 0, memory);

    if (icc_link == NULL)
        return gs_throw(-1, "Could not create link handle for xdev device");

    return(0);

}

static int
xcf_open_profiles(xcf_device *xdev)
{
    int code = 0;

    if (xdev->output_icc_link == NULL && xdev->profile_out_fn[0]) {

        code = xcf_open_profile(xdev->profile_out_fn, xdev->output_profile,
            xdev->output_icc_link, xdev->memory);

    }

    if (code >= 0 && xdev->rgb_icc_link == NULL && xdev->profile_rgb_fn[0]) {

        code = xcf_open_profile(xdev->profile_rgb_fn, xdev->rgb_profile,
            xdev->rgb_icc_link, xdev->memory);

    }

    if (code >= 0 && xdev->cmyk_icc_link == NULL && xdev->profile_cmyk_fn[0]) {

        code = xcf_open_profile(xdev->profile_cmyk_fn, xdev->cmyk_profile,
            xdev->cmyk_icc_link, xdev->memory);

    }

    return code;
}

#define set_param_array(a, d, s)\
  (a.data = d, a.size = s, a.persistent = false);

/* Get parameters.  We provide a default CRD. */
static int
xcf_get_params(gx_device * pdev, gs_param_list * plist)
{
    xcf_device *xdev = (xcf_device *)pdev;
    int code;
    bool seprs = false;
    gs_param_string_array scna;
    gs_param_string pos;
    gs_param_string prgbs;
    gs_param_string pcmyks;

    set_param_array(scna, NULL, 0);

    if ( (code = gdev_prn_get_params(pdev, plist)) < 0 ||
         (code = sample_device_crd_get_params(pdev, plist, "CRDDefault")) < 0 ||
         (code = param_write_name_array(plist, "SeparationColorNames", &scna)) < 0 ||
         (code = param_write_bool(plist, "Separations", &seprs)) < 0)
        return code;

    pos.data = (const byte *)xdev->profile_out_fn,
        pos.size = strlen(xdev->profile_out_fn),
        pos.persistent = false;
    code = param_write_string(plist, "ProfileOut", &pos);
    if (code < 0)
        return code;

    prgbs.data = (const byte *)xdev->profile_rgb_fn,
        prgbs.size = strlen(xdev->profile_rgb_fn),
        prgbs.persistent = false;
    code = param_write_string(plist, "ProfileRgb", &prgbs);
    if (code < 0)
        return code;

    pcmyks.data = (const byte *)xdev->profile_cmyk_fn,
        pcmyks.size = strlen(xdev->profile_cmyk_fn),
        pcmyks.persistent = false;
    code = param_write_string(plist, "ProfileCmyk", &pcmyks);

    return code;
}
#undef set_param_array

#define compare_color_names(name, name_size, str, str_size) \
    (name_size == str_size && \
        (strncmp((const char *)name, (const char *)str, name_size) == 0))

/*
 * This routine will check if a name matches any item in a list of process model
 * color component names.
 */
static bool
check_process_color_names(const fixed_colorant_names_list * pcomp_list,
                          const gs_param_string * pstring)
{
    if (pcomp_list) {
        const fixed_colorant_name * plist = *pcomp_list;
        uint size = pstring->size;

        while( *plist) {
            if (compare_color_names(*plist, strlen(*plist), pstring->data, size)) {
                return true;
            }
            plist++;
        }
    }
    return false;
}

/*
 * This utility routine calculates the number of bits required to store
 * color information.  In general the values are rounded up to an even
 * byte boundary except those cases in which mulitple pixels can evenly
 * into a single byte.
 *
 * The parameter are:
 *   ncomp - The number of components (colorants) for the device.  Valid
 * 	values are 1 to GX_DEVICE_COLOR_MAX_COMPONENTS
 *   bpc - The number of bits per component.  Valid values are 1, 2, 4, 5,
 *	and 8.
 * Input values are not tested for validity.
 */
static int
bpc_to_depth(uchar ncomp, int bpc)
{
    static const byte depths[4][8] = {
        {1, 2, 0, 4, 8, 0, 0, 8},
        {2, 4, 0, 8, 16, 0, 0, 16},
        {4, 8, 0, 16, 16, 0, 0, 24},
        {4, 8, 0, 16, 32, 0, 0, 32}
    };

    if (ncomp <=4 && bpc <= 8)
        return depths[ncomp -1][bpc-1];
    else
        return (ncomp * bpc + 7) & ~7;
}

#define BEGIN_ARRAY_PARAM(pread, pname, pa, psize, e)\
    BEGIN\
    switch (code = pread(plist, (param_name = pname), &(pa))) {\
      case 0:\
        if ((pa).size != psize) {\
          ecode = gs_note_error(gs_error_rangecheck);\
          (pa).data = 0;	/* mark as not filled */\
        } else
#define END_ARRAY_PARAM(pa, e)\
        goto e;\
      default:\
        ecode = code;\
e:	param_signal_error(plist, param_name, ecode);\
      case 1:\
        (pa).data = 0;		/* mark as not filled */\
    }\
    END

static int
xcf_param_read_fn(gs_param_list *plist, const char *name,
                  gs_param_string *pstr, int max_len)
{
    int code = param_read_string(plist, name, pstr);

    if (code == 0) {
        if (pstr->size >= max_len)
            param_signal_error(plist, name, code = gs_error_rangecheck);
    } else {
        pstr->data = 0;
    }
    return code;
}

/* Compare a C string and a gs_param_string. */
static bool
param_string_eq(const gs_param_string *pcs, const char *str)
{
    return (strlen(str) == pcs->size &&
            !strncmp(str, (const char *)pcs->data, pcs->size));
}

static int
xcf_set_color_model(xcf_device *xdev, xcf_color_model color_model)
{
    xdev->color_model = color_model;
    if (color_model == XCF_DEVICE_GRAY) {
        xdev->std_colorant_names = &DeviceGrayComponents;
        xdev->num_std_colorant_names = 1;
        xdev->color_info.cm_name = "DeviceGray";
        xdev->color_info.polarity = GX_CINFO_POLARITY_ADDITIVE;
    } else if (color_model == XCF_DEVICE_RGB) {
        xdev->std_colorant_names = &DeviceRGBComponents;
        xdev->num_std_colorant_names = 3;
        xdev->color_info.cm_name = "DeviceRGB";
        xdev->color_info.polarity = GX_CINFO_POLARITY_ADDITIVE;
    } else if (color_model == XCF_DEVICE_CMYK) {
        xdev->std_colorant_names = &DeviceCMYKComponents;
        xdev->num_std_colorant_names = 4;
        xdev->color_info.cm_name = "DeviceCMYK";
        xdev->color_info.polarity = GX_CINFO_POLARITY_SUBTRACTIVE;
    } else if (color_model == XCF_DEVICE_N) {
        xdev->std_colorant_names = &DeviceCMYKComponents;
        xdev->num_std_colorant_names = 4;
        xdev->color_info.cm_name = "DeviceN";
        xdev->color_info.polarity = GX_CINFO_POLARITY_SUBTRACTIVE;
    } else {
        return -1;
    }

    return 0;
}

/*
 * Close device and clean up ICC structures.
 */

static int
xcf_prn_close(gx_device *dev)
{
    xcf_device * const xdev = (xcf_device *) dev;

    if (xdev->cmyk_icc_link != NULL) {
        gscms_release_link(xdev->cmyk_icc_link);
        rc_decrement(xdev->cmyk_profile, "xcf_prn_close");
    }

    if (xdev->rgb_icc_link != NULL) {
        gscms_release_link(xdev->rgb_icc_link);
        rc_decrement(xdev->rgb_profile, "xcf_prn_close");
    }

    if (xdev->output_icc_link != NULL) {
        gscms_release_link(xdev->output_icc_link);
        rc_decrement(xdev->output_profile, "xcf_prn_close");
    }

    return gdev_prn_close(dev);
}

/* Set parameters.  We allow setting the number of bits per component. */
static int
xcf_put_params(gx_device * pdev, gs_param_list * plist)
{
    xcf_device * const pdevn = (xcf_device *) pdev;
    gx_device_color_info save_info;
    gs_param_name param_name;
    int npcmcolors;
    int num_spot = pdevn->separation_names.num_names;
    int ecode = 0;
    int code;
    gs_param_string_array scna;
    gs_param_string po;
    gs_param_string prgb;
    gs_param_string pcmyk;
    gs_param_string pcm;
    xcf_color_model color_model = pdevn->color_model;

    BEGIN_ARRAY_PARAM(param_read_name_array, "SeparationColorNames", scna, scna.size, scne) {
        break;
    } END_ARRAY_PARAM(scna, scne);

    if (code >= 0)
        code = xcf_param_read_fn(plist, "ProfileOut", &po,
                                 sizeof(pdevn->profile_out_fn));
    if (code >= 0)
        code = xcf_param_read_fn(plist, "ProfileRgb", &prgb,
                                 sizeof(pdevn->profile_rgb_fn));
    if (code >= 0)
        code = xcf_param_read_fn(plist, "ProfileCmyk", &pcmyk,
                                 sizeof(pdevn->profile_cmyk_fn));

    if (code >= 0)
        code = param_read_name(plist, "ProcessColorModel", &pcm);
    if (code == 0) {
        if (param_string_eq (&pcm, "DeviceGray"))
            color_model = XCF_DEVICE_GRAY;
        else if (param_string_eq (&pcm, "DeviceRGB"))
            color_model = XCF_DEVICE_RGB;
        else if (param_string_eq (&pcm, "DeviceCMYK"))
            color_model = XCF_DEVICE_CMYK;
        else if (param_string_eq (&pcm, "DeviceN"))
            color_model = XCF_DEVICE_N;
        else {
            param_signal_error(plist, "ProcessColorModel",
                               code = gs_error_rangecheck);
        }
    }
    if (code < 0)
        return code;

    /*
     * Save the color_info in case gdev_prn_put_params fails, and for
     * comparison.
     */
    save_info = pdevn->color_info;
    ecode = xcf_set_color_model(pdevn, color_model);
    if (ecode == 0)
        ecode = gdev_prn_put_params(pdev, plist);
    if (ecode < 0) {
        pdevn->color_info = save_info;
        return ecode;
    }

    /* Separations are only valid with a subrtractive color model */
    if (pdev->color_info.polarity == GX_CINFO_POLARITY_SUBTRACTIVE) {
        /*
         * Process the separation color names.  Remove any names that already
         * match the process color model colorant names for the device.
         */
        if (scna.data != 0) {
            int i;
            int num_names = scna.size;
            const fixed_colorant_names_list * pcomp_names =
                                ((xcf_device *)pdev)->std_colorant_names;

            for (i = num_spot = 0; i < num_names; i++) {
                if (!check_process_color_names(pcomp_names, &scna.data[i]))
                    pdevn->separation_names.names[num_spot++] = &scna.data[i];
            }
            pdevn->separation_names.num_names = num_spot;
            if (pdevn->is_open)
                gs_closedevice(pdev);
        }
        npcmcolors = pdevn->num_std_colorant_names;
        pdevn->color_info.num_components = npcmcolors + num_spot;
        /*
         * The DeviceN device can have zero components if nothing has been
         * specified.  This causes some problems so force at least one
         * component until something is specified.
         */
        if (!pdevn->color_info.num_components)
            pdevn->color_info.num_components = 1;
        pdevn->color_info.depth = bpc_to_depth(pdevn->color_info.num_components,
                                                pdevn->bitspercomponent);
        if (pdevn->color_info.depth != save_info.depth) {
            gs_closedevice(pdev);
        }
    }

    if (po.data != 0) {
        memcpy(pdevn->profile_out_fn, po.data, po.size);
        pdevn->profile_out_fn[po.size] = 0;
    }
    if (prgb.data != 0) {
        memcpy(pdevn->profile_rgb_fn, prgb.data, prgb.size);
        pdevn->profile_rgb_fn[prgb.size] = 0;
    }
    if (pcmyk.data != 0) {
        memcpy(pdevn->profile_cmyk_fn, pcmyk.data, pcmyk.size);
        pdevn->profile_cmyk_fn[pcmyk.size] = 0;
    }
    code = xcf_open_profiles(pdevn);

    return code;
}

/*
 * This routine will check to see if the color component name  match those
 * that are available amoung the current device's color components.
 *
 * Parameters:
 *   dev - pointer to device data structure.
 *   pname - pointer to name (zero termination not required)
 *   nlength - length of the name
 *
 * This routine returns a positive value (0 to n) which is the device colorant
 * number if the name is found.  It returns a negative value if not found.
 */
static int
xcf_get_color_comp_index(gx_device * dev, const char * pname, int name_size,
                                        int component_type)
{
/* TO_DO_DEVICEN  This routine needs to include the effects of the SeparationOrder array */
    const fixed_colorant_names_list * list = ((const xcf_device *)dev)->std_colorant_names;
    const fixed_colorant_name * pcolor = *list;
    int color_component_number = 0;
    int i;

    /* Check if the component is in the implied list. */
    if (pcolor) {
        while( *pcolor) {
            if (compare_color_names(pname, name_size, *pcolor, strlen(*pcolor)))
                return color_component_number;
            pcolor++;
            color_component_number++;
        }
    }

    /* Check if the component is in the separation names list. */
    {
        const gs_separation_names * separations = &((const xcf_device *)dev)->separation_names;
        int num_spot = separations->num_names;

        for (i=0; i<num_spot; i++) {
            if (compare_color_names((const char *)separations->names[i]->data,
                  separations->names[i]->size, pname, name_size)) {
                return color_component_number;
            }
            color_component_number++;
        }
    }

    return -1;
}

/* ------ Private definitions ------ */

/* All two-byte quantities are stored MSB-first! */
#if ARCH_IS_BIG_ENDIAN
#  define assign_u16(a,v) a = (v)
#  define assign_u32(a,v) a = (v)
#else
#  define assign_u16(a,v) a = ((v) >> 8) + ((v) << 8)
#  define assign_u32(a,v) a = (((v) >> 24) & 0xff) + (((v) >> 8) & 0xff00) + (((v) & 0xff00) << 8) + (((v) & 0xff) << 24)
#endif

typedef struct {
    FILE *f;
    int offset;

    int width;
    int height;
    int base_bytes_pp; /* almost always 3 (rgb) */
    int n_extra_channels;

    int n_tiles_x;
    int n_tiles_y;
    int n_tiles;
    int n_levels;

    /* byte offset of image data */
    int image_data_off;
} xcf_write_ctx;

#define TILE_WIDTH 64
#define TILE_HEIGHT 64

static int
xcf_calc_levels(int size, int tile_size)
{
    int levels = 1;
    while (size > tile_size) {
        size >>= 1;
        levels++;
    }
    return levels;
}

static int
xcf_setup_tiles(xcf_write_ctx *xc, xcf_device *dev)
{
    xc->base_bytes_pp = 3;
    xc->n_extra_channels = dev->separation_names.num_names;
    xc->width = dev->width;
    xc->height = dev->height;
    xc->n_tiles_x = (dev->width + TILE_WIDTH - 1) / TILE_WIDTH;
    xc->n_tiles_y = (dev->height + TILE_HEIGHT - 1) / TILE_HEIGHT;
    xc->n_tiles = xc->n_tiles_x * xc->n_tiles_y;
    xc->n_levels = max(xcf_calc_levels(dev->width, TILE_WIDTH),
                       xcf_calc_levels(dev->height, TILE_HEIGHT));

    return 0;
}

/* Return value: Size of tile in pixels. */
static int
xcf_tile_sizeof(xcf_write_ctx *xc, int tile_idx)
{
    int tile_i = tile_idx % xc->n_tiles_x;
    int tile_j = tile_idx / xc->n_tiles_x;
    int tile_size_x = min(TILE_WIDTH, xc->width - tile_i * TILE_WIDTH);
    int tile_size_y = min(TILE_HEIGHT, xc->height - tile_j * TILE_HEIGHT);
    return tile_size_x * tile_size_y;
}

static int
xcf_write(xcf_write_ctx *xc, const byte *buf, int size) {
    int code;

    code = fwrite(buf, 1, size, xc->f);
    if (code < 0)
        return code;
    xc->offset += code;
    return 0;
}

static int
xcf_write_32(xcf_write_ctx *xc, bits32 v)
{
    bits32 buf;

    assign_u32(buf, v);
    return xcf_write(xc, (byte *)&buf, 4);
}

static int
xcf_write_image_props(xcf_write_ctx *xc)
{
    int code = 0;

    xcf_write_32(xc, 0);
    xcf_write_32(xc, 0);

    return code;
}

/**
 * Return value: Number of bytes needed to write layer.
 **/
static int
xcf_base_size(xcf_write_ctx *xc, const char *layer_name)
{
    int bytes_pp = xc->base_bytes_pp + xc->n_extra_channels;

    return 17 + strlen (layer_name) +		/* header and name */
        8 +					/* layer props */
        12 + xc->n_levels * 16 +		/* layer tile hierarchy */
        12 + xc->n_tiles * 4 +			/* tile offsets */
        xc->width * xc->height * bytes_pp;	/* image data */
}

static int
xcf_channel_size(xcf_write_ctx *xc, int name_size)
{
    return 17 + name_size +			/* header and name */
        8 +					/* channel props */
        4 + xc->n_levels * 16 +			/* channel tile hiearchy */
        12 + xc->n_tiles * 4;			/* tile offsets */
}

static int
xcf_write_header(xcf_write_ctx *xc, xcf_device *pdev)
{
    int code = 0;
    const char *layer_name = "Background";
    int level;
    int tile_offset;
    int tile_idx;
    int n_extra_channels = xc->n_extra_channels;
    int bytes_pp = xc->base_bytes_pp + n_extra_channels;
    int channel_idx;

    xcf_write(xc, (const byte *)"gimp xcf file", 14);
    xcf_write_32(xc, xc->width);
    xcf_write_32(xc, xc->height);
    xcf_write_32(xc, 0);

    xcf_write_image_props(xc);

    /* layer offsets */
    xcf_write_32(xc, xc->offset + 12 + 4 * n_extra_channels);
    xcf_write_32(xc, 0);

    /* channel offsets */
    tile_offset = xc->offset + 4 + 4 * n_extra_channels +
        xcf_base_size(xc, layer_name);
    for (channel_idx = 0; channel_idx < n_extra_channels; channel_idx++) {
        const gs_param_string *separation_name =
            pdev->separation_names.names[channel_idx];
        dmlprintf1(pdev->memory, "tile offset: %d\n", tile_offset);
        xcf_write_32(xc, tile_offset);
        tile_offset += xcf_channel_size(xc, separation_name->size);
    }
    xcf_write_32(xc, 0);

    /* layer */
    xcf_write_32(xc, xc->width);
    xcf_write_32(xc, xc->height);
    xcf_write_32(xc, 0);
    xcf_write_32(xc, strlen(layer_name) + 1);
    xcf_write(xc, (const byte *)layer_name, strlen(layer_name) + 1);

    /* layer props */
    xcf_write_32(xc, 0);
    xcf_write_32(xc, 0);

    /* layer tile hierarchy */
    xcf_write_32(xc, xc->offset + 8);
    xcf_write_32(xc, 0);

    xcf_write_32(xc, xc->width);
    xcf_write_32(xc, xc->height);
    xcf_write_32(xc, xc->base_bytes_pp);
    xcf_write_32(xc, xc->offset + (1 + xc->n_levels) * 4);
    tile_offset = xc->offset + xc->width * xc->height * bytes_pp +
        xc->n_tiles * 4 + 12;
    for (level = 1; level < xc->n_levels; level++) {
        xcf_write_32(xc, tile_offset);
        tile_offset += 12;
    }
    xcf_write_32(xc, 0);

    /* layer tile offsets */
    xcf_write_32(xc, xc->width);
    xcf_write_32(xc, xc->height);
    tile_offset = xc->offset + (xc->n_tiles + 1) * 4;
    for (tile_idx = 0; tile_idx < xc->n_tiles; tile_idx++) {
        xcf_write_32(xc, tile_offset);
        tile_offset += xcf_tile_sizeof(xc, tile_idx) * bytes_pp;
    }
    xcf_write_32(xc, 0);

    xc->image_data_off = xc->offset;

    return code;
}

static void
xcf_shuffle_to_tile(xcf_write_ctx *xc, byte **tile_data, const byte *row,
                    int y)
{
    int tile_j = y / TILE_HEIGHT;
    int yrem = y % TILE_HEIGHT;
    int tile_i;
    int base_bytes_pp = xc->base_bytes_pp;
    int n_extra_channels = xc->n_extra_channels;
    int row_idx = 0;

    for (tile_i = 0; tile_i < xc->n_tiles_x; tile_i++) {
        int x;
        int tile_width = min(TILE_WIDTH, xc->width - tile_i * TILE_WIDTH);
        int tile_height = min(TILE_HEIGHT, xc->height - tile_j * TILE_HEIGHT);
        byte *base_ptr = tile_data[tile_i] +
            yrem * tile_width * base_bytes_pp;
        int extra_stride = tile_width * tile_height;
        byte *extra_ptr = tile_data[tile_i] + extra_stride * base_bytes_pp +
            yrem * tile_width;

        int base_idx = 0;

        for (x = 0; x < tile_width; x++) {
            int plane_idx;
            for (plane_idx = 0; plane_idx < base_bytes_pp; plane_idx++)
                base_ptr[base_idx++] = row[row_idx++];
            for (plane_idx = 0; plane_idx < n_extra_channels; plane_idx++)
                extra_ptr[plane_idx * extra_stride + x] = 255 ^ row[row_idx++];
        }
    }
}

static void
xcf_icc_to_tile(gx_device_printer *pdev, xcf_write_ctx *xc, byte **tile_data, const byte *row,
                    int y, gcmmhlink_t link)
{
    int tile_j = y / TILE_HEIGHT;
    int yrem = y % TILE_HEIGHT;
    int tile_i;
    int base_bytes_pp = xc->base_bytes_pp;
    int n_extra_channels = xc->n_extra_channels;
    int row_idx = 0;

    for (tile_i = 0; tile_i < xc->n_tiles_x; tile_i++) {
        int x;
        int tile_width = min(TILE_WIDTH, xc->width - tile_i * TILE_WIDTH);
        int tile_height = min(TILE_HEIGHT, xc->height - tile_j * TILE_HEIGHT);
        byte *base_ptr = tile_data[tile_i] +
            yrem * tile_width * base_bytes_pp;
        int extra_stride = tile_width * tile_height;
        byte *extra_ptr = tile_data[tile_i] + extra_stride * base_bytes_pp +
            yrem * tile_width;

        int base_idx = 0;

        for (x = 0; x < tile_width; x++) {

            int plane_idx;

                /* This loop could be optimized.  I don't quite
                   understand what is going on in the loop
                   with the 255^row[row_idx++] operation */

            gscms_transform_color((gx_device*) pdev, link, 
                                  (void *) (&(row[row_idx])),
                                   &(base_ptr[base_idx]), 1);

            for (plane_idx = 0; plane_idx < n_extra_channels; plane_idx++)
                extra_ptr[plane_idx * extra_stride + x] = 255 ^ row[row_idx++];
        }
    }
}

static int
xcf_write_image_data(xcf_write_ctx *xc, gx_device_printer *pdev)
{
    int code = 0;
    int raster = gdev_prn_raster(pdev);
    int tile_i, tile_j;
    byte **tile_data;
    byte *line;
    int base_bytes_pp = xc->base_bytes_pp;
    int n_extra_channels = xc->n_extra_channels;
    int bytes_pp = base_bytes_pp + n_extra_channels;
    int chan_idx;
    xcf_device *xdev = (xcf_device *)pdev;
    gcmmhlink_t link = xdev->output_icc_link;

    line = gs_alloc_bytes(pdev->memory, raster, "xcf_write_image_data");
    tile_data = (byte **)gs_alloc_bytes(pdev->memory,
                                        xc->n_tiles_x * sizeof(byte *),
                                        "xcf_write_image_data");
    for (tile_i = 0; tile_i < xc->n_tiles_x; tile_i++) {
        int tile_bytes = xcf_tile_sizeof(xc, tile_i) * bytes_pp;

        tile_data[tile_i] = gs_alloc_bytes(pdev->memory, tile_bytes,
                                           "xcf_write_image_data");
    }
    for (tile_j = 0; tile_j < xc->n_tiles_y; tile_j++) {
        int y0, y1;
        int y;
        byte *row;

        y0 = tile_j * TILE_HEIGHT;
        y1 = min(xc->height, y0 + TILE_HEIGHT);
        for (y = y0; y < y1; y++) {
            code = gdev_prn_get_bits(pdev, y, line, &row);
            if (link == NULL)
                xcf_shuffle_to_tile(xc, tile_data, row, y);
            else
                xcf_icc_to_tile(pdev, xc, tile_data, row, y, link);
        }
        for (tile_i = 0; tile_i < xc->n_tiles_x; tile_i++) {
            int tile_idx = tile_j * xc->n_tiles_x + tile_i;
            int tile_size = xcf_tile_sizeof(xc, tile_idx);
            int base_size = tile_size * base_bytes_pp;

            xcf_write(xc, tile_data[tile_i], base_size);
            for (chan_idx = 0; chan_idx < n_extra_channels; chan_idx++) {
                xcf_write(xc, tile_data[tile_i] + base_size +
                          tile_size * chan_idx, tile_size);
            }
        }
    }

    for (tile_i = 0; tile_i < xc->n_tiles_x; tile_i++) {
        gs_free_object(pdev->memory, tile_data[tile_i],
                "xcf_write_image_data");
    }
    gs_free_object(pdev->memory, tile_data, "xcf_write_image_data");
    gs_free_object(pdev->memory, line, "xcf_write_image_data");
    return code;
}

static int
xcf_write_fake_hierarchy(xcf_write_ctx *xc)
{
    int widthf = xc->width, heightf = xc->height;
    int i;

    for (i = 1; i < xc->n_levels; i++) {
        widthf >>= 1;
        heightf >>= 1;
        xcf_write_32(xc, widthf);
        xcf_write_32(xc, heightf);
        xcf_write_32(xc, 0);
    }
    return 0;
}

static int
xcf_write_footer(xcf_write_ctx *xc, xcf_device *pdev)
{
    int code = 0;
    int base_bytes_pp = xc->base_bytes_pp;
    int n_extra_channels = xc->n_extra_channels;
    int bytes_pp = base_bytes_pp + n_extra_channels;
    int chan_idx;

    xcf_write_fake_hierarchy(xc);

    for (chan_idx = 0; chan_idx < xc->n_extra_channels; chan_idx++) {
        const gs_param_string *separation_name =
            pdev->separation_names.names[chan_idx];
        byte nullbyte[] = { 0 };
        int level;
        int offset;
        int tile_idx;

        dmlprintf2(pdev->memory, "actual tile offset: %d %d\n", xc->offset, (int)ARCH_SIZEOF_COLOR_INDEX);
        xcf_write_32(xc, xc->width);
        xcf_write_32(xc, xc->height);
        xcf_write_32(xc, separation_name->size + 1);
        xcf_write(xc, separation_name->data, separation_name->size);
        xcf_write(xc, nullbyte, 1);

        /* channel props */
        xcf_write_32(xc, 0);
        xcf_write_32(xc, 0);

        /* channel tile hierarchy */
        xcf_write_32(xc, xc->offset + 4);

        xcf_write_32(xc, xc->width);
        xcf_write_32(xc, xc->height);
        xcf_write_32(xc, 1);
        xcf_write_32(xc, xc->offset + xc->n_levels * 16 - 8);
        offset = xc->offset + xc->n_levels * 4;
        for (level = 1; level < xc->n_levels; level++) {
            xcf_write_32(xc, offset);
            offset += 12;
        }
        xcf_write_32(xc, 0);
        xcf_write_fake_hierarchy(xc);

        /* channel tile offsets */
        xcf_write_32(xc, xc->width);
        xcf_write_32(xc, xc->height);
        offset = xc->image_data_off;
        for (tile_idx = 0; tile_idx < xc->n_tiles; tile_idx++) {
            int tile_size = xcf_tile_sizeof(xc, tile_idx);

            xcf_write_32(xc, offset + (base_bytes_pp + chan_idx) * tile_size);
            offset += bytes_pp * tile_size;
        }
        xcf_write_32(xc, 0);

    }
    return code;
}

static int
xcf_print_page(gx_device_printer *pdev, FILE *file)
{
    xcf_write_ctx xc;

    xc.f = file;
    xc.offset = 0;

    xcf_setup_tiles(&xc, (xcf_device *)pdev);
    xcf_write_header(&xc, (xcf_device *)pdev);
    xcf_write_image_data(&xc, pdev);
    xcf_write_footer(&xc, (xcf_device *)pdev);

    return 0;
}
