Mithilfe von Koordinaten und Farbe ein Pixel zeichnen

Hey,
ich bin eher Object-Pascal-Programmierer und habe noch nicht so viel Ahnung von Java.
Möchte nun in einer Funktion, welche als Parameter x- und y-Koordinate und eine Farbe erhält, einen Pixel an der entsprechenden Position mit dieser Farbe einfärben.
Die Funktion sollte natürlich in einer JFrame-erweiternden Klasse lokalisiert sein.

Habe schon diversen Kram ausprobiert; mal wurde nichts angezeigt, mal wurde nur das zuletzt gezeichnete Pixel angezeigt, mal wurde nur die Hälfte angezeigt.

In Object-Pascal ist das enorm einfach, man greift über ein Bild auf ein Pixel-Array zu und bearbeitet dieses.

Hat jemand eine Lösung für mich? Habe natürlich auch schon gesucht, aber nichts zufriedenstellendes gefunden.

Lg Sebastian

Hallo Sebastian,

in Java kann man das eigentlich genauso handhaben. Die Klasse BufferedImage hat ja bereits Methoden zum lesen/schreiben einzelner Pixel. Falls man lieber ein eigenes Array von RGB-Werten verwendet, dann kann man sich auch ein darauf basierendes BufferedImage erzeugen:

BufferedImage createImageFromArray(int[] myRgbArray, int width, int height) {
	ColorModel colorModel = DirectColorModel.getRGBdefault();
	SampleModel sampleModel = colorModel.createCompatibleSampleModel(width, height);
	DataBuffer buffer = new DataBufferInt(myRgbArray, width*height);
	WritableRaster raster = Raster.createWritableRaster(sampleModel, buffer, null);
	BufferedImage image = new BufferedImage( colorModel, raster, false, null);
	return image;
}

Nach Änderungen im Array muss man freilich noch dafür sorgen das das Image neu auf den Screen gezeichnet wird.

Einen Nachteil hat das Ganze aber: BufferedImages werden von Java normalerweise mit Hardwareunterstützung gehandhabt. Diese Hardwareunterstützung entfällt, sobald Java nicht mehr die alleinige Kontrolle über das Image hat - wie in unserem Fall, wo wir ja jederzeit das Array als zugrundeliegende Datenquelle des Images verändern können (siehe dazu Kapitel 3.2.3.4 im TSG). Ob dieser etwaige Performanceverlust für deine Anwendung tragisch ist musst du halt sehen.

[QUOTE=Dow_Jones;29971]Hallo Sebastian,

in Java kann man das eigentlich genauso handhaben. Die Klasse BufferedImage hat ja bereits Methoden zum lesen/schreiben einzelner Pixel. Falls man lieber ein eigenes Array von RGB-Werten verwendet, dann kann man sich auch ein darauf basierendes BufferedImage erzeugen:

BufferedImage createImageFromArray(int[] myRgbArray, int width, int height) {
	ColorModel colorModel = DirectColorModel.getRGBdefault();
	SampleModel sampleModel = colorModel.createCompatibleSampleModel(width, height);
	DataBuffer buffer = new DataBufferInt(myRgbArray, width*height);
	WritableRaster raster = Raster.createWritableRaster(sampleModel, buffer, null);
	BufferedImage image = new BufferedImage( colorModel, raster, false, null);
	return image;
}

Nach Änderungen im Array muss man freilich noch dafür sorgen das das Image neu auf den Screen gezeichnet wird.

Einen Nachteil hat das Ganze aber: BufferedImages werden von Java normalerweise mit Hardwareunterstützung gehandhabt. Diese Hardwareunterstützung entfällt, sobald Java nicht mehr die alleinige Kontrolle über das Image hat - wie in unserem Fall, wo wir ja jederzeit das Array als zugrundeliegende Datenquelle des Images verändern können (siehe dazu Kapitel 3.2.3.4 im TSG). Ob dieser etwaige Performanceverlust für deine Anwendung tragisch ist musst du halt sehen.[/QUOTE]…diese Hardwareunterstützung wird abgeschaltet, sobald mindestens ein Array des DataBuffers innerhalb der JVM noch von anderen APIs ausser Java2D erreichbar ist (bis Java6 “stolen”-Bit, ab Java7 “UNTRACKABLE”-State). Ein Int-Array packt man also am besten so in ein BufferedImage

    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    image.setRGB(0, 0, width, height, myRgbArray, 0, width);
    return image;
}```
Einzelne Punkte kann man in einem BI auch per "setRGB(int x, int y, int argb)" setzen und in Graphics oder Graphics2D funktioniert "setColor(desiredColor); drawLine(x, y, x, y);" recht gut.

Wie wärs mit einer Klasse die von Jpanel erbt einfach folgendes zu tun:

public void paintComponent(Graphics g){
    super.paintComponent(g);
    g.drawLine(x, y, x, y);
}

? :wink:

[QUOTE=mymaksimus]Wie wärs mit einer Klasse die von Jpanel erbt einfach folgendes zu tun:

public void paintComponent(Graphics g){
    super.paintComponent(g);
    g.drawLine(x, y, x, y);
}

? ;)[/QUOTE]Du hast den Farbfilm vergessen…. Siehe mein vorigen Post. :wink:

Wie so oft kann es … unschöne … Auswirkungen haben, wenn man eine Frage zu beantworten versucht, die noch nicht genau genug formuliert wurde. Die Verwendung eines eigenen int-Arrays hätte dabei noch den kleineren technischen Nachteil, nämlich “nur” dass das Bild untrackable wird, aber u.U. den größeren Nachteil dass man denkt, “das muss in Java so kompliziert sein” und den Code unverstanden übernimmt. Das mit dem g.drawLine funktioniert erstmal nur für EINEN Punkt, und ich kann mir nicht vorstellen, dass der TO nur EINEN Punkt malen will (und die Vorstellung, dass er das mit sowas wie int p[][] = new int[1000*1000][2] auf 1000x1000 Pixel erweitern könnte sollte weitere mögliche Nachteile deutlich machen).

Abgesehen von off-topic-Fragen wie z.B. warum denn da von JFrame geerbt werden soll, würde ich zuerst nachfragen, wofür diese Pixel gesetzt werden sollen, wie viele es sein sollen, was damit gemacht werden soll, wie groß der Bereich sein soll, um den es da geht usw…

Ich könnte mir vorstellen, dass sowas wie

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class SetPixelTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        PixelImageComponent pixelImageComponent =
            new PixelImageComponent(100,100);
        f.getContentPane().add(pixelImageComponent);
        
        pixelImageComponent.setPixel(10,10, Color.RED);
        pixelImageComponent.setPixel(11,10, Color.GREEN);
        pixelImageComponent.setPixel(12,10, Color.BLUE);
        pixelImageComponent.setPixel(13,10, Color.CYAN);
        pixelImageComponent.setPixel(14,10, Color.MAGENTA);
        pixelImageComponent.setPixel(15,10, Color.YELLOW);
        
        f.setSize(200,200);
        f.setVisible(true);
    }
}

class PixelImageComponent extends JPanel
{
    private BufferedImage image;
    
    public PixelImageComponent(int width, int height)
    {
        image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = image.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0,0,width,height);
        g.dispose();
    }
    
    public void setPixel(int x, int y, Color color)
    {
        image.setRGB(x, y, color.getRGB());
        repaint();
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawImage(image, 0, 0, this);

    }
}

eher in die Richtung des gewünschten geht, aber das ist natürlich erstmal nur haltlose Spekulation, und ohne genauere Infos kann auch genau DAS wieder eine SEHR ungünstige Lösung sein…

haha stimmt xD

naja ein

g.setColor(Color.FARBFILM)

Tuts auch :wink:

Hey,
erstmal vielen Dank für die zahlreichen und sehr schnellen Antworten!

an Dow_Jones: deine Methode sieht sehr interessant aus, leider weiß ich nicht so recht, wie ich den Code anwenden soll. Funktioniert das nur mit eindimensionalen Arrays?

[QUOTE=Marco13]Wie so oft kann es … unschöne … Auswirkungen haben, wenn man eine Frage zu beantworten versucht, die noch nicht genau genug formuliert wurde. Die Verwendung eines eigenen int-Arrays hätte dabei noch den kleineren technischen Nachteil, nämlich “nur” dass das Bild untrackable wird, aber u.U. den größeren Nachteil dass man denkt, “das muss in Java so kompliziert sein” und den Code unverstanden übernimmt. Das mit dem g.drawLine funktioniert erstmal nur für EINEN Punkt, und ich kann mir nicht vorstellen, dass der TO nur EINEN Punkt malen will (und die Vorstellung, dass er das mit sowas wie int p[][] = new int[1000*1000][2] auf 1000x1000 Pixel erweitern könnte sollte weitere mögliche Nachteile deutlich machen).
[/QUOTE]

Du hast Recht, ich möchte natürlich nicht nur einen Punkt malen. Tut mir Leid, dass ich das nicht perfekt formuliert habe. Ein zweidimensionales Array soll dargestellt werden, welches in der Größenordnung 30x30 - 100x100 liegt.
Deine Variante mit der PixelImageComponent-Klasse funktioniert perfekt, vielen Dank!

Lg Sebastian

Nun, wenn ohnehin schon ein array vorliegt, KÖNNTE Dow_Jones’ variante auch eine Option sein - dass quasi das array das “Modell” ist, und man dafür eine “View” erstellt. Allerdings funktioniert das, wie du schon vermutet zu haben scheinst, erstmal nur mit Eindimensionalen Arrays. Für den zweidimensionalen Fall könnte man sich was überlegen, womit man die Daten ggf. schneller ins BufferedImage bekommt. Aber bei 100x100 sollte das noch nicht relevant sein, außer, wenn das wirklich sehr oft (z.B. für eine Animation) aktualisiert werden müßte. Diese angedeutete “PixelImageComponent” könnte man dann auch dahingehend erweitern, dass man nicht jeden Pixel einzeln setzen muss, sondern ihr den Array übergeben kann, damit sie das Bild aktualisiert (und man nur noch EINmal repaint aufruft).

Das Vorgehen dabei schaut so aus:

  1. erzeuge ein Int-Array in der gewünschten Größe der Zeichenfläche. In diesem Array stehen die RGB-Werte für jeden einzelnen Pixel
  2. erzeuge auf die oben beschriebene Weise ein BufferedImage. Das Image kann dann in die GUI eingebunden werden und interessiert eigentlich nicht weiter.
  3. nun kann man jederzeit nach Herzenslust RGB-Werte in das Array schreiben. Wann immer das zugehörige BufferedImage von der GUI neu gezeichnet wird holt es sich die Pixelwerte aus dem Array.

Beispiel:

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Lavalampe extends TimerTask {

    static final int width = 320;
    static final int height = 240;
    static int rgb[];
    static BufferedImage image;

    static JPanel panel;
    static double plasmaphase = 0;


    public static void main(String[] args) {

        // erzeuge array fuer die rgb-werte
        rgb = new int[ width * height ];

        // erzeuge ein BufferedImage welches an das rgb-array gebunden ist
        ColorModel colorModel = DirectColorModel.getRGBdefault();
        SampleModel sampleModel = colorModel.createCompatibleSampleModel(width, height);
        DataBuffer buffer = new DataBufferInt(rgb, width*height);
        WritableRaster raster = Raster.createWritableRaster(sampleModel, buffer, null);
        image = new BufferedImage( colorModel, raster, false, null);

        // erzeuge jframe
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        panel = new JPanel() {
            @Override
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.drawImage(image, 0, 0, null);
            }
        };
        frame.add(panel);
        frame.setVisible(true);
        frame.setSize(width + frame.getInsets().left + frame.getInsets().right,
                height + frame.getInsets().top + frame.getInsets().bottom);

        // erzeuge task der in regelmaessigen Abstaenden in das rgb-array zeichnet
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new Lavalampe(), 0L, 20L);
    }


    @Override
    public void run() {

        // hier wird in das rgb-array gemalt
        for(int y=0; y<height; y++) {
            for(int x=0; x<width; x++) {
                int color = (int) ( (
                    Math.sin(2*Math.PI*x/width * 1.5528 - plasmaphase*0.81 )
                    + Math.sin(2*Math.PI*x/width * 0.82759 + plasmaphase*0.67 )
                    + Math.sin(2*Math.PI*y/height * 0.734 - plasmaphase*0.95 )
                    + Math.sin(2*Math.PI*y/height * 1.2636 + plasmaphase*0.6597 )
                    + 4 ) *43d);
                rgb[y*width + x] = 0xff000000 | 0x800000 | (color<<16) | (color<<8);
            }
        }
        plasmaphase+= 0.01d;

        // nach dem malen ins array noch das image neu zeichnen lassen
        panel.repaint();
    }
}

Zeile 30: hier wird das array in der gewünschten Größe erzeugt
Zeile 33-37: das BufferedImage wird erzeugt
Zeile 42-48: das BufferedImage wird ueber ein JPanel in die GUI eingebunden. Ein Neuzeichnen des Images können wir nun mittels panel.repaint() anfordern.
Zeile 64-74: Im Timertask werden regelmäßig RGB-Werte errechnet und einfach in das Array geschrieben. Mit dem BufferedImage haben wir hier nichts mehr zu tun.
Zeile 78: die GUI wird angewiesen das Panel mit dem BufferedImage neu zu zeichnen. Da sich das BufferedImage die Pixelwerte aus unserem RGB-Array holt erscheint dieses nun auf dem Bildschirm.

PS: Ob dieses gesamte Vorgehensweise (Pixelmalen über das RGB-Array eines BufferedImages) das Prädikat „schöner Code“ verdient sei mal dahingestellt. Aber sie ist simpel und passte glaube ich ganz gut zu deiner Ausgangsfrage (so wie ich sie verstanden hatte).

PPS: Soweit ich weiss geht das nur mit eindimensionalen Arrays. Ich habe mich aber auch nicht tiefer in die Materie eingegraben, da geht bestimmt noch was.

Hey, dein Code ist mir nun verständlich und ich denke ich weiß wie ich ihn anzuwenden habe :wink:
Das Einzige, was ich noch nicht so ganz verstehe ist, wie dieses eindimensionale Array grafisch dargestellt wird. Mal angenommen ich habe ein Array der Länge 50 und einen zu bemalenden Bereich der Größe 5(breite) * 10(höhe).
Wird das Array dann dargestellt als 50 Pixel langer Strich (mal angenommen, überall stehen RGB-Werte für schwarz drin), oder als 5*10 Feld?
Ich kann mir einfach nicht vorstellen, wie ein eindimensionales Array zweidimensional dargestellt werden soll

Lg Sebastian

[QUOTE=Unregistered]
Ich kann mir einfach nicht vorstellen, wie ein eindimensionales Array zweidimensional dargestellt werden soll[/QUOTE]
Es wird als 5*10 Fläche angezeigt. Die Pixel des Bildes stehen im Array einfach alle hintereinander weg, Spalte für Spalte, Zeile für Zeile. Damit das BufferedImage dieses Array auf eine zweidimensionale Fläche abbilden kann muss es natürlich die Bildgröße (in 2D) kennen - aber das ist ja der Fall, die Werte geben wir bei der Erzeugung des SampleBuffers mit an. Bei einer Zeichenfläche der Größe 4x3 sähe das schlußendlich so aus:


rgbArray = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }

resultierendes Bild der Größe 4x3:
  0  1  2  3
  4  5  6  7
  8  9 10 11

Die Umrechnungen von einer x-y-Position in einen Array-Index und umgekehrt sind dementsprechend simpel:
index = y*width + x
x = index%width, y = index/width

Ganz allgemein gibt es da zwei Möglichkeiten: Row-Major und Column-Major. Bei Row-Major wird der 2D-Array mit dem (großen) 1D-Array zeilenweise gefüllt. Jeder 5er-Block des 1D-Arrays wird also eine 5er-Zeile des 2D-Arrays. Und so ist das auch beim Bild. (Bei Column-Major wäre es genau umgekehrt, d.h. 10 Elemente des 1D-Arrays würden eine 10er-Spalte des 2D-Arrays ergeben).

Habe mich jetzt auch mal hier angemeldet, habe dank eurer schnellen Antworten ein gutes Bild dieses Forums :slight_smile:
Danke für die Erklärungen!
Lg Sebastian

Ich hätte auch eine Frage bezüglich des auf den Bildschirm Zeichnens. Ich möchte möglichst schnell (weil Animation) und effizient auf den Bildschirm zeichnen. Die Antworten hier beziehen sich aber nur auf Swing-Components. Gibt es da auch etwas Hardwarenäheres bzw. schnelleres (Denn Swing ist vergleichsweise langsam).

An 0x2A : Der Beitrag hier war noch freizuschalten, ist aber (aus welchen Gründen auch immer - vermutlich weil der Thread so alt ist) etwas untergegangen (normalerweise sollte das Freischalten DEUTLICH schneller gehen :slight_smile: ).

Die Frage ist sehr allgemein. Vielleicht wäre ein eigener Thread dazu besser, wobei auch die Frage gleich etwas konkreter werden könnte (in bezug auf die Ziele und Anforderungen). Ansonsten könnte man hier nur recht allgemeines bla-bla abgeben, JOGL/LWJGL erwähnen, und nebenbei noch sagen, dass Swing oft SEHR schnell ist, und wenn es „langsam“ ist, das oft auf ungeschickte Implementierung zurückzuführen ist).

Sonst gibt es ja noch soetwas wie Buffer Strategy.
Auch wenn Marco da natürlich Recht hat - für ein paar Animatiönschen reicht Swing meistens vollkommen aus.