# BufferedImage und setRGB()



## Steev (26. Sep 2007)

Hallo liebe Forengemeinde,

ich habe ein BufferedImage
das ich in einer paint-Methode zeichen möchte.
Dazu verwende ich zur Zeit BufferedImage.setRGB(x,y,rgb).
Das funktioniert soweit auch ganz gut, bis auf die Tatsache
das das ganze zu langsam ist.  

Und jetzt die Frage:
Gibt es eine Alternative zu setRGB()?
Vieleich über den Array eines BI.
Und wenn ja wie komme ich an diesen Array heran?

Danke schonmal für eure Antworten. 

PS: Hoffentlich hilft das: der Quellcode der das Problem aufgeworfen hat:

```
/* 
* Erstelle keine neues Bild sondern update nur
* die Pixel die sich vom alten Bild her ändern.
* Dieser Vorgang wird nur ausgeführt wenn sich
*  überhaupt etwas ändert.
*/
if(
  (x!=otx)||
  (y!=oty)||
  (tempW!=otw)||
  (tempH!=oth)
  ){
    for (int xx = 0; xx < screenWidth; xx++)
        for (int yy = 0; yy < screenHeight; yy++)
            if(bi.getRGB(xx,yy)!=pmgt.getImg().get(
                map[(Math.abs(y)+yy) / tempH]
                      [(Math.abs(x)+xx) / tempW] - 1).getRGB(
                            ((Math.abs(x)+xx)-((((Math.abs(x)+xx)/tempW)*tempW))),
                            ((Math.abs(y)+yy)-((((Math.abs(y)+yy)/tempH)*tempH)))))
                bi.setRGB(xx, yy, pmgt.getImg().get(
                    map[(Math.abs(y)+yy) / tempH][(Math.abs(x)+xx) / tempW] - 1).getRGB(
                        ((Math.abs(x)+xx)-((((Math.abs(x)+xx)/tempW)*tempW))),
                        ((Math.abs(y)+yy)-((((Math.abs(y)+yy)/tempH)*tempH)))));
  otx = x;
  oty = y;
  otw = tempW;
  oth = tempH;
}
```
Hinweis:
Der Code ist aus eine stückbasierten Spiel:
pmgt ist eine zentrale Verwaltungsklasse für alle Bilder im Spiel
map ist ein int-Array der die Position der Stücke angibt


----------



## Marco13 (26. Sep 2007)

Auf einer Bockmist-Skala von 1 bis 10 würdest du dafür eine 8.5 bekommen. Du könntest es etwa auf 7.5 verbessern, wenn du diesen Block

```
if(bi.getRGB(xx, yy) != pmgt.getImg().get(
            map[(Math.abs(y) + yy) / tempH]
            [(Math.abs(x) + xx) / tempW] - 1).getRGB(
                ((Math.abs(x) + xx) - ((((Math.abs(x) + xx) / tempW) * tempW))),
                ((Math.abs(y) + yy) - ((((Math.abs(y) + yy) / tempH) * tempH)))))
            bi.setRGB(xx, yy, pmgt.getImg().get(
                map[(Math.abs(y) + yy) / tempH][(Math.abs(x) + xx) / tempW] - 1).getRGB(
                    ((Math.abs(x) + xx) - ((((Math.abs(x) + xx) / tempW) * tempW))),
                    ((Math.abs(y) + yy) - ((((Math.abs(y) + yy) / tempH) * tempH)))));
```
ersetzen würdest durch sowas wie

```
int ax = (Math.abs(x) + xx);
        int ay = (Math.abs(y) + yy);
        int bx = (ax - (((ax / tempW) * tempW)));
        int by = (ay - (((ay / tempH) * tempH)));
        int rgb = pmgt.getImg().get(map[ay / tempH][ax / tempW] - 1).getRGB(bx, by);
        if(bi.getRGB(xx, yy) != rgb)
        {
            bi.setRGB(xx, yy, rgb0);
        }
```

Schonmal überlegt, ob es vielleicht langsam ist, bei du bei jedem einzelnen Pixel irgendwelche wild-wüsten (absurd aussehenden) Berechnungen machst, und diese Abfrage bei jedem Pixel einzeln durchführst, obwohl sich (mit an Sicherheit grenzender Wahrscheinlichkeit) NIE ein einzelner Pixel ändert, sondern immer sehr viele (und vermutlich sogar _immer alle_)!?


----------



## Steev (27. Sep 2007)

@Marco13:


> Auf einer Bockmist-Skala von 1 bis 10 würdest du dafür eine 8.5 bekommen. Du könntest es etwa auf 7.5 verbessern, wenn du diesen Block [...] ersetzen würdest durch sowas wie [...]


Danke für den Verbesserungsvorschlag,
sieht auf jedem Fall übersichtlicher aus als mein Code.
Ich meide meist das zwischenspeichern im Stack wie die Pest
und berechne deswegen alles auf einem Schlag.  :wink: 
Ob das jetzt effektiv ist weis ich nicht.



> Schonmal überlegt, ob es vielleicht langsam ist, bei du bei jedem einzelnen Pixel irgendwelche wild-wüsten (absurd aussehenden) Berechnungen machst, [...]


Daran habe ich auch schon gedacht. Leider ist die Überprüfung notwendig, denn bei einer Auflößung von 800x600px wären es 480.000 Pixel die zu ändern wären. Aber nicht jeder Pixel hat, z.B. wenn eine Karte um 5px nach rechts verschoben wird eine andere Farbe als vorher, darum ändere ich nur die Pixel die eine andere Farbe haben. Diese Methode ändert bei dem Scrollen des Levels pro Zeichenlauf etwa 56.000Pixel anstatt 480.000Pixel.



> und diese Abfrage bei jedem Pixel einzeln durchführst, obwohl sich (mit an Sicherheit grenzender Wahrscheinlichkeit) NIE ein einzelner Pixel ändert, sondern immer sehr viele (und vermutlich sogar immer alle)!?


Ich weis leider nicht wie ich das hinbekommen soll wenn ich NICHT jeden Pixel einzeln in die Hand nehme...
Die absurden Berechnungen rechnen lediglich aus der x/y-Position der Map und einem fortlaufenendem Zähler (xx/yy) die aktuelle Position des zu zeichnenden Pixels in dem Levelstück-Bitmap aus.

x          --> LevelX-Position (0...-*)
xx        --> Zähler von 0..Bildschirmbreite(800)
tempW --> Breite der Stücke aus denen der Level besteht
((Math.abs(x)+xx)-((((Math.abs(x)+xx)/tempW)*tempW)))

[EDIT]:
Muss man die Pixel eigendlich über eine Methode, in diesem Fall setRBG(), ändern.
Es ist doch wesentlich schneller 480.000 Einträge in einem Array zu ändern als 480.000 Mal eine Methode aufzurufen die das für mich erledigt.
Und damit wieder die Frage:
Kann man an den Array, welcher hinter einem Bild steckt, heran und Einträge ändern?


----------



## Marco13 (27. Sep 2007)

Naja. Wenn die Überprüfung, OB ein Pixel geändert werden muss, 10x so lange dauert, die den Pixel einfach zu ändern, ist die Überprüfung kontraproduktiv - selbst wenn nur ein viertel der Pixel tatsächlich geändert werden muss. 

Ehrlich gesagt sehe ich es als grunsätzlich problematisch an, den Inhalt eines BufferedImages zu verschieben - anstatt einfach das Bild woanders hin zu zeichnen.

Es gibt zwar prinzipiell eine Möglichkeit, direkt in den Array zu schreiben, der hinter einem BufferedImage liegt, aber ... abgesehen davon, dass die Methode, mit der man da rankommt, nicht an die berühmte Stricknadel, sondern eher an ein Brecheisen erinnert, das man "von hinten durch die Brust ins Auge" sticht, wird es damit vermutlich nicht SO viel effizienter. Den kompletten Inhalt eines Bildes zu ändern wird IMMER um ein Vielfaches(!) länger dauern, als einfach das Bild woanders hin zu zeichnen. However. Hier ein Stück code, zusammen mit der DRINGENDEN Empfehlung, es komplett anders zu machen!!!

```
BufferedImage createBufferedImage(int w, int h)
    {
        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
        int[] nBits = {8, 8, 8};
        int[] bOffs = {2, 1, 0};
        ColorModel colorModel = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
        Raster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, w, h, w * 3, 3, bOffs, null);
        WritableRaster writableRaster = raster.createCompatibleWritableRaster(w, h);
        DataBufferByte dbb = (DataBufferByte) writableRaster.getDataBuffer();

        byte imageData[]  = dbb.getData(); // Das sind die Bilddaten wo man direkt reinschreiben kann...

        BufferedImage bufferedImage = new BufferedImage(colorModel, writableRaster, false, new java.util.Hashtable());
        return bufferedImage;
    }
```
Das ist ein Hack, und es wird auch damit keine "zufriedenstellende" Geschwindigkeit erreichbar sein. Mach es anders!


----------



## Steev (27. Sep 2007)

@Marco:
OK,
danke für deine Antwort,
ich werde mal meinen Code umbauen und mich dann wieder melden.


----------



## Steev (27. Sep 2007)

Schade,...

Leider funktioniert das ganze noch nicht so wie ich mir das vorgestellt habe.
Vieleicht bin ich einfach zu dumm deine Methode richtig zu verwenden...

Den Code habe ich jetzt wie folgt geändert:

```
public void paintObject(Graphics2D g2, ImageObserver imgobserver)
{
	// calculate the vars
	tempW = ((int) (tileWidth / 100.0 * xscale));
	tempH = ((int) (tileHeight / 100.0 * yscale));
	tempx = Math.abs(x / tempW);
	tempy = Math.abs(y / tempH);
	// manage Range
	if (x > 0)x = 0;
	if (y > 0)y = 0;
	if (x + (tempW * width) < screenWidth)x = -((tempW * width) - screenWidth);
	if (y + (tempH * height) < screenHeight)y = -((tempH * height) - screenHeight);
	/* 
	 * Erstelle keine neues Bild sondern update nur
	 * die Pixel die sich vom alten Bild her ändern.
	 * Dieser Vorgang wird nur ausgeführt wenn sich
	 * überhaupt etwas ändert. 
	 */
	if(
	  (x!=otx)||
	  (y!=oty)||
	  (tempW!=otw)||
	  (tempH!=oth)
	  ){
		bi = createBufferedImage(screenWidth, screenHeight);
		otx = x;
		oty = y;
		otw = tempW;
		oth = tempH;
	}
	g2.drawImage(bi, 0, 0, null);
}
```

Und in deiner Methode habe ich folgendes eingefügt:

```
BufferedImage createBufferedImage(int w, int h)
{ 
	ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); 
	int[] nBits = {8, 8, 8}; 
	int[] bOffs = {2, 1, 0}; 
	ColorModel colorModel = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); 
	Raster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, w, h, w * 3, 3, bOffs, null); 
	WritableRaster writableRaster = raster.createCompatibleWritableRaster(w, h); 
	DataBufferByte dbb = (DataBufferByte) writableRaster.getDataBuffer(); 
		byte imageData[]  = dbb.getData();
	for (int xx = 0; xx < screenWidth; xx++)
		for (int yy = 0; yy < screenHeight; yy++)
		{
			ax = (Math.abs(x) + xx); 
			ay = (Math.abs(y) + yy); 
			bx = (ax - (((ax / tempW) * tempW))); 
			by = (ay - (((ay / tempH) * tempH))); 
			rgb = (byte)pmgt.getImg().get(map[ay / tempH][ax / tempW] - 1).getRGB(bx, by);
			if(imageData[xx*yy] != rgb)
				imageData[xx*yy] = rgb;
		}
	
	BufferedImage bufferedImage = new BufferedImage(colorModel, writableRaster, false, new java.util.Hashtable()); 
	return bufferedImage; 
}
```

Das Ergebniss sieht irgendwie seltsam aus:
Alles ist schwarz und gelegendlich ein paar bunte Pixel.
Ich denke mal dass das irgendwie mit dem getRBG
und dem cast auf byte zu tun haben muss.

[EDIT:]
@Marco:


> Ehrlich gesagt sehe ich es als grunsätzlich problematisch an, den Inhalt eines BufferedImages zu verschieben - anstatt einfach das Bild woanders hin zu zeichnen.


Ich habe erst den gesamten Level am Anfang des Programmes gerendert und ihn dann schön flüssig über den Bildschirm scrollen lassen. Leider sollen die Level jetzt aber eine astronomische Größe von bis 100*100 Tiles á 200*200px annehmen (Das währen 40.000.000.000 Pixel). Deswegen kann ich nicht mehr den gesamten Level am Anfang rendern sondern muss während der Laufzeit ein neues Bild erstellen. 

Der erste Versuch scheiterte an der Performance weil ich per drawImage ein BufferedImage befüllt habe.
Der zweite Versuch (das ist der hier Beschriebene) war zwar schneller als der erste aber immer noch zu langsam für ein Spiel.

Danke für alle Antworten,
natürlich freue ich mich auch über weitere Antworten.


----------



## LoN_Nemesis (30. Sep 2007)

Steev hat gesagt.:
			
		

> Ich habe erst den gesamten Level am Anfang des Programmes gerendert und ihn dann schön flüssig über den Bildschirm scrollen lassen. Leider sollen die Level jetzt aber eine astronomische Größe von bis 100*100 Tiles á 200*200px annehmen (Das währen 40.000.000.000 Pixel). Deswegen kann ich nicht mehr den gesamten Level am Anfang rendern sondern muss während der Laufzeit ein neues Bild erstellen.
> 
> Der erste Versuch scheiterte an der Performance weil ich per drawImage ein BufferedImage befüllt habe.
> Der zweite Versuch (das ist der hier Beschriebene) war zwar schneller als der erste aber immer noch zu langsam für ein Spiel.
> ...




Bestimmt habe ich etwas falsch verstanden, daher ist meine Antwort wahrscheinlich nutzlos.

Aber so wie ich das sehe, hast du ein Tile-basiertes Spiel und hast nun Performance Probleme beim Zeichnen. Das ist auch vollkommen klar, wenn du jedesmal das gesamte Level zeichnest? Das ist doch aber total sinnlos, weil man immer nur einen Bruchteil davon sieht. Warum zeichnest du nicht einfach nur den sichtbaren Teil? Also einfach ein BufferedImage der Größe 800x600, welches genau immer nur den aktuellen sichtbaren Inhalt speichert. Du brauchst dann natürlich ein bisschen Code um zu entscheiden ob ein Objekt sichtbar ist oder nicht, aber das ist definitiv deutlich schneller und vor allem unabhängig von der Levelgröße.

Wie gesagt, falls ich etwas falsch verstanden habe bitte ich um Verzeihung.


----------



## Marco13 (30. Sep 2007)

Selbst wenn du das (aus welchem Grund auch immer) in ein BufferedImage zeichnen willst (kann sinnvoll sein) dann doch garantiert nicht, indem irgendwelche Pixel mit setRGB gesetzt werden. Wenn bei jedem Scroll-Schritt ein neues Bild erzeugt wird, ist das sowieso sinnlos. Ich hatte ja mher als deutlich geschrieben: MACH ES ANDERS!!! Und zwar, indem du die BufferedImages, aus denen die Tiles bestehen, da hin zeichnest, wo sie hin müssen.

Mal ganz pragmatisch: Du hast ein Level, das aus 100x100 Tiles mit jeweils 200x200 Pixeln besteht. Wenn die Tiles nun in einem Array liegen, und du an _irgendweine_ Stelle (x,y) in deinem 20000x20000 Pixel großen level gescrollt hast, dann zeichnest du einfach die Tiles, die man dann sieht...

```
class XXX
{
    Image tiles[][] = ...
    int numTiles = 100;
    int tileSize = 200;

    int xPos, yPos; // Aktuelle scroll-position

    public void paintLevel(Graphics g)
    {
         // Indizes fr linkes oberes Tile ausrechnen
         int tileX = xPos / tileSize;
         int tileY = yPos / tileSize;

         // Anzahl tiles auf dem Bidlschirm ausrechnen
         int tilesWidth = screenWidth / tileSize + 1;
         int tilesHeight = screenHeight / tileSize + 1;

         // Ausgewählte Tiles malen
         for (int tx=tileX; tx<tileX+tilesWidth; tx++)
         {
             for (int ty=tileY; ty<tileY+tilesHeight; ty++)
             {
                  // Ausrechenen, wo das tile gemalt werden muss (geht evlt. auch einfacher)
                  int x = (tileX + tx) * tileWidth + (xPos - tx*tileWidth);
                  int y = (tileY + ty) * tileWidth + (yPos - ty*tileHeight);
                  Image tile = tiles[tx][ty];
                  g.drawImage(tile, x, y, this);
              }
         }
}
```
Naja so in etwa. Also auf jeden fall: Nix BufferedImage, Nix WriteableRaster, Nix setRGB. Einfach malen. Da hin, wo sie hin müssen.


----------



## LoN_Nemesis (1. Okt 2007)

Ganz genau so wie Marco13 das beschrieben hat meinte ich das auch. Das ist eigentlich der Standardansatz bei tile-basierten Spielen. Ich programmiere auch schon seit längerer Zeit an einem Spiel, bei dem die Welt aus Tiles aufgebaut ist und mein Code sieht dem von Marco13 verdammt ähnlich (teilweise sogar gleiche Variablenbenennung ).


----------



## Steev (1. Okt 2007)

Danke schonmal für euren schnellen Antworten:

@LoN_Nemesis:
Ich habe immer nur (und zwar Pixelgenau) das gezeichnet was der Benutzer sieht und natürlich    nicht den ganzen Level.
Für diesen Vorgang hatte ich zwei Methoden entwickelt.
Die erste hatte alle Stücke aus denen der Level besteht (und die der User auf dem Bildschirm sieht) auf den Bildschirm gezeichnet. Das war mir aber zu langsam und daher habe ich die zweite Methode gebaut.
Diese Methode sollte alle Pixel die sich ändern und die zu sehen sind in ein BufferedImage schreiben und dies dann an den Grafikkartenspeicher weiter leiten, diese Methode ist schneller als die erste Methode es war aber noch zu langsam als das man das Spiel genießen könnte.
Daher habe ich mir jetzt übers Wochenende gedacht das man vieleicht den Level doch zu Anfang rendern könnte und zwar auf fenstergroße Stücke ich hoffe das das schneller geht als mit den vielen setRBG.

@Marco:
Mein Code sah fast genau so aus wie deiner auch ich werde jetzt deinen so wie er ist mal umsetzen, vieleicht habe ich auch einfach nur einen Fehler in dem Code gehabt...
Allerdings denke ich das dies


> Image tiles[][]...


nicht so gut ist. (Oder ich verstehe wieder einmal etwas falsch...)
So wie ich das jetzt verstehe gibt das eine sehr große Datenredundanz die zum Heap-Überlauf führen wird. Daher habe ich für die Bilder eine zentrale Klasse gebaut die alle Stücke nur EINMAL beinhaltet.

Ich hoffe ich komme dazu die Idee mit den Fensterstücken in die Tat umzusetzen. Auf jedem Fall poste ich das Ergebniss.

Ich hoffe das der Versuch eine bessere Performance erreicht als meine bisherigen Versuche.
Der einzige Versuch der eine annehmbare Performance hatte war der, indem ich den ganzen Level bei Spielstart in ein BufferedImage geschrieben habe und dieses per drawImage über ein VolatileImage auf den Bildschirm gebracht hatte. Deshalb probiere ich es jetzt so.


----------



## Steev (1. Okt 2007)

Ha,
danke ihr alle (besonders Marco13).
Das mit dem "Image tiles[][]"-Problem bei dir war ein Denkfehler bei mir. Du hast ja warscheinlich mit Referenzen gearbeitet, was schneller ist als mein Ansatz mit der zentralen Klasse. Weil bei mir in der Zentralklasse eine LinkedList für die Bilder zum einsatz kam.
Also habe ich um ein Bild zu zeichnen erst einmal das Bild über mehrere Methoden + Getters holen müssen was natürlich viel langsamer ist als einfach über eine Referenz auf das Objekt zuzugreifen.

Also nochmal DANKE AN ALLE, dieser Thread hat mir sehr geholfen.


----------

