Newer
Older
SmTIAS-Capture / docs / 04_SPEC / SPEC_01_画面機能仕様書.md

画面機能仕様書 (Screen & Function Specification)

本ドキュメントでは,MiniTIAS アプリの画面設計,画面遷移,カメラ制御,ファイル管理,およびアーキテクチャを定義する.

画面一覧 (Screen List)

ID画面名概要
S-01撮影画面インカメラのライブプレビューを表示し,撮影を行う
S-02一覧画面撮影済み画像のサムネイル一覧を表示し,確認・削除を行う

※ 初期バージョンでは 2 画面構成とする.

画面遷移 (Screen Transition)

[アプリ起動]
    │
    ▼
┌──────────┐       BottomNavigationBar       ┌──────────┐
│  S-01    │ ◄──────────────────────────────► │  S-02    │
│ 撮影画面  │         タブ切り替え              │ 一覧画面  │
└──────────┘                                  └──────────┘
                                                   │
                                                   │ サムネイルタップ
                                                   ▼
                                              ┌──────────┐
                                              │ 詳細表示   │
                                              │(インライン) │
                                              └──────────┘
  • アプリ起動時のデフォルト画面は 撮影画面(S-01) とする
  • 画面切り替えには BottomNavigationBar を使用する(タブ 2 つ: 撮影 / 一覧)
  • 画像の詳細表示は一覧画面内の Stack オーバーレイで実装し,独立画面・ダイアログとしない

撮影画面 (S-01: Capture Screen)

レイアウト

アプリ全体を app.dartMaterialApp ごと Transform.rotate(angle: pi) で 180° 回転する.これにより画面・ダイアログ・スナックバー等すべての UI が自動的に回転されるため,個々のウィジェットで回転を意識する必要がない.以下はコード上の配置順(回転前)を示す.操作者が逆さに置いた端末を見ると,上下が反転して表示される.

操作者の視点(回転後)            コード上の配置(回転前)
┌─────────────────────┐      ┌─────────────────────┐
│ (黒い空白)          │      │  BottomNavBar        │
│  アタッチメントで隠れる │      │  明るさスライダー      │
├─────────────────────┤      │                     │
│                     │      │  カメラプレビュー      │
│  カメラプレビュー      │      │  (中央クロップ)      │
│  (中央クロップ)      │      │                     │
│                     │      ├─────────────────────┤
│  明るさスライダー      │      │ (黒い空白)          │
│  BottomNavBar        │      │  アタッチメントで隠れる │
└─────────────────────┘      └─────────────────────┘

※ アタッチメント(SmTIAS)により画面の約 1/3 が隠れるため,その領域は黒い空白とし,UI 要素は見える領域に配置する.

UI 要素

要素仕様
カメラプレビューインカメラのライブ映像.鏡像(左右反転)+上下反転で表示.画面いっぱいに拡大し中央をクロップ
シャッターボタン丸型ボタン(画面下部中央).タップで撮影実行.連続撮影可
タイマーボタン画面左下に配置.ON(黄色)/ OFF(グレー)を切り替える.ON 時はシャッター押下後 3 秒のカウントダウン表示の後に撮影
明るさスライダーナビバーの直下に配置.画面の輝度を手動で調整する(デフォルト: 0.8)
BottomNavigationBar撮影タブ(アクティブ)/ 一覧タブ.背景色は黒系で統一.保存中はタップ無効

動作仕様

  • 起動時: カメラの初期化を行い,プレビューを開始する
  • 即時撮影: タイマー OFF 時,シャッターボタンタップで即座に撮影する
  • タイマー撮影: タイマー ON 時,シャッターボタンタップで 3, 2, 1 のカウントダウン後に撮影する(手ブレ・振動防止用)
  • 連続撮影: 前回の保存完了を待ってから次の撮影が可能
  • 保存中表示: カメラの画像ストリーム(startImageStream)から最新フレームを保持し,撮影開始時に Android ネイティブの YuvImage.compressToJpeg() で JPEG 変換して表示する(オーバーレイなし・瞬時)
  • フィードバック: 撮影成功時にスナックバーで「保存しました: ファイル名」を表示する
  • エラー時: カメラ初期化失敗やストレージ書き込み失敗時は,エラーメッセージをスナックバーで表示する

レイアウト

┌─────────────────────────┐
│     BottomNavBar         │
├─────────────────────────┤
│ ┌─────┐ ┌─────┐ ┌─────┐│
│ │     │ │     │ │     ││
│ │ img │ │ img │ │ img ││
│ │     │ │     │ │     ││
│ └─────┘ └─────┘ └─────┘│
│ ┌─────┐ ┌─────┐ ┌─────┐│
│ │     │ │     │ │     ││
│ │ img │ │ img │ │ img ││
│ │     │ │     │ │     ││
│ └─────┘ └─────┘ └─────┘│
│          ...             │
├─────────────────────────┤
│   [画面上部エリア]        │  ← 逆さ配置のため上部が操作側
└─────────────────────────┘

※ 撮影画面と同様に UI 全体を 180° 回転して表示する.

UI 要素

要素仕様
サムネイルグリッド3 列のグリッド表示.新しい画像が先頭(降順)
サムネイル正方形クロップ.タップで拡大表示
ファイル名ラベル各サムネイル下部にファイル名を表示
BottomNavigationBar撮影タブ / 一覧タブ(アクティブ)

詳細表示(インラインオーバーレイ)

要素仕様
画像一覧画面エリアを全面に覆うオーバーレイで表示.カメラプレビューと同様に上下をクロップして全面表示(BoxFit.cover).ピンチ操作でズーム可能
ファイル名オーバーレイ上部に半透明背景とともに表示
削除ボタンオーバーレイ下部に半透明背景とともに配置.タップで削除確認ダイアログを表示
閉じるボタンヘッダー右端の × ボタンで閉じる

動作仕様

  • 画面表示時: Pictures/MiniTIAS/ ディレクトリ内の PNG ファイルを走査し,一覧を構築する
  • リフレッシュ: 撮影画面から戻った際に一覧を再取得する
  • 詳細表示: サムネイルタップで詳細表示オーバーレイを開く.× ボタンで閉じる
  • 削除: 詳細表示の削除ボタンから確認ダイアログで「削除」を選択すると,ファイルをストレージから削除し,一覧から除去する
  • 空状態: 画像が 0 件の場合は「撮影した画像がありません」のメッセージを表示する

カメラ制御仕様 (Camera Control)

プレビュー設定(フレーミング用)

項目
使用カメラフロントカメラ(インカメラ)
解像度ResolutionPreset.max
制御方式camera パッケージ(オート任せ)
フラッシュOFF(LED ライトはアタッチメント側で制御)

プレビューは見やすさ重視.撮影時は別の Camera2 セッションが走る.

撮影設定(定量モード.Camera2 API マニュアル制御)

項目
使用カメラフロントカメラ(インカメラ)
解像度3264×2448(センサ最大)
画像フォーマットRAW_SENSOR(10-bit BGGR Bayer).DngCreator で DNG ファイル出力
CONTROL_MODEOFF(マニュアル全制御)
CONTROL_AE_MODEOFF
SENSOR_EXPOSURE_TIME8,333,333 ns(1/120 s)固定(アタッチメント + LED 環境で線形領域中央狙い.TECH_04 目標 mean G = 400〜600 達成済み)
SENSOR_SENSITIVITY40(センサ最低 ISO)固定
SENSOR_FRAME_DURATION33,333,333 ns(30fps 相当)
BLACK_LEVEL_LOCKtrue
CONTROL_AWB_MODEOFF
COLOR_CORRECTION_GAINS(1.0, 1.0, 1.0, 1.0)
COLOR_CORRECTION_TRANSFORM単位行列
NOISE_REDUCTION_MODEOFF
EDGE_MODEOFF
TONEMAP_MODECONTRAST_CURVE 線形 [(0,0),(1,1)]
SHADING_MODEHIGH_QUALITY(LSC マップ実値取得のため.画像は LSC 補正済み.PC 側で逆適用可)
STATISTICS_LENS_SHADING_MAP_MODEON
CONTROL_AF_MODEOFF(AQUOS sense3 は固定焦点)
FLASH_MODEOFF

既存 PNG 撮影パス(無効化済み・コードは温存)

旧 YUV→BT.601→PNG 経路は RawCapturePlugin.captureFullResolutionPng として残存.現状の takePicture フローからは呼ばれない.差分比較やフォールバック用に保持.

プレビュー表示

  • camera パッケージの CameraPreview ウィジェットを使用する
  • UI 全体を Transform.rotate(angle: pi) で 180° 回転する
  • カメラ映像に Matrix4.diagonal3Values(-scale, -scale, 1.0) を適用する
    • X 軸反転: 鏡像表示(操作者が自然に見える向き)
    • Y 軸反転: 端末が逆さのため上下を補正
  • プレビューは画面いっぱいに拡大し,はみ出す部分はクロップする(ClipRect + スケール計算)

撮影フロー(定量モード)

シャッターボタンタップ
    │
    ▼
プレビュー停止(CameraController.dispose())
    │
    ▼
baseName 生成(MiniTIAS_QM_YYYYMMDD_HHmmss.重複時 _1, _2 ...)
    │
    ▼
Camera2 API でフロントカメラを開く(RAW_SENSOR,3264×2448)
    │
    ▼
上記マニュアル制御パラメータを CaptureRequest に適用
    │
    ▼
連続 3 枚キャプチャ(設定反映のため最初の 2 枚は破棄)
    │
    ▼
3 枚目の Image + TotalCaptureResult を取得
    │
    ▼
DngCreator で DNG ファイルを直接書き込み(FileOutputStream)
  → Pictures/MiniTIAS/{baseName}.dng(約 16 MB)
    │
    ▼
LSC マップ取得(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP)
    │
    ▼
メタデータ Map 構築(settings / actual / sensorCharacteristics / lscMap / image_statistics / hot_pixel_map)
    │
    ▼
Dart 側で JSON 保存
  → Pictures/MiniTIAS/{baseName}.meta.json
    │
    ▼
[並行] シャッター押下時に取得済みのライブプレビュースナップショット JPEG を保存
  → Pictures/MiniTIAS/{baseName}.preview.jpg
  ※ スナップショット取得失敗時は preview.jpg を保存しない(撮影全体は失敗扱いにしない)
    │
    ▼
MediaStore 通知(DNG / meta.json / preview.jpg すべて scanFile)
    │
    ▼
Camera2 を閉じ,プレビューを再開
    │
    ▼
スナックバーで保存完了を通知

ライフサイクル管理

  • WidgetsBindingObserver を使用し,アプリのライフサイクルを監視する
  • バックグラウンド遷移時paused): カメラリソースを解放する(CameraController.dispose()
  • フォアグラウンド復帰時resumed): カメラを再初期化する
  • 画面遷移でカメラを使用しない画面に移動した場合もカメラリソースを解放する

ファイル管理仕様 (File Management)

保存先

  • パス: Pictures/MiniTIAS/(Android 共有ストレージ)
  • Android 10 以上では MediaStore API を使用する(saver_gallery パッケージで抽象化)
  • ディレクトリが存在しない場合は自動作成する

ファイル命名規則

撮影 1 回で 3 ファイルを生成する(baseName は共通.重複時 _1, _2 ...):

種別ファイル名概要
RAW 画像MiniTIAS_QM_YYYYMMDD_HHmmss.dng解析の本体(10-bit BGGR Bayer,約 16 MB)
メタデータMiniTIAS_QM_YYYYMMDD_HHmmss.meta.json撮影設定 + 実適用値 + LSC マップ + センサ特性(約 24 KB)
プレビューMiniTIAS_QM_YYYYMMDD_HHmmss.preview.jpgアプリ内表示用の縮小プレビュー.シャッター瞬間のライブプレビュー(自動 ISP 処理済み YUV→JPEG)をそのまま保存.解像度はプレビューストリーム依存(おそらく 1280×720 程度)
  • タイムスタンプはデバイスのローカル時刻を使用する
  • 旧通常モードの MiniTIAS_YYYYMMDD_HHmmss.png は引き続き一覧に表示可能(後方互換).新規撮影は QM 形式で行われる

重複回避ロジック

ファイル名候補 = "MiniTIAS_{timestamp}.png"

IF ファイル名候補が存在しない:
    RETURN ファイル名候補

suffix = 1
WHILE "MiniTIAS_{timestamp}_{suffix}.png" が存在する:
    suffix += 1

RETURN "MiniTIAS_{timestamp}_{suffix}.png"

削除

  • 一覧画面の詳細表示オーバーレイから削除を実行する
  • 削除前に確認ダイアログを表示する
    • .preview.jpg の場合:「この画像を削除しますか?(DNG ファイルとメタデータも一緒に削除されます)」
    • .png(旧通常モード)の場合:「この画像を削除しますか?」
  • .preview.jpg 削除時は同 baseName の .dng.meta.json連動削除する
  • ファイルシステムから物理削除する(ゴミ箱機能なし)
  • 削除後,MediaStore からも当該エントリを除去する

パーミッション管理 (Permission Handling)

必要なパーミッション

パーミッション用途対象 API レベル
CAMERAカメラプレビュー・撮影全バージョン
WRITE_EXTERNAL_STORAGE画像保存API 28 以下
READ_EXTERNAL_STORAGE画像一覧取得API 28 以下
READ_MEDIA_IMAGES画像一覧取得API 33 以上

※ API 29〜32 では Scoped Storage により,アプリが MediaStore 経由で保存したファイルは追加権限なしでアクセス可能.

パーミッション要求フロー

アプリ起動
    │
    ▼
カメラ権限を確認
    │
    ├── 許可済み → カメラプレビュー開始
    │
    └── 未許可 → 権限リクエストダイアログ表示
              │
              ├── 許可 → カメラプレビュー開始
              │
              └── 拒否 → 「カメラの権限が必要です」メッセージ表示
                         + 設定画面への誘導ボタン
  • ストレージ権限は撮影時・一覧表示時にそれぞれ確認する
  • permission_handler パッケージを使用する

アーキテクチャ (Architecture)

状態管理

  • provider パッケージを使用する
  • 画面ごとに ChangeNotifier を作成し,ビジネスロジックを UI から分離する

Provider 構成

Provider責務
CameraProviderカメラの初期化・プレビュー制御・撮影実行・ライフサイクル管理
GalleryProvider画像一覧の取得・キャッシュ・削除操作
DiagnosticsProviderカメラ診断の実行・結果保存(インフラのみ・UI 非表示)

ディレクトリ構成

lib/
├── main.dart                     # アプリエントリポイント,Provider 登録
├── app.dart                      # MaterialApp 定義,テーマ設定,180° 回転
├── providers/
│   ├── camera_provider.dart      # カメラ制御の状態管理
│   ├── gallery_provider.dart     # 画像一覧の状態管理
│   └── diagnostics_provider.dart # カメラ診断(インフラのみ)
├── screens/
│   ├── home_screen.dart          # BottomNavigationBar による画面切替
│   ├── capture_screen.dart       # 撮影画面 (S-01)
│   └── gallery_screen.dart       # 一覧画面 (S-02)
├── widgets/
│   ├── camera_preview.dart       # カメラプレビューウィジェット
│   ├── shutter_button.dart       # シャッターボタン
│   └── image_grid.dart           # サムネイルグリッド
└── services/
    ├── file_service.dart         # ファイル保存・命名・削除
    ├── permission_service.dart   # パーミッション管理
    ├── raw_capture_service.dart  # ネイティブ撮影(DNG/PNG)・診断の MethodChannel
    └── sound_service.dart        # シャッター音・保存完了音の制御

※ 撮影・画像変換・診断の実処理は Android ネイティブ android/.../RawCapturePlugin.kt(Camera2 / DngCreator)が担い, raw_capture_service.dart が MethodChannel で橋渡しする。

レイヤー構成

┌─────────────────────────────────┐
│  Screens(画面)                  │  UI 層: ウィジェット構築のみ
├─────────────────────────────────┤
│  Widgets(部品)                  │  再利用可能な UI コンポーネント
├─────────────────────────────────┤
│  Providers(状態管理)            │  ビジネスロジック・状態保持
├─────────────────────────────────┤
│  Services(サービス)             │  外部リソースとのインタフェース
└─────────────────────────────────┘
  • Screens は Provider を context.watch / context.read で参照し,直接 Service を呼ばない
  • Providers は Service を呼び出し,結果を状態として保持する
  • Services はプラットフォーム API やファイルシステムとの橋渡しを行う

非機能要件 (Non-functional Requirements)

画面輝度

  • アプリ起動時に画面の輝度をデフォルト値(0.8)に設定する
  • ユーザーが明るさスライダーで任意に調整できる
  • アプリ終了時にシステムの輝度設定に復帰する
  • アタッチメント装着時に光センサーで画面が暗くなることへの対策

パフォーマンス

  • 撮影から保存完了まで 2 秒以内を目標とする
  • 一覧画面のサムネイル読み込みは非同期で行い,画面表示をブロックしない

データ保全

  • 撮影データは端末ローカルにのみ保存する(クラウド同期なし)
  • 定量モードは RAW(DNG,10-bit BGGR Bayer)で保存し,ISP 処理による情報欠落を防ぐ(旧通常モードは非圧縮 PNG)

対応端末

  • 試験端末: AQUOS sense3(SH-02M,Android 9)
  • minSdkVersion: 21(Android 5.0)
  • targetSdkVersion: Flutter デフォルト(最新安定版に追従)