iPhoneで画像を回転させて保存するには?

デバイスの回転に対応しているiPhoneアプリで、前に書いたようにUIGetScreenImage()を使ってスクリーンキャプチャーを撮ると、当然のごとく画像が回転してしまっている。
琴線探査: iPhoneアプリから画面のキャプチャー画像を取得する2つの方法


縦-逆さま


横-左


横-右

これは多くのユーザーが期待しない画像なので、回転させてから保存する必要がある。

しかし、どうやらUIImageには画像を回転させる機能は無いようだ。UIImageViewを使えば画像を回転させることができるが、使わない場合はどうするか?

回転後の画像をイチから作って、そこにキャプチャー画像を回転させて描画して、それを保存するという方法を考えた。

例えばこうする。

- (void)captureScreen {
 
  NSLog(@"画面キャプチャー");
 
  //スクリーンに写っているものすべてを画像化
  //ドキュメントにはないAPIコール(ひょっとするとREJECTの可能性もあるが、許されているという話も)
  CGImageRef imgRef = UIGetScreenImage();
 
  //CGImageRefをUIImageに変換
  UIImage *img = [UIImage imageWithCGImage:imgRef];
 
  //デバイスが回転していたら画像も回転させる
  if (toInterfaceOrientation != UIInterfaceOrientationPortrait) {

    //イメージ用グラフィックスコンテクスト開始
    if (toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
      UIGraphicsBeginImageContext(CGSizeMake(320, 480)); //縦の場合
    } else {
      UIGraphicsBeginImageContext(CGSizeMake(480, 320)); //横の場合
    }
  
    //イメージ用グラフィックスコンテクスト取得
    CGContextRef context = UIGraphicsGetCurrentContext();

    //座標系を適切に回転させる
    switch (toInterfaceOrientation) {
      case UIInterfaceOrientationPortraitUpsideDown:
        NSLog(@"デバイスの向き:縦-逆さま");
        CGContextTranslateCTM(context, 320, 0);
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextRotateCTM(context, -M_PI);
        break;
      case UIInterfaceOrientationLandscapeLeft:
        NSLog(@"デバイスの向き:横-左");
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextRotateCTM(context, -M_PI/2.0);
        break;
      case UIInterfaceOrientationLandscapeRight:
        NSLog(@"デバイスの向き:横-右");
        CGContextTranslateCTM(context, 480, 320);
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextRotateCTM(context, M_PI/2.0);
        break;
    }

    //キャプチャーイメージ描画(キャプチャーイメージは常に320x480固定)
    CGContextDrawImage(context, CGRectMake(0, 0, img.size.width, img.size.height), imgRef);

    //回転されたイメージ取得
    UIImage *imgAlt = UIGraphicsGetImageFromCurrentImageContext();
  
    //イメージ用グラフィックスコンテクスト終了
    UIGraphicsEndImageContext();
  
    //ソースイメージと回転されたイメージをスワップ
    img = imgAlt;
  
  }

  //キャプチャーしたCGImageRefはUIImageに変換して必要なくなったので解放
  CGImageRelease(imgRef);  
  
  //画像を「写真」に保存
  //JPEGで保存され、クオリーティーはコントロールできないようだ。
  //ImageMagickのidentifyによるとQuality=93らしい。
  UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil);
 
}

ここで最も注意しなければならないのは座標系だ。Java、ActionScriptと違って、CGContextDrawImage()では左下が原点となるらしい。

多くの座標系のように左上を原点として考えてプログラムすると、特に回転が入ると、画像にまったく何も描画されない場合がある。

そこで、適切に座標系を移動させた後
CGContextScaleCTM(context, 1.0, -1.0);
で上下(Y軸)を反転させる。その後適切に回転させる。


このあたりのことは、「iPhone アプリケーションプログラミングガイド」(PDF p.109)にも注意事項として書いてあった。

重要: ビットマップまたはPDFのコンテキストを描画するときに左下の原点を使用するため、作成されたコンテンツをビューにレンダリングするときにその座標系を補正しなければなりません。

言い換えると、画像を作成しCGContextDrawImage関数を使用して描画すると、その画像はデフォルトのままでは上下が逆になって表示されるということです。

これを修正するには、CTMのY軸を反転 (Y軸の座標に-1を乗じる)し、ビューの左下隅から左上隅へと原点を移動する必要があります。

作成するCGImageRefをUIImageオブジェクトを使用してラップする場合、CTMを修正する必要はありません。UIImageオブジェクトは、CGImageRef型の反転した座標系を自動的に補正します。


CG*系、つまりQuartz 2Dの座標系やTransformの動作を理解するには「Quartz 2D Programming Guide」のここがよい。
Quartz 2D Programming Guide: Transforms


いや〜頭が超混乱した!なんで左下が原点なのさよ?

コメント

このブログの人気の投稿

レオナルド・ダ・ビンチはなぜノートを「鏡文字」で書いたのか?

macでsmb(samba)共有サーバーに別名で接続(別アカウント名で接続)する方法

Google DriveにCURLでアップロードするには?