Windows Phoneアプリケーション開発入門

第9回Windows phoneで画像エフェクトアプリを作ろう!(2)

はじめに

「Windows phoneで画像エフェクトアプリを作ろう!」の2回目では、エフェクト処理の高速化、リサイズ、画像の保存の仕方を取り扱います。

エフェクト処理の高速化と、元の画像の縦横比を保ったままWindows phoneの画面に収まる形にリサイズする方法、エフェクト処理した後の画像を保存する方法を取り上げたいと思います。サンプルプログラムを用意していますのでどうぞご利用ください。

エフェクト処理の高速化を行う

前回のさいごにBitmap.SetPixelメソッドとGetPixelメソッドが遅い理由にアンマネージド領域へアクセスするオーバーヘッドのためと書かせて頂きました。480ピクセル×640ピクセルのVGA画像の場合で307,200回アクセスして読み込み、書き込みするのに同じだけのアクセスが発生します。

LockBitsメソッドを使うと、ピクセル配列にアンマネージドメモリのBitmapデータをマーシャリングでコピーしエフェクト処理を行い、処理後のピクセル配列で元のBitmapデータに置き換えることができます。配列へのアクセスだけでエフェクト処理を行いますので、Bitmapへのアクセスに比べ高速に行うことができます。

Image.PixelFormatプロパティが.NET Compact Frameworkではサポートされていませんので、Bitmap.LockBitsメソッドで指定するピクセル形式を明示的に指定する必要があります。ここではPixelFormat.Format24bppRgbを指定しています。

private void EffectGrayScale(ref Bitmap bmp)
{
    Rectangle bmpRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    
    // ImageLockModeに使用できる値は、ReadWrite、ReadOnly、WriteOnly。
    // ここでは加工処理を行うのでReadWriteを指定しています
    System.Drawing.Imaging.ImageLockMode mode
        = System.Drawing.Imaging.ImageLockMode.ReadWrite;
    
    // 明示的にPixelFormatを指定
    System.Drawing.Imaging.PixelFormat format
        = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
    
    // システムメモリへBitmapをロックする
    System.Drawing.Imaging.BitmapData bmpData
        = bmp.LockBits(bmpRect, mode, format);
    
    // Bitmapデータの最初のラインのアドレスの取得
    IntPtr p = bmpData.Scan0;
    
    // Bitmapデータを格納するピクセル配列の確保
    int numBytes = bmp.Width * bmp.Height * 3;
    byte[] rgbValues = new byte[numBytes];
    
    // Bitmapデータからピクセル配列へコピーする
    System.Runtime.InteropServices.Marshal.Copy(p, rgbValues, 0, numBytes);
    
    for (int idx = 0; idx < rgbValues.Length; idx += 3)
    {
        // RGB値の取得
        byte r = rgbValues[idx];
        byte g = rgbValues[idx + 1];
        byte b = rgbValues[idx + 2];
          
        // 平均してグレイスケール値を求める
        byte avg = (byte)((r + g + b) / 3);
          
        // RGB値の設定
        rgbValues[idx] = avg;
        rgbValues[idx + 1] = avg;
        rgbValues[idx + 2] = avg;
    }
    
    // ピクセル配列からBitmapデータへ書き戻す
    System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, p, numBytes);
    
    // システムメモリへロックしたBitmapを解除する
    bmp.UnlockBits(bmpData);
}

適度なサイズの画像でSetPixel/GetPixelが極端に遅い場合、Debugモードが原因かもしれませんので、一度Releaseモードで確かめてみるのも有りだと思います。

写真全体を表示可能な様に縦横比を合わせてリサイズする

技術の進歩とともに撮影可能なサイズは増加の一方です。国内で発売された中で一番大きなディスプレイを持つ端末でもFVGA(480×853)ですので、写真撮影できるWindows phoneのすべてがディスプレイより大きなサイズの写真を撮ることができます。

大きな写真の全体像をディスプレイに表示するには、PictureBoxのSizeModeプロパティをImageSize.Stechに設定するのが簡単ですが、写真をPictureBoxのサイズに合わせてしまいますので縦横比が崩れて(Windows phoneを縦にしていれば縦長に、横にしていれば横長にリサイズされて)しまいます。

デスクトップ版の.NET Frameworkでは、SizeModeプロパティにImageSize.Zoomを設定すると縦横比を保ったままきちんとリサイズして表示されるのですが、.NET Compact Frameworkにはその定義が存在しません。GraphicsのDrawImageメソッドを用いて縦横比を保ったままリサイズするコードを書きましょう。

写真のサイズが表示すべき領域より大きい場合のみリサイズを行うResizeBitmapメソッドを以下に示します。

private Bitmap ResizeBitmap(Bitmap srcBmp, int dispWidth, int dispHeight)
{
    Bitmap retBmp = null;
    
    if ((srcBmp.Height > dispHeight) ||
        (srcBmp.Width > dispWidth))
    {
        int h = srcBmp.Height;
        int w = srcBmp.Width;
        
        // 画像の高さが表示領域の高さよりも大きい場合、
        // 表示領域の高さに収まる様に比率を計算する
        if (h > dispHeight)
        {
            double temp = h;
            h = dispHeight;
            w = (int)(w * (h / temp));
        }
        
        // 画像の幅が表示領域の幅よりも大きい場合、
        // 表示領域の幅に収まる様に比率を計算する
        if (w > dispWidth)
        {
            double temp = w;
            w = dispWidth;
            h = (int)(h * (w / temp));
        }
        
        // リサイズ後の画像を格納するBitmapを生成
        Bitmap resizeBmp = new Bitmap(w, h);
        
        // リサイズ前の画像のサイズを定義
        Rectangle srcRect = new Rectangle(0, 0, srcBmp.Width, srcBmp.Height);
        // リサイズ後の画像のサイズを定義
        Rectangle resizeRect = new Rectangle(0, 0, w, h);
        
        using (Graphics g = Graphics.FromImage(resizeBmp))
        {
            g.DrawImage(srcBmp, resizeRect, srcRect, GraphicsUnit.Pixel);
        }
        retBmp = resizeBmp;
    }
    
    return retBmp;
}

作成したResizeBitmapメソッドを利用方法を示します。このアプリケーションPictureEffectでは、⁠選択」ボタンが押下されるとmenuSelectPicture_Clickメソッドを実行します。

this.OriginalBmpに格納したBitmapをPictureBoxに表示していますので、this.OriginalBmpにBitmapを格納する前にResizeBitmapメソッドを使ってリサイズ処理を行います。

private void menuSelectPicture_Click(object sender, EventArgs e)
{
    Microsoft.WindowsMobile.Forms.SelectPictureDialog dlg
        = new Microsoft.WindowsMobile.Forms.SelectPictureDialog();
    dlg.Title = "加工する画像を選択してください";
    
    if (dlg.ShowDialog() != DialogResult.OK)
    {
        return;
    }
    
    // ファイルパスからBitmapを生成する
    //this.OriginalBmp = new Bitmap(dlg.FileName);
    Bitmap pictBmp = new Bitmap(dlg.FileName);
    
    // 現在表示しているディスプレイの大きさに
    // マッチする様にリサイズする
    this.OriginalBmp = this.ResizeBitmap(
        pictBmp, pictureBox1.Width, pictureBox1.Height);
    
    pictureBox1.Image = this.OriginalBmp;
}

加工した画像を保存する

せっかくですのでエフェクトをかけた画像を保存してみましょう。BitmapクラスにはSaveメソッドが用意されています。これで指定したファイルパスやストリームに画像を保存することが可能です。

MainMenuに「保存」を追加しました。保存ボタンを押下すると、SaveFileDialogのFileNameプロパティから保存先のファイルパスを取得し、pictureBox1にて表示しているImageをファイルとして画像保存します。

private void menuSave_Click(object sender, EventArgs e)
{
    // 保存先のファイルパスを取得
    SaveFileDialog dlg = new SaveFileDialog();
    dlg.Filter = "jpeg file(*.jpg)|*.jpg";
    dlg.ShowDialog();
    
    // 保存する画像フォーマットをJPEG形式に
    System.Drawing.Imaging.ImageFormat imgFmt
      = System.Drawing.Imaging.ImageFormat.Jpeg;
    
    // pictureBox1に指定しているImageをファイル保存
    pictureBox1.Image.Save(dlg.FileName, imgFmt);
}

上記のコードではImageFormat.Jpegを指定してJPEG形式にて保存をしていますが、ほかにもBitmap、PNG、GIFで保存することができます。

画像を転送する場合などストリームで保存するほうが都合のよい時は、第1引数のString型のファイルパスの代わりに、System.IO名前空間のMemoryStreamクラス等を指定しましょう。MemoryStreamを使用する場合場合のコードを以下に示します。

  // メモリを用いたストリームの作成
  System.IO.MemoryStream memStrm
      = new System.IO.MemoryStream();
  
  // 保存する画像フォーマットをJPEG形式に
  System.Drawing.Imaging.ImageFormat imgFmt
    = System.Drawing.Imaging.ImageFormat.Jpeg;
  
  // pictureBox1に指定しているImageをストリームへ保存
  pictureBox1.Image.Save(memStrm, imgFmt);

さいごに

連載第8回9回と使って紹介させて頂いた「Windows phoneで画像エフェクトアプリの作り方」も一旦終わりです。

エフェクトを掛けた後の画像をflickrやTumblrなどのフォトストレージサービスのWebAPIを使ってシェアすると、ネットに常時接続されたモバイルならではの特徴を生かしたアプリケーションになると思います。興味を持たれた方は一度挑戦してみてください。

今回は以上で終わりです。ありがとうございました。

おすすめ記事

記事・ニュース一覧