/* * 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; } }