如何使用Swift / iOS来animation人类书写的中风?

目的

我正尝试使用字形生成的UIBezierPath创build人类写作的animation近似。 我明白,我已经阅读了许多类似于我的UIBezierpath问题(但不是相同的)。 我的目标是创build一个逼近人物表情的animation。

背景

我已经创build了一个操场,用来更好地理解用于animation字形的path,就好像它们是用手绘制的一样。

Apple CAShapeLayer(strokeStart和strokeEnd)中的一些概念在animation时似乎并不像预期的那样运行。 我认为,一般人们往往会像用笔具(基本上是一条直线或曲线)来思考中风。 我们认为中风和填充在一起是一条线,因为我们的书写工具不区分中风和填充。

但是,当animation时,path的轮廓由线段构成(填充分开处理,不清楚如何animation填充的位置?)。 我想要实现的是一个自然的人类书写线条/曲线,它显示了中风的开始和结束,以及随着animation从开始到结束的移动而添加的部分填充。 最初这似乎很简单,但我认为它可能需要animation的填充位置(不知道如何做到这一点),笔画开始/结束(不知道这是否需要给出意外的注意事项与animation如何执行上述),并使用的子path(如何从一个已知的path重build)。

方法1

所以,我已经考虑了Path(CGPath / UIBezierPath)的概念。 每条path实际上都包含构build字形所需的所有子path,因此可能recursion这些子path并使用CAKeyframeAnimation / CABasicAnimations和显示部分构build的子path的animation组将是一个好方法(尽pipe每个子path的填充位置和笔画仍然需要从开始到结束?)。

这种方法导致了一个精炼的问题:

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

如何使用path/子path信息使用书写工具绘制填充和描边animation? (看起来这意味着需要同时animationCALayer的strokeStart / strokeEnd,position和path属性)

注意:

正如在代码中可以看到的那样,我确实从字形中获得了完成的path。 我可以看到path描述给了我类似于path的信息。 一个人如何获取这些信息,并将其重新组合成一个数组 子path 人可写的笔画?

(这里的想法是将点信息转换成类人类笔画的新数据types,这意味着要求algorithm识别每个填充的开始,斜率,端点和边界)

提示

我已经在Pleco(一个成功实现类似algorithm的iOS应用程序)中注意到,每个笔画都是由描述可写入笔画的闭合path组成的。 UIBezierPath有一个基于连续填充的封闭path。 需要一种algorithm来改进重叠填充以为每个笔划types创build不同的闭合path。

Erica Sadun在github上有一组path实用程序 。 我还没有完全探索这些文件,但他们可能certificate有用的确定离散笔画。

UIBezierpath结构似乎基于连续线段/曲线的概念。 填充交点处出现汇合点,代表定向path变化。 是否可以计算曲线/线段的笔画/填充angular度,并search其他曲线/线条以获得相应的合stream点? (即将一条线段跨越交叉填充的间隙连接起来,以产生两条独立的path – 假设一条线路拾取点并用新的线段/曲线重新创buildpath)

内省:有一个更简单的方法吗? 我错过了一个关键的API,一本书还是更好的方法来解决这个问题?

一些替代方法(无用 – 需要加载gif或flash)才能产生所需的结果:

很好的例子(使用Flash) ,performance层显示书写笔画的进展。 (如果可能的话,这是我想在Swift / iOS中近似) – ( alt链接 – 请参阅左侧的animation图像)

显示使用渐进式path和填充来近似书写笔画特征(animation不平滑并需要外部资源) 的不太好的示例 :

不太好

一个Flash版本 – 我很熟悉创buildFlashanimation,但我不愿意在1000年实现这些(不要太在意它不支持在iOS上,虽然我也可以转换algorithm,利用HTML5canvas与CSSanimation)。 但是,这一思路似乎有点远,毕竟,我想要的path信息存储在我从提供的字体/string中提取的字形中。

方法2

我正在考虑使用基于笔划的字体而不是基于轮廓的字体来获取正确的path信息(即填充被表示为path的地方)。 如果成功,这种方法将比接近笔画,笔画types,交叉点和笔画顺序更清洁。 我已经向苹果提交了一个雷达,build议在iOS中添加基于笔画的字体(#20426819)。 尽pipe如此,我仍然没有放弃形成algorithm来解决bezierpath上的线段和曲线的部分笔画,完整笔画,交点和合stream点。

根据讨论/答案更新思想

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

笔画顺序是重要的,大多数语言(在这种情况下,中文)已经明确定义了笔画types和笔顺顺序规则 ,似乎提供了一种基于每个CGPathElement提供的点信息来确定types和顺序的机制。

CGPathApply和CGPathApplierFunction作为枚举子path(保存到数组并应用填充animation)

一个蒙版可能被应用到图层来显示一部分子图层(我以前没有使用过这个属性,但是看起来如果我可以将一个蒙版图层移动到可能有助于填充animation的子path上)?

为每个path定义了大量的点。 就好像贝塞尔path只是使用字形的轮廓来定义的。 这个事实使得理解交叉的开始,结束和联合填补了消除具体填充的重要因素。

额外的外部库可用于更好地解决中风行为。 像藏红花types系统或其衍生物之一的其他技术可能适用于这个问题领域。

最简单的解决方法就是,可用的iOS字体是轮廓字体,而不是基于笔画的字体。 一些商业制造商确实生产基于笔划的字体。 如果您有其中一个用于testing,请随时使用到操场文件的链接 。

我认为这是一个普遍的问题,我将继续更新这个post,因为我正在寻求解决scheme。 如果需要进一步的信息,或者我可能会遗漏一些必要的概念,请在评论中告诉我。

面具

可能的scheme

我总是在寻找最简单的解决scheme。 问题来源于字体的结构是轮廓字体而不是基于笔划。 我find了一个基于笔画的字体样本来testing和使用它来评估概念certificate ( 请参阅video )。 现在我正在寻找一个扩展的单笔字体(包括中文字符)来进一步评估。 一个不那么简单的解决scheme可能是find一种方法来创build填充之后的笔画,然后使用简单的2D几何来评估哪个笔划要先创build(例如,中文规则在笔画顺序上非常清晰)。

链接到Github上的Playground

  • 要使用XPCShowView函数:打开文件导航器和文件实用程序检查器
  • 点击操场文件并在FUI中(select在模拟器中运行)
  • 访问助手编辑器:转到菜单视图>助理编辑器
  • 要查看资源/资源,请右键单击Finder中的操作文件并显示包装内容
  • 如果Playground在打开时空白,将文件复制到桌面并重新打开(bug?)

游乐场代码

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) } 

你想看看CGPathApply(这是你的更精确的问题的简短答案)。 你提供一个函数,它会调用该函数为每个元素(这些将线和弧和closures)的path。 您可以使用它来重build每个closures的项目,并将其存储到列表中。 然后你可以找出每个项目的方向(我认为这实际上可能是最难的部分),而不是使用strokeStart / strokeEnd每个子path将其绘制在一个带有蒙版的图层中,并将蒙版移动到图层上。

Apple CAShapeLayer(strokeStart和strokeEnd)中的一些概念在animation时似乎并不像预期的那样运行。

但是, strokeEnd肯定是你想要做的。 使用多个CAShapeLayers在另一个之上,每一个代表笔的一个笔划,以形成所需的字符形状。

在这里输入图像说明

进度报告

这个答案是为了强调在解决这个问题上取得的重大进展。 有了这么多的细节,我只想澄清一下解决scheme的进展,最终(达到的时候),明确的答案。 虽然我select了一个有用的答案,请考虑这个post的完整解决scheme。

方法#1

使用现有的UIBezierPath信息来标识path的段(最终)利用这些段(及其坐标)来划分每个子​​path(根据可用的语言规则)。

(当前的思考)

埃里卡· 萨顿( Erica Sadun)在Github上制作了一个SwiftSlowly repo,它提供了许多path上的function,包括在UIBezierpath上的一个看起来很有前途的库,Line Intersections和很多function来处理这些项目。 我没有时间来完整地审查,但我可以想象,可以根据已知的笔画types将一个给定的path分解成多个部分。 一旦所有笔画types对于给定path是已知的,则可以评估相对path坐标以分配笔划顺序 。 之后,根据笔画顺序简单地设置笔画(UIBezierPath的子类)的animation。

方法#2

使用基于笔划的字体而不是基于轮廓的字体。

(当前的思考)

我find了一个基于笔划的字体样本,并能够对笔画进行animation处理。 这些字体带有内置的笔画顺序。 我没有访问完整的基于笔划的字体,也支持中文,但鼓励任何知道这种字体的人在评论中回复。

我向苹果公司推荐他们在未来的版本中提供基于笔画的字体。 上面的问题中包含Swift游乐场笔记和文件(带有笔划字体样本)。 如果您对添加到此解决scheme有build设性的意见,请发表评论或发布答案。