KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > fop > render > pcl > PCLGenerator


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 /* $Id$ */
19
20 package org.apache.fop.render.pcl;
21
22 import java.awt.Color JavaDoc;
23 import java.awt.Dimension JavaDoc;
24 import java.awt.Graphics2D JavaDoc;
25 import java.awt.color.ColorSpace JavaDoc;
26 import java.awt.geom.AffineTransform JavaDoc;
27 import java.awt.image.BufferedImage JavaDoc;
28 import java.awt.image.BufferedImageOp JavaDoc;
29 import java.awt.image.ByteLookupTable JavaDoc;
30 import java.awt.image.ColorConvertOp JavaDoc;
31 import java.awt.image.ColorModel JavaDoc;
32 import java.awt.image.DataBuffer JavaDoc;
33 import java.awt.image.IndexColorModel JavaDoc;
34 import java.awt.image.LookupOp JavaDoc;
35 import java.awt.image.Raster JavaDoc;
36 import java.awt.image.RenderedImage JavaDoc;
37 import java.awt.image.WritableRaster JavaDoc;
38 import java.io.DataOutputStream JavaDoc;
39 import java.io.IOException JavaDoc;
40 import java.io.OutputStream JavaDoc;
41 import java.text.DecimalFormat JavaDoc;
42 import java.text.DecimalFormatSymbols JavaDoc;
43 import java.util.Locale JavaDoc;
44
45 import org.apache.commons.io.output.ByteArrayOutputStream;
46 import org.apache.fop.util.UnitConv;
47 import org.apache.xmlgraphics.image.GraphicsUtil;
48
49 /**
50  * This class provides methods for generating PCL print files.
51  */

52 public class PCLGenerator {
53
54     /** The ESC (escape) character */
55     public static final char ESC = '\033';
56     
57     /** A list of all supported resolutions in PCL (values in dpi) */
58     public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600};
59     
60     /** Selects a 4x4 Bayer dither matrix (17 grayscales) */
61     public static final int DITHER_MATRIX_4X4 = 4;
62     /** Selects a 8x8 Bayer dither matrix (65 grayscales) */
63     public static final int DITHER_MATRIX_8X8 = 8;
64     
65     private final DecimalFormatSymbols JavaDoc symbols = new DecimalFormatSymbols JavaDoc(Locale.US);
66     private final DecimalFormat JavaDoc df2 = new DecimalFormat JavaDoc("0.##", symbols);
67     private final DecimalFormat JavaDoc df4 = new DecimalFormat JavaDoc("0.####", symbols);
68     
69     private OutputStream JavaDoc out;
70     
71     private boolean currentSourceTransparency = true;
72     private boolean currentPatternTransparency = true;
73     
74     private int maxBitmapResolution = PCL_RESOLUTIONS[PCL_RESOLUTIONS.length - 1];
75
76     /**
77      * true: Standard PCL shades are used (poor quality). false: user-defined pattern are used
78      * to create custom dither patterns for better grayscale quality.
79      */

80     private boolean usePCLShades = false;
81     
82     /**
83      * Main constructor.
84      * @param out the OutputStream to write the PCL stream to
85      */

86     public PCLGenerator(OutputStream JavaDoc out) {
87         this.out = out;
88     }
89     
90     /**
91      * Main constructor.
92      * @param out the OutputStream to write the PCL stream to
93      * @param maxResolution the maximum resolution to encode bitmap images at
94      */

95     public PCLGenerator(OutputStream JavaDoc out, int maxResolution) {
96         this(out);
97         boolean found = false;
98         for (int i = 0; i < PCL_RESOLUTIONS.length; i++) {
99             if (PCL_RESOLUTIONS[i] == maxResolution) {
100                 found = true;
101                 break;
102             }
103         }
104         if (!found) {
105             throw new IllegalArgumentException JavaDoc("Illegal value for maximum resolution!");
106         }
107         this.maxBitmapResolution = maxResolution;
108     }
109     
110     /** @return the OutputStream that this generator writes to */
111     public OutputStream JavaDoc getOutputStream() {
112         return this.out;
113     }
114     
115     /** @return the maximum resolution to encode bitmap images at */
116     public int getMaximumBitmapResolution() {
117         return this.maxBitmapResolution;
118     }
119     
120     /**
121      * Writes a PCL escape command to the output stream.
122      * @param cmd the command (without the ESCAPE character)
123      * @throws IOException In case of an I/O error
124      */

125     public void writeCommand(String JavaDoc cmd) throws IOException JavaDoc {
126         out.write(27); //ESC
127
out.write(cmd.getBytes("US-ASCII"));
128     }
129     
130     /**
131      * Writes raw text (in ISO-8859-1 encoding) to the output stream.
132      * @param s the text
133      * @throws IOException In case of an I/O error
134      */

135     public void writeText(String JavaDoc s) throws IOException JavaDoc {
136         out.write(s.getBytes("ISO-8859-1"));
137     }
138
139     /**
140      * Formats a double value with two decimal positions for PCL output.
141      *
142      * @param value value to format
143      * @return the formatted value
144      */

145     public final String JavaDoc formatDouble2(double value) {
146         return df2.format(value);
147     }
148
149     /**
150      * Formats a double value with four decimal positions for PCL output.
151      *
152      * @param value value to format
153      * @return the formatted value
154      */

155     public final String JavaDoc formatDouble4(double value) {
156         return df4.format(value);
157     }
158
159     /**
160      * Sends the universal end of language command (UEL).
161      * @throws IOException In case of an I/O error
162      */

163     public void universalEndOfLanguage() throws IOException JavaDoc {
164         writeCommand("%-12345X");
165     }
166     
167     /**
168      * Resets the printer and restores the user default environment.
169      * @throws IOException In case of an I/O error
170      */

171     public void resetPrinter() throws IOException JavaDoc {
172         writeCommand("E");
173     }
174     
175     /**
176      * Sends the job separation command.
177      * @throws IOException In case of an I/O error
178      */

179     public void separateJobs() throws IOException JavaDoc {
180         writeCommand("&l1T");
181     }
182     
183     /**
184      * Sends the form feed character.
185      * @throws IOException In case of an I/O error
186      */

187     public void formFeed() throws IOException JavaDoc {
188         out.write(12); //=OC ("FF", Form feed)
189
}
190
191     /**
192      * Sets the unit of measure.
193      * @param value the resolution value (units per inch)
194      * @throws IOException In case of an I/O error
195      */

196     public void setUnitOfMeasure(int value) throws IOException JavaDoc {
197         writeCommand("&u" + value + "D");
198     }
199     
200     /**
201      * Sets the raster graphics resolution
202      * @param value the resolution value (units per inch)
203      * @throws IOException In case of an I/O error
204      */

205     public void setRasterGraphicsResolution(int value) throws IOException JavaDoc {
206         writeCommand("*t" + value + "R");
207     }
208     
209     /**
210      * Selects the page size.
211      * @param selector the integer representing the page size
212      * @throws IOException In case of an I/O error
213      */

214     public void selectPageSize(int selector) throws IOException JavaDoc {
215         writeCommand("&l" + selector + "A");
216     }
217
218     /**
219      * Selects the paper source. The parameter is usually printer-specific. Usually, "1" is the
220      * default tray, "2" is the manual paper feed, "3" is the manual envelope feed, "4" is the
221      * "lower" tray and "7" is "auto-select". Consult the technical reference for your printer
222      * for all available values.
223      * @param selector the integer representing the paper source/tray
224      * @throws IOException In case of an I/O error
225      */

226     public void selectPaperSource(int selector) throws IOException JavaDoc {
227         writeCommand("&l" + selector + "H");
228     }
229
230     /**
231      * Clears the horizontal margins.
232      * @throws IOException In case of an I/O error
233      */

234     public void clearHorizontalMargins() throws IOException JavaDoc {
235         writeCommand("9");
236     }
237     
238     /**
239      * The Top Margin command designates the number of lines between
240      * the top of the logical page and the top of the text area.
241      * @param numberOfLines the number of lines (See PCL specification for details)
242      * @throws IOException In case of an I/O error
243      */

244     public void setTopMargin(int numberOfLines) throws IOException JavaDoc {
245         writeCommand("&l" + numberOfLines + "E");
246     }
247
248     /**
249      * The Text Length command can be used to define the bottom border. See the PCL specification
250      * for details.
251      * @param numberOfLines the number of lines
252      * @throws IOException In case of an I/O error
253      */

254     public void setTextLength(int numberOfLines) throws IOException JavaDoc {
255         writeCommand("&l" + numberOfLines + "F");
256     }
257
258     /**
259      * Sets the Vertical Motion Index (VMI).
260      * @param value the VMI value
261      * @throws IOException In case of an I/O error
262      */

263     public void setVMI(double value) throws IOException JavaDoc {
264         writeCommand("&l" + formatDouble4(value) + "C");
265     }
266
267     /**
268      * Sets the cursor to a new absolute coordinate.
269      * @param x the X coordinate (in millipoints)
270      * @param y the Y coordinate (in millipoints)
271      * @throws IOException In case of an I/O error
272      */

273     public void setCursorPos(double x, double y) throws IOException JavaDoc {
274         if (x < 0) {
275             //A negative x value will result in a relative movement so go to "0" first.
276
//But this will most probably have no effect anyway since you can't paint to the left
277
//of the logical page
278
writeCommand("&a0h" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V");
279         } else {
280             writeCommand("&a" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V");
281         }
282     }
283
284     /**
285      * Pushes the current cursor position on a stack (stack size: max 20 entries)
286      * @throws IOException In case of an I/O error
287      */

288     public void pushCursorPos() throws IOException JavaDoc {
289         writeCommand("&f0S");
290     }
291     
292     /**
293      * Pops the current cursor position from the stack.
294      * @throws IOException In case of an I/O error
295      */

296     public void popCursorPos() throws IOException JavaDoc {
297         writeCommand("&f1S");
298     }
299     
300     /**
301      * Changes the current print direction while maintaining the current cursor position.
302      * @param rotate the rotation angle (counterclockwise), one of 0, 90, 180 and 270.
303      * @throws IOException In case of an I/O error
304      */

305     public void changePrintDirection(int rotate) throws IOException JavaDoc {
306         writeCommand("&a" + rotate + "P");
307     }
308     
309     /**
310      * Enters the HP GL/2 mode.
311      * @param restorePreviousHPGL2Cursor true if the previous HP GL/2 pen position should be
312      * restored, false if the current position is maintained
313      * @throws IOException In case of an I/O error
314      */

315     public void enterHPGL2Mode(boolean restorePreviousHPGL2Cursor) throws IOException JavaDoc {
316         if (restorePreviousHPGL2Cursor) {
317             writeCommand("%0B");
318         } else {
319             writeCommand("%1B");
320         }
321     }
322     
323     /**
324      * Enters the PCL mode.
325      * @param restorePreviousPCLCursor true if the previous PCL cursor position should be restored,
326      * false if the current position is maintained
327      * @throws IOException In case of an I/O error
328      */

329     public void enterPCLMode(boolean restorePreviousPCLCursor) throws IOException JavaDoc {
330         if (restorePreviousPCLCursor) {
331             writeCommand("%0A");
332         } else {
333             writeCommand("%1A");
334         }
335     }
336     
337     /**
338      * Generate a filled rectangle at the current cursor position.
339      *
340      * @param w the width in millipoints
341      * @param h the height in millipoints
342      * @param col the fill color
343      * @throws IOException In case of an I/O error
344      */

345     protected void fillRect(int w, int h, Color JavaDoc col) throws IOException JavaDoc {
346         if ((w == 0) || (h == 0)) {
347             return;
348         }
349         if (h < 0) {
350             h *= -1;
351         } else {
352             //y += h;
353
}
354         setPatternTransparencyMode(false);
355         if (usePCLShades
356                 || Color.black.equals(col)
357                 || Color.white.equals(col)) {
358             writeCommand("*c" + formatDouble4(w / 100) + "h"
359                               + formatDouble4(h / 100) + "V");
360             int lineshade = convertToPCLShade(col);
361             writeCommand("*c" + lineshade + "G");
362             writeCommand("*c2P"); //Shaded fill
363
} else {
364             defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4);
365
366             writeCommand("*c" + formatDouble4(w / 100) + "h"
367                               + formatDouble4(h / 100) + "V");
368             writeCommand("*c32G");
369             writeCommand("*c4P"); //User-defined pattern
370
}
371         // Reset pattern transparency mode.
372
setPatternTransparencyMode(true);
373     }
374
375     //Bayer dither matrices (4x4 and 8x8 are derived from the 2x2 matrix)
376
private static final int[] BAYER_D2 = new int[] {0, 2, 3, 1};
377     private static final int[] BAYER_D4;
378     private static final int[] BAYER_D8;
379     
380     static {
381         BAYER_D4 = deriveBayerMatrix(BAYER_D2);
382         BAYER_D8 = deriveBayerMatrix(BAYER_D4);
383     }
384     
385     private static void setValueInMatrix(int[] dn, int half, int part, int idx, int value) {
386         int xoff = (part & 1) * half;
387         int yoff = (part & 2) * half * half;
388         int matrixIndex = yoff + ((int)(idx / half) * half * 2) + (idx % half) + xoff;
389         dn[matrixIndex] = value;
390     }
391     
392     private static int[] deriveBayerMatrix(int[] d) {
393         int[] dn = new int[d.length * 4];
394         int half = (int)Math.sqrt(d.length);
395         for (int part = 0; part < 4; part++) {
396             for (int i = 0, c = d.length; i < c; i++) {
397                 setValueInMatrix(dn, half, part, i, d[i] * 4 + BAYER_D2[part]);
398             }
399         }
400         return dn;
401     }
402     
403     /**
404      * Generates a user-defined pattern for a dithering pattern matching the grayscale value
405      * of the color given.
406      * @param col the color to create the pattern for
407      * @param patternID the pattern ID to use
408      * @param ditherMatrixSize the size of the Bayer dither matrix to use (4 or 8 supported)
409      * @throws IOException In case of an I/O error
410      */

411     public void defineGrayscalePattern(Color JavaDoc col, int patternID, int ditherMatrixSize)
412             throws IOException JavaDoc {
413         ByteArrayOutputStream baout = new ByteArrayOutputStream();
414         DataOutputStream JavaDoc data = new DataOutputStream JavaDoc(baout);
415         data.writeByte(0); //Format
416
data.writeByte(0); //Continuation
417
data.writeByte(1); //Pixel Encoding
418
data.writeByte(0); //Reserved
419
data.writeShort(8); //Width in Pixels
420
data.writeShort(8); //Height in Pixels
421
//data.writeShort(600); //X Resolution (didn't manage to get that to work)
422
//data.writeShort(600); //Y Resolution
423
int gray255 = convertToGray(col.getRed(), col.getGreen(), col.getBlue());
424         
425         byte[] pattern;
426         if (ditherMatrixSize == 8) {
427             int gray65 = gray255 * 65 / 255;
428             
429             pattern = new byte[BAYER_D8.length / 8];
430             
431             for (int i = 0, c = BAYER_D8.length; i < c; i++) {
432                 boolean dot = !(BAYER_D8[i] < gray65 - 1);
433                 if (dot) {
434                     int byteIdx = i / 8;
435                     pattern[byteIdx] |= 1 << (i % 8);
436                 }
437             }
438         } else {
439             int gray17 = gray255 * 17 / 255;
440             
441             //Since a 4x4 pattern did not work, the 4x4 pattern is applied 4 times to an
442
//8x8 pattern. Maybe this could be changed to use an 8x8 bayer dither pattern
443
//instead of the 4x4 one.
444
pattern = new byte[BAYER_D4.length / 8 * 4];
445             
446             for (int i = 0, c = BAYER_D4.length; i < c; i++) {
447                 boolean dot = !(BAYER_D4[i] < gray17 - 1);
448                 if (dot) {
449                     int byteIdx = i / 4;
450                     pattern[byteIdx] |= 1 << (i % 4);
451                     pattern[byteIdx] |= 1 << ((i % 4) + 4);
452                     pattern[byteIdx + 4] |= 1 << (i % 4);
453                     pattern[byteIdx + 4] |= 1 << ((i % 4) + 4);
454                 }
455             }
456         }
457         data.write(pattern);
458         if ((baout.size() % 2) > 0) {
459             baout.write(0);
460         }
461         writeCommand("*c" + patternID + "G");
462         writeCommand("*c" + baout.size() + "W");
463         baout.writeTo(this.out);
464         writeCommand("*c4Q"); //temporary pattern
465
}
466
467     /**
468      * Sets the source transparency mode.
469      * @param transparent true if transparent, false for opaque
470      * @throws IOException In case of an I/O error
471      */

472     public void setSourceTransparencyMode(boolean transparent) throws IOException JavaDoc {
473         setTransparencyMode(transparent, currentPatternTransparency);
474     }
475
476     /**
477      * Sets the pattern transparency mode.
478      * @param transparent true if transparent, false for opaque
479      * @throws IOException In case of an I/O error
480      */

481     public void setPatternTransparencyMode(boolean transparent) throws IOException JavaDoc {
482         setTransparencyMode(currentSourceTransparency, transparent);
483     }
484
485     /**
486      * Sets the transparency modes.
487      * @param source source transparency: true if transparent, false for opaque
488      * @param pattern pattern transparency: true if transparent, false for opaque
489      * @throws IOException In case of an I/O error
490      */

491     public void setTransparencyMode(boolean source, boolean pattern) throws IOException JavaDoc {
492         if (source != currentSourceTransparency && pattern != currentPatternTransparency) {
493             writeCommand("*v" + (source ? '0' : '1') + "n" + (pattern ? '0' : '1') + "O");
494         } else if (source != currentSourceTransparency) {
495             writeCommand("*v" + (source ? '0' : '1') + "N");
496         } else if (pattern != currentPatternTransparency) {
497             writeCommand("*v" + (pattern ? '0' : '1') + "O");
498         }
499         this.currentSourceTransparency = source;
500         this.currentPatternTransparency = pattern;
501     }
502
503     /**
504      * Convert an RGB color value to a grayscale from 0 to 100.
505      * @param r the red component
506      * @param g the green component
507      * @param b the blue component
508      * @return the gray value
509      */

510     public final int convertToGray(int r, int g, int b) {
511         return (r * 30 + g * 59 + b * 11) / 100;
512     }
513     
514     /**
515      * Convert a Color value to a PCL shade value (0-100).
516      * @param col the color
517      * @return the PCL shade value (100=black)
518      */

519     public final int convertToPCLShade(Color JavaDoc col) {
520         float gray = convertToGray(col.getRed(), col.getGreen(), col.getBlue()) / 255f;
521         return (int)(100 - (gray * 100f));
522     }
523     
524     /**
525      * Selects the current grayscale color (the given color is converted to grayscales).
526      * @param col the color
527      * @throws IOException In case of an I/O error
528      */

529     public void selectGrayscale(Color JavaDoc col) throws IOException JavaDoc {
530         if (Color.black.equals(col)) {
531             selectCurrentPattern(0, 0); //black
532
} else if (Color.white.equals(col)) {
533             selectCurrentPattern(0, 1); //white
534
} else {
535             if (usePCLShades) {
536                 selectCurrentPattern(convertToPCLShade(col), 2);
537             } else {
538                 defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4);
539                 selectCurrentPattern(32, 4);
540             }
541         }
542     }
543     
544     /**
545      * Select the current pattern
546      * @param patternID the pattern ID (<ESC>*c#G command)
547      * @param pattern the pattern type (<ESC>*v#T command)
548      * @throws IOException In case of an I/O error
549      */

550     public void selectCurrentPattern(int patternID, int pattern) throws IOException JavaDoc {
551         if (pattern > 1) {
552             writeCommand("*c" + patternID + "G");
553         }
554         writeCommand("*v" + pattern + "T");
555     }
556
557     /**
558      * Indicates whether an image is a monochrome (b/w) image.
559      * @param img the image
560      * @return true if it's a monochrome image
561      */

562     public static boolean isMonochromeImage(RenderedImage JavaDoc img) {
563         ColorModel JavaDoc cm = img.getColorModel();
564         if (cm instanceof IndexColorModel JavaDoc) {
565             IndexColorModel JavaDoc icm = (IndexColorModel JavaDoc)cm;
566             return icm.getMapSize() == 2;
567         } else {
568             return false;
569         }
570     }
571     
572     /**
573      * Indicates whether an image is a grayscale image.
574      * @param img the image
575      * @return true if it's a grayscale image
576      */

577     public static boolean isGrayscaleImage(RenderedImage JavaDoc img) {
578         return (img.getColorModel().getColorSpace().getNumComponents() == 1);
579     }
580     
581     private MonochromeBitmapConverter createMonochromeBitmapConverter() {
582         MonochromeBitmapConverter converter = null;
583         try {
584             String JavaDoc clName = "org.apache.fop.render.pcl.JAIMonochromeBitmapConverter";
585             Class JavaDoc clazz = Class.forName(clName);
586             converter = (MonochromeBitmapConverter)clazz.newInstance();
587         } catch (ClassNotFoundException JavaDoc cnfe) {
588             // Class was not compiled so is not available. Simply ignore.
589
} catch (LinkageError JavaDoc le) {
590             // This can happen if fop was build with support for a
591
// particular provider (e.g. a binary fop distribution)
592
// but the required support files (i.e. JAI) are not
593
// available in the current runtime environment.
594
// Simply continue with the backup implementation.
595
} catch (InstantiationException JavaDoc e) {
596             // Problem instantiating the class, simply continue with the backup implementation
597
} catch (IllegalAccessException JavaDoc e) {
598             // Problem instantiating the class, simply continue with the backup implementation
599
}
600         if (converter == null) {
601             converter = new DefaultMonochromeBitmapConverter();
602         }
603         return converter;
604     }
605
606     private int calculatePCLResolution(int resolution) {
607         return calculatePCLResolution(resolution, false);
608     }
609     
610     /**
611      * Calculates the ideal PCL resolution for a given resolution.
612      * @param resolution the input resolution
613      * @param increased true if you want to go to a higher resolution, for example if you
614      * convert grayscale or color images to monochrome images so dithering has
615      * a chance to generate better quality.
616      * @return the resulting PCL resolution (one of 75, 100, 150, 200, 300, 600)
617      */

618     private int calculatePCLResolution(int resolution, boolean increased) {
619         int choice = -1;
620         for (int i = PCL_RESOLUTIONS.length - 2; i >= 0; i--) {
621             if (resolution > PCL_RESOLUTIONS[i]) {
622                 int idx = i + 1;
623                 if (idx < PCL_RESOLUTIONS.length - 2) {
624                     idx += increased ? 2 : 0;
625                 } else if (idx < PCL_RESOLUTIONS.length - 1) {
626                     idx += increased ? 1 : 0;
627                 }
628                 choice = idx;
629                 break;
630                 //return PCL_RESOLUTIONS[idx];
631
}
632         }
633         if (choice < 0) {
634             choice = (increased ? 2 : 0);
635         }
636         while (choice > 0 && PCL_RESOLUTIONS[choice] > getMaximumBitmapResolution()) {
637             choice--;
638         }
639         return PCL_RESOLUTIONS[choice];
640     }
641     
642     private boolean isValidPCLResolution(int resolution) {
643         return resolution == calculatePCLResolution(resolution);
644     }
645     
646     private Dimension JavaDoc getAdjustedDimension(Dimension JavaDoc orgDim, double orgResolution,
647             int pclResolution) {
648         if (orgResolution == pclResolution) {
649             return orgDim;
650         } else {
651             Dimension JavaDoc result = new Dimension JavaDoc();
652             result.width = (int)Math.round((double)orgDim.width * pclResolution / orgResolution);
653             result.height = (int)Math.round((double)orgDim.height * pclResolution / orgResolution);
654             return result;
655         }
656     }
657     
658     //Threshold table to convert an alpha channel (8-bit) into a clip mask (1-bit)
659
private static final byte[] THRESHOLD_TABLE = new byte[256];
660     static { // Initialize the arrays
661
for (int i = 0; i < 256; i++) {
662             THRESHOLD_TABLE[i] = (byte) ((i < 240) ? 255 : 0);
663         }
664     }
665     
666     private RenderedImage JavaDoc getMask(RenderedImage JavaDoc img, Dimension JavaDoc targetDim) {
667         ColorModel JavaDoc cm = img.getColorModel();
668         if (cm.hasAlpha()) {
669             BufferedImage JavaDoc alpha = new BufferedImage JavaDoc(img.getWidth(), img.getHeight(),
670                     BufferedImage.TYPE_BYTE_GRAY);
671             Raster JavaDoc raster = img.getData();
672             GraphicsUtil.copyBand(raster, cm.getNumColorComponents(), alpha.getRaster(), 0);
673
674             BufferedImageOp JavaDoc op1 = new LookupOp JavaDoc(new ByteLookupTable JavaDoc(0, THRESHOLD_TABLE), null);
675             BufferedImage JavaDoc alphat = op1.filter(alpha, null);
676
677             BufferedImage JavaDoc mask;
678             if (true) {
679                 mask = new BufferedImage JavaDoc(targetDim.width, targetDim.height,
680                         BufferedImage.TYPE_BYTE_BINARY);
681             } else {
682                 byte[] arr = {(byte)0, (byte)0xff};
683                 ColorModel JavaDoc colorModel = new IndexColorModel JavaDoc(1, 2, arr, arr, arr);
684                 WritableRaster JavaDoc wraster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE,
685                                                    targetDim.width, targetDim.height, 1, 1, null);
686                 mask = new BufferedImage JavaDoc(colorModel, wraster, false, null);
687             }
688             
689             Graphics2D JavaDoc g2d = mask.createGraphics();
690             try {
691                 AffineTransform JavaDoc at = new AffineTransform JavaDoc();
692                 double sx = targetDim.getWidth() / img.getWidth();
693                 double sy = targetDim.getHeight() / img.getHeight();
694                 at.scale(sx, sy);
695                 g2d.drawRenderedImage(alphat, at);
696             } finally {
697                 g2d.dispose();
698             }
699             /*
700             try {
701                 BatchDiffer.saveAsPNG(alpha, new java.io.File("D:/out-alpha.png"));
702                 BatchDiffer.saveAsPNG(mask, new java.io.File("D:/out-mask.png"));
703             } catch (IOException e) {
704                 e.printStackTrace();
705             }*/

706             return mask;
707         } else {
708             return null;
709         }
710     }
711
712     /**
713      * Paint a bitmap at the current cursor position. The bitmap is converted to a monochrome
714      * (1-bit) bitmap image.
715      * @param img the bitmap image
716      * @param targetDim the target Dimention (in mpt)
717      * @param sourceTransparency true if the background should not be erased
718      * @throws IOException In case of an I/O error
719      */

720     public void paintBitmap(RenderedImage JavaDoc img, Dimension JavaDoc targetDim, boolean sourceTransparency)
721                 throws IOException JavaDoc {
722         double targetResolution = img.getWidth() / UnitConv.mpt2in(targetDim.width);
723         int resolution = (int)Math.round(targetResolution);
724         int effResolution = calculatePCLResolution(resolution, true);
725         Dimension JavaDoc orgDim = new Dimension JavaDoc(img.getWidth(), img.getHeight());
726         Dimension JavaDoc effDim = getAdjustedDimension(orgDim, targetResolution, effResolution);
727         boolean scaled = !orgDim.equals(effDim);
728
729         boolean monochrome = isMonochromeImage(img);
730         if (!monochrome) {
731             //Transparency mask disabled. Doesn't work reliably
732
final boolean transparencyDisabled = true;
733             RenderedImage JavaDoc mask = (transparencyDisabled ? null : getMask(img, effDim));
734             if (mask != null) {
735                 pushCursorPos();
736                 selectCurrentPattern(0, 1); //Solid white
737
setTransparencyMode(true, true);
738                 paintMonochromeBitmap(mask, effResolution);
739                 popCursorPos();
740             }
741             
742             BufferedImage JavaDoc src = null;
743             if (img instanceof BufferedImage JavaDoc && !scaled) {
744                 if (!isGrayscaleImage(img) || img.getColorModel().hasAlpha()) {
745                     src = new BufferedImage JavaDoc(effDim.width, effDim.height,
746                             BufferedImage.TYPE_BYTE_GRAY);
747                     ColorConvertOp JavaDoc op = new ColorConvertOp JavaDoc(
748                             ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
749                     op.filter((BufferedImage JavaDoc)img, src);
750                 } else {
751                     src = (BufferedImage JavaDoc)img;
752                 }
753             }
754             if (src == null) {
755                 src = new BufferedImage JavaDoc(effDim.width, effDim.height,
756                         BufferedImage.TYPE_BYTE_GRAY);
757                 Graphics2D JavaDoc g2d = src.createGraphics();
758                 try {
759                     AffineTransform JavaDoc at = new AffineTransform JavaDoc();
760                     double sx = effDim.getWidth() / orgDim.getWidth();
761                     double sy = effDim.getHeight() / orgDim.getHeight();
762                     at.scale(sx, sy);
763                     g2d.drawRenderedImage(img, at);
764                 } finally {
765                     g2d.dispose();
766                 }
767             }
768             MonochromeBitmapConverter converter = createMonochromeBitmapConverter();
769             converter.setHint("quality", "false");
770
771             BufferedImage JavaDoc buf = (BufferedImage JavaDoc)converter.convertToMonochrome(src);
772             
773             RenderedImage JavaDoc red = buf;
774             selectCurrentPattern(0, 0); //Solid black
775
setTransparencyMode(sourceTransparency || mask != null, true);
776             paintMonochromeBitmap(red, effResolution);
777         } else {
778             //TODO untested!
779
RenderedImage JavaDoc effImg = img;
780             if (scaled) {
781                 BufferedImage JavaDoc buf = new BufferedImage JavaDoc(effDim.width, effDim.height,
782                         BufferedImage.TYPE_BYTE_BINARY);
783                 Graphics2D JavaDoc g2d = buf.createGraphics();
784                 try {
785                     AffineTransform JavaDoc at = new AffineTransform JavaDoc();
786                     double sx = effDim.getWidth() / orgDim.getWidth();
787                     double sy = effDim.getHeight() / orgDim.getHeight();
788                     at.scale(sx, sy);
789                     g2d.drawRenderedImage(img, at);
790                 } finally {
791                     g2d.dispose();
792                 }
793                 effImg = buf;
794             }
795             setSourceTransparencyMode(sourceTransparency);
796             selectCurrentPattern(0, 0); //Solid black
797
paintMonochromeBitmap(effImg, effResolution);
798         }
799     }
800
801     /**
802      * Paint a bitmap at the current cursor position. The bitmap must be a monochrome
803      * (1-bit) bitmap image.
804      * @param img the bitmap image (must be 1-bit b/w)
805      * @param resolution the resolution of the image (must be a PCL resolution)
806      * @throws IOException In case of an I/O error
807      */

808     public void paintMonochromeBitmap(RenderedImage JavaDoc img, int resolution) throws IOException JavaDoc {
809         if (!isValidPCLResolution(resolution)) {
810             throw new IllegalArgumentException JavaDoc("Invalid PCL resolution: " + resolution);
811         }
812         setRasterGraphicsResolution(resolution);
813         writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A");
814         Raster JavaDoc raster = img.getData();
815         boolean monochrome = isMonochromeImage(img);
816         if (!monochrome) {
817             throw new IllegalArgumentException JavaDoc("img must be a monochrome image");
818         }
819         
820         int x = 0;
821         int y = 0;
822         int imgw = img.getWidth();
823         int imgh = img.getHeight();
824         int bytewidth = (imgw / 8);
825         if ((imgw % 8) != 0) {
826             bytewidth++;
827         }
828         byte ib;
829         byte[] rle = new byte[bytewidth * 2]; //compressed (RLE)
830
byte[] uncompressed = new byte[bytewidth]; //uncompressed
831
int lastcount = -1;
832         byte lastbyte = 0;
833         int rlewidth = 0;
834
835         // Transfer graphics data
836
for (y = 0; y < imgh; y++) {
837             ib = 0;
838             for (x = 0; x < imgw; x++) {
839                 int sample = raster.getSample(x, y, 0);
840                 //Set image bit for black
841
if ((sample == 0)) {
842                     ib |= (1 << (7 - (x % 8)));
843                 }
844                     
845                 //RLE encoding
846
if ((x % 8) == 7 || ((x + 1) == imgw)) {
847                     if (rlewidth < bytewidth) {
848                         if (lastcount >= 0) {
849                             if (ib == lastbyte) {
850                                 lastcount++;
851                             } else {
852                                 rle[rlewidth++] = (byte)(lastcount & 0xFF);
853                                 rle[rlewidth++] = lastbyte;
854                                 lastbyte = ib;
855                                 lastcount = 0;
856                             }
857                         } else {
858                             lastbyte = ib;
859                             lastcount = 0;
860                         }
861                         if (lastcount == 255 || ((x + 1) == imgw)) {
862                             rle[rlewidth++] = (byte)(lastcount & 0xFF);
863                             rle[rlewidth++] = lastbyte;
864                             lastbyte = 0;
865                             lastcount = -1;
866                         }
867                     }
868                     uncompressed[x / 8] = ib;
869                     ib = 0;
870                 }
871             }
872             if (rlewidth < bytewidth) {
873                 writeCommand("*b1m" + rlewidth + "W");
874                 this.out.write(rle, 0, rlewidth);
875             } else {
876                 writeCommand("*b0m" + bytewidth + "W");
877                 this.out.write(uncompressed);
878             }
879             lastcount = -1;
880             rlewidth = 0;
881         }
882
883         // End raster graphics
884
writeCommand("*rB");
885     }
886
887 }
888
Popular Tags