Skip to content

Commit b891e1e

Browse files
committed
[camera] Add setJpegImageQuality for JPEG compression control
Adds setJpegImageQuality(int quality) across all camera platform implementations (Android, Android CameraX, iOS AVFoundation) and the app-facing camera package. This allows controlling the JPEG compression quality (1-100) for still image capture. Platform interface changes were landed separately in #11454.
1 parent c3360ac commit b891e1e

49 files changed

Lines changed: 729 additions & 19 deletions

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 `setJpegImageQuality` for controlling JPEG compression quality.
4+
15
## 0.12.0+1
26

37
* Makes `Optional.of` constructor `const`.

packages/camera/camera/example/pubspec.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@ 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}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,24 @@ class CameraController extends ValueNotifier<CameraValue> {
994994
}
995995
}
996996

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

packages/camera/camera/pubspec.yaml

Lines changed: 9 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+1
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,8 @@ 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}

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> setJpegImageQuality(int quality) async {}
154+
152155
@override
153156
bool supportsImageStreaming() => true;
154157
}

packages/camera/camera/test/camera_test.dart

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

888+
test('setJpegImageQuality() 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.setJpegImageQuality(50);
900+
901+
verify(
902+
CameraPlatform.instance.setJpegImageQuality(cameraController.cameraId, 50),
903+
).called(1);
904+
});
905+
906+
test(
907+
'setJpegImageQuality() throws CameraException on PlatformException',
908+
() async {
909+
final cameraController = CameraController(
910+
const CameraDescription(
911+
name: 'cam',
912+
lensDirection: CameraLensDirection.back,
913+
sensorOrientation: 90,
914+
),
915+
ResolutionPreset.max,
916+
);
917+
await cameraController.initialize();
918+
919+
when(
920+
CameraPlatform.instance.setJpegImageQuality(
921+
cameraController.cameraId,
922+
50,
923+
),
924+
).thenThrow(
925+
PlatformException(
926+
code: 'TEST_ERROR',
927+
message: 'This is a test error message',
928+
),
929+
);
930+
931+
expect(
932+
cameraController.setJpegImageQuality(50),
933+
throwsA(
934+
isA<CameraException>().having(
935+
(CameraException error) => error.description,
936+
'TEST_ERROR',
937+
'This is a test error message',
938+
),
939+
),
940+
);
941+
},
942+
);
943+
944+
test('setJpegImageQuality() throws ArgumentError for invalid values', () async {
945+
final cameraController = CameraController(
946+
const CameraDescription(
947+
name: 'cam',
948+
lensDirection: CameraLensDirection.back,
949+
sensorOrientation: 90,
950+
),
951+
ResolutionPreset.max,
952+
);
953+
await cameraController.initialize();
954+
955+
expect(
956+
() => cameraController.setJpegImageQuality(0),
957+
throwsA(isA<ArgumentError>()),
958+
);
959+
expect(
960+
() => cameraController.setJpegImageQuality(101),
961+
throwsA(isA<ArgumentError>()),
962+
);
963+
});
964+
888965
test('setExposureMode() calls $CameraPlatform', () async {
889966
final cameraController = CameraController(
890967
const CameraDescription(
@@ -4152,6 +4229,12 @@ class MockCameraPlatform extends Mock
41524229
) async => super.noSuchMethod(
41534230
Invocation.method(#setVideoStabilizationMode, <Object?>[cameraId, mode]),
41544231
);
4232+
4233+
@override
4234+
Future<void> setJpegImageQuality(int? cameraId, int? quality) async =>
4235+
super.noSuchMethod(
4236+
Invocation.method(#setJpegImageQuality, <Object?>[cameraId, quality]),
4237+
);
41554238
}
41564239

41574240
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 `setJpegImageQuality` for controlling JPEG compression quality.
4+
15
## 0.10.10+16
26

37
* Updates build files from Groovy to Kotlin.

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;
@@ -1416,6 +1417,20 @@ public void setDescriptionWhileRecording(CameraProperties properties) {
14161417
}
14171418
}
14181419

1420+
/**
1421+
* Sets the JPEG compression quality for still image capture.
1422+
*
1423+
* @param quality JPEG quality value between 1 and 100.
1424+
*/
1425+
public void setJpegImageQuality(@NonNull Long quality) {
1426+
JpegQualityFeature jpegQualityFeature = cameraFeatures.getJpegQuality();
1427+
if (jpegQualityFeature == null) {
1428+
jpegQualityFeature = cameraFeatureFactory.createJpegQualityFeature(cameraProperties);
1429+
cameraFeatures.setJpegQuality(jpegQualityFeature);
1430+
}
1431+
jpegQualityFeature.setValue(quality.intValue());
1432+
}
1433+
14191434
public void dispose() {
14201435
Log.i(TAG, "dispose");
14211436

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
@@ -343,6 +343,11 @@ public void setDescriptionWhileRecording(@NonNull String cameraName) {
343343
}
344344
}
345345

346+
@Override
347+
public void setJpegImageQuality(@NonNull Long quality) {
348+
camera.setJpegImageQuality(quality);
349+
}
350+
346351
@Override
347352
public void dispose() {
348353
if (camera != null) {

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,9 @@ void create(
10791079
*/
10801080
void setDescriptionWhileRecording(@NonNull String description);
10811081

1082+
/** Sets the JPEG compression quality for still image capture. */
1083+
void setJpegImageQuality(@NonNull Long quality);
1084+
10821085
/** The codec used by CameraApi. */
10831086
static @NonNull MessageCodec<Object> getCodec() {
10841087
return PigeonCodec.INSTANCE;
@@ -1809,6 +1812,31 @@ public void error(Throwable error) {
18091812
channel.setMessageHandler(null);
18101813
}
18111814
}
1815+
{
1816+
BasicMessageChannel<Object> channel =
1817+
new BasicMessageChannel<>(
1818+
binaryMessenger,
1819+
"dev.flutter.pigeon.camera_android.CameraApi.setJpegImageQuality"
1820+
+ messageChannelSuffix,
1821+
getCodec());
1822+
if (api != null) {
1823+
channel.setMessageHandler(
1824+
(message, reply) -> {
1825+
ArrayList<Object> wrapped = new ArrayList<>();
1826+
ArrayList<Object> args = (ArrayList<Object>) message;
1827+
Long qualityArg = (Long) args.get(0);
1828+
try {
1829+
api.setJpegImageQuality(qualityArg);
1830+
wrapped.add(0, null);
1831+
} catch (Throwable exception) {
1832+
wrapped = wrapError(exception);
1833+
}
1834+
reply.reply(wrapped);
1835+
});
1836+
} else {
1837+
channel.setMessageHandler(null);
1838+
}
1839+
}
18121840
}
18131841
}
18141842

0 commit comments

Comments
 (0)