/**
* Copyright 2004-2016 Riccardo Solmi. All rights reserved.
* This file is part of the Whole Platform.
*
* The Whole Platform is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Whole Platform 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whole.lang.ui.image;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.stream.ImageOutputStream;
public abstract class AbstractDIBGenerator extends AbstractImageGenerator {
public static final int BITMAPINFOHEADER_SIZE = 40;
protected void writeIconImage(ImageOutputStream ios, RenderedImage image, boolean withMask) throws IOException {
ios.writeInt(BITMAPINFOHEADER_SIZE);
ios.writeInt(calculateWidth(image));
ios.writeInt(calculateHeight(image));
int pixelSize = calculateBitDepth(image);
ios.writeShort(1);
ios.writeShort(pixelSize);
ios.writeInt(0); // compression
ios.writeInt(calculateSize(image)); // image size
ios.writeInt(0); // x pixels/meter
ios.writeInt(0); // y pixels/meter
ios.writeInt(0); // colors used
ios.writeInt(0); // colors important
Map<Integer, Integer> mappings = writeColorTable(ios, image);
writeImage(ios, image, mappings);
if (withMask)
writeMask(ios, image);
}
protected abstract int calculateBitDepth(RenderedImage image);
protected int calculateWidth(RenderedImage image) {
return image.getWidth();
}
protected int calculateHeight(RenderedImage image) {
return image.getHeight();
}
protected abstract int calculateSize(RenderedImage image);
protected int roundToMultipleOfFour(int value) {
return (value + 3) & (~ 3);
}
public static int[] processPalette(int[] palette, Map<Integer, Integer> mappings) throws IOException {
int[] sorted = new int[palette.length];
for (int i = 0; i < sorted.length; i++)
palette[i] = sorted[i] = palette[i] & 0x00FFFFFF;
Arrays.sort(sorted);
int blackCount = 0;
for (int i = 0; i < sorted.length; i++) {
if (sorted[i] == 0)
blackCount += 1;
else
break;
}
if (blackCount > 0) {
int[] translated = new int[sorted.length - blackCount];
System.arraycopy(sorted, blackCount, translated, 0, sorted.length - blackCount);
sorted = translated;
}
if (mappings != null) {
for (int i = 0; i < palette.length; i++) {
int color = palette[i];
int offset = Arrays.binarySearch(sorted, color);
mappings.put(i, color == 0 ? sorted.length : offset);
}
}
return sorted;
}
protected Map<Integer, Integer> writeColorTable(ImageOutputStream ios, RenderedImage image) throws IOException {
Map<Integer, Integer> mappings = new HashMap<Integer, Integer>();
int pixelSize = calculateBitDepth(image);
if (pixelSize != 8)
return mappings;
IndexColorModel colorModel = (IndexColorModel) image.getColorModel();
int mapSize = colorModel.getMapSize();
int[] rgbs = new int[mapSize];
colorModel.getRGBs(rgbs);
int[] sorted = processPalette(rgbs, mappings);
for (int i = 0, colors = 1 << pixelSize; i < colors; i++)
ios.writeInt(i < sorted.length ? sorted[i] : 0x0);
return mappings;
}
protected abstract void writeImage(ImageOutputStream ios, RenderedImage image, Map<Integer, Integer> mappings) throws IOException;
protected void writeMask(ImageOutputStream ios, RenderedImage image) throws IOException {
int width = image.getWidth();
int height = image.getHeight();
int maskSize = roundToMultipleOfFour((width+7)/8);
Raster raster = image.getData();
for (int y = height-1; y >= 0; y--) {
BitStream stream = new BitStream(width);
for (int x = 0; x < width; x++) {
Object dataElements = raster.getDataElements(x, y, null);
int alpha = image.getColorModel().getAlpha(dataElements);
stream.set(alpha < 127);
}
// write mask
byte[] bytes = stream.toByteArray();
ios.write(bytes);
// write padding
for (int i = bytes.length; i < maskSize; i++)
ios.write(0);
}
}
public static class BitStream {
protected int bitNum;
protected byte[] bytes;
protected int position;
public BitStream(int bitNum) {
this.bitNum = bitNum;
this.bytes = new byte[(bitNum+7)/8];
}
public void set(boolean value) {
int index = position / 8;
bytes[index] = (byte) ((bytes[index] << 1) | (value ? 0x01 : 0x00));
position++;
if (position == bitNum)
bytes[index] = (byte) (bytes[index] << (bytes.length * 8 - position));
}
public byte[] toByteArray() {
if (position != bitNum)
throw new IllegalStateException("not enough bits");
return Arrays.copyOf(bytes, bytes.length);
}
}
}