Think Big Act Local

iPhone開発を軸にブレブレの記事を書いていきます。

【DECOPIC風】画像にスタンプを挿入できる機能を実装する

f:id:himaratsu:20130502110026j:plain

タップした場所にスタンプ画像を貼りつけていき、
保存ボタンが押されたらスタンプつきの画像を保存する機能を実装してみます。

ゴールとしては DECOPIC のようなものをイメージしています。

スタンプ画像の貼り付け方

タップした場所に生成され、指を動かすとそれに追従し、
指を離すとその場所に貼り付けれられる、という機能を作ります。

まず、.hファイルで以下の変数を用意しておきます。

UIImageView *showImageView;  // ベース(スタンプの下敷き)となる画像
UIImageView *currentStampView;  // 貼り付け中のスタンプ画像
BOOL _isPressStamp;  // スタンプ貼り付け中かどうか

これらの変数は、.mファイルで次のように初期化しておきましょう。

// ベースとなる画像の貼り付け
showImageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 10, 310, 310)];
showImageView.image = [UIImage imageNamed:@"base.png"];
[self.view addSubview:showImageView];

// スタンプ画像は最初はセットしない
currentStampView = nil;

// 最初はスタンプモードでない
_isPressStamp = NO;

では、いよいよスタンプ機能です。
以下のように作ってみました。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // タッチされた座標を取得
  UITouch* touch = [touches anyObject];
  CGPoint point = [touch locationInView:showImageView];
    
  // スタンプを貼り付ける
  currentStampView = [[UIImageView alloc]
                           initWithFrame:CGRectMake(point.x-30, point.y-30, 60, 60)];
  currentStampView.image = [UIImage imageNamed:@"stamp.png"];
  [self.view addSubview:currentStampView];

// スタンプモードON
  _isPressStamp = YES;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  // タッチされた座標を取得
  UITouch* touch = [touches anyObject];
  CGPoint point = [touch locationInView:showImageView];
    
  // スタンプの位置を変更する
  if (_isPressStamp) {
      currentStampView.frame = CGRectMake(point.x-30, point.y-30, 60, 60);
  }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  // スタンプモード終了(スタンプを確定する)
  _isPressStamp = NO;
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  // スタンプモード終了(スタンプを確定する)
  _isPressStamp = NO;
}

ここで使用しているtouchesBegan:withEvent:などはUIResponderクラスのメソッドです。
UIKitフレームワークのほぼ全てのクラスはUIResponderを継承しており、メソッドを書くだけで勝手にタッチイベントを検知してくれます。
(今回の例ではUIResponderを継承したUIViewクラスのメソッドを呼んでいる)

メソッド名 説明
touchesBegan 画面に指が触れた時
touchesMoved 画面に指が触れた状態で指を移動させた時
touchesEnded 画面から指が離れた時
touchesCancelled 画面タッチがキャンセルされた時

touchesCancelled:withEventは、指が触れた状態でホームボタンを押した場合等に呼ばれます。

ここまでのソースで、スタンプを貼り付ける機能を実装できました。

画像をスタンプつきで保存

画像をデコったら、それを保存したくなるのが人情。保存機能を実装しましょう。

エリアを指定して画像を切り抜く(CGContextRefを使う)

UIImageView *showImageViewのサイズで画像を切り抜くコードが以下です。

// 領域を指定して画像を切り抜く
-(UIImage *)captureImage
{
  // 描画領域の設定
  CGSize size = CGSizeMake(showImageView.frame.size.width , showImageView.frame.size.height);
  UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
    
  // グラフィックスコンテキスト取得
  CGContextRef context = UIGraphicsGetCurrentContext();
    
  // コンテキストの位置を切り取り開始位置に合わせる
  CGPoint point = showImageView.frame.origin;
  CGAffineTransform affineMoveLeftTop
  = CGAffineTransformMakeTranslation(
                                     -(int)point.x ,
                                     -(int)point.y );
  CGContextConcatCTM(context , affineMoveLeftTop );
    
  // viewから切り取る
  [(CALayer*)self.view.layer renderInContext:context];
    
  // 切り取った内容をUIImageとして取得
  UIImage *cnvImg = UIGraphicsGetImageFromCurrentImageContext();
    
  // コンテキストの破棄
  UIGraphicsEndImageContext();
    
  return cnvImg;
}

このメソッドを実行すると、showImageViewのサイズで画像を切り抜き、
それをUIImageクラスのオブジェクトにして返してくれます。

画像をカメラロールに保存する

画像をカメラロールに保存するには、以下のように記述します。

- (void)saveCameraRoll {
  // 画像を取得
  UIImage *saveImage = [self captureImage];

  // カメラロールに保存
  if (saveImage != nil) {
      UIImageWriteToSavedPhotosAlbum(saveImage,
                                     self,
                                     @selector(targetImage:didFinishSavingWithError:contextInfo:),
                                     NULL);
  }
}

// 画像の保存完了時に呼ばれるメソッド
- (void)targetImage:(UIImage *)image didFinishSavingWithError:(NSError *)error
        contextInfo:(void *)context
{
  NSString *message = [NSString string];
  if (error) {
      // 保存失敗時の処理
      message = @"保存に失敗しました";
  } else {
      // 保存成功時の処理
      message = @"保存に成功しました";
  }
  UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
                                                  message:message
                                                 delegate:self
                                        cancelButtonTitle:nil
                                        otherButtonTitles:@"OK", nil];
  [alert show];
}

saveCameraRollメソッドを呼べば切り抜き画像をカメラロールに保存できます。
(上記例では保存処理を終えたらでUIAlertViewを表示するようにしている)

以上でスタンプ機能の実装は完了です。

関連書籍

Objective-Cを扱う人のバイブル。プロパティやメモリ管理について詳しい。
touchesBeganイベントに関連するレスポンダチェーンの章は必見です。