Newer
Older
SmTIAS-Capture / docs / 04_SPEC / SPEC_04_プレビュー向きとクロップ.md

プレビュー向きとクロップ仕様書 (Preview Orientation & Crop Specification)

本ドキュメントでは,SmTIAS-Capture のプレビュー表示における 向き変換(反転) と クロップ拡大 の仕組み,座標系の定義,および変更時の確認手順を定義する.

重要:撮影画像(DNG)には影響しない. 本書が扱う向き変換・クロップは すべてプレビュー表示のみの処理である.保存される DNG は別経路(Camera2)で取得する.


1. 背景:なぜ向き変換が必要か

スマホを画面が下向きになるようにアタッチメントへ載せて口元を撮影する運用のため, カメラの生プレビューをそのまま表示すると,撮影者から見て上下・左右が反転している. そこで「撮影者が自然に見える向き」に整える変換を表示時に適用する.

正しい表示状態(撮影者が見る向き)の特徴:

  • 口元が中央に拡大表示される
  • 上部のアタッチメントラベル「SmTIAS 001」が鏡像+上下反転で写る(=向き変換が効いている証拠)
  • カラーチャートが画面右,位置合わせマーカーが四隅

基準スクリーンショットについて: 実機の基準画像は被写体(顔・口元)の 個人情報を含むため Git にはコミットせず,.gitignore 除外の local_reference/ にローカル保管する.確認時はその画像と見比べる(§5).


2. 座標系の定義(用語の統一)

本プロジェクトでは以下の2つの座標系を区別する.コード内コメントもこの用語に統一する.

用語意味
生フレーム座標カメラから来たままの向き(向き変換適用前).OrientedPreview の入力.
表示後座標向き変換を適用し,撮影者が見る向きにした後の座標(正規化 0.0–1.0).PreviewRegion / RegionCrop が扱う座標系.

PreviewRegion のクロップ座標は必ず「表示後座標」で定義する. (向き変換の後に切り出すため.生フレーム座標ではない.)


3. パイプライン全体図

プレビューには ライブ系(描画) と スナップショット系(保存中表示) の2系統があり, いずれも最終的に同じ RegionCrop(同じ region)でクロップして表示を一致させる.

                         ┌─ ライブプレビュー系 (Flutter 描画) ───────────┐
                         │  AspectRatio(h/w で横→縦補正)               │
生カメラフレーム          │  OrientedPreview(向き変換 = 180°回転)        │
(生フレーム座標)      ────┤    oriented_preview.dart                     │── RegionCrop(region)
                         │  ※ camera_preview.dart                       │   表示後座標で
                         └──────────────────────────────────────────────┘   クロップ&拡大
                         ┌─ スナップショット系 (native 変換) ────────────┐      ▲
最新フレーム             │  convertYuvToJpeg(rotation:90, mirror:true)   │      │ 同じ region・
(_latestFrame)       ────┤    camera_provider.dart                      │── CroppedSnapshot
                         │  = 向き処理済みの JPEG(表示後座標)            │   → RegionCrop(region)
                         └──────────────────────────────────────────────┘──────┘

向き変換が2系統に分かれている点(最重要)

同じ「撮影者が見る向き」を作るのに,2箇所で別々の手段を使っている.

系統向き処理実装箇所
ライブ(Flutter 描画)Matrix4.diagonal3Values(-1, -1, 1)(X 反転+Y 反転=180°回転)lib/widgets/oriented_preview.dart(kDisplayFlip)
スナップショット(native)rotation:90 + mirror:true(Kotlin 側で回転・ミラー)lib/providers/camera_provider.dart(convertYuvToJpeg 呼び出し)

向きを変更したいときは,この2箇所を整合させる必要がある. 片方だけ変えるとライブ表示と保存スナップショットがズレる. 変更後は必ず §5 の確認手順を実施すること.


4. クロップ拡大の仕組み(RegionCrop)

lib/widgets/region_crop.dart が表示後座標 0.0–1.0 の矩形 PreviewRegion を受け取り, 以下を行う(ライブ・スナップショット共通).

  1. ROI(関心領域)の中心を画面中心へ平行移動
  2. ROI 全体が画面に収まる contain ズーム(縦横同一スケールで歪み防止)
  3. ClipRect ではみ出しを切り取る

クロップ領域は assets/config/preview_region.json から読み込み, 読み込み失敗・不正値の場合は PreviewRegion.full(全画面・クロップなし)にフォールバックする (lib/services/preview_region_service.dart). 領域の調整には scripts/preview_region_picker.html を用いる.


5. 変更時の確認手順(必須)

向き変換・クロップに関わる変更を行ったら,2段階で確認する.

段階1:自動チェック(リファクタ前後で見た目が変わっていないか)

flutter test test/widgets/preview_orientation_golden_test.dart
  • 合成テストパターン(上=赤/下=青/左=緑/右=黄+左上マーカー円)を OrientedPreview・RegionCrop に流し,正解ゴールデン画像と照合する
  • 反転や座標を取り違えると色帯・円の位置がズレて即失敗する
  • これは「変換を壊していないか」の高速チェックであり,実機は不要

意図的に向きを変更した場合は,ゴールデンが古い正解のままなので失敗する. その場合のみ flutter test --update-goldens で正解を更新し, 更新後のゴールデン画像が意図通りかを目視確認してからコミットする.

段階2:実機での最終確認(向きが本当に正しいか)

  • 実機で口元を撮影し,画面全体のスクリーンショットを撮る
  • ローカル保管の基準画像(local_reference/ 配下,個人情報のため非コミット) と並べて目視で一致を確認する
  • 確認観点:
    • 口元が中央に拡大表示されているか
    • 上部ラベル「SmTIAS 001」が鏡像+上下反転で写るか
    • カラーチャートが右,マーカーが四隅にあるか
    • 上下・左右が実際の口の向きと一致するか

自動チェック(段階1)は「変わっていないこと」を守り, 実機確認(段階2)は「そもそも正しいこと」を守る.役割が異なるため両方行う.


6. 関連ファイル

ファイル役割
lib/models/preview_region.dartクロップ領域(表示後座標)の不変モデル
lib/services/preview_region_service.dartpreview_region.json の読み込み・フォールバック
lib/widgets/oriented_preview.dart向き変換(ライブ系,kDisplayFlip)
lib/widgets/region_crop.dartクロップ拡大(ライブ・スナップショット共通)
lib/widgets/camera_preview.dartライブプレビュー(向き変換+クロップ)
lib/widgets/cropped_snapshot.dart保存中スナップショット表示(クロップのみ)
lib/providers/camera_provider.dartスナップショット系の向き処理(rotation:90, mirror:true)
assets/config/preview_region.jsonクロップ領域の設定値
scripts/preview_region_picker.htmlクロップ領域の調整ツール
test/widgets/preview_orientation_golden_test.dart向き・クロップの回帰テスト(段階1)
local_reference/(非コミット)実機確認の基準スクリーンショット保管場所(段階2,個人情報のため Git 除外)