1 /* Copyright (C) 2006-2007 Artifex Software, Inc.  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: gdevwts.c 10078 2009-09-21 19:38:30Z ray $ */
18 /* ALPHA: Sample Device that provides WTS screening and IMDI color management */
19 /* TODO: this should be configurable */
20 #define LINK_ICC_NAME	"link.icc"
21 
22 #include "errno_.h"
23 #include "string_.h"
24 
25 #include "gdevprn.h"
26 #include "gdevmem.h"
27 #include "gsdevice.h"
28 #include "gxfrac.h"
29 #include "gsht.h"
30 #include "gxwts.h"
31 #include "gswts.h"
32 #include "gxgetbit.h"
33 
34 #include "icc.h"
35 #include "imdi.h"
36 
37 /* Memory arg is included in ghostpcl branch but not main branch. */
38 #define GS_NOTE_ERROR(m, e) gs_note_error(e)
39 
40 #ifndef X_DPI
41 #  define X_DPI 72
42 #endif
43 #ifndef Y_DPI
44 #  define Y_DPI 72
45 #endif
46 
47 typedef struct {
48     wts_screen_t *wts;
49     byte *cell;
50     int width_padded;
51 } wts_cooked_halftone;
52 
53 typedef struct gx_device_wts_s {
54     gx_device_common;
55     gx_prn_device_common;
56     wts_cooked_halftone wcooked[4];
57 } gx_device_wts;
58 
59 static dev_proc_print_page(wtscmyk_print_page);
60 
61 /* 8-bit-per-plane separated CMYK color. */
62 
63 static const gx_device_procs wtscmyk_procs = {
64     gdev_prn_open, NULL, NULL, gdev_prn_output_page, gdev_prn_close,
65     NULL, cmyk_8bit_map_color_cmyk, NULL, NULL, NULL, NULL, NULL, NULL,
66     gdev_prn_get_params, gdev_prn_put_params,
67     cmyk_8bit_map_cmyk_color, NULL, NULL, NULL, gx_page_device_get_page_device
68 };
69 
70 const gx_device_wts gs_wtscmyk_device = {
71     prn_device_body(gx_device_wts, wtscmyk_procs, "wtscmyk",
72 			DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
73 			X_DPI, Y_DPI,
74 			0, 0, 0, 0,	/* Margins */
75 			4, 32, 255, 255, 256, 256, wtscmyk_print_page)
76 };
77 
78 /* RGB with imdi conversion to CMYK and wts halftoning */
79 
80 static dev_proc_open_device(wtsimdi_open_device);
81 static dev_proc_close_device(wtsimdi_close_device);
82 static dev_proc_print_page(wtsimdi_print_page);
83 static dev_proc_create_buf_device(wtsimdi_create_buf_device);
84 static dev_proc_get_bits(wtsimdi_get_bits);
85 static dev_proc_get_bits_rectangle(wtsimdi_contone_get_bits_rectangle);
86 static dev_proc_get_bits_rectangle(wtsimdi_halftoned_get_bits_rectangle);
87 
88 typedef struct cached_color_s {
89     gx_color_index color_index;
90     byte cmyk[4];
91 } cached_color;
92 
93 #define COLOR_CACHE_SIZE 4096
94 /*
95  * Hash function should preserve low bits (for images) and MUST map white and
96  * black to different slots
97  */
98 #if COLOR_CACHE_SIZE == 256
99 #  define COLOR_TO_CACHE_INDEX(color) (((color + 0x000001) ^ (color >> 8) ^ (color >> 16)) & 0xff)
100 #elif COLOR_CACHE_SIZE == 4096
101 #  define COLOR_TO_CACHE_INDEX(color) ((color ^ (color>>4) ^ (color>>8)) & 0xfff)
102 #elif COLOR_CACHE_SIZE == 8192
103 #  define COLOR_TO_CACHE_INDEX(color) (((color + 0x010204) ^ (((color << 24) | color) >> 11)) & 0x1fff)
104 #elif COLOR_CACHE_SIZE == 16384
105 #  define COLOR_TO_CACHE_INDEX(color) (((color + 0x010204) ^ (((color << 24) | color) >> 10)) & 0x3fff)
106 #elif COLOR_CACHE_SIZE == 32768
107 #  define COLOR_TO_CACHE_INDEX(color) (((color + 0x010204) ^ (((color << 24) | color) >> 9)) & 0x7fff)
108 #elif COLOR_CACHE_SIZE == 65536
109 #  define COLOR_TO_CACHE_INDEX(color) (((color + 0x010204) ^ (((color << 24) | color) >> 8)) & 0xffff)
110 #else
111 #  define COLOR_TO_CACHE_INDEX(color) 0
112 #endif
113 
114 typedef struct gx_device_wtsimdi_s {
115     gx_device_common;
116     gx_prn_device_common;
117     wts_cooked_halftone wcooked[4];
118 
119     icmFile *fp;
120     icc *icco;
121     icmLuBase *luo;
122     imdi *mdo;
123     cached_color *color_cache;
124     cached_color current_color;
125 #ifdef DEBUG
126     long color_cache_hit, color_cache_collision, cache_fill_empty, color_is_current;
127 #endif
128 } gx_device_wtsimdi;
129 
130 static const gx_device_procs wtsimdi_procs =
131 {
132     wtsimdi_open_device, NULL, NULL, gdev_prn_output_page, wtsimdi_close_device,
133     gx_default_rgb_map_rgb_color, gx_default_rgb_map_color_rgb,
134     NULL, NULL, NULL, NULL, NULL, wtsimdi_get_bits,
135     gdev_prn_get_params, gdev_prn_put_params,
136     NULL, NULL, NULL, NULL, gx_page_device_get_page_device
137 };
138 
139 const gx_device_wtsimdi gs_wtsimdi_device = {
140     prn_device_body(gx_device_wtsimdi, wtsimdi_procs, "wtsimdi",
141 			DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
142 			X_DPI, Y_DPI,
143 			0, 0, 0, 0,	/* Margins */
144 			3, 24, 255, 255, 256, 256, wtsimdi_print_page)
145 };
146 
147 #if DUMMY_WTS_HALFTONE_LINE
148 static void
wts_halftone_line(void ** wts,int y,int width,int n_planes,long band_offset_x,long band_offset_y,byte ** dst,const byte * src)149 wts_halftone_line(void **wts, int y, int width, int n_planes,
150 		  long band_offset_x, long band_offset_y,
151 		  byte **dst, const byte *src)
152 {
153     int x;
154     int plane_ix;
155 
156     for (plane_ix = 0; plane_ix < n_planes; plane_ix++) {
157 	byte *dline = dst[plane_ix];
158 	for (x = 0; x < width; x += 8) {
159 	    byte b = 0;
160 	    if (src[(x + 0) * n_planes + plane_ix] > 0x20) b |= 0x80;
161 	    if (src[(x + 1) * n_planes + plane_ix] > 0x40) b |= 0x40;
162 	    if (src[(x + 2) * n_planes + plane_ix] > 0x60) b |= 0x20;
163 	    if (src[(x + 3) * n_planes + plane_ix] > 0x80) b |= 0x10;
164 	    if (src[(x + 4) * n_planes + plane_ix] > 0xa0) b |= 0x08;
165 	    if (src[(x + 5) * n_planes + plane_ix] > 0xc0) b |= 0x04;
166 	    if (src[(x + 6) * n_planes + plane_ix] > 0xe0) b |= 0x02;
167 	    if (src[(x + 7) * n_planes + plane_ix] > 0xfe) b |= 0x01;
168 	    dline[x >> 3] = b;
169 	}
170     }
171 }
172 #endif
173 
174 static void
wts_halftone_line_16(wts_cooked_halftone * wch,int y,int width,int n_planes,long band_offset_x,long band_offset_y,byte ** dst,const byte * src)175 wts_halftone_line_16(wts_cooked_halftone *wch, int y, int width, int n_planes,
176 		     long band_offset_x, long band_offset_y,
177 		     byte **dst, const byte *src)
178 {
179     int x;
180     int plane_ix;
181     wts_screen_sample_t *samples;
182 
183     for (plane_ix = 0; plane_ix < n_planes; plane_ix++) {
184 	wts_screen_t *w = wch[plane_ix].wts;
185 	byte *dline = dst[plane_ix];
186 	int imax;
187 
188 	for (x = 0; x < width;) {
189 	    int i;
190 	    int n_samples;
191 	    int cx, cy;
192 
193 	    wts_get_samples(w, band_offset_x + x, band_offset_y + y, &cx, &cy, &n_samples);
194 	    samples = w->samples + cy * w->cell_width + cx;
195 
196 	    imax = min(width - x, n_samples);
197 	    for (i = 0; i < imax; i += 8) {
198 		byte b = 0;
199 		int src_ix = x * 4 + plane_ix;
200 #if 0
201 		if (src[src_ix + 0 * 4] > (samples[i + 0] >> 7)) b |= 0x80;
202 		if (src[src_ix + 1 * 4] > (samples[i + 1] >> 7)) b |= 0x40;
203 		if (src[src_ix + 2 * 4] > (samples[i + 2] >> 7)) b |= 0x20;
204 		if (src[src_ix + 3 * 4] > (samples[i + 3] >> 7)) b |= 0x10;
205 		if (src[src_ix + 4 * 4] > (samples[i + 4] >> 7)) b |= 0x08;
206 		if (src[src_ix + 5 * 4] > (samples[i + 5] >> 7)) b |= 0x04;
207 		if (src[src_ix + 6 * 4] > (samples[i + 6] >> 7)) b |= 0x02;
208 		if (src[src_ix + 7 * 4] > (samples[i + 7] >> 7)) b |= 0x01;
209 #else
210 #if 0
211 		b |= (src[src_ix + 0 * 4] > (samples[i + 0] >> 7)) << 7;
212 		b |= (src[src_ix + 1 * 4] > (samples[i + 1] >> 7)) << 6;
213 		b |= (src[src_ix + 2 * 4] > (samples[i + 2] >> 7)) << 5;
214 		b |= (src[src_ix + 3 * 4] > (samples[i + 3] >> 7)) << 4;
215 		b |= (src[src_ix + 4 * 4] > (samples[i + 4] >> 7)) << 3;
216 		b |= (src[src_ix + 5 * 4] > (samples[i + 5] >> 7)) << 2;
217 		b |= (src[src_ix + 6 * 4] > (samples[i + 6] >> 7)) << 1;
218 		b |= (src[src_ix + 7 * 4] > (samples[i + 7] >> 7)) << 0;
219 #else
220 		b = (((unsigned int)(((int)(samples[i + 0] >> 7)) - ((int)src[src_ix + 0 * 4]))) >> 24) & 0x80;
221 		b |= (((unsigned int)(((int)(samples[i + 1] >> 7)) - ((int)src[src_ix + 1 * 4]))) >> 24) & 0x40;
222 		b |= (((unsigned int)(((int)(samples[i + 2] >> 7)) - ((int)src[src_ix + 2 * 4]))) >> 24) & 0x20;
223 		b |= (((unsigned int)(((int)(samples[i + 3] >> 7)) - ((int)src[src_ix + 3 * 4]))) >> 24) & 0x10;
224 		b |= (((unsigned int)(((int)(samples[i + 4] >> 7)) - ((int)src[src_ix + 4 * 4]))) >> 24) & 0x08;
225 		b |= (((unsigned int)(((int)(samples[i + 5] >> 7)) - ((int)src[src_ix + 5 * 4]))) >> 24) & 0x04;
226 		b |= (((unsigned int)(((int)(samples[i + 6] >> 7)) - ((int)src[src_ix + 6 * 4]))) >> 24) & 0x02;
227 		b |= (((unsigned int)(((int)(samples[i + 7] >> 7)) - ((int)src[src_ix + 7 * 4]))) >> 24) & 0x01;
228 #endif
229 #endif
230 		dline[x >> 3] = b;
231 		x += 8;
232 	    }
233 	}
234     }
235 }
236 
237 static void
wts_halftone_line_8(wts_cooked_halftone * wch,int y,int width,int n_planes,long band_offset_x,long band_offset_y,byte * dst,const byte * src)238 wts_halftone_line_8(wts_cooked_halftone *wch, int y, int width, int n_planes,
239 		    long band_offset_x, long band_offset_y,
240 		    byte * dst, const byte * src)
241 {
242     int x;
243     int plane_ix;
244     byte *samples;
245     int halftoned_bytes = (width + 7) >> 3;
246 
247     for (plane_ix = 0; plane_ix < n_planes; plane_ix++) {
248 	wts_screen_t *w = wch[plane_ix].wts;
249 	int width_padded = wch[plane_ix].width_padded;
250 	byte * dline = dst + plane_ix * halftoned_bytes;
251 	/*byte * dline = dst[plane_ix];*/
252 	int imax;
253 
254 	for (x = 0; x < width;) {
255 	    int i;
256 	    int n_samples;
257 	    int cx, cy;
258 
259 	    wts_get_samples(w, band_offset_x + x, band_offset_y + y, &cx, &cy, &n_samples);
260 	    samples = wch[plane_ix].cell + cy * width_padded + cx;
261 
262 	    imax = min(width - x, n_samples);
263 	    for (i = 0; i < imax; i += 8) {
264 		byte b = 0;
265 		int src_ix = x * 4 + plane_ix;
266 		b = (((unsigned int)(((int)(samples[i + 0])) - ((int)src[src_ix + 0 * 4]))) >> 24) & 0x80;
267 		b |= (((unsigned int)(((int)(samples[i + 1])) - ((int)src[src_ix + 1 * 4]))) >> 24) & 0x40;
268 		b |= (((unsigned int)(((int)(samples[i + 2])) - ((int)src[src_ix + 2 * 4]))) >> 24) & 0x20;
269 		b |= (((unsigned int)(((int)(samples[i + 3])) - ((int)src[src_ix + 3 * 4]))) >> 24) & 0x10;
270 		b |= (((unsigned int)(((int)(samples[i + 4])) - ((int)src[src_ix + 4 * 4]))) >> 24) & 0x08;
271 		b |= (((unsigned int)(((int)(samples[i + 5])) - ((int)src[src_ix + 5 * 4]))) >> 24) & 0x04;
272 		b |= (((unsigned int)(((int)(samples[i + 6])) - ((int)src[src_ix + 6 * 4]))) >> 24) & 0x02;
273 		b |= (((unsigned int)(((int)(samples[i + 7])) - ((int)src[src_ix + 7 * 4]))) >> 24) & 0x01;
274 		*dline++ = b;
275 		x += 8;
276 	    }
277 	}
278     }
279 }
280 
281 static int
wts_load_halftone(gs_memory_t * mem,wts_cooked_halftone * wch,const char * fn)282 wts_load_halftone(gs_memory_t *mem, wts_cooked_halftone *wch, const char *fn)
283 {
284     FILE *f = fopen(fn, "rb");
285     int size;
286     byte *buf;
287     wts_screen_t *wts;
288     int width_padded;
289     byte *cooked_cell;
290     int y;
291 
292     if (f == 0) return gs_error_undefinedfilename;
293     fseek(f, 0, 2);
294     size = ftell(f);
295     fseek(f, 0, 0);
296     buf = gs_malloc(mem, size, 1, "wts_load_halftone");
297     if (buf == 0) {
298 	return gs_error_VMerror;
299     }
300     fread(buf, 1, size, f);
301     fclose(f);
302     wts = gs_wts_from_buf(buf, size);
303     gs_free(mem, buf, size, 1, "wts_load_halftone");
304     wch->wts = wts;
305     width_padded = wts->cell_width + 7;
306     wch->width_padded = width_padded;
307     cooked_cell = gs_malloc(mem, width_padded * wts->cell_height, 1,
308 			    "wts_load_halftone");
309     if (cooked_cell == 0) {
310 	return gs_error_VMerror;
311     }
312 
313     wch->cell = cooked_cell;
314     for (y = 0; y < wts->cell_height; y++) {
315 	wts_screen_sample_t *line = &wts->samples[y * wts->cell_width];
316 	byte *dstline = cooked_cell + y * width_padded;
317 	int x;
318 	for (x = 0; x < width_padded; x++) {
319 	    wts_screen_sample_t sample = line[x % wts->cell_width];
320 	    dstline[x] = (254 * (int)sample + 0x7fc0) / 0x8ff0;
321 	}
322     }
323 
324 #if 0
325     /* Note: we'll need to fix the free discipline here when we change it
326        in gswts.c */
327     free(wts->samples);
328     wts->samples = NULL;
329 #endif
330 
331     return 0;
332 }
333 
334 static int
wts_init_halftones(gx_device_wts * wdev,int n_planes)335 wts_init_halftones(gx_device_wts *wdev, int n_planes)
336 {
337     int i;
338     int code;
339 
340     for (i = 0; i < n_planes; i++) {
341 	if (wdev->wcooked[i].wts == 0) {
342 	    char wts_fn[256];
343 
344 	    sprintf(wts_fn, "wts_plane_%d", i);
345             {
346               FILE *f;
347               if ((f=fopen(wts_fn,"r"))) {
348                 fclose(f);
349               } else {
350 	        sprintf(wts_fn, "/usr/local/lib/ghostscript/wts_plane_%d", i);
351               }
352             }
353 	    code = wts_load_halftone(wdev->memory, &wdev->wcooked[i], wts_fn);
354 	    if (code < 0)
355 		return gs_throw1(code, "could not open file '%s'", wts_fn);
356 	}
357     }
358     return 0;
359 }
360 
361 static int
wtscmyk_print_page(gx_device_printer * pdev,FILE * prn_stream)362 wtscmyk_print_page(gx_device_printer *pdev, FILE *prn_stream)
363 {
364     gx_device_wts *wdev = (gx_device_wts *)pdev;
365     int cmyk_bytes = gdev_mem_bytes_per_scan_line((gx_device *)pdev);
366     /* Output bytes have to be padded to 16 bits. */
367     int y;
368     byte *cmyk_line = 0;
369     byte *data;
370     int code = 0;
371     int pbm_bytes;
372     int n_planes = pdev->color_info.num_components;
373     byte *dst = NULL;
374     FILE *ostream[4] = {0};
375     int i;
376 
377     /* Initialize the wts halftones. */
378     code = wts_init_halftones(wdev, n_planes);
379     if (code < 0) goto out;
380 
381     cmyk_line = (byte *)gs_malloc(pdev->memory, cmyk_bytes, 1, "wtscmyk_print_page(in)");
382     if (cmyk_line == 0) {
383 	code = GS_NOTE_ERROR(pdev->memory, gs_error_VMerror);
384 	goto out;
385     }
386     pbm_bytes = (pdev->width + 7) >> 3;
387     dst = gs_malloc(pdev->memory, pbm_bytes * n_planes, 1,
388 		    "wtscmyk_print_page");
389     if (dst == 0) {
390 	code = GS_NOTE_ERROR(pdev->memory, gs_error_VMerror);
391 	goto out;
392     }
393 
394     /* Create additional output streams. */
395     for (i = 0; i < n_planes; i++) {
396 	if (i == 0) {
397 	    ostream[i] = prn_stream;
398 	} else {
399 	    char fn[256];
400 	    char plane_name[4] = "cmyk";
401 	    int fname_len = strlen(pdev->fname);
402 
403 	    if (fname_len >= 5 && fname_len < 256) {
404 		strcpy(fn, pdev->fname);
405 		if (!strcmp(fn + fname_len - 5, "c.pbm"))
406 		    fn[fname_len - 5] = plane_name[i];
407 	    }
408 	    ostream[i] = fopen(fn, "wb");
409 	}
410 	fprintf(ostream[i], "P4\n%d %d\n", pdev->width, pdev->height);
411     }
412 #if 0
413     dprintf2(OPTIONAL_MEM(pdev->memory) "Output file name: %s %d\n", pdev->fname, sizeof(dst));
414 #endif
415 
416     /* For each raster line */
417     for (y = 0; y < pdev->height; y++) {
418 	code = gdev_prn_get_bits(pdev, y, cmyk_line, &data);
419 	if (code < 0)
420 	    break;		/* return the code below after cleanup */
421 	wts_halftone_line_8(wdev->wcooked, y, pdev->width, n_planes,
422 			    wdev->band_offset_x, wdev->band_offset_y, dst, data);
423 	for (i = 0; i < n_planes; i++)
424 	    if (ostream[i])
425 		fwrite(dst + i * pbm_bytes, 1, pbm_bytes, ostream[i]);
426     }
427 out:
428     /* Clean up... */
429     gs_free(pdev->memory, cmyk_line, cmyk_bytes, 1, "wtscmyk_print_page(in)");
430     gs_free(pdev->memory, dst, pbm_bytes, 1, "wtscmyk_print_page");
431     for (i = 1; i < n_planes; i++) {
432 	/* Don't close ostream[0], because gdev_prn_close will. */
433 	if (ostream[i])
434 	    fclose(ostream[i]);
435     }
436     return code;
437 }
438 
439 /* Code that follows is adapted from imdi device */
440 
incurve(void * ctx,int ch,double val)441 static double incurve(void *ctx, int ch, double val)
442 {
443     return val;
444 }
445 
outcurve(void * ctx,int ch,double val)446 static double outcurve(void *ctx, int ch, double val)
447 {
448     return val;
449 }
450 
mdtable(void * ctx,double * outvals,double * invals)451 static void mdtable(void *ctx, double *outvals, double *invals)
452 {
453     icmLuBase *luo = ctx;
454     luo->lookup(luo, outvals, invals);
455 }
456 
457 /*
458  * Open IMDI device.
459  * Load ICC device link profile (to map sRGB to FOGRA CMYK).
460  */
461 
462 static int
wtsimdi_open_device(gx_device * dev)463 wtsimdi_open_device(gx_device *dev)
464 {
465     gx_device_wtsimdi *idev = (gx_device_wtsimdi*)dev;
466     int i, code;
467 
468     icColorSpaceSignature ins, outs;
469     int inn, outn;
470     icmLuAlgType alg;
471 
472     icmFile *fp;
473     icc *icco;
474     icmLuBase *luo;
475     imdi *mdo;
476     char link_icc_name[256];
477 
478     /*
479      * We replace create_buf_device so we can replace copy_alpha
480      * for memory device, but not clist.
481      */
482     idev->printer_procs.buf_procs.create_buf_device =
483 	wtsimdi_create_buf_device;
484     /* Open and read profile */
485 
486     sprintf(link_icc_name, "%s", LINK_ICC_NAME);
487     {
488       FILE *f;
489       if ((f=fopen(link_icc_name,"r"))) {
490         fclose(f);
491       } else {
492         sprintf(link_icc_name, "/usr/local/lib/ghostscript/%s", LINK_ICC_NAME);
493       }
494     }
495 
496     fp = new_icmFileStd_name((char *)link_icc_name, (char *)"rb");
497     if (!fp)
498 	return gs_throw1(-1, "could not open file '%s'", link_icc_name);
499 
500     icco = new_icc();
501     if (!icco)
502 	return gs_throw(-1, "could not create ICC object");
503 
504     code = icco->read(icco, fp, 0);
505     if (code != 0)
506 	return gs_throw1(-1, "could not read ICC profile: %s", icco->err);
507 
508     /* Get conversion object */
509 
510     luo = icco->get_luobj(icco, icmFwd, icPerceptual, icmSigDefaultData, icmLuOrdNorm);
511     if (!luo)
512 	return gs_throw1(-1, "could not create ICC conversion object: %s", icco->err);
513 
514     luo->spaces(luo, &ins, &inn, &outs, &outn, &alg, NULL, NULL, NULL);
515 
516 #ifdef DEBUG
517     dprintf3("%s -> %s [%s]\n",
518 	    icm2str(icmColorSpaceSignature, ins),
519 	    icm2str(icmColorSpaceSignature, outs),
520 	    icm2str(icmLuAlg, alg));
521 #endif
522 
523     if (inn != 3)
524 	return gs_throw1(-1, "profile must have 3 input channels. got %d.", inn);
525     if (outn != 4)
526 	return gs_throw1(-1, "profile must have 4 output channels. got %d.", outn);
527 
528     /* Create IMDI optimized lookup object */
529 
530     mdo = new_imdi(inn, outn, pixint8, 0, pixint8, 0,
531 			 33, incurve, mdtable, outcurve, luo);
532     if (!mdo)
533 	return gs_throw(-1, "new_imdi failed");
534 
535     idev->fp = fp;
536     idev->icco = icco;
537     idev->luo = luo;
538     idev->mdo = mdo;
539 	/* allocate at least 1 for sytems that return NULL if requested count is 0 */
540     idev->color_cache = (cached_color *)gs_malloc(idev->memory, max(COLOR_CACHE_SIZE, 1),
541 				sizeof(cached_color), "wtsimdi_open_device(color_cache)");
542     if (idev->color_cache == NULL) {
543 	return_error(gs_error_VMerror);
544     }
545     for (i=0; i<COLOR_CACHE_SIZE; i++)		/* clear cache to empty */
546 	idev->color_cache[i].color_index = gx_no_color_index;
547     idev->current_color.color_index = gx_no_color_index;
548 
549     /* guarantee the device bands */
550     ((gx_device_printer *)dev)->space_params.banding_type = BandingAlways;
551     return gdev_prn_open(dev);
552 }
553 
554 
555 /*
556  * Close device and clean up ICC structures.
557  */
558 
559 static int
wtsimdi_close_device(gx_device * dev)560 wtsimdi_close_device(gx_device *dev)
561 {
562     gx_device_wtsimdi *idev = (gx_device_wtsimdi*)dev;
563 
564     idev->mdo->done(idev->mdo);
565     idev->luo->del(idev->luo);
566     idev->icco->del(idev->icco);
567     idev->fp->del(idev->fp);
568     gs_free(dev->memory, idev->color_cache, COLOR_CACHE_SIZE,
569 	sizeof(cached_color), "wtsimdi_close_device(color_cache)");
570     return gdev_prn_close(dev);
571 }
572 
573 /* Resolve a color to cmyk values, using the one-element cache. */
574 static int
wtsimdi_resolve_one(gx_device_wtsimdi * idev,gx_color_index color)575 wtsimdi_resolve_one(gx_device_wtsimdi *idev, gx_color_index color)
576 {
577     if (color != idev->current_color.color_index) { /* quick out for same color */
578 	int hash = COLOR_TO_CACHE_INDEX(color);	/* 24 bits down to cache index */
579 
580 	if (idev->color_cache[hash].color_index == color) {
581 	    /* cache hit */
582 #ifdef DEBUG
583 	    idev->color_cache_hit++;
584 #endif
585 	    idev->current_color = idev->color_cache[hash];
586 	} else {
587 	    /* cache collision or empty, fill it */
588 	    int code;
589 	    int r = (color >> 16) & 0xff;
590 	    int g = (color >> 8) & 0xff;
591 	    int b = color & 0xff;
592 	    double rgb[3];
593 	    double cmyk[4];
594 
595 #ifdef DEBUG
596 	    if (idev->color_cache[hash].color_index == gx_no_color_index)
597 		idev->cache_fill_empty++;
598 	    else
599 		idev->color_cache_collision++;
600 #endif
601 	    rgb[0] = r / 255.0;
602 	    rgb[1] = g / 255.0;
603 	    rgb[2] = b / 255.0;
604 	    code = idev->luo->lookup(idev->luo, cmyk, rgb);
605 	    if (code > 1)
606 		return_error(gs_error_unknownerror);
607 	    idev->current_color.color_index = color;
608 	    idev->current_color.cmyk[0] = cmyk[0] * 255 + 0.5;
609 	    idev->current_color.cmyk[1] = cmyk[1] * 255 + 0.5;
610 	    idev->current_color.cmyk[2] = cmyk[2] * 255 + 0.5;
611 	    idev->current_color.cmyk[3] = cmyk[3] * 255 + 0.5;
612 	    idev->color_cache[hash] = idev->current_color;
613 	}
614     }
615 #ifdef DEBUG
616     else
617 	idev->color_is_current++;
618 #endif
619     return 0;
620 }
621 
622 /* Fill a rectangle with a color. */
623 static int
wtsimdi_fill_rectangle(gx_device * dev,int x,int y,int w,int h,gx_color_index color)624 wtsimdi_fill_rectangle(gx_device * dev,
625 			  int x, int y, int w, int h, gx_color_index color)
626 {
627     gx_device_memory * const mdev = (gx_device_memory *)dev;
628     gx_device_wtsimdi * idev = (gx_device_wtsimdi *)
629 	    ((mdev->target) ? (gx_device_wts *)(mdev->target)
630 			    : (gx_device_wts *)dev);
631     wts_cooked_halftone * wch = idev->wcooked;
632     int code, comp_value;
633     int halftoned_bytes = (idev-> width + 7) >> 3;
634     int width_padded, imax;
635     byte * dst, * base;
636     byte * samples;
637     uint raster, plane_ix;
638     int first_byte, end_x;
639 
640     fit_fill(dev, x, y, w, h);
641 
642     base = scan_line_base(mdev, y);
643     raster = mdev->raster;
644 
645     end_x = x + w - 1;
646     first_byte = x >> 3;
647 
648     /*
649      * Check if this is a new color.  To minimize color conversion
650      * time, we keep a couple of values cached in the wtsimdi device.
651      */
652     if ((code = wtsimdi_resolve_one(idev, color)) < 0)
653 	return code;
654 
655     for (; h--; y++) {
656         base = scan_line_base(mdev, y);
657         for (plane_ix = 0; plane_ix < 4; plane_ix++) {
658 	    int nfill = (end_x >> 3) - first_byte;
659 
660 	    width_padded = wch[plane_ix].width_padded;
661 	    dst = base + first_byte + plane_ix * halftoned_bytes;
662 	    comp_value = idev->current_color.cmyk[plane_ix];
663 	    if (comp_value == 0) {
664 		if (nfill == 0) {
665 		    dst[0] &= (0xff << (8 - (x & 7))) |
666 			((1 << (7 - (end_x & 7))) - 1);
667 		} else {
668 		    int i;
669 
670 		    dst[0] &= (0xff << (8 - (x & 7)));
671                     memset(&dst[1], 0, nfill-1);
672 		    dst[nfill] &= ((1 << (7 - (end_x & 7))) - 1);
673 		}
674 	    } else if (comp_value == 0xff) {
675 		if (nfill == 0) {
676 		    dst[0] |= ~((0xff << (8 - (x & 7))) |
677 			((1 << (7 - (end_x & 7))) - 1));
678 		} else {
679 		    int i;
680 
681 		    dst[0] |= ~(0xff << (8 - (x & 7)));
682                     memset(&dst[1], 0xff, nfill-1);
683 		    dst[nfill] |= ~((1 << (7 - (end_x & 7))) - 1);
684 		}
685 	    } else {
686 		byte save_left = dst[0];
687 		byte save_right = dst[nfill];
688 		int i;
689 
690 		for (i = 0; i < nfill + 1;) {
691 		    int n_samples;
692 		    int cx, cy;
693 		    int j;
694 
695 		    wts_get_samples(wch[plane_ix].wts, ((dev->band_offset_x + x) & -8) + (i << 3),
696 				    (dev->band_offset_y + y), &cx, &cy, &n_samples);
697 		    samples = wch[plane_ix].cell + cy * width_padded + cx;
698 
699 		    imax = min((nfill + 1 - i) << 3, n_samples);
700 		    for (j = 0; j < imax; j += 8) {
701 			int b;
702 			b = (((unsigned int)(((int)(samples[j + 0])) - comp_value) >> 24)) & 0x80;
703 			b |= (((unsigned int)(((int)(samples[j + 1])) - comp_value) >> 24)) & 0x40;
704 			b |= (((unsigned int)(((int)(samples[j + 2])) - comp_value) >> 24)) & 0x20;
705 			b |= (((unsigned int)(((int)(samples[j + 3])) - comp_value) >> 24)) & 0x10;
706 			b |= (((unsigned int)(((int)(samples[j + 4])) - comp_value) >> 24)) & 0x08;
707 			b |= (((unsigned int)(((int)(samples[j + 5])) - comp_value) >> 24)) & 0x04;
708 			b |= (((unsigned int)(((int)(samples[j + 6])) - comp_value) >> 24)) & 0x02;
709 			b |= (((unsigned int)(((int)(samples[j + 7])) - comp_value) >> 24)) & 0x01;
710 			dst[i + (j >> 3)] = b;
711 		    }
712 		    dst[0] = (save_left & (0xff << (8 - (x & 7)))) |
713 			(dst[0] & ~(0xff << (8 - (x & 7))));
714 		    dst[nfill] = (save_right & ((1 << (7 - (end_x & 7))) - 1)) |
715 			(dst[nfill] & ~((1 << (7 - (end_x & 7))) - 1));
716 		    i += (j >> 3);
717 		}
718 	    }
719         }
720     }
721     return 0;
722 }
723 
724 static int
wtsimdi_copy_mono(gx_device * dev,const byte * data,int sourcex,int sraster,gx_bitmap_id id,int x,int y,int w,int h,gx_color_index zero,gx_color_index one)725 wtsimdi_copy_mono(gx_device * dev,
726 		  const byte * data, int sourcex, int sraster, gx_bitmap_id id,
727 	int x, int y, int w, int h, gx_color_index zero, gx_color_index one)
728 {
729     gx_device_memory * const mdev = (gx_device_memory *)dev;
730     gx_device_wtsimdi * idev = (gx_device_wtsimdi *)
731 	    ((mdev->target) ? (gx_device_wts *)(mdev->target)
732 			    : (gx_device_wts *)dev);
733     wts_cooked_halftone * wch = idev->wcooked;
734     int code, comp_value;
735     int halftoned_bytes = (idev-> width + 7) >> 3;
736     int width_padded, imax;
737     byte * dst, * base;
738     byte * samples;
739     uint raster, plane_ix;
740     int first_byte, end_x;
741     const byte *src;
742     int sshift;
743 
744     if (zero != gx_no_color_index)
745 	return gx_default_copy_mono(dev, data, sourcex, sraster, id,
746 				    x, y, w, h, zero, one);
747     if (x < 0) {
748 	sourcex -= x;
749 	w += x;
750 	x = 0;
751     }
752     src = data + ((sourcex - (x & 7)) >> 3);
753     if (y < 0) {
754 	src -= sraster * y;
755 	h += y;
756 	y = 0;
757     }
758     fit_fill(dev, x, y, w, h);
759 
760     base = scan_line_base(mdev, y);
761     raster = mdev->raster;
762 
763     end_x = x + w - 1;
764     first_byte = x >> 3;
765 
766     /*
767      * Check if this is a new color.  To minimize color conversion
768      * time, we keep a couple of values cached in the wtsimdi device.
769      */
770 #define COPY_MONO_BLACK_IS_K
771 #define COPY_MONO_OPTIMIZATION
772 
773 #ifdef COPY_MONO_BLACK_IS_K
774     if (one == 0) {
775 	/* FIXME: This should rely on tag bits, but copy_mono black is (probably) only used for text */
776 	idev->current_color.cmyk[0] =idev->current_color.cmyk[1] =  idev->current_color.cmyk[2] = 0;
777 	idev->current_color.cmyk[3] = 0xff;	/* 100% K channel, 0% C, M, Y */
778 	idev->current_color.color_index = 0;
779     } else
780 #endif
781     if ((code = wtsimdi_resolve_one(idev, one)) < 0)
782 	return code;
783 
784     sshift = 8 - ((sourcex - x) & 7);
785 
786     for (; h--; y++) {
787         base = scan_line_base(mdev, y);
788         for (plane_ix = 0; plane_ix < 4; plane_ix++) {
789 	    int nfill = (end_x >> 3) - first_byte;
790 	    int i;
791 	    byte smask, save_left, save_right;
792 
793 	    width_padded = wch[plane_ix].width_padded;
794 	    dst = base + first_byte + plane_ix * halftoned_bytes;
795 	    save_left = dst[0];
796 	    save_right = dst[nfill];
797 	    comp_value = idev->current_color.cmyk[plane_ix];
798 #ifdef  COPY_MONO_OPTIMIZATION
799 	    if (comp_value == 0) {
800 		for (i = 0; i < nfill + 1; i++) {
801 		    if ((smask = (src[i] << 8 | src[i + 1]) >> sshift) != 0)
802 			dst[i] = dst[i] & ~smask;
803 		}
804 	    } else if (comp_value == 0xff) {
805 		for (i = 0; i < nfill + 1; i++) {
806 		    if ((smask = (src[i] << 8 | src[i + 1]) >> sshift) != 0)
807 			dst[i] = smask | (dst[i] & ~smask) ;
808 		}
809 	    } else
810 #endif
811 	    {
812 		for (i = 0; i < nfill + 1;) {
813 		    int n_samples;
814 		    int cx, cy;
815 		    int j;
816 
817 		    wts_get_samples(wch[plane_ix].wts, ((dev->band_offset_x + x) & -8) + (i << 3),
818 				    (dev->band_offset_y + y), &cx, &cy, &n_samples);
819 		    samples = wch[plane_ix].cell + cy * width_padded + cx;
820 
821 		    imax = min((nfill + 1 - i) << 3, n_samples);
822 		    for (j = 0; j < imax; j += 8) {
823 			smask = (src[i] << 8 | src[i + 1]) >> sshift;
824 			if (smask) {
825 			    byte b;
826 			    b = (((unsigned int)(((int)(samples[j + 0])) - comp_value) >> 24)) & 0x80;
827 			    b |= (((unsigned int)(((int)(samples[j + 1])) - comp_value) >> 24)) & 0x40;
828 			    b |= (((unsigned int)(((int)(samples[j + 2])) - comp_value) >> 24)) & 0x20;
829 			    b |= (((unsigned int)(((int)(samples[j + 3])) - comp_value) >> 24)) & 0x10;
830 			    b |= (((unsigned int)(((int)(samples[j + 4])) - comp_value) >> 24)) & 0x08;
831 			    b |= (((unsigned int)(((int)(samples[j + 5])) - comp_value) >> 24)) & 0x04;
832 			    b |= (((unsigned int)(((int)(samples[j + 6])) - comp_value) >> 24)) & 0x02;
833 			    b |= (((unsigned int)(((int)(samples[j + 7])) - comp_value) >> 24)) & 0x01;
834 			    dst[i] = (b & smask) | (dst[i] & ~smask);
835 			}
836 			i++;
837 		    }
838 		}
839 	    }
840 	    /* Restore edge areas on left and right that may have been overwritten */
841 	    dst[0] = (save_left & (0xff << (8 - (x & 7)))) |
842 			(dst[0] & ~(0xff << (8 - (x & 7))));
843 	    dst[nfill] = (save_right & ((1 << (7 - (end_x & 7))) - 1)) |
844 			(dst[nfill] & ~((1 << (7 - (end_x & 7))) - 1));
845         }
846 	src += sraster;
847     }
848     return 0;
849 }
850 
851 
852 /*
853  * This is a clone of gx_default_get_bits except that we are adding
854  * GB_HALFTONED to the parameter options.  This will tell the buffer
855  * device to halftone the data (if it is not already halftoned).
856  */
857 int
wtsimdi_get_bits(gx_device * dev,int y,byte * data,byte ** actual_data)858 wtsimdi_get_bits(gx_device * dev, int y, byte * data, byte ** actual_data)
859 {	/*
860 	 * Hand off to get_bits_rectangle, being careful to avoid a
861 	 * possible recursion loop.
862 	 */
863     dev_proc_get_bits((*save_get_bits)) = dev_proc(dev, get_bits);
864     gs_int_rect rect;
865     gs_get_bits_params_t params;
866     int code;
867 
868     rect.p.x = 0, rect.p.y = y;
869     rect.q.x = dev->width, rect.q.y = y + 1;
870     params.options =
871 	(actual_data ? GB_RETURN_POINTER : 0) | GB_RETURN_COPY |
872 	(GB_ALIGN_STANDARD | GB_OFFSET_0 | GB_RASTER_STANDARD |
873     /* No depth specified, we always use native colors. */
874 	 GB_PACKING_CHUNKY | GB_COLORS_NATIVE | GB_ALPHA_NONE |
875 	 GB_HALFTONED);
876     params.x_offset = 0;
877     params.raster = bitmap_raster(dev->width * dev->color_info.depth);
878     params.data[0] = data;
879     params.original_y = y;
880     set_dev_proc(dev, get_bits, gx_no_get_bits);
881     code = (*dev_proc(dev, get_bits_rectangle))
882 	(dev, &rect, &params, NULL);
883     if (actual_data)
884 	*actual_data = params.data[0];
885     set_dev_proc(dev, get_bits, save_get_bits);
886     return code;
887 }
888 
889 int
wtsimdi_halftoned_get_bits_rectangle(gx_device * dev,const gs_int_rect * prect,gs_get_bits_params_t * params,gs_int_rect ** unread)890 wtsimdi_halftoned_get_bits_rectangle(gx_device * dev, const gs_int_rect * prect,
891 		       gs_get_bits_params_t * params, gs_int_rect ** unread)
892 {
893     /*
894      * We should only get to this routine if the caller wants the halftoned
895      * version of our output.  In all other cases we should be in
896      * wtsimdi_contone_get_bits_rectangle.
897      */
898     if (!(params->options & GB_HALFTONED))
899 	return_error(gs_error_unknownerror);
900     return mem_get_bits_rectangle(dev, prect, params, unread);
901 }
902 
903 
904 int
wtsimdi_contone_get_bits_rectangle(gx_device * dev,const gs_int_rect * prect,gs_get_bits_params_t * params,gs_int_rect ** unread)905 wtsimdi_contone_get_bits_rectangle(gx_device * dev, const gs_int_rect * prect,
906 		       gs_get_bits_params_t * params, gs_int_rect ** unread)
907 {
908     /*
909      * Save the options since mem_get_bits_rectangle will change them to
910      * indicate what it actually did.  We need the unmodified values.
911      */
912     gs_get_bits_options_t options = params->options;
913     int original_y = params->original_y;
914     byte * buffer = params->data[0];
915     int code = mem_get_bits_rectangle(dev, prect, params, unread);
916 
917     if (code < 0)
918 	return code;
919     if (options & GB_HALFTONED) {
920 	gx_device_memory * const mdev = (gx_device_memory *)dev;
921         gx_device_wtsimdi * idev = (gx_device_wtsimdi *)
922 	    ((mdev->target) ? (gx_device_wts *)(mdev->target)
923 			    : (gx_device_wts *)dev);
924 	int width = dev->width;
925 	int n_planes = 4;
926 	int r_last = -1, g_last = -1, b_last = -1, r, g, b;
927 	int x;
928 	byte * src = params->data[0];
929 	/* Note that the following relies on objects being allocated to
930 	 * at least a 4-byte boundary for efficiency on x86 and to prevent
931 	 * alignment errors on CPU's that gripe about it. We know this is
932 	 * true due to object headers requiring alignment. In the future
933 	 * if we want to remove this invariant, we can use 'obj_align_mod'
934 	 * to allocate a slightly larger buffer and offset 'cmyk_data'
935 	 * to the proper alignment within the buffer.
936 	 */
937         uint32_t * cmyk_buffer = gs_malloc(dev->memory,
938 				(width + 7), sizeof(uint32_t),
939 	   			"wtsimdi_contone_get_bits(cmyk_buffer)");
940 	uint32_t* cmyk_data = cmyk_buffer;
941 
942         if (cmyk_data == NULL)
943 	    return_error(gs_error_VMerror);
944 
945 	for (x = 0; x < width; x++) {
946 	    r = *src++;
947 	    g = *src++;
948 	    b = *src++;
949 	    if (r != r_last || g != g_last || b != b_last) {
950 		gx_color_index color = (((r<<8) | g) << 8) | b;
951 
952 		r_last = r, g_last = g, b_last = b;
953 		wtsimdi_resolve_one(idev, color);
954 	    }
955 	    *cmyk_data++ = *((uint32_t *)idev->current_color.cmyk);
956 	}
957 	wts_halftone_line_8(idev->wcooked, original_y, width, n_planes,
958 			    idev->band_offset_x, idev->band_offset_y,  buffer, (const byte *)cmyk_buffer);
959 	params->data[0] = buffer;
960 	gs_free(dev->memory, cmyk_buffer, halftoned_bytes * n_planes, 1,
961 		       	"wtsimdi_print_page(halftoned_data)");
962     }
963     return code;
964 }
965 
966 /*
967  * We need to create custom memory buffer devices.  We use the default
968  * create_buf_device routine and then we set our custom device procedures.
969  */
970 static int
wtsimdi_create_buf_device(gx_device ** pbdev,gx_device * target,int y,const gx_render_plane_t * render_plane,gs_memory_t * mem,gx_band_complexity_t * band_complexity)971 wtsimdi_create_buf_device(gx_device **pbdev, gx_device *target, int y,
972    const gx_render_plane_t *render_plane, gs_memory_t *mem,
973    gx_band_complexity_t *band_complexity)
974 {
975     int code = gx_default_create_buf_device(pbdev, target, y,
976 	render_plane, mem, band_complexity);
977     /* Now set our custom device procedures. */
978     if (band_complexity && band_complexity->nontrivial_rops) {
979 	set_dev_proc(*pbdev, get_bits_rectangle,
980 		     wtsimdi_contone_get_bits_rectangle);
981     } else {
982 	set_dev_proc(*pbdev, get_bits_rectangle,
983 		     wtsimdi_halftoned_get_bits_rectangle);
984 	set_dev_proc(*pbdev, fill_rectangle, wtsimdi_fill_rectangle);
985 	set_dev_proc(*pbdev, copy_mono, wtsimdi_copy_mono);
986 	/* All procedures which are defined as mem_true24_* need to be either
987 	   implemented or replaced with a default implementation. The following
988 	   three don't have significant usage in testing with Altona.
989 	*/
990 	set_dev_proc(*pbdev, copy_alpha, gx_default_copy_alpha);
991 	set_dev_proc(*pbdev, copy_color, gx_default_copy_color);
992     }
993     return code;
994 }
995 
996 /*
997  * Create a row of output data.  The output is the same as the pkmraw
998  * device.  This a pseudo 1 bit CMYK output.  Actually the output is
999  * 3 byte RGB with each byte being either 0 or 255.
1000  *
1001  * The input data is 1 bit per component CMYK.  The data is separated
1002  * into planes.
1003  */
1004 static void
write_pkmraw_row(int width,byte * data,FILE * pstream)1005 write_pkmraw_row(int width, byte * data, FILE * pstream)
1006 {
1007     if (pstream == NULL)
1008 	return;
1009     {
1010 	int x, bit;
1011 	int halftoned_bytes = (width + 7) >> 3;
1012 	byte * cdata = data;
1013 	byte * mdata = data + halftoned_bytes;
1014 	byte * ydata = mdata + halftoned_bytes;
1015 	byte * kdata = ydata + halftoned_bytes;
1016 	byte c = *cdata++;
1017 	byte m = *mdata++;
1018 	byte y = *ydata++;
1019 	byte k = *kdata++;
1020 
1021 	/*
1022 	 * Contrary to what the documentation implies, gcc compiles putc
1023 	 * as a procedure call.  This is ridiculous, but since we can't
1024 	 * change it, we buffer groups of pixels ourselves and use fwrite.
1025 	 */
1026 	for (bit = 7, x = 0; x < width;) {
1027 	    byte raw[80 * 3];	/* 80 is arbitrary, must be a multiple of 8 */
1028 	    int end = min(x + sizeof(raw) / 3, width);
1029 	    byte *outp = raw;
1030 
1031 	    for (; x < end; x++) {
1032 
1033 		if ((k >> bit) & 1) {
1034 		    *outp++ = 0;	/* Set output color = black */
1035 		    *outp++ = 0;
1036 		    *outp++ = 0;
1037 		} else {
1038 		    *outp++ = 255 & (255 + ((c >> bit) & 1));
1039 		    *outp++ = 255 & (255 + ((m >> bit) & 1));
1040 		    *outp++ = 255 & (255 + ((y >> bit) & 1));
1041 		}
1042 		if (bit == 0) {
1043 		    c = *cdata++;
1044 		    m = *mdata++;
1045 		    y = *ydata++;
1046 		    k = *kdata++;
1047 		    bit = 7;
1048 		} else
1049 		    bit--;
1050 	    }
1051 	    fwrite(raw, 1, outp - raw, pstream);
1052 	}
1053 	return;
1054     }
1055 }
1056 
1057 /*
1058  * Output the page raster.
1059  */
1060 
1061 static int
wtsimdi_print_page(gx_device_printer * pdev,FILE * prn_stream)1062 wtsimdi_print_page(gx_device_printer *pdev, FILE *prn_stream)
1063 {
1064     gx_device_wtsimdi *idev = (gx_device_wtsimdi*)pdev;
1065     int n_planes = 4;
1066     byte * halftoned_data;
1067     byte * halftoned_buffer = NULL;
1068     int halftoned_bytes, y;
1069     int code = 0;
1070     int width = pdev->width;
1071     int height = pdev->height;
1072     dev_proc_get_bits((*save_get_bits)) = dev_proc(pdev, get_bits);
1073     int output_is_nul = !strncmp(pdev->fname, "nul:", min(strlen(pdev->fname), 4)) ||
1074 	!strncmp(pdev->fname, "/dev/null", min(strlen(pdev->fname), 9));
1075 
1076     /*
1077      * The printer device setup changed the get_bits routine to
1078      * gx_default_get_bits.  We want to use our own.
1079      */
1080     set_dev_proc(pdev, get_bits, wtsimdi_get_bits);
1081 
1082     /*
1083      * Initialize the WTS halftones.
1084      */
1085     code = wts_init_halftones((gx_device_wts *)idev, n_planes);
1086     if (code < 0) goto cleanup;
1087 
1088     /*
1089      * Allocate a buffer to hold the halftoned data.  This is 1 bit per
1090      * component CMYK data.
1091      */
1092     halftoned_bytes = (width + 7) >> 3;	/* bytes per component */
1093     halftoned_buffer = gs_malloc(pdev->memory, halftoned_bytes * n_planes, 1,
1094 			       	"wtsimdi_print_page(halftoned_data)");
1095     if (halftoned_buffer == NULL) {
1096 	code = GS_NOTE_ERROR(pdev->memory, gs_error_VMerror);
1097 	goto cleanup;
1098     }
1099 #ifdef DEBUG
1100     /* Collect stats on each page. With the allocation now done once in 'open' the
1101      * statistics won't be the same if other pages have been printed  */
1102     idev->color_cache_hit = idev->color_cache_collision =
1103 	idev->color_is_current = idev->cache_fill_empty = 0;
1104 #endif
1105 
1106     /* Initialize output file header. */
1107     if (!output_is_nul) {
1108 	fprintf(prn_stream, "P6\n%d %d\n", width, height);
1109 	fprintf(prn_stream, "# Image generated by %s %ld.%02ld (device=wtsimdi)\n",
1110 	   	gs_program_name(), gs_revision_number() / 100,
1111 	   			gs_revision_number() % 100);
1112 	fprintf(prn_stream, "%d\n", 255);
1113     }
1114 
1115     /*
1116      * Get raster data and then write data to output file.
1117      */
1118     for (y = 0; y < height; y++) {
1119 	/*
1120 	 * The get_bit routines for our device returns a halftoned copy of
1121 	 * the output data.  Print this data to the output file.
1122 	 */
1123 	code = gdev_prn_get_bits(pdev, y, halftoned_buffer, &halftoned_data);
1124 	if (code < 0)
1125 	    break;		/* return code below after cleanup */
1126 	if (!output_is_nul)
1127 	    write_pkmraw_row(width, halftoned_data, prn_stream);
1128     }
1129 cleanup:
1130     if (idev->color_cache != NULL) {
1131 #ifdef DEBUG
1132     {
1133       int i, unused_color_cache_slots;
1134 
1135       for (i=0,unused_color_cache_slots=0; i<COLOR_CACHE_SIZE; i++)
1136 	if (idev->color_cache[i].color_index == gx_no_color_index)
1137 	    unused_color_cache_slots++;
1138 	if_debug5(':',"wtsimdi_print_page color cache stats:"
1139 	    " current=%ld, hits=%ld,"
1140 	    " collisions=%ld, fill=%ld, unused_slots=%d\n",
1141 	    idev->color_is_current, idev->color_cache_hit,
1142 	    idev->color_cache_collision, idev->cache_fill_empty,
1143 	    unused_color_cache_slots);
1144     }
1145 #endif
1146     }
1147     if (halftoned_buffer != NULL)
1148 	gs_free(pdev->memory, halftoned_buffer, halftoned_bytes * n_planes, 1,
1149 		       	"wtsimdi_print_page(halftoned_buffer)");
1150     set_dev_proc(pdev, get_bits, save_get_bits);
1151     return code;
1152 }
1153