Picassoでデバイス内の画像を表示すると向きが変になる問題を修正した

Androidで画像を非同期に読み込むことができるPicassoライブラリ、私もとても便利に使っているのですが、Android 10でデバイス内の画像を表示すると、画像の向きが正しく表示されない問題がありました。原因を調べて修正したので、修正版の紹介です。

細かい説明が不要な方は、https://github.com/usuiat/picasso/tree/fix-media-exif-orientationを見てください。

Picassoライブラリについて

Picassoライブラリは、オープンソースで開発されているAndroid用の画像読み込み、表示用ライブラリです。インターネット上の画像やデバイス内の画像の読み込みやリサイズを非同期で行い、ImageViewに表示するまでを一文でとてもシンプルに書くことができます。しかもキャッシュの管理も自動でやってくれたり、RecyclerViewと組み合わせて使ったときにスクロール状況に合わせて読み込みのキャンセル処理も自動でやってくれたりと、とても便利なライブラリです。

例えばインターネット上の画像を読み込んで、ImageViewのサイズに合わせてリサイズし、中央に表示するには以下のように書きます。画像の読み込みやリサイズはバックグラウンドスレッドで実行されるので、UIが固まるのを防ぐことができます。

Picasso.get()
    .load("http://example.com/image.png")
    .fit()
    .centerInside()
    .into(imageView)

MediaStore APIが管理するデバイス上の画像も、content://media/で始まるURIを使って同じように読み込むことができます。詳しくは「RecyclerViewとCoroutineで非同期フォトギャラリーを作る」で説明しているので、参考にしてください。

Picasso.get()
    .load("content://media/external/images/media/12345")
    .fit()
    .centerInside()
    .into(imageView)

その他ドキュメントは以下から参照できます。

デバイス内の画像を表示すると、向きがおかしい

とても便利なPicassoライブラリですが、画像の向きがおかしくなる場合があることに気づきました。調査したところ、以下の条件で画像の向きが変になってしまうようでした。

  • Android 10
  • MediaStoreから取得したURIでデバイス内の画像を読み込んだ場合(content://media/で始まるURI)
  • スマホのカメラアプリで撮影した画像など、Exifデータが含まれている場合
  • スマホを縦向きにして撮影した画像の場合(横向きの場合は、撮影時のスマホの向きによる)
  • 表示するImageViewのサイズがある程度大きい場合(画面全体に表示した場合は問題発生するが、RecyclerViewで1行に4枚ずつ画像を並べた場合などは問題ない)

ちなみにPicassoのバージョンはV2.8です。この記事を書いている2021年8月時点の最新版です。

原因

ソースコードを調べると、MediaStoreの画像を読み込むモジュールにバグがあり、Exifデータから取得した画像の向きを正しく保持できていませんでした。その結果、本来は表示時に画像を回転させる必要があるのに、回転されずに表示されてしまっていました。

Android 10だけで問題が発生するのは、画像の向きの取得にMediaStore.MediaColumns.ORIENTATIONをインデックスとして使っているからでした。このインデックスはAPI Level 29以降なので、Android 10以降しか使えません。Android 9以前では回転なしと判定されているのだろうと思います。

ImageViewのサイズが小さいと問題が起きないのは、ImageViewのサイズによって画像の読み込みに使っているAPIが異なっており、小さいサイズでは正しい向きに回転済みの画像が取得されていたので、表示時に回転しなくても問題になっていませんでした。

また、スマホのカメラの向きは、(機種によるかもしれませんが、)スマホを横向きにしたときにExifデータのOrientationが0°になるように設定されているようです。スマホを縦にして撮影しても保存されるデータは横向きのままで、Exifデータに90°回転して表示するように情報が記録されるようです。その結果、表示時に正しく回転させないと向きが変になってしまうという問題が起きるようです。

修正

原因が分かったので、修正しました。Exifデータから取得したデータを正しい形式で保持するようにしました。

修正は本家からForkして、こちらのリポジトリで行いました。本家にもPull Requestを出しましたが、過去のPull Requestのやり取りを見ていると、V2.xの修正には消極的な感じなので、反映されるかどうかわかりません。もし同じ問題でお困りの方がいましたら、私のFork版を使ってもらえればと思います。

Fork版の使用方法

Fork版をアプリに適用する手順も紹介しておきます。

ソースコード取得

GitHubからusuiat/picassoをcloneします。(https://github.com/usuiat/picasso.git

修正はfix-media-exif-orientationブランチで行いました。このブランチをcheckoutします。

ライブラリのビルド

Android Studioでプロジェクトを開きます。ルートディレクトリを選択してAndroid Studioで開けばOKです。モジュールが3つ見えます。

Build Variantsは一応Releaseを選びましたが、Debugと何が違うのかは分かりません。

picasso-root.picassoモジュールのbuild.gradleを選択してBuild > Make Module 'picasso-root.picasso'を実行すると、.\picasso\build\outputs\aar\picasso-release.aarができます。

アプリのプロジェクトに取り込む

Picassoを使う側のプロジェクトで、モジュールのフォルダの中にaarファイルを置きます。通常はアプリはappモジュールになっていると思います。その場合、appフォルダの中にaarファイルを置きます。app/libsというフォルダに置くのが定番らしいです。

appモジュールのbuild.gradleを編集します。

implementation "com.squareup.picasso:picasso:2.8"

を削除し、

implementation files('libs/picasso-release.aar')
implementation "com.squareup.okhttp3:okhttp:3.10.0"

を追加します。okhttpは、Picassoライブラリが依存しているライブラリです。これを追加しておかないと実行時にクラッシュします。(本当はaarファイル自体に依存情報も含めることができるように思うのですが、やり方が分かりませんでした。)

Gradleをsyncしてアプリをビルドすれば、完了です。画像の向きが正しく表示されると思います。