本ドキュメントでは,MiniTIAS アプリの画面設計,画面遷移,カメラ制御,ファイル管理,およびアーキテクチャを定義する.
| ID | 画面名 | 概要 |
|---|---|---|
| S-01 | 撮影画面 | インカメラのライブプレビューを表示し,撮影を行う |
| S-02 | 一覧画面 | 撮影済み画像のサムネイル一覧を表示し,確認・削除を行う |
※ 初期バージョンでは 2 画面構成とする.
[アプリ起動]
│
▼
┌──────────┐ BottomNavigationBar ┌──────────┐
│ S-01 │ ◄──────────────────────────────► │ S-02 │
│ 撮影画面 │ タブ切り替え │ 一覧画面 │
└──────────┘ └──────────┘
│
│ サムネイルタップ
▼
┌──────────┐
│ 詳細表示 │
│(インライン) │
└──────────┘
home_screen.dart の BottomNavigationBar を使用する(タブ 2 つ: 撮影 / 一覧)Stack オーバーレイで実装し,独立画面・ダイアログとしないアプリ全体を app.dart の MaterialApp ごと Transform.rotate(angle: pi) で 180° 回転する.これにより画面・ダイアログ・スナックバー等すべての UI が自動的に回転されるため,個々のウィジェットで回転を意識する必要がない.以下はコード上の配置順(回転前)を示す.操作者が逆さに置いた端末を見ると,上下が反転して表示される.
操作者の視点(回転後) コード上の配置(回転前) ┌─────────────────────┐ ┌─────────────────────┐ │ (黒い空白) │ │ BottomNavBar │ │ アタッチメントで隠れる │ │ 明るさスライダー │ ├─────────────────────┤ │ │ │ │ │ カメラプレビュー │ │ カメラプレビュー │ │ (中央クロップ) │ │ (中央クロップ) │ │ │ │ │ ├─────────────────────┤ │ 明るさスライダー │ │ (黒い空白) │ │ BottomNavBar │ │ アタッチメントで隠れる │ └─────────────────────┘ └─────────────────────┘
※ アタッチメント(SmTIAS)により画面の約 1/3 が隠れるため,その領域は黒い空白とし,UI 要素は見える領域に配置する.
| 要素 | 仕様 |
|---|---|
| カメラプレビュー | インカメラのライブ映像.鏡像(左右反転)+上下反転で表示.画面いっぱいに拡大し中央をクロップ |
| シャッターボタン | 丸型ボタン(画面下部中央).タップで撮影実行.連続撮影可 |
| タイマーボタン | 画面左下に配置.ON(黄色)/ OFF(グレー)を切り替える.ON 時はシャッター押下後 3 秒のカウントダウン表示の後に撮影 |
| サウンドボタン | 画面右下に配置.シャッター音・保存完了音の ON(黄色)/ OFF(グレー)を切り替える.設定は次回起動時も保持(shared_preferences) |
| 明るさスライダー | ナビバーの直下に配置.画面の輝度を手動で調整する(デフォルト: 0.8) |
| BottomNavigationBar | 撮影タブ(アクティブ)/ 一覧タブ.背景色は黒系で統一.保存中はタップ無効 |
startImageStream)から最新フレームを保持し,撮影開始時に Android ネイティブの YuvImage.compressToJpeg() で JPEG 変換して表示する(オーバーレイなし・瞬時)┌─────────────────────────┐ │ BottomNavBar │ ├─────────────────────────┤ │ ┌─────┐ ┌─────┐ ┌─────┐│ │ │ │ │ │ │ ││ │ │ img │ │ img │ │ img ││ │ │ │ │ │ │ ││ │ └─────┘ └─────┘ └─────┘│ │ ┌─────┐ ┌─────┐ ┌─────┐│ │ │ │ │ │ │ ││ │ │ img │ │ img │ │ img ││ │ │ │ │ │ │ ││ │ └─────┘ └─────┘ └─────┘│ │ ... │ ├─────────────────────────┤ │ [画面上部エリア] │ ← 逆さ配置のため上部が操作側 └─────────────────────────┘
※ 撮影画面と同様に UI 全体を 180° 回転して表示する.
| 要素 | 仕様 |
|---|---|
| サムネイルグリッド | 3 列のグリッド表示.新しい画像が先頭(降順) |
| サムネイル | 正方形クロップ.タップで拡大表示 |
| ファイル名ラベル | 各サムネイル下部にファイル名を表示 |
| BottomNavigationBar | 撮影タブ / 一覧タブ(アクティブ) |
| 要素 | 仕様 |
|---|---|
| 画像 | 一覧画面エリアを全面に覆うオーバーレイで表示.カメラプレビューと同様に上下をクロップして全面表示(BoxFit.cover).ピンチ操作でズーム可能 |
| ファイル名 | オーバーレイ上部に半透明背景とともに表示 |
| 削除ボタン | オーバーレイ下部に半透明背景とともに配置.タップで削除確認ダイアログを表示 |
| 閉じるボタン | ヘッダー右端の × ボタンで閉じる |
Pictures/MiniTIAS/ 内のプレビュー JPEG(.preview.jpg)と旧通常モードの .png を走査し,一覧を構築する(DNG 本体は一覧では直接表示しない)| 項目 | 値 |
|---|---|
| 使用カメラ | フロントカメラ(インカメラ) |
| 解像度 | ResolutionPreset.max |
| 制御方式 | camera パッケージ(オート任せ) |
| フラッシュ | OFF(LED ライトはアタッチメント側で制御) |
プレビューは見やすさ重視.撮影時は別の Camera2 セッションが走る.
| 項目 | 値 |
|---|---|
| 使用カメラ | フロントカメラ(インカメラ) |
| 解像度 | 3264×2448(センサ最大) |
| 画像フォーマット | RAW_SENSOR(10-bit BGGR Bayer).DngCreator で DNG ファイル出力 |
CONTROL_MODE | OFF(マニュアル全制御) |
CONTROL_AE_MODE | OFF |
SENSOR_EXPOSURE_TIME | 8,333,333 ns(1/120 s)固定(アタッチメント + LED 環境で線形領域中央狙い.定量モード合意の目標 mean G = 400〜600 達成済み.05_TECH 参照) |
SENSOR_SENSITIVITY | 40(センサ最低 ISO)固定 |
SENSOR_FRAME_DURATION | 33,333,333 ns(30fps 相当) |
BLACK_LEVEL_LOCK | true |
CONTROL_AWB_MODE | OFF |
COLOR_CORRECTION_GAINS | (1.0, 1.0, 1.0, 1.0) |
COLOR_CORRECTION_TRANSFORM | 単位行列 |
NOISE_REDUCTION_MODE | OFF |
EDGE_MODE | OFF |
TONEMAP_MODE | CONTRAST_CURVE 線形 [(0,0),(1,1)] |
SHADING_MODE | HIGH_QUALITY(LSC マップ実値取得のため.画像は LSC 補正済み.PC 側で逆適用可) |
STATISTICS_LENS_SHADING_MAP_MODE | ON |
CONTROL_AF_MODE | OFF(AQUOS sense3 は固定焦点) |
FLASH_MODE | OFF |
旧 YUV→BT.601→PNG 経路は RawCapturePlugin.captureFullResolutionPng として残存.現状の takePicture フローからは呼ばれない.差分比較やフォールバック用に保持.
camera パッケージの CameraPreview ウィジェットを使用するTransform.rotate(angle: pi) で 180° 回転するMatrix4.diagonal3Values(-scale, -scale, 1.0) を適用する
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): カメラを再初期化するPictures/MiniTIAS/(Android 共有ストレージ)MediaScannerConnection.scanFile(MethodChannel scanFile)で MediaStore に登録する撮影 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 形式で行われるbaseName = "MiniTIAS_QM_{timestamp}"
IF "{baseName}.dng" が存在しない:
RETURN baseName
suffix = 1
WHILE "{baseName}_{suffix}.dng" が存在する:
suffix += 1
RETURN "{baseName}_{suffix}"
※ baseName をもとに .dng / .meta.json / .preview.jpg の 3 ファイルを共通名で生成する.
.preview.jpg の場合:「この画像を削除しますか?(DNG ファイルとメタデータも一緒に削除されます)」.png(旧通常モード)の場合:「この画像を削除しますか?」.preview.jpg 削除時は同 baseName の .dng と .meta.json も連動削除する| パーミッション | 用途 | 対象 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 パッケージを使用する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(サービス) │ 外部リソースとのインタフェース └─────────────────────────────────┘
context.watch / context.read で参照し,直接 Service を呼ばないflutter.minSdkVersion(android/app/build.gradle.kts が参照する Flutter 規定値)