Newer
Older
SmTIAS-Capture / lib / widgets / region_crop.dart
import 'dart:math' as math;

import 'package:flutter/material.dart';

import 'package:smtias_capture/models/preview_region.dart';

/// 表示向き適用済みの全体コンテンツ [child] を [region](表示後の画面座標 0.0–1.0)で
/// 切り出して画面いっぱいに拡大表示する(WYSIWYG).
///
/// ライブプレビュー([child] = CameraPreview)と保存中スナップショット
/// ([child] = 画像)で共通利用し,同じ領域・同じ拡大で表示を一致させる.
class RegionCrop extends StatelessWidget {
  const RegionCrop({super.key, required this.region, required this.child});

  /// クロップ領域(表示後の画面座標 0.0–1.0).
  final PreviewRegion region;

  /// クロップ対象(表示向き適用済みで領域全体を埋めるコンテンツ).
  final Widget child;

  @override
  Widget build(BuildContext context) {
    if (region.isFull) {
      return child;
    }

    return LayoutBuilder(
      builder: (context, constraints) {
        final double w = constraints.maxWidth;
        final double h = constraints.maxHeight;

        final double roiCx = (region.left + region.right) / 2.0;
        final double roiCy = (region.top + region.bottom) / 2.0;

        // ROI 中心を画面中心へ寄せる平行移動量(画面ピクセル)
        final double dx = (0.5 - roiCx) * w;
        final double dy = (0.5 - roiCy) * h;

        // ROI 全体を画面に収める contain ズーム(縦横同一スケールで歪み防止)
        final double zoom = math.min(1.0 / region.width, 1.0 / region.height);

        return ClipRect(
          child: Transform.scale(
            scale: zoom,
            alignment: Alignment.center,
            child: Transform.translate(
              offset: Offset(dx, dy),
              child: SizedBox(width: w, height: h, child: child),
            ),
          ),
        );
      },
    );
  }
}