● Carbon視点でiPhone探求(2009/05/17)

  このニュースは、MOSAの会員にのみ配布されているデベロッパー向けの
  デジタルマガジンMOSADeNのに掲載された記事です。ほぼ一ヶ月遅れで
  ここに掲載されて行きます

 〜 画像ファイルの取り扱いを考える 〜


今回は、ファイルから読み込んだ画像データのサイズ(幅と高さ)を変更する処理を考えてみます。オリジナルの大サイズ画像をメモリーに常駐させておいては、iPhoneのメモリーがいくらあっても足りません。特定のケースではサムネイルサイズに縮小して利用します。

ところで、iPhoneのDocumentsフォルダ (ディレクトリ)にちゃんと画像ファイルが保存されたかどうかを確認したい場合があります。もしくは、不必要になった画像ファイルを削除した時なども、それが本当に実行されているか不安です。通常では、iPhone自体のディレクトリの内容をのぞき見ることはできませんが、シミュレータの方であれば、その内容を確認することが可能です。シミュレータが管理しているアプリケーション固有のホームディレクトリは、以下のパスに設定されています。

User Home/ライブラリ/Application Support/iPhone Simulator/User/Applications/

このパスの下に「1F96CDFA-7EEA-4A1F-B63E-C22E8F825A50」などと暗号(?)のような名称が付いたフォルダが作成されますが、それがシミュレータが管理しているiPhoneアプリケーションのホームディレクトリです。このフォルダ名ですが、XcodeでMakeを実行し、シミュレータ上でアプリケーションを起動する度に逐次変更されます。ご注意ください。

この名称では何が何だか判断できませんが、作成日付(最新)をヒントにし、確認すべきターゲット・アプリのフォルダを見つけ出します。このフォルダ内に「Documents」というフォルダが作成されて、その中にアプリケーションから保存した画像ファイルなどが置かれることになります。iPhone側でも同様な階層構造でファイル管理されているので、よほどのことがないかぎりは、このフォルダ内容をチェックすることで、ファイル操作の正確さを確認することが可能となります。

さて、iPhoneアプリケーションで画像を取り扱う場合には、Quartz2DフレームワークのCGImageRefか、UIKitフレームワークのUIImageクラスを使います。処理内容によっては、UIImageよりCGImageRefを用いた方が便利(もしくは高速)な場合もあります。今回は、せっかくですので両方(CGImageRefとUIImage)を対象とした画像の縮小処理を考察してみます。まずは、CGImageRefの方です。

CGImageRef resizeCGImage( int type,CGImageRef simg,CGFloat width,CGFloat height )
{
  CGRect     srt,frt,drt;
  void      *bitmapData;
  CGImageRef  dimg=NULL;
  CGFloat     ww,hh;
  CGContextRef ctx;

  ww=(CGFloat)CGImageGetWidth( simg ); // 画像の幅を得る
  hh=(CGFloat)CGImageGetHeight( simg ); // 画像の高さを得る
  srt=CGRectMake( 0.0,0.0,ww,hh );
  frt=CGRectMake( 0.0,0.0,width,height );
  fitBounds( 1,&frt,&srt,&drt ); // 矩形の縦横比を合わせる自作ルーチン
  if( type==0 ) // 画像と同じ縦横比
  {
    drt.origin.x=drt.origin.y=0.0;
    ctx=createBitmapContext( drt.size.width,drt.size.height );
  }
  else // widthとheightと同じサイズ(背景の余白は白色)
    ctx=createBitmapContext( width,height );
  if( ctx ) // オフスクリーン用CGContextRefが得られたか?
  {
    CGContextDrawImage( ctx,drt,simg );    // オリジナル画像を描画
    dimg=CGBitmapContextCreateImage( ctx ); // CGImageRefを得る
    if( bitmapData=CGBitmapContextGetData( ctx ) ) // 使用メモリを解放
      free( bitmapData );
    CGContextRelease( ctx ); // CGImageRefを解放
  }
  return dimg;
}

縮小させるオリジナルの画像は引数のsimgで与えられます。引数のwidthとhightは、縮小後の(拡大でもOK)幅と高さピクセル数で与えます。引数のtypeがゼロであればオリジナルの縦横比を保持してリサイズすることで背景を作りません。typeが1であれば、リサイズ方法はtypeがゼロの場合と同じですが、フレームの幅と高さは指定通りとなり背景を残します。

fitBounds()は、srt(オリジナル画像の矩形)をfrt(描画先矩形)に合わせて拡大縮小させたdrt(描画矩形)を得るための自作ルーチンです。そして、createBitmapContext()ルーチンで、オフスクリーン用のCGContextRefを作成し、そこへオリジナル画像を描画します。最後に、そこから目的のCGImageRef(縮小した画像)を得ます。オフスクリーンとして確保したCGContextRefは、それ自体をCGContextRelease()で解放するだけではなく、そこに使用していたメモリ領域もfree()で解放することを忘れないでください。

以下は、オフスクリーン用のCGContextRefを作成するcreateBitmapContext()ルーチンです。

CGContextRef createBitmapContext ( int pixelsWide,int pixelsHigh )
{
  CGContextRef   bitmapContext=NULL;
  void        *bitmapData;
  CGColorSpaceRef colorSpace;

  if( bitmapData=calloc( 1,pixelsWide*pixelsHigh*4 ) ) // 画像用メモリ確保
  {
    colorSpace=CGColorSpaceCreateDeviceRGB(); // カラースペースを設定
    bitmapContext=CGBitmapContextCreate( bitmapData,pixelsWide,pixelsHigh,8,pixelsWide*4,colorSpace,kCGImageAlphaPremultipliedLast );
    if( ! bitmapContext ) )  // オフスクリーン用CGContextRef作成
      free( bitmapData ); // 失敗した場合にはメモリを解放する
    CGColorSpaceRelease( colorSpace );
  }
   return bitmapContext;
}

今回の処理で利用しているAPIの詳細については、Quartz2D関連のヘッダーファイルCGBitmapContext.h 、CGGeometry.h、CGImage.hなどを参照してみてください。続いて、UIImageクラスを利用した画像の縮小処理です。

UIImage *resizeUIImage( int type,UIImage *simg,CGFloat width,CGFloat height )
{
  CGRect  srt,frt,drt;
  UIImage  *dimg;
  CGSize  size;

  srt=CGRectMake( 0.0,0.0,simg.size.width,simg.size.height );
  frt=CGRectMake( 0.0,0.0,width,height );
  fitBounds( 1,&frt,&srt,&drt ); // 矩形の縦横比を合わせる自作ルーチン
  if( type==0 )   //   画像と同じ縦横比
    UIGraphicsBeginImageContext( drt.size ); // オフスクリーンContext作成
  else // widthとheightと同じサイズ(背景の余白は白色)
  {
    size.width=width;
    size.height=height;
    UIGraphicsBeginImageContext( size );
  }
  [simg drawInRect:drt];  // 画像の描画(UIImageクラスのメソッド)
  dimg=UIGraphicsGetImageFromCurrentImageContext(); // UIImage作成
  UIGraphicsEndImageContext(); // オフスクリーンContext解放
  return dimg;
}

こちらでは、先ほどのcreateBitmapContext()ルーチンの役割をUIGraphics.hに定義されているUIGraphicsBeginImageContext()ルーチン(メソッドではない)が担います。描画後にUIImageを作成するのは、UIGraphicsGetImageFromCurrentImageContext()です。また、不必要となったImageContextは、UIGraphicsEndImageContext()で解放します。UIGraphics.hには、今回紹介したような便利なCルーチンが多数定義されていますので、ぜひ一度参照してみてください。

iPhone OS 3.0でコピー&ペーストにより画像を別アプリケーションから持ってくることが可能になりました。しかし、iPhone OS 2.2ではまだそうした操作はできません。主な画像の入手方法は、iPhoneの写真ライブラリから画像を得るか、そのアプリから写真撮影するかです。次回は、そうした操作の実装を考えてみます。

copyright 2009 Ottimo, Inc. All rights reserved
無断転載・引用禁止