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 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 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 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 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 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 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 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 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 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 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 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 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