Следуя моим недавним публикациям о 3D Touch и объединении касаний , объединение этих двух вещей в простом приложении для рисования казалось очевидным следующим шагом. Это также дает мне возможность повозиться с тем, CIImageAccumulator
что было недавно представлено в iOS 9.
Мое небольшое демонстрационное приложение ForceSketch позволяет пользователю рисовать на экране iPhone 6. И вес линии, и цвет линии связаны с давлением прикосновения. Как и в моей демонстрации ChromaTouch , давление контролирует оттенок, поэтому самое легкое касание — красный, поворачивающийся к зеленому при трети максимального давления, к синему при двух третях и обратно к красному при максимальном давлении.
Как только пользователь поднимает палец, два фильтра Core Image, CIColorControls и CIGaussianBlur включаются и исчезают из рисунка.
Механика рисования ForceSketch
Код рисования все вызывается из метода моего контроллера представления touchesMoved
. Именно здесь я создаю UIImage
экземпляр, основанный на объединенных касаниях и составном изображении поверх существующего аккумулятора изображений. В производственном приложении я бы, вероятно, применил фильтрацию изображений в фоновом потоке, чтобы улучшить производительность пользовательского интерфейса, но, для этой демонстрации, я думаю, что этот подход в порядке.
Вводное guard
утверждение гарантирует, что у меня есть необязательные константы для наиболее важных элементов:
guard let touch = touches.first,
event = event,
coalescedTouches = event.coalescedTouchesForTouch(touch) else
{
return
}
Следующим шагом является подготовка к созданию объекта изображения. Для этого мне нужно начать контекст изображения и создать ссылку на текущий контекст:
UIGraphicsBeginImageContext(view.frame.size)
let cgContext = UIGraphicsGetCurrentContext()
Чтобы обеспечить максимальную точность жеста пользователя, я зацикливаюсь на объединенных касаниях — это дает мне все промежуточные касания, которые могли произойти между вызовами touchesMoved ().
for coalescedTouch in coalescedTouches {
Используя force
свойство каждого касания, я создаю константы для цвета и веса отрезков. Чтобы убедиться, что пользователи не-3D Touch устройств звонят, все еще используют приложение, я проверяю forceTouchCapability
и даю этим пользователям фиксированный вес и цвет:
let lineWidth = (traitCollection.forceTouchCapability == UIForceTouchCapability.Available) ?
(coalescedTouch.force / coalescedTouch.maximumPossibleForce) * 20 :
10
let lineColor = (traitCollection.forceTouchCapability == UIForceTouchCapability.Available) ?
UIColor(hue: coalescedTouch.force / coalescedTouch.maximumPossibleForce, saturation: 1, brightness: 1, alpha: 1).CGColor :
UIColor.grayColor().CGColor
С помощью этих констант я могу установить ширину линии и цвет обводки в графическом контексте:
CGContextSetLineWidth(cgContext, lineWidth)
CGContextSetStrokeColorWithColor(cgContext, lineColor)
… и теперь я готов определить начало и конец моего отрезка для этого слияния:
CGContextMoveToPoint(cgContext,
previousTouchLocation!.x,
previousTouchLocation!.y)
CGContextAddLineToPoint(cgContext,
coalescedTouch.locationInView(view).x,
coalescedTouch.locationInView(view).y)
Заключительные шаги внутри объединенного цикла касаний должны пройти путь и обновить previousTouchLocation
:
CGContextStrokePath(cgContext)
previousTouchLocation = coalescedTouch.locationInView(view)
После того как все штрихи были добавлены в графический контекст, для создания UIImage
экземпляра требуется одна строка кода, а затем конец контекста:
let drawnImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Отображение нарисованных линий
Чтобы отобразить только что нарисованные линии drawnImage
, я использую CISourceOverCompositing
фильтр с изображением drawnImage
в качестве переднего плана и текущее изображение аккумулятора изображения в качестве фона:
compositeFilter.setValue(CIImage(image: drawnImage),
forKey: kCIInputImageKey)
compositeFilter.setValue(imageAccumulator.image(),
forKey: kCIInputBackgroundImageKey)
Затем возьмите выходные данные источника через композитор, передайте их обратно в аккумулятор и заполните UIImageView
их изображением аккумулятора:
imageAccumulator.setImage(compositeFilter.valueForKey(kCIOutputImageKey) as! CIImage)
imageView.image = UIImage(CIImage: imageAccumulator.image())
Blurry Fade Out
Как только пользователь поднимает палец, я делаю «размытое затухание» нарисованного изображения. Этот эффект использует два фильтра Core Image, которые определены как константы:
let hsb = CIFilter(name: "CIColorControls",
withInputParameters: [kCIInputBrightnessKey: 0.05])!
let gaussianBlur = CIFilter(name: "CIGaussianBlur",
withInputParameters: [kCIInputRadiusKey: 1])!
Первая часть эффекта заключается в использовании a, CADisplayLink
который будет вызываться step()
при каждом обновлении экрана:
let displayLink = CADisplayLink(target: self, selector: Selector("step"))
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
I rely on previousTouchLocation
being nil to infer the user has finished their touch. If that’s the case, I simply pass the accumulator’s current image into the HSB / colour control filter, pass that filter’s output into the Gaussian Blur and finally the blur’s output back into the accumulator:
hsb.setValue(imageAccumulator.image(), forKey: kCIInputImageKey)
gaussianBlur.setValue(hsb.valueForKey(kCIOutputImageKey) as! CIImage, forKey: kCIInputImageKey)
imageAccumulator.setImage(gaussianBlur.valueForKey(kCIOutputImageKey) as! CIImage)
imageView.image = UIImage(CIImage: imageAccumulator.image())
Source Code
As always, the source code for this project is available in my GitHub repository here. Enjoy!