Алгоритм рисования гладких эскизов iPhone
Я работаю над эскизным приложением на iPhone.
У меня это работает, но не так хорошо, как здесь
![enter image description here]()
И я ищу любое предложение сгладить рисунок
В основном, то, что я сделал, - это когда пользователь помещает палец на экран, который я назвал
- (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"
![enter image description here]()
Я думаю, что есть что-то делать с алгоритмом обработки сигналов, чтобы переупорядочить все точки в массиве, но я не уверен. Любая помощь приветствуется.
Спасибо заранее:)
Ответы
Ответ 1
Самый простой способ сгладить такую кривую - использовать кривую Безье вместо прямых. Для математики, стоящей за этим, см. в этой статье (указано в этом ответе), который описывает, как вычислять кривые, необходимые для сглаживания кривой, проходящей через несколько точек.
Я считаю, что Core Plot framework теперь имеет возможность сгладить кривые графиков, чтобы вы могли посмотреть на используемый там код для реализации такого сглаживания.
В этом нет никакой магии, так как эти сглаживающие процедуры выполняются быстро и относительно легко.
Ответ 2
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();
}
Ответ 3
Мне очень нравится эта тема. Спасибо за все реализации, например, Кшиштоф Заблоцкий и Ю-Сен Хан.
Я изменил версию Yu-Sen Han, чтобы изменить толщину линии в зависимости от скорости панорамирования (фактически расстояние между последними штрихами). Кроме того, я реализовал точечный чертеж (для touchBegan и touchEnded местоположения, близкие друг к другу)
Вот результат:
![enter image description here]()
Чтобы определить толщину линии, я выбрал такую функцию расстояния:
(Не спрашивайте меня, почему... Я просто, хотя это подходит, но я уверен, что вы можете найти лучший)
![enter image description here]()
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);
Ответ 4
Спасибо за вход. Я обновляю свой квест здесь, потому что мне нужно место для него.
Я просматриваю оба решения кривой CorePlot и Безье, которые вы предложили с небольшим успехом.
Для corePlot я могу получить график графика из массива int, но не могу найти ничего, связанного с сглаживанием кривой .BTW. Здесь я использую CPScatterPlot с некоторым случайным числом.
![enter image description here]()
как для кривой Безье, Мой квест привел меня к здесь Это как-то связано с реализацией 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", чтобы сравнить
![enter image description here]()
![enter image description here]()
![enter image description here]()
Я все еще пытаюсь сделать это правильно, любое дальнейшее предложение приветствуется.
Ответ 5
Чтобы избавиться от глупой точки в коде 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//
Я знаю, что это не решение нашей проблемы, но это что-то.
Ответ 6
Я перевел kyoji answer в Swift, как многоразовый подкласс UIImageView
. Подкласс TouchDrawImageView
позволяет пользователю рисовать изображение с помощью пальца.
Как только вы добавили этот класс TouchDrawImageView
в свой проект, обязательно откройте раскадровку и
- выберите
TouchDrawImageView
как "Пользовательский класс" вашего изображения.
- проверить свойство "Взаимодействие с пользователем" в вашем представлении изображения
Здесь код 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)
}
}