Skip to content

Commit 52b4cff

Browse files
Bolling88Ludwig Bolling
authored andcommitted
[camera] Add setImageQuality for JPEG compression control
Adds `setImageQuality(int quality)` (1-100 scale) across the camera plugin ecosystem to allow developers to control JPEG compression quality for still image capture. Users who do not call setImageQuality retain the platform's native default behavior. Implementation details per platform: - Platform interface: abstract method + method channel implementation - App-facing: CameraController.setImageQuality with range validation - Android (Camera2): JpegQualityFeature using CaptureRequest.JPEG_QUALITY - Android (CameraX): Recreates ImageCapture with Builder.setJpegQuality, preserving locked capture orientation - iOS (AVFoundation): EXIF-preserving JPEG recompression via CGImageSource/CGImageDestination (ImageIO), gated on JPEG format only
1 parent a27d7c5 commit 52b4cff

54 files changed

Lines changed: 4914 additions & 6352 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.12.1
2+
3+
* Adds `setImageQuality` for controlling JPEG compression quality.
4+
15
## 0.12.0
26

37
* Adds support for video stabilization.

packages/camera/camera/example/pubspec.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@ dev_dependencies:
3131

3232
flutter:
3333
uses-material-design: true
34+
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
35+
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
36+
dependency_overrides:
37+
camera_android_camerax: {path: ../../../../packages/camera/camera_android_camerax}
38+
camera_avfoundation: {path: ../../../../packages/camera/camera_avfoundation}
39+
camera_platform_interface: {path: ../../../../packages/camera/camera_platform_interface}

packages/camera/camera/lib/src/camera_controller.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,24 @@ class CameraController extends ValueNotifier<CameraValue> {
998998
}
999999
}
10001000

1001+
/// Sets the JPEG compression quality for still image capture.
1002+
///
1003+
/// The [quality] must be between 1 (lowest) and 100 (highest).
1004+
Future<void> setImageQuality(int quality) async {
1005+
if (quality < 1 || quality > 100) {
1006+
throw ArgumentError.value(
1007+
quality,
1008+
'quality',
1009+
'Must be between 1 and 100.',
1010+
);
1011+
}
1012+
try {
1013+
await CameraPlatform.instance.setImageQuality(_cameraId, quality);
1014+
} on PlatformException catch (e) {
1015+
throw CameraException(e.code, e.message);
1016+
}
1017+
}
1018+
10011019
/// Check whether the camera platform supports image streaming.
10021020
bool supportsImageStreaming() =>
10031021
CameraPlatform.instance.supportsImageStreaming();

packages/camera/camera/pubspec.yaml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
44
Dart.
55
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
7-
version: 0.12.0
7+
version: 0.12.1
88

99
environment:
1010
sdk: ^3.9.0
@@ -21,9 +21,9 @@ flutter:
2121
default_package: camera_web
2222

2323
dependencies:
24-
camera_android_camerax: ^0.7.0
25-
camera_avfoundation: ^0.10.0
26-
camera_platform_interface: ^2.12.0
24+
camera_android_camerax: ^0.7.1
25+
camera_avfoundation: ^0.10.1
26+
camera_platform_interface: ^2.13.0
2727
camera_web: ^0.3.3
2828
flutter:
2929
sdk: flutter
@@ -38,3 +38,9 @@ dev_dependencies:
3838

3939
topics:
4040
- camera
41+
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
42+
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
43+
dependency_overrides:
44+
camera_android_camerax: {path: ../../../packages/camera/camera_android_camerax}
45+
camera_avfoundation: {path: ../../../packages/camera/camera_avfoundation}
46+
camera_platform_interface: {path: ../../../packages/camera/camera_platform_interface}

packages/camera/camera/test/camera_preview_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ class FakeController extends ValueNotifier<CameraValue>
149149
Future<Iterable<VideoStabilizationMode>>
150150
getSupportedVideoStabilizationModes() async => <VideoStabilizationMode>[];
151151

152+
@override
153+
Future<void> setImageQuality(int quality) async {}
154+
152155
@override
153156
bool supportsImageStreaming() => true;
154157
}

packages/camera/camera/test/camera_test.dart

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,86 @@ void main() {
885885
},
886886
);
887887

888+
test('setImageQuality() calls $CameraPlatform', () async {
889+
final cameraController = CameraController(
890+
const CameraDescription(
891+
name: 'cam',
892+
lensDirection: CameraLensDirection.back,
893+
sensorOrientation: 90,
894+
),
895+
ResolutionPreset.max,
896+
);
897+
await cameraController.initialize();
898+
899+
await cameraController.setImageQuality(50);
900+
901+
verify(
902+
CameraPlatform.instance.setImageQuality(
903+
cameraController.cameraId,
904+
50,
905+
),
906+
).called(1);
907+
});
908+
909+
test(
910+
'setImageQuality() throws $CameraException on $PlatformException',
911+
() async {
912+
final cameraController = CameraController(
913+
const CameraDescription(
914+
name: 'cam',
915+
lensDirection: CameraLensDirection.back,
916+
sensorOrientation: 90,
917+
),
918+
ResolutionPreset.max,
919+
);
920+
await cameraController.initialize();
921+
922+
when(
923+
CameraPlatform.instance.setImageQuality(
924+
cameraController.cameraId,
925+
50,
926+
),
927+
).thenThrow(
928+
PlatformException(
929+
code: 'TEST_ERROR',
930+
message: 'This is a test error message',
931+
),
932+
);
933+
934+
expect(
935+
cameraController.setImageQuality(50),
936+
throwsA(
937+
isA<CameraException>().having(
938+
(CameraException error) => error.description,
939+
'TEST_ERROR',
940+
'This is a test error message',
941+
),
942+
),
943+
);
944+
},
945+
);
946+
947+
test('setImageQuality() throws ArgumentError for invalid values', () async {
948+
final cameraController = CameraController(
949+
const CameraDescription(
950+
name: 'cam',
951+
lensDirection: CameraLensDirection.back,
952+
sensorOrientation: 90,
953+
),
954+
ResolutionPreset.max,
955+
);
956+
await cameraController.initialize();
957+
958+
expect(
959+
() => cameraController.setImageQuality(0),
960+
throwsA(isA<ArgumentError>()),
961+
);
962+
expect(
963+
() => cameraController.setImageQuality(101),
964+
throwsA(isA<ArgumentError>()),
965+
);
966+
});
967+
888968
test('setExposureMode() calls $CameraPlatform', () async {
889969
final cameraController = CameraController(
890970
const CameraDescription(
@@ -4152,6 +4232,12 @@ class MockCameraPlatform extends Mock
41524232
) async => super.noSuchMethod(
41534233
Invocation.method(#setVideoStabilizationMode, <Object?>[cameraId, mode]),
41544234
);
4235+
4236+
@override
4237+
Future<void> setImageQuality(int? cameraId, int? quality) async =>
4238+
super.noSuchMethod(
4239+
Invocation.method(#setImageQuality, <Object?>[cameraId, quality]),
4240+
);
41554241
}
41564242

41574243
class MockCameraDescription extends CameraDescription {

packages/camera/camera_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.10.11
2+
3+
* Adds `setImageQuality` for controlling JPEG compression quality.
4+
15
## 0.10.10+15
26

37
* Updates example to demonstrate correct exception handling for async return statements, ensuring exceptions thrown during return within try blocks are properly caught as per [dart-lang/sdk#44395](https://github.com/dart-lang/sdk/issues/44395).

packages/camera/camera_android/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ buildFeatures {
6868
dependencies {
6969
implementation("androidx.annotation:annotation:1.9.1")
7070
testImplementation("junit:junit:4.13.2")
71-
testImplementation("org.mockito:mockito-core:5.21.0")
71+
testImplementation("org.mockito:mockito-core:5.22.0")
7272
testImplementation("androidx.test:core:1.7.0")
7373
testImplementation("org.robolectric:robolectric:4.16")
7474
}

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import io.flutter.plugins.camera.features.flash.FlashMode;
5454
import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
5555
import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
56+
import io.flutter.plugins.camera.features.jpegquality.JpegQualityFeature;
5657
import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
5758
import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
5859
import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager;
@@ -1407,6 +1408,20 @@ public void setDescriptionWhileRecording(CameraProperties properties) {
14071408
}
14081409
}
14091410

1411+
/**
1412+
* Sets the JPEG compression quality for still image capture.
1413+
*
1414+
* @param quality JPEG quality value between 1 and 100.
1415+
*/
1416+
public void setImageQuality(@NonNull Long quality) {
1417+
JpegQualityFeature jpegQualityFeature = cameraFeatures.getJpegQuality();
1418+
if (jpegQualityFeature == null) {
1419+
jpegQualityFeature = cameraFeatureFactory.createJpegQualityFeature(cameraProperties);
1420+
cameraFeatures.setJpegQuality(jpegQualityFeature);
1421+
}
1422+
jpegQualityFeature.setValue(quality.intValue());
1423+
}
1424+
14101425
public void dispose() {
14111426
Log.i(TAG, "dispose");
14121427

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,11 @@ public void setDescriptionWhileRecording(@NonNull String cameraName) {
341341
}
342342
}
343343

344+
@Override
345+
public void setImageQuality(@NonNull Long quality) {
346+
camera.setImageQuality(quality);
347+
}
348+
344349
@Override
345350
public void dispose() {
346351
if (camera != null) {

0 commit comments

Comments
 (0)