You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Flutter官方camera插件时如何水平翻转原始相机图像

Flutter Web 相机预览方向/镜像错误解决方案

问题场景

开发Flutter Web应用时使用官方camera插件实现相机预览、拍照功能,出现预览画面方向/镜像不符合预期,仅能在拍摄完成后对保存的图片做翻转处理,无法在预览阶段直接显示正确效果。
对应问题复现代码如下:

import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';

import 'package:camera/camera.dart';
import 'package:image/image.dart' as img;

late List<CameraDescription> _cameras;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  _cameras = await availableCameras();
  runApp(const CameraApp());
}

class CameraApp extends StatefulWidget {
  const CameraApp({Key? key}) : super(key: key);

  @override
  State<CameraApp> createState() => _CameraAppState();
}

class _CameraAppState extends State<CameraApp> {
  late CameraController controller;
  Image? image;

  @override
  void initState() {
    super.initState();
    // define and initialize the camera controller once
    controller = CameraController(
      _cameras[0],
      ResolutionPreset.max,
      imageFormatGroup: ImageFormatGroup.jpeg,
    );
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    }).catchError((Object e) {
      if (e is CameraException) {
        switch (e.code) {
          case 'CameraAccessDenied':
            print('User denied camera access.');
            break;
          default:
            print('Handle other errors.');
            break;
        }
      }
    });
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return MaterialApp(
      home: Scaffold(
        body: ListView(
          children: [
            CameraPreview(controller), // shows preview
            if (image != null) image!,
            ElevatedButton(
              onPressed: () async {
                // take picture
                try {
                  final XFile file = await controller.takePicture();
                  // load the picture as bytes
                  final blob = await file.readAsBytes();
                  // try to flip it horizontally
                  final img.Image? original = img.decodeImage(blob);
                  if (original != null) {
                    final img.Image oriented = img.flipHorizontal(original);
                    final orientedBlob =
                        Uint8List.fromList(img.encodeJpg(oriented));
                    setState(() {
                      // set the image to show the output
                      image = Image.memory(orientedBlob);
                    });
                  }
                } catch (e) {
                  print(e);
                }
              },
              child: const Text("Digitalisieren"),
            ),
          ],
        ),
      ),
    );
  }
}

问题效果展示:
展示预览画面与翻转后存储的图像效果


核心结论

官方camera插件没有内置的预览翻转、镜像相关配置选项,无需修改插件源码,使用Flutter框架自带组件即可在预览阶段完成水平翻转,实现正确的显示效果。


实现步骤

1. 预览层直接添加水平镜像变换

使用Transform组件直接包裹CameraPreview,通过矩阵变换在渲染层完成水平翻转,该变换是GPU层面实现,无性能损耗,不会影响预览帧率。
针对前置摄像头(绝大多数镜像问题都出现在前置摄像头场景),将原有代码中CameraPreview(controller)部分替换为如下代码:

Transform(
  alignment: Alignment.center,
  // x轴缩放-1即实现水平镜像效果,y轴保持1.0不变
  transform: Matrix4.identity()..scale(-1.0, 1.0),
  child: CameraPreview(controller),
)

如果你的应用支持前后摄像头切换,可以加判断逻辑,仅对前置摄像头启用镜像:

// 初始化摄像头时记录当前是否为前置
final bool isFrontCamera = controller.description.lensDirection == CameraLensDirection.front;

// 构建预览组件
Widget previewWidget = CameraPreview(controller);
if (isFrontCamera) {
  previewWidget = Transform(
    alignment: Alignment.center,
    transform: Matrix4.identity()..scale(-1.0, 1.0),
    child: previewWidget,
  );
}

2. 保证拍摄结果和预览效果一致

预览做了镜像处理后,原有拍摄完成后对照片做水平翻转的逻辑需要保留,避免出现预览效果和最终保存的照片方向不一致的问题。


常见踩坑说明

  • 不要使用RotatedBox或者旋转类变换处理镜像问题,这类组件是做角度旋转,无法实现水平镜像效果
  • Web端不需要手动修改底层DOM的CSS样式实现翻转,使用Flutter自带的Transform组件可以保证多端逻辑一致,移动端、Web端同一套代码即可生效
  • 切换前后摄像头时,记得同步更新是否启用镜像的判断标记,避免后置摄像头也被翻转出现方向错误
  • 该方案不影响相机拍照、录像的原始数据输出,仅修改渲染层的显示效果,不会增加额外的内存占用

内容的提问来源于stack exchange,提问作者Anneress

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 16:12:18