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