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