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}