Quantcast
Channel: その後のその後
Viewing all 317 articles
Browse latest View live

[iOS][Objective-C][画像処理]シャッター音の鳴らないカメラアプリの実装方法

$
0
0

「カメラ機能をアプリにつけたいけどシャッター音を鳴らしたくない」とか、「カメラ起動時のアニメーションが嫌だ」とか、カメラ機能をもっと自由にカスタマイズしたい場合は、UIImagePickerController を使うのではなく AVFoundation フレームワークを使う必要があります。


先日、UIImagePickerController を使わないカメラアプリの実装方法という記事を書いたのですが、そこでは UIImagePickerController を使わず、AVFoundation を用いて静止画を撮影するところまで書きました。


今回は、シャッター音を鳴らさない、いわゆる「静音カメラ」「マナーカメラ」「無音カメラ」と呼ばれている機能を実装します。


静止画撮影はデフォルトの挙動としてシャッター音がなってしまうようになっていてそこは変えられないので、動画モードで撮影を開始し、必要なフレームを静止画として取り込むことで「シャッター音の鳴らないカメラ」を実現します。


参考書籍及びサンプルコード

今回のサンプルコードは、gumroad よりダウンロードしていただけます。


http://gum.co/pzBMA


※すいません、無料ではなく、85円です。

(ほんとは50円ぐらいにしたかったけど、gumroadの制限によりこれ以下には設定できませんでした)


【注意点】

  • iOS 6.0 で動作確認しております。
  • コードのみの販売です。サポート等はご容赦ください

また、上記サンプルは、次の書籍を参考に実装しました。

iOS4プログラミングブック
畑 圭輔 加藤 寛人 坂本 一樹 藤川 宏之 高橋 啓治郎 沖田 知彦 柳澤 昇
インプレスジャパン
売り上げランキング: 32117

こちらの第3章『マルチメディア』の章とサンプルコードが参考になるかと思います。(本記事の数倍参考になります。おすすめです。)


準備

AVFoundation.framework, AssetsLibrary.framework に加えて、以下のフレームワークをプロジェクトに追加します。

  • CoreMedia.framework
  • CoreVideo.framework

ヘッダでの宣言

変数

撮影を制御するのに使用するフラグと、ビットマップ保存領域用のポインタを定義します。

BOOL isRequireTakePhoto;
BOOL isProcessingTakePhoto;
void *bitmap;

プロパティ
@property (nonatomic, retain) UIImage *imageBuffer;

プロトコルへの準拠を宣言
<AVCaptureVideoDataOutputSampleBufferDelegate>

実装

イメージバッファ初期化処理

バッファ確保処理をカメラデバイス初期化コード(参考:http://d.hatena.ne.jp/shu223/20121002/1354424588title=前回記事])の前あたりに書いておきます。

size_t width = 640;
size_t height = 480;
bitmap = malloc(width * height * 4);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(NULL, bitmap, width * height * 4, NULL);
CGImageRef cgImage = CGImageCreate(width, height, 8, 32, width * 4, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProviderRef, NULL, 0, kCGRenderingIntentDefault);
self.imageBuffer = [UIImage imageWithCGImage:cgImage];
CGColorSpaceRelease(colorSpace);
CGDataProviderRelease(dataProviderRef);

出力関連の初期化処理

前回は静止画の撮影だったので出力先として AVCaptureStillImageOutput を用いましたが、今回は動画の撮影なので、AVCaptureVideoDataOutput を使います。

//self.imageOutput = [[AVCaptureStillImageOutput alloc] init];
AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
[captureSession addOutput:videoOutput];

デリゲートの設定

下記のようにデリゲートを指定することで、AVCaptureVideoDataOutputSampleBufferDelegate プロトコルの captureOutput:didOutputSampleBuffer:fromConnection: が毎フレーム呼ばれるようになります。

videoOutput.alwaysDiscardsLateVideoFrames = YES;
dispatch_queue_t queue = dispatch_queue_create("com.overout223.myQueue", NULL);
[videoOutput setSampleBufferDelegate:self
                               queue:queue];
dispatch_release(queue);

シャッターボタンのアクションを実装

ここでは「写真撮るよフラグ」を立てるだけです。

- (IBAction)pressShutter {

if (!isProcessingTakePhoto) {

        isRequireTakePhoto = YES;
    }
}

プロトコルの実装

カメラが動画のフレームを取得する度に(つまり撮影時の毎フレーム)、AVCaptureVideoDataOutputSampleBufferDelegate プロトコルの captureOutput:didOutputSampleBuffer:fromConnection: メソッドが呼ばれるので、そこで必要に応じて画像の保存処理を行います。


ユーザーがシャッターボタンを押すと isProcessingTakePhoto フラグが YES になっているので、そのときだけカメラロールへの保存処理を行うようにします。

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection {

    if (isRequireTakePhoto) {

        isRequireTakePhoto = NO;
        isProcessingTakePhoto = YES;

        CVPixelBufferRef pixbuff = CMSampleBufferGetImageBuffer(sampleBuffer);

        if(CVPixelBufferLockBaseAddress(pixbuff, 0) == kCVReturnSuccess){

            memcpy(bitmap, CVPixelBufferGetBaseAddress(pixbuff), 640 * 480 * 4);

            CMAttachmentMode attachmentMode;
            CFDictionaryRef metadataDictionary = CMGetAttachment(sampleBuffer, CFSTR("MetadataDictionary"), &attachmentMode);

            // フォトアルバムに保存
            ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];

            [library writeImageToSavedPhotosAlbum:self.imageBuffer.CGImage
                                         metadata:(NSDictionary *)CFBridgingRelease(metadataDictionary)
                                  completionBlock:^(NSURL *assetURL, NSError *error) {
                                      
                                      NSLog(@"URL:%@", assetURL);
                                      NSLog(@"error:%@", error);
                                      isProcessingTakePhoto = NO;
                                  }];

            CVPixelBufferUnlockBaseAddress(pixbuff, 0);
}
}
}

できあがり

f:id:shu223:20121202175000p:image:w160


プレビュー表示と「シャッター音の鳴らない」撮影ボタンだけからなる、シンプルな無音カメラアプリのできあがりです。


まとめ

AVFoundation フレームワークを用いて、「シャッター音の鳴らないカメラ」を実装する方法についてご紹介しました。


サンプルコードを見ると、一見長くて難しいですが、「画像の保存」などおなじみの処理、各種初期化等の手順がややこしくて覚えにくい処理等をスニペット化してしまえば、30分といわず10分でもできてしまうかもしれません。


AVFoundationはまだまだまだ探求の余地があるので、また勉強しつつ記事にしたいと思います。




[ライブラリ][Objective-C][iOS]Method Swizzling をうまく使っている実用例

$
0
0

Method Swizzlingは、既存のメソッドの実装を、自前の実装に差し替えるための手法です。


・・・ということを知ってはいても、どういうときに使うと便利なのかイマイチわかってなかったので、Method Swizzlingをうまく使った実用例を2つほど探してきました。


実用例その1:既存ソースコードに手を入れずに機能追加

xib ファイルのローカライズを IB 上でできるようにする AutoNibL10n

通常、xibで作成したUIをローカライズする場合、

  • xibファイルを言語ごとに用意する
  • アウトレットを定義してプログラム側からローカライズした文言をセットする

といった面倒な作業が必要でしたが、 AutoNibL10n を使用すると、xibファイルを IB から直接多言語対応できるようになります。


たとえば、RootViewController.xibというファイルがあり、その中のUILabelオブジェクトを多言語化したい場合、直接 IB から UILabel のtextプロパティに "mytext" を指定すれば、Localizable.strings に"mytext"というキーで定義された各言語の文字列が挿入されるようになります。


AutoNibL10n における Method Swizzling

この AutoNibL10n、使用するために新たにコードを書く必要は一切なく、ヘッダをインポートする必要すらありません。


どうやっているかというと、Method Swizzling を利用して、load メソッドがコールされるタイミングで(=メモリにアプリケーションがロードされるタイミングで) NSObject の awakeFromNib メソッドを自前の localizeNibObject メソッドに入れ替えることで実現しています。

+(void)load {
    Method localizeNibObject = class_getInstanceMethod([NSObject class], @selector(localizeNibObject));
    Method awakeFromNib = class_getInstanceMethod([NSObject class], @selector(awakeFromNib));
    method_exchangeImplementations(awakeFromNib, localizeNibObject);
}

awakeFromNib の差し替え後のメソッド localizeNibObject の実装はこんな感じです。

-(void)localizeNibObject {
    LocalizeIfClass(UIBarButtonItem);
    else LocalizeIfClass(UIBarItem);
    else LocalizeIfClass(UIButton);
    else LocalizeIfClass(UILabel);
    else LocalizeIfClass(UINavigationItem);
    else LocalizeIfClass(UISearchBar);
    else LocalizeIfClass(UISegmentedControl);
    else LocalizeIfClass(UITextField);
    else LocalizeIfClass(UITextView);
    else LocalizeIfClass(UIViewController);

    if (self.isAccessibilityElement == YES)
    {
        self.accessibilityLabel = localizedString(self.accessibilityLabel);
        self.accessibilityHint = localizedString(self.accessibilityHint);
    }

    [self localizeNibObject]; // actually calls awakeFromNib as we did some method swizzling
}

この中でコールされている localizedString メソッドで、NSBundle の localizedStringForKey:value:table: メソッドを用いて Localizable.strings に定義されたローカライズ文字列を取得しています。

return [[NSBundle mainBundle] localizedStringForKey:aString value:nil table:nil];

実用例その2:iOSバージョンの違いを吸収する

UIRefreshControl を iOS5 でも使えるようにする ISRefreshControl

ISRefreshControl は、iOS6で導入された UIRefreshControl を iOS5 でも使えるようにするライブラリです。


UIRefreshControl と同じ API なので、導入する側は UIRefreshControl と同じように実装するだけ

self.refreshControl = (id)[[ISRefreshControl alloc] init];
[self.refreshControl addTarget:self
                        action:@selector(refresh)
              forControlEvents:UIControlEventValueChanged];

これだけで、iOS6 で動作させると UIKit の UIRefreshControl が呼ばれ、iOS5 で動作させると ISRefreshControl (UIRefreshControl と同じ見た目、挙動)が呼ばれます


ISRefreshControl における Method Swizzling

UITableViewController+RefreshControl というカテゴリが用意されていて、これの load メソッド内で、次のように Method Swizzling が行われています。

+ (void)load
{
    @autoreleasepool {
        if ([[[UIDevice currentDevice] systemVersion] hasPrefix:@"5"]) {
            Swizzle([self class], @selector(refreshControl), @selector(iOS5_refreshControl));
            Swizzle([self class], @selector(setRefreshControl:), @selector(iOS5_setRefreshControl:));
        }
    }
}

(※Swizzle()は同ファイルに定義されているMethod Swizzlingのラッパー関数)

iOSのバージョンが "5" だった場合に、UITableViewController の refreshControl というプロパティの setter と getter を差し替える実装になっていることがわかります。


iOS5のときに差し替える実装は、

- (ISRefreshControl *)iOS5_refreshControl
{
    return objc_getAssociatedObject(self, @"iOS5RefreshControl");
}

- (void)iOS5_setRefreshControl:(ISRefreshControl *)refreshControl
{
    objc_setAssociatedObject(self, @"iOS5RefreshControl", refreshControl, OBJC_ASSOCIATION_RETAIN);
}

となっていて、Associated Objectの仕組みを利用して iOS5 で UITableViewController の refreshControl というプロパティにアクセスできるようにしています。

で、KVOでrefreshControlプロパティを見張り、

[self addObserver:self
       forKeyPath:@"refreshControl"
          options:options
          context:NULL];

refreshControlプロパティに値(UIViewサブクラスのオブジェクト)がセットされたときに、所定の位置にそのビューを貼付ける、という実装になっています。

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == self && [keyPath isEqualToString:@"refreshControl"]) {
        UIView *oldView = [change objectForKey:@"old"];
        UIView *newView = [change objectForKey:@"new"];
    
        if ([oldView isKindOfClass:[UIView class]]) {
            [oldView removeFromSuperview];
        }
        if ([newView isKindOfClass:[UIView class]]) {
            newView.frame = CGRectMake(0, -50, self.view.frame.size.width, 50);
            newView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
            [newView setNeedsLayout];
            [self.view addSubview:newView];
        }
        return;
    }
    // 後略
}

このように、Method Swizzling を用いることで、導入する側は iOS のバージョン判定も、このライブラリ独自のAPIを使う必要もなく、UIRefreshControlと同じように実装するだけ、というカンタン導入を実現しています。


Method Swizzlingの参考書籍

日本語の本だと、『iOS SDK Hacks』に解説と事例が掲載されています。


iOS SDK Hacks ―プロが教えるiPhoneアプリ開発テクニック
吉田 悠一 高山 征大 UICoderz
オライリージャパン
売り上げランキング: 226658

まとめ

Method Swizzlingをうまく使っている実用例を2つ紹介しました。

これ以外にも、単体テストで使える OCMock というライブラリも、Method Swizzling を利用して既存オブジェクトのメソッドを差し替えてテストできるようにしていたりと、Github上にはたくさんの実用例が転がっているようなので、また追記したいと思います。



[Android]Kindle Fire HD 32GB をポチりました

$
0
0

最近家にある本を全部電子化したので、毎日持ち歩けるタブレットが欲しいなーと思ってて、iPad mini を買うつもりでいたんですが、たまたま寄った Amazon で Kindle Fire HD を見て、思わず衝動買いしてしまいました。


Kindle Fire HD 16GB
Amazon.co.jp (2012-12-18)
売り上げランキング: 1

購入の決め手となったのは、

  • HD と銘打つだけあって、解像度が iPad mini より高い
    • Kindle Fire HD : 1280 x 800
    • iPad mini : 1024 x 768
  • そのわりに iPad2 よりは軽くて、幅高さは iPad miniと同じぐらい
  • 32GB版もあるので、持ってる本を全部入れても大丈夫そう
  • もともと Kindle (デバイス自体より、Kindle Store)が気になってた
  • OS が Android 4.0(Android端末を1台持っておきたかった)

唯一気になるのは、厚さが iPad mini が 7mm に対して Kindle Fire HD は 10mm もある点。実物見てないけど、この厚さのせいで iPad mini に流れる人は結構多い気がします。


あと、Wikipedia の Kindle Fire HD のページによると、

OS は Android 4.0 をカスタマイズしたオリジナル。そのため、Google Playストアは利用できない

とのこと。

初めての Android 端末なのでなかなかつらい制約ですが、アプリを動作させたりはできると思うのでよしとしました。



[ライブラリ][iPhone][Objective-C][iOS6]Core Image の全エフェクトを試せるサンプルコードを公開しました

$
0
0

Core Image のフィルタ(画像にエフェクトをかけたり、色を調整したりするもの)を一通り試せるサンプルプロジェクトをgithubに上げました。


というか、これ、1年以上前にアップしてこちらの記事に書いたのですが、ほとんど認知されることがなかったので、改めて紹介させていただきます。


こんな感じでフィルターを試せます。

(フィルタのパラメータはランダムに生成されるので、かける度に変わる場合もあります)


f:id:shu223:20111117111856p:image:left:w240

f:id:shu223:20111117111857p:image:left:w240



プロジェクト一式、こちらからダウンロードできます。

https://github.com/shu223/FilterDemo


注意点として、選んでも何も起こらないフィルターがたくさんあります

詳細は後述しますが、こちら、Apple の PocketCoreImage というサンプルコードを数行いじっただけのものなので、あんまり多種多様なフィルタに対応したつくりになってはいないのです。。

そういうフィルタはリストから除いてもよかったのですが、「どんなフィルタがあるのか?」を一覧できる意味もあるので、残してあります。


PocketCoreImageからの改変点

CIFilterは、下記のように、NSString型の名前を引数に渡してインスタンスを生成します。

CIFilter *newFilter = [CIFilter filterWithName:name];

で、ここに渡せる名前をずらずらっと列挙するために、awakeFromNibで次のようなコードで全ビルトインフィルタの名前を取得しています。

NSArray *filterNames;
filterNames = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
_availableFilters = [NSArray arrayWithArray:filterNames];


それと、PocketCoreImageでは、フィルタをかける箇所で、次のようにKVCを利用して元画像をフィルタに渡しています。

[filter setValue:_filteredImage forKey:@"inputImage"];

ここで、inputImageというアクセサを持たないフィルタもあるので、

if (![filter respondsToSelector:NSSelectorFromString(@"inputImage")]) {
    continue;
}

とチェックを入れて例外を回避しています。

(詳細はこちらの記事をご参照ください)


その他、コードの解説

もともと PocketCoreImage にあった部分なのですが、多種多様なパラメータを持つ不特定多数のフィルタに対してランダム値を設定する部分はこうなっているよ、というのを紹介させていただきます。


まず、CIFilterのattributesメソッドでパラメータ一覧を取得します。

(このあとここでは設定しないパラメータを取り除く処理が入りますが割愛)

NSDictionary *filterAttributes = [filter attributes];

NSNumber型のパラメータだけを取り出し、

for (NSString *key in editableAttributes) {
    
    NSDictionary *attributeDictionary = [editableAttributes objectForKey:key];

    if ([[attributeDictionary objectForKey:kCIAttributeClass] isEqualToString:@"NSNumber"]) {

        // 後述    
    }   
}

BOOL/Decimal/Integerそれぞれの型に応じて、ランダム値を設定しています。

(下記は、上記コードのif文の中身)

        if ([attributeDictionary objectForKey:kCIAttributeType] == kCIAttributeTypeBoolean)
        {
            NSInteger randomValue = (rand() % 2); 
            
            NSLog(@"Setting %i for key %@ of type BOOL", randomValue, key);
            [filter setValue:[NSNumber numberWithInteger:randomValue] forKey:key];
        }
        else if([attributeDictionary objectForKey:kCIAttributeType] == kCIAttributeTypeScalar ||
                [attributeDictionary objectForKey:kCIAttributeType] == kCIAttributeTypeDistance ||
                [attributeDictionary objectForKey:kCIAttributeType] == kCIAttributeTypeAngle)
        {
            // Get the min and max values
            float maximumValue = [[attributeDictionary valueForKey:kCIAttributeSliderMax] floatValue];
            float minimumValue = [[attributeDictionary valueForKey:kCIAttributeSliderMin] floatValue];
            
            float randomValue = randFloat(minimumValue, maximumValue);

            NSLog(@"Setting %f for key %@ of type Decimal", randomValue, key);
            [filter setValue:[NSNumber numberWithFloat:randomValue] forKey:key];
        }
        else
        {
            // Get the min and max values
            NSInteger maximumValue = [[attributeDictionary valueForKey:kCIAttributeMax] integerValue];
            NSInteger minimumValue = [[attributeDictionary valueForKey:kCIAttributeMin] integerValue];
            
            NSInteger randomValue = (rand() % (maximumValue - minimumValue)) + minimumValue;
            
            NSLog(@"Setting %i for key %@ of type Integer", randomValue, key);
            [filter setValue:[NSNumber numberWithInteger:randomValue] forKey:key];
        }

Core Image の参考書籍

iOS Core Frameworksテクニカルガイド

iOS Core Frameworksテクニカルガイド
Shawn Welch
インプレスジャパン
売り上げランキング: 242494

2012年9月と比較的最近出版されたものですが、原著の出版は2011年9月。したがって iOS5 時代のものです。『Core Image』の章があるのですが、広く浅く、という感じです。


iOS5プログラミングブック

iOS5プログラミングブック
加藤 寛人 吉田 悠一 藤川 宏之 西方 夏子 関川 雄介 高丘 知央
インプレスジャパン
売り上げランキング: 88968

Core Imageの章あり。この本は全体的に説明が詳しく、あまり他の書籍やブログにないところまで踏み込んだ情報も多いので、個人的にはこちらの方がおすすめです。


おまけ:CIFilterのフィルタ名一覧(iOS 6 版)

前回記事(2011年11月)時点では 48 個だったものが、なんと 94 個に増えていました。


CIAdditionCompositing

CIAffineClamp

CIAffineTile

CIAffineTransform

CIBarsSwipeTransition

CIBlendWithMask

CIBloom

CIBumpDistortion

CIBumpDistortionLinear

CICheckerboardGenerator

CICircleSplashDistortion

CICircularScreen

CIColorBlendMode

CIColorBurnBlendMode

CIColorControls

CIColorCube

CIColorDodgeBlendMode

CIColorInvert

CIColorMap

CIColorMatrix

CIColorMonochrome

CIColorPosterize

CIConstantColorGenerator

CICopyMachineTransition

CICrop

CIDarkenBlendMode

CIDifferenceBlendMode

CIDisintegrateWithMaskTransition

CIDissolveTransition

CIDotScreen

CIEightfoldReflectedTile

CIExclusionBlendMode

CIExposureAdjust

CIFalseColor

CIFlashTransition

CIFourfoldReflectedTile

CIFourfoldRotatedTile

CIFourfoldTranslatedTile

CIGammaAdjust

CIGaussianBlur

CIGaussianGradient

CIGlideReflectedTile

CIGloom

CIHardLightBlendMode

CIHatchedScreen

CIHighlightShadowAdjust

CIHoleDistortion

CIHueAdjust

CIHueBlendMode

CILanczosScaleTransform

CILightenBlendMode

CILightTunnel

CILinearGradient

CILineScreen

CILuminosityBlendMode

CIMaskToAlpha

CIMaximumComponent

CIMaximumCompositing

CIMinimumComponent

CIMinimumCompositing

CIModTransition

CIMultiplyBlendMode

CIMultiplyCompositing

CIOverlayBlendMode

CIPinchDistortion

CIPixellate

CIRadialGradient

CIRandomGenerator

CISaturationBlendMode

CIScreenBlendMode

CISepiaTone

CISharpenLuminance

CISixfoldReflectedTile

CISixfoldRotatedTile

CISmoothLinearGradient

CISoftLightBlendMode

CISourceAtopCompositing

CISourceInCompositing

CISourceOutCompositing

CISourceOverCompositing

CIStarShineGenerator

CIStraightenFilter

CIStripesGenerator

CISwipeTransition

CITemperatureAndTint

CIToneCurve

CITriangleKaleidoscope

CITwelvefoldReflectedTile

CITwirlDistortion

CIUnsharpMask

CIVibrance

CIVignette

CIVortexDistortion

CIWhitePointAdjust



[Tips][Objective-C][iOS]1行で iOS バージョン判定できる便利マクロ

$
0
0

下記のようにマクロを定義しておけば、

#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] \
compare:v options:NSNumericSearch] == NSOrderedAscending)

こんな感じで1行でバージョン判定できて便利です。

if (SYSTEM_VERSION_LESS_THAN(@"6.0"))
{
    // ios 5.x and below
}
else
{
    // ios 6.0 and above
}

普通の実装をマクロで定義しただけですが・・・



[Tips][iOS][Objective-C]AVFoundation 使用時のカメラ起動を高速化する

$
0
0

AVFoundation を用いて静止画とか動画とかを撮影する場合に、カメラの起動時間を速くする方法です。


計測してみると、どうも AVCaptureSession の startRunning に一番時間がかかってるので、

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    
    [self.captureSession startRunning];

    dispatch_async(dispatch_get_main_queue(), ^{
        
        // メインスレッドでの処理(UIまわり)
    });
});

こうすることで体感起動時間を速くできました。

(要は startRunning をバックグラウンドで処理するだけ)


ただ、AVCaptureSession の startRunning は非同期で行われていてそれ自体は速くはなっていないので、プレビュー用の AVCaptureVideoPreviewLayer を使い回している場合は、カメラ画面に遷移したあと startRunning が完了するまでの間、プレビューレイヤに前回撮影時の画像が描画されたままだったりします。その場合は、

self.previewLayer.hidden = YES;

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{

    [self.captureSession startRunning];

    dispatch_async(dispatch_get_main_queue(), ^{
        
        self.previewLayer.hidden = NO;
        
        // メインスレッドでの処理(UIまわり)
    });
});

こんな感じで出したり隠したりして対処しました。


僕の実装ケースにしか当てはまらないのかもしれませんが・・



面白法人カヤックを退職しました。

$
0
0

今日は本年度の締め日であるとともに、僕のカヤックの最終出社日でした。


この件については、カヤックでやってきた仕事を振り返ったりとか、今後のこととかいろいろ書きたいことがあるのですが、今日はビールを多少嗜んでしまったので、とりいそぎ社内に流したメール(を一部編集したもの)をもってお知らせさせていただきます。


みなさま、おはようございます。

堤です。


チーム内では朝礼で挨拶をさせていただく機会があったのですが、

他のチームにもお世話になった方がたくさんいるので、

改めて全体に挨拶させていただきます。


12/31をもってカヤックを退職することになりました。


2010年の年始の「ぜんいん社長合宿」がカヤック初参加だったので、

ちょうど3年間お世話になったことになります。


別業界で働いていた頃、仕事が100%楽しめてなくて鬱々としていて、たまたま本屋で手に取った『カヤック会社案内』という本がカヤックとの出会いでした。


その本でカヤックの考え方に惚れ込み、ディレクターとして受けてみる実績も経験もないため落ち、それでもあきらめられなくて、webの勉強をして再挑戦。

2度目で「まぁ、いいんじゃない?」(byやなさん)と、何とか拾っていただくことができました。


なんとか憧れのカヤックに入れたものの、そこからがまた失敗続きで、サーバーサイドエンジニアとしてはまったく役に立てず(SQLのselect文から、あるいはsvn statusから教えてもらってました。。)、iPhoneやりたいと言って任せてもらった簡単なアプリの仕事でもcapがクライアントにあやまりにいくぐらいのミスをしたりと、ダメダメでした。


そんな中、カヤックに入って4ヶ月程経った頃、清さんから「アプリに専念してみたら?」と声をかけてもらったのが転機でした。

ディレクターとしては採用されず、サーバーサイドエンジニアとしては4ヶ月やっても成長が見られず、しかも当時のカヤックとしては年齢もだいぶ上の方だったので、自分にとっては最後のチャンスでした。


それからは、クライアントワークとブッコミでひたすらアプリをつくりまくり、ベトナムで玉田さん、はっしーさん、大塚さんとEncountMeをつくり、それをきっかけにかいちさんに声をかけてもらってソーシャルに移ってKOFをつくり、そこで一緒にやった嶋田さんと、ちょうどGoGoCargoを終えたマツケンさんとタップ忍者モンアツをつくり、その後阿部さんの秘蔵企画・バウモンをつくり。。


最高に充実した3年間を送らせていただきました。生きている実感がはんぱなかったです。



そんなカヤック、辞めたいわけがないです。


ただ、ずっと以前から、カヤックに入るよりももっと前から「海外で働きたい」という憧れがあって、それは以前であればただの憧れでしかなかったのですが、iOSアプリをつくれるようになった今では

「今ならいける。というか今行かなかったら一生行けない気がする」

と思うようになり、それで退職を決意しました。

バウモンの開発がひと段落したらこの思いを伝えようと思っていました。


そんなこんなで、次の就職も、どの国に行くかもまだ決めてませんが、とにかく海外に飛び込んでこようと思います。

こんな話を、面談時にスッと理解して受け入れてくれた役員の御三方には本当に感謝です。



3年間、どうもありがとうございました!!!!!


長いですが、要約すると、みなさんお世話になりました、とにかく海外に行きたいです、という話です。


英語も全然話せないので、まずは行くだけ行って、日本の仕事をしつつ、住み慣れるところから始めたいと思っています。(まずはヨーロッパに行きたいと思っています。でもアメリカもいずれ行ってみたい)


遠隔でもいいから仕事あるぜ!とか、海外就職先紹介できるかもよ!とか、とりあえず飲もう!という方はご一報いただけるとうれしいです。今後ともどうぞよろしくお願いいたします!



若輩者ではありますが、iOSの技術書を書かせていただくことになりました。

$
0
0

ちょうど退職の意志を役員に伝えた1週間後ぐらい(出来すぎたタイミング!)に、とある出版社の方より、「書籍企画のご相談をさせていただきたく」とメールをいただきました。


Qiitaに書いた記事 を見てご連絡いただいたようです。


(後述しますが、)僕にはこれ以上ないほどありがたい話だったので、トントン拍子で話が進み、無事企画承認まで漕ぎ着けました。


内容

詳しくはまだ書けないのですが、方向性としては、このブログに書いてるような、iOS開発まわりのTipsというかレシピ集みたいな感じです。自分で調べて作ると時間がかかるとか、応用が効くとか、あまり他に情報がないといったTipsを100個ほど書きたいと思っています。


スケジュール的な話

もちろん本を書くのは初めてなので、常識的なスケジュール感がわからず、でもあんまり長期にわたるスケジュールを提示するとこの奇跡的にありがたいお話がなくなってしまうかもしれない・・・とびびって「1月中旬〜2月いっぱいまでで書き上げます」と、厳しいけどいけるような気がしなくもない、というギリギリのラインで提示しました。


普通はもっと長いらしいです。でももうそれで企画が通ってしまったのでやるしかありません。毎日6ページぐらい書かないと間に合わない計算です。


ブログに今まで書いてたことはベースにはなるものの、文章の品質や情報の精度の観点からそのままは使えないので、基本的に全部書き下ろす必要があります。なんだかこうして書けば書くほどヤバさが実感できてきたので、このあとすぐにでも執筆にとりかかろうと思います。


あれ、海外は?

先日の『面白法人カヤックを退職しました。』にちらっと書いたとおり、まずは海外に住み慣れる、ということから始めたいなぁと思ってまして、とりあえず行くだけ行ってあとは家(宿?)に籠って本を執筆しようと考えてます。


僕は今のところ英語で会話なんてできず、日本以外に住んだこともなく、コネや就労ビザもないので、こうして遠隔でやりやすい仕事でワンクッションおけるのはほんとありがたいです。


執筆し終えたら、ちゃんと英語を勉強しつつ海外就職活動する予定です。


iOSアプリ開発者のみなさまへ

正しく有益な情報の載っている本にしたいと思いますので、僕がTwitterやこのブログで間違ったこととか変なことを言ってたら、ぜひツッコミをいただけると嬉しいです!




仕事に集中するための仕組みづくり

$
0
0

今日からひとりで仕事なので、いろいろと Mac アプリや chrome 拡張入れたりして「さぼらない環境づくり」をしてみました。(これが既にさぼってるわけですが)


「なんとなく Facebook や Twitter を開く」の防止

プログラミングや執筆に行き詰まると、無意識のうちにメールや SNS を開いていることがあります。僕にとってあれはソーシャルゲーム以上に射幸心を煽られるものなので、それらを抑制すべく Chrome 拡張の WaistNoTime を導入しました。


f:id:shu223:20130107181232p:image:w300


もともと上記のような感じでサイトへの滞在時間のログをとって自分を戒めるつもりで入れたのですが、導入してみてから「Block List」と「Allow List」という機能に気付き、これが僕にとって理想的なサボり防止機能だったので、今はログはまったく見ておりません。


Block List の活用

Block Listの設定画面はこんな感じになっていて、


f:id:shu223:20130107183411p:image:w400


このスクリーンショットからもおわかりの通り、僕の場合は次の3つのURLをブロックリストに入れています。

*.facebook.com

*.twitter.com

*mail.google.com


これで chrome から Twitter や Facebook や gmail にアクセスできなくなります。(アクセスできる時間帯とか、何分までアクセスできるとか、わりと制約範囲を細かく設定できます)


僕がうっかりFacebookにアクセスしようとすると、こんなふうに怒られます。



f:id:shu223:20130107181235p:image:w300



アクセスするには別のブラウザを立ち上げるとか、設定を変えるとか、iPhoneにクライアントアプリをインストールするとかしないといけない(※)ので、僕にとって十分なサボり抑止力になっています。


(※)iPhone内のTwitterクライアントアプリ、Facebookアプリは、使うときに入れて、使い終わったら削除、という運用にしてます。常時入れてるとチマチマ見てしまうので。


Allow List の活用

調べものをしてて有用な情報とかがあればツイートだけはしたいので、Allow Listに次のURLだけ入れています。

https://twitter.com/intent/tweet

これで、Twitterへのアクセスは投稿だけ許可されることになります。


「いま何やってたんだっけ?」の防止

上記は割り込み防止の仕組みづくりだったわけですが、ちょっとでも何か別のことに気をとられると、本当にすぐに「何やってたんだっけ?」となってしまいます。


『リファクタリング・ウェットウェア』という本によると、「何やってたんだっけ」から前の没入状態に戻るために必要な時間(つまり頭の中に作業に必要な情報を再度ロードするための時間)は何と20分もかかると述べられています。



これは大いに心当たりがあるので、割り込みを減らすための対策はもちろんのこと、「何やってたんだっけ」からなるべく早く復帰するために次のように仕組み化してみました。


  1. DAY ONE を Alfred のホットキーに登録してキー1発で起動できるようにする
    • 僕は F3 に割り当てました。あと、設定から起動時にタイムラインビューが表示されるよう変更しました。
  2. 作業にとりかかる前に、DAY ONE に「今やること」(ToDoリストではなく、今からやること1項目だけ)を入力しておく。
  3. 「何やってたんだっけ?」となったら F3 で確認!

もっとこういう用途に特化したアプリがありそうですが。。



参考:

どこからでも入力して一瞬で同期するメモツール、Day One

これは便利!Macでのアプリ起動を楽にしてくれるアプリ「Alfred」


辞書へのアクセスを便利に

ハッカーな方々はコード書いてるエディタからコマンドうって辞書検索できるようカスタマイズしてたりするみたいですが、情弱な僕はブラウザ立ち上げて検索してました。


でもブラウザはいろいろ誘惑があるしそもそも面倒なので、 翻訳タブ (Translate Tab) という mac のメニューバーに Google 翻訳を常駐させるアプリを入れて、PopClip の Traslate Tab 拡張で文字選択したらそのまま辞書検索できるようにしました。


f:id:shu223:20130107181237p:image:w300





Time Sink で作業内容を定量化

Time Sink というのは、どの Mac アプリをどれぐらい使ったかのログをとってくれるアプリです。自分の場合は Xcode もしくはテキストエディタをさわってる時間を最大化することが重要だと思ってるので、その辺りを定量化すべく導入しました。


f:id:shu223:20130107181238j:image:w400


結局こういうログってあんまり見なくなる気がするのですが、このアプリはメニューバーからすぐにログを見れる menu bar mode というのがあるので、気軽にログ確認できるようになっています。


あと、毎日自動でその日のレポートを特定フォルダにエクスポートしてくれる機能がよさげです。(まだ一度も見てないけど)



参考:

タスク管理に使える!Macの作業ログをカンタンに取得できる「Time Sink」



[画像処理][iOS][Objective-C]『フラグメントシェーダー事始め』で勉強したメモ

$
0
0

OpenCVで実装した画像処理アルゴリズムを、OpenGL ESのフラグメントシェーダに移植しようとしていて、GPUImageに入っているフィルタのシェーダプログラムを参考として読み始めたものの、そもそもこのシェーダというものがよくわかってないので、下記の記事を読みつつ勉強してみました。


EZ-NET: Objective-C フラグメントシェーダー事始め


この記事は頂点シェーダについても基本から丁寧に解説されているのですが、以下は主にフラグメントシェーダまわりについて勉強になったことの抜粋です。


頂点シェーダとフラグメントシェーダ

どちらかを使うというわけではなくて、ポリゴンの頂点座標やテクスチャ位置などを元データとして、頂点シェーダーではポリゴンデータをクリッピング座標系に変換し、それを元に得られた画素データをフラグメントシェーダーが受け取って加工するという流れになるようです。


lowpとかhighpとか

精度の詳細はシェーダー言語や GPU に依存するらしいのと、詳しいことは解りませんが、シェーダー言語が演算する際にこの精度が影響してくるらしいですが、必要になったら気にしだせば大丈夫なような気がします。


0.0 から 1.0 で表す色のような情報であれば lowp で十分らしいですが、同じ 0.0 から 1.0 でも精度が必要になってくると mediump みたいな選択をするというお話もありました。


precisionは、データ型に既定の精度を設定したい場合に使うようです。

precision mediump float;

uniformとか

  • attribute:頂点シェーダーで、プログラムから受け取る変数に指定します。
  • uniform:頂点シェーダーやフラグメントシェーダーで、プログラムから受け取る変数に指定します。
  • varying:頂点シェーダーで代入して、フラグメントシェーダーでそれを受け取る変数に指定します。

texture2D(handle, xy)

テクスチャのある座標上の色を取得する関数。


gl_FragColor

フラグメントシェーダーで、ピクセルの色を決定する変数です。ここにセットされた色が、そのピクセルの色になります。


以上をふまえてGPUImageのビルトインフィルタのシェーダプログラムを読むと、処理内容がグッと理解しやすくなりました。


執筆、海外就職、その他もろもろについての近況報告

$
0
0

前職をやめて3週間ほど経ち、いろいろ進んだり滞ったりしてきたので、ここらで近況報告をさせていただきます。


iOSアプリ開発本の執筆

100個Tipsを載せるつもりで書いていて、1/18(土)現在の進捗状況は 16/100 です。


まったく進んでないわけではないですが、昨年末に立てた計画ではすでに 38/100 ほど進んでいるはずなので、えらい遅れてます


書く内容や書き方についていろいろ迷いがあって遅れてしまったのですが、こうやってちゃんと書いてみると、1日1本ずつノルマを増やしてやっとギリギリ間に合う、というレベルの遅れ。


迷うのはあとにしてとにかく書き進めます。。


Qiitaへの投稿

1/6〜1/10ぐらいに続けてQiitaに投稿していたネタは、実は書籍用に書いたものです。


http://qiita.com/users/shu223


中間アウトプットがあった方がモチベーションの持続になるし、技術的に変なことを書いていればツッコミもいただけるし、と思ってアップしてたのですが、Qiita自体が注目されているのもあって意外とアクセスがあり、本が出たときに「全部webで見れる ★1」みたいなレビュー書かれるとつらいので、ストップしました。


「限定公開機能」というのを知ったので、今後はそちらを利用するかも。

(今はローカルで書いてます。)



海外進出

パスポートの有効期限がちょうど切れるところだったので、切り替えとかしてたら1月出発は無理になり、2月前半出発になりました。


2月前半〜3月前半までスペインのバルセロナに行ってきます。主な目的は執筆と、(旅行ではなく)海外生活をしてみること。


なぜスペイン?

これはよく聞かれるのですが、あんまりまともな理由はないです。


寒いところは嫌だ、というのと、イギリスやアメリカなどの就職の本命となる国は(本を書き上げたあとに)英語の勉強をしてから挑戦したい、という事情があります。


あとは単純にいちど行ってみたかったというのが大きいです。


Airbnbでのアパート探し

バルセロナで住むところは Airbnb で探しました。


コンセプト自体魅力的なサービスですが、探す際にも予約する際にもいろいろ気の利いた細かい工夫があって、便利だし、ワクワクするし、安心にも配慮されているしで、サービスとしての完成度が非常に高いと感じました。


オンラインでの航空券探し

航空券もオンラインで取りました。


「オンライン予約機能を提供しているサイトは結構あるけど、どれも結局航空会社のシステムに接続してるわけだし、サイトがとる手数料なんて雀の涙みたいなもんだからほとんど違いはないはず。とりあえず大手で検索しとけばいいや」


と思ってたのですが試しにいろんなオンライン予約サイトで照会してみたら、違いは大アリでした。同じ航空会社/同じ便でも空席照会結果はサイト毎に全然違いました


あと使い勝手も全然違いました。HIS、スカイゲートは変なところでページ遷移しないので使いやすかったです。


最終的にはたまたま所望の便の空席があった「フリーバード」ってとこで購入。(この便はHISとスカイゲートでは満席、楽天トラベルではちょっと高かった)


スペインでの就職活動

もともと最初の海外滞在中は執筆だけでいいやと思ってたのですが、行き先をスペインに決めたあとに、「せっかく行くことだし、iOSアプリ開発の仕事があるならダメもとで受けてみよう」と思い立ちました。


そもそもスペインにどんな会社があるのか知らないので、LinkedInで「iOS」というキーワードで求人を検索。2つほどiOSアプリ開発を職務に含む求人がありましたが、スペイン語なので詳細不明。。


せっかくなので応募はしてみようと思います。


フリーランス的な活動

固定収入がいまのところなく、書籍の印税が入るのは遥か先の話、本命の海外就職はできるのかどうかすら怪しいというかまだ活動してない、という状況なので、ちょこちょこ個人で仕事をさせていただいてます。


キンコーズで特急名刺

「ごはんとFlash」というweb業界の人達が大勢集まるイベントでお仕事募集のプレゼンをすべく10分宣伝枠を買ったのですが、そもそも名刺がないという大いなる矛盾にイベント当日の午後に気付き、キンコーズに電話して3時間特急コースで名刺を作成してもらいました。


気になるお値段は100枚で3500円ぐらい。いままで名刺をつくったことないので相場がわかりませんが、こだわりのインクとか紙とかじゃないわりには高い気はします。。


ごはんとFlashでの発表

お仕事募集しております。



交通費精算

人に会うことが多くなり、しかも住まいが鎌倉なのでやたら交通費がかかります。ちゃんと経費にすべく「交通費精算」っていう有料アプリで交通費をメモってるのですが、デザインと、経路探索と連動していない点がとてもつらいです。


まとめ

  • 執筆遅れてるのでがんばります。
  • スペインにいってきます。
  • お仕事も募集してます。

[海外就職]はじめて英語でSkype面接受けたメモ

$
0
0

いまスペインのバルセロナにおります。


(Airbnbで借りたアパートからの風景)


今回は就職活動が目的ではないのですが、LinkedInでバルセロナにオフィスがある会社のiOSアプリ開発者の求人を見つけたので、申し込んでみました。


で、初めて海外の会社のSkype面接を受けたので、そのメモを残しておきます。


※なぜスペインにいるの?というあたりの経緯はこちらをご参照ください


経緯

冒頭にさらっと書きましたが、Skype面接を受けるまでの経緯は

  • LinkedInで3件ほど「バルセロナの会社で、かつiOS開発者を募集している」求人を見つけて、申し込む
  • うち1件は速攻で返信がきて落ちる
  • 数時間後もう1件の担当者からメールがあり、スカイプ面接の打診を受ける
  • すぐに提示された日程の中で一番早い時間を指定してOKの返信をする

という感じです。申し込み〜実際の面接まで30時間ぐらい。ちなみにもう1件は今のところ返信がありません。


自分の英語力

こちらの記事で書かれているところの、レベル3ぐらいです。「技術ブログや技術書も頑張れば読める」けど、英語でプレゼンするとか、講演を聞くとかは無理。飛行機や空港の機内放送も全然聴き取れません。


最近英語でメールのやりとりをする機会がたびたびあり(たとえば今回のSkype面接の件とか)、以前は30分かかって書いていた返信を、10分ぐらいで書けるようにはなりましたが、まだまだリアルタイムで英語が出てくるにはほど遠い感じです。


準備したこと

下記事項については、言いたいことを英文にしておきました。自己紹介は言葉に出して練習もしました。

  • 自己紹介
  • 志望理由
  • いつから来れるか?
  • 自分の作品(アプリ)の紹介

本番!!

指定の時間の5分前にSkypeの連絡先追加のリクエストがあり、時間ぴったりにCallが来ました。


で、結果的には、自己紹介すらする機会もなく、「"working permit"(就労ビザ)はあるか?」「ありません」というでいきなり「おお、それは厳しいね」という話になってしまい、5分ちょいで面接は終了


英語力の観点からの反省

自己紹介もできない、たった5分のわずかな時間でしたが、自分の英語力の低さを痛感するには十分でした。


まず、5単語ぐらいまでの短い文なら聴き取れるものの、相手がちゃんと話し出すとまったく聴き取れません


で、「ちょっと待ってください」「もうちょっとゆっくり話してください」とか、そういう言葉がパッと出てこないのでそのまま聞いちゃったりして、さらに「今の質問はこういうことでしょうか」といった言葉も出てこず、憶測で(志望動機かな・・・?)と思って用意してあった志望動機を話してみたら相手が「・・・・・・・・(テンション下がった感じで)OK, (話題が変わる)」となって違ったっぽかったりして痛々しかったです。


Speaking力はListening力にも繋がるのだなぁと。


Working Permitの話は、実は以前にメールのやりとりでそういう話があったので、詳細はやはり聴き取れないものの、「あ、就労ビザがないから厳しい、という展開か」ということは予測したうえで理解することができました。やはり相手の話への相づち的な言葉が出てこずThank you Thank youばっかり言ってましたが。。(←相手が申し訳なさそうに話してるので、ご配慮ありがとうございます、なニュアンスで言ってるつもり)


今後の海外就職に関する展望

以前にイギリスの会社で働いている方を紹介していただいてメールのやりとりをしたときも、最初に就労ビザの話になって終了、ということがありました。アメリカのビザが厳しいという話もよく聞くので、EU圏やアメリカでの就職に関していえば、英語力の問題を克服できたとしても、ずっと今回のように就労ビザの壁はついてまわりそうです。


ただ、そもそも自分にとっての海外就職は、「いろんな場所でいろんな人と仕事したい」という目的があって、そのための修行としてちゃんと一度は海外の会社に就職した方がいいだろう、という順番なので、まずはこのクソ過ぎる英語力をなんとかすれば本来の目的には近づけるのかもしれません。


たとえば、最近知り合った方の話で「海外の友人と組んでサービスを作っていて、海外のファンドからオファーを受けた、アメリカにオフィスを持つことも打診された」という話を聞きました。ものづくりに精を出していたら海外に行くことになった、というのはクリエイターとしては一番理想的な展開のように思います。(この方は海外に行きたくてそうしてたわけではありませんが。)



何はともあれ英語がなんとかなればもっと人生楽しくなりそうなので、なんとかしようと思います。



[オーディオ][iOS][Objective-C]ダウンロード可能な Audio Unit 関連のサンプルコード11個

$
0
0

Audio Unit は、iOS の Core Audio においてもっとも低レベル(ハードウェアより)に位置するフレームワークです。そのため低レイテンシを要求されるオーディオ処理機能を提供するアプリに向いています。


というのがよく言われる Audio Unit のメリットなのですが、個人的には Audio Unit の「ユニットをつなげて複雑なオーディオ処理を実現する」というしくみ(AUGraph)がまるでギターのエフェクターをつないで音をつくる感じに似ていて、そのあたりも興味深いポイントとなっています。


ただ API は全部 C ベースだし、音を再生するだけでも(AVAudioPlayer 等と比較すると)複雑だったりするので、まずは参考になるサンプルをいろいろと集めてみました。

  • どの Audio Unit を使用しているか(kAudioUnitSubType_xxxx で判断)
  • どういうサンプルか
  • 最終更新はいつか

の観点からまとめています。


使用ユニットの項目で、そのユニットを使用しているサンプルが他にない場合は、貴重なサンプルである、という意味をこめて太字にしています。


  • (2013.2.22追記情報)実際に参考にしつつ実装してみてわかったことなどを追記。
  • (2013.2.23追記情報)DrumMachine を追加。

iPhoneMultichannelMixerTest

【使用ユニット】

  • Remote IO (kAudioUnitSubType_RemoteIO)
  • Multi Channel Mixer (kAudioUnitSubType_MultiChannelMixer)

【サンプル概要】

on/offとボリュームをチャンネル毎に変えられる、2チャンネルのミキサー


f:id:shu223:20130220235308p:image:w240


オーディオファイルの再生と、複数チャンネルを扱いたい場合に参考になります。


(2013.2.22追記)PublicUtilityという、Appleが配布しているCore Audioのユーティリティクラスに依存しており、これを参考に自分のアプリを作ろうとするとこのユーティリティクラスまわりが若干面倒なことになります。(プリプロセッサマクロにDEBUGを定義していると別のユーティリティクラスを持ってこないとビルド通らないとか、このサンプルに付属しているコードと、別途Appleが配布しているものとバージョンが違っていたりとか。)


【最終更新日】

2010-07-07


Audio Mixer (MixerHost)

【使用ユニット】

  • Remote IO (kAudioUnitSubType_RemoteIO)
  • Multi Channel Mixer (kAudioUnitSubType_MultiChannelMixer)

【サンプル概要】

on/offとボリュームをチャンネル毎に変えられる、2チャンネルのミキサー。


f:id:shu223:20130220171932p:image:w240


サンプルの機能としては、上の "iPhoneMultichannelMixerTest" とまったく同じに見えるのですが、なぜこの2つが存在するのでしょうか。。


こっちは、そのままビルドするとエラーになるので、xibをちょっといじる必要があります。


(2013.2.22追記)こちらのサンプルは、"iPhoneMultichannelMixerTest" と違いPublicUtilityに依存していないので、とりあえずマルチチャンネル再生の参考にするならこっちの方がミニマルでわかりやすいかと思います。


【最終更新日】

2010-07-27


AVCaptureAudioDataOutput To AudioUnit iOS

【使用ユニット】

  • Delay (kAudioUnitSubType_Delay)
  • Converter (kAudioUnitSubType_AUConverter)

【サンプル概要】

AVFoundation の AVCapture で録音した音声に、Audio Unit でディレイエフェクトをかけ、ExtAudioFile でファイル出力するサンプル。


f:id:shu223:20130220171439p:image:w240


Audio Unit の Delay Unit を取り扱っているのは今回紹介する中では唯一、という点だけでも貴重なサンプルですが、AVCaptureSession の captureOutput:didOutputSampleBuffer:fromConnection: で取得した波形データを Audio Unit に渡してエフェクトをかける方法、Audio Unit から ExtAudioFileWriteAsync でファイル出力する方法、どっちも重要なので、見た目は地味ですがコードはとても参考になるサンプルとなっています。


【最終更新日】

2012-10-08


DrumMachine

【使用ユニット】

  • Sampler (kAudioUnitSubType_Sampler)
  • Remote IO (kAudioUnitSubType_RemoteIO)

【サンプル概要】

Sampler Unit を使用した TR909(ドラムマシン)的なサンプルアプリ。4つの音をタイムライン上でon/offすることでリズムを作る。


f:id:shu223:20130223092258p:image:w480


ちなみに作者は Core Audio本の著者である永野さん。


【最終更新日】

2013-02-23


Sampler Unit Presets (LoadPresetDemo)

【使用ユニット】

  • Sampler (kAudioUnitSubType_Sampler)
  • Remote IO (kAudioUnitSubType_RemoteIO)

【サンプル概要】

楽器を切り替えるボタンと、Low, Mid, High の音を鳴らすボタンをもつサンプラー


f:id:shu223:20130220172310p:image:w240


Sampler Unit を使用するものは他にもありますが、AUPreset ファイル(拡張子.aupreset)なるものを使用する点では唯一のサンプルです。(中身見たけどどういう調整をするものなのかよくわかりませんでした。DJとかやる人はピンとくるのかも。)


【最終更新日】

2011-10-12


Reverb

iOS5プログラミングブックのサンプル。ダウンロードURLは書籍をご参照ください。


【使用ユニット】

  • Remote IO (kAudioUnitSubType_RemoteIO)
  • Converter (kAudioUnitSubType_AUConverter)
  • Reverb2 (kAudioUnitSubType_Reverb2)
  • Multi Channel Mixer (kAudioUnitSubType_MultiChannelMixer)

【サンプル概要】

マイクから入力された音声にリバーブをかけるサンプル。


f:id:shu223:20130220172311p:image:w240


唯一の Reverb2 のサンプル。マイク入力、エフェクト、マルチチャネルミキサーと、Audio Unit の基本が学べて、リバーブのパラメータをいろいろいじれて楽しい好サンプルです。


書籍の方も、AUGraph や AUNode の概念から、ひとつひとつのユニットの解説まで、わかりやすい図入りで説明されているので激しくおすすめです。


iOS5プログラミングブック
加藤 寛人 吉田 悠一 藤川 宏之 西方 夏子 関川 雄介 高丘 知央
インプレスジャパン
売り上げランキング: 88968


audioGraph

【使用ユニット】

  • Remote IO (kAudioUnitSubType_RemoteIO)
  • Multi Channel Mixer (kAudioUnitSubType_MultiChannelMixer)
  • Low Pass Filter (kAudioUnitSubType_LowPassFilter)
  • High Pass Filter (kAudioUnitSubType_HighPassFilter)
  • Audio File Player (kAudioUnitSubType_AudioFilePlayer)
  • Sampler (kAudioUnitSubType_Sampler)

【サンプル概要】

マイク入力とかシンセ音とかファイルからの音とかをマルチチャネル再生しつつ、エフェクトをかけられる。ステレオレベルメータつき。


f:id:shu223:20130220235309p:image:w240


Audio Unit のもろもろ詰め合わせといった感じです。エフェクトはリングモジュレータやピッチシフターも実装されています。


(2013.2.22追記)いろんな機能が入ってるのですごく参考になりそうですが、いかんせんコードが汚いです。1ファイル4000行とかあって、コードの見通しも悪く、参考にするにも結構骨が折れます。(ただしコメントは多い)


【最終更新日】

2013-01-20


iPhoneMixerEQGraphTest

【使用ユニット】

  • Remote IO (kAudioUnitSubType_RemoteIO)
  • iPod EQ (kAudioUnitSubType_AUiPodEQ)
  • Multi Channel Mixer (kAudioUnitSubType_MultiChannelMixer)

【サンプル概要】

iPod のイコライザー効果をかけるサンプル。(グラフィックイコライザーではなく、プリセットから選択する)


f:id:shu223:20130220172312p:image:w240


【最終更新日】

2010-06-25


aurioTouch2

【使用ユニット】

  • Remote IO (kAudioUnitSubType_RemoteIO)

【サンプル概要】

マイク入力の波形を描画するサンプル。通常の時系列での波形表示の他、周波数ドメイン(FFTを使用)表示、ソノグラム表示もあり。


f:id:shu223:20130220172313p:image:w420


Audio Unit の使用は Remote IO による入出力のみ。FFT には Accelerate フレームワークの vDSP が、波形やソノグラムの描画には OpenGL が使用されています。


【最終更新日】

2011-12-06


iPhone Core Audioプログラミング

※ Audio Unit のサンプルが多数あるので、これだけ例外的に書籍名をタイトルにしました。サンプルコードのダウンロードURLは書籍をご参照ください。


【サンプル概要】

各項目毎にサンプルが用意されているので、目次を載せておきます。

  • Audio Unitの概要
  • Audio Component とAudio Unit
  • Audio Unit 正準形
  • サイン波を再生する
  • 周波数を変更する
  • Examole -加速度センサーテルミンー
  • Audio Unit Processina Graoh Services
  • MultiChannel Mixer Unit
  • 3D Mixer Unit
  • Converter Unit
  • iPod Equalizer Unit
  • マイクのモニタリング
  • Remote IOを使って録音する
  • レコーデイングレベルを調整する
  • Voice Processing IO Unit

【使用ユニット】

たくさんあるので全ては確認できていませんが、iOS3.x時代にあったユニットは全部入っていると思われます。(多くのユニットがiOS5で追加されましたが、iOS5登場以前の本なのでそれらについては載っていません)


iPhone Core Audioプログラミング
永野 哲久
ソフトバンククリエイティブ
売り上げランキング: 28453


RemoteIO

※iPhone SDK アプリケーション開発ガイドのサンプル。サンプルコードのダウンロードURLは書籍をご参照ください。


【サンプル概要】

Remote IOでサイン波を鳴らすサンプル。書籍の Audio Unit の章は Core Audio 本の著者永野さんが書いているので、Core Audio本のChapter09のサンプルと同様と思われます。

Core Audio本は絶版になっていて中古で高値がついてしまってますが、こっちはまだ普通に買えるようです。


iPhone SDK アプリケーション開発ガイド
Jonathan Zdziarski
オライリージャパン
売り上げランキング: 28500


Audio Unitの参考資料



[オーディオ][iOS][Objective-C]Audio Unitのコールバック関数を登録する方法3種の比較

$
0
0

Audio Unit で、コールバック関数を登録するための方法が何通りかあって、何だかややこしかったので整理してみました。


AudioUnitSetProperty

まず一番基本的なものである AudioUnitSetProperty から。

AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = renderCallback;
callbackStruct.inputProcRefCon = &audioDataInfo;
AudioUnitSetProperty(hogeUnit,             // 対象となるAudio Unit
                     kAudioUnitProperty_SetRenderCallback,
                     kAudioUnitScope_Input,     // スコープ
                     0,                         // バスナンバー
                     &callbackStruct,           // AURenderCallBackStruct構造体へのポインタ
                     sizeof(callbackStruct)     // AURenderCallBackStruct構造体のサイズ
                     );

AudioUnitSetProperty は Audio Unit に対して各種プロパティをセットするための関数です。ここでは {対象となるAudio Unit}{スコープ}{バスナンバー} に対して kAudioUnitProperty_SetRenderCallback というプロパティに AURenderCallBackStruct 型の値を指定しています。


引数は多いですが、そのぶん暗黙的な部分がなくてわかりやすいです。


AUGraphSetNodeInputCallback

こちらは AUGraph の特定のノードの入力に対してコールバック関数を指定するための関数。

AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = renderCallback;
callbackStruct.inputProcRefCon = &audioDataInfo;
AUGraphSetNodeInputCallback(processingGraph,    // 対象となるAUGraph
                            hogeNode,      // 対象となるAUNode
                            0,                  // バスナンバー
                            &callbackStruct     // AURenderCallBackStruct構造体へのポインタ
                            );

暗黙的にスコープは kAudioUnitScope_Input、構造体のサイズは AURenderCallBackStruct のサイズなので、AudioUnitSetProperty と比較するとこれら2つ分の引数が省略されています。


AUGraphAddRenderNotify

こちらは AUGraph がレンダリングを行った際に通知を受けるコールバック関数を登録するための関数。

AUGraphAddRenderNotify(processingGraph,
                       renderCallback,
                       (__bridge void *)(self));

これを使用した場合、AUGraph の最後、たとえば Remote IO Unit でスピーカー出力している場合は、その出力時に通知を受け、コールバックが呼ばれるようになります。


AURenderCallbackStruct 構造体を介さず直接コールバック関数と、コールバック関数で参照するデータ(inputProcRefCon にセットしていたもの)を渡します。



[海外就職]34歳無職のiOSプログラマが1ヶ月間スペインに滞在してきたまとめ

$
0
0

約1ヶ月間(23泊26日)、スペインはバルセロナに滞在してきました。刺激的でかっこいい紀行文もしくは滞在記みたいなのを書けるのであれば書きたいところですが、それだと永遠に書かないまま終わりそうなので、箇条書きベースでまとめたいと思います。


目的とか経緯とか

ざっくりいうと、「海外就職したいけど、とっかかりもないので、まずはちょっとだけ海外に住んでみたりしてみよう」というのが目的です。


(経緯)


スペインでやったこと

ほぼアパートにこもってPCに向かってました。バルセロナを満喫した!とは言い難いですが、自分にとっていろいろ新しい&重要なチャレンジがあったので、成果には満足しております。


(やったことリスト)

  • 執筆
    • 31%(出発前) → 59%(帰国時点)
    • だいたい60ページ分
    • この倍は進める予定だった。。
  • 受託案件
  • 初の英語スカイプ面接
  • バルセロナの凄腕クリエイターと食事
    • 超いい人達だったので、英語はダメダメだったけど楽しかった
    • (行く前は吐きそうになるぐらい緊張した

    

  • 散歩
    • 毎日3〜8kmぐらい
    • 結局観光は一度も行けなかったけど、散歩中にサグラダファミリアを見ることはできた

    


住環境

割高にはなるけど、ずっと同じところよりはいろんなところに住んだ方が楽しいだろう、ということで前半の13日間はここに、


後半の10日間はここに住みました。


最初のアパートのあるエリアは、似たような建物がずっと立ち並んでて、店はほとんどシャッターが閉まってて、人通りも少なくて、今思えばだいぶ寂しいところでした。僕は初めてのスペインだったので、「やっぱり不況なんだなぁ」と無理矢理納得しつつ13日間を過ごしたのですが、後半のアパートのあるエリアが超オシャレで賑やかで、海も近ければ歴史的な建物や美術館も近いという好立地で、単に場所の選択の問題だったと気付きました。


住むエリアの下調べは重要!という気づきを得ました。ありがとうございます。


    (1件目のアパートからの風景)

    


    (2件目のアパートの内装)

    


    (2件目のアパートの近所にあった教会)

    

かかったお金

下記プラス、鎌倉の家賃とかインターネット代とかも普通にかかってることを考えると、だいぶ贅沢な企画となってしまいました。「生活」とは言い難い金額。

  • 渡航費:約21万円(2人分。燃油サーチャージ込み)
  • 家賃:約15万円(2人分。airbnb手数料込み)
    • 1泊あたり約6500円
  • 滞在中に使ったお金:未清算
    • なんとなく10万円ぐらいはかかってる気がする

持ちもの

普段日本でも(PC持ち運び用として)日常的に使ってるリュック+機内に持ち込めるサイズのスーツケースで行きました。


    f:id:shu223:20130306144541j:image:w300


(持ちものリスト)

  • 開発用機器:Macbook Pro 15インチ、アダプター、予備バッテリー(MBP用なので超でかい&重い)、バックアップ用ポータブルHDDなどなどなど。。
  • 着替え・・・下着2セットと寝間着
  • 地球の歩き方・・・地図とか、電車やバスの乗り方とか、やっぱり役に立つ(電車もバスも一度も乗ってないけど。。)
  • アパートのホストへのおみやげ・・・渡し忘れた
  • デジカメ・・・持ち歩かないので結局使わなかった
  • タオル・・・これも使わなかった。アパートにあったし、どこでも売ってる

スリッパ、シャンプーとかは現地調達しました。タオル等の日用品系、寝間着や下着も現地調達できることを考えると、もっと長期でもこのサイズで大丈夫そうです。


その他雑感など

  • 日本より暖かいので仕事ははかどった
    • 日本では暖房をフルパワーで効かせるか、カフェとかに行くかしないと手が冷たくて仕事に着手できない
    • スペインも寒いのは寒いけど手がかじかむほどではないので、夜中とか早朝にのそっと起きて仕事できた
  • 英文メールのライティング力は上がった
    • airbnbでアパート借りる際のやりとり
    • LinkedInでの応募にまつわるやりとり
    • Marcosとごはん食べる日程や場所を調整するやりとり
  • 海外就職はやっぱり厳しい
    • 就労ビザの壁と英語の壁
  • 就職にこだわらなければ、海外を転々としながら暮らしていくことはできそう
    • 遠隔で仕事を受注して納品するところまでやるのは十分可能
    • ただ、それだと英語力は一向に伸びないし、キャリアが薄められていくし、初心からずれてる感は否めない

最後に

そんな34歳無職のiOSプログラマに、Amazonのほしい物リストから何か送っていただけるととても喜びます。




[iOS][画像処理][Objective-C]Core Image の遷移エフェクトを使う

$
0
0

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

  • CIBarsSwipeTransition
  • CICopyMachineTransition
  • CIDisintegrateWithMaskTransition
  • CIDissolveTransition
  • CIFlashTransition
  • CIModTransition
  • CIPageCurlTransition
  • CIRippleTransition
  • CISwipeTransition

たとえば CIDisintegrateWithMaskTransition を使用すると、マスク画像を使用して次のような一風変わった遷移エフェクトを実現できます。



(gifアニメにするためFPSを大幅に下げてしますが、実際はもっとスムーズに遷移します)


宣言と初期化処理の実装

使い方の基本的な部分は 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 オブジェクトの生成と、表示領域を示す矩形、遷移アニメーション制御に必要な時刻とタイマーの生成を行います。


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: をコールし、受け取ったフィルタ処理結果の画像を描画しています。


サンプルコード

Githubにサンプルをアップしてあります。


CoreImageTransition


上記で例に挙げた CIDisintegrateWithMaskTransition ほか、6種類の遷移エフェクトを試すことができます。



※サンプルはシミュレータではなく実機でご確認ください。



.ipa ファイル生成を自動化する

$
0
0

通常.ipaファイルを作成するには、Product メニューの Archive 実行後に、

  • Organizer から アーカイブを選択し Distribute ボタン押下
  • "Save for Enterprise or Ad-Hoc Deployment" を選択し Next ボタン押下
  • ドロップダウンリストから該当するプロビジョニングプロファイルを選択
  • 保存先を指定

という手順を踏みます。ウィザードに従って進めるだけなので、たまに行う程度であれば問題ないのですが、.ipaファイルを作成して共有する頻度が多い場合(例えばリリースが近く細かい修正と確認を繰り返す場合)は面倒になってきます。


Xcode の Run Script を利用し、アーカイブ完了後に自動で ipa ファイルを生成するように設定する方法を紹介します。


※通常のアドホックビルドの準備の手順(アドホックビルド用のプロビジョニングプロファイルの作成とインストール、Configurationの作成と Code Signing Identity の設定)については説明を省略します。


準備

Build Phases タブの右下にある "Add Build Phase" ボタンから Run Script を追加し、次のスクリプトを登録するだけ

if [ "${CONFIGURATION}" = "<Configuration名>" ]; then
xcrun -sdk iphoneos PackageApplication "${INSTALL_DIR}"/<バンドル名>.app -o <出力パス>/<ipaファイル名>.ipa --embed ~/Library/MobileDevice/Provisioning Profiles/<プロファイルのファイル名>.mobileprovision
fi

<Configuration名>と<バンドル名>はプロジェクトの設定に従って置き換え、<出力パス>、<ipaファイル名>はそれぞれ任意のパス、名前に置き換えてください。


<プロファイルのファイル名>は、オーガナイザで該当するプロファイルを選択し、右クリックメニューの「Reveal Profile in Finder」からプロファイルの実体ファイルをFinderで表示して調べることができます。


たとえば、Configuration名が Ad Hoc、バンドル名が Hoge となるプロジェクトの.ipaファイルをホームフォルダ直下に自動生成したい場合のスクリプトは次のようになります。

if [ "${CONFIGURATION}" = "Ad Hoc" ]; then
xcrun -sdk iphoneos PackageApplication "${INSTALL_DIR}"/Hoge.app -o ~/Hoge.ipa --embed ~/Library/MobileDevice/Provisioning Profiles/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.mobileprovision
fi

実行

(Edit SchemeからArchiveのConfigurationとして "Ad Hoc" を選択した状態で、)Product メニューから Archive を実行するだけで、自動で.ipa ファイルが生成されます。


More

xcrunコマンド(これで .ipa ファイルを生成している)の後に curl コマンドで Testflight の API を叩くようにすれば、アーカイブ実行時に Testflight へのアップロードまで自動化することもできます。


参考



[Mac]Mac のスクリーンショットを定期的に自動撮影する

$
0
0

10分ごととかにMacスクリーンショットを自動撮影する方法です。ライフログに、あるいはサボり防止に役立つかもしれません。


以下、手順です。


1. シェルスクリプトの準備

適当なフォルダを作成して、シェルスクリプトのファイルを生成します。(下記サンプルではホーム直下にssというフォルダを作成)

$ cd
$ mkdir ss
$ cd ss
$ touch capture.sh

生成したスクリプトファイルを編集します。(最後のパスは出力先。よしなに変えてください)

#!/bin/bash

/usr/sbin/screencapture -m -C -T0 -x ~/ss/capture_`date +%Y%m%d%H%M`.png

スクリプトに実行権限をつけます。

$ chmod +x capture.sh

※注意:このスクリプトではファイル名に日時をつけているので、画像がどんどん溜まっていくことになります。定期的に溜まったファイルを削除するスクリプトを登録する等、HDDがいつの間にか一杯にならないような工夫をしないと実用的ではないので、ご注意ください。


2. crontab の編集

$ crontab -e

で、crontabの編集に入るので、


*/5 * * * * ~/ss/capture.sh

と書いて保存します。


こうすると5分ごとにスクリーンキャプチャが保存されるようになります。


保存間隔を10分ごとにしたい場合は、次のようにします。

*/10 * * * * ~/ss/capture.sh

ちなみに、現在の crontab の内容を確認するには、-l オプションを使用します。

$ crontab -l

(参考)

crontabの書き方


余談

本件で crontab を初めて使ったのですが、はじめ動作せず困りました。


スクリプトの1行目の

#!/bin/bash

を書いていなかったことと、


screencaptureコマンドを

/usr/sbin/screencapture

とせず

screencapture

とだけ指定していたことが原因でした。


どちらもスクリプトをターミナルから実行する際には問題なかったので、なかなか気付くことができず。。


この辺りは、環境変数の設定とか実行ユーザーとか、環境に依るところなので誰にでも当てはまる話ではないと思いますが、ご参考まで。


Thanks

こちらのリポジトリで方法を知りました。大西さんありがとうございます!


https://github.com/024t910/yc2013



[アニメーション][iOS][Objective-C]UIKit上でパーティクルエフェクトを表示する

$
0
0

iOS5より、Core Animationでパーティクルシステムがサポートされ、UIKitで実装されたUI上でパーティクル表現を簡単に行えるようになりました。


ここでは CAEmitterLayer と CAEmmiterCell を用いたパーティクルエフェクトの基本的な実装方法を説明し、入れ子にして花火のような段階的なエフェクトを実現する方法や、動的にパラメータを変更する方法を紹介します。


基本的な実装方法

1. パーティクル画像をプロジェクトに追加する

パーティクルシステムは、1つの画像を大量に描画することで多様な表現を行うものなので、その素となる画像が必要になります。ここでは、わかりやすいように次のようなシンプルな円形のpng画像を使います。


    

(※視認しやすいよう背景を黒にして載せています)


プロパティから色を変えられるので、白ベースの画像を用いることが多いですが、あらかじめ着色した画像を用いてもOKです。


2. QuartzCore.frameworkをプロジェクトに追加し、ヘッダをインポート

#import <QuartzCore/QuartzCore.h>

3. CAEmitterLayerオブジェクトを生成

パーティクルを発生させるレイヤーであるCAEmitterLayerオブジェクトを1つ生成します。

CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
CGSize size = self.view.bounds.size;
emitterLayer.emitterPosition = CGPointMake(size.width / 2, size.height / 2);
emitterLayer.renderMode = kCAEmitterLayerAdditive;
[self.view.layer addSublayer:emitterLayer];

renderModeプロパティは各パーティクルを重ね合わせる際の描画モードを指定できます。ここで使用しているkCAEmitterLayerAdditiveは加算による重ね合わせが行われます。

emitterPositionプロパティにはパーティクルを発生させる座標を指定します。


4. CAEmitterCellオブジェクトを生成

パーティクルの発生源となるCAEmitterCellオブジェクトを生成します。

CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
UIImage *image = [UIImage imageNamed:@"particle.png"];
emitterCell.contents = (__bridge id)(image.CGImage);
emitterCell.emissionLongitude = M_PI * 2;
emitterCell.emissionRange = M_PI * 2;
emitterCell.birthRate = 800;
emitterCell.lifetimeRange = 1.2;
emitterCell.velocity = 240;
emitterCell.color = [UIColor colorWithRed:0.89
                                    green:0.56
                                     blue:0.36
                                    alpha:0.5].CGColor;

contentsプロパティには、手順1で用意したパーティクル画像をCGImageRef型でセットします。


他にもプロパティがたくさんありますが、パーティクルの発生具合を調整するためのものです。それぞれの具体的な意味は後述します。


5. CAEmitterCellsプロパティを設定

CAEmitterLayerはemitterCellsというプロパティを持ち、NSArray型で複数のCAEmitterCellオブジェクトを登録できます。

self.emitterLayer.emitterCells = @[emitterCell];

ここでは手順4で生成したCAEmitterCellオブジェクトを登録しています。


以上で次のようなパーティクルエフェクトを表示できます。


    


CAEmitterCellを入れ子にして用いる

先ほどの手順ではCAEmitterLayerのemitterCellsプロパティにCAEmitterCellオブジェクトの配列をセットしましたが、CAEmitterCell自体もemitterCellsプロパティを持っており、(CAEmitterLayerと同様に)CAEmitterCellオブジェクトの配列を持つことができます。


これはどういうことかというと、CAEmitterCellから発されたパーティクル自身がまたパーティクルの発生源となれる、ということを意味しています。


例えば次のように、ひとつのCAEmitterCellオブジェクトから、

  • 上昇中のパーティクルの発生源となるCAEmitterCellオブジェクト
  • 破裂後に飛散するパーティクルの発生源となるCAEmitterCellオブジェクト

の2種類のCAEmitterCellオブジェクトを発生させるようにすると、花火のようなパーティクルエフェクトを実現することができます。

// パーティクル画像
UIImage *particleImage = [UIImage imageNamed:@"particle.png"];

// 花火自体の発生源
CAEmitterCell *baseCell = [CAEmitterCell emitterCell];
baseCell.emissionLongitude = -M_PI / 2;
baseCell.emissionLatitude = 0;
baseCell.emissionRange = M_PI / 5;
baseCell.lifetime = 2.0;
baseCell.birthRate = 1;
baseCell.velocity = 400;
baseCell.velocityRange = 50;
baseCell.yAcceleration = 300;
baseCell.color = CGColorCreateCopy([UIColor colorWithRed:0.5
                                                   green:0.5
                                                    blue:0.5
                                                   alpha:0.5].CGColor);
baseCell.redRange   = 0.5;
baseCell.greenRange = 0.5;
baseCell.blueRange  = 0.5;
baseCell.alphaRange = 0.5;

// 上昇中のパーティクルの発生源
CAEmitterCell *risingCell = [CAEmitterCell emitterCell];
risingCell.contents = (__bridge id)particleImage.CGImage;
risingCell.emissionLongitude = (4 * M_PI) / 2;
risingCell.emissionRange = M_PI / 7;
risingCell.scale = 0.4;
risingCell.velocity = 100;
risingCell.birthRate = 50;
risingCell.lifetime = 1.5;
risingCell.yAcceleration = 350;
risingCell.alphaSpeed = -0.7;
risingCell.scaleSpeed = -0.1;
risingCell.scaleRange = 0.1;
risingCell.beginTime = 0.01;
risingCell.duration = 0.7;

// 破裂後に飛散するパーティクルの発生源
CAEmitterCell *sparkCell = [CAEmitterCell emitterCell];
sparkCell.contents = (__bridge id)particleImage.CGImage;
sparkCell.emissionRange = 2 * M_PI;
sparkCell.birthRate = 8000;
sparkCell.scale = 0.5;
sparkCell.velocity = 130;
sparkCell.lifetime = 3.0;
sparkCell.yAcceleration = 80;
sparkCell.beginTime = risingCell.lifetime;
sparkCell.duration = 0.1;
sparkCell.alphaSpeed = -0.1;
sparkCell.scaleSpeed = -0.1;

// baseCellからrisingCellとsparkCellを発生させる
baseCell.emitterCells = [NSArray arrayWithObjects:risingCell, sparkCell, nil];

// baseCellはemitterLayerから発生させる
self.emitterLayer.emitterCells = [NSArray arrayWithObjects:baseCell, nil];

花火の「上昇後に破裂する」という2段階のプロセスを表現するため、sparkCellのbeginTimeプロパティに、risingCellのlifeTimeプロパティと同じ値をセットすることで、risingCellによる放射が完了した後にsparkCellによる放射が開始するようにしています。


    


動的にパラメータを変更する

CAEmitterCellのnameプロパティに値をセットしておけば、パラメータを動的変更することができます。


花火のサンプルを例にとると、baseCellのnameプロパティに下記のように名前をセットしておけば、

baseCell.name = @"fireworks";

setValue:forKeyPath:メソッドを用いて次のようにパラメータを変更することができます。

[self.emitterLayer setValue:@0
                 forKeyPath:@"emitterCells.fireworks.birthRate"];

emitterCellsプロパティが保持する配列内にある "fireworks" という名前の CAEmitterCell オブジェクトの birthRate プロパティに値 0 をセットしています。


birthRateに 0 をセットすることは、パーティクルの発生を停止することを意味するので、上記コードは動的にパーティクルエフェクトを停止していることになります。


CAEmitterCellの各プロパティの効果

種類が多く、言葉で定義を説明するよりも実際に動作させながら「変更してみると何が起きるか」を確認した方が理解が早いので、ここでは必要最小限のものについて説明します。

  • birthRate:1秒間に生成するパーティクルの数。
  • lifetime:パーティクルが発生してから消えるまでの時間。単位は秒。
  • color:パーティクルの色。
  • velocity:パーティクルの秒速。
  • emissionRange:パーティクルを発生する角度の範囲。単位はラジアン。

lifetimeRangeやvelocityRangeのように、xxxxRangeという名前のプロパティは、それぞれ各プロパティにランダム性をもたせるために、幅を設定するものです。たとえば、velocityRangeは、velocityに設定した値に幅を持たせるためのプロパティです。


サンプルコード

Githubにアップしてあります。

CAEmitterSample



[自然言語処理][Objective-C][iOS]自然言語のテキストを属性で区分する

$
0
0

NSLinguisticTaggerを用いると、自然言語のテキストを品詞(名詞、動詞、代名詞)や「個人名」「地名」といった属性で区分(トークンに分解)することができます。日本語の形態素解析も可能です。



使い方は非常にシンプルで、基本的な手順は

1. スキームを引数に渡してNSLinguisticTaggerオブジェクトを生成

2. 処理対象テキストをセット

3. トークン分解開始

の3ステップです。

// スキーム
NSArray *schemes = @[NSLinguisticTagSchemeLexicalClass];

// NSLinguisticTaggerオブジェクトを生成
NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes:schemes
                                                                    options:0];

// 処理対象テキスト
NSString *targetText = self.textView.text;
[tagger setString:targetText];

// トークンへの分解開始
[tagger enumerateTagsInRange:NSMakeRange(0, targetText.length)
                      scheme:NSLinguisticTagSchemeLexicalClass
                     options:0
                  usingBlock:
 ^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
     
     NSString *subStr = [targetText substringWithRange:tokenRange];
     NSLog(@"%@ : %@", subStr, tag);
 }];

スキームとは、トークン分解をどのような属性に基づいて行うかを指定するもので、上記サンプルで使用している NSLinguisticTagSchemeLexicalClass は品詞(「名詞」「動詞」etc)によってテキストを分解するスキームです。


enumerateTagsInRange:scheme:options:usingBlock: メソッドをコールするとトークン分解が開始され、トークンが見つかるごとに引数 usingBlock に渡されたBloksの処理が実行されます。


品詞で区分する

名詞、動詞といった品詞でテキストを区分するには、スキームとして NSLinguisticTagSchemeLexicalClass を使用します。


enumerateTagsInRange:scheme:options:usingBlock: メソッドで取得できるタグの種類には、次のようなものがあります。(主なものを抜粋)

  • NSLinguisticTagNoun:名詞
  • NSLinguisticTagVerb:動詞
  • NSLinguisticTagAdjective:形容詞
  • NSLinguisticTagPronoun:代名詞
  • NSLinguisticTagAdverb:副詞
  • NSLinguisticTagConjunction:接続詞
  • NSLinguisticTagPreposition:前置詞
  • NSLinguisticTagParticle:助詞
  • NSLinguisticTagDeterminer:限定詞

たとえば、品詞で区分して色分けする場合、この NSLinguisticTagger で取得できるタグに応じて色を決定するメソッドを実装しておき、

- (UIColor *)colorForAtteributeForLinguisticTag:(NSString *)linguisticTag {

    // 名詞
    if ([linguisticTag isEqualToString:NSLinguisticTagNoun]) {
        
        return [UIColor redColor];
    }
    // 動詞
    else if ([linguisticTag isEqualToString:NSLinguisticTagVerb]) {
        
        return [UIColor blueColor];
    }
    // 形容詞
    else if ([linguisticTag isEqualToString:NSLinguisticTagAdjective]) {
        
        return [UIColor magentaColor];
    }
    // 代名詞
    else if ([linguisticTag isEqualToString:NSLinguisticTagPronoun]) {
        
        return [UIColor orangeColor];
    }
    
    return [UIColor whiteColor];
}

次のように enumerateTagsInRange:scheme:options:usingBlock: メソッドのBlocks内でタグに応じた色分けを実施します。

// スキーム
NSArray *schemes = @[NSLinguisticTagSchemeLexicalClass];

// NSLinguisticTaggerオブジェクトを生成
NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes:schemes
                                                                    options:0];

// 処理対象テキスト
NSString *targetText = self.textView.text;
[tagger setString:targetText];

// テキストに色をつけるためにNSMutableAttributedStringを生成
NSMutableAttributedString *formatted;
formatted = [[NSMutableAttributedString alloc] initWithString:targetText];

// トークンのタグを取得開始
[tagger enumerateTagsInRange:NSMakeRange(0, targetText.length)
                      scheme:NSLinguisticTagSchemeLexicalClass
                     options:0
                  usingBlock:
 ^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
     
     // タグに応じた色分け
     UIColor *color = [self colorForAtteributeForLinguisticTag:tag];
     [formatted addAttribute:NSForegroundColorAttributeName
                       value:color
                       range:tokenRange];
 }];

// 色分けされたテキストを表示
self.textView.attributedText = formatted;


地名、個人名、組織名で区分する

スキームとして NSLinguisticTagSchemeNameType を使用します。他は品詞による区分の場合と同じ手順でOKです。



スキーム種別

上で使用した NSLinguisticTagSchemeLexicalClass, NSLinguisticTagSchemeNameType 以外に、次のようなスキームが用意されています。

  • NSLinguisticTagSchemeTokenType:「単語」「区切り文字」「ホワイトスペース」「その他」で分解する
  • NSLinguisticTagSchemeLemma:各単語の原形をタグとして取得する
  • NSLinguisticTagSchemeLanguage:言語名("ja", "en",...)をタグとして取得する
  • NSLinguisticTagSchemeScript:書き文字種別("Jpan", "Latn",...)をタグとして取得する
  • NSLinguisticTagSchemeNameTypeOrLexicalClass: NSLinguisticTagSchemeLexicalClass または NSLinguisticTagSchemeNameType にある属性で分解する

言語ごとの対応スキームを確認する

availableTagSchemesForLanguage: メソッドを使用すると、引数に指定した言語で利用できるスキームの配列(NSArray型)を取得することができます。

NSArray *schemes = [NSLinguisticTagger availableTagSchemesForLanguage:@"ja"];

NSLog(@"schemes:%@", schemes);

上記コードの実行結果は、

schemes:(
    TokenType,
    Language,
    Script
)

となりました。(iPhone 6.1 Simulatorで実行)


つまり、"ja"(日本語)で利用できるスキームは NSLinguisticTagSchemeTokenType, NSLinguisticTagSchemeLanguage, NSLinguisticTagSchemeScript の3種のみであることがわかります。


一方、引数に "en"(英語)を指定しての実行結果は、次のようになりました。

schemes:(
    TokenType,
    Language,
    Script,
    Lemma,
    LexicalClass,
    NameType,
    NameTypeOrLexicalClass
)

英語ではすべてのスキームが利用可能であることがわかります。


日本語の形態素解析を行う

日本語は英語と違い、単語の間がスペースで区切られていません。そこで自然言語処理を行う際のベースとなる処理として、「形態素解析」という、文を形態素(言語で意味を持つ最小単位)に分解する処理が必要となります。


前項で availableTagSchemesForLanguage: メソッドを用いて調べたとおり、英語と比較すると日本語の対応スキームは少ないですが、 NSLinguisticTagSchemeTokenType には対応しているので、単語への分解が可能です。すなわち、 NSLinguisticTagger を用いて日本語の形態素解析を行うことができます。(スキームに NSLinguisticTagSchemeTokenType を指定し、 string プロパティに日本語の文字列を渡すだけ、つまり英語の場合と実装方法は同じなので、詳細な説明は省略します。)


NSStringのカテゴリを利用する

NSLinguisticTagger.h には、NSString のカテゴリも定義されており、NSLinguisticTaggerを使用せず NSStringだけでも同様の処理ができるようになっています。

NSStringに追加された enumerateLinguisticTagsInRange:scheme:options:orthography:usingBlock: というメソッドを使用します。使い方は NSLinguisticTagger とほぼ同様です。

NSString *targetText = self.textView.text;

[targetText enumerateLinguisticTagsInRange:NSMakeRange(0, targetText.length)
                              scheme:NSLinguisticTagSchemeLexicalClass
                             options:0
                         orthography:nil
                          usingBlock:
 ^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
     
     // トークン取得時の処理
 }];

サンプルコード

Githubにアップしてあります。

LinguisticTaggerSample


参考資料

詳解iOS5プログラミング
沼田 哲史
秀和システム
売り上げランキング: 336,835

NSLinguisticTagger Class Reference



Viewing all 317 articles
Browse latest View live