# BufferedImage -> Farbe wechselt willkürlich



## Gromit (11. Jan 2011)

Hallo,

wenn ich auf ein BufferedImage zeichnen will, wechselt die Farbe willkürlich und ich habe keine Ahnung, warum. Meine Methode:


```
public void paint(Graphics g) {
        if (screenImage == null) {
            screenImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
            screenGraphics = screenImage.getGraphics();
            screenGraphics.setFont(font);
        }

        if (hasTextChanged) {
            for (int i = 0, x = 0; i < chars.length; i++, x++) {
                if (x >= columns)
                    x = 0;

                int y = i / columns;

                screenGraphics.setColor(Color.YELLOW);
                screenGraphics.fillRect(x * CHAR_WIDTH, y * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT);

                screenGraphics.setColor(Color.RED); // Testweise
                screenGraphics.setColor(Color.GREEN);
                screenGraphics.drawString("A", x * CHAR_WIDTH, (y * CHAR_HEIGHT) + 10);
            }

            hasTextChanged = false;
        }

        g.drawImage(screenImage, 0, 0, null);
    }
```

Ergebnis ist, dass die Farbe des Hintergrundes (bzw. der einzelnen Blöcke, aus denen der Hintergrund besteht) und die Farben der einzelnen Buchstaben (drawString) zwischen Gelb, Rot und Grün wechseln. Da die Methode von einem Thread einmal pro Sekunde aufgerufen wird, wechseln unterschiedliche Buchstaben und Hintergrundblöcke jeweils einmal pro Sekunde ihre Farbe.

Nur woran liegt das?


----------



## Marco13 (11. Jan 2011)

Da arbeiten mehrere Threads, und painten "gleichzeitig" in ein Graphics von EINEM BufferedImage? Joa, das haut ihn raus, das ist klar. Wenn das unbedingt von mehreren Threads gemacht werden MUSS, könnte schon ein

```
snychronized (screenImage)
{
     for (int i = 0, x = 0; i < chars.length; i++, x++) {
      ...
}
```
helfen...


----------



## Gromit (11. Jan 2011)

Na eigentlich übergibt der Thread der Methode _paint_ nur das eigene Graphics g, auf dann das screenImage gezeichnet wird.

Auf das screenImage hat sonst nur die Methode _paint(Graphics g)_ Zugriff (und nur die zeichnet darauf). Insofern kann ich mir auch nicht vorstellen, dass es daran liegt. :-?

Edit: Beispiel angehangen

Edit 2: Rechtschreibung


----------



## Marco13 (11. Jan 2011)

Hast du es ausprobiert? Das Problem ist vermutlich, dass mehrere Threads die Methode gleichzeitig ausführen, und alle dasSELBE screenImage verwenden. Der erste Thread macht
screenGraphics.setColor(Color.YELLOW);
und bevor er 
screenGraphics.fillRect(x * CHAR_WIDTH, y * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT);
machen kann, pfuscht ihm ein anderer Thread mit seinem
screenGraphics.setColor(Color.RED); // Testweise
dazwischen.


----------



## Gromit (11. Jan 2011)

Es gibt ja nur einen Thread, der die paint-Methode aufruft.

Edit: Aber ja, du hast recht. Mit synchronized geht es. Kann man das nicht eleganter lösen? Und warum greift dieser eine Thread mehrmals parallel auf die Methode zu (obwohl der Aufruf nur einmal pro Sekunde passiert)?


----------



## Marco13 (11. Jan 2011)

Häm? ???:L Wie komm' ich eigentlich drauf, dass es mehrere Threads sind... ? (Hattest du den ersten Beitrag editiert oder so? ???:L ). Mit EINEM Thread sollte das eigentlich gehen. Es kann zwar theoretisch ein "inkonsistenter" (also halb-fertig gezeichneter) Zustand auf dem Bildschirm dargestellt werden, aber falsche Farben sollte es nicht geben. Kann man da ein KSKB basteln?


----------



## Gromit (11. Jan 2011)

KSKB?

Edit: Nee, hab von Anfang an nur von einem Thread gesprochen.


----------



## Gromit (11. Jan 2011)

Seltsam ist, dass es funktioniert, wenn ich screenImage erst beim Aufruf von paint erstelle:


```
public void paint(Graphics g) {
        BufferedImage screenImage;
        Graphics screenGraphics;
        screenImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
        screenGraphics = screenImage.getGraphics();
        screenGraphics.setFont(font);

        if (hasTextChanged) {

           ...
```


----------



## Marco13 (12. Jan 2011)

Dort werden ja jetzt lokale Variablen angelegt (und immer neue Bilder - nicht gut). Eigentlich KANN das nur ein Threading-Problem sein ???:L
*K*leines
*S*elbstständig
*K*ompilierbares
*B*eispiel...?


----------



## Empire Phoenix (12. Jan 2011)

Nur so zur frage, du rufst die paint aber nicht selber auf oder? Das sollte man dem Swing krams überlassen(ausser in Sonderfällen)


----------



## Gromit (12. Jan 2011)

Der Thread hat eine run-Methode:


```
public void run() {
        running = true;

        while (running) {
            update();
            render(); // <--
            paintScreen();

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                System.err.println(e);
            }
        }

        System.exit(0);
    }
```

Die Methode render() ruft die Methode paint des ColorConsole-Objektes auf und übergibt das Graphics, damit ColorConsole darauf zeichnen kann:


```
// render-Methode des Threads
    private void render() {
        doubleBufferedGraphics.setColor(Color.BLACK);
        doubleBufferedGraphics.fillRect(0, 0, width, height);

        console.paint(doubleBufferedGraphics);    // übergebe Graphics, damit ColorConsole darauf zeichnen kann
    }
```

In der paint-Methode wird dann erst auf das screenImage gezeichnet und das screenImage anschließend via 

```
g.drawImage(screenImage, 0, 0, null) // g: doubleBufferedGraphics
```
aufs doubleBufferedGraphics. Das ist auch schon alles.

Der Name "paint" für die Methode ist vielleicht etwas irreleitend. ColorConsole ist keine Swing- oder AWT-Komponente. Der Name "render" wäre vielleicht besser.


----------



## EgonOlsen (12. Jan 2011)

Du hast vermutlich deinen eigenen Thread und den normalen AWT Event Dispatch Thread, der das Zeichnen von Swing/AWT üblicherweise macht. Wenn du das Zeichnen unbedingt über deinen eigenen Thread machen willst, mach es über deine eigene Methode und hebel das Swing-Zeichnen aus, indem du update und paint mit leeren Implementierungen überschreibst.


----------



## Gromit (12. Jan 2011)

Anbei mal ein funktionierendes Minimalbeispiel.


 io.ColorConsole: zeichnet den Text
 lang.ColoredCharacter: eigener Datentyp; speichert char, Vorder- und Hintergrundfarbe
 main.Main: erstellt Swing-Fenster
 swing.ColorConsolePanel: der Thread; abgeleitet von JPanel, Runnable


----------



## Marco13 (12. Jan 2011)

```
public void paint(Graphics g) {
        System.out.println("Start on "+Thread.currentThread());
        if (hasTextChanged) {
...
        }
        System.out.println("End on "+Thread.currentThread());
        g.drawImage(screenImage, 0, 0, null);
    }
```

---> :idea:

Du rufst in der main "run()" auf, UND startest einen zweiten Thread, der auch "run()" ausführt. Nur der zweite Thread sollte run() ausführen. Und er sollte definitiv NICHT in addNotify gestartet werden. Im Konstruktor, oder sonstwo, aber NICHT diese Methode überschreiben!

Also: Das run() aus der main rausnehmen, dann funzts...


----------



## Gromit (12. Jan 2011)

Marco13 hat gesagt.:


> ---> :idea:
> 
> Du rufst in der main "run()" auf, UND startest einen zweiten Thread, der auch "run()" ausführt. Nur der zweite Thread sollte run() ausführen. Und er sollte definitiv NICHT in addNotify gestartet werden. Im Konstruktor, oder sonstwo, aber NICHT diese Methode überschreiben!
> 
> Also: Das run() aus der main rausnehmen, dann funzts...


Uff, das hätte ich im Leben nicht gefunden. Vielen Dank erst einmal!! Hatte das run() im Main anfangs mal testweise drin und dann ganz vergessen.

Was spricht gegen addNotify? Und im Konstruktur gibt es eine NullPointerException.


----------



## Marco13 (12. Jan 2011)

Man sollte nicht willkürlich irgendeine Methode überschreiben und sich da einklinken (von solchen Sachen dass mehrere Threads gestartet werden, wenn man das ding merhfach irgendwo entfernt und hinzufügt mal ganz abgesehen). Wegen NPE...notfalls pragmatisch

```
private void render() {
        if (doubleBufferedImage == null) {
            doubleBufferedImage = createImage(width, height);
            if (doubleBufferedImage != null)
            {
                doubleBufferedGraphics = doubleBufferedImage.getGraphics();
            }
        }
        if (doubleBufferedGraphics != null)
        {
            doubleBufferedGraphics.setColor(Color.BLACK);
            doubleBufferedGraphics.fillRect(0, 0, width, height);

            console.paint(doubleBufferedGraphics);
        }
    }
```

Ich seh grad, da steht ja DOCH ein getGraphics drin... das sollte man nicht verwenden. Was soll paintScreen eigentlich machen? Mach' das mal raus, ein "repaint()" aufruf stattdessen sollte es schon tun...


----------

