Алгоритм плавного рисования эскизов iPhone

Я работаю над приложением для зарисовок на iPhone. Я получил это работает, но не красиво, как видно здесь

И я ищу любое предложение, чтобы сгладить рисунок. В основном, я делал, когда пользователь кладет палец на экран, который я назвал.

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

затем я собираю одно касание в массиве с

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

и когда пользователь отрывает палец от экрана, я звонил

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

Затем я рисую все точки в массиве, используя

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

А теперь я хочу улучшить рисунок, чтобы он больше походил на приложение "Sketch Book"

Я думаю, что есть что-то, что связано с алгоритмом обработки сигналов, чтобы переставить все точки в массиве, но я не уверен. Любая помощь приветствуется.

Заранее благодарен:)

6 ответов

Решение

Самый простой способ сгладить такую ​​кривую - использовать кривую Безье вместо отрезков прямых линий. Математика, стоящая за этим, приведена в этой статье (на которую указывает этот ответ), в которой описано, как рассчитать кривые, необходимые для сглаживания кривой, проходящей через несколько точек.

Я считаю, что инфраструктура Core Plot теперь имеет возможность сглаживать кривые графиков, поэтому вы можете взглянуть на код, используемый там для реализации такого рода сглаживания.

В этом нет ничего волшебного, поскольку эти процедуры сглаживания быстры и относительно просты в реализации.

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

}

Я действительно люблю эту тему. Спасибо за все реализации, особенно Кшиштоф Заблоцкий и Ю-Сен Хан. Я изменил версию Yu-Sen Han, чтобы изменить толщину линии в зависимости от скорости панорамирования (фактически расстояние между последними касаниями). Также я реализовал точечное рисование (для точек touchBegan и touchEnded, расположенных близко друг к другу). Вот результат:

Чтобы определить толщину линии, я выбрал такую ​​функцию расстояния:

(Не спрашивайте меня, почему... Я просто думаю, что это хорошо, но я уверен, что вы можете найти лучший)

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

Еще один намек. Чтобы толщина менялась плавно, я ограничил ее в зависимости от толщины предыдущего сегмента и пользовательского коэффициента:

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

Я перевел ответ Кёдзи на Swift, как многократно используемый подкласс UIImageView, Подкласс TouchDrawImageView позволяет пользователю рисовать на изображении пальцем.

Как только вы добавили это TouchDrawImageView класс для вашего проекта, не забудьте открыть раскадровку и

  1. Выбрать TouchDrawImageView как "Пользовательский класс" вашего изображения
  2. отметьте свойство "Взаимодействие с пользователем включено" в вашем представлении изображения

Вот код 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)
    }
}

Спасибо за ввод. Я обновляю свой квест здесь, потому что мне нужно место для него.

Я смотрю как на кривые CorePlot, так и на кривые Безье, которые вы предложили с небольшим успехом.

Для corePlot я могу получить график графика из массива int, но не могу найти ничего, связанного со сглаживанием кривой. Кстати, здесь я использую CPScatterPlot с некоторым случайным числом.

Что касается кривой Безье, Мои поиски привели меня сюда. Это как-то связано с реализацией Spline в 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);

и результат, который я получаю:

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

Я не совсем уверен, как идти оттуда. Так что я застрял на этом фронте:(

Я посмотрел GLPaint, как последний ресурс. Он использует OpenGLES и использует спрайт с "мягкой точкой" для построения точек в массиве. Я знаю, что это больше похоже на избежание проблемы, чем на ее решение. Но я думаю, что в любом случае я поделюсь своими выводами здесь.

Черный - это GLPaint, а белый - старый метод. И последний из них - это рисунок из "Sketch Book", просто для сравнения.

Я все еще пытаюсь сделать это правильно, любые дальнейшие предложения приветствуются.

Избавиться от глупой точки в коде GLPaint.

Изменить в

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

эта функция

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

Я знаю, что это не решение нашей проблемы, но это нечто.

Другие вопросы по тегам