この記事はアピリッツの技術ブログ「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ブログなどで是非教えていただきたいと思います。