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 87cb0815..12309f6c 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
@@ -51,7 +51,7 @@ interface ICNS {
/** 16×12 8 bit icon */
int icm8 = ('i' << 24) + ('c' << 16) + ('m' << 8) + '8';
- /** 16×16 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 */
int ics4 = ('i' << 24) + ('c' << 16) + ('s' << 8) + '4';
@@ -71,7 +71,7 @@ interface ICNS {
/** 32×32 8-bit mask */
int l8mk = ('l' << 24) + ('8' << 16) + ('m' << 8) + 'k';
- /** 48×48 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 */
int ich4 = ('i' << 24) + ('c' << 16) + ('h' << 8) + '4';
@@ -87,41 +87,21 @@ interface ICNS {
/** 128×128 8-bit mask */
int t8mk = ('t' << 24) + ('8' << 16) + ('m' << 8) + 'k';
- /** 256×256 JPEG 2000 or PNG icon */
+ /** 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 */
+ /** 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';
- /*
- ICN# 256 32 32×32 1-bit mono icon with 1-bit mask
- icm# 24 16 16×12 1 bit mask
- icm4 96 16 16×12 4 bit icon
- icm8 192 16 16×12 8 bit icon
- ics# 32 16 16×16 1-bit mask
- ics4 128 16 16×16 4-bit icon
- ics8 256 16 16x16 8 bit icon
- is32 varies (768) 16 16×16 24-bit icon
- s8mk 256 16 16x16 8-bit mask
- icl4 512 32 32×32 4-bit icon
- icl8 1,024 32 32×32 8-bit icon
- il32 varies (3,072) 32 32x32 24-bit icon
- l8mk 1,024 32 32×32 8-bit mask
- ich# 288 48 48×48 1-bit mask
- ich4 1,152 48 48×48 4-bit icon
- ich8 2,304 48 48×48 8-bit icon
- ih32 varies (6,912) 48 48×48 24-bit icon
- h8mk 2,304 48 48×48 8-bit mask
- it32 varies (49,152) 128 128×128 24-bit icon
- t8mk 16,384 128 128×128 8-bit mask
- ic08 varies 256 256×256 icon in JPEG 2000 or PNG format
- ic09 varies 512 512×512 icon in JPEG 2000 or PNG format
- ic10 varies 1024 1024×1024 icon in PNG format (added in Mac OS X 10.7)
- */
+ /** Unknown */
+ int icnV = ('i' << 24) + ('c' << 16) + ('n' << 8) + 'V';
+ /** 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 */
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/ICNS1BitColorModel.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS1BitColorModel.java
new file mode 100644
index 00000000..12a42ff4
--- /dev/null
+++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS1BitColorModel.java
@@ -0,0 +1,52 @@
+/*
+ * 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 java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+
+/**
+ * ICNS1BitColorModel
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ICNS1BitColorModel.java,v 1.0 07.11.11 10:49 haraldk Exp$
+ */
+final class ICNS1BitColorModel extends IndexColorModel {
+ private static final int[] CMAP = {
+ // Inverted from default Java 1 bit...
+ 0xffffffff, 0xff000000
+ };
+
+ static final IndexColorModel INSTANCE = new ICNS1BitColorModel();
+
+ private ICNS1BitColorModel() {
+ super(1, 2, CMAP, 0, false, -1, DataBuffer.TYPE_BYTE);
+ }
+}
diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS4BitColorModel.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS4BitColorModel.java
new file mode 100644
index 00000000..10a90f18
--- /dev/null
+++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS4BitColorModel.java
@@ -0,0 +1,52 @@
+/*
+ * 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 java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+
+/**
+ * ICNS4BitColorModel
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ICNS4BitColorModel.java,v 1.0 07.11.11 10:49 haraldk Exp$
+ */
+final class ICNS4BitColorModel extends IndexColorModel {
+ private static final int[] CMAP = {
+ 0xffffffff, 0xfffcf305, 0xffff6402, 0xffdd0806, 0xfff20884, 0xff4600a5, 0xff0000d4, 0xff02abea,
+ 0xff1fb714, 0xff006411, 0xff562c05, 0xff90713a, 0xffc0c0c0, 0xff808080, 0xff404040, 0xff000000
+ };
+
+ static final IndexColorModel INSTANCE = new ICNS4BitColorModel();
+
+ private ICNS4BitColorModel() {
+ super(4, 16, CMAP, 0, false, -1, DataBuffer.TYPE_BYTE);
+ }
+}
diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS8BitColorModel.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS8BitColorModel.java
new file mode 100644
index 00000000..19d12f86
--- /dev/null
+++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS8BitColorModel.java
@@ -0,0 +1,82 @@
+/*
+ * 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 java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+
+/**
+ * ICNS8BitColorModel
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ICNS8BitColorModel.java,v 1.0 07.11.11 10:49 haraldk Exp$
+ */
+final class ICNS8BitColorModel extends IndexColorModel {
+ private static final int[] CMAP = {
+ 0xffffffff, 0xffffffcc, 0xffffff99, 0xffffff66, 0xffffff33, 0xffffff00, 0xffffccff, 0xffffcccc,
+ 0xffffcc99, 0xffffcc66, 0xffffcc33, 0xffffcc00, 0xffff99ff, 0xffff99cc, 0xffff9999, 0xffff9966,
+ 0xffff9933, 0xffff9900, 0xffff66ff, 0xffff66cc, 0xffff6699, 0xffff6666, 0xffff6633, 0xffff6600,
+ 0xffff33ff, 0xffff33cc, 0xffff3399, 0xffff3366, 0xffff3333, 0xffff3300, 0xffff00ff, 0xffff00cc,
+ 0xffff0099, 0xffff0066, 0xffff0033, 0xffff0000, 0xffccffff, 0xffccffcc, 0xffccff99, 0xffccff66,
+ 0xffccff33, 0xffccff00, 0xffccccff, 0xffcccccc, 0xffcccc99, 0xffcccc66, 0xffcccc33, 0xffcccc00,
+ 0xffcc99ff, 0xffcc99cc, 0xffcc9999, 0xffcc9966, 0xffcc9933, 0xffcc9900, 0xffcc66ff, 0xffcc66cc,
+ 0xffcc6699, 0xffcc6666, 0xffcc6633, 0xffcc6600, 0xffcc33ff, 0xffcc33cc, 0xffcc3399, 0xffcc3366,
+ 0xffcc3333, 0xffcc3300, 0xffcc00ff, 0xffcc00cc, 0xffcc0099, 0xffcc0066, 0xffcc0033, 0xffcc0000,
+ 0xff99ffff, 0xff99ffcc, 0xff99ff99, 0xff99ff66, 0xff99ff33, 0xff99ff00, 0xff99ccff, 0xff99cccc,
+ 0xff99cc99, 0xff99cc66, 0xff99cc33, 0xff99cc00, 0xff9999ff, 0xff9999cc, 0xff999999, 0xff999966,
+ 0xff999933, 0xff999900, 0xff9966ff, 0xff9966cc, 0xff996699, 0xff996666, 0xff996633, 0xff996600,
+ 0xff9933ff, 0xff9933cc, 0xff993399, 0xff993366, 0xff993333, 0xff993300, 0xff9900ff, 0xff9900cc,
+ 0xff990099, 0xff990066, 0xff990033, 0xff990000, 0xff66ffff, 0xff66ffcc, 0xff66ff99, 0xff66ff66,
+ 0xff66ff33, 0xff66ff00, 0xff66ccff, 0xff66cccc, 0xff66cc99, 0xff66cc66, 0xff66cc33, 0xff66cc00,
+ 0xff6699ff, 0xff6699cc, 0xff669999, 0xff669966, 0xff669933, 0xff669900, 0xff6666ff, 0xff6666cc,
+ 0xff666699, 0xff666666, 0xff666633, 0xff666600, 0xff6633ff, 0xff6633cc, 0xff663399, 0xff663366,
+ 0xff663333, 0xff663300, 0xff6600ff, 0xff6600cc, 0xff660099, 0xff660066, 0xff660033, 0xff660000,
+ 0xff33ffff, 0xff33ffcc, 0xff33ff99, 0xff33ff66, 0xff33ff33, 0xff33ff00, 0xff33ccff, 0xff33cccc,
+ 0xff33cc99, 0xff33cc66, 0xff33cc33, 0xff33cc00, 0xff3399ff, 0xff3399cc, 0xff339999, 0xff339966,
+ 0xff339933, 0xff339900, 0xff3366ff, 0xff3366cc, 0xff336699, 0xff336666, 0xff336633, 0xff336600,
+ 0xff3333ff, 0xff3333cc, 0xff333399, 0xff333366, 0xff333333, 0xff333300, 0xff3300ff, 0xff3300cc,
+ 0xff330099, 0xff330066, 0xff330033, 0xff330000, 0xff00ffff, 0xff00ffcc, 0xff00ff99, 0xff00ff66,
+ 0xff00ff33, 0xff00ff00, 0xff00ccff, 0xff00cccc, 0xff00cc99, 0xff00cc66, 0xff00cc33, 0xff00cc00,
+ 0xff0099ff, 0xff0099cc, 0xff009999, 0xff009966, 0xff009933, 0xff009900, 0xff0066ff, 0xff0066cc,
+ 0xff006699, 0xff006666, 0xff006633, 0xff006600, 0xff0033ff, 0xff0033cc, 0xff003399, 0xff003366,
+ 0xff003333, 0xff003300, 0xff0000ff, 0xff0000cc, 0xff000099, 0xff000066, 0xff000033, 0xffee0000,
+ 0xffdd0000, 0xffbb0000, 0xffaa0000, 0xff880000, 0xff770000, 0xff550000, 0xff440000, 0xff220000,
+ 0xff110000, 0xff00ee00, 0xff00dd00, 0xff00bb00, 0xff00aa00, 0xff008800, 0xff007700, 0xff005500,
+ 0xff004400, 0xff002200, 0xff001100, 0xff0000ee, 0xff0000dd, 0xff0000bb, 0xff0000aa, 0xff000088,
+ 0xff000077, 0xff000055, 0xff000044, 0xff000022, 0xff000011, 0xffeeeeee, 0xffdddddd, 0xffbbbbbb,
+ 0xffaaaaaa, 0xff888888, 0xff777777, 0xff555555, 0xff444444, 0xff222222, 0xff111111, 0xff000000
+ };
+
+ static final IndexColorModel INSTANCE = new ICNS8BitColorModel();
+
+ private ICNS8BitColorModel() {
+ super(8, 256, CMAP, 0, false, -1, DataBuffer.TYPE_BYTE);
+ }
+}
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 a4e34d80..842f9a41 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
@@ -30,6 +30,8 @@ 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;
@@ -57,7 +59,9 @@ public final class ICNSImageReader extends ImageReaderBase {
// TODO: Merge masks with icon in front + calculate image count based on this...
private static final int HEADER_SIZE = 8;
- private List iconHeaders = new ArrayList();
+ private List icons = new ArrayList();
+ private List masks = new ArrayList();
+
private int length;
public ICNSImageReader() {
@@ -70,6 +74,10 @@ public final class ICNSImageReader extends ImageReaderBase {
@Override
protected void resetMembers() {
+ length = 0;
+
+ icons.clear();
+ masks.clear();
}
@Override
@@ -82,31 +90,52 @@ public final class ICNSImageReader extends ImageReaderBase {
return readIconHeader(imageIndex).size().height;
}
+ @Override
+ public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
+ IconHeader header = readIconHeader(imageIndex);
+
+ switch (header.depth()) {
+ case 1:
+ return IndexedImageTypeSpecifier.createFromIndexColorModel(ICNS1BitColorModel.INSTANCE);
+ case 4:
+ return IndexedImageTypeSpecifier.createFromIndexColorModel(ICNS4BitColorModel.INSTANCE);
+ case 8:
+ return IndexedImageTypeSpecifier.createFromIndexColorModel(ICNS8BitColorModel.INSTANCE);
+ case 32:
+ int bandLen = header.size().width * header.size().height;
+ return ImageTypeSpecifier.createBanded(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{0, 1, 2, 3}, new int[]{0, bandLen, 2 * bandLen, 3 * bandLen}, DataBuffer.TYPE_BYTE, true, false);
+ default:
+ throw new IllegalStateException(String.format("Unknown bit depth: %d", header.depth()));
+ }
+ }
+
@Override
public Iterator getImageTypes(int imageIndex) throws IOException {
+ ImageTypeSpecifier rawType = getRawImageType(imageIndex);
IconHeader header = readIconHeader(imageIndex);
List specifiers = new ArrayList();
switch (header.depth()) {
case 1:
- specifiers.add(ImageTypeSpecifier.createGrayscale(1, DataBuffer.TYPE_BYTE, false));
- // Fall through
+// break;
+ // TODO: Fall through & convert during read?
case 4:
- specifiers.add(ImageTypeSpecifier.createGrayscale(4, DataBuffer.TYPE_BYTE, false));
- // Fall through
+// break;
+ // TODO: Fall through & convert during read?
case 8:
- specifiers.add(ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false));
- // Fall through
- case 24:
- specifiers.add(ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{0, 1, 2}, DataBuffer.TYPE_BYTE, false, false));
+// break;
+ // TODO: Fall through & convert during read?
case 32:
- specifiers.add(ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{0, 1, 2, 3}, DataBuffer.TYPE_BYTE, true, false));
+ specifiers.add(ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), 0xff0000, 0x00ff00, 0x0000ff, 0xff000000, DataBuffer.TYPE_INT, false));
+ specifiers.add(ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
break;
default:
throw new IllegalStateException(String.format("Unknown bit depth: %d", header.depth()));
}
+ specifiers.add(rawType);
+
return specifiers.iterator();
}
@@ -115,21 +144,21 @@ public final class ICNSImageReader extends ImageReaderBase {
assertInput();
if (!allowSearch) {
+ // Return icons.size if we know we have read all?
return -1;
}
- int num = iconHeaders.size();
+ int num = icons.size();
while (true) {
try {
- readIconHeader(num);
- num++;
+ readIconHeader(num++);
}
catch (IndexOutOfBoundsException expected) {
break;
}
}
- return num;
+ return icons.size();
}
@Override
@@ -139,86 +168,55 @@ public final class ICNSImageReader extends ImageReaderBase {
imageInput.seek(header.start + HEADER_SIZE);
- // TODO: Extract in separate method/class
// Special handling of PNG/JPEG 2000 icons
if (header.isForeignFormat()) {
- ImageInputStream stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, header.length));
- try {
- // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
- // JPEG2000 magic bytes: 00 00 00 0C 6A 50 20 20 0D 0A 87 0A 00 00 00 14 66 74 79 70 6A 70 32
- // 00 00 00 0C 6A 50 20 20 0D 0A 87 0A
- // 12 j P sp sp \r \n
- byte[] magic = new byte[12];
- stream.readFully(magic);
-// System.out.println("magic: " + Arrays.toString(magic));
-
- String format;
- if (Arrays.equals(ICNS.PNG_MAGIC, magic)) {
- format = "PNG";
- }
- else if (Arrays.equals(ICNS.JPEG_2000_MAGIC, magic)) {
- format = "JPEG 2000";
- }
- else {
- format = "unknown";
- }
-
- stream.seek(0);
-
- Iterator readers = ImageIO.getImageReaders(stream);
-
- while (readers.hasNext()) {
- ImageReader reader = readers.next();
- reader.setInput(stream);
-
- try {
- return reader.read(0, param);
- }
- catch (IOException ignore) {
- }
-
- stream.seek(0);
- }
-
- // TODO: There's no JPEG 2000 reader installed in ImageIO by default (requires JAI ImageIO installed)
- // TODO: Return blank icon? We know the image dimensions, we just can't read the data... Return blank image? Pretend it's not in the stream? ;-)
- // TODO: Create JPEG 2000 reader..? :-P
- throw new IIOException(String.format(
- "Cannot read %s format in type '%s' icon (no reader; installed: %s)",
- format, ICNSUtil.intToStr(header.type), Arrays.toString(ImageIO.getReaderFormatNames())
- ));
- }
- finally {
- stream.close();
- }
+ return readForeignFormat(param, header);
}
+ return readICNSFormat(imageIndex, param, header);
+ }
+
+ private BufferedImage readICNSFormat(final int imageIndex, final ImageReadParam param, final IconHeader header) throws IOException {
Dimension size = header.size();
+
int width = size.width;
int height = size.height;
BufferedImage image = getDestination(param, getImageTypes(imageIndex), width, height);
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
- checkReadParamBandSettings(param, rawType.getNumBands(), image.getSampleModel().getNumBands());
+ if (rawType instanceof IndexedImageTypeSpecifier && rawType.getBufferedImageType() != image.getType()) {
+ checkReadParamBandSettings(param, 4, image.getSampleModel().getNumBands());
+ }
+ else {
+ checkReadParamBandSettings(param, rawType.getNumBands(), image.getSampleModel().getNumBands());
+ }
final Rectangle source = new Rectangle();
final Rectangle dest = new Rectangle();
computeRegions(param, width, height, image, source, dest);
+ processImageStarted(imageIndex);
+
// Read image data
byte[] data;
- if (header.isPackbits()) {
+ if (header.isCompressed()) {
+ // Only 32 bit icons may be compressed
data = new byte[width * height * header.depth() / 8];
int packedSize = header.length - HEADER_SIZE;
+
if (width >= 128 && height >= 128) {
- imageInput.skipBytes(4);
+ imageInput.skipBytes(4); // Seems to be 4 byte 0-pad
packedSize -= 4;
}
InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize);
- unpackbits(new DataInputStream(input), data, 0, data.length);
- input.close();
+ try {
+ decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data
+ }
+ finally {
+ input.close();
+ }
}
else {
data = new byte[header.length - HEADER_SIZE];
@@ -232,47 +230,183 @@ public final class ICNSImageReader extends ImageReaderBase {
break;
case 8:
break;
- case 24:
+ case 32:
break;
default:
throw new IllegalStateException(String.format("Unknown bit depth for icon: %d", header.depth()));
}
- if (header.depth() <= 8) {
+ if (header.depth() == 1) {
+ DataBufferByte buffer = new DataBufferByte(data, data.length / 2, 0);
+ WritableRaster raster = Raster.createPackedRaster(buffer, width, height, header.depth(), null);
+
+ if (image.getType() == rawType.getBufferedImageType() && ((IndexColorModel) image.getColorModel()).getMapSize() == 2) {
+ image.setData(raster);
+ }
+ else {
+ DataBufferByte maskBuffer = new DataBufferByte(data, data.length / 2, data.length / 2);
+ WritableRaster mask = Raster.createPackedRaster(maskBuffer, width, height, header.depth(), null);
+
+ Graphics2D graphics = image.createGraphics();
+ try {
+ BufferedImage temp = new BufferedImage(rawType.getColorModel(), raster, false, null);
+ graphics.drawImage(temp, 0, 0, null);
+
+ temp = new BufferedImage(ICNSBitMaskColorModel.INSTANCE, mask, false, null);
+ temp.setData(mask);
+ graphics.setComposite(AlphaComposite.DstIn);
+ graphics.drawImage(temp, 0, 0, null);
+ }
+ finally {
+ graphics.dispose();
+ }
+ }
+ }
+ else if (header.depth() <= 8) {
DataBufferByte buffer = new DataBufferByte(data, data.length);
- image.setData(Raster.createPackedRaster(buffer, width, height, header.depth(), null));
+ WritableRaster raster = Raster.createPackedRaster(buffer, width, height, header.depth(), null);
+
+ if (image.getType() == rawType.getBufferedImageType()) {
+ image.setData(raster);
+ }
+ else {
+ Graphics2D graphics = image.createGraphics();
+ try {
+ BufferedImage temp = new BufferedImage(rawType.getColorModel(), raster, false, null);
+ graphics.drawImage(temp, 0, 0, null);
+ }
+ finally {
+ graphics.dispose();
+ }
+
+ processImageProgress(50f);
+
+ // Look up/read mask from later IconHeader and apply
+ Raster mask = readMask(findMask(header));
+ image.getAlphaRaster().setRect(mask);
+ }
}
else {
-// System.err.println("image: " + image);
-// DataBufferByte buffer = new DataBufferByte(data, data.length);
-// WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, width * header.depth() / 8, header.depth() / 8, new int[]{0, 1, 2}, null);
-// WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, width * header.depth() / 8, header.depth() / 8, new int[]{0, 1, 2, 3}, null);
-// int bandLen = data.length / 4;
-// DataBufferByte buffer = new DataBufferByte(data, data.length);
-// WritableRaster raster = Raster.createBandedRaster(buffer, width, height, width, new int[]{0, 0, 0, 0}, new int[]{0, bandLen, bandLen * 2, bandLen * 3}, null);
- int bandLen = data.length / 3;
+ int bandLen = data.length / 4;
+
DataBufferByte buffer = new DataBufferByte(data, data.length);
- WritableRaster raster = Raster.createBandedRaster(buffer, width, height, width, new int[]{0, 0, 0}, new int[]{0, bandLen, bandLen * 2}, null);
- ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
+ WritableRaster raster = Raster.createBandedRaster(buffer, width, height, width, new int[]{0, 0, 0, 0}, new int[]{0, bandLen, bandLen * 2, bandLen * 3}, null);
+ image.setData(raster);
- BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
-// showIt(temp, "foo");
+ processImageProgress(75f);
-// image.setData(raster);
- Graphics2D graphics = image.createGraphics();
- try {
- graphics.drawImage(temp, 0, 0, null);
- }
- finally {
- graphics.dispose();
- }
+ // Read mask from later IconHeader and apply
+ Raster mask = readMask(findMask(header));
+ image.getAlphaRaster().setRect(mask);
+ }
+
+ // For now: Make listener tests happy
+ // TODO: Implement more sophisticated reading
+ processImageProgress(100f);
+
+ if (abortRequested()) {
+ processReadAborted();
+ }
+ else {
+ processImageComplete();
}
return image;
}
+ private Raster readMask(IconHeader header) throws IOException {
+ Dimension size = header.size();
+
+ int width = size.width;
+ int height = size.height;
+
+ byte[] alpha = new byte[header.length - HEADER_SIZE];
+
+ imageInput.seek(header.start + HEADER_SIZE);
+ imageInput.readFully(alpha);
+
+ return Raster.createBandedRaster(new DataBufferByte(alpha, alpha.length), width, height, width, new int[]{0}, new int[]{0}, null);
+ }
+
+ private IconHeader findMask(final IconHeader icon) throws IOException {
+ try {
+ int i = 0;
+
+ while (true) {
+ IconHeader mask = i < masks.size() ? masks.get(i++) : readNextIconHeader();
+
+ if (mask.isMask() && mask.size().equals(icon.size())) {
+ return mask;
+ }
+ }
+ }
+ catch (IndexOutOfBoundsException ignore) {
+ }
+
+ throw new IIOException(String.format("No mask for icon: %s", icon));
+ }
+
+ private BufferedImage readForeignFormat(final ImageReadParam param, final IconHeader header) throws IOException {
+ ImageInputStream stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, header.length));
+
+ try {
+ Iterator readers = ImageIO.getImageReaders(stream);
+
+ while (readers.hasNext()) {
+ ImageReader reader = readers.next();
+ reader.setInput(stream);
+
+ try {
+ return reader.read(0, param);
+ }
+ catch (IOException ignore) {
+ }
+ finally {
+ stream.seek(0);
+ }
+ }
+
+ // There's no JPEG 2000 reader installed in ImageIO by default (requires JAI ImageIO installed).
+ // The current implementation is correct, but a bit harsh maybe..? Other options:
+ // TODO: Return blank icon + issue warning? We know the image dimensions, we just can't read the data...
+ // TODO: Pretend it's not in the stream + issue warning?
+ // TODO: Create JPEG 2000 reader..? :-P
+ throw new IIOException(String.format(
+ "Cannot read %s format in type '%s' icon (no reader; installed: %s)",
+ getForeignFormat(stream), ICNSUtil.intToStr(header.type), Arrays.toString(ImageIO.getReaderFormatNames())
+ ));
+ }
+ finally {
+ stream.close();
+ }
+ }
+
+ private String getForeignFormat(final ImageInputStream stream) throws IOException {
+ byte[] magic = new byte[12]; // Length of JPEG 2000 magic
+ try {
+ stream.readFully(magic);
+ }
+ finally {
+ stream.seek(0);
+ }
+
+ String format;
+ if (Arrays.equals(ICNS.PNG_MAGIC, magic)) {
+ format = "PNG";
+ }
+ else if (Arrays.equals(ICNS.JPEG_2000_MAGIC, magic)) {
+ format = "JPEG 2000";
+ }
+ else {
+ format = "unknown";
+ }
+
+ return format;
+ }
+
+ // http://www.macdisk.com/maciconen.php
// TODO: Is this really packbits?! Don't think so, but it's very close...
- static void unpackbits(final DataInputStream input, final byte[] result, int offset, int length) throws IOException {
+ static void decompress(final DataInputStream input, final byte[] result, int offset, int length) throws IOException {
int resultPos = offset;
int remaining = length;
@@ -282,11 +416,11 @@ public final class ICNSImageReader extends ImageReaderBase {
if ((run & 0x80) != 0) {
// Repeated run
- runLength = run + 131; // Packbits says: -run + 1 and 0x80 should be no-op... This inverts the lengths, but allows longer runs...
+ 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;
+ result[resultPos++] = runData;
}
}
else {
@@ -301,27 +435,43 @@ public final class ICNSImageReader extends ImageReaderBase {
}
}
- private IconHeader readIconHeader(int imageIndex) throws IOException {
+ private IconHeader readIconHeader(final int imageIndex) throws IOException {
checkBounds(imageIndex);
readeFileHeader();
- if (iconHeaders.size() <= imageIndex) {
- int lastReadIndex = iconHeaders.size() - 1;
- IconHeader lastRead = iconHeaders.isEmpty() ? null : iconHeaders.get(lastReadIndex);
-
- for (int i = lastReadIndex; i < imageIndex; i++) {
- imageInput.seek(lastRead == null ? HEADER_SIZE : lastRead.start + lastRead.length);
-
- if (imageInput.getStreamPosition() >= length) {
- throw new IndexOutOfBoundsException();
- }
-
- lastRead = IconHeader.read(imageInput);
- iconHeaders.add(lastRead);
- }
+ while (icons.size() <= imageIndex) {
+ readNextIconHeader();
}
- return iconHeaders.get(imageIndex);
+ return icons.get(imageIndex);
+ }
+
+ private IconHeader readNextIconHeader() throws IOException {
+ IconHeader lastIcon = icons.isEmpty() ? null : icons.get(icons.size() - 1);
+ IconHeader lastMask = masks.isEmpty() ? null : masks.get(masks.size() - 1);
+
+ long lastReadPos = Math.max(
+ lastIcon == null ? HEADER_SIZE : lastIcon.start + lastIcon.length,
+ lastMask == null ? HEADER_SIZE : lastMask.start + lastMask.length
+ );
+
+ imageInput.seek(lastReadPos);
+
+ if (imageInput.getStreamPosition() >= length) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ IconHeader header = IconHeader.read(imageInput);
+
+ // Filter out special case icnV (version?), as this isn't really an icon..
+ if (header.isMask() || header.type == ICNS.icnV) {
+ masks.add(header);
+ }
+ else {
+ icons.add(header);
+ }
+
+ return header;
}
private void readeFileHeader() throws IOException {
@@ -359,64 +509,51 @@ public final class ICNSImageReader extends ImageReaderBase {
private void validate(int type, int length) {
switch (type) {
case ICNS.ICON:
- if (length == 128) {
- return;
- }
+ validateLengthForType(type, length, 128);
+ break;
case ICNS.ICN_:
- if (length == 256) {
- return;
- }
+ validateLengthForType(type, length, 256);
+ break;
case ICNS.icm_:
- if (length == 24) {
- return;
- }
+ validateLengthForType(type, length, 24);
+ break;
case ICNS.icm4:
- if (length == 96) {
- return;
- }
+ validateLengthForType(type, length, 96);
+ break;
case ICNS.icm8:
- if (length == 192) {
- return;
- }
+ validateLengthForType(type, length, 192);
+ break;
case ICNS.ics_:
- if (length == 32) {
- return;
- }
+ validateLengthForType(type, length, 64);
+ break;
case ICNS.ics4:
- if (length == 128) {
- return;
- }
+ validateLengthForType(type, length, 128);
+ break;
case ICNS.ics8:
case ICNS.s8mk:
- if (length == 256) {
- return;
- }
+ validateLengthForType(type, length, 256);
+ break;
case ICNS.icl4:
- if (length == 512) {
- return;
- }
+ validateLengthForType(type, length, 512);
+ break;
case ICNS.icl8:
case ICNS.l8mk:
- if (length == 1024) {
- return;
- }
+ validateLengthForType(type, length, 1024);
+ break;
case ICNS.ich_:
- if (length == 288) {
- return;
- }
+// validateLengthForType(type, length, 288);
+ validateLengthForType(type, length, 576);
+ break;
case ICNS.ich4:
- if (length == 1152) {
- return;
- }
+ validateLengthForType(type, length, 1152);
+ break;
case ICNS.ich8:
case ICNS.h8mk:
- if (length == 2034) {
- return;
- }
+ validateLengthForType(type, length, 2304);
+ break;
case ICNS.t8mk:
- if (length == 16384) {
- return;
- }
+ validateLengthForType(type, length, 16384);
+ break;
case ICNS.ih32:
case ICNS.is32:
case ICNS.il32:
@@ -425,12 +562,26 @@ public final class ICNSImageReader extends ImageReaderBase {
case ICNS.ic09:
case ICNS.ic10:
if (length > 0) {
- return;
+ 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;
default:
throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type)));
}
+
+ }
+
+ private void validateLengthForType(int type, int length, final int expectedLength) {
+ Validate.isTrue(
+ length == expectedLength + 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 - HEADER_SIZE, expectedLength
+ )
+ );
}
public Dimension size() {
@@ -502,17 +653,29 @@ public final class ICNSImageReader extends ImageReaderBase {
case ICNS.ic08:
case ICNS.ic09:
case ICNS.ic10:
- return 24;
+ return 32;
default:
throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type)));
}
}
- public boolean isPackbits() {
+ public boolean isMask() {
+ 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.ih32:
- case ICNS.il32:
case ICNS.is32:
+ case ICNS.il32:
+ case ICNS.ih32:
case ICNS.it32:
return true;
}
@@ -550,6 +713,7 @@ public final class ICNSImageReader extends ImageReaderBase {
}
}
+ @SuppressWarnings({"UnusedAssignment"})
public static void main(String[] args) throws IOException {
int argIndex = 0;
@@ -568,7 +732,7 @@ public final class ICNSImageReader extends ImageReaderBase {
for (int i = start; i < numImages; i++) {
try {
BufferedImage image = reader.read(i);
- System.err.println("image: " + image);
+// System.err.println("image: " + image);
showIt(image, String.format("%s - %d", input.getName(), i));
}
catch (IIOException e) {
@@ -576,4 +740,12 @@ public final class ICNSImageReader extends ImageReaderBase {
}
}
}
+
+ 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/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java
index c8534631..c31da2bd 100644
--- a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java
+++ b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java
@@ -49,20 +49,28 @@ public class ICNSImageReaderTest extends ImageReaderAbstractTestCase {
return Arrays.asList(
new TestData(
getClassLoaderResource("/icns/GenericJavaApp.icns"),
- new Dimension(16, 16), new Dimension(16, 16), // 1 bit, 8 bit
- new Dimension(16, 16), new Dimension(16, 16), // 24 bit + 8 bit mask
- new Dimension(32, 32), new Dimension(32, 32),
- new Dimension(32, 32), new Dimension(32, 32),
- new Dimension(128, 128), new Dimension(128, 128)
+ new Dimension(16, 16), // 1 bit + 1 bit mask
+ new Dimension(16, 16), new Dimension(16, 16), // 8 bit CMAP, 32 bit
+ new Dimension(32, 32), // 1 bit + 1 bit mask
+ new Dimension(32, 32), new Dimension(32, 32), // 8 bit CMAP, 32 bit
+ new Dimension(128, 128) // 32 bit
),
new TestData(
getClassLoaderResource("/icns/Apple Retro.icns"),
- new Dimension(16, 16), new Dimension(16, 16), // 24 bit + 8 bit mask
- new Dimension(32, 32), new Dimension(32, 32), // 24 bit + 8 bit mask
- new Dimension(48, 48), new Dimension(48, 48) ,// 24 bit + 8 bit mask
- new Dimension(128, 128), new Dimension(128, 128), // 24 bit + 8 bit mask
- new Dimension(256, 256), // JPEG 2000
- new Dimension(512, 512) // JPEG 2000
+ new Dimension(16, 16), // 24 bit + 8 bit mask
+ new Dimension(32, 32), // 24 bit + 8 bit mask
+ new Dimension(48, 48), // 24 bit + 8 bit mask
+ new Dimension(128, 128) // 24 bit + 8 bit mask
+//, new Dimension(256, 256), // JPEG 2000, not readable without JAI or other JPEG 2000 support
+// new Dimension(512, 512) // JPEG 2000
+ ),
+ new TestData(
+ getClassLoaderResource("/icns/7zIcon.icns"), // Contains the icnV resource, that isn't an icon
+ new Dimension(16, 16), // 24 bit + 8 bit mask
+ new Dimension(32, 32), // 24 bit + 8 bit mask
+ new Dimension(128, 128) // 24 bit + 8 bit mask
+//, new Dimension(256, 256), // JPEG 2000
+// new Dimension(512, 512) // JPEG 2000
)
);
}
@@ -87,7 +95,6 @@ public class ICNSImageReaderTest extends ImageReaderAbstractTestCase {
return Arrays.asList("icns");
}
-
@Override
protected List getSuffixes() {
return Arrays.asList("icns");
diff --git a/imageio/imageio-icns/src/test/resources/icns/7zIcon.icns b/imageio/imageio-icns/src/test/resources/icns/7zIcon.icns
new file mode 100755
index 00000000..32f1cd6a
Binary files /dev/null and b/imageio/imageio-icns/src/test/resources/icns/7zIcon.icns differ