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