Skip to content

Commit ab036ed

Browse files
committed
adjust the surface producer delegate to use a separate preview widget & fix formula
1 parent 4a710ef commit ab036ed

File tree

3 files changed

+161
-116
lines changed

3 files changed

+161
-116
lines changed
Lines changed: 20 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:flutter/services.dart';
2-
import 'package:flutter/widgets.dart';
32
import 'package:mobile_scanner/src/enums/camera_facing.dart';
43
import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
54
import 'package:mobile_scanner/src/mobile_scanner_exception.dart';
@@ -10,10 +9,10 @@ import 'package:mobile_scanner/src/utils/parse_device_orientation_extension.dart
109
class AndroidSurfaceProducerDelegate {
1110
/// Construct a new [AndroidSurfaceProducerDelegate].
1211
AndroidSurfaceProducerDelegate({
13-
required this.cameraIsFrontFacing,
14-
required this.isPreviewPreTransformed,
15-
required this.naturalOrientation,
16-
required this.sensorOrientation,
12+
required this.cameraFacingDirection,
13+
required this.handlesCropAndRotation,
14+
required this.initialDeviceOrientation,
15+
required this.sensorOrientationDegrees,
1716
});
1817

1918
/// Construct a new [AndroidSurfaceProducerDelegate]
@@ -26,18 +25,19 @@ class AndroidSurfaceProducerDelegate {
2625
) {
2726
if (config
2827
case {
29-
'isPreviewPreTransformed': final bool isPreviewPreTransformed,
28+
'handlesCropAndRotation': final bool handlesCropAndRotation,
3029
'naturalDeviceOrientation': final String naturalDeviceOrientation,
31-
'sensorOrientation': final int sensorOrientation
30+
'sensorOrientation': final int sensorOrientation,
3231
}) {
3332
final DeviceOrientation naturalOrientation =
3433
naturalDeviceOrientation.parseDeviceOrientation();
3534

3635
return AndroidSurfaceProducerDelegate(
37-
cameraIsFrontFacing: cameraDirection == CameraFacing.front,
38-
isPreviewPreTransformed: isPreviewPreTransformed,
39-
naturalOrientation: naturalOrientation,
40-
sensorOrientation: sensorOrientation,
36+
cameraFacingDirection: cameraDirection,
37+
handlesCropAndRotation: handlesCropAndRotation,
38+
initialDeviceOrientation: naturalOrientation,
39+
// FIXME: This is bad, will cause a flash/frame in the wrong rotation if started in another rotation.
40+
sensorOrientationDegrees: sensorOrientation.toDouble(),
4141
);
4242
}
4343

@@ -49,97 +49,17 @@ class AndroidSurfaceProducerDelegate {
4949
);
5050
}
5151

52-
/// The rotation degrees corresponding to each device orientation.
53-
static const Map<DeviceOrientation, int> _degreesForDeviceOrientation =
54-
<DeviceOrientation, int>{
55-
DeviceOrientation.portraitUp: 0,
56-
DeviceOrientation.landscapeRight: 90,
57-
DeviceOrientation.portraitDown: 180,
58-
DeviceOrientation.landscapeLeft: 270,
59-
};
52+
/// The facing direction of the active camera.
53+
final CameraFacing cameraFacingDirection;
6054

61-
/// Whether the current camera is a front facing camera.
55+
/// Whether the underlying surface producer handles crop and rotation.
6256
///
63-
/// This is used to determine whether the orientation correction
64-
/// should apply an additional correction for front facing cameras.
65-
final bool cameraIsFrontFacing;
57+
/// If this is false, the preview needs to be manually rotated.
58+
final bool handlesCropAndRotation;
6659

67-
/// Whether the camera preview is pre-transformed,
68-
/// and thus does not need an orientation correction.
69-
final bool isPreviewPreTransformed;
60+
/// The initial device orientation when this [AndroidSurfaceProducerDelegate] is created.
61+
final DeviceOrientation initialDeviceOrientation;
7062

71-
/// The initial orientation of the device, when the camera was started.
72-
///
73-
/// The camera preview will use this orientation as the natural orientation
74-
/// to correct its rotation with respect to, if necessary.
75-
final DeviceOrientation naturalOrientation;
76-
77-
/// The sensor orientation of the current camera, in degrees.
78-
final int sensorOrientation;
79-
80-
/// Apply a rotation correction to the given [texture] widget.
81-
///
82-
/// The [currentDeviceOrientation] is the current device orientation
83-
/// at the time this method is called.
84-
Widget applyRotationCorrection(
85-
Widget texture,
86-
DeviceOrientation currentDeviceOrientation,
87-
) {
88-
int naturalDeviceOrientationDegrees =
89-
_degreesForDeviceOrientation[naturalOrientation]!;
90-
91-
if (isPreviewPreTransformed) {
92-
// If the camera preview is backed by a SurfaceTexture, the transformation
93-
// needed to correctly rotate the preview has already been applied.
94-
//
95-
// However, the camera preview rotation may need to be corrected if the
96-
// device is naturally landscape-oriented.
97-
if (naturalOrientation == DeviceOrientation.landscapeLeft ||
98-
naturalOrientation == DeviceOrientation.landscapeRight) {
99-
final int quarterTurns = (-naturalDeviceOrientationDegrees + 360) ~/ 4;
100-
101-
return RotatedBox(
102-
quarterTurns: quarterTurns,
103-
child: texture,
104-
);
105-
}
106-
107-
return texture;
108-
}
109-
110-
// If the camera preview is not backed by a SurfaceTexture,
111-
// the camera preview rotation needs to be manually applied,
112-
// while also taking into account devices that are naturally landscape-oriented.
113-
final int signForCameraDirection = cameraIsFrontFacing ? 1 : -1;
114-
115-
// For front-facing cameras, the preview is rotated counterclockwise,
116-
// so determine the rotation needed to correct the camera preview with
117-
// respect to the natural orientation of the device, based on the inverse of
118-
// of the natural orientation.
119-
if (signForCameraDirection == 1 &&
120-
(currentDeviceOrientation == DeviceOrientation.landscapeLeft ||
121-
currentDeviceOrientation == DeviceOrientation.landscapeRight)) {
122-
naturalDeviceOrientationDegrees += 180;
123-
}
124-
125-
// See https://developer.android.com/media/camera/camera2/camera-preview#orientation_calculation
126-
final double rotation = (sensorOrientation +
127-
naturalDeviceOrientationDegrees * signForCameraDirection +
128-
360) %
129-
360;
130-
131-
int quarterTurnsToCorrectPreview = rotation ~/ 90;
132-
133-
// Correct the camera preview rotation for devices that are naturally landscape-oriented.
134-
if (naturalOrientation == DeviceOrientation.landscapeLeft ||
135-
naturalOrientation == DeviceOrientation.landscapeRight) {
136-
quarterTurnsToCorrectPreview +=
137-
(-naturalDeviceOrientationDegrees + 360) ~/ 4;
138-
}
139-
140-
return RotatedBox(
141-
quarterTurns: quarterTurnsToCorrectPreview,
142-
child: texture,
143-
);
144-
}
63+
/// The orientation of the camera sensor on the device, in degrees.
64+
final double sensorOrientationDegrees;
14565
}

lib/src/method_channel/mobile_scanner_method_channel.dart

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:mobile_scanner/src/enums/mobile_scanner_authorization_state.dart
99
import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
1010
import 'package:mobile_scanner/src/enums/torch_state.dart';
1111
import 'package:mobile_scanner/src/method_channel/android_surface_producer_delegate.dart';
12+
import 'package:mobile_scanner/src/method_channel/rotated_preview.dart';
1213
import 'package:mobile_scanner/src/mobile_scanner_exception.dart';
1314
import 'package:mobile_scanner/src/mobile_scanner_platform_interface.dart';
1415
import 'package:mobile_scanner/src/mobile_scanner_view_attributes.dart';
@@ -245,22 +246,13 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
245246
// when the first listener is attached.
246247
if (_surfaceProducerDelegate
247248
case final AndroidSurfaceProducerDelegate delegate
248-
when !delegate.isPreviewPreTransformed) {
249-
return StreamBuilder<DeviceOrientation>(
250-
stream: deviceOrientationChangedStream,
251-
builder:
252-
(BuildContext context, AsyncSnapshot<DeviceOrientation> snapshot) {
253-
final DeviceOrientation? currentDeviceOrientation = snapshot.data;
254-
255-
if (currentDeviceOrientation == null) {
256-
return texture;
257-
}
258-
259-
return delegate.applyRotationCorrection(
260-
texture,
261-
currentDeviceOrientation,
262-
);
263-
},
249+
when !delegate.handlesCropAndRotation) {
250+
return RotatedPreview.fromCameraDirection(
251+
delegate.cameraFacingDirection,
252+
deviceOrientationStream: deviceOrientationChangedStream,
253+
initialDeviceOrientation: delegate.initialDeviceOrientation,
254+
sensorOrientationDegrees: delegate.sensorOrientationDegrees,
255+
child: texture,
264256
);
265257
}
266258

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter/services.dart';
4+
import 'package:flutter/widgets.dart';
5+
import 'package:mobile_scanner/src/enums/camera_facing.dart';
6+
7+
/// This widget represents a camera preview that rotates itself,
8+
/// based on changes in the device orientation.
9+
final class RotatedPreview extends StatefulWidget {
10+
/// Construct a new [RotatedPreview] instance.
11+
const RotatedPreview({
12+
required this.child,
13+
required this.deviceOrientationStream,
14+
required this.facingSign,
15+
required this.initialDeviceOrientation,
16+
required this.sensorOrientationDegrees,
17+
super.key,
18+
});
19+
20+
/// Construct a new [RotatedPreview] instance, from the given [cameraFacingDirection].
21+
factory RotatedPreview.fromCameraDirection(
22+
CameraFacing cameraFacingDirection, {
23+
required Widget child,
24+
required Stream<DeviceOrientation> deviceOrientationStream,
25+
required DeviceOrientation initialDeviceOrientation,
26+
required double sensorOrientationDegrees,
27+
Key? key,
28+
}) {
29+
final int facingSignForDirection = switch (cameraFacingDirection) {
30+
CameraFacing.front => 1,
31+
CameraFacing.back => -1,
32+
CameraFacing.unknown => 1,
33+
CameraFacing.external => 1,
34+
};
35+
36+
return RotatedPreview(
37+
deviceOrientationStream: deviceOrientationStream,
38+
facingSign: facingSignForDirection,
39+
initialDeviceOrientation: initialDeviceOrientation,
40+
sensorOrientationDegrees: sensorOrientationDegrees,
41+
key: key,
42+
child: child,
43+
);
44+
}
45+
46+
/// The preview widget to rotate.
47+
///
48+
/// This is typically a [Texture] widget.
49+
final Widget child;
50+
51+
/// The stream that provides updates to the device orientation.
52+
final Stream<DeviceOrientation> deviceOrientationStream;
53+
54+
/// The facing sign for the camera facing direction.
55+
final int facingSign;
56+
57+
/// The initial device orientation when this [RotatedPreview] widget is created.
58+
final DeviceOrientation initialDeviceOrientation;
59+
60+
/// The orientation of the camera sensor on the device, in degrees.
61+
final double sensorOrientationDegrees;
62+
63+
@override
64+
State<RotatedPreview> createState() => _RotatedPreviewState();
65+
}
66+
67+
final class _RotatedPreviewState extends State<RotatedPreview> {
68+
/// The current device orientation.
69+
late DeviceOrientation deviceOrientation;
70+
71+
/// The subscription for the device orientation stream.
72+
StreamSubscription<Object?>? _deviceOrientationSubscription;
73+
74+
/// Compute the rotation correction for the preview.
75+
///
76+
/// See also: https://developer.android.com/media/camera/camera2/camera-preview#orientation_calculation
77+
double _computeRotation(
78+
DeviceOrientation orientation, {
79+
required double sensorOrientationDegrees,
80+
required int sign,
81+
}) {
82+
final double deviceOrientationDegrees = switch (orientation) {
83+
DeviceOrientation.portraitUp => 0,
84+
DeviceOrientation.landscapeRight => 90,
85+
DeviceOrientation.portraitDown => 180,
86+
DeviceOrientation.landscapeLeft => 270, // FIXME: Should this be -90?
87+
};
88+
final double preAppliedRotation = deviceOrientationDegrees;
89+
90+
return ((sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) %
91+
360) -
92+
preAppliedRotation;
93+
}
94+
95+
@override
96+
void initState() {
97+
super.initState();
98+
99+
deviceOrientation = widget.initialDeviceOrientation;
100+
_deviceOrientationSubscription =
101+
widget.deviceOrientationStream.listen((DeviceOrientation event) {
102+
if (!mounted || deviceOrientation == event) {
103+
return;
104+
}
105+
106+
setState(() {
107+
deviceOrientation = event;
108+
});
109+
});
110+
}
111+
112+
@override
113+
void dispose() {
114+
unawaited(_deviceOrientationSubscription?.cancel());
115+
_deviceOrientationSubscription = null;
116+
super.dispose();
117+
}
118+
119+
@override
120+
Widget build(BuildContext context) {
121+
final double rotation = _computeRotation(
122+
deviceOrientation,
123+
sensorOrientationDegrees: widget.sensorOrientationDegrees,
124+
sign: widget.facingSign,
125+
);
126+
127+
// FIXME: Can this be improved?
128+
return RotatedBox(
129+
quarterTurns: rotation ~/ 90,
130+
child: widget.child,
131+
);
132+
}
133+
}

0 commit comments

Comments
 (0)