/* * org.openmicroscopy.shoola.util.image.io.IconReader * *------------------------------------------------------------------------------ * Copyright (C) 2006-2010 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.util.image.io; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; import org.openmicroscopy.shoola.util.image.geom.Factory; /** * Reads ICNS file. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * @since 3.0-Beta4 */ public class IconReader { /** The extension of an <code>ICNS</code> icon file. */ public static final String ICNS = "icns"; /** The 16x16 icon. */ public static final int ICON_16 = 16; /** The 32x32 icon. */ public static final int ICON_32 = 32; /** The 48x48 icon. */ public static final int ICON_48 = 48; /** The 128x128 icon. */ public static final int ICON_128 = 128; /** Identifies the 16x16 icon within an <code>ICNS</code> file. */ private static final String ICON_SMALL_32_BIT_RGB = "is32"; /** Identifies the 16x16 mask icon within an <code>ICNS</code> file. */ private static final String ICON_SMALL_8_BIT_MASK = "s8mk"; /** Identifies the 32x32 icon within an <code>ICNS</code> file. */ private static final String ICON_LARGE_32_BIT_RGB = "il32"; /** Identifies the 32x32 mask icon within an <code>ICNS</code> file. */ private static final String ICON_LARGE_8_BIT_MASK = "l8mk"; /** Identifies the 48x48 icon within an <code>ICNS</code> file. */ private static final String ICON_HUGE_32_BIT_RGB = "ih32"; /** Identifies the 48x48 mask icon within an <code>ICNS</code> file. */ private static final String ICON_HUGE_8_BIT_MASK = "h8mk"; /** Identifies the 128x128 icon within an <code>ICNS</code> file. */ private static final String THUMBNAIL_32_BIT_RGB = "it32"; /** Identifies the 128x128 mask icon within an <code>ICNS</code> file. */ private static final String THUMBNAIL_8_BIT_MASK = "t8mk"; /** The length of data to read. */ private static final int SIZE = 4; /** The file to decode. */ private FileInputStream stream; /** * Reads the passed array of bytes. * * @param array The array to handle. * @throws IOException Thrown if an error occurred while reading. */ private void read(byte[] array) throws IOException { int toRead = array.length; int read = 0; int n; while (read < toRead) { n = stream.read(array, read, toRead - read); if (n < 0) throw new IOException("Value cannot be negative."); read += n; } } /** * Ignores the passed number of bytes. * * @param toIgnore The number of bytes to ignore. * @throws IOException Thrown if an error occurred while reading. */ private void ignore(long toIgnore) throws IOException { long ignored = 0; long n; while (ignored < toIgnore) { n = stream.skip(toIgnore-ignored); if (n < 0) throw new IOException("Value cannot be negative."); ignored += n; } } /** * Creates a new instance. * * @param path The path to the file to decode. */ public IconReader(String path) { if (path == null || path.length() == 0) throw new IllegalArgumentException("No path specified."); try { stream = new FileInputStream(path); } catch (Exception e) { throw new IllegalArgumentException("Unable to initialize the " + "file stream."); } } /** * Decodes the passed array of bytes. * * @param data The data to decode. * @param destination The array hosting the decoded value. * @param size The size of the icon to handle. * @return See above. */ private int[] decode32bitIcon(byte[] data, int[] destination, int size) { int nbPixels = size*size; byte[] unpackedData; if (data.length == nbPixels*44) { unpackedData = data; } else { unpackedData = new byte[nbPixels*33]; unpackIconData(data, unpackedData); } int[] pixels; if (destination == null) { pixels = new int[nbPixels]; // Make all pixels opaque for (int i = 0; i < pixels.length; i++) pixels[i] = 0xFF000000; } else { pixels = destination; } //assert pixels.length == size *size : "Incorrect pixel buffer size"; int unpackedIndex = 0; for (int i = 0; i < pixels.length; i++) pixels[i] |= (unpackedData[unpackedIndex++] & 0xFF) << 16; for (int i = 0; i < pixels.length; i++) pixels[i] |= (unpackedData[unpackedIndex++] & 0xFF) << 8; for (int i = 0; i < pixels.length; i++) pixels[i] |= (unpackedData[unpackedIndex++] & 0xFF); return pixels; } /** * Decodes the passed data. * * @param data The data to decode. * @param destination The array hosting the decoded value. * @param size The size of the icon to handle. * @return See above. */ private int[] decode8bitMask(byte[] data, int[] destination, int size) { int[] pixels; int arraySize = size*size; if (destination == null) pixels = new int[arraySize]; else pixels = destination; for (int i = 0; i < pixels.length; i++) { pixels[i] &= 0x00FFFFFF; pixels[i] |= (data[i] & 0xFF) << 24; } return pixels; } /** * Unpacked the icon. * * @param packedData The packed data. * @param unpackedData The unpacked one. */ private void unpackIconData(byte[] packedData, byte[] unpackedData) { int in = 0; int out = 0; int h; int n; while (in < packedData.length && out < unpackedData.length) { h = packedData[in++] & 0xFF; if ((h & 0x80) == 0) { n = h+1; System.arraycopy(packedData, in, unpackedData, out, n); in += n; out += n; } else { n = h-125; byte data = packedData[in++]; Arrays.fill(unpackedData, out, out+n, data); out += n; } } } /** * Returns the size. * * @return See above. * @throws IOException Thrown if an error occurred while reading. */ private int getSize() throws IOException { byte[] data = new byte[SIZE]; read(data); return ((data[0] & 0xFF) << 24)+((data[1] & 0xFF) << 16)+ ((data[2] & 0xFF) << 8)+(data[3] & 0xFF); } /** * Decodes the <code>ICNS</code> file. * * @param iconIndex The type of icon to create. * @return See above. * @throws IOException Thrown if an error occurred while reading. */ private BufferedImage decodeICNS(int iconIndex) throws IOException { int[] icon16 = null; int[] icon32 = null; int[] icon48 = null; int[] icon128 = null; int fileSize = getSize(); int left = fileSize-2*SIZE; String type; int size; int dataSize; byte[] data; while (left > 0) { data = new byte[SIZE]; read(data); type = new String(data); size = getSize(); dataSize = size-2*SIZE; if (ICON_SMALL_32_BIT_RGB.equals(type)) { data = new byte[dataSize]; read(data); icon16 = decode32bitIcon(data, icon16, ICON_16); } else if (ICON_LARGE_32_BIT_RGB.equals(type)) { data = new byte[dataSize]; read(data); icon32 = decode32bitIcon(data, icon32, ICON_32); } else if (ICON_HUGE_32_BIT_RGB.equals(type)) { data = new byte[dataSize]; read(data); icon48 = decode32bitIcon(data, icon48, ICON_48); } else if (THUMBNAIL_32_BIT_RGB.equals(type)) { // unknown value ignore(SIZE); data = new byte[dataSize-SIZE]; read(data); icon128 = decode32bitIcon(data, icon128, ICON_128); } else if (ICON_SMALL_8_BIT_MASK.equals(type)) { data = new byte[dataSize]; read(data); icon16 = decode8bitMask(data, icon16, ICON_16); } else if (ICON_LARGE_8_BIT_MASK.equals(type)) { data = new byte[dataSize]; read(data); icon32 = decode8bitMask(data, icon32, ICON_32); } else if (ICON_HUGE_8_BIT_MASK.equals(type)) { data = new byte[dataSize]; read(data); icon48 = decode8bitMask(data, icon48, ICON_48); } else if (THUMBNAIL_8_BIT_MASK.equals(type)) { data = new byte[dataSize]; read(data); icon128 = decode8bitMask(data, icon128, ICON_128); } else { ignore(dataSize); } left -= size; } switch (iconIndex) { case ICON_16: default: return Factory.create(ICON_16, ICON_16, icon16); case ICON_32: return Factory.create(ICON_32, ICON_32, icon32); case ICON_48: return Factory.create(ICON_48, ICON_48, icon48); case ICON_128: return Factory.create(ICON_128, ICON_128, icon128); } } /** * Creates a new instance. * * @param file The file to decode. */ public IconReader(File file) { if (file == null) throw new IllegalArgumentException("No file specified."); try { stream = new FileInputStream(file); } catch (Exception e) { throw new IllegalArgumentException("Unable to initialize the " + "file stream."); } } /** * Returns the icon identified by the passed type. * * @param iconIndex One of the constants defined by this class. * @return See above. * @throws IOException If an error occurred while reading the file. */ public BufferedImage decode(int iconIndex) throws IOException { byte[] data = new byte[SIZE]; read(data); String header = new String(data); if (ICNS.equals(header))return decodeICNS(iconIndex); return null; } }