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