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