/*
* Copyright (c) 2014 tabletoptool.com team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* rptools.com team - initial implementation
* tabletoptool.com team - further development
*/
package com.t3.image;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import org.apache.commons.io.IOUtils;
/**
*
* @author trevor
*/
public class ImageUtil {
public static final String HINT_TRANSPARENCY = "hintTransparency";
// TODO: perhaps look at reintroducing this later
//private static GraphicsConfiguration graphicsConfig = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
public static final FilenameFilter SUPPORTED_IMAGE_FILE_FILTER = new FilenameFilter () {
@Override
public boolean accept(File dir, String name) {
name = name.toLowerCase();
return name.endsWith("png") ||
name.endsWith("gif") ||
name.endsWith("jpg") ||
name.endsWith("jpeg") ||
name.endsWith("bmp");
}
};
// public static void setGraphicsConfiguration(GraphicsConfiguration config) {
// graphicsConfig = config;
// }
//
/**
* Load the image. Does not create a graphics configuration compatible version.
*/
public static Image getImage (File file) throws IOException {
try(FileInputStream is=new FileInputStream(file)) {
return bytesToImage(IOUtils.toByteArray(is));
}
}
/**
* Load the image in the classpath. Does not create a graphics configuration compatible version.
*/
public static Image getImage(String image) throws IOException {
try (
ByteArrayOutputStream dataStream = new ByteArrayOutputStream(8192);
InputStream in1Stream = ImageUtil.class.getClassLoader().getResourceAsStream(image);
) {
int bite;
if (in1Stream == null) {
throw new IOException("Image not found: " + image);
}
try(BufferedInputStream inStream = new BufferedInputStream(in1Stream)) {
while ((bite = inStream.read()) >= 0) {
dataStream.write(bite);
}
return bytesToImage(dataStream.toByteArray());
}
}
}
public static BufferedImage getCompatibleImage(String image) throws IOException {
return getCompatibleImage(image, null);
}
public static BufferedImage getCompatibleImage(String image, Map<String, Object> hints) throws IOException {
return createCompatibleImage(getImage(image), hints);
}
/**
* Create a copy of the image that is compatible with the current graphics context
* @param img
* @return
*/
public static BufferedImage createCompatibleImage(Image img) {
return createCompatibleImage(img, null);
}
public static BufferedImage createCompatibleImage(Image img, Map<String, Object> hints) {
if (img == null) {
return null;
}
return createCompatibleImage(img, img.getWidth(null), img.getHeight(null), hints);
}
public static BufferedImage createCompatibleImage(int width, int height, int transparency) {
return new BufferedImage(width, height, transparency);
}
/**
* Create a copy of the image that is compatible with the current graphics context
* and scaled to the supplied size
*/
public static BufferedImage createCompatibleImage(Image img, int width, int height, Map<String, Object> hints) {
width = Math.max(width, 1);
height = Math.max(height, 1);
int transparency;
if (hints != null && hints.containsKey(HINT_TRANSPARENCY)) {
transparency = (Integer) hints.get(HINT_TRANSPARENCY);
} else {
transparency = pickBestTransparency(img);
}
BufferedImage compImg = new BufferedImage(width, height, transparency);
Graphics2D g = null;
try {
g = compImg.createGraphics();
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.drawImage(img, 0, 0, width, height, null);
} finally {
if (g != null) {
g.dispose();
}
}
return compImg;
}
/**
* Look at the image and determine which Transparency is most appropriate.
* If it finds any translucent pixels it returns Transparency.TRANSLUCENT, if
* it finds at least one purely transparent pixel and no translucent pixels
* it will return Transparency.BITMASK, in all other cases it returns
* Transparency.OPAQUE, including errors
*
* @param image
* @return one of Transparency constants
*/
public static int pickBestTransparency ( Image image ) {
// Take a shortcut if possible
if (image instanceof BufferedImage) {return pickBestTransparency((BufferedImage) image);}
// Legacy method
// NOTE: This is a horrible memory hog
int width = image.getWidth(null);
int height = image.getHeight(null);
int [] pixelArray = new int [ width * height ];
PixelGrabber pg = new PixelGrabber( image, 0, 0, width, height, pixelArray, 0, width );
try
{
pg.grabPixels();
}
catch (InterruptedException e)
{
System.err.println("interrupted waiting for pixels!");
return Transparency.OPAQUE;
}
if ((pg.getStatus() & ImageObserver.ABORT) != 0)
{
System.err.println("image fetch aborted or errored");
return Transparency.OPAQUE;
}
// Look for specific pixels
boolean foundTransparent = false;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Get the next pixel
int pixel = pixelArray [ y*width + x ];
int alpha = (pixel >> 24) & 0xff;
// Is there translucency or just pure transparency ?
if ( alpha > 0 && alpha < 255 )
{
return Transparency.TRANSLUCENT;
}
if ( alpha == 0 && !foundTransparent )
{
foundTransparent = true;
}
}
}
return foundTransparent ? Transparency.BITMASK : Transparency.OPAQUE;
}
public static int pickBestTransparency ( BufferedImage image ) {
// See if we can short circuit
ColorModel colorModel = image.getColorModel();
if (colorModel.getTransparency() == Transparency.OPAQUE) {
return Transparency.OPAQUE;
}
// Get the pixels
int width = image.getWidth();
int height = image.getHeight();
// Look for specific pixels
boolean foundTransparent = false;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Get the next pixel
int pixel = image.getRGB(x, y);
int alpha = (pixel >> 24) & 0xff;
// Is there translucency or just pure transparency ?
if ( alpha > 0 && alpha < 255 )
{
return Transparency.TRANSLUCENT;
}
if ( alpha == 0 && !foundTransparent )
{
foundTransparent = true;
}
}
}
return foundTransparent ? Transparency.BITMASK : Transparency.OPAQUE;
}
public static byte[] imageToBytes(BufferedImage image) throws IOException {
return imageToBytes(image, "jpg");
}
public static byte[] imageToBytes(BufferedImage image, String format) throws IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream(10000);
ImageIO.write(image, format, outStream);
return outStream.toByteArray();
}
private static final JPanel observer = new JPanel();
public static Image bytesToImage(byte[] imageBytes) throws IOException {
if (imageBytes == null) {
System.out.println("WEhaah??");
}
Throwable exception = null;
Image image = null;
try {
image = Toolkit.getDefaultToolkit().createImage(imageBytes);
MediaTracker tracker = new MediaTracker(observer);
tracker.addImage(image, 0);
tracker.waitForID(0);
} catch (Throwable t) {
exception = t;
}
if (image == null || exception != null || image.getWidth(null) <= 0 || image.getHeight(null) <= 0) {
// Try the newer way (although it pretty much sucks rocks)
image = ImageIO.read(new ByteArrayInputStream(imageBytes));
}
if (image == null) {
throw new IOException("Could not load image: " + exception);
}
return image;
}
public static void clearImage(BufferedImage image) {
if (image == null) {return;}
Graphics2D g = null;
try {
g = (Graphics2D)image.getGraphics();
Composite oldComposite = g.getComposite();
g.setComposite(AlphaComposite.Clear);
g.fillRect(0, 0, image.getWidth(), image.getHeight());
g.setComposite(oldComposite);
} finally {
if (g != null) {
g.dispose();
}
}
}
public static BufferedImage rgbToGrayscale(BufferedImage image) {
if (image == null) {
return null;
}
BufferedImage returnImage = new BufferedImage (image.getWidth(), image.getHeight(), pickBestTransparency(image));
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int encodedPixel = image.getRGB(x, y);
int alpha = (encodedPixel >> 24) & 0xff;
int red = (encodedPixel >> 16) & 0xff;
int green = (encodedPixel >> 8) & 0xff;
int blue = (encodedPixel ) & 0xff;
int average = (int)((red + blue + green) / 3.0);
// y = 0.3R + 0.59G + 0.11B luminance formula
int value = (alpha << 24) + (average << 16) + (average << 8) + average;
returnImage.setRGB(x, y, value);
}
}
return returnImage;
}
private static final int[][] outlineNeighborMap = {
{0, -1, 100}, // N
{1, 0, 100}, // E
{0, 1, 100}, // S
{-1, 0, 100} // W
,
{-1, -1}, // NW
{1, -1}, // NE
{-1, 1}, // SW
{1, 1}, // SE
};
public static BufferedImage createOutline(BufferedImage sourceImage, Color color) {
if (sourceImage == null) {
return null;
}
BufferedImage image = new BufferedImage(sourceImage.getWidth()+2, sourceImage.getHeight()+2, Transparency.BITMASK);
for (int row = 0; row < image.getHeight(); row++) {
for (int col = 0; col < image.getWidth(); col++) {
int sourceX = col-1;
int sourceY = row-1;
// Pixel under current location
if (sourceX >= 0 && sourceY >= 0 && sourceX <= sourceImage.getWidth()-1 && sourceY <= sourceImage.getHeight()-1) {
int sourcePixel = sourceImage.getRGB(sourceX, sourceY);
if (sourcePixel >> 24 != 0) {
// Not an empty pixel, don't overwrite it
continue;
}
}
for (int i = 0; i < outlineNeighborMap.length; i++) {
int[] neighbor = outlineNeighborMap[i];
int x = sourceX + neighbor[0];
int y = sourceY + neighbor[1];
if (x >= 0 && y >= 0 && x <= sourceImage.getWidth()-1 && y <= sourceImage.getHeight()-1) {
if ((sourceImage.getRGB(x, y) >> 24) != 0) {
image.setRGB(col, row, color.getRGB());
break;
}
}
}
}
}
return image;
}
/**
* Flip the image and return a new image
* @param direction 0-nothing, 1-horizontal, 2-vertical, 3-both
* @return
*/
public static BufferedImage flip(BufferedImage image, int direction) {
BufferedImage workImage = new BufferedImage(image.getWidth(), image.getHeight(), image.getTransparency());
boolean flipHorizontal = (direction&1) == 1;
boolean flipVertical = (direction&2) == 2;
int workW = image.getWidth() * (flipHorizontal ? -1 : 1);
int workH = image.getHeight() * (flipVertical ? -1 : 1);
int workX = flipHorizontal ? image.getWidth() : 0;
int workY = flipVertical ? image.getHeight() : 0;
Graphics2D wig = workImage.createGraphics();
wig.drawImage(image, workX, workY, workW, workH, null);
wig.dispose();
return workImage;
}
}