diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 397973454698..a9be275590e0 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.1 + +* Adds `setImageQuality` for controlling JPEG compression quality. + ## 0.12.0 * Adds support for video stabilization. diff --git a/packages/camera/camera/example/pubspec.yaml b/packages/camera/camera/example/pubspec.yaml index b5d9dff6e913..8af413ad15b6 100644 --- a/packages/camera/camera/example/pubspec.yaml +++ b/packages/camera/camera/example/pubspec.yaml @@ -31,3 +31,9 @@ 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} + camera_platform_interface: {path: ../../../../packages/camera/camera_platform_interface} diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 5a6e71ae3055..00493464db4b 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -998,6 +998,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 setImageQuality(int quality) async { + if (quality < 1 || quality > 100) { + throw ArgumentError.value( + quality, + 'quality', + 'Must be between 1 and 100.', + ); + } + try { + await CameraPlatform.instance.setImageQuality(_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 1c9e8bb0b145..5e7f5712c5bc 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 +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,9 @@ 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} + camera_platform_interface: {path: ../../../packages/camera/camera_platform_interface} diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index 912a583e9255..78967a8746ff 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 setImageQuality(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 97f4f2b424a9..78193fd2da1b 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -885,6 +885,83 @@ void main() { }, ); + test('setImageQuality() calls $CameraPlatform', () async { + final cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90, + ), + ResolutionPreset.max, + ); + await cameraController.initialize(); + + await cameraController.setImageQuality(50); + + verify( + CameraPlatform.instance.setImageQuality(cameraController.cameraId, 50), + ).called(1); + }); + + test( + 'setImageQuality() 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.setImageQuality( + cameraController.cameraId, + 50, + ), + ).thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + ), + ); + + expect( + cameraController.setImageQuality(50), + throwsA( + isA().having( + (CameraException error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ), + ), + ); + }, + ); + + test('setImageQuality() 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.setImageQuality(0), + throwsA(isA()), + ); + expect( + () => cameraController.setImageQuality(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 setImageQuality(int? cameraId, int? quality) async => + super.noSuchMethod( + Invocation.method(#setImageQuality, [cameraId, quality]), + ); } class MockCameraDescription extends CameraDescription { diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 9357004bb894..c00f33202527 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.11 + +* Adds `setImageQuality` for controlling JPEG compression quality. + ## 0.10.10+15 * 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). 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 b4f89adef9a3..23b65976ad36 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; @@ -1407,6 +1408,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 setImageQuality(@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 a84c86516f37..daa3b44ccb8f 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 @@ -341,6 +341,11 @@ public void setDescriptionWhileRecording(@NonNull String cameraName) { } } + @Override + public void setImageQuality(@NonNull Long quality) { + camera.setImageQuality(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 f0c3f2ff146f..920a911e1fe0 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 @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.1.8), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.camera; @@ -1048,6 +1048,8 @@ void create( *

This should be called only while video recording is active. */ void setDescriptionWhileRecording(@NonNull String description); + /** Sets the JPEG compression quality for still image capture. */ + void setImageQuality(@NonNull Long quality); /** The codec used by CameraApi. */ static @NonNull MessageCodec getCodec() { @@ -1778,6 +1780,31 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setImageQuality" + + 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.setImageQuality(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 9b2b4ffeec81..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; @@ -1219,11 +1220,11 @@ public void startVideoRecording_shouldApplySettingsToMediaRecorder() final ResolutionPreset resolutionPreset = ResolutionPreset.high; final boolean enableAudio = true; - //region These parameters should be set in android MediaRecorder. + // region These parameters should be set in android MediaRecorder. final int fps = 15; final int videoBitrate = 200000; final int audioBitrate = 32000; - //endregion + // endregion when(mockCameraProperties.getCameraName()).thenReturn(cameraName); @@ -1303,7 +1304,7 @@ public void startVideoRecording_shouldApplySettingsToMediaRecorder() camera.startVideoRecording(null); - //region Check that FPS parameter affects AE range at which the camera captures frames. + // region Check that FPS parameter affects AE range at which the camera captures frames. assertEquals(camera.cameraFeatures.getFpsRange().getValue().getLower(), Integer.valueOf(fps)); assertEquals(camera.cameraFeatures.getFpsRange().getValue().getUpper(), Integer.valueOf(fps)); @@ -1312,15 +1313,15 @@ public void startVideoRecording_shouldApplySettingsToMediaRecorder() eq(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE), argThat( (Range range) -> range.getLower() == fps && range.getUpper() == fps)); - //endregion + // endregion final MediaRecorder recorder = camera.mediaRecorder; - //region Check that parameters affects movies, written by MediaRecorder. + // region Check that parameters affects movies, written by MediaRecorder. verify(recorder).setVideoFrameRate(fps); verify(recorder).setAudioEncodingBitRate(audioBitrate); verify(recorder).setVideoEncodingBitRate(videoBitrate); - //endregion + // endregion } } @@ -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/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index f4d614defd67..4164ee2b121a 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -31,3 +31,7 @@ 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_platform_interface: {path: ../../../../packages/camera/camera_platform_interface} diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index 9f7d580f2364..94f79968ff54 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 setImageQuality(int cameraId, int quality) => + _hostApi.setImageQuality(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..e207ae1915d3 100644 --- a/packages/camera/camera_android/lib/src/messages.g.dart +++ b/packages/camera/camera_android/lib/src/messages.g.dart @@ -1,9 +1,9 @@ // 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. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.1.8), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; @@ -385,25 +385,25 @@ class _PigeonCodec extends StandardMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 129: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : PlatformCameraLensDirection.values[value]; case 130: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : PlatformDeviceOrientation.values[value]; case 131: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : PlatformExposureMode.values[value]; case 132: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : PlatformFocusMode.values[value]; case 133: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : PlatformResolutionPreset.values[value]; case 134: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : PlatformImageFormatGroup.values[value]; case 135: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : PlatformFlashMode.values[value]; case 136: return PlatformCameraDescription.decode(readValue(buffer)!); @@ -441,17 +441,15 @@ class CameraApi { /// Returns the list of available cameras. Future> getAvailableCameras() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.getAvailableCameras$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -476,19 +474,17 @@ class CameraApi { String cameraName, PlatformMediaSettings mediaSettings, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.create$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [cameraName, mediaSettings], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -509,19 +505,17 @@ class CameraApi { /// Initializes the camera with the given ID for the given image format. Future initialize(PlatformImageFormatGroup imageFormat) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.initialize$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [imageFormat], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -537,17 +531,15 @@ class CameraApi { /// Disposes of the camera with the given ID. Future dispose() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.dispose$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -565,19 +557,17 @@ class CameraApi { Future lockCaptureOrientation( PlatformDeviceOrientation orientation, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.lockCaptureOrientation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [orientation], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -593,17 +583,15 @@ class CameraApi { /// Unlocks the orientation for the camera with the given ID. Future unlockCaptureOrientation() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.unlockCaptureOrientation$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -620,17 +608,15 @@ class CameraApi { /// Takes a picture on the camera with the given ID and returns a path to the /// resulting file. Future takePicture() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.takePicture$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -651,19 +637,17 @@ class CameraApi { /// Starts recording a video on the camera with the given ID. Future startVideoRecording(bool enableStream) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.startVideoRecording$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [enableStream], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -680,17 +664,15 @@ class CameraApi { /// Ends video recording on the camera with the given ID and returns the path /// to the resulting file. Future stopVideoRecording() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.stopVideoRecording$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -711,17 +693,15 @@ class CameraApi { /// Pauses video recording on the camera with the given ID. Future pauseVideoRecording() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.pauseVideoRecording$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -737,17 +717,15 @@ class CameraApi { /// Resumes previously paused video recording on the camera with the given ID. Future resumeVideoRecording() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.resumeVideoRecording$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -763,17 +741,15 @@ class CameraApi { /// Begins streaming frames from the camera. Future startImageStream() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.startImageStream$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -789,17 +765,15 @@ class CameraApi { /// Stops streaming frames from the camera. Future stopImageStream() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.stopImageStream$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -815,19 +789,17 @@ class CameraApi { /// Sets the flash mode of the camera with the given ID. Future setFlashMode(PlatformFlashMode flashMode) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.setFlashMode$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [flashMode], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -843,19 +815,17 @@ class CameraApi { /// Sets the exposure mode of the camera with the given ID. Future setExposureMode(PlatformExposureMode exposureMode) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.setExposureMode$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [exposureMode], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -873,19 +843,17 @@ class CameraApi { /// /// A null value resets to the default exposure point. Future setExposurePoint(PlatformPoint? point) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.setExposurePoint$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [point], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -901,17 +869,15 @@ class CameraApi { /// Returns the minimum exposure offset of the camera with the given ID. Future getMinExposureOffset() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.getMinExposureOffset$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -932,17 +898,15 @@ class CameraApi { /// Returns the maximum exposure offset of the camera with the given ID. Future getMaxExposureOffset() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.getMaxExposureOffset$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -963,17 +927,15 @@ class CameraApi { /// Returns the exposure step size of the camera with the given ID. Future getExposureOffsetStepSize() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.getExposureOffsetStepSize$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -995,19 +957,17 @@ class CameraApi { /// Sets the exposure offset of the camera with the given ID and returns the /// actual exposure offset. Future setExposureOffset(double offset) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.setExposureOffset$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [offset], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1028,19 +988,17 @@ class CameraApi { /// Sets the focus mode of the camera with the given ID. Future setFocusMode(PlatformFocusMode focusMode) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.setFocusMode$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [focusMode], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1058,19 +1016,17 @@ class CameraApi { /// /// A null value resets to the default focus point. Future setFocusPoint(PlatformPoint? point) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.setFocusPoint$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [point], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1086,17 +1042,15 @@ class CameraApi { /// Returns the maximum zoom level of the camera with the given ID. Future getMaxZoomLevel() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.getMaxZoomLevel$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1117,17 +1071,15 @@ class CameraApi { /// Returns the minimum zoom level of the camera with the given ID. Future getMinZoomLevel() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.getMinZoomLevel$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1148,19 +1100,17 @@ class CameraApi { /// Sets the zoom level of the camera with the given ID. Future setZoomLevel(double zoom) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.setZoomLevel$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [zoom], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1176,17 +1126,15 @@ class CameraApi { /// Pauses streaming of preview frames. Future pausePreview() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.pausePreview$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1202,17 +1150,15 @@ class CameraApi { /// Resumes previously paused streaming of preview frames. Future resumePreview() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.resumePreview$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1230,19 +1176,43 @@ class CameraApi { /// /// This should be called only while video recording is active. Future setDescriptionWhileRecording(String description) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.camera_android.CameraApi.setDescriptionWhileRecording$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [description], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + 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; + } + } + + /// Sets the JPEG compression quality for still image capture. + Future setImageQuality(int quality) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setImageQuality$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) { @@ -1273,8 +1243,7 @@ abstract class CameraGlobalEventApi { ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.camera_android.CameraGlobalEventApi.deviceOrientationChanged$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger, @@ -1332,8 +1301,7 @@ abstract class CameraEventApi { ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.camera_android.CameraEventApi.initialized$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger, @@ -1367,8 +1335,7 @@ abstract class CameraEventApi { } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.camera_android.CameraEventApi.error$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger, @@ -1401,8 +1368,7 @@ abstract class CameraEventApi { } } { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.camera_android.CameraEventApi.closed$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger, diff --git a/packages/camera/camera_android/pigeons/messages.dart b/packages/camera/camera_android/pigeons/messages.dart index 4489fe3f6fb8..db226646a181 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 setImageQuality(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 4448279e3a32..c2c64722ac94 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+15 +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 @@ -36,3 +36,7 @@ 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_platform_interface: {path: ../../../packages/camera/camera_platform_interface} diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index 8321001ed954..544c83410f38 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.setImageQuality(cameraId, 50); + + // Assert + verify(mockCameraApi.setImageQuality(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..2dc3c96183f7 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 setImageQuality(int? quality) => + (super.noSuchMethod( + Invocation.method(#setImageQuality, [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 4e9229a78da6..95e7ed8172e3 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.2 + +* Adds `setImageQuality` for controlling JPEG compression quality. + ## 0.7.1 * Removes outdated restrictions against concurrent camera use cases. 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..7813f22eab7f 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 @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v26.1.7), do not edit directly. +// Autogenerated from Pigeon (v26.1.8), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -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 7bf6bd508a25..4d3387830ee9 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..e0ddf679e3cd 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,7 +39,7 @@ 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); diff --git a/packages/camera/camera_android_camerax/example/pubspec.yaml b/packages/camera/camera_android_camerax/example/pubspec.yaml index 81a4e079b2ae..2f59cb505d84 100644 --- a/packages/camera/camera_android_camerax/example/pubspec.yaml +++ b/packages/camera/camera_android_camerax/example/pubspec.yaml @@ -28,4 +28,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_platform_interface: {path: ../../../../packages/camera/camera_platform_interface} 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..94185b11ddba 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 [setImageQuality]). + @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 setImageQuality(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..40960369e615 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 @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v26.1.7), do not edit directly. +// Autogenerated from Pigeon (v26.1.8), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -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); @@ -5155,14 +5160,14 @@ class ImageCapture extends UseCase { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [ - pigeonVar_instanceIdentifier, - resolutionSelector, - targetRotation, - flashMode, - ], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel + .send([ + pigeonVar_instanceIdentifier, + resolutionSelector, + targetRotation, + flashMode, + jpegQuality, + ]); () async { final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 2aa23c062bce..31d3bf9c6bf6 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 83ccda21ad1f..286aa536031e 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.7.1 +version: 0.7.2 environment: sdk: ^3.9.0 @@ -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 @@ -35,3 +35,7 @@ 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_platform_interface: {path: ../../../packages/camera/camera_platform_interface} 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..bdd9b5d01158 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( + 'setImageQuality 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.setImageQuality(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( + 'setImageQuality 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.setImageQuality(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..41353a5efa93 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.2 + +* Adds `setImageQuality` 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 4e3c5a377f06..feae0ae3862f 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 setImageQualityCalled = false + mockCamera.setImageQualityStub = { quality in + XCTAssertEqual(quality, targetQuality) + setImageQualityCalled = true + } + + cameraPlugin.setImageQuality(quality: targetQuality) { result in + let _ = self.assertSuccess(result) + expectation.fulfill() + } + + waitForExpectations(timeout: 30, handler: nil) + + XCTAssertTrue(setImageQualityCalled) + } + 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..54b6904e0994 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 setImageQualityStub: ((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 setImageQuality(_ quality: Int64) { + setImageQualityStub?(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..d78e31aee2c3 100644 --- a/packages/camera/camera_avfoundation/example/pubspec.yaml +++ b/packages/camera/camera_avfoundation/example/pubspec.yaml @@ -29,3 +29,7 @@ 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_platform_interface: {path: ../../../../packages/camera/camera_platform_interface} 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..2c3ae6e03d47 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 setImageQuality(_ 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..d7c1cf33262f 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 setImageQuality( + quality: Int64, completion: @escaping (Result) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.setImageQuality(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..18b3476827dd 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 @@ -739,6 +740,7 @@ final class DefaultCamera: NSObject, Camera { let savePhotoDelegate = SavePhotoDelegate( path: path, ioQueue: photoIOQueue, + imageQuality: fileExtension == "jpg" && imageQuality < 100 ? imageQuality : nil, completionHandler: { [weak self] path, error in guard let strongSelf = self else { return } @@ -842,6 +844,10 @@ final class DefaultCamera: NSObject, Camera { self.fileFormat = fileFormat } + func setImageQuality(_ 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..792ba70e8f7d 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 @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v26.1.5), do not edit directly. +// Autogenerated from Pigeon (v26.1.8), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -723,6 +723,8 @@ 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 setImageQuality(quality: Int64, completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -1363,6 +1365,26 @@ class CameraApiSetup { } else { setImageFileFormatChannel.setMessageHandler(nil) } + /// Sets the JPEG compression quality for still image capture. + let setImageQualityChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.camera_avfoundation.CameraApi.setImageQuality\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setImageQualityChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let qualityArg = args[0] as! Int64 + api.setImageQuality(quality: qualityArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + setImageQualityChannel.setMessageHandler(nil) + } } } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/SavePhotoDelegate.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/SavePhotoDelegate.swift index 35050120e118..7a710ccaaa09 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/SavePhotoDelegate.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/SavePhotoDelegate.swift @@ -5,6 +5,8 @@ import AVFoundation import Flutter import Foundation +import ImageIO +import UIKit /// The completion handler block for save photo operations. /// Can be called from either main queue or IO queue. @@ -22,6 +24,9 @@ class SavePhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate { /// The queue on which captured photos are written to disk. private let ioQueue: DispatchQueue + /// The JPEG compression quality (1-100), or nil for default quality. + private let imageQuality: Int64? + /// The completion handler block for capture and save photo operations. let completionHandler: SavePhotoDelegateCompletionHandler @@ -34,15 +39,19 @@ class SavePhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate { /// Initialize a photo capture delegate. /// path - the path for captured photo file. /// ioQueue - the queue on which captured photos are written to disk. + /// imageQuality - optional JPEG compression quality (1-100). When nil or 100, + /// the original photo data is used without re-encoding. /// completionHandler - The completion handler block for save photo operations. Can /// be called from either main queue or IO queue. init( path: String, ioQueue: DispatchQueue, + imageQuality: Int64? = nil, completionHandler: @escaping SavePhotoDelegateCompletionHandler ) { self.path = path self.ioQueue = ioQueue + self.imageQuality = imageQuality self.completionHandler = completionHandler super.init() } @@ -78,8 +87,49 @@ class SavePhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate { didFinishProcessingPhoto photo: AVCapturePhoto, error: Error? ) { - handlePhotoCaptureResult(error: error) { - photo.fileDataRepresentation() + handlePhotoCaptureResult(error: error) { [weak self] in + guard let quality = self?.imageQuality, quality < 100 else { + return photo.fileDataRepresentation() + } + guard let originalData = photo.fileDataRepresentation() else { + return nil + } + return Self.reencodeJPEG(data: originalData, quality: quality) + } + } + + /// Re-encodes JPEG data at the given quality (1-99) while preserving EXIF metadata. + static func reencodeJPEG(data: Data, quality: Int64) -> Data? { + guard + let source = CGImageSourceCreateWithData(data as CFData, nil), + let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) + else { + return data + } + + let metadata = + CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any] ?? [:] + + let mutableData = NSMutableData() + guard + let destination = CGImageDestinationCreateWithData( + mutableData as CFMutableData, + "public.jpeg" as CFString, + 1, + nil) + else { + return data } + + var properties = metadata + properties[kCGImageDestinationLossyCompressionQuality] = CGFloat(quality) / 100.0 + + CGImageDestinationAddImage(destination, cgImage, properties as CFDictionary) + + guard CGImageDestinationFinalize(destination) else { + return data + } + + return mutableData as Data } } diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 3907ed89219b..a2d1f772356d 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 setImageQuality(int cameraId, int quality) async { + await _hostApi.setImageQuality(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..de281c10203a 100644 --- a/packages/camera/camera_avfoundation/lib/src/messages.g.dart +++ b/packages/camera/camera_avfoundation/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v26.1.5), do not edit directly. +// Autogenerated from Pigeon (v26.1.8), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -1487,6 +1487,32 @@ class CameraApi { return; } } + + /// Sets the JPEG compression quality for still image capture. + Future setImageQuality(int quality) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setImageQuality$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 29ed9ed4b6b9..a23307ffcdcf 100644 --- a/packages/camera/camera_avfoundation/pigeons/messages.dart +++ b/packages/camera/camera_avfoundation/pigeons/messages.dart @@ -352,6 +352,11 @@ abstract class CameraApi { @async @ObjCSelector('setImageFileFormat:') void setImageFileFormat(PlatformImageFileFormat format); + + /// Sets the JPEG compression quality for still image capture. + @async + @ObjCSelector('setImageQuality:') + void setImageQuality(int quality); } @EventChannelApi() diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 8a10d0d21f72..a025bc3d654e 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 @@ -33,3 +33,7 @@ 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_platform_interface: {path: ../../../packages/camera/camera_platform_interface} diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 53d7965c6bac..24e20c504fc4 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.setImageQuality(cameraId, 50); + + verify(mockApi.setImageQuality(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..7b41d83647c9 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 setImageQuality(int? quality) => + (super.noSuchMethod( + Invocation.method(#setImageQuality, [quality]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); } diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index fb538d49fd98..b5b13f51027b 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 2.13.0 * Updates minimum supported SDK version to Flutter 3.35/Dart 3.9. +* Adds `setImageQuality` for controlling JPEG compression quality. ## 2.12.0 diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 3652de5861d5..3bc52c3ca3ff 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -518,6 +518,14 @@ class MethodChannelCamera extends CameraPlatform { }); } + @override + Future setImageQuality(int cameraId, int quality) { + return _channel.invokeMethod('setImageQuality', { + 'cameraId': cameraId, + 'quality': quality, + }); + } + @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 152437a73312..ffb00e6eca13 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -347,4 +347,11 @@ abstract class CameraPlatform extends PlatformInterface { Future setImageFileFormat(int cameraId, ImageFileFormat format) { throw UnimplementedError('setImageFileFormat() is not implemented.'); } + + /// Sets the JPEG compression quality for still image capture. + /// + /// The [quality] must be between 1 (lowest) and 100 (highest). + Future setImageQuality(int cameraId, int quality) { + throw UnimplementedError('setImageQuality() is not implemented.'); + } } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 3c2a1cbea685..8336114d1837 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ 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 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.12.0 +version: 2.13.0 environment: sdk: ^3.9.0 diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 9a9731062fdd..a4931d2582bc 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -520,6 +520,20 @@ void main() { }, ); + test( + 'Default implementation of setImageQuality() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setImageQuality(1, 50), + throwsUnimplementedError, + ); + }, + ); + test( 'Default implementation of supportsImageStreaming() should return false', () { diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 15d1c34ef555..b1465d423ff5 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -1302,6 +1302,25 @@ void main() { ), ]); }); + + test('Should set the image quality', () async { + // Arrange + final channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setImageQuality': null}, + ); + + // Act + await camera.setImageQuality(cameraId, 50); + + // Assert + expect(channel.log, [ + isMethodCall( + 'setImageQuality', + arguments: {'cameraId': cameraId, 'quality': 50}, + ), + ]); + }); }); }); }