/* * 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.commons.imaging.formats.icns; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.imaging.ImageFormat; import org.apache.commons.imaging.ImageInfo; import org.apache.commons.imaging.ImageParser; import org.apache.commons.imaging.ImageReadException; import org.apache.commons.imaging.ImageWriteException; import org.apache.commons.imaging.common.BinaryOutputStream; import org.apache.commons.imaging.common.ByteOrder; import org.apache.commons.imaging.common.IImageMetadata; import org.apache.commons.imaging.common.bytesource.ByteSource; import org.apache.commons.imaging.util.Debug; import org.apache.commons.imaging.util.ParamMap; public class IcnsImageParser extends ImageParser { public static final int ICNS_MAGIC = IcnsType.typeAsInt("icns"); public IcnsImageParser() { super.setByteOrder(ByteOrder.BIG_ENDIAN); } @Override public String getName() { return "icns-Custom"; } @Override public String getDefaultExtension() { return DEFAULT_EXTENSION; } private static final String DEFAULT_EXTENSION = ".icns"; private static final String ACCEPTED_EXTENSIONS[] = { ".icns", }; @Override protected String[] getAcceptedExtensions() { return ACCEPTED_EXTENSIONS; } @Override protected ImageFormat[] getAcceptedTypes() { return new ImageFormat[] { ImageFormat.IMAGE_FORMAT_ICNS }; } @Override public boolean embedICCProfile(final File src, final File dst, final byte profile[]) { return false; } @Override public IImageMetadata getMetadata(final ByteSource byteSource, final Map<String,Object> params) throws ImageReadException, IOException { return null; } @Override public ImageInfo getImageInfo(final ByteSource byteSource, Map<String,Object> params) throws ImageReadException, IOException { // make copy of params; we'll clear keys as we consume them. params = (params == null) ? new HashMap<String,Object>() : new HashMap<String,Object>(params); ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE, false); if (params.containsKey(PARAM_KEY_VERBOSE)) { params.remove(PARAM_KEY_VERBOSE); } if (params.size() > 0) { final Object firstKey = params.keySet().iterator().next(); throw new ImageReadException("Unknown parameter: " + firstKey); } final IcnsContents contents = readImage(byteSource); final List<BufferedImage> images = IcnsDecoder .decodeAllImages(contents.icnsElements); if (images.isEmpty()) { throw new ImageReadException("No icons in ICNS file"); } final BufferedImage image0 = images.get(0); return new ImageInfo("Icns", 32, new ArrayList<String>(), ImageFormat.IMAGE_FORMAT_ICNS, "ICNS Apple Icon Image", image0.getHeight(), "image/x-icns", images.size(), 0, 0, 0, 0, image0.getWidth(), false, true, false, ImageInfo.COLOR_TYPE_RGB, ImageInfo.COMPRESSION_ALGORITHM_UNKNOWN); } @Override public Dimension getImageSize(final ByteSource byteSource, Map<String,Object> params) throws ImageReadException, IOException { // make copy of params; we'll clear keys as we consume them. params = (params == null) ? new HashMap<String,Object>() : new HashMap<String,Object>(params); ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE, false); if (params.containsKey(PARAM_KEY_VERBOSE)) { params.remove(PARAM_KEY_VERBOSE); } if (params.size() > 0) { final Object firstKey = params.keySet().iterator().next(); throw new ImageReadException("Unknown parameter: " + firstKey); } final IcnsContents contents = readImage(byteSource); final List<BufferedImage> images = IcnsDecoder .decodeAllImages(contents.icnsElements); if (images.isEmpty()) { throw new ImageReadException("No icons in ICNS file"); } final BufferedImage image0 = images.get(0); return new Dimension(image0.getWidth(), image0.getHeight()); } @Override public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String,Object> params) throws ImageReadException, IOException { return null; } private static class IcnsHeader { public final int magic; // Magic literal (4 bytes), always "icns" public final int fileSize; // Length of file (4 bytes), in bytes. public IcnsHeader(final int magic, final int fileSize) { this.magic = magic; this.fileSize = fileSize; } public void dump(final PrintWriter pw) { pw.println("IcnsHeader"); pw.println("Magic: 0x" + Integer.toHexString(magic) + " (" + IcnsType.describeType(magic) + ")"); pw.println("FileSize: " + fileSize); pw.println(""); } } private IcnsHeader readIcnsHeader(final InputStream is) throws ImageReadException, IOException { final int Magic = read4Bytes("Magic", is, "Not a Valid ICNS File"); final int FileSize = read4Bytes("FileSize", is, "Not a Valid ICNS File"); if (Magic != ICNS_MAGIC) { throw new ImageReadException("Not a Valid ICNS File: " + "magic is 0x" + Integer.toHexString(Magic)); } return new IcnsHeader(Magic, FileSize); } public static class IcnsElement { public final int type; public final int elementSize; public final byte[] data; public IcnsElement(final int type, final int elementSize, final byte[] data) { this.type = type; this.elementSize = elementSize; this.data = data; } public void dump(final PrintWriter pw) { pw.println("IcnsElement"); final IcnsType icnsType = IcnsType.findAnyType(type); String typeDescription; if (icnsType == null) { typeDescription = ""; } else { typeDescription = " " + icnsType.toString(); } pw.println("Type: 0x" + Integer.toHexString(type) + " (" + IcnsType.describeType(type) + ")" + typeDescription); pw.println("ElementSize: " + elementSize); pw.println(""); } } private IcnsElement readIcnsElement(final InputStream is) throws IOException { final int type = read4Bytes("Type", is, "Not a Valid ICNS File"); // Icon type // (4 bytes) final int elementSize = read4Bytes("ElementSize", is, "Not a Valid ICNS File"); // Length // of // data // (4 // bytes), // in // bytes, // including // this // header final byte[] data = readBytes("Data", is, elementSize - 8, "Not a Valid ICNS File"); return new IcnsElement(type, elementSize, data); } private static class IcnsContents { public final IcnsHeader icnsHeader; public final IcnsElement icnsElements[]; public IcnsContents(final IcnsHeader icnsHeader, final IcnsElement[] icnsElements) { super(); this.icnsHeader = icnsHeader; this.icnsElements = icnsElements; } } private IcnsContents readImage(final ByteSource byteSource) throws ImageReadException, IOException { InputStream is = null; try { is = byteSource.getInputStream(); final IcnsHeader icnsHeader = readIcnsHeader(is); final List<IcnsElement> icnsElementList = new ArrayList<IcnsElement>(); for (int remainingSize = icnsHeader.fileSize - 8; remainingSize > 0;) { final IcnsElement icnsElement = readIcnsElement(is); icnsElementList.add(icnsElement); remainingSize -= icnsElement.elementSize; } final IcnsElement[] icnsElements = new IcnsElement[icnsElementList.size()]; for (int i = 0; i < icnsElements.length; i++) { icnsElements[i] = icnsElementList.get(i); } return new IcnsContents(icnsHeader, icnsElements); } finally { try { is.close(); } catch (final Exception e) { Debug.debug(e); } } } @Override public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImageReadException, IOException { final IcnsContents icnsContents = readImage(byteSource); icnsContents.icnsHeader.dump(pw); for (final IcnsElement icnsElement : icnsContents.icnsElements) { icnsElement.dump(pw); } return true; } @Override public final BufferedImage getBufferedImage(final ByteSource byteSource, final Map<String,Object> params) throws ImageReadException, IOException { final IcnsContents icnsContents = readImage(byteSource); final List<BufferedImage> result = IcnsDecoder .decodeAllImages(icnsContents.icnsElements); if (result.size() > 0) { return result.get(0); } else { throw new ImageReadException("No icons in ICNS file"); } } @Override public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImageReadException, IOException { final IcnsContents icnsContents = readImage(byteSource); return IcnsDecoder.decodeAllImages(icnsContents.icnsElements); } @Override public void writeImage(final BufferedImage src, final OutputStream os, Map<String,Object> params) throws ImageWriteException, IOException { // make copy of params; we'll clear keys as we consume them. params = (params == null) ? new HashMap<String,Object>() : new HashMap<String,Object>(params); // clear format key. if (params.containsKey(PARAM_KEY_FORMAT)) { params.remove(PARAM_KEY_FORMAT); } if (params.size() > 0) { final Object firstKey = params.keySet().iterator().next(); throw new ImageWriteException("Unknown parameter: " + firstKey); } IcnsType imageType; if (src.getWidth() == 16 && src.getHeight() == 16) { imageType = IcnsType.ICNS_16x16_32BIT_IMAGE; } else if (src.getWidth() == 32 && src.getHeight() == 32) { imageType = IcnsType.ICNS_32x32_32BIT_IMAGE; } else if (src.getWidth() == 48 && src.getHeight() == 48) { imageType = IcnsType.ICNS_48x48_32BIT_IMAGE; } else if (src.getWidth() == 128 && src.getHeight() == 128) { imageType = IcnsType.ICNS_128x128_32BIT_IMAGE; } else { throw new ImageWriteException("Invalid/unsupported source width " + src.getWidth() + " and height " + src.getHeight()); } final BinaryOutputStream bos = new BinaryOutputStream(os, ByteOrder.BIG_ENDIAN); bos.write4Bytes(ICNS_MAGIC); bos.write4Bytes(4 + 4 + 4 + 4 + 4 * imageType.getWidth() * imageType.getHeight() + 4 + 4 + imageType.getWidth() * imageType.getHeight()); bos.write4Bytes(imageType.getType()); bos.write4Bytes(4 + 4 + 4 * imageType.getWidth() * imageType.getHeight()); for (int y = 0; y < src.getHeight(); y++) { for (int x = 0; x < src.getWidth(); x++) { final int argb = src.getRGB(x, y); bos.write(0); bos.write(argb >> 16); bos.write(argb >> 8); bos.write(argb); } } final IcnsType maskType = IcnsType.find8BPPMaskType(imageType); bos.write4Bytes(maskType.getType()); bos.write4Bytes(4 + 4 + imageType.getWidth() * imageType.getWidth()); for (int y = 0; y < src.getHeight(); y++) { for (int x = 0; x < src.getWidth(); x++) { final int argb = src.getRGB(x, y); bos.write(argb >> 24); } } } /** * Extracts embedded XML metadata as XML string. * <p> * * @param byteSource * File containing image data. * @param params * Map of optional parameters, defined in SanselanConstants. * @return Xmp Xml as String, if present. Otherwise, returns null. */ @Override public String getXmpXml(final ByteSource byteSource, final Map<String,Object> params) throws ImageReadException, IOException { return null; } }