# Performance von BufferedImages



## Marco13 (20. Jun 2011)

_Bemerkung: Abgetrennt von Apos Spielesammelthread.
Es geht um schlechte Performance von BufferedImages._

Das Problem ist nicht so unbekannt. Dass das Laden länger dauert könnte an der Kompression liegen (da gibt's bei PNG auch verschiedene), aber das Laden ist meistens das geringste Problem. 

Das kritische ist das Zeichnen: Wenn man PNGs lädt, dann haben die (meistens?) den Typ "TYPE_CUSTOM". Ich hatte da mal in den Tiefen des Sun-Quellcodes gewühlt, die leicht vereinfachte Quintessenz: RICHTIG schnell wird das ganze nur mit TYPE_INT_*, ganz grob: weil diese Bilder "Managed" sein können und Sun da magische Optimierungen (mit VRAM-Nutzung und was weiß ich) veranstalten kann. Bei allen anderen Typen muss praktisch bei jedem Zugriff auf einen einzelnen Pixel eine absurd-aufwändige Konvertierung der Pixel über ColorModel und irgendwelche Bands und so gemacht werden, damit am Ende ein 32bit int mit der passenden Reihenfolge von RGB und A rauskommt (auch wenn das beim Zeichnen "blockweise" gemacht wird, ist es gähnend langsam). 

Meine "Faustregel" wenn es darum geht, Bilder schnell auf den Bildschirm zu bringen: Genau das, was du jetzt gemacht hast  Das Bild laden und als allererstes in ein TYPE_INT_* Bild reinmalen. Kürzlich hatte ich in http://www.java-forum.org/spiele-multimedia-programmierung/120102-pixel-bufferedimage-bearbeiten-performance.html#post775119 auch ein paar (teilweise vielleicht etwas veraltete, aber zumindest interessante) Links dazu gepostet, aber im Web und dem Sun-Sourcecode findet man da noch mehr.


----------



## Apo (20. Jun 2011)

Tausend Dank Marco. Ich hab mir deine Links mal durchgelesen und gesehen, dass auch getSubImage() betroffen ist/sein könnte. Natürlich (unwissend) habe ich das häufig zum Zeichnen benutzt. Ich werde die Bilder mal zwischenspeichern und den direkten Test fahren lassen. So bekomme ich meine kleine Engine aber auch endlich mal schneller. =)
Mal schauen was ich noch alles zum Verbessern/Optimieren finde. 

€dit: Entschuldigt meinen Kraftausdruck aber OMFG ... ich habe gerade beim neuen Programmierwettbewerb für das nächste Jahr die Methode getSubimage() eliminiert und was soll ich sagen ... ich brauche nun statt 3 Millisekunden sage und schreibe maximal 1 Millisekunde zum Zeichnen (tendenz eher weniger) und das auf meinem 3 Jahre alten Laptop ... Ich weiß nicht, ob ich grad Fluchen oder Jubeln soll. Mensch das hätte man mir echt früher sagen können bzw. hätte ich das mal früher rausfinden sollen.


----------



## LoR (20. Jun 2011)

Aus den Gründen die Marco genannt hat gibt's die Funktion GraphicsConfiguration#createCompatibleImage GraphicsConfiguration (Java 2 Platform SE v1.4.2). Diese erstellt ein BufferedImage "... that is closest to this native device configuration and can therefore be optimally blitted to this device.".

Edit:
Der Modus "godlike" ist noch etwas zu einfach. Habs direkt beim ersten Versuch geschaft. Ansonsten ech nett gemacht :toll:.


----------



## Marco13 (20. Jun 2011)

@LoR: Laut diesem Absatz in den 1.5er release notes sollte gerade das nicht mehr notwendig sein. (Aber bei ImageIO.read können eben noch unpassende Bilder geliefert werden, deswegen am besten erst in ein neu erstelletes BufferedImage.TYPE_INT_* reinmalen)


----------



## LoR (20. Jun 2011)

Hi Marco,

interssanter Absatz war mir bisher nicht bekannt. Dennoch heisst das nicht, dass ein Image das mit der Methode 
	
	
	
	





```
GraphicsConfiguration#createCompatibleImage
```
 erstellt wurde nicht besser performt. Aus meinem Verständnis heraus wird im Fall eines Managed-Image ("accelerated") eine Kopie des eigentlichen BufferedImage im VRAM abgelegt das dann innerhalb des VRAM in den BackBuffer der Swing-App kopiert wird. Die Transformation des BufferedImage in das "Device-Image"-Format (Kopie im VRAM) kann aber, wie du auch schon erklärt hast, durchaus aufwändig sein. Ein kompatibles Format zum verwendeten Screen wäre somit sinnvoll. Was meinst du?


----------



## Marco13 (20. Jun 2011)

Ja, die Möglichkeit hatte ich auch in Erwägung gezogen - schließlich hat man durch Angabe des BufferedImage.TYPE... noch Freiheitsgrade, von denen bei der GraphicsConfig-Methode vielleicht die "beste" verwendet werden würde. Aber ich bin mir nicht sicher, ob, wenn wirklich noch ein Unterschied bestünde, das nicht in den release notes erwähnt worden wäre...? 

Vermutlich müßte man das auch durch eine Mischung aus Ausprobieren und viel Sun-Sourcecode-Lesen herauskristallisieren... Aber wie schon angedeutet: Der Sun-Code ist da nicht direkt durch Draufschauen zu verstehen  (Vielleicht wäre es besser, wenn man ihn sich komplett ziehen würde und dann in der IDE rumbrowsen - ich hatte nur auf so einer Seite wie docjar oder so reingeschaut...). Zumindest ist "fast alles" besser, als ein TYPE_CUSTOM, und man sollte darauf achten das "Managed-sein" nicht durch Dinge wie getSubImage kaputt zu machen...

@Apo: Das driftet schon in Spezifika ab ... sorry, sollte kein Hijacking werden  Vielleicht kannst du ja einem Mod sagen, ab wann das zu einer Art "Performance von BufferedImages"-Thread abgezwackt werden soll (vielleicht mit einer Überschneidung von 1,2 Beiträgen) weil's hier ja vermutlich eher um die Spiele an sich, und nicht um Detailfragen der Implementierung gehen sollte...


----------



## Apo (20. Jun 2011)

Ich finde das ehrlich gesagt hochgradig spannend. 
[off-Topic] Ich habe auf dem Rechner auf Arbeit den kompletten SunSourceCode. Ich werde da mal näher reinschauen. Bin bis jetzt nur nicht auf die Idee gekommen, dass es sich da lohnt reinzuschauen.
Wir brauchen den SourceCode derzeit "nur" um unsere ganzen Sachen zu synchronisieren und es gibt einige richtig schöne (in meinen Augen sehr unbekannte) versteckte Listen z.B. wo richtig viel "Hirnschmalz" eingeflossen ist, um das langsame synchronized zu vermeiden und es doch sicherzustellen.
Aber ich schweife auch gerade ab. Ich weiß nun auf jeden Fall was ich machen werde, wenn ich auf Arbeit wieder etwas Zeit habe. 
[/off-Topic]

Es lohnt sich aber glaube ich trotzdem, einen eigenen Thread dafür zu eröffnen, damit das Thema nicht untergeht. Ich wusste ja bis vor kurzem selber nicht, dass es für alle eigentlich sehr sinnvoll ist, sich darüber zu informieren. Es gibt bestimmt noch Informationen dazu, die derzeit hier im Forum noch nicht bekannt sind.

Ich denke, wenn ich etwas besser im Thema stehe, dann werde ich auch ein bis zwei Sätze auf meiner Seite dazu schreiben. Erstens damit ich es nicht vergesse und zweitens vlt hilft es dann ja auch anderen mal.


----------



## Marco13 (20. Jun 2011)

Hmja, zugegeben, eine ausführliche, aktuelle Websuche zu dem Thema hatte ich auch nicht gemacht (die Links aus dem anderen Thread waren schon etwas veraltet, ist eine Weile her, dass das für mich akut relevant war). Aber es könnte sein, dass man z.B. in den Sun-Blogs von "den üblichen Verdächtigen" (Chet Haase & Co) oder den Release Notes von Java 6/7 noch aktuellere, interessante Infos dazu findet.


----------



## Marlon (21. Jun 2011)

Danke für diesen sinnvollen Thread! 

Ich habe die Erfahrung gemacht, dass BufferedImages generell viel zu langsam sind und sich bei Echtzeitspielen die Implementierung nicht auszahlt (man möge mich gerne eines Besseren belehren).
Für mein Spiel habe ich eine eigene Bildklasse geschrieben, welche ein BufferedImage und das jeweilige VolatileImage dazu beinhaltet. Wird das Bild bearbeitet (was abgesehen von Schatten- und Beleuchtungseffekten relativ selten der Fall ist) so wird das BufferedImage genutzt und das VolatileImage aktualisiert, zum Zeichnen wird ausnahmslos auf das VolatileImage zurückgegriffen.
Die Performance ist auf PCs mit aktueller Grafikkarte sehr gut (bis zu 200 FPS möglich bei ca. 500 halbtransparenten Bildern), lässt jedoch bei PCs mit schlechterer Grafikkarte zu wünschen übrig (auf meinem Netbook läufts mit 10 FPS).

Ich habe folgenden Link beherzigt und die Technik meines Spieles demnach geändert:
performance - Java 2D game graphics - Stack Overflow (siehe erste Antwort)
Zitat: "Performance solely depends on the stuff you want to draw, my games mostly use images. With around 1500 of them I'm still above 200 FPS at 480x480. And with just 100 images I'm hitting 6k FPS when disabling the frame limiting."
Leider sind bei meinem Game lediglich bis zu 25 FPS bei guter Hardware möglich, das Spiel läuft sehr langsam.

So, wollte einfach meinen Senf dazugeben, falls jemand Vorschläge, Tipps oder Hinweise hat immer her damit.

Link zu meinem Action MMORPG: Forgotten Elements Online Action MMORPG


----------



## Marco13 (21. Jun 2011)

Ist die Frage http://www.java-forum.org/awt-swing-swt/111255-bufferedimage-performance.html noch "aktiv" oder in der Frage hier mit eingeschlossen?

Das mit den zwei Images klingt seltsam. Eigentlich sollte in den meisten Fällen ein BufferedImage reichen (ich habe aber noch NICHT viel mit VolatileImage gemacht, und bin mir nicht sicher, wo genau das wie viel schneller ist...)


----------



## Kr0e (4. Jul 2011)

VolatileImage muss von den Treibern des OS unterstuezt sein. Auf einem alten MAcBook gab es null Performanceschub, auf meinem neuen Rechner mit Win7 erheblich... Fast 4mal so schnell beim zeichnen. Diese Loesung mit 2 Images macht durchaus Sinn, gerade wenn das BufferedImage nur selten upgedated wird.


----------



## Marco13 (8. Sep 2011)

Um das Code-Snippet auch noch hier zu haben:

// Vorher:

```
BufferedImage image=ImageIO.read(new File(filename));
```

// Nachher:

```
BufferedImage tempImage=ImageIO.read(new File(filename));
BufferedImage image = new BufferedImage(
    tempImage.getWidth(), tempImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.drawImage(tempImage, 0, 0, null);
g.dispose();
```


----------



## yyannekk (6. Okt 2011)

Mir ist zu dem Thema aufgeffallen das es deutlich aufwendiger (zeitlich) ist, ein TYPE_INT_ARGB auf selbiges zu zeichnen als beispielsweise ein TYPE_INT_ARGB auf ein TYPE_INT_RGB oder auch ein TYPE_INT_RGB auf ein TYPE_INT_RGB.


```
public static void main( String... args )
	{
		BufferedImage srcARGB = new BufferedImage(1024, 768, BufferedImage.TYPE_INT_ARGB);
		BufferedImage srcRGB = new BufferedImage(1024, 768, BufferedImage.TYPE_INT_RGB);

		BufferedImage destARGB = new BufferedImage(1024, 768, BufferedImage.TYPE_INT_ARGB);
		BufferedImage destRGB = new BufferedImage(1024, 768, BufferedImage.TYPE_INT_RGB);
		
		long l = System.currentTimeMillis();
		
		destARGB.getGraphics().drawImage(srcARGB, 0, 0, null);
		System.out.println("Male ARGB auf ARGB : " + (System.currentTimeMillis() - l));
		l = System.currentTimeMillis();
		
		destARGB.getGraphics().drawImage(srcRGB, 0, 0, null);
		System.out.println("Male RGB auf ARGB : " + (System.currentTimeMillis() - l));
		l = System.currentTimeMillis();
		
		destRGB.getGraphics().drawImage(srcARGB, 0, 0, null);
		System.out.println("Male ARGB auf RGB : " + (System.currentTimeMillis() - l));
		l = System.currentTimeMillis();
		
		destRGB.getGraphics().drawImage(srcRGB, 0, 0, null);
		System.out.println("Male RGB auf RGB : " + (System.currentTimeMillis() - l));
	}
```

Ausgabe bei mir:
Male ARGB auf ARGB : 51
Male RGB auf ARGB : 2
Male ARGB auf RGB : 2
Male RGB auf RGB : 2

Ich hatte nämlich genau das Problem dass ich mit ARGB auf ARGB gemalt hab. Zudem brauche ich diese Transzparentzsachen nicht. Für mich hat es sich extrem gelohnt. Vielleicht hilfts ja jemandem


----------



## Marco13 (6. Okt 2011)

Die Zeitmessung ist so ziemlich unsinnig. Die Zeiten liegen weit unterhalb dessen, was System.currentTimeMillis auflösen kann. Selbst mit System.nanoTime wäre es noch fragwürdig. Und selbst wenn man dann (wie ich gerade zum Testen mal) ein Größeres Bild (>5000x5000) verwendet, könnte die Zeit noch stark davon abhängen, in welcher Reihenfolge die Test gemacht werden, weil noch der JIT zuschlagen könnte. Und selbst dann hat das alles noch nichts mit der Frage zu tun, wie schnell so ein Bild wirklich _auf dem Bildschirm_ (und nicht nur in ein anderes Bild rein) gezeichnet werden kann.

Aber trotz all dieser Bashings (nicht böse gemeint) noch der Zusatz: Da kann durchaus was dran sein. Wenn das Quell-Bild einen Alpha-Anteil enthält, muss "mit jedem Pixel etwas gemacht werden". Wenn das Quell-Bild RGB ist, also klar ist, dass das Quell-Bild keinen Alpha-Anteil hat (also quasi immer Alpha=1.0) kann es einfach bedenkenlos über das bestehende Bild drübergepinselt werden.

Bekräftigt (wenn auch noch lange, lange nicht bestätigt) wird das durch einen entsprechend angepassten Test:

```
import java.awt.image.*;

class ImageSpeedTest
{
    public static void main( String... args )
    {

        for (int i=1; i<10; i++)
        {
            int s = i * 600;

            System.out.println("Run "+i+" size "+s);

            BufferedImage srcARGB = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB);
            BufferedImage srcRGB = new BufferedImage(s, s, BufferedImage.TYPE_INT_RGB);

            BufferedImage destARGB = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB);
            BufferedImage destRGB = new BufferedImage(s, s, BufferedImage.TYPE_INT_RGB);

            long t = 0;

            t = System.nanoTime();
            destARGB.getGraphics().drawImage(srcRGB, 0, 0, null);
            System.out.println("Male  RGB auf ARGB: " + (System.nanoTime() - t)/1e6);

            t = System.nanoTime();
            destRGB.getGraphics().drawImage(srcARGB, 0, 0, null);
            System.out.println("Male ARGB auf  RGB: " + (System.nanoTime() - t)/1e6);

            t = System.nanoTime();
            destRGB.getGraphics().drawImage(srcRGB, 0, 0, null);
            System.out.println("Male  RGB auf  RGB: " + (System.nanoTime() - t)/1e6);

            t = System.nanoTime();
            destARGB.getGraphics().drawImage(srcARGB, 0, 0, null);
            System.out.println("Male ARGB auf ARGB: " + (System.nanoTime() - t)/1e6);
        }
    }

}
```

Beispiel:

```
...
Run 9 size 5400
Male  RGB auf ARGB: 64.483894
Male ARGB auf  RGB: 75.919705
Male  RGB auf  RGB: 63.243234
Male ARGB auf ARGB: 76.868988
```

Der Unterschied ist anscheinend (nicht überbewerten!) relativ gering, aber wichtiger: Das sagt nichts über die letztendliche _Zeichen_geschwindigkeit aus. Auch da könnte es sein, dass RGB schneller ist als ARGB, aus dem gleichen Grund, aber dieser Thread hatte sich ja aus einen Thread heraus entwickelt, wo es um Sprites in Spielen ging - die brauchen meistens Alpha. Wenn man die nicht braucht, kann man schauen, ob es mit RGB statt ARGB nochmal schneller ist. Die Zeit für das Zeichnen selbst vernünftig und einigermaßen verläßlich zu messen ist nochmal eine ganze Ecke schwieriger - das kann dann nämlich auch noch stärker von Betriebssystem, Grafikkarte, Treiber und JVM-Parametern (sun.java2d.opengl, noddraw, und ganz speziell natürlich translaccel) abhängen ... Das ist wohl der Preis für die Plattformunabhängigkeit...


----------



## yyannekk (7. Okt 2011)

Mh ja du hast recht. Dieser Peak mit den 50 ms scheint irgendwie nur beim ersten Durchlauf aufzutreten.
Dann liegt die verbesserte Performance evtl wirklich eher beim Darstellen des Bildes. Auf jeden Fall hat sich bei mir seit dem ich nur das RGB Bild verwende die Performance deutlich verbessert und darauf kam es mir ja an... Auch wenn es hier um Sprites in Spielen ging, passt mein Beitrag zum Thema Performance von BufferedImages finde ich 

//edit:

Ich habe den Test nochma abgeändert. Nun werden die Bilder mit zufälligen Farbwerten gefüllt, und siehe da: Der Unterschied scheint doch wieder sehr groß und zu werden:


```
public static void main( String... args )
	{

		for (int i = 1; i < 4; i++)
		{
			int s = i * 600;
			
			System.out.println("Run " + i + " size " + s);

			BufferedImage srcARGB = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB);
			BufferedImage srcRGB = new BufferedImage(s, s, BufferedImage.TYPE_INT_RGB);

			BufferedImage destARGB = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB);
			BufferedImage destRGB = new BufferedImage(s, s, BufferedImage.TYPE_INT_RGB);

			bemale(srcARGB, true);
			bemale(srcRGB, false);

			long t = 0;

			t = System.nanoTime();
			destARGB.getGraphics().drawImage(srcRGB, 0, 0, null);
			System.out.println("Male  RGB auf ARGB: " + (System.nanoTime() - t) / 1e6);

			t = System.nanoTime();
			destRGB.getGraphics().drawImage(srcARGB, 0, 0, null);
			System.out.println("Male ARGB auf  RGB: " + (System.nanoTime() - t) / 1e6);

			t = System.nanoTime();
			destRGB.getGraphics().drawImage(srcRGB, 0, 0, null);
			System.out.println("Male  RGB auf  RGB: " + (System.nanoTime() - t) / 1e6);

			t = System.nanoTime();
			destARGB.getGraphics().drawImage(srcARGB, 0, 0, null);
			System.out.println("Male ARGB auf ARGB: " + (System.nanoTime() - t) / 1e6);
		}
	}

	private static void bemale( BufferedImage img , boolean hasAlpha)
	{
		Graphics graphic = img.getGraphics();
		for (int i = 0; i < img.getWidth(); i++)
		{
			for (int j = 0; j < img.getHeight(); j++)
			{
				int r = (int) (Math.random() * 255);
				int g = (int) (Math.random() * 255);
				int b = (int) (Math.random() * 255);
				int a = (int) (Math.random() * 100);
				graphic.setColor(new Color(r, g, b, a));
				if (mitAlpha) graphic.setColor(new Color(r, g, b, a));
				else graphic.setColor(new Color(r, g, b));
				graphic.fillRect(i, j, 1, 1);
			}
		}
	}
```

Und das mit der Genauigkeit der Zeitmessung ist doch bei so unterschiedlichen Werten zu vernachlässigen oder!?

Ausgabe:
Run 3 size 1800
Male  RGB auf ARGB:  5.374559
Male ARGB auf  RGB:  42.679783
Male  RGB auf  RGB:   4.467645
Male ARGB auf ARGB: 57.510377


----------



## Marco13 (20. Mrz 2012)

Noch ein Snippet:

```
public static BufferedImage convertToARGB(BufferedImage image)
    {
        BufferedImage newImage = new BufferedImage(
            image.getWidth(), image.getHeight(), 
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose(); 
        return newImage;
    }
```

(Eigentlich könnte man noch abfragen, ob das übergebene Bild schon den passenden Typ hat, aber mit dieser Konvertierung wird IMMER ein neues Image erstellt, von dem man dann sicher sein kann, dass es hardwarebeschleunigt ist - auch wenn das übergebene Bild z.B. durch Aufrufe wie getSubImage usw. "kaputt" gemacht wurde)


----------

