/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.gef.internal.image;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
import org.xmind.neuquant.NeuQuant;
/**
* @author Frank Shaka
*/
public class ImageConverter {
private static final int DEPTH_256 = 8;
private static final int OFFSET = 3;
private static final int MASK = (0xFF >> OFFSET) << OFFSET;
private static final int INIT_COLOR_DIFFERENCE_THRESHOLD = 255 * 3;
/**
* @param srcImage
* @return
*/
public static ImageData converTo256Colors(ImageData srcImage) {
return convertDepth_4(srcImage, DEPTH_256);
}
protected static ImageData convertDepth(ImageData srcImage, int depth) {
int numMaxColors = 1 << depth;
final Map<RGB, Integer> colorOccurrences = new HashMap<RGB, Integer>();
/* Calculate occurrences of each color in source image */
PaletteData srcPalette = srcImage.palette;
for (int i = 0; i < srcImage.width; i++) {
for (int j = 0; j < srcImage.height; j++) {
RGB rgb = srcPalette.getRGB(srcImage.getPixel(i, j));
/*
* Cut off lower bits of each color value in order to reduce the
* differences among colors and raise the frequencies of colors
* with lower occurrences. The disadvantage is that it makes the
* image a little distorted since most of the colors will be
* changed.
*/
rgb = getShortenedRGB(rgb);
int occur = !colorOccurrences.containsKey(rgb) ? 1
: colorOccurrences.get(rgb) + 1;
colorOccurrences.put(rgb, occur);
}
}
/* Sort colors by occurrences */
Set<RGB> sortedColors = new TreeSet<RGB>(new Comparator<RGB>() {
public int compare(RGB o1, RGB o2) {
int x = colorOccurrences.get(o2) - colorOccurrences.get(o1);
return x == 0 ? 1 : x;
}
});
sortedColors.addAll(colorOccurrences.keySet());
/* Filter colors to fit within the max size */
List<RGB> newPaletteColors = new ArrayList<RGB>(sortedColors);
if (newPaletteColors.size() > numMaxColors)
/* Cut off colors with lower occurrrences */
newPaletteColors = newPaletteColors.subList(0, numMaxColors);
/* Generate new palette */
RGB[] colors = newPaletteColors
.toArray(new RGB[newPaletteColors.size()]);
PaletteData newPalette = new PaletteData(colors);
Map<RGB, Integer> pixelValues = new HashMap<RGB, Integer>();
for (int i = 0; i < colors.length; i++) {
pixelValues.put(colors[i], i);
}
Map<RGB, RGB> oldToNew = new HashMap<RGB, RGB>();
/* Generate new image from source image with new palette */
ImageData result = new ImageData(srcImage.width, srcImage.height,
depth, newPalette);
for (int i = 0; i < srcImage.width; i++) {
for (int j = 0; j < srcImage.height; j++) {
RGB oldColor = srcPalette.getRGB(srcImage.getPixel(i, j));
/* Convert colors from source image to colors in new palette */
RGB newColor = oldToNew.get(oldColor);
if (newColor == null) {
newColor = findSimilarColor(oldColor, colors);
oldToNew.put(oldColor, newColor);
}
result.setPixel(i, j, pixelValues.get(newColor));
}
}
return result;
}
/**
* @param rgb
* @return
*/
private static RGB getShortenedRGB(RGB rgb) {
rgb.red = rgb.red & MASK;
rgb.blue = rgb.blue & MASK;
rgb.green = rgb.green & MASK;
return rgb;
}
private static RGB findSimilarColor(RGB src, RGB[] colors) {
RGB result = null;
int droppingThreshold = INIT_COLOR_DIFFERENCE_THRESHOLD;
for (int i = 0; i < colors.length; i++) {
RGB toTest = colors[i];
int diff = getColorDifference(src, toTest);
if (diff < droppingThreshold) {
droppingThreshold = diff;
result = toTest;
}
}
return result;
}
static int getColorDifference(RGB c1, RGB c2) {
return Math.abs(c1.red - c2.red) + Math.abs(c1.green - c2.green)
+ Math.abs(c1.blue - c2.blue);
}
protected static ImageData convertDepth_2(ImageData srcImage, int depth) {
int numMaxColors = 1 << depth;
final Map<RGB, Integer> colorOccurrences = new HashMap<RGB, Integer>();
/* Calculate occurrences of each color in source image */
PaletteData srcPalette = srcImage.palette;
for (int i = 0; i < srcImage.width; i++) {
for (int j = 0; j < srcImage.height; j++) {
RGB rgb = srcPalette.getRGB(srcImage.getPixel(i, j));
int occur = !colorOccurrences.containsKey(rgb) ? 1
: colorOccurrences.get(rgb) + 1;
colorOccurrences.put(rgb, occur);
}
}
IColorReplacingPolicy policy;
if (colorOccurrences.size() <= numMaxColors) {
policy = new SameColorReplacingPolicy();
} else {
policy = new MinRiskColorReplacingPolicy();
}
PaletteData newPalette = new PaletteData(policy.getReplacingColors(
numMaxColors, colorOccurrences));
Map<RGB, Integer> pixelValues = new HashMap<RGB, Integer>();
RGB[] rgbs = newPalette.getRGBs();
for (int i = 0; i < rgbs.length; i++) {
pixelValues.put(rgbs[i], i);
}
/* Generate new image from source image with new palette */
ImageData result = new ImageData(srcImage.width, srcImage.height,
depth, newPalette);
for (int i = 0; i < srcImage.width; i++) {
for (int j = 0; j < srcImage.height; j++) {
RGB rgb = srcPalette.getRGB(srcImage.getPixel(i, j));
RGB newRGB = policy.getReplacedColor(rgb);
result.setPixel(i, j, pixelValues.get(newRGB));
}
}
return result;
}
// protected static ImageData convertDepth_3(ImageData srcImage, int depth) {
// EightTreeQuantizer quantizer = new EightTreeQuantizer(depth);
// final Map<RGB, Integer> colorOccurrences = new HashMap<RGB, Integer>();
//
// /* Calculate occurrences of each color in source image */
// PaletteData srcPalette = srcImage.palette;
// for (int i = 0; i < srcImage.width; i++) {
// for (int j = 0; j < srcImage.height; j++) {
// RGB rgb = srcPalette.getRGB(srcImage.getPixel(i, j));
// int occur = !colorOccurrences.containsKey(rgb) ? 1
// : colorOccurrences.get(rgb) + 1;
// colorOccurrences.put(rgb, occur);
// }
// }
// quantizer.setColorOccurrences(colorOccurrences);
// return quantizer.processImage(srcImage);
// }
protected static ImageData convertDepth_4(ImageData srcImage, int depth) {
if (depth != DEPTH_256)
throw new IllegalArgumentException();
NeuQuant nq = new NeuQuant();
nq.init(srcImage, 1);
PaletteData oldPalette = srcImage.palette;
PaletteData newPalette = new PaletteData(nq.getColourMap());
int width = srcImage.width;
int height = srcImage.height;
ImageData result = new ImageData(width, height, depth, newPalette);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
RGB rgb = oldPalette.getRGB(srcImage.getPixel(i, j));
// rgb = nq.convert( rgb );
// result.setPixel( i, j, newPalette.getPixel( rgb ) );
// result.setPixel( i, j, newPalette.getPixel( rgb ) );
result.setPixel(i, j, nq.lookup(rgb));
}
}
return result;
}
private static final PaletteData PALETTE_DATA = new PaletteData(0xFF0000,
0xFF00, 0xFF);
/**
* Converts an AWT based buffered image into an SWT <code>Image</code>. This
* will always return an <code>Image</code> that has 24 bit depth regardless
* of the type of AWT buffered image that is passed into the method.
*
* @param srcImage
* the {@link java.awt.image.BufferedImage} to be converted to an
* <code>Image</code>
* @return an <code>Image</code> that represents the same image data as the
* AWT <code>BufferedImage</code> type.
*/
public static Image convert(Device device, BufferedImage srcImage) {
// We can force bitdepth to be 24 bit because BufferedImage getRGB allows us to always
// retrieve 24 bit data regardless of source color depth.
ImageData swtImageData = new ImageData(srcImage.getWidth(), srcImage
.getHeight(), 24, PALETTE_DATA);
// ensure scansize is aligned on 32 bit.
int scansize = (((srcImage.getWidth() * 3) + 3) * 4) / 4;
WritableRaster alphaRaster = srcImage.getAlphaRaster();
byte[] alphaBytes = new byte[srcImage.getWidth()];
for (int y = 0; y < srcImage.getHeight(); y++) {
int[] buff = srcImage.getRGB(0, y, srcImage.getWidth(), 1, null, 0,
scansize);
swtImageData.setPixels(0, y, srcImage.getWidth(), buff, 0);
// check for alpha channel
if (alphaRaster != null) {
int[] alpha = alphaRaster.getPixels(0, y, srcImage.getWidth(),
1, (int[]) null);
for (int i = 0; i < srcImage.getWidth(); i++)
alphaBytes[i] = (byte) alpha[i];
swtImageData
.setAlphas(0, y, srcImage.getWidth(), alphaBytes, 0);
}
}
return new Image(device, swtImageData);
}
/**
* Converts an swt based image into an AWT <code>BufferedImage</code>. This
* will always return a <code>BufferedImage</code> that is of type
* <code>BufferedImage.TYPE_INT_ARGB</code> regardless of the type of swt
* image that is passed into the method.
*
* @param srcImage
* the {@link org.eclipse.swt.graphics.Image} to be converted to
* a <code>BufferedImage</code>
* @return a <code>BufferedImage</code> that represents the same image data
* as the swt <code>Image</code>
*/
public static BufferedImage convert(Image srcImage) {
ImageData imageData = srcImage.getImageData();
int width = imageData.width;
int height = imageData.height;
ImageData maskData = null;
int alpha[] = new int[1];
if (imageData.alphaData == null)
maskData = imageData.getTransparencyMask();
// now we should have the image data for the bitmap, decompressed in imageData[0].data.
// Convert that to a Buffered Image.
BufferedImage image = new BufferedImage(imageData.width,
imageData.height, BufferedImage.TYPE_INT_ARGB);
WritableRaster alphaRaster = image.getAlphaRaster();
// loop over the imagedata and set each pixel in the BufferedImage to the appropriate color.
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int color = imageData.getPixel(x, y);
color = translateColor(imageData, color);
image.setRGB(x, y, color);
// check for alpha channel
if (alphaRaster != null) {
if (imageData.alphaData != null) {
alpha[0] = imageData.getAlpha(x, y);
alphaRaster.setPixel(x, y, alpha);
} else {
// check for transparency mask
if (maskData != null) {
alpha[0] = maskData.getPixel(x, y) == 0 ? 0 : 255;
alphaRaster.setPixel(x, y, alpha);
}
}
}
}
}
return image;
}
private static int translateColor(ImageData imageData, int color) {
int bitCount = imageData.depth;
RGB[] rgb = imageData.getRGBs();
if (bitCount == 1 || bitCount == 4 || bitCount == 8) {
// Look up actual rgb value in the rgb array.
if (rgb != null) {
java.awt.Color foo = new java.awt.Color(rgb[color].red,
rgb[color].green, rgb[color].blue);
color = foo.getRGB();
} else {
color = 0;
}
} else if (bitCount == 16) {
int BLUE_MASK = 0x1f;
int GREEN_MASK = 0x3e0;
int RED_MASK = 0x7C00;
// Each word in the bitmap array represents a single pixels, 5 bits for each
// red, green and blue.
color = applyRGBMask(color, RED_MASK, GREEN_MASK, BLUE_MASK);
} else if (bitCount == 24) {
// 3 8 bit color values.
int blue = (color & 0x00ff0000) >> 16;
int green = (color & 0x0000ff00) >> 8;
int red = (color & 0x000000ff);
java.awt.Color foo = new java.awt.Color(red, green, blue);
color = foo.getRGB();
} else if (bitCount == 32) {
int blue = (color & 0xff000000) >>> 24;
int green = (color & 0x00ff0000) >> 16;
int red = (color & 0x0000ff00) >> 8;
java.awt.Color foo = new java.awt.Color(red, green, blue);
color = foo.getRGB();
}
return color;
}
private static int applyRGBMask(int color, int redMask, int greenMask,
int blueMask) {
int shiftCount;
int maskSize;
int red;
int green;
int blue;
shiftCount = getShiftCount(redMask);
maskSize = countBits(redMask);
red = (color & redMask) >>> shiftCount;
// Scale the color value to something between 0 and 255.
red = red * 255 / ((int) Math.pow(2, maskSize) - 1);
shiftCount = getShiftCount(greenMask);
maskSize = countBits(greenMask);
green = (color & greenMask) >>> shiftCount;
// Scale the color value to something between 0 and 255.
green = green * 255 / ((int) Math.pow(2, maskSize) - 1);
shiftCount = getShiftCount(blueMask);
maskSize = countBits(blueMask);
blue = (color & blueMask) >>> shiftCount;
// Scale the color value to something between 0 and 255.
blue = blue * 255 / ((int) Math.pow(2, maskSize) - 1);
java.awt.Color foo = new java.awt.Color(red, green, blue);
color = foo.getRGB();
return color;
}
private static int getShiftCount(int mask) {
int count = 0;
while (mask != 0 && ((mask & 0x1) == 0)) {
mask = mask >>> 1;
count++;
}
return count;
}
private static int countBits(int mask) {
int count = 0;
for (int index = 0; index < 32; index++) {
if ((mask & 0x1) != 0) {
count++;
}
mask = mask >>> 1;
}
return count;
}
}