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