web-dev-qa-db-ja.com

Flutterカメラプレビューをスクエアクロップする方法

アプリで正方形の写真を撮ろうとしています。私は camera パッケージを使用していて、CameraPreviewウィジェットの中央が正方形に切り抜かれたバージョンを表示しようとしています。

私の目標は、プレビューの中央の四角形(全幅)を表示し、上下から均等にトリミングすることです。

これを機能させるのに苦労していたため、固定イメージを使用して最小限の例を作成しました。 (椅子でのぼんやりした写真の謝罪):

import 'package:flutter/material.Dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Example',
      theme: ThemeData(),
      home: Scaffold(
        body: Example(),
      ),
    );
  }
}

class Example extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        CroppedCameraPreview(),

        // Something to occupy the rest of the space
        Expanded(
          child: Container(),
        )
      ],
    );
  }
}

class CroppedCameraPreview extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // We will pretend this is a camera preview (to make demo easier)
    var cameraImage = Image.network("https://i.imgur.com/gZfg4jm.jpg");
    var aspectRatio = 1280 / 720;

    return Container(
      width: MediaQuery.of(context).size.width,
      height: MediaQuery.of(context).size.width,
      child: ClipRect(
        child: new OverflowBox(
          alignment: Alignment.center,
          child: FittedBox(
            fit: BoxFit.fitWidth,
            child: cameraImage,
          ),
        ),
      ),
    );
  }
}

これは正常に動作します-全画面幅の画像が表示され、中央がトリミングされてアプリの上部にプッシュされます。

ただし、このコードを既存のアプリにドロップしてcameraImageCameraPreviewに置き換えると、多くのレイアウトエラーが発生します。

flutter: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
flutter: The following assertion was thrown during performResize():
flutter: TextureBox object was given an infinite size during layout.
flutter: This probably means that it is a render object that tries to be as big as possible, but it was put
flutter: inside another render object that allows its children to pick their own size.
flutter: The nearest ancestor providing an unbounded width constraint is:
flutter:   RenderFittedBox#0bd54 NEEDS-LAYOUT NEEDS-Paint
flutter:   creator: FittedBox ← OverflowBox ← ClipRect ← ConstrainedBox ← Container ← Stack ← ConstrainedBox
flutter:   ← Container ← CameraWidget ← Column ← CameraPage ← MediaQuery ← ⋯
flutter:   parentData: offset=Offset(0.0, 0.0) (can use size)
flutter:   constraints: BoxConstraints(w=320.0, h=320.0)
flutter:   size: MISSING
flutter:   fit: fitWidth
flutter:   alignment: center
flutter:   textDirection: ltr
flutter: The nearest ancestor providing an unbounded height constraint is:
flutter:   RenderFittedBox#0bd54 NEEDS-LAYOUT NEEDS-Paint
flutter:   creator: FittedBox ← OverflowBox ← ClipRect ← ConstrainedBox ← Container ← Stack ← ConstrainedBox
flutter:   ← Container ← CameraWidget ← Column ← CameraPage ← MediaQuery ← ⋯
flutter:   parentData: offset=Offset(0.0, 0.0) (can use size)
flutter:   constraints: BoxConstraints(w=320.0, h=320.0)
flutter:   size: MISSING
flutter:   fit: fitWidth
flutter:   alignment: center
flutter:   textDirection: ltr
flutter: The constraints that applied to the TextureBox were:
flutter:   BoxConstraints(unconstrained)
flutter: The exact size it was given was:
flutter:   Size(Infinity, Infinity)
flutter: See https://flutter.io/layout/ for more information.

プレビューでエラーが発生する理由とその回避方法を誰かが提案できますか?

9
Duncan Jones

これを解決するには、CameraPreviewインスタンスに特定のサイズを指定し、Containerでラップします。

  var size = MediaQuery.of(context).size.width;

  // ...

  Container(
    width: size,
    height: size,
    child: ClipRect(
      child: OverflowBox(
        alignment: Alignment.center,
        child: FittedBox(
          fit: BoxFit.fitWidth,
          child: Container(
            width: size,
            height:
                size / widget.cameraController.value.aspectRatio,
            child: camera, // this is my CameraPreview
          ),
        ),
      ),
    ),
  );

ルークのコメントに応答するために、このコードを使用して結果の画像を四角く切り取りました。 (プレビューが正方形であっても、キャプチャされた画像は標準的な比率であるため)。

  Future<String> _resizePhoto(String filePath) async {
      ImageProperties properties =
          await FlutterNativeImage.getImageProperties(filePath);

      int width = properties.width;
      var offset = (properties.height - properties.width) / 2;

      File croppedFile = await FlutterNativeImage.cropImage(
          filePath, 0, offset.round(), width, width);

      return croppedFile.path;
  }

これは https://github.com/btastic/flutter_native_image を使用します。私がこのコードを使用して久しぶりです-現在は縦向きの画像でのみ機能すると思いますが、横向きを処理するために簡単に拡張できるはずです。

20
Duncan Jones

回答で使用したものと同様のコードスニペットがあります。

回答と同様に、カメラのアスペクト比と画面のアスペクト比が異なる場合に対応しています。

私のバージョンにはいくつかの違いがあります:デバイスサイズを取得するためにメディアクエリを使用する必要がないため、親の幅(全画面幅だけでなく)に適合します

          ....

          return AspectRatio(
            aspectRatio: 1,
            child: ClipRect(
              child: Transform.scale(
                scale: 1 / _cameraController.value.aspectRatio,
                child: Center(
                  child: AspectRatio(
                    aspectRatio: _cameraController.value.aspectRatio,
                    child: CameraPreview(_cameraController),
                  ),
                ),
              ),
            ),
          );

画像を中央直角にトリミングするには、以下のスニペットを参照してください。

縦向きと横向きの画像でも同じように機能します。オプションで画像をミラーリングすることもできます(自撮りカメラからの元のミラーされた外観を保持する場合に便利です)

import 'Dart:io';
import 'Dart:math';
import 'package:flutter/rendering.Dart';
import 'package:image/image.Dart' as IMG;

class ImageProcessor {
  static Future cropSquare(String srcFilePath, String destFilePath, bool flip) async {
    var bytes = await File(srcFilePath).readAsBytes();
    IMG.Image src = IMG.decodeImage(bytes);

    var cropSize = min(src.width, src.height);
    int offsetX = (src.width - min(src.width, src.height)) ~/ 2;
    int offsetY = (src.height - min(src.width, src.height)) ~/ 2;

    IMG.Image destImage =
      IMG.copyCrop(src, offsetX, offsetY, cropSize, cropSize);

    if (flip) {
        destImage = IMG.flipVertical(destImage);
    }

    var jpg = IMG.encodeJpg(destImage);
    await File(destFilePath).writeAsBytes(jpg);
  }
}

このコードにはimageパッケージが必要です。それをpubspec.yamlに追加します。

  dependencies:
      image: ^2.1.4
2
VeganHunter