summaryrefslogtreecommitdiff
path: root/contrib/pcl3/eprn/eprnrend.c
blob: 49f806941fad3a92583db011da5264ce0261fbfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
/******************************************************************************
  File:     $Id: eprnrend.c,v 1.15 2001/08/01 05:12:56 Martin Rel $
  Contents: Colour rendering functionality for the ghostscript device 'eprn'
  Author:   Martin Lottermoser, Greifswaldstrasse 28, 38124 Braunschweig,
            Germany. E-mail: Martin.Lottermoser@t-online.de.

*******************************************************************************
*									      *
*	Copyright (C) 2000, 2001 by Martin Lottermoser			      *
*	All rights reserved						      *
*									      *
*******************************************************************************

  Preprocessor variables:

    EPRN_TRACE
        Define this to enable tracing. Only useful for development.

    EPRN_TRAILING_BIT_BUG_FIXED
        Define this to deactivate compensation for a bug in ghostscript which
        leads to the last pixel in an RGB line being black instead of white.
        This occurs at least in gs 6.01 and 6.50. The correction covers only
        the one-bit-per-colorant case and is equivalent to clipping the pixel.

*******************************************************************************

  The eprn device uses 'gx_color_index' values with varying interpretations,
  depending on the colour model and the rendering method, and stores them at
  different pixmaps depths, normally using the smallest depth which can
  accommodate all colorants at the same number of bits per colorant.

  To simplify matters, a field for the black component is always included, even
  for RGB and CMY, i.e., there are either 1 or 4 bit fields in a
  'gx_color_index' value. If there are 4, the interpretation is either YMCK or
  BGRK, looking from left to right (most to least significant). The width of
  the fields can be found in the 'bits_per_colorant' variable in the eprn part
  of the device instance.

  Within each colorant field, not all bits need be used. Except when using the
  *_max() colour mapping functions, the values returned by
  eprn_bits_for_levels() for the parameters 'black_levels' and
  'non_black_levels' determine the number of bits which are actually
  meaningful. Only the last (least significant) bits are used.

******************************************************************************/

/*****************************************************************************/

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE	500
#endif

/* Special Aladdin header, must be included before <sys/types.h> on some
   platforms (e.g., FreeBSD). */
#include "std.h"

/* Standard headers */
#include "assert_.h"
#include <stdlib.h>

/* Ghostscript headers */
#ifdef EPRN_TRACE
#include "gdebug.h"
#endif	/* EPRN_TRACE */

/* Special headers */
#include "gdeveprn.h"

/*****************************************************************************/

/* Macros for 'gx_color_index' values used mainly for non-monochrome modes and
   a pixmap depth of 4 */

/* Colorants bits, numbered from 0 on the right to 3 on the left */
#define COLORANT_0_BIT	1U
#define COLORANT_1_BIT	2U
#define COLORANT_2_BIT	4U
#define COLORANT_3_BIT	8U

/* Alias names for the bits in particular colour models */
#define BLACK_BIT	COLORANT_0_BIT
#define CYAN_BIT	COLORANT_1_BIT
#define MAGENTA_BIT	COLORANT_2_BIT
#define YELLOW_BIT	COLORANT_3_BIT
#define RED_BIT		COLORANT_1_BIT
#define GREEN_BIT	COLORANT_2_BIT
#define BLUE_BIT	COLORANT_3_BIT

/* Bit plane indices for splitting */
#define COLORANT_0_INDEX	0
#define COLORANT_1_INDEX	1
#define COLORANT_2_INDEX	2
#define COLORANT_3_INDEX	3

/*  Macro to extract the dominant 8 bits from a 'gx_color_value'. This
    definition assumes that 'gx_color_value' uses the least significant 16 bits
    of the underlying type (unsigned short). Splitting this part off looks
    inefficient because left shifts will usually follow, but I'm relying on the
    compiler to be sufficiently intelligent to eliminate this inefficiency.
    This way the code is easier to check.
    The type cast is needed to prevent problems with negative values on
    platforms where 'gx_color_index' has more bits than 'int'.
 */
#define dominant_8bits(value)	((unsigned int)((value) >> 8))

/******************************************************************************

  Function: eprn_number_of_bitplanes

  Total number of bit planes returned by eprn_get_planes().
  This value is constant while the device is open.

******************************************************************************/

unsigned int eprn_number_of_bitplanes(eprn_Device *dev)
{
  return dev->eprn.output_planes;
}

/******************************************************************************

  Function: eprn_number_of_octets

  Maximal lengths, in terms of the number of 'eprn_Octet' instances, for each
  bit plane returned by eprn_get_planes() for this device. These values
  are constant while the device is open.

******************************************************************************/

void eprn_number_of_octets(eprn_Device *dev, unsigned int lenghts[])
{
  unsigned int j, length;

  length = (dev->eprn.octets_per_line + dev->color_info.depth - 1)/
      dev->color_info.depth;
   /* This results in length >= ceiling((number of pixels per line)/8)
      because:
              8 * octets_per_line >= pixels_per_line * depth
        <==>  octets_per_line/depth >= pixels_per_line/8
      where division is to be understood as exact.
    */

  for (j = 0; j < dev->eprn.output_planes; j++) lenghts[j] = length;

  return;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_RGB

  Colour mapping function for the process colour model 'DeviceRGB' and
  2 intensity levels per colorant.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_RGB(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  static const gx_color_value half = gx_max_color_value/2;
  gx_color_index value = 0;
  const eprn_Device *dev = (eprn_Device *)device;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_RGB() called for RGB = (%hu, %hu, %hu),\n",
    red, green, blue);
#endif

  assert(dev->eprn.colour_model == eprn_DeviceRGB);

  if (red   > half) value |= RED_BIT;
  if (green > half) value |= GREEN_BIT;
  if (blue  > half) value |= BLUE_BIT;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_CMY_or_K

  Colour mapping function for the native colour spaces DeviceGray and DeviceRGB
  and a process colour model using a selection of CMYK colorants with at most
  2 intensity levels per colorant. This function must not be called for the
  process colour models 'DeviceRGB' and 'DeviceCMYK'.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_CMY_or_K(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  static const gx_color_value half = gx_max_color_value/2;
  gx_color_index value = (CYAN_BIT | MAGENTA_BIT | YELLOW_BIT);
  const eprn_Device *dev = (eprn_Device *)device;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_CMY_or_K() called for RGB = (%hu, %hu, %hu),\n",
    red, green, blue);
#endif

  assert((dev->eprn.colour_model == eprn_DeviceGray && red == green &&
          green == blue && (blue == 0 || blue == gx_max_color_value)) ||
         dev->eprn.colour_model == eprn_DeviceCMY ||
         dev->eprn.colour_model == eprn_DeviceCMY_plus_K);

  /* Map to CMY */
  if (red   > half) value &= ~CYAN_BIT;
  if (green > half) value &= ~MAGENTA_BIT;
  if (blue  > half) value &= ~YELLOW_BIT;

  /* Remap composite black to true black if available */
  if (dev->eprn.colour_model != eprn_DeviceCMY &&
      value == (CYAN_BIT | MAGENTA_BIT | YELLOW_BIT))
    value = BLACK_BIT;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_RGB_flex

  This is a 'map_rgb_color' method for the process colour model 'DeviceRGB'
  supporting any number of intensity levels.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_RGB_flex(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  gx_color_index value = 0;
  gx_color_value step;
  unsigned int level;
  const eprn_Eprn *eprn = &((eprn_Device *)device)->eprn;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_RGB_flex() called for RGB = (%hu, %hu, %hu),\n",
    red, green, blue);
#endif

  /* See the discussion in eprn_map_cmyk_color_flex() below. */

  step = gx_max_color_value/eprn->non_black_levels;

  /* The order has to be BGR from left to right */
  level = blue/step;
  if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
  value = level << eprn->bits_per_colorant;
  level = green/step;
  if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
  value = (value | level) << eprn->bits_per_colorant;
  level = red/step;
  if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
  value = (value | level) << eprn->bits_per_colorant;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_CMY_or_K_flex

  This is a flexible 'map_rgb_color' method. It must not be called for the
  process colour models 'DeviceRGB' and 'DeviceCMYK'.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_CMY_or_K_flex(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  const eprn_Device *dev = (eprn_Device *)device;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_CMY_or_K_flex() called for "
      "RGB = (%hu, %hu, %hu).\n",
    red, green, blue);
#endif

  /* Treat pure grey levels differently if we have black. This implies that for
     CMY+K only "true" grey shades will be printed with black ink, all others
     will be mixed from CMY. */
  gx_color_value tmpcv[4];
  if (dev->eprn.colour_model != eprn_DeviceCMY && red == green && green == blue) {
    tmpcv[0] = 0; tmpcv[1] = 0; tmpcv[2] = 0;
    tmpcv[3] = gx_max_color_value - red;
    return eprn_map_cmyk_color_flex(device, tmpcv);

  }
  tmpcv[0] = gx_max_color_value - red;
  tmpcv[1] = gx_max_color_value - green;
  tmpcv[2] = gx_max_color_value - blue;
  tmpcv[3] = 0;
  return eprn_map_cmyk_color_flex(device, tmpcv);
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_RGB_max

  Colour mapping function for the process colour model 'DeviceRGB' retaining as
  much information as possible.

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_RGB_max(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  gx_color_index value;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_RGB_max() called for RGB = (%hu, %hu, %hu),\n",
    red, green, blue);
#endif

  value  = dominant_8bits(red)   <<  8;
  value |= dominant_8bits(green) << 16;
  value |= dominant_8bits(blue)  << 24;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%08lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_rgb_color_for_CMY_or_K_max

  "Maximal" colour mapping function for the process colour models "DeviceGray",
  "DeviceCMY", and "CMY+K".

******************************************************************************/

gx_color_index eprn_map_rgb_color_for_CMY_or_K_max(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value red = cv[0], green = cv[1], blue = cv[2];
  const eprn_Device *dev = (eprn_Device *)device;

#ifdef EPRN_TRACE
  if_debug3(EPRN_TRACE_CHAR,
    "! eprn_map_rgb_color_for_CMY_or_K_max() called for "
      "RGB = (%hu, %hu, %hu).\n",
    red, green, blue);
#endif

  gx_color_value tmpcv[4];
  if (dev->eprn.colour_model == eprn_DeviceGray) {
    tmpcv[0] = 0; tmpcv[1] = 0; tmpcv[2] = 0;
    tmpcv[3] = gx_max_color_value - red;
    return eprn_map_cmyk_color_max(device, tmpcv);
  }
  /* Note that the conversion from composite black to true black for CMY+K can
     only happen at the output pixel level, not here. */
  tmpcv[0] = gx_max_color_value - red;
  tmpcv[1] = gx_max_color_value - green;
  tmpcv[2] = gx_max_color_value - blue;
  tmpcv[3] = 0;
  return eprn_map_cmyk_color_max(device, tmpcv);
}

/******************************************************************************

  Function: eprn_map_color_rgb

  This function is a 'map_color_rgb' method.

  Such a method should return an RGB triple for the specified 'color'. This is
  apparently intended for devices supporting colour maps.

  The function param_HWColorMap() in gsdparam.c tries to compile such a
  colour map by calling this method repeatedly for all values from 0 to
  2^color_info.depth - 1, provided the depth is less than or equal to 8 and
  the process colour model is not DeviceCMYK. If the function for one of these
  values returns a negative code, the assumption seems to be that there is no
  such colour map. Because there is a default method which always returns zero,
  such a colour map is always constructed if a device does not implement its
  own 'map_color_rgb' method. This can be seen in the appearance of the
  "HWColorMap" page device parameter.

  The key purpose of this function is therefore to return a negative code.

******************************************************************************/

int eprn_map_color_rgb(gx_device *device, gx_color_index color,
  gx_color_value rgb[])
{
#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR,
    "! eprn_map_color_rgb() called for 0x%lX.\n", (unsigned long)color);
#endif

  /* Just to be safe we return defined values (white) */
  if (((eprn_Device *)device)->eprn.colour_model == eprn_DeviceRGB)
    rgb[0] = rgb[1] = rgb[2] = gx_max_color_value;
  else
    rgb[0] = rgb[1] = rgb[2] = 0;

  return -1;
}

/******************************************************************************

  Function: eprn_map_cmyk_color

  Colour mapping function for a process colour model of 'DeviceCMYK' with
  2 intensity levels for all colorants.

******************************************************************************/

gx_color_index eprn_map_cmyk_color(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value cyan = cv[0], magenta = cv[1], yellow = cv[2], black = cv[3];
  gx_color_index value = 0;
  static const gx_color_value threshold = gx_max_color_value/2;

#ifdef EPRN_TRACE
  if_debug4(EPRN_TRACE_CHAR,
    "! eprn_map_cmyk_color() called for CMYK = (%hu, %hu, %hu, %hu),\n",
      cyan, magenta, yellow, black);
#endif

  if (cyan    > threshold) value |= CYAN_BIT;
  if (magenta > threshold) value |= MAGENTA_BIT;
  if (yellow  > threshold) value |= YELLOW_BIT;
  if (black   > threshold) value |= BLACK_BIT;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_cmyk_color_flex

  This is a 'map_cmyk_color' method supporting arbitrary numbers of intensity
  levels. It may be called for every colour model except DeviceRGB. Colorants
  not present in the model will be ignored.

******************************************************************************/

gx_color_index eprn_map_cmyk_color_flex(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value cyan = cv[0], magenta = cv[1], yellow = cv[2], black = cv[3];
  gx_color_index value = 0;
  gx_color_value step;
  unsigned int level;
  const eprn_Eprn *eprn = &((eprn_Device *)device)->eprn;

#ifdef EPRN_TRACE
  if_debug4(EPRN_TRACE_CHAR,
    "! eprn_map_cmyk_color_flex() called for CMYK = (%hu, %hu, %hu, %hu),\n",
      cyan, magenta, yellow, black);
#endif

  /*  I can think of three linear methods to extract discrete values from a
      continuous intensity in the range [0, 1]:
      (a) multiply by the number of levels minus 1 and truncate,
      (b) multiply by the number of levels minus 1 and round, and
      (c) multiply by the number of levels and truncate, except for an
          intensity of 1 in which case one returns the number of levels minus 1.
      For intensity values which can be represented exactly, i.e.,

        intensity = i/(levels-1)	for some non-negative i < levels,

      these three methods are identical. (a) is however inappropriate here
      because for less than 32 levels ghostscript already provides intensity
      values which have been adjusted to a representable level. A rounding
      error could now result in a level which is too small by one. I prefer (c)
      because it gives equal shares to all levels.

      I'm using integer arithmetic here although floating point numbers would
      be more accurate. This routine may, however, be called quite frequently,
      and the loss in accuray is acceptable as long as the values determined
      for 'step' are large compared to the number of levels. If you consider
      "large" as meaning "10 times as large", the critical boundary is at about
      81 levels. The highest number of intensity levels at present supported by
      HP DeskJets is apparently 4.

      A more accurate implementation would determine 'step' as a floating point
      value, divide the intensity by it, and take the floor (entier) of the
      result as the component intensity.
  */

  /* The order has to be (YMC)(K) from left to right */
  if (eprn->colour_model != eprn_DeviceGray) {
    step = gx_max_color_value/eprn->non_black_levels;

    level = yellow/step;
    if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
    value = level << eprn->bits_per_colorant;
    level = magenta/step;
    if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
    value = (value | level) << eprn->bits_per_colorant;
    level = cyan/step;
    if (level >= eprn->non_black_levels) level = eprn->non_black_levels - 1;
    value = (value | level) << eprn->bits_per_colorant;
  }
  if (eprn->colour_model != eprn_DeviceCMY) {
    step = gx_max_color_value/eprn->black_levels;
    level = black/step;
    if (level >= eprn->black_levels) level = eprn->black_levels - 1;
    value |= level;
  }

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_cmyk_color_max

  This is a 'map_cmyk_color' method retaining as much colour information as
  possible. The reduction to the printer's capabilities must happen in the
  output routine.

******************************************************************************/

gx_color_index eprn_map_cmyk_color_max(gx_device *device,
  const gx_color_value cv[])
{
  gx_color_value cyan = cv[0], magenta = cv[1], yellow = cv[2], black = cv[3];
  gx_color_index value;

#ifdef EPRN_TRACE
  if_debug4(EPRN_TRACE_CHAR,
    "! eprn_map_cmyk_color_max() called for CMYK = (%hu, %hu, %hu, %hu),\n",
      cyan, magenta, yellow, black);
#endif

  value = dominant_8bits(black);
  value |= dominant_8bits(cyan)    <<  8;
  value |= dominant_8bits(magenta) << 16;
  value |= dominant_8bits(yellow)  << 24;

#ifdef EPRN_TRACE
  if_debug1(EPRN_TRACE_CHAR, "  returning 0x%08lX.\n", (unsigned long)value);
#endif
  return value;
}

/******************************************************************************

  Function: eprn_map_cmyk_color_glob

  Global eprn_map_cmyk_color for all cases handled above used to
  be able to work together with ghostscript 8.15 and CMYK model.

******************************************************************************/
gx_color_index eprn_map_cmyk_color_glob(gx_device *device, const gx_color_value cv[])
{
  const eprn_Eprn *eprn = &((eprn_Device *)device)->eprn;
  if (eprn->intensity_rendering == eprn_IR_FloydSteinberg)
    return eprn_map_cmyk_color_max(device, cv);
  else if (device->color_info.max_gray > 1 || device->color_info.max_color > 1)
    return eprn_map_cmyk_color_flex(device, cv);
  else
    return eprn_map_cmyk_color(device, cv);
}

/******************************************************************************

  Function: eprn_finalize

  This function fills the last octet in a set of bit planes with white if
  needed and sets the lengths of all these planes.

  'is_RGB' denotes whether the colour model is eprn_DeviceRGB,
  'non_black_levels' is the number of intensity levels for non-black colorants,
  'planes' the number of bit planes,
  'plane' points to an array of at least 'planes' bit planes,
  'ptr' points to an array of at least 'planes' pointers into the bit planes,
  'pixels' is the number of pixels written to the group of bit planes.

  If 'pixels' is divisible by 8, the 'ptr' pointers point to octets after
  the last completely written octets. Otherwise, they point to the last
  incompletely written octet in which the pixels written so far occupy the
  least significant bits.

******************************************************************************/

void eprn_finalize(bool is_RGB, unsigned int non_black_levels,
  int planes, eprn_OctetString *plane, eprn_Octet **ptr, int pixels)
{
  int j;

  /* Execute remaining left shifts in the last octet of the output planes when
     the number of pixels is not a multiple of 8, and fill with white on the
     right */
  if (pixels % 8 != 0) {
    int shift = 8 - pixels % 8;

    if (is_RGB) {
      /* White may be any intensity, but it's the same for all three colorants,
         and it's the highest. */
      eprn_Octet imax = non_black_levels - 1;
      int c, rgb_planes = eprn_bits_for_levels(non_black_levels);

      j = 0;	/* next output plane */

      /* Loop over RGB */
      for (c = 0; c < 3; c++) {
        eprn_Octet value = imax;
        int m;

        /* Loop over all planes for this colorant */
        for (m = 0; m < rgb_planes; m++, j++) {
          eprn_Octet bit = value & 1;
          int p;

          value = value >> 1;

          /* Put the bit into all remaining pixels for this plane */
          for (p = 0; p < shift; p++)
            *ptr[j] = (*ptr[j] << 1) | bit;
        }
      }
    }
    else /* White is zero */
      for (j = 0; j < planes; j++)
        *ptr[j] = *ptr[j] << shift;

    /* Advance all plane pointers by 1 */
    for (j = 0; j < planes; j++) ptr[j]++;
  }

  /* Set the lengths of the bit plane strings */
  for (j = 0; j < planes; j++) {
    if (pixels == 0) plane[j].length = 0;
    else plane[j].length = ptr[j] - plane[j].str;
  }

  return;
}

/******************************************************************************

  Function: split_line_le8

  This is the first of the "split_line implementations". See the body of
  eprn_get_planes() for the calling conventions common to them.

  This particular implementation has the following restrictions:
  - The pixmap depth must be a divisor of 8.

******************************************************************************/

static void split_line_le8(eprn_Device *dev, const eprn_Octet *line,
  int length, eprn_OctetString plane[])
{
  gx_color_index
    pixel;
  int
    black_planes,	/* number of planes to send for black */
    non_black_planes,	/* number of planes to send for each of CMY/RGB */
    j,
    k,
    pixels,		/* number of pixels transferred to bit planes */
    planes;		/* number of planes to send */
  eprn_Octet
    comp_mask = 0,	/* 'bits_per_component' 1s in the lowest part */
    pixel_mask = 0,	/* 'depth' 1s in the lowest part */
    *ptr[8];		/* pointers into planes (next octet to write to) */

  /* Number of planes to send */
  black_planes = eprn_bits_for_levels(dev->eprn.black_levels);
  non_black_planes = eprn_bits_for_levels(dev->eprn.non_black_levels);
  planes = black_planes + 3*non_black_planes;

  /* Initialize the bit plane pointers */
  for (j = 0; j < planes; j++) ptr[j] = plane[j].str;

  /* Determine some bit masks */
  for (j = 0; j < dev->color_info.depth; j++)
    pixel_mask = (pixel_mask << 1) | 1;
  for (j = 0; j < dev->eprn.bits_per_colorant; j++)
    comp_mask = (comp_mask << 1) | 1;

  /* Copy from 'line' to 'plane[]', converting Z format to XY format */
  pixels = 0;
  k = 0; /* Next octet index in the input line */
  while (k < length) {
    int l, m, p;

    /* Initialize plane storage if it's a new output octet */
    if (pixels % 8 == 0) for (j = 0; j < planes; j++) *ptr[j] = 0;

    /* Loop over pixels within the input octet, starting at the leftmost
       pixel (highest-order bits) */
    p = 8/dev->color_info.depth - 1;
    do {
      eprn_Octet comp;

      /* Extract pixel */
      pixel = (line[k] >> p*dev->color_info.depth) & pixel_mask;

      /* Extract components from the pixel and distribute each over its planes
       */
      comp = pixel & comp_mask;	/* black */
      for (j = 0; j < black_planes; j++) {
        *ptr[j] = (*ptr[j] << 1) | (comp & 1);
        comp >>= 1;
      }
      if (non_black_planes > 0) for (l = 1; l < 4; l++) {
        comp = (pixel >> l*dev->eprn.bits_per_colorant) & comp_mask;
        for (m = 0; m < non_black_planes; m++, j++) {
          *ptr[j] = (*ptr[j] << 1) | (comp & 1);
          comp >>= 1;
        }
      }

      pixels++;
      p--;
    } while (p >= 0);
    k++;

    /* Increase plane pointers if an output octet boundary has been reached */
    if (pixels % 8 == 0) for (j = 0; j < planes; j++) ptr[j]++;
  }

  eprn_finalize(dev->eprn.colour_model == eprn_DeviceRGB,
    dev->eprn.non_black_levels, planes, plane, ptr, pixels);

  return;
}

/******************************************************************************

  Function: split_line_ge8

  This split_line function has the following restrictions:
  - The pixmap depth must be a multiple of 8.
  - There may be at most 8 bits per colorant.
    (Hence the depth is at most 32.)
  - 'length' must be divisible by depth/8.

******************************************************************************/

static void split_line_ge8(eprn_Device *dev, const eprn_Octet *line,
  int length, eprn_OctetString plane[])
{
  gx_color_index
    pixel;
  int
    black_planes,	/* number of planes to send for black */
    non_black_planes,	/* number of planes to send for each of CMY/RGB */
    j,
    k,
    octets_per_pixel = dev->color_info.depth/8,
    pixels,		/* number of pixels transferred to bit planes */
    planes;		/* number of planes to send */
  eprn_Octet
    comp_mask = 0,	/* bits_per_component 1s in the lowest part */
    *ptr[32];		/* pointers into planes (next octet to write to) */

  /* Number of planes to send */
  black_planes = eprn_bits_for_levels(dev->eprn.black_levels);
  non_black_planes = eprn_bits_for_levels(dev->eprn.non_black_levels);
  planes = black_planes + 3*non_black_planes;

  /* Initialize the bit plane pointers */
  for (j = 0; j < planes; j++) ptr[j] = plane[j].str;

  /* Determine the component mask */
  for (j = 0; j < dev->eprn.bits_per_colorant; j++)
    comp_mask = (comp_mask << 1) | 1;

  /* Copy from 'line' to 'plane[]', converting Z format to XY format */
  pixels = 0;
  k = 0; /* Next octet index in the input line */
  while (k < length) {
    eprn_Octet comp;
    int l, m;

    /* Initialize plane storage if it's a new octet */
    if (pixels % 8 == 0) for (j = 0; j < planes; j++) *ptr[j] = 0;

    /* Reconstruct pixel from several octets */
    j = 0;
    pixel = line[k];
    do {
      j++; k++;
      if (j >= octets_per_pixel) break;
      pixel = (pixel << 8) | line[k];	/* MSB (Big Endian) */
    } while (1);

    /* Split and distribute over planes */
    comp = pixel & comp_mask;	/* black */
    for (j = 0; j < black_planes; j++) {
      *ptr[j] = (*ptr[j] << 1) | (comp & 1);
      comp >>= 1;
    }
    for (l = 1; l < 4; l++) {
      comp = (pixel >> l*dev->eprn.bits_per_colorant) & comp_mask;
      for (m = 0; m < non_black_planes; m++, j++) {
        *ptr[j] = (*ptr[j] << 1) | (comp & 1);
        comp >>= 1;
      }
    }

    pixels++;

    /* Increase plane pointers if an octet boundary has been reached */
    if (pixels % 8 == 0) for (j = 0; j < planes; j++) ptr[j]++;
  }

  eprn_finalize(dev->eprn.colour_model == eprn_DeviceRGB,
    dev->eprn.non_black_levels, planes, plane, ptr, pixels);

  return;
}

/******************************************************************************

  Function: split_line_3or4x1

  This function is a split_line() implementation for a non-monochrome colour
  model (3 or 4 components) with 1 bit per component.

******************************************************************************/

static void split_line_3or4x1(eprn_Device *dev, const eprn_Octet *line,
  int length, eprn_OctetString plane[])
{
  int
    from = (dev->eprn.colour_model == eprn_DeviceRGB ||
      dev->eprn.colour_model == eprn_DeviceCMY? 1: 0),
    j,
    k,
    l;
  eprn_Octet *ptr[4];	/* pointers into planes, indexed KCMY/-RGB */

  ptr[0] = NULL;	/* defensive programming */
  for (j = from; j < 4; j++) ptr[j] = plane[j-from].str;

  /*  Loop over the input line, taking four octets (8 pixels) at a time, as far
      as available, and split them into four output octets, one for each
      colorant.
  */
  k = 0;
  while (k < length) {
    eprn_Octet octet[4] = {0, 0, 0, 0};

    for (l = 0; l < 4 && k < length; l++, k++) {
      eprn_Octet part;
#define treat_quartet()						\
      octet[COLORANT_0_INDEX] <<= 1;				\
      if (part & COLORANT_0_BIT) octet[COLORANT_0_INDEX] |= 1;	\
      octet[COLORANT_1_INDEX] <<= 1;				\
      if (part & COLORANT_1_BIT) octet[COLORANT_1_INDEX] |= 1;	\
      octet[COLORANT_2_INDEX] <<= 1;				\
      if (part & COLORANT_2_BIT) octet[COLORANT_2_INDEX] |= 1;	\
      octet[COLORANT_3_INDEX] <<= 1;				\
      if (part & COLORANT_3_BIT) octet[COLORANT_3_INDEX] |= 1;

      /* Upper four bits */
      part = (line[k] >> 4) & 0x0F;
      treat_quartet()

      /* Lower four bits */
      part = line[k] & 0x0F;
      treat_quartet()

#undef treat_quartet
    }
    if (l < 4) {
      for (j = from; j < 4; j++) octet[j] <<= 8 - 2*l;
      if (dev->eprn.colour_model == eprn_DeviceRGB) {
        /* Add white in the last 8 - 2*l pixels */
        for (j = 1; j < 4; j++) {
          int k;
          /* We add two pixels at a time */
          for (k = 3 - l; k >= 0; k--) octet[j] |= 0x03 << k;
        }
      }
    }
    for (j = from; j < 4; j++) *(ptr[j]++) = octet[j];
  }

  /* Set the lengths of the bit plane strings */
  for (j = 0; j < dev->eprn.output_planes; j++) {
    if (length == 0) plane[j].length = 0;
    else plane[j].length = ptr[from + j] - plane[j].str;
  }

  return;
}

/******************************************************************************

  Function: split_line_4x2

  This is a split_line() implementation for 4 colour components (i.e., CMYK)
  with 3 or 4 levels each (2 bit planes to send for each component).

******************************************************************************/

static void split_line_4x2(eprn_Device *dev, const eprn_Octet *line,
  int length, eprn_OctetString plane[])
{
  gx_color_index
    pixel;
  int
    j,
    k;
  eprn_Octet
    *ptr[8];		/* pointers into planes (next octet to write to) */

  /* Initialize the bit plane pointers */
  for (j = 0; j < 8; j++) ptr[j] = plane[j].str;

  /* Copy from 'line' to 'plane[]', converting Z format to XY format */
  for (k = 0; k < length; k++) {
    /* k is the index of the next octet in the input line and the number of
       pixels processed so far. */

    /* Initialize plane storage if it's a new octet */
    if (k % 8 == 0) for (j = 0; j < 8; j++) *ptr[j] = 0;

    /* Fetch pixel */
    pixel = line[k];

    /* Split and distribute over planes */
    *ptr[0] = (*ptr[0] << 1) | (pixel & 0x01);
#define assign_bit(index) \
    *ptr[index] = (*ptr[index] << 1) | ((pixel >> index) & 0x01)
    assign_bit(1);
    assign_bit(2);
    assign_bit(3);
    assign_bit(4);
    assign_bit(5);
    assign_bit(6);
    assign_bit(7);
#undef assign_bit

    /* Increase plane pointers if an output octet boundary has been reached */
    if (k % 8 == 7) for (j = 0; j < 8; j++) ptr[j]++;
  }

  /* Execute remaining left shifts in the last octet of the output planes when
     the number of pixels is not a multiple of 8 */
  k = length % 8;
  if (k != 0) {
    int shift = 8 - k;
    for (j = 0; j < 8; j++)
      *(ptr[j]++) <<= shift;
  }

  /* Set the lengths of the bit plane strings */
  for (j = 0; j < 8; j++) {
    if (length == 0) plane[j].length = 0;
    else plane[j].length = ptr[j] - plane[j].str;
  }

  return;
}

/******************************************************************************

  Function: eprn_fetch_scan_line

  If there is a next scan line with y coordinate 'dev->eprn.next_y', this
  function fetches it into '*line' and returns zero. Otherwise the function
  returns a non-zero value.

  The storage allocated for 'line->str' must be at least of size
  'dev->eprn.octets_per_line'.

  On success, the 'length' field in 'line' does not include trailing zero
  pixels.

******************************************************************************/

int eprn_fetch_scan_line(eprn_Device *dev, eprn_OctetString *line)
{
  int rc;
  const eprn_Octet *str;

  rc = gdev_prn_copy_scan_lines((gx_device_printer *)dev, dev->eprn.next_y,
    line->str, dev->eprn.octets_per_line);
   /* gdev_prn_copy_scan_lines() returns the number of scan lines it fetched
      or a negative value on error. The number of lines to fetch is the value
      of the last argument divided by the length of a single line, hence in
      this case 1. */
  if (rc != 1) return 1;

  /* Set the length to ignore trailing zero octets in the scan line */
  str = line->str + (dev->eprn.octets_per_line - 1);
  while (str > line->str && *str == 0) str--;
  if (*str == 0) line->length = 0;
  else line->length = str - line->str + 1;

  /* Ensure we have an integral number of pixels in the line */
  if (dev->color_info.depth > 8) {
    int rem;
    unsigned int octets_per_pixel = dev->color_info.depth/8;
      /* If the depth is larger than 8, it is a multiple of 8. */
    rem = line->length % octets_per_pixel;
    if (rem != 0) line->length += octets_per_pixel - rem;
  }

#if 0 && defined(EPRN_TRACE)
  if (gs_debug_c(EPRN_TRACE_CHAR)) {
    int j;
    dmlprintf(dev->memory, "! eprn_fetch_scan_line(): Fetched ");
    if (line->length == 0) dmprintf(dev->memory, "empty scan line.");
    else {
      dmprintf(dev->memory, "scan line: 0x");
      for (j = 0; j < line->length; j++) dmprintf1(dev->memory, "%02X", line->str[j]);
    }
    dmlprintf(dev->memory, "\n");
  }
#endif

  return 0;
}

/******************************************************************************

  Function: eprn_get_planes

  For the description of this function, see gdeveprn.h.

******************************************************************************/

int eprn_get_planes(eprn_Device *dev, eprn_OctetString bitplanes[])
{
  eprn_OctetString *line; /* where to copy the scan line from the prn device */
  int
    j,
    rc;

  /* Avoid a copying step for a depth of 1 */
  if (dev->color_info.depth == 1) line = bitplanes;
  else line = &dev->eprn.scan_line;

  /* Fetch the scan line if available */
  if (dev->eprn.intensity_rendering == eprn_IR_FloydSteinberg &&
      dev->eprn.next_y == 0) return 1;
  rc = eprn_fetch_scan_line(dev, line);
  if (rc == 0) dev->eprn.next_y++;
  else {
    if (dev->eprn.intensity_rendering != eprn_IR_FloydSteinberg) return 1;
    dev->eprn.next_y = 0;
  }

  if (dev->color_info.depth == 1) return 0;

  if (dev->eprn.intensity_rendering == eprn_IR_FloydSteinberg) {
    eprn_OctetString tmp;

    eprn_split_FS(&dev->eprn.next_scan_line, &dev->eprn.scan_line,
      dev->eprn.octets_per_line,
      dev->eprn.colour_model,
      dev->eprn.black_levels, dev->eprn.non_black_levels,
      bitplanes);

    /* Switch 'next_scan_line' to refer to what is currently 'scan_line' */
    tmp = dev->eprn.next_scan_line;
    dev->eprn.next_scan_line = dev->eprn.scan_line;
    dev->eprn.scan_line = tmp;
  }
  else {
    /*  Here we split multi-bit pixels which are already adapted to the
        printer's capabilities.

        All the functions called here have the following signature:

          static void split_line...(eprn_Device *dev, const eprn_Octet *line,
            int length, eprn_OctetString plane[])

        Such a "split_line implementation" must take the scan line of length
        'length', pointed to by 'line', split it into bit planes according to
        the state of 'dev', and return these planes via 'plane'. The length
        fields of the planes must be set. Trailing zero octets should not be
        removed because it's done here afterwards anyway.
    */

    if (dev->eprn.colour_model == eprn_DeviceGray)
      split_line_le8(dev, line->str, line->length, bitplanes);
    else {
      if (dev->eprn.bits_per_colorant == 1) {
#ifndef EPRN_TRAILING_BIT_BUG_FIXED
        if (dev->eprn.colour_model == eprn_DeviceRGB &&
            line->length == dev->eprn.octets_per_line) {
         /* At least gs 6.01 and 6.50 sometimes generate pixel lines where the
            last pixel is not white but black (last octet in 'line' is 0xE0
            instead of 0xEE; with pcl3 it shows up for A6 and A4, but not for
            A3, A5, US Letter, or US Legal).
            I'm overwriting it with white. */
#ifdef EPRN_TRACE
          if (gs_debug_c(EPRN_TRACE_CHAR)) {
            static bool already_noted = false;
            if (!already_noted && line->str[line->length - 1] != 0xEE) {
              dmlprintf1(dev->memory, "! eprn_get_planes(): "
                         "Line-terminating octet is 0x%02X.\n",
                         line->str[line->length - 1]);
              already_noted = true;
            }
          }
#endif	/* EPRN_TRACE */
          line->str[line->length - 1] |= RED_BIT | GREEN_BIT | BLUE_BIT;
        }
#endif	/* EPRN_TRAILING_BIT_BUG_FIXED */
        split_line_3or4x1(dev, line->str, line->length, bitplanes);
      }
      else if (dev->eprn.bits_per_colorant == 2 && dev->eprn.black_levels > 2 &&
          dev->eprn.non_black_levels > 2)
        split_line_4x2(dev, line->str, line->length, bitplanes);
      else if (dev->color_info.depth < 8)
        split_line_le8(dev, line->str, line->length, bitplanes);
      else split_line_ge8(dev, line->str, line->length, bitplanes);
    }
  }

  /* Reduce the lengths of the individual bit planes to not include trailing
     zero octets */
  for (j = 0; j < dev->eprn.output_planes; j++) {
    if (bitplanes[j].length > 0) {
      const eprn_Octet *str = bitplanes[j].str + (bitplanes[j].length - 1);
      while (str > bitplanes[j].str && *str == 0) str--;
      if (*str == 0) bitplanes[j].length = 0;
      else bitplanes[j].length = (str - bitplanes[j].str) + 1;
    }
  }

  return 0;
}