CoreGraphicsコードをSwift 3.0に変換する

Xcode 8.0が登場しました。
旧プロジェクトをXcode 8.0で開くと、Swift 3.0(あるいはSwift 2.4)に変換するよう促すダイアログが出現しました。

CoreGraphicsコードをSwift 3.0への自動コンバートにかけたところ、変換されない部分が残りました。

        let pathRef = CGMutablePath()
        CGPathMoveToPoint(pathRef, nil, 20, 0)
        CGPathAddLineToPoint(pathRef, nil, 200, 0)
        CGPathAddCurveToPoint(pathRef, nil, 205.523, 0, 210, 4.435, 210, 10)
        CGPathAddLineToPoint(pathRef, nil, 210, 122)
        CGPathAddCurveToPoint(pathRef, nil, 210, 127.565, 205.523, 132, 200, 132)
        CGPathAddLineToPoint(pathRef, nil, 10, 132)
        CGPathAddCurveToPoint(pathRef, nil, 4.477, 132, -0, 127.565, -0, 122)
        CGPathAddLineToPoint(pathRef, nil, -0, 20)
        CGPathAddCurveToPoint(pathRef, nil, -0, 9.087, 9.081, 0, 20, 0)
        pathRef.closeSubpath()

変換後、このようなコードになったのですが最終的には

        let pathRef = CGMutablePath()
        pathRef.move(to: CGPoint(x: 20, y: 0))
        pathRef.addLine(to: CGPoint(x: 200, y: 0))
        pathRef.addCurve(to: CGPoint(x: 210, y: 10), control1: CGPoint(x: 205.523, y: 0), control2: CGPoint(x: 210, y: 4.435))
        pathRef.addLine(to: CGPoint(x: 210, y: 122))
        pathRef.addCurve(to: CGPoint(x: 200, y: 132), control1: CGPoint(x: 210, y: 127.565), control2: CGPoint(x: 205.523, y: 132))
        pathRef.addLine(to: CGPoint(x: 10, y: 132))
        pathRef.addCurve(to: CGPoint(x: -0, y: 122), control1: CGPoint(x: 4.477, y: 132), control2: CGPoint(x: -0, y: 127.565))
        pathRef.addLine(to: CGPoint(x: -0, y: 20))
        pathRef.addCurve(to: CGPoint(x: 20, y: 0), control1: CGPoint(x: -0, y: 9.087), control2: CGPoint(x: 9.081, y: 0))
        pathRef.closeSubpath()

と変換したい。

文字列の処理がやりやすいのはなにかしらと考えて、急遽Rubyを学ぶことにしました。


文字列操作とファイル操作の基本的なやりかたを知ったので、以下の変換プログラム converter.rbを書きました。
CGPathMoveToPoint(pathRef, nil, 20, 0)

pathRef.move(to: CGPoint(x: 20, y: 0))

CGPathAddLineToPoint(pathRef, nil, 200, 0)

pathRef.addLine(to: CGPoint(x: 200, y: 0))

CGPathAddCurveToPoint(pathRef, nil, 205.523, 0, 210, 4.435, 210, 10)

pathRef.addCurve(to: CGPoint(x: 210, y: 10), control1: CGPoint(x: 205.523, y: 0), control2: CGPoint(x: 210, y: 4.435))

と変換するものです。

converter.rb

class Converter

    def convert()
        loop do 
            print "Filename? "
            fileName = gets.chomp
            
            if fileName == ""
                break
            end
            
            # Backup
            from = fileName
            to = "_" + fileName + ".bak"
            copy(from, to)
            
            # Conversion
            array = []
            File.open(fileName) do |file|
                file.each_line do |line|
                    line = convertCoreGraphicsCode(line)
                    array.push(line)
                end
            end
                
            # Writing
            File.open(fileName, "w") do |file|
                file.puts(array)
                puts fileName + " 変換終了"
            end
        end
    end
    
    def copy(from, to)
        File.open(from) do |input|
            File.open(to, "w") do |output|
                output.write(input.read)
            end
        end
    end

    def convertCoreGraphicsCode(line)
        if line.include?("CGPathMoveToPoint")
            return convertMoveToPoint(line)
        elsif line.include?("CGPathAddLineToPoint") 
            return convertAddLineToPoint(line)
        elsif line.include?("CGPathAddCurveToPoint")
            return convertAddCurveToPoint(line)
        else
            return line
        end
    end
    
    # CGPathMoveToPoint(clipPath, nil, 240, 122)
    # to
    # clipPath.move(to: CGPoint(x: 240, y: 122))
    def convertMoveToPoint(line)
        # indentを取得
        index = line.index("CGPathMoveToPoint")
        indent = line[0, index]
        
        # path名を含むかたまりを取得("CGPathMoveToPoint(clipPath,")
        pathStr = line.match(/CGPathMoveToPoint\(\w+,/)
        
        # path名を取得("clipPath")
        pathName = pathStr[0].sub("CGPathMoveToPoint\(", "").chop
        # puts pathName
        
        # 数値
        figuresStr = line.sub(/CGPathMoveToPoint\(\w+, nil,/, "").chomp.chop.lstrip
        figuresStr = " " + figuresStr
        figures = figuresStr.split(",")
        # puts figures
        
        dstStr = "%s%s.move(to: CGPoint(x:%s, y:%s))" % [indent, pathName, figures[0], figures[1]]
        # puts dstStr
        return dstStr
    end
    
    
    # CGPathAddLineToPoint(clipPath, nil, 240, 122)
    # to
    # clipPath.addLine(to: CGPoint(x: 240, y: 122))
    def convertAddLineToPoint(line)
        # indentを取得
        index = line.index("CGPathAddLineToPoint")
        indent = line[0, index]
        
        # path名を含むかたまりを取得("CGPathAddLineToPoint(clipPath,")
        pathStr = line.match(/CGPathAddLineToPoint\(\w+,/)
        
        # path名を取得("clipPath")
        pathName = pathStr[0].sub("CGPathAddLineToPoint\(", "").chop
        # puts pathName
        
        # 数値
        figuresStr = line.sub(/CGPathAddLineToPoint\(\w+, nil,/, "").chomp.chop.lstrip
        figuresStr = " " + figuresStr
        figures = figuresStr.split(",")
        # puts figures

        dstStr = "%s%s.addLine(to: CGPoint(x:%s, y:%s))" % [indent, pathName, figures[0], figures[1]]
        # puts dstStr
        return dstStr
    end
    
    # CGPathAddCurveToPoint(pathRef2, nil, 4.477, 132, 0, 127.565, 0, 122)
    # to
    # pathRef2.addCurve(to: CGPoint(x: 0, y: 122), control1: CGPoint(x: 4.477, y: 132), control2: CGPoint(x: 0, y: 127.565))
    def convertAddCurveToPoint(line)

        # indentを取得
        index = line.index("CGPathAddCurveToPoint")
        indent = line[0, index]
        
        # path名を含むかたまりを取得("CGPathAddCurveToPoint(pathRef2,")
        pathStr = line.match(/CGPathAddCurveToPoint\(\w+,/)
        
        # path名を取得("pathRef2")
        pathName = pathStr[0].sub("CGPathAddCurveToPoint\(", "").chop
        # puts pathName
        
        # 数値を取得
        figuresStr = line.sub(/CGPathAddCurveToPoint\(\w+, nil,/, "").chomp.chop.lstrip
        figuresStr = " " + figuresStr
        figures = figuresStr.split(",")
        # puts figures
        
        dstStr = "%s%s.addCurve(to: CGPoint(x:%s, y:%s), control1: CGPoint(x:%s, y:%s), control2: CGPoint(x:%s, y:%s))" % [indent, pathName, figures[4], figures[5], figures[0], figures[1], figures[2], figures[3]]
        # puts dstStr
        return dstStr 
    end

end

converter = Converter.new
converter.convert()

変換したいswiftファイルがあるフォルダに、converter.rbをおき、Terminalでそのフォルダへ移動した後、

ruby converter.rb  

と入力するとプログラムが実行されます。
実行すると、

FileName?  

とたずねられるので、そこで変換したいファイル名を入力すると変換が行われます。
(このさい、”_元ファイル名.bak”というバックアップファイルも作成します。)
ファイル名を入力せずにreturnキーを押すと、プログラムは終了します。

関連

Autodesk Graphic(旧iDraw)はCore Graphicsのコードを生成できる – nackpan Blog

Autodesk Graphic(旧iDraw)で線のアウトライン化

20160622outline
Autodesk Graphicでパスツールやラインツールで線を引いた後、アウトライン化する方法。

図形を選択して
変更 > 線の外側
ss 2016-06-22 7.40.51

を行う。

また、
ウィンドウ > プロパティ
を表示して、そこで「線の外側」を選択することでも行える。
ss 2016-06-22 7.41.22


当初、メニューに「アウトライン」を含むものを探していましたが見つからず、アウトライン化はできないのかと思ってしまいました。実は「線の外側化」と日本語化されていたのでした。

参考
Create a Set of Flat Weather Icons | Autodesk Graphic Tutorials
ss 2016-06-22 21.51.48
こちらのチュートリアルのStep 8でアウトライン化を使っている。

Autodesk Graphic(旧iDraw)| テキストをパスに沿わせる

Autodesk Graphicのチュートリアルのページ。
Autodesk Graphic Tutorials

そのなかに、ロゴを作成するチュートリアルがあります。
ss 2016-02-16 2.56.08
Design a Logo with Curved Text | Autodesk Graphic Tutorials
ここで用いられている技法のうち、テキストをパスに沿わせる方法をメモ。

テキストをパスに沿わせる

テキストとパスを選択

テキストとパスをどちらも選択する。(Shiftキーを押しながら選択することで複数選択)
ss 2016-02-16 3.04.40

テキストを配置

メニューから「変更」 > 「パス上にテキストを配置」を選択
ss 2016-02-16 3.04.56
パス上にテキストが配置されます。
ss 2016-02-16 3.05.16

テキストを移動

左下の点を動かすことでテキストを移動
ss 2016-02-16 3.06.15

字間調整

アピアランスパネルから、アピアランス:テキスト > A↔︎B で数値を調整
ss 2016-02-16 3.23.35
数値をAutoから4にしてみると間隔が開きました
ss 2016-02-16 3.29.56


以上、Autodesk Graphicでテキストをパスに沿わせて描く方法でした。

Autodesk Graphic(旧iDraw)はCore Graphicsのコードを生成できる

Autodesk Graphic(旧iDraw)は、図形からCore Graphicsのコードを書き出せることを先日知ったので、記します。

言語を選択

環境設定 > 読み込み / 書き出し
で、SwiftかObjective-Cかを選択
ss 2016-02-07 8.05.12

クリップボードへ書き出し

必要なレイヤーを選択して、Ctrl+クリック(もしくは編集メニュー)から、
別名でコピー > Core Graphics Codeを選択。
ss 2016-02-14 22.19.30
これでクリッブボードにコードがコピーされています。
ペースト(Ctrl + Vなど)すると、以下のようなコードが貼り付けられます。

#if os(iOS)
    let ctx = UIGraphicsGetCurrentContext() // iOS
#else
    let contextPtr = NSGraphicsContext.currentContext()!.graphicsPort   // OS X
    let opaqueCtx = COpaquePointer(contextPtr)
    let ctx = Unmanaged<CGContext>.fromOpaque(opaqueCtx).takeUnretainedValue()
#endif

// enable the following lines for flipped coordinate systems
// CGContextTranslateCTM(ctx, 0, self.bounds.size.height)
// CGContextScaleCTM(ctx, 1, -1)
let colorSpace = CGColorSpaceCreateDeviceRGB()

let scaleFactor: CGFloat = 1;
// CGContextScaleCTM(ctx, scaleFactor, scaleFactor);

/*  Shape   */
let pathRef = CGPathCreateMutable()
CGPathMoveToPoint(pathRef, nil, 10, -0)
CGPathAddLineToPoint(pathRef, nil, 230, -0)
CGPathAddCurveToPoint(pathRef, nil, 235.523, -0, 240, 4.477, 240, 10)
CGPathAddLineToPoint(pathRef, nil, 240, 121)
CGPathAddCurveToPoint(pathRef, nil, 240, 126.523, 235.523, 131, 230, 131)
CGPathAddLineToPoint(pathRef, nil, 10, 131)
CGPathAddCurveToPoint(pathRef, nil, 4.477, 131, 0, 126.523, 0, 121)
CGPathAddLineToPoint(pathRef, nil, 0, 10)
CGPathAddCurveToPoint(pathRef, nil, 0, 4.477, 4.477, -0, 10, -0)
CGPathCloseSubpath(pathRef)

// (以下略)

省略してしまいましたが、インナーシャドー、グラデーション、輪郭線を用いた角丸四角形と三角形を描くのに130行程度のコードになっています。

生成されたコードからUIImageを作成

生成されたコードでは、CurrentContextを用いて、そこに描画しています。
サイズを指定してContextを作成し、それをCurrentContextとするのには
UIGraphicsBeginImageContextWithOptionsが使えます。

// サイズを指定してContextを作成し、それをCurrentContextとする
// (sizeはCGSize型の変数)  
UIGraphicsBeginImageContextWithOptions(size, false, 0)

// (ここにAutodesk Graphicsが生成したコード)

// CurrentContextからUIImage?の作成  
let image: UIImage? = UIGraphicsGetImageFromCurrentImageContext()

// このContextをスタックから取り除く(CurrentContextではなくなる)
UIGraphicsEndImageContext()


こんなかんじで、UIImageに持ってくることができます。

スケールについて
let scaleFactor: CGFloat = 1;
// CGContextScaleCTM(ctx, scaleFactor, scaleFactor);

生成されたコードではコメントアウトされているCGContextScaleCTMを生かしてscaleFactorを変えれば、スケールの変更ができます。

一部書き方があっていないところあり

Autodesk GraphicのVer 3.0.1を使用しているのですが、一部、書き方がSwift 2.1に適合していない部分がありました。
グラデーションのOption指定部分を修正しました。

// 修正前
CGContextDrawLinearGradient(ctx, gradientRef, CGPoint(x: 130.334, y: 82.171), CGPoint(x: 130.334, y: 44.818), CGGradientDrawingOptions(kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation))
// 修正後
CGContextDrawLinearGradient(ctx, gradientRef, CGPoint(x: 130.334, y: 82.171), CGPoint(x: 130.334, y: 44.818), [.DrawsBeforeStartLocation, .DrawsAfterEndLocation])
テキスト

テキスト部分を書き出してみるとCoreTextを使用したコードで書き出されました。
Path化されてしまうのではなく、テキスト情報が残っています。
ss 2016-02-14 22.54.08

#if os(iOS)
    let ctx = UIGraphicsGetCurrentContext() // iOS
#else
    let contextPtr = NSGraphicsContext.currentContext()!.graphicsPort   // OS X
    let opaqueCtx = COpaquePointer(contextPtr)
    let ctx = Unmanaged<CGContext>.fromOpaque(opaqueCtx).takeUnretainedValue()
#endif

// enable the following lines for flipped coordinate systems
// CGContextTranslateCTM(ctx, 0, self.bounds.size.height)
// CGContextScaleCTM(ctx, 1, -1)
let colorSpace = CGColorSpaceCreateDeviceRGB()

let scaleFactor: CGFloat = 1;
// CGContextScaleCTM(ctx, scaleFactor, scaleFactor);

/*  Text   */
let textBox = CGRect(x: 13.912, y: 5.804, width: 65.76, height: 38)
let textStr: CFString = "ABC"

let attributedStr = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)
CFAttributedStringReplaceString(attributedStr, CFRange(location: 0, length: 0), textStr)

let fontRef = CTFontCreateWithName("HelveticaNeue", 32, nil)
let textRange = CFRange(location: 0, length: CFAttributedStringGetLength(attributedStr))
CFAttributedStringSetAttribute(attributedStr, textRange, kCTFontAttributeName, fontRef)

let textColorComps: [CGFloat] = [0.953, 0.388, 0.388, 1]
let textColor = CGColorCreate(colorSpace, textColorComps)
CFAttributedStringSetAttribute(attributedStr, textRange, kCTForegroundColorAttributeName, textColor)

var alignment = CTTextAlignment.TextAlignmentCenter
var paragraphSettings = CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.Alignment, valueSize: UInt(sizeof(UInt8)), value: &alignment)
let paragraphStyle = CTParagraphStyleCreate(&paragraphSettings, 1)
CFAttributedStringSetAttribute(attributedStr, textRange, kCTParagraphStyleAttributeName, paragraphStyle)

let textBoxPath = CGPathCreateWithRect(CGRect(x: 0, y: 0, width: textBox.size.width, height: textBox.size.height), nil)
let framesetter = CTFramesetterCreateWithAttributedString(attributedStr)
let frameRef = CTFramesetterCreateFrame(framesetter, CFRange(location: 0, length: 0), textBoxPath, nil)
CGContextSaveGState(ctx)
CGContextTranslateCTM(ctx, textBox.origin.x, textBox.origin.y)

CGContextSetTextMatrix(ctx, CGAffineTransformIdentity)
CGContextTranslateCTM(ctx, 0.0, textBox.size.height)
CGContextScaleCTM(ctx, 1.0, -1.0)
CTFrameDraw(frameRef, ctx)

CGContextRestoreGState(ctx)

一部でエラーが出たので修正しました

// 修正前
var alignment = CTTextAlignment.TextAlignmentCenter
var paragraphSettings = CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.Alignment, valueSize: UInt(sizeof(UInt8)), value: &alignment)
// (略)
CTFrameDraw(frameRef, ctx)
// 修正後
var alignment = CTTextAlignment.Center
var paragraphSettings = CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.Alignment, valueSize: Int(sizeof(UInt8)), value: &alignment)
// (略)
CTFrameDraw(frameRef, ctx!)

ということで、Autodesk GraphicはCore Graphics Codeを書き出せます。

関連

CoreGraphicsコードをSwift 3.0に変換する – nackpan Blog

iDrawがAutodesk Graphicになっていた

iDraw – Mac Illustration and Graphic Design
iDrawのページ。Autodesk Graphicに変更されたことが記されている。

Introducing Autodesk Graphic, a Full-Featured Vector Design and Illustration Application
Autodeskの広報ブログ。2015年10月8日の記事。Autodesk Graphicが登場。

Autodesk Graphic
Autodesk Graphicのページ。

ドローイングツールiDrawの名称が変わっていました。
iDrawの開発会社IndeeoはAutodeskに買収されて、iDrawはAutodesk Graphicに変更されました。

AutodeskといえばAutoCADなど。
個人的には、3DグラフィックスソフトのMaya, 3ds MAX、Softimageをつぎつぎ買収して、えらいことになったなーって思った会社。