How to square crop a Flutter camera preview

I have a code snippet similar to the one used in the answer.

Same as the answer, it supports cases when aspect ratio of the camera is different from aspect ratio of the screen.

Though my version has some difference: it does not require MediaQuery to get the device size, so it will fit the width of any parent (not just full-screen-width)

          ....

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

To center-square-crop the image, see the snippet below.

It works equally fine with images in portrait and landscape orientation. It also allows to optionally mirror the image (it can be useful if you want to retain original mirrored look from selfie camera)

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

This code requires image package. Add it into pubspec.yaml:

  dependencies:
      image: ^2.1.4

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.


After some testing here is what I suggest for cropping the CameraPreview (not the image itself) to any rectangular shape:

 Widget buildCameraPreview(CameraController cameraController) {
    final double previewAspectRatio = 0.7;
    return AspectRatio(
      aspectRatio: 1 / previewAspectRatio,
      child: ClipRect(
        child: Transform.scale(
          scale: cameraController.value.aspectRatio / previewAspectRatio,
          child: Center(
            child: CameraPreview(cameraController),
          ),
        ),
      ),
    );
  }

The advantage are similar to @VeganHunter's solution but can be extended to any rectangular shape by playing with the previewAspectRatio. A value of 1 for previewAspectRatio will result in a square shape, a value of 0.5 will be a rectangle half the height of its width, and a value of cameraController.value.aspectRatio will be the full size preview.

Also, here is the explanation why this work at all:

One thing with Flutter is that making a child overflow its parent is usually impossible (because of the way the widgets size is computed during the layout phase). The use of Transform.scale is crucial here, because according to the doc:

This object applies its transformation just prior to painting, which means the transformation is not taken into account when calculating how much space this widget's child (and thus this widget) consumes.

This means the widget will be able to overflow it's container when scaled up, and we can clip (and thus hide) the overflowing parts with CLipRect, limiting the overall size of the preview to its parent size. It would not be possible to achieve the same effect using only Containers, as they would scale themselves based on the available space during the layout phase, and there would be no overflow so nothing to clip.

The scale (cameraController.value.aspectRatio * previewAspectRatio) is chosen to have the width of the preview match the width of its parent.

If this solution does not work for you try to replace all the cameraController.value.aspectRatio by its inverse (1 / cameraController.value.aspectRatio).

This was tested for portraitUp mode only and some tweaking may be needed for Landscape.