# Schnelles kopieren von Pixeln in ein BufferedImage



## Degush (11. Feb 2012)

Hey,
ich habe eine eigene Klasse Image (MengineImage), die Pixel enthält.
Ich möchte diese eigenen Bilder zu BufferedImages machen und habe dafür erst bufferedImage.setRGB(...) verwandt. Das ist allerdings zu langsam (Image kopieren dauerte 150!! Milisekunden im Schnitt .. das ist für ein Spiel deutlich zu langsam 0=> 6-7 FPS).
Deshalb möchte ich nun ein Raster verwenden.

Hier ist mein code:


```
public static BufferedImage getBufferedImage(MengineImage img)
    {
        System.out.println("A: "+img.getWidth()*img.getHeight());
        long a = System.currentTimeMillis();
        BufferedImage bImage  = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);

        int[] pixels = new int[img.getHeight()*img.getWidth()];
        
        for(int w = 0; w < img.getWidth(); w++)
        {
            for(int h = 0; h < img.getHeight(); h++)
            {
                pixels[ w + h * img.getWidth() ] = img.getPixel(w, h).getPixelValue();
            }
        }

        bImage.getRaster().setPixels(0, 0, img.getWidth(), img.getHeight(), pixels);

        System.out.println("Time: "+(System.currentTimeMillis()-a) );
        return bImage;
    }
```

Fehler:
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 1080000


----------



## Spacerat (11. Feb 2012)

Bis zur Zeile 5 könnte man es stehen lassen... dann aber vllt.

```
bImage.getGraphics().drawImage(img, 0, 0, bImage.getWidth(), bImage.getHeight(), null);
```
Funktioniert allerdings nur bei gültigen ImageInstanzen von Image, RenderedImage oder BufferedImage.
Darüber hinaus ist's eigentlich völlig egal, ob du das Image pixelweise oder sonst irgendwie kopierst, im BufferedImage wird eh' jeder einzelne auf Gültigkeit überprüft (z.B. bei IndexedColorModel, ob der Farbwert auch tatsächlich definiert wurde).


----------



## Degush (11. Feb 2012)

Hey,

danke für die Hilfe.
Leider geht die vorgeschlagene Lösung bei mir nicht, da MengineImage nicht von Image abgeleitet ist.


```
public class MengineImage
{
    private Pixel[][] pixels;
    private int width;
    private int height;

    public MengineImage(int width, int height)
    {
        pixels = new Pixel[width][height];
        this.width = width;
        this.height = height;
    }

    public void setPixel(int x, int y, Pixel p)
    {
        pixels[x][y] = p;
    }

    public Pixel getPixel(int x, int y)
    {
        if ( pixels[x][y] == null )
            return new Pixel(0,255,255,255);
        return pixels[x][y];
    }
.....
```

Die Klasse spart sich desweiteren Index-Abfragen und Abfragen, ob ein Farbwert gültig ist, da sie absolut performant sein muss. Um Speicherplatz zu sparen, gibt es auch nur Pixel-Referenzen. Das ist zwar keine ideal Datenkapselung, aber da mache ich der Geschwindigkeit und des Speichers wegen Kompromisse.

Es bleibt mir also nichts anderes übrig als ein pixelweises kopieren. Und wenn ich setRGB benutze, dauert das lächerlich lange (Java hat mMn sowieso die ganzen Image-Klassen verhunzt). Woanders habe ich gelesen, dass pixelweises kopieren mit einem Raster schneller geht.

Zur Information hier die Pixel-Klasse (noch nicht kommentiert, kommt später):

```
public class Pixel
{
    public static Logger log = new Logger("Pixel");
    public int alpha;
    public int red;
    public int green;
    public int blue;

    public Pixel()
    {
        alpha = 0;
        red = 0;
        green = 0;
        blue = 0;
    }

    private void checkColorRange()
    {
        if ( alpha > 255 || alpha < 0 )
            log.log("Alpha out of range",1);
        if ( red > 255 || red < 0 )
            log.log("Red out of range",1);
        if ( green > 255 || green < 0 )
            log.log("Green out of range",1);
        if ( blue > 255 || blue < 0 )
            log.log("Blue out of range",1);
    }

    public Pixel(int alpha, int red, int green, int blue)
    {
        this.alpha = alpha;
        this.red = red;
        this.green = green;
        this.blue = blue;
        //checkColorRange();
    }

    public Pixel(int pixelValue)
    {
        alpha   = (pixelValue >> 24) & 0xFF;
        red   = (pixelValue >> 16) & 0xFF;
        green = (pixelValue >> 8) & 0xFF;
        blue  = (pixelValue) & 0xFF;
        //checkColorRange();
    }

    /**
     * Mixes this color with p. New ARGB are average ARGB
     * @param p the "color" to be added
     */
    public void addPixel(Pixel p)
    {
        alpha = (alpha+p.alpha)/2;
        red = (red+p.red)/2;
        blue = (blue+p.blue)/2;
        green = (green+p.green)/2;
    }

    public int getPixelValue()
    {
        return  ((alpha & 0xFF) << 24) |
                ((red & 0xFF) << 16) |
                ((green & 0xFF) << 8)  |
                ((blue & 0xFF));
    }

    public void log()
    {
        log.log("Alpha: "+alpha);
        log.log("Red: "+red);
        log.log("Green: "+green);
        log.log("Blue: "+blue);
        log.log("Pixelvalue: "+getPixelValue());
    }
}
```


----------



## Marco13 (11. Feb 2012)

Wah :shock: Mach das anders!

Nachdem der erste Schock überwunden ist: Wie groß ist denn so ein MengineImage? Und wie "dicht belegt" ist es? Die Aussage: "Es gibt nur Pixel-Referenzen, um speicherplatz zu sparen" ist insofern absurd, dass jede dieser Pixel-Referenzen locker ~40 bytes belegt, und das ganze nochmal in einem 2D-Array.... also wenn so ein Bild eine Belegungsrate von mehr als 10% hat, ist man mit einen puren BufferedImage bzw. einem profanen, rohen int[]-Array deutlich besser dran. Und dass dessen Performance um Größenordnungen(!) höher sein wird, ist klar - schon wegen der Speicherzugriffe, aber nicht zuletzt auch wegen der Rechnungen die bei sowas wie getPixelValue gemacht werden müssen. Und dass bei jedem Zugriff mit "getPixel" (auf eine unbelegte Stelle) ein neues Pixel-Objekt erzeugt wird, ist das i-Tüpfelchen.

Es gibt sicher ein paar glitches und Altlasten bei den Image-Klassen, aber ein BufferedImage ist schon geil. Wenn's dir nicht gefällt... nimm doch mal ein SWT-Image, dann weißt du, was ein Krampf ist :autsch:


----------



## Spacerat (11. Feb 2012)

Degush hat gesagt.:


> Woanders habe ich gelesen, dass pixelweises kopieren mit einem Raster schneller geht.


Das ist teilweise richtig und zwar genau dann, wenn BufferedImage ein DirectColorModel verwendet. Deine Implementation von MengineImage ist aber auch nicht wirklich @best, ich hätte z.B. einen kompletten IntBuffer (evtl. sogar Direct) in der Grösse [c]width * height[/c] aus dem java.nio Paket erstellt und den Index eines Pixels nach der Formel [c]index = 4 * (y * width + x)[/c] bestimmt. Dazu noch ein allgemein gültiges Pixelformat (0xAARRGGBB) und fertig. Für mein Projekt schlage ich mich mitunter auch grad' noch mit einem performantem Imageformat rum, welches obendrein auch noch per [c]Graphics.drawImage()[/c] gezeichnet werden soll. Meine Farb-Berechnungen erledige ich zumindest schon mal über eine erweiterte Color-Klasse, welche mit einem winzigen Reflection-Trick sogar mutable ist.
	
	
	
	





```
import java.awt.Color;
import java.lang.reflect.Field;

public final class MutableColor
extends Color
{
	/*
	 * The Transparent Mask.
	 */
	public static final int TRANSPARENT_MASK = 0xFF000000;

	/*
	 * The Opaque Mask
	 */
	public static final int OPAQUE_MASK = ~TRANSPARENT_MASK;

	/*
	 * The Serial Version UID
	 */
	private static final long serialVersionUID = 998713561408105619L;

	/*
	 * the HSB (Hue-Saturation-Brightness) values with alpha
	 */
	final float[] hsba = new float[4];

	/*
	 * the reflective value field of super class
	 */
	private final Field valueField;

	/**
	 * Overall constructor. Would be called within any other constructor.
	 * It sets access to the value field of super class.
	 */
	{
		try {
			valueField = Color.class.getDeclaredField("value");
		} catch(NoSuchFieldException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Intantiates a mutable black color as initial value
	 */
	public MutableColor()
	{
		this(0);
	}

	/**
	 * Intantiates a mutable color with given initial value
	 */
	public MutableColor(int value)
	{
		super(value, true);
		setValue(0); // to init hsba values;
	}


	/**
	 * Intantiates a mutable color with given initial color
	 */
	public MutableColor(Color color)
	{
		this(color.getRGB());
	}

	/**
	 * Sets this mutable color to new value.
	 * @param new value
	 * @return old value
	 */
	public int setValue(int value)
	{
		int rc = getRGB();
		try {
			valueField.setAccessible(true);
			valueField.set(this, value);
			valueField.setAccessible(false);
		} catch(IllegalAccessException e) {
			throw new RuntimeException(e);
		}
		RGBtoHSB(getRed(), getGreen(), getBlue(), hsba);
		hsba[3] = getAlpha() / 255.0f;
		return rc;
	}

	/**
	 * Cycles this color to value hue + steps. A continous cycle will be
	 * reached with i.e. "c = c.cycle(0.1f);". Steps aren't really an angle,
	 * but the range between 0 and 1 is like the angular range between 0 and 2PI.
	 * @param steps
	 * @return new color value
	 */
	public int cycleColor(float steps)
	{
		return setValue(clamp((steps + hsba[0]) % 1.0f, hsba[1], hsba[2], hsba[3], true));
	}

	/**
	 * Dim this color to value brightness + value. This method is the
	 * better way to dim a color, since "brighter()" and "darker()"
	 * does the same with fixed (+/-0.7f) values.
	 * @param value
	 * @return new color value
	 */
	public int dimColor(float value)
	{
		return setValue(clamp(hsba[0], hsba[1], clamp(hsba[2] + value), hsba[3], true));
	}

	/**
	 * Saturates this color to value saturation + value.
	 * @param value
	 * @return new color value
	 */
	public int saturateColor(float value)
	{
		return setValue(clamp(hsba[0], clamp(hsba[1] + value), hsba[2], hsba[3], true));
	}

	/**
	 * Blends this color to value alpha + value.
	 * @param value
	 * @return new color value
	 */
	public int blendColor(float value)
	{
		return setValue(clamp(hsba[0], hsba[1], hsba[2], clamp(hsba[3] + value), true));
	}

	/**
	 * Mixes this color with another.
	 * @param value
	 * @return new color value
	 */
	public int mixColor(int value)
	{
		int alpha = ((getAlpha() + (value >> 24)) >> 1) &0xFF;
		int red = ((getRed() + (value >> 16)) >> 1) & 0xFF;
		int green = ((getGreen() + (value >> 8)) >> 1) & 0xFF;
		int blue = ((getBlue() + value) >> 1) & 0xFF;
		return setValue((alpha << 24) | (red << 16) | (green << 8) | blue);
	}

	/**
	 * Sets this mutable color to new color.
	 * @param new color
	 * @return this color
	 */
	public MutableColor setValue(Color color)
	{
		setValue(color.getRGB());
		return this;
	}

	/**
	 * Dim this color to value brightness + value. This method is the
	 * better way to dim a color, since "brighter()" and "darker()"
	 * does the same with fixed (+/-0.7f) values.
	 * @param value
	 * @return new color
	 */
	public MutableColor dim(float value)
	{
		dimColor(value);
		return this;
	}

	/**
	 * Saturates this color to value saturation + value.
	 * @param value
	 * @return new color
	 */
	public MutableColor saturate(float value)
	{
		saturateColor(value);
		return this;
	}

	/**
	 * Cycles this color to value hue + steps. A continous cycle will be
	 * reached with i.e. "c = c.cycle(0.1f);". Steps aren't really an angle,
	 * but the range between 0 and 1 is like the angular range between 0 and 2PI.
	 * @param steps
	 * @return this color
	 */
	public MutableColor cycle(float steps)
	{
		cycleColor(steps);
		return this;
	}

	/**
	 * Blends this color to value alpha + value.
	 * @param value
	 * @return this color
	 */
	public MutableColor blend(float value)
	{
		blendColor(value);
		return this;
	}

	/**
	 * Mixes this color with another.
	 * @param color
	 * @return this color
	 */
	public MutableColor mix(Color color)
	{
		mixColor(color.getRGB());
		return this;
	}

	/**
	 * Clamps floats into values between 0 and 1
	 * @param value
	 * @return clamped value
	 */
	private float clamp(float value)
	{
		if(value < 0.0f) value = 0.0f;
		if(value > 1.0f) value = 1.0f;
		return value;
	}

	/**
	 * clamps float values between 0 and 1.
	 * @param i
	 * @param j
	 * @param k
	 * @param l
	 * @param hsb
	 * @return argb integer
	 */
	private int clamp(float i, float j, float k, float l, boolean hsb)
	{
		if(hsb) {
			return (HSBtoRGB(clamp(i), clamp(j), clamp(k)) | ((int) (l * 255.0f) << 24));
		}
		return ((int) (clamp(i) * 255.0f) << 16 | (int) (clamp(j) * 255.0f) << 8 | (int) (clamp(k) * 255.0f) | (int) (clamp(l) * 255.0f) << 24);
	}
}
```
[EDIT]





Marco13 hat gesagt.:


> Wenn's dir nicht gefällt... nimm doch mal ein SWT-Image, dann weißt du, was ein Krampf ist :autsch:


:lol::lol::lol:[/EDIT]


----------



## Degush (12. Feb 2012)

Marco13 hat gesagt.:


> Wah :shock: Mach das anders!
> 
> Nachdem der erste Schock überwunden ist: Wie groß ist denn so ein MengineImage? Und wie "dicht belegt" ist es? Die Aussage: "Es gibt nur Pixel-Referenzen, um speicherplatz zu sparen" ist insofern absurd, dass jede dieser Pixel-Referenzen locker ~40 bytes belegt, und das ganze nochmal in einem 2D-Array.... also wenn so ein Bild eine Belegungsrate von mehr als 10% hat, ist man mit einen puren BufferedImage bzw. einem profanen, rohen int[]-Array deutlich besser dran. Und dass dessen Performance um Größenordnungen(!) höher sein wird, ist klar - schon wegen der Speicherzugriffe, aber nicht zuletzt auch wegen der Rechnungen die bei sowas wie getPixelValue gemacht werden müssen. Und dass bei jedem Zugriff mit "getPixel" (auf eine unbelegte Stelle) ein neues Pixel-Objekt erzeugt wird, ist das i-Tüpfelchen.
> 
> Es gibt sicher ein paar glitches und Altlasten bei den Image-Klassen, aber ein BufferedImage ist schon geil. Wenn's dir nicht gefällt... nimm doch mal ein SWT-Image, dann weißt du, was ein Krampf ist :autsch:



Hmm,
wie problematisch wäre eine Pixel-Klasse, wenn ich die ints durch bytes ersetze (größer als 255 werden die Werte ja nicht für ARGB)?
Eine Objektreferenz kostet ~40 bytes? Das ist allerdings viel. Ich glaube, ich werde das MengineImage dann wohl durch einen int array aufbauen. BufferedImage kann ich leider nicht benutzen, da das System für Android und das normale java gleichermaßen laufen soll. 
GetPixelValue wird höchst selten aufgerufen. Ich habe es intern so realisiert, dass jedes Paintable Objekt ein eigenes MengineImage hat. Wenn ein Frame gerendert wird, werden die Bilder aller Paintables in ein MengineImage reinkopiert. GetPixelValue brauche ich eigentlich nur bei der Umwandlung in ein BufferedImage.



> Das ist teilweise richtig und zwar genau dann, wenn BufferedImage ein DirectColorModel verwendet. Deine Implementation von MengineImage ist aber auch nicht wirklich @best, ich hätte z.B. einen kompletten IntBuffer (evtl. sogar Direct) in der Grösse width * height aus dem java.nio Paket erstellt und den Index eines Pixels nach der Formel index = 4 * (y * width + x) bestimmt.



Mit nio habe ich mich noch nicht beschäftigt. Was macht einen IntBuffer in der Hinischt so viel schneller? Und wie benutzt man ihn?

Was wäre, wenn ich vier byte-arrays nutzen würde anstatt eines int-arrays?
Vielen Dank!

MfG,
Degush

edit: Btw, ich nutze an vielen Orten doubles anstatt floats. Ich habe gelesen, dass der Speicherplatzverbrauch vom System abhängt (64 oder 32-bit). Stimmt das? Oder sollte ich lieber floats verwenden?

edit2: Rechnet ein 2D array nicht intern sowieso mit Matrizen? Also ist es nicht  egal, ob ich manuell 2 Koordinaten in eine Zahl umwandle oder ob java das macht?


----------



## Marco13 (12. Feb 2012)

Degush hat gesagt.:


> wie problematisch wäre eine Pixel-Klasse, wenn ich die ints durch bytes ersetze (größer als 255 werden die Werte ja nicht für ARGB)?



Der Speicherverbrauch wäre der gleiche. Ein byte belegt (als instanzvariable) auch 4 bytes. Nur in einem array werden die einzelnen Elemente (d.h. bei byte[] oder short[]) weniger belegen.

Die ~40 bytes waren ein Größenordnung, man findet da leicht unterschiedliche Angaben, und am Ende hängt's von der VM ab. Aber wegen der 4 ints sind's schonmal 16 bytes, und dann noch (was man immer so liest) ca. (!) 20 bytes für die Referenz auf das Objekt selbst und ggf. fields in 'Object'... auf jeden Fall ist es ein vielfaches von dem, was man eigentlich braucht: 4 bytes für ARGB. 

Das Argument mit Android... hättest du mal früher erwähnen sollen  Ich stand vor ein paar Tagen vor dem gleichen Problem: Habe so eine Art "Rendering-Engine" gebaut, und darin la-di-da ganz pragmatisch in der ImageTexture-Klasse ein BufferedImage verwendet. Jetzt hab' ich es wegen Android auch umgestellt auf eine ImageData-Klasse, die eigentlich nur einen int[] und width/height kapselt.



> Mit nio habe ich mich noch nicht beschäftigt. Was macht einen IntBuffer in der Hinischt so viel schneller? Und wie benutzt man ihn?



Ein IntBuffer ist nicht per se schneller: Im Zweifelsfall kapselt der auch nur einen int[]-Array, kann also nicht schneller sein als der (ggf. sogar einen Tick langsamer, weil er noch offsets verwendet und zusätzliche Bounds-Tests macht). Wenn es ein _direct_ buffer wäre, wären allgemeine Aussagen noch schwieriger, vor allem wenn es einmal um Java und einmal um Android geht - da müßte man ganz gezielt Benchmarks machen, und selbst diese Ergebnisse wären dann noch mit Vorsicht zu genießen.




> edit: Btw, ich nutze an vielen Orten doubles anstatt floats. Ich habe gelesen, dass der Speicherplatzverbrauch vom System abhängt (64 oder 32-bit). Stimmt das? Oder sollte ich lieber floats verwenden?



Theoretisch braucht ein double doublet..äh.. doppelt so viel platz wie ein float. Intern ist ein double über zwei Einträge repräsentiert, die jeweils mindestens 4 bytes groß sind. Wie genau die Repräsentation solcher Einträge auf einem 64bitter ist, weiß ich auch nicht auswendig...



> edit2: Rechnet ein 2D array nicht intern sowieso mit Matrizen? Also ist es nicht  egal, ob ich manuell 2 Koordinaten in eine Zahl umwandle oder ob java das macht?



Da ist nicht ganz klar, was gemeint ist. Aber.... diese Schleifen....

```
for(int w = 0; w < img.getWidth(); w++)
        {
            for(int h = 0; h < img.getHeight(); h++)
            {
                pixels[ w + h * img.getWidth() ] = img.getPixel(w, h).getPixelValue();
            }
        }
```
könntest du mal testweise umbauen zu 

```
for(int h = 0; h < img.getHeight(); h++)
        {
            for(int w = 0; w < img.getWidth(); w++)
            {
                pixels[ w + h * img.getWidth() ] = img.getPixel(w, h).getPixelValue();
            }
        }
```
und schauen, ob sich die Geschwindigkeit merklich verändert....


----------



## Spacerat (12. Feb 2012)

Du musst ja keinen Buffer benutzen. Aber wie Marco schon sagt: "Mach' es anders!". Ein eindimensionales int-Array reicht völlig aus, die Pixelformel klappt auch dort. Vllt. stört dich ja die 4 dabei... nun, ein int hat in Java nun mal 4 Bytes. Bei nochmaligem überlegen, wird diese 4 allerdings nur bei der Verwendung eines ByteBuffers bzw. byte-Arrays benötigt.
Buffer selber bekommen in diesem Fall eigentlich erst Bedeutung, wenn sie als DirectBuffer instanziert werden, weil dadurch nicht der Heap der JVM sondern der Hauptspeicher des Systems beansprucht wird. Ein normaler Buffer ist im Prinzip nichts anderes als ein Array des Buffertyps (ByteBuffer -> byteArray usw.) mit diversen Zugriffs-, Kopier- und Positionierungsmethoden. Ein Buffer hat gegenüber einem Array aber auch noch mal einen gewissen Overhead, weswegen ein Array hier eigentlich völlig ausreicht, es sei denn, du möchtest den Java-Heap nicht belasten.
Letzteres ist im übrigen genau die Kleinigkeit, die mir am BufferedImage (faktisch liegt das gar nicht mal an dieser Klasse, sondern eher an den von ihr verwendeten Klassen ColorModel, SampleModel, DataBuffer und Raster) nicht gefällt - es belastet afaik ausschliesslich den Heap und dass nur aus Gründen der Abwärtskompatibilität. Darüber hinaus gibt es viel zu viele BufferedImage-Typen (13 ohne TYPE_CUSTOM) obwohl man Images unabhängig ihres Farbmodels digital nur auf 3 Wegen produzieren kann - wovon einer dabei nicht mal bedacht wurde.
1. Pixelsized:
Jedes Rasterelement dieser Bilder beinhaltet sämtliche Farbinformationen des Farbmodels (in Java als ColorSpace bezeichnet). Eine Palette (in Java als ColorModel bezeichnet) für solche Bilder würde nur statistischen Zwecken dienen.
2. Indexed:
Jedes Rasterelement dieser Bilder zeigt auf ein Element einer Palette. Diese Palette enthält eine Auswahl an Farbinformationen des Farbmodels. Die Anzahl der Farbeinträge einer solchen Palette sind begrenzt. Bilder mit Paletten die mehr als 256 Farben haben, sind mir nicht bekannt. Für ein indiziertes Raster würde also stets ein Byte pro Element vollkommen ausreichen.
3. Hold and Modify:
Dieses "Kompressionsverfahren" wurde in Java gar nicht beachtet. Selbst wenn es eigens für den Amiga konzipiert wurde, heisst das noch lange nicht, dass man es nicht auch anderweitig verwenden kann. Ein Patent auf dieses Verfahren scheint es jedenfalls nicht bzw. nicht mehr zu geben.
Ähnlich wie bei Indexed zeigen Rasterelemente, bei denen die obersten beiden Bits nicht gesetzt sind, auf eine Farbe in einer Palette. Je nach Kombination der oberen beiden Bytes wird anschliessend der Rot- (%10), Grün- (%01) oder Blaukanal (%11) um den Wert in den verbleibenden 4 bzw. 6 Bits modifiziert. Solange anschliessend die obersten beiden Bits ungleich 0 und der Farbwert gleich 0 aus dem Raster übergeben werden, wird in der zuletzt aktiven HAM-Farbe gezeichnet. Auch hier genügt ein Byte pro Rasterelement. Die maximale Anzahl an Palettenfarben ist auf 64 (HAM8) bzw. 16 (HAM6) begrenzt.
Alles in allem müsste ein BufferedImage also nur per

```
BufferedImage(int width, int height, Type type, ColorSpace space);
```
instanziert werden, wobei Type eben eines von PIXELSIZED, INDEXED oder HOLD_AND_MODIFY ist. Die Grösse eines Rasterelements entspricht der Grösse eines Farbwertes des übergebenen ColorSpace bei PIXELSIZED oder halt 1 bei bei INDEXED oder HOLD_AND_MODIFY. Aber das ist schlicht nur meine Theorie... Hab' ich irgendwas vergessen?


----------



## Degush (13. Feb 2012)

Habe jetzt tatsächlich durch eine neue Implementierung die Framrate verzehnfachen (!!!!) können.
Bei der sind die Methoden in der Klasse Pixel alle statisch und das Bild über ein 2D-array realisiert.
Das rübrekopieren mit getRGB und setRGB dauert aber immer noch 50 Milisekunden, viel zu lange.

@Spacerat Ich habe leider nicht alles von dem ganz verstanden, was du meinst. Ich habe mich mit den BufferedImages bisher nur wenig beschäftigt. Wie verwende ich ein DirectColorModel und diese ganzen Raster und was-weiß-ich-Klassen zum rüberkopieren?

MfG,
Malte


----------



## Spacerat (14. Feb 2012)

Degush hat gesagt.:


> Ich habe leider nicht alles von dem ganz verstanden, was du meinst. Ich habe mich mit den BufferedImages bisher nur wenig beschäftigt. Wie verwende ich ein DirectColorModel und diese ganzen Raster und was-weiß-ich-Klassen zum rüberkopieren?


Das merkt man, aber fass das jetzt nicht negativ auf, denn eigentlich hättest du dich damit befassen sollen, bevor du dir eine eigene Imageklasse baust. Eigentlich hätte meine Frage, wie du auf die Art der Implementation deines Imagetyps kommst, auch schon viel früher kommen müssen, denn man kann schon fast davon ausgehen, dass die besagten Klassen (Raster usw.) genau dass machen, was du vor hast und sogar noch viel mehr. Z.B. liefert dir
	
	
	
	





```
int[] data = bufferedImage.getRaster().getDataBuffer().getData();
```
die kompletten Bilddaten (ints im 0xAARRGGBB-FORMAT) eines TYPE_INT_ARGB-BufferedImages und das sogar ungeklont, so dass sich alle Änderungen, die man in diesem Array vornimmt, direkt auf das Bild auswirken. Das dürfte dann auch der beste Weg sein, wie man dort einzelne Pixel ändert. Die Bilddaten sind dort zeilenweise abgelegt, so dass man einzelne Pixel wie gesagt mit der Formel [c]index = y * width + x[/c] ansprechen kann. Genau dass macht deine Imageklasse und vorallem das 2D-Array schon mal recht überflüssig. Im übrigen bin ich auch erst vor kurzem auf den Umstand gestossen, dass man ARGB-Images so einfach bearbeiten kann. Im selben Moment aber leider auch darauf, dass man Imagedaten nicht ausserhalb des JVM-Heap halten kann, solange man kompatibilität gewährleisten will.


----------



## Marco13 (14. Feb 2012)

Wenn man sich die Pixel so direkt abholt, muss man zumindest damit rechnen, dass das Bild danach nicht mehr "managed" ist, und das eigentliche _Zeichnen_ des Bildes deutlich langsamer wird (diese Erfahrung habe ich auch ganz praktisch gemacht - vorer konnte man das Bild mit g.drawImage schnell und flüssig malen, und NACH so einem Zugriff hat es deutlich geruckelt). Es gibt aber viele Fälle, wo das Bild gar nicht gezeichnet werden muss, da ist es natürlich OK, es so als "Pixelspeicher" zu verwenden. 

Ein bißchen was dazu hatte ich ja schon in http://www.java-forum.org/spiele-mu...18-performance-bufferedimages.html#post776167 geschrieben, bzw. steht in den von dort verlinkten Beiträgen - speziell sowas wie Java Image Processing - Managed Images and Performance . Inwieweit das zwischen verschiedenen JVM-Versionen, Betriebssystemen (oder sogar abhängig von Details wie Grafikkarte und Treiber) nochmal unterschiedlich ist, müßte man mal mit einem "neuesten Stand von allem" testen.


----------



## Spacerat (14. Feb 2012)

Okay, dann hätte ich gerne gewusst, was es mit diesem managed auf sich hat. Lt. Quelltext bekommt man nämlich tatsächlich das unkopierte Daten-Array des Bildes, zumindest ist's beim ARGB-Typ so. Das einzige, was ich mir vorstellen kann, ist dass Bilder erst dann managed werden, wenn sie zum ersten mal gezeichnet wurden. Bei jedem Neuzeichnen werden möglicherweise die Daten gehashed, das Array anschliessend bei unterschieden zum vorherigem Hash erneut kopiert und während dieses Kopiervorganges werden noch mal alle Pixel auf Gültigkeit geprüft - was bei ARGB (Hardwareformat) natürlich auch unheimlich Sinn macht.


----------



## Marco13 (14. Feb 2012)

Das wüßte ich auch gerne  Es stimmt, dass man den Array direkt bekommt, und Änderungen an diesem Array auch auf dem Bildschirm sichtbar werden. Diesen Performanceunterschied hatte ich wirklich nur bemerkt, als ich einmal ein (sehr, sehr, großes ... AFAIR >5000x5000) Bild gemalt habe, das man so mit der Maus verschieben konnte, und das ging flüssigst. Aber sobald ich mir den Array geholt hatte, hat es mit ~<10FPS rumgeruckelt. Das ganze müßte man nochmal reporoduzierbar in einen Testfall packen... 

Das eigentliche Managen ist ziemlich kompliziert. Ich hatte da mal ausführlicher gesucht, und z.B. in http://www.java-forum.org/awt-swing-swt/123479-int-bufferedimage-konvertieren.html#post798875 ein bißchen was dazu geschrieben (siehe auch das mit "setStolen") aber wie da schon steht: Man landet schnell bei nicht-öffentlichen Klassen, und im nativen Teil (den man sich ohnehin nur im OpenJDK ansehen könnte) habe ich noch nicht weiter geschaut. Selbst wenn, sind das ja auch alles Erkenntnisse, die schon bei der nächsten VM anders sein könnten....


----------



## Degush (16. Feb 2012)

Gut, ich habe mich mal ein wenig mit den Links beschäftigt.
Ich wusste bisher gar nicht, dass BufferedImages möglicherweise im VRam gelagert werden und habe mich immer über diese ganzen Klassen gewundert, die die Imageraffinerie bereithält.

Mein aktueller Ansatz sind so aus: Anstatt eine Klasse MengineImage zu haben und eine Klasse, die MengineImages zu plattformkompatiblen Images umwandelt, habe ich das EngineImage nun so realisiert, dass man einfach andere Klassen verwendet.
Meine derzeitige Implementation für Standard-Java sieht also so aus:


```
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */


package tig;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

/**
 *
 * @author Malte
 */
public class MengineImage extends BufferedImage
{
    private int[] xHeight;
    private boolean[][] transparent;
    
    public MengineImage(int width, int height)
    {
        super(width,height,BufferedImage.TYPE_INT_ARGB);
    }
    
    public MengineImage(BufferedImage source)
    {
        super(source.getWidth(),source.getHeight(),BufferedImage.TYPE_INT_ARGB);
        
        int[] data = ((DataBufferInt)source.getRaster().getDataBuffer()).getData();
        
        xHeight = new int[source.getWidth()];
        transparent = new boolean[source.getWidth()][source.getHeight()];

        for(int x = 0; x < source.getWidth(); x++)
        {
            for(int y = 0; y < source.getHeight(); y++)
            {
                transparent[x][y] = !Pixel.isTransparent( data[ ( y * source.getWidth() + x )] );
            }
            int h = 0;
            while( h < source.getHeight() )
            {
                if ( !transparent[x][h] )
                {
                    xHeight[x] = h;
                    break;
                }
                h++;
                
            }
        }
        
        this.getGraphics().drawImage(source, 0, 0, null);
    }
    
    public void clear()
    {
        this.setData(null);
    }
    
    public void copy(MengineImage img, int x, int y)
    {
        this.getGraphics().drawImage(img, x, y, null);
        for(int w = x; w < img.getWidth()+x && w < getWidth(); w++)
        {
            for(int h = y; h < img.getHeight()+y && h < getHeight(); h++ )
            {
                transparent[w][h] = img.transparent[w-x][h-x];
            }
        }
    }

    @Override
    public int getWidth()
    {
        return super.getWidth();
    }

    @Override
    public int getHeight()
    {
        return super.getHeight();
    }

    public boolean isPixelTransparent(int x, int y)
    {
        return !transparent[x][y];
    }

    public int getHeightAt(int x)
    {
        return xHeight[x];
    }

    public void setScale(float f)
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
```

Kommentierung kommt später


----------



## Marco13 (16. Feb 2012)

Vorsicht: Ein "extends BufferedImage" wird auf Android nicht funktionieren. Besser wäre GANZ GROB (!!!) sowas wie

```
interface MengineImage
{
    void doThis();
    void doThat();
    // was ein Image so alles kann ;) 
}

// Für Java:
class MengineImageAWT implements MengineImage
{
    private BufferedImage image;
    ...
}
```
GGf. auch noch mit einer Factory für diese Klassen

Wie du Android und AWT "auf höherer Ebene" unter einen Hut bringen willst, wird sowieso spannend - mindestens genauso spannend wie die Frage, was im MengineImage-Interface so alles stehen soll - aber im Zweifelsfall wohl sowas ähnliches wie jetzt in deiner Klasse (unabhängig davon, ob das "die beste Lösung" ist)


----------



## Degush (16. Feb 2012)

So hatte ich es erst realisiert ( mit einem Interface ). Allerdings ergibt sich dann eine unschöne copy-Methode, da sie ein Interface als Parameter erwarten muss, BufferedImage bzw graphics.drawImage aber mit diesem interface nichts anfangen kann.

Das extends BufferedImage ist nur "Engine-extern" von Belang. Deshalb kann man die Klasse für android einfach ganz anders ausfüllen. Übrigens habe ich irgendwo gelesen, dass interface-Aufrufe nicht so schnell sind wie normale Methodenaufrufe - wenn ich mir das Interface sowieso sparen kann, nehme ich ein bisschen Geschwindigkeit mehr gerne mit.

Problematisch im Moment:
Ich kriege, obwohl ich nicht mehr BufferedImages zu MengineImages kopieren und zurückkopieren muss, nur im Schnitt 30 FPS bei einer einfachen Demo.
Wenn man sich anschaut, wie häufig die Methoden aufgerufen werden, stellt man fest, dass copy von allen Methoden die effizienteste sein muss.
Frage also: Hat ein BufferedImage einen großen Performancevorteil beim Zeichnen auf ein anderes BufferedImage gegenüber einfachem Kopieren des Pixel arrays?

MfG,
Malte


----------



## Marco13 (16. Feb 2012)

Ja, die Copy-Methode wäre einer dieser spannenden Punkte. Da könnte man SEHR viel nachdenken, und SEHR weit abstrahieren und so... aber in diesem Fall KÖNNTE eine pragmatische Lösung reichen:

```
class MengineImageAWT implements MengineImage
{
    @Override
    public void copy(MengineImage other)
    {
        if (other instanceof MengineImageAWT)
        {
            MengineImageAWT otherAWT = (MengineImageAWT)other;
            // mach was mit otherAWT.bufferedImage...
        }
        else
        {
            throw new IllegalArgumentException("Hey, wir sind in AWT - wo kommt da ein nicht-AWT-image her?");
        }
    }
}
```
Wenn es in AWT auch merhere Implementierungen geben soll, muss man sich was allgemeineres überlegen.

Mach' dir um die Geschwindigkeit bei Interfaces keine Gedanken. (Mehr Infos gibt's unter [JavaSpecialists 158] - Polymorphism Performance Mysteries Explained falls es dich doch interessiert)

Spannend ist die Frage, warum so oft kopiert werden muss. Kann man das nicht vermeiden? Ein BufferedImage in ein anderes zu malen solle sehr schnell sein, speziell wenn beide vom Typ INT_ARGB oder INT_RGB sind. Für genauere Infos müßte man gezielt Benchmarken.


----------



## Degush (16. Feb 2012)

Hmm,
das sieht nach einer ganz angenehmen Lösung aus, danke.

Im Grunde genommen funktioniert das Ganze so:
Es gibt eine Klasse Universe, die 3 Hauptthreads hält.
Diese Klasse kennt alle Paintable-Objekte und entscheidet, welche sichtbar sind und gerendert werden müssen und welche nicht.
Ein Thread davon zeichnet alle Objekte. Das funktioniert so:
1) In der Klasse Universe ist ein MengineImage mit der Größe des Universums angelegt (meinetwegen 1024*768)
2) Eine Schleife geht durch alle Paintable-Objekte und kopiert sie, abhängig von ihrer gegenwärtigen x-und y-Position
in das im Universum angelegte Bild hinein. Ein Paintable Objekt hat ein MengineImage, das seine grafische Representation festlegt. Ein Paintable kann Animationen abspielen, die diese Representation verändern
3) das Bild wird angezeigt. Das heißt in ein double-buffered Canvas kopiert.

Habe zur Zeit nur 30 FPS


----------



## bERt0r (16. Feb 2012)

Wieso musst du da Bilder rumkopieren, du kannst es doch einfach auf dein Graphics zeichnen. Obwohl, canvas klingt ja nach AWT... *hust* Swing hat automatisches doublebuffering *hust*


----------



## Spacerat (17. Feb 2012)

Marco13 hat gesagt.:


> Vorsicht: Ein "extends BufferedImage" wird auf Android nicht funktionieren.


Das ist nur die halbe Wahrheit... Im Prinzip existiert bei Android ja kein Paket [c]java.awt[/c]. Deswegen kann es kaum geschützt werden und deswegen auch sein eigenes [c]java.awt.image.BufferedImage[/c] implementieren... naja... wer auf Schmerzen steht... 
[EDIT][OT]Richtig fies: Sämtliche [c]java.awt[/c] Klassen findet man im [c]src.zip[/c]des Sun/Oracle-JDKs. Fügt man dieses einem Android-Projekt in Eclipse hinzu, färbt sich zwar einiges Rot, aber vllt. kann man die fehlenden Teile ja mit Androidmitteln realisieren... wär ja mal 'nen Versuch wert.[/OT][/EDIT]


----------



## Marco13 (17. Feb 2012)

Hmja... das eigentliche Umkopieren könnte man vermutlich so schnell machen, dass es aktzeptabel wäre... Aber... den Krampf mit dem "transparent" konnte ich nicht ganz nachvollziehen... was sird da gemacht?

BTW: Könnten die MengineImages, die in den Paintables liegen, nicht in irgendeiner Form sein, dass sie direkt gezeichnet werden können? (Damit man sich das Kopieren ins Universe spart...)


----------



## Degush (17. Feb 2012)

Wo ist denn der große Unterschied, ob ich die alle ins Universe kopiere und dann zeichne oder nicht?
Habe ich dann ca. 2x so viele FPs, weil ich mir die Hälfte des Kopierens (1x alle in Universe Aufwand ~ Universe zeichnen Aufwan) spare?

Das transparent boolean array ist für die Kollisionsüberprüfung von zwei Paintables.


----------



## Marco13 (17. Feb 2012)

FALLS es eine vertretbare Lösung wäre, den Paintables sowas wie eine "paintYourself"-Methode zu geben, könnten darin die Funktionen verwendet werden, die die schnellsten sind - auf AWT eben ein BufferedImage mit g.drawImage zeichnen, und auf Android was anderes - aber das muss man sich GENAU überlegen.


----------



## Degush (17. Feb 2012)

naja,
das Kopieren sollte nicht allzu lange dauern:


```
public void copy(MengineImage img, int px, int py)
    {
        this.getGraphics().drawImage(img, px, py, null);
    }
```


----------



## bERt0r (18. Feb 2012)

Bin jetzt betrunken aber mal eine Frage für den Hausverstand: Was dauert länger/ist umständlicher? 
1. 10 Fotos auf ein Blatt zu zeichnen und dieses Blatt dann auf eine Leinwand zu malen
oder
2. die 10 Fotos gleich auf die Leinwand zu zeichnen?

Ich sag mal 1.


----------



## Degush (22. Feb 2012)

So.
Ich stelle die Plattformkompatibilität mit Android erst einmal zurück, das ist zu unterschiedlich und komplex, um sinnvollen Code zu schreiben.
Ich habe es nun so gelöst, dass ein Universe ein JFrame und ein Canvas hat und die BufferedImage direkt darein gezeichnet werden.

Wenn ich mich konzentriere auf das Standard-Java werde ich noch Dinge einbauen können wie dynamische Lichteffekte und hardwarehungrige Physik et cetetera.

Vielen Dank für alle Antworten in diesem Thread!

MfG,
Malte


----------

