2010年4月2日金曜日

iPhoneアプリから画面のキャプチャー画像を取得する2つの方法

iPhoneでは、ホームボタンと電源ボタンを同時押しするとスクリーンショットがとれる。しかもこのスクリーンショットはPNG形式で保存されるので高画質だ。

ただこの方法の問題は、間違えてスリープさせてしまったり、アプリを終了させてしまったり、スクリーンショットをとるだけなのに相当集中・気合を必要とすることだ。場合によっては間違ってiPhoneを落としてしまう可能性もある。

そこで、アプリ内からもっと気軽に簡単に画面のキャプチャーを取れないか試してみることにした。

アプリのキャプチャーをとるには、少なくとも2つ方法があるとわかった。スタンダードな方法と、ハックハックな方法だ。


スタンダードな方法

例えば、こんな風にする。

/**
 *
 * 画面キャプチャー開始
 *
 */
- (IBAction)onTouchUpInsideBtnCapture {

  //画面をキャプチャー
  NSLog(@"画面キャプチャー開始");
  CGRect rect = [[UIScreen mainScreen] bounds];
  UIGraphicsBeginImageContext(rect.size); //コンテクスト開始
  UIApplication *app = [UIApplication sharedApplication];

  //#import をしておかないとrenderInContextで警告が出る
  [app.keyWindow.layer renderInContext:UIGraphicsGetCurrentContext()];

  UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext(); //画像を取得してからコンテクスト終了

  //画像を「写真」に保存
  //JPEGで保存され、クオリーティーはコントロールできないようだ。
  //ImageMagickのidentifyによるとQuality=93らしい。
  //UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil); //完了通知が必要ない場合
  UIImageWriteToSavedPhotosAlbum(img, self, @selector(onCompleteCapture:didFinishSavingWithError:contextInfo:), nil);
 
}


/**
 *
 * 画面キャプチャー完了
 *
 */
- (void)onCompleteCapture:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void*)contextInfo {
  NSLog(@"画面キャプチャー完了");
}

この方法ではUIWindowのコンテンツをキャプチャーするのでステータスバーは写らない。場合によってはこの方が都合がいい場合もあるだろう。

しかし、今回の場合の最大の問題は、UIWindowにカメラ入力を表示している場合でも、カメラ入力のレイヤーが無視されて写らないことだ。

なお、UIImageWriteToSavedPhotosAlbum()を使って保存した画像は、ホームボタンと電源ボタンの同時押しによるキャプチャー画像と違い、JPEG画像として「写真」に保存される。クオリティーコントロールもできないようだ。

ただ、見た目にはそれほど劣化しているようには見えず、ImageMagickのidentifyコマンドで書き出されたJPEGのクオリティーを調べてみたところ93と結構高かった。


ハックハックな方法

こうすると、ステータスバーも写るし、カメラ入力も写る。

/**
 *
 * 画面キャプチャー開始
 *
 */
- (IBAction)onTouchUpInsideBtnCapture {

  //画面キャプチャー開始
  NSLog(@"画面キャプチャー開始");

  //スクリーンに写っているものすべてを画像化
  //ドキュメントにはないAPIコール(ひょっとするとREJECTの可能性もあるが、許されているという話も)
  CGImageRef imgRef = UIGetScreenImage();

  //CGImageRefをUIImageに変換
  UIImage *img = [UIImage imageWithCGImage:imgRef];

  //キャプチャーしたCGImageRefはUIImageに変換して必要なくなったので解放
  CGImageRelease(imgRef);

  //画像を「写真」に保存
  //JPEGで保存され、クオリーティーはコントロールできないようだ。
  //ImageMagickのidentifyによるとQuality=93らしい。
  //UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil); //完了通知が必要ない場合
  UIImageWriteToSavedPhotosAlbum(img, self, @selector(onCompleteCapture:didFinishSavingWithError:contextInfo:), nil);
 
}


/**
 *
 * 画面キャプチャー完了
 *
 */
- (void)onCompleteCapture:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void*)contextInfo {
  NSLog(@"画面キャプチャー完了");
}

これだけだと「Implicit declaration of function 'UIGetScreenImage'」の警告が出るので、ヘッダーファイルに次のコードを入れると警告が出なくなる。

//「Implicit declaration of function 'UIGetScreenImage'」の警告が出ることへの対処
//頭に「-」がついておらず後ろに「(void)」がついている事に注意。これはタイプミスではない。
CGImageRef UIGetScreenImage(void);

ポイントであるUIGetScreenImage()は、ドキュメントに載っていないAPIだ。つまり、これを使えばAppleの審査に通らない可能性がある。

しかしネット上には、最近はAppleがこのAPIの使用を認めたとの情報がいくつもある。


Saving a view as an image. : SkyBlog

・・・Update: Apple have now allowed the usage of UIGetScreenImage()・・・The announcement hints strongly that Apple will be adding a new public method into future frameworks that will gather a screenshot.・・・

と、将来は正式なAPIとして認められるかもしれないとのご指摘も。


iPhone ARは第2章へ突入 >> akalogue

・・・2009年12月になって、唐突にUstream Live Broadcasterなどのライブ映像系のアプリケーションがApp Storeで公開されるように・・・これらは非公式APIであるUIGetScreenImage()・・・を使っていました・・・デベロッパー・フォーラムではUIGetScreenImage()を使っていいよとのAppleからのメッセージも掲載されちゃいました・・・

本当のところは実際に審査に出してみないとわからないが、赤松さんがおっしゃるように、Ustreamのアプリの存在を考えれば認められていると考える方が自然だ。

まぁ、やってみるさ。