import 'package:flutter/services.dart'; /// Camera2 API を使用してフル解像度の画像をキャプチャするサービス. class RawCaptureService { static const _channel = MethodChannel('com.example.mini_tias/raw_capture'); /// フロントカメラからフル解像度の PNG バイト列をキャプチャする(ネイティブ変換). Future captureFullResolutionPng() async { final result = await _channel .invokeMethod('captureFullResolutionPng') .timeout( const Duration(seconds: 30), onTimeout: () => throw Exception('カメラキャプチャがタイムアウトしました'), ); if (result == null) { throw Exception('PNG キャプチャに失敗しました'); } return result; } /// YUV_420_888 を JPEG に変換する(Android ネイティブの高速変換). Future 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('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?> getCameraDiagnostics() async { final result = await _channel.invokeMethod>( 'getCameraDiagnostics', ); if (result == null) return null; return result.cast(); } /// MediaStore にファイルを登録し,PC から MTP で見えるようにする. Future scanFile(String path) async { await _channel.invokeMethod('scanFile', {'path': path}); } /// Android のシャッター音を再生する. Future playShutterSound() async { await _channel.invokeMethod('playShutterSound'); } /// Android の通知音を保存完了音として再生する. Future playSaveCompleteSound() async { await _channel.invokeMethod('playSaveCompleteSound'); } /// 定量モード DNG キャプチャを行う. /// /// [baseName] / [directoryPath] を Kotlin 側に渡し,Kotlin 側で /// `${directoryPath}/${baseName}.dng` として直接保存する. /// タイムアウトは 30 秒. Future captureQuantitativeDng({ required String baseName, required String directoryPath, }) async { final result = await _channel .invokeMethod>('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( (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; /// 撮影メタデータ(設定値・実測値・センサ特性など). final Map metadata; /// LSC ゲインマップ(行ごとに 4ch 分のゲイン値を格納).null の場合は取得不可. final List>? lscMap; /// LSC マップの行数. final int? lscMapRowCount; /// LSC マップの列数. final int? lscMapColumnCount; const QuantitativeCaptureResult({ required this.dngPath, required this.dngFileSize, required this.metadata, this.lscMap, this.lscMapRowCount, this.lscMapColumnCount, }); /// Kotlin から渡された Map を Dart 型に変換する. factory QuantitativeCaptureResult.fromMap(Map map) { final dngPath = map['dngPath'] as String; final dngFileSize = (map['dngFileSize'] as num).toInt(); final rawMetadata = map['metadata']; final metadata = (_deepConvert(rawMetadata) as Map?) ?? {}; final rawLscMap = map['lscMap']; final lscMap = rawLscMap == null ? null : (rawLscMap as List).map>((row) { return (row as List) .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, metadata: metadata, lscMap: lscMap, lscMapRowCount: lscMapRowCount, lscMapColumnCount: lscMapColumnCount, ); } }