1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.formats.png;
18
19 import java.awt.Dimension;
20 import java.awt.color.ColorSpace;
21 import java.awt.color.ICC_ColorSpace;
22 import java.awt.color.ICC_Profile;
23 import java.awt.image.BufferedImage;
24 import java.awt.image.ColorModel;
25 import java.io.ByteArrayInputStream;
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.io.PrintWriter;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.logging.Level;
34 import java.util.logging.Logger;
35 import java.util.zip.InflaterInputStream;
36
37 import org.apache.commons.imaging.AbstractImageParser;
38 import org.apache.commons.imaging.ColorTools;
39 import org.apache.commons.imaging.ImageFormat;
40 import org.apache.commons.imaging.ImageFormats;
41 import org.apache.commons.imaging.ImageInfo;
42 import org.apache.commons.imaging.ImagingException;
43 import org.apache.commons.imaging.bytesource.ByteSource;
44 import org.apache.commons.imaging.common.Allocator;
45 import org.apache.commons.imaging.common.BinaryFunctions;
46 import org.apache.commons.imaging.common.GenericImageMetadata;
47 import org.apache.commons.imaging.common.ImageMetadata;
48 import org.apache.commons.imaging.common.XmpEmbeddable;
49 import org.apache.commons.imaging.common.XmpImagingParameters;
50 import org.apache.commons.imaging.formats.png.chunks.AbstractPngTextChunk;
51 import org.apache.commons.imaging.formats.png.chunks.PngChunk;
52 import org.apache.commons.imaging.formats.png.chunks.PngChunkGama;
53 import org.apache.commons.imaging.formats.png.chunks.PngChunkIccp;
54 import org.apache.commons.imaging.formats.png.chunks.PngChunkIdat;
55 import org.apache.commons.imaging.formats.png.chunks.PngChunkIhdr;
56 import org.apache.commons.imaging.formats.png.chunks.PngChunkItxt;
57 import org.apache.commons.imaging.formats.png.chunks.PngChunkPhys;
58 import org.apache.commons.imaging.formats.png.chunks.PngChunkPlte;
59 import org.apache.commons.imaging.formats.png.chunks.PngChunkScal;
60 import org.apache.commons.imaging.formats.png.chunks.PngChunkText;
61 import org.apache.commons.imaging.formats.png.chunks.PngChunkZtxt;
62 import org.apache.commons.imaging.formats.png.transparencyfilters.AbstractTransparencyFilter;
63 import org.apache.commons.imaging.formats.png.transparencyfilters.TransparencyFilterGrayscale;
64 import org.apache.commons.imaging.formats.png.transparencyfilters.TransparencyFilterIndexedColor;
65 import org.apache.commons.imaging.formats.png.transparencyfilters.TransparencyFilterTrueColor;
66 import org.apache.commons.imaging.icc.IccProfileParser;
67
68 public class PngImageParser extends AbstractImageParser<PngImagingParameters> implements XmpEmbeddable<PngImagingParameters> {
69
70 private static final Logger LOGGER = Logger.getLogger(PngImageParser.class.getName());
71
72 private static final String DEFAULT_EXTENSION = ImageFormats.PNG.getDefaultExtension();
73 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.PNG.getExtensions();
74
75 public static String getChunkTypeName(final int chunkType) {
76 final StringBuilder result = new StringBuilder();
77 result.append((char) (0xff & chunkType >> 24));
78 result.append((char) (0xff & chunkType >> 16));
79 result.append((char) (0xff & chunkType >> 8));
80 result.append((char) (0xff & chunkType >> 0));
81 return result.toString();
82 }
83
84 @Override
85 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
86 final ImageInfo imageInfo = getImageInfo(byteSource);
87 if (imageInfo == null) {
88 return false;
89 }
90
91 imageInfo.toString(pw, "");
92
93 final List<PngChunk> chunks = readChunks(byteSource, null, false);
94 final List<PngChunk> IHDRs = filterChunks(chunks, ChunkType.IHDR);
95 if (IHDRs.size() != 1) {
96 if (LOGGER.isLoggable(Level.FINEST)) {
97 LOGGER.finest("PNG contains more than one Header");
98 }
99 return false;
100 }
101 final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0);
102 pw.println("Color: " + pngChunkIHDR.getPngColorType().name());
103
104 pw.println("chunks: " + chunks.size());
105
106 if (chunks.isEmpty()) {
107 return false;
108 }
109
110 for (int i = 0; i < chunks.size(); i++) {
111 final PngChunk chunk = chunks.get(i);
112 BinaryFunctions.printCharQuad(pw, "\t" + i + ": ", chunk.getChunkType());
113 }
114
115 pw.println("");
116
117 pw.flush();
118
119 return true;
120 }
121
122 private List<PngChunk> filterChunks(final List<PngChunk> chunks, final ChunkType type) {
123 final List<PngChunk> result = new ArrayList<>();
124
125 for (final PngChunk chunk : chunks) {
126 if (chunk.getChunkType() == type.value) {
127 result.add(chunk);
128 }
129 }
130
131 return result;
132 }
133
134 @Override
135 protected String[] getAcceptedExtensions() {
136 return ACCEPTED_EXTENSIONS.clone();
137 }
138
139 @Override
140 protected ImageFormat[] getAcceptedTypes() {
141 return new ImageFormat[] { ImageFormats.PNG,
142 };
143 }
144
145
146
147 @Override
148 public BufferedImage getBufferedImage(final ByteSource byteSource, final PngImagingParameters params) throws ImagingException, IOException {
149
150 final List<PngChunk> chunks = readChunks(byteSource,
151 new ChunkType[] { ChunkType.IHDR, ChunkType.PLTE, ChunkType.IDAT, ChunkType.tRNS, ChunkType.iCCP, ChunkType.gAMA, ChunkType.sRGB, }, false);
152
153 if (chunks.isEmpty()) {
154 throw new ImagingException("PNG: no chunks");
155 }
156
157 final List<PngChunk> IHDRs = filterChunks(chunks, ChunkType.IHDR);
158 if (IHDRs.size() != 1) {
159 throw new ImagingException("PNG contains more than one Header");
160 }
161
162 final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0);
163
164 final List<PngChunk> PLTEs = filterChunks(chunks, ChunkType.PLTE);
165 if (PLTEs.size() > 1) {
166 throw new ImagingException("PNG contains more than one Palette");
167 }
168
169 PngChunkPlte pngChunkPLTE = null;
170 if (PLTEs.size() == 1) {
171 pngChunkPLTE = (PngChunkPlte) PLTEs.get(0);
172 }
173
174 final List<PngChunk> IDATs = filterChunks(chunks, ChunkType.IDAT);
175 if (IDATs.isEmpty()) {
176 throw new ImagingException("PNG missing image data");
177 }
178
179 ByteArrayOutputStream baos = new ByteArrayOutputStream();
180 for (final PngChunk IDAT : IDATs) {
181 final PngChunkIdat pngChunkIDAT = (PngChunkIdat) IDAT;
182 final byte[] bytes = pngChunkIDAT.getBytes();
183
184 baos.write(bytes);
185 }
186
187 final byte[] compressed = baos.toByteArray();
188
189 baos = null;
190
191 AbstractTransparencyFilter abstractTransparencyFilter = null;
192
193 final List<PngChunk> tRNSs = filterChunks(chunks, ChunkType.tRNS);
194 if (!tRNSs.isEmpty()) {
195 final PngChunk pngChunktRNS = tRNSs.get(0);
196 abstractTransparencyFilter = getTransparencyFilter(pngChunkIHDR.getPngColorType(), pngChunktRNS);
197 }
198
199 ICC_Profile iccProfile = null;
200 GammaCorrection gammaCorrection = null;
201 {
202 final List<PngChunk> sRGBs = filterChunks(chunks, ChunkType.sRGB);
203 final List<PngChunk> gAMAs = filterChunks(chunks, ChunkType.gAMA);
204 final List<PngChunk> iCCPs = filterChunks(chunks, ChunkType.iCCP);
205 if (sRGBs.size() > 1) {
206 throw new ImagingException("PNG: unexpected sRGB chunk");
207 }
208 if (gAMAs.size() > 1) {
209 throw new ImagingException("PNG: unexpected gAMA chunk");
210 }
211 if (iCCPs.size() > 1) {
212 throw new ImagingException("PNG: unexpected iCCP chunk");
213 }
214
215 if (sRGBs.size() == 1) {
216
217 if (LOGGER.isLoggable(Level.FINEST)) {
218 LOGGER.finest("sRGB, no color management necessary.");
219 }
220 } else if (iCCPs.size() == 1) {
221 if (LOGGER.isLoggable(Level.FINEST)) {
222 LOGGER.finest("iCCP.");
223 }
224
225 final PngChunkIccp pngChunkiCCP = (PngChunkIccp) iCCPs.get(0);
226 final byte[] bytes = pngChunkiCCP.getUncompressedProfile();
227
228 try {
229 iccProfile = ICC_Profile.getInstance(bytes);
230 } catch (final IllegalArgumentException iae) {
231 throw new ImagingException("The image data does not correspond to a valid ICC Profile", iae);
232 }
233 } else if (gAMAs.size() == 1) {
234 final PngChunkGama pngChunkgAMA = (PngChunkGama) gAMAs.get(0);
235 final double gamma = pngChunkgAMA.getGamma();
236
237
238
239 final double targetGamma = 1.0;
240 final double diff = Math.abs(targetGamma - gamma);
241 if (diff >= 0.5) {
242 gammaCorrection = new GammaCorrection(gamma, targetGamma);
243 }
244
245 if (gammaCorrection != null) {
246 if (pngChunkPLTE != null) {
247 pngChunkPLTE.correct(gammaCorrection);
248 }
249 }
250
251 }
252 }
253
254 {
255 final int width = pngChunkIHDR.getWidth();
256 final int height = pngChunkIHDR.getHeight();
257 final PngColorType pngColorType = pngChunkIHDR.getPngColorType();
258 final int bitDepth = pngChunkIHDR.getBitDepth();
259
260 if (pngChunkIHDR.getFilterMethod() != 0) {
261 throw new ImagingException("PNG: unknown FilterMethod: " + pngChunkIHDR.getFilterMethod());
262 }
263
264 final int bitsPerPixel = bitDepth * pngColorType.getSamplesPerPixel();
265
266 final boolean hasAlpha = pngColorType.hasAlpha() || abstractTransparencyFilter != null;
267
268 BufferedImage result;
269 if (pngColorType.isGreyscale()) {
270 result = getBufferedImageFactory(params).getGrayscaleBufferedImage(width, height, hasAlpha);
271 } else {
272 result = getBufferedImageFactory(params).getColorBufferedImage(width, height, hasAlpha);
273 }
274
275 final ByteArrayInputStream bais = new ByteArrayInputStream(compressed);
276 final InflaterInputStream iis = new InflaterInputStream(bais);
277
278 AbstractScanExpediter abstractScanExpediter;
279
280 switch (pngChunkIHDR.getInterlaceMethod()) {
281 case NONE:
282 abstractScanExpediter = new ScanExpediterSimple(width, height, iis, result, pngColorType, bitDepth, bitsPerPixel, pngChunkPLTE, gammaCorrection,
283 abstractTransparencyFilter);
284 break;
285 case ADAM7:
286 abstractScanExpediter = new ScanExpediterInterlaced(width, height, iis, result, pngColorType, bitDepth, bitsPerPixel, pngChunkPLTE,
287 gammaCorrection, abstractTransparencyFilter);
288 break;
289 default:
290 throw new ImagingException("Unknown InterlaceMethod: " + pngChunkIHDR.getInterlaceMethod());
291 }
292
293 abstractScanExpediter.drive();
294
295 if (iccProfile != null) {
296 final boolean isSrgb = new IccProfileParser().isSrgb(iccProfile);
297 if (!isSrgb) {
298 final ICC_ColorSpace cs = new ICC_ColorSpace(iccProfile);
299
300 final ColorModel srgbCM = ColorModel.getRGBdefault();
301 final ColorSpace csSrgb = srgbCM.getColorSpace();
302
303 result = new ColorTools().convertBetweenColorSpaces(result, cs, csSrgb);
304 }
305 }
306
307 return result;
308
309 }
310
311 }
312
313
314
315
316
317
318
319 public List<String> getChunkTypes(final InputStream is) throws ImagingException, IOException {
320 final List<PngChunk> chunks = readChunks(is, null, false);
321 final List<String> chunkTypes = Allocator.arrayList(chunks.size());
322 for (final PngChunk chunk : chunks) {
323 chunkTypes.add(getChunkTypeName(chunk.getChunkType()));
324 }
325 return chunkTypes;
326 }
327
328 @Override
329 public String getDefaultExtension() {
330 return DEFAULT_EXTENSION;
331 }
332
333 @Override
334 public PngImagingParameters getDefaultParameters() {
335 return new PngImagingParameters();
336 }
337
338 @Override
339 public byte[] getIccProfileBytes(final ByteSource byteSource, final PngImagingParameters params) throws ImagingException, IOException {
340 final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.iCCP }, true);
341
342 if (chunks.isEmpty()) {
343 return null;
344 }
345
346 if (chunks.size() > 1) {
347 throw new ImagingException("PNG contains more than one ICC Profile ");
348 }
349
350 final PngChunkIccp pngChunkiCCP = (PngChunkIccp) chunks.get(0);
351
352 return pngChunkiCCP.getUncompressedProfile();
353 }
354
355 @Override
356 public ImageInfo getImageInfo(final ByteSource byteSource, final PngImagingParameters params) throws ImagingException, IOException {
357 final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.IHDR, ChunkType.pHYs, ChunkType.sCAL, ChunkType.tEXt, ChunkType.zTXt,
358 ChunkType.tRNS, ChunkType.PLTE, ChunkType.iTXt, }, false);
359
360 if (chunks.isEmpty()) {
361 throw new ImagingException("PNG: no chunks");
362 }
363
364 final List<PngChunk> IHDRs = filterChunks(chunks, ChunkType.IHDR);
365 if (IHDRs.size() != 1) {
366 throw new ImagingException("PNG contains more than one Header");
367 }
368
369 final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) IHDRs.get(0);
370
371 boolean transparent = false;
372
373 final List<PngChunk> tRNSs = filterChunks(chunks, ChunkType.tRNS);
374 if (!tRNSs.isEmpty()) {
375 transparent = true;
376 } else {
377
378 transparent = pngChunkIHDR.getPngColorType().hasAlpha();
379
380 }
381
382 PngChunkPhys pngChunkpHYs = null;
383
384 final List<PngChunk> pHYss = filterChunks(chunks, ChunkType.pHYs);
385 if (pHYss.size() > 1) {
386 throw new ImagingException("PNG contains more than one pHYs: " + pHYss.size());
387 }
388 if (pHYss.size() == 1) {
389 pngChunkpHYs = (PngChunkPhys) pHYss.get(0);
390 }
391
392 PhysicalScale physicalScale = PhysicalScale.UNDEFINED;
393
394 final List<PngChunk> sCALs = filterChunks(chunks, ChunkType.sCAL);
395 if (sCALs.size() > 1) {
396 throw new ImagingException("PNG contains more than one sCAL:" + sCALs.size());
397 }
398 if (sCALs.size() == 1) {
399 final PngChunkScal pngChunkScal = (PngChunkScal) sCALs.get(0);
400 if (pngChunkScal.getUnitSpecifier() == 1) {
401 physicalScale = PhysicalScale.createFromMeters(pngChunkScal.getUnitsPerPixelXAxis(), pngChunkScal.getUnitsPerPixelYAxis());
402 } else {
403 physicalScale = PhysicalScale.createFromRadians(pngChunkScal.getUnitsPerPixelXAxis(), pngChunkScal.getUnitsPerPixelYAxis());
404 }
405 }
406
407 final List<PngChunk> tEXts = filterChunks(chunks, ChunkType.tEXt);
408 final List<PngChunk> zTXts = filterChunks(chunks, ChunkType.zTXt);
409 final List<PngChunk> iTXts = filterChunks(chunks, ChunkType.iTXt);
410
411 final int chunkCount = tEXts.size() + zTXts.size() + iTXts.size();
412 final List<String> comments = Allocator.arrayList(chunkCount);
413 final List<AbstractPngText> textChunks = Allocator.arrayList(chunkCount);
414
415 for (final PngChunk tEXt : tEXts) {
416 final PngChunkText pngChunktEXt = (PngChunkText) tEXt;
417 comments.add(pngChunktEXt.getKeyword() + ": " + pngChunktEXt.getText());
418 textChunks.add(pngChunktEXt.getContents());
419 }
420 for (final PngChunk zTXt : zTXts) {
421 final PngChunkZtxt pngChunkzTXt = (PngChunkZtxt) zTXt;
422 comments.add(pngChunkzTXt.getKeyword() + ": " + pngChunkzTXt.getText());
423 textChunks.add(pngChunkzTXt.getContents());
424 }
425 for (final PngChunk iTXt : iTXts) {
426 final PngChunkItxt pngChunkiTXt = (PngChunkItxt) iTXt;
427 comments.add(pngChunkiTXt.getKeyword() + ": " + pngChunkiTXt.getText());
428 textChunks.add(pngChunkiTXt.getContents());
429 }
430
431 final int bitsPerPixel = pngChunkIHDR.getBitDepth() * pngChunkIHDR.getPngColorType().getSamplesPerPixel();
432 final ImageFormat format = ImageFormats.PNG;
433 final String formatName = "PNG Portable Network Graphics";
434 final int height = pngChunkIHDR.getHeight();
435 final String mimeType = "image/png";
436 final int numberOfImages = 1;
437 final int width = pngChunkIHDR.getWidth();
438 final boolean progressive = pngChunkIHDR.getInterlaceMethod().isProgressive();
439
440 int physicalHeightDpi = -1;
441 float physicalHeightInch = -1;
442 int physicalWidthDpi = -1;
443 float physicalWidthInch = -1;
444
445
446
447
448
449
450
451
452
453
454 if (pngChunkpHYs != null && pngChunkpHYs.getUnitSpecifier() == 1) {
455 final double metersPerInch = 0.0254;
456
457 physicalWidthDpi = (int) Math.round(pngChunkpHYs.getPixelsPerUnitXAxis() * metersPerInch);
458 physicalWidthInch = (float) (width / (pngChunkpHYs.getPixelsPerUnitXAxis() * metersPerInch));
459 physicalHeightDpi = (int) Math.round(pngChunkpHYs.getPixelsPerUnitYAxis() * metersPerInch);
460 physicalHeightInch = (float) (height / (pngChunkpHYs.getPixelsPerUnitYAxis() * metersPerInch));
461 }
462
463 boolean usesPalette = false;
464
465 final List<PngChunk> PLTEs = filterChunks(chunks, ChunkType.PLTE);
466 if (!PLTEs.isEmpty()) {
467 usesPalette = true;
468 }
469
470 ImageInfo.ColorType colorType;
471 switch (pngChunkIHDR.getPngColorType()) {
472 case GREYSCALE:
473 case GREYSCALE_WITH_ALPHA:
474 colorType = ImageInfo.ColorType.GRAYSCALE;
475 break;
476 case TRUE_COLOR:
477 case INDEXED_COLOR:
478 case TRUE_COLOR_WITH_ALPHA:
479 colorType = ImageInfo.ColorType.RGB;
480 break;
481 default:
482 throw new ImagingException("Png: Unknown ColorType: " + pngChunkIHDR.getPngColorType());
483 }
484
485 final String formatDetails = "Png";
486 final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.PNG_FILTER;
487
488 return new PngImageInfo(formatDetails, bitsPerPixel, comments, format, formatName, height, mimeType, numberOfImages, physicalHeightDpi,
489 physicalHeightInch, physicalWidthDpi, physicalWidthInch, width, progressive, transparent, usesPalette, colorType, compressionAlgorithm,
490 textChunks, physicalScale);
491 }
492
493 @Override
494 public Dimension getImageSize(final ByteSource byteSource, final PngImagingParameters params) throws ImagingException, IOException {
495 final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.IHDR, }, true);
496
497 if (chunks.isEmpty()) {
498 throw new ImagingException("Png: No chunks");
499 }
500
501 if (chunks.size() > 1) {
502 throw new ImagingException("PNG contains more than one Header");
503 }
504
505 final PngChunkIhdr pngChunkIHDR = (PngChunkIhdr) chunks.get(0);
506
507 return new Dimension(pngChunkIHDR.getWidth(), pngChunkIHDR.getHeight());
508 }
509
510 @Override
511 public ImageMetadata getMetadata(final ByteSource byteSource, final PngImagingParameters params) throws ImagingException, IOException {
512 final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.tEXt, ChunkType.zTXt, ChunkType.iTXt }, false);
513
514 if (chunks.isEmpty()) {
515 return null;
516 }
517
518 final GenericImageMetadata result = new GenericImageMetadata();
519
520 for (final PngChunk chunk : chunks) {
521 final AbstractPngTextChunk textChunk = (AbstractPngTextChunk) chunk;
522
523 result.add(textChunk.getKeyword(), textChunk.getText());
524 }
525
526 return result;
527 }
528
529 @Override
530 public String getName() {
531 return "Png-Custom";
532 }
533
534 private AbstractTransparencyFilter getTransparencyFilter(final PngColorType pngColorType, final PngChunk pngChunktRNS)
535 throws ImagingException, IOException {
536 switch (pngColorType) {
537 case GREYSCALE:
538 return new TransparencyFilterGrayscale(pngChunktRNS.getBytes());
539 case TRUE_COLOR:
540 return new TransparencyFilterTrueColor(pngChunktRNS.getBytes());
541 case INDEXED_COLOR:
542 return new TransparencyFilterIndexedColor(pngChunktRNS.getBytes());
543 case GREYSCALE_WITH_ALPHA:
544 case TRUE_COLOR_WITH_ALPHA:
545 default:
546 throw new ImagingException("Simple Transparency not compatible with ColorType: " + pngColorType);
547 }
548 }
549
550 @Override
551 public String getXmpXml(final ByteSource byteSource, final XmpImagingParameters<PngImagingParameters> params) throws ImagingException, IOException {
552
553 final List<PngChunk> chunks = readChunks(byteSource, new ChunkType[] { ChunkType.iTXt }, false);
554
555 if (chunks.isEmpty()) {
556 return null;
557 }
558
559 final List<PngChunkItxt> xmpChunks = new ArrayList<>();
560 for (final PngChunk chunk : chunks) {
561 final PngChunkItxt itxtChunk = (PngChunkItxt) chunk;
562 if (!itxtChunk.getKeyword().equals(PngConstants.XMP_KEYWORD)) {
563 continue;
564 }
565 xmpChunks.add(itxtChunk);
566 }
567
568 if (xmpChunks.isEmpty()) {
569 return null;
570 }
571 if (xmpChunks.size() > 1) {
572 throw new ImagingException("PNG contains more than one XMP chunk.");
573 }
574
575 final PngChunkItxt chunk = xmpChunks.get(0);
576 return chunk.getText();
577 }
578
579
580
581
582
583 public boolean hasChunkType(final ByteSource byteSource, final ChunkType chunkType) throws ImagingException, IOException {
584 try (InputStream is = byteSource.getInputStream()) {
585 readSignature(is);
586 final List<PngChunk> chunks = readChunks(is, new ChunkType[] { chunkType }, true);
587 return !chunks.isEmpty();
588 }
589 }
590
591 private boolean keepChunk(final int chunkType, final ChunkType[] chunkTypes) {
592
593 if (chunkTypes == null) {
594 return true;
595 }
596
597 for (final ChunkType chunkType2 : chunkTypes) {
598 if (chunkType2.value == chunkType) {
599 return true;
600 }
601 }
602 return false;
603 }
604
605 private List<PngChunk> readChunks(final ByteSource byteSource, final ChunkType[] chunkTypes, final boolean returnAfterFirst)
606 throws ImagingException, IOException {
607 try (InputStream is = byteSource.getInputStream()) {
608 readSignature(is);
609 return readChunks(is, chunkTypes, returnAfterFirst);
610 }
611 }
612
613 private List<PngChunk> readChunks(final InputStream is, final ChunkType[] chunkTypes, final boolean returnAfterFirst) throws ImagingException, IOException {
614 final List<PngChunk> result = new ArrayList<>();
615
616 while (true) {
617 final int length = BinaryFunctions.read4Bytes("Length", is, "Not a Valid PNG File", getByteOrder());
618 if (length < 0) {
619 throw new ImagingException("Invalid PNG chunk length: " + length);
620 }
621 final int chunkType = BinaryFunctions.read4Bytes("ChunkType", is, "Not a Valid PNG File", getByteOrder());
622
623 if (LOGGER.isLoggable(Level.FINEST)) {
624 BinaryFunctions.logCharQuad("ChunkType", chunkType);
625 debugNumber("Length", length, 4);
626 }
627 final boolean keep = keepChunk(chunkType, chunkTypes);
628
629 byte[] bytes = null;
630 if (keep) {
631 bytes = BinaryFunctions.readBytes("Chunk Data", is, length, "Not a Valid PNG File: Couldn't read Chunk Data.");
632 } else {
633 BinaryFunctions.skipBytes(is, length, "Not a Valid PNG File");
634 }
635
636 if (LOGGER.isLoggable(Level.FINEST)) {
637 if (bytes != null) {
638 debugNumber("bytes", bytes.length, 4);
639 }
640 }
641
642 final int crc = BinaryFunctions.read4Bytes("CRC", is, "Not a Valid PNG File", getByteOrder());
643
644 if (keep) {
645 if (chunkType == ChunkType.iCCP.value) {
646 result.add(new PngChunkIccp(length, chunkType, crc, bytes));
647 } else if (chunkType == ChunkType.tEXt.value) {
648 result.add(new PngChunkText(length, chunkType, crc, bytes));
649 } else if (chunkType == ChunkType.zTXt.value) {
650 result.add(new PngChunkZtxt(length, chunkType, crc, bytes));
651 } else if (chunkType == ChunkType.IHDR.value) {
652 result.add(new PngChunkIhdr(length, chunkType, crc, bytes));
653 } else if (chunkType == ChunkType.PLTE.value) {
654 result.add(new PngChunkPlte(length, chunkType, crc, bytes));
655 } else if (chunkType == ChunkType.pHYs.value) {
656 result.add(new PngChunkPhys(length, chunkType, crc, bytes));
657 } else if (chunkType == ChunkType.sCAL.value) {
658 result.add(new PngChunkScal(length, chunkType, crc, bytes));
659 } else if (chunkType == ChunkType.IDAT.value) {
660 result.add(new PngChunkIdat(length, chunkType, crc, bytes));
661 } else if (chunkType == ChunkType.gAMA.value) {
662 result.add(new PngChunkGama(length, chunkType, crc, bytes));
663 } else if (chunkType == ChunkType.iTXt.value) {
664 result.add(new PngChunkItxt(length, chunkType, crc, bytes));
665 } else {
666 result.add(new PngChunk(length, chunkType, crc, bytes));
667 }
668
669 if (returnAfterFirst) {
670 return result;
671 }
672 }
673
674 if (chunkType == ChunkType.IEND.value) {
675 break;
676 }
677
678 }
679
680 return result;
681
682 }
683
684 public void readSignature(final InputStream is) throws ImagingException, IOException {
685 BinaryFunctions.readAndVerifyBytes(is, PngConstants.PNG_SIGNATURE, "Not a Valid PNG Segment: Incorrect Signature");
686
687 }
688
689 @Override
690 public void writeImage(final BufferedImage src, final OutputStream os, final PngImagingParameters params) throws ImagingException, IOException {
691 new PngWriter().writeImage(src, os, params, null);
692 }
693
694 }