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