1. /*
  2. * @(#)PNGMetadata.java 1.41 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.image.ColorModel;
  9. import java.awt.image.IndexColorModel;
  10. import java.awt.image.SampleModel;
  11. import java.util.ArrayList;
  12. import java.util.Iterator;
  13. import java.util.StringTokenizer;
  14. import javax.imageio.ImageTypeSpecifier;
  15. import javax.imageio.metadata.IIOInvalidTreeException;
  16. import javax.imageio.metadata.IIOMetadata;
  17. import javax.imageio.metadata.IIOMetadataFormat;
  18. import javax.imageio.metadata.IIOMetadataFormatImpl;
  19. import javax.imageio.metadata.IIOMetadataNode;
  20. import org.w3c.dom.Node;
  21. /**
  22. * @version 0.5
  23. */
  24. public class PNGMetadata extends IIOMetadata implements Cloneable {
  25. // package scope
  26. public static final String
  27. nativeMetadataFormatName = "javax_imageio_png_1.0";
  28. protected static final String nativeMetadataFormatClassName
  29. = "com.sun.imageio.plugins.png.PNGMetadataFormat";
  30. // Color types for IHDR chunk
  31. public static final String[] IHDR_colorTypeNames = {
  32. "Grayscale", null, "RGB", "Palette",
  33. "GrayAlpha", null, "RGBAlpha"
  34. };
  35. public static final int[] IHDR_numChannels = {
  36. 1, 0, 3, 3, 2, 0, 4
  37. };
  38. // Bit depths for IHDR chunk
  39. public static final String[] IHDR_bitDepths = {
  40. "1", "2", "4", "8", "16"
  41. };
  42. // Compression methods for IHDR chunk
  43. public static final String[] IHDR_compressionMethodNames = {
  44. "deflate"
  45. };
  46. // Filter methods for IHDR chunk
  47. public static final String[] IHDR_filterMethodNames = {
  48. "adaptive"
  49. };
  50. // Interlace methods for IHDR chunk
  51. public static final String[] IHDR_interlaceMethodNames = {
  52. "none", "adam7"
  53. };
  54. // Compression methods for iCCP chunk
  55. public static final String[] iCCP_compressionMethodNames = {
  56. "deflate"
  57. };
  58. // Compression methods for zTXt chunk
  59. public static final String[] zTXt_compressionMethodNames = {
  60. "deflate"
  61. };
  62. // "Unknown" unit for pHYs chunk
  63. public static final int PHYS_UNIT_UNKNOWN = 0;
  64. // "Meter" unit for pHYs chunk
  65. public static final int PHYS_UNIT_METER = 1;
  66. // Unit specifiers for pHYs chunk
  67. public static final String[] unitSpecifierNames = {
  68. "unknown", "meter"
  69. };
  70. // Rendering intents for sRGB chunk
  71. public static final String[] renderingIntentNames = {
  72. "Perceptual", // 0
  73. "Relative colorimetric", // 1
  74. "Saturation", // 2
  75. "Absolute colorimetric" // 3
  76. };
  77. // Color space types for Chroma->ColorSpaceType node
  78. public static final String[] colorSpaceTypeNames = {
  79. "GRAY", null, "RGB", "RGB",
  80. "GRAY", null, "RGB"
  81. };
  82. // IHDR chunk
  83. public boolean IHDR_present;
  84. public int IHDR_width;
  85. public int IHDR_height;
  86. public int IHDR_bitDepth;
  87. public int IHDR_colorType;
  88. public int IHDR_compressionMethod;
  89. public int IHDR_filterMethod;
  90. public int IHDR_interlaceMethod; // 0 == none, 1 == adam7
  91. // PLTE chunk
  92. public boolean PLTE_present;
  93. public byte[] PLTE_red;
  94. public byte[] PLTE_green;
  95. public byte[] PLTE_blue;
  96. // If non-null, used to reorder palette entries during encoding in
  97. // order to minimize the size of the tRNS chunk. Thus an index of
  98. // 'i' in the source should be encoded as index 'PLTE_order[i]'.
  99. // PLTE_order will be null unless 'initialize' is called with an
  100. // IndexColorModel image type.
  101. public int[] PLTE_order = null;
  102. // bKGD chunk
  103. // If external (non-PNG sourced) data has red = green = blue,
  104. // always store it as gray and promote when writing
  105. public boolean bKGD_present;
  106. public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
  107. public int bKGD_index;
  108. public int bKGD_gray;
  109. public int bKGD_red;
  110. public int bKGD_green;
  111. public int bKGD_blue;
  112. // cHRM chunk
  113. public boolean cHRM_present;
  114. public int cHRM_whitePointX;
  115. public int cHRM_whitePointY;
  116. public int cHRM_redX;
  117. public int cHRM_redY;
  118. public int cHRM_greenX;
  119. public int cHRM_greenY;
  120. public int cHRM_blueX;
  121. public int cHRM_blueY;
  122. // gAMA chunk
  123. public boolean gAMA_present;
  124. public int gAMA_gamma;
  125. // hIST chunk
  126. public boolean hIST_present;
  127. public char[] hIST_histogram;
  128. // iCCP chunk
  129. public boolean iCCP_present;
  130. public String iCCP_profileName;
  131. public int iCCP_compressionMethod;
  132. public byte[] iCCP_compressedProfile;
  133. // iTXt chunk
  134. public ArrayList iTXt_keyword = new ArrayList(); // Strings
  135. public ArrayList iTXt_compressionFlag = new ArrayList(); // Integers
  136. public ArrayList iTXt_compressionMethod = new ArrayList(); // Integers
  137. public ArrayList iTXt_languageTag = new ArrayList(); // Strings
  138. public ArrayList iTXt_translatedKeyword = new ArrayList(); // Strings
  139. public ArrayList iTXt_text = new ArrayList(); // Strings
  140. // pHYs chunk
  141. public boolean pHYs_present;
  142. public int pHYs_pixelsPerUnitXAxis;
  143. public int pHYs_pixelsPerUnitYAxis;
  144. public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter
  145. // sBIT chunk
  146. public boolean sBIT_present;
  147. public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA
  148. public int sBIT_grayBits;
  149. public int sBIT_redBits;
  150. public int sBIT_greenBits;
  151. public int sBIT_blueBits;
  152. public int sBIT_alphaBits;
  153. // sPLT chunk
  154. public boolean sPLT_present;
  155. public String sPLT_paletteName; // 1-79 characters
  156. public int sPLT_sampleDepth; // 8 or 16
  157. public int[] sPLT_red;
  158. public int[] sPLT_green;
  159. public int[] sPLT_blue;
  160. public int[] sPLT_alpha;
  161. public int[] sPLT_frequency;
  162. // sRGB chunk
  163. public boolean sRGB_present;
  164. public int sRGB_renderingIntent;
  165. // tEXt chunk
  166. public ArrayList tEXt_keyword = new ArrayList(); // 1-79 char Strings
  167. public ArrayList tEXt_text = new ArrayList(); // Strings
  168. // tIME chunk
  169. public boolean tIME_present;
  170. public int tIME_year;
  171. public int tIME_month;
  172. public int tIME_day;
  173. public int tIME_hour;
  174. public int tIME_minute;
  175. public int tIME_second;
  176. // tRNS chunk
  177. // If external (non-PNG sourced) data has red = green = blue,
  178. // always store it as gray and promote when writing
  179. public boolean tRNS_present;
  180. public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
  181. public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc.
  182. public int tRNS_gray;
  183. public int tRNS_red;
  184. public int tRNS_green;
  185. public int tRNS_blue;
  186. // zTXt chunk
  187. public ArrayList zTXt_keyword = new ArrayList(); // Strings
  188. public ArrayList zTXt_compressionMethod = new ArrayList(); // Integers
  189. public ArrayList zTXt_text = new ArrayList(); // Strings
  190. // Unknown chunks
  191. public ArrayList unknownChunkType = new ArrayList(); // Strings
  192. public ArrayList unknownChunkData = new ArrayList(); // byte arrays
  193. public PNGMetadata() {
  194. super(true,
  195. nativeMetadataFormatName,
  196. nativeMetadataFormatClassName,
  197. null, null);
  198. }
  199. public PNGMetadata(IIOMetadata metadata) {
  200. // TODO -- implement
  201. }
  202. /**
  203. * Sets the IHDR_bitDepth and IHDR_colorType variables.
  204. * The <code>numBands</code> parameter is necessary since
  205. * we may only be writing a subset of the image bands.
  206. */
  207. public void initialize(ImageTypeSpecifier imageType, int numBands) {
  208. ColorModel colorModel = imageType.getColorModel();
  209. SampleModel sampleModel = imageType.getSampleModel();
  210. // Initialize IHDR_bitDepth
  211. int[] sampleSize = sampleModel.getSampleSize();
  212. int bitDepth = sampleSize[0];
  213. // Choose max bit depth over all channels
  214. // Fixes bug 4413109
  215. for (int i = 1; i < sampleSize.length; i++) {
  216. if (sampleSize[i] > bitDepth) {
  217. bitDepth = sampleSize[i];
  218. }
  219. }
  220. // Multi-channel images must have a bit depth of 8 or 16
  221. if (sampleSize.length > 1 && bitDepth < 8) {
  222. bitDepth = 8;
  223. }
  224. // Round bit depth up to a power of 2
  225. if (bitDepth > 2 && bitDepth < 4) {
  226. bitDepth = 4;
  227. } else if (bitDepth > 4 && bitDepth < 8) {
  228. bitDepth = 8;
  229. } else if (bitDepth > 8 && bitDepth < 16) {
  230. bitDepth = 16;
  231. } else if (bitDepth > 16) {
  232. throw new RuntimeException("bitDepth > 16!");
  233. }
  234. IHDR_bitDepth = bitDepth;
  235. // Initialize IHDR_colorType
  236. if (colorModel instanceof IndexColorModel) {
  237. IndexColorModel icm = (IndexColorModel)colorModel;
  238. int size = icm.getMapSize();
  239. byte[] reds = new byte[size];
  240. icm.getReds(reds);
  241. byte[] greens = new byte[size];
  242. icm.getGreens(greens);
  243. byte[] blues = new byte[size];
  244. icm.getBlues(blues);
  245. // Determine whether the color tables are actually a gray ramp
  246. // if the color type has not been set previously
  247. boolean isGray = false;
  248. if (!IHDR_present ||
  249. (IHDR_colorType != PNGImageReader.PNG_COLOR_PALETTE)) {
  250. isGray = true;
  251. int scale = 255/((1 << IHDR_bitDepth) - 1);
  252. for (int i = 0; i < size; i++) {
  253. byte red = reds[i];
  254. if ((red != (byte)(i*scale)) ||
  255. (red != greens[i]) ||
  256. (red != blues[i])) {
  257. isGray = false;
  258. break;
  259. }
  260. }
  261. }
  262. // Determine whether transparency exists
  263. boolean hasAlpha = colorModel.hasAlpha();
  264. byte[] alpha = null;
  265. if (hasAlpha) {
  266. alpha = new byte[size];
  267. icm.getAlphas(alpha);
  268. }
  269. if (isGray && hasAlpha) {
  270. IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
  271. } else if (isGray) {
  272. IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
  273. } else {
  274. IHDR_colorType = PNGImageReader.PNG_COLOR_PALETTE;
  275. PLTE_present = true;
  276. PLTE_order = null;
  277. PLTE_red = (byte[])reds.clone();
  278. PLTE_green = (byte[])greens.clone();
  279. PLTE_blue = (byte[])blues.clone();
  280. if (hasAlpha) {
  281. tRNS_present = true;
  282. tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
  283. PLTE_order = new int[alpha.length];
  284. // Reorder the palette so that non-opaque entries
  285. // come first. Since the tRNS chunk does not have
  286. // to store trailing 255's, this can save a
  287. // considerable amount of space when encoding
  288. // images with only one transparent pixel value,
  289. // e.g., images from GIF sources.
  290. byte[] newAlpha = new byte[alpha.length];
  291. // Scan for non-opaque entries and assign them
  292. // positions starting at 0.
  293. int newIndex = 0;
  294. for (int i = 0; i < alpha.length; i++) {
  295. if (alpha[i] != (byte)255) {
  296. PLTE_order[i] = newIndex;
  297. newAlpha[newIndex] = alpha[i];
  298. ++newIndex;
  299. }
  300. }
  301. int numTransparent = newIndex;
  302. // Scan for opaque entries and assign them
  303. // positions following the non-opaque entries.
  304. for (int i = 0; i < alpha.length; i++) {
  305. if (alpha[i] == (byte)255) {
  306. PLTE_order[i] = newIndex++;
  307. }
  308. }
  309. // Reorder the palettes
  310. byte[] oldRed = PLTE_red;
  311. byte[] oldGreen = PLTE_green;
  312. byte[] oldBlue = PLTE_blue;
  313. int len = oldRed.length; // All have the same length
  314. PLTE_red = new byte[len];
  315. PLTE_green = new byte[len];
  316. PLTE_blue = new byte[len];
  317. for (int i = 0; i < len; i++) {
  318. PLTE_red[PLTE_order[i]] = oldRed[i];
  319. PLTE_green[PLTE_order[i]] = oldGreen[i];
  320. PLTE_blue[PLTE_order[i]] = oldBlue[i];
  321. }
  322. // Copy only the transparent entries into tRNS_alpha
  323. tRNS_alpha = new byte[numTransparent];
  324. System.arraycopy(newAlpha, 0,
  325. tRNS_alpha, 0, numTransparent);
  326. }
  327. }
  328. } else {
  329. if (numBands == 1) {
  330. IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
  331. } else if (numBands == 2) {
  332. IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
  333. } else if (numBands == 3) {
  334. IHDR_colorType = PNGImageReader.PNG_COLOR_RGB;
  335. } else if (numBands == 4) {
  336. IHDR_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
  337. } else {
  338. throw new RuntimeException("Number of bands not 1-4!");
  339. }
  340. }
  341. IHDR_present = true;
  342. }
  343. public boolean isReadOnly() {
  344. return false;
  345. }
  346. private ArrayList cloneBytesArrayList(ArrayList in) {
  347. if (in == null) {
  348. return null;
  349. } else {
  350. ArrayList list = new ArrayList(in.size());
  351. Iterator iter = in.iterator();
  352. while (iter.hasNext()) {
  353. Object o = iter.next();
  354. if (o == null) {
  355. list.add(null);
  356. } else {
  357. list.add(((byte[])o).clone());
  358. }
  359. }
  360. return list;
  361. }
  362. }
  363. // Deep clone
  364. public Object clone() {
  365. PNGMetadata metadata;
  366. try {
  367. metadata = (PNGMetadata)super.clone();
  368. } catch (CloneNotSupportedException e) {
  369. return null;
  370. }
  371. // unknownChunkData needs deep clone
  372. metadata.unknownChunkData =
  373. cloneBytesArrayList(this.unknownChunkData);
  374. return metadata;
  375. }
  376. public Node getAsTree(String formatName) {
  377. if (formatName.equals(nativeMetadataFormatName)) {
  378. return getNativeTree();
  379. } else if (formatName.equals
  380. (IIOMetadataFormatImpl.standardMetadataFormatName)) {
  381. return getStandardTree();
  382. } else {
  383. throw new IllegalArgumentException("Not a recognized format!");
  384. }
  385. }
  386. private Node getNativeTree() {
  387. IIOMetadataNode node = null; // scratch node
  388. IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
  389. // IHDR
  390. if (IHDR_present) {
  391. IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR");
  392. IHDR_node.setAttribute("width", Integer.toString(IHDR_width));
  393. IHDR_node.setAttribute("height", Integer.toString(IHDR_height));
  394. IHDR_node.setAttribute("bitDepth",
  395. Integer.toString(IHDR_bitDepth));
  396. IHDR_node.setAttribute("colorType",
  397. IHDR_colorTypeNames[IHDR_colorType]);
  398. // IHDR_compressionMethod must be 0 in PNG 1.1
  399. IHDR_node.setAttribute("compressionMethod",
  400. IHDR_compressionMethodNames[IHDR_compressionMethod]);
  401. // IHDR_filterMethod must be 0 in PNG 1.1
  402. IHDR_node.setAttribute("filterMethod",
  403. IHDR_filterMethodNames[IHDR_filterMethod]);
  404. IHDR_node.setAttribute("interlaceMethod",
  405. IHDR_interlaceMethodNames[IHDR_interlaceMethod]);
  406. root.appendChild(IHDR_node);
  407. }
  408. // PLTE
  409. if (PLTE_present) {
  410. IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE");
  411. int numEntries = PLTE_red.length;
  412. for (int i = 0; i < numEntries; i++) {
  413. IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry");
  414. entry.setAttribute("index", Integer.toString(i));
  415. entry.setAttribute("red",
  416. Integer.toString(PLTE_red[i] & 0xff));
  417. entry.setAttribute("green",
  418. Integer.toString(PLTE_green[i] & 0xff));
  419. entry.setAttribute("blue",
  420. Integer.toString(PLTE_blue[i] & 0xff));
  421. PLTE_node.appendChild(entry);
  422. }
  423. root.appendChild(PLTE_node);
  424. }
  425. // bKGD
  426. if (bKGD_present) {
  427. IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD");
  428. if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
  429. node = new IIOMetadataNode("bKGD_Palette");
  430. node.setAttribute("index", Integer.toString(bKGD_index));
  431. } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
  432. node = new IIOMetadataNode("bKGD_Grayscale");
  433. node.setAttribute("gray", Integer.toString(bKGD_gray));
  434. } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_RGB) {
  435. node = new IIOMetadataNode("bKGD_RGB");
  436. node.setAttribute("red", Integer.toString(bKGD_red));
  437. node.setAttribute("green", Integer.toString(bKGD_green));
  438. node.setAttribute("blue", Integer.toString(bKGD_blue));
  439. }
  440. bKGD_node.appendChild(node);
  441. root.appendChild(bKGD_node);
  442. }
  443. // cHRM
  444. if (cHRM_present) {
  445. IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM");
  446. cHRM_node.setAttribute("whitePointX",
  447. Integer.toString(cHRM_whitePointX));
  448. cHRM_node.setAttribute("whitePointY",
  449. Integer.toString(cHRM_whitePointY));
  450. cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX));
  451. cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY));
  452. cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX));
  453. cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY));
  454. cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX));
  455. cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY));
  456. root.appendChild(cHRM_node);
  457. }
  458. // gAMA
  459. if (gAMA_present) {
  460. IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA");
  461. gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma));
  462. root.appendChild(gAMA_node);
  463. }
  464. // hIST
  465. if (hIST_present) {
  466. IIOMetadataNode hIST_node = new IIOMetadataNode("hIST");
  467. for (int i = 0; i < hIST_histogram.length; i++) {
  468. IIOMetadataNode hist =
  469. new IIOMetadataNode("hISTEntry");
  470. hist.setAttribute("index", Integer.toString(i));
  471. hist.setAttribute("value",
  472. Integer.toString(hIST_histogram[i]));
  473. hIST_node.appendChild(hist);
  474. }
  475. root.appendChild(hIST_node);
  476. }
  477. // iCCP
  478. if (iCCP_present) {
  479. IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP");
  480. iCCP_node.setAttribute("profileName", iCCP_profileName);
  481. iCCP_node.setAttribute("compressionMethod",
  482. iCCP_compressionMethodNames[iCCP_compressionMethod]);
  483. Object profile = iCCP_compressedProfile;
  484. if (profile != null) {
  485. profile = ((byte[])profile).clone();
  486. }
  487. iCCP_node.setUserObject(profile);
  488. root.appendChild(iCCP_node);
  489. }
  490. // iTXt
  491. if (iTXt_keyword.size() > 0) {
  492. IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt");
  493. for (int i = 0; i < iTXt_keyword.size(); i++) {
  494. Integer val;
  495. IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry");
  496. iTXt_node.setAttribute("keyword", (String)iTXt_keyword.get(i));
  497. val = (Integer)iTXt_compressionFlag.get(i);
  498. iTXt_node.setAttribute("compressionFlag", val.toString());
  499. val = (Integer)iTXt_compressionMethod.get(i);
  500. iTXt_node.setAttribute("compressionMethod", val.toString());
  501. iTXt_node.setAttribute("languageTag",
  502. (String)iTXt_languageTag.get(i));
  503. iTXt_node.setAttribute("translatedKeyword",
  504. (String)iTXt_translatedKeyword.get(i));
  505. iTXt_node.setAttribute("text", (String)iTXt_text.get(i));
  506. iTXt_parent.appendChild(iTXt_node);
  507. }
  508. root.appendChild(iTXt_parent);
  509. }
  510. // pHYs
  511. if (pHYs_present) {
  512. IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs");
  513. pHYs_node.setAttribute("pixelsPerUnitXAxis",
  514. Integer.toString(pHYs_pixelsPerUnitXAxis));
  515. pHYs_node.setAttribute("pixelsPerUnitYAxis",
  516. Integer.toString(pHYs_pixelsPerUnitYAxis));
  517. pHYs_node.setAttribute("unitSpecifier",
  518. unitSpecifierNames[pHYs_unitSpecifier]);
  519. root.appendChild(pHYs_node);
  520. }
  521. // sBIT
  522. if (sBIT_present) {
  523. IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT");
  524. if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY) {
  525. node = new IIOMetadataNode("sBIT_Grayscale");
  526. node.setAttribute("gray",
  527. Integer.toString(sBIT_grayBits));
  528. } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
  529. node = new IIOMetadataNode("sBIT_GrayAlpha");
  530. node.setAttribute("gray",
  531. Integer.toString(sBIT_grayBits));
  532. node.setAttribute("alpha",
  533. Integer.toString(sBIT_alphaBits));
  534. } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB) {
  535. node = new IIOMetadataNode("sBIT_RGB");
  536. node.setAttribute("red",
  537. Integer.toString(sBIT_redBits));
  538. node.setAttribute("green",
  539. Integer.toString(sBIT_greenBits));
  540. node.setAttribute("blue",
  541. Integer.toString(sBIT_blueBits));
  542. } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
  543. node = new IIOMetadataNode("sBIT_RGBAlpha");
  544. node.setAttribute("red",
  545. Integer.toString(sBIT_redBits));
  546. node.setAttribute("green",
  547. Integer.toString(sBIT_greenBits));
  548. node.setAttribute("blue",
  549. Integer.toString(sBIT_blueBits));
  550. node.setAttribute("alpha",
  551. Integer.toString(sBIT_alphaBits));
  552. } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
  553. node = new IIOMetadataNode("sBIT_Palette");
  554. node.setAttribute("red",
  555. Integer.toString(sBIT_redBits));
  556. node.setAttribute("green",
  557. Integer.toString(sBIT_greenBits));
  558. node.setAttribute("blue",
  559. Integer.toString(sBIT_blueBits));
  560. }
  561. sBIT_node.appendChild(node);
  562. root.appendChild(sBIT_node);
  563. }
  564. // sPLT
  565. if (sPLT_present) {
  566. IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT");
  567. sPLT_node.setAttribute("name", sPLT_paletteName);
  568. sPLT_node.setAttribute("sampleDepth",
  569. Integer.toString(sPLT_sampleDepth));
  570. int numEntries = sPLT_red.length;
  571. for (int i = 0; i < numEntries; i++) {
  572. IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry");
  573. entry.setAttribute("index", Integer.toString(i));
  574. entry.setAttribute("red", Integer.toString(sPLT_red[i]));
  575. entry.setAttribute("green", Integer.toString(sPLT_green[i]));
  576. entry.setAttribute("blue", Integer.toString(sPLT_blue[i]));
  577. entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i]));
  578. entry.setAttribute("frequency",
  579. Integer.toString(sPLT_frequency[i]));
  580. sPLT_node.appendChild(entry);
  581. }
  582. root.appendChild(sPLT_node);
  583. }
  584. // sRGB
  585. if (sRGB_present) {
  586. IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB");
  587. sRGB_node.setAttribute("renderingIntent",
  588. renderingIntentNames[sRGB_renderingIntent]);
  589. root.appendChild(sRGB_node);
  590. }
  591. // tEXt
  592. if (tEXt_keyword.size() > 0) {
  593. IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt");
  594. for (int i = 0; i < tEXt_keyword.size(); i++) {
  595. IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry");
  596. tEXt_node.setAttribute("keyword" , (String)tEXt_keyword.get(i));
  597. tEXt_node.setAttribute("value" , (String)tEXt_text.get(i));
  598. tEXt_parent.appendChild(tEXt_node);
  599. }
  600. root.appendChild(tEXt_parent);
  601. }
  602. // tIME
  603. if (tIME_present) {
  604. IIOMetadataNode tIME_node = new IIOMetadataNode("tIME");
  605. tIME_node.setAttribute("year", Integer.toString(tIME_year));
  606. tIME_node.setAttribute("month", Integer.toString(tIME_month));
  607. tIME_node.setAttribute("day", Integer.toString(tIME_day));
  608. tIME_node.setAttribute("hour", Integer.toString(tIME_hour));
  609. tIME_node.setAttribute("minute", Integer.toString(tIME_minute));
  610. tIME_node.setAttribute("second", Integer.toString(tIME_second));
  611. root.appendChild(tIME_node);
  612. }
  613. // tRNS
  614. if (tRNS_present) {
  615. IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS");
  616. if (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
  617. node = new IIOMetadataNode("tRNS_Palette");
  618. for (int i = 0; i < tRNS_alpha.length; i++) {
  619. IIOMetadataNode entry =
  620. new IIOMetadataNode("tRNS_PaletteEntry");
  621. entry.setAttribute("index", Integer.toString(i));
  622. entry.setAttribute("alpha",
  623. Integer.toString(tRNS_alpha[i] & 0xff));
  624. node.appendChild(entry);
  625. }
  626. } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
  627. node = new IIOMetadataNode("tRNS_Grayscale");
  628. node.setAttribute("gray", Integer.toString(tRNS_gray));
  629. } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
  630. node = new IIOMetadataNode("tRNS_RGB");
  631. node.setAttribute("red", Integer.toString(tRNS_red));
  632. node.setAttribute("green", Integer.toString(tRNS_green));
  633. node.setAttribute("blue", Integer.toString(tRNS_blue));
  634. }
  635. tRNS_node.appendChild(node);
  636. root.appendChild(tRNS_node);
  637. }
  638. // zTXt
  639. if (zTXt_keyword.size() > 0) {
  640. IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt");
  641. for (int i = 0; i < zTXt_keyword.size(); i++) {
  642. IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry");
  643. zTXt_node.setAttribute("keyword", (String)zTXt_keyword.get(i));
  644. int cm = ((Integer)zTXt_compressionMethod.get(i)).intValue();
  645. zTXt_node.setAttribute("compressionMethod",
  646. zTXt_compressionMethodNames[cm]);
  647. zTXt_node.setAttribute("text", (String)zTXt_text.get(i));
  648. zTXt_parent.appendChild(zTXt_node);
  649. }
  650. root.appendChild(zTXt_parent);
  651. }
  652. // Unknown chunks
  653. if (unknownChunkType.size() > 0) {
  654. IIOMetadataNode unknown_parent =
  655. new IIOMetadataNode("UnknownChunks");
  656. for (int i = 0; i < unknownChunkType.size(); i++) {
  657. IIOMetadataNode unknown_node =
  658. new IIOMetadataNode("UnknownChunk");
  659. unknown_node.setAttribute("type",
  660. (String)unknownChunkType.get(i));
  661. unknown_node.setUserObject((byte[])unknownChunkData.get(i));
  662. unknown_parent.appendChild(unknown_node);
  663. }
  664. root.appendChild(unknown_parent);
  665. }
  666. return root;
  667. }
  668. private int getNumChannels() {
  669. // Determine number of channels
  670. // Be careful about palette color with transparency
  671. int numChannels = IHDR_numChannels[IHDR_colorType];
  672. if (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
  673. tRNS_present && tRNS_colorType == IHDR_colorType) {
  674. numChannels = 4;
  675. }
  676. return numChannels;
  677. }
  678. public IIOMetadataNode getStandardChromaNode() {
  679. IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
  680. IIOMetadataNode node = null; // scratch node
  681. node = new IIOMetadataNode("ColorSpaceType");
  682. node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]);
  683. chroma_node.appendChild(node);
  684. node = new IIOMetadataNode("NumChannels");
  685. node.setAttribute("value", Integer.toString(getNumChannels()));
  686. chroma_node.appendChild(node);
  687. if (gAMA_present) {
  688. node = new IIOMetadataNode("Gamma");
  689. node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F));
  690. chroma_node.appendChild(node);
  691. }
  692. node = new IIOMetadataNode("BlackIsZero");
  693. node.setAttribute("value", "true");
  694. chroma_node.appendChild(node);
  695. if (PLTE_present) {
  696. boolean hasAlpha = tRNS_present &&
  697. (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE);
  698. node = new IIOMetadataNode("Palette");
  699. for (int i = 0; i < PLTE_red.length; i++) {
  700. IIOMetadataNode entry =
  701. new IIOMetadataNode("PaletteEntry");
  702. entry.setAttribute("index", Integer.toString(i));
  703. entry.setAttribute("red",
  704. Integer.toString(PLTE_red[i] & 0xff));
  705. entry.setAttribute("green",
  706. Integer.toString(PLTE_green[i] & 0xff));
  707. entry.setAttribute("blue",
  708. Integer.toString(PLTE_blue[i] & 0xff));
  709. if (hasAlpha) {
  710. int alpha = (i < tRNS_alpha.length) ?
  711. (tRNS_alpha[i] & 0xff) : 255;
  712. entry.setAttribute("alpha", Integer.toString(alpha));
  713. }
  714. node.appendChild(entry);
  715. }
  716. chroma_node.appendChild(node);
  717. }
  718. if (bKGD_present) {
  719. if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
  720. node = new IIOMetadataNode("BackgroundIndex");
  721. node.setAttribute("value", Integer.toString(bKGD_index));
  722. } else {
  723. node = new IIOMetadataNode("BackgroundColor");
  724. int r, g, b;
  725. if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
  726. r = g = b = bKGD_gray;
  727. } else {
  728. r = bKGD_red;
  729. g = bKGD_green;
  730. b = bKGD_blue;
  731. }
  732. node.setAttribute("red", Integer.toString(r));
  733. node.setAttribute("green", Integer.toString(g));
  734. node.setAttribute("blue", Integer.toString(b));
  735. }
  736. chroma_node.appendChild(node);
  737. }
  738. return chroma_node;
  739. }
  740. public IIOMetadataNode getStandardCompressionNode() {
  741. IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
  742. IIOMetadataNode node = null; // scratch node
  743. node = new IIOMetadataNode("CompressionTypeName");
  744. node.setAttribute("value", "deflate");
  745. compression_node.appendChild(node);
  746. node = new IIOMetadataNode("Lossless");
  747. node.setAttribute("value", "true");
  748. compression_node.appendChild(node);
  749. node = new IIOMetadataNode("NumProgressiveScans");
  750. node.setAttribute("value",
  751. (IHDR_interlaceMethod == 0) ? "1" : "7");
  752. compression_node.appendChild(node);
  753. return compression_node;
  754. }
  755. private String repeat(String s, int times) {
  756. if (times == 1) {
  757. return s;
  758. }
  759. StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1);
  760. sb.append(s);
  761. for (int i = 1; i < times; i++) {
  762. sb.append(" ");
  763. sb.append(s);
  764. }
  765. return sb.toString();
  766. }
  767. public IIOMetadataNode getStandardDataNode() {
  768. IIOMetadataNode data_node = new IIOMetadataNode("Data");
  769. IIOMetadataNode node = null; // scratch node
  770. node = new IIOMetadataNode("PlanarConfiguration");
  771. node.setAttribute("value", "PixelInterleaved");
  772. data_node.appendChild(node);
  773. node = new IIOMetadataNode("SampleFormat");
  774. node.setAttribute("value",
  775. IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE ?
  776. "Index" : "UnsignedIntegral");
  777. data_node.appendChild(node);
  778. String bitDepth = Integer.toString(IHDR_bitDepth);
  779. node = new IIOMetadataNode("BitsPerSample");
  780. node.setAttribute("value", repeat(bitDepth, getNumChannels()));
  781. data_node.appendChild(node);
  782. if (sBIT_present) {
  783. node = new IIOMetadataNode("SignificantBitsPerSample");
  784. String sbits;
  785. if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY ||
  786. sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
  787. sbits = Integer.toString(sBIT_grayBits);
  788. } else { // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB ||
  789. // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA
  790. sbits = Integer.toString(sBIT_redBits) + " " +
  791. Integer.toString(sBIT_greenBits) + " " +
  792. Integer.toString(sBIT_blueBits);
  793. }
  794. if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
  795. sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
  796. sbits += " " + Integer.toString(sBIT_alphaBits);
  797. }
  798. node.setAttribute("value", sbits);
  799. data_node.appendChild(node);
  800. }
  801. // SampleMSB
  802. return data_node;
  803. }
  804. public IIOMetadataNode getStandardDimensionNode() {
  805. IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
  806. IIOMetadataNode node = null; // scratch node
  807. node = new IIOMetadataNode("PixelAspectRatio");
  808. float ratio = pHYs_present ?
  809. (float)pHYs_pixelsPerUnitXAxispHYs_pixelsPerUnitYAxis : 1.0F;
  810. node.setAttribute("value", Float.toString(ratio));
  811. dimension_node.appendChild(node);
  812. node = new IIOMetadataNode("ImageOrientation");
  813. node.setAttribute("value", "Normal");
  814. dimension_node.appendChild(node);
  815. if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) {
  816. node = new IIOMetadataNode("HorizontalPixelSize");
  817. node.setAttribute("value",
  818. Float.toString(1000.0FpHYs_pixelsPerUnitXAxis));
  819. dimension_node.appendChild(node);
  820. node = new IIOMetadataNode("VerticalPixelSize");
  821. node.setAttribute("value",
  822. Float.toString(1000.0FpHYs_pixelsPerUnitYAxis));
  823. dimension_node.appendChild(node);
  824. }
  825. return dimension_node;
  826. }
  827. public IIOMetadataNode getStandardDocumentNode() {
  828. if (!tIME_present) {
  829. return null;
  830. }
  831. IIOMetadataNode document_node = new IIOMetadataNode("Document");
  832. IIOMetadataNode node = null; // scratch node
  833. node = new IIOMetadataNode("ImageModificationTime");
  834. node.setAttribute("year", Integer.toString(tIME_year));
  835. node.setAttribute("month", Integer.toString(tIME_month));
  836. node.setAttribute("day", Integer.toString(tIME_day));
  837. node.setAttribute("hour", Integer.toString(tIME_hour));
  838. node.setAttribute("minute", Integer.toString(tIME_minute));
  839. node.setAttribute("second", Integer.toString(tIME_second));
  840. document_node.appendChild(node);
  841. return document_node;
  842. }
  843. public IIOMetadataNode getStandardTextNode() {
  844. int numEntries = tEXt_keyword.size() +
  845. iTXt_keyword.size() + zTXt_keyword.size();
  846. if (numEntries == 0) {
  847. return null;
  848. }
  849. IIOMetadataNode text_node = new IIOMetadataNode("Text");
  850. IIOMetadataNode node = null; // scratch node
  851. for (int i = 0; i < tEXt_keyword.size(); i++) {
  852. node = new IIOMetadataNode("TextEntry");
  853. node.setAttribute("keyword", (String)tEXt_keyword.get(i));
  854. node.setAttribute("value", (String)tEXt_text.get(i));
  855. node.setAttribute("encoding", "ISO-8859-1");
  856. node.setAttribute("compression", "none");
  857. text_node.appendChild(node);
  858. }
  859. for (int i = 0; i < iTXt_keyword.size(); i++) {
  860. node = new IIOMetadataNode("TextEntry");
  861. node.setAttribute("keyword", (String)iTXt_keyword.get(i));
  862. node.setAttribute("value", (String)iTXt_text.get(i));
  863. node.setAttribute("language",
  864. (String)iTXt_languageTag.get(i));
  865. if (((Integer)iTXt_compressionFlag.get(i)).intValue() == 1) {
  866. node.setAttribute("compression", "deflate");
  867. } else {
  868. node.setAttribute("compression", "none");
  869. }
  870. text_node.appendChild(node);
  871. }
  872. for (int i = 0; i < zTXt_keyword.size(); i++) {
  873. node = new IIOMetadataNode("TextEntry");
  874. node.setAttribute("keyword", (String)zTXt_keyword.get(i));
  875. node.setAttribute("value", (String)zTXt_text.get(i));
  876. node.setAttribute("compression", "deflate");
  877. text_node.appendChild(node);
  878. }
  879. return text_node;
  880. }
  881. public IIOMetadataNode getStandardTransparencyNode() {
  882. IIOMetadataNode transparency_node =
  883. new IIOMetadataNode("Transparency");
  884. IIOMetadataNode node = null; // scratch node
  885. node = new IIOMetadataNode("Alpha");
  886. boolean hasAlpha =
  887. (IHDR_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) ||
  888. (IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) ||
  889. (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
  890. tRNS_present &&
  891. (tRNS_colorType == IHDR_colorType) &&
  892. (tRNS_alpha != null));
  893. node.setAttribute("value", hasAlpha ? "nonpremultipled" : "none");
  894. transparency_node.appendChild(node);
  895. if (tRNS_present) {
  896. node = new IIOMetadataNode("TransparentColor");
  897. if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
  898. node.setAttribute("value",
  899. Integer.toString(tRNS_red) + " " +
  900. Integer.toString(tRNS_green) + " " +
  901. Integer.toString(tRNS_blue));
  902. } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
  903. node.setAttribute("value", Integer.toString(tRNS_gray));
  904. }
  905. transparency_node.appendChild(node);
  906. }
  907. return transparency_node;
  908. }
  909. // Shorthand for throwing an IIOInvalidTreeException
  910. private void fatal(Node node, String reason)
  911. throws IIOInvalidTreeException {
  912. throw new IIOInvalidTreeException(reason, node);
  913. }
  914. // Get an integer-valued attribute
  915. private String getStringAttribute(Node node, String name,
  916. String defaultValue, boolean required)
  917. throws IIOInvalidTreeException {
  918. Node attr = node.getAttributes().getNamedItem(name);
  919. if (attr == null) {
  920. if (!required) {
  921. return defaultValue;
  922. } else {
  923. fatal(node, "Required attribute " + name + " not present!");
  924. }
  925. }
  926. return attr.getNodeValue();
  927. }
  928. // Get an integer-valued attribute
  929. private int getIntAttribute(Node node, String name,
  930. int defaultValue, boolean required)
  931. throws IIOInvalidTreeException {
  932. String value = getStringAttribute(node, name, null, required);
  933. if (value == null) {
  934. return defaultValue;
  935. }
  936. return Integer.parseInt(value);
  937. }
  938. // Get a float-valued attribute
  939. private float getFloatAttribute(Node node, String name,
  940. float defaultValue, boolean required)
  941. throws IIOInvalidTreeException {
  942. String value = getStringAttribute(node, name, null, required);
  943. if (value == null) {
  944. return defaultValue;
  945. }
  946. return Float.parseFloat(value);
  947. }
  948. // Get a required integer-valued attribute
  949. private int getIntAttribute(Node node, String name)
  950. throws IIOInvalidTreeException {
  951. return getIntAttribute(node, name, -1, true);
  952. }
  953. // Get a required float-valued attribute
  954. private float getFloatAttribute(Node node, String name)
  955. throws IIOInvalidTreeException {
  956. return getFloatAttribute(node, name, -1.0F, true);
  957. }
  958. // Get a boolean-valued attribute
  959. private boolean getBooleanAttribute(Node node, String name,
  960. boolean defaultValue,
  961. boolean required)
  962. throws IIOInvalidTreeException {
  963. Node attr = node.getAttributes().getNamedItem(name);
  964. if (attr == null) {
  965. if (!required) {
  966. return defaultValue;
  967. } else {
  968. fatal(node, "Required attribute " + name + " not present!");
  969. }
  970. }
  971. String value = attr.getNodeValue();
  972. if (value.equals("true")) {
  973. return true;
  974. } else if (value.equals("false")) {
  975. return false;
  976. } else {
  977. fatal(node, "Attribute " + name + " must be 'true' or 'false'!");
  978. return false;
  979. }
  980. }
  981. // Get a required boolean-valued attribute
  982. private boolean getBooleanAttribute(Node node, String name)
  983. throws IIOInvalidTreeException {
  984. return getBooleanAttribute(node, name, false, true);
  985. }
  986. // Get an enumerated attribute as an index into a String array
  987. private int getEnumeratedAttribute(Node node,
  988. String name, String[] legalNames,
  989. int defaultValue, boolean required)
  990. throws IIOInvalidTreeException {
  991. Node attr = node.getAttributes().getNamedItem(name);
  992. if (attr == null) {
  993. if (!required) {
  994. return defaultValue;
  995. } else {
  996. fatal(node, "Required attribute " + name + " not present!");
  997. }
  998. }
  999. String value = attr.getNodeValue();
  1000. for (int i = 0; i < legalNames.length; i++) {
  1001. if (value.equals(legalNames[i])) {
  1002. return i;
  1003. }
  1004. }
  1005. fatal(node, "Illegal v