Цель
Я пытаюсь создать анимированную аппроксимацию человеческого письма, используя UIBezierPath, сгенерированный из глифа. Я понимаю, и я прочитал множество вопросов UIBezierPath, которые похожи на мои (но не то же самое). Моя цель - создать анимацию, которая аппроксимирует внешний вид человека, выполняющего написание символов и букв.
Фон
Я создал игровую площадку, которую я использую, чтобы лучше понимать пути для использования в анимационных глифах, как если бы они были нарисованы вручную.
Несколько концепций в Apple CAShapeLayer (strokeStart и strokeEnd) действительно не работают так, как ожидалось при анимации. Я думаю, что обычно люди склонны думать об инсульте, как если бы делали это с помощью пишущего инструмента (в основном, прямой или изогнутой линии). Мы рассматриваем штрих и заливаем вместе линию, так как наши пишущие инструменты не различают ход и заливку.
Но при анимации контур пути строится по сегментам строк (заполнение обрабатывается отдельно, и неясно, как оживить положение заливки?). То, что я хочу достичь, - это естественная человеческая письменная линия/кривая, которая показывает начало и конец удара вместе с добавлением части заливки, когда анимация перемещается от начала до конца. Первоначально это кажется простым, но я думаю, что это может потребовать анимации позиции заполнения (неуверенность в том, как это сделать), начало/конец инсульта (не уверен, что это необходимо, учитывая непредвиденные оговорки с тем, как анимация работает выше) и использование суб-путей (как восстановить по известному пути).
Подход 1
Итак, я рассмотрел идею Path (CGPath/UIBezierPath). Каждый путь на самом деле содержит все подпуты, необходимые для построения глифа, поэтому, возможно, рекурсия этих подпутей и использование CAKeyframeAnimation/CABasicAnimations и анимационная группа, показывающая частично построенные подпуты, были бы хорошим подходом (хотя положение заполнения и ход каждого подпутника все равно нужно анимировать от начала до конца?).
Этот подход приводит к уточненному вопросу:
Как получить доступ и создать UIBezierPath/CGPath (подпапки), если у вас есть полный UIBezierPath/CGPath?
Как оживить заливку и штрих, как если бы они были нарисованы с помощью инструмента записи, используя информацию о пути/подпункте? (казалось бы, это означает, что необходимо одновременно анимировать свойства strokeStart/strokeEnd, положение и путь CALayer)
Примечание:
Как можно заметить в коде, у меня есть готовые пути, полученные из глифов. Я вижу, что описание пути дает мне информацию, подобную пути. Как можно взять эту информацию и переделать ее в виде массива sub paths человеческих записей?
(Идея здесь состоит в том, чтобы преобразовать информацию о точках в новый тип данных человекоподобных штрихов. Это подразумевает требование для алгоритма для определения начала, наклона, конечной точки и границы каждого заполнения)
Советы
Я заметил в Pleco (приложение iOS, которое успешно реализует аналогичный алгоритм), что каждый штрих состоит из замкнутого пути, описывающего человеческий доступ. UIBezierPath имеет замкнутый путь, основанный на непрерывных подключенных записях. Алгоритм необходим для уточнения перекрывающихся заливок для создания отдельных закрытых путей для каждого типа штриха.
Эрика Садун имеет набор утилит путей, доступных на github. Я не полностью изучил эти файлы, но они могут оказаться полезными при определении дискретных штрихов.
Структура UIBezierPath, по-видимому, основана на понятии смежных отрезков/кривых линии. На пересечениях заливок появляются точки слияния, которые представляют собой изменение направления движения. Можно ли вычислить угол удара/заливки сегмента кривой/линии и найти другие кривые/линии для соответствующей точки слияния? (т.е. соединить отрезок линии через промежуток пересекающихся заполнений, чтобы создать два отдельных пути - при условии, что один поднял точки и воссоздал путь с новым сегментом/кривой линии)
Вперед: существует ли более простой метод? Я пропустил критический API, книгу или лучший подход к этой проблеме?
Некоторые альтернативные методы (не полезны - требуют загрузки gif или flash) для получения желаемого результата:
Хороший пример (с использованием Flash)с уровнем представления, показывающим прогрессию письменного хода. (Если возможно, это то, что я хотел бы приблизиться в Swift/iOS) - (alt link - см. Анимационное изображение слева)
Менее хороший пример, показывающий использование прогрессивных путей и заливок для аппроксимации характеристик записанного штриха (анимация негладкая и требует внешних ресурсов):
Flash-версия - Я знаком с созданием флеш-анимаций, но я не желаю реализовывать их в 1000 (не говоря уже о том, что он не поддерживается на iOS, хотя Возможно, я мог бы также преобразовать алгоритм для использования холста HTML5 с анимацией css). Но эта линия мысли кажется немного далекой, в конце концов, информация о пути, которую я хочу, хранится в глифах, которые я извлек из предоставленных шрифтов/строк.
Подход 2
Я рассматриваю использование шрифта stroke-based, а не на основе получить правильную информацию о пути (т.е. тот, где заполнение представлено в виде пути). В случае успеха этот подход будет более чистым, чем аппроксимация штрихов, штрихового типа, пересечений и порядка инсульта. Я уже представил радиолокатору Apple, предлагая добавить в iOS шрифты на основе штриха (# 20426819). Несмотря на это, я до сих пор не отказался от формирования алгоритма, который разрешает частичные штрихи, полные штрихи, пересечения и точки слияния от сегментов линии и кривых, найденных на пути безье.
Обновленные мысли, основанные на обсуждении/ответах
Следующая дополнительная информация предоставляется на основе любых текущих разговоров и ответов ниже.
Порядок штрихов важен, и большинство языков (в данном случае китайцы) четко определили типы штрихов и правила порядка штрихов, которые, как представляется, обеспечивают механизм для определения типа и порядка на основе информации о точках, предоставляемой каждому элементу CGPathElement.
CGPathApply и CGPathApplierFunction кажутся перспективными как средство для перечисления подпутей (сохраненных в массиве и применения анимации заполнения)
Маска может быть применена к слою, чтобы выявить часть подслоя (Я раньше не использовал это свойство, но кажется, что если бы я мог перемещать маскированный слой поверх подпапок, которые могли бы помочь в анимации заполнения?)
Для каждого пути определено большое количество точек. Как будто BezierPath определяется только контур глифа. Этот факт позволяет понять, что начало, конец и объединение пересечений заполняют важный фактор для устранения неоднозначности конкретных заполнений.
Доступны дополнительные внешние библиотеки, которые могут позволить более эффективно решать поведение штрихов. Другие технологии, такие как Saffron Type System или одна из ее производных, могут быть применимы к этой проблемной области.
Основная проблема с простейшим решением просто анимации штриха заключается в том, что доступные шрифты iOS являются общими шрифтами, а не штриховыми шрифтами. Некоторые коммерческие производители выпускают штриховые шрифты. Пожалуйста, не стесняйтесь использовать ссылку для файла детской площадки, если у вас есть один из них для тестирования.
Я думаю, что это распространенная проблема, и я продолжу обновлять сообщение, когда я продвигаюсь к решению. Пожалуйста, дайте мне знать в комментариях, если вам нужна дополнительная информация или я могу пропустить некоторые из необходимых понятий.
Возможное решение
Я всегда ищу простейшее возможное решение. Проблема возникает из-за структуры шрифтов, которые являются общими шрифтами, а не штриховыми. Я нашел образец шрифта на основе штрихов для тестирования и использовал его для оценки доказательства концепции (см. Видео). Теперь я ищу расширенный шрифт с одним штрихом (который включает в себя иероглифы) для дальнейшей оценки. Менее простое решение может заключаться в том, чтобы найти способ создания штриха, который следует за заполнением, а затем использовать простую 2D-геометрию для оценки того, какой штрих для анимации сначала (например, китайские правила очень понятны в порядке инсульта).
Ссылка на игровую площадку на Гитубе
- Чтобы использовать функцию XPCShowView: Откройте File Navigator и File Инспектор коммунальных услуг
- Щелкните файл детской площадки и в FUI (выберите Запуск в симуляторе)
- Для доступа к редактору помощника: выберите меню "Вид" > "Редактор помощников"
- Чтобы просмотреть ресурсы/источники, щелкните правой кнопкой мыши файл детской площадки в Finder и покажите содержимое пакета.
- Если Playground пуста при открытии, скопируйте файл на рабочий стол и снова запустите (ошибка?)
Код площадки для игр
import CoreText
import Foundation
import UIKit
import QuartzCore
import XCPlayground
//research layers
//var l:CALayer? = nil
//var txt:CATextLayer? = nil
//var r:CAReplicatorLayer? = nil
//var tile:CATiledLayer? = nil
//var trans:CATransformLayer? = nil
//var b:CAAnimation?=nil
// Setup playground to run in full simulator (⌘-0:Select Playground File; ⌘-alt-0:Choose option Run in Full Simulator)
//approach 2 using a custom stroke font requires special font without an outline whose path is the actual fill
var customFontPath = NSBundle.mainBundle().pathForResource("cwTeXFangSong-zhonly", ofType: "ttf")
// Within the playground folder create Resources folder to hold fonts. NB - Sources folder can also be created to hold additional Swift files
//ORTE1LOT.otf
//NISC18030.ttf
//cwTeXFangSong-zhonly
//cwTeXHei-zhonly
//cwTeXKai-zhonly
//cwTeXMing-zhonly
//cwTeXYen-zhonly
var customFontData = NSData(contentsOfFile: customFontPath!) as! CFDataRef
var error:UnsafeMutablePointer<Unmanaged<CFError>?> = nil
var provider:CGDataProviderRef = CGDataProviderCreateWithCFData ( customFontData )
var customFont = CGFontCreateWithDataProvider(provider) as CGFont!
let registered = CTFontManagerRegisterGraphicsFont(customFont, error)
if !registered {
println("Failed to load custom font: ")
}
let string:NSString = "五"
//"ABCDEFGHIJKLMNOPQRSTUVWXYZ一二三四五六七八九十什我是美国人"
//use the Postscript name of the font
let font = CTFontCreateWithName("cwTeXFangSong", 72, nil)
//HiraMinProN-W6
//WeibeiTC-Bold
//OrachTechDemo1Lotf
//XinGothic-Pleco-W4
//GB18030 Bitmap
var count = string.length
//must initialize with buffer to enable assignment within CTFontGetGlyphsForCharacters
var glyphs = Array<CGGlyph>(count: string.length, repeatedValue: 0)
var chars = [UniChar]()
for index in 0..<string.length {
chars.append(string.characterAtIndex(index))
}
//println ("\(chars)") //ok
//println(font)
//println(chars)
//println(chars.count)
//println(glyphs.count)
let gotGlyphs = CTFontGetGlyphsForCharacters(font, &chars, &glyphs, chars.count)
//println(glyphs)
//println(glyphs.count)
if gotGlyphs {
// loop and pass paths to animation function
let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)
//how to break the path apart?
let path = UIBezierPath(CGPath: cgpath)
//path.hashValue
//println(path)
// all shapes are closed paths
// how to distinguish overlapping shapes, confluence points connected by line segments?
// compare curve angles to identify stroke type
// for curves that intersect find confluence points and create separate line segments by adding the linesegmens between the gap areas of the intersection
/* analysis of movepoint
This method implicitly ends the current subpath (if any) and
sets the current point to the value in the point parameter.
When ending the previous subpath, this method does not actually
close the subpath. Therefore, the first and last points of the
previous subpath are not connected to each other.
For many path operations, you must call this method before
issuing any commands that cause a line or curve segment to be
drawn.
*/
//CGPathApplierFunction should allow one to add behavior to each glyph obtained from a string (Swift version??)
// func processPathElement(info:Void, element: CGPathElement?) {
// var pointsForPathElement=[UnsafeMutablePointer<CGPoint>]()
// if let e = element?.points{
// pointsForPathElement.append(e)
//
// }
// }
//
// var pathArray = [CGPathElement]() as! CFMutableArrayRef
//var pathArray = Array<CGPathElement>(count: 4, repeatedValue: 0)
//CGPathApply(<#path: CGPath!#>, <#info: UnsafeMutablePointer<Void>#>, function: CGPathApplierFunction)
// CGPathApply(path.CGPath, info: &pathArray, function:processPathElement)
/*
NSMutableArray *pathElements = [NSMutableArray arrayWithCapacity:1];
// This contains an array of paths, drawn to this current view
CFMutableArrayRef existingPaths = displayingView.pathArray;
CFIndex pathCount = CFArrayGetCount(existingPaths);
for( int i=0; i < pathCount; i++ ) {
CGMutablePathRef pRef = (CGMutablePathRef) CFArrayGetValueAtIndex(existingPaths, i);
CGPathApply(pRef, pathElements, processPathElement);
}
*/
//given the structure
let pathString = path.description
// println(pathString)
//regex patthern matcher to produce subpaths?
//...
//must be simpler method
//...
/*
NOTES:
Use assistant editor to view
UIBezierPath String
http://www.google.com/fonts/earlyaccess
Stroke-based fonts
Donald Knuth
*/
// var redColor = UIColor.redColor()
// redColor.setStroke()
var pathLayer = CAShapeLayer()
pathLayer.frame = CGRect(origin: CGPointZero, size: CGSizeMake(300.0,300.0))
pathLayer.lineJoin = kCALineJoinRound
pathLayer.lineCap = kCALineCapRound
//pathLayer.backgroundColor = UIColor.whiteColor().CGColor
pathLayer.strokeColor = UIColor.redColor().CGColor
pathLayer.path = path.CGPath
// pathLayer.backgroundColor = UIColor.redColor().CGColor
// regarding strokeStart, strokeEnd
/* These values define the subregion of the path used to draw the
* stroked outline. The values must be in the range [0,1] with zero
* representing the start of the path and one the end. Values in
* between zero and one are interpolated linearly along the path
* length. strokeStart defaults to zero and strokeEnd to one. Both are
* animatable. */
var pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = 10.0
pathAnimation.fromValue = NSNumber(float: 0.0)
pathAnimation.toValue = NSNumber(float: 1.0)
/*
var fillAnimation = CABasicAnimation (keyPath: "fill")
fillAnimation.fromValue = UIColor.blackColor().CGColor
fillAnimation.toValue = UIColor.blueColor().CGColor
fillAnimation.duration = 10.0
pathLayer.addAnimation(fillAnimation, forKey: "fillAnimation") */
//given actual behavior of boundary animation, it is more likely that some other animation will better simulate a written stroke
var someView = UIView(frame: CGRect(origin: CGPointZero, size: CGSizeMake(300.0, 300.0)))
someView.layer.addSublayer(pathLayer)
//SHOW VIEW IN CONSOLE (ASSISTANT EDITOR)
XCPShowView("b4Animation", someView)
pathLayer.addAnimation(pathAnimation, forKey: "strokeEndAnimation")
someView.layer.addSublayer(pathLayer)
XCPShowView("Animation", someView)
}