web-dev-qa-db-fra.com

Algorithme de dessin d'esquisse fluide pour iPhone

Je travaille sur une application de dessin sur iPhone. Je l'ai fait fonctionner mais pas joli comme on le voit ici enter image description here

Et je cherche n'importe quelle suggestion pour lisser le dessin Fondamentalement, ce que j'ai fait, c'est quand l'utilisateur place un doigt sur l'écran que j'ai appelé

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

puis je recueille une seule touche dans un tableau avec

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

et lorsque l'utilisateur laisse un doigt sur l'écran, j'ai appelé

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

puis je dessine tous les points du tableau en utilisant

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);

Et maintenant, je veux améliorer le dessin pour qu'il ressemble davantage à l'application "Sketch Book" enter image description here

Je pense qu'il y a quelque chose à voir avec l'algorithme de traitement du signal pour réorganiser tous les points du tableau, mais je ne suis pas sûr. Toute aide serait très appréciée.

Merci d'avance :)

60
Suwitcha Sugthana

Le moyen le plus simple de lisser une courbe comme celle-ci consiste à utiliser une courbe de Bézier au lieu de segments de ligne droite. Pour les mathématiques derrière cela, voir cet article (pointé dans cette réponse ), qui décrit comment calculer les courbes nécessaires pour lisser une courbe qui passe par plusieurs points.

Je crois que le Core Plot framework a maintenant la capacité de lisser les courbes des tracés, vous pouvez donc regarder le code utilisé pour implémenter ce type de lissage.

Il n'y a rien de magique à tout cela, car ces routines de lissage sont rapides et relativement faciles à mettre en œuvre.

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

J'adore vraiment le sujet. Merci pour toutes les implémentations, en particulier Krzysztof Zabłocki et Yu-Sen Han. J'ai modifié la version de Yu-Sen Han afin de changer l'épaisseur du trait en fonction de la vitesse de panoramique (en fait la distance entre les dernières touches). J'ai également implémenté le dessin par points (pour les emplacements touchBegan et touchEnded proches les uns des autres) Voici le résultat: enter image description here

Pour définir l'épaisseur de la ligne, j'ai choisi une telle fonction de la distance:

(Ne me demandez pas pourquoi ... je pense que ça va bien, mais je suis sûr que vous pouvez en trouver un meilleur)

enter image description here

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

Encore un indice. Pour être sûr que l'épaisseur change en douceur, je l'ai limitée en fonction de l'épaisseur du segment précédent et d'un coef personnalisé:

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

J'ai traduit réponse de kyoji en Swift, comme une sous-classe réutilisable de UIImageView. La sous-classe TouchDrawImageView permet à l'utilisateur de dessiner sur une vue d'image avec son doigt.

Une fois que vous avez ajouté cette classe TouchDrawImageView à votre projet, assurez-vous d'ouvrir votre storyboard et

  1. sélectionnez TouchDrawImageView comme "Classe personnalisée" de votre vue d'image
  2. vérifiez la propriété "Interaction utilisateur activée" de votre vue d'image

Voici le code de 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

Merci pour la contribution. Je mets à jour ma quête ici parce que j'ai besoin d'espace pour cela.

Je recherche les solutions de courbes corePlot et Bézier que vous avez suggérées avec peu de succès.

Pour le corePlot, je peux obtenir le graphique à partir d'un tableau d'int, mais je ne trouve rien lié au lissage de courbe.BTW Ici, j'utilise CPScatterPlot avec un certain nombre aléatoire.

enter image description here

quant à la courbe de Bézier, ma quête m'a amené à ici C'est quelque chose à voir avec l'implémentation Spline dans iOS

  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);

et le résultat que j'obtiens est:

  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"
  )

Je ne sais pas vraiment comment y aller. Je suis donc coincé sur ce front aussi :(

J'ai cherché GLPaint, comme dernière ressource. Il utilise OpenGLES et utilise un Sprite "soft dot" pour tracer les points dans le tableau. Je sais que c'est plus comme éviter le problème que le résoudre. Mais je suppose que je partagerai mes conclusions ici de toute façon.

Le noir est GLPaint et le blanc est l'ancienne méthode. Et le dernier est le dessin de l'application "Sketch Book" juste pour comparer

enter image description hereenter image description hereenter image description here

J'essaie toujours de bien faire les choses, toute autre suggestion est la bienvenue.

3
Suwitcha Sugthana

Pour se débarrasser du point idiot dans le code GLPaint.

Changer

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

cette fonction

//Ä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//

Je sais que ce n'est pas la solution à notre problème, mais c'est quelque chose.

1
Olle