web-dev-qa-db-ja.com

Swift / iOSを使用して人間が書いたストロークをアニメーション化する方法は?

目的

グリフから生成されたUIBezierPathを使用して、人間の文章のアニメーション近似を作成しようとしています。私は理解していて、私のように聞こえる(しかし同じではない)多くのUIBezierPathの質問を読みました。私の目標は、文字や文字を書く人間の外観に近いアニメーションを作成することです。

背景

グリフを手で描いたかのようにアニメーション化するためのパスをよりよく理解するために使用している遊び場を作成しました。

Apple CAShapeLayer(strokeStartおよびstrokeEnd)のいくつかの概念は、アニメーション化されたときに実際には期待どおりに動作しないようです。一般的に、人々はストロークを次のように考える傾向があると思います。筆記具(基本的には直線または曲線)。筆記具はストロークと塗りつぶしを区別しないため、ストロークと塗りつぶしを一緒に線と見なします。

ただし、アニメーション化すると、パスのアウトラインは線分で構成されます(塗りつぶしは個別に扱われ、塗りつぶしの位置をアニメーション化する方法が不明ですか?)。私が達成したいのは、アニメーションが最初から最後まで移動するときに追加される塗りの部分とともに、ストロークの開始と終了を示す自然な人間が書いた線/曲線です。最初はこれは単純に見えますが、塗りつぶしの位置(これを行う方法がわからない)、ストロークの開始/終了(上記のアニメーションの実行方法に関する予期しない警告があるため、これが必要かどうかはわかりません)、および使用が必要になる場合があります。サブパスの数(既知のパスから再構築する方法)。

アプローチ1

そこで、パス(CGPath/UIBezierPath)のアイデアを検討しました。各パスには、実際にはグリフの作成に必要なすべてのサブパスが含まれているため、これらのサブパスを繰り返し、CAKeyframeAnimation/CABasicAnimationsと、部分的に作成されたサブパスを示すアニメーショングループを使用することをお勧めします(ただし、各サブパスの塗りつぶし位置とストロークは引き続き使用できます)。最初から最後までアニメーション化する必要がありますか?).

このアプローチは、洗練された質問につながります。

完全なUIBezierPath/CGPathがある場合にUIBezierPath/CGPath(サブパス)にアクセスして作成する方法は?

パス/サブパス情報を使用して、筆記具で描画したかのように塗りと線をアニメーション化するにはどうすればよいですか? (これは、CALayerのstrokeStart/strokeEnd、position、およびpathプロパティを同時にアニメーション化する必要があることを意味しているようです)

注意:

コードで確認できるように、グリフから取得した完成したパスがあります。パスの説明からパスのような情報が得られることがわかります。その情報をどのように取得し、次の配列として再キャストするのでしょうか。 サブパス 人間が書くことができるストローク?

(ここでのアイデアは、ポイント情報を人間のようなストロークの新しいデータ型に変換することです。これは、各塗りつぶしの開始、傾斜、終点、および境界を識別するアルゴリズムの要件を意味します)

ヒント

Pleco(同様のアルゴリズムを正常に実装するiOSアプリ)で、各ストロークは、人間が記述できるストロークを表す閉じたパスで構成されていることを指摘しました。 UIBezierPathには、連続的に接続された塗りつぶしに基づく閉じたパスがあります。 stroke-type ごとに異なる閉じたパスを作成するために、重複する塗りつぶしを調整するアルゴリズムが必要です。

Erica Sadunには、githubで利用可能な パスユーティリティ のセットがあります。これらのファイルについては十分に調べていませんが、個別のストロークを決定するのに役立つ可能性があります。

UIBezierPath構造は、連続する線分/曲線の概念に基づいているようです。塗りつぶしの交点に合流点が表示されます。これは、方向性のあるパスの変更を表します。曲線/線分のストローク/塗りつぶし角度を計算し、他の曲線/線で対応する合流点を検索できますか? (つまり、交差する塗りつぶしのギャップを越えて線分を接続し、2つの別々のパスを生成します-1つがポイントを取得し、新しい線分/曲線でパスを再作成したと仮定します)

内省的に:もっと簡単な方法はありますか?重要なAPI、本、またはこの問題へのより良いアプローチが不足していますか?

望ましい結果を生成するためのいくつかの代替方法(役に立たない-gifまたはフラッシュをロードする必要があります):

良い例(Flashを使用) 書かれたストロークの進行を示すプレゼンテーション層があります。 (可能であれば、これは私がSwift/iOSで概算したいものです) ---(alt link -左のアニメーション画像を参照してください)

あまり良い例ではありません 書き込まれたストローク特性を概算するためのプログレッシブパスと塗りつぶしの使用を示しています(アニメーションはスムーズではなく、外部リソースが必要です):

less-good

Flashバージョン -私はFlashアニメーションの作成に精通していますが、1000年代にこれらを実装することに嫌気がさしています(iOSではサポートされていないことは言うまでもありませんが、アルゴリズムを変換してcssアニメーション付きのHTML5キャンバス)。しかし、この考え方は少し遠いように思えます。結局のところ、必要なパス情報は、提供されたフォント/文字列から抽出したグリフに格納されています。

アプローチ2

正しいパス情報(つまり、塗りつぶしがパスとして表されるもの)を取得するために、 アウトラインベース フォントではなく ストロークベース フォントの使用を検討しています。成功した場合、このアプローチは、ストローク、ストロークタイプ、交差、およびストロークの順序を概算するよりもクリーンになります。私はすでにAppleにストロークベースのフォントをiOSに追加することを提案している(#20426819)にレーダーを提出しました。この努力にもかかわらず、私はまだ部分的に解決するアルゴリズムの形成をあきらめていません-線分からのストローク、フルストローク、交差点、および合流点-ベジェパスで見つかったセグメントと曲線。

ディスカッション/回答に基づいて更新された考え

以下の追加情報は、以下の進行中の会話と回答に基づいて提供されます。

筆順は重要であり、ほとんどの言語(この場合は中国語)は明確に定義されています 筆順 および 筆順規則 これらはタイプと順序を決定するメカニズムを提供するように見えます各CGPathElementで提供されるポイント情報。

CGPathApplyとCGPathApplierFunctionは、サブパスを列挙する手段として有望であるように見えます(配列に保存され、塗りつぶしアニメーションを適用します)

マスクをレイヤーに適用して、サブレイヤーの一部を表示することができます(これまでこのプロパティを使用したことはありませんが、塗りつぶしのアニメーション化に役立つ可能性のあるサブパス上でマスクされたレイヤーを移動できると思われますか?)

パスごとに多数のポイントが定義されています。 BezierPathがグリフのアウトラインのみを使用して定義されているかのように。この事実により、クロスフィルの開始、終了、および結合を理解することが、特定のフィルを明確にするための重要な要素になります。

追加の 外部ライブラリ が利用可能であり、ストロークの動作をより適切に解決できる可能性があります。 Saffron Type System またはその派生物の1つなどの他の技術は、この問題領域に適用できる可能性があります。

ストロークをアニメーション化するだけの最も簡単なソリューションの基本的な問題は、使用可能なiOSフォントがストロークベースのフォントではなくアウトラインフォントであるということです。一部の商用メーカーは、ストロークベースのフォントを作成しています。テスト用にこれらのいずれかをお持ちの場合は、 プレイグラウンドファイルへのリンク を自由に使用してください。

これは一般的な問題だと思います。解決に向けて投稿を更新していきます。さらに詳しい情報が必要な場合、または必要な概念のいくつかが欠落している可能性がある場合は、コメントでお知らせください。

mask

考えられる解決策

私は常に可能な限り簡単な解決策を探しています。この問題は、フォントの構造がストロークベースではなくアウトラインフォントであることに起因します。テストするストロークベースのフォントのサンプルを見つけ、それを使用して概念実証ビデオを参照 )を評価しました。私は現在、さらに評価するために、拡張されたシングルストロークフォント(中国語の文字を含む)を探しています。それほど単純ではない解決策は、塗りつぶしに続くストロークを作成し、単純な2Dジオメトリを使用して、最初にアニメーション化するストロークを評価する方法を見つけることです(たとえば、中国のルールはストロークの順序が非常に明確です)。

Githubのプレイグラウンドへのリンク

  • XPCShowView関数を使用するには:ファイルナビゲータとファイルユーティリティインスペクタを開きます
  • プレイグラウンドファイルをクリックし、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)




}
36
Tommie C.

CGPathApplyを見たいと思います(これは、より洗練された質問に対する短い答えです)。関数を指定すると、パスの各要素(これらは線と円弧で閉じます)に対してその関数が呼び出されます。これを使用して、閉じた各アイテムを再構築し、それらをリストに格納できます。次に、各アイテムがどの方向に描画されるかを把握し(これは実際には最も難しい部分だと思います)、strokeStart/strokeEndを使用するのではなく、各サブパスがマスクを使用してレイヤーに描画し、レイヤー全体にマスクを移動します。

6
Stripes

Apple CAShapeLayer(strokeStartおよびstrokeEnd)のいくつかの概念は、アニメーション化されたときに実際には期待どおりに動作しないようです。

しかし、確かにstrokeEndをアニメーション化することは正確にあなたがやりたいことです。複数のCAShapeLayerを重ねて使用します。各レイヤーは、ペンの1ストロークを表し、目的の文字の形状を形成します。

enter image description here

6
matt

経過報告

この回答は、この質問の解決に関して行われている重要な進歩を強調するために投稿されています。質問に非常に多くの詳細が追加されたので、私はソリューションの進捗状況を明確にし、最終的に(達成された場合)、決定的な答えを明らかにしたいと思いました。役に立った回答を選択しましたが、完全な解決策については、この投稿を検討してください。

アプローチ#1

既存のUIBezierPath情報を使用して、パスのセグメントを識別します(そして、最終的には、それらのセグメント(およびそれらの座標)を使用して、(使用可能な言語規則に従って)各サブパスをストロークします。

(現在の考え方)

Erica Sadunは、Githubで SwiftSlowly リポジトリを作成しています。これは、(UIBezierPathの)セグメント上の有望なライブラリ、ライン交差、およびこれらのアイテムに作用する多くの関数を含む、パス上の多くの関数を提供します。完全に確認する時間はありませんでしたが、既知の ストロークタイプ に基づいて、特定のパスをセグメントに分解する可能性があると想像できます。特定のパスのすべてのストロークタイプがわかったら、相対パス座標を評価して stroke-order を割り当てることができます。その後、ストロークの順序に従ってストローク(UIBezierPathのサブクラス)をアニメーション化します。

アプローチ#2

アウトラインベースのフォントの代わりにストロークベースのフォントを使用します。

(現在の考え方)

ストロークベースのフォントのサンプルを見つけて、ストロークをアニメーション化することができました。これらのフォントには、書き順が組み込まれています。私は中国語もサポートする完成したストロークベースのフォントにアクセスできませんが、そのようなフォントの知識がある人は誰でもコメントで返信することをお勧めします。

Appleは、将来のリリースでストロークベースのフォントを提供することをお勧めします。Swift Playgroundのメモとファイル(サンプルのストロークフォントを含む)は上記の質問に含まれています。このソリューションに追加する建設的なものがある場合は、コメントするか、回答を投稿してください。

筆順規則

Clear Chinese website で説明されている筆順規則を参照してください。

stroke-order-rules

1
Tommie C.