1 /* 2 * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.imageio.plugins.png; 27 28 import java.awt.image.ColorModel; 29 import java.awt.image.IndexColorModel; 30 import java.awt.image.SampleModel; 31 import java.util.ArrayList; 32 import java.util.StringTokenizer; 33 import java.util.ListIterator; 34 import java.time.LocalDateTime; 35 import java.time.OffsetDateTime; 36 import java.time.ZoneId; 37 import java.time.ZoneOffset; 38 import java.time.format.DateTimeFormatter; 39 import java.time.format.DateTimeParseException; 40 import java.time.temporal.TemporalAccessor; 41 import javax.imageio.ImageTypeSpecifier; 42 import javax.imageio.metadata.IIOInvalidTreeException; 43 import javax.imageio.metadata.IIOMetadata; 44 import javax.imageio.metadata.IIOMetadataFormatImpl; 45 import javax.imageio.metadata.IIOMetadataNode; 46 import org.w3c.dom.Node; 47 48 public class PNGMetadata extends IIOMetadata implements Cloneable { 49 50 // package scope 51 public static final String 52 nativeMetadataFormatName = "javax_imageio_png_1.0"; 53 54 protected static final String nativeMetadataFormatClassName 55 = "com.sun.imageio.plugins.png.PNGMetadataFormat"; 56 57 // Color types for IHDR chunk 58 static final String[] IHDR_colorTypeNames = { 59 "Grayscale", null, "RGB", "Palette", 60 "GrayAlpha", null, "RGBAlpha" 61 }; 62 63 static final int[] IHDR_numChannels = { 64 1, 0, 3, 3, 2, 0, 4 65 }; 66 67 // Bit depths for IHDR chunk 68 static final String[] IHDR_bitDepths = { 69 "1", "2", "4", "8", "16" 70 }; 71 72 // Compression methods for IHDR chunk 73 static final String[] IHDR_compressionMethodNames = { 74 "deflate" 75 }; 76 77 // Filter methods for IHDR chunk 78 static final String[] IHDR_filterMethodNames = { 79 "adaptive" 80 }; 81 82 // Interlace methods for IHDR chunk 83 static final String[] IHDR_interlaceMethodNames = { 84 "none", "adam7" 85 }; 86 87 // Compression methods for iCCP chunk 88 static final String[] iCCP_compressionMethodNames = { 89 "deflate" 90 }; 91 92 // Compression methods for zTXt chunk 93 static final String[] zTXt_compressionMethodNames = { 94 "deflate" 95 }; 96 97 // "Unknown" unit for pHYs chunk 98 public static final int PHYS_UNIT_UNKNOWN = 0; 99 100 // "Meter" unit for pHYs chunk 101 public static final int PHYS_UNIT_METER = 1; 102 103 // Unit specifiers for pHYs chunk 104 static final String[] unitSpecifierNames = { 105 "unknown", "meter" 106 }; 107 108 // Rendering intents for sRGB chunk 109 static final String[] renderingIntentNames = { 110 "Perceptual", // 0 111 "Relative colorimetric", // 1 112 "Saturation", // 2 113 "Absolute colorimetric" // 3 114 115 }; 116 117 // Color space types for Chroma->ColorSpaceType node 118 static final String[] colorSpaceTypeNames = { 119 "GRAY", null, "RGB", "RGB", 120 "GRAY", null, "RGB" 121 }; 122 123 // IHDR chunk 124 public boolean IHDR_present; 125 public int IHDR_width; 126 public int IHDR_height; 127 public int IHDR_bitDepth; 128 public int IHDR_colorType; 129 public int IHDR_compressionMethod; 130 public int IHDR_filterMethod; 131 public int IHDR_interlaceMethod; // 0 == none, 1 == adam7 132 133 // PLTE chunk 134 public boolean PLTE_present; 135 public byte[] PLTE_red; 136 public byte[] PLTE_green; 137 public byte[] PLTE_blue; 138 139 // If non-null, used to reorder palette entries during encoding in 140 // order to minimize the size of the tRNS chunk. Thus an index of 141 // 'i' in the source should be encoded as index 'PLTE_order[i]'. 142 // PLTE_order will be null unless 'initialize' is called with an 143 // IndexColorModel image type. 144 public int[] PLTE_order = null; 145 146 // bKGD chunk 147 // If external (non-PNG sourced) data has red = green = blue, 148 // always store it as gray and promote when writing 149 public boolean bKGD_present; 150 public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE 151 public int bKGD_index; 152 public int bKGD_gray; 153 public int bKGD_red; 154 public int bKGD_green; 155 public int bKGD_blue; 156 157 // cHRM chunk 158 public boolean cHRM_present; 159 public int cHRM_whitePointX; 160 public int cHRM_whitePointY; 161 public int cHRM_redX; 162 public int cHRM_redY; 163 public int cHRM_greenX; 164 public int cHRM_greenY; 165 public int cHRM_blueX; 166 public int cHRM_blueY; 167 168 // gAMA chunk 169 public boolean gAMA_present; 170 public int gAMA_gamma; 171 172 // hIST chunk 173 public boolean hIST_present; 174 public char[] hIST_histogram; 175 176 // iCCP chunk 177 public boolean iCCP_present; 178 public String iCCP_profileName; 179 public int iCCP_compressionMethod; 180 public byte[] iCCP_compressedProfile; 181 182 // iTXt chunk 183 public ArrayList<String> iTXt_keyword = new ArrayList<String>(); 184 public ArrayList<Boolean> iTXt_compressionFlag = new ArrayList<Boolean>(); 185 public ArrayList<Integer> iTXt_compressionMethod = new ArrayList<Integer>(); 186 public ArrayList<String> iTXt_languageTag = new ArrayList<String>(); 187 public ArrayList<String> iTXt_translatedKeyword = new ArrayList<String>(); 188 public ArrayList<String> iTXt_text = new ArrayList<String>(); 189 190 // pHYs chunk 191 public boolean pHYs_present; 192 public int pHYs_pixelsPerUnitXAxis; 193 public int pHYs_pixelsPerUnitYAxis; 194 public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter 195 196 // sBIT chunk 197 public boolean sBIT_present; 198 public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA 199 public int sBIT_grayBits; 200 public int sBIT_redBits; 201 public int sBIT_greenBits; 202 public int sBIT_blueBits; 203 public int sBIT_alphaBits; 204 205 // sPLT chunk 206 public boolean sPLT_present; 207 public String sPLT_paletteName; // 1-79 characters 208 public int sPLT_sampleDepth; // 8 or 16 209 public int[] sPLT_red; 210 public int[] sPLT_green; 211 public int[] sPLT_blue; 212 public int[] sPLT_alpha; 213 public int[] sPLT_frequency; 214 215 // sRGB chunk 216 public boolean sRGB_present; 217 public int sRGB_renderingIntent; 218 219 // tEXt chunk 220 public ArrayList<String> tEXt_keyword = new ArrayList<String>(); // 1-79 characters 221 public ArrayList<String> tEXt_text = new ArrayList<String>(); 222 223 // tIME chunk. Gives the image modification time. 224 public boolean tIME_present; 225 public int tIME_year; 226 public int tIME_month; 227 public int tIME_day; 228 public int tIME_hour; 229 public int tIME_minute; 230 public int tIME_second; 231 232 // Specifies whether metadata contains Standard/Document/ImageCreationTime 233 public boolean creation_time_present; 234 235 // Values that make up Standard/Document/ImageCreationTime 236 public int creation_time_year; 237 public int creation_time_month; 238 public int creation_time_day; 239 public int creation_time_hour; 240 public int creation_time_minute; 241 public int creation_time_second; 242 public ZoneOffset creation_time_offset; 243 244 /* 245 * tEXt_creation_time_present- Specifies whether any text chunk (tEXt, iTXt, 246 * zTXt) exists with image creation time. The data structure corresponding 247 * to the last decoded text chunk with creation time is indicated by the 248 * iterator- tEXt_creation_time_iter. 249 * 250 * Any update to the text chunks with creation time is reflected on 251 * Standard/Document/ImageCreationTime after retrieving time from the text 252 * chunk. If there are multiple text chunks with creation time, the time 253 * retrieved from the last decoded text chunk will be used. A point to note 254 * is that, retrieval of time from text chunks is possible only if the 255 * encoded time in the chunk confirms to either the recommended RFC1123 256 * format or ISO format. 257 * 258 * Similarly, any update to Standard/Document/ImageCreationTime is reflected 259 * on the last decoded text chunk's data structure with time encoded in 260 * RFC1123 format. By updating the text chunk's data structure, we also 261 * ensure that PNGImageWriter will write image creation time on the output. 262 */ 263 public boolean tEXt_creation_time_present; 264 private ListIterator<String> tEXt_creation_time_iter = null; 265 public static final String tEXt_creationTimeKey = "Creation Time"; 266 267 // tRNS chunk 268 // If external (non-PNG sourced) data has red = green = blue, 269 // always store it as gray and promote when writing 270 public boolean tRNS_present; 271 public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE 272 public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc. 273 public int tRNS_gray; 274 public int tRNS_red; 275 public int tRNS_green; 276 public int tRNS_blue; 277 278 // zTXt chunk 279 public ArrayList<String> zTXt_keyword = new ArrayList<String>(); 280 public ArrayList<Integer> zTXt_compressionMethod = new ArrayList<Integer>(); 281 public ArrayList<String> zTXt_text = new ArrayList<String>(); 282 283 // Unknown chunks 284 public ArrayList<String> unknownChunkType = new ArrayList<String>(); 285 public ArrayList<byte[]> unknownChunkData = new ArrayList<byte[]>(); 286 287 public PNGMetadata() { 288 super(true, 289 nativeMetadataFormatName, 290 nativeMetadataFormatClassName, 291 null, null); 292 } 293 294 public PNGMetadata(IIOMetadata metadata) { 295 // TODO -- implement 296 } 297 298 /** 299 * Sets the IHDR_bitDepth and IHDR_colorType variables. 300 * The {@code numBands} parameter is necessary since 301 * we may only be writing a subset of the image bands. 302 */ 303 public void initialize(ImageTypeSpecifier imageType, int numBands) { 304 ColorModel colorModel = imageType.getColorModel(); 305 SampleModel sampleModel = imageType.getSampleModel(); 306 307 // Initialize IHDR_bitDepth 308 int[] sampleSize = sampleModel.getSampleSize(); 309 int bitDepth = sampleSize[0]; 310 // Choose max bit depth over all channels 311 // Fixes bug 4413109 312 for (int i = 1; i < sampleSize.length; i++) { 313 if (sampleSize[i] > bitDepth) { 314 bitDepth = sampleSize[i]; 315 } 316 } 317 // Multi-channel images must have a bit depth of 8 or 16 318 if (sampleSize.length > 1 && bitDepth < 8) { 319 bitDepth = 8; 320 } 321 322 // Round bit depth up to a power of 2 323 if (bitDepth > 2 && bitDepth < 4) { 324 bitDepth = 4; 325 } else if (bitDepth > 4 && bitDepth < 8) { 326 bitDepth = 8; 327 } else if (bitDepth > 8 && bitDepth < 16) { 328 bitDepth = 16; 329 } else if (bitDepth > 16) { 330 throw new RuntimeException("bitDepth > 16!"); 331 } 332 IHDR_bitDepth = bitDepth; 333 334 // Initialize IHDR_colorType 335 if (colorModel instanceof IndexColorModel) { 336 IndexColorModel icm = (IndexColorModel)colorModel; 337 int size = icm.getMapSize(); 338 339 byte[] reds = new byte[size]; 340 icm.getReds(reds); 341 byte[] greens = new byte[size]; 342 icm.getGreens(greens); 343 byte[] blues = new byte[size]; 344 icm.getBlues(blues); 345 346 // Determine whether the color tables are actually a gray ramp 347 // if the color type has not been set previously 348 boolean isGray = false; 349 if (!IHDR_present || 350 (IHDR_colorType != PNGImageReader.PNG_COLOR_PALETTE)) { 351 isGray = true; 352 int scale = 255/((1 << IHDR_bitDepth) - 1); 353 for (int i = 0; i < size; i++) { 354 byte red = reds[i]; 355 if ((red != (byte)(i*scale)) || 356 (red != greens[i]) || 357 (red != blues[i])) { 358 isGray = false; 359 break; 360 } 361 } 362 } 363 364 // Determine whether transparency exists 365 boolean hasAlpha = colorModel.hasAlpha(); 366 367 byte[] alpha = null; 368 if (hasAlpha) { 369 alpha = new byte[size]; 370 icm.getAlphas(alpha); 371 } 372 373 /* 374 * NB: PNG_COLOR_GRAY_ALPHA color type may be not optimal for images 375 * contained more than 1024 pixels (or even than 768 pixels in case of 376 * single transparent pixel in palette). 377 * For such images alpha samples in raster will occupy more space than 378 * it is required to store palette so it could be reasonable to 379 * use PNG_COLOR_PALETTE color type for large images. 380 */ 381 382 if (isGray && hasAlpha && (bitDepth == 8 || bitDepth == 16)) { 383 IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; 384 } else if (isGray && !hasAlpha) { 385 IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY; 386 } else { 387 IHDR_colorType = PNGImageReader.PNG_COLOR_PALETTE; 388 PLTE_present = true; 389 PLTE_order = null; 390 PLTE_red = reds.clone(); 391 PLTE_green = greens.clone(); 392 PLTE_blue = blues.clone(); 393 394 if (hasAlpha) { 395 tRNS_present = true; 396 tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE; 397 398 PLTE_order = new int[alpha.length]; 399 400 // Reorder the palette so that non-opaque entries 401 // come first. Since the tRNS chunk does not have 402 // to store trailing 255's, this can save a 403 // considerable amount of space when encoding 404 // images with only one transparent pixel value, 405 // e.g., images from GIF sources. 406 407 byte[] newAlpha = new byte[alpha.length]; 408 409 // Scan for non-opaque entries and assign them 410 // positions starting at 0. 411 int newIndex = 0; 412 for (int i = 0; i < alpha.length; i++) { 413 if (alpha[i] != (byte)255) { 414 PLTE_order[i] = newIndex; 415 newAlpha[newIndex] = alpha[i]; 416 ++newIndex; 417 } 418 } 419 int numTransparent = newIndex; 420 421 // Scan for opaque entries and assign them 422 // positions following the non-opaque entries. 423 for (int i = 0; i < alpha.length; i++) { 424 if (alpha[i] == (byte)255) { 425 PLTE_order[i] = newIndex++; 426 } 427 } 428 429 // Reorder the palettes 430 byte[] oldRed = PLTE_red; 431 byte[] oldGreen = PLTE_green; 432 byte[] oldBlue = PLTE_blue; 433 int len = oldRed.length; // All have the same length 434 PLTE_red = new byte[len]; 435 PLTE_green = new byte[len]; 436 PLTE_blue = new byte[len]; 437 for (int i = 0; i < len; i++) { 438 PLTE_red[PLTE_order[i]] = oldRed[i]; 439 PLTE_green[PLTE_order[i]] = oldGreen[i]; 440 PLTE_blue[PLTE_order[i]] = oldBlue[i]; 441 } 442 443 // Copy only the transparent entries into tRNS_alpha 444 tRNS_alpha = new byte[numTransparent]; 445 System.arraycopy(newAlpha, 0, 446 tRNS_alpha, 0, numTransparent); 447 } 448 } 449 } else { 450 if (numBands == 1) { 451 IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY; 452 } else if (numBands == 2) { 453 IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; 454 } else if (numBands == 3) { 455 IHDR_colorType = PNGImageReader.PNG_COLOR_RGB; 456 } else if (numBands == 4) { 457 IHDR_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA; 458 } else { 459 throw new RuntimeException("Number of bands not 1-4!"); 460 } 461 } 462 463 IHDR_present = true; 464 } 465 466 public boolean isReadOnly() { 467 return false; 468 } 469 470 private ArrayList<byte[]> cloneBytesArrayList(ArrayList<byte[]> in) { 471 if (in == null) { 472 return null; 473 } else { 474 ArrayList<byte[]> list = new ArrayList<byte[]>(in.size()); 475 for (byte[] b: in) { 476 list.add((b == null) ? null : b.clone()); 477 } 478 return list; 479 } 480 } 481 482 // Deep clone 483 public Object clone() { 484 PNGMetadata metadata; 485 try { 486 metadata = (PNGMetadata)super.clone(); 487 } catch (CloneNotSupportedException e) { 488 return null; 489 } 490 491 // unknownChunkData needs deep clone 492 metadata.unknownChunkData = 493 cloneBytesArrayList(this.unknownChunkData); 494 495 return metadata; 496 } 497 498 public Node getAsTree(String formatName) { 499 if (formatName.equals(nativeMetadataFormatName)) { 500 return getNativeTree(); 501 } else if (formatName.equals 502 (IIOMetadataFormatImpl.standardMetadataFormatName)) { 503 return getStandardTree(); 504 } else { 505 throw new IllegalArgumentException("Not a recognized format!"); 506 } 507 } 508 509 private Node getNativeTree() { 510 IIOMetadataNode node = null; // scratch node 511 IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); 512 513 // IHDR 514 if (IHDR_present) { 515 IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR"); 516 IHDR_node.setAttribute("width", Integer.toString(IHDR_width)); 517 IHDR_node.setAttribute("height", Integer.toString(IHDR_height)); 518 IHDR_node.setAttribute("bitDepth", 519 Integer.toString(IHDR_bitDepth)); 520 IHDR_node.setAttribute("colorType", 521 IHDR_colorTypeNames[IHDR_colorType]); 522 // IHDR_compressionMethod must be 0 in PNG 1.1 523 IHDR_node.setAttribute("compressionMethod", 524 IHDR_compressionMethodNames[IHDR_compressionMethod]); 525 // IHDR_filterMethod must be 0 in PNG 1.1 526 IHDR_node.setAttribute("filterMethod", 527 IHDR_filterMethodNames[IHDR_filterMethod]); 528 IHDR_node.setAttribute("interlaceMethod", 529 IHDR_interlaceMethodNames[IHDR_interlaceMethod]); 530 root.appendChild(IHDR_node); 531 } 532 533 // PLTE 534 if (PLTE_present) { 535 IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE"); 536 int numEntries = PLTE_red.length; 537 for (int i = 0; i < numEntries; i++) { 538 IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry"); 539 entry.setAttribute("index", Integer.toString(i)); 540 entry.setAttribute("red", 541 Integer.toString(PLTE_red[i] & 0xff)); 542 entry.setAttribute("green", 543 Integer.toString(PLTE_green[i] & 0xff)); 544 entry.setAttribute("blue", 545 Integer.toString(PLTE_blue[i] & 0xff)); 546 PLTE_node.appendChild(entry); 547 } 548 549 root.appendChild(PLTE_node); 550 } 551 552 // bKGD 553 if (bKGD_present) { 554 IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD"); 555 556 if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { 557 node = new IIOMetadataNode("bKGD_Palette"); 558 node.setAttribute("index", Integer.toString(bKGD_index)); 559 } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) { 560 node = new IIOMetadataNode("bKGD_Grayscale"); 561 node.setAttribute("gray", Integer.toString(bKGD_gray)); 562 } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_RGB) { 563 node = new IIOMetadataNode("bKGD_RGB"); 564 node.setAttribute("red", Integer.toString(bKGD_red)); 565 node.setAttribute("green", Integer.toString(bKGD_green)); 566 node.setAttribute("blue", Integer.toString(bKGD_blue)); 567 } 568 bKGD_node.appendChild(node); 569 570 root.appendChild(bKGD_node); 571 } 572 573 // cHRM 574 if (cHRM_present) { 575 IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM"); 576 cHRM_node.setAttribute("whitePointX", 577 Integer.toString(cHRM_whitePointX)); 578 cHRM_node.setAttribute("whitePointY", 579 Integer.toString(cHRM_whitePointY)); 580 cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX)); 581 cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY)); 582 cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX)); 583 cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY)); 584 cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX)); 585 cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY)); 586 587 root.appendChild(cHRM_node); 588 } 589 590 // gAMA 591 if (gAMA_present) { 592 IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA"); 593 gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma)); 594 595 root.appendChild(gAMA_node); 596 } 597 598 // hIST 599 if (hIST_present) { 600 IIOMetadataNode hIST_node = new IIOMetadataNode("hIST"); 601 602 for (int i = 0; i < hIST_histogram.length; i++) { 603 IIOMetadataNode hist = 604 new IIOMetadataNode("hISTEntry"); 605 hist.setAttribute("index", Integer.toString(i)); 606 hist.setAttribute("value", 607 Integer.toString(hIST_histogram[i])); 608 hIST_node.appendChild(hist); 609 } 610 611 root.appendChild(hIST_node); 612 } 613 614 // iCCP 615 if (iCCP_present) { 616 IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP"); 617 iCCP_node.setAttribute("profileName", iCCP_profileName); 618 iCCP_node.setAttribute("compressionMethod", 619 iCCP_compressionMethodNames[iCCP_compressionMethod]); 620 621 Object profile = iCCP_compressedProfile; 622 if (profile != null) { 623 profile = ((byte[])profile).clone(); 624 } 625 iCCP_node.setUserObject(profile); 626 627 root.appendChild(iCCP_node); 628 } 629 630 // iTXt 631 if (iTXt_keyword.size() > 0) { 632 IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt"); 633 for (int i = 0; i < iTXt_keyword.size(); i++) { 634 IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry"); 635 iTXt_node.setAttribute("keyword", iTXt_keyword.get(i)); 636 iTXt_node.setAttribute("compressionFlag", 637 iTXt_compressionFlag.get(i) ? "TRUE" : "FALSE"); 638 iTXt_node.setAttribute("compressionMethod", 639 iTXt_compressionMethod.get(i).toString()); 640 iTXt_node.setAttribute("languageTag", 641 iTXt_languageTag.get(i)); 642 iTXt_node.setAttribute("translatedKeyword", 643 iTXt_translatedKeyword.get(i)); 644 iTXt_node.setAttribute("text", iTXt_text.get(i)); 645 646 iTXt_parent.appendChild(iTXt_node); 647 } 648 649 root.appendChild(iTXt_parent); 650 } 651 652 // pHYs 653 if (pHYs_present) { 654 IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs"); 655 pHYs_node.setAttribute("pixelsPerUnitXAxis", 656 Integer.toString(pHYs_pixelsPerUnitXAxis)); 657 pHYs_node.setAttribute("pixelsPerUnitYAxis", 658 Integer.toString(pHYs_pixelsPerUnitYAxis)); 659 pHYs_node.setAttribute("unitSpecifier", 660 unitSpecifierNames[pHYs_unitSpecifier]); 661 662 root.appendChild(pHYs_node); 663 } 664 665 // sBIT 666 if (sBIT_present) { 667 IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT"); 668 669 if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY) { 670 node = new IIOMetadataNode("sBIT_Grayscale"); 671 node.setAttribute("gray", 672 Integer.toString(sBIT_grayBits)); 673 } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { 674 node = new IIOMetadataNode("sBIT_GrayAlpha"); 675 node.setAttribute("gray", 676 Integer.toString(sBIT_grayBits)); 677 node.setAttribute("alpha", 678 Integer.toString(sBIT_alphaBits)); 679 } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB) { 680 node = new IIOMetadataNode("sBIT_RGB"); 681 node.setAttribute("red", 682 Integer.toString(sBIT_redBits)); 683 node.setAttribute("green", 684 Integer.toString(sBIT_greenBits)); 685 node.setAttribute("blue", 686 Integer.toString(sBIT_blueBits)); 687 } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { 688 node = new IIOMetadataNode("sBIT_RGBAlpha"); 689 node.setAttribute("red", 690 Integer.toString(sBIT_redBits)); 691 node.setAttribute("green", 692 Integer.toString(sBIT_greenBits)); 693 node.setAttribute("blue", 694 Integer.toString(sBIT_blueBits)); 695 node.setAttribute("alpha", 696 Integer.toString(sBIT_alphaBits)); 697 } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_PALETTE) { 698 node = new IIOMetadataNode("sBIT_Palette"); 699 node.setAttribute("red", 700 Integer.toString(sBIT_redBits)); 701 node.setAttribute("green", 702 Integer.toString(sBIT_greenBits)); 703 node.setAttribute("blue", 704 Integer.toString(sBIT_blueBits)); 705 } 706 sBIT_node.appendChild(node); 707 708 root.appendChild(sBIT_node); 709 } 710 711 // sPLT 712 if (sPLT_present) { 713 IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT"); 714 715 sPLT_node.setAttribute("name", sPLT_paletteName); 716 sPLT_node.setAttribute("sampleDepth", 717 Integer.toString(sPLT_sampleDepth)); 718 719 int numEntries = sPLT_red.length; 720 for (int i = 0; i < numEntries; i++) { 721 IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry"); 722 entry.setAttribute("index", Integer.toString(i)); 723 entry.setAttribute("red", Integer.toString(sPLT_red[i])); 724 entry.setAttribute("green", Integer.toString(sPLT_green[i])); 725 entry.setAttribute("blue", Integer.toString(sPLT_blue[i])); 726 entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i])); 727 entry.setAttribute("frequency", 728 Integer.toString(sPLT_frequency[i])); 729 sPLT_node.appendChild(entry); 730 } 731 732 root.appendChild(sPLT_node); 733 } 734 735 // sRGB 736 if (sRGB_present) { 737 IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB"); 738 sRGB_node.setAttribute("renderingIntent", 739 renderingIntentNames[sRGB_renderingIntent]); 740 741 root.appendChild(sRGB_node); 742 } 743 744 // tEXt 745 if (tEXt_keyword.size() > 0) { 746 IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt"); 747 for (int i = 0; i < tEXt_keyword.size(); i++) { 748 IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry"); 749 tEXt_node.setAttribute("keyword" , tEXt_keyword.get(i)); 750 tEXt_node.setAttribute("value" , tEXt_text.get(i)); 751 752 tEXt_parent.appendChild(tEXt_node); 753 } 754 755 root.appendChild(tEXt_parent); 756 } 757 758 // tIME 759 if (tIME_present) { 760 IIOMetadataNode tIME_node = new IIOMetadataNode("tIME"); 761 tIME_node.setAttribute("year", Integer.toString(tIME_year)); 762 tIME_node.setAttribute("month", Integer.toString(tIME_month)); 763 tIME_node.setAttribute("day", Integer.toString(tIME_day)); 764 tIME_node.setAttribute("hour", Integer.toString(tIME_hour)); 765 tIME_node.setAttribute("minute", Integer.toString(tIME_minute)); 766 tIME_node.setAttribute("second", Integer.toString(tIME_second)); 767 768 root.appendChild(tIME_node); 769 } 770 771 // tRNS 772 if (tRNS_present) { 773 IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS"); 774 775 if (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE) { 776 node = new IIOMetadataNode("tRNS_Palette"); 777 778 for (int i = 0; i < tRNS_alpha.length; i++) { 779 IIOMetadataNode entry = 780 new IIOMetadataNode("tRNS_PaletteEntry"); 781 entry.setAttribute("index", Integer.toString(i)); 782 entry.setAttribute("alpha", 783 Integer.toString(tRNS_alpha[i] & 0xff)); 784 node.appendChild(entry); 785 } 786 } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) { 787 node = new IIOMetadataNode("tRNS_Grayscale"); 788 node.setAttribute("gray", Integer.toString(tRNS_gray)); 789 } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) { 790 node = new IIOMetadataNode("tRNS_RGB"); 791 node.setAttribute("red", Integer.toString(tRNS_red)); 792 node.setAttribute("green", Integer.toString(tRNS_green)); 793 node.setAttribute("blue", Integer.toString(tRNS_blue)); 794 } 795 tRNS_node.appendChild(node); 796 797 root.appendChild(tRNS_node); 798 } 799 800 // zTXt 801 if (zTXt_keyword.size() > 0) { 802 IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt"); 803 for (int i = 0; i < zTXt_keyword.size(); i++) { 804 IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry"); 805 zTXt_node.setAttribute("keyword", zTXt_keyword.get(i)); 806 807 int cm = (zTXt_compressionMethod.get(i)).intValue(); 808 zTXt_node.setAttribute("compressionMethod", 809 zTXt_compressionMethodNames[cm]); 810 811 zTXt_node.setAttribute("text", zTXt_text.get(i)); 812 813 zTXt_parent.appendChild(zTXt_node); 814 } 815 816 root.appendChild(zTXt_parent); 817 } 818 819 // Unknown chunks 820 if (unknownChunkType.size() > 0) { 821 IIOMetadataNode unknown_parent = 822 new IIOMetadataNode("UnknownChunks"); 823 for (int i = 0; i < unknownChunkType.size(); i++) { 824 IIOMetadataNode unknown_node = 825 new IIOMetadataNode("UnknownChunk"); 826 unknown_node.setAttribute("type", 827 unknownChunkType.get(i)); 828 unknown_node.setUserObject(unknownChunkData.get(i)); 829 830 unknown_parent.appendChild(unknown_node); 831 } 832 833 root.appendChild(unknown_parent); 834 } 835 836 return root; 837 } 838 839 private int getNumChannels() { 840 // Determine number of channels 841 // Be careful about palette color with transparency 842 int numChannels = IHDR_numChannels[IHDR_colorType]; 843 if (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE && 844 tRNS_present && tRNS_colorType == IHDR_colorType) { 845 numChannels = 4; 846 } 847 return numChannels; 848 } 849 850 public IIOMetadataNode getStandardChromaNode() { 851 IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); 852 IIOMetadataNode node = null; // scratch node 853 854 node = new IIOMetadataNode("ColorSpaceType"); 855 node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]); 856 chroma_node.appendChild(node); 857 858 node = new IIOMetadataNode("NumChannels"); 859 node.setAttribute("value", Integer.toString(getNumChannels())); 860 chroma_node.appendChild(node); 861 862 if (gAMA_present) { 863 node = new IIOMetadataNode("Gamma"); 864 node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F)); 865 chroma_node.appendChild(node); 866 } 867 868 node = new IIOMetadataNode("BlackIsZero"); 869 node.setAttribute("value", "TRUE"); 870 chroma_node.appendChild(node); 871 872 if (PLTE_present) { 873 boolean hasAlpha = tRNS_present && 874 (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE); 875 876 node = new IIOMetadataNode("Palette"); 877 for (int i = 0; i < PLTE_red.length; i++) { 878 IIOMetadataNode entry = 879 new IIOMetadataNode("PaletteEntry"); 880 entry.setAttribute("index", Integer.toString(i)); 881 entry.setAttribute("red", 882 Integer.toString(PLTE_red[i] & 0xff)); 883 entry.setAttribute("green", 884 Integer.toString(PLTE_green[i] & 0xff)); 885 entry.setAttribute("blue", 886 Integer.toString(PLTE_blue[i] & 0xff)); 887 if (hasAlpha) { 888 int alpha = (i < tRNS_alpha.length) ? 889 (tRNS_alpha[i] & 0xff) : 255; 890 entry.setAttribute("alpha", Integer.toString(alpha)); 891 } 892 node.appendChild(entry); 893 } 894 chroma_node.appendChild(node); 895 } 896 897 if (bKGD_present) { 898 if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { 899 node = new IIOMetadataNode("BackgroundIndex"); 900 node.setAttribute("value", Integer.toString(bKGD_index)); 901 } else { 902 node = new IIOMetadataNode("BackgroundColor"); 903 int r, g, b; 904 905 if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) { 906 r = g = b = bKGD_gray; 907 } else { 908 r = bKGD_red; 909 g = bKGD_green; 910 b = bKGD_blue; 911 } 912 node.setAttribute("red", Integer.toString(r)); 913 node.setAttribute("green", Integer.toString(g)); 914 node.setAttribute("blue", Integer.toString(b)); 915 } 916 chroma_node.appendChild(node); 917 } 918 919 return chroma_node; 920 } 921 922 public IIOMetadataNode getStandardCompressionNode() { 923 IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); 924 IIOMetadataNode node = null; // scratch node 925 926 node = new IIOMetadataNode("CompressionTypeName"); 927 node.setAttribute("value", "deflate"); 928 compression_node.appendChild(node); 929 930 node = new IIOMetadataNode("Lossless"); 931 node.setAttribute("value", "TRUE"); 932 compression_node.appendChild(node); 933 934 node = new IIOMetadataNode("NumProgressiveScans"); 935 node.setAttribute("value", 936 (IHDR_interlaceMethod == 0) ? "1" : "7"); 937 compression_node.appendChild(node); 938 939 return compression_node; 940 } 941 942 private String repeat(String s, int times) { 943 if (times == 1) { 944 return s; 945 } 946 StringBuilder sb = new StringBuilder((s.length() + 1)*times - 1); 947 sb.append(s); 948 for (int i = 1; i < times; i++) { 949 sb.append(" "); 950 sb.append(s); 951 } 952 return sb.toString(); 953 } 954 955 public IIOMetadataNode getStandardDataNode() { 956 IIOMetadataNode data_node = new IIOMetadataNode("Data"); 957 IIOMetadataNode node = null; // scratch node 958 959 node = new IIOMetadataNode("PlanarConfiguration"); 960 node.setAttribute("value", "PixelInterleaved"); 961 data_node.appendChild(node); 962 963 node = new IIOMetadataNode("SampleFormat"); 964 node.setAttribute("value", 965 IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE ? 966 "Index" : "UnsignedIntegral"); 967 data_node.appendChild(node); 968 969 String bitDepth = Integer.toString(IHDR_bitDepth); 970 node = new IIOMetadataNode("BitsPerSample"); 971 node.setAttribute("value", repeat(bitDepth, getNumChannels())); 972 data_node.appendChild(node); 973 974 if (sBIT_present) { 975 node = new IIOMetadataNode("SignificantBitsPerSample"); 976 String sbits; 977 if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY || 978 sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { 979 sbits = Integer.toString(sBIT_grayBits); 980 } else { // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB || 981 // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA 982 sbits = Integer.toString(sBIT_redBits) + " " + 983 Integer.toString(sBIT_greenBits) + " " + 984 Integer.toString(sBIT_blueBits); 985 } 986 987 if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA || 988 sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { 989 sbits += " " + Integer.toString(sBIT_alphaBits); 990 } 991 992 node.setAttribute("value", sbits); 993 data_node.appendChild(node); 994 } 995 996 // SampleMSB 997 998 return data_node; 999 } 1000 1001 public IIOMetadataNode getStandardDimensionNode() { 1002 IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); 1003 IIOMetadataNode node = null; // scratch node 1004 1005 node = new IIOMetadataNode("PixelAspectRatio"); 1006 float ratio = pHYs_present ? 1007 (float)pHYs_pixelsPerUnitXAxis/pHYs_pixelsPerUnitYAxis : 1.0F; 1008 node.setAttribute("value", Float.toString(ratio)); 1009 dimension_node.appendChild(node); 1010 1011 node = new IIOMetadataNode("ImageOrientation"); 1012 node.setAttribute("value", "Normal"); 1013 dimension_node.appendChild(node); 1014 1015 if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) { 1016 node = new IIOMetadataNode("HorizontalPixelSize"); 1017 node.setAttribute("value", 1018 Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis)); 1019 dimension_node.appendChild(node); 1020 1021 node = new IIOMetadataNode("VerticalPixelSize"); 1022 node.setAttribute("value", 1023 Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis)); 1024 dimension_node.appendChild(node); 1025 } 1026 1027 return dimension_node; 1028 } 1029 1030 public IIOMetadataNode getStandardDocumentNode() { 1031 IIOMetadataNode document_node = null; 1032 1033 // Check if image modification time exists 1034 if (tIME_present) { 1035 // Create new document node 1036 document_node = new IIOMetadataNode("Document"); 1037 1038 // Node to hold image modification time 1039 IIOMetadataNode node = new IIOMetadataNode("ImageModificationTime"); 1040 node.setAttribute("year", Integer.toString(tIME_year)); 1041 node.setAttribute("month", Integer.toString(tIME_month)); 1042 node.setAttribute("day", Integer.toString(tIME_day)); 1043 node.setAttribute("hour", Integer.toString(tIME_hour)); 1044 node.setAttribute("minute", Integer.toString(tIME_minute)); 1045 node.setAttribute("second", Integer.toString(tIME_second)); 1046 document_node.appendChild(node); 1047 } 1048 1049 // Check if image creation time exists 1050 if (creation_time_present) { 1051 if (document_node == null) { 1052 // Create new document node 1053 document_node = new IIOMetadataNode("Document"); 1054 } 1055 1056 // Node to hold image creation time 1057 IIOMetadataNode node = new IIOMetadataNode("ImageCreationTime"); 1058 node.setAttribute("year", Integer.toString(creation_time_year)); 1059 node.setAttribute("month", Integer.toString(creation_time_month)); 1060 node.setAttribute("day", Integer.toString(creation_time_day)); 1061 node.setAttribute("hour", Integer.toString(creation_time_hour)); 1062 node.setAttribute("minute", Integer.toString(creation_time_minute)); 1063 node.setAttribute("second", Integer.toString(creation_time_second)); 1064 document_node.appendChild(node); 1065 } 1066 1067 return document_node; 1068 } 1069 1070 public IIOMetadataNode getStandardTextNode() { 1071 int numEntries = tEXt_keyword.size() + 1072 iTXt_keyword.size() + zTXt_keyword.size(); 1073 if (numEntries == 0) { 1074 return null; 1075 } 1076 1077 IIOMetadataNode text_node = new IIOMetadataNode("Text"); 1078 IIOMetadataNode node = null; // scratch node 1079 1080 for (int i = 0; i < tEXt_keyword.size(); i++) { 1081 node = new IIOMetadataNode("TextEntry"); 1082 node.setAttribute("keyword", tEXt_keyword.get(i)); 1083 node.setAttribute("value", tEXt_text.get(i)); 1084 node.setAttribute("encoding", "ISO-8859-1"); 1085 node.setAttribute("compression", "none"); 1086 1087 text_node.appendChild(node); 1088 } 1089 1090 for (int i = 0; i < iTXt_keyword.size(); i++) { 1091 node = new IIOMetadataNode("TextEntry"); 1092 node.setAttribute("keyword", iTXt_keyword.get(i)); 1093 node.setAttribute("value", iTXt_text.get(i)); 1094 node.setAttribute("language", 1095 iTXt_languageTag.get(i)); 1096 if (iTXt_compressionFlag.get(i)) { 1097 node.setAttribute("compression", "zip"); 1098 } else { 1099 node.setAttribute("compression", "none"); 1100 } 1101 1102 text_node.appendChild(node); 1103 } 1104 1105 for (int i = 0; i < zTXt_keyword.size(); i++) { 1106 node = new IIOMetadataNode("TextEntry"); 1107 node.setAttribute("keyword", zTXt_keyword.get(i)); 1108 node.setAttribute("value", zTXt_text.get(i)); 1109 node.setAttribute("compression", "zip"); 1110 1111 text_node.appendChild(node); 1112 } 1113 1114 return text_node; 1115 } 1116 1117 public IIOMetadataNode getStandardTransparencyNode() { 1118 IIOMetadataNode transparency_node = 1119 new IIOMetadataNode("Transparency"); 1120 IIOMetadataNode node = null; // scratch node 1121 1122 node = new IIOMetadataNode("Alpha"); 1123 boolean hasAlpha = 1124 (IHDR_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) || 1125 (IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) || 1126 (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE && 1127 tRNS_present && 1128 (tRNS_colorType == IHDR_colorType) && 1129 (tRNS_alpha != null)); 1130 node.setAttribute("value", hasAlpha ? "nonpremultipled" : "none"); 1131 transparency_node.appendChild(node); 1132 1133 if (tRNS_present) { 1134 node = new IIOMetadataNode("TransparentColor"); 1135 if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) { 1136 node.setAttribute("value", 1137 Integer.toString(tRNS_red) + " " + 1138 Integer.toString(tRNS_green) + " " + 1139 Integer.toString(tRNS_blue)); 1140 } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) { 1141 node.setAttribute("value", Integer.toString(tRNS_gray)); 1142 } 1143 transparency_node.appendChild(node); 1144 } 1145 1146 return transparency_node; 1147 } 1148 1149 // Shorthand for throwing an IIOInvalidTreeException 1150 private void fatal(Node node, String reason) 1151 throws IIOInvalidTreeException { 1152 throw new IIOInvalidTreeException(reason, node); 1153 } 1154 1155 // Get an integer-valued attribute 1156 private String getStringAttribute(Node node, String name, 1157 String defaultValue, boolean required) 1158 throws IIOInvalidTreeException { 1159 Node attr = node.getAttributes().getNamedItem(name); 1160 if (attr == null) { 1161 if (!required) { 1162 return defaultValue; 1163 } else { 1164 fatal(node, "Required attribute " + name + " not present!"); 1165 } 1166 } 1167 return attr.getNodeValue(); 1168 } 1169 1170 1171 // Get an integer-valued attribute 1172 private int getIntAttribute(Node node, String name, 1173 int defaultValue, boolean required) 1174 throws IIOInvalidTreeException { 1175 String value = getStringAttribute(node, name, null, required); 1176 if (value == null) { 1177 return defaultValue; 1178 } 1179 return Integer.parseInt(value); 1180 } 1181 1182 // Get a float-valued attribute 1183 private float getFloatAttribute(Node node, String name, 1184 float defaultValue, boolean required) 1185 throws IIOInvalidTreeException { 1186 String value = getStringAttribute(node, name, null, required); 1187 if (value == null) { 1188 return defaultValue; 1189 } 1190 return Float.parseFloat(value); 1191 } 1192 1193 // Get a required integer-valued attribute 1194 private int getIntAttribute(Node node, String name) 1195 throws IIOInvalidTreeException { 1196 return getIntAttribute(node, name, -1, true); 1197 } 1198 1199 // Get a required float-valued attribute 1200 private float getFloatAttribute(Node node, String name) 1201 throws IIOInvalidTreeException { 1202 return getFloatAttribute(node, name, -1.0F, true); 1203 } 1204 1205 // Get a boolean-valued attribute 1206 private boolean getBooleanAttribute(Node node, String name, 1207 boolean defaultValue, 1208 boolean required) 1209 throws IIOInvalidTreeException { 1210 Node attr = node.getAttributes().getNamedItem(name); 1211 if (attr == null) { 1212 if (!required) { 1213 return defaultValue; 1214 } else { 1215 fatal(node, "Required attribute " + name + " not present!"); 1216 } 1217 } 1218 String value = attr.getNodeValue(); 1219 // Allow lower case booleans for backward compatibility, #5082756 1220 if (value.equals("TRUE") || value.equals("true")) { 1221 return true; 1222 } else if (value.equals("FALSE") || value.equals("false")) { 1223 return false; 1224 } else { 1225 fatal(node, "Attribute " + name + " must be 'TRUE' or 'FALSE'!"); 1226 return false; 1227 } 1228 } 1229 1230 // Get a required boolean-valued attribute 1231 private boolean getBooleanAttribute(Node node, String name) 1232 throws IIOInvalidTreeException { 1233 return getBooleanAttribute(node, name, false, true); 1234 } 1235 1236 // Get an enumerated attribute as an index into a String array 1237 private int getEnumeratedAttribute(Node node, 1238 String name, String[] legalNames, 1239 int defaultValue, boolean required) 1240 throws IIOInvalidTreeException { 1241 Node attr = node.getAttributes().getNamedItem(name); 1242 if (attr == null) { 1243 if (!required) { 1244 return defaultValue; 1245 } else { 1246 fatal(node, "Required attribute " + name + " not present!"); 1247 } 1248 } 1249 String value = attr.getNodeValue(); 1250 for (int i = 0; i < legalNames.length; i++) { 1251 if (value.equals(legalNames[i])) { 1252 return i; 1253 } 1254 } 1255 1256 fatal(node, "Illegal value for attribute " + name + "!"); 1257 return -1; 1258 } 1259 1260 // Get a required enumerated attribute as an index into a String array 1261 private int getEnumeratedAttribute(Node node, 1262 String name, String[] legalNames) 1263 throws IIOInvalidTreeException { 1264 return getEnumeratedAttribute(node, name, legalNames, -1, true); 1265 } 1266 1267 // Get a String-valued attribute 1268 private String getAttribute(Node node, String name, 1269 String defaultValue, boolean required) 1270 throws IIOInvalidTreeException { 1271 Node attr = node.getAttributes().getNamedItem(name); 1272 if (attr == null) { 1273 if (!required) { 1274 return defaultValue; 1275 } else { 1276 fatal(node, "Required attribute " + name + " not present!"); 1277 } 1278 } 1279 return attr.getNodeValue(); 1280 } 1281 1282 // Get a required String-valued attribute 1283 private String getAttribute(Node node, String name) 1284 throws IIOInvalidTreeException { 1285 return getAttribute(node, name, null, true); 1286 } 1287 1288 public void mergeTree(String formatName, Node root) 1289 throws IIOInvalidTreeException { 1290 if (formatName.equals(nativeMetadataFormatName)) { 1291 if (root == null) { 1292 throw new IllegalArgumentException("root == null!"); 1293 } 1294 mergeNativeTree(root); 1295 } else if (formatName.equals 1296 (IIOMetadataFormatImpl.standardMetadataFormatName)) { 1297 if (root == null) { 1298 throw new IllegalArgumentException("root == null!"); 1299 } 1300 mergeStandardTree(root); 1301 } else { 1302 throw new IllegalArgumentException("Not a recognized format!"); 1303 } 1304 } 1305 1306 private void mergeNativeTree(Node root) 1307 throws IIOInvalidTreeException { 1308 Node node = root; 1309 if (!node.getNodeName().equals(nativeMetadataFormatName)) { 1310 fatal(node, "Root must be " + nativeMetadataFormatName); 1311 } 1312 1313 node = node.getFirstChild(); 1314 while (node != null) { 1315 String name = node.getNodeName(); 1316 1317 if (name.equals("IHDR")) { 1318 IHDR_width = getIntAttribute(node, "width"); 1319 IHDR_height = getIntAttribute(node, "height"); 1320 IHDR_bitDepth = 1321 Integer.valueOf(IHDR_bitDepths[ 1322 getEnumeratedAttribute(node, 1323 "bitDepth", 1324 IHDR_bitDepths)]); 1325 IHDR_colorType = getEnumeratedAttribute(node, "colorType", 1326 IHDR_colorTypeNames); 1327 IHDR_compressionMethod = 1328 getEnumeratedAttribute(node, "compressionMethod", 1329 IHDR_compressionMethodNames); 1330 IHDR_filterMethod = 1331 getEnumeratedAttribute(node, 1332 "filterMethod", 1333 IHDR_filterMethodNames); 1334 IHDR_interlaceMethod = 1335 getEnumeratedAttribute(node, "interlaceMethod", 1336 IHDR_interlaceMethodNames); 1337 IHDR_present = true; 1338 } else if (name.equals("PLTE")) { 1339 byte[] red = new byte[256]; 1340 byte[] green = new byte[256]; 1341 byte[] blue = new byte[256]; 1342 int maxindex = -1; 1343 1344 Node PLTE_entry = node.getFirstChild(); 1345 if (PLTE_entry == null) { 1346 fatal(node, "Palette has no entries!"); 1347 } 1348 1349 while (PLTE_entry != null) { 1350 if (!PLTE_entry.getNodeName().equals("PLTEEntry")) { 1351 fatal(node, 1352 "Only a PLTEEntry may be a child of a PLTE!"); 1353 } 1354 1355 int index = getIntAttribute(PLTE_entry, "index"); 1356 if (index < 0 || index > 255) { 1357 fatal(node, 1358 "Bad value for PLTEEntry attribute index!"); 1359 } 1360 if (index > maxindex) { 1361 maxindex = index; 1362 } 1363 red[index] = 1364 (byte)getIntAttribute(PLTE_entry, "red"); 1365 green[index] = 1366 (byte)getIntAttribute(PLTE_entry, "green"); 1367 blue[index] = 1368 (byte)getIntAttribute(PLTE_entry, "blue"); 1369 1370 PLTE_entry = PLTE_entry.getNextSibling(); 1371 } 1372 1373 int numEntries = maxindex + 1; 1374 PLTE_red = new byte[numEntries]; 1375 PLTE_green = new byte[numEntries]; 1376 PLTE_blue = new byte[numEntries]; 1377 System.arraycopy(red, 0, PLTE_red, 0, numEntries); 1378 System.arraycopy(green, 0, PLTE_green, 0, numEntries); 1379 System.arraycopy(blue, 0, PLTE_blue, 0, numEntries); 1380 PLTE_present = true; 1381 } else if (name.equals("bKGD")) { 1382 bKGD_present = false; // Guard against partial overwrite 1383 Node bKGD_node = node.getFirstChild(); 1384 if (bKGD_node == null) { 1385 fatal(node, "bKGD node has no children!"); 1386 } 1387 String bKGD_name = bKGD_node.getNodeName(); 1388 if (bKGD_name.equals("bKGD_Palette")) { 1389 bKGD_index = getIntAttribute(bKGD_node, "index"); 1390 bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE; 1391 } else if (bKGD_name.equals("bKGD_Grayscale")) { 1392 bKGD_gray = getIntAttribute(bKGD_node, "gray"); 1393 bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY; 1394 } else if (bKGD_name.equals("bKGD_RGB")) { 1395 bKGD_red = getIntAttribute(bKGD_node, "red"); 1396 bKGD_green = getIntAttribute(bKGD_node, "green"); 1397 bKGD_blue = getIntAttribute(bKGD_node, "blue"); 1398 bKGD_colorType = PNGImageReader.PNG_COLOR_RGB; 1399 } else { 1400 fatal(node, "Bad child of a bKGD node!"); 1401 } 1402 if (bKGD_node.getNextSibling() != null) { 1403 fatal(node, "bKGD node has more than one child!"); 1404 } 1405 1406 bKGD_present = true; 1407 } else if (name.equals("cHRM")) { 1408 cHRM_whitePointX = getIntAttribute(node, "whitePointX"); 1409 cHRM_whitePointY = getIntAttribute(node, "whitePointY"); 1410 cHRM_redX = getIntAttribute(node, "redX"); 1411 cHRM_redY = getIntAttribute(node, "redY"); 1412 cHRM_greenX = getIntAttribute(node, "greenX"); 1413 cHRM_greenY = getIntAttribute(node, "greenY"); 1414 cHRM_blueX = getIntAttribute(node, "blueX"); 1415 cHRM_blueY = getIntAttribute(node, "blueY"); 1416 1417 cHRM_present = true; 1418 } else if (name.equals("gAMA")) { 1419 gAMA_gamma = getIntAttribute(node, "value"); 1420 gAMA_present = true; 1421 } else if (name.equals("hIST")) { 1422 char[] hist = new char[256]; 1423 int maxindex = -1; 1424 1425 Node hIST_entry = node.getFirstChild(); 1426 if (hIST_entry == null) { 1427 fatal(node, "hIST node has no children!"); 1428 } 1429 1430 while (hIST_entry != null) { 1431 if (!hIST_entry.getNodeName().equals("hISTEntry")) { 1432 fatal(node, 1433 "Only a hISTEntry may be a child of a hIST!"); 1434 } 1435 1436 int index = getIntAttribute(hIST_entry, "index"); 1437 if (index < 0 || index > 255) { 1438 fatal(node, 1439 "Bad value for histEntry attribute index!"); 1440 } 1441 if (index > maxindex) { 1442 maxindex = index; 1443 } 1444 hist[index] = 1445 (char)getIntAttribute(hIST_entry, "value"); 1446 1447 hIST_entry = hIST_entry.getNextSibling(); 1448 } 1449 1450 int numEntries = maxindex + 1; 1451 hIST_histogram = new char[numEntries]; 1452 System.arraycopy(hist, 0, hIST_histogram, 0, numEntries); 1453 1454 hIST_present = true; 1455 } else if (name.equals("iCCP")) { 1456 iCCP_profileName = getAttribute(node, "profileName"); 1457 iCCP_compressionMethod = 1458 getEnumeratedAttribute(node, "compressionMethod", 1459 iCCP_compressionMethodNames); 1460 Object compressedProfile = 1461 ((IIOMetadataNode)node).getUserObject(); 1462 if (compressedProfile == null) { 1463 fatal(node, "No ICCP profile present in user object!"); 1464 } 1465 if (!(compressedProfile instanceof byte[])) { 1466 fatal(node, "User object not a byte array!"); 1467 } 1468 1469 iCCP_compressedProfile = ((byte[])compressedProfile).clone(); 1470 1471 iCCP_present = true; 1472 } else if (name.equals("iTXt")) { 1473 Node iTXt_node = node.getFirstChild(); 1474 while (iTXt_node != null) { 1475 if (!iTXt_node.getNodeName().equals("iTXtEntry")) { 1476 fatal(node, 1477 "Only an iTXtEntry may be a child of an iTXt!"); 1478 } 1479 1480 String keyword = getAttribute(iTXt_node, "keyword"); 1481 if (isValidKeyword(keyword)) { 1482 iTXt_keyword.add(keyword); 1483 1484 boolean compressionFlag = 1485 getBooleanAttribute(iTXt_node, "compressionFlag"); 1486 iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag)); 1487 1488 String compressionMethod = 1489 getAttribute(iTXt_node, "compressionMethod"); 1490 iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); 1491 1492 String languageTag = 1493 getAttribute(iTXt_node, "languageTag"); 1494 iTXt_languageTag.add(languageTag); 1495 1496 String translatedKeyword = 1497 getAttribute(iTXt_node, "translatedKeyword"); 1498 iTXt_translatedKeyword.add(translatedKeyword); 1499 1500 String text = getAttribute(iTXt_node, "text"); 1501 iTXt_text.add(text); 1502 1503 // Check if the text chunk contains image creation time 1504 if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) { 1505 // Update Standard/Document/ImageCreationTime 1506 int index = iTXt_text.size()-1; 1507 decodeImageCreationTimeFromTextChunk( 1508 iTXt_text.listIterator(index)); 1509 } 1510 } 1511 // silently skip invalid text entry 1512 1513 iTXt_node = iTXt_node.getNextSibling(); 1514 } 1515 } else if (name.equals("pHYs")) { 1516 pHYs_pixelsPerUnitXAxis = 1517 getIntAttribute(node, "pixelsPerUnitXAxis"); 1518 pHYs_pixelsPerUnitYAxis = 1519 getIntAttribute(node, "pixelsPerUnitYAxis"); 1520 pHYs_unitSpecifier = 1521 getEnumeratedAttribute(node, "unitSpecifier", 1522 unitSpecifierNames); 1523 1524 pHYs_present = true; 1525 } else if (name.equals("sBIT")) { 1526 sBIT_present = false; // Guard against partial overwrite 1527 Node sBIT_node = node.getFirstChild(); 1528 if (sBIT_node == null) { 1529 fatal(node, "sBIT node has no children!"); 1530 } 1531 String sBIT_name = sBIT_node.getNodeName(); 1532 if (sBIT_name.equals("sBIT_Grayscale")) { 1533 sBIT_grayBits = getIntAttribute(sBIT_node, "gray"); 1534 sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY; 1535 } else if (sBIT_name.equals("sBIT_GrayAlpha")) { 1536 sBIT_grayBits = getIntAttribute(sBIT_node, "gray"); 1537 sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha"); 1538 sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; 1539 } else if (sBIT_name.equals("sBIT_RGB")) { 1540 sBIT_redBits = getIntAttribute(sBIT_node, "red"); 1541 sBIT_greenBits = getIntAttribute(sBIT_node, "green"); 1542 sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); 1543 sBIT_colorType = PNGImageReader.PNG_COLOR_RGB; 1544 } else if (sBIT_name.equals("sBIT_RGBAlpha")) { 1545 sBIT_redBits = getIntAttribute(sBIT_node, "red"); 1546 sBIT_greenBits = getIntAttribute(sBIT_node, "green"); 1547 sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); 1548 sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha"); 1549 sBIT_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA; 1550 } else if (sBIT_name.equals("sBIT_Palette")) { 1551 sBIT_redBits = getIntAttribute(sBIT_node, "red"); 1552 sBIT_greenBits = getIntAttribute(sBIT_node, "green"); 1553 sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); 1554 sBIT_colorType = PNGImageReader.PNG_COLOR_PALETTE; 1555 } else { 1556 fatal(node, "Bad child of an sBIT node!"); 1557 } 1558 if (sBIT_node.getNextSibling() != null) { 1559 fatal(node, "sBIT node has more than one child!"); 1560 } 1561 1562 sBIT_present = true; 1563 } else if (name.equals("sPLT")) { 1564 sPLT_paletteName = getAttribute(node, "name"); 1565 sPLT_sampleDepth = getIntAttribute(node, "sampleDepth"); 1566 1567 int[] red = new int[256]; 1568 int[] green = new int[256]; 1569 int[] blue = new int[256]; 1570 int[] alpha = new int[256]; 1571 int[] frequency = new int[256]; 1572 int maxindex = -1; 1573 1574 Node sPLT_entry = node.getFirstChild(); 1575 if (sPLT_entry == null) { 1576 fatal(node, "sPLT node has no children!"); 1577 } 1578 1579 while (sPLT_entry != null) { 1580 if (!sPLT_entry.getNodeName().equals("sPLTEntry")) { 1581 fatal(node, 1582 "Only an sPLTEntry may be a child of an sPLT!"); 1583 } 1584 1585 int index = getIntAttribute(sPLT_entry, "index"); 1586 if (index < 0 || index > 255) { 1587 fatal(node, 1588 "Bad value for PLTEEntry attribute index!"); 1589 } 1590 if (index > maxindex) { 1591 maxindex = index; 1592 } 1593 red[index] = getIntAttribute(sPLT_entry, "red"); 1594 green[index] = getIntAttribute(sPLT_entry, "green"); 1595 blue[index] = getIntAttribute(sPLT_entry, "blue"); 1596 alpha[index] = getIntAttribute(sPLT_entry, "alpha"); 1597 frequency[index] = 1598 getIntAttribute(sPLT_entry, "frequency"); 1599 1600 sPLT_entry = sPLT_entry.getNextSibling(); 1601 } 1602 1603 int numEntries = maxindex + 1; 1604 sPLT_red = new int[numEntries]; 1605 sPLT_green = new int[numEntries]; 1606 sPLT_blue = new int[numEntries]; 1607 sPLT_alpha = new int[numEntries]; 1608 sPLT_frequency = new int[numEntries]; 1609 System.arraycopy(red, 0, sPLT_red, 0, numEntries); 1610 System.arraycopy(green, 0, sPLT_green, 0, numEntries); 1611 System.arraycopy(blue, 0, sPLT_blue, 0, numEntries); 1612 System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries); 1613 System.arraycopy(frequency, 0, 1614 sPLT_frequency, 0, numEntries); 1615 1616 sPLT_present = true; 1617 } else if (name.equals("sRGB")) { 1618 sRGB_renderingIntent = 1619 getEnumeratedAttribute(node, "renderingIntent", 1620 renderingIntentNames); 1621 1622 sRGB_present = true; 1623 } else if (name.equals("tEXt")) { 1624 Node tEXt_node = node.getFirstChild(); 1625 while (tEXt_node != null) { 1626 if (!tEXt_node.getNodeName().equals("tEXtEntry")) { 1627 fatal(node, 1628 "Only an tEXtEntry may be a child of an tEXt!"); 1629 } 1630 1631 String keyword = getAttribute(tEXt_node, "keyword"); 1632 tEXt_keyword.add(keyword); 1633 1634 String text = getAttribute(tEXt_node, "value"); 1635 tEXt_text.add(text); 1636 1637 // Check if the text chunk contains image creation time 1638 if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) { 1639 // Update Standard/Document/ImageCreationTime 1640 int index = tEXt_text.size()-1; 1641 decodeImageCreationTimeFromTextChunk( 1642 tEXt_text.listIterator(index)); 1643 } 1644 tEXt_node = tEXt_node.getNextSibling(); 1645 } 1646 } else if (name.equals("tIME")) { 1647 tIME_year = getIntAttribute(node, "year"); 1648 tIME_month = getIntAttribute(node, "month"); 1649 tIME_day = getIntAttribute(node, "day"); 1650 tIME_hour = getIntAttribute(node, "hour"); 1651 tIME_minute = getIntAttribute(node, "minute"); 1652 tIME_second = getIntAttribute(node, "second"); 1653 1654 tIME_present = true; 1655 } else if (name.equals("tRNS")) { 1656 tRNS_present = false; // Guard against partial overwrite 1657 Node tRNS_node = node.getFirstChild(); 1658 if (tRNS_node == null) { 1659 fatal(node, "tRNS node has no children!"); 1660 } 1661 String tRNS_name = tRNS_node.getNodeName(); 1662 if (tRNS_name.equals("tRNS_Palette")) { 1663 byte[] alpha = new byte[256]; 1664 int maxindex = -1; 1665 1666 Node tRNS_paletteEntry = tRNS_node.getFirstChild(); 1667 if (tRNS_paletteEntry == null) { 1668 fatal(node, "tRNS_Palette node has no children!"); 1669 } 1670 while (tRNS_paletteEntry != null) { 1671 if (!tRNS_paletteEntry.getNodeName().equals( 1672 "tRNS_PaletteEntry")) { 1673 fatal(node, 1674 "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!"); 1675 } 1676 int index = 1677 getIntAttribute(tRNS_paletteEntry, "index"); 1678 if (index < 0 || index > 255) { 1679 fatal(node, 1680 "Bad value for tRNS_PaletteEntry attribute index!"); 1681 } 1682 if (index > maxindex) { 1683 maxindex = index; 1684 } 1685 alpha[index] = 1686 (byte)getIntAttribute(tRNS_paletteEntry, 1687 "alpha"); 1688 1689 tRNS_paletteEntry = 1690 tRNS_paletteEntry.getNextSibling(); 1691 } 1692 1693 int numEntries = maxindex + 1; 1694 tRNS_alpha = new byte[numEntries]; 1695 tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE; 1696 System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries); 1697 } else if (tRNS_name.equals("tRNS_Grayscale")) { 1698 tRNS_gray = getIntAttribute(tRNS_node, "gray"); 1699 tRNS_colorType = PNGImageReader.PNG_COLOR_GRAY; 1700 } else if (tRNS_name.equals("tRNS_RGB")) { 1701 tRNS_red = getIntAttribute(tRNS_node, "red"); 1702 tRNS_green = getIntAttribute(tRNS_node, "green"); 1703 tRNS_blue = getIntAttribute(tRNS_node, "blue"); 1704 tRNS_colorType = PNGImageReader.PNG_COLOR_RGB; 1705 } else { 1706 fatal(node, "Bad child of a tRNS node!"); 1707 } 1708 if (tRNS_node.getNextSibling() != null) { 1709 fatal(node, "tRNS node has more than one child!"); 1710 } 1711 1712 tRNS_present = true; 1713 } else if (name.equals("zTXt")) { 1714 Node zTXt_node = node.getFirstChild(); 1715 while (zTXt_node != null) { 1716 if (!zTXt_node.getNodeName().equals("zTXtEntry")) { 1717 fatal(node, 1718 "Only an zTXtEntry may be a child of an zTXt!"); 1719 } 1720 1721 String keyword = getAttribute(zTXt_node, "keyword"); 1722 zTXt_keyword.add(keyword); 1723 1724 int compressionMethod = 1725 getEnumeratedAttribute(zTXt_node, "compressionMethod", 1726 zTXt_compressionMethodNames); 1727 zTXt_compressionMethod.add(compressionMethod); 1728 1729 String text = getAttribute(zTXt_node, "text"); 1730 zTXt_text.add(text); 1731 1732 // Check if the text chunk contains image creation time 1733 if (keyword.equals(PNGMetadata.tEXt_creationTimeKey)) { 1734 // Update Standard/Document/ImageCreationTime 1735 int index = zTXt_text.size()-1; 1736 decodeImageCreationTimeFromTextChunk( 1737 zTXt_text.listIterator(index)); 1738 } 1739 zTXt_node = zTXt_node.getNextSibling(); 1740 } 1741 } else if (name.equals("UnknownChunks")) { 1742 Node unknown_node = node.getFirstChild(); 1743 while (unknown_node != null) { 1744 if (!unknown_node.getNodeName().equals("UnknownChunk")) { 1745 fatal(node, 1746 "Only an UnknownChunk may be a child of an UnknownChunks!"); 1747 } 1748 String chunkType = getAttribute(unknown_node, "type"); 1749 Object chunkData = 1750 ((IIOMetadataNode)unknown_node).getUserObject(); 1751 1752 if (chunkType.length() != 4) { 1753 fatal(unknown_node, 1754 "Chunk type must be 4 characters!"); 1755 } 1756 if (chunkData == null) { 1757 fatal(unknown_node, 1758 "No chunk data present in user object!"); 1759 } 1760 if (!(chunkData instanceof byte[])) { 1761 fatal(unknown_node, 1762 "User object not a byte array!"); 1763 } 1764 unknownChunkType.add(chunkType); 1765 unknownChunkData.add(((byte[])chunkData).clone()); 1766 1767 unknown_node = unknown_node.getNextSibling(); 1768 } 1769 } else { 1770 fatal(node, "Unknown child of root node!"); 1771 } 1772 1773 node = node.getNextSibling(); 1774 } 1775 } 1776 1777 /* 1778 * Accrding to PNG spec, keywords are restricted to 1 to 79 bytes 1779 * in length. Keywords shall contain only printable Latin-1 characters 1780 * and spaces; To reduce the chances for human misreading of a keyword, 1781 * leading spaces, trailing spaces, and consecutive spaces are not 1782 * permitted in keywords. 1783 * 1784 * See: http://www.w3.org/TR/PNG/#11keywords 1785 */ 1786 private boolean isValidKeyword(String s) { 1787 int len = s.length(); 1788 if (len < 1 || len >= 80) { 1789 return false; 1790 } 1791 if (s.startsWith(" ") || s.endsWith(" ") || s.contains(" ")) { 1792 return false; 1793 } 1794 return isISOLatin(s, false); 1795 } 1796 1797 /* 1798 * According to PNG spec, keyword shall contain only printable 1799 * Latin-1 [ISO-8859-1] characters and spaces; that is, only 1800 * character codes 32-126 and 161-255 decimal are allowed. 1801 * For Latin-1 value fields the 0x10 (linefeed) control 1802 * character is aloowed too. 1803 * 1804 * See: http://www.w3.org/TR/PNG/#11keywords 1805 */ 1806 private boolean isISOLatin(String s, boolean isLineFeedAllowed) { 1807 int len = s.length(); 1808 for (int i = 0; i < len; i++) { 1809 char c = s.charAt(i); 1810 if (c < 32 || c > 255 || (c > 126 && c < 161)) { 1811 // not printable. Check whether this is an allowed 1812 // control char 1813 if (!isLineFeedAllowed || c != 0x10) { 1814 return false; 1815 } 1816 } 1817 } 1818 return true; 1819 } 1820 1821 private void mergeStandardTree(Node root) 1822 throws IIOInvalidTreeException { 1823 Node node = root; 1824 if (!node.getNodeName() 1825 .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { 1826 fatal(node, "Root must be " + 1827 IIOMetadataFormatImpl.standardMetadataFormatName); 1828 } 1829 1830 node = node.getFirstChild(); 1831 while (node != null) { 1832 String name = node.getNodeName(); 1833 1834 if (name.equals("Chroma")) { 1835 Node child = node.getFirstChild(); 1836 while (child != null) { 1837 String childName = child.getNodeName(); 1838 if (childName.equals("Gamma")) { 1839 float gamma = getFloatAttribute(child, "value"); 1840 gAMA_present = true; 1841 gAMA_gamma = (int)(gamma*100000 + 0.5); 1842 } else if (childName.equals("Palette")) { 1843 byte[] red = new byte[256]; 1844 byte[] green = new byte[256]; 1845 byte[] blue = new byte[256]; 1846 int maxindex = -1; 1847 1848 Node entry = child.getFirstChild(); 1849 while (entry != null) { 1850 int index = getIntAttribute(entry, "index"); 1851 if (index >= 0 && index <= 255) { 1852 red[index] = 1853 (byte)getIntAttribute(entry, "red"); 1854 green[index] = 1855 (byte)getIntAttribute(entry, "green"); 1856 blue[index] = 1857 (byte)getIntAttribute(entry, "blue"); 1858 if (index > maxindex) { 1859 maxindex = index; 1860 } 1861 } 1862 entry = entry.getNextSibling(); 1863 } 1864 1865 int numEntries = maxindex + 1; 1866 PLTE_red = new byte[numEntries]; 1867 PLTE_green = new byte[numEntries]; 1868 PLTE_blue = new byte[numEntries]; 1869 System.arraycopy(red, 0, PLTE_red, 0, numEntries); 1870 System.arraycopy(green, 0, PLTE_green, 0, numEntries); 1871 System.arraycopy(blue, 0, PLTE_blue, 0, numEntries); 1872 PLTE_present = true; 1873 } else if (childName.equals("BackgroundIndex")) { 1874 bKGD_present = true; 1875 bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE; 1876 bKGD_index = getIntAttribute(child, "value"); 1877 } else if (childName.equals("BackgroundColor")) { 1878 int red = getIntAttribute(child, "red"); 1879 int green = getIntAttribute(child, "green"); 1880 int blue = getIntAttribute(child, "blue"); 1881 if (red == green && red == blue) { 1882 bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY; 1883 bKGD_gray = red; 1884 } else { 1885 bKGD_red = red; 1886 bKGD_green = green; 1887 bKGD_blue = blue; 1888 } 1889 bKGD_present = true; 1890 } 1891 // } else if (childName.equals("ColorSpaceType")) { 1892 // } else if (childName.equals("NumChannels")) { 1893 1894 child = child.getNextSibling(); 1895 } 1896 } else if (name.equals("Compression")) { 1897 Node child = node.getFirstChild(); 1898 while (child != null) { 1899 String childName = child.getNodeName(); 1900 if (childName.equals("NumProgressiveScans")) { 1901 // Use Adam7 if NumProgressiveScans > 1 1902 int scans = getIntAttribute(child, "value"); 1903 IHDR_interlaceMethod = (scans > 1) ? 1 : 0; 1904 // } else if (childName.equals("CompressionTypeName")) { 1905 // } else if (childName.equals("Lossless")) { 1906 // } else if (childName.equals("BitRate")) { 1907 } 1908 child = child.getNextSibling(); 1909 } 1910 } else if (name.equals("Data")) { 1911 Node child = node.getFirstChild(); 1912 while (child != null) { 1913 String childName = child.getNodeName(); 1914 if (childName.equals("BitsPerSample")) { 1915 String s = getAttribute(child, "value"); 1916 StringTokenizer t = new StringTokenizer(s); 1917 int maxBits = -1; 1918 while (t.hasMoreTokens()) { 1919 int bits = Integer.parseInt(t.nextToken()); 1920 if (bits > maxBits) { 1921 maxBits = bits; 1922 } 1923 } 1924 if (maxBits < 1) { 1925 maxBits = 1; 1926 } 1927 if (maxBits == 3) maxBits = 4; 1928 if (maxBits > 4 || maxBits < 8) { 1929 maxBits = 8; 1930 } 1931 if (maxBits > 8) { 1932 maxBits = 16; 1933 } 1934 IHDR_bitDepth = maxBits; 1935 } else if (childName.equals("SignificantBitsPerSample")) { 1936 String s = getAttribute(child, "value"); 1937 StringTokenizer t = new StringTokenizer(s); 1938 int numTokens = t.countTokens(); 1939 if (numTokens == 1) { 1940 sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY; 1941 sBIT_grayBits = Integer.parseInt(t.nextToken()); 1942 } else if (numTokens == 2) { 1943 sBIT_colorType = 1944 PNGImageReader.PNG_COLOR_GRAY_ALPHA; 1945 sBIT_grayBits = Integer.parseInt(t.nextToken()); 1946 sBIT_alphaBits = Integer.parseInt(t.nextToken()); 1947 } else if (numTokens == 3) { 1948 sBIT_colorType = PNGImageReader.PNG_COLOR_RGB; 1949 sBIT_redBits = Integer.parseInt(t.nextToken()); 1950 sBIT_greenBits = Integer.parseInt(t.nextToken()); 1951 sBIT_blueBits = Integer.parseInt(t.nextToken()); 1952 } else if (numTokens == 4) { 1953 sBIT_colorType = 1954 PNGImageReader.PNG_COLOR_RGB_ALPHA; 1955 sBIT_redBits = Integer.parseInt(t.nextToken()); 1956 sBIT_greenBits = Integer.parseInt(t.nextToken()); 1957 sBIT_blueBits = Integer.parseInt(t.nextToken()); 1958 sBIT_alphaBits = Integer.parseInt(t.nextToken()); 1959 } 1960 if (numTokens >= 1 && numTokens <= 4) { 1961 sBIT_present = true; 1962 } 1963 // } else if (childName.equals("PlanarConfiguration")) { 1964 // } else if (childName.equals("SampleFormat")) { 1965 // } else if (childName.equals("SampleMSB")) { 1966 } 1967 child = child.getNextSibling(); 1968 } 1969 } else if (name.equals("Dimension")) { 1970 boolean gotWidth = false; 1971 boolean gotHeight = false; 1972 boolean gotAspectRatio = false; 1973 1974 float width = -1.0F; 1975 float height = -1.0F; 1976 float aspectRatio = -1.0F; 1977 1978 Node child = node.getFirstChild(); 1979 while (child != null) { 1980 String childName = child.getNodeName(); 1981 if (childName.equals("PixelAspectRatio")) { 1982 aspectRatio = getFloatAttribute(child, "value"); 1983 gotAspectRatio = true; 1984 } else if (childName.equals("HorizontalPixelSize")) { 1985 width = getFloatAttribute(child, "value"); 1986 gotWidth = true; 1987 } else if (childName.equals("VerticalPixelSize")) { 1988 height = getFloatAttribute(child, "value"); 1989 gotHeight = true; 1990 // } else if (childName.equals("ImageOrientation")) { 1991 // } else if 1992 // (childName.equals("HorizontalPhysicalPixelSpacing")) { 1993 // } else if 1994 // (childName.equals("VerticalPhysicalPixelSpacing")) { 1995 // } else if (childName.equals("HorizontalPosition")) { 1996 // } else if (childName.equals("VerticalPosition")) { 1997 // } else if (childName.equals("HorizontalPixelOffset")) { 1998 // } else if (childName.equals("VerticalPixelOffset")) { 1999 } 2000 child = child.getNextSibling(); 2001 } 2002 2003 if (gotWidth && gotHeight) { 2004 pHYs_present = true; 2005 pHYs_unitSpecifier = 1; 2006 pHYs_pixelsPerUnitXAxis = (int)(width*1000 + 0.5F); 2007 pHYs_pixelsPerUnitYAxis = (int)(height*1000 + 0.5F); 2008 } else if (gotAspectRatio) { 2009 pHYs_present = true; 2010 pHYs_unitSpecifier = 0; 2011 2012 // Find a reasonable rational approximation 2013 int denom = 1; 2014 for (; denom < 100; denom++) { 2015 int num = (int)(aspectRatio*denom); 2016 if (Math.abs(num/denom - aspectRatio) < 0.001) { 2017 break; 2018 } 2019 } 2020 pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom); 2021 pHYs_pixelsPerUnitYAxis = denom; 2022 } 2023 } else if (name.equals("Document")) { 2024 Node child = node.getFirstChild(); 2025 while (child != null) { 2026 String childName = child.getNodeName(); 2027 if (childName.equals("ImageModificationTime")) { 2028 tIME_present = true; 2029 tIME_year = getIntAttribute(child, "year"); 2030 tIME_month = getIntAttribute(child, "month"); 2031 tIME_day = getIntAttribute(child, "day"); 2032 tIME_hour = 2033 getIntAttribute(child, "hour", 0, false); 2034 tIME_minute = 2035 getIntAttribute(child, "minute", 0, false); 2036 tIME_second = 2037 getIntAttribute(child, "second", 0, false); 2038 // } else if (childName.equals("SubimageInterpretation")) { 2039 } else if (childName.equals("ImageCreationTime")) { 2040 // Extract the creation time values 2041 int year = getIntAttribute(child, "year"); 2042 int month = getIntAttribute(child, "month"); 2043 int day = getIntAttribute(child, "day"); 2044 int hour = getIntAttribute(child, "hour", 0, false); 2045 int mins = getIntAttribute(child, "minute", 0, false); 2046 int sec = getIntAttribute(child, "second", 0, false); 2047 2048 /* 2049 * Update Standard/Document/ImageCreationTime and encode 2050 * the same in the last decoded text chunk with creation 2051 * time 2052 */ 2053 initImageCreationTime(year, month, day, hour, mins, sec); 2054 encodeImageCreationTimeToTextChunk(); 2055 } 2056 child = child.getNextSibling(); 2057 } 2058 } else if (name.equals("Text")) { 2059 Node child = node.getFirstChild(); 2060 while (child != null) { 2061 String childName = child.getNodeName(); 2062 if (childName.equals("TextEntry")) { 2063 String keyword = 2064 getAttribute(child, "keyword", "", false); 2065 String value = getAttribute(child, "value"); 2066 String language = 2067 getAttribute(child, "language", "", false); 2068 String compression = 2069 getAttribute(child, "compression", "none", false); 2070 2071 if (!isValidKeyword(keyword)) { 2072 // Just ignore this node, PNG requires keywords 2073 } else if (isISOLatin(value, true)) { 2074 if (compression.equals("zip")) { 2075 // Use a zTXt node 2076 zTXt_keyword.add(keyword); 2077 zTXt_text.add(value); 2078 zTXt_compressionMethod.add(Integer.valueOf(0)); 2079 } else { 2080 // Use a tEXt node 2081 tEXt_keyword.add(keyword); 2082 tEXt_text.add(value); 2083 } 2084 } else { 2085 // Use an iTXt node 2086 iTXt_keyword.add(keyword); 2087 iTXt_compressionFlag.add(Boolean.valueOf(compression.equals("zip"))); 2088 iTXt_compressionMethod.add(Integer.valueOf(0)); 2089 iTXt_languageTag.add(language); 2090 iTXt_translatedKeyword.add(keyword); // fake it 2091 iTXt_text.add(value); 2092 } 2093 } 2094 child = child.getNextSibling(); 2095 } 2096 // } else if (name.equals("Transparency")) { 2097 // Node child = node.getFirstChild(); 2098 // while (child != null) { 2099 // String childName = child.getNodeName(); 2100 // if (childName.equals("Alpha")) { 2101 // } else if (childName.equals("TransparentIndex")) { 2102 // } else if (childName.equals("TransparentColor")) { 2103 // } else if (childName.equals("TileTransparencies")) { 2104 // } else if (childName.equals("TileOpacities")) { 2105 // } 2106 // child = child.getNextSibling(); 2107 // } 2108 // } else { 2109 // // fatal(node, "Unknown child of root node!"); 2110 } 2111 2112 node = node.getNextSibling(); 2113 } 2114 } 2115 2116 void initImageCreationTime(OffsetDateTime offsetDateTime) { 2117 // Check for incoming arguments 2118 if (offsetDateTime != null) { 2119 // set values that make up Standard/Document/ImageCreationTime 2120 creation_time_present = true; 2121 creation_time_year = offsetDateTime.getYear(); 2122 creation_time_month = offsetDateTime.getMonthValue(); 2123 creation_time_day = offsetDateTime.getDayOfMonth(); 2124 creation_time_hour = offsetDateTime.getHour(); 2125 creation_time_minute = offsetDateTime.getMinute(); 2126 creation_time_second = offsetDateTime.getSecond(); 2127 creation_time_offset = offsetDateTime.getOffset(); 2128 } 2129 } 2130 2131 void initImageCreationTime(int year, int month, int day, 2132 int hour, int min,int second) { 2133 /* 2134 * Though LocalDateTime suffices the need to store Standard/Document/ 2135 * ImageCreationTime, we require the zone offset to encode the same 2136 * in the text chunk based on RFC1123 format. 2137 */ 2138 LocalDateTime locDT = LocalDateTime.of(year, month, day, hour, min, second); 2139 ZoneOffset offset = ZoneId.systemDefault() 2140 .getRules() 2141 .getOffset(locDT); 2142 OffsetDateTime offDateTime = OffsetDateTime.of(locDT,offset); 2143 initImageCreationTime(offDateTime); 2144 } 2145 2146 void decodeImageCreationTimeFromTextChunk(ListIterator<String> iterChunk) { 2147 // Check for incoming arguments 2148 if (iterChunk != null && iterChunk.hasNext()) { 2149 /* 2150 * Save the iterator to mark the last decoded text chunk with 2151 * creation time. The contents of this chunk will be updated when 2152 * user provides creation time by merging a standard tree with 2153 * Standard/Document/ImageCreationTime. 2154 */ 2155 setCreationTimeChunk(iterChunk); 2156 2157 // Parse encoded time and set Standard/Document/ImageCreationTime. 2158 String encodedTime = getEncodedTime(); 2159 initImageCreationTime(parseEncodedTime(encodedTime)); 2160 } 2161 } 2162 2163 void encodeImageCreationTimeToTextChunk() { 2164 // Check if Standard/Document/ImageCreationTime exists. 2165 if (creation_time_present) { 2166 // Check if a text chunk with creation time exists. 2167 if (tEXt_creation_time_present == false) { 2168 // No text chunk exists with image creation time. Add an entry. 2169 this.tEXt_keyword.add(tEXt_creationTimeKey); 2170 this.tEXt_text.add("Creation Time Place Holder"); 2171 2172 // Update the iterator 2173 int index = tEXt_text.size() - 1; 2174 setCreationTimeChunk(tEXt_text.listIterator(index)); 2175 } 2176 2177 // Encode image creation time with RFC1123 formatter 2178 OffsetDateTime offDateTime = OffsetDateTime.of(creation_time_year, 2179 creation_time_month, creation_time_day, 2180 creation_time_hour, creation_time_minute, 2181 creation_time_second, 0, creation_time_offset); 2182 DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME; 2183 String encodedTime = offDateTime.format(formatter); 2184 setEncodedTime(encodedTime); 2185 } 2186 } 2187 2188 private void setCreationTimeChunk(ListIterator<String> iter) { 2189 // Check for iterator's valid state 2190 if (iter != null && iter.hasNext()) { 2191 tEXt_creation_time_iter = iter; 2192 tEXt_creation_time_present = true; 2193 } 2194 } 2195 2196 private void setEncodedTime(String encodedTime) { 2197 if (tEXt_creation_time_iter != null 2198 && tEXt_creation_time_iter.hasNext() 2199 && encodedTime != null) { 2200 // Set the value at the iterator and reset its state 2201 tEXt_creation_time_iter.next(); 2202 tEXt_creation_time_iter.set(encodedTime); 2203 tEXt_creation_time_iter.previous(); 2204 } 2205 } 2206 2207 private String getEncodedTime() { 2208 String encodedTime = null; 2209 if (tEXt_creation_time_iter != null 2210 && tEXt_creation_time_iter.hasNext()) { 2211 // Get the value at iterator and reset its state 2212 encodedTime = tEXt_creation_time_iter.next(); 2213 tEXt_creation_time_iter.previous(); 2214 } 2215 return encodedTime; 2216 } 2217 2218 private OffsetDateTime parseEncodedTime(String encodedTime) { 2219 OffsetDateTime retVal = null; 2220 boolean timeDecoded = false; 2221 2222 /* 2223 * PNG specification recommends that image encoders use RFC1123 format 2224 * to represent time in String but doesn't mandate. Encoders could 2225 * use any convenient format. Hence, we extract time provided the 2226 * encoded time complies with either RFC1123 or ISO standards. 2227 */ 2228 try { 2229 // Check if the encoded time complies with RFC1123 2230 retVal = OffsetDateTime.parse(encodedTime, 2231 DateTimeFormatter.RFC_1123_DATE_TIME); 2232 timeDecoded = true; 2233 } catch (DateTimeParseException exception) { 2234 // No Op. Encoded time did not comply with RFC1123 standard. 2235 } 2236 2237 if (timeDecoded == false) { 2238 try { 2239 // Check if the encoded time complies with ISO standard. 2240 DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME; 2241 TemporalAccessor dt = formatter.parseBest(encodedTime, 2242 OffsetDateTime::from, LocalDateTime::from); 2243 2244 if (dt instanceof OffsetDateTime) { 2245 // Encoded time contains date time and zone offset 2246 retVal = (OffsetDateTime) dt; 2247 } else if (dt instanceof LocalDateTime) { 2248 /* 2249 * Encoded time contains only date and time. Since zone 2250 * offset information isn't available, we set to the default 2251 */ 2252 LocalDateTime locDT = (LocalDateTime) dt; 2253 retVal = OffsetDateTime.of(locDT, ZoneOffset.UTC); 2254 } 2255 } catch (DateTimeParseException exception) { 2256 // No Op. Encoded time did not comply with ISO standard. 2257 } 2258 } 2259 return retVal; 2260 } 2261 2262 boolean hasTransparentColor() { 2263 return tRNS_present && 2264 (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB || 2265 tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY); 2266 } 2267 2268 // Reset all instance variables to their initial state 2269 public void reset() { 2270 IHDR_present = false; 2271 PLTE_present = false; 2272 bKGD_present = false; 2273 cHRM_present = false; 2274 gAMA_present = false; 2275 hIST_present = false; 2276 iCCP_present = false; 2277 iTXt_keyword = new ArrayList<String>(); 2278 iTXt_compressionFlag = new ArrayList<Boolean>(); 2279 iTXt_compressionMethod = new ArrayList<Integer>(); 2280 iTXt_languageTag = new ArrayList<String>(); 2281 iTXt_translatedKeyword = new ArrayList<String>(); 2282 iTXt_text = new ArrayList<String>(); 2283 pHYs_present = false; 2284 sBIT_present = false; 2285 sPLT_present = false; 2286 sRGB_present = false; 2287 tEXt_keyword = new ArrayList<String>(); 2288 tEXt_text = new ArrayList<String>(); 2289 // tIME chunk with Image modification time 2290 tIME_present = false; 2291 // Text chunk with Image creation time 2292 tEXt_creation_time_present = false; 2293 tEXt_creation_time_iter = null; 2294 creation_time_present = false; 2295 tRNS_present = false; 2296 zTXt_keyword = new ArrayList<String>(); 2297 zTXt_compressionMethod = new ArrayList<Integer>(); 2298 zTXt_text = new ArrayList<String>(); 2299 unknownChunkType = new ArrayList<String>(); 2300 unknownChunkData = new ArrayList<byte[]>(); 2301 } 2302 } 2303