綴Photo の勉強ノートに戻る
Flutter技術2026.06.19

サムネのサイズを渡さないと、毎回フル画像を読み込んでいた — Flutter photo_manager の thumbnailLogicalSize と decodeLogicalSize

Flutterphoto_managerパフォーマンスiOS綴Photo

写真表示が重かった原因を追って、自分の言葉で腹落ちさせた学習メモです。

綴Photo の写真表示ウィジェット AppPhotoImage には、thumbnailLogicalSize というサイズのヒントを渡せます。これが有るか無いかで、画像を「どこから・どう取ってくるか」がまるごと変わっていました。サムネで足りる表示なのに、無指定だとフル解像度を掴みにいっていました。まとめておく。

サイズを渡すと、OSのサムネを使う

thumbnailLogicalSize を渡すと、OS が写真アプリのグリッド用にあらかじめ持っている縮小版を取りにいきます。ピクセルサイズは thumbnailLogicalSize × devicePixelRatio で決めます。devicePixelRatio は画面の1点に実際に何個の物理ピクセルが詰まっているかの倍率で、高精細画面だと2〜3倍になります。

渡さないと、フル解像度を自前デコード

thumbnailLogicalSize が無いと、経路が裏返ります。

OS のサムネは Photos / MediaStore のキャッシュ。画面の絵はメモリ。保存しない。サイズヒントありサイズヒントなしOS のサムネキャッシュiOS Photos / Android MediaStore縮小 JPEG が既にある。閉じても OS 側に残るアプリの DB ではない写真ライブラリのフルファイルカメラロールの本体iOS は tmp へコピーし得るアプリの DB ではない縮小バイトを渡す実体を要求するメモリの JPEG バイトImage.memory で表示保存しない。軽いメモリで自前デコードImage.file で展開・リサイズ保存しない。重い

どれくらい違うのか。iPhone の標準的な12MP写真(4032 × 3024)を、一覧に並ぶくらいの大きさ(180pt 幅・3倍の高精細画面)で出す場合の概算がこれ。

フル解像度 OSサムネ
画素数 4032 × 3024 540 × 405
JPEGファイル 約 2〜3 MB 約 30〜60 KB
展開後のビットマップ 48.8 MB 0.87 MB

ここが引っかかりやすいところで、「数MB」はファイルの大きさであって、メモリに載せたときの大きさではありません。JPEG は圧縮された状態なので、画面に出すために展開すると1画素4バイトで 4032 × 3024 × 4 ≒ 48.8 MB になります。ファイルサイズの感覚で「数MBか」と思っていると、15倍ほど過小に見積もることになります。

ただし綴Photo で実際に抜けていたのは thumbnailLogicalSize だけで、decodeLogicalSize のほうは付いていた。展開後のサイズはそちらで抑えられていたので、メモリがここまで振り切れていたわけではない。毎回払っていたのは、数MBのファイルをディスクから読み直し、OSが既に作ってあるサムネを使わずに JPEG を展開し直すぶんだ。iOS では実体ファイルを要求した時点で tmp コピーも走りえます。

秒数は計測していないので何秒縮んだかは言えない。ただ「出来合いのものがあるのに毎回作り直している」構図だったのは確かだった。

直した方向

綴Photo は実装を AI に手伝ってもらいながら進めていて、decodeLogicalSize だけ有って thumbnailLogicalSize が抜けていた箇所には、AI に thumbnailLogicalSize を足してもらった(detail_layout_album / detail_layout_letter / tsuzuru_photo_stage)。何がどのレイヤで効くのかを切り分けて揃えるところは、自分で確かめている。

引っかかっていたのは、この2つを混同していたことだった。

引数はメモリのヒントで保存しない。効く場所は取得の入口と、フルを掴んだあとの展開。OS キャッシュ / フルファイル写真ライブラリ側。閉じても残るアプリの DB ではない分岐は thumbnailLogicalSize交差。置き場ではない指定しないとフルを掴むメモリのビットマップImage.file が展開する出力保存しない。指定しないとフルのまま展開縮小は decodeLogicalSize交差。置き場ではない指定しても掴んだ事実は変わらない名前はどちらもサイズに見える。効く場所は前後で分かれている。

「どちらか書けばいい」と誤解しやすいです。どちらも省略可能な引数なので、片方だけ書いても何のエラーも出ず動いてしまいます。重いまま表示されるだけなので気づきにくいです。

学びポイント

  • サムネ表示にフル解像度を掴むと、表示に要らない重さを毎回払います。サイズヒントを渡してOSのサムネキャッシュに乗せます
  • decodeLogicalSize(デコード時のリサイズ)と thumbnailLogicalSize(取得経路の切替)は別レイヤ。名前が似ていて混同しやすいので、何を変えるパラメータなのかで見分けます
  • iOSは実体ファイルを要求すると tmp コピーが走り得ます。「取得経路」を意識すると、隠れたコピーや通信に気づけます

おわりに

実装をAIに手伝ってもらいながら後から読み解く中で、最初は「画像が重い」としか分からなかった症状が、thumbnailLogicalSize と decodeLogicalSize を別物として分けて捉えた時に経路の違いとして見えた。


この記事は2026年6月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。