webentwicklung-frage-antwort-db.com.de

iPhone glatte Skizze Zeichnungsalgorithmus

Ich arbeite an einer Skizzier-App für das iPhone. Ich habe es zum Laufen gebracht, aber nicht so hübsch wie hier enter image description here

Und ich suche nach einem Vorschlag, um die Zeichnung zu glätten. Grundsätzlich habe ich einen Finger auf den Bildschirm gelegt, den ich angerufen habe

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 

dann sammle ich eine einzelne Berührung in einem Array mit

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

und als der benutzer einen finger vom bildschirm gelassen hat, habe ich angerufen

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

dann zeichne ich alle Punkte im Array mit

NSMutableArray *points = [collectedArray points];   

CGPoint firstPoint;
[[points objectAtIndex:0] getValue:&firstPoint];

CGContextMoveToPoint(context, firstPoint.x, firstPoint.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);

for (int i=1; i < [points count]; i++) {
    NSValue *value = [points objectAtIndex:i];
    CGPoint point;
    [value getValue:&point];    
    CGContextAddLineToPoint(context, point.x, point.y);

} 

CGContextStrokePath(context);
UIGraphicsPushContext(context);

Und jetzt möchte ich die Zeichnung verbessern, damit sie mehr der "Sketch Book" -App ähnelt enter image description here

Ich denke, es gibt etwas mit dem Signalverarbeitungsalgorithmus zu tun, um alle Punkte im Array neu anzuordnen, aber ich bin nicht sicher. Jede Hilfe wäre sehr dankbar.

Vielen Dank im Voraus :)

60

Der einfachste Weg, eine solche Kurve zu glätten, ist die Verwendung einer Bezier-Kurve anstelle von geraden Liniensegmenten. Informationen zur Berechnung finden Sie unter dieser Artikel (auf diese Antwort ), in dem beschrieben wird, wie die Kurven berechnet werden, die zum Glätten einer Kurve erforderlich sind, die durch mehrere Punkte verläuft.

Ich glaube, dass das Core-Plot-Framework jetzt die Möglichkeit bietet, die Kurven von Plots zu glätten, sodass Sie sich den dort verwendeten Code ansehen können, um diese Art von Glättung zu implementieren.

All dies ist kein Wunder, da diese Glättungsroutinen schnell und relativ einfach zu implementieren sind.

22
Brad Larson
CGPoint midPoint(CGPoint p1, CGPoint p2)
{

    return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5);

}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

    UITouch *touch = [touches anyObject];

    previousPoint1 = [touch previousLocationInView:self];
    previousPoint2 = [touch previousLocationInView:self];
    currentPoint = [touch locationInView:self];

}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{

    UITouch *touch = [touches anyObject];

    previousPoint2 = previousPoint1;
    previousPoint1 = [touch previousLocationInView:self];
    currentPoint = [touch locationInView:self];


    // calculate mid point
    CGPoint mid1 = midPoint(previousPoint1, previousPoint2); 
    CGPoint mid2 = midPoint(currentPoint, previousPoint1);

    UIGraphicsBeginImageContext(self.imageView.frame.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.imageView.image drawInRect:CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height)];

    CGContextMoveToPoint(context, mid1.x, mid1.y);
    // Use QuadCurve is the key
    CGContextAddQuadCurveToPoint(context, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y); 

    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineWidth(context, 2.0);
    CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
    CGContextStrokePath(context);

    self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

}
56
kyoji

Ich liebe das Thema wirklich. Vielen Dank für alle Implementierungen, insbesondere Krzysztof Zabłocki und Yu-Sen Han. Ich habe die Version von Yu-Sen Han geändert, um die Linienstärke in Abhängigkeit von der Panning-Geschwindigkeit (tatsächlich der Entfernung zwischen den letzten Berührungen) zu ändern. Außerdem habe ich das Punktzeichnen implementiert (für TouchBegan- und TouchEnded-Standorte, die nahe beieinander liegen). Hier ist das Ergebnis: enter image description here

Um die Linienstärke zu definieren, habe ich eine solche Abstandsfunktion gewählt:

(Frag mich nicht, warum ... ich finde, dass es gut passt, aber ich bin mir sicher, dass du ein besseres finden kannst.)

enter image description here

CGFloat dist = distance(previousPoint1, currentPoint);
CGFloat newWidth = 4*(atan(-dist/15+1) + M_PI/2)+2;

Noch ein Hinweis. Damit sich die Dicke reibungslos ändert, habe ich sie abhängig von der Dicke des vorherigen Segments und einem benutzerdefinierten Coef begrenzt:

self.lineWidth = MAX(MIN(newWidth,lastWidth*WIDTH_RANGE_COEF),lastWidth/WIDTH_RANGE_COEF);
15
alexburtnik

Ich übersetzte Kyojis Antwort in Swift als wiederverwendbare Unterklasse von UIImageView. Mit der Unterklasse TouchDrawImageView kann der Benutzer mit dem Finger auf eine Bildansicht zeichnen.

Öffnen Sie nach dem Hinzufügen dieser TouchDrawImageView -Klasse zu Ihrem Projekt das Storyboard und

  1. wählen Sie TouchDrawImageView als "Benutzerdefinierte Klasse" Ihrer Bildansicht
  2. aktivieren Sie die Eigenschaft "Benutzerinteraktion aktiviert" in Ihrer Bildansicht

Hier ist der Code von TouchDrawImageView.Swift:

import UIKit

class TouchDrawImageView: UIImageView {

    var previousPoint1 = CGPoint()

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        previousPoint1 = touch.previousLocation(in: self)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }

        let previousPoint2 = previousPoint1
        previousPoint1 = touch.previousLocation(in: self)
        let currentPoint = touch.location(in: self)


        // calculate mid point
        let mid1 = midPoint(p1: previousPoint1, p2: previousPoint2)
        let mid2 = midPoint(p1: currentPoint, p2: previousPoint1)

        UIGraphicsBeginImageContext(self.frame.size)
        guard let context = UIGraphicsGetCurrentContext() else { return }
        if let image = self.image {
            image.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
        }

        context.move(to: mid1)
        context.addQuadCurve(to: mid2, control: previousPoint1)

        context.setLineCap(.round)
        context.setLineWidth(2.0)
        context.setStrokeColor(red: 1.0, green: 0, blue: 0, alpha: 1.0)
        context.strokePath()

        self.image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    }

    func midPoint(p1: CGPoint, p2: CGPoint) -> CGPoint {
        return CGPoint(x: (p1.x + p2.x) / 2.0, y: (p1.y + p2.y) / 2.0)
    }
}
5
Lars Blumberg

Vielen Dank für die Eingabe. Ich aktualisiere meine Suche hier, weil ich den Platz dafür brauche.

Ich schaue sowohl nach CorePlot- als auch nach Bezier-Kurvenlösungen, die Sie mit wenig Erfolg vorgeschlagen haben.

Für das CorePlot kann ich das Diagramm aus einem Array von int abrufen, kann aber nichts im Zusammenhang mit der Kurvenglättung finden. Übrigens: Hier verwende ich CPScatterPlot mit einer Zufallszahl.

enter image description here

bezier-Kurve: Meine Suche führte mich zu hier Dies hat etwas mit der Spline-Implementierung in iOS zu tun

  CatmullRomSpline *myC = [[CatmullRomSpline alloc] initAtPoint:CGPointMake(1.0, 1.0)];
  [myC addPoint:CGPointMake(1.0, 1.5)];
  [myC addPoint:CGPointMake(1.0, 1.15)];
  [myC addPoint:CGPointMake(1.0, 1.25)];
  [myC addPoint:CGPointMake(1.0, 1.23)];
  [myC addPoint:CGPointMake(1.0, 1.24)];
  [myC addPoint:CGPointMake(1.0, 1.26)];
  NSLog(@"xxppxx %@",[myC asPointArray]);
  NSLog(@"xxppxx2 %@",myC.curves);

und das Ergebnis, das ich bekomme, ist:

  2011-02-24 14:45:53.915 DVA[10041:40b] xxppxx (
  "NSPoint: {1, 1}",
  "NSPoint: {1, 1.26}"
   )

  2011-02-24 14:45:53.942 DVA[10041:40b] xxppxx2 (
  "QuadraticBezierCurve: 0x59eea70"
  )

Ich bin mir nicht sicher, wie ich von dort aus vorgehen soll. Also stecke ich auch an dieser Front fest :(

Ich habe GLPaint als letzte Ressource nachgeschlagen. Es verwendet OpenGLES und ein Sprite mit "weichen Punkten", um die Punkte im Array zu zeichnen. Ich weiß, dass es eher darum geht, das Problem zu vermeiden, als es zu beheben. Aber ich schätze, ich werde meine Erkenntnisse hier trotzdem teilen.

Das Schwarze ist GLPaint und das Weiße ist die alte Methode. Und die letzte ist die Zeichnung von "Sketch Book" App nur zum Vergleich

enter image description hereenter image description hereenter image description here

Ich versuche immer noch, dies richtig zu machen. Weitere Vorschläge sind sehr willkommen.

3

Den dummen Punkt im GLPaint-Code loswerden.

Verändern in

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

diese Funktion

//Ändrat av OLLE
/*
// Convert touch point from UIView referential to OpenGL one (upside-down flip)
if (firstTouch) {
    firstTouch = NO;
    previousLocation = [touch previousLocationInView:self];
    previousLocation.y = bounds.size.height - previousLocation.y;
} else {
    location = [touch locationInView:self];
    location.y = bounds.size.height - location.y;
    previousLocation = [touch previousLocationInView:self];
    previousLocation.y = bounds.size.height - previousLocation.y;
}
 */
location = [touch locationInView:self];
location.y = bounds.size.height - location.y;
previousLocation = [touch previousLocationInView:self];
previousLocation.y = bounds.size.height - previousLocation.y;

//Ändrat av OLLE//

Ich weiß, dass dies nicht die Lösung für unser Problem ist, aber es ist etwas.

1
Olle