Подтвердить что ты не робот

Как оживить человеческий письменный удар с помощью Swift/iOS?

Цель

Я пытаюсь создать анимированную аппроксимацию человеческого письма, используя 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 - см. Анимационное изображение слева)

Менее хороший пример, показывающий использование прогрессивных путей и заливок для аппроксимации характеристик записанного штриха (анимация негладкая и требует внешних ресурсов):

less-good

Flash-версия - Я знаком с созданием флеш-анимаций, но я не желаю реализовывать их в 1000 (не говоря уже о том, что он не поддерживается на iOS, хотя Возможно, я мог бы также преобразовать алгоритм для использования холста HTML5 с анимацией css). Но эта линия мысли кажется немного далекой, в конце концов, информация о пути, которую я хочу, хранится в глифах, которые я извлек из предоставленных шрифтов/строк.

Подход 2

Я рассматриваю использование шрифта stroke-based, а не на основе получить правильную информацию о пути (т.е. тот, где заполнение представлено в виде пути). В случае успеха этот подход будет более чистым, чем аппроксимация штрихов, штрихового типа, пересечений и порядка инсульта. Я уже представил радиолокатору Apple, предлагая добавить в iOS шрифты на основе штриха (# 20426819). Несмотря на это, я до сих пор не отказался от формирования алгоритма, который разрешает частичные штрихи, полные штрихи, пересечения и точки слияния от сегментов линии и кривых, найденных на пути безье.

Обновленные мысли, основанные на обсуждении/ответах

Следующая дополнительная информация предоставляется на основе любых текущих разговоров и ответов ниже.

Порядок штрихов важен, и большинство языков (в данном случае китайцы) четко определили типы штрихов и правила порядка штрихов, которые, как представляется, обеспечивают механизм для определения типа и порядка на основе информации о точках, предоставляемой каждому элементу CGPathElement.

CGPathApply и CGPathApplierFunction кажутся перспективными как средство для перечисления подпутей (сохраненных в массиве и применения анимации заполнения)

Маска может быть применена к слою, чтобы выявить часть подслоя (Я раньше не использовал это свойство, но кажется, что если бы я мог перемещать маскированный слой поверх подпапок, которые могли бы помочь в анимации заполнения?)

Для каждого пути определено большое количество точек. Как будто BezierPath определяется только контур глифа. Этот факт позволяет понять, что начало, конец и объединение пересечений заполняют важный фактор для устранения неоднозначности конкретных заполнений.

Доступны дополнительные внешние библиотеки, которые могут позволить более эффективно решать поведение штрихов. Другие технологии, такие как Saffron Type System или одна из ее производных, могут быть применимы к этой проблемной области.

Основная проблема с простейшим решением просто анимации штриха заключается в том, что доступные шрифты iOS являются общими шрифтами, а не штриховыми шрифтами. Некоторые коммерческие производители выпускают штриховые шрифты. Пожалуйста, не стесняйтесь использовать ссылку для файла детской площадки, если у вас есть один из них для тестирования.

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

mask

Возможное решение

Я всегда ищу простейшее возможное решение. Проблема возникает из-за структуры шрифтов, которые являются общими шрифтами, а не штриховыми. Я нашел образец шрифта на основе штрихов для тестирования и использовал его для оценки доказательства концепции (см. Видео). Теперь я ищу расширенный шрифт с одним штрихом (который включает в себя иероглифы) для дальнейшей оценки. Менее простое решение может заключаться в том, чтобы найти способ создания штриха, который следует за заполнением, а затем использовать простую 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)




}
4b9b3361

Ответ 1

Вы хотите посмотреть на CGPathApply (это короткий ответ на ваш более утонченный вопрос). Вы предоставляете ему функцию, и она будет вызывать эту функцию для каждого элемента (это будут линии и дуги и замыкания) пути. Вы можете использовать это для восстановления каждого закрытого элемента и вставить их в список. Затем вы можете выяснить, в каком направлении нарисован каждый элемент (я думаю, что это может быть самая сложная часть), а затем использовать strokeStart/strokeEnd каждый из подкатегорий нарисовать его в слое с маской и переместить маску на слой.

Ответ 2

Несколько концепций в Apple CAShapeLayer (strokeStart и strokeEnd) действительно не работают так, как ожидалось при анимации.

Но, безусловно, анимация strokeEnd - это именно то, что вы хотите сделать. Используйте несколько CAShapeLayers поверх друг друга, каждый из которых представляет один штрих пера для формирования желаемой формы символа.

enter image description here

Ответ 3

Отчет о ходе работы

Этот ответ опубликован, чтобы подчеркнуть значительный прогресс, достигнутый в решении этого вопроса. С таким большим количеством деталей, добавленным к вопросу, я просто хотел прояснить прогресс в решении и в конечном счете (когда это было достигнуто) окончательный ответ. Хотя я и выбрал ответ, который был полезен, рассмотрите этот пост для полного решения.

Подход № 1

Использовать существующую информацию UIBezierPath для идентификации сегментов пути (и в конечном итоге) использовать эти сегменты (и их координаты) для обхода каждого подпути (в соответствии с доступными языковыми правилами).

(Текущее мышление)

Эрика Садун производит репортаж SwiftSlowly в Github, который предоставляет множество функций на дорожках, включая то, что кажется многообещающей библиотекой для сегментов (UIBezierPath), пересечения строк и множество функций для работы с этими элементами. У меня не было времени на просмотр полностью, но я могу представить, что можно деконструировать данный путь в сегменты на основе известных типов штрихов. Как только все типы хода известны для данного пути, можно затем оценить относительные координаты пути, чтобы назначить stroke-order. После этого просто анимируйте штрихи (подкласс UIBezierPath) в соответствии с их порядком хода.

Подход # 2

Используйте шрифт на основе штрихов вместо шрифта на основе структуры.

(Текущее мышление)

Я нашел образец шрифта на основе штриха и смог анимировать штрих. Эти шрифты имеют встроенный порядок инсульта. У меня нет доступа к завершенному шрифту, основанному на штрихах, который также поддерживает китайский язык, но поощряйте любого, кто знает такой шрифт, отвечать в комментариях.

Я сделал рекомендацию Apple о том, что они поставляют шрифты, основанные на штрихах, в будущих выпусках. Замечания Swift Playground и файлы (с типовыми штриховыми шрифтами) включены в вопрос выше. Прокомментируйте или отправьте ответ, если у вас есть что-то конструктивное, чтобы добавить к этому решению.