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 46KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266
  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.Image;
  23. import java.awt.image.BufferedImage;
  24. import java.awt.image.ColorModel;
  25. import java.awt.image.DataBuffer;
  26. import java.awt.image.DataBufferByte;
  27. import java.awt.image.DataBufferInt;
  28. import java.awt.image.DirectColorModel;
  29. import java.awt.image.IndexColorModel;
  30. import java.awt.image.MultiPixelPackedSampleModel;
  31. import java.awt.image.Raster;
  32. import java.awt.image.RenderedImage;
  33. import java.awt.image.SinglePixelPackedSampleModel;
  34. import java.io.DataOutputStream;
  35. import java.io.IOException;
  36. import java.io.OutputStream;
  37. import java.text.DecimalFormat;
  38. import java.text.DecimalFormatSymbols;
  39. import java.util.Locale;
  40. import org.apache.commons.io.IOUtils;
  41. import org.apache.commons.io.output.ByteArrayOutputStream;
  42. import org.apache.xmlgraphics.util.UnitConv;
  43. import org.apache.fop.util.bitmap.BitmapImageUtil;
  44. import org.apache.fop.util.bitmap.DitherUtil;
  45. /**
  46. * This class provides methods for generating PCL print files.
  47. */
  48. public class PCLGenerator {
  49. private static final String US_ASCII = "US-ASCII";
  50. private static final String ISO_8859_1 = "ISO-8859-1";
  51. /** The ESC (escape) character */
  52. public static final char ESC = '\033';
  53. /** A list of all supported resolutions in PCL (values in dpi) */
  54. public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600};
  55. private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
  56. private final DecimalFormat df2 = new DecimalFormat("0.##", symbols);
  57. private final DecimalFormat df4 = new DecimalFormat("0.####", symbols);
  58. private final OutputStream out;
  59. private boolean currentSourceTransparency = true;
  60. private boolean currentPatternTransparency = true;
  61. private int maxBitmapResolution = PCL_RESOLUTIONS[PCL_RESOLUTIONS.length - 1];
  62. private float ditheringQuality = 0.5f;
  63. /**
  64. * true: Standard PCL shades are used (poor quality). false: user-defined pattern are used
  65. * to create custom dither patterns for better grayscale quality.
  66. */
  67. private static final boolean USE_PCL_SHADES = false;
  68. /**
  69. * Main constructor.
  70. * @param out the OutputStream to write the PCL stream to
  71. */
  72. public PCLGenerator(OutputStream out) {
  73. this.out = out;
  74. }
  75. /**
  76. * Main constructor.
  77. * @param out the OutputStream to write the PCL stream to
  78. * @param maxResolution the maximum resolution to encode bitmap images at
  79. */
  80. public PCLGenerator(OutputStream out, int maxResolution) {
  81. this(out);
  82. boolean found = false;
  83. for (int i = 0; i < PCL_RESOLUTIONS.length; i++) {
  84. if (PCL_RESOLUTIONS[i] == maxResolution) {
  85. found = true;
  86. break;
  87. }
  88. }
  89. if (!found) {
  90. throw new IllegalArgumentException("Illegal value for maximum resolution!");
  91. }
  92. this.maxBitmapResolution = maxResolution;
  93. }
  94. /** @return the OutputStream that this generator writes to */
  95. public OutputStream getOutputStream() {
  96. return this.out;
  97. }
  98. /**
  99. * Returns the currently active text encoding.
  100. * @return the text encoding
  101. */
  102. public String getTextEncoding() {
  103. return ISO_8859_1;
  104. }
  105. /** @return the maximum resolution to encode bitmap images at */
  106. public int getMaximumBitmapResolution() {
  107. return this.maxBitmapResolution;
  108. }
  109. /**
  110. * Writes a PCL escape command to the output stream.
  111. * @param cmd the command (without the ESCAPE character)
  112. * @throws IOException In case of an I/O error
  113. */
  114. public void writeCommand(String cmd) throws IOException {
  115. out.write(27); //ESC
  116. out.write(cmd.getBytes(US_ASCII));
  117. }
  118. /**
  119. * Writes raw text (in ISO-8859-1 encoding) to the output stream.
  120. * @param s the text
  121. * @throws IOException In case of an I/O error
  122. */
  123. public void writeText(String s) throws IOException {
  124. out.write(s.getBytes(ISO_8859_1));
  125. }
  126. /**
  127. * Writes raw bytes to the output stream
  128. * @param bytes The bytes
  129. * @throws IOException In case of an I/O error
  130. */
  131. public void writeBytes(byte[] bytes) throws IOException {
  132. out.write(bytes);
  133. }
  134. /**
  135. * Formats a double value with two decimal positions for PCL output.
  136. *
  137. * @param value value to format
  138. * @return the formatted value
  139. */
  140. public final String formatDouble2(double value) {
  141. return df2.format(value);
  142. }
  143. /**
  144. * Formats a double value with four decimal positions for PCL output.
  145. *
  146. * @param value value to format
  147. * @return the formatted value
  148. */
  149. public final String formatDouble4(double value) {
  150. return df4.format(value);
  151. }
  152. /**
  153. * Sends the universal end of language command (UEL).
  154. * @throws IOException In case of an I/O error
  155. */
  156. public void universalEndOfLanguage() throws IOException {
  157. writeCommand("%-12345X");
  158. }
  159. /**
  160. * Resets the printer and restores the user default environment.
  161. * @throws IOException In case of an I/O error
  162. */
  163. public void resetPrinter() throws IOException {
  164. writeCommand("E");
  165. }
  166. /**
  167. * Sends the job separation command.
  168. * @throws IOException In case of an I/O error
  169. */
  170. public void separateJobs() throws IOException {
  171. writeCommand("&l1T");
  172. }
  173. /**
  174. * Sends the form feed character.
  175. * @throws IOException In case of an I/O error
  176. */
  177. public void formFeed() throws IOException {
  178. out.write(12); //=OC ("FF", Form feed)
  179. }
  180. /**
  181. * Sets the unit of measure.
  182. * @param value the resolution value (units per inch)
  183. * @throws IOException In case of an I/O error
  184. */
  185. public void setUnitOfMeasure(int value) throws IOException {
  186. writeCommand("&u" + value + "D");
  187. }
  188. /**
  189. * Sets the raster graphics resolution
  190. * @param value the resolution value (units per inch)
  191. * @throws IOException In case of an I/O error
  192. */
  193. public void setRasterGraphicsResolution(int value) throws IOException {
  194. writeCommand("*t" + value + "R");
  195. }
  196. /**
  197. * Selects the page size.
  198. * @param selector the integer representing the page size
  199. * @throws IOException In case of an I/O error
  200. */
  201. public void selectPageSize(int selector) throws IOException {
  202. writeCommand("&l" + selector + "A");
  203. }
  204. /**
  205. * Selects the paper source. The parameter is usually printer-specific. Usually, "1" is the
  206. * default tray, "2" is the manual paper feed, "3" is the manual envelope feed, "4" is the
  207. * "lower" tray and "7" is "auto-select". Consult the technical reference for your printer
  208. * for all available values.
  209. * @param selector the integer representing the paper source/tray
  210. * @throws IOException In case of an I/O error
  211. */
  212. public void selectPaperSource(int selector) throws IOException {
  213. writeCommand("&l" + selector + "H");
  214. }
  215. /**
  216. * Selects the output bin. The parameter is usually printer-specific. Usually, "1" is the
  217. * default output bin (upper bin) and "2" is the lower (rear) output bin. Some printers
  218. * may support additional output bins. Consult the technical reference for your printer
  219. * for all available values.
  220. * @param selector the integer representing the output bin
  221. * @throws IOException In case of an I/O error
  222. */
  223. public void selectOutputBin(int selector) throws IOException {
  224. writeCommand("&l" + selector + "G");
  225. }
  226. /**
  227. * Selects the duplexing mode for the page.
  228. * The parameter is usually printer-specific.
  229. * "0" means Simplex,
  230. * "1" means Duplex, Long-Edge Binding,
  231. * "2" means Duplex, Short-Edge Binding.
  232. * @param selector the integer representing the duplexing mode of the page
  233. * @throws IOException In case of an I/O error
  234. */
  235. public void selectDuplexMode(int selector) throws IOException {
  236. writeCommand("&l" + selector + "S");
  237. }
  238. /**
  239. * Clears the horizontal margins.
  240. * @throws IOException In case of an I/O error
  241. */
  242. public void clearHorizontalMargins() throws IOException {
  243. writeCommand("9");
  244. }
  245. /**
  246. * The Top Margin command designates the number of lines between
  247. * the top of the logical page and the top of the text area.
  248. * @param numberOfLines the number of lines (See PCL specification for details)
  249. * @throws IOException In case of an I/O error
  250. */
  251. public void setTopMargin(int numberOfLines) throws IOException {
  252. writeCommand("&l" + numberOfLines + "E");
  253. }
  254. /**
  255. * The Text Length command can be used to define the bottom border. See the PCL specification
  256. * for details.
  257. * @param numberOfLines the number of lines
  258. * @throws IOException In case of an I/O error
  259. */
  260. public void setTextLength(int numberOfLines) throws IOException {
  261. writeCommand("&l" + numberOfLines + "F");
  262. }
  263. /**
  264. * Sets the Vertical Motion Index (VMI).
  265. * @param value the VMI value
  266. * @throws IOException In case of an I/O error
  267. */
  268. public void setVMI(double value) throws IOException {
  269. writeCommand("&l" + formatDouble4(value) + "C");
  270. }
  271. /**
  272. * Sets the cursor to a new absolute coordinate.
  273. * @param x the X coordinate (in millipoints)
  274. * @param y the Y coordinate (in millipoints)
  275. * @throws IOException In case of an I/O error
  276. */
  277. public void setCursorPos(double x, double y) throws IOException {
  278. if (x < 0) {
  279. //A negative x value will result in a relative movement so go to "0" first.
  280. //But this will most probably have no effect anyway since you can't paint to the left
  281. //of the logical page
  282. writeCommand("&a0h" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V");
  283. } else {
  284. writeCommand("&a" + formatDouble2(x / 100) + "h" + formatDouble2(y / 100) + "V");
  285. }
  286. }
  287. /**
  288. * Pushes the current cursor position on a stack (stack size: max 20 entries)
  289. * @throws IOException In case of an I/O error
  290. */
  291. public void pushCursorPos() throws IOException {
  292. writeCommand("&f0S");
  293. }
  294. /**
  295. * Pops the current cursor position from the stack.
  296. * @throws IOException In case of an I/O error
  297. */
  298. public void popCursorPos() throws IOException {
  299. writeCommand("&f1S");
  300. }
  301. /**
  302. * Changes the current print direction while maintaining the current cursor position.
  303. * @param rotate the rotation angle (counterclockwise), one of 0, 90, 180 and 270.
  304. * @throws IOException In case of an I/O error
  305. */
  306. public void changePrintDirection(int rotate) throws IOException {
  307. writeCommand("&a" + rotate + "P");
  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 {
  316. if (restorePreviousHPGL2Cursor) {
  317. writeCommand("%0B");
  318. } else {
  319. writeCommand("%1B");
  320. }
  321. }
  322. /**
  323. * Enters the PCL mode.
  324. * @param restorePreviousPCLCursor true if the previous PCL cursor position should be restored,
  325. * false if the current position is maintained
  326. * @throws IOException In case of an I/O error
  327. */
  328. public void enterPCLMode(boolean restorePreviousPCLCursor) throws IOException {
  329. if (restorePreviousPCLCursor) {
  330. writeCommand("%0A");
  331. } else {
  332. writeCommand("%1A");
  333. }
  334. }
  335. /**
  336. * Generate a filled rectangle at the current cursor position.
  337. *
  338. * @param w the width in millipoints
  339. * @param h the height in millipoints
  340. * @param col the fill color
  341. * @throws IOException In case of an I/O error
  342. */
  343. protected void fillRect(int w, int h, Color col, boolean colorEnabled) throws IOException {
  344. if ((w == 0) || (h == 0)) {
  345. return;
  346. }
  347. if (h < 0) {
  348. h *= -1;
  349. } else {
  350. //y += h;
  351. }
  352. setPatternTransparencyMode(false);
  353. if (USE_PCL_SHADES
  354. || Color.black.equals(col)
  355. || Color.white.equals(col)) {
  356. writeCommand("*c" + formatDouble4(w / 100.0) + "h"
  357. + formatDouble4(h / 100.0) + "V");
  358. int lineshade = convertToPCLShade(col);
  359. writeCommand("*c" + lineshade + "G");
  360. writeCommand("*c2P"); //Shaded fill
  361. } else {
  362. if (colorEnabled) {
  363. selectColor(col);
  364. writeCommand("*c" + formatDouble4(w / 100.0) + "h"
  365. + formatDouble4(h / 100.0) + "V");
  366. writeCommand("*c0P"); //Solid fill
  367. } else {
  368. defineGrayscalePattern(col, 32, DitherUtil.DITHER_MATRIX_4X4);
  369. writeCommand("*c" + formatDouble4(w / 100.0) + "h"
  370. + formatDouble4(h / 100.0) + "V");
  371. writeCommand("*c32G");
  372. writeCommand("*c4P"); //User-defined pattern
  373. }
  374. }
  375. // Reset pattern transparency mode.
  376. setPatternTransparencyMode(true);
  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. pattern = DitherUtil.getBayerDither(DitherUtil.DITHER_MATRIX_8X8, gray255, false);
  402. } else {
  403. //Since a 4x4 pattern did not work, the 4x4 pattern is applied 4 times to an
  404. //8x8 pattern. Maybe this could be changed to use an 8x8 bayer dither pattern
  405. //instead of the 4x4 one.
  406. pattern = DitherUtil.getBayerDither(DitherUtil.DITHER_MATRIX_4X4, gray255, true);
  407. }
  408. data.write(pattern);
  409. if ((baout.size() % 2) > 0) {
  410. baout.write(0);
  411. }
  412. writeCommand("*c" + patternID + "G");
  413. writeCommand("*c" + baout.size() + "W");
  414. baout.writeTo(this.out);
  415. IOUtils.closeQuietly(data);
  416. IOUtils.closeQuietly(baout);
  417. writeCommand("*c4Q"); //temporary pattern
  418. }
  419. /**
  420. * Sets the source transparency mode.
  421. * @param transparent true if transparent, false for opaque
  422. * @throws IOException In case of an I/O error
  423. */
  424. public void setSourceTransparencyMode(boolean transparent) throws IOException {
  425. setTransparencyMode(transparent, currentPatternTransparency);
  426. }
  427. /**
  428. * Sets the pattern transparency mode.
  429. * @param transparent true if transparent, false for opaque
  430. * @throws IOException In case of an I/O error
  431. */
  432. public void setPatternTransparencyMode(boolean transparent) throws IOException {
  433. setTransparencyMode(currentSourceTransparency, transparent);
  434. }
  435. /**
  436. * Sets the transparency modes.
  437. * @param source source transparency: true if transparent, false for opaque
  438. * @param pattern pattern transparency: true if transparent, false for opaque
  439. * @throws IOException In case of an I/O error
  440. */
  441. public void setTransparencyMode(boolean source, boolean pattern) throws IOException {
  442. if (source != currentSourceTransparency && pattern != currentPatternTransparency) {
  443. writeCommand("*v" + (source ? '0' : '1') + "n" + (pattern ? '0' : '1') + "O");
  444. } else if (source != currentSourceTransparency) {
  445. writeCommand("*v" + (source ? '0' : '1') + "N");
  446. } else if (pattern != currentPatternTransparency) {
  447. writeCommand("*v" + (pattern ? '0' : '1') + "O");
  448. }
  449. this.currentSourceTransparency = source;
  450. this.currentPatternTransparency = pattern;
  451. }
  452. /**
  453. * Convert an RGB color value to a grayscale from 0 to 100.
  454. * @param r the red component
  455. * @param g the green component
  456. * @param b the blue component
  457. * @return the gray value
  458. */
  459. public final int convertToGray(int r, int g, int b) {
  460. return BitmapImageUtil.convertToGray(r, g, b);
  461. }
  462. /**
  463. * Convert a Color value to a PCL shade value (0-100).
  464. * @param col the color
  465. * @return the PCL shade value (100=black)
  466. */
  467. public final int convertToPCLShade(Color col) {
  468. float gray = convertToGray(col.getRed(), col.getGreen(), col.getBlue()) / 255f;
  469. return (int)(100 - (gray * 100f));
  470. }
  471. /**
  472. * Selects the current grayscale color (the given color is converted to grayscales).
  473. * @param col the color
  474. * @throws IOException In case of an I/O error
  475. */
  476. public void selectGrayscale(Color col) throws IOException {
  477. if (Color.black.equals(col)) {
  478. selectCurrentPattern(0, 0); //black
  479. } else if (Color.white.equals(col)) {
  480. selectCurrentPattern(0, 1); //white
  481. } else {
  482. if (USE_PCL_SHADES) {
  483. selectCurrentPattern(convertToPCLShade(col), 2);
  484. } else {
  485. defineGrayscalePattern(col, 32, DitherUtil.DITHER_MATRIX_4X4);
  486. selectCurrentPattern(32, 4);
  487. }
  488. }
  489. }
  490. public void selectColor(Color col) throws IOException {
  491. writeCommand("*v6W");
  492. writeBytes(new byte[]{0, 1, 1, 8, 8, 8});
  493. writeCommand(String.format("*v%da%db%dc0I", col.getRed(), col.getGreen(), col.getBlue()));
  494. writeCommand("*v0S");
  495. }
  496. /**
  497. * Select the current pattern
  498. * @param patternID the pattern ID (<ESC>*c#G command)
  499. * @param pattern the pattern type (<ESC>*v#T command)
  500. * @throws IOException In case of an I/O error
  501. */
  502. public void selectCurrentPattern(int patternID, int pattern) throws IOException {
  503. if (pattern > 1) {
  504. writeCommand("*c" + patternID + "G");
  505. }
  506. writeCommand("*v" + pattern + "T");
  507. }
  508. /**
  509. * Sets the dithering quality used when encoding gray or color images. If not explicitely
  510. * set a medium setting (0.5f) is used.
  511. * @param quality a quality setting between 0.0f (worst/fastest) and 1.0f (best/slowest)
  512. */
  513. public void setDitheringQuality(float quality) {
  514. quality = Math.min(Math.max(0f, quality), 1.0f);
  515. this.ditheringQuality = quality;
  516. }
  517. /**
  518. * Returns the dithering quality used when encoding gray or color images.
  519. * @return the quality setting between 0.0f (worst/fastest) and 1.0f (best/slowest)
  520. */
  521. public float getDitheringQuality() {
  522. return this.ditheringQuality;
  523. }
  524. /**
  525. * Indicates whether an image is a monochrome (b/w) image.
  526. * @param img the image
  527. * @return true if it's a monochrome image
  528. */
  529. public static boolean isMonochromeImage(RenderedImage img) {
  530. return BitmapImageUtil.isMonochromeImage(img);
  531. }
  532. /**
  533. * Indicates whether an image is a grayscale image.
  534. * @param img the image
  535. * @return true if it's a grayscale image
  536. */
  537. public static boolean isGrayscaleImage(RenderedImage img) {
  538. return BitmapImageUtil.isGrayscaleImage(img);
  539. }
  540. private static int jaiAvailable = -1; //no synchronization necessary, not critical
  541. /**
  542. * Indicates whether JAI is available. JAI has shown to be reliable when dithering a
  543. * grayscale or color image to monochrome bitmaps (1-bit).
  544. * @return true if JAI is available
  545. */
  546. public static boolean isJAIAvailable() {
  547. if (jaiAvailable < 0) {
  548. try {
  549. String clName = "javax.media.jai.JAI";
  550. Class.forName(clName);
  551. jaiAvailable = 1;
  552. } catch (ClassNotFoundException cnfe) {
  553. jaiAvailable = 0;
  554. }
  555. }
  556. return (jaiAvailable > 0);
  557. }
  558. private int calculatePCLResolution(int resolution) {
  559. return calculatePCLResolution(resolution, false);
  560. }
  561. /**
  562. * Calculates the ideal PCL resolution for a given resolution.
  563. * @param resolution the input resolution
  564. * @param increased true if you want to go to a higher resolution, for example if you
  565. * convert grayscale or color images to monochrome images so dithering has
  566. * a chance to generate better quality.
  567. * @return the resulting PCL resolution (one of 75, 100, 150, 200, 300, 600)
  568. */
  569. private int calculatePCLResolution(int resolution, boolean increased) {
  570. int choice = -1;
  571. for (int i = PCL_RESOLUTIONS.length - 2; i >= 0; i--) {
  572. if (resolution > PCL_RESOLUTIONS[i]) {
  573. int idx = i + 1;
  574. if (idx < PCL_RESOLUTIONS.length - 2) {
  575. idx += increased ? 2 : 0;
  576. } else if (idx < PCL_RESOLUTIONS.length - 1) {
  577. idx += increased ? 1 : 0;
  578. }
  579. choice = idx;
  580. break;
  581. //return PCL_RESOLUTIONS[idx];
  582. }
  583. }
  584. if (choice < 0) {
  585. choice = (increased ? 2 : 0);
  586. }
  587. while (choice > 0 && PCL_RESOLUTIONS[choice] > getMaximumBitmapResolution()) {
  588. choice--;
  589. }
  590. return PCL_RESOLUTIONS[choice];
  591. }
  592. private boolean isValidPCLResolution(int resolution) {
  593. return resolution == calculatePCLResolution(resolution);
  594. }
  595. //Threshold table to convert an alpha channel (8-bit) into a clip mask (1-bit)
  596. private static final byte[] THRESHOLD_TABLE = new byte[256];
  597. static { // Initialize the arrays
  598. for (int i = 0; i < 256; i++) {
  599. THRESHOLD_TABLE[i] = (byte) ((i < 240) ? 255 : 0);
  600. }
  601. }
  602. /* not used
  603. private RenderedImage getMask(RenderedImage img, Dimension targetDim) {
  604. ColorModel cm = img.getColorModel();
  605. if (cm.hasAlpha()) {
  606. BufferedImage alpha = new BufferedImage(img.getWidth(), img.getHeight(),
  607. BufferedImage.TYPE_BYTE_GRAY);
  608. Raster raster = img.getData();
  609. GraphicsUtil.copyBand(raster, cm.getNumColorComponents(), alpha.getRaster(), 0);
  610. BufferedImageOp op1 = new LookupOp(new ByteLookupTable(0, THRESHOLD_TABLE), null);
  611. BufferedImage alphat = op1.filter(alpha, null);
  612. BufferedImage mask;
  613. if (true) {
  614. mask = new BufferedImage(targetDim.width, targetDim.height,
  615. BufferedImage.TYPE_BYTE_BINARY);
  616. } else {
  617. byte[] arr = {(byte)0, (byte)0xff};
  618. ColorModel colorModel = new IndexColorModel(1, 2, arr, arr, arr);
  619. WritableRaster wraster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE,
  620. targetDim.width, targetDim.height, 1, 1, null);
  621. mask = new BufferedImage(colorModel, wraster, false, null);
  622. }
  623. Graphics2D g2d = mask.createGraphics();
  624. try {
  625. AffineTransform at = new AffineTransform();
  626. double sx = targetDim.getWidth() / img.getWidth();
  627. double sy = targetDim.getHeight() / img.getHeight();
  628. at.scale(sx, sy);
  629. g2d.drawRenderedImage(alphat, at);
  630. } finally {
  631. g2d.dispose();
  632. }
  633. return mask;
  634. } else {
  635. return null;
  636. }
  637. }
  638. */
  639. /**
  640. * Paint a bitmap at the current cursor position. The bitmap is converted to a monochrome
  641. * (1-bit) bitmap image.
  642. * @param img the bitmap image
  643. * @param targetDim the target Dimention (in mpt)
  644. * @param sourceTransparency true if the background should not be erased
  645. * @throws IOException In case of an I/O error
  646. */
  647. public void paintBitmap(RenderedImage img, Dimension targetDim, boolean sourceTransparency,
  648. PCLRenderingUtil pclUtil) throws IOException {
  649. final boolean printerSupportsColor = pclUtil.isColorEnabled();
  650. boolean monochrome = isMonochromeImage(img);
  651. double targetHResolution = img.getWidth() / UnitConv.mpt2in(targetDim.width);
  652. double targetVResolution = img.getHeight() / UnitConv.mpt2in(targetDim.height);
  653. double targetResolution = Math.max(targetHResolution, targetVResolution);
  654. int resolution = (int)Math.round(targetResolution);
  655. int effResolution = calculatePCLResolution(resolution, !(printerSupportsColor && !monochrome));
  656. Dimension orgDim = new Dimension(img.getWidth(), img.getHeight());
  657. Dimension effDim;
  658. if (targetResolution == effResolution) {
  659. effDim = orgDim; //avoid scaling side-effects
  660. } else {
  661. effDim = new Dimension(
  662. (int)Math.ceil(UnitConv.mpt2px(targetDim.width, effResolution)),
  663. (int)Math.ceil(UnitConv.mpt2px(targetDim.height, effResolution)));
  664. }
  665. boolean scaled = !orgDim.equals(effDim);
  666. if (!monochrome) {
  667. if (printerSupportsColor) {
  668. selectCurrentPattern(0, 0); //Solid black
  669. renderImageAsColor(img, effResolution);
  670. } else {
  671. //Transparency mask disabled. Doesn't work reliably
  672. /*
  673. final boolean transparencyDisabled = true;
  674. RenderedImage mask = (transparencyDisabled ? null : getMask(img, effDim));
  675. if (mask != null) {
  676. pushCursorPos();
  677. selectCurrentPattern(0, 1); //Solid white
  678. setTransparencyMode(true, true);
  679. paintMonochromeBitmap(mask, effResolution);
  680. popCursorPos();
  681. }
  682. */
  683. RenderedImage red = BitmapImageUtil.convertToMonochrome(
  684. img, effDim, this.ditheringQuality);
  685. selectCurrentPattern(0, 0); //Solid black
  686. setTransparencyMode(sourceTransparency /*|| mask != null*/, true);
  687. paintMonochromeBitmap(red, effResolution);
  688. }
  689. } else {
  690. RenderedImage effImg = img;
  691. if (scaled) {
  692. effImg = BitmapImageUtil.convertToMonochrome(img, effDim);
  693. }
  694. setSourceTransparencyMode(sourceTransparency);
  695. selectCurrentPattern(0, 0); //Solid black
  696. paintMonochromeBitmap(effImg, effResolution);
  697. }
  698. }
  699. private int toGray(int rgb) {
  700. // see http://www.jguru.com/faq/view.jsp?EID=221919
  701. double greyVal = 0.072169d * (rgb & 0xff);
  702. rgb >>= 8;
  703. greyVal += 0.715160d * (rgb & 0xff);
  704. rgb >>= 8;
  705. greyVal += 0.212671d * (rgb & 0xff);
  706. return (int)greyVal;
  707. }
  708. private void renderImageAsColor(RenderedImage imgOrg, int dpi) throws IOException {
  709. BufferedImage img = new BufferedImage(imgOrg.getWidth(), imgOrg.getHeight(), BufferedImage.TYPE_INT_RGB);
  710. Graphics2D g = img.createGraphics();
  711. g.setColor(Color.WHITE);
  712. g.fillRect(0, 0, imgOrg.getWidth(), imgOrg.getHeight());
  713. g.drawImage((Image) imgOrg, 0, 0, null);
  714. if (!isValidPCLResolution(dpi)) {
  715. throw new IllegalArgumentException("Invalid PCL resolution: " + dpi);
  716. }
  717. int w = img.getWidth();
  718. ColorModel cm = img.getColorModel();
  719. if (cm instanceof DirectColorModel) {
  720. writeCommand("*v6W"); // ImagingMode
  721. out.write(new byte[]{0, 3, 0, 8, 8, 8});
  722. } else {
  723. IndexColorModel icm = (IndexColorModel)cm;
  724. writeCommand("*v6W"); // ImagingMode
  725. out.write(new byte[]{0, 1, (byte)icm.getMapSize(), 8, 8, 8});
  726. byte[] reds = new byte[256];
  727. byte[] greens = new byte[256];
  728. byte[] blues = new byte[256];
  729. icm.getReds(reds);
  730. icm.getGreens(greens);
  731. icm.getBlues(blues);
  732. for (int i = 0; i < icm.getMapSize(); i++) {
  733. writeCommand("*v" + (reds[i] & 0xFF) + "A"); //ColorComponentOne
  734. writeCommand("*v" + (greens[i] & 0xFF) + "B"); //ColorComponentTwo
  735. writeCommand("*v" + (blues[i] & 0xFF) + "C"); //ColorComponentThree
  736. writeCommand("*v" + i + "I"); //AssignColorIndex
  737. }
  738. }
  739. setRasterGraphicsResolution(dpi);
  740. writeCommand("*r0f" + img.getHeight() + "t" + (w) + "S");
  741. writeCommand("*r1A");
  742. Raster raster = img.getData();
  743. ColorEncoder encoder = new ColorEncoder(img);
  744. // Transfer graphics data
  745. if (cm.getTransferType() == DataBuffer.TYPE_BYTE) {
  746. DataBufferByte dataBuffer = (DataBufferByte)raster.getDataBuffer();
  747. if (img.getSampleModel() instanceof MultiPixelPackedSampleModel && dataBuffer.getNumBanks() == 1) {
  748. byte[] buf = dataBuffer.getData();
  749. MultiPixelPackedSampleModel sampleModel = (MultiPixelPackedSampleModel)img.getSampleModel();
  750. int scanlineStride = sampleModel.getScanlineStride();
  751. int idx = 0;
  752. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  753. for (int x = 0; x < scanlineStride; x++) {
  754. encoder.add8Bits(buf[idx]);
  755. idx++;
  756. }
  757. encoder.endLine();
  758. }
  759. } else {
  760. throw new IOException("Unsupported image");
  761. }
  762. } else if (cm.getTransferType() == DataBuffer.TYPE_INT) {
  763. DataBufferInt dataBuffer = (DataBufferInt)raster.getDataBuffer();
  764. if (img.getSampleModel() instanceof SinglePixelPackedSampleModel && dataBuffer.getNumBanks() == 1) {
  765. int[] buf = dataBuffer.getData();
  766. SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel)img.getSampleModel();
  767. int scanlineStride = sampleModel.getScanlineStride();
  768. int idx = 0;
  769. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  770. for (int x = 0; x < scanlineStride; x++) {
  771. encoder.add8Bits((byte)(buf[idx] >> 16));
  772. encoder.add8Bits((byte)(buf[idx] >> 8));
  773. encoder.add8Bits((byte)(buf[idx] >> 0));
  774. idx++;
  775. }
  776. encoder.endLine();
  777. }
  778. } else {
  779. throw new IOException("Unsupported image");
  780. }
  781. } else {
  782. throw new IOException("Unsupported image");
  783. }
  784. // End raster graphics
  785. writeCommand("*rB");
  786. }
  787. /**
  788. * Paint a bitmap at the current cursor position. The bitmap must be a monochrome
  789. * (1-bit) bitmap image.
  790. * @param img the bitmap image (must be 1-bit b/w)
  791. * @param resolution the resolution of the image (must be a PCL resolution)
  792. * @throws IOException In case of an I/O error
  793. */
  794. public void paintMonochromeBitmap(RenderedImage img, int resolution) throws IOException {
  795. if (!isValidPCLResolution(resolution)) {
  796. throw new IllegalArgumentException("Invalid PCL resolution: " + resolution);
  797. }
  798. boolean monochrome = isMonochromeImage(img);
  799. if (!monochrome) {
  800. throw new IllegalArgumentException("img must be a monochrome image");
  801. }
  802. setRasterGraphicsResolution(resolution);
  803. writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A");
  804. Raster raster = img.getData();
  805. Encoder encoder = new Encoder(img);
  806. // Transfer graphics data
  807. int imgw = img.getWidth();
  808. IndexColorModel cm = (IndexColorModel)img.getColorModel();
  809. if (cm.getTransferType() == DataBuffer.TYPE_BYTE) {
  810. DataBufferByte dataBuffer = (DataBufferByte)raster.getDataBuffer();
  811. MultiPixelPackedSampleModel packedSampleModel = new MultiPixelPackedSampleModel(
  812. DataBuffer.TYPE_BYTE, img.getWidth(), img.getHeight(), 1);
  813. if (img.getSampleModel().equals(packedSampleModel)
  814. && dataBuffer.getNumBanks() == 1) {
  815. //Optimized packed encoding
  816. byte[] buf = dataBuffer.getData();
  817. int scanlineStride = packedSampleModel.getScanlineStride();
  818. int idx = 0;
  819. int c0 = toGray(cm.getRGB(0));
  820. int c1 = toGray(cm.getRGB(1));
  821. boolean zeroIsWhite = c0 > c1;
  822. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  823. for (int x = 0, maxx = scanlineStride; x < maxx; x++) {
  824. if (zeroIsWhite) {
  825. encoder.add8Bits(buf[idx]);
  826. } else {
  827. encoder.add8Bits((byte)~buf[idx]);
  828. }
  829. idx++;
  830. }
  831. encoder.endLine();
  832. }
  833. } else {
  834. //Optimized non-packed encoding
  835. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  836. byte[] line = (byte[])raster.getDataElements(0, y, imgw, 1, null);
  837. for (int x = 0, maxx = imgw; x < maxx; x++) {
  838. encoder.addBit(line[x] == 0);
  839. }
  840. encoder.endLine();
  841. }
  842. }
  843. } else {
  844. //Safe but slow fallback
  845. for (int y = 0, maxy = img.getHeight(); y < maxy; y++) {
  846. for (int x = 0, maxx = imgw; x < maxx; x++) {
  847. int sample = raster.getSample(x, y, 0);
  848. encoder.addBit(sample == 0);
  849. }
  850. encoder.endLine();
  851. }
  852. }
  853. // End raster graphics
  854. writeCommand("*rB");
  855. }
  856. private class Encoder {
  857. private int imgw;
  858. private int bytewidth;
  859. private byte[] rle; //compressed (RLE)
  860. private byte[] uncompressed; //uncompressed
  861. private int lastcount = -1;
  862. private byte lastbyte;
  863. private int rlewidth;
  864. private byte ib; //current image bits
  865. private int x;
  866. private boolean zeroRow = true;
  867. public Encoder(RenderedImage img) {
  868. imgw = img.getWidth();
  869. bytewidth = (imgw / 8);
  870. if ((imgw % 8) != 0) {
  871. bytewidth++;
  872. }
  873. rle = new byte[bytewidth * 2];
  874. uncompressed = new byte[bytewidth];
  875. }
  876. public void addBit(boolean bit) {
  877. //Set image bit for black
  878. if (bit) {
  879. ib |= 1;
  880. }
  881. //RLE encoding
  882. if ((x % 8) == 7 || ((x + 1) == imgw)) {
  883. finishedByte();
  884. } else {
  885. ib <<= 1;
  886. }
  887. x++;
  888. }
  889. public void add8Bits(byte b) {
  890. ib = b;
  891. finishedByte();
  892. x += 8;
  893. }
  894. private void finishedByte() {
  895. if (rlewidth < bytewidth) {
  896. if (lastcount >= 0) {
  897. if (ib == lastbyte) {
  898. lastcount++;
  899. } else {
  900. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  901. rle[rlewidth++] = lastbyte;
  902. lastbyte = ib;
  903. lastcount = 0;
  904. }
  905. } else {
  906. lastbyte = ib;
  907. lastcount = 0;
  908. }
  909. if (lastcount == 255 || ((x + 1) == imgw)) {
  910. rle[rlewidth++] = (byte)(lastcount & 0xFF);
  911. rle[rlewidth++] = lastbyte;
  912. lastbyte = 0;
  913. lastcount = -1;
  914. }
  915. }
  916. uncompressed[x / 8] = ib;
  917. if (ib != 0) {
  918. zeroRow = false;
  919. }
  920. ib = 0;
  921. }
  922. public void endLine() throws IOException {
  923. if (zeroRow && PCLGenerator.this.currentSourceTransparency) {
  924. writeCommand("*b1Y");
  925. } else if (rlewidth < bytewidth) {
  926. writeCommand("*b1m" + rlewidth + "W");
  927. out.write(rle, 0, rlewidth);
  928. } else {
  929. writeCommand("*b0m" + bytewidth + "W");
  930. out.write(uncompressed);
  931. }
  932. lastcount = -1;
  933. rlewidth = 0;
  934. ib = 0;
  935. x = 0;
  936. zeroRow = true;
  937. }
  938. }
  939. private class ColorEncoder {
  940. private int imgw;
  941. private int bytewidth;
  942. private byte ib; //current image bits
  943. private int currentIndex;
  944. private int len;
  945. private int shiftBit = 0x80;
  946. private int whiteLines;
  947. final byte[] zeros;
  948. final byte[] buff1;
  949. final byte[] buff2;
  950. final byte[] encodedRun;
  951. final byte[] encodedTagged;
  952. final byte[] encodedDelta;
  953. byte[] seed;
  954. byte[] current;
  955. int compression;
  956. int seedLen;
  957. public ColorEncoder(RenderedImage img) {
  958. imgw = img.getWidth();
  959. bytewidth = imgw * 3 + 1;
  960. zeros = new byte[bytewidth];
  961. buff1 = new byte[bytewidth];
  962. buff2 = new byte[bytewidth];
  963. encodedRun = new byte[bytewidth];
  964. encodedTagged = new byte[bytewidth];
  965. encodedDelta = new byte[bytewidth];
  966. seed = buff1;
  967. current = buff2;
  968. seedLen = 0;
  969. compression = (-1);
  970. System.arraycopy(zeros, 0, seed, 0, zeros.length);
  971. }
  972. private int runCompression(byte[] buff, int len) {
  973. int bytes = 0;
  974. try {
  975. for (int i = 0; i < len;) {
  976. int sameCount;
  977. byte seed = current[i++];
  978. for (sameCount = 1; i < len && current[i] == seed; i++) {
  979. sameCount++;
  980. }
  981. for (; sameCount > 256; sameCount -= 256) {
  982. buff[bytes++] = (byte)255;
  983. buff[bytes++] = seed;
  984. }
  985. if (sameCount > 0) {
  986. buff[bytes++] = (byte)(sameCount - 1);
  987. buff[bytes++] = seed;
  988. }
  989. }
  990. } catch (ArrayIndexOutOfBoundsException e) {
  991. return len + 1;
  992. }
  993. return bytes;
  994. }
  995. private int deltaCompression(byte[] seed, byte[] buff, int len) {
  996. int bytes = 0;
  997. try {
  998. for (int i = 0; i < len;) {
  999. int sameCount;
  1000. int diffCount;
  1001. for (sameCount = 0; i < len && current[i] == seed[i]; i++) {
  1002. sameCount++;
  1003. }
  1004. for (diffCount = 0; i < len && current[i] != seed[i]; i++) {
  1005. diffCount++;
  1006. }
  1007. for (; diffCount != 0;) {
  1008. int diffToWrite = (diffCount > 8) ? 8 : diffCount;
  1009. int sameToWrite = (sameCount > 31) ? 31 : sameCount;
  1010. buff[bytes++] = (byte)(((diffToWrite - 1) << 5) | sameToWrite);
  1011. sameCount -= sameToWrite;
  1012. if (sameToWrite == 31) {
  1013. for (; sameCount >= 255; sameCount -= 255) {
  1014. buff[bytes++] = (byte)255;
  1015. }
  1016. buff[bytes++] = (byte)sameCount;
  1017. sameCount = 0;
  1018. }
  1019. System.arraycopy(current, i - diffCount, buff, bytes, diffToWrite);
  1020. bytes += diffToWrite;
  1021. diffCount -= diffToWrite;
  1022. }
  1023. }
  1024. } catch (ArrayIndexOutOfBoundsException e) {
  1025. return len + 1;
  1026. }
  1027. return bytes;
  1028. }
  1029. private int tiffCompression(byte[] encodedTagged, int len) {
  1030. int literalCount = 0;
  1031. int bytes = 0;
  1032. try {
  1033. for (int from = 0; from < len;) {
  1034. int repeatLength;
  1035. int repeatValue = current[from];
  1036. for (repeatLength = 1; repeatLength < 128
  1037. && from + repeatLength < len
  1038. && current[from + repeatLength] == repeatValue;) {
  1039. repeatLength++;
  1040. }
  1041. if (literalCount == 128 || (repeatLength > 2 && literalCount > 0)) {
  1042. encodedTagged[bytes++] = (byte)(literalCount - 1);
  1043. System.arraycopy(current, from - literalCount, encodedTagged, bytes, literalCount);
  1044. bytes += literalCount;
  1045. literalCount = 0;
  1046. }
  1047. if (repeatLength > 2) {
  1048. encodedTagged[bytes++] = (byte)(1 - repeatLength);
  1049. encodedTagged[bytes++] = current[from];
  1050. from += repeatLength;
  1051. } else {
  1052. literalCount++;
  1053. from++;
  1054. }
  1055. }
  1056. if (literalCount > 0) {
  1057. encodedTagged[bytes++] = (byte)(literalCount - 1);
  1058. System.arraycopy(current, (3 * len) - literalCount, encodedTagged, bytes, literalCount);
  1059. bytes += literalCount;
  1060. }
  1061. } catch (ArrayIndexOutOfBoundsException e) {
  1062. return len + 1;
  1063. }
  1064. return bytes;
  1065. }
  1066. public void addBit(boolean bit) {
  1067. //Set image bit for black
  1068. if (bit) {
  1069. ib |= shiftBit;
  1070. }
  1071. shiftBit >>= 1;
  1072. if (shiftBit == 0) {
  1073. add8Bits(ib);
  1074. shiftBit = 0x80;
  1075. ib = 0;
  1076. }
  1077. }
  1078. public void add8Bits(byte b) {
  1079. current[currentIndex++] = b;
  1080. if (b != 0) {
  1081. len = currentIndex;
  1082. }
  1083. }
  1084. public void endLine() throws IOException {
  1085. if (len == 0) {
  1086. whiteLines++;
  1087. } else {
  1088. if (whiteLines > 0) {
  1089. writeCommand("*b" + whiteLines + "Y");
  1090. whiteLines = 0;
  1091. }
  1092. int unencodedCount = len;
  1093. int runCount = runCompression(encodedRun, len);
  1094. int tiffCount = tiffCompression(encodedTagged, len);
  1095. int deltaCount = deltaCompression(seed, encodedDelta, Math.max(len, seedLen));
  1096. int bestCount = Math.min(unencodedCount, Math.min(runCount, Math.min(tiffCount, deltaCount)));
  1097. int bestCompression;
  1098. if (bestCount == unencodedCount) {
  1099. bestCompression = 0;
  1100. } else if (bestCount == runCount) {
  1101. bestCompression = 1;
  1102. } else if (bestCount == tiffCount) {
  1103. bestCompression = 2;
  1104. } else {
  1105. bestCompression = 3;
  1106. }
  1107. if (compression != bestCompression) {
  1108. compression = bestCompression;
  1109. writeCommand("*b" + compression + "M");
  1110. }
  1111. if (bestCompression == 0) {
  1112. writeCommand("*b" + unencodedCount + "W");
  1113. out.write(current, 0, unencodedCount);
  1114. } else if (bestCompression == 1) {
  1115. writeCommand("*b" + runCount + "W");
  1116. out.write(encodedRun, 0, runCount);
  1117. } else if (bestCompression == 2) {
  1118. writeCommand("*b" + tiffCount + "W");
  1119. out.write(encodedTagged, 0, tiffCount);
  1120. } else if (bestCompression == 3) {
  1121. writeCommand("*b" + deltaCount + "W");
  1122. out.write(encodedDelta, 0, deltaCount);
  1123. }
  1124. if (current == buff1) {
  1125. seed = buff1;
  1126. current = buff2;
  1127. } else {
  1128. seed = buff2;
  1129. current = buff1;
  1130. }
  1131. seedLen = len;
  1132. }
  1133. shiftBit = 0x80;
  1134. ib = 0;
  1135. len = 0;
  1136. currentIndex = 0;
  1137. }
  1138. }
  1139. }