Vueer
Vueer

Reputation: 1512

AspectRatio problem with Flutter camera and image layer

I have an AspectRatio problem with my Flutter camera (Plugin "camera_camera) and an image that I put on top of it with transparency as a layer.

I send you a screenshot of the problem. In the screenshot you can see the open camera and above it the picture I took right in front of it. Unfortunately you can see at different places that it does not match.

enter image description here

How do I get the camera to show exactly the same proportions as I photographed it from exactly the same position before?

If this helps: I recorded also a video with the issue: https://danielederosa.de/downloads/flutter_issue.mp4

My Code

@override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    if (!controller.value.isInitialized) {
      return Container(
          color: theme.colorScheme.onPrimary,
          child: Center(child: CircularProgressIndicator()));
    }

    return Scaffold(
      appBar: CupertinoNavigationBar(
        backgroundColor: theme.colorScheme.primary,
        border: Border.symmetric(
            vertical: BorderSide.none, horizontal: BorderSide.none),
        automaticallyImplyLeading: false,
        leading: IconButton(
          icon: Icon(
            Icons.chevron_left,
            size: 30,
            color: theme.colorScheme.onPrimary,
          ),
          onPressed: () => Navigator.pop(context),
        ),
        middle: Text("Memories",
            style: TextStyle(
                color: theme.colorScheme.onPrimary,
                fontSize: theme.textTheme.headline3.fontSize)),
      ),
      body: Container(
        child: Column(
          children: [
            Expanded(
              child: Camera(
                mode: CameraMode.normal,
                imageMask: lastPicture != null
                    ? new Positioned.fill(
                        child: new Opacity(
                          opacity: 0.3,
                          child: RotatedBox(
                            quarterTurns: 1,
                            child: new Image.file(
                              File(lastPicture),
                              fit: BoxFit.cover,
                            ),
                          ),
                        ),
                      )
                    : Container(),
                onFile: (File file) {
                  _workWithImage(file);
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

I also tried to wrap the Camera widget into an AspectRatio widget with aspectRatio: 3/4 because my saved image are saved in this aspectRatio. But without success.

Do you have any idea to solve this issue?

Upvotes: 2

Views: 1210

Answers (1)

Vueer
Vueer

Reputation: 1512

I found a solution and got it to work.

Example code

@override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    var deviceSize = MediaQuery.of(context).size;
    var sizeWidth = MediaQuery.of(context).size.width;
    final deviceRatio = deviceSize.width / deviceSize.height;
    var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;

    return Scaffold(
      backgroundColor: theme.colorScheme.primary,
      appBar: CupertinoNavigationBar(
        backgroundColor: theme.colorScheme.primary,
        border: Border.symmetric(
            vertical: BorderSide.none, horizontal: BorderSide.none),
        automaticallyImplyLeading: false,
        leading: IconButton(
          icon: Icon(
            Icons.chevron_left,
            size: 30,
            color: theme.colorScheme.onPrimary,
          ),
          onPressed: () => Navigator.pop(context),
        ),
        middle: Text(APP_NAME,
            style: TextStyle(
                color: theme.colorScheme.onPrimary,
                fontSize: theme.textTheme.headline3.fontSize)),
      ),
      body: NativeDeviceOrientationReader(
        useSensor: true,
        builder: (context) {
          NativeDeviceOrientation orientation =
              NativeDeviceOrientationReader.orientation(context);
          return Stack(children: [
            FutureBuilder<void>(
              future: _initializeControllerFuture,
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done) {
                  // If the Future is complete, display the preview.
                  return MeasureSize(
                    onChange: (size) {
                      setState(() {
                        cameraSize = size;
                      });
                    },
                    child: Transform.scale(
                      scale: cameraController.value.aspectRatio / deviceRatio,
                      child: Center(
                        child: AspectRatio(
                          aspectRatio: cameraController.value.aspectRatio,
                          child: ClipRect(
                            child: OverflowBox(
                              alignment: Alignment.center,
                              child: FittedBox(
                                fit: BoxFit.fitWidth,
                                child: Container(
                                  width: sizeWidth,
                                  height: sizeWidth /
                                      cameraController.value.aspectRatio,
                                  child: CameraPreview(
                                      cameraController), // this is my CameraPreview
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  );
                } else {
                  // Otherwise, display a loading indicator.
                  return Center(child: CircularProgressIndicator());
                }
              },
            ),
            helpMode == true
                ? Transform.scale(
                    scale: cameraController.value.aspectRatio / deviceRatio,
                    child: Center(
                      child: Opacity(
                        opacity: .3,
                        child: orientation ==
                                    NativeDeviceOrientation.landscapeLeft ||
                                orientation ==
                                    NativeDeviceOrientation.landscapeRight
                            ? RotatedBox(
                                quarterTurns: orientation ==
                                        NativeDeviceOrientation.landscapeLeft
                                    ? 1
                                    : 3,
                                child: Image.file(
                                  File(lastPicture),
                                  height: cameraSize.width,
                                  fit: BoxFit.contain,
                                ))
                            : Image.file(
                                File(lastPicture),
                                width: cameraSize.width,
                                height: cameraSize.height,
                                fit: BoxFit.contain,
                              ),
                      ),
                    ),
                  )
                : Container(),
          ]);
        },
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      floatingActionButton: Container(
        transform: Matrix4.translationValues(0.0, -8.0, 0.0),
        child: FloatingActionButton(
          backgroundColor: theme.colorScheme.primary,

          child: Icon(
            Icons.camera_alt,
            color: theme.colorScheme.onPrimary,
          ),
          // Provide an onPressed callback.
          onPressed: () async {
            // Take the Picture in a try / catch block. If anything goes wrong,
            // catch the error.
            try {
              // Ensure that the camera is initialized.
              //await _initializeControllerFuture;

              // Construct the path where the image should be saved using the path
              // package.
              final path = join(
                // Store the picture in the temp directory.
                // Find the temp directory using the `path_provider` plugin.
                (await getTemporaryDirectory()).path,
                '${DateTime.now()}.png',
              );

              // Attempt to take a picture and log where it's been saved.
              await cameraController.takePicture(path);
              _workWithImage(File(path));
            } catch (e) {
              // If an error occurs, log the error to the console.
              print(e);
            }
          },
        ),
      ),
    );
  }
}

typedef void OnWidgetSizeChange(Size size);

class MeasureSize extends StatefulWidget {
  final Widget child;
  final OnWidgetSizeChange onChange;

  const MeasureSize({
    Key key,
    @required this.onChange,
    @required this.child,
  }) : super(key: key);

  @override
  _MeasureSizeState createState() => _MeasureSizeState();
}

class _MeasureSizeState extends State<MeasureSize> {
  @override
  Widget build(BuildContext context) {
    SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
    return Container(
      key: widgetKey,
      child: widget.child,
    );
  }

  var widgetKey = GlobalKey();
  var oldSize;

  void postFrameCallback(_) {
    var context = widgetKey.currentContext;
    if (context == null) return;

    var newSize = context.size;
    if (oldSize == newSize) return;

    oldSize = newSize;
    widget.onChange(newSize);
  }
}

I created the MeasureSize class. With this class I get the dimensions of a child widget. In this case I need the width and height from the camera. (Transform.scale) After I got this I had only to set this dimensions for the image overlay:

Image.file(
  File(lastPicture),
  width: cameraSize.width,
  height: cameraSize.height,
  fit: BoxFit.contain,
),

Now the image overlay fits to this what the camera displays.

Upvotes: 1

Related Questions