import 'package:flutter/services.dart';
/// Camera2 API を使用してフル解像度の画像をキャプチャするサービス.
class RawCaptureService {
static const _channel = MethodChannel('com.example.mini_tias/raw_capture');
/// フロントカメラからフル解像度の PNG バイト列をキャプチャする(ネイティブ変換).
Future<Uint8List> captureFullResolutionPng() async {
final result = await _channel
.invokeMethod<Uint8List>('captureFullResolutionPng')
.timeout(
const Duration(seconds: 30),
onTimeout: () => throw Exception('カメラキャプチャがタイムアウトしました'),
);
if (result == null) {
throw Exception('PNG キャプチャに失敗しました');
}
return result;
}
/// YUV_420_888 を JPEG に変換する(Android ネイティブの高速変換).
Future<Uint8List> convertYuvToJpeg({
required int width,
required int height,
required Uint8List yPlane,
required Uint8List uPlane,
required Uint8List vPlane,
required int yRowStride,
required int uvRowStride,
required int uvPixelStride,
int rotation = 0,
bool mirror = false,
int quality = 85,
}) async {
final result = await _channel.invokeMethod<Uint8List>('convertYuvToJpeg', {
'width': width,
'height': height,
'yPlane': yPlane,
'uPlane': uPlane,
'vPlane': vPlane,
'yRowStride': yRowStride,
'uvRowStride': uvRowStride,
'uvPixelStride': uvPixelStride,
'rotation': rotation,
'mirror': mirror,
'quality': quality,
});
if (result == null) {
throw Exception('YUV to JPEG 変換に失敗しました');
}
return result;
}
/// フロントカメラの Camera2 API 能力を診断情報として取得する.
///
/// 戻り値はカメラの各種パラメータを含む Map.取得できない場合は null を返す.
Future<Map<String, dynamic>?> getCameraDiagnostics() async {
final result = await _channel.invokeMethod<Map<dynamic, dynamic>>(
'getCameraDiagnostics',
);
if (result == null) return null;
return result.cast<String, dynamic>();
}
/// MediaStore にファイルを登録し,PC から MTP で見えるようにする.
Future<void> scanFile(String path) async {
await _channel.invokeMethod('scanFile', {'path': path});
}
/// Android のシャッター音を再生する.
Future<void> playShutterSound() async {
await _channel.invokeMethod('playShutterSound');
}
/// Android の通知音を保存完了音として再生する.
Future<void> playSaveCompleteSound() async {
await _channel.invokeMethod('playSaveCompleteSound');
}
/// 定量モード DNG キャプチャを行う.
///
/// [baseName] / [directoryPath] を Kotlin 側に渡し,Kotlin 側で
/// `${directoryPath}/${baseName}.dng` として直接保存する.
/// タイムアウトは 30 秒.
Future<QuantitativeCaptureResult> captureQuantitativeDng({
required String baseName,
required String directoryPath,
}) async {
final result = await _channel
.invokeMethod<Map<dynamic, dynamic>>('captureQuantitativeDng', {
'baseName': baseName,
'directoryPath': directoryPath,
})
.timeout(
const Duration(seconds: 30),
onTimeout: () => throw Exception('定量モード撮影がタイムアウトしました'),
);
if (result == null) {
throw Exception('定量モード撮影に失敗しました');
}
return QuantitativeCaptureResult.fromMap(result);
}
}
/// `_deepConvert` の実装:ネストした Map / List を Dart 型に再帰変換する.
dynamic _deepConvert(dynamic value) {
if (value is Map) {
return value.map<String, dynamic>(
(key, v) => MapEntry(key.toString(), _deepConvert(v)),
);
}
if (value is List) {
return value.map(_deepConvert).toList();
}
return value;
}
/// 定量モード撮影の結果を保持する値クラス.
class QuantitativeCaptureResult {
/// Kotlin 側が書き込んだ DNG ファイルの絶対パス.
final String dngPath;
/// DNG ファイルのバイト数(書き込み確認用).
final int dngFileSize;
/// 縮小プレビュー JPEG の絶対パス.
///
/// Kotlin 側でプレビュー生成に失敗した場合は null.
final String? previewPath;
/// プレビュー JPEG のバイト数.
///
/// Kotlin 側でプレビュー生成に失敗した場合は null.
final int? previewFileSize;
/// 撮影メタデータ(設定値・実測値・センサ特性など).
final Map<String, dynamic> metadata;
/// LSC ゲインマップ(行ごとに 4ch 分のゲイン値を格納).null の場合は取得不可.
final List<List<double>>? lscMap;
/// LSC マップの行数.
final int? lscMapRowCount;
/// LSC マップの列数.
final int? lscMapColumnCount;
const QuantitativeCaptureResult({
required this.dngPath,
required this.dngFileSize,
this.previewPath,
this.previewFileSize,
required this.metadata,
this.lscMap,
this.lscMapRowCount,
this.lscMapColumnCount,
});
/// Kotlin から渡された Map を Dart 型に変換する.
factory QuantitativeCaptureResult.fromMap(Map<dynamic, dynamic> map) {
final dngPath = map['dngPath'] as String;
final dngFileSize = (map['dngFileSize'] as num).toInt();
final previewPath = map['previewPath'] as String?;
final previewFileSize = (map['previewFileSize'] as num?)?.toInt();
final rawMetadata = map['metadata'];
final metadata = (_deepConvert(rawMetadata) as Map<String, dynamic>?) ?? {};
final rawLscMap = map['lscMap'];
final lscMap = rawLscMap == null
? null
: (rawLscMap as List<dynamic>).map<List<double>>((row) {
return (row as List<dynamic>)
.map((e) => (e as num).toDouble())
.toList();
}).toList();
final lscMapRowCount = map['lscMapRowCount'] as int?;
final lscMapColumnCount = map['lscMapColumnCount'] as int?;
return QuantitativeCaptureResult(
dngPath: dngPath,
dngFileSize: dngFileSize,
previewPath: previewPath,
previewFileSize: previewFileSize,
metadata: metadata,
lscMap: lscMap,
lscMapRowCount: lscMapRowCount,
lscMapColumnCount: lscMapColumnCount,
);
}
}