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