410 lines
13 KiB
Java
410 lines
13 KiB
Java
package schule.ngb.zm.util.io;
|
|
|
|
import schule.ngb.zm.util.Log;
|
|
import schule.ngb.zm.util.Cache;
|
|
import schule.ngb.zm.util.Validator;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import java.awt.Color;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.GraphicsEnvironment;
|
|
import java.awt.Image;
|
|
import java.awt.image.BufferedImage;
|
|
import java.awt.image.ColorModel;
|
|
import java.awt.image.WritableRaster;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.lang.ref.SoftReference;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* Eine Helferklasse mit Klassenmethoden, um Bilder zu laden.
|
|
* <p>
|
|
* Bilder können von verschiedenen Quellen als {@link Image} geladen werden. Die
|
|
* Objekte werden in einem internen Cache gespeichert und nachfolgende Versuche,
|
|
* dieselbe Quelle zu laden, werden aus dem Cache bedient.
|
|
*/
|
|
public final class ImageLoader {
|
|
|
|
public static boolean caching = true;
|
|
|
|
private static final Cache<String, BufferedImage> imageCache = Cache.newSoftCache();
|
|
|
|
/**
|
|
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
|
* <p>
|
|
* Die Bilddatei wird nach den Regeln von
|
|
* {@link ResourceStreamProvider#getInputStream(String)} gesucht und
|
|
* geöffnet. Tritt dabei ein Fehler auf oder konnte keine passende Datei
|
|
* gefunden werden, wird {@code null} zurückgegeben.
|
|
* <p>
|
|
* Ist der {@link #activateCache() Cache aktiviert} und ein Bild mit der
|
|
* angegebenen Quelle schon vorhanden, wird das gespeicherte Bild
|
|
* zurückgegeben.
|
|
*
|
|
* @param source
|
|
* @return
|
|
* @see #loadImage(String, boolean)
|
|
*/
|
|
public static BufferedImage loadImage( String source ) {
|
|
return loadImage(source, caching);
|
|
}
|
|
|
|
/**
|
|
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
|
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden konnte.
|
|
* Ist ein Bild mit der angegebenen Quelle im Cache, wird das gespeicherte
|
|
* Bild zurückgegeben. Dies kann mit {@code caching = false} verhindert
|
|
* werden.
|
|
* <p>
|
|
* Wurde das Caching global deaktiviert, kann mit <code>caching =
|
|
* true</code> das Bild trotzdem aus dem Cache geladen werden, wenn es
|
|
* vorhanden ist.
|
|
*
|
|
* @param source Die Quelle des Bildes.
|
|
* @param caching Ob das Bild (falls vorhanden) aus dem Zwischenspeicher
|
|
* geladen werden soll.
|
|
* @return
|
|
*/
|
|
public static BufferedImage loadImage( String source, boolean caching ) {
|
|
Validator.requireNotNull(source, "Image source may not be null");
|
|
Validator.requireNotEmpty(source, "Image source may not be empty.");
|
|
|
|
if( caching && isCached(source) ) {
|
|
return getCache(source);
|
|
}
|
|
|
|
BufferedImage img = null;
|
|
try( InputStream in = ResourceStreamProvider.getInputStream(source) ) {
|
|
//URL url = ResourceStreamProvider.getResourceURL(source);
|
|
//BufferedImage img = ImageIO.read(url);
|
|
|
|
img = ImageIO.read(in);
|
|
|
|
if( caching && img != null ) {
|
|
putCache(source, img);
|
|
}
|
|
} catch( IOException ioex ) {
|
|
LOG.error(ioex, "Error loading image file from source <%s>.", source);
|
|
}
|
|
return img;
|
|
}
|
|
|
|
/**
|
|
* Lädt ein Bild aus der angegebenen Quelle unter dem angegebenen Namen in
|
|
* den Cache.
|
|
* <p>
|
|
* Der {@code name} kann beliebig gewählt werden. Existiert unter dem Namen
|
|
* schon ein Bild im Zwischenspeicher, wird dieses überschrieben.
|
|
* <p>
|
|
* Wenn der Cache aktiviert ist, werden zukünftige Aufrufe von
|
|
* {@link #loadImage(String)} mit {@code name} als Quelle das gespeicherte
|
|
* Bild zurückgeben.
|
|
*
|
|
* @param name Name des Bildes im Zwischenspeicher.
|
|
* @param source Quelle, aus dem das Bild geladen werden soll.
|
|
* @return {@code true}, wenn das Bild erfolgreich geladen wurde,
|
|
* {@code false} sonst.
|
|
* @see #loadImage(String)
|
|
*/
|
|
public static boolean preloadImage( String name, String source ) {
|
|
BufferedImage img = loadImage(source, true);
|
|
if( img != null ) {
|
|
putCache(name, img);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Speichert das angegebene Bild unter dem angegebenen Namen im Cache.
|
|
* <p>
|
|
* Existiert zu {@code name} schon ein Bild im Zwischenspeicher
|
|
* ({@code ImageLoader.isCached(name) == true}), dann wird dieses
|
|
* überschrieben.
|
|
* <p>
|
|
* Wenn der Cache aktiviert ist, werden zukünftige Aufrufe von
|
|
* {@link #loadImage(String)} mit {@code name} als Quelle das gespeicherte
|
|
* Bild zurückgeben.
|
|
*
|
|
* @param name Name des Bildes im Zwischenspeicher.
|
|
* @param img ZU speicherndes Bild.
|
|
*/
|
|
public static void preloadImage( String name, BufferedImage img ) {
|
|
putCache(name, img);
|
|
}
|
|
|
|
/**
|
|
* Prüft, ob zum angegebenen Namen ein Bild im Cache gespeichert ist.
|
|
*
|
|
* @param name Name des Bildes im Cache.
|
|
* @return {@code true}, wenn es ein Bild zum Namen gibt, sonst
|
|
* {@code false}.
|
|
*/
|
|
public static boolean isCached( String name ) {
|
|
return imageCache.containsKey(name);
|
|
}
|
|
|
|
/**
|
|
* Entfernt das Bild zum angegebenen Namen aus dem Cache. Gibt es zum Namen
|
|
* kein Bild im Zwischenspeicher, dann passiert nichts.
|
|
*
|
|
* @param name Name des Bildes im Cache.
|
|
*/
|
|
public static void invalidateCache( final String name ) {
|
|
imageCache.remove(name);
|
|
}
|
|
|
|
/**
|
|
* Speichert ein Bild als {@link SoftReference} im Cache.
|
|
*
|
|
* @param name Name des Bildes im Zwischenspeicher.
|
|
* @param img Das zu speichernde Bild.
|
|
*/
|
|
private static void putCache( final String name, final BufferedImage img ) {
|
|
imageCache.put(name, img);
|
|
}
|
|
|
|
/**
|
|
* Holt ein Bild aus dem Cache.
|
|
* <p>
|
|
* Prüft nicht, ob ein Bild vorhanden ist. Dies sollte vom Aufrufenden
|
|
* übernommen werden, da sonst eine {@link NullPointerException} erzeugt
|
|
* werden kann.
|
|
*
|
|
* @param name
|
|
* @return
|
|
*/
|
|
private static BufferedImage getCache( final String name ) {
|
|
return imageCache.get(name);
|
|
}
|
|
|
|
/**
|
|
* Deaktiviert den Cache für die angegebene Quelle.
|
|
* <p>
|
|
* Selbst wenn der {@link #activateCache() Cache aktiviert} ist, wird das
|
|
* Bild zur angegebenen Quelle niemals zwischengespeichert und immer neu
|
|
* geladen.
|
|
*
|
|
* @param name Die Quelle des Bildes.
|
|
*/
|
|
public static void preventCache( final String name ) {
|
|
imageCache.nocache(name);
|
|
}
|
|
|
|
/**
|
|
* Leer den Cache und löschte alle bisher gespeicherten Bilder.
|
|
* <p>
|
|
* Auch vorher mit {@link #preventCache(String)} verhinderte Caches werden
|
|
* gelöscht und müssen neu gesetzt werden.
|
|
*/
|
|
public static void clearCache() {
|
|
imageCache.clear();
|
|
}
|
|
|
|
/**
|
|
* Aktiviert den Cache.
|
|
* <p>
|
|
* Der Cache ist ein Zwischenspeicher für geladene Bilder. Wenn er aktiviert
|
|
* ist (Standard), dann werden mit dem {@code ImageLoader} geladene Bilder
|
|
* im Zwischenspeicher abgelegt. Bei jedem folgenden laden desselben Bildes
|
|
* (bzw. eines Bildes mit derselben {@code source}), wird das gespeicherte
|
|
* Bild zurückgegeben und nicht komplett neu geladen.
|
|
* <p>
|
|
* <b>Wichtig:</b> Bildreferenzen, die aus dem Cache geladen werden,
|
|
* verweisen alle auf dasselbe Objekt. Änderungen schlagen sich daher in
|
|
* allen anderen Versionen des Bildes nieder (inklusive dem Bild im
|
|
* Zwischenspeicher). Für Änderungen sollte daher immer
|
|
* {@link #copyImage(BufferedImage) eine Kopie} des Bildes erstellt werden:
|
|
* <pre>
|
|
* BufferedImage originalImage = ImageLoader.loadImage("assets/image.gif");
|
|
* BufferedImage copiedImage = ImageLoader.copyImage(originalImage);
|
|
* </pre>
|
|
* <p>
|
|
* Alternativ kann der Cache umgangen werden, indem
|
|
* {@link #loadImage(String, boolean)} verwendet wird.
|
|
*/
|
|
public static void activateCache() {
|
|
caching = true;
|
|
}
|
|
|
|
/**
|
|
* Deaktiviert den Cache.
|
|
*
|
|
* @see #activateCache()
|
|
*/
|
|
public static void deactivateCache() {
|
|
caching = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Erstellt eine exakte Kopie des angegebenen Bildes als neues Objekt.
|
|
* <p>
|
|
* Die Methode ist hilfreich, wenn ein Bild aus dem
|
|
* {@link #activateCache() Cache} geladen wurde und dann verändert werden
|
|
* soll, ohne den Cache (oder andere Referenzen auf das Bild) zu verändern.
|
|
*
|
|
* @param image Das Originalbild.
|
|
* @return Eine exakte Kopie des Originals.
|
|
*/
|
|
public static BufferedImage copyImage( BufferedImage image ) {
|
|
ColorModel cm = image.getColorModel();
|
|
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
|
|
WritableRaster raster = image.copyData(null);
|
|
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
|
|
}
|
|
|
|
/**
|
|
* Erstellt ein {@code BufferedImage} mit demselben Inhalt wie das
|
|
* angegebene {@code Image}.
|
|
*
|
|
* @param image Das Originalbild.
|
|
* @return Eine exakte Kopie des Originals.
|
|
*/
|
|
public static BufferedImage copyImage( Image image ) {
|
|
if( image instanceof BufferedImage ) {
|
|
return copyImage((BufferedImage) image);
|
|
} else {
|
|
//BufferedImage outimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
|
|
BufferedImage outimage = createImage(image.getWidth(null), image.getHeight(null));
|
|
|
|
Graphics2D g = outimage.createGraphics();
|
|
g.setColor(Color.WHITE);
|
|
g.fillRect(0, 0, outimage.getWidth(), outimage.getHeight());
|
|
g.drawImage(image, 0, 0, null);
|
|
g.dispose();
|
|
|
|
return outimage;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Speichert das angegebene Bild in der angegebenen Datei auf der
|
|
* Festplatte.
|
|
*
|
|
* @param image Das zu speichernde Bild. Falls die Datei schon existiert,
|
|
* wird sie überschrieben.
|
|
* @param file Die Zieldatei.
|
|
* @throws NullPointerException Falls {@code image} oder {@code file}
|
|
* {@code null} ist.
|
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
|
*/
|
|
public static void saveImage( Image image, File file ) throws IOException {
|
|
saveImage(copyImage(image), file, false);
|
|
}
|
|
|
|
/**
|
|
* Speichert das angegebene Bild in der angegebenen Datei auf der
|
|
* Festplatte.
|
|
*
|
|
* @param image Das zu speichernde Bild. Falls die Datei schon existiert,
|
|
* wird sie überschrieben.
|
|
* @param file Die Zieldatei.
|
|
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
|
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
|
* wenn die Datei schon existiert.
|
|
* @throws NullPointerException Falls {@code image} oder {@code file}
|
|
* {@code null} ist.
|
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
|
*/
|
|
public static void saveImage( Image image, File file, boolean overwriteIfExists ) throws IOException {
|
|
saveImage(copyImage(image), file, overwriteIfExists);
|
|
}
|
|
|
|
/**
|
|
* Speichert das angegebene Bild in der angegebenen Datei auf der
|
|
* Festplatte.
|
|
*
|
|
* @param image Das zu speichernde Bild. Falls die Datei schon existiert,
|
|
* wird sie überschrieben.
|
|
* @param file Die Zieldatei.
|
|
* @throws NullPointerException Falls {@code image} oder {@code file}
|
|
* {@code null} ist.
|
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
|
*/
|
|
public static void saveImage( BufferedImage image, File file ) throws IOException {
|
|
saveImage(image, file, false);
|
|
}
|
|
|
|
/**
|
|
* Speichert das angegebene Bild in der angegebenen Datei auf der
|
|
* Festplatte.
|
|
*
|
|
* @param image Das zu speichernde Bild. Falls die Datei schon existiert,
|
|
* wird sie überschrieben.
|
|
* @param file Die Zieldatei.
|
|
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
|
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
|
* wenn die Datei schon existiert.
|
|
* @throws NullPointerException Falls {@code image} oder {@code file}
|
|
* {@code null} ist.
|
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
|
*/
|
|
public static void saveImage( BufferedImage image, File file, boolean overwriteIfExists ) throws IOException {
|
|
if( image == null ) {
|
|
throw new NullPointerException("Image may not be <null>.");
|
|
}
|
|
if( file == null ) {
|
|
throw new NullPointerException("File may not be <null>.");
|
|
}
|
|
|
|
if( file.isFile() ) {
|
|
// Datei existiert schon
|
|
if( !overwriteIfExists ) {
|
|
throw new IOException("File already exists. Delete target file before saving image.");
|
|
} else if( !file.canWrite() ) {
|
|
throw new IOException("File already exists and is not writeable. Change permissions before saving again.");
|
|
}
|
|
}
|
|
|
|
// Dateiformat anhand der Dateiendung ermitteln oder PNG nutzen
|
|
String filename = file.getName();
|
|
String formatName = "png";
|
|
if( filename.lastIndexOf('.') >= 0 ) {
|
|
formatName = filename.substring(filename.lastIndexOf('.') + 1);
|
|
} else {
|
|
file = new File(file.getAbsolutePath() + ".png");
|
|
}
|
|
|
|
// Datei schreiben
|
|
ImageIO.write(image, formatName, file);
|
|
}
|
|
|
|
/**
|
|
* Erstellt ein neues, leeres {@code BufferedImage} passend für dieses
|
|
* Anzeigegerät.
|
|
*
|
|
* @param width Breite des leeren Bildes.
|
|
* @param height Höhe des leeren Bildes.
|
|
* @return Ein neues, leeres Bild.
|
|
*/
|
|
public static BufferedImage createImage( int width, int height ) {
|
|
return createImage(width, height, BufferedImage.TYPE_INT_RGB);
|
|
}
|
|
|
|
/**
|
|
* Erstellt ein neues, leeres {@code BufferedImage} passend für dieses
|
|
* Anzeigegerät.
|
|
*
|
|
* @param width Breite des neuen Bildes.
|
|
* @param height Höhe des neuen Bildes.
|
|
* @param type {@link BufferedImage#getType() Typ} des neuen Bildes.
|
|
* @return Ein neues, leeres Bild.
|
|
*/
|
|
public static BufferedImage createImage( int width, int height, int type ) {
|
|
return GraphicsEnvironment
|
|
.getLocalGraphicsEnvironment()
|
|
.getDefaultScreenDevice()
|
|
.getDefaultConfiguration()
|
|
.createCompatibleImage(width, height, type);
|
|
}
|
|
|
|
private ImageLoader() {
|
|
}
|
|
|
|
private static final Log LOG = Log.getLogger(ImageLoader.class);
|
|
|
|
}
|