Return-Path: Delivered-To: apmail-commons-commits-archive@minotaur.apache.org Received: (qmail 3967 invoked from network); 9 Sep 2010 17:45:57 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 9 Sep 2010 17:45:57 -0000 Received: (qmail 82307 invoked by uid 500); 9 Sep 2010 17:45:57 -0000 Delivered-To: apmail-commons-commits-archive@commons.apache.org Received: (qmail 82250 invoked by uid 500); 9 Sep 2010 17:45:56 -0000 Mailing-List: contact commits-help@commons.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@commons.apache.org Delivered-To: mailing list commits@commons.apache.org Received: (qmail 82243 invoked by uid 99); 9 Sep 2010 17:45:56 -0000 Received: from Unknown (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 09 Sep 2010 17:45:56 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 09 Sep 2010 17:45:35 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 7F69223889E9; Thu, 9 Sep 2010 17:45:12 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r995515 - in /commons/proper/sanselan/trunk/src: main/java/org/apache/sanselan/formats/bmp/ main/java/org/apache/sanselan/formats/ico/ test/java/org/apache/sanselan/formats/ico/ test/java/org/apache/sanselan/roundtrip/ Date: Thu, 09 Sep 2010 17:45:12 -0000 To: commits@commons.apache.org From: cmchen@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100909174512.7F69223889E9@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: cmchen Date: Thu Sep 9 17:45:11 2010 New Revision: 995515 URL: http://svn.apache.org/viewvc?rev=995515&view=rev Log: "Improve ICO file support" patch from Damjan Jovanovic. Jira issue key: SANSELAN-40 Damjan's notes, part 1: I am submitting a patch that improves Sanselan's ICO format support. It adds: 1. Support for Windows Vista ICO files with embedded PNGs 2. Support for 256x256 icons (reported as 0x0) 3. Support for top-down and bottom-up bitmaps Damjan's notes, part 2: New version of the patch. The patch now: Uses only the BITMAPINFOHEADER to parse the icon (using the ICONDIRENTRY is wrong and the tests included prove it) Adds support for writing ICO files Adds support for reading 16 BPP ICO files Adds support for compressed bitmaps Adds support for the Windows Vista (and later) PNG ICO format Verifies crucial fields in ICO files Adds extensive tests In fact the only feature now missing is support for the alpha channel in 32 BPP bitmaps, and that is something that needs to be added to BmpImageParser along with support for BITMAPV4HEADER/BITMAPV5HEADER anyway. Added: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoBaseTest.java (with props) commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoReadTest.java (with props) commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoRoundtripTest.java (with props) Modified: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/bmp/BmpImageParser.java commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/ico/IcoImageParser.java commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java Modified: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/bmp/BmpImageParser.java URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/bmp/BmpImageParser.java?rev=995515&r1=995514&r2=995515&view=diff ============================================================================== --- commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/bmp/BmpImageParser.java (original) +++ commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/bmp/BmpImageParser.java Thu Sep 9 17:45:11 2010 @@ -568,6 +568,12 @@ public class BmpImageParser extends Imag public BufferedImage getBufferedImage(ByteSource byteSource, Map params) throws ImageReadException, IOException { + return getBufferedImage(byteSource.getInputStream(), params); + } + + public BufferedImage getBufferedImage(InputStream inputStream, Map params) + throws ImageReadException, IOException + { // make copy of params; we'll clear keys as we consume them. params = (params == null) ? new HashMap() : new HashMap(params); @@ -585,7 +591,7 @@ public class BmpImageParser extends Imag throw new ImageReadException("Unknown parameter: " + firstKey); } - ImageContents ic = readImageContents(byteSource.getInputStream(), + ImageContents ic = readImageContents(inputStream, FormatCompliance.getDefault(), verbose); if (ic == null) throw new ImageReadException("Couldn't read BMP Data"); @@ -710,4 +716,4 @@ public class BmpImageParser extends Imag return null; } -} \ No newline at end of file +} Modified: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/ico/IcoImageParser.java URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/ico/IcoImageParser.java?rev=995515&r1=995514&r2=995515&view=diff ============================================================================== --- commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/ico/IcoImageParser.java (original) +++ commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/ico/IcoImageParser.java Thu Sep 9 17:45:11 2010 @@ -23,18 +23,28 @@ import java.awt.image.DataBufferInt; import java.awt.image.Raster; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.Map; import org.apache.sanselan.ImageFormat; import org.apache.sanselan.ImageInfo; import org.apache.sanselan.ImageParser; import org.apache.sanselan.ImageReadException; +import org.apache.sanselan.ImageWriteException; +import org.apache.sanselan.Sanselan; +import org.apache.sanselan.formats.bmp.BmpImageParser; +import org.apache.sanselan.common.BinaryOutputStream; import org.apache.sanselan.common.IImageMetadata; import org.apache.sanselan.common.byteSources.ByteSource; +import org.apache.sanselan.palette.PaletteFactory; +import org.apache.sanselan.palette.SimplePalette; import org.apache.sanselan.util.Debug; public class IcoImageParser extends ImageParser @@ -136,6 +146,11 @@ public class IcoImageParser extends Imag int IconType = read2Bytes("IconType", is, "Not a Valid ICO File"); int IconCount = read2Bytes("IconCount", is, "Not a Valid ICO File"); + if (Reserved != 0) + throw new ImageReadException("Not a Valid ICO File: reserved is " + Reserved); + if (IconType != 1 && IconType != 2) + throw new ImageReadException("Not a Valid ICO File: icon type is " + IconType); + return new FileHeader(Reserved, IconType, IconCount); } @@ -250,60 +265,76 @@ public class IcoImageParser extends Imag } } - private static class IconData + private static abstract class IconData { public final IconInfo iconInfo; - public final BitmapHeader header; - public final byte palette[]; - public final byte color_map[]; - public final int scanline_size; - public final byte transparency_map[]; - public final int t_scanline_size; - - public IconData(final IconInfo iconInfo, final BitmapHeader header, - final byte[] palette, final byte[] color_map, - final int scanline_size, final byte[] transparency_map, - final int t_scanline_size) + + public IconData(final IconInfo iconInfo) { - super(); this.iconInfo = iconInfo; + } + + public abstract BufferedImage readBufferedImage() throws ImageReadException; + } + + private static class BitmapIconData extends IconData + { + public final BitmapHeader header; + public final BufferedImage bufferedImage; + + public BitmapIconData(final IconInfo iconInfo, final BitmapHeader header, + final BufferedImage bufferedImage) + { + super(iconInfo); this.header = header; - this.palette = palette; - this.color_map = color_map; - this.scanline_size = scanline_size; - this.transparency_map = transparency_map; - this.t_scanline_size = t_scanline_size; + this.bufferedImage = bufferedImage; + } + + public BufferedImage readBufferedImage() throws ImageReadException + { + return bufferedImage; } public void dump() { - System.out.println("IconData"); + System.out.println("BitmapIconData"); iconInfo.dump(); header.dump(); + } + } + + private static class PNGIconData extends IconData + { + public final BufferedImage bufferedImage; - System.out.println("scanline_size: " + scanline_size); - System.out.println("t_scanline_size: " + t_scanline_size); - System.out.println("palette: " - + ((palette == null) ? "null" : "" + palette.length)); - System.out.println("color_map: " - + ((color_map == null) ? "null" : "" + color_map.length)); - System.out.println("transparency_map: " - + ((transparency_map == null) ? "null" : "" - + transparency_map.length)); + public PNGIconData(final IconInfo iconInfo, final BufferedImage bufferedImage) + { + super(iconInfo); + this.bufferedImage = bufferedImage; + } - System.out.println(""); + public BufferedImage readBufferedImage() + { + return bufferedImage; + } + + public void dump() + { + System.out.println("PNGIconData"); + + iconInfo.dump(); } } - private IconData readIconData(InputStream is, IconInfo fIconInfo) - throws ImageReadException, IOException + private IconData readBitmapIconData(byte[] iconData, IconInfo fIconInfo) throws ImageReadException, ImageWriteException, IOException { + ByteArrayInputStream is = new ByteArrayInputStream(iconData); int Size = read4Bytes("Size", is, "Not a Valid ICO File"); // Size (4 bytes), size of this structure (always 40) int Width = read4Bytes("Width", is, "Not a Valid ICO File"); // Width (4 bytes), width of the image (same as iconinfo.width) int Height = read4Bytes("Height", is, "Not a Valid ICO File"); // Height (4 bytes), scanlines in the color map + transparent map (iconinfo.height * 2) int Planes = read2Bytes("Planes", is, "Not a Valid ICO File"); // Planes (2 bytes), always 1 - int BitCount = read2Bytes("BitCount", is, "Not a Valid ICO File"); // BitCount (2 bytes), 1,4,8,24,32 (see iconinfo for details) + int BitCount = read2Bytes("BitCount", is, "Not a Valid ICO File"); // BitCount (2 bytes), 1,4,8,16,24,32 (see iconinfo for details) int Compression = read4Bytes("Compression", is, "Not a Valid ICO File"); // Compression (4 bytes), we don?t use this (0) int SizeImage = read4Bytes("SizeImage", is, "Not a Valid ICO File"); // SizeImage (4 bytes), we don?t use this (0) int XPelsPerMeter = read4Bytes("XPelsPerMeter", is, @@ -314,50 +345,125 @@ public class IcoImageParser extends Imag int ColorsImportant = read4Bytes("ColorsImportant", is, "Not a Valid ICO File"); // ColorsImportant (4 bytes), we don?t use this (0) + if (Size != 40) + throw new ImageReadException("Not a Valid ICO File: Wrong bitmap header size " + Size); + if (Planes != 1) + throw new ImageReadException("Not a Valid ICO File: Planes can't be " + Planes); + BitmapHeader header = new BitmapHeader(Size, Width, Height, Planes, BitCount, Compression, SizeImage, XPelsPerMeter, YPelsPerMeter, ColorsUsed, ColorsImportant); - // System.out.println("XXXXXXXXXXXXXXXXXXXX"); - // fIconInfo.dump(); - // header.dump(); - // System.out.println("XXXXXXXXXXXXXXXXXXXX"); - - int palette_size = 0; - byte palette[] = null; - - if ((fIconInfo.BitCount == 1) || (fIconInfo.BitCount == 4) - || (fIconInfo.BitCount == 8)) + int bitmapPixelsOffset = 14 + 40 + ((Compression == 3) ? 3*4 : 0) + + 4 * ((ColorsUsed == 0 && BitCount <= 8) ? (1 << BitCount) : ColorsUsed); + int bitmapSize = 14 + iconData.length; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(14 + iconData.length); + BinaryOutputStream bos = new BinaryOutputStream(baos, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + + bos.write((int)'B'); + bos.write((int)'M'); + bos.write4Bytes(bitmapSize); + bos.write4Bytes(0); + bos.write4Bytes(bitmapPixelsOffset); + + bos.write4Bytes(Size); + bos.write4Bytes(Width); + bos.write4Bytes(Height / 2); + bos.write2Bytes(Planes); + bos.write2Bytes(BitCount); + bos.write4Bytes(Compression); + bos.write4Bytes(SizeImage); + bos.write4Bytes(XPelsPerMeter); + bos.write4Bytes(YPelsPerMeter); + bos.write4Bytes(ColorsUsed); + bos.write4Bytes(ColorsImportant); + bos.write(iconData, 40, iconData.length - 40); + bos.flush(); + + ByteArrayInputStream bmpInputStream = new ByteArrayInputStream(baos.toByteArray()); + BufferedImage bmpImage = new BmpImageParser().getBufferedImage(bmpInputStream, null); + + // Transparency map is optional with 32 BPP icons, because they already have + // an alpha channel, and Windows only uses the transparency map when it has to + // display the icon on a < 32 BPP screen. But it's still used instead of alpha + // if the image would be completely transparent with alpha... + int t_scanline_size = (Width + 7) / 8; + if ((t_scanline_size % 4) != 0) + t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4 byte size. + int tcolor_map_size_bytes = t_scanline_size * (Height/2); + byte[] transparency_map = null; + try { - palette_size = 4 * (1 << fIconInfo.BitCount); - palette = this.readByteArray("palette", palette_size, is, - "Not a Valid ICO File"); + transparency_map = this.readByteArray("transparency_map", + tcolor_map_size_bytes, bmpInputStream, "Not a Valid ICO File"); } - - byte color_map[] = null; - int scanline_size = (fIconInfo.BitCount * fIconInfo.Width + 7) / 8; - if ((scanline_size % 4) != 0) - scanline_size += 4 - (scanline_size % 4); // pad scanline to 4 byte size. - - int color_map_size_bytes = scanline_size * fIconInfo.Height; + catch (IOException ioEx) { - color_map = this.readByteArray("color_map", color_map_size_bytes, - is, "Not a Valid ICO File"); + if (BitCount != 32) + throw ioEx; } - byte transparency_map[] = null; - int t_scanline_size = (fIconInfo.Width + 7) / 8; - if ((t_scanline_size % 4) != 0) - t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4 byte size. - - int tcolor_map_size_bytes = t_scanline_size * fIconInfo.Height; + // FIXME: get BmpImageParser to support alpha, then uncomment below +// boolean allAlphasZero = true; +// if (BitCount == 32) +// { +// for (int y = 0; allAlphasZero && y < bmpImage.getHeight(); y++) +// { +// for (int x = 0; x < bmpImage.getWidth(); x++) +// { +// if ((bmpImage.getRGB(x, y) & 0xff000000) != 0) +// { +// allAlphasZero = false; +// break; +// } +// } +// } +// } + BufferedImage resultImage = new BufferedImage(bmpImage.getWidth(), bmpImage.getHeight(), + BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < resultImage.getHeight(); y++) { - transparency_map = this.readByteArray("transparency_map", - tcolor_map_size_bytes, is, "Not a Valid ICO File"); + for (int x = 0; x < resultImage.getWidth(); x++) + { + int alpha = 0xff; + if (transparency_map != null) + { + int alpha_byte = 0xff & transparency_map[t_scanline_size + * (bmpImage.getHeight() - y - 1) + (x / 8)]; + alpha = 0x01 & (alpha_byte >> (7 - (x % 8))); + alpha = (alpha == 0) ? 0xff : 0x00; + } + // FIXME: get the BMP decoder to support alpha, then uncomment below + //if (BitCount < 32 || allAlphasZero) + resultImage.setRGB(x, y, (alpha << 24) | (0xffffff & bmpImage.getRGB(x, y))); + } } + return new BitmapIconData(fIconInfo, header, resultImage); + } - return new IconData(fIconInfo, header, palette, color_map, - scanline_size, transparency_map, t_scanline_size); + private IconData readIconData(byte[] iconData, IconInfo fIconInfo) + throws ImageReadException, IOException + { + ImageFormat imageFormat = Sanselan.guessFormat(iconData); + if (imageFormat.equals(ImageFormat.IMAGE_FORMAT_PNG)) + { + BufferedImage bufferedImage = Sanselan.getBufferedImage(iconData); + PNGIconData pngIconData = new PNGIconData(fIconInfo, bufferedImage); + return pngIconData; + } + else + { + try + { + return readBitmapIconData(iconData, fIconInfo); + } + catch (ImageWriteException imageWriteException) + { + throw new IOException(imageWriteException); + } + } } private static class ImageContents @@ -395,7 +501,9 @@ public class IcoImageParser extends Imag IconData fIconDatas[] = new IconData[fileHeader.iconCount]; for (int i = 0; i < fileHeader.iconCount; i++) { - fIconDatas[i] = readIconData(is, fIconInfos[i]); + byte[] iconData = byteSource.getBlock(fIconInfos[i].ImageOffset, + fIconInfos[i].ImageSize); + fIconDatas[i] = readIconData(iconData, fIconInfos[i]); // fIconDatas[i].dump(); } @@ -418,82 +526,12 @@ public class IcoImageParser extends Imag public final BufferedImage getBufferedImage(ByteSource byteSource, Map params) throws ImageReadException, IOException { - throw new ImageReadException( - "Use getAllBufferedImages() instead for .ico images."); - } - - private BufferedImage readBufferedImage(IconData fIconData) - throws ImageReadException - { - IconInfo fIconInfo = fIconData.iconInfo; - - int width = fIconInfo.Width; - int height = fIconInfo.Height; - int data[] = new int[width * height]; - - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - int rgb; - - int alpha_byte = 0xff & fIconData.transparency_map[fIconData.t_scanline_size - * y + (x / 8)]; - int alpha = 0x01 & (alpha_byte >> (7 - (x % 8))); - alpha = (alpha == 0) ? 0xff : 0x00; - - if ((fIconInfo.BitCount == 1) || (fIconInfo.BitCount == 4) - || (fIconInfo.BitCount == 8)) - { - int bit_index = fIconData.scanline_size * y * 8 + x - * fIconInfo.BitCount; - int b = 0xff & fIconData.color_map[(bit_index >> 3)]; - int mask = (1 << fIconInfo.BitCount) - 1; - int shift = 8 - (bit_index % 8) - fIconInfo.BitCount; - int color_index = mask & (b >> shift); - - int red = 0xff & fIconData.palette[4 * color_index + 2]; - int green = 0xff & fIconData.palette[4 * color_index + 1]; - int blue = 0xff & fIconData.palette[4 * color_index + 0]; - rgb = ((0xff & red) << 16) | ((0xff & green) << 8) - | ((0xff & blue) << 0); - } - else if ((fIconInfo.BitCount == 24) - || (fIconInfo.BitCount == 32)) - { - int byte_count = fIconInfo.BitCount >> 3; - int index = fIconData.scanline_size * y + x * byte_count; - int red = 0xff & fIconData.color_map[index + 2]; - int green = 0xff & fIconData.color_map[index + 1]; - int blue = 0xff & fIconData.color_map[index + 0]; - - rgb = ((0xff & red) << 16) | ((0xff & green) << 8) - | ((0xff & blue) << 0); - } - else - throw new ImageReadException("Unknown BitCount: " - + fIconInfo.BitCount); - - int argb = ((alpha & 0xff) << 24) | (rgb & 0xffffff); - - // argb = 0xff000000 | argb; - - data[(height - y - 1) * width + x] = argb; - } - } - - ColorModel cModel = ColorModel.getRGBdefault(); - DataBufferInt intBuf = new DataBufferInt(data, (width * height)); - SampleModel sModel = cModel.createCompatibleSampleModel(width, height); - - // create our raster - WritableRaster raster = Raster.createWritableRaster(sModel, intBuf, - null); - - // now create and return our buffered image - BufferedImage result = new BufferedImage(cModel, raster, false, null); - - return result; + ImageContents contents = readImage(byteSource); + FileHeader fileHeader = contents.fileHeader; + if (fileHeader.iconCount > 0) + return contents.iconDatas[0].readBufferedImage(); + else + throw new ImageReadException("No icons in ICO file"); } public ArrayList getAllBufferedImages(ByteSource byteSource) @@ -507,7 +545,7 @@ public class IcoImageParser extends Imag { IconData iconData = contents.iconDatas[i]; - BufferedImage image = readBufferedImage(iconData); + BufferedImage image = iconData.readBufferedImage(); result.add(image); } @@ -539,6 +577,196 @@ public class IcoImageParser extends Imag // return true; // } + public void writeImage(BufferedImage src, OutputStream os, Map params) + throws ImageWriteException, IOException + { + // make copy of params; we'll clear keys as we consume them. + params = (params == null) ? new HashMap() : new HashMap(params); + + // clear format key. + if (params.containsKey(PARAM_KEY_FORMAT)) + params.remove(PARAM_KEY_FORMAT); + + if (params.size() > 0) + { + Object firstKey = params.keySet().iterator().next(); + throw new ImageWriteException("Unknown parameter: " + firstKey); + } + + final PaletteFactory paletteFactory = new PaletteFactory(); + final SimplePalette palette = paletteFactory.makePaletteSimple(src, 256); + final int bitCount; + final boolean hasTransparency = paletteFactory.hasTransparency(src); + if (palette == null) + { + if (hasTransparency) + bitCount = 32; + else + bitCount = 24; + } + else if (palette.length() <= 2) + bitCount = 1; + else if (palette.length() <= 16) + bitCount = 4; + else + bitCount = 8; + + BinaryOutputStream bos = new BinaryOutputStream(os, BYTE_ORDER_INTEL); + + int scanline_size = (bitCount * src.getWidth() + 7) / 8; + if ((scanline_size % 4) != 0) + scanline_size += 4 - (scanline_size % 4); // pad scanline to 4 byte size. + int t_scanline_size = (src.getWidth() + 7) / 8; + if ((t_scanline_size % 4) != 0) + t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4 byte size. + int imageSize = 40 + 4 * (bitCount <= 8 ? (1 << bitCount) : 0) + + src.getHeight() * scanline_size + + src.getHeight() * t_scanline_size; + + // ICONDIR + bos.write2Bytes(0); // reserved + bos.write2Bytes(1); // 1=ICO, 2=CUR + bos.write2Bytes(1); // count + + // ICONDIRENTRY + int iconDirEntryWidth = src.getWidth(); + int iconDirEntryHeight = src.getHeight(); + if (iconDirEntryWidth > 255 || iconDirEntryHeight > 255) + { + iconDirEntryWidth = 0; + iconDirEntryHeight = 0; + } + bos.write(iconDirEntryWidth); + bos.write(iconDirEntryHeight); + bos.write((bitCount >= 8) ? 0 : (1 << bitCount)); + bos.write(0); // reserved + bos.write2Bytes(1); // color planes + bos.write2Bytes(bitCount); + bos.write4Bytes(imageSize); + bos.write4Bytes(22); // image offset + + // BITMAPINFOHEADER + bos.write4Bytes(40); // size + bos.write4Bytes(src.getWidth()); + bos.write4Bytes(2 * src.getHeight()); + bos.write2Bytes(1); // planes + bos.write2Bytes(bitCount); + bos.write4Bytes(0); // compression + bos.write4Bytes(0); // image size + bos.write4Bytes(0); // x pixels per meter + bos.write4Bytes(0); // y pixels per meter + bos.write4Bytes(0); // colors used, 0 = (1 << bitCount) (ignored) + bos.write4Bytes(0); // colors important + + if (palette != null) + { + for (int i = 0; i < (1 << bitCount); i++) + { + if (i < palette.length()) + { + int argb = palette.getEntry(i); + bos.write(0xff & argb); + bos.write(0xff & (argb >> 8)); + bos.write(0xff & (argb >> 16)); + bos.write(0); + } + else + { + bos.write(0); + bos.write(0); + bos.write(0); + bos.write(0); + } + } + } + + int bit_cache = 0; + int bits_in_cache = 0; + int row_padding = scanline_size - (bitCount * src.getWidth() + 7) / 8; + for (int y = src.getHeight() - 1; y >= 0; y--) + { + for (int x = 0; x < src.getHeight(); x++) + { + int argb = src.getRGB(x, y); + if (bitCount < 8) + { + int rgb = 0xffffff & argb; + int index = palette.getPaletteIndex(rgb); + bit_cache <<= bitCount; + bit_cache |= index; + bits_in_cache += bitCount; + if (bits_in_cache >= 8) + { + bos.write(0xff & bit_cache); + bit_cache = 0; + bits_in_cache = 0; + } + } + else if (bitCount == 8) + { + int rgb = 0xffffff & argb; + int index = palette.getPaletteIndex(rgb); + bos.write(0xff & index); + } + else if (bitCount == 24) + { + bos.write(0xff & argb); + bos.write(0xff & (argb >> 8)); + bos.write(0xff & (argb >> 16)); + } + else if (bitCount == 32) + { + bos.write(0xff & argb); + bos.write(0xff & (argb >> 8)); + bos.write(0xff & (argb >> 16)); + bos.write(0xff & (argb >> 24)); + } + } + + if (bits_in_cache > 0) + { + bit_cache <<= (8 - bits_in_cache); + bos.write(0xff & bit_cache); + bit_cache = 0; + bits_in_cache = 0; + } + + for (int x = 0; x < row_padding; x++) + bos.write(0); + } + + int t_row_padding = t_scanline_size - (src.getWidth() + 7) / 8; + for (int y = src.getHeight() - 1; y >= 0; y--) + { + for (int x = 0; x < src.getWidth(); x++) + { + int argb = src.getRGB(x, y); + int alpha = 0xff & (argb >> 24); + bit_cache <<= 1; + if (alpha == 0) + bit_cache |= 1; + bits_in_cache++; + if (bits_in_cache >= 8) + { + bos.write(0xff & bit_cache); + bit_cache = 0; + bits_in_cache = 0; + } + } + + if (bits_in_cache > 0) + { + bit_cache <<= (8 - bits_in_cache); + bos.write(0xff & bit_cache); + bit_cache = 0; + bits_in_cache = 0; + } + + for (int x = 0; x < t_row_padding; x++) + bos.write(0); + } + } + /** * Extracts embedded XML metadata as XML string. *

@@ -553,4 +781,4 @@ public class IcoImageParser extends Imag throws ImageReadException, IOException { return null; } -} \ No newline at end of file +} Added: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoBaseTest.java URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoBaseTest.java?rev=995515&view=auto ============================================================================== --- commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoBaseTest.java (added) +++ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoBaseTest.java Thu Sep 9 17:45:11 2010 @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sanselan.formats.ico; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.apache.sanselan.ImageFormat; +import org.apache.sanselan.ImageReadException; +import org.apache.sanselan.Sanselan; +import org.apache.sanselan.SanselanTest; + +public abstract class IcoBaseTest extends SanselanTest +{ + + private static boolean isIco(File file) throws IOException, + ImageReadException + { + return file.getName().toLowerCase().endsWith(".ico") || + file.getName().toLowerCase().endsWith(".cur"); + } + + private static final ImageFilter IMAGE_FILTER = new ImageFilter() { + public boolean accept(File file) throws IOException, ImageReadException + { + return isIco(file); + } + }; + + protected List getIcoImages() throws IOException, ImageReadException + { + return getTestImages(IMAGE_FILTER); + } + + +} Propchange: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoBaseTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoReadTest.java URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoReadTest.java?rev=995515&view=auto ============================================================================== --- commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoReadTest.java (added) +++ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoReadTest.java Thu Sep 9 17:45:11 2010 @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sanselan.formats.ico; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.sanselan.ImageInfo; +import org.apache.sanselan.ImageReadException; +import org.apache.sanselan.ImageWriteException; +import org.apache.sanselan.Sanselan; +import org.apache.sanselan.common.IImageMetadata; +import org.apache.sanselan.util.Debug; + +public class IcoReadTest extends IcoBaseTest +{ + + public void test() throws IOException, ImageReadException, + ImageWriteException + { + Debug.debug("start"); + + List images = getIcoImages(); + for (int i = 0; i < images.size(); i++) + { + if (i % 10 == 0) + Debug.purgeMemory(); + + File imageFile = (File) images.get(i); + Debug.debug("imageFile", imageFile); + + IImageMetadata metadata = Sanselan.getMetadata(imageFile); + // assertNotNull(metadata); + + Map params = new HashMap(); + ImageInfo imageInfo = Sanselan.getImageInfo(imageFile, params); + // assertNotNull(imageInfo); + + BufferedImage image = Sanselan.getBufferedImage(imageFile); + assertNotNull(image); + } + } + +} Propchange: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoReadTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoRoundtripTest.java URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoRoundtripTest.java?rev=995515&view=auto ============================================================================== --- commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoRoundtripTest.java (added) +++ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoRoundtripTest.java Thu Sep 9 17:45:11 2010 @@ -0,0 +1,595 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sanselan.formats.ico; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.apache.sanselan.ImageFormat; +import org.apache.sanselan.ImageReadException; +import org.apache.sanselan.ImageWriteException; +import org.apache.sanselan.Sanselan; +import org.apache.sanselan.common.BinaryOutputStream; +import org.apache.sanselan.util.Debug; +import org.apache.sanselan.util.IOUtils; + +public class IcoRoundtripTest extends IcoBaseTest +{ + // 16x16 test image + private static final int[][] image = { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0}, + {0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,0}, + {0,0,1,0,1,0,0,0,1,0,0,0,0,1,0,0}, + {0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0}, + {0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0}, + {0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0}, + {0,0,0,0,1,0,0,0,1,0,1,1,1,0,0,0}, + {0,0,0,0,1,0,0,0,1,1,0,0,0,1,0,0}, + {0,0,0,0,1,0,0,0,1,0,0,0,0,1,0,0}, + {0,0,0,0,1,0,0,0,1,0,0,0,0,1,0,0}, + {0,0,0,0,1,0,0,0,0,1,0,0,0,1,0,0}, + {0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }; + + private Map generatorMap = new HashMap(); + + public IcoRoundtripTest() + { + generatorMap.put(Integer.valueOf(1), new GeneratorFor1BitBitmaps()); + generatorMap.put(Integer.valueOf(4), new GeneratorFor4BitBitmaps()); + generatorMap.put(Integer.valueOf(8), new GeneratorFor8BitBitmaps()); + generatorMap.put(Integer.valueOf(16), new GeneratorFor16BitBitmaps()); + generatorMap.put(Integer.valueOf(24), new GeneratorFor24BitBitmaps()); + generatorMap.put(Integer.valueOf(32), new GeneratorFor32BitBitmaps()); + } + + private static interface BitmapGenerator + { + byte[] generateBitmap(int foreground, int background, int paletteSize) + throws IOException, ImageWriteException; + } + + private class GeneratorFor1BitBitmaps implements BitmapGenerator + { + public byte[] generateBitmap(int foreground, int background, int paletteSize) + throws IOException, ImageWriteException + { + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(byteArrayStream, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + // Palette + bos.write3Bytes(background); + bos.write(0); + bos.write3Bytes(foreground); + bos.write(0); + for (int i = 2; i < paletteSize; i++) + bos.write4Bytes(0); + // Image + for (int y = 15; y >= 0; y--) + { + for (int x = 0; x < 16; x += 8) + { + bos.write( + ((0x1 & image[y][x]) << 7) | + ((0x1 & image[y][x+1]) << 6) | + ((0x1 & image[y][x+2]) << 5) | + ((0x1 & image[y][x+3]) << 4) | + ((0x1 & image[y][x+4]) << 3) | + ((0x1 & image[y][x+5]) << 2) | + ((0x1 & image[y][x+6]) << 1) | + ((0x1 & image[y][x+7]) << 0)); + } + // Pad to multiple of 32 bytes + bos.write(0); + bos.write(0); + } + // Mask + for (int y = image.length - 1; y >= 0; y--) + { + bos.write(0); + bos.write(0); + // Pad to 4 bytes: + bos.write(0); + bos.write(0); + } + bos.flush(); + return byteArrayStream.toByteArray(); + } + } + + private class GeneratorFor4BitBitmaps implements BitmapGenerator + { + public byte[] generateBitmap(int foreground, int background, int paletteSize) + throws IOException, ImageWriteException + { + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(byteArrayStream, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + // Palette + bos.write3Bytes(background); + bos.write(0); + bos.write3Bytes(foreground); + bos.write(0); + for (int i = 2; i < paletteSize; i++) + bos.write4Bytes(0); + // Image + for (int y = 15; y >= 0; y--) + { + for (int x = 0; x < 16; x += 2) + { + bos.write(((0xf & image[y][x]) << 4) | + (0xf & image[y][x+1])); + } + } + // Mask + for (int y = image.length - 1; y >= 0; y--) + { + bos.write(0); + bos.write(0); + // Pad to 4 bytes: + bos.write(0); + bos.write(0); + } + bos.flush(); + return byteArrayStream.toByteArray(); + } + } + + private class GeneratorFor8BitBitmaps implements BitmapGenerator + { + public byte[] generateBitmap(int foreground, int background, int paletteSize) + throws IOException, ImageWriteException + { + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(byteArrayStream, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + // Palette + bos.write3Bytes(background); + bos.write(0); + bos.write3Bytes(foreground); + bos.write(0); + for (int i = 2; i < paletteSize; i++) + bos.write4Bytes(0); + // Image + for (int y = 15; y >= 0; y--) + { + for (int x = 0; x < 16; x++) + { + bos.write(image[y][x]); + } + } + // Mask + for (int y = image.length - 1; y >= 0; y--) + { + bos.write(0); + bos.write(0); + // Pad to 4 bytes: + bos.write(0); + bos.write(0); + } + bos.flush(); + return byteArrayStream.toByteArray(); + } + } + + private class GeneratorFor16BitBitmaps implements BitmapGenerator + { + public byte[] generateBitmap(int foreground, int background, int paletteSize) + throws IOException, ImageWriteException + { + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(byteArrayStream, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + // Palette + for (int i = 0; i < paletteSize; i++) + bos.write4Bytes(0); + // Image + for (int y = 15; y >= 0; y--) + { + for (int x = 0; x < 16; x++) + { + if (image[y][x] == 1) + bos.write2Bytes((0x1f & (foreground >> 3)) | + ((0x1f & (foreground >> 11)) << 5) | + ((0x1f & (foreground >> 19)) << 10)); + else + bos.write2Bytes((0x1f & (background >> 3)) | + ((0x1f & (background >> 11)) << 5) | + ((0x1f & (background >> 19)) << 10)); + } + } + // Mask + for (int y = image.length - 1; y >= 0; y--) + { + bos.write(0); + bos.write(0); + // Pad to 4 bytes: + bos.write(0); + bos.write(0); + } + bos.flush(); + return byteArrayStream.toByteArray(); + } + } + + private class GeneratorFor24BitBitmaps implements BitmapGenerator + { + public byte[] generateBitmap(int foreground, int background, int paletteSize) + throws IOException, ImageWriteException + { + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(byteArrayStream, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + // Palette + for (int i = 0; i < paletteSize; i++) + bos.write4Bytes(0); + // Image + for (int y = 15; y >= 0; y--) + { + for (int x = 0; x < 16; x++) + { + if (image[y][x] == 1) + bos.write3Bytes(0xffffff & foreground); + else + bos.write3Bytes(0xffffff & background); + } + } + // Mask + for (int y = image.length - 1; y >= 0; y--) + { + bos.write(0); + bos.write(0); + // Pad to 4 bytes: + bos.write(0); + bos.write(0); + } + bos.flush(); + return byteArrayStream.toByteArray(); + } + } + + private class GeneratorFor32BitBitmaps implements BitmapGenerator + { + public byte[] generateBitmap(int foreground, int background, int paletteSize) + throws IOException, ImageWriteException + { + return generate32bitRGBABitmap(foreground, background, paletteSize, true); + } + + public byte[] generate32bitRGBABitmap(int foreground, int background, + int paletteSize, boolean writeMask) throws IOException, + ImageWriteException + { + ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(byteArrayStream, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + // Palette + for (int i = 0; i < paletteSize; i++) + bos.write4Bytes(0); + // Image + for (int y = 15; y >= 0; y--) + { + for (int x = 0; x < 16; x++) + { + if (image[y][x] == 1) + bos.write4Bytes(foreground); + else + bos.write4Bytes(background); + } + } + // Mask + if (writeMask) + { + for (int y = image.length - 1; y >= 0; y--) + { + bos.write(0); + bos.write(0); + // Pad to 4 bytes: + bos.write(0); + bos.write(0); + } + } + bos.flush(); + return byteArrayStream.toByteArray(); + } + } + + private void writeICONDIR(BinaryOutputStream bos, int reserved, int type, int count) + throws IOException, ImageWriteException + { + bos.write2Bytes(reserved); + bos.write2Bytes(type); + bos.write2Bytes(count); + } + + private void writeICONDIRENTRY(BinaryOutputStream bos, int width, int height, + int colorCount, int reserved, int planes, int bitCount, int bytesInRes) + throws IOException, ImageWriteException + { + bos.write(width); + bos.write(height); + bos.write(colorCount); + bos.write(reserved); + bos.write2Bytes(planes); + bos.write2Bytes(bitCount); + bos.write4Bytes(bytesInRes); + bos.write4Bytes(22); // image comes immediately after this + } + + private void writeBITMAPINFOHEADER(BinaryOutputStream bos, int width, int height, + int colorPlanes, int bitCount, int compression, int colorsUsed, + int colorsImportant) throws IOException, ImageWriteException + { + // BITMAPINFOHEADER + bos.write4Bytes(40); // biSize, always 40 for BITMAPINFOHEADER + bos.write4Bytes(width); // biWidth + bos.write4Bytes(height); // biHeight + bos.write2Bytes(colorPlanes); // biPlanes + bos.write2Bytes(bitCount); // bitCount + bos.write4Bytes(compression); // biCompression + bos.write4Bytes(0); // biSizeImage, can be 0 for uncompressed + bos.write4Bytes(0); // X pixels per metre + bos.write4Bytes(0); // Y pixels per metre + bos.write4Bytes(colorsUsed); // colors used, ignored + bos.write4Bytes(colorsImportant); // colors important + } + + public void testNormalIcons() throws IOException, ImageWriteException, ImageReadException + { + final int foreground = 0xFFF000E0; + final int background = 0xFF102030; + for (Iterator it = generatorMap.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry entry = (Map.Entry) it.next(); + int bitDepth = ((Integer)entry.getKey()).intValue(); + BitmapGenerator bitmapGenerator = (BitmapGenerator) entry.getValue(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(baos, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + byte[] bitmap = bitmapGenerator.generateBitmap(foreground, background, + (bitDepth <= 8) ? (1 << bitDepth) : 0); + writeICONDIR(bos, 0, 1, 1); + writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, bitDepth, 40 + bitmap.length); + writeBITMAPINFOHEADER(bos, 16, 2*16, 1, bitDepth, 0, 0, 0); + bos.write(bitmap); + bos.flush(); + writeAndReadImageData("16x16x" + bitDepth, baos.toByteArray(), foreground, background); + } + } + + public void testBadICONDIRENTRYIcons() throws IOException, ImageWriteException, ImageReadException + { + final int foreground = 0xFFF000E0; + final int background = 0xFF102030; + // Windows ignores the ICONDIRENTRY values when parsing the ICO file. + for (Iterator it = generatorMap.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry entry = (Map.Entry) it.next(); + int bitDepth = ((Integer)entry.getKey()).intValue(); + BitmapGenerator bitmapGenerator = (BitmapGenerator) entry.getValue(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(baos, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + byte[] bitmap = bitmapGenerator.generateBitmap(foreground, background, + (bitDepth <= 8) ? (1 << bitDepth) : 0); + writeICONDIR(bos, 0, 1, 1); + writeICONDIRENTRY(bos, 3 /* width, should be 16 */, + 4 /* height, should be 16 */, + 7 /* colorCount, should be 2 or 0 */, + 20 /* reserved, should be 0 */, + 11 /* planes, should be 1 or 0 */, + 19 /* bitCount, should be bitDepth */, + 40 + bitmap.length); + writeBITMAPINFOHEADER(bos, 16, 2*16, 1, bitDepth, 0, 0, 0); + bos.write(bitmap); + bos.flush(); + writeAndReadImageData("16x16x" + bitDepth + "-corrupt-icondirentry", + baos.toByteArray(), foreground, background); + } + } + + public void testColorsUsed() throws IOException, ImageWriteException, ImageReadException + { + final int foreground = 0xFFF000E0; + final int background = 0xFF102030; + for (Iterator it = generatorMap.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry entry = (Map.Entry) it.next(); + int bitDepth = ((Integer)entry.getKey()).intValue(); + BitmapGenerator bitmapGenerator = (BitmapGenerator) entry.getValue(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(baos, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + byte[] bitmap = bitmapGenerator.generateBitmap(foreground, background, 2); + writeICONDIR(bos, 0, 1, 1); + writeICONDIRENTRY(bos, 3, 4, 7, 20, 11, 19, 40 + bitmap.length); + writeBITMAPINFOHEADER(bos, 16, 2*16, 1, bitDepth, 0, 2, 0); + bos.write(bitmap); + bos.flush(); + writeAndReadImageData("16x16x" + bitDepth + "-custom-palette", + baos.toByteArray(), foreground, background); + } + } + + public void testZeroColorPlanes() throws IOException, ImageWriteException, ImageReadException + { + final int foreground = 0xFFF000E0; + final int background = 0xFF102030; + for (Iterator it = generatorMap.entrySet().iterator(); it.hasNext(); ) + { + Map.Entry entry = (Map.Entry) it.next(); + int bitDepth = ((Integer)entry.getKey()).intValue(); + BitmapGenerator bitmapGenerator = (BitmapGenerator) entry.getValue(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(baos, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + byte[] bitmap = bitmapGenerator.generateBitmap(foreground, background, + (bitDepth <= 8) ? (1 << bitDepth) : 0); + writeICONDIR(bos, 0, 1, 1); + writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, bitDepth, 40 + bitmap.length); + writeBITMAPINFOHEADER(bos, 16, 2*16, 0 /* should be 1 */, bitDepth, 0, 0, 0); + bos.write(bitmap); + bos.flush(); + + boolean threw = false; + try + { + writeAndReadImageData("16x16x" + bitDepth + "-zero-colorPlanes", + baos.toByteArray(), foreground, background); + } + catch (ImageReadException imageReadException) + { + threw = true; + } + assertTrue(threw); + } + } + + public void testBitfieldCompression() throws IOException, ImageWriteException, ImageReadException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(baos, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + byte[] bitmap = new GeneratorFor32BitBitmaps().generate32bitRGBABitmap( + 0xFFFF0000, 0xFFFFFFFF, 0, true); + writeICONDIR(bos, 0, 1, 1); + writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, 32, 40 + bitmap.length); + writeBITMAPINFOHEADER(bos, 16, 2*16, 1, 32, 3 /* BI_BITFIELDS */, 0, 0); + bos.write4Bytes(0x000000FF); // red mask + bos.write4Bytes(0x0000FF00); // green mask + bos.write4Bytes(0x00FF0000); // blue mask + bos.write(bitmap); + bos.flush(); + writeAndReadImageData("16x16x32-bitfield-compressed", baos.toByteArray(), + 0xFF0000FF, 0xFFFFFFFF); + } + + public void test32bitMask() throws IOException, ImageWriteException, ImageReadException + { + final int foreground = 0xFFF000E0; + final int background = 0xFF102030; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(baos, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + // For 32 bit RGBA, the AND mask can be missing: + byte[] bitmap = new GeneratorFor32BitBitmaps().generate32bitRGBABitmap( + foreground, background, 0, false); + writeICONDIR(bos, 0, 1, 1); + writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, 32, 40 + bitmap.length); + writeBITMAPINFOHEADER(bos, 16, 2*16, 1, 32, 0, 0, 0); + bos.write(bitmap); + bos.flush(); + writeAndReadImageData("16x16x32-no-mask", baos.toByteArray(), foreground, background); + } + + // FIXME: get the BMP decoder to support alpha, then uncomment below +// public void testAlphaVersusANDMask() throws IOException, ImageWriteException, ImageReadException +// { +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// BinaryOutputStream bos = new BinaryOutputStream(baos, +// BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); +// byte[] bitmap = new GeneratorFor32BitBitmaps().generate32bitRGBABitmap( +// 0xFF000000, 0x00000000, 0, true); +// writeICONDIR(bos, 0, 1, 1); +// writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, 32, 40 + bitmap.length); +// writeBITMAPINFOHEADER(bos, 16, 2*16, 1, 32, 0, 0, 0); +// bos.write(bitmap); +// bos.flush(); +// // The AND mask is fully opaque, yet the fully transparent alpha should win: +// writeAndReadImageData("16x16x32-alpha-vs-mask", baos.toByteArray(), +// 0xFF000000, 0x00000000); +// } + + public void testFullyTransparent32bitRGBA() throws IOException, ImageWriteException, ImageReadException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputStream bos = new BinaryOutputStream(baos, + BinaryOutputStream.BYTE_ORDER_LITTLE_ENDIAN); + byte[] bitmap = new GeneratorFor32BitBitmaps().generate32bitRGBABitmap( + 0x00000000, 0x00FFFFFF, 0, true); + writeICONDIR(bos, 0, 1, 1); + writeICONDIRENTRY(bos, 16, 16, 0, 0, 1, 32, 40 + bitmap.length); + writeBITMAPINFOHEADER(bos, 16, 2*16, 1, 32, 0, 0, 0); + bos.write(bitmap); + bos.flush(); + // Because every pixel is fully trasparent, ***ALPHA GETS IGNORED***: + writeAndReadImageData("16x16x32-fully-transparent", baos.toByteArray(), + 0xFF000000, 0xFFFFFFFF); + } + + private void writeAndReadImageData(String description, byte[] rawData, + int foreground, int background) throws IOException, + ImageReadException, ImageWriteException + { + // Uncomment to generate ICO files that can be tested with Windows: + //File exportFile = new File("/tmp/" + description + ".ico"); + //IOUtils.writeToFile(rawData, exportFile); + + File tempFile = createTempFile("temp", ".ico"); + IOUtils.writeToFile(rawData, tempFile); + + BufferedImage dstImage = Sanselan.getBufferedImage(tempFile); + + assertNotNull(dstImage); + assertTrue(dstImage.getWidth() == image[0].length); + assertTrue(dstImage.getHeight() == image.length); + + verify(dstImage, foreground, background); + } + + private void verify(BufferedImage data, int foreground, int background) + { + assertNotNull(data); + assertTrue(data.getHeight() == image.length); + + for (int y = 0; y < data.getHeight(); y++) + { + assertTrue(data.getWidth() == image[y].length); + for (int x = 0; x < data.getWidth(); x++) + { + int imageARGB = (image[y][x] == 1) ? foreground : background; + int dataARGB = data.getRGB(x, y); + + if (imageARGB != dataARGB) + { + Debug.debug("x: " + x + ", y: " + y + ", image: " + imageARGB + + " (0x" + Integer.toHexString(imageARGB) + ")" + + ", data: " + dataARGB + " (0x" + + Integer.toHexString(dataARGB) + ")"); + } + assertTrue(imageARGB == dataARGB); + } + } + } + +} Propchange: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/ico/IcoRoundtripTest.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java?rev=995515&r1=995514&r2=995515&view=diff ============================================================================== --- commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java (original) +++ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java Thu Sep 9 17:45:11 2010 @@ -66,7 +66,7 @@ public class RoundtripTest extends Sanse COLOR_FULL_RGB, true), // new FormatInfo(ImageFormat.IMAGE_FORMAT_GIF, true, true, COLOR_LIMITED_INDEX, true), // - new FormatInfo(ImageFormat.IMAGE_FORMAT_ICO, true, false, + new FormatInfo(ImageFormat.IMAGE_FORMAT_ICO, true, true, COLOR_FULL_RGB, true), // new FormatInfo(ImageFormat.IMAGE_FORMAT_TIFF, true, true, COLOR_FULL_RGB, true), //