/*
* ICODecoder.java
*
* Created on May 9, 2006, 9:31 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.inet.gradle.setup.image.image4j.codec.ico;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.IOException;
import com.inet.gradle.setup.image.image4j.codec.bmp.BMPDecoder;
import com.inet.gradle.setup.image.image4j.codec.bmp.ColorEntry;
import com.inet.gradle.setup.image.image4j.codec.bmp.InfoHeader;
/**
* Decodes images in ICO format.
* @author Ian McDonagh
*/
public class ICODecoder {
private static final int PNG_MAGIC = 0x89504E47;
private static final int PNG_MAGIC_LE = 0x474E5089;
private static final int PNG_MAGIC2 = 0x0D0A1A0A;
private static final int PNG_MAGIC2_LE = 0x0A1A0A0D;
//private java.util.List<BufferedImage> img;
private ICODecoder() { }
/**
* Reads and decodes the given ICO file. Convenience method equivalent to {@link #read(java.io.InputStream) read(new java.io.FileInputStream(file))}.
* @param file the source file to read
* @return the list of images decoded from the ICO data
* @throws java.io.IOException if an error occurs
*/
public static java.util.List<BufferedImage> read(java.io.File file) throws IOException {
return read(new java.io.FileInputStream(file));
}
/**
* Reads and decodes the given ICO file, together with all metadata.
* Convenience method equivalent to {@link #readExt(java.io.InputStream) readExt(new java.io.FileInputStream(file))}.
* @param file the source file to read
* @return the list of images decoded from the ICO data
* @throws java.io.IOException if an error occurs
* @since 0.7
*/
public static java.util.List<ICOImage> readExt(java.io.File file) throws IOException {
return readExt(new java.io.FileInputStream(file));
}
/**
* Reads and decodes ICO data from the given source.
* The returned list of images is in the order in which they appear in the source ICO data.
* @param is the source <tt>InputStream</tt> to read
* @return the list of images decoded from the ICO data
* @throws java.io.IOException if an error occurs
*/
public static java.util.List<BufferedImage> read(java.io.InputStream is) throws IOException {
java.util.List<ICOImage> list = readExt(is);
java.util.List<BufferedImage> ret = new java.util.ArrayList<BufferedImage>(list.size());
for (int i = 0; i < list.size(); i++) {
ICOImage icoImage = list.get(i);
BufferedImage image = icoImage.getImage();
ret.add(image);
}
return ret;
}
/**
* Reads and decodes ICO data from the given source, together with all metadata.
* The returned list of images is in the order in which they appear in the source ICO data.
* @param is the source <tt>InputStream</tt> to read
* @return the list of images decoded from the ICO data
* @throws java.io.IOException if an error occurs
* @since 0.7
*/
public static java.util.List<ICOImage> readExt(java.io.InputStream is) throws IOException {
//long t = System.currentTimeMillis();
com.inet.gradle.setup.image.image4j.io.LittleEndianInputStream in = new com.inet.gradle.setup.image.image4j.io.LittleEndianInputStream(is);
//Reserved 2 byte =0
short sReserved = in.readShortLE();
//Type 2 byte =1
short sType = in.readShortLE();
//Count 2 byte Number of Icons in this file
short sCount = in.readShortLE();
//Entries Count * 16 list of icons
IconEntry[] entries = new IconEntry[sCount];
for (short s = 0; s < sCount; s++) {
entries[s] = new IconEntry(in);
}
int i = 0;
//images list of bitmap structures in BMP format
java.util.List<ICOImage> ret = new java.util.ArrayList<ICOImage>(sCount);
try {
for (i = 0; i < sCount; i++) {
int info = in.readIntLE();
if (info == 40) {
//read XOR bitmap
//BMPDecoder bmp = new BMPDecoder(is);
InfoHeader infoHeader = BMPDecoder.readInfoHeader(in, info);
InfoHeader andHeader = new InfoHeader(infoHeader);
andHeader.iHeight = (int) (infoHeader.iHeight / 2);
InfoHeader xorHeader = new InfoHeader(infoHeader);
xorHeader.iHeight = andHeader.iHeight;
andHeader.sBitCount = 1;
andHeader.iNumColors = 2;
//for now, just read all the raster data (xor + and)
// and store as separate images
BufferedImage xor = BMPDecoder.read(xorHeader, in);
//img.add(xor);
BufferedImage img = new BufferedImage(
xorHeader.iWidth, xorHeader.iHeight,
BufferedImage.TYPE_INT_ARGB
);
ColorEntry[] andColorTable = new ColorEntry[] {
new ColorEntry(255, 255, 255, 255),
new ColorEntry(0, 0, 0, 0)
};
if (infoHeader.sBitCount == 32) {
//transparency from alpha
//ignore bytes after XOR bitmap
int size = entries[i].iSizeInBytes;
int infoHeaderSize = infoHeader.iSize;
// data size = w * h * 4
int dataSize = xorHeader.iWidth * xorHeader.iHeight * 4;
int skip = size - infoHeaderSize - dataSize;
//ignore AND bitmap since alpha channel stores transparency
int skipped = in.skipBytes(skip);
int s = skip;
while (skipped < s) {
if (skipped < 0) {
throw new IOException("Failed to read [skip]");
}
s = skip - skipped;
skipped = in.skipBytes(s);
}
////read AND bitmap
//BufferedImage and = BMPDecoder.read(andHeader, in, andColorTable);
//this.img.add(and);
WritableRaster srgb = xor.getRaster();
WritableRaster salpha = xor.getAlphaRaster();
WritableRaster rgb = img.getRaster();
WritableRaster alpha = img.getAlphaRaster();
for (int y = xorHeader.iHeight - 1; y >= 0; y--) {
for (int x = 0; x < xorHeader.iWidth; x++) {
int r = srgb.getSample(x, y, 0);
int g = srgb.getSample(x, y, 1);
int b = srgb.getSample(x, y, 2);
int a = salpha.getSample(x, y, 0);
rgb.setSample(x, y, 0, r);
rgb.setSample(x, y, 1, g);
rgb.setSample(x, y, 2, b);
alpha.setSample(x, y, 0, a);
}
}
} else {
BufferedImage and = BMPDecoder.read(andHeader, in, andColorTable);
//img.add(and);
//copy rgb
WritableRaster srgb = xor.getRaster();
WritableRaster rgb = img.getRaster();
//copy alpha
WritableRaster alpha = img.getAlphaRaster();
WritableRaster salpha = and.getRaster();
for (int y = 0; y < xorHeader.iHeight; y++) {
for (int x = 0; x < xorHeader.iWidth; x++) {
int r, g, b;
int c = xor.getRGB(x, y);
r = (c >> 16) & 0xFF;
g = (c >> 8) & 0xFF;
b = (c) & 0xFF;
//red
rgb.setSample(x, y, 0, r);
//green
rgb.setSample(x, y, 1, g);
//blue
rgb.setSample(x, y, 2, b);
//System.out.println(x+","+y+"="+Integer.toHexString(c));
//img.setRGB(x, y, c);
//alpha
int a = and.getRGB(x, y);
alpha.setSample(x, y, 0, a);
}
}
}
// create ICOImage
IconEntry iconEntry = entries[i];
ICOImage icoImage = new ICOImage(img, infoHeader, iconEntry);
icoImage.setPngCompressed(false);
icoImage.setIconIndex(i);
ret.add(icoImage);
}
//check for PNG magic header and that image height and width = 0 = 256 -> Vista format
else if (info == PNG_MAGIC_LE) {
int info2 = in.readIntLE();
if (info2 != PNG_MAGIC2_LE) {
throw new IOException("Unrecognized icon format for image #"+i);
}
IconEntry e = entries[i];
byte[] pngData = new byte[e.iSizeInBytes - 8];
int count = in.read(pngData);
if (count != pngData.length) {
throw new IOException("Unable to read image #"+i+" - incomplete PNG compressed data");
}
java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream();
java.io.DataOutputStream dout = new java.io.DataOutputStream(bout);
dout.writeInt(PNG_MAGIC);
dout.writeInt(PNG_MAGIC2);
dout.write(pngData);
byte[] pngData2 = bout.toByteArray();
java.io.ByteArrayInputStream bin = new java.io.ByteArrayInputStream(pngData2);
javax.imageio.stream.ImageInputStream input = javax.imageio.ImageIO.createImageInputStream(bin);
javax.imageio.ImageReader reader = getPNGImageReader();
reader.setInput(input);
java.awt.image.BufferedImage img = reader.read(0);
// create ICOImage
IconEntry iconEntry = entries[i];
ICOImage icoImage = new ICOImage(img, null, iconEntry);
icoImage.setPngCompressed(true);
icoImage.setIconIndex(i);
ret.add(icoImage);
}
else {
throw new IOException("Unrecognized icon format for image #"+i);
}
/*
InfoHeader andInfoHeader = new InfoHeader();
andInfoHeader.iColorsImportant = 0;
andInfoHeader.iColorsUsed = 0;
andInfoHeader.iCompression = BMPConstants.BI_RGB;
andInfoHeader.iHeight = xorInfoHeader.iHeight / 2;
andInfoHeader.iWidth = xorInfoHeader.
*/
}
} catch (IOException ex) {
throw new IOException("Failed to read image # "+i);
}
//long t2 = System.currentTimeMillis();
//System.out.println("Loaded ICO file in "+(t2 - t)+"ms");
return ret;
}
private static javax.imageio.ImageReader getPNGImageReader() {
javax.imageio.ImageReader ret = null;
java.util.Iterator<javax.imageio.ImageReader> itr = javax.imageio.ImageIO.getImageReadersByFormatName("png");
if (itr.hasNext()) {
ret = itr.next();
}
return ret;
}
}