/**
* Copyright (c) 2009 - 2010 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org>
*
* This file is part of org.appwork.utils.ImageProvider
*
* This software is licensed under the Artistic License 2.0,
* see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php
* for details
*/
package org.appwork.utils.ImageProvider;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.logging.Level;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.UIManager;
import org.appwork.storage.config.MinTimeWeakReference;
import org.appwork.utils.Application;
import org.appwork.utils.logging.Log;
import sun.awt.image.ToolkitImage;
/**
* This class grants easy access to images stored in APPROOT/images/**.png
*
* @author $Author: unknown$
*
*/
public class ImageProvider {
private static final long MIN_LIFETIME = 20000l;
/**
* Hashcashmap to cache images.
*/
private static HashMap<String, MinTimeWeakReference<BufferedImage>> IMAGE_CACHE = new HashMap<String, MinTimeWeakReference<BufferedImage>>();
private static HashMap<String, MinTimeWeakReference<ImageIcon>> IMAGEICON_CACHE = new HashMap<String, MinTimeWeakReference<ImageIcon>>();
private static HashMap<Icon, MinTimeWeakReference<Icon>> DISABLED_ICON_CACHE = new HashMap<Icon, MinTimeWeakReference<Icon>>();
private static Object LOCK = new Object();
// stringbuilder die concat strings fast
static {
/* we dont want images to get cached on disk */
ImageIO.setUseCache(false);
}
/**
* @param bufferedImage
* @return
*/
public static BufferedImage convertToGrayScale(final BufferedImage bufferedImage) {
final BufferedImage dest = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
final Graphics2D g2 = dest.createGraphics();
g2.drawImage(bufferedImage, 0, 0, null);
g2.dispose();
return dest;
}
/**
* Creates a dummy Icon
*
* @param string
* @param i
* @param j
* @return
*/
public static BufferedImage createIcon(final String string, final int width, final int height) {
final int w = width;
final int h = height;
final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice gd = ge.getDefaultScreenDevice();
final GraphicsConfiguration gc = gd.getDefaultConfiguration();
final BufferedImage image = gc.createCompatibleImage(w, h, Transparency.BITMASK);
final Graphics2D g = image.createGraphics();
int size = 1 + width / string.length();
// find max font size
int ww = 0;
int hh = 0;
while (size > 0) {
size--;
g.setFont(new Font("Arial", Font.BOLD, size));
ww = g.getFontMetrics().stringWidth(string);
hh = g.getFontMetrics().getAscent();
if (ww < w - 4 && hh < h - 2) {
break;
}
}
g.setColor(Color.WHITE);
g.fillRect(0, 0, w - 1, h - 1);
g.draw3DRect(0, 0, w - 1, h - 1, true);
g.setColor(Color.BLACK);
g.drawString(string, (w - ww) / 2, hh + (h - hh) / 2);
g.dispose();
return image;
}
/**
* this creates a new BufferedImage from an existing Image. Is used for
* dereferencing the sourceImage in scaled Images created with
* image.getScaledInstance, which always keeps a reference to its original
* image
*
* @param image
* @return
* @throws IOException
*/
public static BufferedImage dereferenceImage(final Image image) throws IOException {
final BufferedImage bu = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
final Graphics g = bu.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return bu;
}
/**
*
* @param name
* to the png file
* @param createDummy
* TODO
* @return
* @throws IOException
*/
public static BufferedImage getBufferedImage(final String name, final boolean createDummy) throws IOException {
return ImageProvider.getBufferedImage(name, createDummy, true);
}
public static BufferedImage getBufferedImage(final String name, final boolean createDummy, final boolean putIntoCache) throws IOException {
synchronized (ImageProvider.LOCK) {
if (ImageProvider.IMAGE_CACHE.containsKey(name)) {
final MinTimeWeakReference<BufferedImage> cache = ImageProvider.IMAGE_CACHE.get(name);
if (cache.get() != null) { return cache.get(); }
}
final URL absolutePath = Application.getRessourceURL("images/" + name + ".png");
try {
Log.L.info("Init Image: " + name + ": " + absolutePath);
final BufferedImage image = ImageProvider.read(absolutePath);
if (putIntoCache) {
if (image.getHeight() * image.getWidth() > 100 * 100) {
// Log.exception(new Throwable("BIG IMAGE IN CACHE: " +
// name));
}
ImageProvider.IMAGE_CACHE.put(name, new MinTimeWeakReference<BufferedImage>(image, ImageProvider.MIN_LIFETIME, name));
}
return image;
} catch (final IOException e) {
Log.L.severe("Could not Init Image: " + absolutePath);
if (createDummy) {
Log.exception(Level.WARNING, e);
return ImageProvider.createIcon(name.toUpperCase(Locale.ENGLISH), 48, 48);
} else {
throw e;
}
} catch (final Throwable e) {
Log.L.severe("Could not Init Image: " + absolutePath);
Log.exception(Level.WARNING, e);
return ImageProvider.createIcon(name.toUpperCase(Locale.ENGLISH), 48, 48);
}
}
}
/**
* Uses the uimanager to get a grayscaled disabled Icon
*
* @param icon
* @return
*/
public static Icon getDisabledIcon(final Icon icon) {
if (icon == null) { return null; }
final MinTimeWeakReference<Icon> cache = ImageProvider.DISABLED_ICON_CACHE.get(icon);
Icon ret = cache == null ? null : cache.get();
if (ret != null) { return ret; }
ret = UIManager.getLookAndFeel().getDisabledIcon(null, icon);
ImageProvider.DISABLED_ICON_CACHE.put(icon, new MinTimeWeakReference<Icon>(ret, ImageProvider.MIN_LIFETIME, "disabled icon"));
return ret;
}
/**
* @param string
* @param i
* @param j
* @return
*/
public static ImageIcon getImageIcon(final String name, final int x, final int y) {
// TODO Auto-generated method stub
try {
return ImageProvider.getImageIcon(name, x, y, true);
} catch (final IOException e) {
// can not happen. true creates a dummyicon in case of io errors
Log.exception(e);
return null;
}
}
/**
* Loads the image, scales it to the desired size and returns it as an
* imageicon
*
* @param name
* @param width
* @param height
* @param createDummy
* TODO
* @return
* @throws IOException
*/
public static ImageIcon getImageIcon(final String name, final int width, final int height, final boolean createDummy) throws IOException {
return ImageProvider.getImageIcon(name, width, height, createDummy, true);
}
public static ImageIcon getImageIcon(final String name, int width, int height, final boolean createDummy, final boolean putIntoCache) throws IOException {
synchronized (ImageProvider.LOCK) {
final StringBuilder SB = new StringBuilder();
SB.append(name);
SB.append('_');
SB.append(width);
SB.append('_');
SB.append(height);
String key;
if (ImageProvider.IMAGEICON_CACHE.containsKey(key = SB.toString())) {
final MinTimeWeakReference<ImageIcon> cache = ImageProvider.IMAGEICON_CACHE.get(key);
if (cache.get() != null) { return cache.get(); }
}
final BufferedImage image = ImageProvider.getBufferedImage(name, createDummy, putIntoCache);
final double faktor = Math.max((double) image.getWidth(null) / width, (double) image.getHeight(null) / height);
width = (int) (image.getWidth(null) / faktor);
height = (int) (image.getHeight(null) / faktor);
/**
* WARNING: getScaledInstance will return a scaled image, BUT keeps
* a reference to original unscaled image
*/
final Image scaledWithFuckingReference = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
final BufferedImage referencelessVersion = ImageProvider.dereferenceImage(scaledWithFuckingReference);
final ImageIcon imageicon = new ImageIcon(referencelessVersion);
if (putIntoCache) {
ImageProvider.IMAGEICON_CACHE.put(key, new MinTimeWeakReference<ImageIcon>(imageicon, ImageProvider.MIN_LIFETIME, key));
}
return imageicon;
}
}
public static ImageIcon getImageIconUnCached(final String name, final int x, final int y) {
// TODO Auto-generated method stub
try {
return ImageProvider.getImageIcon(name, x, y, true, false);
} catch (final IOException e) {
// can not happen. true creates a dummyicon in case of io errors
Log.exception(e);
return null;
}
}
/**
* Taken from http://today.java.net/pub/a/today/2007/04/03/perils-of-image-
* getscaledinstance.html License: unknown Convenience method that returns a
* scaled instance of the provided {@code BufferedImage}.
*
* @param img
* the original image to be scaled
* @param targetWidth
* the desired width of the scaled instance, in pixels
* @param targetHeight
* the desired height of the scaled instance, in pixels
* @param hint
* one of the rendering hints that corresponds to
* {@code RenderingHints.KEY_INTERPOLATION} (e.g.
* {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
* {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
* {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
* @param higherQuality
* if true, this method will use a multi-step scaling technique
* that provides higher quality than the usual one-step technique
* (only useful in downscaling cases, where {@code targetWidth}
* or {@code targetHeight} is smaller than the original
* dimensions, and generally only when the {@code BILINEAR} hint
* is specified)
* @return a scaled version of the original {@code BufferedImage}
*/
public static BufferedImage getScaledInstance(final BufferedImage img, int width, int height, final Object hint, final boolean higherQuality) {
final double faktor = Math.max((double) img.getWidth() / width, (double) img.getHeight() / height);
width = (int) (img.getWidth() / faktor);
height = (int) (img.getHeight() / faktor);
if (faktor == 1.0) { return img; }
BufferedImage ret = img;
int w, h;
if (higherQuality) {
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = Math.max(width, img.getWidth());
h = Math.max(height, img.getHeight());
} else {
// Use one-step technique: scale directly from original
// size to target size with a single drawImage() call
w = width;
h = height;
}
do {
if (higherQuality && w > width) {
w /= 2;
if (w < width) {
w = width;
}
}
if (higherQuality && h > height) {
h /= 2;
if (h < height) {
h = height;
}
}
// use 6 as default image type. java versions <16 u17 return type 0
// for loaded pngs
final BufferedImage tmp = new BufferedImage(w, h, ret.getType() == 0 ? 6 : ret.getType());
final Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != width || h != height);
return ret;
}
/**
* @param back
* @param imageIcon
* @param i
* @param j
*/
public static BufferedImage merge(final Image back, final Image front, final int xoffset, final int yoffset) {
int xoffsetTop, yoffsetTop, xoffsetBottom, yoffsetBottom;
if (xoffset >= 0) {
xoffsetTop = 0;
xoffsetBottom = xoffset;
} else {
xoffsetTop = -xoffset;
xoffsetBottom = 0;
}
if (yoffset >= 0) {
yoffsetTop = 0;
yoffsetBottom = yoffset;
} else {
yoffsetTop = -yoffset;
yoffsetBottom = 0;
}
return ImageProvider.merge(back, front, xoffsetTop, yoffsetTop, xoffsetBottom, yoffsetBottom);
}
public static BufferedImage merge(final Image back, final Image front, final int xoffsetBack, final int yoffsetBack, final int xoffsetFront, final int yoffsetFront) {
final int width = Math.max(xoffsetBack + back.getWidth(null), xoffsetFront + front.getWidth(null));
final int height = Math.max(yoffsetBack + back.getHeight(null), yoffsetFront + front.getHeight(null));
final BufferedImage dest = new BufferedImage(width, height, Transparency.TRANSLUCENT);
final Graphics2D g2 = dest.createGraphics();
g2.drawImage(back, xoffsetBack, yoffsetBack, null);
g2.drawImage(front, xoffsetFront, yoffsetFront, null);
g2.dispose();
return dest;
}
/* copied from ImageIO, to close the inputStream */
public static BufferedImage read(final File input) throws IOException {
if (!input.canRead()) { throw new IIOException("Can't read input file!"); }
return ImageProvider.read(input.toURI().toURL());
}
/**
* @param absolutePath
* @return
* @throws IOException
*/
private static BufferedImage read(final URL absolutePath) throws IOException {
if (absolutePath == null) { throw new IllegalArgumentException("input == null!"); }
final ImageInputStream stream = ImageIO.createImageInputStream(absolutePath.openStream());
BufferedImage bi = null;
try {
if (stream == null) { throw new IIOException("Can't create an ImageInputStream!"); }
bi = ImageIO.read(stream);
} finally {
try {
stream.close();
} catch (final Throwable e) {
}
}
return bi;
}
/**
* @param scaleBufferedImage
* @param width
* @param height
* @return
*/
public static BufferedImage resizeWorkSpace(final Image scaleBufferedImage, final int width, final int height) {
// final GraphicsEnvironment ge =
// GraphicsEnvironment.getLocalGraphicsEnvironment();
// final GraphicsDevice gd = ge.getDefaultScreenDevice();
// final GraphicsConfiguration gc = gd.getDefaultConfiguration();
// final BufferedImage image = gc.createCompatibleImage(width, height,
// Transparency.BITMASK);
final BufferedImage image = new BufferedImage(width, height, Transparency.TRANSLUCENT);
final Graphics2D g = image.createGraphics();
g.drawImage(scaleBufferedImage, (width - scaleBufferedImage.getWidth(null)) / 2, (height - scaleBufferedImage.getHeight(null)) / 2, null);
g.dispose();
return image;
}
/**
* Scales a buffered Image to the given size. This method is NOT cached. so
* take care to cache it externally if you use it frequently
*
* @param img
* @param width
* @param height
* @return
*/
public static Image scaleBufferedImage(final BufferedImage img, int width, int height) {
if (img == null) { return null; }
final double faktor = Math.max((double) img.getWidth() / width, (double) img.getHeight() / height);
width = (int) (img.getWidth() / faktor);
height = (int) (img.getHeight() / faktor);
if (faktor == 1.0) { return img; }
final Image image = img.getScaledInstance(width, height, Image.SCALE_SMOOTH);
try {
return ImageProvider.dereferenceImage(image);
} catch (final IOException e) {
Log.exception(e);
return null;
}
}
/**
* Scales an imageicon to w x h.<br>
* like {@link #scaleBufferedImage(BufferedImage, int, int)}, this Function
* is NOT cached. USe an external cache if you use it frequently
*
* @param img
* @param w
* @param h
* @return
*/
public static ImageIcon scaleImageIcon(final ImageIcon img, final int w, final int h) {
// already has the desired size?
if (img.getIconHeight() == h && img.getIconWidth() == w) { return img; }
BufferedImage dest;
if (img.getImage() instanceof ToolkitImage) {
dest = new BufferedImage(w, h, Transparency.TRANSLUCENT);
final Graphics2D g2 = dest.createGraphics();
g2.drawImage(img.getImage(), 0, 0, null);
g2.dispose();
} else {
dest = (BufferedImage) img.getImage();
}
return new ImageIcon(ImageProvider.scaleBufferedImage(dest, w, h));
}
/**
* Converts an Icon to an Imageicon.
*
* @param icon
* @return
*/
public static ImageIcon toImageIcon(final Icon icon) {
if (icon == null) { return null; }
if (icon instanceof ImageIcon) {
return (ImageIcon) icon;
} else {
final int w = icon.getIconWidth();
final int h = icon.getIconHeight();
final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice gd = ge.getDefaultScreenDevice();
final GraphicsConfiguration gc = gd.getDefaultConfiguration();
final BufferedImage image = gc.createCompatibleImage(w, h, Transparency.BITMASK);
final Graphics2D g = image.createGraphics();
icon.paintIcon(null, g, 0, 0);
g.dispose();
return new ImageIcon(image);
}
}
}