BitmapIndexed.java
/*
* Copyright (c) 2009, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.*;
import java.util.Hashtable;
/**
* Describes an indexed bitmap structure (1, 4, or 8 bits per pixes).
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
final class BitmapIndexed extends BitmapDescriptor {
final int[] bits;
final int[] colors;
public BitmapIndexed(final DirectoryEntry entry, final DIBHeader header) {
super(entry, header);
bits = new int[getWidth() * getHeight()];
// NOTE: We're adding space for one extra color, for transparency
colors = new int[getColorCount() + 1];
}
public BufferedImage createImageIndexed() {
// TODO: This is very stupid, maybe we need a TYPE_CUSTOM image, with separate alphaRaster?!
// As ICO has a separate bitmask, not related to palette index (allows 256 colors + trans) :-P
IndexColorModel icm = createColorModel();
// We add cursor hotspot as a property to images created from CUR format.
// This is slightly obscure, and should probably be moved...
Hashtable<String, Object> properties = null;
if (entry instanceof DirectoryEntry.CUREntry) {
properties = new Hashtable<>(1);
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
}
WritableRaster raster = icm.createCompatibleWritableRaster(getWidth(), getHeight());
BufferedImage image = new BufferedImage(icm, raster, icm.isAlphaPremultiplied(), properties);
// Make pixels transparent according to mask
final int trans = icm.getTransparentPixel();
for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
if (mask.isTransparent(x, y)) {
bits[x + getWidth() * y] = trans;
}
}
}
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
return image;
}
/**
* @return Color model created from color palette in entry
*/
IndexColorModel createColorModel() {
// NOTE: This is a hack to make room for transparent pixel for mask
int bits = getBitCount();
int colors = this.colors.length;
int transparent = -1;
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
if (colors > (1 << getBitCount())) {
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
if (index == -1) {
// No duplicate found, increase bit count
bits++;
transparent = this.colors.length - 1;
}
else {
// Found a duplicate, use it as transparent
transparent = index;
colors--;
}
}
// NOTE: Setting hasAlpha to true, makes things work on 1.2
return new IndexColorModel(bits, colors, this.colors, 0, true, transparent,
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT);
}
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
// Look for unused colors, to use as transparent
boolean[] used = new boolean[colors.length - 1];
for (int bit : bits) {
if (!used[bit]) {
used[bit] = true;
}
}
for (int i = 0; i < used.length; i++) {
if (!used[i]) {
return i;
}
}
// Try to find duplicates in colormap, and remap
int transparent = -1;
int duplicate = -1;
for (int i = 0; transparent == -1 && i < colors.length - 1; i++) {
for (int j = i + 1; j < colors.length - 1; j++) {
if (colors[i] == colors[j]) {
transparent = j;
duplicate = i;
break;
}
}
}
if (transparent != -1) {
// Remap duplicate
for (int i = 0; i < bits.length; i++) {
if (bits[i] == transparent) {
bits[i] = duplicate;
}
}
}
return transparent;
}
@Override
public BufferedImage getImage() {
if (image == null) {
image = createImageIndexed();
}
return image;
}
}