Fehler bei Verwendung von TexturePaint

Marco13

Top Contributor
Hallo

Habe hier gerade einen seltsamen Fehler, und wollte mal fragen ob der woanders auch auftritt:

Ich erstelle ein TexturePaint aus einem BufferedImage mit einem Karomuster. Mit diesem Paint male ich 2 Quadrate.
- Das erste Quadrat wird um 22.5° gedreht gezeichnet. Die Textur wird richtig angezeigt, nämlich auch um 22.5° gedreht.
- Das zweite Quadrat wird um 45° gedreht gezeichnet. Die Textur wird falsch angezeigt, nämlich wieder nur um 22.5° gedreht (statt 45°)

(Wenn man NACH dem ersten Zeichnen eine andere Zeichenoperation ausführt, funktioniert es - siehe mit XXX markierte Zeile - aber das ist nur eine oberflächliche Beobachtung...)

Hier mal der Code:

Java:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

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

public class TexturePaintTest extends JPanel
{
    
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setSize(400,400);
                f.getContentPane().add(new TexturePaintTest());
                f.setVisible(true);
            }
        });
    }
    
    Paint paint = null;
    List<Shape> shapes = null;
    
    TexturePaintTest()
    {
        BufferedImage texture = new BufferedImage(
            10, 10, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = texture.createGraphics();
        g.setColor(Color.GRAY);
        g.fillRect(0,0,10,10);
        g.setColor(Color.BLACK);
        g.drawRect(0,0,10,10);
        g.dispose();
        paint = new TexturePaint(texture, 
            new Rectangle(texture.getWidth(), texture.getHeight()));
        
        shapes = new ArrayList<Shape>();
        shapes.add(new Rectangle2D.Double(100, 100, 50,  50));
        shapes.add(new Rectangle2D.Double(200, 100, 50,  50));
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());
        
        for (int i=0; i<2; i++)
        {
            AffineTransform oldAT = g.getTransform();
            g.transform(AffineTransform.getRotateInstance(
                Math.toRadians(22.5+i*22.5), 200,200));
            g.setPaint(paint);
            g.fill(shapes.get(i));
            g.setTransform(oldAT);
            
            // XXX Works when uncommenting this line:
            //g.fillRect(0, 0, 10, 10);
        }
    }
    
}

Und im Anhang zwei Bilder. Das erste zeigt den Fehler (die Textur im zweiten Quadrat ist nicht mit dem Quadrat ausgerichtet, sondern so wie das erste). Das zweite zeigt das, was eigentlich rauskommen sollte (und was auch rauskommt, wenn man die XXX-Zeile einfügt - aber das ist ja Unfug...)

Getestet auf WinXP 32, GeForce8800 mit ziemlich aktuellen Treibern, JDK 1.7.0_07 und 1.6.0_26
 

Anhänge

  • TexturePaintBug01.png
    TexturePaintBug01.png
    2,8 KB · Aufrufe: 35
  • TexturePaintBug02.png
    TexturePaintBug02.png
    2,4 KB · Aufrufe: 35
Funktioniert bei mir selbst mit der einen Zeile nicht.

Windows 8 Pro 64-Bit
GeForce GTX 260M
JDK 1.7.0_09
 

Anhänge

  • TexturePaintTest.png
    TexturePaintTest.png
    4,9 KB · Aufrufe: 34
Danke für's Testen. Hab' gerade gar keine Idee, woran das liegen könnte. (Ein Test auf einer AMD-Karte wäre mal interessant ... ist so ein Bauchgefühl :reflect: )

Ich hatte es jetzt eigentlich erstmal so "halb" antizipiert, indem ich mit
g.setColor(Color.WHITE);
g.fillRect(0, 0, 1, 1);
einen ein weißen Pixel setze, aber ... das ist ja Murx, und würde deiner Beschreibung nach dann vermutlich auch nicht überall helfen 🙁

In der Bugdatabase habe ich auf die schnelle nichts gefunden ( Bug ID: 4987370 OGL: accelerate Gradient/TexturePaint with complex transform enabled ist wohl in der Nähe davon, aber hat wohl nichts damit zu tun). Mal schauen, ob ich das reporte...:rtfm:
 
Du setzt innerhalb der Scheife die Transformation zurück. Darum erfährt dein TexturePaint auch ständig nur eine Drehung um 22,5°, wenn sich "setPaint()" dessen Kontext holt ("<Paint>.createContext()"). Also entweder beide Male "setTransform()" (getestet) verwenden oder das Zurücksetzen nach ausserhalb der Schleife verlagern (getestet).
Java:
    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
 
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());
        
        AffineTransform oldAT = g.getTransform();
        for (int i=0; i<2; i++)
        {
            //g.setTransform(AffineTransform.getRotateInstance(
            //    Math.toRadians(22.5+i*22.5), 200,200));
            g.transform(AffineTransform.getRotateInstance(
                Math.toRadians(22.5+i*22.5), 200,200));
            g.setPaint(paint);
            g.fill(shapes.get(i));
        }
        g.setTransform(oldAT);
    }
Win7 Ulti 64, JRE1.7u7, GeForce GTX 260
 
Zuletzt bearbeitet von einem Moderator:
Hatte grad' Zeit und mal was rausgesucht:
JAVA: 12.4 Java 2D
Der Satz auf den es ankommt:
Transformationen werden einmal mittels setTransform(), einer Methode der Graphics2D-Klasse gesetzt. Als Alternative gibt es die transform()-Methode, die die vorhandene Transformation des Graphics2D-Objekts mit einer als Parameter übergebenen Transformation verkettet.
Daraus würde ich schliessen, dass die durch "transform()" veränderte Transformation erst nach Verwendung einer der draw- bzw. fill-Methoden gültig wird.
 
Zuletzt bearbeitet von einem Moderator:
Du setzt innerhalb der Scheife die Transformation zurück. Darum erfährt dein TexturePaint auch ständig nur eine Drehung um 22,5°, wenn sich "setPaint()" dessen Kontext holt ("<Paint>.createContext()"). Also entweder beide Male "setTransform()" (getestet) verwenden oder das Zurücksetzen nach ausserhalb der Schleife verlagern (getestet).

Die Schleife fängt damit an, dass ich mir die aktuelle Transformation speichere, und hört damit auf, dass ich die Anfangs gespeicherte wieder herstelle. Dazwischen wird einmal um 22.5° gedreht, und dann um 45°. Es ist also nicht so, dass beim mehrfachen Durchlaufen der Schleife die Drehung in 22.5°-Schritten "auf-akkumuliert" werden soll. Es soll bei jedem Duchlauf eine neue (ggf. "unbekannte") AffineTransform angewendet werden.

Zu dem setTransform verweise ich mal auf die API-Doku zu dieser Methode: Graphics2D (Java Platform SE 6)

Wie du (nur in bezug auf 'transform') auf den Schluß
Daraus würde ich schliessen, dass die durch "transform()" veränderte Transformation erst nach Verwendung einer der draw- bzw. fill-Methoden gültig wird.
kommst, leuchtet mir nicht ganz ein: Wenn man sagt
g.transform(rotation);
g.draw(something);
sollte das, was da gezeichnet wird, gedreht gezeichnet werden. Und diese Drehung sollte sich auch auf ein evtl. vorher oder zwischen diesen Zeilen gesetztes TexturePaint beziehen.

Ich bin mir zu 99.99% sicher, dass der Code, so wie er ist, funktionieren sollte, und es sich dabei um einen Bug handelt. Die Indizien dafür sind
- Die API-Doku, Intuition und mein Verständnis von Zeichnen und Transformationen
- Dass das Verhalten sich (bei mir) durch das 'fillRect' ändert (irgendwas ist also in jedem Fall falsch!)
- Mit einem GradientPaint funktioniert es immer richtig, d.h. der wird wie erwartet gedreht!
 
Bei draw wird ja auch verdreht gezeichnet. Vorher wird allerdings die durch "transform()" gesetzte Transformation mit der vorhandenen verknüpft, das geschieht anscheinend erst innerhalb der draw-Methode.
Bei "setPaint()" aber wird anscheinend nur die vorhandene Methode an "<Paint>.createContext()" übergeben. Kannst diese Methode in deinem Paint ja mal überschreiben und nachsehen, wann sie womit und von wo aufgerufen wird.
Mein Code funktioniert jedenfalls. Kannst das aber gerne trotzdem noch als Bug melden, bin selber gespannt, was da rauskommt.
 
Ja, rein von den Symptomen her deutet alles darauf hin, was du beschrieben hast: Das Paint übernimmt nicht die aktuelle Transformation. (Dabei macht es auch keinen Unterschied, ob man das setPaint VOR oder NACH dem 'transform' macht).

Die Verwendung von 'setTransform' fällt weg - die Methode ist schlicht nicht dafür gedacht. Den Code, den du gepostet hast, hatte ich probiert: Er zeigt (ohne 'setTransform') bei mir das gleiche Verhalten (aber selbst wenn nicht, würde er mit nichts bringen, weil er ja die Rotationen "auf-akkumuliert", d.h. das zweite Quadrat ist nicht um 45° sondern um 67.5° gedreht)

Das mit dem Überschreiben von 'createContext' habe ich mal interessehalber ausprobiert, aber jetzt wird's völlig absurd :autsch: : Wenn man
Java:
        paint = new TexturePaint(texture, 
            new Rectangle(texture.getWidth(), texture.getHeight()))
//*/        
        {
            @Override
            public java.awt.PaintContext createContext(java.awt.image.ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, java.awt.RenderingHints hints) 
            {
                System.out.println("Using xform "+xform);
                return super.createContext(cm, deviceBounds, userBounds, xform, hints);
            }
        }
//*/        
        ;
verwendet, funktioniert es RICHTIG. Wenn man die Methode aber NICHT überschreibt (erstes '/' wegnehmen) dann funktioniert es wieder NICHT :autsch: Irgendwas liegt da IMHO ganz, ganz im Argen :autsch:
 
Moin,

bei mir sieht das ebenfalls so aus wie in den ersten beiden Beiträgen, also nicht korrekt gedreht. Egal ob mit der zusätzlichen Zeile oder nicht. Bei Spacerats Variante wird das zweite Quadrat weiter gedreht, die Textur aber ebenfalls nicht.

Wie sähe den ein korrekter GradientPaint aus? Ein kurzer Versuch mit [c]paint = new GradientPaint(0, 0, Color.cyan, 200, 200, Color.red);[/c] sieht nämlich ebenfalls nicht wirklich richtig aus. (Habe aber noch nie etwas mit diesen Klassen gemacht, der Fehler kann also natürlich auch an mir liegen 😉)

Win7 64, ATI HD5850, JDK 1.7.0_03

Edit: Die letzte Variante von Marco funktioniert hier auch.

Viele Grüße,
Fancy
 
Ähhhh... okay, das gibt einen :bahnhof:
Ist das nur bei der 1.6 so oder auch bei der neuen? Weil bei JDK1.7u7 ist's egal, ob ich die Methode überschreibe oder nicht (bei mir zumindest).
Eigentlich kann doch eine überschriebene Methode die nur "super.method()" aufruft, den Verlauf der überschriebenen Methode gar nicht verändern. Was genau macht "<AffineTransform>.toString()"? Die müsste die Transformation dann ja irgendwie verändern (evtl. "backen").
[EDIT]Okay, in "toString()" passiert nichts, wär' ja auch was gewesen.
Der Fehler tritt wohl nicht mehr auf, wenn man TexturePaint erweitert, aber warum? Dazu genügt im übrigen auch ein [c]new TexturePaint(parameter) {};[/c]
@Marco13: Wenn du das meldest, informier uns bitte, was dabei raus kommt. Danke.[/EDIT]
 
Zuletzt bearbeitet von einem Moderator:
@Fancy: Hab's gerade nochmal probiert - das Problem tritt AUCH bei einem GradientPaint auf (ich dachte ursprünglich, dass nicht, aber das war ein Irrtum - dabei hatte ich verschiedene GradientPaints verwendet)

@Spacerat: Ja, ich hatte es auch ohne das System.out in der überschriebenen Methode versucht (aber noch nicht OHNE überschriebene Methode) ... das riecht verdächtig danach, als ob irgendwo ein [c]if (paint.getClass() == TexturePaint.class)[/c] vorkäme...

EDIT-> Ach ja, ich hatte es auch auf 1.7.0_07 getestet, aber nicht in jedem Fall alle möglichen Workarounds (fillRect, überschreiben der Methode etc) durchprobiert <-EDIT


Nochmal mit GradientPaint, es geht damit auch nicht, also nichts neues dabei, nur der Vollständigkeit halber:
Java:
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
 
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
 
public class TexturePaintTest2 extends JPanel
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setSize(400,400);
                f.getContentPane().add(new TexturePaintTest2());
                f.setVisible(true);
            }
        });
    }
    
    Paint texturePaint = null;
    Paint gradientPaint = null;
    List<Shape> texturedShapes = null;
    List<Shape> gradientShapes = null;
    
    TexturePaintTest2()
    {
        BufferedImage texture = new BufferedImage(
            10, 10, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = texture.createGraphics();
        g.setColor(Color.GRAY);
        g.fillRect(0,0,10,10);
        g.setColor(Color.BLACK);
        g.drawRect(0,0,10,10);
        g.dispose();
 
        texturePaint = new TexturePaint(texture, 
            new Rectangle(texture.getWidth(), texture.getHeight()));

        texturedShapes = new ArrayList<Shape>();
        texturedShapes.add(new Rectangle2D.Double(100, 100, 50,  50));
        texturedShapes.add(new Rectangle2D.Double(200, 100, 50,  50));

        gradientPaint = new GradientPaint(
            150, 100, Color.RED, 150, 150, Color.GREEN);
        gradientShapes = new ArrayList<Shape>();
        gradientShapes.add(new Rectangle2D.Double(150, 100, 50,  50));
        gradientShapes.add(new Rectangle2D.Double(250, 100, 50,  50));
    }
 
    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
 
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());
        
        for (int i=0; i<2; i++)
        {
            AffineTransform oldAT = g.getTransform();
            g.transform(AffineTransform.getRotateInstance(
                Math.toRadians(22.5+i*22.5), 200,200));
            g.setPaint(texturePaint);
            g.fill(texturedShapes.get(i));
            g.setTransform(oldAT);
        }
        
        for (int i=0; i<2; i++)
        {
            AffineTransform oldAT = g.getTransform();
            g.transform(AffineTransform.getRotateInstance(
                Math.toRadians(22.5+i*22.5), 200,200));
            g.setPaint(gradientPaint);
            g.fill(gradientShapes.get(i));
            g.setTransform(oldAT);
        }
        
    }
    
}
 
... das riecht verdächtig danach, als ob irgendwo ein [c]if (paint.getClass() == TexturePaint.class)[/c] vorkäme...
Gib's zu, du hast gelinzt. :lol:
In der Tat kommt so etwas direkt in "<SunGraphics2D>.setPaint()".
Java:
        Class paintClass = paint.getClass();
        if (paintClass == GradientPaint.class) {
            paintState = PAINT_GRADIENT;
        } else if (paintClass == LinearGradientPaint.class) {
            paintState = PAINT_LIN_GRADIENT;
        } else if (paintClass == RadialGradientPaint.class) {
            paintState = PAINT_RAD_GRADIENT;
        } else if (paintClass == TexturePaint.class) {
            paintState = PAINT_TEXTURE;
        } else {
            paintState = PAINT_CUSTOM;
        }
Mir ist das schon vorher aufgefallen, aber irgendwie passierte hier noch etwas viel peinlicheres, was ich mir nur damit erklären kann, dass meine Klassen nicht mit den Quelltexten übereinstimmen, deswegen hab' ich es nicht gepostet. Darfst aber gerne Raten, welchen Wert "paintState" bei mir lt. Debugger annimmt.
PAINT_RAD_GRADIENT... und dabei ist's egal, ob TexturePaint erweitert wurde oder nicht. ???:L PAINT_CUSTOM hätte ich ja noch verstanden.
 
Zuletzt bearbeitet von einem Moderator:
@Fancy: Danke, heisser Tipp! Das dürfte den Sun-Leuten sicher helfen, werde das in den Bug-Report aufnehmen!

@Spacerat: Wie hast du das überprüft?
 
@Spacerat: Wie hast du das überprüft?
Man kann sich doch die Quellen bei OpenJDK laden. Wenn man in Eclipse dann die JRE des JDKs verwendet, lassen sich auch rt-Klassen (und auch andere) debuggen und man kann dort auch Haltepunkte setzen. Leider kann es passieren, dass die Zeilennummern des Quelltextes nicht mit den Debuginformationen der aktuellen JDK-JRE-Klasse übereinstimmen. Das Debugging klappt dann zwar immer noch, aber die Haltepunkte sind dann halt an falscher Stelle. Nun sieht man zwar die aktuellen Werte nicht wirklich, aber wenn man sieht was beim Haltepunkt zugewiesen wird, kann man's nachvollziehen.
[EDIT]...allerdings sollte man sich auch vergewissern, dass die Zeilenzahlen stimmen. Bei mir stimmen sie offensichtlich nicht, alles andere hätte mich doch sehr gewundert. 😳
Wenn man's genauer unter die Lupe nimmt, werden wahrhaftig, PAINT_TEXTURE oder PAINT_CUSTOM zugewiesen.[/EDIT]
 
Zuletzt bearbeitet von einem Moderator:
Hmja, dann bräuchte man noch einen genaueren Überblick darüber, wo die Unterschiede zwischen dem normalen und dem OpenJDK sind...
 

Neue Themen


Zurück
Oben