iPhone smooth sketch drawing algorithm

Pracuję nad aplikacją szkicującą na iPhone ' a. Mam go działa, ale nie ładny jak widać tutaj Tutaj wpisz opis obrazka

I szukam jakiejkolwiek sugestii, aby wygładzić rysunek Zasadniczo, to, co zrobiłem, to kiedy użytkownik kładzie palec na ekranie, który wywołałem

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

Następnie zbieram Pojedynczy dotyk w tablicy z

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

I kiedy użytkownik wyciągnie palec z ekranu, zadzwoniłem

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

Następnie rysuję wszystkie punkty w tablicy używając

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

And now I want to popraw rysunek, aby bardziej przypominał aplikację" Sketch Book" Tutaj wpisz opis obrazka

Myślę, że jest coś wspólnego z algorytmem przetwarzania sygnału, aby zmienić wszystkie punkty w tablicy, ale nie jestem pewien. Każda pomoc będzie mile widziana.

Z góry dziękuję:)

Author: Lars Blumberg, 2011-02-22

6 answers

Najprostszym sposobem wygładzenia krzywej takiej jak ta jest użycie krzywej Beziera zamiast odcinków prostych. Dla matematyki za tym, Zobacz Ten artykuł (wskazywany w ta odpowiedź), który opisuje, jak obliczyć krzywe wymagane do wygładzenia krzywej, która przechodzi przez wiele punktów.

Wierzę, że Core Plot framework ma teraz możliwość wygładzenia krzywych Wykresów, więc można spojrzeć na kod używany tam do implementacji tego rodzaju wygładzanie.

Nie ma w tym żadnej magii, ponieważ procedury wygładzania są szybkie i stosunkowo łatwe do wdrożenia.

 22
Author: Brad Larson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:32:35
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
Author: kyoji,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-31 14:20:33

Naprawdę podoba mi się ten temat. Dziękuję za wszystkie realizacje, szczególnie Krzysztofowi Zabłockiemu i Yu-Sen Han. Zmodyfikowałem wersję Yu-Sen Han, aby zmienić grubość linii w zależności od prędkości panoramowania (w rzeczywistości odległość między ostatnimi dotknięciami). Również zaimplementowałem rysowanie kropek (dla touchbegan i touchEnded lokalizacje są blisko siebie) Oto wynik: Tutaj wpisz opis obrazka

Aby określić grubość linii Wybrałem taką funkcję odległości:

(nie zapytaj dlaczego... Po prostu uważam, że pasuje dobrze, ale jestem pewien, że znajdziesz lepszy)

Tutaj wpisz opis obrazka

CGFloat dist = distance(previousPoint1, currentPoint);
CGFloat newWidth = 4*(atan(-dist/15+1) + M_PI/2)+2;
Jeszcze jedna wskazówka. Aby mieć pewność, że grubość zmienia się płynnie, ograniczyłem ją w zależności od grubości poprzedniego segmentu i niestandardowego coef:
self.lineWidth = MAX(MIN(newWidth,lastWidth*WIDTH_RANGE_COEF),lastWidth/WIDTH_RANGE_COEF);
 15
Author: alexburtnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-24 21:51:12

Dziękuję za wkład.Aktualizuję moje zadanie tutaj, ponieważ potrzebuję na to miejsca.

Sprawdzam zarówno rozwiązania corePlot, jak i Bezier curve, które zaproponowałeś z niewielkim sukcesem.

Dla corePlot jestem w stanie uzyskać Wykres z tablicy int, ale nie mogę znaleźć niczego związanego z wygładzaniem krzywych.BTW tutaj używam CPScatterPlot z jakimś losowym numerem.

Tutaj wpisz opis obrazka

Co do krzywej Beziera, to moje poszukiwania prowadzą mnie do tutaj jest to coś wspólnego ze Splinem implementacja w 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);

A wynik jaki otrzymuję to:

  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"
  )
Nie jestem pewien, jak stąd wyjść. Więc ja też utknąłem na tym froncie: (

Szukałem GLPaint, jako ostatniego zasobu. Używa Opengle i używa "miękkiej kropki" sprite do wykreślenia punktów w tablicy. Wiem, że to bardziej unikanie problemu, niż naprawianie go. Ale i tak podzielę się swoimi odkryciami.

Czarny to GLPaint, a biały to stara metoda. I ostatni czy rysunek z aplikacji "Sketch Book" jest tylko po to, aby porównać

Tutaj wpisz opis obrazkaTutaj wpisz opis obrazkaTutaj wpisz opis obrazka

Nadal staram się to zrobić dobrze, wszelkie dalsze sugestie są mile widziane.

 3
Author: Suwitcha Sugthana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-13 10:57:24

Przetłumaczyłem odpowiedź kyoji na Swift, jako podklasę wielokrotnego użytku UIImageView. Podklasa TouchDrawImageView pozwala użytkownikowi rysować na widoku obrazu palcem.

Po dodaniu tej klasy TouchDrawImageView do swojego projektu, upewnij się, że otworzysz storyboard i

  1. Wybierz TouchDrawImageView jako "klasę niestandardową" widoku obrazu
  2. zaznacz właściwość "interakcja użytkownika włączona" w widoku obrazu

Oto kod 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)
    }
}
 3
Author: Lars Blumberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-03 21:55:44

Aby pozbyć się głupiej kropki w kodzie GLPaint.

Zmiana w

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

Ta funkcja

//Ä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//
Wiem, że to nie jest rozwiązanie naszego problemu, ale to coś.
 1
Author: Olle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-13 10:53:28