Apparence-io / CamerAwesome

📸 Embedding a camera experience within your own app shouldn't be that hard. A flutter plugin to integrate awesome Android / iOS camera experience.
https://ApparenceKit.dev
MIT License
912 stars 201 forks source link

Move focus point #272

Open M123-dev opened 1 year ago

M123-dev commented 1 year ago

Proposal

I couldn't find anything about this in the docs, but I am wondering if it is possible to move the focus point to a different position, for my use case we would need it to be on the top 2/3 of the screen as we have full screen barcode scanning and the lower part is needed to show the results

Screenshot ![Screenshot_20230316_175901](https://user-images.githubusercontent.com/39344769/225695786-a7eec67f-120e-4e73-aa9d-2892ec1054a4.jpg)
apalala-dev commented 1 year ago

In the tap to focus feature, we use this:

OnPreviewTap(
  onTap: (position, flutterPreviewSize,
      pixelPreviewSize) {
    snapshot.requireData.when(
      onPhotoMode: (photoState) =>
          photoState.focusOnPoint(
        flutterPosition: position,
        pixelPreviewSize: pixelPreviewSize,
        flutterPreviewSize: flutterPreviewSize,
      ),
      onVideoMode: (videoState) =>
          videoState.focusOnPoint(
        flutterPosition: position,
        pixelPreviewSize: pixelPreviewSize,
        flutterPreviewSize: flutterPreviewSize,
      ),
      onVideoRecordingMode: (videoRecState) =>
          videoRecState.focusOnPoint(
        flutterPosition: position,
        pixelPreviewSize: pixelPreviewSize,
        flutterPreviewSize: flutterPreviewSize,
      ),
    );
  },
)

On the above screen snapshot.requireData is your CameraState and flutterPosition is an Offset on the screen where you want to focus. It could be the center of your rectangle in your case. These two should be easy to retrieve.

The other two parameters are a bit more complicated:

Method 1: manual flutterPreviewSize calculation

On our preview widget (which includes tap to focus), the flutterPreviewSize depends on several parameters (previewFit, previewPadding, size available).

Based on your screenshot, I'd say that you are using a PreviewFit.cover to fill the whole screen. Our current calcul for the flutterPreviewSize in this case is the following:

final previewRatio = _previewSize!.width / _previewSize!.height;
maxSize = Size(
  previewRatio > 1
      ? constrainedSize.height / previewRatio
      : constrainedSize.height * previewRatio,
  constrainedSize.height,
);

However, there's a PR (#256) to fix it which does the following:

final previewRatio = _previewSize!.width / _previewSize!.height;
if (previewRatio <= 1) {
  maxSize = Size(
    constraints.maxWidth,
    constraints.maxWidth / previewRatio,
  );
} else {
  maxSize = Size(
    constraints.maxHeight * previewRatio,
    constraints.maxHeight,
  );
}

In both of these codes, previewSize is pixelPreviewSize obtained from state.previewSize() and maxSize is the flutterPreviewSize.

Method 2: using the builder to retrieve the flutterPreviewSize

If you are using CameraAwesomeBuilder.awesome(), there is a previewDecoratorBuilder which includes the calculated flutterPreviewSize:

CameraAwesomeBuilder.awesome(
   previewDecoratorBuilder: (state, previewSize, previewRect) {
           if(previewSize != _previewSize){
              focus(previewSize);
           }
            return YourWidgetDecoration();
  },
)

If you are using CameraAwesomeBuilder.custom() instead, there is a similar property in the builder:

CameraAwesomeBuilder.custom(
   builder: (state, previewSize, previewRect) {
           if(previewSize != _previewSize){
              focus(state, previewSize);
           }
            return YourWidgetDecoration();
  },
)

You could save the current previewSize in a variable _previewSize and launch the focus when you have all the needed parameters:

focus(CameraState state, PreviewSize flutterPreviewSize){
  final position = ... // Center of your scan area
  // pixelPreviewSize could be retrieved once in initState()

  state.when(
      onPhotoMode: (photoState) =>
          photoState.focusOnPoint(
        flutterPosition: position,
        pixelPreviewSize: pixelPreviewSize,
        flutterPreviewSize: flutterPreviewSize,
      ),
      onVideoMode: (videoState) =>
          videoState.focusOnPoint(
        flutterPosition: position,
        pixelPreviewSize: pixelPreviewSize,
        flutterPreviewSize: flutterPreviewSize,
      ),
      onVideoRecordingMode: (videoRecState) =>
          videoRecState.focusOnPoint(
        flutterPosition: position,
        pixelPreviewSize: pixelPreviewSize,
        flutterPreviewSize: flutterPreviewSize,
      ),
    );
  },
}

Hope it helps!

M123-dev commented 1 year ago

Heyy @apalala-dev thanks a lot, this definitely helped a lot. I was looking for such a method in the normal CameraState makes sense to only find one in the "real" states, and it definitely works. The problem with this approach is that the focus point has moved, but it's just a one time focus.

When I start the camera the focus point it set, but when I then move the phone around the point is kept on the same depth.

Looking at the Kotlin implementation, it looks like this is because of the https://github.com/Apparence-io/CamerAwesome/blob/4827ea785936eb1a1e54294b563556a2ddb66a03/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraAwesomeX.kt#L553


I see a few possible ways to counter this (with my limited Kotlin knowledge):

  1. Call focusOnPoint repetitive (probably with Timer.periodic) but this doesn't seem to be a proper way to do this.
  2. Allow to pass coordinates to the native (auto) focus
  3. Allow to change the AutoCancelDuration infocusOnPoint, but wouldn't it jump back to the default autofocus point after that

Couldn't test the behavior for iOS

apalala-dev commented 1 year ago

I'm only talking about the Android side.

First of all, let's distinguish two things:

Read more about these here.

While you are in control of the area when you make an auto focus, you aren't when using the passive focus.

Current implementation of CamerAwesome tap to focus feature disables the passive focus once it launches a focus on an area (tap to focus). This means that from that time, the user will need to launch an auto focus to set the focus to another point (if the focused object has moved for example, as you said).

I am considering to change that behaviour so once a focus on a point has been done, it goes back to passive focus after a while with setAutoCancelDuration() (instead of disabling it with disableAutoCancel() like now). The duration after which it goes back to passive focus will probably be a parameter and if it's set to 0, it would be disabled (current behaviour).

In your case, I believe you have 2 options:

  1. With the change I mentioned, you could launch an auto focus when the barcode screen is shown with an autoCancel after e.g. 5sec.
  2. If you really want to focus on that specific area, use a Timer.periodic or similar to launch the auto focus on that area every once in a while.

Let me know if you have other ideas.

apalala-dev commented 1 year ago

I started implementing the method 1.

As expected, once the auto focus is canceled and the passive focus takes the lead, you might encounter problems like this one: ezgif com-optimize (1) I set the auto focus on the pack of tissues behind the book by tapping on it. Once the auto focus is cancelled, the passive focus takes the lead and tries to focus again: this time, the book is focused. In this example, I've set the auto cancel duration to 1000 ms, which is quite low. Only one sec after having a "good" focus the camera tries to focus again with the passive mode. This problem arises quite fast here because of it, but you might get a good result with a bigger number (a few seconds).