    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}