xref: /plan9/sys/src/cmd/gs/src/gxwts.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 2002 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 /*$Id: gxwts.c,v 1.5 2002/11/02 07:31:45 raph Exp $ */
17 /* Rendering using Well Tempered Screening. */
18 #include "stdpre.h"
19 #include "memory_.h" /* for memcmp */
20 #include <stdlib.h> /* for malloc */
21 #include "gx.h"
22 #include "gxstate.h"
23 #include "gsht.h"
24 #include "math_.h"
25 #include "gserrors.h"
26 #include "gxdcolor.h"
27 #include "gxdevcli.h"
28 #include "gxdht.h"
29 #include "gxwts.h"
30 
31 #define GXWTS_USE_DOUBLE
32 
33 #ifndef UNIT_TEST
34 /* device color type for wts. */
35 
36 /* todo: trace and relocate pointers */
37 gs_private_st_simple(st_dc_wts, gx_device_color, "dc_wts");
38 private dev_color_proc_save_dc(gx_dc_wts_save_dc);
39 private dev_color_proc_get_dev_halftone(gx_dc_wts_get_dev_halftone);
40 private dev_color_proc_load(gx_dc_wts_load);
41 private dev_color_proc_fill_rectangle(gx_dc_wts_fill_rectangle);
42 private dev_color_proc_equal(gx_dc_wts_equal);
43 private dev_color_proc_write(gx_dc_wts_write);
44 private dev_color_proc_read(gx_dc_wts_read);
45 private dev_color_proc_get_nonzero_comps(gx_dc_wts_get_nonzero_comps);
46 const gx_device_color_type_t gx_dc_type_data_wts = {
47     &st_dc_wts,
48     gx_dc_wts_save_dc, gx_dc_wts_get_dev_halftone,
49     gx_dc_ht_get_phase,
50     gx_dc_wts_load, gx_dc_wts_fill_rectangle,
51     gx_dc_default_fill_masked, gx_dc_wts_equal,
52     gx_dc_wts_write, gx_dc_wts_read,
53     gx_dc_wts_get_nonzero_comps
54 };
55 #undef gx_dc_type_wts
56 const gx_device_color_type_t *const gx_dc_type_wts =
57 &gx_dc_type_data_wts;
58 #endif
59 
60 /* Low-level implementation follows. */
61 
62 /**
63  * mul_shr_16: Multiply and shift right 16.
64  * @a: 32-bit signed number.
65  * @b: 32-bit signed number.
66  *
67  * Multiply @a and @b, then shift right 16 bits. Allow intermediate value
68  * to overflow 32 bits.
69  *
70  * Return value: result.
71  **/
72 #ifdef GXWTS_USE_DOUBLE
73 private int
mul_shr_16(int a,int b)74 mul_shr_16 (int a, int b)
75 {
76   return (int)floor(((double) a) * ((double) b) * (1.0 / (1 << 16)));
77 }
78 #else
79 #error todo: supply mul_shr_16 based on 64 bit integer type
80 #endif
81 
82 /* Implementation of wts_get_samples for rational cells. */
83 #if 0
84 private int
85 wts_get_samples_rat(const wts_screen_t *ws, int x, int y,
86 		    wts_screen_sample_t **samples, int *p_nsamples)
87 {
88     int d = y / ws->cell_height;
89     int r = y % ws->cell_height;
90     int x_ix = ((d * ws->cell_shift) + x) % ws->cell_width;
91     *p_nsamples = ws->cell_width - x_ix;
92     *samples = ws->samples + x_ix + r * ws->cell_width;
93     return 0;
94 }
95 #endif
96 
97 /* Implementation of wts_get_samples for Screen J. */
98 private int
wts_get_samples_j(const wts_screen_t * ws,int x,int y,wts_screen_sample_t ** samples,int * p_nsamples)99 wts_get_samples_j(const wts_screen_t *ws, int x, int y,
100 		  wts_screen_sample_t **samples, int *p_nsamples)
101 {
102     const wts_screen_j_t *wsj = (const wts_screen_j_t *)ws;
103     /* int d = y / ws->cell_height; */
104     int y_ix = y;
105     int x_ix = x;
106     double pad = (wsj->pa) * (1.0 / (1 << 16));
107     double pbd = (wsj->pb) * (1.0 / (1 << 16));
108     double afrac = x * pad;
109     double bfrac = x * pbd;
110     int acount = (int)floor(afrac);
111     int bcount = (int)floor(bfrac);
112     int ccount = mul_shr_16(y, wsj->pc);
113     int dcount = mul_shr_16(y, wsj->pd);
114     int nsamples;
115 
116     x_ix += acount * wsj->XA + bcount * wsj->XB +
117 	ccount * wsj->XC + dcount * wsj->XD;
118     y_ix += acount * wsj->YA + bcount * wsj->YB +
119 	ccount * wsj->YC + dcount * wsj->YD;
120 
121     x_ix += (y_ix / ws->cell_height) * ws->cell_shift;
122     x_ix %= ws->cell_width;
123     y_ix %= ws->cell_height;
124 
125     nsamples = ws->cell_width - x_ix;
126     if (floor (afrac + (nsamples - 1) * pad) > acount)
127 	nsamples = (int)ceil((acount + 1 - afrac) / pad);
128 
129     if (floor (bfrac + (nsamples - 1) * pbd) > bcount)
130 	nsamples = (int)ceil((bcount + 1 - bfrac) / pbd);
131 #if 0
132     printf("get_samples: (%d, %d) -> (%d, %d) %d (cc=%d)\n",
133 	   x, y, x_ix, y_ix, nsamples, ccount);
134 #endif
135     *p_nsamples = nsamples;
136     *samples = ws->samples + x_ix + y_ix * ws->cell_width;
137     return 0;
138 }
139 
140 private int
wts_screen_h_offset(int x,double p1,int m1,int m2)141 wts_screen_h_offset(int x, double p1, int m1, int m2)
142 {
143     /* todo: this is a linear search; constant time should be feasible */
144     double running_p = 0;
145     int width_so_far;
146     int this_width;
147 
148     for (width_so_far = 0;; width_so_far += this_width) {
149 	running_p += p1;
150 	if (running_p >= 0.5) {
151 	    this_width = m1;
152 	    running_p -= 1;
153 	} else {
154 	    this_width = m2;
155 	}
156 	if (width_so_far + this_width > x)
157 	    break;
158     }
159     return x - width_so_far + (this_width == m1 ? 0 : m1);
160 }
161 
162 /* Implementation of wts_get_samples for Screen H. */
163 private int
wts_get_samples_h(const wts_screen_t * ws,int x,int y,wts_screen_sample_t ** samples,int * p_nsamples)164 wts_get_samples_h(const wts_screen_t *ws, int x, int y,
165 		  wts_screen_sample_t **samples, int *p_nsamples)
166 {
167     const wts_screen_h_t *wsh = (const wts_screen_h_t *)ws;
168     int x_ix = wts_screen_h_offset(x, wsh->px,
169 				   wsh->x1, ws->cell_width - wsh->x1);
170     int y_ix = wts_screen_h_offset(y, wsh->py,
171 				   wsh->y1, ws->cell_height - wsh->y1);
172     *p_nsamples = (x_ix >= wsh->x1 ? ws->cell_width : wsh->x1) - x_ix;
173     *samples = ws->samples + x_ix + y_ix * ws->cell_width;
174     return 0;
175 }
176 
177 /**
178  * wts_get_samples: Get samples from Well Tempered Screening cell.
179  * @ws: Well Tempered Screening cell.
180  * @x: X coordinate of starting point.
181  * @y: Y coordinate of starting point.
182  * @samples: Where to store pointer to samples.
183  * @p_nsamples: Where to store number of valid samples.
184  *
185  * Finds samples from the cell for use in halftoning. On success,
186  * @p_nsamples is set to the number of valid samples, ie for 0 <= i <
187  * nsamples, samples[i] is a valid sample for coordinate (x + i, y).
188  * p_nsamples is guaranteed to at least 1. The samples in @samples
189  * are valid for the lifetime of the cell, or until the next garbage
190  * collection, whichever comes first.
191  *
192  * Todo: describe meaning of wts_screen_sample_t (particularly edge
193  * cases).
194  *
195  * Note: may want to add a "cursor" to the api as an optimization. It
196  * can wait, though.
197  *
198  * Return value: 0 on success.
199  **/
200 int
wts_get_samples(const wts_screen_t * ws,int x,int y,wts_screen_sample_t ** samples,int * p_nsamples)201 wts_get_samples(const wts_screen_t *ws, int x, int y,
202 		wts_screen_sample_t **samples, int *p_nsamples)
203 {
204     if (ws->type == WTS_SCREEN_J)
205 	return wts_get_samples_j(ws, x, y, samples, p_nsamples);
206     if (ws->type == WTS_SCREEN_H)
207 	return wts_get_samples_h(ws, x, y, samples, p_nsamples);
208     else
209 	return -1;
210 }
211 
212 /* Device color methods follow. */
213 
214 private void
gx_dc_wts_save_dc(const gx_device_color * pdevc,gx_device_color_saved * psdc)215 gx_dc_wts_save_dc(const gx_device_color * pdevc, gx_device_color_saved * psdc)
216 {
217     psdc->type = pdevc->type;
218     memcpy( psdc->colors.wts.levels,
219             pdevc->colors.wts.levels,
220             sizeof(psdc->colors.wts.levels) );
221     psdc->phase = pdevc->phase;
222 }
223 
224 private const gx_device_halftone *
gx_dc_wts_get_dev_halftone(const gx_device_color * pdevc)225 gx_dc_wts_get_dev_halftone(const gx_device_color * pdevc)
226 {
227     return pdevc->colors.wts.w_ht;
228 }
229 
230 private int
gx_dc_wts_load(gx_device_color * pdevc,const gs_imager_state * pis,gx_device * ignore_dev,gs_color_select_t select)231 gx_dc_wts_load(gx_device_color *pdevc, const gs_imager_state * pis,
232 	       gx_device *ignore_dev, gs_color_select_t select)
233 {
234     return 0;
235 }
236 
237 /**
238  * wts_draw: Draw a halftoned shade into a 1 bit deep buffer.
239  * @ws: WTS screen.
240  * @shade: Gray shade to draw.
241  * @data: Destination buffer.
242  * @data_raster: Rowstride for destination buffer.
243  * @x, @y, @w, @h: coordinates of rectangle to draw.
244  *
245  * This is close to an implementation of the "draw" method for the
246  * gx_ht_order class. Currently, only WTS screens implement this
247  * method, and only WTS device colors invoke it. However, implementing
248  * this for legacy order objects is probably a good idea, to improve
249  * halftoning performance as the cell size scales up.
250  *
251  * However, it's not exactly an implementation of the "draw" method
252  * for the gx_ht_order class because the "self" type would need to be
253  * gx_ht_order. Currently, however, device colors don't hold a pointer
254  * to the order object. Some amount of refactoring seems to be in
255  * order.
256  *
257  * Return value: 0 on success.
258  **/
259 private int
wts_draw(wts_screen_t * ws,wts_screen_sample_t shade,byte * data,int data_raster,int x,int y,int w,int h)260 wts_draw(wts_screen_t *ws, wts_screen_sample_t shade,
261 	 byte *data, int data_raster,
262 	 int x, int y, int w, int h)
263 {
264     int xo, yo;
265     unsigned char *line_start = data;
266 
267     for (yo = 0; yo < h; yo++) {
268 	unsigned char *line_ptr = line_start;
269 	int mask = 0x80;
270 	unsigned char b = 0;
271 	int imax;
272 
273 	for (xo = 0; xo < w; xo += imax) {
274 	    wts_screen_sample_t *samples;
275 	    int n_samples, i;
276 
277 	    wts_get_samples(ws, x + xo, y + yo, &samples, &n_samples);
278 	    imax = min(w - xo, n_samples);
279 	    for (i = 0; i < imax; i++) {
280 		if (shade > samples[i])
281 		    b |= mask;
282 		mask >>= 1;
283 		if (mask == 0) {
284 		    *line_ptr++ = b;
285 		    b = 0;
286 		    mask = 0x80;
287 		}
288 	    }
289 	}
290 	if (mask != 0x80)
291 	    *line_ptr = b;
292 	line_start += data_raster;
293     }
294     return 0;
295 }
296 
297 /**
298  * Special case implementation for one component. When we do plane_mask,
299  * we'll want to generalize this to handle any single-bit plane_mask.
300  **/
301 private int
gx_dc_wts_fill_rectangle_1(const gx_device_color * pdevc,int x,int y,int w,int h,gx_device * dev,gs_logical_operation_t lop,const gx_rop_source_t * source)302 gx_dc_wts_fill_rectangle_1(const gx_device_color *pdevc,
303 			   int x, int y, int w, int h,
304 			   gx_device *dev, gs_logical_operation_t lop,
305 			   const gx_rop_source_t *source)
306 {
307     /* gx_rop_source_t no_source; */
308     int tile_raster = ((w + 31) & -32) >> 3;
309     int tile_size = tile_raster * h;
310     unsigned char *tile_data;
311     int code = 0;
312     gx_ht_order_component *components = pdevc->colors.wts.w_ht->components;
313     wts_screen_t *ws = components[0].corder.wts;
314     wts_screen_sample_t shade = pdevc->colors.wts.levels[0];
315     gx_color_index color0, color1;
316 
317     color0 = dev->color_info.separable_and_linear == GX_CINFO_SEP_LIN ? 0 :
318 	pdevc->colors.wts.plane_vector[1];
319     color1 = pdevc->colors.wts.plane_vector[0];
320 
321     tile_data = malloc(tile_size);
322 
323     wts_draw(ws, shade, tile_data, tile_raster, x, y, w, h);
324 
325     /* See gx_dc_ht_binary_fill_rectangle() for explanation. */
326     if (dev->color_info.depth > 1)
327 	lop &= ~lop_T_transparent;
328 
329     /* Interesting question: should data_x be (x & 7), rather than 0,
330        to improve alignment? */
331     if (source == NULL && lop_no_S_is_T(lop))
332 	code = (*dev_proc(dev, copy_mono))
333 	    (dev, tile_data, 0, tile_raster, gx_no_bitmap_id,
334 	     x, y, w, h, color0, color1);
335 
336     free(tile_data);
337     return code;
338 }
339 
340 private int
gx_dc_wts_write(const gx_device_color * pdevc,const gx_device_color_saved * psdc,const gx_device * dev,byte * pdata,uint * psize)341 gx_dc_wts_write(
342     const gx_device_color *         pdevc,
343     const gx_device_color_saved *   psdc,
344     const gx_device *               dev,
345     byte *                          pdata,
346     uint *                          psize )
347 {
348     /* not yet implemented */
349     return_error(gs_error_unknownerror);
350 }
351 
352 private int
gx_dc_wts_read(gx_device_color * pdevc,const gs_imager_state * pis,const gx_device_color * prior_devc,const gx_device * dev,const byte * pdata,uint size,gs_memory_t * mem)353 gx_dc_wts_read(
354     gx_device_color *       pdevc,
355     const gs_imager_state * pis,
356     const gx_device_color * prior_devc,
357     const gx_device *       dev,
358     const byte *            pdata,
359     uint                    size,
360     gs_memory_t *           mem )
361 {
362     /* not yet implemented */
363     return_error(gs_error_unknownerror);
364 }
365 
366 
367 /**
368  * wts_repack_tile_4: Repack four 1-bit tiles into chunky nibbles.
369  * Note: argument list will change. plane_mask and base_color will
370  * probably get added as an optimization.
371  *
372  * Note: we round w up to an even value. We're counting on the
373  * subsequent copy_color to ignore any extra bits.
374  **/
375 private void
wts_repack_tile_4(unsigned char * ctile_data,int ctile_raster,const unsigned char ** tile_data,int tile_raster,const gx_color_index * plane_vector,bool invert,int w,int h)376 wts_repack_tile_4(unsigned char *ctile_data, int ctile_raster,
377 		  const unsigned char **tile_data, int tile_raster,
378 		  const gx_color_index *plane_vector, bool invert,
379 		  int w, int h)
380 {
381     int y;
382     int tile_idx_start = 0;
383     unsigned char *ctile_start = ctile_data;
384     byte inv_byte = invert ? 0xff : 0;
385 
386     for (y = 0; y < h; y++) {
387 	int x;
388 	int tile_idx = tile_idx_start;
389 
390 	for (x = 0; x < w; x += 2) {
391 	    byte b = 0;
392 	    byte m0 = 0x80 >> (x & 6);
393 	    byte m1 = m0 >> 1;
394 	    byte td;
395 
396 	    td = tile_data[0][tile_idx] ^ inv_byte;
397 	    if (td & m0) b |= plane_vector[0] << 4;
398 	    if (td & m1) b |= plane_vector[0];
399 
400 	    td = tile_data[1][tile_idx] ^ inv_byte;
401 	    if (td & m0) b |= plane_vector[1] << 4;
402 	    if (td & m1) b |= plane_vector[1];
403 
404 	    td = tile_data[2][tile_idx] ^ inv_byte;
405 	    if (td & m0) b |= plane_vector[2] << 4;
406 	    if (td & m1) b |= plane_vector[2];
407 
408 	    td = tile_data[3][tile_idx] ^ inv_byte;
409 	    if (td & m0) b |= plane_vector[3] << 4;
410 	    if (td & m1) b |= plane_vector[3];
411 
412 	    if ((x & 6) == 6)
413 		tile_idx++;
414 	    ctile_start[x >> 1] = b;
415 	}
416 	tile_idx_start += tile_raster;
417 	ctile_start += ctile_raster;
418     }
419 }
420 
421 /* Special case implementation for four components. Intermediate color
422  * to the order objecttile (for copy_color) is packed 2 to a byte.
423  *
424  * Looking at this code, it should generalize to more than four
425  * components. Probably the repack code should get factored out.
426  */
427 private int
gx_dc_wts_fill_rectangle_4(const gx_device_color * pdevc,int x,int y,int w,int h,gx_device * dev,gs_logical_operation_t lop,const gx_rop_source_t * source)428 gx_dc_wts_fill_rectangle_4(const gx_device_color *pdevc,
429 			   int x, int y, int w, int h,
430 			   gx_device *dev, gs_logical_operation_t lop,
431 			   const gx_rop_source_t *source)
432 {
433     int num_comp = pdevc->colors.wts.num_components;
434     /* gx_rop_source_t no_source; */
435 
436     int tile_raster = ((w + 31) & -32) >> 3;
437     int tile_size = tile_raster * h;
438     unsigned char *tile_data[4];
439 
440     int ctile_raster = ((w + 7) & -8) >> 1;
441     int ctile_size = ctile_raster * h;
442     unsigned char *ctile_data;
443 
444     int code = 0;
445     bool invert = 0 && dev->color_info.polarity == GX_CINFO_POLARITY_SUBTRACTIVE;
446     int i;
447 
448     for (i = 0; i < num_comp; i++) {
449 	wts_screen_sample_t shade = pdevc->colors.wts.levels[i];
450 	gx_ht_order_component *components = pdevc->colors.wts.w_ht->components;
451 	wts_screen_t *ws = components[i].corder.wts;
452 
453 	tile_data[i] = malloc(tile_size);
454 	wts_draw(ws, shade, tile_data[i], tile_raster, x, y, w, h);
455     }
456 
457     ctile_data = malloc(ctile_size);
458     wts_repack_tile_4(ctile_data, ctile_raster,
459     		  (const unsigned char **)tile_data, tile_raster,
460 		      pdevc->colors.wts.plane_vector, invert, w, h);
461 
462     /* See gx_dc_ht_binary_fill_rectangle() for explanation. */
463     if (dev->color_info.depth > 1)
464 	lop &= ~lop_T_transparent;
465 
466     if (source == NULL && lop_no_S_is_T(lop))
467 	code = (*dev_proc(dev, copy_color))
468 	    (dev, ctile_data, 0, ctile_raster, gx_no_bitmap_id,
469 	     x, y, w, h);
470 
471     free(ctile_data);
472     for (i = 0; i < num_comp; i++) {
473 	free(tile_data[i]);
474     }
475 
476     return code;
477 }
478 
479 private int
gx_dc_wts_fill_rectangle(const gx_device_color * pdevc,int x,int y,int w,int h,gx_device * dev,gs_logical_operation_t lop,const gx_rop_source_t * source)480 gx_dc_wts_fill_rectangle(const gx_device_color *pdevc,
481 			 int x, int y, int w, int h,
482 			 gx_device *dev, gs_logical_operation_t lop,
483 			 const gx_rop_source_t *source)
484 {
485     int num_comp = pdevc->colors.wts.num_components;
486 
487     if (num_comp == 1)
488 	return gx_dc_wts_fill_rectangle_1(pdevc, x, y, w, h, dev, lop, source);
489     else if (num_comp <= 4)
490 	return gx_dc_wts_fill_rectangle_4(pdevc, x, y, w, h, dev, lop, source);
491     else
492 	return -1;
493 }
494 
495 /* Compare two wts colors for equality. */
496 private int
gx_dc_wts_equal(const gx_device_color * pdevc1,const gx_device_color * pdevc2)497 gx_dc_wts_equal(const gx_device_color *pdevc1,
498 		const gx_device_color *pdevc2)
499 {
500     uint num_comp = pdevc1->colors.wts.num_components;
501 
502     if (pdevc2->type != pdevc1->type ||
503 	pdevc1->phase.x != pdevc2->phase.x ||
504 	pdevc1->phase.y != pdevc2->phase.y ||
505 	num_comp != pdevc2->colors.wts.num_components
506 	)
507 	return false;
508     return
509 	!memcmp(pdevc1->colors.wts.levels,
510 		pdevc2->colors.wts.levels,
511 		num_comp * sizeof(pdevc1->colors.wts.levels[0]));
512 }
513 
514 /*
515  * Get the nonzero components of a wts halftone. This is used to
516  * distinguish components that are given zero intensity due to halftoning
517  * from those for which the original color intensity was in fact zero.
518  */
519 int
gx_dc_wts_get_nonzero_comps(const gx_device_color * pdevc,const gx_device * dev_ignored,gx_color_index * pcomp_bits)520 gx_dc_wts_get_nonzero_comps(
521     const gx_device_color * pdevc,
522     const gx_device *       dev_ignored,
523     gx_color_index *        pcomp_bits )
524 {
525     int                     i, ncomps =  pdevc->colors.wts.num_components;
526     gx_color_index comp_bits = 0; /* todo: plane_mask */
527 
528     for (i = 0; i < ncomps; i++) {
529         if (pdevc->colors.wts.levels[i] != 0)
530             comp_bits |= ((gx_color_index)1) << i;
531     }
532     *pcomp_bits = comp_bits;
533 
534     return 0;
535 }
536