iPhoneで動作する映像フィルタをつくる:GPUImage+iOSのAVFoundationフレームワークその4
こんにちは、andyです。
今回は、フィルタを複数使用したカスタムフィルタをつくり、そのフィルタのパラメータを動的にスライダを使って変更してみたいと思います。
あまり複数のフィルタを使いすぎるとプレビュースピードが遅くなりますのでご注意を!
今回のコード
それでは今回もコードから。
ヘッダーファイルはこんな感じ。
#import <UIKit/UIKit.h> #import <AVFoundation/AVFoundation.h> #import <AssetsLibrary/AssetsLibrary.h> #import "GPUImage.h" @interface VideoFilterViewController : UIViewController //解説-1 @property (strong, nonatomic) IBOutlet UIView *sepia; @property (strong, nonatomic) IBOutlet UIView *gamma; @property (strong, nonatomic) IBOutlet UIView *start; @property (strong, nonatomic) IBOutlet UIView *end; - (IBAction)exportToMovie:(id)sender; - (IBAction)slider:(id)sender; @end
実装部分は、こんな感じ
#import "VideoFilterViewController.h" @interface VideoFilterViewController () { GPUImageMovieComposition *movieFile; GPUImageView *filterView; AVMutableComposition* composition; AVMutableVideoComposition* transformVideoComposition; //解説-2 GPUImageOutput<GPUImageInput>* filter; GPUImageMovieWriter* movieWriter; } @end @implementation VideoFilterViewController - (void)viewDidLoad { [super viewDidLoad]; [self showFilteringMovie]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)showFilteringMovie { NSURL *fileURL1 = [[NSBundle mainBundle] URLForResource:@"videoviewdemo" withExtension:@"mp4"]; NSURL *fileURL2 = [[NSBundle mainBundle] URLForResource:@"sample3" withExtension:@"mp4"]; composition = [AVMutableComposition composition]; AVMutableCompositionTrack* videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; AVURLAsset* firstMovie = [AVURLAsset URLAssetWithURL:fileURL1 options:nil]; AVURLAsset* secondMovie = [AVURLAsset URLAssetWithURL:fileURL2 options:nil]; AVAssetTrack* firstMovieVideo = [[firstMovie tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVAssetTrack* secondMovieVideo = [[secondMovie tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstMovie.duration) ofTrack:firstMovieVideo atTime:kCMTimeZero error:nil]; [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondMovie.duration) ofTrack:secondMovieVideo atTime:firstMovie.duration error:nil]; transformVideoComposition = [AVMutableVideoComposition videoComposition]; NSMutableArray *inst = [NSMutableArray array]; CGSize frameSize = CGSizeMake(640.0f, 480.0f); float scale = 0.7f; AVMutableVideoCompositionInstruction * firstInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; firstInstruction.backgroundColor = [[UIColor blueColor] CGColor]; firstInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstMovie.duration); AVMutableVideoCompositionLayerInstruction *firstlayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:firstMovieVideo]; CGAffineTransform firstLayerScale = CGAffineTransformMakeScale(scale, scale); CGAffineTransform firstLayerMove = CGAffineTransformMakeTranslation((frameSize.width - firstMovieVideo.naturalSize.width * scale) / 2, (frameSize.height-firstMovieVideo.naturalSize.height * scale) / 2); [firstlayerInstruction setTransform:CGAffineTransformConcat(firstLayerScale, firstLayerMove) atTime:kCMTimeZero]; firstInstruction.layerInstructions = [NSArray arrayWithObjects:firstlayerInstruction,nil];; [inst addObject:firstInstruction]; AVMutableVideoCompositionInstruction * secondInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; secondInstruction.backgroundColor = [[UIColor blueColor] CGColor]; secondInstruction.timeRange = CMTimeRangeMake(firstMovie.duration, secondMovie.duration); AVMutableVideoCompositionLayerInstruction *secondlayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:secondMovieVideo]; CGAffineTransform secondLayerScale = CGAffineTransformMakeScale(scale, scale); CGAffineTransform secondLayerMove = CGAffineTransformMakeTranslation((frameSize.width - secondMovieVideo.naturalSize.width * scale) / 2, (frameSize.height - secondMovieVideo.naturalSize.height * scale) / 2); [secondlayerInstruction setTransform:CGAffineTransformConcat(secondLayerScale, secondLayerMove) atTime:kCMTimeZero]; secondInstruction.layerInstructions = [NSArray arrayWithObjects:secondlayerInstruction,nil];; [inst addObject:secondInstruction]; transformVideoComposition.instructions = inst; transformVideoComposition.renderSize = frameSize; transformVideoComposition.frameDuration = CMTimeMake(1, 30); filterView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 240)]; //filter = [[GPUImagePolkaDotFilter alloc] init]; //解説-3 filter = [[GPUImageFilterGroup alloc] init]; GPUImageSepiaFilter *sepiaFilter = [[GPUImageSepiaFilter alloc] init]; [(GPUImageFilterGroup *)filter addFilter:sepiaFilter]; GPUImageGammaFilter *gammaFilter = [[GPUImageGammaFilter alloc] init]; gammaFilter.gamma = 1.2; [(GPUImageFilterGroup *)filter addFilter:gammaFilter]; GPUImageVignetteFilter* vignetteFilter = [[GPUImageVignetteFilter alloc] init]; [sepiaFilter addTarget:gammaFilter]; [gammaFilter addTarget:vignetteFilter]; [(GPUImageFilterGroup *)filter setInitialFilters:[NSArray arrayWithObject:sepiaFilter]]; [(GPUImageFilterGroup *)filter setTerminalFilter:vignetteFilter]; //movieFile = [[GPUImageMovie alloc] initWithAsset:composition]; movieFile = [[GPUImageMovieComposition alloc] initWithComposition:composition andVideoComposition:transformVideoComposition andAudioMix:nil]; movieFile.playAtActualSpeed = YES; [movieFile addTarget:filter]; [filter addTarget:filterView]; [self.view addSubview:filterView]; [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector: @selector(play:) userInfo:nil repeats:NO]; } - (void)play:(id)sender { __weak GPUImageMovie* weakMovieFile = movieFile; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [weakMovieFile startProcessing]; }); } - (IBAction)exportToMovie:(id)sender; { if (filterView == nil) { filterView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 240)]; } if (movieFile) { [movieFile cancelProcessing]; [movieFile removeAllTargets]; [filter removeAllTargets]; movieFile = nil; [filterView removeFromSuperview]; } movieFile = [[GPUImageMovieComposition alloc] initWithComposition:composition andVideoComposition:transformVideoComposition andAudioMix:nil]; movieFile.playAtActualSpeed = NO; [movieFile addTarget:filter]; [filter addTarget:filterView]; [self.view addSubview:filterView]; NSString* pathToMovie = [NSHomeDirectory() stringByAppendingPathComponent:@"tmp/tmp.mov"]; unlink([pathToMovie UTF8String]); NSURL *movieURL = [NSURL fileURLWithPath:pathToMovie]; CGSize frame = CGSizeMake(640, 480); movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:movieURL size:frame fileType:AVFileTypeQuickTimeMovie outputSettings:nil]; [filter addTarget:movieWriter]; [movieFile enableSynchronizedEncodingUsingMovieWriter:movieWriter]; [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector: @selector(exportStart:) userInfo:nil repeats:NO]; } - (void)exportStart:(id)sender { __weak GPUImageMovieWriter* weakMovieWriter = movieWriter; __weak GPUImageMovie* weakMovieFile = movieFile; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [weakMovieWriter startRecording]; [weakMovieFile startProcessing]; }); } //解説-4 - (IBAction)slider:(id)sender { switch (((UISlider*)sender).tag) { case 100://sepia [(GPUImageSepiaFilter*)[(GPUImageFilterGroup *)filter filterAtIndex:0] setIntensity:[(UISlider *)sender value]]; break; case 101://gammma [(GPUImageGammaFilter*)[(GPUImageFilterGroup *)filter filterAtIndex:1] setGamma:[(UISlider *)sender value]]; break; case 102://start [(GPUImageVignetteFilter*)((GPUImageFilterGroup *)filter).terminalFilter setVignetteStart:[(UISlider *)sender value]]; break; case 103://end [(GPUImageVignetteFilter*)((GPUImageFilterGroup *)filter).terminalFilter setVignetteEnd:[(UISlider *)sender value]]; break; } } @end
解説
今回の解説はたったの4箇所です。
それでは始めます。
解説-1
まず始めにStoryboardでスライダを4つ画面に追加しておきます。そのスライダに100〜103までのタグを設定しておいてください。
今回使用するフィルタは、
- GPUImageSepiaFilter
- GPUImageGammaFilter、
- GPUImageVignetteFilter
です。
GPUImageSepiaFilterでは、sepiaスライダを使いIntensityを可変します。
GPUImageGammaFilterでは、gammaスライダを使いgammaを可変します。
GPUImageVignetteFilterでは、start、endスライダを使いvignetteStartとvignetteEndを可変します。
解説-2
前回まではGPUImageSepiaFilterのインスタンスとしてfilterを扱っていましたが、今回はスーパークラスであるGPUImageOutputで取り扱います。
解説-3
カスタムフィルタを作っている部分です。フィルタを複数使用する場合にはGPUImageFilterGroupのインスタンスを作成し、フィルタを掛ける順番にそれぞれのフィルタインスタンスをグループに加えていきます。
それから、フィルタのアウトプットを順に後に続くフィルタのインプットに繋げていくという感じでしょうか。
解説-4
ここでスライダからのコールバックを受けています。
それぞれのスライダにタグが設定されているので、その値を元に処理を分岐し、それぞれのフィルタの設定値を可変できるようにしています。
フィルタグループからその中にある任意のフィルタを取得する場合には、NSArrayなどと同様に追加された順のインデックスで行います。ただし、一番最後に掛けたフィルタのみterminalFilterというプロパティから取得します。
後は、スライダからの戻り値をそれぞれの設定値にセットしているだけです。
わかってしまうと意外と簡単!
後は、数あるフィルタの中から自分なりにフィルタを複合してオリジナルフィルタを作るだけ。この辺はどちらかというと、映像制作に関わっている人の方がよくわかっている部分だと思います。
今回はここまで。
次回はどうしようか考え中。
出来たらクロマキー合成をやりたいんですが、動画同士のクロマキーがどうもうまく動かなくて悩んでおります。デバイス自体の性能も影響してるのかもしれませんが・・・
それではまた。
コメントをどうぞ