blob: 5d8be49c7ba5542fc4617a158bb1637de4a738ca [file] [log] [blame]
/*
* 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.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.common.ImageBuilder;
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,
ImageBuilder image)
{
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--;
image.setRGB(x, y, argb);
}
}
}
private static void decode4BPPImage(IcnsType imageType, byte[] imageData,
ImageBuilder image)
{
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;
image.setRGB(x, y, palette_4bpp[index]);
}
}
}
private static void decode8BPPImage(IcnsType imageType, byte[] imageData,
ImageBuilder image)
{
for (int y = 0; y < imageType.getHeight(); y++)
{
for (int x = 0; x < imageType.getWidth(); x++)
{
int index = 0xff & imageData[y*imageType.getWidth() + x];
image.setRGB(x, y, palette_8bpp[index]);
}
}
}
private static void decode32BPPImage(IcnsType imageType, byte[] imageData,
ImageBuilder image)
{
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]);
image.setRGB(x, y, argb);
}
}
}
private static void apply1BPPMask(byte[] maskData, ImageBuilder image) 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 = (image.getWidth() * image.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 < image.getHeight(); y++)
{
for (int x = 0; x < image.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--;
image.setRGB(x, y, (alpha << 24) |
(0xffffff & image.getRGB(x, y)));
}
}
}
private static void apply8BPPMask(byte[] maskData, ImageBuilder image)
{
for (int y = 0; y < image.getHeight(); y++)
{
for (int x = 0; x < image.getWidth(); x++)
{
int alpha = 0xff & maskData[y*image.getWidth() + x];
image.setRGB(x, y, (alpha << 24) |
(0xffffff & image.getRGB(x, y)));
}
}
}
public static List<BufferedImage> decodeAllImages(IcnsImageParser.IcnsElement[] icnsElements)
throws ImageReadException
{
List<BufferedImage> result = new ArrayList<BufferedImage>();
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;
ImageBuilder imageBuilder = new ImageBuilder(imageType.getWidth(),
imageType.getHeight(), true);
switch (imageType.getBitsPerPixel())
{
case 1:
decode1BPPImage(imageType, imageData, imageBuilder);
break;
case 4:
decode4BPPImage(imageType, imageData, imageBuilder);
break;
case 8:
decode8BPPImage(imageType, imageData, imageBuilder);
break;
case 32:
decode32BPPImage(imageType, imageData, imageBuilder);
break;
default:
throw new ImageReadException(
"Unsupported bit depth " + imageType.getBitsPerPixel());
}
if (maskElement != null)
{
if (maskType.getBitsPerPixel() == 1)
apply1BPPMask(maskElement.data, imageBuilder);
else if (maskType.getBitsPerPixel() == 8)
apply8BPPMask(maskElement.data, imageBuilder);
else
throw new ImageReadException("Unsupport mask bit depth " +
maskType.getBitsPerPixel());
}
result.add(imageBuilder.getBufferedImage());
}
return result;
}
}