diff --git a/lib/data/models/camera_feature.dart b/lib/data/models/camera_feature.dart index 20193bf..59f2db0 100644 --- a/lib/data/models/camera_feature.dart +++ b/lib/data/models/camera_feature.dart @@ -1,6 +1,7 @@ enum CameraFeature { spotMetering, histogram, + showFocalLength, } typedef CameraFeaturesConfig = Map; diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index 726fd63..bfb91fc 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -22,6 +22,7 @@ class UserPreferencesService { static const lightSensorEvCalibrationKey = "lightSensorEvCalibration"; static const meteringScreenLayoutKey = "meteringScreenLayout"; static const cameraFeaturesKey = "cameraFeatures"; + static const cameraFocalLengthKey = "cameraFocalLength"; static const caffeineKey = "caffeine"; static const hapticsKey = "haptics"; @@ -118,6 +119,7 @@ class UserPreferencesService { return { CameraFeature.spotMetering: false, CameraFeature.histogram: false, + CameraFeature.showFocalLength: false, }; } } @@ -125,6 +127,9 @@ class UserPreferencesService { set cameraFeatures(CameraFeaturesConfig value) => _sharedPreferences.setString(cameraFeaturesKey, json.encode(value.toJson())); + int? get cameraFocalLength => _sharedPreferences.getInt(cameraFocalLengthKey); + set cameraFocalLength(int? value) => _sharedPreferences.setInt(cameraFocalLengthKey, value!); + bool get caffeine => _sharedPreferences.getBool(caffeineKey) ?? false; set caffeine(bool value) => _sharedPreferences.setBool(caffeineKey, value); diff --git a/lib/interactors/metering_interactor.dart b/lib/interactors/metering_interactor.dart index b4427de..1aa8499 100644 --- a/lib/interactors/metering_interactor.dart +++ b/lib/interactors/metering_interactor.dart @@ -30,8 +30,7 @@ class MeteringInteractor { if (_userPreferencesService.caffeine) { _caffeineService.keepScreenOn(true); } - _volumeEventsService - .setVolumeHandling(_userPreferencesService.volumeAction != VolumeAction.none); + _volumeEventsService.setVolumeHandling(_userPreferencesService.volumeAction != VolumeAction.none); } double get cameraEvCalibration => _userPreferencesService.cameraEvCalibration; @@ -62,15 +61,11 @@ class MeteringInteractor { } Future checkCameraPermission() async { - return _permissionsService - .checkCameraPermission() - .then((value) => value == PermissionStatus.granted); + return _permissionsService.checkCameraPermission().then((value) => value == PermissionStatus.granted); } Future requestCameraPermission() async { - return _permissionsService - .requestCameraPermission() - .then((value) => value == PermissionStatus.granted); + return _permissionsService.requestCameraPermission().then((value) => value == PermissionStatus.granted); } void openAppSettings() { @@ -80,4 +75,6 @@ class MeteringInteractor { Future hasAmbientLightSensor() async => _lightSensorService.hasSensor(); Stream luxStream() => _lightSensorService.luxStream(); + + void setCameraFocalLength(int focalLength) => _userPreferencesService.cameraFocalLength = focalLength; } diff --git a/lib/screens/metering/components/camera_container/bloc_container_camera.dart b/lib/screens/metering/components/camera_container/bloc_container_camera.dart index ef6125e..d28b9c6 100644 --- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart +++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:math' as math; import 'package:camera/camera.dart'; +import 'package:exif/exif.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -17,7 +18,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/event_co import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart'; import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart'; import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart'; -import 'package:lightmeter/utils/ev_from_bytes.dart'; +import 'package:lightmeter/utils/exif_utils.dart'; part 'mock_bloc_container_camera.part.dart'; @@ -157,7 +158,8 @@ class CameraContainerBloc extends EvSourceBlocBase _takePhoto() async { try { final bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List(); - return await evFromImage(bytes); + final tags = await readExifFromBytes(bytes); + _meteringInteractor.setCameraFocalLength(focalLengthFromTags(tags)); + return evFromTags(tags); } catch (e, stackTrace) { log(e.toString(), stackTrace: stackTrace); return null; diff --git a/lib/utils/ev_from_bytes.dart b/lib/utils/exif_utils.dart similarity index 69% rename from lib/utils/ev_from_bytes.dart rename to lib/utils/exif_utils.dart index 9010402..13a1346 100644 --- a/lib/utils/ev_from_bytes.dart +++ b/lib/utils/exif_utils.dart @@ -1,22 +1,21 @@ import 'dart:math' as math; import 'package:exif/exif.dart'; -import 'package:flutter/foundation.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; const String _isoExifKey = 'EXIF ISOSpeedRatings'; const String _apertureExifKey = 'EXIF FNumber'; const String _shutterSpeedExifKey = 'EXIF ExposureTime'; +const String _focalLengthIn35mmExifKey = "EXIF FocalLengthIn35mmFilm"; -Future evFromImage(Uint8List bytes) async { - final tags = await readExifFromBytes(bytes); +double evFromTags(Map tags) { final iso = double.tryParse("${tags[_isoExifKey]}"); final apertureValueRatio = (tags[_apertureExifKey]?.values as IfdRatios?)?.ratios.first; final speedValueRatio = (tags[_shutterSpeedExifKey]?.values as IfdRatios?)?.ratios.first; if (iso == null || apertureValueRatio == null || speedValueRatio == null) { throw ArgumentError( - 'Error parsing EXIF', + 'Error calculating EV', [ if (iso == null) '$_isoExifKey: ${tags[_isoExifKey]?.printable} ${tags[_isoExifKey]?.printable.runtimeType}', if (apertureValueRatio == null) '$_apertureExifKey: $apertureValueRatio', @@ -30,3 +29,14 @@ Future evFromImage(Uint8List bytes) async { return log2(math.pow(aperture, 2)) - log2(speed) - log2(iso / 100); } + +int focalLengthFromTags(Map tags) { + final focalLengthIn35mm = int.tryParse("${tags[_focalLengthIn35mmExifKey]}"); + if (focalLengthIn35mm == null) { + throw ArgumentError( + 'Error parsing focal length', + ['$_focalLengthIn35mmExifKey: ${tags[_focalLengthIn35mmExifKey]}'].join(', '), + ); + } + return focalLengthIn35mm; +} diff --git a/test/utils/ev_from_bytes_test.dart b/test/utils/ev_from_bytes_test.dart deleted file mode 100644 index 488d77e..0000000 --- a/test/utils/ev_from_bytes_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:lightmeter/utils/ev_from_bytes.dart'; - -void main() { - group('evFromImage', () { - test( - 'camera_stub_image.jpg', - () { - final bytes = File('assets/camera_stub_image.jpg').readAsBytesSync(); - expectLater(evFromImage(bytes), completion(8.25230310752341)); - }, - ); - - test( - 'no EXIF', - () { - final bytes = File('assets/launcher_icon_dev_512.png').readAsBytesSync(); - expectLater(evFromImage(bytes), throwsArgumentError); - }, - ); - }); -} diff --git a/test/utils/exif_utils_test.dart b/test/utils/exif_utils_test.dart new file mode 100644 index 0000000..495f9dc --- /dev/null +++ b/test/utils/exif_utils_test.dart @@ -0,0 +1,27 @@ +import 'dart:io'; + +import 'package:exif/exif.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/utils/exif_utils.dart'; + +void main() { + group('evFromTags', () { + test( + 'camera_stub_image.jpg', + () async { + final bytes = File('assets/camera_stub_image.jpg').readAsBytesSync(); + final tags = await readExifFromBytes(bytes); + expectLater(evFromTags(tags), completion(8.25230310752341)); + }, + ); + + test( + 'no EXIF', + () async { + final bytes = File('assets/launcher_icon_dev_512.png').readAsBytesSync(); + final tags = await readExifFromBytes(bytes); + expectLater(evFromTags(tags), throwsArgumentError); + }, + ); + }); +}