1 /*
2 * Copyright (c) 2000, 2017, 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 // Reset all instance variables to their initial state
2263 public void reset() {
2264 IHDR_present = false;
2265 PLTE_present = false;
2266 bKGD_present = false;
2267 cHRM_present = false;
2268 gAMA_present = false;
2269 hIST_present = false;
2270 iCCP_present = false;
2271 iTXt_keyword = new ArrayList<String>();
2272 iTXt_compressionFlag = new ArrayList<Boolean>();
2273 iTXt_compressionMethod = new ArrayList<Integer>();
2274 iTXt_languageTag = new ArrayList<String>();
2275 iTXt_translatedKeyword = new ArrayList<String>();
2276 iTXt_text = new ArrayList<String>();
2277 pHYs_present = false;
2278 sBIT_present = false;
2279 sPLT_present = false;
2280 sRGB_present = false;
2281 tEXt_keyword = new ArrayList<String>();
2282 tEXt_text = new ArrayList<String>();
2283 // tIME chunk with Image modification time
2284 tIME_present = false;
2285 // Text chunk with Image creation time
2286 tEXt_creation_time_present = false;
2287 tEXt_creation_time_iter = null;
2288 creation_time_present = false;
2289 tRNS_present = false;
2290 zTXt_keyword = new ArrayList<String>();
2291 zTXt_compressionMethod = new ArrayList<Integer>();
2292 zTXt_text = new ArrayList<String>();
2293 unknownChunkType = new ArrayList<String>();
2294 unknownChunkData = new ArrayList<byte[]>();
2295 }
2296 }
--- EOF ---