Skip to content

Commit aad08ec

Browse files
authored
[camera_android_camerax] Remove logic used to previously correct preview rotation (#8256)
Removes correction logic used to the correct the rotation of the camera preview, since it appears* to not be needed anymore. Partially addresses flutter/flutter#154241. *This is **not true** for emulators, but if we are ok landing this issue for real devices, I'll file an issue for emulators and follow up there. jonahwilliams@ mentioned there is known different behavior in emulators with Impeller, so this fact is not totally a surprise. My basis for landing this is: 1. I tested the following devices that use both Impeller backends and this PR works: - Realme X2 Pro, Android 11 (API 30) that uses Vulkan - Pixel 3A, Android 12 (Android 31/32) that falls back to OpenGLES - Samsung Galaxy (Android 8.1.0) that uses OpenGLES 2. If this misses an edge case (like a class of device), this PR will make it easier to debug what is going on, since the camera preview will now **only** introduce a rotation if the device itself is rotated (as per the logic in https://github.com/flutter/packages/blob/8ca81bd20231ed638cfc3ebf5f8a3f96b3d22e60/packages/camera/camera/lib/src/camera_preview.dart#L46-L72) with no additional logic concerning the sensor orientation and UI orientation as it does now.
1 parent 0f42943 commit aad08ec

File tree

4 files changed

+8
-249
lines changed

4 files changed

+8
-249
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
## NEXT
1+
## 0.6.12
22

33
* Suppresses deprecation and removal warnings for
44
`TextureRegistry.SurfaceProducer.onSurfaceDestroyed`.
5+
* Removes logic added to correct the rotation of the camera preview, since it is no longer required.
56

67
## 0.6.11
78

packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart

Lines changed: 3 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart';
1010
import 'package:flutter/services.dart'
1111
show DeviceOrientation, PlatformException;
1212
import 'package:flutter/widgets.dart'
13-
show RotatedBox, Size, Texture, Widget, visibleForTesting;
13+
show Size, Texture, Widget, visibleForTesting;
1414
import 'package:stream_transform/stream_transform.dart';
1515

1616
import 'analyzer.dart';
@@ -237,26 +237,10 @@ class AndroidCameraCameraX extends CameraPlatform {
237237
@visibleForTesting
238238
late bool cameraIsFrontFacing;
239239

240-
/// Whether or not the Surface used to create the camera preview is backed
241-
/// by a SurfaceTexture.
242-
@visibleForTesting
243-
late bool isPreviewPreTransformed;
244-
245-
/// The initial orientation of the device.
246-
///
247-
/// The camera preview will use this orientation as the natural orientation
248-
/// to correct its rotation with respect to, if necessary.
249-
@visibleForTesting
250-
DeviceOrientation? naturalOrientation;
251-
252240
/// The camera sensor orientation.
253241
@visibleForTesting
254242
late int sensorOrientation;
255243

256-
/// The current orientation of the device.
257-
@visibleForTesting
258-
DeviceOrientation? currentDeviceOrientation;
259-
260244
/// Subscription for listening to changes in device orientation.
261245
StreamSubscription<DeviceOrientationChangedEvent>?
262246
_subscriptionForDeviceOrientationChanges;
@@ -399,20 +383,7 @@ class AndroidCameraCameraX extends CameraPlatform {
399383

400384
final Camera2CameraInfo camera2CameraInfo =
401385
await proxy.getCamera2CameraInfo(cameraInfo!);
402-
await Future.wait(<Future<Object>>[
403-
SystemServices.isPreviewPreTransformed()
404-
.then((bool value) => isPreviewPreTransformed = value),
405-
proxy
406-
.getSensorOrientation(camera2CameraInfo)
407-
.then((int value) => sensorOrientation = value),
408-
proxy
409-
.getUiOrientation()
410-
.then((DeviceOrientation value) => naturalOrientation ??= value),
411-
]);
412-
_subscriptionForDeviceOrientationChanges = onDeviceOrientationChanged()
413-
.listen((DeviceOrientationChangedEvent event) {
414-
currentDeviceOrientation = event.orientation;
415-
});
386+
sensorOrientation = await proxy.getSensorOrientation(camera2CameraInfo);
416387

417388
return flutterSurfaceTextureId;
418389
}
@@ -865,68 +836,7 @@ class AndroidCameraCameraX extends CameraPlatform {
865836
);
866837
}
867838

868-
final Widget cameraPreview = Texture(textureId: cameraId);
869-
final Map<DeviceOrientation, int> degreesForDeviceOrientation =
870-
<DeviceOrientation, int>{
871-
DeviceOrientation.portraitUp: 0,
872-
DeviceOrientation.landscapeRight: 90,
873-
DeviceOrientation.portraitDown: 180,
874-
DeviceOrientation.landscapeLeft: 270,
875-
};
876-
int naturalDeviceOrientationDegrees =
877-
degreesForDeviceOrientation[naturalOrientation]!;
878-
879-
if (isPreviewPreTransformed) {
880-
// If the camera preview is backed by a SurfaceTexture, the transformation
881-
// needed to correctly rotate the preview has already been applied.
882-
// However, we may need to correct the camera preview rotation if the
883-
// device is naturally landscape-oriented.
884-
if (naturalOrientation == DeviceOrientation.landscapeLeft ||
885-
naturalOrientation == DeviceOrientation.landscapeRight) {
886-
final int quarterTurnsToCorrectForLandscape =
887-
(-naturalDeviceOrientationDegrees + 360) ~/ 4;
888-
return RotatedBox(
889-
quarterTurns: quarterTurnsToCorrectForLandscape,
890-
child: cameraPreview);
891-
}
892-
return cameraPreview;
893-
}
894-
895-
// Fix for the rotation of the camera preview not backed by a SurfaceTexture
896-
// with respect to the naturalOrientation of the device:
897-
898-
final int signForCameraDirection = cameraIsFrontFacing ? 1 : -1;
899-
900-
if (signForCameraDirection == 1 &&
901-
(currentDeviceOrientation == DeviceOrientation.landscapeLeft ||
902-
currentDeviceOrientation == DeviceOrientation.landscapeRight)) {
903-
// For front-facing cameras, the image buffer is rotated counterclockwise,
904-
// so we determine the rotation needed to correct the camera preview with
905-
// respect to the naturalOrientation of the device based on the inverse of
906-
// naturalOrientation.
907-
naturalDeviceOrientationDegrees += 180;
908-
}
909-
910-
// See https://developer.android.com/media/camera/camera2/camera-preview#orientation_calculation
911-
// for more context on this formula.
912-
final double rotation = (sensorOrientation +
913-
naturalDeviceOrientationDegrees * signForCameraDirection +
914-
360) %
915-
360;
916-
int quarterTurnsToCorrectPreview = rotation ~/ 90;
917-
918-
if (naturalOrientation == DeviceOrientation.landscapeLeft ||
919-
naturalOrientation == DeviceOrientation.landscapeRight) {
920-
// We may need to correct the camera preview rotation if the device is
921-
// naturally landscape-oriented.
922-
quarterTurnsToCorrectPreview +=
923-
(-naturalDeviceOrientationDegrees + 360) ~/ 4;
924-
return RotatedBox(
925-
quarterTurns: quarterTurnsToCorrectPreview, child: cameraPreview);
926-
}
927-
928-
return RotatedBox(
929-
quarterTurns: quarterTurnsToCorrectPreview, child: cameraPreview);
839+
return Texture(textureId: cameraId);
930840
}
931841

932842
/// Captures an image and returns the file where it was saved.

packages/camera/camera_android_camerax/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_android_camerax
22
description: Android implementation of the camera plugin using the CameraX library.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.6.11
5+
version: 0.6.12
66

77
environment:
88
sdk: ^3.6.0

packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart

Lines changed: 2 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ import 'package:camera_android_camerax/src/zoom_state.dart';
5050
import 'package:camera_platform_interface/camera_platform_interface.dart';
5151
import 'package:flutter/services.dart'
5252
show DeviceOrientation, PlatformException, Uint8List;
53-
import 'package:flutter/widgets.dart'
54-
show BuildContext, RotatedBox, Size, Texture, Widget;
53+
import 'package:flutter/widgets.dart' show BuildContext, Size, Texture, Widget;
5554
import 'package:flutter_test/flutter_test.dart';
5655
import 'package:mockito/annotations.dart';
5756
import 'package:mockito/mockito.dart';
@@ -920,9 +919,7 @@ void main() {
920919
expect(camera.recorder!.qualitySelector, isNull);
921920
});
922921

923-
test(
924-
'createCamera sets sensor and device orientations needed to correct preview rotation as expected',
925-
() async {
922+
test('createCamera sets sensor orientation as expected', () async {
926923
final AndroidCameraCameraX camera = AndroidCameraCameraX();
927924
const CameraLensDirection testLensDirection = CameraLensDirection.back;
928925
const int testSensorOrientation = 270;
@@ -933,8 +930,6 @@ void main() {
933930
const bool enableAudio = true;
934931
const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh;
935932
const DeviceOrientation testUiOrientation = DeviceOrientation.portraitDown;
936-
const DeviceOrientation testCurrentOrientation =
937-
DeviceOrientation.portraitUp;
938933

939934
// Mock/Detached objects for (typically attached) objects created by
940935
// createCamera.
@@ -965,18 +960,7 @@ void main() {
965960
await camera.createCamera(testCameraDescription, testResolutionPreset,
966961
enableAudio: enableAudio);
967962

968-
const DeviceOrientationChangedEvent testEvent =
969-
DeviceOrientationChangedEvent(testCurrentOrientation);
970-
971-
DeviceOrientationManager.deviceOrientationChangedStreamController
972-
.add(testEvent);
973-
974-
// Wait for currentDeviceOrientation to update.
975-
await Future<void>.value();
976-
977-
expect(camera.naturalOrientation, testUiOrientation);
978963
expect(camera.sensorOrientation, testSensorOrientation);
979-
expect(camera.currentDeviceOrientation, testCurrentOrientation);
980964
});
981965

982966
test(
@@ -1336,148 +1320,12 @@ void main() {
13361320
// bound to the lifecycle of the camera.
13371321
camera.previewInitiallyBound = true;
13381322

1339-
// Tell camera the Surface used to build camera preview is backed by a
1340-
// SurfaceTexture.
1341-
camera.isPreviewPreTransformed = true;
1342-
1343-
camera.naturalOrientation = DeviceOrientation.portraitDown;
1344-
13451323
final Widget widget = camera.buildPreview(cameraId);
13461324

13471325
expect(widget is Texture, isTrue);
13481326
expect((widget as Texture).textureId, cameraId);
13491327
});
13501328

1351-
test(
1352-
'buildPreview returns preview with expected rotation if camera is not front facing and the preview is not backed by a SurfaceTexture',
1353-
() async {
1354-
final AndroidCameraCameraX camera = AndroidCameraCameraX();
1355-
const int cameraId = 33;
1356-
1357-
// Tell camera that createCamera has been called and thus, preview has been
1358-
// bound to the lifecycle of the camera.
1359-
camera.previewInitiallyBound = true;
1360-
1361-
// Tell camera the Surface used to build camera preview is not backed by a
1362-
// SurfaceTexture.
1363-
camera.isPreviewPreTransformed = false;
1364-
1365-
// Mock sensor and device orientation.
1366-
camera.sensorOrientation = 270;
1367-
camera.cameraIsFrontFacing = false;
1368-
camera.naturalOrientation = DeviceOrientation.portraitUp;
1369-
1370-
final double expectedRotation = (camera.sensorOrientation +
1371-
0 /* the natural orientation in clockwise degrees */ *
1372-
-1 /* camera is not front facing */ +
1373-
360) %
1374-
360;
1375-
final int expectedQuarterTurns = (expectedRotation / 90).toInt();
1376-
1377-
final Widget widget = camera.buildPreview(cameraId);
1378-
1379-
expect(widget is RotatedBox, isTrue);
1380-
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
1381-
expect(widget.child is Texture, isTrue);
1382-
expect((widget.child! as Texture).textureId, cameraId);
1383-
});
1384-
1385-
test(
1386-
'buildPreview returns preview with expected rotation if camera is front facing, the current orientation is landscape, and the preview is not backed by a SurfaceTexture',
1387-
() async {
1388-
final AndroidCameraCameraX camera = AndroidCameraCameraX();
1389-
const int cameraId = 7;
1390-
1391-
// Tell camera that createCamera has been called and thus, preview has been
1392-
// bound to the lifecycle of the camera.
1393-
camera.previewInitiallyBound = true;
1394-
1395-
// Tell camera the Surface used to build camera preview is not backed by a
1396-
// SurfaceTexture.
1397-
camera.isPreviewPreTransformed = false;
1398-
1399-
// Mock sensor and device orientation.
1400-
camera.sensorOrientation = 270;
1401-
camera.naturalOrientation = DeviceOrientation.portraitUp;
1402-
camera.cameraIsFrontFacing = true;
1403-
1404-
// Calculate expected rotation with offset due to counter-clockwise rotation
1405-
// of the image with th efront camera in use.
1406-
final double expectedRotation = ((camera.sensorOrientation +
1407-
0 /* the natural orientation in clockwise degrees */ *
1408-
1 /* camera is front facing */ +
1409-
360) %
1410-
360) +
1411-
180;
1412-
final int expectedQuarterTurns = (expectedRotation / 90).toInt() % 4;
1413-
1414-
// Test landscape left.
1415-
camera.currentDeviceOrientation = DeviceOrientation.landscapeLeft;
1416-
Widget widget = camera.buildPreview(cameraId);
1417-
1418-
expect(widget is RotatedBox, isTrue);
1419-
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
1420-
expect(widget.child is Texture, isTrue);
1421-
expect((widget.child! as Texture).textureId, cameraId);
1422-
1423-
// Test landscape right.
1424-
camera.currentDeviceOrientation = DeviceOrientation.landscapeRight;
1425-
widget = camera.buildPreview(cameraId);
1426-
1427-
expect(widget is RotatedBox, isTrue);
1428-
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
1429-
expect(widget.child is Texture, isTrue);
1430-
expect((widget.child! as Texture).textureId, cameraId);
1431-
});
1432-
1433-
test(
1434-
'buildPreview returns preview with expected rotation if camera is front facing, the current orientation is not landscape, and the preview is not backed by a SurfaceTexture',
1435-
() async {
1436-
final AndroidCameraCameraX camera = AndroidCameraCameraX();
1437-
const int cameraId = 733;
1438-
1439-
// Tell camera that createCamera has been called and thus, preview has been
1440-
// bound to the lifecycle of the camera.
1441-
camera.previewInitiallyBound = true;
1442-
1443-
// Tell camera the Surface used to build camera preview is not backed by a
1444-
// SurfaceTexture.
1445-
camera.isPreviewPreTransformed = false;
1446-
1447-
// Mock sensor and device orientation.
1448-
camera.sensorOrientation = 270;
1449-
camera.naturalOrientation = DeviceOrientation.portraitUp;
1450-
camera.cameraIsFrontFacing = true;
1451-
1452-
// Calculate expected rotation without offset needed for landscape orientations
1453-
// due to counter-clockwise rotation of the image with th efront camera in use.
1454-
final double expectedRotation = (camera.sensorOrientation +
1455-
0 /* the natural orientation in clockwise degrees */ *
1456-
1 /* camera is front facing */ +
1457-
360) %
1458-
360;
1459-
1460-
final int expectedQuarterTurns = (expectedRotation / 90).toInt() % 4;
1461-
1462-
// Test portrait up.
1463-
camera.currentDeviceOrientation = DeviceOrientation.portraitUp;
1464-
Widget widget = camera.buildPreview(cameraId);
1465-
1466-
expect(widget is RotatedBox, isTrue);
1467-
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
1468-
expect(widget.child is Texture, isTrue);
1469-
expect((widget.child! as Texture).textureId, cameraId);
1470-
1471-
// Test portrait down.
1472-
camera.currentDeviceOrientation = DeviceOrientation.portraitDown;
1473-
widget = camera.buildPreview(cameraId);
1474-
1475-
expect(widget is RotatedBox, isTrue);
1476-
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
1477-
expect(widget.child is Texture, isTrue);
1478-
expect((widget.child! as Texture).textureId, cameraId);
1479-
});
1480-
14811329
group('video recording', () {
14821330
test(
14831331
'startVideoCapturing binds video capture use case, updates saved camera instance and its properties, and starts the recording',

0 commit comments

Comments
 (0)