Search code examples
camerawidgetflutterflutter-layout

How to square crop a Flutter camera preview


I'm trying to take square pictures in my app. I'm using the camera package and I'm trying to display a centre square-cropped version of the CameraPreview widget.

My goal is to show the central square of the preview (full width), with an even amount cropped from the top and bottom.

I was struggling to get this to work, so I created a minimal example using a fixed image. (Apologies for the dull picture of me in a chair):

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

This works fine - I get a full screen width image, centre cropped and pushed to the top of my app.

However, if I drop this code into my existing app and replace cameraImage with a CameraPreview, I get a lot of layout errors:

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.

Can anyone suggest why I'm getting errors with the preview and how to avoid them?


Solution

  • I solved this by giving a specific size to my CameraPreview instance, by wrapping it in a 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
              ),
            ),
          ),
        ),
      );
    

    To respond to Luke's comment, I then used this code to square crop the resulting image. (Because even though the preview is square, the image captured is still standard ratio).

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

    This uses https://github.com/btastic/flutter_native_image. It's been a while since I used this code - think it currently just works for portrait images, but should easily be extendable to handle landscape.