Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8059d48
[camera] Add setImageQuality for JPEG compression control
Bolling88 Mar 2, 2026
756e12c
Run dart format, google-java-format, and ktfmt
Bolling88 Mar 2, 2026
c856cd3
Fix ImageCaptureTest for new jpegQuality parameter
Bolling88 Mar 2, 2026
3815049
Suppress KotlinPropertyAccess lint in JpegQualityFeature
Bolling88 Mar 2, 2026
7a089e5
Add createJpegQualityFeature to TestCameraFeatureFactory in tests
Bolling88 Mar 2, 2026
fe7995c
Address review feedback on iOS implementation
Bolling88 Mar 9, 2026
af20239
Revert UTType.jpeg to string literal for iOS 13 compatibility
Bolling88 Mar 9, 2026
16235a7
Use fileFormat enum instead of extension string for quality check
Bolling88 Mar 12, 2026
26d3d89
Add JpegQualityFeature tests and verify CameraX jpegQuality
Bolling88 Mar 17, 2026
3609683
Address review: add JPEG format guard and inline comments
Bolling88 Mar 20, 2026
d2414f8
Move JPEG format check out of reencodeJPEG into caller
Bolling88 Mar 26, 2026
9ba9b11
Add JPEG format check in photoOutput before calling reencodeJPEG
Bolling88 Mar 26, 2026
3e38ef0
Rename setImageQuality to setJpegImageQuality
Bolling88 Apr 1, 2026
639d9fd
Merge branch 'main' into fix-set-quality
Bolling88 Apr 1, 2026
626fdb1
Merge branch 'main' into fix-set-quality
Bolling88 Apr 2, 2026
2c34081
Use AVCapturePhotoSettings for JPEG quality instead of re-encoding
Bolling88 Apr 2, 2026
c5f584a
Merge branch 'main' into fix-set-quality
Bolling88 Apr 7, 2026
562f1d8
Update packages/camera/camera/test/camera_test.dart
Bolling88 Apr 7, 2026
b5eb6cc
Update packages/camera/camera/test/camera_test.dart
Bolling88 Apr 7, 2026
4f01d62
Add assertion for JPEG image quality range
Bolling88 Apr 7, 2026
96cff08
Merge branch 'main' into fix-set-quality
Bolling88 Apr 10, 2026
7332ecf
Merge branch 'main' into fix-set-quality
Bolling88 Apr 15, 2026
87ab15f
Remove camera_platform_interface dependency overrides
Bolling88 Apr 15, 2026
4f60ea9
Merge branch 'main' into fix-set-quality
Bolling88 Apr 15, 2026
647e2e8
Merge remote-tracking branch 'upstream/main' into fix-set-quality
Bolling88 Apr 15, 2026
e44d2e0
Merge remote-tracking branch 'origin/fix-set-quality' into fix-set-qu…
Bolling88 Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.12.1

* Adds `setJpegImageQuality` for controlling JPEG compression quality.

## 0.12.0+1

* Makes `Optional.of` constructor `const`.
Expand Down
5 changes: 5 additions & 0 deletions packages/camera/camera/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ dev_dependencies:

flutter:
uses-material-design: true
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
camera_android_camerax: {path: ../../../../packages/camera/camera_android_camerax}
camera_avfoundation: {path: ../../../../packages/camera/camera_avfoundation}
18 changes: 18 additions & 0 deletions packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,24 @@ class CameraController extends ValueNotifier<CameraValue> {
}
}

/// Sets the JPEG compression quality for still image capture.
///
/// The [quality] must be between 1 (lowest) and 100 (highest).
Future<void> setJpegImageQuality(int quality) async {
if (quality < 1 || quality > 100) {
throw ArgumentError.value(
quality,
'quality',
'Must be between 1 and 100.',
);
}
try {
await CameraPlatform.instance.setJpegImageQuality(_cameraId, quality);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

/// Check whether the camera platform supports image streaming.
bool supportsImageStreaming() =>
CameraPlatform.instance.supportsImageStreaming();
Expand Down
13 changes: 9 additions & 4 deletions packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.12.0+1
version: 0.12.1

environment:
sdk: ^3.9.0
Expand All @@ -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
Expand All @@ -38,3 +38,8 @@ dev_dependencies:

topics:
- camera
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
camera_android_camerax: {path: ../../../packages/camera/camera_android_camerax}
camera_avfoundation: {path: ../../../packages/camera/camera_avfoundation}
3 changes: 3 additions & 0 deletions packages/camera/camera/test/camera_preview_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ class FakeController extends ValueNotifier<CameraValue>
Future<Iterable<VideoStabilizationMode>>
getSupportedVideoStabilizationModes() async => <VideoStabilizationMode>[];

@override
Future<void> setJpegImageQuality(int quality) async {}

@override
bool supportsImageStreaming() => true;
}
Expand Down
83 changes: 83 additions & 0 deletions packages/camera/camera/test/camera_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,83 @@ void main() {
},
);

test('setJpegImageQuality() calls CameraPlatform', () async {
final cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90,
),
ResolutionPreset.max,
);
await cameraController.initialize();

await cameraController.setJpegImageQuality(50);

verify(
CameraPlatform.instance.setJpegImageQuality(cameraController.cameraId, 50),
).called(1);
});

test(
'setJpegImageQuality() throws CameraException on PlatformException',
() async {
final cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90,
),
ResolutionPreset.max,
);
await cameraController.initialize();

when(
CameraPlatform.instance.setJpegImageQuality(
cameraController.cameraId,
50,
),
).thenThrow(
PlatformException(
code: 'TEST_ERROR',
message: 'This is a test error message',
),
);

expect(
cameraController.setJpegImageQuality(50),
throwsA(
isA<CameraException>().having(
(CameraException error) => error.description,
'TEST_ERROR',
'This is a test error message',
),
),
);
},
);

test('setJpegImageQuality() throws ArgumentError for invalid values', () async {
final cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90,
),
ResolutionPreset.max,
);
await cameraController.initialize();

expect(
() => cameraController.setJpegImageQuality(0),
throwsA(isA<ArgumentError>()),
);
expect(
() => cameraController.setJpegImageQuality(101),
throwsA(isA<ArgumentError>()),
);
});

test('setExposureMode() calls $CameraPlatform', () async {
final cameraController = CameraController(
const CameraDescription(
Expand Down Expand Up @@ -4152,6 +4229,12 @@ class MockCameraPlatform extends Mock
) async => super.noSuchMethod(
Invocation.method(#setVideoStabilizationMode, <Object?>[cameraId, mode]),
);

@override
Future<void> setJpegImageQuality(int? cameraId, int? quality) async =>
super.noSuchMethod(
Invocation.method(#setJpegImageQuality, <Object?>[cameraId, quality]),
);
}

class MockCameraDescription extends CameraDescription {
Expand Down
4 changes: 4 additions & 0 deletions packages/camera/camera_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.10.11

* Adds `setJpegImageQuality` for controlling JPEG compression quality.

## 0.10.10+16

* Updates build files from Groovy to Kotlin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1416,6 +1417,20 @@ public void setDescriptionWhileRecording(CameraProperties properties) {
}
}

/**
* Sets the JPEG compression quality for still image capture.
*
* @param quality JPEG quality value between 1 and 100.
*/
public void setJpegImageQuality(@NonNull Long quality) {
JpegQualityFeature jpegQualityFeature = cameraFeatures.getJpegQuality();
if (jpegQualityFeature == null) {
jpegQualityFeature = cameraFeatureFactory.createJpegQualityFeature(cameraProperties);
cameraFeatures.setJpegQuality(jpegQualityFeature);
}
jpegQualityFeature.setValue(quality.intValue());
}

public void dispose() {
Log.i(TAG, "dispose");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ public void setDescriptionWhileRecording(@NonNull String cameraName) {
}
}

@Override
public void setJpegImageQuality(@NonNull Long quality) {
camera.setJpegImageQuality(quality);
}

@Override
public void dispose() {
if (camera != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,9 @@ void create(
*/
void setDescriptionWhileRecording(@NonNull String description);

/** Sets the JPEG compression quality for still image capture. */
void setJpegImageQuality(@NonNull Long quality);

/** The codec used by CameraApi. */
static @NonNull MessageCodec<Object> getCodec() {
return PigeonCodec.INSTANCE;
Expand Down Expand Up @@ -1809,6 +1812,31 @@ public void error(Throwable error) {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.camera_android.CameraApi.setJpegImageQuality"
+ messageChannelSuffix,
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) message;
Long qualityArg = (Long) args.get(0);
try {
api.setJpegImageQuality(qualityArg);
wrapped.add(0, null);
} catch (Throwable exception) {
wrapped = wrapError(exception);
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}
Loading