mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 20:15:28 -04:00
Cleaned up + added som references to doc.
This commit is contained in:
parent
b5fd17ba24
commit
5782c8c824
@ -54,10 +54,11 @@ import java.util.List;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: ICNSImageReader.java,v 1.0 25.10.11 18:42 haraldk Exp$
|
* @version $Id: ICNSImageReader.java,v 1.0 25.10.11 18:42 haraldk Exp$
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.macdisk.com/maciconen.php">Macintosh Icons</a>
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Apple_Icon_Image_format">Apple Icon Image format (Wikipedia)</a>
|
||||||
*/
|
*/
|
||||||
public final class ICNSImageReader extends ImageReaderBase {
|
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 static final int HEADER_SIZE = 8;
|
||||||
private List<IconHeader> icons = new ArrayList<IconHeader>();
|
private List<IconHeader> icons = new ArrayList<IconHeader>();
|
||||||
private List<IconHeader> masks = new ArrayList<IconHeader>();
|
private List<IconHeader> masks = new ArrayList<IconHeader>();
|
||||||
@ -102,13 +103,20 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
case 8:
|
case 8:
|
||||||
return IndexedImageTypeSpecifier.createFromIndexColorModel(ICNS8BitColorModel.INSTANCE);
|
return IndexedImageTypeSpecifier.createFromIndexColorModel(ICNS8BitColorModel.INSTANCE);
|
||||||
case 32:
|
case 32:
|
||||||
int bandLen = header.size().width * header.size().height;
|
return ImageTypeSpecifier.createBanded(
|
||||||
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);
|
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||||
|
new int[]{0, 1, 2, 3}, createBandOffsets(header.size().width * header.size().height),
|
||||||
|
DataBuffer.TYPE_BYTE, true, false
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException(String.format("Unknown bit depth: %d", header.depth()));
|
throw new IllegalStateException(String.format("Unknown bit depth: %d", header.depth()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int[] createBandOffsets(int bandLen) {
|
||||||
|
return new int[]{0, bandLen, 2 * bandLen, 3 * bandLen};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
@ -118,14 +126,9 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
switch (header.depth()) {
|
switch (header.depth()) {
|
||||||
case 1:
|
case 1:
|
||||||
// break;
|
|
||||||
// TODO: Fall through & convert during read?
|
|
||||||
case 4:
|
case 4:
|
||||||
// break;
|
|
||||||
// TODO: Fall through & convert during read?
|
|
||||||
case 8:
|
case 8:
|
||||||
// break;
|
// Fall through & convert during read
|
||||||
// TODO: Fall through & convert during read?
|
|
||||||
case 32:
|
case 32:
|
||||||
specifiers.add(ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), 0xff0000, 0x00ff00, 0x0000ff, 0xff000000, DataBuffer.TYPE_INT, 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));
|
specifiers.add(ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
|
||||||
@ -164,7 +167,6 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
@Override
|
@Override
|
||||||
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||||
IconHeader header = readIconHeader(imageIndex);
|
IconHeader header = readIconHeader(imageIndex);
|
||||||
// System.err.println("header: " + header);
|
|
||||||
|
|
||||||
imageInput.seek(header.start + HEADER_SIZE);
|
imageInput.seek(header.start + HEADER_SIZE);
|
||||||
|
|
||||||
@ -184,6 +186,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
BufferedImage image = getDestination(param, getImageTypes(imageIndex), width, height);
|
BufferedImage image = getDestination(param, getImageTypes(imageIndex), width, height);
|
||||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
|
|
||||||
if (rawType instanceof IndexedImageTypeSpecifier && rawType.getBufferedImageType() != image.getType()) {
|
if (rawType instanceof IndexedImageTypeSpecifier && rawType.getBufferedImageType() != image.getType()) {
|
||||||
checkReadParamBandSettings(param, 4, image.getSampleModel().getNumBands());
|
checkReadParamBandSettings(param, 4, image.getSampleModel().getNumBands());
|
||||||
}
|
}
|
||||||
@ -206,11 +209,14 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
int packedSize = header.length - HEADER_SIZE;
|
int packedSize = header.length - HEADER_SIZE;
|
||||||
|
|
||||||
if (width >= 128 && height >= 128) {
|
if (width >= 128 && height >= 128) {
|
||||||
|
// http://www.macdisk.com/maciconen.php:
|
||||||
|
// "In some icon sizes, there is a 32bit integer at the beginning of the run, whose role remains unknown."
|
||||||
imageInput.skipBytes(4); // Seems to be 4 byte 0-pad
|
imageInput.skipBytes(4); // Seems to be 4 byte 0-pad
|
||||||
packedSize -= 4;
|
packedSize -= 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize);
|
InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data
|
decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data
|
||||||
}
|
}
|
||||||
@ -223,35 +229,28 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
imageInput.readFully(data);
|
imageInput.readFully(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (header.depth()) {
|
|
||||||
case 1:
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
break;
|
|
||||||
case 32:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException(String.format("Unknown bit depth for icon: %d", header.depth()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.depth() == 1) {
|
if (header.depth() == 1) {
|
||||||
|
// Binary
|
||||||
DataBufferByte buffer = new DataBufferByte(data, data.length / 2, 0);
|
DataBufferByte buffer = new DataBufferByte(data, data.length / 2, 0);
|
||||||
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, header.depth(), null);
|
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, header.depth(), null);
|
||||||
|
|
||||||
if (image.getType() == rawType.getBufferedImageType() && ((IndexColorModel) image.getColorModel()).getMapSize() == 2) {
|
if (image.getType() == rawType.getBufferedImageType() && ((IndexColorModel) image.getColorModel()).getMapSize() == 2) {
|
||||||
|
// Preserve raw data as read (binary), discard mask
|
||||||
image.setData(raster);
|
image.setData(raster);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// Convert to 32 bit ARGB
|
||||||
DataBufferByte maskBuffer = new DataBufferByte(data, data.length / 2, data.length / 2);
|
DataBufferByte maskBuffer = new DataBufferByte(data, data.length / 2, data.length / 2);
|
||||||
WritableRaster mask = Raster.createPackedRaster(maskBuffer, width, height, header.depth(), null);
|
WritableRaster mask = Raster.createPackedRaster(maskBuffer, width, height, header.depth(), null);
|
||||||
|
|
||||||
Graphics2D graphics = image.createGraphics();
|
Graphics2D graphics = image.createGraphics();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Apply image data
|
||||||
BufferedImage temp = new BufferedImage(rawType.getColorModel(), raster, false, null);
|
BufferedImage temp = new BufferedImage(rawType.getColorModel(), raster, false, null);
|
||||||
graphics.drawImage(temp, 0, 0, null);
|
graphics.drawImage(temp, 0, 0, null);
|
||||||
|
|
||||||
|
// Apply mask
|
||||||
temp = new BufferedImage(ICNSBitMaskColorModel.INSTANCE, mask, false, null);
|
temp = new BufferedImage(ICNSBitMaskColorModel.INSTANCE, mask, false, null);
|
||||||
temp.setData(mask);
|
temp.setData(mask);
|
||||||
graphics.setComposite(AlphaComposite.DstIn);
|
graphics.setComposite(AlphaComposite.DstIn);
|
||||||
@ -263,14 +262,18 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (header.depth() <= 8) {
|
else if (header.depth() <= 8) {
|
||||||
|
// Indexed
|
||||||
DataBufferByte buffer = new DataBufferByte(data, data.length);
|
DataBufferByte buffer = new DataBufferByte(data, data.length);
|
||||||
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, header.depth(), null);
|
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, header.depth(), null);
|
||||||
|
|
||||||
if (image.getType() == rawType.getBufferedImageType()) {
|
if (image.getType() == rawType.getBufferedImageType()) {
|
||||||
|
// Preserve raw data as read (indexed), discard mask
|
||||||
image.setData(raster);
|
image.setData(raster);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// Convert to 32 bit ARGB
|
||||||
Graphics2D graphics = image.createGraphics();
|
Graphics2D graphics = image.createGraphics();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BufferedImage temp = new BufferedImage(rawType.getColorModel(), raster, false, null);
|
BufferedImage temp = new BufferedImage(rawType.getColorModel(), raster, false, null);
|
||||||
graphics.drawImage(temp, 0, 0, null);
|
graphics.drawImage(temp, 0, 0, null);
|
||||||
@ -281,12 +284,13 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageProgress(50f);
|
processImageProgress(50f);
|
||||||
|
|
||||||
// Look up/read mask from later IconHeader and apply
|
// Read mask and apply
|
||||||
Raster mask = readMask(findMask(header));
|
Raster mask = readMask(findMask(header));
|
||||||
image.getAlphaRaster().setRect(mask);
|
image.getAlphaRaster().setRect(mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// 32 bit ARGB (true color)
|
||||||
int bandLen = data.length / 4;
|
int bandLen = data.length / 4;
|
||||||
|
|
||||||
DataBufferByte buffer = new DataBufferByte(data, data.length);
|
DataBufferByte buffer = new DataBufferByte(data, data.length);
|
||||||
@ -295,7 +299,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageProgress(75f);
|
processImageProgress(75f);
|
||||||
|
|
||||||
// Read mask from later IconHeader and apply
|
// Read mask and apply
|
||||||
Raster mask = readMask(findMask(header));
|
Raster mask = readMask(findMask(header));
|
||||||
image.getAlphaRaster().setRect(mask);
|
image.getAlphaRaster().setRect(mask);
|
||||||
}
|
}
|
||||||
@ -404,8 +408,22 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://www.macdisk.com/maciconen.php
|
/*
|
||||||
// TODO: Is this really packbits?! Don't think so, but it's very close...
|
* 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 {
|
static void decompress(final DataInputStream input, final byte[] result, int offset, int length) throws IOException {
|
||||||
int resultPos = offset;
|
int resultPos = offset;
|
||||||
int remaining = length;
|
int remaining = length;
|
||||||
@ -415,16 +433,17 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
int runLength;
|
int runLength;
|
||||||
|
|
||||||
if ((run & 0x80) != 0) {
|
if ((run & 0x80) != 0) {
|
||||||
// Repeated run
|
// Compressed run
|
||||||
runLength = run + 131; // Packbits: -run + 1 and run == 0x80 is no-op... This allows 1 byte longer runs...
|
runLength = run + 131; // PackBits: -run + 1 and run == 0x80 is no-op... This allows 1 byte longer runs...
|
||||||
|
|
||||||
byte runData = input.readByte();
|
byte runData = input.readByte();
|
||||||
|
|
||||||
for (int i = 0; i < runLength; i++) {
|
for (int i = 0; i < runLength; i++) {
|
||||||
result[resultPos++] = runData;
|
result[resultPos++] = runData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Literal run
|
// Uncompressed run
|
||||||
runLength = run + 1;
|
runLength = run + 1;
|
||||||
|
|
||||||
input.readFully(result, resultPos, runLength);
|
input.readFully(result, resultPos, runLength);
|
||||||
@ -541,7 +560,6 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
validateLengthForType(type, length, 1024);
|
validateLengthForType(type, length, 1024);
|
||||||
break;
|
break;
|
||||||
case ICNS.ich_:
|
case ICNS.ich_:
|
||||||
// validateLengthForType(type, length, 288);
|
|
||||||
validateLengthForType(type, length, 576);
|
validateLengthForType(type, length, 576);
|
||||||
break;
|
break;
|
||||||
case ICNS.ich4:
|
case ICNS.ich4:
|
||||||
@ -627,7 +645,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
public int depth() {
|
public int depth() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ICNS.ICON:
|
case ICNS.ICON:
|
||||||
case ICNS.ICN_: // Specical case? Wikipedi say 1 bit + 1 bit mask
|
case ICNS.ICN_:
|
||||||
case ICNS.icm_:
|
case ICNS.icm_:
|
||||||
case ICNS.ics_:
|
case ICNS.ics_:
|
||||||
case ICNS.ich_:
|
case ICNS.ich_:
|
||||||
@ -677,7 +695,12 @@ public final class ICNSImageReader extends ImageReaderBase {
|
|||||||
case ICNS.il32:
|
case ICNS.il32:
|
||||||
case ICNS.ih32:
|
case ICNS.ih32:
|
||||||
case ICNS.it32:
|
case ICNS.it32:
|
||||||
return true;
|
// 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) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user