View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.psd;
18  
19  import static org.apache.commons.imaging.common.BinaryFunctions.read2Bytes;
20  import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes;
21  import static org.apache.commons.imaging.common.BinaryFunctions.readAndVerifyBytes;
22  import static org.apache.commons.imaging.common.BinaryFunctions.readByte;
23  import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
24  import static org.apache.commons.imaging.common.BinaryFunctions.skipBytes;
25  
26  import java.awt.Dimension;
27  import java.awt.image.BufferedImage;
28  import java.io.ByteArrayInputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.PrintWriter;
32  import java.nio.ByteOrder;
33  import java.nio.charset.StandardCharsets;
34  import java.util.ArrayList;
35  import java.util.List;
36  
37  import org.apache.commons.imaging.ImageFormat;
38  import org.apache.commons.imaging.ImageFormats;
39  import org.apache.commons.imaging.ImageInfo;
40  import org.apache.commons.imaging.ImageParser;
41  import org.apache.commons.imaging.ImageReadException;
42  import org.apache.commons.imaging.common.ImageMetadata;
43  import org.apache.commons.imaging.common.XmpEmbeddable;
44  import org.apache.commons.imaging.common.XmpImagingParameters;
45  import org.apache.commons.imaging.common.bytesource.ByteSource;
46  import org.apache.commons.imaging.formats.psd.dataparsers.DataParser;
47  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserBitmap;
48  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserCmyk;
49  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserGrayscale;
50  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserIndexed;
51  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserLab;
52  import org.apache.commons.imaging.formats.psd.dataparsers.DataParserRgb;
53  import org.apache.commons.imaging.formats.psd.datareaders.CompressedDataReader;
54  import org.apache.commons.imaging.formats.psd.datareaders.DataReader;
55  import org.apache.commons.imaging.formats.psd.datareaders.UncompressedDataReader;
56  
57  public class PsdImageParser extends ImageParser<PsdImagingParameters> implements XmpEmbeddable {
58      private static final String DEFAULT_EXTENSION = ImageFormats.PSD.getDefaultExtension();
59      private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.PSD.getExtensions();
60      private static final int PSD_SECTION_HEADER = 0;
61      private static final int PSD_SECTION_COLOR_MODE = 1;
62      private static final int PSD_SECTION_IMAGE_RESOURCES = 2;
63      private static final int PSD_SECTION_LAYER_AND_MASK_DATA = 3;
64      private static final int PSD_SECTION_IMAGE_DATA = 4;
65      private static final int PSD_HEADER_LENGTH = 26;
66      private static final int COLOR_MODE_INDEXED = 2;
67      public static final int IMAGE_RESOURCE_ID_ICC_PROFILE = 0x040F;
68      public static final int IMAGE_RESOURCE_ID_XMP = 0x0424;
69      public static final String BLOCK_NAME_XMP = "XMP";
70  
71      public PsdImageParser() {
72          super.setByteOrder(ByteOrder.BIG_ENDIAN);
73      }
74  
75      @Override
76      public PsdImagingParameters getDefaultParameters() {
77          return new PsdImagingParameters();
78      }
79  
80      @Override
81      public String getName() {
82          return "PSD-Custom";
83      }
84  
85      @Override
86      public String getDefaultExtension() {
87          return DEFAULT_EXTENSION;
88      }
89  
90      @Override
91      protected String[] getAcceptedExtensions() {
92          return ACCEPTED_EXTENSIONS.clone();
93      }
94  
95      @Override
96      protected ImageFormat[] getAcceptedTypes() {
97          return new ImageFormat[] { ImageFormats.PSD, //
98          };
99      }
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 }