Newer
Older
SmTIAS-Capture / lib / services / raw_capture_service.dart
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;

  /// 撮影メタデータ(設定値・実測値・センサ特性など).
  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,
    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 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,
      metadata: metadata,
      lscMap: lscMap,
      lscMapRowCount: lscMapRowCount,
      lscMapColumnCount: lscMapColumnCount,
    );
  }
}