Weiche Federführung mit Java


B
#1
Guten Morgen!

Ich arbeite gerade an einem kleinen Programm in dem es möglich sein soll zu zeichnen. Das ist erstmal kein problem. Ich habe ein JPanel so erweitert, dass es ein BufferedImage verwaltet, auf dem gezeichnet wird und die Methode paintComponent sorgt dafür, dass das BufferedImage als Hintergrund des JPanels gesetzt wird. Das sieht ungefähr so aus:

Java:
	public void paintComponent(Graphics g) {
		// Call the super paintComponent method.
		super.paintComponent(g);

		// Cast the Graphics to Graphics2D object, ...
		Graphics2D graphics = (Graphics2D) g;
        
		// ... fetch the current zoom factor from the model ...
		float zoomFactor = this.model.getZoomFactor();
        
		// ... and scale the graphics.
		graphics.scale(zoomFactor, zoomFactor);
        
		// Fetch the old image from the model ...
		BufferedImage image = this.model.getImage();
        
		// ... and draw it to the graphics if possible.
		if(image != null) {
        		graphics.drawImage(image, 0, 0, null);
		}
	}
Außerdem habe ich das JPanel um einen MouseMotionListener und einen MouseListener erweitert, sodass ich mit den events mousePressed, mouseReleased und mouseDragged arbeiten kann. Innerhalb der entsprechenden Listener rufe ich immer die Methode action auf, die die Event-Id, die x- und y-Position, sowie das Graphic-Objekt übergeben bekommt. Sie ist für das eigentliche Zeichnen zuständig.

Java:
	public void action(int eventID, float x, float y, Graphics2D g) {
		// If mouse pressed ...
		if (eventID == MouseEvent.MOUSE_PRESSED) {
			// ... set the start point.
			path.moveTo(x, y);
		} else if (eventID == MouseEvent.MOUSE_DRAGGED) {		
			// Get the start point ...
			Point2D startPoint = path.getCurrentPoint();
			
			// ... and the end point for the current section.
			Point2D endPoint = new Point2D.Float(x, y);
			
			// ... and calulate their distance.
			double curDist = startPoint.distance(endPoint);
			
			if(curDist > this.lastDist) {
				strokeWidth *= 1.3;
			} else if(strokeWidth >= 2) {
				strokeWidth *= 0.7;
			}
			//float strokeWidth = (float) Math.pow(Math.log(startPoint.distance(endPoint)),2);
			//float strokeWidth = (float) startPoint.distance(endPoint);
			
			// ... and setup a new stroke with this width.
			setStroke(new BasicStroke(strokeWidth,
					BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
			
			// Assign the current color and stroke.
			g.setColor(getColor());
			g.setStroke(getStroke());
			
			// Interpolate the path between these two points.
			for(float i = 0; i + 0.01 < 1; i += 0.01) {
				Point2D p1 = cosInterpolate(startPoint, endPoint, i);
				Point2D p2 = cosInterpolate(startPoint, endPoint, (float) (i + 0.01));
				Line2D l = new Line2D.Float(p1, p2);
				g.draw(l);
			}
			
			// Finally  update the last distance ...
			lastDist = curDist;
			
			// ... and the path.
			path.lineTo(x, y);
		}
	}
Wie man sieht verbinde ich eigentlich nur den aktuellen mit dem letzten Punkt durch eine Linie. Ich interpoliere also linear. Das funktioniert eigenlich ganz gut, sieht aber bescheiden aus.

Nun habe ich überlegt anstatt linear zu interpolieren mit Bezier-Kurven zu arbeiten (hierzu ein alter Post von mir). Diese haben jedoch den großen Nachteil, dass die Gewichte äußerst schwer zu berechnen sind und beim Hinzufügen eines neuen Punktes die komplette Kurve verändert wird.

Meine Frage(n) also ... Wäre eine Interpolation mit Splines eine bessere Alternative? Wie müsste ich soetwas implementieren, sodass ich segmentweise, immer wenn ein neuer Punkt hinzukommt das letzte Segment interpolieren kann?

Zuletzt bleibt dann noch das Problem der doch recht kantigen Ausgabe. Eine geeignetere Interpolation würde das Problem sicherlich teilweise lösen, ist aber sicherlich nicht die alleinige Lösung. Ich habe schon etwas über Perlin Noise gelesen, finde diese Lösung aber nicht wirklich elegant. Gibt es da nichts "schöneres"?

An dieser Stelle erstmal vielen Dank für euer Durchhaltevermögen. Ich freue mich auf eure Antworten.

Liebe Grüße

Andreas