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}