Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[camera] zoom feature #1304

Closed
wants to merge 7 commits into from
Closed

[camera] zoom feature #1304

wants to merge 7 commits into from

Conversation

ivk1800
Copy link
Contributor

@ivk1800 ivk1800 commented Mar 2, 2019

fixed flutter/flutter#28694
I added zoom support

demonstration:
https://imgur.com/a/8u8m5HQ

@tungvn
Copy link

tungvn commented Mar 7, 2019

Can I use this for pinch to zoom?

@ivk1800
Copy link
Contributor Author

ivk1800 commented Mar 8, 2019

@tungvn you must implement own widget for this

@vodemn
Copy link

vodemn commented Jul 26, 2019

How to implement Zoom feature in code?

@topnax
Copy link

topnax commented Aug 25, 2019

Will this ever be merged into master?

@coredeveloper
Copy link

How many years we have to wait for this simple feature?;) code is already there.

Copy link
Contributor

@bparrishMines bparrishMines left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ivk1800

Thanks for the contribution! I like the approach and added a comment.

I'm following the initial PR review policy, this PR isn't trivial to review (e.g. We would need a test for this and the PR has become a little stale from the latest changes to this plugin) so I'm labeling it with "backlog" and we will prioritize according to the issue's priority.

Relevant issue: flutter/flutter#28694

packages/camera/lib/camera.dart Show resolved Hide resolved
@winsonet
Copy link

winsonet commented Nov 3, 2019

so...may I know whether has been fix this issue? I really need that, or it's fixed but just didn't release?

thanks!

# Conflicts:
#	packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java
#	packages/camera/example/lib/main.dart
#	packages/camera/ios/Classes/CameraPlugin.m
@winsonet
Copy link

winsonet commented Nov 5, 2019

when can we use this in the release version?

thanks!

@baughmann
Copy link

when can we use this in the release version?

thanks!

There appears to be a bit of a backlog. I'm looking forward to this feature myself as well.

@piotrek1543
Copy link

Looks good for me as well

@b3dl4m
Copy link

b3dl4m commented Jan 27, 2020

Any update on this? Are we still waiting on someone fixing a one-line formatting error?

@morbyosef
Copy link

Can you please review it asap?
Highly demanded feature.

@mklim mklim removed their request for review February 14, 2020 22:16
@magicleader
Copy link

when will it be possible to have this feature?

@adrianv425
Copy link

Not interested in when this will be made official. I'm working on making this work regardless, and I'm getting crashes with zooming in/out on iOS. It crashes on the 3rd zoom in and the first zoom out. For now, I have a workaround that does not allow more than 2x zoom but I would like zoom out to work too. It seems there should be error checking when performing the zooming operations. Anybody have anyway of fixing this?

@agreensh
Copy link

agreensh commented Feb 25, 2020 via email

@adrianv425
Copy link

Where is the code? I could have a look and debug why it’s crashing.

On 25 Feb 2020, 02:54 +0000, Adrian Valencia @.***>, wrote: Not interested in when this will be made official. I'm working on making this work regardless, and I'm getting crashes with zooming in/out on iOS. It crashes on the 3rd zoom in and the first zoom out. For now, I have a workaround that does not allow more than 2x zoom but I would like zoom out to work too. It seems there should be error checking when performing the zooming operations. Anybody have anyway of fixing this? — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

Thank you for the reply. I have emphasized where the app crashed with comments in the code. Weirdly enough, the try-catch block does not catch the error. This is my output from terminal at the time of crashing and my cameraView.dart file.

flutter: current offset = -114.0-116.5
flutter: current offset = -114.0-112.0
flutter: current offset = -114.0-107.5
flutter: current offset = -114.0-103.5
flutter: current offset = -114.0-98.0
flutter: zooming out....
Lost connection to device.
import 'dart:io';
import 'package:provider/provider.dart';
import 'model/userRepository.dart';
import 'package:camera/camera.dart';
import 'dart:async';
import 'package:path_provider/path_provider.dart';

class CameraView extends StatefulWidget {
  CameraView({Key key, this.cameras}) : super(key: key);

  final List<CameraDescription> cameras;

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

class _CameraViewState extends State<CameraView> with WidgetsBindingObserver {
  CameraController controller;
  String imagePath;
  String videoPath;
  bool enableAudio = true;
  UserRepository user;
  String venueID;
  bool frontCamera = false;
  Flash flashStatus = Flash.off;
  int zoomLevel = 0;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    controller = CameraController(widget.cameras[0], ResolutionPreset.high,);
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      controller.prepareForVideoRecording();
      setState(() {});
    });
  }

  @override
  void dispose() {
    controller?.dispose();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.inactive) {
      controller?.dispose();
    } else if (state == AppLifecycleState.resumed) {
      if (controller != null) {
        onNewCameraSelected(controller.description);
      }
    }
  }
  double _currentScale = 1.0;
  double _currentOffset = 0.0;

  @override

  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      backgroundColor: Colors.black,
      body: GestureDetector(
        onScaleUpdate: (ScaleUpdateDetails one){
          print("Scale: "+ one.scale.toString());
          if(one.scale <= _currentScale - 0.1 && zoomLevel > 0){
            try{

            //****CRASHES HERE ON FIRST PASS*****

            controller.zoomOut();
            zoomLevel -= 1;
            _currentScale = one.scale;
            }catch(err){
              print(err.toString());
            }
          }
          else if (one.scale > _currentScale + 0.1 && zoomLevel < 2){
            try{

            //****CRASHES HERE ON THIRD PASS*****

            controller.zoomIn();
            zoomLevel += 1;
            _currentScale = one.scale;
            }catch(err){
              print(err.toString());
            }
          }
        },
        onScaleEnd: (one){
          _currentScale = 1.0;
        },
        child: Stack(
          children: <Widget>[
            Center(child:Container(
              child: Padding(
                padding: const EdgeInsets.all(1.0),
                child: AspectRatio(
                  aspectRatio: 9.0/16.0,
                  child: Center(
                    child: _cameraPreviewWidget(),
                  ),
                  ),
                ),
              ),
            ),
            Positioned.fill(child: _captureControlRowWidget()),
            controller != null && controller.value.isRecordingVideo
                ? Container()
                : Positioned(
                    bottom: 40,
                    right: 10,
                    child: Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: <Widget>[
                          _cameraTogglesRowWidget(),
                        ],
                      ),
                    ),
                  ),
          ],
        ),
      ),
    );
  }

  Widget _cameraPreviewWidget() {
    if (controller == null || !controller.value.isInitialized) {
      return const Text(
        'Tap a camera',
        style: TextStyle(
          color: Colors.white,
          fontSize: 24.0,
          fontWeight: FontWeight.w900,
        ),
      );
    } else {
      return CameraPreview(controller);
    }
  }

  /// Display the control bar with buttons to take pictures and record videos.
  Widget _captureControlRowWidget() {
    return Align(
      alignment: Alignment.bottomCenter,
      child: Container(
        margin: EdgeInsets.only(bottom: 50),
        height: 100,
        child: GestureDetector(
          child: controller.value.isInitialized &&
                  controller.value.isRecordingVideo
              ? FlareActor(
                  'assets/recordButton.flr',
                  animation: 'record',
                )
              : FlareActor('assets/recordButton.flr'),
          onTap: controller != null &&
                  controller.value.isInitialized &&
                  !controller.value.isRecordingVideo
              ? onTakePictureButtonPressed
              : null,
          onLongPress: controller != null &&
                  controller.value.isInitialized &&
                  !controller.value.isRecordingVideo
              ? onVideoRecordButtonPressed
              : null,
          onLongPressUp: controller.value.isInitialized &&
                  controller.value.isRecordingVideo
              ? onStopButtonPressed
              : null,
          onLongPressMoveUpdate: (update){
            print("current offset = " + _currentOffset.toString() + update.offsetFromOrigin.dy.toString());
            if(_currentOffset + 15 < update.offsetFromOrigin.dy && zoomLevel > 0){
              try{
                print("zooming out....");

              //****CRASHES HERE ON FIRST PASS*****

              controller.zoomOut();
              zoomLevel -= 1;
              _currentOffset = update.offsetFromOrigin.dy; 
              }catch(err){
              print(err.toString());
            }
            }
            else if(_currentOffset - 50 > update.offsetFromOrigin.dy && zoomLevel < 2){
              try{
                print("zooming in....");

             //****CRASHES HERE ON THIRD PASS*****

              controller.zoomIn();
              zoomLevel += 1;
              _currentOffset = update.offsetFromOrigin.dy;
              }catch(err){
              print(err.toString());
            }
            }
          },
        ),
      ),
    );
  }

  /// Display a row of toggle to select the camera (or a message if no camera is available).
  Widget _cameraTogglesRowWidget() {
    final List<Widget> toggles = <Widget>[];

    if (widget.cameras.isEmpty) {
      return const Text('No camera found');
    } else {
      CameraDescription cameraDescription = widget.cameras[0];
      toggles.add(
        SizedBox(
          width: 90.0,
          child: MaterialButton(
            child: Icon(getCameraLensIcon(cameraDescription.lensDirection), color: Colors.white,),
            onPressed: () => frontCamera
                ? onNewCameraSelected(widget.cameras[0])
                : onNewCameraSelected(widget.cameras[1]),
          ),
        ),
      );
    }

    return Row(children: toggles);
  }

  String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();

  void showInSnackBar(String message) {
    // _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message)));
  }

  void onNewCameraSelected(CameraDescription cameraDescription) async {
    if (cameraDescription == widget.cameras[0]) {
      frontCamera = false;
    } else {
      frontCamera = true;
    }
    if (controller != null) {
      await controller.dispose();
    }
    controller = CameraController(
      cameraDescription,
      ResolutionPreset.high,
    );

    // If the controller is updated then update the UI.
    controller.addListener(() {
      if (mounted) setState(() {});
      if (controller.value.hasError) {
        showInSnackBar('Camera error ${controller.value.errorDescription}');
      }
    });

    try {
      await controller.initialize();
    } on CameraException catch (e) {
      _showCameraException(e);
    }

    if (mounted) {
      setState(() {});
    }
  }

  void onTakePictureButtonPressed() {
    if(flashStatus == Flash.on){
      Lantern.turnOn();
    }
    takePicture().then((String filePath) async {
      if (mounted) {
        setState(() {
          imagePath = filePath;
        });
        if (filePath != null) showInSnackBar('Picture saved to $filePath');
        if(flashStatus != Flash.off){
          Lantern.turnOff();
        }
      }
       await Navigator.of(context).push(MaterialPageRoute(builder: (context) =>  PhotoPreviewPlayer(path: imagePath, venueID: venueID,)));
    });
  }

  void onVideoRecordButtonPressed() {
    if(flashStatus == Flash.on){
      Lantern.turnOn();
    }
    startVideoRecording().then((String filePath) {
      if (mounted) setState(() {});
      if (filePath != null) showInSnackBar('Saving video to $filePath');
    });
  }

  void onStopButtonPressed() {
    stopVideoRecording().then((_) async {
      if(flashStatus != Flash.off) {
        Lantern.turnOff();
      }
      showInSnackBar('Video recorded to: $videoPath');

      await Navigator.of(context).push(MaterialPageRoute(builder: (context) =>  VideoPreviewPlayer(path: videoPath, venueID: venueID,)));
    });
  }

  Future<String> startVideoRecording() async {
    if (!controller.value.isInitialized) {
      showInSnackBar('Error: select a camera first.');
      return null;
    }

    final Directory extDir = await getTemporaryDirectory();
    final String dirPath = '${extDir.path}/Movies/flutter_test';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${timestamp()}.mp4';

    if (controller.value.isRecordingVideo) {
      // A recording is already started, do nothing.
      return null;
    }

    try {
      videoPath = filePath;
      Timer(Duration(seconds: 6), () {
        if (controller.value.isRecordingVideo) {
          onStopButtonPressed();
        }
      });

      await controller.startVideoRecording(filePath);
    } on CameraException catch (e) {
      _showCameraException(e);
      return null;
    }
    return filePath;
  }

  Future<void> stopVideoRecording() async {
    if (!controller.value.isRecordingVideo) {
      return null;
    }

    try {
      await controller.stopVideoRecording();
    } on CameraException catch (e) {
      _showCameraException(e);
      return null;
    }
  }

  Future<String> takePicture() async {
    if (!controller.value.isInitialized) {
      showInSnackBar('Error: select a camera first.');
      return null;
    }
    final Directory extDir = await getTemporaryDirectory();
    final String dirPath = '${extDir.path}/Pictures/flutter_test';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${timestamp()}.jpg';

    if (controller.value.isTakingPicture) {
      // A capture is already pending, do nothing.
      return null;
    }

    try {
      await controller.takePicture(filePath);
    } on CameraException catch (e) {
      _showCameraException(e);
      return null;
    }
    return filePath;
  }

  void _showCameraException(CameraException e) {
    logError(e.code, e.description);
    showInSnackBar('Error: ${e.code}\n${e.description}');
  }
}

@agreensh
Copy link

agreensh commented Feb 26, 2020

Looking at the iOS code, it does not handle conversion from 'NSUInteger' to 'int' correctly (in the zoom function). This can (and does) cause erroneous (massive) zooms. The 'zoom' level also needs to be checked against the 'videoMaxZoomFactor' before calling 'setVideoZoomFactor' (if the zoom factor is larger than the max allowed, it will - and does - crash).

Also, the current implementation only allows zoom levels in integers 0 - 'setVideoZoomFactor' (on iOS iPhone X, that is only 0 - 24), which with pinch and zoom will look very "blocky". It would be better to pass a larger integer range 0 - 256 (or 0 - 512, say) which could then be mapped to the double value 0 - 'setVideoZoomFactor' on iOS (and similar on Android) to give a much smoother zoom.

@adrianv425
Copy link

Looking at the iOS code, it does not handle conversion from 'NSUInteger' to 'int' correctly (in the zoom function). This can (and does) cause erroneous (massive) zooms. The 'zoom' level also needs to be checked against the 'videoMaxZoomFactor' before calling 'setVideoZoomFactor' (if the zoom factor is larger than the max allowed, it will - and does - crash).

Also, the current implementation only allows zoom levels in integers 0 - 'setVideoZoomFactor' (on iOS iPhone X, that is only 0 - 24), which with pinch and zoom will look very "blocky". It would be better to pass a larger integer range 0 - 256 (or 0 - 512, say) which could then be mapped to the double value 0 - 'setVideoZoomFactor' on iOS (and similar on Android) to give a much smoother zoom.

Thank you and great catch on the NSUInteger to int conversion issue. I agree with error checking on the videoMaxZoomFactor. That must be where the crash is coming from.

As for the blocky zoom, yes this would be great to have a range of 0-256. As for now, I will work on fixing the crashes.

@adrianv425
Copy link

adrianv425 commented Mar 6, 2020

So a few things.

First, yes the crash was happening because setVideoZoomFactor was being set above the videoMaxZoomFacor and lower than videoMinZoomFactor. There are some boundary checks that must be implemented.

Secondly, we must not use setVideoZoomFactor as there is another function called rampVideoZoomFactor that takes care of smooth zooming.

Furthermore, I have changed the parameter of zoom(int) to zoom(double) to help with the rampVideoZoomFactor function.

Lastly, I realized there must be two types of zooms. One that increments from the current zoom level (pinch to zoom), and one that zooms to a specific zoom level (drag to zoom). So that leads to the creation of another function called zoomTo(double zoomLevel).

Pull request coming soon. Testing on actual device right now. Code has been uploaded to my repository if you're interested.

@gummie4444
Copy link

https://github.com/gummie4444/flutter-better-camera

I took the most of the major pull requests and added support for flash/zoom/exposure and more in this fork. Meanwhile this is not merged in you can check it out.

@adrianv425
Copy link

@gummie4444 awesome! I was looking at the zoom ios code. Does setting videoZoomFactor to zoom have a smooth zoom? If not, there's a helper function called rampVideoZoomFactor that will zoom smoothly. Great job though on bringing all the features together.

@milomarsland
Copy link

fixed flutter/flutter#28694
I added zoom support

demonstration:
https://imgur.com/a/8u8m5HQ

I am new to flutter - so this may be an obvious question - but how do I add these changes to enable the zoom feature.

@mateusfccp
Copy link

fixed flutter/flutter#28694
I added zoom support
demonstration:
https://imgur.com/a/8u8m5HQ

I am new to flutter - so this may be an obvious question - but how do I add these changes to enable the zoom feature.

You have to make a fork and merge the branches yourself.

@adrianv425
Copy link

adrianv425 commented Apr 21, 2020

Git dependency on a package in a folder
The pub tool assumes the package is located in the root of the Git repository. If that is not the case, specify the location with the path argument. For example:

dependencies:
  package1:
    git:
      url: git://github.com/flutter/packages.git
      path: packages/package1

Finally, use the ref argument to pin the dependency to a specific git commit, branch, or tag.

https://flutter.dev/docs/development/packages-and-plugins/using-packages

@jonathanmiller2
Copy link

Bump. Need this for 2 different apps.

@marcobraghim
Copy link

Highly needed feature, any new about it?

@GioPan04
Copy link

C'mon it's more than a year that we are waiting for this feature

@josepaulodelacruz
Copy link

Any updates for camera zoom function? if none. any plugins recommendation?

@micaelsn
Copy link

Any updates for camera zoom function? if none. any plugins recommendation?

Any updates for camera zoom function? if none. any plugins recommendation?

try this https://github.com/ivk1800/plugins/tree/zoom_camera
worked fine to me

@mvanbeusekom
Copy link
Contributor

Closing this PR in favor of #3315

@kaneki2610
Copy link

Any updates for camera zoom function? if none. any plugins recommendation?

Any updates for camera zoom function? if none. any plugins recommendation?

try this https://github.com/ivk1800/plugins/tree/zoom_camera
worked fine to me
camera version?
camera: ^0.5.8+7 ?

@mvanbeusekom
Copy link
Contributor

@kaneki2610 zoom is supported since 0.6.2 of the camera plugin

@marcobraghim
Copy link

marcobraghim commented Jan 11, 2021

Flash and Zoom are now supported by official camera plugin. I recommend you to prefer the use of officials packages
https://pub.dev/packages/camera/changelog#062

@kaneki2610
Copy link

@kaneki2610 zoom is supported since 0.6.2 of the camera plugin

Hi @mvanbeusekom . How to immplement zoom in project ? getMaxZoomLevel, setZoomLevel, getMinZoomLevel

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support camera zoom feature