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