LoginSignup
44
44

More than 5 years have passed since last update.

Core Image の遷移エフェクトを使う

Last updated at Posted at 2013-03-11

Core Image のフィルタ (CIFilter) には、CICategoryTransition というカテゴリーがあり、次のような遷移エフェクトが用意されています。

  • CIBarsSwipeTransition
  • CICopyMachineTransition
  • CIDisintegrateWithMaskTransition
  • CIDissolveTransition
  • CIFlashTransition
  • CIModTransition
  • CISwipeTransition
  • CIAccordionFoldTransition(iOS 8)
  • CIPageCurlTransition(iOS 9
  • CIPageCurlWithShadowTransition(iOS 9
  • CIRippleTransition(iOS 9

次のような一風変わった遷移エフェクトを実現できます。

CoreImageTransition

サンプルコード『CoreImageTransition』

Githubにサンプルをアップしてあります。iOS 9 で追加された3種類を含む、全9種類の遷移エフェクトを試すことができます。

※GPUを利用するためシミュレータではなく実機でご確認ください

以下実装手順です。

宣言と初期化処理の実装

使い方の基本的な部分は CIFilter の他のフィルタと同じなのですが、遷移エフェクトなので、遷移前と遷移後の2つの画像を用意します。

@interface TransitionView ()
{
    NSTimeInterval  base;
    CGRect imageRect;
}
@property (nonatomic, strong) CIImage *image1;
@property (nonatomic, strong) CIImage *image2;
@property (nonatomic, strong) CIFilter *transition;
@end
// 遷移前後の画像を生成
UIImage *uiImage1 = [UIImage imageNamed:@"sample1.jpg"];
UIImage *uiImage2 = [UIImage imageNamed:@"sample2.jpg"];
self.image1 = [CIImage imageWithCGImage:uiImage1.CGImage];
self.image2 = [CIImage imageWithCGImage:uiImage2.CGImage];

// マスク画像を生成
UIImage *uiMaskImage = [UIImage imageNamed:@"mask.jpg"];
CIImage *maskImage = [[CIImage alloc] initWithCGImage:uiMaskImage.CGImage];

// CIFilterオブジェクトを生成
self.transition = [CIFilter filterWithName: @"CIDisintegrateWithMaskTransition"
                             keysAndValues:
                   @"inputMaskImage", maskImage,
                   nil];

// 表示領域を示す矩形(CGRect型)
imageRect = CGRectMake(0, 0, uiImage1.size.width / 2, uiImage1.size.height / 2);

// 遷移アニメーション制御の基準となる時刻
base = [NSDate timeIntervalSinceReferenceDate];    

// 遷移アニメーションを制御するタイマー
[NSTimer scheduledTimerWithTimeInterval:1.0/30.0
                                 target:self
                               selector:@selector(onTimer:)
                               userInfo:nil
                                repeats:YES];

初期化処理では各種 CIImage オブジェクトの生成、CIFilter オブジェクトの生成と、表示領域を示す矩形、遷移アニメーション制御に必要な時刻とタイマーの生成を行います。(※実際にはNSTimer ではなく CADisplayLink を使うべき)

CIDisintegrateWithMaskTransition はマスクを使用するエフェクトなので、マスク画像の CIImage オブジェクトを生成し、@"inputMaskImage"キーの値に設定しています。

フィルタ処理の実装

遷移アニメーション中の各フレームで行うフィルタ処理を実装します。

- (CIImage *)imageForTransitionAtTime:(float)time
{
    // 遷移前後の画像をtimeによって切り替える
    if (fmodf(time, 2.0) < 1.0f)
    {
        [self.transition setValue:self.image1 forKey:@"inputImage"];
        [self.transition setValue:self.image2 forKey:@"inputTargetImage"];
    }
    else
    {
        [self.transition setValue:self.image2 forKey:@"inputImage"];
        [self.transition setValue:self.image1 forKey:@"inputTargetImage"];
    }

    // 遷移アニメーションの時間を指定
    [self.transition setValue:@(fmodf(time, 1.0f)) forKey:@"inputTime"];

    // フィルタ処理実行
    CIImage *transitionImage = [self.transition valueForKey:@"outputImage"];

    return transitionImage;
}

遷移前後の画像をそれぞれ @"inputImage" キーと @"inputTargetImage" キーに指定し、遷移アニメーションの時間(範囲は0〜1)を @"inputTime" キーに指定します。

アニメーション制御

初期化時に生成したタイマーのハンドラではsetNeedsDisplayをコールし、

- (void)onTimer:(NSTimer *)timer {

    [self setNeedsDisplay];
}

それにより呼ばれるようになる UIView の drawRect: で次のように処理を行います。

- (void)drawRect:(CGRect)rect {

    CGFloat time = 0.4 * ([NSDate timeIntervalSinceReferenceDate] - base);

    CIImage *image = [self imageForTransitionAtTime:time];
    UIImage *uiImage = [UIImage imageWithCIImage:image];

    [uiImage drawInRect:imageRect];
}

先に実装した imageForTransitionAtTime: をコールし、受け取ったフィルタ処理結果の画像を描画しています。

GLKView を用いた高速化

スムーズにアニメーションさせるには描画が追いつかないので、GLKView に描画します。このあたりはコメント欄をご参照ください。(サンプルは GLKit を利用して実装してあります。また拙著にも解説を書きました)

44
44
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
44