/* * 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.icns; import java.io.IOException; import java.util.ArrayList; import org.apache.sanselan.ImageReadException; import com.google.code.appengine.awt.image.BufferedImage; public class IcnsDecoder { private static final int[] palette_4bpp = { 0xffffffff, 0xfffcf305, 0xffff6402, 0xffdd0806, 0xfff20884, 0xff4600a5, 0xff0000d4, 0xff02abea, 0xff1fb714, 0xff006411, 0xff562c05, 0xff90713a, 0xffc0c0c0, 0xff808080, 0xff404040, 0xff000000 }; private static final int[] palette_8bpp = { 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 }; private static void decode1BPPImage(IcnsType imageType, byte[] imageData, BufferedImage bufferedImage) { int position = 0; int bitsLeft = 0; int value = 0; for (int y = 0; y < imageType.getHeight(); y++) { for (int x = 0; x < imageType.getWidth(); x++) { if (bitsLeft == 0) { value = 0xff & imageData[position++]; bitsLeft = 8; } int argb; if ((value & 0x80) != 0) argb = 0xff000000; else argb = 0xffffffff; value <<= 1; bitsLeft--; bufferedImage.setRGB(x, y, argb); } } } private static void decode4BPPImage(IcnsType imageType, byte[] imageData, BufferedImage bufferedImage) { int i = 0; boolean visited = false; for (int y = 0; y < imageType.getHeight(); y++) { for (int x = 0; x < imageType.getWidth(); x++) { int index; if (!visited) index = 0xf & (imageData[i] >> 4); else index = 0xf & imageData[i++]; visited = !visited; bufferedImage.setRGB(x, y, palette_4bpp[index]); } } } private static void decode8BPPImage(IcnsType imageType, byte[] imageData, BufferedImage bufferedImage) { for (int y = 0; y < imageType.getHeight(); y++) { for (int x = 0; x < imageType.getWidth(); x++) { int index = 0xff & imageData[y*imageType.getWidth() + x]; bufferedImage.setRGB(x, y, palette_8bpp[index]); } } } private static void decode32BPPImage(IcnsType imageType, byte[] imageData, BufferedImage bufferedImage) { for (int y = 0; y < imageType.getHeight(); y++) { for (int x = 0; x < imageType.getWidth(); x++) { int argb = 0xff000000 /* the "alpha" is ignored */ | ((0xff & imageData[4*(y*imageType.getWidth() + x) + 1]) << 16) | ((0xff & imageData[4*(y*imageType.getWidth() + x) + 2]) << 8) | (0xff & imageData[4*(y*imageType.getWidth() + x) + 3]); bufferedImage.setRGB(x, y, argb); } } } private static void apply1BPPMask(byte[] maskData, BufferedImage bufferedImage) throws ImageReadException { int position = 0; int bitsLeft = 0; int value = 0; // 1 bit icon types have image data followed by mask data in the same entry int totalBytes = (bufferedImage.getWidth() * bufferedImage.getHeight() + 7) / 8; if (maskData.length >= 2*totalBytes) position = totalBytes; else throw new ImageReadException("1 BPP mask underrun parsing ICNS file"); for (int y = 0; y < bufferedImage.getHeight(); y++) { for (int x = 0; x < bufferedImage.getWidth(); x++) { if (bitsLeft == 0) { value = 0xff & maskData[position++]; bitsLeft = 8; } int alpha; if ((value & 0x80) != 0) alpha = 0xff; else alpha = 0x00; value <<= 1; bitsLeft--; bufferedImage.setRGB(x, y, (alpha << 24) | (0xffffff & bufferedImage.getRGB(x, y))); } } } private static void apply8BPPMask(byte[] maskData, BufferedImage bufferedImage) { for (int y = 0; y < bufferedImage.getHeight(); y++) { for (int x = 0; x < bufferedImage.getWidth(); x++) { int alpha = 0xff & maskData[y*bufferedImage.getWidth() + x]; bufferedImage.setRGB(x, y, (alpha << 24) | (0xffffff & bufferedImage.getRGB(x, y))); } } } public static ArrayList decodeAllImages(IcnsImageParser.IcnsElement[] icnsElements) throws ImageReadException, IOException { ArrayList result = new ArrayList(); for (int i = 0; i < icnsElements.length; i++) { IcnsImageParser.IcnsElement imageElement = icnsElements[i]; IcnsType imageType = IcnsType.findImageType(imageElement.type); if (imageType == null) continue; IcnsType maskType = null; IcnsImageParser.IcnsElement maskElement = null; if (imageType.hasMask()) { maskType = imageType; maskElement = imageElement; } else { maskType = IcnsType.find8BPPMaskType(imageType); if (maskType != null) { for (int j = 0; j < icnsElements.length; j++) { if (icnsElements[j].type == maskType.getType()) { maskElement = icnsElements[j]; break; } } } if (maskElement == null) { maskType = IcnsType.find1BPPMaskType(imageType); if (maskType != null) { for (int j = 0; j < icnsElements.length; j++) { if (icnsElements[j].type == maskType.getType()) { maskElement = icnsElements[j]; break; } } } } } // FIXME: don't skip these when JPEG 2000 support is added: if (imageType == IcnsType.ICNS_256x256_32BIT_ARGB_IMAGE || imageType == IcnsType.ICNS_512x512_32BIT_ARGB_IMAGE) continue; int expectedSize = (imageType.getWidth()*imageType.getHeight()* imageType.getBitsPerPixel() + 7) / 8; byte[] imageData; if (imageElement.data.length < expectedSize) { if (imageType.getBitsPerPixel() == 32) { imageData = Rle24Compression.decompress(imageType.getWidth(), imageType.getHeight(), imageElement.data); } else throw new ImageReadException( "Short image data but not a 32 bit compressed type"); } else imageData = imageElement.data; BufferedImage bufferedImage = new BufferedImage(imageType.getWidth(), imageType.getHeight(), BufferedImage.TYPE_INT_ARGB); switch (imageType.getBitsPerPixel()) { case 1: decode1BPPImage(imageType, imageData, bufferedImage); break; case 4: decode4BPPImage(imageType, imageData, bufferedImage); break; case 8: decode8BPPImage(imageType, imageData, bufferedImage); break; case 32: decode32BPPImage(imageType, imageData, bufferedImage); break; default: throw new ImageReadException( "Unsupported bit depth " + imageType.getBitsPerPixel()); } if (maskElement != null) { if (maskType.getBitsPerPixel() == 1) apply1BPPMask(maskElement.data, bufferedImage); else if (maskType.getBitsPerPixel() == 8) apply8BPPMask(maskElement.data, bufferedImage); else throw new ImageReadException("Unsupport mask bit depth " + maskType.getBitsPerPixel()); } result.add(bufferedImage); } return result; } }