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 static org.apache.commons.imaging.common.BinaryFunctions.read2Bytes;
020import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes;
021import static org.apache.commons.imaging.common.BinaryFunctions.readAndVerifyBytes;
022import static org.apache.commons.imaging.common.BinaryFunctions.readByte;
023import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
024import static org.apache.commons.imaging.common.BinaryFunctions.skipBytes;
025
026import java.awt.Dimension;
027import java.awt.image.BufferedImage;
028import java.io.ByteArrayInputStream;
029import java.io.IOException;
030import java.io.InputStream;
031import java.io.PrintWriter;
032import java.nio.charset.StandardCharsets;
033import java.util.ArrayList;
034import java.util.List;
035
036import org.apache.commons.imaging.AbstractImageParser;
037import org.apache.commons.imaging.ImageFormat;
038import org.apache.commons.imaging.ImageFormats;
039import org.apache.commons.imaging.ImageInfo;
040import org.apache.commons.imaging.ImagingException;
041import org.apache.commons.imaging.bytesource.ByteSource;
042import org.apache.commons.imaging.common.ImageMetadata;
043import org.apache.commons.imaging.common.XmpEmbeddable;
044import org.apache.commons.imaging.common.XmpImagingParameters;
045import org.apache.commons.imaging.formats.psd.dataparsers.DataParser;
046import org.apache.commons.imaging.formats.psd.dataparsers.DataParserBitmap;
047import org.apache.commons.imaging.formats.psd.dataparsers.DataParserCmyk;
048import org.apache.commons.imaging.formats.psd.dataparsers.DataParserGrayscale;
049import org.apache.commons.imaging.formats.psd.dataparsers.DataParserIndexed;
050import org.apache.commons.imaging.formats.psd.dataparsers.DataParserLab;
051import org.apache.commons.imaging.formats.psd.dataparsers.DataParserRgb;
052import org.apache.commons.imaging.formats.psd.datareaders.CompressedDataReader;
053import org.apache.commons.imaging.formats.psd.datareaders.DataReader;
054import org.apache.commons.imaging.formats.psd.datareaders.UncompressedDataReader;
055
056public class PsdImageParser extends AbstractImageParser<PsdImagingParameters> implements XmpEmbeddable {
057
058    private static final String DEFAULT_EXTENSION = ImageFormats.PSD.getDefaultExtension();
059    private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.PSD.getExtensions();
060    private static final int PSD_SECTION_HEADER = 0;
061    private static final int PSD_SECTION_COLOR_MODE = 1;
062    private static final int PSD_SECTION_IMAGE_RESOURCES = 2;
063    private static final int PSD_SECTION_LAYER_AND_MASK_DATA = 3;
064    private static final int PSD_SECTION_IMAGE_DATA = 4;
065    private static final int PSD_HEADER_LENGTH = 26;
066    private static final int COLOR_MODE_INDEXED = 2;
067    public static final int IMAGE_RESOURCE_ID_ICC_PROFILE = 0x040F;
068    public static final int IMAGE_RESOURCE_ID_XMP = 0x0424;
069    public static final String BLOCK_NAME_XMP = "XMP";
070
071    @Override
072    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
073        pw.println("gif.dumpImageFile");
074
075        final ImageInfo fImageData = getImageInfo(byteSource);
076        if (fImageData == null) {
077            return false;
078        }
079
080        fImageData.toString(pw, "");
081        final PsdImageContents imageContents = readImageContents(byteSource);
082
083        imageContents.dump(pw);
084        imageContents.header.dump(pw);
085
086        final List<ImageResourceBlock> blocks = readImageResourceBlocks(byteSource,
087                // fImageContents.ImageResources,
088                null, -1);
089
090        pw.println("blocks.size(): " + blocks.size());
091
092        // System.out.println("gif.blocks: " + blocks.blocks.size());
093        for (int i = 0; i < blocks.size(); i++) {
094            final ImageResourceBlock block = blocks.get(i);
095            pw.println("\t" + i + " (" + Integer.toHexString(block.id) + ", " + "'" + new String(block.nameData, StandardCharsets.ISO_8859_1) + "' ("
096                    + block.nameData.length + "), "
097                    // + block.getClass().getName()
098                    // + ", "
099                    + " 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}