# int-Array zu BufferedImage (performance?)



## pexx (2. Mrz 2010)

hi leute..

ein kommilitone und ich basteln seit einiger zeit an einer bildverarbeitungs-sw. 

der aufbau is kurz erklärt: die bildquelle (cam/film/bild) ist ein _BufferedImage_, aus dem generieren wir uns ein eindimensionales int-array der länge _width * height_.

der ausgabethread holt sich jetzt das aktuelle bild als int-array, schickt es durch die filterkette, wandelt das resultierende _int[]_ in ein _BufferedImage_ um und zeichnet ggf. irgendwelche Overlays drauf.

in der paint() methode wird nun immer das image aufs frame gezeichnet.

beim optimieren dieses vorgangs is mir aufgefallen das die schnellste möglichkeit das _int[]_ in ein _BI_ zu bekommen ein instanziieren eines neuen objekts war ???:L


```
public void draw(int[] picture) {								
	image = new BufferedImage(
		dcm, 
		WritableRaster.createWritableRaster(sample, new DataBufferInt(picture, length), ZERO_POINT),
		false, 
		null
	);

	// ....

	repaint();
}
```

dieser versuch benötigt ~20ms um das bild zu zeichnen. 
//EDIT: das ist die dauer in der die bilder letztendlich im Frame erscheinen

wie man weiss is das anlegen neuer objekte in zeitkritischen abschnitten nich unbedingt die beste vorgehensweise, also hab ich versucht nur die daten des BI zu überschreiben.


```
//...
image.setData(WritableRaster.createWritableRaster(sample, new DataBufferInt(picture, length), ZERO_POINT));
//...
```

komischerweise benötigt das mit ~40ms etwa doppelt soviel zeit ???:L

andere versuche direkt im Buffer eines vorher instanziierten BI herum zu fummeln hatten irgendwie garkeinen effekt (schwarzes bild).

tja und direkt den puffer des quellbildes können wir leider auch nicht manipulieren da die filterkette die größe des bildes (und damit die länge des arrays) ändern könnte und ggf. auch mehrere ausgabethreads gleichzeitig laufen.

---

kennt jemand eine möglichkeit den vorgang zu beschleunigen (_int[] --> BufferedImage_) ??


mfg


----------



## Steev (2. Mrz 2010)

Ich würde direkt für die Bildverarbeitung das WriteableRaster des BufferedImages verwenden, so arbeitest du immer mit demselben Objekt und erzeugst nicht ständig neue Objekte.

Das der zweite Vorgang noch mehr zeit benötigt ist ja eigendlich logisch, weil ja noch mehr (unnötige) Objekte erstellt werden.


----------



## pexx (2. Mrz 2010)

danke für die schnelle antwort



Steev hat gesagt.:


> Das der zweite Vorgang noch mehr zeit benötigt ist ja eigendlich logisch, weil ja noch mehr (unnötige) Objekte erstellt werden.



die objekte werden doch beim ersten vorgang genauso angelegt. WritableRaster und DataBufferInt, nur BufferedImage kommt noch hinzu und trotzdem dauert das alles nicht so lang wie _bufferdImage.setData(...)_

----

zunächst mal waren die zeitangaben mit 20ms humbuck .. hab an der falschen stelle gemessen.

die ausgabe hab ich jetzt derart verändert das dass BufferedImage einmal beim start des threads erstellt und mit jedem frame manipuliert wird.


```
public void draw(int[] picture) {

	for (int i = picture.length - 1; i >= 0; i--)
		image.getRaster().getDataBuffer().setElem(i, picture[i]);
				
	filter.drawOverlay(image.getGraphics());
	repaint(sleeptime);		
}
```

im gegensatz dazu die originalidee mit jedem frame ein neues BufferedImage anzulegen und zu zeichnen


```
public void draw(int[] picture) {		
	image = new BufferedImage(
		dcm, 
		WritableRaster.createWritableRaster(sample, new DataBufferInt(picture, length), ZERO_POINT), 
		false, 
		null
	);
		
	filter.drawOverlay(image.getGraphics());
	repaint(sleeptime);		

}
```

---

ich hab mal die performance untersucht. das anlegen eines neues BI + DataBufferInt + WritableRaster dauert etwa 60 000 *nano*sekunden. 

das iterieren über das komplette array und überschreiben des rasters mit den werten dagegen dauert.. jetzt kommts.. etwa 3*mili*sekunden. also ist damit etwa 50x langsamer.


verrückte welt.. ich werd das mal noch genauer auseinander nehmen. hat jemand ne idee wie das sein kann?


trotzdem die frage bleibt, wie schreibt man RGB-Daten im int-format am effektivsten in ein BufferedImage?


mfg


----------



## Steev (2. Mrz 2010)

Ja, erstmal scheint die Methode mit einem neuen Objekt schneller zu sein, aber warte einfach mal ein paar Minuten, dann wirst du schon merken, wie die Performance in die Knie geht. Erstmal wird das alte Objekt einfach verworfen und ein neues Objekt wird angelegt. Das geht natürlich schneller, als das abändern eines bestehenden. Aber nach einiger Zeit schaltet sich dann der GarbageCollector hinzu und fängt an deinen Objektmüll aufzuräumen. Und dabei geht dann die Performance extrem in die Knie. Als fährt man im Schnitt mit dem Ändern eines bestehenden Objektes besser.

Gruß
Steev


----------



## pexx (2. Mrz 2010)

Steev hat gesagt.:


> Ja, erstmal scheint die Methode mit einem neuen Objekt schneller zu sein, aber warte einfach mal ein paar Minuten, dann wirst du schon merken, wie die Performance in die Knie geht.



Danke für den Tipp... ich hab den memory mal auf 8mb gesetzt und mit visualvm zugeschaut wie der heap voller und voller wurde. nach ner minute oderso war der heap dann auf 80% und es hat im 5sekundentakt ein lustiges zackenmuster gemalt. also wohl immer wenn der GC den müll raus bringt.

wenn das geschieht ist kurzzeitig ein performanzverlust zu erkennen. das erstellen des BI dauert dann etwa 1.000.00 nanosek (also etwa 2x solang). da der ausgabethread aber eh die meiste zeit (0.05s also 60FPS) pennt ist visuell kein unterschied zu merken.

ich bin ja auch kein fan davon den speicher so voll zu müllen, aber selbst wenn der GC vollauf beschäftigt ist, macht diese methode ihren job immernoch 20x schneller als das pixelweise neusetzen des Rasters.

ich vermute mal das der performanzgewinn hier daraus entsteht, dass der datenpuffer vom int-array nicht durchiteriert werden muss sondern einfach das orginalarray zum erstellen verwendet wird. is aber nur ne idee, muss mal ins jdk src schaun.

vlt. gibts ja ne möglichkeit um die referenz auf den datenpuffer im raster zu ändern?

mfg


----------



## Marco13 (2. Mrz 2010)

Hmja, ich hatte da auch mal rumprobiert... Erstmal vorneweg: Zeiten im Nano- und auch Millisekundenbereich zu messen ist meistens nicht verläßlich. Und man kann davon ausgehen, dass der Garbage Collector ziemlich clever ist, und nicht sooo schnell zum Flaschenhals wird. 

Ich hatte damals auch versucht, einen Array in ein BufferedImage zu packen. Bei mir war es ursprünglich ein byte-Array. Was man zumindest schonmal sagen kann, dass sowas wie image.setRGB(...) meistens SEHR schlecht ist, weil dort erstmal ein ganzer Rattenschwanz von Konversionen von ColorModel & Co kommt, bevor die Daten dann doch 1:1 im DataBuffer landen. Zwischendurch hatte ich auch den Versuch gemacht, dass ich direkt in den byte[]-Array geschrieben habe, der im BufferedImage im Raster im DataBuffer lag. (Also wirklich denSELBEN byte-Array). Man sollte eigentlich meinen, dass das schnell geht - es schien aber (ohne, dass ich eine Möglichkeit sähe, das unumstößlich zu verifizieren) so, dass das eigentliche _Zeichnen_ eines BufferedImages mit sowas wie g.drawImage(...) bei einem BufferedImage, das einen byte[]-Array enthielt DEUTLICH langsamer war, als bei einem BufferedImage, das einen int[]-Array enthielt. (Das nur als Randnotiz - du hast einen int[], deswegen stellt sich die Frage bei dir nicht - aber vermeintliche Optimierungen an der einen Stelle (an der man irgendwelche Millisekunden-Zeiten mißt) können eben u.U. mehr als aufgefressen werden, wenn die Optimierung an der einen Stelle eine Verlangsamung an einer anderen Stelle nach sich zieht (die man ggf. gar nicht messen KANN)).

Mein letzter Stand (nach vielen Tests) ist, dass wenn man sicher sein kann, dass der BufferedImage.TYPE irgendwas mit "_INT" ist, es schon sehr schnell ist, sowas zu machen wie

```
int data[] = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
for (...) { data[i] = source[i]; } // Oder System.arraycopy
```
Allerdings kann es sein, dass bei meinen vielen Tests ein Neu-Erzeugen des Bildes in der beschriebenen Form nicht dabei war, und es ist gut möglich, dass das deutlich schneller ist. Ein Objekt zu erzeugen (Speicher zu allokieren) ist zwar aufwändig, aber vermutlich nicht so aufwändig wie durch einen Array mit 1 Million Elementen zu laufen. 

Mit letzter Bestimmtheit etwas dazu zu sagen ist aber schwierig. Java führt intern ziemlich viele Optimierungen durch, zu denen man Informationen findet wenn man nach sowas wie "managed image bufferedimage" usw. sucht. Teilweise werden Bilder im VRAM gehalten, aber dieses Halten im VRAM kann unmöglich gemacht werden, wenn man direkt auf die Pixel zugreift. Es gibt verschiedene JVM-Flags die dieses Verhalten beeinflussen, und es hängt STARK von der verwendeten Version der JVM ab... Alles nicht so einfach


----------



## Steev (2. Mrz 2010)

pexx hat gesagt.:


> ich vermute mal das der performanzgewinn hier daraus entsteht, dass der datenpuffer vom int-array nicht durchiteriert werden muss sondern einfach das orginalarray zum erstellen verwendet wird. is aber nur ne idee, muss mal ins jdk src schaun.
> 
> vlt. gibts ja ne möglichkeit um die referenz auf den datenpuffer im raster zu ändern?



Ja genau da liegt der Performancegewinn. Um die Referenz auf den Datenpuffer zu ändern müsste man den Source bzw. sein eigenes BI schreiben. Das ist aber prinziepiell unnötig, da eine Referenzänderung auf einen anderen, neuen Datenpuffer ja gleichzeitig ein verwerfen des alten bedeutet. Was einer neuinitialisierung gleich kommt. Da ja das größte am BI der Datenpuffer ist.

Gruß
Steev


----------



## Marco13 (2. Mrz 2010)

Oh, da war ich mal wieder langsam - aber zur letzten Frage: Man kann in seiner Anwendung prinzipiell denSELBEN Array verwenden, wie der, der im DataBuffer des BufferedImages liegt - aber nachträglich ändern kann man ihn (ohne Hack-arounds) nicht...

Musst du den picture[]-Array jedes mal übergeben, oder könnte nicht da, wo dieser Array herkommt, IMMER derSELBE Array verwendet werden (der auch einmal im Image liegt)?


----------



## pexx (2. Mrz 2010)

@marco13:

das die getRGB methode schnecke ist hab ich schon gemerkt  hier bietet es sich wirklich an statt getRGB(..) den DataBuffer mit bytes selbst (iterativ) in ein rgb-int-array zu überführen.



```
public static final int[] byteToInt (final byte[] b, final int[] ret) {
	for (int i = ret.length-1; i >= 0; i--) 
		ret[i] =  byteToRGBInt (b, i*3);		
	return ret;
}

public static final int byteToRGBInt (final byte b[], final int i) {
	return ((b[i+2] & 0xFF) << 16) | ((b[i+1] & 0xFF) << 8) | (b[i] & 0xFF);
}

// ...
byteToInt(((DataBufferByte)graph.getImage().getRaster().getDataBuffer()).getData(), imageData);
```

//EDIT: resultat war ein ~10facher performanzgewinn (von 30ms zu 3ms)
--



Marco13 hat gesagt.:


> Musst du den picture[]-Array jedes mal übergeben, oder könnte nicht da, wo dieser Array herkommt, IMMER derSELBE Array verwendet werden (der auch einmal im Image liegt)?



das selbe array kann ich leider nicht verwenden da meine filter dann nichmehr funzen. die können das bild z.B. scalen und damit die größe des arrays ändern oder das bild in einzelne kanäle (RGB) splitten, einzeln verändern und später wieder zusammen führen. kurzum, ich kann nicht sicher gehen das dass array nach der filterung noch das selbe ist.
--
gut, kann sein das dass timing fehlerhaft ist. dazu hab ich ne klasse gebaut welche die differenz zwischen stop() und start() in einem ringbuffer hällt und daraus den mittelwert berechnet. hab die kapazität auf 60 gestellt (da ich mit 60fps abfrage missts also immer auf eine sekunde) und auch nach 10min laufzeit ist ein konstantes verhalten zu beobachten. 60k ns zum erstellen des BI und 3ms zum überschreiben des rasters.
--
diese hacks von denen du da sprichst würden mich übrigens mal interessieren 

-----

naja ich hab die ganze sache jetzt dahingehend verändert das ich die 3ms in kauf nehme.. irgendwie passts mir auch nicht in den kram ständig neue BIs zu erstellen. der GC wirds mir hoffentlich danken.

die 3ms fallen wohl nicht mehr sonderlich auf bei filtern (Faltung, Median, Adaptiver Contrast, oderso) die gerne mal 50ms benötigen.


ich werd trotzdem mal weiter rumoptimieren, vlt. beschäftige ich mich auchmal mit volatileimage. vlt kann man damit noch ein paar fps rauskitzeln!? ???:L


mfg


----------



## Marco13 (2. Mrz 2010)

pexx hat gesagt.:


> diese hacks von denen du da sprichst würden mich übrigens mal interessieren



Mit Reflection kommt man auch an private Variablen dran .... 



> ich werd trotzdem mal weiter rumoptimieren, vlt. beschäftige ich mich auchmal mit volatileimage. vlt kann man damit noch ein paar fps rauskitzeln!? ???:L


Das das VolatileImage nur (und auch nur potentiell) schneller ist, weil es im VRAM gehalten wird, sehe ich da nicht so viele Chancen, aber ... versuchen kann man's ja...


----------



## Steev (2. Mrz 2010)

Bei dem VolatileImage kommt man aber nicht an ein WriteableRaster ran, man kann nur über die bekannten Methoden in ein VolateileImage reinschreiben. Sozusagen write only. Jedenfalls habe ich es noch nicht hinbekommen, falls es doch gehen sollte, würde mich das sehr interessieren


----------



## pexx (3. Mrz 2010)

Steev hat gesagt.:


> ...nur über die bekannten Methoden in ein VolateileImage reinschreiben. Sozusagen write only.



also nur über des Graphics-Object?! das is madig 

--

@marco13:
mit reflection was?  mein swe-prof würd mich dafür lynchen aber gute idee, ich probiers mal :toll:


ps. ist die seite nur in meinem browser so zerrissen?


----------



## Steev (3. Mrz 2010)

pexx hat gesagt.:


> ps. ist die seite nur in meinem browser so zerrissen?



Nein, bei mir (Firefox) sieht die Seite auf so komisch aus...


----------



## pexx (3. Mrz 2010)

so wie's aussieht funzt das überschreiben des Rasters 1zu1 mit den werten des picture-arrays doch nicht richtig. einige filter haben jetzt die angewohnheit ein schwarzes bild zu erzeugen. ich nehm mal an das da irgendeine konvertierung des formats fehlt ;(

ich geh der sache mal nach..


----------



## pexx (4. Mrz 2010)

hey leute..

ich hab jetzt alle mir denkbaren möglichkeiten ausprobiert um die Daten eines TYPE_INT_RGB BufferedImages mit einem int-array zu überschreiben.

-- 1 --
zunächst mal, die *setRGB()*-Methode ist tierisch lahm (~20ms). warum dem jetzt so ist oder was die methode für vorzüge hat, hab ich nicht näher nachgeforscht.


```
image.setRGB(startX, startY, w, h, rgbArray, offset, scansize)
```

-- 2 --
als nächstes hab ich versucht den Raster des bildes zu überschreiben. auch hier sehr langsam (~20ms). wie man sieht werden hier bei jedem durchlauf zusätzlich ein neues WritableRaster und ein DataBufferInt angelegt.


```
// length  : int mit width*height
// picture : int[] mit rgb-daten
// sample : SampleModel zuvor generiertes SampleModel
// ZERO_POINT : Point (0,0)  glaub hier kann man auch null nehmen

image.setData(WritableRaster.createWritableRaster(sample, new DataBufferInt(picture, length), ZERO_POINT));
```

-- 3 --
so jetzt wirds ein wenig schneller. das überschreiben der daten direkt im Raster dauerte etwa 3ms.
auch hier wird zusätzlich noch ein WritableRaster & DataBufferInt erzeugt.


```
image.getRaster().setDataElements(0, 0, WritableRaster.createWritableRaster(sample, new DataBufferInt(picture, length), ZERO_POINT));
```

-- 4 --
wie schon zu beginn des threads erwähnt war das schnellste immer ein neues BufferedImage zu erstellen. hat allerdings den nachteil das der speicher zumüllt. dauerte übrigens <1ms.


```
image = new BufferedImage(
     dcm, 
     WritableRaster.createWritableRaster(sample, new DataBufferInt(picture, length), ZERO_POINT),
     false, 
     null
);
```

-- 5 --
so last but not least meine finale variante. auch hier dauert das ganze ~1ms jedoch werden keine weiteren objekte angelegt. 
//EDIT: wichtig ist hier noch die flush()-methode aufzurufen vor jedem überschreiben des BI, sonst sieht man nur schwarz 


```
image.flush();
int[][] bankdata = ((DataBufferInt) (image.getRaster().getDataBuffer())).getBankData();		
System.arraycopy(picture, 0, bankdata[0], 0, length);
```


so vlt. hilfts ja mal jemandem

@Marco13:
übrigens ist der versuch (mittels reflection) die referenz des int[] zu ändern immer in einem fatal error der vm geendet :shock:

damit ist der thread wohl erledigt.

mfg


----------

