xref: /plan9/sys/src/cmd/gs/src/gsequivc.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 2004 Artifex Software Inc., artofcode LLC.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /*$Id: gsequivc.c,v 1.6 2005/07/08 22:04:31 dan Exp $ */
18 /* Routines for determining equivalent color for spot colors */
19 
20 #include "math_.h"
21 #include "gdevprn.h"
22 #include "gsparam.h"
23 #include "gstypes.h"
24 #include "gxdcconv.h"
25 #include "gdevdevn.h"
26 #include "gsequivc.h"
27 #include "gzstate.h"
28 #include "gsstate.h"
29 #include "gscspace.h"
30 #include "gxcspace.h"
31 
32 /*
33  * These routines are part of the logic for determining an equivalent
34  * process color model color for a spot color.  The definition of a
35  * Separation or DeviceN color space include a tint transform function.
36  * This tint transform function is used if the specified spot or separation
37  * colorant is not present.  We want to be able to display the spot colors
38  * on our RGB or CMYK display.  We are using the tint transform function
39  * to determine a CMYK equivalent to the spot color.  Current only CMYK
40  * equivalent colors are supported.  This is because the CMYK is the only
41  * standard subtractive color space.
42  *
43  * This process consists of the following steps:
44  *
45  * 1.  Whenever new spot colors are found, set status flags indicating
46  * that we have one or more spot colors for which we need to determine an
47  * equivalent color.  New spot colors can either be explicitly specified by
48  * the SeparationColorNames device parameter or they may be detected by the
49  * device's get_color_comp_index routine.
50  *
51  * 2.  Whenever a Separation or DeviceN color space is installed, the
52  * update_spot_equivalent_colors device proc is called.  This allows the
53  * device to check if the color space contains a spot color for which the
54  * device does not know the equivalent CMYK color.  The routine
55  * update_spot_equivalent_cmyk_colors is provided for this task (and the
56  * next item).
57  *
58  * 3.  For each spot color for which an equivalent color is not known, we
59  * do the following:
60  *   a.  Create a copy of the color space and change the copy so that it
61  *       uses its alternate colorspace.
62  *   b.  Create a copy of the current imager state and modify its color
63  *       mapping (cmap) procs to use a special set of 'capture' procs.
64  *   c.  Based upon the type of color space (Separation or DeviceN) create
65  *       a 'color' which consists of the desired spot color set to 100% and
66  *       and other components set to zero.
67  *   d.  Call the remap_color routine using our modified color space and
68  *       state structures.  Since we have forced the use of the alternate
69  *       color space, we will eventually execute one of the 'capture' color
70  *       space mapping procs.  This will give us either a gray, RGB, or
71  *       CMYK color which is equivalent to the original spot color.  If the
72  *       color is gray or RGB we convert it to CMYK.
73  *   e.  Save the equivalent CMYK color in the device structure.
74  *
75  * 4.  When a page is to be displayed or a file created, the saved equivalent
76  * color is used as desired.  It can be written into the output file.  It
77  * may also be used to provide color values which can be combined with the
78  * process color model components for a pixel, to correctly display spot
79  * colors on a monitor.  (Note:  Overprinting effects with spot colors are
80  * not correctly displayed on an RGB monitor if the device simply uses an RGB
81  * process color model.  Instead it is necessary to use a subtractive process
82  * color model and save both process color and spot color data and then
83  * convert the overall result to RGB for display.)
84  *
85  * For this process to work properly, the following changes need to made to
86  * the device.
87  *
88  * 1.  The device source module needs to include gsequivc.c for a definition
89  *     of the relevant structures and routines.  An equivalent_cmyk_color_params
90  *     structure needs to be added to the device's structure definition and
91  *     it needs to be initialized.  For examples see the definition of the
92  *     psd_device structure in src/gdevpsd.c and the definitions of the
93  *     gs_psdrgb_device and gs_psdcmyk_devices devices in the same module.
94  * 2.  Logic needs to be added to the device's get_color_comp_index and
95  *     put_param routines to check if any separations have been added to the
96  *     device.  For examples see code fragments in psd_get_color_comp_index and
97  *     psd_put_params in src/gdevpsd.c.
98  * 3.  The device needs to have its own version of the
99  *     update_spot_equivalent_colors routine.  For examples see the definition
100  *     of the device_procs macro and the psd_update_spot_equivalent_colors
101  *     routine in src/gdevpsd.c.
102  * 4.  The device then uses the saved equivalent color values when its output
103  *     is created.  For example see the psd_write_header routine in
104  *     src/gdevpsd.c.
105  */
106 
107 /* Function protypes */
108 private void capture_spot_equivalent_cmyk_colors(gx_device * pdev,
109 		    const gs_state * pgs, const gs_client_color * pcc,
110 		    const gs_color_space * pcs, int sep_num,
111 		    equivalent_cmyk_color_params * pparams);
112 
113 #define compare_color_names(name, name_size, str, str_size) \
114     (name_size == str_size && \
115 	(strncmp((const char *)name, (const char *)str, name_size) == 0))
116 
117 private void
update_Separation_spot_equivalent_cmyk_colors(gx_device * pdev,const gs_state * pgs,const gs_color_space * pcs,gs_devn_params * pdevn_params,equivalent_cmyk_color_params * pparams)118 update_Separation_spot_equivalent_cmyk_colors(gx_device * pdev,
119 		    const gs_state * pgs, const gs_color_space * pcs,
120 		    gs_devn_params * pdevn_params,
121 		    equivalent_cmyk_color_params * pparams)
122 {
123     int i;
124 
125     /*
126      * Check if the color space's separation name matches any of the
127      * separations for which we need an equivalent CMYK color.
128      */
129     for (i = 0; i < pdevn_params->separations.num_separations; i++) {
130 	if (pparams->color[i].color_info_valid == false) {
131 	    const devn_separation_name * dev_sep_name =
132 			    &(pdevn_params->separations.names[i]);
133 	    unsigned int cs_sep_name_size;
134 	    unsigned char * pcs_sep_name;
135 
136 	    pcs->params.separation.get_colorname_string
137 		(pdev->memory, pcs->params.separation.sep_name, &pcs_sep_name,
138 		 &cs_sep_name_size);
139 	    if (compare_color_names(dev_sep_name->data, dev_sep_name->size,
140 			    pcs_sep_name, cs_sep_name_size)) {
141 		gs_color_space temp_cs = *pcs;
142 		gs_client_color client_color;
143 
144 		/*
145 	         * Create a copy of the color space and then modify it
146 		 * to force the use of the alternate color space.
147 	         */
148 		temp_cs.params.separation.use_alt_cspace = true;
149 		client_color.paint.values[0] = 1.0;
150 		capture_spot_equivalent_cmyk_colors(pdev, pgs, &client_color,
151 					    &temp_cs, i, pparams);
152 		break;
153 	    }
154 	}
155     }
156 }
157 
158 private void
update_DeviceN_spot_equivalent_cmyk_colors(gx_device * pdev,const gs_state * pgs,const gs_color_space * pcs,gs_devn_params * pdevn_params,equivalent_cmyk_color_params * pparams)159 update_DeviceN_spot_equivalent_cmyk_colors(gx_device * pdev,
160 		    const gs_state * pgs, const gs_color_space * pcs,
161 		    gs_devn_params * pdevn_params,
162 		    equivalent_cmyk_color_params * pparams)
163 {
164     int i;
165     unsigned int j;
166     unsigned int cs_sep_name_size;
167     unsigned char * pcs_sep_name;
168 
169     /*
170      * Check if the color space contains components named 'None'.  If so then
171      * our capture logic does not work properly.  When present, the 'None'
172      * components contain alternate color information.  However this info is
173      * specified as part of the 'color' and not part of the color space.  Thus
174      * we do not have this data when this routine is called.  See the
175      * description of DeviceN color spaces in section 4.5 of the PDF spec.
176      * In this situation we exit rather than produce invalid values.
177      */
178      for (j = 0; j < pcs->params.device_n.num_components; j++) {
179 	pcs->params.device_n.get_colorname_string
180 	    (pdev->memory, pcs->params.device_n.names[j],
181 	     &pcs_sep_name, &cs_sep_name_size);
182 	if (compare_color_names("None", 4, pcs_sep_name, cs_sep_name_size))
183 	    return;
184     }
185 
186     /*
187      * Check if the color space's separation names matches any of the
188      * separations for which we need an equivalent CMYK color.
189      */
190     for (i = 0; i < pdevn_params->separations.num_separations; i++) {
191 	if (pparams->color[i].color_info_valid == false) {
192 	    const devn_separation_name * dev_sep_name =
193 			    &(pdevn_params->separations.names[i]);
194 
195 	    for (j = 0; j < pcs->params.device_n.num_components; j++) {
196 	        pcs->params.device_n.get_colorname_string
197 		    (pdev->memory, pcs->params.device_n.names[j], &pcs_sep_name,
198 		     &cs_sep_name_size);
199 	        if (compare_color_names(dev_sep_name->data, dev_sep_name->size,
200 			    pcs_sep_name, cs_sep_name_size)) {
201 		    gs_color_space temp_cs = *pcs;
202 		    gs_client_color client_color;
203 
204 		    /*
205 	             * Create a copy of the color space and then modify it
206 		     * to force the use of the alternate color space.
207 	             */
208 		    memset(&client_color, 0, sizeof(client_color));
209 		    temp_cs.params.device_n.use_alt_cspace = true;
210 		    client_color.paint.values[j] = 1.0;
211 		    capture_spot_equivalent_cmyk_colors(pdev, pgs, &client_color,
212 					    &temp_cs, i, pparams);
213 		    break;
214 	        }
215 	    }
216 	}
217     }
218 }
219 
check_all_colors_known(int num_spot,equivalent_cmyk_color_params * pparams)220 private bool check_all_colors_known(int num_spot,
221 		equivalent_cmyk_color_params * pparams)
222 {
223     for (num_spot--; num_spot >= 0; num_spot--)
224 	if (pparams->color[num_spot].color_info_valid == false)
225 	    return false;
226     return true;
227 }
228 
229 /* If possible, update the equivalent CMYK color for a spot color */
230 void
update_spot_equivalent_cmyk_colors(gx_device * pdev,const gs_state * pgs,gs_devn_params * pdevn_params,equivalent_cmyk_color_params * pparams)231 update_spot_equivalent_cmyk_colors(gx_device * pdev, const gs_state * pgs,
232     gs_devn_params * pdevn_params, equivalent_cmyk_color_params * pparams)
233 {
234     const gs_color_space * pcs;
235 
236     /* If all of the color_info is valid then there is nothing to do. */
237     if (pparams->all_color_info_valid)
238 	return;
239 
240     /* Verify that we really have some separations. */
241     if (pdevn_params->separations.num_separations == 0) {
242 	pparams->all_color_info_valid = true;
243 	return;
244     }
245     /*
246      * Verify that the given color space is a Separation or a DeviceN color
247      * space.  If so then when check if the color space contains a separation
248      * color for which we need a CMYK equivalent.
249      */
250     pcs = pgs->color_space;
251     if (pcs != NULL) {
252 	if (pcs->type->index == gs_color_space_index_Separation) {
253 	    update_Separation_spot_equivalent_cmyk_colors(pdev, pgs, pcs,
254 						pdevn_params, pparams);
255 	    pparams->all_color_info_valid = check_all_colors_known
256 		    (pdevn_params->separations.num_separations, pparams);
257 	}
258 	else if (pcs->type->index == gs_color_space_index_DeviceN) {
259 	    update_DeviceN_spot_equivalent_cmyk_colors(pdev, pgs, pcs,
260 						pdevn_params, pparams);
261 	    pparams->all_color_info_valid = check_all_colors_known
262 		    (pdevn_params->separations.num_separations, pparams);
263 	}
264     }
265 }
266 
267 private void
save_spot_equivalent_cmyk_color(int sep_num,equivalent_cmyk_color_params * pparams,frac cmyk[4])268 save_spot_equivalent_cmyk_color(int sep_num,
269 		equivalent_cmyk_color_params * pparams, frac cmyk[4])
270 {
271     pparams->color[sep_num].c = cmyk[0];
272     pparams->color[sep_num].m = cmyk[1];
273     pparams->color[sep_num].y = cmyk[2];
274     pparams->color[sep_num].k = cmyk[3];
275     pparams->color[sep_num].color_info_valid = true;
276 }
277 
278 /*
279  * A structure definition for a device for capturing equivalent colors
280  */
281 typedef struct color_capture_device_s {
282     gx_device_common;
283     gx_prn_device_common;
284     /*        ... device-specific parameters ... */
285     /* The following values are needed by the cmap procs for saving data */
286     int sep_num;	/* Number of the separation being captured */
287     /* Pointer to original device's equivalent CMYK colors */
288     equivalent_cmyk_color_params * pequiv_cmyk_colors;
289 } color_capture_device;
290 
291 /*
292  * Replacement routines for the cmap procs.  These routines will capture the
293  * equivalent color.
294  */
295 private cmap_proc_gray(cmap_gray_capture_cmyk_color);
296 private cmap_proc_rgb(cmap_rgb_capture_cmyk_color);
297 private cmap_proc_cmyk(cmap_cmyk_capture_cmyk_color);
298 private cmap_proc_rgb_alpha(cmap_rgb_alpha_capture_cmyk_color);
299 private cmap_proc_separation(cmap_separation_capture_cmyk_color);
300 private cmap_proc_devicen(cmap_devicen_capture_cmyk_color);
301 
302 private const gx_color_map_procs cmap_capture_cmyk_color = {
303     cmap_gray_capture_cmyk_color,
304     cmap_rgb_capture_cmyk_color,
305     cmap_cmyk_capture_cmyk_color,
306     cmap_rgb_alpha_capture_cmyk_color,
307     cmap_separation_capture_cmyk_color,
308     cmap_devicen_capture_cmyk_color
309 };
310 
311 private void
cmap_gray_capture_cmyk_color(frac gray,gx_device_color * pdc,const gs_imager_state * pis,gx_device * dev,gs_color_select_t select)312 cmap_gray_capture_cmyk_color(frac gray, gx_device_color * pdc,
313 	const gs_imager_state * pis, gx_device * dev, gs_color_select_t select)
314 {
315     equivalent_cmyk_color_params * pparams =
316 	    ((color_capture_device *)dev)->pequiv_cmyk_colors;
317     int sep_num = ((color_capture_device *)dev)->sep_num;
318     frac cmyk[4];
319 
320     cmyk[0] = cmyk[1] = cmyk[2] = frac_0;
321     cmyk[3] = frac_1 - gray;
322     save_spot_equivalent_cmyk_color(sep_num, pparams, cmyk);
323 }
324 
325 private void
cmap_rgb_capture_cmyk_color(frac r,frac g,frac b,gx_device_color * pdc,const gs_imager_state * pis,gx_device * dev,gs_color_select_t select)326 cmap_rgb_capture_cmyk_color(frac r, frac g, frac b, gx_device_color * pdc,
327      const gs_imager_state * pis, gx_device * dev, gs_color_select_t select)
328 {
329     equivalent_cmyk_color_params * pparams =
330 	    ((color_capture_device *)dev)->pequiv_cmyk_colors;
331     int sep_num = ((color_capture_device *)dev)->sep_num;
332     frac cmyk[4];
333 
334     color_rgb_to_cmyk(r, g, b, pis, cmyk);
335     save_spot_equivalent_cmyk_color(sep_num, pparams, cmyk);
336 }
337 
338 private void
cmap_cmyk_capture_cmyk_color(frac c,frac m,frac y,frac k,gx_device_color * pdc,const gs_imager_state * pis,gx_device * dev,gs_color_select_t select)339 cmap_cmyk_capture_cmyk_color(frac c, frac m, frac y, frac k, gx_device_color * pdc,
340      const gs_imager_state * pis, gx_device * dev, gs_color_select_t select)
341 {
342     equivalent_cmyk_color_params * pparams =
343 	    ((color_capture_device *)dev)->pequiv_cmyk_colors;
344     int sep_num = ((color_capture_device *)dev)->sep_num;
345     frac cmyk[4];
346 
347     cmyk[0] = c;
348     cmyk[1] = m;
349     cmyk[2] = y;
350     cmyk[3] = k;
351     save_spot_equivalent_cmyk_color(sep_num, pparams, cmyk);
352 }
353 
354 private void
cmap_rgb_alpha_capture_cmyk_color(frac r,frac g,frac b,frac alpha,gx_device_color * pdc,const gs_imager_state * pis,gx_device * dev,gs_color_select_t select)355 cmap_rgb_alpha_capture_cmyk_color(frac r, frac g, frac b, frac alpha,
356 	gx_device_color * pdc, const gs_imager_state * pis, gx_device * dev,
357 			 gs_color_select_t select)
358 {
359     cmap_rgb_capture_cmyk_color(r, g, b, pdc, pis, dev, select);
360 }
361 
362 private void
cmap_separation_capture_cmyk_color(frac all,gx_device_color * pdc,const gs_imager_state * pis,gx_device * dev,gs_color_select_t select)363 cmap_separation_capture_cmyk_color(frac all, gx_device_color * pdc,
364      const gs_imager_state * pis, gx_device * dev, gs_color_select_t select)
365 {
366     dprintf("cmap_separation_capture_cmyk_color - this routine should not be executed\n");
367 }
368 
369 private void
cmap_devicen_capture_cmyk_color(const frac * pcc,gx_device_color * pdc,const gs_imager_state * pis,gx_device * dev,gs_color_select_t select)370 cmap_devicen_capture_cmyk_color(const frac * pcc, gx_device_color * pdc,
371      const gs_imager_state * pis, gx_device * dev, gs_color_select_t select)
372 {
373     dprintf("cmap_devicen_capture_cmyk_color - this routine should not be executed\n");
374 }
375 
376 /*
377  * Note:  The color space (pcs) has already been modified to use the
378  * alternate color space.
379  */
380 private void
capture_spot_equivalent_cmyk_colors(gx_device * pdev,const gs_state * pgs,const gs_client_color * pcc,const gs_color_space * pcs,int sep_num,equivalent_cmyk_color_params * pparams)381 capture_spot_equivalent_cmyk_colors(gx_device * pdev, const gs_state * pgs,
382     const gs_client_color * pcc, const gs_color_space * pcs,
383     int sep_num, equivalent_cmyk_color_params * pparams)
384 {
385     gs_imager_state temp_state = *((const gs_imager_state *)pgs);
386     color_capture_device temp_device = { 0 };
387     gx_device_color dev_color;
388 
389     /*
390      * Create a temp device.  The primary purpose of this device is pass the
391      * separation number and a pointer to the original device's equivalent
392      * color parameters.  Since we only using this device for a very specific
393      * purpose, we only set up the color_info structure and and our data.
394      */
395     temp_device.color_info = pdev->color_info;
396     temp_device.sep_num = sep_num;
397     temp_device.pequiv_cmyk_colors = pparams;
398     /*
399      * Create a temp copy of the imager state.  We do this so that we
400      * can modify the color space mapping (cmap) procs.  We use our
401      * replacment procs to capture the color.  The installation of a
402      * Separation or DeviceN color space also sets a use_alt_cspace flag
403      * in the state.  We also need to set this to use the alternate space.
404      */
405     temp_state.cmap_procs = &cmap_capture_cmyk_color;
406     temp_state.color_component_map.use_alt_cspace = true;
407 
408     /* Now capture the color */
409     pcs->type->remap_color (pcc, pcs, &dev_color, &temp_state,
410 		    (gx_device *)&temp_device, gs_color_select_texture);
411 }
412