getRaster().getData() vs PixelGrabber

Degush

Aktives Mitglied
Hallo liebe Community,

um die Bilder in einer Engine schnell darzustellen, nutze ich BufferedImages. Nun möchte ich die Bilder aber ( Geschwindigkeit spielt eine untergeordnete Rolle ) mit bestimmten Operatoren filtern - Movarec Operator, Sobel, Median, usw. um sie zu glätten, schärfen, Kanten zu detektieren, Rauschen zu verkleinern usw.
Dafür brauche ich die konkreten Pixeldaten des Bildes.

An die kann ich ja kommen über:
Java:
(DataBufferInt)getRaster().getDataBuffer()).getData();
in einer Klasse, die BufferedImage erweitert.
Ich habe allerdings bereits jetzt einen sehr hohen Arbeitsspeicher- und CPUverbrauch und habe Angst, dass die Methode da zu langsam ist, um sie bei jeder Nachfrage nach einem Pixel auf Position XY neu zu verwenden und dass ich zu viel Arbeitsspeicher aufbrauche, wenn ich jetzt anfange, für jedes BufferedImage zusätzlich noch seine Rohinformationen anzuzeigen.

Ich weiß, dass die PixelInformationen von BufferedImages, wenn man es richitg macht, im Grafikkartenspeicher gespeichert sind, um das Rendern zu beschleunigen.
Und ich habe noch irgendwelche Informationen mit PixelGrabbern gesehen, die auch die Bildpixel auslesen.

Frage: Wie kriege ich möglichst direkten Zugriff auf die Bildpixel?
 

Marco13

Top Contributor
Wenn die Geschwindigkeit eine Untergeordnete Rolle spielt, könnte man ggf. auch BufferedImageOp (Java Platform SE 6) (ConvoleOp für Sobel etc) verwenden - damit ist das ganze etwas abstrakter und allgemeingültiger.

Wenn du auf die Bilddaten direkt zugreifen musst/willst, kannst du dir den DataBufferInt holen. Das erfordert nicht mehr Speicher, weil die Daten sowieso einmal im normalen RAM liegen, und man sich mit der angedeuteten Methode direkt DEN (echten) Array aus dem BufferedImage holt (und nicht etwa eine Kopie).

Beachte aber, dass wenn man sich einmal diesen Array geholt hat, das Bild nicht mehr "managed" sein kann - GROB gesagt muss danach sichergestellt werden, dass die Daten im Array (im RAM) und auf der Grafikkarte (im VRAM) konsistent sind - und im speziellen danach das Zeichnen des Bildes langsamer gehen kann (siehe auch http://www.java-forum.org/spiele-multimedia-programmierung/120318-performance-bufferedimages.html )

Auf Java Image Processing - Managed Images and Performance gibt's noch ein paar Infos dazu, und allgemein könnte die Seite für dich auch interessant sein.
 

Degush

Aktives Mitglied
Hey, danke Marco!

Eine Frage habe ich noch;

Java:
Wenn du auf die Bilddaten direkt zugreifen musst/willst, kannst du dir den DataBufferInt holen

Heißt dass, dass diese Funktion:
Java:
 (DataBufferInt)getRaster().getDataBuffer()).getData();
einfach nur den Zugriff auf ein DataBufferInt kapselt?
Und dass ich die Daten nicht zwischenspeichern sollte?

Java:
Beachte aber, dass wenn man sich einmal diesen Array geholt hat, das Bild nicht mehr "managed" sein kann

Nach der Bearbeitung kann ich das Bild ja neu auf sich selbst zeichnen lassen.
 

Marco13

Top Contributor
In dem DataBufferInt steckt ja der int[]-Array mit den Pixeln, den man sich mit "getData" abholen kann. Den kann man speichern, es ist ja nur eine Referenz auf die Daten im Image (und wenn man den Arrayinhalt ändert, ändert man das Bild).
Aber wenn ein Bild erstmal "unmanaged" ist, kann man es nicht wieder "managed" machen. Ggf. könnte (!) es dann sogar sinnvoll sein, das "unmanaged" Bild (bei dem man den int[] verändert hat) in ein neues Bild reinzumalen, und dann das neue (managed) Bild zu zeichnen.
 

Degush

Aktives Mitglied
Habe es jetzt so gemacht, dass ich einfach den Buffer speichere. Der DataIntBuffer ist ja nur eine Referenz. Wenn ich den Array auslese und verändere, passiert nichts.
Allerdubgs habe ich irgendwie unerwartete Resultate beim Filtern.

Bei einem Glättefilter mit der Maske
1 1 1
1 1 1
1 1 1

mal 1/9 kriege ich beim Filtern ein Bild mit folgendem Ergebnis:

Directupload.net - dl9wepzk.png

Als Pixelwerte kriege ich auch negative Pixel. Ich vermute, dass das am DataBufferInt liegt. Dass er signed Integers ausspuckt oder so.
 

Degush

Aktives Mitglied
Da ist mein Code:

Java:
/**
 *
 * @author Malte
 */
public class LinearImageFilter implements ImageFilter
{
    public static final int SMOOTHING_FILTER = 0;
    public static final int SHARPENING_FILTER = 1;
    public static final int SOBEL_FILTER = 2;
    
    public static Logger log = new Logger("LinearImageFilter");
    
    private int[][] filterMask;
    private double weighfactor = 1;
    private int maxIndex;
    
    public static LinearImageFilter create(int type, int size)
    {
        if ( type == SMOOTHING_FILTER )
        {
            int[][] matrix = new int[size][size];
            for(int x = 0; x < size; x++)
            {
                for(int y = 0; y < size; y++)
                {
                    matrix[x][y] = 1;
                    log.log("Matrix "+x+"/"+y+": "+matrix[x][y]);
                }
            }
            return new LinearImageFilter(matrix, 1./(size*size) );
        } else if ( type == SHARPENING_FILTER )
        {
            
        } else if ( type == SOBEL_FILTER )
        {
            int[][] matrix = {
            {1,2,1},
            {0,0,0},
            {-1,-2,-1}
            };
            return new LinearImageFilter(matrix, 1);
        }
        return null;
    }
    
    public LinearImageFilter(int[][] multi, double weight)
    {
        this.filterMask = multi;
        log.log("Weightfactor: "+weight);
        this.weighfactor = weight;
        this.maxIndex = (multi.length-1)/2;
        log.log("MaxIndex: "+maxIndex);
    }
    
    @Override
    public int getSize()
    {
        return filterMask.length;
    }
    
    @Override
    public int operate(int[][] values)
    {
        int value = 0;
        for(int i = 0; i < values.length; i++)
        {
            for(int y = 0; y < values[0].length; y++)
            {
                value += values[i][y]*filterMask[i][y];
            }
        }
        return (int)(value*weighfactor);
    }
    
    private double getPositive(double d)
    {
        if ( d < 0 )
            return -d;
        return d;
    }
    
    @Override
    public int[] filter(int[] pixels, int imageWidth)
    {
        int[] filteredData = new int[pixels.length];
        int imageHeight = pixels.length/imageWidth;
        
        if ( log.isActivated() )
            log.log("Filtering Image width Width: "+imageWidth+" and Height: "+imageHeight);
        
        double value;
        int i;
        
        for(int y = 0; y < imageHeight-maxIndex; y++)
        {
            for(int x = 0; x < imageWidth-maxIndex; x++)
            {
                value = 0;
                //log.log("===================================");
                for(int u = 0; u < filterMask.length; u++)
                {
                    for(int v = 0; v < filterMask.length; v++)
                    {
                        i = ((y+v-maxIndex)*imageWidth)+(x+u-maxIndex);
                        if ( i >= 0 && i < pixels.length )
                        {
                            value += (((double)pixels[i])*weighfactor*filterMask[u][v]);
                            //log.log("Value: "+value);
                        }
                    }
                }
                i = ( (y + maxIndex) * imageWidth + x + maxIndex);
                if ( i >= 0 && i < filteredData.length )
                {
                    if ( !(value > -4000000 && value < 250000) )
                    {
                        filteredData[i] = (int)(value);
                    } else
                    {
                        filteredData[i] = 0;
                    }
                }
                //log.log("Pixel: "+filteredData[i]);
            }
        }
        
        return filteredData;
    }
}
 

Marco13

Top Contributor
Ein KSKB wäre nicht schlecht. Aber wenn du das mit einem programmatisch erstellten BufferedImage füttern würdest, bei dem die Pixel-Werte alle 1 sind, würdest du den Fehler vermutlich schon selbst finden.
 

Degush

Aktives Mitglied
Hmm, ich finde den Fehler nicht. In einem schwarzen Quadrat auf weißem Untergrund mit der Filtergröße 25 und einem Glättefilter, ändern sich die Farben Ränder des schwarzen Kastens von dunkelblau (innen) zu hellorange (außen). Nicht von dunkel zu hell oder von hell zu dunkel.

Result.png
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Jetzt wo das bei mir im Großhirn angekommen ist: Das ist ja auch kompletter Unfug... Du rechnest ja mit den int-Werten, die den RGB-Farbcode beschreiben. Rechnungen (Addition, Division...) darauf und nebenbei solche Abfragen wie
if ( !(value > -4000000 && value < 250000) )
ergeben dort gar keinen Sinn. Berechne die RGB-Komponenen einzeln
Java:
int rSum = 0;
int gSum = 0;
int bSum = 0;
for (... all pixels...)
{
    for (... kernel ... )
    {
        int rgb = pixel[index]; 
        byte r = (byte)((rgb >> 16) & 0xFF);
        byte g = (byte)((rgb >>  8) & 0xFF);
        byte b = (byte)((rgb >>  0) & 0xFF);
        rSum += r * ...
        gSum += g * ...
        bSum += b* ...
   }
    rSum /= sum;
    gSum /= sum;
    bSum /= sum;
    int newRgb = (rSum << 16) | (gSum << 8) | bSum;
    ...

}
 

Namphiz

Mitglied
Aber wenn ein Bild erstmal "unmanaged" ist, kann man es nicht wieder "managed" machen.

Ist das auch der Grund, weshalb man keine Graphics-Methoden (brauchen die "managed" Bilder?) mehr auf dem Bild ausführen kann?

Ich benutze ein Pixel Array und eine BufferStrategy... von der BufferStrategy bekomme ich das Graphics-Objekt, welches ich an eine Methode weitergebe und später das Bild anzeigen lasse.

In dieser Methode möchte ich zum Beispiel einen String auf das Bild "malen". Ein Aufruf der Form g.drawString(...) führt jedoch zu keiner Änderung.

Folgendes funktioniert allerdings:
Java:
pixels[5433] = 0xFF0000;
In diesem Fall würde Pixel Nr. 5433 rot sein.
 

Marco13

Top Contributor
BufferStrategy habe ich nocht nicht so intensiv verwendet, und kann das gerade nicht einordnen, kann man da ein KSKB erstellen?
 

Namphiz

Mitglied
Hier habe ich dir ein kleines Beispiel geschrieben, dass die Benutzung demonstriert:

Java:
package graphics;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

import javax.swing.JFrame;

public class PerPixelRendering extends Canvas implements Runnable {
	
	public static final int WIDTH = 640;
	public static final int HEIGHT = 480;
	
	Thread renderThread;
	BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
	int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
	
	@Override
	public void run() {
		while (true) {
			render();
		}
	}
	
	public void render() {
		BufferStrategy bs = getBufferStrategy();
		if (bs == null) {
			createBufferStrategy(3);
			return;
		}
		
		Graphics g = bs.getDrawGraphics();
		
		renderBackground();	// FUNKTIONIERT
		drawString(g);		// FUNKTIONIERT NICHT
		
		g.drawImage(image, 0, 0, null);
		g.dispose();
		bs.show();
	}
	
	private void renderBackground() {
		for (int y = 0; y < HEIGHT; y++) {
			for (int x = 0; x < WIDTH; x++) {
				pixels[x + y * WIDTH] = 0xFF0000;
			}
		}
	}
	
	private void drawString(Graphics g) {
		g.setColor(new Color(0xFFFFFF));
		g.drawString("TEST", 100, 100);
	}
	
	public void start() {
		renderThread = new Thread(this, "Render Thread");
		renderThread.start();
	}

	public static void main(String[] args) {
		PerPixelRendering ppr = new PerPixelRendering();
		ppr.setPreferredSize(new Dimension(PerPixelRendering.WIDTH, PerPixelRendering.HEIGHT));
		JFrame frame = new JFrame("Per Pixel Rendering");
		frame.add(ppr);
		frame.setResizable(false);
		frame.pack();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
		ppr.start();
	}
}

Ach.. und ich habe noch eine Verständnisfrage:

Das Pixel-Array ist ja eine Referenz (ich wusste gar nicht genau, dass es sowas in Java gibt...kenne das nur von C/C++) auf den Speicherbereich, der die Integer für die Pixelfarben darstellt oder?
Weil ich mich gewundert habe, wieso das Darstellen funktioniert, wenn das Pixel-Array dem Image doch nirgendwo mehr zugewiesen wird.
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Ja, der int[] array ist eine Referenz (einschließlich der Auswirkungen auf das managed-sein).

Bzgl des Codes: Das Bild wird einfach über den Text drübergepinselt.... Mit sowas wie

Graphics gg = image.createGraphics();
drawString(gg); // FUNKTIONIERT DOCH
gg.dispose();

landet der Text IM Bild (d.h. im int-array), und das sieht man dann auch... Ansonsten müßte man den Text NACH dem Bild malen...
 

Ähnliche Java Themen

Neue Themen


Oben