You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PCLGenerator.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  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. /* $Id$ */
  18. package org.apache.fop.render.pcl;
  19. import java.awt.Color;
  20. import java.awt.Dimension;
  21. import java.awt.Graphics2D;
  22. import java.awt.color.ColorSpace;
  23. import java.awt.geom.AffineTransform;
  24. import java.awt.image.BufferedImage;
  25. import java.awt.image.BufferedImageOp;
  26. import java.awt.image.ByteLookupTable;
  27. import java.awt.image.ColorConvertOp;
  28. import java.awt.image.ColorModel;
  29. import java.awt.image.DataBuffer;
  30. import java.awt.image.IndexColorModel;
  31. import java.awt.image.LookupOp;
  32. import java.awt.image.Raster;
  33. import java.awt.image.RenderedImage;
  34. import java.awt.image.WritableRaster;
  35. import java.io.DataOutputStream;
  36. import java.io.IOException;
  37. import java.io.OutputStream;
  38. import java.text.DecimalFormat;
  39. import java.text.DecimalFormatSymbols;
  40. import java.util.Locale;
  41. import org.apache.commons.io.output.ByteArrayOutputStream;
  42. import org.apache.xmlgraphics.image.GraphicsUtil;
  43. import org.apache.xmlgraphics.util.UnitConv;
  44. /**
  45. * This class provides methods for generating PCL print files.
  46. */
  47. public class PCLGenerator {
  48. private static final String US_ASCII = "US-ASCII";
  49. private static final String ISO_8859_1 = "ISO-8859-1";
  50. /** The ESC (escape) character */
  51. public static final char ESC = '\033';
  52. /** A list of all supported resolutions in PCL (values in dpi) */
  53. public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600};
  54. /** Selects a 4x4 Bayer dither matrix (17 grayscales) */
  55. public static final int DITHER_MATRIX_4X4 = 4;
  56. /** Selects a 8x8 Bayer dither matrix (65 grayscales) */
  57. public static final int DITHER_MATRIX_8X8 = 8;
  58. private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
  59. private final DecimalFormat df2 = new DecimalFormat("0.##", symbols);
  60. private final DecimalFormat df4 = new DecimalFormat("0.####", symbols);
  61. private final OutputStream out;
  62. private boolean currentSourceTransparency = true;
  63. private boolean currentPatternTransparency = true;
  64. private int maxBitmapResolution = PCL_RESOLUTIONS[PCL_RESOLUTIONS.length - 1];
  65. /**
  66. * true: Standard PCL shades are used (poor quality). false: user-defined pattern are used
  67. * to create custom dither patterns for better grayscale quality.
  68. */
  69. private final boolean usePCLShades = false;
  70. /**
  71. * Main constructor.
  72. * @param out the OutputStream to write the PCL stream to
  73. */
  74. public PCLGenerator(OutputStream out) {
  75. this.out = out;
  76. }
  77. /**
  78. * Main constructor.
  79. * @param out the OutputStream to write the PCL stream to
  80. * @param maxResolution the maximum resolution to encode bitmap images at
  81. */
  82. public PCLGenerator(OutputStream out, int maxResolution) {
  83. this(out);
  84. boolean found = false;
  85. for (int i = 0; i < PCL_RESOLUTIONS.length; i++) {
  86. if (PCL_RESOLUTIONS[i] == maxResolution) {
  87. found = true;
  88. break;
  89. }
  90. }
  91. if (!found) {
  92. throw new IllegalArgumentException("Illegal value for maximum resolution!");
  93. }
  94. this.maxBitmapResolution = maxResolution;
  95. }
  96. /** @return the OutputStream that this generator writes to */
  97. public OutputStream getOutputStream() {
  98. return this.out;
  99. }
  100. /**
  101. * Returns the currently active text encoding.
  102. * @return the text encoding
  103. */
  104. public String getTextEncoding() {
  105. return ISO_8859_1;
  106. }
  107. /** @return the maximum resolution to encode bitmap images at */
  108. public int getMaximumBitmapResolution() {
  109. return this.maxBitmapResolution;
  110. }
  111. /**
  112. * Writes a PCL escape command to the output stream.
  113. * @param cmd the command (without the ESCAPE character)
  114. * @throws IOException In case of an I/O error
  115. */
  116. public void writeCommand(String cmd) throws IOException {
  117. out.write(27); //ESC
  118. out.write(cmd.getBytes(US_ASCII));
  119. }
  120. /**
  121. * Writes raw text (in ISO-8859-1 encoding) to the output stream.
  122. * @param s the text
  123. * @throws IOException In case of an I/O error
  124. */
  125. public void writeText(String s) throws IOException {
  126. out.write(s.getBytes(ISO_8859_1));
  127. }
  128. /**
  129. * Formats a double value with two decimal positions for PCL output.
  130. *
  131. * @param value value to format
  132. * @return the formatted value
  133. */
  134. public final String formatDouble2(double value) {
  135. return df2.format(value);
  136. }
  137. /**
  138. * Formats a double value with four decimal positions for PCL output.
  139. *
  140. * @param value value to format
  141. * @return the formatted value
  142. */
  143. public final String formatDouble4(double value) {
  144. return df4.format(value);
  145. }
  146. /**
  147. * Sends the universal end of language command (UEL).
  148. * @throws IOException In case of an I/O error
  149. */
  150. public void universalEndOfLanguage() throws IOException {
  151. writeCommand("%-12345X");
  152. }
  153. /**
  154. * Resets the printer and restores the user default environment.
  155. * @throws IOException In case of an I/O error
  156. */
  157. public void resetPrinter() throws IOException {
  158. writeCommand("E");
  159. }
  160. /**
  161. * Sends the job separation command.
  162. * @throws IOException In case of an I/O error
  163. */
  164. public void separateJobs() throws IOException {
  165. writeCommand("&l1T");
  166. }
  167. /**
  168. * Sends the form feed character.
  169. * @throws IOException In case of an I/O error
  170. */
  171. public void formFeed() throws IOException {
  172. out.write(12); //=OC ("FF", Form feed)
  173. }
  174. /**
  175. * Sets the unit of measure.
  176. * @param value the resolution value (units per inch)
  177. * @throws IOException In case of an I/O error
  178. */
  179. public void setUnitOfMeasure(int value) throws IOException {
  180. writeCommand("&u" + value + "D");
  181. }
  182. /**
  183. * Sets the raster graphics resolution
  184. * @param value the resolution value (units per inch)
  185. * @throws IOException In case of an I/O error
  186. */
  187. public void setRasterGraphicsResolution(int value) throws IOException {
  188. writeCommand("*t" + value + "R");
  189. }
  190. /**
  191. * Selects the page size.
  192. * @param selector the integer representing the page size
  193. * @throws IOException In case of an I/O error
  194. */
  195. public void selectPageSize(int selector) throws IOException {
  196. writeCommand("&l" + selector + "A");
  197. }
  198. /**
  199. * Selects the paper source. The parameter is usually printer-specific. Usually, "1" is the
  200. * default tray, "2" is the manual paper feed, "3" is the manual envelope feed, "4" is the
  201. * "lower" tray and "7" is "auto-select". Consult the technical reference for your printer
  202. * for all available values.
  203. * @param selector the integer representing the paper source/tray
  204. * @throws IOException In case of an I/O error
  205. */
  206. public void selectPaperSource(int selector) throws IOException {
  207. writeCommand("&l" + selector + "H");
  208. }
  209. /**
  210. * Selects the duplexing mode for the page.
  211. * The parameter is usually printer-specific.
  212. * "0" means Simplex,
  213. * "1" means Duplex, Long-Edge Binding,
  214. * "2" means Duplex, Short-Edge Binding.
  215. * @param selector the integer representing the duplexing mode of the page
  216. * @throws IOException In case of an I/O error
  217. */
  218. public void selectDuplexMode(int selector) throws IOException {
  219. writeCommand("&l" + selector + "S");
  220. }
  221. /**
  222. * Clears the horizontal margins.
  223. * @throws IOException In case of an I/O error
  224. */
  225. public void clearHorizontalMargins() throws IOException {
  226. writeCommand("9");
  227. }
  228. /**
  229. * The Top Margin command designates the number of lines between
  230. * the top of the logical page and the top of the text area.
  231. * @param numberOfLines the number of lines (See PCL specification for details)
  232. * @throws IOException In case of an I/O error
  233. */
  234. public void setTopMargin(int numberOfLines) throws IOException {
  235. writeCommand("&l" + numberOfLines + "E");
  236. }
  237. /**
  238. * The Text Length command can be used to define the bottom border. See the PCL specification
  239. * for details.
  240. * @param numberOfLines the number of lines
  241. * @throws IOException In case of an I/O error
  242. */
  243. public void setTextLength(int numberOfLines) throws IOException {
  244. writeCommand("&l" + numberOfLines + "F");
  245. }
  246. /**
  247. * Sets the Vertical Motion Index (VMI).
  248. * @param value the VMI value
  249. * @throws IOException In case of an I/O error
  250. */
  251. public void setVMI(double value) throws IOException {
  252. writeCommand("&l" + formatDouble4(value) + "C");
  253. }
  254. /**
  255. * Sets the cursor to a new absolute coordinate.
  256. * @param x the X coordinate (in millipoints)
  257. * @param y the Y coordinate (in millipoints)
  258. * @throws IOException In case of an I/O error
  259. */
  260. public void setCursorPos(double x, double y) throws IOException {
  261. if (x < 0) {
  262. //A negative x value will result in a relative movement so go to "0" first.
  263. //But this will most probably have no effect anyway since you can't paint to the left
  264. //of the logical page
  265. writeCommand("&a0h" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V");
  266. } else {
  267. writeCommand("&a" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V");
  268. }
  269. }
  270. /**
  271. * Pushes the current cursor position on a stack (stack size: max 20 entries)
  272. * @throws IOException In case of an I/O error
  273. */
  274. public void pushCursorPos() throws IOException {
  275. writeCommand("&f0S");
  276. }
  277. /**
  278. * Pops the current cursor position from the stack.
  279. * @throws IOException In case of an I/O error
  280. */
  281. public void popCursorPos() throws IOException {
  282. writeCommand("&f1S");
  283. }
  284. /**
  285. * Changes the current print direction while maintaining the current cursor position.
  286. * @param rotate the rotation angle (counterclockwise), one of 0, 90, 180 and 270.
  287. * @throws IOException In case of an I/O error
  288. */
  289. public void changePrintDirection(int rotate) throws IOException {
  290. writeCommand("&a" + rotate + "P");
  291. }
  292. /**
  293. * Enters the HP GL/2 mode.
  294. * @param restorePreviousHPGL2Cursor true if the previous HP GL/2 pen position should be
  295. * restored, false if the current position is maintained
  296. * @throws IOException In case of an I/O error
  297. */
  298. public void enterHPGL2Mode(boolean restorePreviousHPGL2Cursor) throws IOException {
  299. if (restorePreviousHPGL2Cursor) {
  300. writeCommand("%0B");
  301. } else {
  302. writeCommand("%1B");
  303. }
  304. }
  305. /**
  306. * Enters the PCL mode.
  307. * @param restorePreviousPCLCursor true if the previous PCL cursor position should be restored,
  308. * false if the current position is maintained
  309. * @throws IOException In case of an I/O error
  310. */
  311. public void enterPCLMode(boolean restorePreviousPCLCursor) throws IOException {
  312. if (restorePreviousPCLCursor) {
  313. writeCommand("%0A");
  314. } else {
  315. writeCommand("%1A");
  316. }
  317. }
  318. /**
  319. * Generate a filled rectangle at the current cursor position.
  320. *
  321. * @param w the width in millipoints
  322. * @param h the height in millipoints
  323. * @param col the fill color
  324. * @throws IOException In case of an I/O error
  325. */
  326. protected void fillRect(int w, int h, Color col) throws IOException {
  327. if ((w == 0) || (h == 0)) {
  328. return;
  329. }
  330. if (h < 0) {
  331. h *= -1;
  332. } else {
  333. //y += h;
  334. }
  335. setPatternTransparencyMode(false);
  336. if (usePCLShades
  337. || Color.black.equals(col)
  338. || Color.white.equals(col)) {
  339. writeCommand("*c" + formatDouble4(w / 100.0) + "h"
  340. + formatDouble4(h / 100.0) + "V");
  341. int lineshade = convertToPCLShade(col);
  342. writeCommand("*c" + lineshade + "G");
  343. writeCommand("*c2P"); //Shaded fill
  344. } else {
  345. defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4);
  346. writeCommand("*c" + formatDouble4(w / 100.0) + "h"
  347. + formatDouble4(h / 100.0) + "V");
  348. writeCommand("*c32G");
  349. writeCommand("*c4P"); //User-defined pattern
  350. }
  351. // Reset pattern transparency mode.
  352. setPatternTransparencyMode(true);
  353. }
  354. //Bayer dither matrices (4x4 and 8x8 are derived from the 2x2 matrix)
  355. private static final int[] BAYER_D2 = new int[] {0, 2, 3, 1};
  356. private static final int[] BAYER_D4;
  357. private static final int[] BAYER_D8;
  358. static {
  359. BAYER_D4 = deriveBayerMatrix(BAYER_D2);
  360. BAYER_D8 = deriveBayerMatrix(BAYER_D4);
  361. }
  362. private static void setValueInMatrix(int[] dn, int half, int part, int idx, int value) {
  363. int xoff = (part & 1) * half;
  364. int yoff = (part & 2) * half * half;
  365. int matrixIndex = yoff + ((idx / half) * half * 2) + (idx % half) + xoff;
  366. dn[matrixIndex] = value;
  367. }
  368. private static int[] deriveBayerMatrix(int[] d) {
  369. int[] dn = new int[d.length * 4];
  370. int half = (int)Math.sqrt(d.length);
  371. for (int part = 0; part < 4; part++) {
  372. for (int i = 0, c = d.length; i < c; i++) {
  373. setValueInMatrix(dn, half, part, i, d[i] * 4 + BAYER_D2[part]);
  374. }
  375. }
  376. return dn;
  377. }
  378. /**
  379. * Generates a user-defined pattern for a dithering pattern matching the grayscale value
  380. * of the color given.
  381. * @param col the color to create the pattern for
  382. * @param patternID the pattern ID to use
  383. * @param ditherMatrixSize the size of the Bayer dither matrix to use (4 or 8 supported)
  384. * @throws IOException In case of an I/O error
  385. */
  386. public void defineGrayscalePattern(Color col, int patternID, int ditherMatrixSize)
  387. throws IOException {
  388. ByteArrayOutputStream baout = new ByteArrayOutputStream();
  389. DataOutputStream data = new DataOutputStream(baout);
  390. data.writeByte(0); //Format
  391. data.writeByte(0); //Continuation
  392. data.writeByte(1); //Pixel Encoding
  393. data.writeByte(0); //Reserved
  394. data.writeShort(8); //Width in Pixels
  395. data.writeShort(8); //Height in Pixels
  396. //data.writeShort(600); //X Resolution (didn't manage to get that to work)
  397. //data.writeShort(600); //Y Resolution
  398. int gray255 = convertToGray(col.getRed(), col.getGreen(), col.getBlue());
  399. byte[] pattern;
  400. if (ditherMatrixSize == 8) {
  401. int gray65 = gray255 * 65 / 255;
  402. pattern = new byte[BAYER_D8.length / 8];
  403. for (int i = 0, c = BAYER_D8.length; i < c; i++) {
  404. boolean dot = !(BAYER_D8[i] < gray65 - 1);
  405. if (dot) {
  406. int byteIdx = i / 8;
  407. pattern[byteIdx] |= 1 << (i % 8);
  408. }
  409. }
  410. } else {
  411. int gray17 = gray255 * 17 / 255;
  412. //Since a 4x4 pattern did not work, the 4x4 pattern is applied 4 times to an
  413. //8x8 pattern. Maybe this could be changed to use an 8x8 bayer dither pattern
  414. //instead of the 4x4 one.
  415. pattern = new byte[BAYER_D4.length / 8 * 4];
  416. for (int i = 0, c = BAYER_D4.length; i < c; i++) {
  417. boolean dot = !(BAYER_D4[i] < gray17 - 1);
  418. if (dot) {
  419. int byteIdx = i / 4;
  420. pattern[byteIdx] |= 1 << (i % 4);
  421. pattern[byteIdx] |= 1 << ((i % 4) + 4);
  422. pattern[byteIdx + 4] |= 1 << (i % 4);
  423. pattern[byteIdx + 4] |= 1 << ((i % 4) + 4);
  424. }
  425. }
  426. }
  427. data.write(pattern);
  428. if ((baout.size() % 2) > 0) {
  429. baout.write(0);
  430. }
  431. writeCommand("*c" + patternID + "G");
  432. writeCommand("*c" + baout.size() + "W");
  433. baout.writeTo(this.out);
  434. writeCommand("*c4Q"); //temporary pattern
  435. }
  436. /**
  437. * Sets the source transparency mode.
  438. * @param transparent true if transparent, false for opaque
  439. * @throws IOException In case of an I/O error
  440. */
  441. public void setSourceTransparencyMode(boolean transparent) throws IOException {
  442. setTransparencyMode(transparent, currentPatternTransparency);
  443. }
  444. /**
  445. * Sets the pattern transparency mode.
  446. * @param transparent true if transparent, false for opaque
  447. * @throws IOException In case of an I/O error
  448. */
  449. public void setPatternTransparencyMode(boolean transparent) throws IOException {
  450. setTransparencyMode(currentSourceTransparency, transparent);
  451. }
  452. /**
  453. * Sets the transparency modes.
  454. * @param source source transparency: true if transparent, false for opaque
  455. * @param pattern pattern transparency: true if transparent, false for opaque
  456. * @throws IOException In case of an I/O error
  457. */
  458. public void setTransparencyMode(boolean source, boolean pattern) throws IOException {
  459. if (source != currentSourceTransparency && pattern != currentPatternTransparency) {
  460. writeCommand("*v" + (source ? '0' : '1') + "n" + (pattern ? '0' : '1') + "O");
  461. } else if (source != currentSourceTransparency) {
  462. writeCommand("*v" + (source ? '0' : '1') + "N");
  463. } else if (pattern != currentPatternTransparency) {
  464. writeCommand("*v" + (pattern ? '0' : '1') + "O");
  465. }
  466. this.currentSourceTransparency = source;
  467. this.currentPatternTransparency = pattern;
  468. }
  469. /**
  470. * Convert an RGB color value to a grayscale from 0 to 100.
  471. * @param r the red component
  472. * @param g the green component
  473. * @param b the blue component
  474. * @return the gray value
  475. */
  476. public final int convertToGray(int r, int g, int b) {
  477. return (r * 30 + g * 59 + b * 11) / 100;
  478. }
  479. /**
  480. * Convert a Color value to a PCL shade value (0-100).
  481. * @param col the color
  482. * @return the PCL shade value (100=black)
  483. */
  484. public final int convertToPCLShade(Color col) {
  485. float gray = convertToGray(col.getRed(), col.getGreen(), col.getBlue()) / 255f;
  486. return (int)(100 - (gray * 100f));
  487. }
  488. /**
  489. * Selects the current grayscale color (the given color is converted to grayscales).
  490. * @param col the color
  491. * @throws IOException In case of an I/O error
  492. */
  493. public void selectGrayscale(Color col) throws IOException {
  494. if (Color.black.equals(col)) {
  495. selectCurrentPattern(0, 0); //black
  496. } else if (Color.white.equals(col)) {
  497. selectCurrentPattern(0, 1); //white
  498. } else {
  499. if (usePCLShades) {
  500. selectCurrentPattern(convertToPCLShade(col), 2);
  501. } else {
  502. defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4);
  503. selectCurrentPattern(32, 4);
  504. }
  505. }
  506. }
  507. /**
  508. * Select the current pattern
  509. * @param patternID the pattern ID (<ESC>*c#G command)
  510. * @param pattern the pattern type (<ESC>*v#T command)
  511. * @throws IOException In case of an I/O error
  512. */
  513. public void selectCurrentPattern(int patternID, int pattern) throws IOException {
  514. if (pattern > 1) {
  515. writeCommand("*c" + patternID + "G");
  516. }
  517. writeCommand("*v" + pattern + "T");
  518. }
  519. /**
  520. * Indicates whether an image is a monochrome (b/w) image.
  521. * @param img the image
  522. * @return true if it's a monochrome image
  523. */
  524. public static boolean isMonochromeImage(RenderedImage img) {
  525. ColorModel cm = img.getColorModel();
  526. if (cm instanceof IndexColorModel) {
  527. IndexColorModel icm = (IndexColorModel)cm;
  528. return icm.getMapSize() == 2;
  529. } else {
  530. return false;
  531. }
  532. }
  533. /**
  534. * Indicates whether an image is a grayscale image.
  535. * @param img the image
  536. * @return true if it's a grayscale image
  537. */
  538. public static boolean isGrayscaleImage(RenderedImage img) {
  539. return (img.getColorModel().getColorSpace().getNumComponents() == 1);
  540. }
  541. private MonochromeBitmapConverter createMonochromeBitmapConverter() {
  542. MonochromeBitmapConverter converter = null;
  543. try {
  544. String clName = "org.apache.fop.render.pcl.JAIMonochromeBitmapConverter";
  545. Class clazz = Class.forName(clName);
  546. converter = (MonochromeBitmapConverter)clazz.newInstance();
  547. } catch (ClassNotFoundException cnfe) {
  548. // Class was not compiled so is not available. Simply ignore.
  549. } catch (LinkageError le) {
  550. // This can happen if fop was build with support for a
  551. // particular provider (e.g. a binary fop distribution)
  552. // but the required support files (i.e. JAI) are not
  553. // available in the current runtime environment.
  554. // Simply continue with the backup implementation.
  555. } catch (InstantiationException e) {
  556. // Problem instantiating the class, simply continue with the backup implementation
  557. } catch (IllegalAccessException e) {
  558. // Problem instantiating the class, simply continue with the backup implementation
  559. }
  560. if (converter == null) {
  561. converter = new DefaultMonochromeBitmapConverter();
  562. }
  563. return converter;
  564. }
  565. private int calculatePCLResolution(int resolution) {
  566. return calculatePCLResolution(resolution, false);
  567. }
  568. /**
  569. * Calculates the ideal PCL resolution for a given resolution.
  570. * @param resolution the input resolution
  571. * @param increased true if you want to go to a higher resolution, for example if you
  572. * convert grayscale or color images to monochrome images so dithering has
  573. * a chance to generate better quality.
  574. * @return the resulting PCL resolution (one of 75, 100, 150, 200, 300, 600)
  575. */
  576. private int calculatePCLResolution(int resolution, boolean increased) {
  577. int choice = -1;
  578. for (int i = PCL_RESOLUTIONS.length - 2; i >= 0; i--) {
  579. if (resolution > PCL_RESOLUTIONS[i]) {
  580. int idx = i + 1;
  581. if (idx < PCL_RESOLUTIONS.length - 2) {
  582. idx += increased ? 2 : 0;
  583. } else if (idx < PCL_RESOLUTIONS.length - 1) {
  584. idx += increased ? 1 : 0;
  585. }
  586. choice = idx;
  587. break;
  588. //return PCL_RESOLUTIONS[idx];
  589. }
  590. }
  591. if (choice < 0) {
  592. choice = (increased ? 2 : 0);
  593. }
  594. while (choice > 0 && PCL_RESOLUTIONS[choice] > getMaximumBitmapResolution()) {
  595. choice--;
  596. }
  597. return PCL_RESOLUTIONS[choice];
  598. }
  599. private boolean isValidPCLResolution(int resolution) {
  600. return resolution == calculatePCLResolution(resolution);
  601. }
  602. private Dimension getAdjustedDimension(Dimension orgDim, double orgResolution,
  603. int pclResolution) {
  604. if (orgResolution == pclResolution) {
  605. return orgDim;
  606. } else {
  607. Dimension result = new Dimension();
  608. result.width = (int)Math.round((double)orgDim.width * pclResolution / orgResolution);
  609. result.height = (int)Math.round((double)orgDim.height * pclResolution / orgResolution);
  610. return result;
  611. }
  612. }
  613. //Threshold table to convert an alpha channel (8-bit) into a clip mask (1-bit)
  614. private static final byte[] THRESHOLD_TABLE = new byte[256];
  615. static { // Initialize the arrays
  616. for (int i = 0; i < 256; i++) {
  617. THRESHOLD_TABLE[i] = (byte) ((i < 240) ? 255 : 0);
  618. }
  619. }
  620. private RenderedImage getMask(RenderedImage img, Dimension targetDim) {
  621. ColorModel cm = img.getColorModel();
  622. if (cm.hasAlpha()) {
  623. BufferedImage alpha = new BufferedImage(img.getWidth(), img.getHeight(),
  624. BufferedImage.TYPE_BYTE_GRAY);
  625. Raster raster = img.getData();
  626. GraphicsUtil.copyBand(raster, cm.getNumColorComponents(), alpha.getRaster(), 0);
  627. BufferedImageOp op1 = new LookupOp(new ByteLookupTable(0, THRESHOLD_TABLE), null);
  628. BufferedImage alphat = op1.filter(alpha, null);
  629. BufferedImage mask;
  630. if (true) {
  631. mask = new BufferedImage(targetDim.width, targetDim.height,
  632. BufferedImage.TYPE_BYTE_BINARY);
  633. } else {
  634. byte[] arr = {(byte)0, (byte)0xff};
  635. ColorModel colorModel = new IndexColorModel(1, 2, arr, arr, arr);
  636. WritableRaster wraster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE,
  637. targetDim.width, targetDim.height, 1, 1, null);
  638. mask = new BufferedImage(colorModel, wraster, false, null);
  639. }
  640. Graphics2D g2d = mask.createGraphics();
  641. try {
  642. AffineTransform at = new AffineTransform();
  643. double sx = targetDim.getWidth() / img.getWidth();
  644. double sy = targetDim.getHeight() / img.getHeight();
  645. at.scale(sx, sy);
  646. g2d.drawRenderedImage(alphat, at);
  647. } finally {
  648. g2d.dispose();
  649. }
  650. /*
  651. try {
  652. BatchDiffer.saveAsPNG(alpha, new java.io.File("D:/out-alpha.png"));
  653. BatchDiffer.saveAsPNG(mask, new java.io.File("D:/out-mask.png"));
  654. } catch (IOException e) {
  655. e.printStackTrace();
  656. }*/
  657. return mask;
  658. } else {
  659. return null;
  660. }
  661. }
  662. /**
  663. * Paint a bitmap at the current cursor position. The bitmap is converted to a monochrome
  664. * (1-bit) bitmap image.
  665. * @param img the bitmap image
  666. * @param targetDim the target Dimention (in mpt)
  667. * @param sourceTransparency true if the background should not be erased
  668. * @throws IOException In case of an I/O error
  669. */
  670. public void paintBitmap(RenderedImage img, Dimension targetDim, boolean sourceTransparency)
  671. throws IOException {
  672. double targetResolution = img.getWidth() / UnitConv.mpt2in(targetDim.width);
  673. int resolution = (int)Math.round(targetResolution);
  674. int effResolution = calculatePCLResolution(resolution, true);
  675. Dimension orgDim = new Dimension(img.getWidth(), img.getHeight());
  676. Dimension effDim = getAdjustedDimension(orgDim, targetResolution, effResolution);
  677. boolean scaled = !orgDim.equals(effDim);
  678. boolean monochrome = isMonochromeImage(img);
  679. if (!monochrome) {
  680. //Transparency mask disabled. Doesn't work reliably
  681. final boolean transparencyDisabled = true;
  682. RenderedImage mask = (transparencyDisabled ? null : getMask(img, effDim));
  683. if (mask != null) {
  684. pushCursorPos();
  685. selectCurrentPattern(0, 1); //Solid white
  686. setTransparencyMode(true, true);
  687. paintMonochromeBitmap(mask, effResolution);
  688. popCursorPos();
  689. }
  690. BufferedImage src = null;
  691. if (img instanceof BufferedImage && !scaled) {
  692. if (!isGrayscaleImage(img) || img.getColorModel().hasAlpha()) {
  693. src = new BufferedImage(effDim.width, effDim.height,
  694. BufferedImage.TYPE_BYTE_GRAY);
  695. ColorConvertOp op = new ColorConvertOp(
  696. ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
  697. op.filter((BufferedImage)img, src);
  698. } else {
  699. src = (BufferedImage)img;
  700. }
  701. }
  702. if (src == null) {
  703. src = new BufferedImage(effDim.width, effDim.height,
  704. BufferedImage.TYPE_BYTE_GRAY);
  705. Graphics2D g2d = src.createGraphics();
  706. try {
  707. AffineTransform at = new AffineTransform();
  708. double sx = effDim.getWidth() / orgDim.getWidth();
  709. double sy = effDim.getHeight() / orgDim.getHeight();
  710. at.scale(sx, sy);
  711. g2d.drawRenderedImage(img, at);
  712. } finally {
  713. g2d.dispose();
  714. }
  715. }
  716. MonochromeBitmapConverter converter = createMonochromeBitmapConverter();
  717. converter.setHint("quality", "false");
  718. BufferedImage buf = (BufferedImage)converter.convertToMonochrome(src);
  719. RenderedImage red = buf;
  720. selectCurrentPattern(0, 0); //Solid black
  721. setTransparencyMode(sourceTransparency || mask != null, true);
  722. paintMonochromeBitmap(red, effResolution);
  723. } else {
  724. //TODO untested!
  725. RenderedImage effImg = img;
  726. if (scaled) {
  727. BufferedImage buf = new BufferedImage(effDim.width, effDim.height,
  728. BufferedImage.TYPE_BYTE_BINARY);
  729. Graphics2D g2d = buf.createGraphics();
  730. try {
  731. AffineTransform at = new AffineTransform();
  732. double sx = effDim.getWidth() / orgDim.getWidth();
  733. double sy = effDim.getHeight() / orgDim.getHeight();
  734. at.scale(sx, sy);
  735. g2d.drawRenderedImage(img, at);
  736. } finally {
  737. g2d.dispose();
  738. }
  739. effImg = buf;
  740. }
  741. setSourceTransparencyMode(sourceTransparency);
  742. selectCurrentPattern(0, 0); //Solid black
  743. paintMonochromeBitmap(effImg, effResolution);
  744. }
  745. }
  746. /**
  747. * Paint a bitmap at the current cursor position. The bitmap must be a monochrome
  748. * (1-bit) bitmap image.
  749. * @param img the bitmap image (must be 1-bit b/w)
  750. * @param resolution the resolution of the image (must be a PCL resolution)
  751. * @throws IOException In case of an I/O error
  752. */
  753. public void paintMonochromeBitmap(RenderedImage img, int resolution) throws IOException {
  754. if (!isValidPCLResolution(resolution)) {
  755. throw new IllegalArgumentException("Invalid PCL resolution: " + resolution);
  756. }
  757. setRasterGraphicsResolution(resolution);
  758. writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A");
  759. Raster raster = img.getData();
  760. boolean monochrome = isMonochromeImage(img);
  761. if (!monochrome) {
  762. throw new IllegalArgumentException("img must be a monochrome image");
  763. }
  764. int x = 0;
  765. int y = 0;
  766. int imgw = img.getWidth();
  767. int imgh = img.getHeight();
  768. int bytewidth = (imgw / 8);
  769. if ((imgw % 8) != 0) {
  770. bytewidth++;
  771. }
  772. byte ib;
  773. byte[] rle = new byte[bytewidth * 2]; //compressed (RLE)
  774. byte[] uncompressed = new byte[bytewidth]; //uncompressed
  775. int lastcount = -1;
  776. byte lastbyte = 0;
  777. int rlewidth = 0;
  778. // Transfer graphics data
  779. for (y = 0; y < imgh; y++) {
  780. ib = 0;
  781. for (x = 0; x < imgw; x++) {
  782. int sample = raster.getSample(x, y, 0);
  783. //Set image bit for black
  784. if ((sample == 0)) {
  785. ib |= (1 << (7 - (x % 8)));
  786. }
  787. //RLE encoding
  788. if ((x % 8) == 7 || ((x + 1) == imgw)) {
  789. if (rlewidth < bytewidth) {
  790. if (lastcount >= 0) {
  791. if (ib == lastbyte) {
  792. lastcount++;
  793. } else {
  794. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  795. rle[rlewidth++] = lastbyte;
  796. lastbyte = ib;
  797. lastcount = 0;
  798. }
  799. } else {
  800. lastbyte = ib;
  801. lastcount = 0;
  802. }
  803. if (lastcount == 255 || ((x + 1) == imgw)) {
  804. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  805. rle[rlewidth++] = lastbyte;
  806. lastbyte = 0;
  807. lastcount = -1;
  808. }
  809. }
  810. uncompressed[x / 8] = ib;
  811. ib = 0;
  812. }
  813. }
  814. if (rlewidth < bytewidth) {
  815. writeCommand("*b1m" + rlewidth + "W");
  816. this.out.write(rle, 0, rlewidth);
  817. } else {
  818. writeCommand("*b0m" + bytewidth + "W");
  819. this.out.write(uncompressed);
  820. }
  821. lastcount = -1;
  822. rlewidth = 0;
  823. }
  824. // End raster graphics
  825. writeCommand("*rB");
  826. }
  827. }