vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Unity5】【Android】画像のリサイズと回転

昨日Android版です。

やったこと

  • 画像の回転情報を取得する
  • 取得した画像の容量を調べて大きすぎる場合はそこで処理を止める
  • 取得した画像の縦または横のサイズが大きすぎる場合は縮小する
  • 画像の回転情報を取得して、画像を元の向きに戻す
  • 変更した画像を出力して、そのパスをUnity側に渡す

画像の回転情報を取得する

ギャラリーから取得した画像(カメラアプリを使って撮影されたもの)をそのまま表示しようとすると、90度(または180度)傾いて表示されることがあります。
元の画像が回転していて、表示するときにその回転が反映されないからなのですが、これを取得します。

PluginConnector.java

〜省略〜
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode != RESULT_OK) {
      return;
    }
    Cursor crsOrientation = getContentResolver().query(data.getData(),
            new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
    if (crsOrientation.getCount() != 1) {
      showAlertFailedLoadFile();
      return;
    }
    crsOrientation.moveToFirst();
    int intOrientationDegree = crsOrientation.getInt(0);
    crsOrientation.close();
〜省略〜
  • この辺で画像のパスを取得したときと同じく、Cursorを使います。
  • 結果として「crsOrientation.getInt(0)」で「90」や「180」というint値が取れるので、これを使って画像を回転させます(画像の回転は後述)。

ファイルサイズを調べる

ファイルのサイズ(容量)を調べて、あまりに大きいものはその時点で処理をストップさせてみます。

...とは言ったものの、調べた限りだとそのままペラっとチェックできるような魔法のメソッドは無いようで、取得した画像のパスを使って調べてみました。

PluginConnector.java

〜省略〜
  private void editImage(final String strSendPath, final int intOrientationDegree)
  {
    File filNewImg = new File(strSendPath);

    if(! filNewImg.exists())
    {
      showAlertFailedLoadFile();
    }
    else if(filNewImg.length() > 5242880)
    {
      // ファイルサイズが5MBを超えていたらアラートを表示.
      showAlertFileTooLarge();
    }
〜省略〜
  • ファイルサイズを確認するためにパスからFileオブジェクトを作成しているわけですが、あまりに大きなファイルならこの時点で問題が発生しそうな気もします...。
    今回は行っていませんが、ネットワーク上にアップロードする場合などは有用だと思います。

画像を縮小する

PluginConnector.java

画像の大きさを変更したり、回転させるには「Bitmap」や「BitmapFactory」を使用します。

〜省略〜
BitmapFactory.Options btfOptions = new BitmapFactory.Options();
// 画像自体は読み込まず、データだけ取得する.
btfOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(strSendPath, btfOptions);

int intScaleWidth = btfOptions.outWidth / 1920;
int intScaleHeight = btfOptions.outHeight / 1920;
int intScale;
// 大きい方のScale値を使用.
if(intScaleWidth >= intScaleHeight)
{
  intScale = intScaleWidth;
}
else
{
  intScale = intScaleHeight;
}
if(intScale < 1)
{
  intScale = 1;
}
// 画像データを読み込むようセット.
btfOptions.inJustDecodeBounds = false ;
// 縮小率の指定.
btfOptions.inSampleSize = intScale;

// 画像データをBitmapとして読み込む.
Bitmap bmpLoadedImg = BitmapFactory.decodeFile(strSendPath, btfOptions);
〜省略〜
  • 「BitmapFactory.Options」の「inJustDecodeBounds」をTrueに指定することで、画像自体は読み込むことなく縦横サイズなどの情報を取得できます。
  • 「BitmapFactory.Options」の「inSampleSize」を使って画像サイズを拡大・縮小できるのですが、セットできる値が何故かint型。
    「1.5倍」のような指定ができないので、近似値を使うことになります。
    ここでは大雑把にサイズを合わせて、次に触れる画像を回転させるときに一緒に調整してやると良さそうです。
  • 画像の最大サイズを

画像を回転させる

画像の回転情報を取得する」で取得した回転情報を使って正しい方向に画像を回してやります。

Bitmapの回転にはカメラ使用時にも使った「Matrix」を使います。

PluginConnector.java

〜省略〜
// 画像データをBitmapとして読み込む.
Bitmap bmpLoadedImg = BitmapFactory.decodeFile(strSendPath, btfOptions);

Matrix mtxRotate = new Matrix();
// 元の画像データの回転を元に戻す.
mtxRotate.postRotate(intOrientationDegree);
Bitmap bmpRotatedImg = Bitmap.createBitmap(bmpLoadedImg, 0, 0, btfOptions.outWidth, btfOptions.outHeight, mtxRotate, true);

〜省略〜

// 使い終わったbitmapはカラにしておく.
bmpLoadedImg = null;
bmpRotatedImg = null;
〜省略〜
  • 縮小した画像データ(回転前)と回転後のデータとで、Bitmapが2つ作成されることになります。
  • メモリリークを防ぐため、Bitmapにはnullを入れておきます。

編集した画像を保存する

編集し終わったBitmapをJPEGファイルとして出力します。

PluginConnector.java

〜省略〜
String strSaveDir = UnityPlayer.currentActivity.getFilesDir().toString();
String strSaveFileName = "tmp.jpg";

try {
  File file = new File(strSaveDir, strSaveFileName);
  FileOutputStream outStream = new FileOutputStream(file);
  bmpRotatedImg.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
  outStream.close();

} catch (FileNotFoundException e) {
  e.printStackTrace();
} catch (IOException e) {
  e.printStackTrace();
}
〜省略〜

エラー発生

上記を実行すると、エラーが発生しました。

アプリの固有ディレクトリ内(/data/data/jp.setmaterialsfromgallery)にあるFilesディレクトリに保存しようとしたところ、どうやら対象のディレクトリが無かったようです。

というわけで、アプリの起動時にディレクトリを作成することにしました。

PluginConnector.java

〜省略〜
// --- Copied from UnityPlayerActivity.java ---
// Setup activity layout
@Override protected void onCreate (Bundle savedInstanceState)
{
〜省略〜

  UnityPlayer.currentActivity.runOnUiThread(new Runnable() {
    public void run() {
      // アプリケーションのディレクトリ内にFilesがなければ作成.
      File filAppFileDir = new File(UnityPlayer.currentActivity.getFilesDir().toString());
      if (!filAppFileDir.exists()) {
        filAppFileDir.mkdir();
      }
    }
  });
}
〜省略〜

エラー発生2

ディレクトリが作成されることで、Unityでも表示されることが確認できました。
しかし、Android Device Monitorを使ってログを見ていると、ファイルを保存した後のタイミングで「BitmapFactory」の「fileNotFoundException」が発生。

…なんぞこれ?

調べたところ、上記のファイル保存処理のあと「MediaScannerConnection.scanFile()」を実行していたのが原因でした。
ピクチャーやDCIMなど、どのアプリからもアクセスできるディレクトリと違い、アプリ固有のディレクトリはそのアプリ自身からしかアクセスができないため、エラーとなっていたようです。

見た目上問題なさそうだったため、原因究明により時間がかかった気がします汗

参考

画像を縮小して読み込む

画像を回転させる

画像の回転情報を取得する

画像の保存

別スレッドでの実行