From 7d9beaa1622c4512debb4813872096bde7601ca5 Mon Sep 17 00:00:00 2001 From: Ludwig Bolling Date: Wed, 26 Aug 2026 11:22:38 +0200 Subject: [PATCH 1/2] [camera] Add setJpegImageQuality for JPEG compression control Adds `CameraController.setJpegImageQuality(int quality)` to the app-facing camera package, forwarding to the platform interface method added in camera_platform_interface 2.13.0 and implemented in camera_android_camerax 0.7.4 and camera_avfoundation 0.10.2. The default platform interface implementation is a no-op (2.13.1), so the dartdoc documents this as a best-effort setting and links to https://github.com/flutter/flutter/issues/191790 for the current state of platform support. Fixes https://github.com/flutter/flutter/issues/183229 --- packages/camera/camera/CHANGELOG.md | 4 ++ .../camera/lib/src/camera_controller.dart | 20 ++++++ packages/camera/camera/pubspec.yaml | 8 +-- .../camera/test/camera_preview_test.dart | 3 + packages/camera/camera/test/camera_test.dart | 62 +++++++++++++++++++ 5 files changed, 93 insertions(+), 4 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 73cbd87ada2f..259c4a16d744 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.1 + +* Adds `setJpegImageQuality` for controlling JPEG compression quality. + ## 0.12.0+2 * Fixes a crash where a `CameraController` could update its value after being disposed, throwing "A CameraController was used after being disposed". diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index a930723761aa..a66efe3e12bb 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -969,6 +969,26 @@ class CameraController extends ValueNotifier { } } + /// Sets the JPEG compression quality for still image capture. + /// + /// This only applies to images captured in JPEG format. + /// The [quality] must be between 1 (lowest) and 100 (highest). + /// + /// This is a best-effort setting: platforms that do not support controlling + /// the JPEG quality ignore it rather than throwing. See + /// https://github.com/flutter/flutter/issues/191790 for the current state of + /// platform support. + Future setJpegImageQuality(int quality) async { + if (quality < 1 || quality > 100) { + throw ArgumentError.value(quality, 'quality', 'Must be between 1 and 100.'); + } + try { + await CameraPlatform.instance.setJpegImageQuality(_cameraId, quality); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Check whether the camera platform supports image streaming. bool supportsImageStreaming() => CameraPlatform.instance.supportsImageStreaming(); diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 84966be525a0..88b81c58ac32 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.12.0+2 +version: 0.12.1 environment: sdk: ^3.10.0 @@ -21,9 +21,9 @@ flutter: default_package: camera_web dependencies: - camera_android_camerax: ^0.7.0 - camera_avfoundation: ^0.10.0 - camera_platform_interface: ^2.12.0 + camera_android_camerax: ^0.7.4 + camera_avfoundation: ^0.10.2 + camera_platform_interface: ^2.13.1 camera_web: ^0.3.3 flutter: sdk: flutter diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index c5dfde4f7383..beb78a7a9381 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -148,6 +148,9 @@ class FakeController extends ValueNotifier implements CameraControl Future> getSupportedVideoStabilizationModes() async => []; + @override + Future setJpegImageQuality(int quality) async {} + @override bool supportsImageStreaming() => true; } diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 5ddb256513c9..d40040494d2e 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -772,6 +772,64 @@ void main() { ); }); + test('setJpegImageQuality() calls CameraPlatform', () async { + final cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90, + ), + ResolutionPreset.max, + ); + await cameraController.initialize(); + + await cameraController.setJpegImageQuality(50); + + verify(CameraPlatform.instance.setJpegImageQuality(cameraController.cameraId, 50)).called(1); + }); + + test('setJpegImageQuality() throws CameraException on PlatformException', () async { + final cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90, + ), + ResolutionPreset.max, + ); + await cameraController.initialize(); + + when( + CameraPlatform.instance.setJpegImageQuality(cameraController.cameraId, 50), + ).thenThrow(PlatformException(code: 'TEST_ERROR', message: 'This is a test error message')); + + expect( + cameraController.setJpegImageQuality(50), + throwsA( + isA().having( + (CameraException error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ), + ), + ); + }); + + test('setJpegImageQuality() throws ArgumentError for invalid values', () async { + final cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90, + ), + ResolutionPreset.max, + ); + await cameraController.initialize(); + + expect(() => cameraController.setJpegImageQuality(0), throwsA(isA())); + expect(() => cameraController.setJpegImageQuality(101), throwsA(isA())); + }); + test('setExposureMode() calls $CameraPlatform', () async { final cameraController = CameraController( const CameraDescription( @@ -3646,6 +3704,10 @@ class MockCameraPlatform extends Mock with MockPlatformInterfaceMixin implements @override Future setVideoStabilizationMode(int cameraId, VideoStabilizationMode mode) async => super.noSuchMethod(Invocation.method(#setVideoStabilizationMode, [cameraId, mode])); + + @override + Future setJpegImageQuality(int? cameraId, int? quality) async => + super.noSuchMethod(Invocation.method(#setJpegImageQuality, [cameraId, quality])); } class MockCameraDescription extends CameraDescription { From 0550e16474ecee134a92f3993e629cb7e65a48ef Mon Sep 17 00:00:00 2001 From: Ludwig Bolling Date: Thu, 27 Aug 2026 07:17:54 +0200 Subject: [PATCH 2/2] [camera] Bump min SDK to match camera_android_camerax The bumped camera_android_camerax constraint requires Flutter 3.44/Dart 3.12, so camera has to declare the same minimum for the legacy analysis jobs to skip it. Raising the language version surfaces prefer_initializing_formals on the CameraValue constructor; an initializing formal is not possible there because named parameters cannot be private, so the lint is ignored with a reason. --- packages/camera/camera/CHANGELOG.md | 1 + packages/camera/camera/lib/src/camera_controller.dart | 3 +++ packages/camera/camera/pubspec.yaml | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 259c4a16d744..387edc20b730 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.12.1 * Adds `setJpegImageQuality` for controlling JPEG compression quality. +* Updates minimum supported SDK version to Flutter 3.44/Dart 3.12. ## 0.12.0+2 diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index a66efe3e12bb..00cfc91d4d8e 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -51,6 +51,9 @@ class CameraValue { this.isPreviewPaused = false, this.previewPauseOrientation, this.videoStabilizationMode = VideoStabilizationMode.off, + // An initializing formal isn't possible here: named parameters can't be + // private, and `isRecordingPaused` is a derived getter, not a stored field. + // ignore: prefer_initializing_formals }) : _isRecordingPaused = isRecordingPaused; /// Creates a new camera controller state for an uninitialized controller. diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 88b81c58ac32..25358a82583f 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -7,8 +7,8 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 0.12.1 environment: - sdk: ^3.10.0 - flutter: ">=3.38.0" + sdk: ^3.12.0 + flutter: ">=3.44.0" flutter: plugin: