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