View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.psd;
18  
19  import java.awt.Dimension;
20  import java.awt.image.BufferedImage;
21  import java.io.ByteArrayInputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.PrintWriter;
25  import java.nio.charset.StandardCharsets;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.commons.imaging.AbstractImageParser;
30  import org.apache.commons.imaging.ImageFormat;
31  import org.apache.commons.imaging.ImageFormats;
32  import org.apache.commons.imaging.ImageInfo;
33  import org.apache.commons.imaging.ImagingException;
34  import org.apache.commons.imaging.bytesource.ByteSource;
35  import org.apache.commons.imaging.common.BinaryFunctions;
36  import org.apache.commons.imaging.common.ImageMetadata;
37  import org.apache.commons.imaging.common.XmpEmbeddable;
38  import org.apache.commons.imaging.common.XmpImagingParameters;
39  import org.apache.commons.imaging.formats.psd.dataparsers.AbstractDataParser;
40  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserBitmap;
41  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserCmyk;
42  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserGrayscale;
43  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserIndexed;
44  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserLab;
45  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserRgb;
46  import org.apache.commons.imaging.formats.psd.datareaders.CompressedDataReader;
47  import org.apache.commons.imaging.formats.psd.datareaders.DataReader;
48  import org.apache.commons.imaging.formats.psd.datareaders.UncompressedDataReader;
49  import org.apache.commons.io.IOUtils;
50  import org.apache.commons.lang3.ArrayUtils;
51  
52  public class PsdImageParser extends AbstractImageParser<PsdImagingParameters> implements XmpEmbeddable {
53  
54      private static final String DEFAULT_EXTENSION = ImageFormats.PSD.getDefaultExtension();
55      private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.PSD.getExtensions();
56      private static final int PSD_SECTION_HEADER = 0;
57      private static final int PSD_SECTION_COLOR_MODE = 1;
58      private static final int PSD_SECTION_IMAGE_RESOURCES = 2;
59      private static final int PSD_SECTION_LAYER_AND_MASK_DATA = 3;
60      private static final int PSD_SECTION_IMAGE_DATA = 4;
61      private static final int PSD_HEADER_LENGTH = 26;
62      private static final int COLOR_MODE_INDEXED = 2;
63      public static final int IMAGE_RESOURCE_ID_ICC_PROFILE = 0x040F;
64      public static final int IMAGE_RESOURCE_ID_XMP = 0x0424;
65      public static final String BLOCK_NAME_XMP = "XMP";
66  
67      /**
68       * Constructs a new instance with the big-endian byte order.
69       */
70      public PsdImageParser() {
71          // empty
72      }
73  
74      @Override
75      public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
76          pw.println("gif.dumpImageFile");
77  
78          final ImageInfo fImageData = getImageInfo(byteSource);
79          if (fImageData == null) {
80              return false;
81          }
82  
83          fImageData.toString(pw, "");
84          final PsdImageContents imageContents = readImageContents(byteSource);
85  
86          imageContents.dump(pw);
87          imageContents.header.dump(pw);
88  
89          final List<ImageResourceBlock> blocks = readImageResourceBlocks(byteSource,
90                  // fImageContents.ImageResources,
91                  null, -1);
92  
93          pw.println("blocks.size(): " + blocks.size());
94  
95          // System.out.println("gif.blocks: " + blocks.blocks.size());
96          for (int i = 0; i < blocks.size(); i++) {
97              final ImageResourceBlock block = blocks.get(i);
98              pw.println("\t" + i + " (" + Integer.toHexString(block.id) + ", " + "'" + new String(block.nameData, StandardCharsets.ISO_8859_1) + "' ("
99                      + block.nameData.length + "), "
100                     // + block.getClass().getName()
101                     // + ", "
102                     + " data: " + block.data.length + " type: '" + ImageResourceType.getDescription(block.id) + "' " + ")");
103         }
104 
105         pw.println("");
106 
107         return true;
108     }
109 
110     @Override
111     protected String[] getAcceptedExtensions() {
112         return ACCEPTED_EXTENSIONS.clone();
113     }
114 
115     @Override
116     protected ImageFormat[] getAcceptedTypes() {
117         return new ImageFormat[] { ImageFormats.PSD, //
118         };
119     }
120 
121     @Override
122     public BufferedImage getBufferedImage(final ByteSource byteSource, final PsdImagingParameters params) throws ImagingException, IOException {
123         final PsdImageContents imageContents = readImageContents(byteSource);
124         // ImageContents imageContents = readImage(byteSource, false);
125 
126         final PsdHeaderInfo header = imageContents.header;
127         if (header == null) {
128             throw new ImagingException("PSD: Couldn't read Header");
129         }
130 
131         // ImageDescriptor id = (ImageDescriptor)
132         // findBlock(fImageContents.blocks,
133         // kImageSeperator);
134         // if (id == null)
135         // throw new ImageReadException("PSD: Couldn't read Image Descriptor");
136         // GraphicControlExtension gce = (GraphicControlExtension) findBlock(
137         // fImageContents.blocks, kGraphicControlExtension);
138 
139         readImageResourceBlocks(byteSource,
140                 // fImageContents.ImageResources,
141                 null, -1);
142 
143         final int width = header.columns;
144         final int height = header.rows;
145         // int height = header.Columns;
146 
147         // int transfer_type;
148 
149         // transfer_type = DataBuffer.TYPE_BYTE;
150 
151         final boolean hasAlpha = false;
152         final BufferedImage result = getBufferedImageFactory(params).getColorBufferedImage(width, height, hasAlpha);
153 
154         final AbstractDataParser dataParser;
155         switch (imageContents.header.mode) {
156         case 0: // bitmap
157             dataParser = new DataParserBitmap();
158             break;
159         case 1:
160         case 8: // Duotone=8;
161             dataParser = new DataParserGrayscale();
162             break;
163         case 3:
164             dataParser = new DataParserRgb();
165             break;
166         case 4:
167             dataParser = new DataParserCmyk();
168             break;
169         case 9:
170             dataParser = new DataParserLab();
171             break;
172         case COLOR_MODE_INDEXED: {
173             // case 2 : // Indexed=2;
174             final byte[] ColorModeData = getData(byteSource, PSD_SECTION_COLOR_MODE);
175 
176             // ImageResourceBlock block = findImageResourceBlock(blocks,
177             // 0x03EB);
178             // if (block == null)
179             // throw new ImageReadException(
180             // "Missing: Indexed Color Image Resource Block");
181 
182             dataParser = new DataParserIndexed(ColorModeData);
183             break;
184         }
185         case 7: // Multichannel=7;
186             // fDataParser = new DataParserStub();
187             // break;
188 
189             // case 1 :
190             // fDataReader = new CompressedDataReader();
191             // break;
192         default:
193             throw new ImagingException("Unknown Mode: " + imageContents.header.mode);
194         }
195         final DataReader fDataReader;
196         switch (imageContents.compression) {
197         case 0:
198             fDataReader = new UncompressedDataReader(dataParser);
199             break;
200         case 1:
201             fDataReader = new CompressedDataReader(dataParser);
202             break;
203         default:
204             throw new ImagingException("Unknown Compression: " + imageContents.compression);
205         }
206 
207         try (InputStream is = getInputStream(byteSource, PSD_SECTION_IMAGE_DATA)) {
208             fDataReader.readData(is, result, imageContents, this);
209 
210             // is.
211             // ImageContents imageContents = readImageContents(is);
212             // return imageContents;
213         }
214 
215         return result;
216 
217     }
218 
219     private int getChannelsPerMode(final int mode) {
220         switch (mode) {
221         case 0: // Bitmap
222             return 1;
223         case 1: // Grayscale
224             return 1;
225         case 2: // Indexed
226             return -1;
227         case 3: // RGB
228             return 3;
229         case 4: // CMYK
230             return 4;
231         case 7: // Multichannel
232             return -1;
233         case 8: // Duotone
234             return -1;
235         case 9: // Lab
236             return 4;
237         default:
238             return -1;
239 
240         }
241     }
242 
243     private byte[] getData(final ByteSource byteSource, final int section) throws ImagingException, IOException {
244         try (InputStream is = byteSource.getInputStream()) {
245             // PsdHeaderInfo header = readHeader(is);
246             if (section == PSD_SECTION_HEADER) {
247                 return BinaryFunctions.readBytes("Header", is, PSD_HEADER_LENGTH, "Not a Valid PSD File");
248             }
249             BinaryFunctions.skipBytes(is, PSD_HEADER_LENGTH);
250 
251             final int colorModeDataLength = BinaryFunctions.read4Bytes("ColorModeDataLength", is, "Not a Valid PSD File", getByteOrder());
252 
253             if (section == PSD_SECTION_COLOR_MODE) {
254                 return BinaryFunctions.readBytes("ColorModeData", is, colorModeDataLength, "Not a Valid PSD File");
255             }
256 
257             BinaryFunctions.skipBytes(is, colorModeDataLength);
258             // byte[] ColorModeData = readByteArray("ColorModeData",
259             // ColorModeDataLength, is, "Not a Valid PSD File");
260 
261             final int imageResourcesLength = BinaryFunctions.read4Bytes("ImageResourcesLength", is, "Not a Valid PSD File", getByteOrder());
262 
263             if (section == PSD_SECTION_IMAGE_RESOURCES) {
264                 return BinaryFunctions.readBytes("ImageResources", is, imageResourcesLength, "Not a Valid PSD File");
265             }
266 
267             BinaryFunctions.skipBytes(is, imageResourcesLength);
268             // byte[] ImageResources = readByteArray("ImageResources",
269             // ImageResourcesLength, is, "Not a Valid PSD File");
270 
271             final int layerAndMaskDataLength = BinaryFunctions.read4Bytes("LayerAndMaskDataLength", is, "Not a Valid PSD File", getByteOrder());
272 
273             if (section == PSD_SECTION_LAYER_AND_MASK_DATA) {
274                 return BinaryFunctions.readBytes("LayerAndMaskData", is, layerAndMaskDataLength, "Not a Valid PSD File");
275             }
276 
277             BinaryFunctions.skipBytes(is, layerAndMaskDataLength);
278             // byte[] LayerAndMaskData = readByteArray("LayerAndMaskData",
279             // LayerAndMaskDataLength, is, "Not a Valid PSD File");
280 
281             BinaryFunctions.read2Bytes("Compression", is, "Not a Valid PSD File", getByteOrder());
282 
283             // byte[] ImageData = readByteArray("ImageData",
284             // LayerAndMaskDataLength, is, "Not a Valid PSD File");
285 
286             // if (section == kPSD_SECTION_IMAGE_DATA)
287             // return readByteArray("LayerAndMaskData", LayerAndMaskDataLength,
288             // is,
289             // "Not a Valid PSD File");
290         }
291         throw new ImagingException("getInputStream: Unknown Section: " + section);
292     }
293 
294     @Override
295     public String getDefaultExtension() {
296         return DEFAULT_EXTENSION;
297     }
298 
299     @Override
300     public PsdImagingParameters getDefaultParameters() {
301         return new PsdImagingParameters();
302     }
303 
304     @Override
305     public byte[] getIccProfileBytes(final ByteSource byteSource, final PsdImagingParameters params) throws ImagingException, IOException {
306         final List<ImageResourceBlock> blocks = readImageResourceBlocks(byteSource, new int[] { IMAGE_RESOURCE_ID_ICC_PROFILE, }, 1);
307 
308         if (blocks.isEmpty()) {
309             return null;
310         }
311 
312         final ImageResourceBlock irb = blocks.get(0);
313         final byte[] bytes = irb.data;
314         if (bytes == null || bytes.length < 1) {
315             return null;
316         }
317         return bytes.clone();
318     }
319 
320     @Override
321     public ImageInfo getImageInfo(final ByteSource byteSource, final PsdImagingParameters params) throws ImagingException, IOException {
322         final PsdImageContents imageContents = readImageContents(byteSource);
323         // ImageContents imageContents = readImage(byteSource, false);
324 
325         final PsdHeaderInfo header = imageContents.header;
326         if (header == null) {
327             throw new ImagingException("PSD: Couldn't read Header");
328         }
329 
330         final int width = header.columns;
331         final int height = header.rows;
332 
333         final List<String> comments = new ArrayList<>();
334         // TODO: comments...
335 
336         int bitsPerPixel = header.depth * getChannelsPerMode(header.mode);
337         // System.out.println("header.Depth: " + header.Depth);
338         // System.out.println("header.Mode: " + header.Mode);
339         // System.out.println("getChannelsPerMode(header.Mode): " +
340         // getChannelsPerMode(header.Mode));
341         if (bitsPerPixel < 0) {
342             bitsPerPixel = 0;
343         }
344         final ImageFormat format = ImageFormats.PSD;
345         final String formatName = "Photoshop";
346         final String mimeType = "image/x-photoshop";
347         // we ought to count images, but don't yet.
348         final int numberOfImages = -1;
349         // not accurate ... only reflects first
350         final boolean progressive = false;
351 
352         final int physicalWidthDpi = 72;
353         final float physicalWidthInch = (float) ((double) width / (double) physicalWidthDpi);
354         final int physicalHeightDpi = 72;
355         final float physicalHeightInch = (float) ((double) height / (double) physicalHeightDpi);
356 
357         final String formatDetails = "Psd";
358 
359         final boolean transparent = false; // TODO: inaccurate.
360         final boolean usesPalette = header.mode == COLOR_MODE_INDEXED;
361         final ImageInfo.ColorType colorType = ImageInfo.ColorType.UNKNOWN;
362 
363         final ImageInfo.CompressionAlgorithm compressionAlgorithm;
364         switch (imageContents.compression) {
365         case 0:
366             compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
367             break;
368         case 1:
369             compressionAlgorithm = ImageInfo.CompressionAlgorithm.PSD;
370             break;
371         default:
372             compressionAlgorithm = ImageInfo.CompressionAlgorithm.UNKNOWN;
373         }
374 
375         return new ImageInfo(formatDetails, bitsPerPixel, comments, format, formatName, height, mimeType, numberOfImages, physicalHeightDpi, physicalHeightInch,
376                 physicalWidthDpi, physicalWidthInch, width, progressive, transparent, usesPalette, colorType, compressionAlgorithm);
377     }
378 
379     @Override
380     public Dimension getImageSize(final ByteSource byteSource, final PsdImagingParameters params) throws ImagingException, IOException {
381         final PsdHeaderInfo bhi = readHeader(byteSource);
382 
383         return new Dimension(bhi.columns, bhi.rows);
384 
385     }
386 
387     private InputStream getInputStream(final ByteSource byteSource, final int section) throws ImagingException, IOException {
388         InputStream is = null;
389         boolean notFound = false;
390         try {
391             is = byteSource.getInputStream();
392 
393             if (section == PSD_SECTION_HEADER) {
394                 return is;
395             }
396 
397             BinaryFunctions.skipBytes(is, PSD_HEADER_LENGTH);
398             // is.skip(kHeaderLength);
399 
400             final int colorModeDataLength = BinaryFunctions.read4Bytes("ColorModeDataLength", is, "Not a Valid PSD File", getByteOrder());
401 
402             if (section == PSD_SECTION_COLOR_MODE) {
403                 return is;
404             }
405 
406             BinaryFunctions.skipBytes(is, colorModeDataLength);
407             // byte[] ColorModeData = readByteArray("ColorModeData",
408             // ColorModeDataLength, is, "Not a Valid PSD File");
409 
410             final int imageResourcesLength = BinaryFunctions.read4Bytes("ImageResourcesLength", is, "Not a Valid PSD File", getByteOrder());
411 
412             if (section == PSD_SECTION_IMAGE_RESOURCES) {
413                 return is;
414             }
415 
416             BinaryFunctions.skipBytes(is, imageResourcesLength);
417             // byte[] ImageResources = readByteArray("ImageResources",
418             // ImageResourcesLength, is, "Not a Valid PSD File");
419 
420             final int layerAndMaskDataLength = BinaryFunctions.read4Bytes("LayerAndMaskDataLength", is, "Not a Valid PSD File", getByteOrder());
421 
422             if (section == PSD_SECTION_LAYER_AND_MASK_DATA) {
423                 return is;
424             }
425 
426             BinaryFunctions.skipBytes(is, layerAndMaskDataLength);
427             // byte[] LayerAndMaskData = readByteArray("LayerAndMaskData",
428             // LayerAndMaskDataLength, is, "Not a Valid PSD File");
429 
430             BinaryFunctions.read2Bytes("Compression", is, "Not a Valid PSD File", getByteOrder());
431 
432             // byte[] ImageData = readByteArray("ImageData",
433             // LayerAndMaskDataLength, is, "Not a Valid PSD File");
434 
435             if (section == PSD_SECTION_IMAGE_DATA) {
436                 return is;
437             }
438             notFound = true;
439         } finally {
440             if (notFound) {
441                 IOUtils.close(is);
442             }
443         }
444         throw new ImagingException("getInputStream: Unknown Section: " + section);
445     }
446 
447     @Override
448     public ImageMetadata getMetadata(final ByteSource byteSource, final PsdImagingParameters params) throws ImagingException, IOException {
449         return null;
450     }
451 
452     @Override
453     public String getName() {
454         return "PSD-Custom";
455     }
456 
457     /**
458      * Extracts embedded XML metadata as XML string.
459      *
460      * @param byteSource File containing image data.
461      * @param params     Map of optional parameters, defined in ImagingConstants.
462      * @return Xmp Xml as String, if present. Otherwise, returns null.
463      */
464     @Override
465     public String getXmpXml(final ByteSource byteSource, final XmpImagingParameters params) throws ImagingException, IOException {
466 
467         final PsdImageContents imageContents = readImageContents(byteSource);
468 
469         final PsdHeaderInfo header = imageContents.header;
470         if (header == null) {
471             throw new ImagingException("PSD: Couldn't read Header");
472         }
473 
474         final List<ImageResourceBlock> blocks = readImageResourceBlocks(byteSource, new int[] { IMAGE_RESOURCE_ID_XMP, }, -1);
475 
476         if (blocks.isEmpty()) {
477             return null;
478         }
479 
480         final List<ImageResourceBlock> xmpBlocks = new ArrayList<>(blocks);
481         if (xmpBlocks.isEmpty()) {
482             return null;
483         }
484         if (xmpBlocks.size() > 1) {
485             throw new ImagingException("PSD contains more than one XMP block.");
486         }
487 
488         final ImageResourceBlock block = xmpBlocks.get(0);
489 
490         // segment data is UTF-8 encoded xml.
491         return new String(block.data, 0, block.data.length, StandardCharsets.UTF_8);
492     }
493 
494     private boolean keepImageResourceBlock(final int id, final int[] imageResourceIDs) {
495         return ArrayUtils.contains(imageResourceIDs, id);
496     }
497 
498     private PsdHeaderInfo readHeader(final ByteSource byteSource) throws ImagingException, IOException {
499         try (InputStream is = byteSource.getInputStream()) {
500             return readHeader(is);
501         }
502     }
503 
504     private PsdHeaderInfo readHeader(final InputStream is) throws ImagingException, IOException {
505         BinaryFunctions.readAndVerifyBytes(is, new byte[] { 56, 66, 80, 83 }, "Not a Valid PSD File");
506 
507         final int version = BinaryFunctions.read2Bytes("Version", is, "Not a Valid PSD File", getByteOrder());
508         final byte[] reserved = BinaryFunctions.readBytes("Reserved", is, 6, "Not a Valid PSD File");
509         final int channels = BinaryFunctions.read2Bytes("Channels", is, "Not a Valid PSD File", getByteOrder());
510         final int rows = BinaryFunctions.read4Bytes("Rows", is, "Not a Valid PSD File", getByteOrder());
511         final int columns = BinaryFunctions.read4Bytes("Columns", is, "Not a Valid PSD File", getByteOrder());
512         final int depth = BinaryFunctions.read2Bytes("Depth", is, "Not a Valid PSD File", getByteOrder());
513         final int mode = BinaryFunctions.read2Bytes("Mode", is, "Not a Valid PSD File", getByteOrder());
514 
515         return new PsdHeaderInfo(version, reserved, channels, rows, columns, depth, mode);
516     }
517 
518     private PsdImageContents readImageContents(final ByteSource byteSource) throws ImagingException, IOException {
519         try (InputStream is = byteSource.getInputStream()) {
520             return readImageContents(is);
521         }
522     }
523 
524     private PsdImageContents readImageContents(final InputStream is) throws ImagingException, IOException {
525         final PsdHeaderInfo header = readHeader(is);
526 
527         final int colorModeDataLength = BinaryFunctions.read4Bytes("ColorModeDataLength", is, "Not a Valid PSD File", getByteOrder());
528         BinaryFunctions.skipBytes(is, colorModeDataLength);
529         // is.skip(ColorModeDataLength);
530         // byte[] ColorModeData = readByteArray("ColorModeData",
531         // ColorModeDataLength, is, "Not a Valid PSD File");
532 
533         final int imageResourcesLength = BinaryFunctions.read4Bytes("ImageResourcesLength", is, "Not a Valid PSD File", getByteOrder());
534         BinaryFunctions.skipBytes(is, imageResourcesLength);
535         // long skipped = is.skip(ImageResourcesLength);
536         // byte[] ImageResources = readByteArray("ImageResources",
537         // ImageResourcesLength, is, "Not a Valid PSD File");
538 
539         final int layerAndMaskDataLength = BinaryFunctions.read4Bytes("LayerAndMaskDataLength", is, "Not a Valid PSD File", getByteOrder());
540         BinaryFunctions.skipBytes(is, layerAndMaskDataLength);
541         // is.skip(LayerAndMaskDataLength);
542         // byte[] LayerAndMaskData = readByteArray("LayerAndMaskData",
543         // LayerAndMaskDataLength, is, "Not a Valid PSD File");
544 
545         final int compression = BinaryFunctions.read2Bytes("Compression", is, "Not a Valid PSD File", getByteOrder());
546 
547         // skip_bytes(is, LayerAndMaskDataLength);
548         // byte[] ImageData = readByteArray("ImageData", LayerAndMaskDataLength,
549         // is, "Not a Valid PSD File");
550 
551         // System.out.println("Compression: " + Compression);
552 
553         return new PsdImageContents(header, colorModeDataLength,
554                 // ColorModeData,
555                 imageResourcesLength,
556                 // ImageResources,
557                 layerAndMaskDataLength,
558                 // LayerAndMaskData,
559                 compression);
560     }
561 
562     private List<ImageResourceBlock> readImageResourceBlocks(final byte[] bytes, final int[] imageResourceIDs, final int maxBlocksToRead)
563             throws ImagingException, IOException {
564         return readImageResourceBlocks(new ByteArrayInputStream(bytes), imageResourceIDs, maxBlocksToRead, bytes.length);
565     }
566 
567     private List<ImageResourceBlock> readImageResourceBlocks(final ByteSource byteSource, final int[] imageResourceIDs, final int maxBlocksToRead)
568             throws ImagingException, IOException {
569         try (InputStream imageStream = byteSource.getInputStream();
570                 InputStream resourceStream = getInputStream(byteSource, PSD_SECTION_IMAGE_RESOURCES)) {
571             final PsdImageContents imageContents = readImageContents(imageStream);
572             final byte[] ImageResources = BinaryFunctions.readBytes("ImageResources", resourceStream, imageContents.imageResourcesLength,
573                     "Not a Valid PSD File");
574             return readImageResourceBlocks(ImageResources, imageResourceIDs, maxBlocksToRead);
575         }
576     }
577 
578     private List<ImageResourceBlock> readImageResourceBlocks(final InputStream is, final int[] imageResourceIDs, final int maxBlocksToRead, int available)
579             throws ImagingException, IOException {
580         final List<ImageResourceBlock> result = new ArrayList<>();
581 
582         while (available > 0) {
583             BinaryFunctions.readAndVerifyBytes(is, new byte[] { 56, 66, 73, 77 }, "Not a Valid PSD File");
584             available -= 4;
585 
586             final int id = BinaryFunctions.read2Bytes("ID", is, "Not a Valid PSD File", getByteOrder());
587             available -= 2;
588 
589             final int nameLength = BinaryFunctions.readByte("NameLength", is, "Not a Valid PSD File");
590 
591             available -= 1;
592             final byte[] nameBytes = BinaryFunctions.readBytes("NameData", is, nameLength, "Not a Valid PSD File");
593             available -= nameLength;
594             if ((nameLength + 1) % 2 != 0) {
595                 // final int NameDiscard =
596                 BinaryFunctions.readByte("NameDiscard", is, "Not a Valid PSD File");
597                 available -= 1;
598             }
599             // String Name = readPString("Name", 6, is, "Not a Valid PSD File");
600             final int dataSize = BinaryFunctions.read4Bytes("Size", is, "Not a Valid PSD File", getByteOrder());
601             available -= 4;
602             // int ActualDataSize = ((DataSize % 2) == 0)
603             // ? DataSize
604             // : DataSize + 1; // pad to make even
605 
606             final byte[] data = BinaryFunctions.readBytes("Data", is, dataSize, "Not a Valid PSD File");
607             available -= dataSize;
608 
609             if (dataSize % 2 != 0) {
610                 // final int DataDiscard =
611                 BinaryFunctions.readByte("DataDiscard", is, "Not a Valid PSD File");
612                 available -= 1;
613             }
614 
615             if (keepImageResourceBlock(id, imageResourceIDs)) {
616                 result.add(new ImageResourceBlock(id, nameBytes, data));
617 
618                 if (maxBlocksToRead >= 0 && result.size() >= maxBlocksToRead) {
619                     return result;
620                 }
621             }
622             // debugNumber("ID", ID, 2);
623 
624         }
625 
626         return result;
627     }
628 
629 }