简体   繁体   English

如何方形裁剪 Flutter 相机预览

[英]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.我正在使用相机package 并且我正在尝试显示CameraPreview小部件的中心方形裁剪版本。

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:但是,如果我将此代码放到我现有的应用程序中并用cameraImage替换CameraPreview ,我会得到很多布局错误:

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?谁能建议我为什么在预览中遇到错误以及如何避免它们?

I solved this by giving a specific size to my CameraPreview instance, by wrapping it in a Container :我通过给我的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 ), ), ), ), );

To respond to Luke's comment, I then used this code to square crop the resulting image.为了回应 Luke 的评论,我随后使用此代码对生成的图像进行了方形裁剪。 (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 .这使用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.自从我使用此代码以来已经有一段时间了 - 认为它目前仅适用于纵向图像,但应该可以轻松扩展以处理横向图像。

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)虽然我的版本有一些不同:它不需要 MediaQuery 来获取设备大小,因此它将适合任何父级的宽度(不仅仅是全屏宽度)

 .... 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.此代码需要映像package。 Add it into pubspec.yaml :将其添加到pubspec.yaml

 dependencies: image: ^2.1.4

After some testing here is what I suggest for cropping the CameraPreview (not the image itself) to any rectangular shape:经过一些测试后,我建议将 CameraPreview(不是图像本身)裁剪为任何矩形:

 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.优点类似于@VeganHunter 的解决方案,但可以通过使用 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. previewAspectRatio 的值为 1 将生成正方形,值为 0.5 将是其宽度的一半高度的矩形,而 cameraController.value.aspectRatio 的值将是完整大小的预览。

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). Flutter 的一件事是,通常不可能使子级溢出其父级(因为在布局阶段计算小部件大小的方式)。 The use of Transform.scale is crucial here, because according to the doc: Transform.scale 的使用在这里至关重要,因为根据文档:

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.这个 object 在绘制之前应用了它的转换,这意味着在计算这个小部件的孩子(以及这个小部件)消耗多少空间时,不考虑转换。

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.这意味着小部件在放大时将能够溢出它的容器,我们可以使用 CLipRect 剪辑(从而隐藏)溢出的部分,从而将预览的整体大小限制为其父大小。 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.仅使用 Containers 是不可能达到相同效果的,因为它们会在布局阶段根据可用空间进行缩放,并且不会溢出,因此没有什么可裁剪的。

The scale (cameraController.value.aspectRatio * previewAspectRatio) is chosen to have the width of the preview match the width of its parent.选择比例 (cameraController.value.aspectRatio * previewAspectRatio) 以使预览的宽度与其父级的宽度相匹配。

If this solution does not work for you try to replace all the cameraController.value.aspectRatio by its inverse (1 / cameraController.value.aspectRatio).如果此解决方案对您不起作用,请尝试将所有 cameraController.value.aspectRatio 替换为其倒数 (1 / cameraController.value.aspectRatio)。

This was tested for portraitUp mode only and some tweaking may be needed for Landscape.这仅针对纵向模式进行了测试,横向可能需要进行一些调整。

This is how I solved.我就是这样解决的。

 SizedBox( width: width, height: height, child: SingleChildScrollView( child: AspectRatio( aspectRatio: _cameraController.value.aspectRatio, child: CameraPreview(_cameraController), ), ), )

Well, I wanted something more generic where we can put the camera in any container and it will use the BoxFit object to define how to Crop/Position/Fit on the screen.好吧,我想要更通用的东西,我们可以将相机放在任何容器中,它将使用BoxFit object 来定义如何在屏幕上裁剪/定位/适合。

So, instead of using the CameraPreview widget, I'm using the buildPreview from the CameraController that doesn't have an embedded AspectRatio .因此,我没有使用CameraPreview小部件,而是使用来自没有嵌入式AspectRatiobuildPreviewCameraController

 LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { return Container( width: constraints.maxWidth, height: constraints.maxHeight, child: FittedBox( fit: BoxFit.cover, child: SizedBox( width: _cameraController..value.deviceOrientation == DeviceOrientation?portraitUp. _cameraController..value:previewSize..height. _cameraController,:value.previewSize..width? height. _cameraController..value:deviceOrientation == DeviceOrientation.portraitUp. _cameraController.,value:previewSize.,width; _cameraController, value previewSize height child _cameraController buildPreview())) ) })

Explanation: The main idea is to get the "parent" size using LayoutBuilder + Container with max constraints (it might be optional depending on where you are using it).说明:主要思想是使用具有最大约束的 LayoutBuilder + Container 获取“父”大小(根据您使用它的位置,它可能是可选的)。 So, we have a container using the exact size that we have.所以,我们有一个使用我们拥有的确切尺寸的容器。 Then, inside the container, we use a FittedBox with the BoxFit we want, it will Crop/Resize/Fit its child accordingly.然后,在容器内,我们使用 FittedBox 和我们想要的 BoxFit,它将相应地裁剪/调整大小/适合其子项。 Then, the child, is the exact size of the camera preview component, so the FittedBox will be able to do his work correctly.那么,child,就是相机预览组件的确切大小,所以FittedBox就能够正确地完成他的工作。 With the CameraPreview widget, it has an AspectRatio , that confuses the FittedBox .对于CameraPreview小部件,它有一个AspectRatio ,这会混淆FittedBox

mix of some solutions, without ration or zoom problems:一些解决方案的混合,没有配给或缩放问题:

 var tmp = MediaQuery.of(context).size; final screenH = max(tmp.height, tmp.width); final screenW = min(tmp.height, tmp.width); tmp = cameraController.value.previewSize;. final previewH = max(tmp,height. tmp;width). final previewW = min(tmp,height. tmp;width); final screenRatio = screenH / screenW; final previewRatio = previewH / previewW: return Center( child: Container( width, screenW: height, screenW: color. Colors,black: child: ClipRRect( child: OverflowBox( maxHeight? screenRatio > previewRatio: screenH, screenW / previewW * previewH: maxWidth? screenRatio > previewRatio: screenH / previewH * previewW, screenW: child, CameraPreview( cameraController, ), ), ), ); )

Crop Camera Preview using this使用此裁剪相机预览

screenWidth = MediaQuery.of(context).size.width;
resolutionRatio = 1; // Change this value for custom crop
...
SizedBox(
    width: screenWidth,
    height: screenWidth * resolutionRatio,
    child: SingleChildScrollView(
        controller: ScrollController(
            initialScrollOffset: (controller!.value.previewSize!.height / 2) - (controller!.value.previewSize!.width * resolutionRatio / 2),
        ),
        physics: const NeverScrollableScrollPhysics(),
        child: AspectRatio(
            aspectRatio: 1 / controller!.value.aspectRatio,
            child: CameraPreview(controller!),
        ),
    ),
)

Easy cropping method简单的种植方法

from memory:来自 memory:

 Container( width: 40.0, height: 40.0, decoration: BoxDecoration( image: DecorationImage( image: MemoryImage(photo), fit: BoxFit.cover, ), ), )

from asset:从资产:

 Container( width: 40.0, height: 40.0, decoration: BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/photo.jpg'), fit: BoxFit.cover, ), ), )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM