From dc6b8d3035a508c15d6f802ce43d8215fd0bd5d3 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 23 Nov 2011 13:58:26 +0100 Subject: [PATCH] Code clean-up. No functional changes. --- .../imageio/plugins/icns/ICNS.java | 61 +-- .../imageio/plugins/icns/ICNSImageReader.java | 354 +----------------- .../imageio/plugins/icns/ICNSUtil.java | 49 +++ .../imageio/plugins/icns/IconResource.java | 320 ++++++++++++++++ 4 files changed, 415 insertions(+), 369 deletions(-) create mode 100644 imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java index 2ec75bcc..723d7a51 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java @@ -36,75 +36,78 @@ package com.twelvemonkeys.imageio.plugins.icns; * @version $Id: ICNS.java,v 1.0 25.10.11 19:10 haraldk Exp$ */ interface ICNS { - /** "icns" magic identifier */ + /** Resource header size (8). */ + int RESOURCE_HEADER_SIZE = 8; + + /** ICNS magic identifier ("icns"). */ int MAGIC = ('i' << 24) + ('c' << 16) + ('n' << 8) + 's'; - /** 32×32 1-bit mono icon */ + /** 32×32 1-bit mono icon. */ int ICON = ('I' << 24) + ('C' << 16) + ('O' << 8) + 'N'; - /** 32×32 1-bit mono icon with 1-bit mask*/ + /** 32×32 1-bit mono icon with 1-bit mask. */ int ICN_ = ('I' << 24) + ('C' << 16) + ('N' << 8) + '#'; - /** 16×12 1 bit mask*/ + /** 16×12 1 bit mask. */ int icm_ = ('i' << 24) + ('c' << 16) + ('m' << 8) + '#'; - /** 16×12 4 bit icon */ + /** 16×12 4 bit icon. */ int icm4 = ('i' << 24) + ('c' << 16) + ('m' << 8) + '4'; - /** 16×12 8 bit icon */ + /** 16×12 8 bit icon. */ int icm8 = ('i' << 24) + ('c' << 16) + ('m' << 8) + '8'; - /** 16×16 1-bit icon with 1-bit mask */ + /** 16×16 1-bit icon with 1-bit mask. */ int ics_ = ('i' << 24) + ('c' << 16) + ('s' << 8) + '#'; - /** 16×16 4-bit icon */ + /** 16×16 4-bit icon. */ int ics4 = ('i' << 24) + ('c' << 16) + ('s' << 8) + '4'; - /** 16×16 8-bit icon */ + /** 16×16 8-bit icon. */ int ics8 = ('i' << 24) + ('c' << 16) + ('s' << 8) + '8'; - /** 16×16 24-bit icon, run-length compressed */ + /** 16×16 24-bit icon, possibly run-length compressed. */ int is32 = ('i' << 24) + ('s' << 16) + ('3' << 8) + '2'; - /** 16x16 8-bit mask */ + /** 16x16 8-bit mask. */ int s8mk = ('s' << 24) + ('8' << 16) + ('m' << 8) + 'k'; - /** 32×32 4-bit icon */ + /** 32×32 4-bit icon. */ int icl4 = ('i' << 24) + ('c' << 16) + ('l' << 8) + '4'; - /** 32×32 8-bit icon */ + /** 32×32 8-bit icon. */ int icl8 = ('i' << 24) + ('c' << 16) + ('l' << 8) + '8'; - /** 32×32 24-bit icon, run-length compressed */ + /** 32×32 24-bit icon, possibly run-length compressed. */ int il32 = ('i' << 24) + ('l' << 16) + ('3' << 8) + '2'; - /** 32×32 8-bit mask */ + /** 32×32 8-bit mask. */ int l8mk = ('l' << 24) + ('8' << 16) + ('m' << 8) + 'k'; - /** 48×48 1-bit icon with 1 bit mask */ + /** 48×48 1-bit icon with 1 bit mask. */ int ich_ = ('i' << 24) + ('c' << 16) + ('h' << 8) + '#'; - /** 48×48 4-bit icon */ + /** 48×48 4-bit icon. */ int ich4 = ('i' << 24) + ('c' << 16) + ('h' << 8) + '4'; - /** 48×48 8-bit icon */ + /** 48×48 8-bit icon. */ int ich8 = ('i' << 24) + ('c' << 16) + ('h' << 8) + '8'; - /** 48×48 24-bit icon, run-length compressed */ + /** 48×48 24-bit icon, possibly run-length compressed. */ int ih32 = ('i' << 24) + ('h' << 16) + ('3' << 8) + '2'; - /** 48×48 8-bit mask */ + /** 48×48 8-bit mask. */ int h8mk = ('h' << 24) + ('8' << 16) + ('m' << 8) + 'k'; - /** 128×128 24-bit icon, run-length compressed */ + /** 128×128 24-bit icon, possibly run-length compressed. */ int it32 = ('i' << 24) + ('t' << 16) + ('3' << 8) + '2'; - /** 128×128 8-bit mask */ + /** 128×128 8-bit mask. */ int t8mk = ('t' << 24) + ('8' << 16) + ('m' << 8) + 'k'; - /** 256×256 JPEG 2000 or PNG icon (10.x+) */ + /** 256×256 JPEG 2000 or PNG icon (10.x+). */ int ic08 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '8'; - /** 512×512 JPEG 2000 or PNG icon (10.x+) */ + /** 512×512 JPEG 2000 or PNG icon (10.x+). */ int ic09 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '9'; - /** 1024×1024 PNG icon (10.7+)*/ + /** 1024×1024 PNG icon (10.7+). */ int ic10 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '0'; - /** Unknown (Version) */ + /** Unknown (Version). */ int icnV = ('i' << 24) + ('c' << 16) + ('n' << 8) + 'V'; - /** Unknown (Table of Contents) */ + /** Unknown (Table of Contents). */ int TOC_ = ('T' << 24) + ('O' << 16) + ('C' << 8) + ' '; - /** JPEG 2000 magic header */ + /** JPEG 2000 magic header. */ byte[] JPEG_2000_MAGIC = new byte[] {0x00, 0x00, 0x00, 0x0C, 'j', 'P', 0x20, 0x20, 0x0D, 0x0A, (byte) 0x87, 0x0A}; - /** PNG magic header */ + /** PNG magic header. */ byte[] PNG_MAGIC = new byte[] {(byte) 0x89, (byte) 'P', (byte) 'N', (byte) 'G', 0x0d, 0x0a, 0x1a, 0x0a}; } diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java index 388b8f96..7f6cb105 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java @@ -31,7 +31,6 @@ package com.twelvemonkeys.imageio.plugins.icns; import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier; -import com.twelvemonkeys.lang.Validate; import javax.imageio.*; import javax.imageio.spi.ImageReaderSpi; @@ -49,7 +48,7 @@ import java.util.Iterator; import java.util.List; /** - * ICNSImageReader + * ImageReader for Apple Icon Image (ICNS) format. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ @@ -61,9 +60,6 @@ import java.util.List; public final class ICNSImageReader extends ImageReaderBase { // TODO: Support ToC resource for faster parsing/faster determine number of icons? // TODO: Subsampled reading for completeness, even if never used? - - private static final int RESOURCE_HEADER_SIZE = 8; - private List icons = new ArrayList(); private List masks = new ArrayList(); private IconResource lastResourceRead; @@ -163,6 +159,7 @@ public final class ICNSImageReader extends ImageReaderBase { if (!allowSearch) { // Return icons.size if we know we have read all? + // TODO: If the first resource is a TOC_ resource, we don't need to perform a search. return -1; } @@ -183,7 +180,7 @@ public final class ICNSImageReader extends ImageReaderBase { public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { IconResource resource = readIconResource(imageIndex); - imageInput.seek(resource.start + RESOURCE_HEADER_SIZE); + imageInput.seek(resource.start + ICNS.RESOURCE_HEADER_SIZE); // Special handling of PNG/JPEG 2000 icons if (resource.isForeignFormat()) { @@ -221,7 +218,7 @@ public final class ICNSImageReader extends ImageReaderBase { // Only 32 bit icons may be compressed data = new byte[width * height * resource.depth() / 8]; - int packedSize = resource.length - RESOURCE_HEADER_SIZE; + int packedSize = resource.length - ICNS.RESOURCE_HEADER_SIZE; if (width >= 128 && height >= 128) { // http://www.macdisk.com/maciconen.php: @@ -233,14 +230,14 @@ public final class ICNSImageReader extends ImageReaderBase { InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize); try { - decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data + ICNSUtil.decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data } finally { input.close(); } } else { - data = new byte[resource.length - RESOURCE_HEADER_SIZE]; + data = new byte[resource.length - ICNS.RESOURCE_HEADER_SIZE]; imageInput.readFully(data); } @@ -365,15 +362,15 @@ public final class ICNSImageReader extends ImageReaderBase { int height = size.height; byte[] mask = new byte[width * height]; - imageInput.seek(resource.start + RESOURCE_HEADER_SIZE); + imageInput.seek(resource.start + ICNS.RESOURCE_HEADER_SIZE); if (resource.isMaskType()) { // 8 bit mask - imageInput.readFully(mask, 0, resource.length - RESOURCE_HEADER_SIZE); + imageInput.readFully(mask, 0, resource.length - ICNS.RESOURCE_HEADER_SIZE); } else if (resource.hasMask()) { // Embedded 1bit mask - byte[] maskData = new byte[(resource.length - RESOURCE_HEADER_SIZE) / 2]; + byte[] maskData = new byte[(resource.length - ICNS.RESOURCE_HEADER_SIZE) / 2]; imageInput.skipBytes(maskData.length); // Skip the 1 bit image data imageInput.readFully(maskData); @@ -482,52 +479,6 @@ public final class ICNSImageReader extends ImageReaderBase { return format; } - /* - * http://www.macdisk.com/maciconen.php: - * "For [...] (width * height of the icon), read a byte. - * if bit 8 of the byte is set: - * This is a compressed run, for some value (next byte). - * The length is byte - 125. (* - * Put so many copies of the byte in the current color channel. - * Else: - * This is an uncompressed run, whose values follow. - * The length is byte + 1. - * Read the bytes and put them in the current color channel." - * - * *): With signed bytes, byte is always negative in this case, so it's actually -byte - 125, - * which is the same as byte + 131. - */ - // NOTE: This is very close to PackBits (as described by the Wikipedia article), but it is not PackBits! - static void decompress(final DataInputStream input, final byte[] result, int offset, int length) throws IOException { - int resultPos = offset; - int remaining = length; - - while (remaining > 0) { - byte run = input.readByte(); - int runLength; - - if ((run & 0x80) != 0) { - // Compressed run - runLength = run + 131; // PackBits: -run + 1 and run == 0x80 is no-op... This allows 1 byte longer runs... - - byte runData = input.readByte(); - - for (int i = 0; i < runLength; i++) { - result[resultPos++] = runData; - } - } - else { - // Uncompressed run - runLength = run + 1; - - input.readFully(result, resultPos, runLength); - resultPos += runLength; - } - - remaining -= runLength; - } - } - private IconResource readIconResource(final int imageIndex) throws IOException { checkBounds(imageIndex); readeFileHeader(); @@ -540,7 +491,7 @@ public final class ICNSImageReader extends ImageReaderBase { } private IconResource readNextIconResource() throws IOException { - long lastReadPos = lastResourceRead == null ? RESOURCE_HEADER_SIZE : lastResourceRead.start + lastResourceRead.length; + long lastReadPos = lastResourceRead == null ? ICNS.RESOURCE_HEADER_SIZE : lastResourceRead.start + lastResourceRead.length; imageInput.seek(lastReadPos); @@ -578,280 +529,11 @@ public final class ICNSImageReader extends ImageReaderBase { } } - // TODO: Rewrite using subclasses/instances! - static final class IconResource { - private final long start; - private final int type; - private final int length; + private static final class ICNSBitMaskColorModel extends IndexColorModel { + static final IndexColorModel INSTANCE = new ICNSBitMaskColorModel(); - IconResource(long start, int type, int length) { - validate(type, length); - - this.start = start; - this.type = type; - this.length = length; - } - - public static IconResource read(ImageInputStream input) throws IOException { - return new IconResource(input.getStreamPosition(), input.readInt(), input.readInt()); - } - - private void validate(int type, int length) { - switch (type) { - case ICNS.ICON: - validateLengthForType(type, length, 128); - break; - case ICNS.ICN_: - validateLengthForType(type, length, 256); - break; - case ICNS.icm_: - validateLengthForType(type, length, 48); - break; - case ICNS.icm4: - validateLengthForType(type, length, 96); - break; - case ICNS.icm8: - validateLengthForType(type, length, 192); - break; - case ICNS.ics_: - validateLengthForType(type, length, 64); - break; - case ICNS.ics4: - validateLengthForType(type, length, 128); - break; - case ICNS.ics8: - case ICNS.s8mk: - validateLengthForType(type, length, 256); - break; - case ICNS.icl4: - validateLengthForType(type, length, 512); - break; - case ICNS.icl8: - case ICNS.l8mk: - validateLengthForType(type, length, 1024); - break; - case ICNS.ich_: - validateLengthForType(type, length, 576); - break; - case ICNS.ich4: - validateLengthForType(type, length, 1152); - break; - case ICNS.ich8: - case ICNS.h8mk: - validateLengthForType(type, length, 2304); - break; - case ICNS.t8mk: - validateLengthForType(type, length, 16384); - break; - case ICNS.ih32: - case ICNS.is32: - case ICNS.il32: - case ICNS.it32: - case ICNS.ic08: - case ICNS.ic09: - case ICNS.ic10: - if (length > RESOURCE_HEADER_SIZE) { - break; - } - throw new IllegalArgumentException(String.format("Wrong combination of icon type '%s' and length: %d", ICNSUtil.intToStr(type), length)); - case ICNS.icnV: - validateLengthForType(type, length, 4); - break; - case ICNS.TOC_: - default: - if (length > RESOURCE_HEADER_SIZE) { - break; - } - throw new IllegalStateException(String.format("Unknown icon type: '%s' length: %d", ICNSUtil.intToStr(type), length)); - } - } - - private void validateLengthForType(int type, int length, final int expectedLength) { - Validate.isTrue( - length == expectedLength + RESOURCE_HEADER_SIZE, // Compute to make lengths more logical - String.format( - "Wrong combination of icon type '%s' and length: %d (expected: %d)", - ICNSUtil.intToStr(type), length - RESOURCE_HEADER_SIZE, expectedLength - ) - ); - } - - public Dimension size() { - switch (type) { - case ICNS.ICON: - case ICNS.ICN_: - return new Dimension(32, 32); - case ICNS.icm_: - case ICNS.icm4: - case ICNS.icm8: - return new Dimension(16, 12); - case ICNS.ics_: - case ICNS.ics4: - case ICNS.ics8: - case ICNS.is32: - case ICNS.s8mk: - return new Dimension(16, 16); - case ICNS.icl4: - case ICNS.icl8: - case ICNS.il32: - case ICNS.l8mk: - return new Dimension(32, 32); - case ICNS.ich_: - case ICNS.ich4: - case ICNS.ich8: - case ICNS.ih32: - case ICNS.h8mk: - return new Dimension(48, 48); - case ICNS.it32: - case ICNS.t8mk: - return new Dimension(128, 128); - case ICNS.ic08: - return new Dimension(256, 256); - case ICNS.ic09: - return new Dimension(512, 512); - case ICNS.ic10: - return new Dimension(1024, 1024); - default: - throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type))); - } - } - - public int depth() { - switch (type) { - case ICNS.ICON: - case ICNS.ICN_: - case ICNS.icm_: - case ICNS.ics_: - case ICNS.ich_: - return 1; - case ICNS.icm4: - case ICNS.ics4: - case ICNS.icl4: - case ICNS.ich4: - return 4; - case ICNS.icm8: - case ICNS.ics8: - case ICNS.icl8: - case ICNS.ich8: - case ICNS.s8mk: - case ICNS.l8mk: - case ICNS.h8mk: - case ICNS.t8mk: - return 8; - case ICNS.is32: - case ICNS.il32: - case ICNS.ih32: - case ICNS.it32: - case ICNS.ic08: - case ICNS.ic09: - case ICNS.ic10: - return 32; - default: - throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type))); - } - } - - public boolean isUnknownType() { - // These should simply be skipped - switch (type) { - case ICNS.ICON: - case ICNS.ICN_: - case ICNS.icm_: - case ICNS.ics_: - case ICNS.ich_: - case ICNS.icm4: - case ICNS.ics4: - case ICNS.icl4: - case ICNS.ich4: - case ICNS.icm8: - case ICNS.ics8: - case ICNS.icl8: - case ICNS.ich8: - case ICNS.s8mk: - case ICNS.l8mk: - case ICNS.h8mk: - case ICNS.t8mk: - case ICNS.is32: - case ICNS.il32: - case ICNS.ih32: - case ICNS.it32: - case ICNS.ic08: - case ICNS.ic09: - case ICNS.ic10: - return false; - } - - return true; - } - - public boolean hasMask() { - switch (type) { - case ICNS.ICN_: - case ICNS.icm_: - case ICNS.ics_: - case ICNS.ich_: - return true; - } - - return false; - } - - public boolean isMaskType() { - switch (type) { - case ICNS.s8mk: - case ICNS.l8mk: - case ICNS.h8mk: - case ICNS.t8mk: - return true; - } - - return false; - } - - public boolean isCompressed() { - switch (type) { - case ICNS.is32: - case ICNS.il32: - case ICNS.ih32: - case ICNS.it32: - // http://www.macdisk.com/maciconen.php - // "One should check whether the data length corresponds to the theoretical length (width * height)." - Dimension size = size(); - if (length != (size.width * size.height * depth() / 8 + RESOURCE_HEADER_SIZE)) { - return true; - } - } - - return false; - } - - public boolean isForeignFormat() { - switch (type) { - case ICNS.ic08: - case ICNS.ic09: - case ICNS.ic10: - return true; - } - return false; - } - - @Override - public int hashCode() { - return (int) start ^ type; - } - - @Override - public boolean equals(Object other) { - return other == this || other != null && other.getClass() == getClass() && isEqual((IconResource) other); - } - - private boolean isEqual(IconResource other) { - return start == other.start && type == other.type && length == other.length; - } - - @Override - public String toString() { - return String.format("%s['%s' start: %d, length: %d%s]", getClass().getSimpleName(), ICNSUtil.intToStr(type), start, length, isCompressed() ? " (compressed)" : ""); + private ICNSBitMaskColorModel() { + super(1, 2, new int[]{0, 0xffffffff}, 0, true, 0, DataBuffer.TYPE_BYTE); } } @@ -910,12 +592,4 @@ public final class ICNSImageReader extends ImageReaderBase { System.err.printf("Read %s images (%d skipped) in %d files\n", imagesRead, imagesSkipped, args.length); } - - private static final class ICNSBitMaskColorModel extends IndexColorModel { - static final IndexColorModel INSTANCE = new ICNSBitMaskColorModel(); - - private ICNSBitMaskColorModel() { - super(1, 2, new int[]{0, 0xffffffff}, 0, true, 0, DataBuffer.TYPE_BYTE); - } - } } diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSUtil.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSUtil.java index fb1246f4..71fb6b06 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSUtil.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSUtil.java @@ -28,6 +28,9 @@ package com.twelvemonkeys.imageio.plugins.icns; +import java.io.DataInputStream; +import java.io.IOException; + /** * ICNSUtil * @@ -50,4 +53,50 @@ final class ICNSUtil { } ); } + + /* + * http://www.macdisk.com/maciconen.php: + * "For [...] (width * height of the icon), read a byte. + * if bit 8 of the byte is set: + * This is a compressed run, for some value (next byte). + * The length is byte - 125. (* + * Put so many copies of the byte in the current color channel. + * Else: + * This is an uncompressed run, whose values follow. + * The length is byte + 1. + * Read the bytes and put them in the current color channel." + * + * *): With signed bytes, byte is always negative in this case, so it's actually -byte - 125, + * which is the same as byte + 131. + */ + // NOTE: This is very close to PackBits (as described by the Wikipedia article), but it is not PackBits! + static void decompress(final DataInputStream input, final byte[] result, int offset, int length) throws IOException { + int resultPos = offset; + int remaining = length; + + while (remaining > 0) { + byte run = input.readByte(); + int runLength; + + if ((run & 0x80) != 0) { + // Compressed run + runLength = run + 131; // PackBits: -run + 1 and run == 0x80 is no-op... This allows 1 byte longer runs... + + byte runData = input.readByte(); + + for (int i = 0; i < runLength; i++) { + result[resultPos++] = runData; + } + } + else { + // Uncompressed run + runLength = run + 1; + + input.readFully(result, resultPos, runLength); + resultPos += runLength; + } + + remaining -= runLength; + } + } } diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java new file mode 100644 index 00000000..04ce6d90 --- /dev/null +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2011, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.icns; + +import com.twelvemonkeys.lang.Validate; + +import javax.imageio.stream.ImageInputStream; +import java.awt.*; +import java.io.IOException; + +/** + * IconResource + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: IconResource.java,v 1.0 23.11.11 13:35 haraldk Exp$ + */ +final class IconResource { + // TODO: Rewrite using subclasses/instances! + + protected final long start; + protected final int type; + protected final int length; + + private IconResource(long start, int type, int length) { + validate(type, length); + + this.start = start; + this.type = type; + this.length = length; + } + + public static IconResource read(ImageInputStream input) throws IOException { + return new IconResource(input.getStreamPosition(), input.readInt(), input.readInt()); + } + + private void validate(int type, int length) { + switch (type) { + case ICNS.ICON: + validateLengthForType(type, length, 128); + break; + case ICNS.ICN_: + validateLengthForType(type, length, 256); + break; + case ICNS.icm_: + validateLengthForType(type, length, 48); + break; + case ICNS.icm4: + validateLengthForType(type, length, 96); + break; + case ICNS.icm8: + validateLengthForType(type, length, 192); + break; + case ICNS.ics_: + validateLengthForType(type, length, 64); + break; + case ICNS.ics4: + validateLengthForType(type, length, 128); + break; + case ICNS.ics8: + case ICNS.s8mk: + validateLengthForType(type, length, 256); + break; + case ICNS.icl4: + validateLengthForType(type, length, 512); + break; + case ICNS.icl8: + case ICNS.l8mk: + validateLengthForType(type, length, 1024); + break; + case ICNS.ich_: + validateLengthForType(type, length, 576); + break; + case ICNS.ich4: + validateLengthForType(type, length, 1152); + break; + case ICNS.ich8: + case ICNS.h8mk: + validateLengthForType(type, length, 2304); + break; + case ICNS.t8mk: + validateLengthForType(type, length, 16384); + break; + case ICNS.ih32: + case ICNS.is32: + case ICNS.il32: + case ICNS.it32: + case ICNS.ic08: + case ICNS.ic09: + case ICNS.ic10: + if (length > ICNS.RESOURCE_HEADER_SIZE) { + break; + } + throw new IllegalArgumentException(String.format("Wrong combination of icon type '%s' and length: %d", ICNSUtil.intToStr(type), length)); + case ICNS.icnV: + validateLengthForType(type, length, 4); + break; + case ICNS.TOC_: + default: + if (length > ICNS.RESOURCE_HEADER_SIZE) { + break; + } + throw new IllegalStateException(String.format("Unknown icon type: '%s' length: %d", ICNSUtil.intToStr(type), length)); + } + } + + private void validateLengthForType(int type, int length, final int expectedLength) { + Validate.isTrue( + length == expectedLength + ICNS.RESOURCE_HEADER_SIZE, // Compute to make lengths more logical + String.format( + "Wrong combination of icon type '%s' and length: %d (expected: %d)", + ICNSUtil.intToStr(type), length - ICNS.RESOURCE_HEADER_SIZE, expectedLength + ) + ); + } + + public Dimension size() { + switch (type) { + case ICNS.ICON: + case ICNS.ICN_: + return new Dimension(32, 32); + case ICNS.icm_: + case ICNS.icm4: + case ICNS.icm8: + return new Dimension(16, 12); + case ICNS.ics_: + case ICNS.ics4: + case ICNS.ics8: + case ICNS.is32: + case ICNS.s8mk: + return new Dimension(16, 16); + case ICNS.icl4: + case ICNS.icl8: + case ICNS.il32: + case ICNS.l8mk: + return new Dimension(32, 32); + case ICNS.ich_: + case ICNS.ich4: + case ICNS.ich8: + case ICNS.ih32: + case ICNS.h8mk: + return new Dimension(48, 48); + case ICNS.it32: + case ICNS.t8mk: + return new Dimension(128, 128); + case ICNS.ic08: + return new Dimension(256, 256); + case ICNS.ic09: + return new Dimension(512, 512); + case ICNS.ic10: + return new Dimension(1024, 1024); + default: + throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type))); + } + } + + public int depth() { + switch (type) { + case ICNS.ICON: + case ICNS.ICN_: + case ICNS.icm_: + case ICNS.ics_: + case ICNS.ich_: + return 1; + case ICNS.icm4: + case ICNS.ics4: + case ICNS.icl4: + case ICNS.ich4: + return 4; + case ICNS.icm8: + case ICNS.ics8: + case ICNS.icl8: + case ICNS.ich8: + case ICNS.s8mk: + case ICNS.l8mk: + case ICNS.h8mk: + case ICNS.t8mk: + return 8; + case ICNS.is32: + case ICNS.il32: + case ICNS.ih32: + case ICNS.it32: + case ICNS.ic08: + case ICNS.ic09: + case ICNS.ic10: + return 32; + default: + throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type))); + } + } + + public boolean isUnknownType() { + // These should simply be skipped + switch (type) { + case ICNS.ICON: + case ICNS.ICN_: + case ICNS.icm_: + case ICNS.ics_: + case ICNS.ich_: + case ICNS.icm4: + case ICNS.ics4: + case ICNS.icl4: + case ICNS.ich4: + case ICNS.icm8: + case ICNS.ics8: + case ICNS.icl8: + case ICNS.ich8: + case ICNS.s8mk: + case ICNS.l8mk: + case ICNS.h8mk: + case ICNS.t8mk: + case ICNS.is32: + case ICNS.il32: + case ICNS.ih32: + case ICNS.it32: + case ICNS.ic08: + case ICNS.ic09: + case ICNS.ic10: + return false; + } + + return true; + } + + public boolean hasMask() { + switch (type) { + case ICNS.ICN_: + case ICNS.icm_: + case ICNS.ics_: + case ICNS.ich_: + return true; + } + + return false; + } + + public boolean isMaskType() { + switch (type) { + case ICNS.s8mk: + case ICNS.l8mk: + case ICNS.h8mk: + case ICNS.t8mk: + return true; + } + + return false; + } + + public boolean isCompressed() { + switch (type) { + case ICNS.is32: + case ICNS.il32: + case ICNS.ih32: + case ICNS.it32: + // http://www.macdisk.com/maciconen.php + // "One should check whether the data length corresponds to the theoretical length (width * height)." + Dimension size = size(); + if (length != (size.width * size.height * depth() / 8 + ICNS.RESOURCE_HEADER_SIZE)) { + return true; + } + } + + return false; + } + + public boolean isForeignFormat() { + switch (type) { + case ICNS.ic08: + case ICNS.ic09: + case ICNS.ic10: + return true; + } + return false; + } + + @Override + public int hashCode() { + return (int) start ^ type; + } + + @Override + public boolean equals(Object other) { + return other == this || other != null && other.getClass() == getClass() && isEqual((IconResource) other); + } + + private boolean isEqual(IconResource other) { + return start == other.start && type == other.type && length == other.length; + } + + @Override + public String toString() { + return String.format("%s['%s' start: %d, length: %d%s]", getClass().getSimpleName(), ICNSUtil.intToStr(type), start, length, isCompressed() ? " (compressed)" : ""); + } +}