その他
    ホーム 技術発信 DoRuby Rubyでどう書く?:RubyCocoa+Core Animationでお手軽アニメーション

    Rubyでどう書く?:RubyCocoa+Core Animationでお手軽アニメーション

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    KBMJがZDnetで連載している記事を紹介します。コメントなどありましたらそちらでお願いします。

    Rubyでどう書く?:RubyCocoa+Core Animationでお手軽アニメーション 佐藤伸吾(KBMJ)

    今回はRubyCocoaとMac OS XのフレームワークであるCore Animationを使って、お手軽にアニメーションを作成してみましょう。

    問題

    RubyCocoaは、Mac OS XのCocoaオブジェクトをRubyスクリプトからRubyオブジェクトとして扱うための、Ruby用ライブラリとフレームワークです。RubyCocoaを用いれば、RubyでCocoaアプリケーションを記述することができるのです。

     Core AnimationはMax OS Xのフレームワークで、その利点は手軽さにあります。普通にアニメーションプログラミングを行おうとすると、時間軸管理、非同期な描画スレッド、パフォーマンスの向上など、考慮すべき点があまりにも多すぎます。

     しかし、Core Animationを用いれば、面倒な部分はOSに任せて、自分の実現したいことに集中できるのです。

     今回はRubyCocoaとMac OS XのフレームワークであるCore Animationを使って、お手軽にアニメーションを作成してみましょう。

    回答例

     AppController.rbを作成し、以下のように記述します。

    require ‘osx/cocoa’
    OSX.require_framework ‘QuartzCore’

    class AppController < OSX::NSObject
    ib_outlet :window
    ib_outlet :view
    ib_action :pressPosition
    ib_action :pressRotation
    ib_action :pressSepia
    ib_action :pressBloom
    ib_action :pressHole
    ib_action :pressZoom
    ib_action :pressHalftone

    def awakeFromNib
    bitmapImage = OSX::NSBitmapImageRep.imageRepWithContentsOfFile_(‘/Users/ssato/Pictures/ruby.png’)
    image = bitmapImage.CGImage()

    layer = OSX::CALayer.layer()
    layer.contents = image
    layer.frame = OSX::CGRectMake(0,0,995/4,996/4)

    black=OSX::CGColorCreateGenericRGB(0,0, 0,1.0)
    backgroundLayer=OSX::CALayer.layer()
    backgroundLayer.backgroundColor=black

    backgroundLayer.addSublayer_(layer)

    @view.setLayer_(backgroundLayer)
    @view.setWantsLayer_(true)
    end
    def pressPosition(sender)
    animation = OSX::CABasicAnimation.animationWithKeyPath_(‘position’)
    animation.duration = 1.0

    layer = @view.layer.sublayers.objectAtIndex_(0)
    position = layer.position
    animation.fromValue = OSX::NSValue.valueWithPoint_(OSX::NSPointFromCGPoint(position))
    position.x += 256;
    position.y += 160;
    animation.toValue = OSX::NSValue.valueWithPoint_(OSX::NSPointFromCGPoint(position))
    animation.autoreverses = true
    animation.repeatCount = 4
    layer.addAnimation_forKey_(animation, ‘positionAnimation’)
    end
    def pressRotation(sender)
    animation = OSX::CABasicAnimation.animationWithKeyPath_(‘transform’)
    animation.duration = 0.5
    animation.autoreverses = true
    animation.repeatCount = 4
    transform = OSX::CATransform3DIdentity
    animation.fromValue = OSX::NSValue.valueWithCATransform3D(transform)
    transform = OSX::CATransform3DMakeRotation(Math::PI, 0, 1.0, 0)
    transform.m34 = 1.0 / -420.0
    animation.toValue = OSX::NSValue.valueWithCATransform3D(transform)
    layer = @view.layer.sublayers.objectAtIndex_(0)
    layer.addAnimation_forKey_(animation, ‘transformAnimation’)
    end
    def pressSepia(sender)
    filter = OSX::CIFilter.filterWithName_(‘CISepiaTone’)
    filter.setDefaults()
    layer = @view.layer.sublayers.objectAtIndex_(0)
    layer.name = ‘sepiaFilter’
    layer.setFilters_(OSX::NSArray.arrayWithObject_(filter))
    end
    def pressBloom(sender)
    filter2 = OSX::CIFilter.filterWithName_(‘CIBloom’)
    filter2.setDefaults
    filter2.setValue_forKey_(OSX::NSNumber.numberWithFloat_(0.0), ‘inputIntensity’)
    filter2.setValue_forKey_(OSX::NSNumber.numberWithFloat_(5.0), ‘inputRadius’)
    filter2.setName_(‘bloomFilter’)

    layer = @view.layer.sublayers.objectAtIndex_(0)
    layer.setFilters_(OSX::NSArray.arrayWithObject_(filter2))

    pluseAnimation = OSX::CABasicAnimation.animation
    pluseAnimation.keyPath = ‘filters.bloomFilter.inputIntensity’
    pluseAnimation.fromValue = OSX::NSNumber.numberWithFloat_(0.0)
    pluseAnimation.toValue = OSX::NSNumber.numberWithFloat_(1.5*2)
    pluseAnimation.duration = 1.0
    pluseAnimation.repeatCount = 4
    pluseAnimation.autoreverses = true
    layer.addAnimation_forKey_(pluseAnimation, ‘bloom’)
    end
    def pressHole(sender)
    filter = OSX::CIFilter.filterWithName_(‘CIHoleDistortion’)
    filter.setDefaults()
    layer = @view.layer.sublayers.objectAtIndex_(0)
    layer.setFilters_(OSX::NSArray.arrayWithObject_(filter))
    end
    def pressZoom(sender)
    filter = OSX::CIFilter.filterWithName_(‘CIZoomBlur’)
    filter.setDefaults()
    layer = @view.layer.sublayers.objectAtIndex_(0)
    layer.setFilters_(OSX::NSArray.arrayWithObject_(filter))
    end
    def pressHalftone(sender)
    filter = OSX::CIFilter.filterWithName_(‘CICMYKHalftone’)
    filter.setDefaults()
    layer = @view.layer.sublayers.objectAtIndex_(0)
    layer.setFilters_(OSX::NSArray.arrayWithObject_(filter))
    end
    end

    Interface Builderで画像1のようにコンポーネントを配置します。

    画像1

    NSObjectを追加して、ClassにAppcontrollerを指定します(画像2)。

    画像2

    OutletsとActionsのConnectionを画像3のように設定します。

    画像3

    実行結果

    Positionボタンを押すと、画像が斜めに移動します(画像4)。

    画像4

    Rotationボタンを押すと画像が回転し、Sepiaをクリックすると画像がセピア色になります(画像5)。

    画像5

    同様に、Bloomは画像が光り輝き(画像6)、Holeは画像に穴が空きます(画像7)。Zoomボタンはズームエフェクト(画像8)で、Halftoneはハーフトーンフィルタです(画像9)。

    画像6

    画像7

    画像8

    画像9

    解説

    共通部分

    Core Animationを使用するためにフレームワークを設定します。

    OSX.require_framework ‘QuartzCore’

    AppControllerからviewにアクセスするためのアウトレットを宣言します。

    ib_outlet :view

    ib_action :pressPosition
    ib_action :pressRotation
    ib_action :pressSepia
    ib_action :pressBloom
    ib_action :pressHole
    ib_action :pressZoom
    ib_action :pressHalftone

    pressPosition部分

    アクションを宣言し、ボタンが押された時の処理として登録できるようにします。

    layerオブジェクトを作成します。Core Animationではレイヤーを基本単位としてアニメーションを実行します。

    layer = OSX::CALayer.layer()

    backgroundLayerの下にlayerをぶら下げます。

    backgroundLayer=OSX::CALayer.layer()
    backgroundLayer.addSublayer_(layer)

    viewの描画内容としてbackgroundLayerを設定する。

    @view.setLayer_(backgroundLayer)
    @view.setWantsLayer_(true)

    アニメーション化するレイヤプロパティのキーパスを指定して、CABasicAnimationのインスタンスを作成します。

    animation = OSX::CABasicAnimation.animationWithKeyPath_(‘position’)

    再生時間を1秒に設定します。

    animation.duration = 1.0

    アニメーションの開始位置を指定します。

    animation.fromValue = OSX::NSValue.valueWithPoint_(OSX::NSPointFromCGPoint(position))

    アニメーションの終了位置を指定します。

    position.x += 256;
    position.y += 160;
    animation.toValue = OSX::NSValue.valueWithPoint_(OSX::NSPointFromCGPoint(position))

    繰り返しアニメーションされるように設定します。

    animation.autoreverses = true
    animation.repeatCount = 4

    layerにアニメーションを設定します。

    layer.addAnimation_forKey_(animation, ‘positionAnimation’)

    pressRotation部分

    単位行列を取得します。

    transform = OSX::CATransform3DIdentity

    アニメーション開始時の変換行列を設定します。

    animation.fromValue = OSX::NSValue.valueWithCATransform3D(transform)
    animation.fromValue = OSX::NSValue.valueWithCATransform3D(transform)

    回転行列を取得します。

    transform = OSX::CATransform3DMakeRotation(Math::PI, 0, 1.0, 0)

     pressSepia部分

    セピア調フィルタを作成する。

    filter = OSX::CIFilter.filterWithName_(‘CISepiaTone’)

    フィルタの入力値をデフォルト値に初期化する。

    filter.setDefaults()

    layerにfilterを設定する。

    layer.setFilters_(OSX::NSArray.arrayWithObject_(filter))

    pressBloom部分

    Bloomフィルタを作成する。このフィルタはエッジをソフト化し、輝きを画像に適用します。

    filter2 = OSX::CIFilter.filterWithName_(‘CIBloom’)

    フィルタ出力とオリジナル画像の線形のブレンドを指定するスカラー値であるinputIntensityを0.0に設定する。

    filter2.setValue_forKey_(OSX::NSNumber.numberWithFloat_(0.0), ‘inputIntensity’)

    フィルタ処理の対象となる領域の半径を指定する値であるinputRadiusを5.0に設定する。

    filter2.setValue_forKey_(OSX::NSNumber.numberWithFloat_(5.0), ‘inputRadius’)

    フィルタに名前を設定する。この名前は後でアニメーション設定する時に必要となる。

    filter2.setName_(‘bloomFilter’)

    アニメーション化する属性を指定する。

    pluseAnimation.keyPath = ‘filters.bloomFilter.inputIntensity’

    inputIntensityの開始値と終了値を指定する。

    pluseAnimation.fromValue = OSX::NSNumber.numberWithFloat_(0.0)
    pluseAnimation.toValue = OSX::NSNumber.numberWithFloat_(1.5*2)

    pressHole部分

    穴歪みフィルタを作成する。

    filter = OSX::CIFilter.filterWithName_(‘CIHoleDistortion’)

    pressZoom部分

    カメラをズームしたようなエフェクトをシミュレートするズームブラーを作成する。

    filter = OSX::CIFilter.filterWithName_(‘CIZoomBlur’)

    pressHalftone部分

    ハーフトーン表現を行うフィルタを作成する。

    filter = OSX::CIFilter.filterWithName_(‘CICMYKHalftone’)

    最後に

    駆け足で説明しましたが、いかがだったでしょうか?RubyCocoaを使うと、RubyからMac OS Xのリッチなフレームワークを使うことができます。

    もっと良い方法があれば、コメントやbuilderブログなどで是非教えていただきたいと思います。

    記事を共有
    モバイルバージョンを終了