Java Swing Bezier Curves


B

Bgag

Guten Abend!

Ich programmiere schon seit längerem im Rahmen meines Studiums in Java, habe jedoch bisher nur Konsolen-Anwendungen geschrieben, Nun soll ich im Rahmen eines Praktikums eine kleine Desktop-Anwendung mit Swing realisieren, die verschiedene Algorithmen aus der Numerik veranschaulicht. Beginnen wollte ich mit dem Zeichnen von Bezier-Kurven.

Dabei stellen sich mir nun ein Haufen Probleme, die ich nicht zu lösen weiß. Ich arbeite mit Netbeans und habe mir eine normale Desktop-Anwendung erstellt. Diese bietet Standard gemäß ein Menü, einen Content-Bereich und eine Statusbar.

Nun habe ich einen weiteren Menüpunkt hinzugefügt, der einfach Numerics heißt und einen ersten Untermenüpunkt "Bezier Curves" enthält. Wenn dieser Unterpunkt angeklickt wird, soll es möglich sein, im Arbeitsbereich mit der linken Maustaste zu klicken und so einen quadratischen Punkt zu erstellen, der dann verschoben werden kann. Diese Punkte sollen verschiebbar sein und durch einen Rechtsklick auf einen Punkt wieder entfernt werden können.

Wurden nun mehr als nur ein Punkt gesetzt, soll eine Methode punktweise eine Funktion zeichnen. Ich möchte also einzelne Pixel setzen können. Diese Kurve soll zudem jedes mal, wenn ein Punkt entfernt, verschoben oder hinzugefügt wird, aktualisiert werden, es soll also neu gezeichnet werden. Da diese Punkte frei positionierbar sind, ist es wichtig sie nach dem Zeitpunkt ihrer Erzeugung geordnet abzuspeichern.

Wie kann ich so etwas realisieren? Was brauche ich dafür? Wie organisiert man solche UserInterfaces? Welchen Typ sollte ein solches Interface haben (JFrame, JPanel, ...)?

Ein kleines Beispiel, ein gutes Tutorial und ein paar kleine Ratschläge reichen mir schon. Über mehr freue ich mich natürlich auch.

Liebe Grüße,

Andreas
 

FrankBooth

Erfahrenes Mitglied
Hallo,

also ich bin der Meinung das sind ein paar Fragen zu viel. Du solltest dir überlegen, wie dein Programmablauf aussehen soll. Wo soll was genau passieren. Ich geh mal davon aus, das du bisher einen JFrame mit einer JMenuBar hast. Aber ganau da geht das Problem ja los. Ich kann es alles nur vermuten. Poste deinen Code und man kann an kleinen Beispielen Tipps geben. Du wist aber auf jeden Fall für deinen Content-Bereich ein JPanel brauchen :)

Grüße
 

takidoso

Erfahrenes Mitglied
Hi Avedo,
Du möchtest also auf einem Fenster quasi zeichnen (ich weiß Du hast da natürlich keine Freihandzeichnung im Kopf).
Prinzipiell, solltest Du Dich mit dem Thema MouseListener und MouseMoveListener beschäftigen, sofern Du nicht, falls Du die von Twagi vorgeschlagenen Bibliotheken verwenden kannst, in dieser Level zu arbeiten hast.

Ich würde JPanel für die Zeichnung verwenden, dann hast du die Möglichkeit es z.B. in Deinem Hauptframe aber auch unabhängig von Deinem Hauptframe einzubetten.

viel Spaß

Takidoso
 
B

Bgag

Danke für eure Mühen.

Bisher sieht mein Panel wie folgt aus.

Java:
/*
 * BezierPanel.java
 *
 * Created on 16.07.2010, 03:41:28
 */

package fancynumerics;

import java.util.*;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;
/**
 *
 * @author awilhelm
 */
public class BezierPanel extends JPanel implements MouseListener  {

    /** Creates new form BezierPanel */
    public BezierPanel() {
        initComponents();
    }

    /**
     * This method will be called when
     * the mouse has been clicked.
     *
     * @param me
     */
    public void mouseClicked(MouseEvent me) {
        // Get the current position of the mouse, ...
        Point mouse = me.getPoint();

        // ... check if there is a point, ...
        if( points.contains(mouse) ) {
            // ... and remove it if so.
            points.remove(mouse);
        }
    }

    public void mouseEntered(MouseEvent me) {
    }

    public void mousePressed(MouseEvent me) {
    }

    public void mouseReleased(MouseEvent me) {
    }

    public void mouseExited(MouseEvent me) {
    }

    /**
     * This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")                  
    private void initComponents() {

        setName("Form"); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }
   
    private ArrayList<Point> points;
}
Wichtig wäre nun bei einem Klick mit der linken Maustaste prüfen zu können, ob an dieser Stelle bereits ein Punkt existiert oder nicht. Falls nein, soll dieser erstellt werden, falls ja soll dieser gelöscht werden. Leider habe ich bisher keine Idee, wie ich die Punkte dem Einfügezeitpunkt nach geordnet abspeichern kann, noch wie ich zwischen der linken, rechten und mittleren Maustaste unterscheiden kann.

Liebe Grüße,

Andreas
 
Zuletzt bearbeitet von einem Moderator:

FrankBooth

Erfahrenes Mitglied
Hallo,

wenn ich mich recht entsinne ist die rechte Maustaste durch:

Java:
public void mouseReleased(MouseEvent e) {
       if (e.isPopupTrigger()) {
       }
}
zu identifizieren.

Ich würde mir die Punkte in einer verketteten List speichern.
 
Zuletzt bearbeitet:
B

Bgag

Danke für deine Hilfe. Habe es nun wie folgt abgeändert.

Java:
/*
 * BezierPanel.java
 *
 * Created on 16.07.2010, 03:41:28
 */

package fancynumerics;

import java.util.*;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;

/**
 *
 * @author awilhelm
 */
public class BezierPanel extends JPanel implements MouseListener  {

    /** Creates new form BezierPanel */
    public BezierPanel() {
        initComponents();
        addMouseListener(this);
    }
    
    @Override
    public void paint(Graphics g) {
        // Define a color for the different points ...
        g.setColor(Color.red);

        // .. and then draw them.
        for (Point p : points) {
            // Therefore draw a little rectangle.
            g.fillRect((int) p.getX(), (int) p.getY(), 2, 2);
        }
    }

    /**
     * This method will be called when
     * the mouse has been clicked.
     *
     * @param me
     */
    public void mouseClicked(MouseEvent me) {
        // If the left button was clicked, ...
        if( !me.isPopupTrigger() ) {
            // ... get the current position of the mouse, ...
            Point mouse = me.getPoint();

            // ... check if there is a point, ...
            if( !points.contains(mouse) ) {
                // ... and add one if not.
                points.add(new Point(me.getX(), me.getY()));
            }
        }

        // If the right button was clicked, ...
        else if( me.isPopupTrigger() ) {
            // ... get the current position of the mouse, ...
            Point mouse = me.getPoint();

            // ... check if there is a point, ...
            if( points.contains(mouse) ) {
                // ... and remove it if so.
                points.remove(mouse);
            }
        }

        repaint();
    }

    public void mouseEntered(MouseEvent me) {
    }

    public void mousePressed(MouseEvent me) {
    }

    public void mouseReleased(MouseEvent me) {
    }

    public void mouseExited(MouseEvent me) {
    }

    /**
     * This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked") 
    private void initComponents() {

        setName("Form"); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }

    private ArrayList<Point> points;
}
Sollte meiner Meinung nach so funktionieren. Es kompiliert auch, es geschieht aber nichts, wenn ich in das Panel wechsle und hinein klicke. In der Hauptapplikation wird das Panel wie folgt eingefügt.

Java:
    private void bezierMenuItemActionPerformed(java.awt.event.ActionEvent evt) {

            JFrame mainFrame = FancyNumericsApp.getApplication().getMainFrame();

            mainFrame.getContentPane().removeAll();
            mainPanel = new BezierPanel();
            mainFrame.getContentPane().add(mainPanel);
            mainFrame.validate();
            mainFrame.setVisible(true);

    }
Habe es so probiert, aber es funktioniert nicht.
Liebe Grüße,

Andreas
 
Zuletzt bearbeitet von einem Moderator:
B

Bgag

Hallo zusammen!

Entschuldigt das doppelte Posting, ich bin nun jedoch schon ein gutes Stück weiter, habe aber noch ein paar kleine Probleme.

Es ist mir nun möglich Punkte zu setzen und zu löschen. Leider gelingt mir das Verschieben noch nicht. Woran kann das liegen? Die Methoden mouseDragged(), mouseExited() und mouseReleased(), sowie mousePressed() wurden zu diesem Zweck extra überladen.

Außerdem erhalte ich auch schon eine Ausgabe der Kurve, die jedoch immer in (0,0) anstatt im ersten Punkt beginnt. Der Fehler, der dies verursacht, muss im Abschnitt von Zeile 63 bis 85 liegen. Ich hoffe es kennt sich jemand mit dem mathematischen Hintergrund von Bezier-Kurven aus.

EDIT: Kurve wird nun korrekt ausgegeben!

Zum Testen des nachstehenden Quellcodes kann die nachfolgende TestFrame Klasse verwendet werden.

Java:
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package fancynumerics;

import javax.swing.JFrame;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;

/**
 *
 * @author awilhelm
 */
public class TestFrame {
    public static void main(String[] args) {
        JFrame frame = new JFrame(); // JFrame erzeugen
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Beim Schließen wird das Programm beendet
        frame.setTitle("Funktionsgraphen zeichnen"); // Fenstertitel
        frame.setSize(400, 400); // Größe festlegen

        frame.setLayout(new BorderLayout()); // LayoutManager festlegen; dieser kümmert sich um die Anordnung des Inhalts

        Container contPane = frame.getContentPane(); // In diesen Container wird der Inhalt eingefügt

        BezierPanel bezier = new BezierPanel(); // FunctionPanel erzeugen
        bezier.setPreferredSize(new Dimension(400,400)); // Standardgröße setzen
        contPane.add(bezier, BorderLayout.CENTER); // functionPanel hinzufügen

        frame.pack(); // Fenster auf die richtige Größe skalieren
        frame.setVisible(true); // sichtbar machen
    }
}
Ich hoffe ihr könnt mir helfen.

Liebe Grüße,

Andreas

Java:
/*
 * BezierPanel.java
 *
 * Created on 16.07.2010, 03:41:28
 */
package fancynumerics;

import java.util.*;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;

import java.awt.Graphics;
import java.awt.Color;

/**
 *
 * @author awilhelm
 */
public class BezierPanel extends JPanel implements MouseListener, MouseMotionListener {

    /** Creates new form BezierPanel */
    public BezierPanel() {
        initComponents();
        addMouseListener(this);
        setVisible(true);
    }

    @Override
    public void paintComponent(Graphics g) {
        // Call the parent paint method.
        super.paintComponent(g);

        // Get the first point in the list ...
        Point lastOne = points.isEmpty() ? null : points.get(0);

        // .. and then draw the points.
        for (Point p : points) {
            // Therefore define a color for the lines ...
            g.setColor(Color.lightGray);

            // ... and add a line from last to the current point if possible.
            if (!lastOne.equals(p)) {
                g.drawLine(
                        (int) lastOne.getX(),
                        (int) lastOne.getY(),
                        (int) p.getX(),
                        (int) p.getY());
            }

            // Change to point color, ...
            g.setColor(Color.darkGray);

            // ... draw a little rectangle for each point ...
            g.fillRect((int) p.getX() - 2, (int) p.getY() - 2, 4, 4);

            // ... and set current to last point.
            lastOne = p;
        }

        // Finally set the curve color, ...
        g.setColor(avedoBlue);

        // ... create a var to hold last point in curve ...
        Point last = points.isEmpty() ? null : points.get(0);

        // ... and draw the bezier curve.
        for (float t = 0.0f; t <= 1.0f; t += 0.03f) {
            // Therefore get the first point in the list, ...
            Point cur = new Point(0,0);

            // ... loop over all points in list ...
            for (int i = 0; i < points.size(); i++) {
                Point p = points.get(i);
                cur.x += p.x * bernstein(points.size()-1, points.indexOf(p), t);
                cur.y += p.y * bernstein(points.size()-1, points.indexOf(p), t);
            }

            if (cur != null) {
                g.drawLine(last.x, last.y, cur.x, cur.y);
            }

            last = cur;
        }
    }

    /**
     * This method will be called when
     * the mouse has been clicked.
     *
     * @param me
     */
    @Override
    public void mouseClicked(MouseEvent me) {
        Point forced = null;

        // If the left button was clicked, ...
        if (me.getButton() == MouseEvent.BUTTON1) {
            // ... get the current position of the mouse, ...
            Point mouse = me.getPoint();

            // ... check if there is a point, ...
            if (hasPoint(mouse) == null) {
                // ... and add one if not.
                points.add(mouse);
            }

            System.out.print("Left button clicked ");
        }

        // If the right button was clicked, ...
        else if (me.getButton() == MouseEvent.BUTTON3) {
            // ... get the current position of the mouse, ...
            Point mouse = me.getPoint();

            // ... check if there is a point, ...
            if ((forced = hasPoint(mouse)) != null) {
                // ... and remove it if so.
                points.remove(forced);
            }

            System.out.print("Right button clicked ");
        }

        System.out.println("at (" + me.getX() + ", " + me.getY() + ")!");

        repaint();
    }

    @Override
    public void mouseEntered(MouseEvent me) {
    }

    @Override
    public void mousePressed(MouseEvent me) {
        Point p = null;
        if ( (p = hasPoint(me.getPoint())) != null ) {
            _inDrag = true;
            _dragFrom = p;
            System.out.println("In drag!");
        } else {
            _inDrag = false;
        }
    }

    @Override
    public void mouseReleased(MouseEvent me) {
        _inDrag = false;
    }

    @Override
    public void mouseExited(MouseEvent me) {
        _inDrag = false;
    }

    @Override
    public void mouseDragged(MouseEvent me) {
        if (_inDrag) {
            // Get the new position.
            Point p = me.getPoint();
            System.out.println("dragging!");

            // Get the forced point ...
            Point cur = points.get(points.indexOf(_dragFrom));

            // ... and move it.
            cur.move(me.getX() - _dragFrom.x, me.getY() - _dragFrom.y);

            // Repaint because position changed.
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent me) {
    }

    private Point hasPoint(Point forced) {
        for (Point p : points) {
            if (forced.x >= p.x - 2
                    && forced.x <= p.x + 2
                    && forced.y >= p.y - 2
                    && forced.y <= p.y + 2) {
                return p;
            }
        }

        return null;
    }

    private double bernstein(int n, int k, float t) {
        return choose(n, k) * Math.pow(1 - t, n - k) * Math.pow(t, k);
    }

    private long choose(int n, int k) {
        return fact(n) / (fact(k) * fact(n - k));
    }

    private long fact(int n) {
        if( n > 1 ) {
            return fact(n-1) * n;
        }

        return 1;
    }

    /**
     * This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")    
    private void initComponents() {

        setName("Form"); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }

    private boolean _inDrag;
    private Point _dragFrom;
    private Color avedoBlue = new Color(159, 191, 254);
    private ArrayList<Point> points = new ArrayList<Point>();
}
 
Zuletzt bearbeitet von einem Moderator:
B

Bgag

Guten Morgen!

Entschuldigt nun das dritte Posting in Folge. Ich habe nun alles zum Laufen bekommen. Danke an dieser Stelle noch einmal für eure Hilfe. Die Interessierten unter euch und Leute die ähnliche Probleme haben, finden im Anhang die Lösung, die mit der oben geposteten Umgebung getestet werden kann.

Alternativ findet man im Anhang auch ein *.jar File. Wählt man nach dem Ausführen "Numerics -> Bezier", kann man irgendwo mit der linken Maustaste in das Panel klicken, um einen neuen Punkt zu erstellen. Klickt man einen Punkt mit der rechten Maustaste an, wird dieser gelöscht. Punkte können zudem mit der linken Maustaste verschoben werden.

Liebe Grüße,

Andreas

Java:
/*
 * BezierPanel.java
 *
 * Created on 16.07.2010, 03:41:28
 */
package fancynumerics;

import java.util.*;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;

import java.awt.Graphics;
import java.awt.Color;

/**
 *
 * @author awilhelm
 */
public class BezierPanel extends JPanel implements MouseListener, MouseMotionListener {

    /** Creates new form BezierPanel */
    public BezierPanel() {
        initComponents();
        addMouseListener(this);
        addMouseMotionListener(this);
        setVisible(true);
    }

    @Override
    public void paintComponent(Graphics g) {
        // Call the parent paint method.
        super.paintComponent(g);

        // Get a 2d graphics object.
        Graphics2D g2d = (Graphics2D) g;

        // Get the first point in the list ...
        Point lastOne = points.isEmpty() ? null : points.get(0);

        // .. and then draw the points.
        for (Point p : points) {
            // Therefore define a color for the lines ...
            g.setColor(Color.lightGray);

            // ... and add a line from last to the current point if possible.
            if (!lastOne.equals(p)) {
                Stroke drawingStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{9}, 0);
                Line2D line = new Line2D.Double(lastOne.x, lastOne.y, p.x, p.y);
                g2d.setStroke(drawingStroke);
                g2d.draw(line);
                g2d.setStroke(new BasicStroke());
            }

            // Change to point color, ...
            g.setColor(Color.darkGray);

            // ... draw a little rectangle for each point ...
            g.fillRect((int) p.getX() - 2, (int) p.getY() - 2, 4, 4);

            // ... and set current to last point.
            lastOne = p;
        }

        // Finally set the curve color, ...
        g.setColor(avedoBlue);

        // ... create a var to hold last point in curve ...
        Point last = points.isEmpty() ? null : points.get(0);

        // ... and draw the bezier curve.
        for (float t = 0.0f; t <= 1.0f; t += 0.03f) {
            // Therefore get the first point in the list, ...
            Point cur = new Point(0,0);

            // ... loop over all points in list ...
            for (int i = 0; i < points.size(); i++) {
                Point p = points.get(i);
                cur.x += p.x * bernstein(points.size()-1, points.indexOf(p), t);
                cur.y += p.y * bernstein(points.size()-1, points.indexOf(p), t);
            }

            if (!points.isEmpty()) {
                g.drawLine(last.x, last.y, cur.x, cur.y);
            }

            last = cur;
        }
    }

    /**
     * This method will be called when
     * the mouse has been clicked.
     *
     * @param me
     */
    @Override
    public void mouseClicked(MouseEvent me) {
        Point forced = null;

        // If the left button was clicked, ...
        if (me.getButton() == MouseEvent.BUTTON1) {
            // ... get the current position of the mouse, ...
            Point mouse = me.getPoint();

            // ... check if there is a point, ...
            if (hasPoint(mouse) == null) {
                // ... and add one if not.
                points.add(mouse);
            }
        }

        // If the right button was clicked, ...
        else if (me.getButton() == MouseEvent.BUTTON3) {
            // ... get the current position of the mouse, ...
            Point mouse = me.getPoint();

            // ... check if there is a point, ...
            if ((forced = hasPoint(mouse)) != null) {
                // ... and remove it if so.
                points.remove(forced);
            }
        }

        repaint();
    }

    @Override
    public void mouseEntered(MouseEvent me) {
    }

    @Override
    public void mousePressed(MouseEvent me) {
        Point p = null;
        if ( (p = hasPoint(me.getPoint())) != null ) {
            _inDrag = true;
            _dragFrom = p;
        } else {
            _inDrag = false;
        }
    }

    @Override
    public void mouseReleased(MouseEvent me) {
        _inDrag = false;
    }

    @Override
    public void mouseExited(MouseEvent me) {
        _inDrag = false;
    }

    @Override
    public void mouseDragged(MouseEvent me) {
        // Check if a point is currently dragged ...
        if( _inDrag ) {
            // ... and move it if so.
            _dragFrom.move(me.getX(), me.getY());

            // Repaint because position changed.
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent me) {
    }

    private Point hasPoint(Point forced) {
        for (Point p : points) {
            if (forced.x >= p.x - 2
                    && forced.x <= p.x + 2
                    && forced.y >= p.y - 2
                    && forced.y <= p.y + 2) {
                return p;
            }
        }

        return null;
    }

    private double bernstein(int n, int k, float t) {
        return choose(n, k) * Math.pow(1 - t, n - k) * Math.pow(t, k);
    }

    private long choose(int n, int k) {
        return fact(n) / (fact(k) * fact(n - k));
    }

    private long fact(int n) {
        if( n > 1 ) {
            return fact(n-1) * n;
        }

        return 1;
    }

    /**
     * This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    private void initComponents() {

        setName("Form");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }
      
    private boolean _inDrag;
    private Point _dragFrom;
    private Color avedoBlue = new Color(159, 191, 254);
    private ArrayList<Point> points = new ArrayList<Point>();
}
 

Anhänge