001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.formats.psd;
018
019import java.awt.Dimension;
020import java.awt.image.BufferedImage;
021import java.io.ByteArrayInputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.PrintWriter;
025import java.nio.charset.StandardCharsets;
026import java.util.ArrayList;
027import java.util.List;
028
029import org.apache.commons.imaging.AbstractImageParser;
030import org.apache.commons.imaging.ImageFormat;
031import org.apache.commons.imaging.ImageFormats;
032import org.apache.commons.imaging.ImageInfo;
033import org.apache.commons.imaging.ImagingException;
034import org.apache.commons.imaging.bytesource.ByteSource;
035import org.apache.commons.imaging.common.BinaryFunctions;
036import org.apache.commons.imaging.common.ImageMetadata;
037import org.apache.commons.imaging.common.XmpEmbeddable;
038import org.apache.commons.imaging.common.XmpImagingParameters;
039import org.apache.commons.imaging.formats.psd.dataparsers.AbstractDataParser;
040import org.apache.commons.imaging.formats.psd.dataparsers.DataParserBitmap;
041import org.apache.commons.imaging.formats.psd.dataparsers.DataParserCmyk;
042import org.apache.commons.imaging.formats.psd.dataparsers.DataParserGrayscale;
043import org.apache.commons.imaging.formats.psd.dataparsers.DataParserIndexed;
044import org.apache.commons.imaging.formats.psd.dataparsers.DataParserLab;
045import org.apache.commons.imaging.formats.psd.dataparsers.DataParserRgb;
046import org.apache.commons.imaging.formats.psd.datareaders.CompressedDataReader;
047import org.apache.commons.imaging.formats.psd.datareaders.DataReader;
048import org.apache.commons.imaging.formats.psd.datareaders.UncompressedDataReader;
049import org.apache.commons.io.IOUtils;
050import org.apache.commons.lang3.ArrayUtils;
051
052public class PsdImageParser extends AbstractImageParser<PsdImagingParameters> implements XmpEmbeddable {
053
054    private static final String DEFAULT_EXTENSION = ImageFormats.PSD.getDefaultExtension();
055    private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.PSD.getExtensions();
056    private static final int PSD_SECTION_HEADER = 0;
057    private static final int PSD_SECTION_COLOR_MODE = 1;
058    private static final int PSD_SECTION_IMAGE_RESOURCES = 2;
059    private static final int PSD_SECTION_LAYER_AND_MASK_DATA = 3;
060    private static final int PSD_SECTION_IMAGE_DATA = 4;
061    private static final int PSD_HEADER_LENGTH = 26;
062    private static final int COLOR_MODE_INDEXED = 2;
063    public static final int IMAGE_RESOURCE_ID_ICC_PROFILE = 0x040F;
064    public static final int IMAGE_RESOURCE_ID_XMP = 0x0424;
065    public static final String BLOCK_NAME_XMP = "XMP";
066
067    /**
068     * Constructs a new instance with the big-endian byte order.
069     */
070    public PsdImageParser() {
071        // empty
072    }
073
074    @Override
075    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
076        pw.println("gif.dumpImageFile");
077
078        final ImageInfo fImageData = getImageInfo(byteSource);
079        if (fImageData == null) {
080            return false;
081        }
082
083        fImageData.toString(pw, "");
084        final PsdImageContents imageContents = readImageContents(byteSource);
085
086        imageContents.dump(pw);
087        imageContents.header.dump(pw);
088
089        final List<ImageResourceBlock> blocks = readImageResourceBlocks(byteSource,
090                // fImageContents.ImageResources,
091                null, -1);
092
093        pw.println("blocks.size(): " + blocks.size());
094
095        // System.out.println("gif.blocks: " + blocks.blocks.size());
096        for (int i = 0; i < blocks.size(); i++) {
097            final ImageResourceBlock block = blocks.get(i);
098            pw.println("\t" + i + " (" + Integer.toHexString(block.id) + ", " + "'" + new String(block.nameData, StandardCharsets.ISO_8859_1) + "' ("
099                    + 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}