BufferedImage vs. Image & Bilder in der Grösse verändern

Hey Leute.

2 Fragen:

  1. Welche der folgenden methoden ist besser, wenn man ein bild zur laufzeit in einer anderen grösse benötigt?
    (man gehe davon aus, das in der zweiten variante die paintComponent ziemlich oft, also in einer game loop aufgerufen wird)

g.drawImage(img, x, y, width, height, null);

oder mit

img = img.getScaledInstance(width, height, hint);

Welche Variante ist “grundsätzlich” besser?
Wenn ich zum Beispiel ein Bild im Spiel immer wieder mit g.drawImage() in anderer grösse zeichne, ist das dann
langsamer als einfach am anfang das img mit getScaledInstance() zu bekommen?

  1. Frage:

Welche Vorteile haben BufferedImage’s gegenüber normalen Images - oder eben auch nicht?

Ich würde 1. bevorzugen, damit überlässt du der Implementierung von dem Graphics-Objekt die Arbeit des skalierten Zeichnens und erzwingst keine neue BufferedImage-Instanz (die vielleicht garnicht benötigt wird).

Eine skalierte Instanz könnte aber auch Sinn machen, wenn deine Bilder oft identische Größen haben, die sich nur selten Ändern (Beispielsweise bei Stufenbasiertem Zoom, der nur alle paar 1000 Frames geändert wird o.ä).

Gruß

DIE ERSTE!

Die zweite vergiss blos ganz schnell wieder, die ist redundant seitdem es Swing (bzw. Java2D) gibt, also mindestens seit Java 1.2. Das geht mit ImageFiltern, BufferedImages und “createGraphics()” erstens viel schneller und zweitens spart man sich die ständige Überwachung durch MediaTracker oder ImageObserver. Womit wir auch gleich bei der Antwort auf Frage 2 wären: Bei BufferedImages ist man stets versichert, dass diese vollständig geladen wurden, also Speicher (einen Buffer) belegen. Nicht zu vergessen, dass Bildhöhe und -breite bei BIs nie <= 0 werden können.

okay vielen dank euch!
Also niemals in der paintComponent() width und height setzen?

[QUOTE=mymaksimus]Also niemals in der paintComponent() width und height setzen?[/QUOTE]Erstens, was hat das damit zu tun?
Zweitens, blos nicht (also ja, niemals)! Das könnte (oder wird auf jeden Fall sogar, denk’ ich mal) in einer Endlosrekursion führen, weil der EDT dann immer den EDT aufruft. Solche Dinge nur mit „EventQueue.invokeLater()“. :wink:

Aeh sry ich war grad verwirrt.

Also get scaled instance nicht benutzen?

Ich meinte natuerlich das man get scaled instance nur einmal aufruft; und dann gar keine groessen parameter benoetigt

Ein bißchen was steht in http://wiki.byte-welt.net/wiki/Bilder_skalieren .

Wenn man EINE skalierte Instanz erstellen will (und danach NUR noch die, “unendlich oft” gezeichnet werden soll), kann getScaledInstance OK sein (wobei ich da nochmal schauen müßte, was für BufferedImage.TYPE_* da denn rauskommen), aber in dem im Wiki-Eintrag verlinkten Artikel stehen noch ein paar weitere Details dazu.

[QUOTE=mymaksimus]Aeh sry ich war grad verwirrt.

Also get scaled instance nicht benutzen?

Ich meinte natuerlich das man get scaled instance nur einmal aufruft; und dann gar keine groessen parameter benoetigt[/QUOTE]“Nicht benötigt” ist gut btw ;). Zumindest nicht beim “drawImage()”-Aufruf, aber genau dort sollte man sie verwenden.
Ist alles eine Frage des Speicherverbrauchs. “getScaledInstance()” liefert stets ein Image mit neu reserviertem Speicherbereich, jedoch ohne Garantie, dass das Original auch gleich in diesen hineinkopiert wurde (erkennt man daran, dass die Breiten- und Höhenangaben des Images negative Werte liefern). Ein “drawImage()” mir Grössenangaben hingegen zeichnet das Image gescaled in den Graphics-Context und dass ohne dabei neuer Speicher reserviert wird. Nimmt man nun noch den Umstand hinzu, das "java.awt.Image"es alles andere als verlässlich zu handhaben sind, haben Methoden, die ein solches liefern durch die Bank bis auf @Deprecated eigentlich gar keine Existenzberechtigung mehr, weil man bei BufferedImages die bessere Kontrolle hat. Ein Image scaled man (wenns denn sein muß) also besser so:

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageProducer;
import java.awt.image.WritableRaster;
import java.util.Hashtable;

public final class ImageUtils {
	private ImageUtils() {
		// nothing
	}

	public static BufferedImage waitForLoad(Image img) {
		if(img instanceof BufferedImage) {
			return (BufferedImage) img;
		}
		ImageProducer producer = img.getSource();
		final BufferedImage[] rc = new BufferedImage[1];
		ImageConsumer consumer = new ImageConsumer() {
			private Hashtable<?, ?> props;
			private ColorModel model;
			private WritableRaster raster;
			private int width, height;
			boolean err = true;

			@Override
			public void setProperties(Hashtable<?, ?> props) {
				this.props = props;
			}
			
			@Override
			public void setPixels(int x, int y, int w, int h, ColorModel model,
					int[] pixels, int off, int scansize) {
				if(this.model == null) {
					this.model = model;
				}
				if(this.model != model) {
					err = true;
				}
				raster = model.createCompatibleWritableRaster(width, height);
				raster.setDataElements(x, y, w, h, pixels);
			}
			
			@Override
			public void setPixels(int x, int y, int w, int h, ColorModel model,
					byte[] pixels, int off, int scansize) {
				if(this.model == null) {
					this.model = model;
				}
				if(this.model != model) {
					err = true;
				}
				raster = model.createCompatibleWritableRaster(width, height);
				raster.setDataElements(x, y, w, h, pixels);
			}
			
			@Override
			public void setHints(int hintflags) {
			}
			
			@Override
			public void setDimensions(int width, int height) {
				this.width = width;
				this.height = height;
			}
			
			@Override
			public void setColorModel(ColorModel model) {
				this.model = model;
			}
			
			@Override
			public void imageComplete(int status) {
				if(status == SINGLEFRAMEDONE || status == STATICIMAGEDONE && !err) {
					rc[0] = new BufferedImage(model, raster, model.isAlphaPremultiplied(), props);
				}
				synchronized (this) {
					notifyAll();
				}
			}
		};
		producer.startProduction(consumer);
		synchronized(consumer) {
			try {
				consumer.wait();
			} catch(InterruptedException e) {
				// tja
			}
		}
		return rc[0];
	}

	public static BufferedImage scale(Image src, int width, int height) {
		if(width <= 0 || height <= 0) {
			throw new IllegalArgumentException("invalid width or height");
		}
		BufferedImage tmp = waitForLoad(src);
		if(width == tmp.getWidth() && height == tmp.getHeight()) {
			return tmp;
		}
		ColorModel cm = tmp.getColorModel();
		WritableRaster wr = cm.createCompatibleWritableRaster(width, height);
		BufferedImage rc = new BufferedImage(cm, wr, cm.isAlphaPremultiplied(), null);
		Graphics g2d = rc.createGraphics();
		g2d.drawImage(tmp, 0, 0, width, height, null);
		g2d.dispose();
		return rc;
	}
}```

edit: Bei der waitForLoad-Methode bin ich mir im Übrigen gar nicht so sicher, ob das die zuletzt funktionierende war... Da hatte ich mal arg viele Probleme bei den beiden "setPixel()"-Methoden des ImageConsumers. Wenn diese Methode nicht immer funktioniert, bitte melden... Die letzte Version ist gemeinsam mit 'ner Festplatte leider vor einiger Zeit in den Hades gegangen.