- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
目标
我正在尝试使用从字形生成的UIBezierPath创建逼真的人类文字动画。我了解并且已经阅读了许多UIBezierPath问题,这些问题听起来与我的相似(但并不相同)。我的目标是创建一种动画,该动画近似执行字符和字母书写的人类的外观。
背景
我创建了一个游乐场,以便更好地了解用于对字形进行动画处理的路径,就好像它们是手工绘制的一样。
Apple CAShapeLayer中的几个概念(strokeStart和strokeEnd)在制作动画时似乎并没有达到预期的效果。我认为一般人会认为笔触就好像是用书写工具(基本上是直线或曲线)完成的一样。我们认为笔划和填充线是一条线,因为我们的书写工具无法区分笔划和填充。
但是,当设置动画时,路径的轮廓由线段构成(填充被单独处理,并且不清楚如何为填充位置设置动画吗?)。我要实现的是自然的人为笔迹/曲线,它显示了笔画的开始和结束以及随着动画从头到尾的移动而添加的填充部分。最初看起来很简单,但我认为可能需要对填充位置进行动画处理(不确定如何执行此操作),笔画的开始/结束(不确定是否需要这样做,因为上述动画的执行方式有意料之外的警告),并加以利用子路径(如何从已知路径进行重构)。
方法1
因此,我考虑了路径(CGPath/UIBezierPath)的概念。每个路径实际上都包含构造字形所需的所有子路径,因此也许可以重复使用这些子路径并使用CAKeyframeAnimation/CABasicAnimations和动画组来显示部分构造的子路径将是一个不错的方法(尽管每个子路径的填充位置和笔触仍会需要从头到尾进行动画处理?)。
这种方法导致了一个改进的问题:
如果拥有完整的UIBezierPath/CGPath,如何访问和创建UIBezierPath/CGPath(子路径)?
如何使用路径/子路径信息为填充和笔划设置动画,就像使用书写工具绘制的那样? (似乎这意味着需要同时为CALayer的strokeStart/strokeEnd,位置和路径属性设置动画)
注意:
正如人们在代码中可以看到的那样,我确实有从字形获得的完成路径。我可以看到,路径描述为我提供了类似路径的信息。人们将如何获取这些信息并将其重铸为一系列可写笔画的子路径?
(这里的想法是将点信息转换为类似于人的笔触的新数据类型。这意味着需要一种算法来识别每个填充的起点,斜率,终点和边界)
提示
我在Pleco(一个成功实现类似算法的iOS应用)中注意到,每个笔划均由描述人类可写笔划的闭合路径组成。 UIBezierPath具有基于连续连接的填充的闭合路径。需要一种算法来优化重叠填充,以为每个stroke-type创建不同的闭合路径。
埃里卡·萨顿(Erica Sadun)在github上有一组path utilities。我还没有完全研究过这些文件,但是它们可能对确定离散笔划很有用。
UIBezierPath结构似乎基于连续线段/曲线的概念。在填充的相交处出现汇合点,这些汇合点表示方向路径的变化。可以计算一条曲线/线段的笔触/填充角度,并在其他曲线/线中搜索相应的汇合点吗? (即,将线段连接到相交填充的间隙以产生两条单独的路径-假设一条线拾取了点并使用新的线段/曲线重新创建了路径)
内省(introspection):有没有更简单的方法?我是否缺少解决此问题的关键API,书籍或更好的方法?
一些替代方法(无用-需要加载gif或flash)以产生所需的结果:
Good Example (using Flash),带有表示笔划进度的表示层。 (如果可能的话,这就是我想要在Swift/iOS中近似的值)-(alt link-参见左侧的动画图像)
A less good example显示使用渐进路径和填充来近似笔画特征(动画不流畅,需要外部资源):
A Flash version-我对创建Flash动画很熟悉,但是我不希望在1000年代实现这些动画(尽管我可能还可以转换算法以利用带有CSS动画的HTML5 Canvas ,但我不太愿意在iOS上实现这些动画)。但是这种思路似乎有点遥远,毕竟,我想要的路径信息存储在我从提供的字体/字符串中提取的字形中。
方法2
我正在考虑使用stroke-based字体而不是outline-based字体来获取正确的路径信息(即一种将填充表示为路径的信息)。如果成功,此方法将比近似笔划,笔划类型,交点和笔划顺序更干净。我已经向苹果提交了一份雷达,建议将基于笔触的字体添加到iOS(#20426819)。尽管进行了这些努力,但我仍然没有放弃形成一种算法,该算法可以根据贝塞尔曲线路径上的线段和曲线来解析部分笔划,完整笔划,相交和汇合点。
根据讨论/答案更新思想
根据以下任何正在进行的对话和答案提供以下附加信息。
笔划顺序很重要,大多数语言(在这种情况下为中文)都明确定义了stroke types和stroke order rules,它们似乎提供了一种机制,可以根据每个CGPathElement提供的点信息来确定类型和顺序。
CGPathApply和CGPathApplierFunction看起来很有希望作为枚举子路径的一种方法(保存到数组并应用填充动画)
可以将掩模应用于该层以露出子层的一部分
(我以前没有使用过此属性,但是看来是否可以在子路径上移动一个可以帮助动画填充的 mask 层?)
每个路径定义了大量的点。好像BezierPath是仅使用字形的轮廓定义的。这一事实使了解交叉填充的开始,结束和并集成为消除特定填充歧义的重要因素。
可以使用其他external libraries,以便更好地解决笔划行为。其他技术,例如Saffron Type System或其派生词之一,也可以应用于此问题域。
最简单的仅对笔触进行动画处理的解决方案的一个基本问题是,可用的iOS字体是轮廓字体,而不是基于笔触的字体。一些商业制造商确实生产基于笔触的字体。如果您要测试其中的一种,请随时使用link to the playground file。
我认为这是一个普遍的问题,在寻求解决方案时,我将继续更新帖子。如果需要进一步的信息,或者我可能缺少某些必要的概念,请在评论中让我知道。
可能的解决方案
我一直在寻找最简单的解决方案。问题源于字体的结构是轮廓字体,而不是基于笔划的字体。我找到了一个基于笔触的字体样本进行测试,并将其用于评估概念证明(see video)。我现在正在寻找扩展的单笔触字体(包括汉字)以进一步评估。一个不太简单的解决方案可能是找到一种方法来创建跟随填充的笔触,然后使用简单的2D几何来评估首先要对哪个笔触进行动画处理(例如,中文规则在笔划顺序上非常清晰)。
Link to Playground on Github
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(这是对您更精确的问题的简短回答)。您为它提供一个函数,它将为路径的每个元素(这些元素为直线,圆弧和闭合线)调用该函数。您可以使用它来重构每个已关闭的项目,并将它们存储到列表中。然后,您可以找出每个项目的绘制方向(我认为这实际上可能是最难的部分),而不是使用strokeStart/strokeEnd一个子路径,每个子路径将其绘制为带有蒙版的图层并将蒙版移动到整个图层上。
关于ios - 如何使用Swift/iOS为人类笔画制作动画?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29365732/
我正在开发一个基于fabricjs 的图表工具。我们的工具有我们自己的形状集合,它是基于 svg 的。我的问题是当我缩放对象时,边框(描边)也会缩放。我的问题是:如何缩放对象但保持笔触宽度固定。请检查
我在 Canvas 上画线时遇到一点问题, 基本上我希望线条漂亮、柔软且半不透明,但 Canvas 似乎只想对最后渲染的线段执行此操作。 看看这里,您会看到最后绘制的线段很好,而且..我想要它)但是随
也许这是一个错误,但请检查一下。 https://codepen.io/Firsh/pen/LegGQq /* Only these work:*/ /* symbol{ overflow: visi
我已经在终端中启动了一个 python 脚本(该终端已关闭)并将其发送到后台。现在这个程序需要一些来自键盘的输入并一直等待。如何将输入“y\n”(字母“y”后跟一个 Enter)发送到该程序?假设它的
我想实现这样的效果: 有人知道如何在 Canvas 上画这样一条线吗? 最佳答案 再靠近一点: chalkPaint = new Paint(); chalkPaint.setStyl
我建立了一个very simple Twitter Instant Search为了好玩,使用 jQuery 和 PHP。我将一个事件绑定(bind)到搜索表单上的 keyup,并对 PHP 页面进行
使用通用 Windows 平台 (UWP) 使用 InkCanvas 控件 我似乎无法确定在使用 InkCanvas 时删除墨迹笔划的正确方法 - 有一个可以处理的事件“StrokeErased”。
我是一名优秀的程序员,十分优秀!