Weitergeben des graphics-Objektes

Hyo,
ich arbeite gerade an einer GUI die Kanten und Knoten darstellt. Das tut sie auch.
realisiert habe ich das mit einem JPanel, dessen Hauptkomponente

public void paintComponent(Graphics g) {} ist

soweit so gut. Damit ich hybsche Kanten habe, nutze ich Graphics2D, indem ich g nach Graphics2D caste. Mit diesem neuen Objekt setze ich dann Anti-Aliasing. Das geht auch alles wunderbar. Anschließend rufe ich eine Funktion namens
drawAllNodes(Graphics g) auf. Wenn ich dieses graphic objekt mitgebe, werden die Kanten immernoch glatt dargestellt. Jedoch habe ich für drawAllNodes eine Funktion drawNode(Graphics g, Node n) implementiert. Ich wollte dass in drawAllNodes für jeden Knoten einmal drawNode aufgerufen wird. Das Problem ist, dass dann keine Kantenglättung mehr aktiviert ist. Ich caste das übergebene Objekt wieder nach Graphics2D, aber das hat keinen Einfluss darauf.

So gesehen wäre der Ablauf ya der hier:
paintComponent --> ruft drawAllNodes --> ruft in einer For-Schleife drawNode
wenn ich folgendes mache:
paintComponent --> drawNode, wird die kantenglättung wie erwartet angezeigt.
Ich weiß dass es etwas wirr geschrieben ist, aber ich kann den Code hier nicht einfach reinposten. Ich müsste erst viele Anpassungen machen usw.

Fazit: wieso ist das Antialiasing nicht aktiviert wenn ich eine zeichnen-Methode aufrufe, die nicht direkt von paintComponent aus gestartet wird?

räuspert sich

ok, das ganze ist ein Missverständnis, hervorgerufen durch eine Aneinanderreihung mehrere Zufälle.
Das eigentliche Problem war, dass ich jeden Knoten x-mal gezeichnet habe. Dadurch akkumulierte sich die Kantenbreite. Man kann demnach das Graphicsobjekt ohne bedenken weiterreichen und ich bin ein Idiot, weil ich einmal geschlampt habe ^^

Ohne Quelltext kann man da wirklich schwer etwas zu sagen. Vielleicht ist die Reihenfolge verrutscht; zum Beispiel die Hints erst nach dem Aufruf von drawAllNodes gesetzt, oder vor dem Aufruf wieder zurückgesetzt, oder versehentlich eine Kopie des Graphic-Objekts genutzt, oder oder oder …

Alle RenderingHints bleiben auch über Methodengrenzen erhalten, der Fehler muss woanders stecken. Kleines Beispiel, wo das funktioniert:


/* Copyright 2009 Sebastian Haufe

 * Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

package com.ebenius;

import java.awt.*;
import java.awt.geom.Arc2D;
import java.awt.geom.Arc2D.Double;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.*;

/**
 * TODO: Javadoc me!
 * 
 * @version $Revision$ as of $Date$
 * @author Sebastian Haufe
 * @since Playground-3.8
 */
public class AntiAliasingTest extends JPanel {

  private final List<Arc2D.Double> circles = new ArrayList<Arc2D.Double>();
  private final Random rnd = new Random();

  /** Creates a new <code>AntiAliasingTest</code>. */
  public AntiAliasingTest() {
    setPreferredSize(new Dimension(400, 400));
    for (int i = 100; i < 300; i += 10) {
      circles.add(new Arc2D.Double(i, i, 50, 50, 0, 360, Arc2D.OPEN));
    }
  }

  // -------------------------------------------------------------------------
  // Painting
  // -------------------------------------------------------------------------

  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    final Graphics2D g2d = (Graphics2D) g.create();
    try {
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
      paintCircles(g2d);
    } finally {
      g2d.dispose();
    }
  }

  private void paintCircles(Graphics2D g2d) {
    for (Arc2D.Double circle : circles) {
      paintCircle(g2d, circle);
    }
  }

  private void paintCircle(Graphics2D g2d, Double circle) {
    g2d.setColor(new Color(rnd.nextInt(255), rnd.nextInt(255), rnd
          .nextInt(255)));
    g2d.draw(circle);
  }

  // -------------------------------------------------------------------------
  // Program Entry Point
  // -------------------------------------------------------------------------

  /**
   * Test main method.
   * 
   * @param args ignored
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {

      @Override
      public void run() {
        createAndShowGui();
      }
    });
  }

  private static void createAndShowGui() {
    final JPanel contentPane = new JPanel(new BorderLayout(6, 6));
    contentPane.add(new AntiAliasingTest());

    final JFrame f = new JFrame("Test Frame: AntiAliasingTest");
    f.setContentPane(contentPane);
    f.pack();
    f.setResizable(false);
    f.setLocationRelativeTo(null);
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.setVisible(true);
  }
}

Ebenius

Schade, ich war zu langsam. :slight_smile:

Ebenius

hihi, dennoch danke für die Antwort. Ich weiß, dass es dafür sicher schon 10000 Threads gibt, aber das passt hier rein. Und da ich sehe, dass hier einem kompetent geholfen werden kann, hier meine zweite Frage:

welcher Ansatz eignet sich am besten um Grafiken flüssig und cpu-schonend auf den Bildschirm zu zaubern?

Ich nutze ein JPanel als Zeichenblatt. Dieses kann durch Mausziehen in alle 2D-Achsen geschoben werden - demnach muss ich mit einem virtuellen Zeichenblatt arbeiten, was die neuen Koordinaten auf die absoluten Koordinaten übersetzt. Wenn ich jetzt einen Knoten aufs Zeichenblatt zeichne, nutze ich anschließend repaint der eine gewisse Rechteckumgebung um den Knoten herum neuzeichnet. Wenn ich aber das gesamte Zeichenfeld mit der Maus ziehe, muss ich demnach auch das gesamte Zeichenfeld neuzeichnen. Wenn ich das ziemlich oft mache, hört man auch wie mein Laptoplüfter aufdreht. Und es sieht auch ein bisschen abgehakt aus. Aber nur wenn man gezielt danach Ausschau hält. Ergo nutze ich eine Technik aus repaint und repaint mit gezielten Bildschirmausschnitten. Ich weiß nicht ob doubleBuffer genutzt werden. Ich habe in Netbeans einfach das Flag auf true gesetzt. Setze aber nirgends ein flip (was ich von anderen Programmiersprachen so kenne) ab.
Eine nächste Überlegung wäre, die Koordinatenberechnung aus dem JPanel-Objekt (dem Zeichenfeld) in einen Thread zu verlagern. Da passierten aber die seltsamsten Dinge. Nachdem ich mit der Maus die Fläche verschob, hinkte der Thread mit dem Berechnen scheinbar tierisch hinterher. Ich habe aber nicht die Zeit der Sache jetzt auf den Grund zu gehen.
Da einige sicher nicht unähnliche Probleme hatten, wäre ich sehr neugierig auf die anderen Lösungsansätze.

Dazu würde ich in jedem Fall ein JScrollPane nutzen. Das optimiert das Zeichnen bereits, so dass die Verschiebung möglichst flüssig wirkt. Ggf. kannst Du mit dem ScrollMode des im ScrollPane liegenden Viewports herumspielen, um das zu optimieren. Und den zu zeichnenden Ausschnitt kannst Du in paintComponent(…) aus dem ClipRect des Graphics-Objekts auslesen.

Selber bauen würde ich sowas erst dann, wenn ich die Zeichenfläche frei drehen, oder zerren/strecken können müsste.

Ich weiß nicht, ob ich Dich richtig verstehe; ich stelle’s lieber nochmal klar: Der DoubleBuffer ist nur dazu da, den Zeichenvorgang atomar zu machen; das heißt, die gesamte Fläche einer Komponente zu (quasi) einem Zeitpunkt auf dem Bildschirm zu aktualisieren.

Wenn Du die Zeichengeschwindigkeit weiter optimieren willst/musst, dann erweitere die Zeichenfläche um ein Offscreen-Image in der vollen Größe. Leg Dir ein Flag in der Zeichenflächen-Klasse an, das ausdrückt, ob das Offscreen-Image gültig ist oder neu gezeichnet werden muss. Die paintComponent(…) muss den Inhalt ins Offscreen-Image zeichnen, wenn das Flag nicht gesetzt ist und das Flag setzen und anschließend das Offscreen-Image zeichnen. Bei Änderungen des Inhalts und bei invalidate() musst Du das Flag löschen. Dieser Ansatz funktioniert sehr flink, braucht aber ein bisschen Speicher. Wenn Du diesen Ansatz wählst, solltest Du besser den Double-Buffer-Mechanismus für die Zeichenfläche deaktivieren, da Du ja schon einen eigenen Puffer verwendest. Außerdem kann es dann Sinn ergeben, den SIMPLE_SCROLL_MODE auf dem JViewport zu nutzen.

Happy Hacking!
Ebenius

Ich hab mal eben als Beispiel den AntialiasingTest von oben entsprechend umgebaut: ```/* (@)AntiAliasingTest.java */

/* Copyright 2009 Sebastian Haufe

  • Licensed under the Apache License, Version 2.0 (the “License”);
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an “AS IS” BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License. */

package com.ebenius;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Arc2D;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.swing.*;

/**

  • TODO: Javadoc me!
  • @version $Revision$ as of $Date$
  • @author Sebastian Haufe
  • @since Playground-3.8
    */
    public class AntiAliasingTest extends JPanel {

private final Arc2D.Double circle =
new Arc2D.Double(0, 0, 0, 0, 0, 360, Arc2D.OPEN);
private final Random rnd = new Random();
private final Color[] colors =
{ Color.WHITE, Color.LIGHT_GRAY, Color.GRAY, Color.DARK_GRAY,
Color.BLACK, Color.RED, Color.PINK, Color.ORANGE, Color.YELLOW,
Color.GREEN, Color.MAGENTA, Color.CYAN, Color.BLUE, };

private int stepSize = 20;
private int arcRadius = 50;

private BufferedImage offScreenImage = null;
private boolean offScreenImageValid = false;

// -------------------------------------------------------------------------
// Constructors
// -------------------------------------------------------------------------

/** Creates a new AntiAliasingTest. */
public AntiAliasingTest() {
super(false);
final int size = 4000;
setPreferredSize(new Dimension(size, size));
}

// -------------------------------------------------------------------------
// Bean getters and setters
// -------------------------------------------------------------------------

/**

  • Returns the stepSize.
  • @return the stepSize
    */
    public int getStepSize() {
    return stepSize;
    }

/**

  • Sets the stepSize.
  • @param stepSize the stepSize to set
    */
    public void setStepSize(int stepSize) {
    final int old = this.stepSize;
    this.stepSize = stepSize;
    firePropertyChange(“stepSize”, old, stepSize); //$NON-NLS-1$
    }

/**

  • Returns the arcRadius.
  • @return the arcRadius
    */
    public int getArcRadius() {
    return arcRadius;
    }

/**

  • Sets the arcRadius.
  • @param arcRadius the arcRadius to set
    */
    public void setArcRadius(int arcRadius) {
    final int old = this.arcRadius;
    this.arcRadius = arcRadius;
    firePropertyChange(“arcRadius”, old, arcRadius); //$NON-NLS-1$
    }

// -------------------------------------------------------------------------
// Off screen image
// -------------------------------------------------------------------------

@Override
public void invalidate() {
offScreenImageValid = false;
super.invalidate();
}

@Override
public void validate() {
super.validate();
updateOffScreenImageSize();
}

private void updateOffScreenImageSize() {
final int w = getWidth();
final int h = getHeight();
BufferedImage img = offScreenImage;
if (img == null || img.getWidth() != w || img.getHeight() != h) {
offScreenImage =
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
offScreenImageValid = false;
}
}

private void updateOffScreenImageAsNecessary() {
if (!offScreenImageValid) {
updateOffScreenImageSize();
paintComponent2D(offScreenImage.createGraphics(), true);
offScreenImageValid = true;
}
}

// -------------------------------------------------------------------------
// Painting
// -------------------------------------------------------------------------

@Override
protected void paintComponent(Graphics g) {
updateOffScreenImageAsNecessary();
g.drawImage(offScreenImage, 0, 0, null);
}

protected void paintComponent2D(Graphics2D g2d, boolean disposeGraphics) {
try {
super.paintComponent(g2d);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
paintCircles(g2d);
} finally {
if (disposeGraphics) {
g2d.dispose();
}
}
}

private void paintCircles(Graphics2D g2d) {
final int r = arcRadius;
final int dia = r * 2;
final int w = getWidth();
final int h = getHeight();
circle.width = dia;
circle.height = dia;
for (int x = r; x < w - dia - r; x += stepSize) {
for (int y = r; y < h - dia - r; y += stepSize) {
paintCircle(g2d, x, y);
}
}
}

private void paintCircle(Graphics2D g2d, double x, double y) {
g2d.setColor(colors[rnd.nextInt(colors.length)]);
circle.x = x;
circle.y = y;
g2d.draw(circle);
}

// -------------------------------------------------------------------------
// Program Entry Point
// -------------------------------------------------------------------------

/**

  • Test main method.

  • @param args ignored
    */
    public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {

    @Override
    public void run() {
    createAndShowGui();
    }
    });
    }

private static void createAndShowGui() {
final JPanel contentPane = new JPanel(new BorderLayout(6, 6));
final AntiAliasingTest view = new AntiAliasingTest();
view.setPreferredSize(new Dimension(400, 400));

final JScrollPane sp =
      new JScrollPane(view, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
            JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
final JViewport vp = sp.getViewport();
vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
vp.setPreferredSize(new Dimension(400, 400));
contentPane.add(sp);

final JComboBox cb =
      new JComboBox(new String[] { "Size: 400x400", "Size: 800x800",
        "Size: 1600x1600", "Size: 3200x3200", "Size: 6400x6400" });
cb.addActionListener(new ActionListener() {

  @Override
  public void actionPerformed(ActionEvent e) {
    switch (cb.getSelectedIndex()) {
    case 0:
      view.setPreferredSize(new Dimension(400, 400));
      break;
    case 1:
      view.setPreferredSize(new Dimension(800, 800));
      break;
    case 2:
      view.setPreferredSize(new Dimension(1600, 1600));
      break;
    case 3:
      view.setPreferredSize(new Dimension(3200, 3200));
      break;
    case 4:
      view.setPreferredSize(new Dimension(6400, 6400));
      break;
    }
    view.revalidate();
    view.repaint();
  }
});
contentPane.add(cb, BorderLayout.NORTH);

final JFrame f = new JFrame("Test Frame: AntiAliasingTest");
f.setContentPane(contentPane);
f.pack();
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setVisible(true);

}
}```
Ebenius

Je nachdem wie groß die Fläche ist, die gezeichnet/ verschoben wird, lohnt es sich bestimmt auch nur einen Teilbereich der Fläche zu betrachten/ neuzuzeichnen/ verschieben und nicht ein rießiges Image.

Man müsste testen, ob man das Verschieben dahingehend optimieren kann, dass man den Teil der Fläche der im nächsten Bild eh nicht mehr sichtbar ist gar nicht mehr zeichnet, sondern ignoriert.

Hängt sehr vom Anwendungsfall ab. Wenn die Performance nur beim scrollen schlecht ist, dann kann man darauf wahrscheinlich verzichten und hält sich eine Menge Arbeit vom Hals. Wenn’s nötig wird, dann kann man das natürlich machen. Ansonsten gilt:

The main rules of optimization
Rule 1: Don’t do it.
Rule 2: (For experts only) Don’t do it yet

:slight_smile:

Ebenius

muaah, ich bin begeistert. Da waren Antworten auf meine Frage dabei. Ich frug, ja was sich besten macht. Die Offscreen-Technik habe ich schonmal gehört - und für mich hört es sich nicht groß anders an als doublebuffering. Aber das werde ich später mal ausgiebig testen g mit einem anderen Projekt. Und ya, Ebenius hat recht. Ob man nur einen Teil neuzeichnet, hängt stark von der Anwendung ab. Meine Anwendung zeichnet sehr viele Kanten zu den einzelnen Knoten. Da muss ich demnach alles neuzeichnen, da die Kanten sonst verschoben werden könnten und somit außerhalb des neuzuzeichnenden Fenster an ihrem Ort blieben - die Folge wären u.U. abgetrennte Kanten. Ich zeichne aber die Knoten zuerst, und dort kann man diese Technik nutzen. Mache ich ja auch *g
Auf die Idee mit JScrollPane bin ich nicht gekommen. Da mein Viewport zoom- und verschiebbar sein muss, habe ich ihn mir selber programmiert. So viel Aufwand ist das auch nicht. Und ich will mal anmerken, dass die Grafik schon flüssig ist. Ich habe noch ein paar Bugs gefunden - so leuchtet der Knoten z.B. auf wenn man mit der Maus drüber fährt, und zeigt alle Kanten an, die von ihm abgehen (schön mit halbtransparenten, fetten Linien) und das hat gelaggt, wegen fehlerhafter Algorithmierung. Jetzt ist das sehr flott.
Vielen Dank für die kompetente Hilfe hier :open_mouth: - da wird man ja fast versucht sich anzumelden *g

Technisch ist’s kaum was anderes. Es gibt eigentlich nur einen Unterschied: Beim DoubleBuffering wird der Puffer bei jedem Zeichnen neu befüllt und dann gesamtheitlich auf das Gerät kopiert. Bei OffScreen-Images merkt man sich aber die Darstellung als Bild (spart sich also das permanente Neuerstellen der Graphik), muss dafür aber mit dem Mehraufwand leben, entscheiden zu müssen, wann die Graphik aktualisiert werden muss.

Natürlich unterscheiden sich die Techniken im Zweck: DoubleBuffering benutzt man, um Flackern während des Zeichnens zu vermeiden. Man stellt also sicher, dass nicht ein Teil des gezeichneten Inhaltes sichtbar wird, bevor der gesamte Inhalt schon fertig gezeichnet wurde. DoubleBuffering benötigt mehr Zeit als ungepuffertes Zeichnen. OffScreen-Images haben primär die Aufgabe die Zeichengeschwindigkeit zu erhöhen, weil die einmal generierte Darstellung Zeit ihrer Gültigkeit nachgenutzt wird.

Gern. Wir freuen uns über (fast) jeden Neuankömmling; besonders wenn er interessante Sachen programmiert, die notwendigen Grundlagen versteht, sich mit seinen eigenen Themen auseinander setzt und darüber hinaus nicht beratungsresistent ist. :slight_smile: Also: Bis bald.

Grüße, Ebenius

das gehört zwar nicht mehr hier rein, aber ich will am Schluss erklären, warum ich es hier poste.

Folgendes möchte ich erreichen:
Meine Gui besitzt einen Load-Button, beim Drücken desselben soll das typische Explorerfenster geöffnet werden, sodass ich dann zu der Zieldatei navigieren kann. Die Frage ist 1) womit macht man das? JTree? und 2) wird es darauf hinauslaufen, dass ich mir diesen Explorer selbst coden muss? D.h. ich muss immer alle Dateien aus dem Ordner parsen und dann in einer Scrollliste angeben etc.
Es wäre natürlich klasse, wenn Java diese Komponente von sich aus bereithält. Ich weiß aber nicht mit welchen Schlagwörtern ich da suchen soll. Mir fehlen die Oberbegriffe. Und das ist auch der Grund weshalb ich dafür keinen neuen Thread aufmache. Dateien mit Java kann ja wohl jeder öffnen, dafür braucht man keinen eigenen Thread. Und möchte nochmal zur Sicherheit darauf hinweisen, dass ich nicht wissen will, wie ich Dateien auslese. Ich möchte bestenfalls eine Möglichkeit den Pfad als String zurückzuerhalten. Das soll ja auch nutzerfreundlich sein und nicht aus der IT-Steinzeit - wobei ich dazusagen muss, dass ich absolut konsolenvernarrt bin xO

Da gibt 's auf jeden Fall was fertiges. Heißt FileDialog oder so ähnlich. Kannst auch mal nach “file chooser” googeln oder so. Man kann da auch Filter anlegen, so dass nur bestimmte Dateien angezeigt werden oder sogar Verzeichnisse auswählen. Ist ziemlich einfach und cool, hab ich auch irgendwo mal gemacht. Wenn du’s nicht findest kann ich nochmal genau schaun.

http://java.sun.com/docs/books/tutorial/uiswing/components/filechooser.html

hervorragend. Vielen Dank. Ich werde mir das morgen mal reinziehen :smiley:

in der Tat, noch leichter gehts fast gar nicht.

JFileChooser fc = new JFileChooser();
if (fc.showOpenDialog(bLoad) == JFileChooser.APPROVE_OPTION)    // bLoad ist mein Laden-Button
    guiCtrl.loadFile(fc.getSelectedFile().getAbsolutePath());

3 Zeilen Code - natürlich ist das jetzt ohne Schnickschnack