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}