LoginSignup
5
4

More than 5 years have passed since last update.

CursorJoinerを使ってMediaStore.Images.Thumbnailsに回転情報を追加する

Last updated at Posted at 2016-05-12

ギャラリーから画像をまとめて表示したい場合、ContentResolverを使って画像のThumbnails情報を取ってきて表示するのが一般的かと思います。
が、注意すべき点として、Thumbnails画像には回転情報が含まれていないため、端末の機種によっては縦横の表示がおかしくなるケースがあります。
具体的にはGalaxy S4以降では縦長画像が横向きになってしまう現象が確認されました。

解決方法

ContentResolverの MediaStore.Images.Media._IDMediaStore.Images.Thumbnails.IMAGE_IDが同じことを利用し、MediaStore.Images.Mediaの回転情報を取得します。表示する際はこの回転情報を元にbitmapを回転して表示してあげます。PicassoやGlideを使えば画像回転をよろしくやってくれる(と、いうかPicassoでは内部的にMediaStore.Imagesの回転情報を取得する機構があるっぽい)かもしれませんが、今回は割愛します。

CursorJoinerをカスタマイズする

一致するMediaStore.Images.Media._IDMediaStore.Images.Thumbnails.IMAGE_IDを抽出するにはCursorJoinerを使います。

CursorJoinerはその名の通りCursorをjoinします。内部的には2つのCursorを比較し、一致する項目が出てくるまでそれぞれのCursorを順に進めていくようになっています。ただしString型でしか比較してくれません。そのため、IDのint型でも対応できるようにCursorJoinerをカスタマイズします。実装方法はここのブログ(なんと5年前!)にも書いてあるのですが、写真の一覧の場合新しいものを先頭に持ってきたいので降順(DESC)で比較できるように工夫します。
実際の実装はGistに貼っています。

変更点をかいつまんで書くと

(1)型と比較順序に関するenum追加

/**
 * The type of column to join
 */
public enum JoinColumnType {
    STRING,
    INT
}
/**
 * Order
 */
public enum OrderType {
    ASC, DESC;
}

//コンストラクタ内
        mType = type;
        switch (mType) {
            case STRING:
                mStringValues = new String[mColumnsLeft.length * 2];
                break;
            case INT:
                mIntValues = new int[mColumnsLeft.length * 2];
        }

        sOrderType = orderType;

(2) 型によって比較方法を分岐させる
CursorJoinerはIteratorになっています。next()メソッドで順次Cursorを移動させて比較するわけですが、型によって処理を分岐させます。

public Result next() {
...
  if (hasLeft && hasRight) {
      int compareResult = 0;
      switch (mType){
          case STRING:
              populateStringValues(mStringValues, mCursorLeft, mColumnsLeft, 0);
              populateStringValues(mStringValues, mCursorRight, mColumnsRight, 1);
              compareResult = compareStrings(mStringValues);
              break;
          case INT:
              populateIntValues(mIntValues, mCursorLeft, mColumnsLeft, 0);
              populateIntValues(mIntValues, mCursorRight, mColumnsRight, 1);
              compareResult = compareInts(mIntValues);
              break;

int型の比較(compareInts())は以下のようになります。OrderTypeによって返る値が変わる(どちらのCursorを進めるか決める)ことがわかります。

private static int compareInts(int... values) {
    if ((values.length % 2) != 0) {
        throw new IllegalArgumentException("you must specify an even number of values");
    }

    for (int index = 0; index < values.length; index += 2) {
        int comp = values[index] - values[index + 1];
        if (comp != 0) {
            if (sOrderType == OrderType.DESC) {
                // if LEFT is smaller than RIGHT, move RIGHT cursor since value order is desc
                return comp < 0 ? 1 : -1;
            } else if (sOrderType == OrderType.ASC) {
                return comp > 0 ? 1 : -1;
            }
        }
    }
    return 0;
}

使い方

このCustomCursorJoinerは以下のように使います。

private static final Uri THUMB_URI = MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI;
private static final String[] THUMB_PROJECTION = {MediaStore.Images.Thumbnails.DATA,MediaStore.Images.Thumbnails.IMAGE_ID};
private static final String[] THUMB_WHERE = {String.valueOf(MediaStore.Images.Thumbnails.MINI_KIND)};
private static final String THUMB_ORDER_BY = MediaStore.Images.Thumbnails.IMAGE_ID + " DESC";

private static final Uri IMAGE_MEDIA_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
private static final String IMAGE_ORDER_BY = MediaStore.Images.Media._ID + " DESC";
private static final String[] CONTENT_ORIENTATION = new String[] {
        MediaStore.Images.Media._ID,
        MediaStore.Images.ImageColumns.ORIENTATION
};
private static final String IMAGE_ORDER_BY = MediaStore.Images.Media._ID + " DESC";



String selection = MediaStore.Images.Thumbnails.KIND + " = ? AND "
        + MediaStore.Images.Thumbnails.IMAGE_ID + " IN (" + TextUtils.join(",", imageIdList) + ")";
ContentResolver resolver = context.getContentResolver();
Cursor thumbNailCursor = resolver.query(
        THUMB_URI,
        THUMB_PROJECTION,
        selection,
        THUMB_WHERE,
        THUMB_ORDER_BY);

String rotationSelection = MediaStore.Images.Media._ID + " IN (" + TextUtils.join(",", imageIdList) + ")";
Cursor rotationCursor = resolver.query(
        IMAGE_MEDIA_URI,
        CONTENT_ORIENTATION,
        rotationSelection,
        null,
        IMAGE_ORDER_BY);

CustomCursorJoiner joiner = new CustomCursorJoiner(
        thumbNailCursor,
        new String[]{MediaStore.Images.Thumbnails.IMAGE_ID},
        rotationCursor,
        new String[]{MediaStore.Images.Media._ID},
        CustomCursorJoiner.JoinColumnType.INT,
        CustomCursorJoiner.OrderType.DESC
);
for(CustomCursorJoiner.Result result : joiner){
    if(result == CustomCursorJoiner.Result.BOTH){
        PhotoItem item = new PhotoItem(); // 写真情報(thumbnail, id, 回転情報)を管理するオブジェクト
        item.setThumbNailPath(CursorUtil.getString(thumbNailCursor, MediaStore.Images.Thumbnails.DATA));
        item.setId(CursorUtil.getInt(thumbNailCursor, MediaStore.Images.Thumbnails.IMAGE_ID));
        item.setRotation(CursorUtil.getInt(rotationCursor, MediaStore.Images.ImageColumns.ORIENTATION));
        resultList.add(item);
    }
}
rotationCursor.close();
thumbNailCursor.close();

ありがたいことにMediaStore.Images.ImageColumns.ORIENTATIONはdegreeで返ってきます。扱いやすいですね。
あとは以下のようにBitmap生成時にrotation情報を加えてあげて、imageView等にsetしてあげればOKです。

Bitmap bitmap;
bitmap = BitmapFactory.decodeFile(item.getThumbNailPath(), options);

int degree = item.getRotation();
if (degree > 0F) {
    Matrix matrix = new Matrix();
    matrix.postRotate(degree);

    Bitmap rotateBm;
    rotateBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

    bitmap.recycle();
    bitmap = rotateBm;
}
...

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4