From b891e1eaab545351a4dc6cf36776bf75b49c671a Mon Sep 17 00:00:00 2001 From: Ludwig Bolling Date: Thu, 30 Apr 2026 16:10:32 +0200 Subject: [PATCH 1/2] [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. --- packages/camera/camera/CHANGELOG.md | 4 + packages/camera/camera/example/pubspec.yaml | 5 + .../camera/lib/src/camera_controller.dart | 18 +++ packages/camera/camera/pubspec.yaml | 13 ++- .../camera/test/camera_preview_test.dart | 3 + packages/camera/camera/test/camera_test.dart | 83 +++++++++++++ packages/camera/camera_android/CHANGELOG.md | 4 + .../io/flutter/plugins/camera/Camera.java | 15 +++ .../flutter/plugins/camera/CameraApiImpl.java | 5 + .../io/flutter/plugins/camera/Messages.java | 28 +++++ .../camera/features/CameraFeatureFactory.java | 11 ++ .../features/CameraFeatureFactoryImpl.java | 7 ++ .../camera/features/CameraFeatures.java | 22 ++++ .../jpegquality/JpegQualityFeature.java | 53 +++++++++ .../io/flutter/plugins/camera/CameraTest.java | 6 + .../CameraTest_getRecordingProfileTest.java | 6 + .../jpegquality/JpegQualityFeatureTest.java | 73 ++++++++++++ .../camera_android/example/pubspec.yaml | 2 +- .../lib/src/android_camera.dart | 4 + .../camera_android/lib/src/messages.g.dart | 27 +++++ .../camera_android/pigeons/messages.dart | 3 + packages/camera/camera_android/pubspec.yaml | 4 +- .../test/android_camera_test.dart | 9 ++ .../test/android_camera_test.mocks.dart | 13 ++- .../camera_android_camerax/CHANGELOG.md | 1 + .../plugins/camerax/CameraXLibrary.g.kt | 6 +- .../plugins/camerax/ImageCaptureProxyApi.java | 6 +- .../plugins/camerax/ImageCaptureTest.java | 19 ++- .../example/pubspec.yaml | 3 +- .../lib/src/android_camera_camerax.dart | 34 ++++++ .../lib/src/camerax_library.g.dart | 6 + .../pigeons/camerax_library.dart | 6 +- .../camera_android_camerax/pubspec.yaml | 2 +- .../test/android_camera_camerax_test.dart | 109 ++++++++++++++++++ .../test/preview_rotation_test.dart | 1 + .../camera/camera_avfoundation/CHANGELOG.md | 4 + .../CameraPluginDelegatingMethodTests.swift | 22 ++++ .../ios/RunnerTests/Mocks/MockCamera.swift | 5 + .../camera_avfoundation/example/pubspec.yaml | 2 +- .../Sources/camera_avfoundation/Camera.swift | 1 + .../camera_avfoundation/CameraPlugin.swift | 9 ++ .../camera_avfoundation/DefaultCamera.swift | 16 +++ .../camera_avfoundation/Messages.swift | 23 ++++ .../lib/src/avfoundation_camera.dart | 5 + .../lib/src/messages.g.dart | 25 ++++ .../camera_avfoundation/pigeons/messages.dart | 5 + .../camera/camera_avfoundation/pubspec.yaml | 4 +- .../test/avfoundation_camera_test.dart | 6 + .../test/avfoundation_camera_test.mocks.dart | 10 ++ 49 files changed, 729 insertions(+), 19 deletions(-) create mode 100644 packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/jpegquality/JpegQualityFeature.java create mode 100644 packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/jpegquality/JpegQualityFeatureTest.java diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 557e16ea59e4..46cfad017a1a 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+1 * Makes `Optional.of` constructor `const`. diff --git a/packages/camera/camera/example/pubspec.yaml b/packages/camera/camera/example/pubspec.yaml index b5d9dff6e913..1b82bcd3ed4e 100644 --- a/packages/camera/camera/example/pubspec.yaml +++ b/packages/camera/camera/example/pubspec.yaml @@ -31,3 +31,8 @@ dev_dependencies: flutter: uses-material-design: true +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + camera_android_camerax: {path: ../../../../packages/camera/camera_android_camerax} + camera_avfoundation: {path: ../../../../packages/camera/camera_avfoundation} diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index d941c2e85f66..1683ac8c2f0c 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -994,6 +994,24 @@ class CameraController extends ValueNotifier { } } + /// Sets the JPEG compression quality for still image capture. + /// + /// The [quality] must be between 1 (lowest) and 100 (highest). + 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 2d5967181c32..0425307fef98 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+1 +version: 0.12.1 environment: sdk: ^3.9.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.1 + camera_avfoundation: ^0.10.1 + camera_platform_interface: ^2.13.0 camera_web: ^0.3.3 flutter: sdk: flutter @@ -38,3 +38,8 @@ dev_dependencies: topics: - camera +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + camera_android_camerax: {path: ../../../packages/camera/camera_android_camerax} + camera_avfoundation: {path: ../../../packages/camera/camera_avfoundation} diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index 912a583e9255..cfd3af86bc6a 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -149,6 +149,9 @@ class FakeController extends ValueNotifier 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 913d3391cd9e..fe95180cccd0 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -885,6 +885,83 @@ 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( @@ -4152,6 +4229,12 @@ class MockCameraPlatform extends Mock ) 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 { diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 0bf9721e6ab9..6276e41013da 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.11 + +* Adds `setJpegImageQuality` for controlling JPEG compression quality. + ## 0.10.10+16 * Updates build files from Groovy to Kotlin. diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java index 818151d754fe..5f59bc425495 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -53,6 +53,7 @@ import io.flutter.plugins.camera.features.flash.FlashMode; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; +import io.flutter.plugins.camera.features.jpegquality.JpegQualityFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager; @@ -1416,6 +1417,20 @@ public void setDescriptionWhileRecording(CameraProperties properties) { } } + /** + * Sets the JPEG compression quality for still image capture. + * + * @param quality JPEG quality value between 1 and 100. + */ + public void setJpegImageQuality(@NonNull Long quality) { + JpegQualityFeature jpegQualityFeature = cameraFeatures.getJpegQuality(); + if (jpegQualityFeature == null) { + jpegQualityFeature = cameraFeatureFactory.createJpegQualityFeature(cameraProperties); + cameraFeatures.setJpegQuality(jpegQualityFeature); + } + jpegQualityFeature.setValue(quality.intValue()); + } + public void dispose() { Log.i(TAG, "dispose"); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java index b3b04d5309e5..28a8d9a17a27 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java @@ -343,6 +343,11 @@ public void setDescriptionWhileRecording(@NonNull String cameraName) { } } + @Override + public void setJpegImageQuality(@NonNull Long quality) { + camera.setJpegImageQuality(quality); + } + @Override public void dispose() { if (camera != null) { diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java index e61e0c48c199..0670586bd782 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java @@ -1079,6 +1079,9 @@ void create( */ void setDescriptionWhileRecording(@NonNull String description); + /** Sets the JPEG compression quality for still image capture. */ + void setJpegImageQuality(@NonNull Long quality); + /** The codec used by CameraApi. */ static @NonNull MessageCodec getCodec() { return PigeonCodec.INSTANCE; @@ -1809,6 +1812,31 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setJpegImageQuality" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + Long qualityArg = (Long) args.get(0); + try { + api.setJpegImageQuality(qualityArg); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java index 4027e665e71a..7f5ad2e6b9dd 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java @@ -15,6 +15,7 @@ import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; +import io.flutter.plugins.camera.features.jpegquality.JpegQualityFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; @@ -157,4 +158,14 @@ ExposurePointFeature createExposurePointFeature( */ @NonNull NoiseReductionFeature createNoiseReductionFeature(@NonNull CameraProperties cameraProperties); + + /** + * Creates a new instance of the JPEG quality feature. + * + * @param cameraProperties instance of the CameraProperties class containing information about the + * cameras features. + * @return newly created instance of the JpegQualityFeature class. + */ + @NonNull + JpegQualityFeature createJpegQualityFeature(@NonNull CameraProperties cameraProperties); } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java index c333d8f485ad..91ff9d991a48 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java @@ -15,6 +15,7 @@ import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; +import io.flutter.plugins.camera.features.jpegquality.JpegQualityFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; @@ -105,4 +106,10 @@ public NoiseReductionFeature createNoiseReductionFeature( @NonNull CameraProperties cameraProperties) { return new NoiseReductionFeature(cameraProperties); } + + @NonNull + @Override + public JpegQualityFeature createJpegQualityFeature(@NonNull CameraProperties cameraProperties) { + return new JpegQualityFeature(cameraProperties); + } } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java index c700e3b2c184..69068cec7050 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java @@ -6,6 +6,7 @@ import android.app.Activity; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.DartMessenger; import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature; @@ -15,6 +16,7 @@ import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; +import io.flutter.plugins.camera.features.jpegquality.JpegQualityFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; @@ -41,6 +43,7 @@ public class CameraFeatures { private static final String REGION_BOUNDARIES = "REGION_BOUNDARIES"; private static final String RESOLUTION = "RESOLUTION"; private static final String SENSOR_ORIENTATION = "SENSOR_ORIENTATION"; + private static final String JPEG_QUALITY = "JPEG_QUALITY"; private static final String ZOOM_LEVEL = "ZOOM_LEVEL"; @NonNull @@ -297,4 +300,23 @@ public ZoomLevelFeature getZoomLevel() { public void setZoomLevel(@NonNull ZoomLevelFeature zoomLevel) { this.featureMap.put(ZOOM_LEVEL, zoomLevel); } + + /** + * Gets the JPEG quality feature if it has been set. + * + * @return the JPEG quality feature, or null if not set. + */ + @Nullable + public JpegQualityFeature getJpegQuality() { + return (JpegQualityFeature) featureMap.get(JPEG_QUALITY); + } + + /** + * Sets the instance of the JPEG quality feature. + * + * @param jpegQuality the {@link JpegQualityFeature} instance to set. + */ + public void setJpegQuality(@NonNull JpegQualityFeature jpegQuality) { + this.featureMap.put(JPEG_QUALITY, jpegQuality); + } } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/jpegquality/JpegQualityFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/jpegquality/JpegQualityFeature.java new file mode 100644 index 000000000000..5b07b82fccda --- /dev/null +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/jpegquality/JpegQualityFeature.java @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camera.features.jpegquality; + +import android.annotation.SuppressLint; +import android.hardware.camera2.CaptureRequest; +import androidx.annotation.NonNull; +import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.features.CameraFeature; + +/** Controls the JPEG compression quality on the {@link android.hardware.camera2} API. */ +public class JpegQualityFeature extends CameraFeature { + private int currentSetting = 100; + + /** + * Creates a new instance of the {@link JpegQualityFeature}. + * + * @param cameraProperties Collection of characteristics for the current camera device. + */ + public JpegQualityFeature(@NonNull CameraProperties cameraProperties) { + super(cameraProperties); + } + + @NonNull + @Override + public String getDebugName() { + return "JpegQualityFeature"; + } + + @SuppressLint("KotlinPropertyAccess") + @NonNull + @Override + public Integer getValue() { + return currentSetting; + } + + @Override + public void setValue(@NonNull Integer value) { + this.currentSetting = value; + } + + @Override + public boolean checkIsSupported() { + return true; + } + + @Override + public void updateBuilder(@NonNull CaptureRequest.Builder requestBuilder) { + requestBuilder.set(CaptureRequest.JPEG_QUALITY, (byte) currentSetting); + } +} diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java index 85e317cd0c0b..108225e06055 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java @@ -45,6 +45,7 @@ import io.flutter.plugins.camera.features.flash.FlashMode; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; +import io.flutter.plugins.camera.features.jpegquality.JpegQualityFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; @@ -1468,5 +1469,10 @@ public NoiseReductionFeature createNoiseReductionFeature( @NonNull CameraProperties cameraProperties) { return mockNoiseReductionFeature; } + + @Override + public JpegQualityFeature createJpegQualityFeature(@NonNull CameraProperties cameraProperties) { + return mock(JpegQualityFeature.class); + } } } diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest_getRecordingProfileTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest_getRecordingProfileTest.java index 4094be54f4d4..c6998199ae61 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest_getRecordingProfileTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest_getRecordingProfileTest.java @@ -26,6 +26,7 @@ import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; +import io.flutter.plugins.camera.features.jpegquality.JpegQualityFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; @@ -200,5 +201,10 @@ public NoiseReductionFeature createNoiseReductionFeature( @NonNull CameraProperties cameraProperties) { return mockNoiseReductionFeature; } + + @Override + public JpegQualityFeature createJpegQualityFeature(@NonNull CameraProperties cameraProperties) { + return mock(JpegQualityFeature.class); + } } } diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/jpegquality/JpegQualityFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/jpegquality/JpegQualityFeatureTest.java new file mode 100644 index 000000000000..6c873e7ca589 --- /dev/null +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/jpegquality/JpegQualityFeatureTest.java @@ -0,0 +1,73 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camera.features.jpegquality; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.hardware.camera2.CaptureRequest; +import io.flutter.plugins.camera.CameraProperties; +import org.junit.Test; + +public class JpegQualityFeatureTest { + @Test + public void getDebugName_shouldReturnTheNameOfTheFeature() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + JpegQualityFeature jpegQualityFeature = new JpegQualityFeature(mockCameraProperties); + + assertEquals("JpegQualityFeature", jpegQualityFeature.getDebugName()); + } + + @Test + public void getValue_shouldReturn100IfNotSet() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + JpegQualityFeature jpegQualityFeature = new JpegQualityFeature(mockCameraProperties); + + assertEquals(Integer.valueOf(100), jpegQualityFeature.getValue()); + } + + @Test + public void getValue_shouldEchoTheSetValue() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + JpegQualityFeature jpegQualityFeature = new JpegQualityFeature(mockCameraProperties); + + jpegQualityFeature.setValue(50); + assertEquals(Integer.valueOf(50), jpegQualityFeature.getValue()); + } + + @Test + public void checkIsSupported_shouldReturnTrue() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + JpegQualityFeature jpegQualityFeature = new JpegQualityFeature(mockCameraProperties); + + assertTrue(jpegQualityFeature.checkIsSupported()); + } + + @Test + public void updateBuilder_shouldSetJpegQualityOnBuilder() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + JpegQualityFeature jpegQualityFeature = new JpegQualityFeature(mockCameraProperties); + + jpegQualityFeature.setValue(75); + jpegQualityFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, times(1)).set(CaptureRequest.JPEG_QUALITY, (byte) 75); + } + + @Test + public void updateBuilder_shouldSetDefaultQualityWhenNotExplicitlySet() { + CameraProperties mockCameraProperties = mock(CameraProperties.class); + CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); + JpegQualityFeature jpegQualityFeature = new JpegQualityFeature(mockCameraProperties); + + jpegQualityFeature.updateBuilder(mockBuilder); + + verify(mockBuilder, times(1)).set(CaptureRequest.JPEG_QUALITY, (byte) 100); + } +} diff --git a/packages/camera/camera_android/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index f4d614defd67..505e8dcdd0ca 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - camera_platform_interface: ^2.6.0 + camera_platform_interface: ^2.13.0 flutter: sdk: flutter path_provider: ^2.0.0 diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index 9f7d580f2364..e79d4ef1f55b 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -380,6 +380,10 @@ class AndroidCamera extends CameraPlatform { await _hostApi.setDescriptionWhileRecording(description.name); } + @override + Future setJpegImageQuality(int cameraId, int quality) => + _hostApi.setJpegImageQuality(quality); + @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_android/lib/src/messages.g.dart b/packages/camera/camera_android/lib/src/messages.g.dart index e4c047452511..f6ae1293e4d9 100644 --- a/packages/camera/camera_android/lib/src/messages.g.dart +++ b/packages/camera/camera_android/lib/src/messages.g.dart @@ -1255,6 +1255,33 @@ class CameraApi { return; } } + + Future setJpegImageQuality(int quality) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setJpegImageQuality$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [quality], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } } /// Handles calls from native side to Dart that are not camera-specific. diff --git a/packages/camera/camera_android/pigeons/messages.dart b/packages/camera/camera_android/pigeons/messages.dart index 4489fe3f6fb8..a8f1bd582270 100644 --- a/packages/camera/camera_android/pigeons/messages.dart +++ b/packages/camera/camera_android/pigeons/messages.dart @@ -207,6 +207,9 @@ abstract class CameraApi { /// /// This should be called only while video recording is active. void setDescriptionWhileRecording(String description); + + /// Sets the JPEG compression quality for still image capture. + void setJpegImageQuality(int quality); } /// Handles calls from native side to Dart that are not camera-specific. diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 34ffc900e502..d532e9fd41b2 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.10+16 +version: 0.10.11 environment: sdk: ^3.9.0 @@ -19,7 +19,7 @@ flutter: dartPluginClass: AndroidCamera dependencies: - camera_platform_interface: ^2.9.0 + camera_platform_interface: ^2.13.0 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.2 diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index 8321001ed954..de6c4eee7057 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -873,5 +873,14 @@ void main() { verify(mockCameraApi.startImageStream()).called(1); verify(mockCameraApi.stopImageStream()).called(1); }); + + test('Should set the image quality', () async { + // Arrange + // Act + await camera.setJpegImageQuality(cameraId, 50); + + // Assert + verify(mockCameraApi.setJpegImageQuality(50)).called(1); + }); }); } diff --git a/packages/camera/camera_android/test/android_camera_test.mocks.dart b/packages/camera/camera_android/test/android_camera_test.mocks.dart index 53cd6e9489a1..e03db33b106d 100644 --- a/packages/camera/camera_android/test/android_camera_test.mocks.dart +++ b/packages/camera/camera_android/test/android_camera_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in camera_android/test/android_camera_test.dart. // Do not manually edit this file. @@ -17,10 +17,12 @@ import 'package:mockito/src/dummies.dart' as _i3; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member /// A class which mocks [CameraApi]. /// @@ -316,4 +318,13 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi { returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); + + @override + _i4.Future setJpegImageQuality(int? quality) => + (super.noSuchMethod( + Invocation.method(#setJpegImageQuality, [quality]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); } diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 191d67e7a1c8..db6f5da368ce 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.7.2 +* Adds `setJpegImageQuality` for controlling JPEG compression quality. * Bumps camerax_version from 1.5.3 to 1.6.0. ## 0.7.1+2 diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt index 2bdf4371dfed..8945b77c0101 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt @@ -4247,7 +4247,8 @@ abstract class PigeonApiImageCapture( abstract fun pigeon_defaultConstructor( resolutionSelector: androidx.camera.core.resolutionselector.ResolutionSelector?, targetRotation: Long?, - flashMode: CameraXFlashMode? + flashMode: CameraXFlashMode?, + jpegQuality: Long? ): androidx.camera.core.ImageCapture abstract fun resolutionSelector( @@ -4288,11 +4289,12 @@ abstract class PigeonApiImageCapture( args[1] as androidx.camera.core.resolutionselector.ResolutionSelector? val targetRotationArg = args[2] as Long? val flashModeArg = args[3] as CameraXFlashMode? + val jpegQualityArg = args[4] as Long? val wrapped: List = try { api.pigeonRegistrar.instanceManager.addDartCreatedInstance( api.pigeon_defaultConstructor( - resolutionSelectorArg, targetRotationArg, flashModeArg), + resolutionSelectorArg, targetRotationArg, flashModeArg, jpegQualityArg), pigeon_identifierArg) listOf(null) } catch (exception: Throwable) { diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java index 78ba586b2314..eeb4731b227e 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java @@ -40,7 +40,8 @@ public ProxyApiRegistrar getPigeonRegistrar() { public ImageCapture pigeon_defaultConstructor( @Nullable ResolutionSelector resolutionSelector, @Nullable Long targetRotation, - @Nullable CameraXFlashMode flashMode) { + @Nullable CameraXFlashMode flashMode, + @Nullable Long jpegQuality) { final ImageCapture.Builder builder = new ImageCapture.Builder(); if (targetRotation != null) { builder.setTargetRotation(targetRotation.intValue()); @@ -62,6 +63,9 @@ public ImageCapture pigeon_defaultConstructor( if (resolutionSelector != null) { builder.setResolutionSelector(resolutionSelector); } + if (jpegQuality != null) { + builder.setJpegQuality(jpegQuality.intValue()); + } return builder.build(); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java index 5c84dff947df..13f4397fbe29 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java @@ -39,13 +39,30 @@ public void pigeon_defaultConstructor_createsImageCaptureWithCorrectConfiguratio final long targetResolution = Surface.ROTATION_0; final ImageCapture imageCapture = api.pigeon_defaultConstructor( - mockResolutionSelector, targetResolution, CameraXFlashMode.OFF); + mockResolutionSelector, targetResolution, CameraXFlashMode.OFF, null); assertEquals(imageCapture.getResolutionSelector(), mockResolutionSelector); assertEquals(imageCapture.getTargetRotation(), Surface.ROTATION_0); assertEquals(imageCapture.getFlashMode(), ImageCapture.FLASH_MODE_OFF); } + @Test + public void pigeon_defaultConstructor_setsJpegQualityWhenProvided() { + final PigeonApiImageCapture api = new TestProxyApiRegistrar().getPigeonApiImageCapture(); + + final ResolutionSelector mockResolutionSelector = new ResolutionSelector.Builder().build(); + final long targetRotation = Surface.ROTATION_0; + final long jpegQuality = 75; + final ImageCapture imageCapture = + api.pigeon_defaultConstructor( + mockResolutionSelector, targetRotation, CameraXFlashMode.OFF, jpegQuality); + + assertEquals(imageCapture.getResolutionSelector(), mockResolutionSelector); + assertEquals(imageCapture.getTargetRotation(), Surface.ROTATION_0); + assertEquals(imageCapture.getFlashMode(), ImageCapture.FLASH_MODE_OFF); + assertEquals(imageCapture.getJpegQuality(), 75); + } + @Test public void resolutionSelector_returnsExpectedResolutionSelector() { final PigeonApiImageCapture api = new TestProxyApiRegistrar().getPigeonApiImageCapture(); diff --git a/packages/camera/camera_android_camerax/example/pubspec.yaml b/packages/camera/camera_android_camerax/example/pubspec.yaml index 81a4e079b2ae..8fc5e2380436 100644 --- a/packages/camera/camera_android_camerax/example/pubspec.yaml +++ b/packages/camera/camera_android_camerax/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - camera_platform_interface: ^2.6.0 + camera_platform_interface: ^2.13.0 flutter: sdk: flutter video_player: ^2.7.0 @@ -28,4 +28,3 @@ dev_dependencies: flutter: uses-material-design: true - diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index e52e4bd09cf1..bffcee28b3ec 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -205,6 +205,13 @@ class AndroidCameraCameraX extends CameraPlatform { @visibleForTesting bool captureOrientationLocked = false; + /// The target rotation set by [lockCaptureOrientation], if any. + /// + /// Used to preserve the locked rotation when recreating use cases (e.g., + /// in [setJpegImageQuality]). + @visibleForTesting + int? lockedCaptureOrientation; + /// Whether or not the default rotation for [UseCase]s needs to be set /// manually because the capture orientation was previously locked. /// @@ -588,6 +595,7 @@ class AndroidCameraCameraX extends CameraPlatform { final int targetLockedRotation = _getRotationConstantFromDeviceOrientation( orientation, ); + lockedCaptureOrientation = targetLockedRotation; // Update UseCases to use target device orientation. await imageCapture!.setTargetRotation(targetLockedRotation); @@ -600,6 +608,7 @@ class AndroidCameraCameraX extends CameraPlatform { Future unlockCaptureOrientation(int cameraId) async { // Flag that default rotation should be set for UseCases as needed. captureOrientationLocked = false; + lockedCaptureOrientation = null; } /// Sets the exposure point for automatically determining the exposure values for @@ -1126,6 +1135,31 @@ class AndroidCameraCameraX extends CameraPlatform { } } + /// Sets the JPEG compression quality for still image capture. + /// + /// CameraX only supports setting JPEG quality via `ImageCapture.Builder` + /// at construction time, so this recreates the `ImageCapture` use case + /// with the requested quality. The next call to [takePicture] will bind + /// the new instance automatically. + @override + Future setJpegImageQuality(int cameraId, int quality) async { + // Unbind the current ImageCapture if it exists and is bound. + if (imageCapture != null) { + await _unbindUseCaseFromLifecycle(imageCapture!); + } + + // Recreate ImageCapture with the requested JPEG quality. + // Preserve locked orientation if set, otherwise use default display rotation. + final int targetRotation = + lockedCaptureOrientation ?? + await deviceOrientationManager.getDefaultDisplayRotation(); + imageCapture = ImageCapture( + resolutionSelector: _presetResolutionSelector, + targetRotation: targetRotation, + jpegQuality: quality, + ); + } + /// Prepare the capture session for video recording. /// /// This optimization is not used on Android, so this implementation is a diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart index 7b642695e0b0..6d06b565b09c 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart @@ -123,6 +123,7 @@ class PigeonOverrides { ResolutionSelector? resolutionSelector, int? targetRotation, CameraXFlashMode? flashMode, + int? jpegQuality, })? imageCapture_new; @@ -5118,12 +5119,14 @@ class ImageCapture extends UseCase { ResolutionSelector? resolutionSelector, int? targetRotation, CameraXFlashMode? flashMode, + int? jpegQuality, }) { if (PigeonOverrides.imageCapture_new != null) { return PigeonOverrides.imageCapture_new!( resolutionSelector: resolutionSelector, targetRotation: targetRotation, flashMode: flashMode, + jpegQuality: jpegQuality, ); } return ImageCapture.pigeon_new( @@ -5132,6 +5135,7 @@ class ImageCapture extends UseCase { resolutionSelector: resolutionSelector, targetRotation: targetRotation, flashMode: flashMode, + jpegQuality: jpegQuality, ); } @@ -5142,6 +5146,7 @@ class ImageCapture extends UseCase { this.resolutionSelector, int? targetRotation, CameraXFlashMode? flashMode, + int? jpegQuality, }) : super.pigeon_detached() { final int pigeonVar_instanceIdentifier = pigeon_instanceManager .addDartCreatedInstance(this); @@ -5161,6 +5166,7 @@ class ImageCapture extends UseCase { resolutionSelector, targetRotation, flashMode, + jpegQuality, ], ); () async { diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 611157f65248..479dc0a6829d 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -598,7 +598,11 @@ enum CameraXFlashMode { ), ) abstract class ImageCapture extends UseCase { - ImageCapture(int? targetRotation, CameraXFlashMode? flashMode); + ImageCapture( + int? targetRotation, + CameraXFlashMode? flashMode, + int? jpegQuality, + ); late final ResolutionSelector? resolutionSelector; diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 6e928e2518aa..45dd2812c1ca 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -19,7 +19,7 @@ flutter: dependencies: async: ^2.5.0 - camera_platform_interface: ^2.12.0 + camera_platform_interface: ^2.13.0 flutter: sdk: flutter meta: ^1.7.0 diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 71ed3d58ccde..5f68b48b3802 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -197,6 +197,7 @@ void main() { int? targetRotation, CameraXFlashMode? flashMode, ResolutionSelector? resolutionSelector, + int? jpegQuality, }) { final mockImageCapture = MockImageCapture(); when( @@ -630,6 +631,7 @@ void main() { int? targetRotation, CameraXFlashMode? flashMode, ResolutionSelector? resolutionSelector, + int? jpegQuality, }) { return mockImageCapture; }; @@ -1281,6 +1283,7 @@ void main() { int? targetRotation, CameraXFlashMode? flashMode, ResolutionSelector? resolutionSelector, + int? jpegQuality, }) { return mockImageCapture; }; @@ -1771,6 +1774,7 @@ void main() { int? targetRotation, CameraXFlashMode? flashMode, ResolutionSelector? resolutionSelector, + int? jpegQuality, }) { return mockImageCapture; }; @@ -2195,6 +2199,7 @@ void main() { int? targetRotation, CameraXFlashMode? flashMode, ResolutionSelector? resolutionSelector, + int? jpegQuality, }) => mockImageCapture; PigeonOverrides.recorder_new = ({ @@ -3364,6 +3369,7 @@ void main() { CameraXFlashMode? flashMode, ResolutionSelector? resolutionSelector, int? targetRotation, + int? jpegQuality, }) { return mockImageCapture; }; @@ -3627,6 +3633,7 @@ void main() { CameraXFlashMode? flashMode, ResolutionSelector? resolutionSelector, int? targetRotation, + int? jpegQuality, }) { return mockImageCapture; }; @@ -3905,6 +3912,108 @@ void main() { }, ); + test( + 'setJpegImageQuality unbinds and recreates ImageCapture with requested quality', + () async { + final camera = AndroidCameraCameraX(); + final mockProcessCameraProvider = MockProcessCameraProvider(); + final mockDeviceOrientationManager = MockDeviceOrientationManager(); + final mockImageCapture = MockImageCapture(); + final mockNewImageCapture = MockImageCapture(); + const int defaultTargetRotation = Surface.rotation90; + const jpegQuality = 73; + const cameraId = 9; + int? actualTargetRotation; + int? actualJpegQuality; + + camera.processCameraProvider = mockProcessCameraProvider; + camera.imageCapture = mockImageCapture; + + PigeonOverrides.deviceOrientationManager_new = + ({ + required void Function(DeviceOrientationManager, String) + onDeviceOrientationChanged, + }) { + when( + mockDeviceOrientationManager.getDefaultDisplayRotation(), + ).thenAnswer((_) async => defaultTargetRotation); + return mockDeviceOrientationManager; + }; + PigeonOverrides.imageCapture_new = + ({ + int? targetRotation, + CameraXFlashMode? flashMode, + ResolutionSelector? resolutionSelector, + int? jpegQuality, + }) { + actualTargetRotation = targetRotation; + actualJpegQuality = jpegQuality; + return mockNewImageCapture; + }; + + when( + mockProcessCameraProvider.isBound(mockImageCapture), + ).thenAnswer((_) async => true); + + await camera.setJpegImageQuality(cameraId, jpegQuality); + + verify( + mockProcessCameraProvider.unbind([mockImageCapture]), + ).called(1); + verify( + mockDeviceOrientationManager.getDefaultDisplayRotation(), + ).called(1); + expect(actualTargetRotation, defaultTargetRotation); + expect(actualJpegQuality, jpegQuality); + expect(camera.imageCapture, same(mockNewImageCapture)); + }, + ); + + test( + 'setJpegImageQuality preserves locked target rotation when recreating ImageCapture', + () async { + final camera = AndroidCameraCameraX(); + final mockDeviceOrientationManager = MockDeviceOrientationManager(); + final mockNewImageCapture = MockImageCapture(); + const int lockedTargetRotation = Surface.rotation270; + const jpegQuality = 64; + const cameraId = 11; + int? actualTargetRotation; + int? actualJpegQuality; + + camera.lockedCaptureOrientation = lockedTargetRotation; + + PigeonOverrides.deviceOrientationManager_new = + ({ + required void Function(DeviceOrientationManager, String) + onDeviceOrientationChanged, + }) { + when( + mockDeviceOrientationManager.getDefaultDisplayRotation(), + ).thenAnswer((_) async => Surface.rotation0); + return mockDeviceOrientationManager; + }; + PigeonOverrides.imageCapture_new = + ({ + int? targetRotation, + CameraXFlashMode? flashMode, + ResolutionSelector? resolutionSelector, + int? jpegQuality, + }) { + actualTargetRotation = targetRotation; + actualJpegQuality = jpegQuality; + return mockNewImageCapture; + }; + + await camera.setJpegImageQuality(cameraId, jpegQuality); + + verifyNever(mockDeviceOrientationManager.getDefaultDisplayRotation()); + expect(actualTargetRotation, lockedTargetRotation); + expect(actualJpegQuality, jpegQuality); + expect(camera.imageCapture, same(mockNewImageCapture)); + }, + ); + test( 'takePicture turns non-torch flash mode off when torch mode enabled', () async { diff --git a/packages/camera/camera_android_camerax/test/preview_rotation_test.dart b/packages/camera/camera_android_camerax/test/preview_rotation_test.dart index cfc6b2db103b..9d7b2531ba09 100644 --- a/packages/camera/camera_android_camerax/test/preview_rotation_test.dart +++ b/packages/camera/camera_android_camerax/test/preview_rotation_test.dart @@ -123,6 +123,7 @@ void main() { int? targetRotation, CameraXFlashMode? flashMode, ResolutionSelector? resolutionSelector, + int? jpegQuality, }) => MockImageCapture(); PigeonOverrides.recorder_new = ({ diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index ca97b101df8b..fe3789d174a1 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.2 + +* Adds `setJpegImageQuality` for controlling JPEG compression quality. + ## 0.10.1 * Fixes fatal crash on iPhone 17 when using `ResolutionPreset.max`. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPluginDelegatingMethodTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPluginDelegatingMethodTests.swift index 34a306b511d7..f01da07aa12f 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPluginDelegatingMethodTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPluginDelegatingMethodTests.swift @@ -251,6 +251,28 @@ final class CameraPluginDelegatingMethodTests: XCTestCase { XCTAssertTrue(setImageFileFormatCalled) } + func testSetImageQuality_callsCameraSetImageQuality() { + let (cameraPlugin, mockCamera) = createCameraPlugin() + let expectation = expectation(description: "Call completed") + + let targetQuality: Int64 = 50 + + var setJpegImageQualityCalled = false + mockCamera.setJpegImageQualityStub = { quality in + XCTAssertEqual(quality, targetQuality) + setJpegImageQualityCalled = true + } + + cameraPlugin.setJpegImageQuality(quality: targetQuality) { result in + let _ = self.assertSuccess(result) + expectation.fulfill() + } + + waitForExpectations(timeout: 30, handler: nil) + + XCTAssertTrue(setJpegImageQualityCalled) + } + func testStartImageStream_callsCameraStartImageStream() { let (cameraPlugin, mockCamera) = createCameraPlugin() let expectation = expectation(description: "Call completed") diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift index ec6e6fd88f9c..569f55630155 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift @@ -27,6 +27,7 @@ final class MockCamera: NSObject, Camera { var lockCaptureOrientationStub: ((PlatformDeviceOrientation) -> Void)? var unlockCaptureOrientationStub: (() -> Void)? var setImageFileFormatStub: ((PlatformImageFileFormat) -> Void)? + var setJpegImageQualityStub: ((Int64) -> Void)? var setExposureModeStub: ((PlatformExposureMode) -> Void)? var setExposureOffsetStub: ((Double) -> Void)? var setExposurePointStub: ((PlatformPoint?, @escaping (Result) -> Void) -> Void)? @@ -144,6 +145,10 @@ final class MockCamera: NSObject, Camera { setImageFileFormatStub?(fileFormat) } + func setJpegImageQuality(_ quality: Int64) { + setJpegImageQualityStub?(quality) + } + func setExposureMode(_ mode: PlatformExposureMode) { setExposureModeStub?(mode) } diff --git a/packages/camera/camera_avfoundation/example/pubspec.yaml b/packages/camera/camera_avfoundation/example/pubspec.yaml index 4b9a5b5a5e53..d9cce5808257 100644 --- a/packages/camera/camera_avfoundation/example/pubspec.yaml +++ b/packages/camera/camera_avfoundation/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - camera_platform_interface: ^2.10.0 + camera_platform_interface: ^2.13.0 flutter: sdk: flutter path_provider: ^2.0.0 diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift index 117fd909d32e..02e9503f6adc 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift @@ -62,6 +62,7 @@ protocol Camera: FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate, func unlockCaptureOrientation() func setImageFileFormat(_ fileFormat: PlatformImageFileFormat) + func setJpegImageQuality(_ quality: Int64) func setExposureMode(_ mode: PlatformExposureMode) func setExposureOffset(_ offset: Double) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift index 43ef5f48916c..136c05f18d58 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift @@ -555,4 +555,13 @@ extension CameraPlugin: CameraApi { completion(.success(())) } } + + func setJpegImageQuality( + quality: Int64, completion: @escaping (Result) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.setJpegImageQuality(quality) + completion(.success(())) + } + } } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift index 16d1637d23fa..d702248a6d7b 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift @@ -121,6 +121,7 @@ final class DefaultCamera: NSObject, Camera { private var maxStreamingPendingFramesCount = 4 private var fileFormat = PlatformImageFileFormat.jpeg + private var imageQuality: Int64 = 100 private var lockedCaptureOrientation = UIDeviceOrientation.unknown private var exposureMode = PlatformExposureMode.auto private var focusMode = PlatformFocusMode.auto @@ -719,6 +720,17 @@ final class DefaultCamera: NSObject, Camera { fileExtension = "heif" } else { fileExtension = "jpg" + if imageQuality < 100 { + settings = AVCapturePhotoSettings(format: [ + AVVideoCodecKey: AVVideoCodecType.jpeg, + AVVideoCompressionPropertiesKey: [ + AVVideoQualityKey: CGFloat(imageQuality) / 100.0 + ], + ]) + if mediaSettings.resolutionPreset == .max { + settings.isHighResolutionPhotoEnabled = true + } + } } if flashMode != .torch { @@ -842,6 +854,10 @@ final class DefaultCamera: NSObject, Camera { self.fileFormat = fileFormat } + func setJpegImageQuality(_ quality: Int64) { + self.imageQuality = quality + } + func setExposureMode(_ mode: PlatformExposureMode) { exposureMode = mode applyExposureMode() diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Messages.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Messages.swift index 47e6abbbe750..e170c2acb97a 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Messages.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Messages.swift @@ -723,6 +723,9 @@ protocol CameraApi { /// Sets the file format used for taking pictures. func setImageFileFormat( format: PlatformImageFileFormat, completion: @escaping (Result) -> Void) + /// Sets the JPEG compression quality for still image capture. + func setJpegImageQuality( + quality: Int64, completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -1363,6 +1366,26 @@ class CameraApiSetup { } else { setImageFileFormatChannel.setMessageHandler(nil) } + /// Sets the JPEG compression quality for still image capture. + let setJpegImageQualityChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.camera_avfoundation.CameraApi.setJpegImageQuality\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setJpegImageQualityChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let qualityArg = args[0] is Int64 ? args[0] as! Int64 : Int64(args[0] as! Int32) + api.setJpegImageQuality(quality: qualityArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + setJpegImageQualityChannel.setMessageHandler(nil) + } } } diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 3907ed89219b..2327e66314c5 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -442,6 +442,11 @@ class AVFoundationCamera extends CameraPlatform { await _hostApi.setImageFileFormat(_pigeonImageFileFormat(format)); } + @override + Future setJpegImageQuality(int cameraId, int quality) async { + await _hostApi.setJpegImageQuality(quality); + } + @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_avfoundation/lib/src/messages.g.dart b/packages/camera/camera_avfoundation/lib/src/messages.g.dart index 46c94d58f8a1..8371f6481f9f 100644 --- a/packages/camera/camera_avfoundation/lib/src/messages.g.dart +++ b/packages/camera/camera_avfoundation/lib/src/messages.g.dart @@ -1487,6 +1487,31 @@ class CameraApi { return; } } + + Future setJpegImageQuality(int quality) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setJpegImageQuality$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [quality], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } } Stream imageDataStream({String instanceName = ''}) { diff --git a/packages/camera/camera_avfoundation/pigeons/messages.dart b/packages/camera/camera_avfoundation/pigeons/messages.dart index b4976758cf47..2d0e3ceec187 100644 --- a/packages/camera/camera_avfoundation/pigeons/messages.dart +++ b/packages/camera/camera_avfoundation/pigeons/messages.dart @@ -349,6 +349,11 @@ abstract class CameraApi { @async @ObjCSelector('setImageFileFormat:') void setImageFileFormat(PlatformImageFileFormat format); + + /// Sets the JPEG compression quality for still image capture. + @async + @ObjCSelector('setJpegImageQuality:') + void setJpegImageQuality(int quality); } @EventChannelApi() diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 8a10d0d21f72..7ff9cd278609 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.1 +version: 0.10.2 environment: sdk: ^3.9.0 @@ -17,7 +17,7 @@ flutter: dartPluginClass: AVFoundationCamera dependencies: - camera_platform_interface: ^2.12.0 + camera_platform_interface: ^2.13.0 flutter: sdk: flutter stream_transform: ^2.0.0 diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 53d7965c6bac..733d5e0d8e05 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -992,5 +992,11 @@ void main() { verify(mockApi.setImageFileFormat(PlatformImageFileFormat.jpeg)); }); + + test('Should set the image quality', () async { + await camera.setJpegImageQuality(cameraId, 50); + + verify(mockApi.setJpegImageQuality(50)); + }); }); } diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart index 225eac9931c6..7e946cdf89f5 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart @@ -22,6 +22,7 @@ import 'package:mockito/src/dummies.dart' as _i3; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member /// A class which mocks [CameraApi]. /// @@ -360,4 +361,13 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi { returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); + + @override + _i4.Future setJpegImageQuality(int? quality) => + (super.noSuchMethod( + Invocation.method(#setJpegImageQuality, [quality]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); } From 42f3060a04940ff3cb56c64f3b367dde03815bdc Mon Sep 17 00:00:00 2001 From: Ludwig Bolling Date: Thu, 30 Apr 2026 16:27:36 +0200 Subject: [PATCH 2/2] Remove camera app-facing package changes The app-facing camera package should be submitted as a separate follow-up PR after the implementation packages are published. This removes the dependency_overrides that were blocking merge. --- packages/camera/camera/CHANGELOG.md | 4 - packages/camera/camera/example/pubspec.yaml | 5 -- .../camera/lib/src/camera_controller.dart | 18 ---- packages/camera/camera/pubspec.yaml | 13 +-- .../camera/test/camera_preview_test.dart | 3 - packages/camera/camera/test/camera_test.dart | 83 ------------------- 6 files changed, 4 insertions(+), 122 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 46cfad017a1a..557e16ea59e4 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.12.1 - -* Adds `setJpegImageQuality` for controlling JPEG compression quality. - ## 0.12.0+1 * Makes `Optional.of` constructor `const`. diff --git a/packages/camera/camera/example/pubspec.yaml b/packages/camera/camera/example/pubspec.yaml index 1b82bcd3ed4e..b5d9dff6e913 100644 --- a/packages/camera/camera/example/pubspec.yaml +++ b/packages/camera/camera/example/pubspec.yaml @@ -31,8 +31,3 @@ dev_dependencies: flutter: uses-material-design: true -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins -dependency_overrides: - camera_android_camerax: {path: ../../../../packages/camera/camera_android_camerax} - camera_avfoundation: {path: ../../../../packages/camera/camera_avfoundation} diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 1683ac8c2f0c..d941c2e85f66 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -994,24 +994,6 @@ class CameraController extends ValueNotifier { } } - /// Sets the JPEG compression quality for still image capture. - /// - /// The [quality] must be between 1 (lowest) and 100 (highest). - 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 0425307fef98..2d5967181c32 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.1 +version: 0.12.0+1 environment: sdk: ^3.9.0 @@ -21,9 +21,9 @@ flutter: default_package: camera_web dependencies: - camera_android_camerax: ^0.7.1 - camera_avfoundation: ^0.10.1 - camera_platform_interface: ^2.13.0 + camera_android_camerax: ^0.7.0 + camera_avfoundation: ^0.10.0 + camera_platform_interface: ^2.12.0 camera_web: ^0.3.3 flutter: sdk: flutter @@ -38,8 +38,3 @@ dev_dependencies: topics: - camera -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins -dependency_overrides: - camera_android_camerax: {path: ../../../packages/camera/camera_android_camerax} - camera_avfoundation: {path: ../../../packages/camera/camera_avfoundation} diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index cfd3af86bc6a..912a583e9255 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -149,9 +149,6 @@ class FakeController extends ValueNotifier 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 fe95180cccd0..913d3391cd9e 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -885,83 +885,6 @@ 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( @@ -4229,12 +4152,6 @@ class MockCameraPlatform extends Mock ) 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 {