1. /*
  2. * @(#)PNGImageWriter.java 1.32 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.imageio.plugins.png;
  8. import java.awt.Rectangle;
  9. import java.awt.image.ColorModel;
  10. import java.awt.image.Raster;
  11. import java.awt.image.RenderedImage;
  12. import java.awt.image.SampleModel;
  13. import java.io.ByteArrayOutputStream;
  14. import java.io.DataOutput;
  15. import java.io.IOException;
  16. import java.io.OutputStream;
  17. import java.util.Iterator;
  18. import java.util.Locale;
  19. import java.util.zip.Deflater;
  20. import java.util.zip.DeflaterOutputStream;
  21. import javax.imageio.IIOException;
  22. import javax.imageio.IIOImage;
  23. import javax.imageio.ImageTypeSpecifier;
  24. import javax.imageio.ImageWriteParam;
  25. import javax.imageio.ImageWriter;
  26. import javax.imageio.metadata.IIOMetadata;
  27. import javax.imageio.metadata.IIOMetadata;
  28. import javax.imageio.spi.ImageWriterSpi;
  29. import javax.imageio.stream.ImageOutputStream;
  30. import javax.imageio.stream.ImageOutputStreamImpl;
  31. class CRC {
  32. private static int[] crcTable = new int[256];
  33. private int crc = 0xffffffff;
  34. static {
  35. // Initialize CRC table
  36. for (int n = 0; n < 256; n++) {
  37. int c = n;
  38. for (int k = 0; k < 8; k++) {
  39. if ((c & 1) == 1) {
  40. c = 0xedb88320 ^ (c >>> 1);
  41. } else {
  42. c >>>= 1;
  43. }
  44. crcTable[n] = c;
  45. }
  46. }
  47. }
  48. public CRC() {}
  49. public void reset() {
  50. crc = 0xffffffff;
  51. }
  52. public void update(byte[] data, int off, int len) {
  53. for (int n = 0; n < len; n++) {
  54. crc = crcTable[(crc ^ data[off + n]) & 0xff] ^ (crc >>> 8);
  55. }
  56. }
  57. public void update(int data) {
  58. crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
  59. }
  60. public int getValue() {
  61. return crc ^ 0xffffffff;
  62. }
  63. }
  64. class ChunkStream extends ImageOutputStreamImpl {
  65. private ImageOutputStream stream;
  66. private long startPos;
  67. private CRC crc = new CRC();
  68. public ChunkStream(int type, ImageOutputStream stream) throws IOException {
  69. this.stream = stream;
  70. this.startPos = stream.getStreamPosition();
  71. stream.writeInt(-1); // length, will backpatch
  72. writeInt(type);
  73. }
  74. public int read() throws IOException {
  75. throw new RuntimeException("Method not available");
  76. }
  77. public int read(byte[] b, int off, int len) throws IOException {
  78. throw new RuntimeException("Method not available");
  79. }
  80. public void write(byte[] b, int off, int len) throws IOException {
  81. crc.update(b, off, len);
  82. stream.write(b, off, len);
  83. }
  84. public void write(int b) throws IOException {
  85. crc.update(b);
  86. stream.write(b);
  87. }
  88. public void finish() throws IOException {
  89. // Write CRC
  90. stream.writeInt(crc.getValue());
  91. // Write length
  92. long pos = stream.getStreamPosition();
  93. stream.seek(startPos);
  94. stream.writeInt((int)(pos - startPos) - 12);
  95. // Return to end of chunk and flush to minimize buffering
  96. stream.seek(pos);
  97. stream.flushBefore(pos);
  98. }
  99. }
  100. // Compress output and write as a series of 'IDAT' chunks of
  101. // fixed length.
  102. class IDATOutputStream extends ImageOutputStreamImpl {
  103. private static byte[] chunkType = {
  104. (byte)'I', (byte)'D', (byte)'A', (byte)'T'
  105. };
  106. private ImageOutputStream stream;
  107. private int chunkLength;
  108. private long startPos;
  109. private CRC crc = new CRC();
  110. Deflater def = new Deflater(Deflater.BEST_COMPRESSION);
  111. byte[] buf = new byte[512];
  112. private int bytesRemaining;
  113. public IDATOutputStream(ImageOutputStream stream, int chunkLength)
  114. throws IOException {
  115. this.stream = stream;
  116. this.chunkLength = chunkLength;
  117. startChunk();
  118. }
  119. private void startChunk() throws IOException {
  120. crc.reset();
  121. this.startPos = stream.getStreamPosition();
  122. stream.writeInt(-1); // length, will backpatch
  123. crc.update(chunkType, 0, 4);
  124. stream.write(chunkType, 0, 4);
  125. this.bytesRemaining = chunkLength;
  126. }
  127. private void finishChunk() throws IOException {
  128. // Write CRC
  129. stream.writeInt(crc.getValue());
  130. // Write length
  131. long pos = stream.getStreamPosition();
  132. stream.seek(startPos);
  133. stream.writeInt((int)(pos - startPos) - 12);
  134. // Return to end of chunk and flush to minimize buffering
  135. stream.seek(pos);
  136. stream.flushBefore(pos);
  137. }
  138. public int read() throws IOException {
  139. throw new RuntimeException("Method not available");
  140. }
  141. public int read(byte[] b, int off, int len) throws IOException {
  142. throw new RuntimeException("Method not available");
  143. }
  144. public void write(byte[] b, int off, int len) throws IOException {
  145. if (len == 0) {
  146. return;
  147. }
  148. if (!def.finished()) {
  149. def.setInput(b, off, len);
  150. while (!def.needsInput()) {
  151. deflate();
  152. }
  153. }
  154. }
  155. public void deflate() throws IOException {
  156. int len = def.deflate(buf, 0, buf.length);
  157. int off = 0;
  158. while (len > 0) {
  159. if (bytesRemaining == 0) {
  160. finishChunk();
  161. startChunk();
  162. }
  163. int nbytes = Math.min(len, bytesRemaining);
  164. crc.update(buf, off, nbytes);
  165. stream.write(buf, off, nbytes);
  166. off += nbytes;
  167. len -= nbytes;
  168. bytesRemaining -= nbytes;
  169. }
  170. }
  171. public void write(int b) throws IOException {
  172. byte[] wbuf = new byte[1];
  173. wbuf[0] = (byte)b;
  174. write(wbuf, 0, 1);
  175. }
  176. public void finish() throws IOException {
  177. if (!def.finished()) {
  178. def.finish();
  179. while (!def.finished()) {
  180. deflate();
  181. }
  182. }
  183. finishChunk();
  184. }
  185. }
  186. class PNGImageWriteParam extends ImageWriteParam {
  187. public PNGImageWriteParam(Locale locale) {
  188. super();
  189. this.canWriteProgressive = true;
  190. this.locale = locale;
  191. }
  192. }
  193. /**
  194. * @version 0.5
  195. */
  196. public class PNGImageWriter extends ImageWriter {
  197. ImageOutputStream stream = null;
  198. PNGMetadata metadata = null;
  199. // Factors from the ImageWriteParam
  200. int sourceXOffset = 0;
  201. int sourceYOffset = 0;
  202. int sourceWidth = 0;
  203. int sourceHeight = 0;
  204. int[] sourceBands = null;
  205. int periodX = 1;
  206. int periodY = 1;
  207. int numBands;
  208. int bpp;
  209. RowFilter rowFilter = new RowFilter();
  210. byte[] prevRow = null;
  211. byte[] currRow = null;
  212. byte[][] filteredRows = null;
  213. // Per-band scaling tables
  214. //
  215. // After the first call to initializeScaleTables, either scale and scale0
  216. // will be valid, or scaleh and scalel will be valid, but not both.
  217. //
  218. // The tables will be designed for use with a set of input but depths
  219. // given by sampleSize, and an output bit depth given by scalingBitDepth.
  220. //
  221. int[] sampleSize = null; // Sample size per band, in bits
  222. int scalingBitDepth = -1; // Output bit depth of the scaling tables
  223. // Tables for 1, 2, 4, or 8 bit output
  224. byte[][] scale = null; // 8 bit table
  225. byte[] scale0 = null; // equivalent to scale[0]
  226. // Tables for 16 bit output
  227. byte[][] scaleh = null; // High bytes of output
  228. byte[][] scalel = null; // Low bytes of output
  229. int totalPixels; // Total number of pixels to be written by write_IDAT
  230. int pixelsDone; // Running count of pixels written by write_IDAT
  231. public PNGImageWriter(ImageWriterSpi originatingProvider) {
  232. super(originatingProvider);
  233. }
  234. public void setOutput(Object output) {
  235. super.setOutput(output);
  236. if (output != null) {
  237. if (!(output instanceof ImageOutputStream)) {
  238. throw new IllegalArgumentException("output not an ImageOutputStream!");
  239. }
  240. this.stream = (ImageOutputStream)output;
  241. } else {
  242. this.stream = null;
  243. }
  244. }
  245. private static int[] allowedProgressivePasses = { 1, 7 };
  246. public ImageWriteParam getDefaultWriteParam() {
  247. return new PNGImageWriteParam(getLocale());
  248. }
  249. public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
  250. return null;
  251. }
  252. public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
  253. ImageWriteParam param) {
  254. PNGMetadata m = new PNGMetadata();
  255. m.initialize(imageType, imageType.getSampleModel().getNumBands());
  256. return m;
  257. }
  258. public IIOMetadata convertStreamMetadata(IIOMetadata inData,
  259. ImageWriteParam param) {
  260. return null;
  261. }
  262. public IIOMetadata convertImageMetadata(IIOMetadata inData,
  263. ImageTypeSpecifier imageType,
  264. ImageWriteParam param) {
  265. // TODO - deal with imageType
  266. if (inData instanceof PNGMetadata) {
  267. return (PNGMetadata)((PNGMetadata)inData).clone();
  268. } else {
  269. return new PNGMetadata(inData);
  270. }
  271. }
  272. private void write_magic() throws IOException {
  273. // Write signature
  274. byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
  275. stream.write(magic);
  276. }
  277. private void write_IHDR() throws IOException {
  278. // Write IHDR chunk
  279. ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream);
  280. cs.writeInt(metadata.IHDR_width);
  281. cs.writeInt(metadata.IHDR_height);
  282. cs.writeByte(metadata.IHDR_bitDepth);
  283. cs.writeByte(metadata.IHDR_colorType);
  284. if (metadata.IHDR_compressionMethod != 0) {
  285. throw new IIOException(
  286. "Only compression method 0 is defined in PNG 1.1");
  287. }
  288. cs.writeByte(metadata.IHDR_compressionMethod);
  289. if (metadata.IHDR_filterMethod != 0) {
  290. throw new IIOException(
  291. "Only filter method 0 is defined in PNG 1.1");
  292. }
  293. cs.writeByte(metadata.IHDR_filterMethod);
  294. if (metadata.IHDR_interlaceMethod < 0 ||
  295. metadata.IHDR_interlaceMethod > 1) {
  296. throw new IIOException(
  297. "Only interlace methods 0 (node) and 1 (adam7) are defined in PNG 1.1");
  298. }
  299. cs.writeByte(metadata.IHDR_interlaceMethod);
  300. cs.finish();
  301. }
  302. private void write_cHRM() throws IOException {
  303. if (metadata.cHRM_present) {
  304. ChunkStream cs = new ChunkStream(PNGImageReader.cHRM_TYPE, stream);
  305. cs.writeInt(metadata.cHRM_whitePointX);
  306. cs.writeInt(metadata.cHRM_whitePointY);
  307. cs.writeInt(metadata.cHRM_redX);
  308. cs.writeInt(metadata.cHRM_redY);
  309. cs.writeInt(metadata.cHRM_greenX);
  310. cs.writeInt(metadata.cHRM_greenY);
  311. cs.writeInt(metadata.cHRM_blueX);
  312. cs.writeInt(metadata.cHRM_blueY);
  313. cs.finish();
  314. }
  315. }
  316. private void write_gAMA() throws IOException {
  317. if (metadata.gAMA_present) {
  318. ChunkStream cs = new ChunkStream(PNGImageReader.gAMA_TYPE, stream);
  319. cs.writeInt(metadata.gAMA_gamma);
  320. cs.finish();
  321. }
  322. }
  323. private void write_iCCP() throws IOException {
  324. if (metadata.iCCP_present) {
  325. ChunkStream cs = new ChunkStream(PNGImageReader.iCCP_TYPE, stream);
  326. cs.writeBytes(metadata.iCCP_profileName);
  327. cs.writeByte(0); // null terminator
  328. cs.writeByte(metadata.iCCP_compressionMethod);
  329. cs.write(metadata.iCCP_compressedProfile);
  330. cs.finish();
  331. }
  332. }
  333. private void write_sBIT() throws IOException {
  334. if (metadata.sBIT_present) {
  335. ChunkStream cs = new ChunkStream(PNGImageReader.sBIT_TYPE, stream);
  336. int colorType = metadata.IHDR_colorType;
  337. if (metadata.sBIT_colorType != colorType) {
  338. processWarningOccurred(0,
  339. "sBIT metadata has wrong color type.\n" +
  340. "The chunk will not be written.");
  341. return;
  342. }
  343. if (colorType == PNGImageReader.PNG_COLOR_GRAY ||
  344. colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
  345. cs.writeByte(metadata.sBIT_grayBits);
  346. } else if (colorType == PNGImageReader.PNG_COLOR_RGB ||
  347. colorType == PNGImageReader.PNG_COLOR_PALETTE ||
  348. colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
  349. cs.writeByte(metadata.sBIT_redBits);
  350. cs.writeByte(metadata.sBIT_greenBits);
  351. cs.writeByte(metadata.sBIT_blueBits);
  352. }
  353. if (colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
  354. colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
  355. cs.writeByte(metadata.sBIT_alphaBits);
  356. }
  357. cs.finish();
  358. }
  359. }
  360. private void write_sRGB() throws IOException {
  361. if (metadata.sRGB_present) {
  362. ChunkStream cs = new ChunkStream(PNGImageReader.sRGB_TYPE, stream);
  363. cs.writeByte(metadata.sRGB_renderingIntent);
  364. cs.finish();
  365. }
  366. }
  367. private void write_PLTE() throws IOException {
  368. if (metadata.PLTE_present) {
  369. if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY ||
  370. metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
  371. // PLTE cannot occur in a gray image
  372. processWarningOccurred(0,
  373. "A PLTE chunk may not appear in a gray or gray alpha image.\n" +
  374. "The chunk will not be written");
  375. return;
  376. }
  377. ChunkStream cs = new ChunkStream(PNGImageReader.PLTE_TYPE, stream);
  378. int numEntries = metadata.PLTE_red.length;
  379. byte[] palette = new byte[numEntries*3];
  380. int index = 0;
  381. for (int i = 0; i < numEntries; i++) {
  382. palette[index++] = metadata.PLTE_red[i];
  383. palette[index++] = metadata.PLTE_green[i];
  384. palette[index++] = metadata.PLTE_blue[i];
  385. }
  386. cs.write(palette);
  387. cs.finish();
  388. }
  389. }
  390. private void write_hIST() throws IOException, IIOException {
  391. if (metadata.hIST_present) {
  392. ChunkStream cs = new ChunkStream(PNGImageReader.hIST_TYPE, stream);
  393. if (!metadata.PLTE_present) {
  394. throw new IIOException("hIST chunk without PLTE chunk!");
  395. }
  396. cs.writeChars(metadata.hIST_histogram,
  397. 0, metadata.hIST_histogram.length);
  398. cs.finish();
  399. }
  400. }
  401. private void write_tRNS() throws IOException, IIOException {
  402. if (metadata.tRNS_present) {
  403. ChunkStream cs = new ChunkStream(PNGImageReader.tRNS_TYPE, stream);
  404. int colorType = metadata.IHDR_colorType;
  405. int chunkType = metadata.tRNS_colorType;
  406. // Special case: image is RGB and chunk is Gray
  407. // Promote chunk contents to RGB
  408. int chunkRed = metadata.tRNS_red;
  409. int chunkGreen = metadata.tRNS_green;
  410. int chunkBlue = metadata.tRNS_blue;
  411. if (colorType == PNGImageReader.PNG_COLOR_RGB &&
  412. chunkType == PNGImageReader.PNG_COLOR_GRAY) {
  413. chunkType = colorType;
  414. chunkRed = chunkGreen = chunkBlue =
  415. metadata.tRNS_gray;
  416. }
  417. if (chunkType != colorType) {
  418. processWarningOccurred(0,
  419. "tRNS metadata has incompatible color type.\n" +
  420. "The chunk will not be written.");
  421. return;
  422. }
  423. if (colorType == PNGImageReader.PNG_COLOR_PALETTE) {
  424. if (!metadata.PLTE_present) {
  425. throw new IIOException("tRNS chunk without PLTE chunk!");
  426. }
  427. cs.write(metadata.tRNS_alpha);
  428. } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) {
  429. cs.writeShort(metadata.tRNS_gray);
  430. } else if (colorType == PNGImageReader.PNG_COLOR_RGB) {
  431. cs.writeShort(chunkRed);
  432. cs.writeShort(chunkGreen);
  433. cs.writeShort(chunkBlue);
  434. } else {
  435. throw new IIOException("tRNS chunk for color type 4 or 6!");
  436. }
  437. cs.finish();
  438. }
  439. }
  440. private void write_bKGD() throws IOException {
  441. if (metadata.bKGD_present) {
  442. ChunkStream cs = new ChunkStream(PNGImageReader.bKGD_TYPE, stream);
  443. int colorType = metadata.IHDR_colorType & 0x3;
  444. int chunkType = metadata.bKGD_colorType;
  445. // Special case: image is RGB(A) and chunk is Gray
  446. // Promote chunk contents to RGB
  447. int chunkRed = metadata.bKGD_red;
  448. int chunkGreen = metadata.bKGD_red;
  449. int chunkBlue = metadata.bKGD_red;
  450. if (colorType == PNGImageReader.PNG_COLOR_RGB &&
  451. chunkType == PNGImageReader.PNG_COLOR_GRAY) {
  452. // Make a gray bKGD chunk look like RGB
  453. chunkType = colorType;
  454. chunkRed = chunkGreen = chunkBlue =
  455. metadata.bKGD_gray;
  456. }
  457. // Ignore status of alpha in colorType
  458. if (chunkType != colorType) {
  459. processWarningOccurred(0,
  460. "bKGD metadata has incompatible color type.\n" +
  461. "The chunk will not be written.");
  462. return;
  463. }
  464. if (colorType == PNGImageReader.PNG_COLOR_PALETTE) {
  465. cs.writeByte(metadata.bKGD_index);
  466. } else if (colorType == PNGImageReader.PNG_COLOR_GRAY ||
  467. colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
  468. cs.writeShort(metadata.bKGD_gray);
  469. } else { // colorType == PNGImageReader.PNG_COLOR_RGB ||
  470. // colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA
  471. cs.writeShort(chunkRed);
  472. cs.writeShort(chunkGreen);
  473. cs.writeShort(chunkBlue);
  474. }
  475. cs.finish();
  476. }
  477. }
  478. private void write_pHYs() throws IOException {
  479. if (metadata.pHYs_present) {
  480. ChunkStream cs = new ChunkStream(PNGImageReader.pHYs_TYPE, stream);
  481. cs.writeInt(metadata.pHYs_pixelsPerUnitXAxis);
  482. cs.writeInt(metadata.pHYs_pixelsPerUnitYAxis);
  483. cs.writeByte(metadata.pHYs_unitSpecifier);
  484. cs.finish();
  485. }
  486. }
  487. private void write_sPLT() throws IOException {
  488. if (metadata.sPLT_present) {
  489. ChunkStream cs = new ChunkStream(PNGImageReader.sPLT_TYPE, stream);
  490. cs.writeBytes(metadata.sPLT_paletteName);
  491. cs.writeByte(0); // null terminator
  492. cs.writeByte(metadata.sPLT_sampleDepth);
  493. int numEntries = metadata.sPLT_red.length;
  494. if (metadata.sPLT_sampleDepth == 8) {
  495. for (int i = 0; i < numEntries; i++) {
  496. cs.writeByte(metadata.sPLT_red[i]);
  497. cs.writeByte(metadata.sPLT_green[i]);
  498. cs.writeByte(metadata.sPLT_blue[i]);
  499. cs.writeByte(metadata.sPLT_alpha[i]);
  500. cs.writeShort(metadata.sPLT_frequency[i]);
  501. }
  502. } else { // sampleDepth == 16
  503. for (int i = 0; i < numEntries; i++) {
  504. cs.writeShort(metadata.sPLT_red[i]);
  505. cs.writeShort(metadata.sPLT_green[i]);
  506. cs.writeShort(metadata.sPLT_blue[i]);
  507. cs.writeShort(metadata.sPLT_alpha[i]);
  508. cs.writeShort(metadata.sPLT_frequency[i]);
  509. }
  510. }
  511. cs.finish();
  512. }
  513. }
  514. private void write_tIME() throws IOException {
  515. if (metadata.tIME_present) {
  516. ChunkStream cs = new ChunkStream(PNGImageReader.tIME_TYPE, stream);
  517. cs.writeShort(metadata.tIME_year);
  518. cs.writeByte(metadata.tIME_month);
  519. cs.writeByte(metadata.tIME_day);
  520. cs.writeByte(metadata.tIME_hour);
  521. cs.writeByte(metadata.tIME_minute);
  522. cs.writeByte(metadata.tIME_second);
  523. cs.finish();
  524. }
  525. }
  526. private void write_tEXt() throws IOException {
  527. Iterator keywordIter = metadata.tEXt_keyword.iterator();
  528. Iterator textIter = metadata.tEXt_text.iterator();
  529. while (keywordIter.hasNext()) {
  530. ChunkStream cs = new ChunkStream(PNGImageReader.tEXt_TYPE, stream);
  531. String keyword = (String)keywordIter.next();
  532. cs.writeBytes(keyword);
  533. cs.writeByte(0);
  534. String text = (String)textIter.next();
  535. cs.writeBytes(text);
  536. cs.finish();
  537. }
  538. }
  539. private byte[] deflate(String s) throws IOException {
  540. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  541. DeflaterOutputStream dos = new DeflaterOutputStream(baos);
  542. int len = s.length();
  543. for (int i = 0; i < len; i++) {
  544. dos.write((int)s.charAt(i));
  545. }
  546. dos.close();
  547. return baos.toByteArray();
  548. }
  549. private void write_iTXt() throws IOException {
  550. Iterator keywordIter = metadata.iTXt_keyword.iterator();
  551. Iterator flagIter = metadata.iTXt_compressionFlag.iterator();
  552. Iterator methodIter = metadata.iTXt_compressionMethod.iterator();
  553. Iterator languageIter = metadata.iTXt_languageTag.iterator();
  554. Iterator translatedKeywordIter =
  555. metadata.iTXt_translatedKeyword.iterator();
  556. Iterator textIter = metadata.iTXt_text.iterator();
  557. while (keywordIter.hasNext()) {
  558. ChunkStream cs = new ChunkStream(PNGImageReader.iTXt_TYPE, stream);
  559. String keyword = (String)keywordIter.next();
  560. cs.writeBytes(keyword);
  561. cs.writeByte(0);
  562. int flag = ((Integer)flagIter.next()).intValue();
  563. cs.writeByte(flag);
  564. int method = ((Integer)methodIter.next()).intValue();
  565. cs.writeByte(method);
  566. String languageTag = (String)languageIter.next();
  567. cs.writeBytes(languageTag);
  568. cs.writeByte(0);
  569. String translatedKeyword = (String)translatedKeywordIter.next();
  570. cs.writeBytes(translatedKeyword);
  571. cs.writeByte(0);
  572. String text = (String)textIter.next();
  573. if (flag == 1) {
  574. cs.write(deflate(text));
  575. } else {
  576. cs.writeUTF(text);
  577. }
  578. cs.finish();
  579. }
  580. }
  581. private void write_zTXt() throws IOException {
  582. Iterator keywordIter = metadata.zTXt_keyword.iterator();
  583. Iterator methodIter = metadata.zTXt_compressionMethod.iterator();
  584. Iterator textIter = metadata.zTXt_text.iterator();
  585. while (keywordIter.hasNext()) {
  586. ChunkStream cs = new ChunkStream(PNGImageReader.zTXt_TYPE, stream);
  587. String keyword = (String)keywordIter.next();
  588. cs.writeBytes(keyword);
  589. cs.writeByte(0);
  590. int compressionMethod = ((Integer)methodIter.next()).intValue();
  591. cs.writeByte(compressionMethod);
  592. String text = (String)textIter.next();
  593. cs.write(deflate(text));
  594. cs.finish();
  595. }
  596. }
  597. private void writeUnknownChunks() throws IOException {
  598. Iterator typeIter = metadata.unknownChunkType.iterator();
  599. Iterator dataIter = metadata.unknownChunkData.iterator();
  600. while (typeIter.hasNext() && dataIter.hasNext()) {
  601. String type = (String)typeIter.next();
  602. ChunkStream cs = new ChunkStream(PNGImageReader.chunkType(type),
  603. stream);
  604. byte[] data = (byte[])dataIter.next();
  605. cs.write(data);
  606. cs.finish();
  607. }
  608. }
  609. private void encodePass(ImageOutputStream os,
  610. RenderedImage image,
  611. int xOffset, int yOffset,
  612. int xSkip, int ySkip) throws IOException {
  613. int minX = sourceXOffset;
  614. int minY = sourceYOffset;
  615. int width = sourceWidth;
  616. int height = sourceHeight;
  617. // Adjust offsets and skips based on source subsampling factors
  618. xOffset *= periodX;
  619. xSkip *= periodX;
  620. yOffset *= periodY;
  621. ySkip *= periodY;
  622. // Early exit if no data for this pass
  623. int hpixels = (width - xOffset + xSkip - 1)/xSkip;
  624. int vpixels = (height - yOffset + ySkip - 1)/ySkip;
  625. if (hpixels == 0 || vpixels == 0) {
  626. return;
  627. }
  628. // Convert X offset and skip from pixels to samples
  629. xOffset *= numBands;
  630. xSkip *= numBands;
  631. // Create row buffers
  632. int samplesPerByte = 8/metadata.IHDR_bitDepth;
  633. int numSamples = width*numBands;
  634. int[] samples = new int[numSamples];
  635. int bytesPerRow = hpixels*numBands;
  636. if (metadata.IHDR_bitDepth < 8) {
  637. bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte;
  638. } else if (metadata.IHDR_bitDepth == 16) {
  639. bytesPerRow *= 2;
  640. }
  641. currRow = new byte[bytesPerRow + bpp];
  642. prevRow = new byte[bytesPerRow + bpp];
  643. filteredRows = new byte[5][bytesPerRow + bpp];
  644. int bitDepth = metadata.IHDR_bitDepth;
  645. for (int row = minY + yOffset; row < minY + height; row += ySkip) {
  646. Rectangle rect = new Rectangle(minX, row, width, 1);
  647. Raster ras = image.getData(rect);
  648. if (sourceBands != null) {
  649. ras = ras.createChild(minX, row, width, 1, minX, row,
  650. sourceBands);
  651. }
  652. // TODO - need to divide out premultiplied alpha?
  653. ras.getPixels(minX, row, width, 1, samples);
  654. // Reorder palette data if necessary
  655. int[] paletteOrder = metadata.PLTE_order;
  656. if (paletteOrder != null) {
  657. for (int i = 0; i < numSamples; i++) {
  658. samples[i] = paletteOrder[samples[i]];
  659. }
  660. }
  661. int count = bpp; // leave first 'bpp' bytes zero
  662. int pos = 0;
  663. int tmp = 0;
  664. switch (bitDepth) {
  665. case 1: case 2: case 4:
  666. // Image can only have a single band
  667. int mask = samplesPerByte - 1;
  668. for (int s = xOffset; s < numSamples; s += xSkip) {
  669. byte val = scale0[samples[s]];
  670. tmp = (tmp << bitDepth) | val;
  671. if ((pos++ & mask) == mask) {
  672. currRow[count++] = (byte)tmp;
  673. tmp = 0;
  674. pos = 0;
  675. }
  676. }
  677. // Left shift the last byte
  678. if ((pos & mask) != 0) {
  679. tmp <<= ((8/bitDepth) - pos)*bitDepth;
  680. currRow[count++] = (byte)tmp;
  681. }
  682. break;
  683. case 8:
  684. if (numBands == 1) {
  685. for (int s = xOffset; s < numSamples; s += xSkip) {
  686. currRow[count++] = scale0[samples[s]];
  687. }
  688. } else {
  689. for (int s = xOffset; s < numSamples; s += xSkip) {
  690. for (int b = 0; b < numBands; b++) {
  691. currRow[count++] = scale[b][samples[s + b]];
  692. }
  693. }
  694. }
  695. break;
  696. case 16:
  697. for (int s = xOffset; s < numSamples; s += xSkip) {
  698. for (int b = 0; b < numBands; b++) {
  699. currRow[count++] = scaleh[b][samples[s + b]];
  700. currRow[count++] = scalel[b][samples[s + b]];
  701. }
  702. }
  703. break;
  704. }
  705. // Perform filtering
  706. int filterType = rowFilter.filterRow(metadata.IHDR_colorType,
  707. currRow, prevRow,
  708. filteredRows,
  709. bytesPerRow, bpp);
  710. os.write(filterType);
  711. os.write(filteredRows[filterType], bpp, bytesPerRow);
  712. // Swap current and previous rows
  713. byte[] swap = currRow;
  714. currRow = prevRow;
  715. prevRow = swap;
  716. pixelsDone += hpixels;
  717. processImageProgress(100.0F*pixelsDonetotalPixels);
  718. // If write has been aborted, just return;
  719. // processWriteAborted will be called later
  720. if (abortRequested()) {
  721. return;
  722. }
  723. }
  724. }
  725. // Use sourceXOffset, etc.
  726. private void write_IDAT(RenderedImage image) throws IOException {
  727. IDATOutputStream ios = new IDATOutputStream(stream, 32768);
  728. if (metadata.IHDR_interlaceMethod == 1) {
  729. for (int i = 0; i < 7; i++) {
  730. encodePass(ios, image,
  731. PNGImageReader.adam7XOffset[i],
  732. PNGImageReader.adam7YOffset[i],
  733. PNGImageReader.adam7XSubsampling[i],
  734. PNGImageReader.adam7YSubsampling[i]);
  735. if (abortRequested()) {
  736. break;
  737. }
  738. }
  739. } else {
  740. encodePass(ios, image, 0, 0, 1, 1);
  741. }
  742. ios.finish();
  743. }
  744. private void writeIEND() throws IOException {
  745. ChunkStream cs = new ChunkStream(PNGImageReader.IEND_TYPE, stream);
  746. cs.finish();
  747. }
  748. // Check two int arrays for value equality, always returns false
  749. // if either array is null
  750. private boolean equals(int[] s0, int[] s1) {
  751. if (s0 == null || s1 == null) {
  752. return false;
  753. }
  754. if (s0.length != s1.length) {
  755. return false;
  756. }
  757. for (int i = 0; i < s0.length; i++) {
  758. if (s0[i] != s1[i]) {
  759. return false;
  760. }
  761. }
  762. return true;
  763. }
  764. // Initialize the scale/scale0 or scaleh/scalel arrays to
  765. // hold the results of scaling an input value to the desired
  766. // output bit depth
  767. private void initializeScaleTables(int[] sampleSize) {
  768. int bitDepth = metadata.IHDR_bitDepth;
  769. // If the existing tables are still valid, just return
  770. if (bitDepth == scalingBitDepth &&
  771. equals(sampleSize, this.sampleSize)) {
  772. return;
  773. }
  774. // Compute new tables
  775. this.sampleSize = sampleSize;
  776. this.scalingBitDepth = bitDepth;
  777. int maxOutSample = (1 << bitDepth) - 1;
  778. if (bitDepth <= 8) {
  779. scale = new byte[numBands][];
  780. for (int b = 0; b < numBands; b++) {
  781. int maxInSample = (1 << sampleSize[b]) - 1;
  782. int halfMaxInSample = maxInSample2;
  783. scale[b] = new byte[maxInSample + 1];
  784. for (int s = 0; s <= maxInSample; s++) {
  785. scale[b][s] =
  786. (byte)((s*maxOutSample + halfMaxInSample)/maxInSample);
  787. }
  788. }
  789. scale0 = scale[0];
  790. scaleh = scalel = null;
  791. } else { // bitDepth == 16
  792. // Divide scaling table into high and low bytes
  793. scaleh = new byte[numBands][];
  794. scalel = new byte[numBands][];
  795. for (int b = 0; b < numBands; b++) {
  796. int maxInSample = (1 << sampleSize[b]) - 1;
  797. int halfMaxInSample = maxInSample2;
  798. scaleh[b] = new byte[maxInSample + 1];
  799. scalel[b] = new byte[maxInSample + 1];
  800. for (int s = 0; s <= maxInSample; s++) {
  801. int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
  802. scaleh[b][s] = (byte)(val >> 8);
  803. scalel[b][s] = (byte)(val & 0xff);
  804. }
  805. }
  806. scale = null;
  807. scale0 = null;
  808. }
  809. }
  810. public void write(IIOMetadata streamMetadata,
  811. IIOImage image,
  812. ImageWriteParam param) throws IIOException {
  813. if (stream == null) {
  814. throw new IllegalStateException("output == null!");
  815. }
  816. if (image == null) {
  817. throw new IllegalArgumentException("image == null!");
  818. }
  819. if (image.hasRaster()) {
  820. throw new UnsupportedOperationException("image has a Raster!");
  821. }
  822. RenderedImage im = image.getRenderedImage();
  823. SampleModel sampleModel = im.getSampleModel();
  824. this.numBands = sampleModel.getNumBands();
  825. // Set source region and subsampling to default values
  826. this.sourceXOffset = im.getMinX();
  827. this.sourceYOffset = im.getMinY();
  828. this.sourceWidth = im.getWidth();
  829. this.sourceHeight = im.getHeight();
  830. this.sourceBands = null;
  831. this.periodX = 1;
  832. this.periodY = 1;
  833. if (param != null) {
  834. // Get source region and subsampling factors
  835. Rectangle sourceRegion = param.getSourceRegion();
  836. if (sourceRegion != null) {
  837. Rectangle imageBounds = new Rectangle(im.getMinX(),
  838. im.getMinY(),
  839. im.getWidth(),
  840. im.getHeight());
  841. // Clip to actual image bounds
  842. sourceRegion = sourceRegion.intersection(imageBounds);
  843. sourceXOffset = sourceRegion.x;
  844. sourceYOffset = sourceRegion.y;
  845. sourceWidth = sourceRegion.width;
  846. sourceHeight = sourceRegion.height;
  847. }
  848. // Adjust for subsampling offsets
  849. int gridX = param.getSubsamplingXOffset();
  850. int gridY = param.getSubsamplingYOffset();
  851. sourceXOffset += gridX;
  852. sourceYOffset += gridY;
  853. sourceWidth -= gridX;
  854. sourceHeight -= gridY;
  855. // Get subsampling factors
  856. periodX = param.getSourceXSubsampling();
  857. periodY = param.getSourceYSubsampling();
  858. int[] sBands = param.getSourceBands();
  859. if (sBands != null) {
  860. sourceBands = sBands;
  861. numBands = sourceBands.length;
  862. }
  863. }
  864. // Compute output dimensions
  865. int destWidth = (sourceWidth + periodX - 1)/periodX;
  866. int destHeight = (sourceHeight + periodY - 1)/periodY;
  867. if (destWidth <= 0 || destHeight <= 0) {
  868. throw new IllegalArgumentException("Empty source region!");
  869. }
  870. // Compute total number of pixels for progress notification
  871. this.totalPixels = destWidth*destHeight;
  872. this.pixelsDone = 0;
  873. // Create metadata
  874. IIOMetadata imd = image.getMetadata();
  875. if (imd != null) {
  876. metadata = (PNGMetadata)convertImageMetadata(imd,
  877. ImageTypeSpecifier.createFromRenderedImage(im),
  878. null);
  879. } else {
  880. metadata = new PNGMetadata();
  881. }
  882. if (param != null) {
  883. // Use Adam7 interlacing if set in write param
  884. switch (param.getProgressiveMode()) {
  885. case ImageWriteParam.MODE_DEFAULT:
  886. metadata.IHDR_interlaceMethod = 1;
  887. break;
  888. case ImageWriteParam.MODE_DISABLED:
  889. metadata.IHDR_interlaceMethod = 0;
  890. break;
  891. // MODE_COPY_FROM_METADATA should alreay be taken care of
  892. // MODE_EXPLICIT is not allowed
  893. }
  894. }
  895. // Initialize bitDepth and colorType
  896. metadata.initialize(new ImageTypeSpecifier(im), numBands);
  897. // Overwrite IHDR width and height values with values from image
  898. metadata.IHDR_width = destWidth;
  899. metadata.IHDR_height = destHeight;
  900. this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1);
  901. // Initialize scaling tables for this image
  902. initializeScaleTables(sampleModel.getSampleSize());
  903. clearAbortRequest();
  904. processImageStarted(0);
  905. try {
  906. write_magic();
  907. write_IHDR();
  908. write_cHRM();
  909. write_gAMA();
  910. write_iCCP();
  911. write_sBIT();
  912. write_sRGB();
  913. write_PLTE();
  914. write_hIST();
  915. write_tRNS();
  916. write_bKGD();
  917. write_pHYs();
  918. write_sPLT();
  919. write_tIME();
  920. write_tEXt();
  921. write_iTXt();
  922. write_zTXt();
  923. writeUnknownChunks();
  924. write_IDAT(im);
  925. if (abortRequested()) {
  926. processWriteAborted();
  927. } else {
  928. // Finish up and inform the listeners we are done
  929. writeIEND();
  930. processImageComplete();
  931. }
  932. } catch (IOException e) {
  933. throw new IIOException("I/O error writing PNG file!", e);
  934. }
  935. }
  936. }