Skip to content
Merged
3 changes: 2 additions & 1 deletion packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
## NEXT
## 0.6.12

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

## 0.6.11

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/services.dart'
show DeviceOrientation, PlatformException;
import 'package:flutter/widgets.dart'
show RotatedBox, Size, Texture, Widget, visibleForTesting;
show Size, Texture, Widget, visibleForTesting;
import 'package:stream_transform/stream_transform.dart';

import 'analyzer.dart';
Expand Down Expand Up @@ -237,26 +237,10 @@ class AndroidCameraCameraX extends CameraPlatform {
@visibleForTesting
late bool cameraIsFrontFacing;

/// Whether or not the Surface used to create the camera preview is backed
/// by a SurfaceTexture.
@visibleForTesting
late bool isPreviewPreTransformed;

/// The initial orientation of the device.
///
/// The camera preview will use this orientation as the natural orientation
/// to correct its rotation with respect to, if necessary.
@visibleForTesting
DeviceOrientation? naturalOrientation;

/// The camera sensor orientation.
@visibleForTesting
late int sensorOrientation;

/// The current orientation of the device.
@visibleForTesting
DeviceOrientation? currentDeviceOrientation;

/// Subscription for listening to changes in device orientation.
StreamSubscription<DeviceOrientationChangedEvent>?
_subscriptionForDeviceOrientationChanges;
Expand Down Expand Up @@ -399,20 +383,7 @@ class AndroidCameraCameraX extends CameraPlatform {

final Camera2CameraInfo camera2CameraInfo =
await proxy.getCamera2CameraInfo(cameraInfo!);
await Future.wait(<Future<Object>>[
SystemServices.isPreviewPreTransformed()
.then((bool value) => isPreviewPreTransformed = value),
proxy
.getSensorOrientation(camera2CameraInfo)
.then((int value) => sensorOrientation = value),
proxy
.getUiOrientation()
.then((DeviceOrientation value) => naturalOrientation ??= value),
]);
_subscriptionForDeviceOrientationChanges = onDeviceOrientationChanged()
.listen((DeviceOrientationChangedEvent event) {
currentDeviceOrientation = event.orientation;
});
sensorOrientation = await proxy.getSensorOrientation(camera2CameraInfo);

return flutterSurfaceTextureId;
}
Expand Down Expand Up @@ -865,68 +836,7 @@ class AndroidCameraCameraX extends CameraPlatform {
);
}

final Widget cameraPreview = Texture(textureId: cameraId);
final Map<DeviceOrientation, int> degreesForDeviceOrientation =
<DeviceOrientation, int>{
DeviceOrientation.portraitUp: 0,
DeviceOrientation.landscapeRight: 90,
DeviceOrientation.portraitDown: 180,
DeviceOrientation.landscapeLeft: 270,
};
int naturalDeviceOrientationDegrees =
degreesForDeviceOrientation[naturalOrientation]!;

if (isPreviewPreTransformed) {
// If the camera preview is backed by a SurfaceTexture, the transformation
// needed to correctly rotate the preview has already been applied.
// However, we may need to correct the camera preview rotation if the
// device is naturally landscape-oriented.
if (naturalOrientation == DeviceOrientation.landscapeLeft ||
naturalOrientation == DeviceOrientation.landscapeRight) {
final int quarterTurnsToCorrectForLandscape =
(-naturalDeviceOrientationDegrees + 360) ~/ 4;
return RotatedBox(
quarterTurns: quarterTurnsToCorrectForLandscape,
child: cameraPreview);
}
return cameraPreview;
}

// Fix for the rotation of the camera preview not backed by a SurfaceTexture
// with respect to the naturalOrientation of the device:

final int signForCameraDirection = cameraIsFrontFacing ? 1 : -1;

if (signForCameraDirection == 1 &&
(currentDeviceOrientation == DeviceOrientation.landscapeLeft ||
currentDeviceOrientation == DeviceOrientation.landscapeRight)) {
// For front-facing cameras, the image buffer is rotated counterclockwise,
// so we determine the rotation needed to correct the camera preview with
// respect to the naturalOrientation of the device based on the inverse of
// naturalOrientation.
naturalDeviceOrientationDegrees += 180;
}

// See https://developer.android.com/media/camera/camera2/camera-preview#orientation_calculation
// for more context on this formula.
final double rotation = (sensorOrientation +
naturalDeviceOrientationDegrees * signForCameraDirection +
360) %
360;
int quarterTurnsToCorrectPreview = rotation ~/ 90;

if (naturalOrientation == DeviceOrientation.landscapeLeft ||
naturalOrientation == DeviceOrientation.landscapeRight) {
// We may need to correct the camera preview rotation if the device is
// naturally landscape-oriented.
quarterTurnsToCorrectPreview +=
(-naturalDeviceOrientationDegrees + 360) ~/ 4;
return RotatedBox(
quarterTurns: quarterTurnsToCorrectPreview, child: cameraPreview);
}

return RotatedBox(
quarterTurns: quarterTurnsToCorrectPreview, child: cameraPreview);
return Texture(textureId: cameraId);
}

/// Captures an image and returns the file where it was saved.
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_android_camerax/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_android_camerax
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.6.11
version: 0.6.12

environment:
sdk: ^3.6.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ import 'package:camera_android_camerax/src/zoom_state.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/services.dart'
show DeviceOrientation, PlatformException, Uint8List;
import 'package:flutter/widgets.dart'
show BuildContext, RotatedBox, Size, Texture, Widget;
import 'package:flutter/widgets.dart' show BuildContext, Size, Texture, Widget;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
Expand Down Expand Up @@ -920,9 +919,7 @@ void main() {
expect(camera.recorder!.qualitySelector, isNull);
});

test(
'createCamera sets sensor and device orientations needed to correct preview rotation as expected',
() async {
test('createCamera sets sensor orientation as expected', () async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const CameraLensDirection testLensDirection = CameraLensDirection.back;
const int testSensorOrientation = 270;
Expand All @@ -933,8 +930,6 @@ void main() {
const bool enableAudio = true;
const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh;
const DeviceOrientation testUiOrientation = DeviceOrientation.portraitDown;
const DeviceOrientation testCurrentOrientation =
DeviceOrientation.portraitUp;

// Mock/Detached objects for (typically attached) objects created by
// createCamera.
Expand Down Expand Up @@ -965,18 +960,7 @@ void main() {
await camera.createCamera(testCameraDescription, testResolutionPreset,
enableAudio: enableAudio);

const DeviceOrientationChangedEvent testEvent =
DeviceOrientationChangedEvent(testCurrentOrientation);

DeviceOrientationManager.deviceOrientationChangedStreamController
.add(testEvent);

// Wait for currentDeviceOrientation to update.
await Future<void>.value();

expect(camera.naturalOrientation, testUiOrientation);
expect(camera.sensorOrientation, testSensorOrientation);
expect(camera.currentDeviceOrientation, testCurrentOrientation);
});

test(
Expand Down Expand Up @@ -1336,148 +1320,12 @@ void main() {
// bound to the lifecycle of the camera.
camera.previewInitiallyBound = true;

// Tell camera the Surface used to build camera preview is backed by a
// SurfaceTexture.
camera.isPreviewPreTransformed = true;

camera.naturalOrientation = DeviceOrientation.portraitDown;

final Widget widget = camera.buildPreview(cameraId);

expect(widget is Texture, isTrue);
expect((widget as Texture).textureId, cameraId);
});

test(
'buildPreview returns preview with expected rotation if camera is not front facing and the preview is not backed by a SurfaceTexture',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 33;

// Tell camera that createCamera has been called and thus, preview has been
// bound to the lifecycle of the camera.
camera.previewInitiallyBound = true;

// Tell camera the Surface used to build camera preview is not backed by a
// SurfaceTexture.
camera.isPreviewPreTransformed = false;

// Mock sensor and device orientation.
camera.sensorOrientation = 270;
camera.cameraIsFrontFacing = false;
camera.naturalOrientation = DeviceOrientation.portraitUp;

final double expectedRotation = (camera.sensorOrientation +
0 /* the natural orientation in clockwise degrees */ *
-1 /* camera is not front facing */ +
360) %
360;
final int expectedQuarterTurns = (expectedRotation / 90).toInt();

final Widget widget = camera.buildPreview(cameraId);

expect(widget is RotatedBox, isTrue);
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
expect(widget.child is Texture, isTrue);
expect((widget.child! as Texture).textureId, cameraId);
});

test(
'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',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 7;

// Tell camera that createCamera has been called and thus, preview has been
// bound to the lifecycle of the camera.
camera.previewInitiallyBound = true;

// Tell camera the Surface used to build camera preview is not backed by a
// SurfaceTexture.
camera.isPreviewPreTransformed = false;

// Mock sensor and device orientation.
camera.sensorOrientation = 270;
camera.naturalOrientation = DeviceOrientation.portraitUp;
camera.cameraIsFrontFacing = true;

// Calculate expected rotation with offset due to counter-clockwise rotation
// of the image with th efront camera in use.
final double expectedRotation = ((camera.sensorOrientation +
0 /* the natural orientation in clockwise degrees */ *
1 /* camera is front facing */ +
360) %
360) +
180;
final int expectedQuarterTurns = (expectedRotation / 90).toInt() % 4;

// Test landscape left.
camera.currentDeviceOrientation = DeviceOrientation.landscapeLeft;
Widget widget = camera.buildPreview(cameraId);

expect(widget is RotatedBox, isTrue);
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
expect(widget.child is Texture, isTrue);
expect((widget.child! as Texture).textureId, cameraId);

// Test landscape right.
camera.currentDeviceOrientation = DeviceOrientation.landscapeRight;
widget = camera.buildPreview(cameraId);

expect(widget is RotatedBox, isTrue);
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
expect(widget.child is Texture, isTrue);
expect((widget.child! as Texture).textureId, cameraId);
});

test(
'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',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 733;

// Tell camera that createCamera has been called and thus, preview has been
// bound to the lifecycle of the camera.
camera.previewInitiallyBound = true;

// Tell camera the Surface used to build camera preview is not backed by a
// SurfaceTexture.
camera.isPreviewPreTransformed = false;

// Mock sensor and device orientation.
camera.sensorOrientation = 270;
camera.naturalOrientation = DeviceOrientation.portraitUp;
camera.cameraIsFrontFacing = true;

// Calculate expected rotation without offset needed for landscape orientations
// due to counter-clockwise rotation of the image with th efront camera in use.
final double expectedRotation = (camera.sensorOrientation +
0 /* the natural orientation in clockwise degrees */ *
1 /* camera is front facing */ +
360) %
360;

final int expectedQuarterTurns = (expectedRotation / 90).toInt() % 4;

// Test portrait up.
camera.currentDeviceOrientation = DeviceOrientation.portraitUp;
Widget widget = camera.buildPreview(cameraId);

expect(widget is RotatedBox, isTrue);
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
expect(widget.child is Texture, isTrue);
expect((widget.child! as Texture).textureId, cameraId);

// Test portrait down.
camera.currentDeviceOrientation = DeviceOrientation.portraitDown;
widget = camera.buildPreview(cameraId);

expect(widget is RotatedBox, isTrue);
expect((widget as RotatedBox).quarterTurns, expectedQuarterTurns);
expect(widget.child is Texture, isTrue);
expect((widget.child! as Texture).textureId, cameraId);
});

group('video recording', () {
test(
'startVideoCapturing binds video capture use case, updates saved camera instance and its properties, and starts the recording',
Expand Down
Loading