如何使用Swift/iOS为人类笔画动画?

前端之家收集整理的这篇文章主要介绍了如何使用Swift/iOS为人类笔画动画?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
目的

我试图创建一个人类写作的动画近似,使用从字形生成的UIBezierPath。我理解,我已经阅读了许多UIBezierPath问题听起来类似于我(但不一样)。我的目标是创建一个动画,近似一个人的表情,字符和字母的写作。

背景

我创建了一个操场,我使用更好地了解用于动画字形的路径,就像他们是手工绘制。

在Apple CAShapeLayer(strokeStart和strokeEnd)中的几个概念真的似乎不像预期的那样动画。我认为一般来说,人们倾向于把笔画看作是用书写工具做的(基本上是直线或曲线)。我们认为笔画和填充在一起是一条线,因为我们的书写工具不区分笔画和填充。

但是当动画时,路径的轮廓由线段构成(填充被单独处理,并且不清楚如何对填充的位置进行动画处理)。我想要实现的是一个自然的人类写的线/曲线,显示一个笔画的开始和结束,以及随着动画从开始到结束而添加的填充部分。最初这看起来很简单,但我认为它可能需要动画填充位置(不确定如何做),笔画开始/结束(不确定这是否需要给出如上所述的动画执行的意想不到的注意),并使用的子路径(如何从已知路径重建)。

方法1

所以,我已经考虑了一个Path(CGPath / UIBezierPath)的想法。每个路径实际上包含构造字形所需的所有子路径,因此可能递归这些子路径,并且使用CAKeyframeAnimation / CABasicAnimations和显示部分构造的子路径的动画组将是一个好的方法(尽管每个子路径的填充位置和笔画仍然需要动画从开始到结束?)。

这种方法导致精确的问题:

如果有一个完整的UIBezierPath / CGPath,如何访问和创建UIBezierPath / CGPath(子路径)?

如何使用路径/子路径信息使用书写工具绘制填充和笔划动画? (似乎这意味着需要同时动画CALayer的strokeStart / strokeEnd,position和path属性)

注意:

正如在代码中可以看到的,我有从字形获得的完成的路径。我可以看到路径描述给我类似路径的信息。如何获取这些信息,并将其重写为一个子路径人类可写的笔画数组?

(这里的想法是将点信息转换为人类笔划的新数据类型,这意味着需要一种算法来识别每个填充的开始,斜率,端点和边界)

提示

我注意到在Pleco(一个成功实现类似的算法的iOS应用程序),每个笔画是由一个闭合的路径,描述人类可写的笔画组成。 UIBezierPath具有基于连续连接填充的封闭路径。需要一种算法来改进重叠填充,以为每个stroke-type创建不同的封闭路径。

Erica Sadun有一组path utilities在github上可用。我没有完全探索这些文件,但它们可能被证明是有用的确定离散笔画。

UIBezierPath结构似乎基于连续线段/曲线的概念。在填充的交点处出现汇合点,这表示方向性路径改变。可以计算曲线/线段的行程/填充角,并搜索其他曲线/线的相应的合流点吗? (即,跨越相交填充的间隙连接线段以产生两个单独的路径 – 假设一个点拾取点并用新的线段/曲线重新创建路径)

内省:有更简单的方法吗?我缺少一个关键的API,一本书或更好的方法解决这个问题?

一些替代方法(不是有用的 – 需要加载gif或闪存)以产生所需的结果:

Good Example (using Flash)与示出书写笔划的进展的表示层。 (如果可能,这是我想在Swift / iOS中近似) – (alt link – 看到左侧的动画图像)

A less good example示出使用渐进路径和填充来近似书写笔画特征(动画不平滑并且需要外部资源):

A Flash version – 我熟悉创建Flash动画,但我不愿意在1000年实现这些(没有太多提及,它不支持在iOS上,虽然我也可以转换算法来利用HTML5画布与CSS动画)。但是这种思路似乎有点远,毕竟,我想要的路径信息存储在我从字体/字符串中提取的字形中。

方法2

我正在考虑使用stroke-based字体而不是outline-based字体来获得正确的路径信息(即填充表示为路径)。如果成功,这种方法将比近似笔划,笔划类型,交叉点和笔划顺序更干净。我已经提交了一个雷达苹果建议基于笔画的字体添加到iOS(#20426819)。尽管做了这些努力,我仍然没有放弃形成一个算法,从贝塞尔路径上找到的线段和曲线解决部分笔画,全笔画,交叉点和汇合点。

基于讨论/答案的更新思想

根据以下任何正在进行的对话和答案提供以下附加信息。

笔划顺序很重要,大多数语言(在这种情况下为中文)已经清楚地定义了stroke typesstroke order rules,它们似乎提供了一种基于每个CGPathElement提供的点信息来确定类型和顺序的机制。

CGPathApply和CGPathApplierFunction似乎有希望作为枚举子路径(保存到数组并应用填充动画)的手段,

可以将掩模施加到该层以暴露出子层的一部分
(我没有使用这个属性之前,但似乎如果我可以移动掩蔽的层在子路径,可能有助于动画填充?)

为每个路径定义了大量的点。就像只使​​用字形的轮廓来定义BezierPath一样。这个事实使得理解交叉填充的开始,结束和联合是消除特定填充的歧义的重要因素。

额外的external libraries可用,可以允许更好地解决冲程行为。其他技术如Saffron Type System或其衍生产品之一可能适用于此问题域。

最简单的解决方案的一个基本问题是动画的笔画是可用的iOS字体是轮廓字体,而不是基于笔画的字体。一些商业制造商生产基于笔画的字体。如果您有其中一个测试,请随意使用link to the playground file

我认为这是一个常见的问题,我会继续更新的职位,当我走向解决方案。如果需要更多信息,或者我可能缺少一些必要的概念,请在评论中告知我。

可能的解决方

我总是在寻找最简单的可能的解决方案。该问题起源于字体的结构是轮廓字体,而不是基于笔划。我发现一个基于笔画的字体的示例来测试和使用它来评估概念证明(see video)。我现在正在寻找一个扩展的单笔画字体(其中包括汉字)进一步评估。一个不太简单的解决方案可能是找到一种方法来创建一个笔画跟随填充,然后使用简单的2D几何来评估哪个笔画动画首先(例如中国的规则是非常清楚的笔顺)。

Link to Playground on Github

>使用XPCShowView函数:打开文件导航器和文件
工具检查器
>单击游乐场文件并在FUI中(选择
在模拟器中运行)
>要访问助理编辑器:转到菜单视图>助理编辑
>要查看资源/源,请右键单击Finder中的playground文件显示内容
>如果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,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)




}
你想看看CGPathApply(这是你更精确的问题的简短答案)。你为它提供一个函数,它将为每个元素(这些将是线和弧和关闭)调用函数的路径。您可以使用它来重建每个已关闭的项目,并将它们保存到列表中。然后你可以找出每个项目绘制哪个方向(我认为这可能实际上是最难的部分),而不是使用strokeStart / strokeEnd一个每个子路径在带有蒙版的图层中移动和移动蒙版在图层。
原文链接:https://www.f2er.com/swift/320935.html

猜你在找的Swift相关文章