web-dev-qa-db-fra.com

Comment recadrer un aperçu de la caméra Flutter

J'essaie de prendre des photos carrées dans mon application. J'utilise le package camera et j'essaie d'afficher une version carrée centrale du widget CameraPreview.

Mon objectif est de montrer le carré central de l'aperçu (pleine largeur), avec une quantité uniforme rognée de haut en bas.

J'avais du mal à faire fonctionner cela, alors j'ai créé un exemple minimal en utilisant une image fixe. (Toutes mes excuses pour l'image terne de moi sur une chaise):

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,
          ),
        ),
      ),
    );
  }
}

Cela fonctionne très bien - j'obtiens une image en plein écran, recadrée au centre et poussée vers le haut de mon application.

Cependant, si je dépose ce code dans mon application existante et que je remplace cameraImage par un CameraPreview, j'obtiens beaucoup d'erreurs de mise en page:

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.

Quelqu'un peut-il suggérer pourquoi j'obtiens des erreurs avec l'aperçu et comment les éviter?

9
Duncan Jones

J'ai résolu cela en donnant une taille spécifique à mon instance CameraPreview, en l'enveloppant dans un 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
          ),
        ),
      ),
    ),
  );

Pour répondre au commentaire de Luke, j'ai ensuite utilisé ce code pour recadrer l'image résultante. (Parce que même si l'aperçu est carré, l'image capturée est toujours au format standard).

  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;
  }

Cela utilise https://github.com/btastic/flutter_native_image . Cela fait un moment que je n'ai pas utilisé ce code - pense qu'il ne fonctionne actuellement que pour les images de portrait, mais devrait facilement être extensible pour gérer le paysage.

20
Duncan Jones

J'ai un extrait de code similaire à celui utilisé dans la réponse.

Identique à la réponse, il prend en charge les cas où le rapport d'aspect de la caméra est différent du rapport d'aspect de l'écran.

Bien que ma version ait une certaine différence: elle ne nécessite pas l'utilisation de la requête multimédia pour obtenir la taille de l'appareil, elle s'adaptera donc à la largeur de n'importe quel parent (pas seulement en plein écran)

          ....

          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),
                  ),
                ),
              ),
            ),
          );

Pour recadrer au centre l'image carrée, consultez l'extrait ci-dessous.

Il fonctionne aussi bien avec des images en orientation portrait et paysage. Il permet également de refléter l'image en option (cela peut être utile si vous souhaitez conserver l'aspect miroir d'origine de la caméra selfie)

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);
  }
}

Ce code nécessite image package. Ajoutez-le dans pubspec.yaml:

  dependencies:
      image: ^2.1.4
2
VeganHunter