diff --git a/.gitignore b/.gitignore index 70221792..c82b9bce 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,15 @@ build/ test/out/ +android_new/ +example2/ +example/android_old/ test_lab.sh firebase_key.txt google-services.json + +android/gradlew +android/gradlew.bat +android/gradle/wrapper/gradle-wrapper.jar +example/android/app/.cxx/** diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d33521..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/camera_awesome.iml b/.idea/camera_awesome.iml deleted file mode 100644 index 8d63fc9b..00000000 --- a/.idea/camera_awesome.iml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml deleted file mode 100644 index 2421cae5..00000000 --- a/.idea/libraries/Dart_SDK.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml deleted file mode 100644 index 6b7239fd..00000000 --- a/.idea/libraries/Flutter_Plugins.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 7de1a832..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 54cbac56..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 83067447..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.metadata b/.metadata index ccb634bb..566a7ded 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,30 @@ # This file should be version controlled and should not be manually edited. version: - revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 - channel: stable + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" + channel: "stable" project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: android + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: ios + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/.vscode/launch.json b/.vscode/launch.json index 90c1017d..35ccde85 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -64,6 +64,12 @@ "request": "launch", "type": "dart", "program": "example/lib/custom_awesome_ui.dart" + }, + { + "name": "Fix preview", + "request": "launch", + "type": "dart", + "program": "example/lib/fix_preview.dart" } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index c5f3f6b9..0e14d8e2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "java.configuration.updateBuildConfiguration": "interactive" + "java.configuration.updateBuildConfiguration": "disabled" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 10b77e82..8c62646c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,39 +1,73 @@ +# 2.5.0 +- Fix iOS camera zoom change crash (thanks @haf and @chaosue) +- Fix camera preview not accurate with the result photo + +# 2.4.0 + +- upgrade cameraX to 1.4.2 (thanks @fabricio-sisplan) +- upgrade dependencies +- preview fit is now contain by default +- Allow simultaneous video recording and image analysis (thanks @haf Henrik Feldt) +- Fix: conflict name between Preview from cameraawesome and a new class preview from flutter (thanks @juarezfranco Juarez Franco) + +# 2.0.1 + +- πŸ› Fix preview orientation on tablets for iOS and Android +- πŸ› Fix preview alignment +- πŸ› Fix get preview size on iOS + # 2.0.0 - Multi camera is here ! Hello everyone πŸ‘‹ ! -We are proud to announce the two most requested features on the official camera plugin: +We are proud to announce the two most requested features on the official camera +plugin: - Multi-camera πŸ“Ή - Video settings πŸŽ₯ +- Preview rework πŸ“Έ -This release introduces breaking changes in order to support above features. -See the [migration guide](https://docs.page/Apparence-io/camera_awesome/migration_guide/from_1_to_2) for details. +This release introduces breaking changes in order to support above features. See +the +[migration guide](https://docs.page/Apparence-io/camera_awesome/migration_guides/from_1_to_2) +for details. Here is the complete changelog: -- ✨ Added multi-camera feature, allowing users to display multiple camera previews simultaneously. Note that this feature is currently in beta, and we do not recommend using it in production. -- ✨ Users can now pass options (such as bitrate, fps, and quality) when recording a video. +- ✨ Added multi-camera feature, allowing users to display multiple camera + previews simultaneously. Note that this feature is currently in beta, and we + do not recommend using it in production. +- ✨ Users can now pass options (such as bitrate, fps, and quality) when + recording a video. - ✨ You can now mirror video recording. - ✨🍏 Implemented brightness and exposure level settings on iOS / iPadOS. - βœ¨πŸ€– Added zoom indicator UI. - βœ¨πŸ€– Video recording is now mirrored if `mirrorFrontCamera` is set to true. - β™»οΈπŸ Completely reworked the code for increased clarity and performance. - πŸ› Fixed patrol tests. -- πŸ› Fixed the use of capture button parameter in awesome bottom actions (thanks to @juliuszmandrosz). +- πŸ› Fixed the use of capture button parameter in awesome bottom actions (thanks + to @juliuszmandrosz). - πŸ“ Added Chinese README.md (thanks to @chyiiiiiiiiiiii). +- ↗️ Android CameraX version is now 1.3.0 +- takePhoto ans stopVideoRecording now have callbacks for success and error. +- by default the awesome builder has a filter list but you can pass an empty + list to remove it # 1.4.0 -- ✨ Add utilities to convert AnalysisImage into JPEG in order to display them using `toJpeg()`. -- ✨ Add `preview()` and `analysisOnly()` constructors to `CameraAwesomeBuilder`. +- ✨ Add utilities to convert AnalysisImage into JPEG in order to display them + using `toJpeg()`. +- ✨ Add `preview()` and `analysisOnly()` constructors to + `CameraAwesomeBuilder`. - ✨ Volume button trigger to take picture or record/stop video. - ✨🍏 Add brightness exposure level on iOS / iPadOS. -- πŸ’₯ AnalysisConfig has changed slightly its parameters to have platform-specific setup. -- πŸ’₯ Storage permission is now optional on Android since the introduction of `preview()` - and `analysisOnly()` modes. +- πŸ’₯ AnalysisConfig has changed slightly its parameters to have + platform-specific setup. +- πŸ’₯ Storage permission is now optional on Android since the introduction of + `preview()` and `analysisOnly()` modes. - πŸ›πŸ iOS / iPadOS max zoom limit. -- πŸ›πŸ€– Better handle use cases conflicts (video + image analysis on lower-end devices) for Android. +- πŸ›πŸ€– Better handle use cases conflicts (video + image analysis on lower-end + devices) for Android. # 1.3.1 @@ -43,11 +77,11 @@ Here is the complete changelog: # 1.3.0 - ✨ Customize the built-in UI by setting an `AwesomeTheme`. -- ✨ Top, middle and bottom parts of `CameraAwesomeBuilder.awesome()` can now be replaced by your - own. +- ✨ Top, middle and bottom parts of `CameraAwesomeBuilder.awesome()` can now be + replaced by your own. - ✨ Ability to set camera preview alignment and padding. -- ✨ Ability to set aspect ratio, zoom, flash mode and SensorType when switching between front and back - camera. +- ✨ Ability to set aspect ratio, zoom, flash mode and SensorType when switching + between front and back camera. - ✨ Enable/disable front camera mirroring. - ⬆️ Upgrade `image` dependency. - πŸ› Fix aspect ratio changes animation. @@ -68,13 +102,14 @@ Here is the complete changelog: - Add filters for photo mode. - Rework UI for awesome layout. - Add start and stop method for image analysis. -- **BREAKING** Location and audio recording permissions are now optional. Add them to your - AndroidManifest manually if you need them. +- **BREAKING** Location and audio recording permissions are now optional. Add + them to your AndroidManifest manually if you need them. - Fix preview aspectRatio on iOS. # 1.1.0 -- Use [**pigeon**](https://pub.dev/packages/pigeon) for iOS instead of classic method channel. +- Use [**pigeon**](https://pub.dev/packages/pigeon) for iOS instead of classic + method channel. - Greatly improve performances on analysis mode when FPS limit disabled. - Fix barcode scrolling to bottom. - Fix iOS stream guards. @@ -104,58 +139,100 @@ Here is the complete changelog: - Add Video recording for Android. # 0.3.6 + - Add GPS location in Exif photo on iOS. - Fix some issues + # 0.3.4 + - Add pinch to zoom. + # 0.3.3 + - update android build tools to 30 - fix first permission request crash + # 0.3.2 + - Update to Flutter 3. - Update Android example project. - Upgrade dependencies. - Clean some code. + # 0.3.1 + - handle app lifecycle (stop camera on background) + # 0.3.0 + - Migrate null safety. - Fixed aspect ratio of camera preview when using smaller image sizes. -- Fixed image capture on older android devices which use continuous (passive) focus. +- Fixed image capture on older android devices which use continuous (passive) + focus. - Fix image capture on iOS + # 0.2.1+1 + - build won't show red screen in debug if camerAwesome is running on slow phones -- [Android] bind activity +- [Android] bind activity + # 0.2.1 + - [iOS] image stream available to use MLkit or other image live processing - [iOS] code refactoring + # 0.2.0 + - [iOS] video recording support - [iOS] thread and perf enhancements + # 0.1.2+1 -- [Android] onDetachedFromActivity : fix stopping the camera should be only done if camera has been started -- listen native Orientation should be canceled correctly on dispose CameraAwesomeState + +- [Android] onDetachedFromActivity : fix stopping the camera should be only done + if camera has been started +- listen native Orientation should be canceled correctly on dispose + CameraAwesomeState - unlock focus now restart session correctly after taking a photo - takePicture listener now cannot send result more than one time + # 0.1.2 + - [Android] get luminosity level from device - [Android] apply brightness correction + # 0.1.1+1 + - [android] fix release onOpenListener after emit result to Flutter platform + # 0.1.1 + - prevent starting camera when already open on Flutter side - stability between rebuilds improved on Flutter side - [android] check size is correctly set before starting camera -- CameraPreview try 3 times to start if camera is locked (each try are 1s ellapsed) +- CameraPreview try 3 times to start if camera is locked (each try are 1s + ellapsed) - Fix android zoom when taking picture + # 0.1.0 -- image stream available to use MLkit or other image live processing (Only android) + +- image stream available to use MLkit or other image live processing (Only + android) + # 0.0.2+3 -- fix switch camera on Android with new update (now correctly switch ImageReader and cameraCharacteristics when switch sensor). + +- fix switch camera on Android with new update (now correctly switch ImageReader + and cameraCharacteristics when switch sensor). + # 0.0.2+1 -- comment com.google.gms.google-services from example build.gradle. - This is aimed only to start our e2e tests on testlabs. Put your own google-services.json if you want to start them there. + +- comment com.google.gms.google-services from example build.gradle. This is + aimed only to start our e2e tests on testlabs. Put your own + google-services.json if you want to start them there. + # 0.0.2 + - updated readme + # 0.0.1 + - first version. See readme for complete features list diff --git a/README.md b/README.md index b567dfd7..c3bad7df 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,17 @@ /> + + ApparenceKit Flutter template to bootstrap your next app + + +This plugin is also available as a template in +[ApparenceKit](https://apparencekit.dev).
+
# CamerAwesome @@ -32,10 +43,11 @@ -[![en](https://img.shields.io/badge/language-english-cyan.svg)](https://github.com/Apparence-io/CamerAwesome/blob/master/README.md) [![zh](https://img.shields.io/badge/language-chinese-cyan.svg)](https://github.com/Apparence-io/CamerAwesome/blob/master/README.zh.md) +[![en](https://img.shields.io/badge/language-english-cyan.svg)](https://github.com/Apparence-io/CamerAwesome/blob/master/README.md) +[![zh](https://img.shields.io/badge/language-chinese-cyan.svg)](https://github.com/Apparence-io/CamerAwesome/blob/master/README.zh.md) -πŸ“Έ Embedding a camera experience within your own app shouldn't be that hard.
-A flutter plugin to integrate awesome Android / iOS camera experience.
+πŸ“Έ Embedding a camera experience within your own app shouldn't be that hard. +
A flutter plugin to integrate awesome Android / iOS camera experience.

This package provides you with a fully customizable camera experience that you can use within your app.
@@ -54,32 +66,32 @@ Use our awesome built-in interface or customize it as you want. ## Migration guide -If you are migrating from version 1.x.x to 2.x.x, please read the [migration guide](docs.page/todo). - +If you are migrating from version 1.x.x to 2.x.x, please read the +[migration guide](https://docs.page/Apparence-io/camera_awesome/migration_guides/from_1_to_2). ## Native features Here's all native features that cameraAwesome provides to the flutter side. -| Features | Android | iOS | -| :--------------------------------------- | :-----: | :---: | -| πŸ”– Ask permissions | βœ… | βœ… | -| πŸŽ₯ Record video | βœ… | βœ… | -| πŸ“Ή Multi camera (🚧 BETA) | βœ… | βœ… | -| πŸ”ˆ Enable/disable audio | βœ… | βœ… | -| 🎞 Take photos | βœ… | βœ… | -| πŸŒ† Photo live filters | βœ… | βœ… | -| 🌀 Exposure level | βœ… | βœ… | -| πŸ“‘ Broadcast live image stream | βœ… | βœ… | -| πŸ§ͺ Image analysis (barcode scan & more.) | βœ… | βœ… | -| πŸ‘ Zoom | βœ… | βœ… | -| πŸ“Έ Device flash support | βœ… | βœ… | -| βŒ›οΈ Auto focus | βœ… | βœ… | -| πŸ“² Live switching camera | βœ… | βœ… | -| πŸ˜΅β€πŸ’« Camera rotation stream | βœ… | βœ… | -| 🀐 Background auto stop | βœ… | βœ… | -| πŸ”€ Sensor type switching | ⛔️ | βœ… | -| πŸͺž Enable/disable front camera mirroring | βœ… | βœ… | +| Features | Android | iOS | +| :--------------------------------------- | :-----: | :-: | +| πŸ”– Ask permissions | βœ… | βœ… | +| πŸŽ₯ Record video | βœ… | βœ… | +| πŸ“Ή Multi camera (🚧 BETA) | βœ… | βœ… | +| πŸ”ˆ Enable/disable audio | βœ… | βœ… | +| 🎞 Take photos | βœ… | βœ… | +| πŸŒ† Photo live filters | βœ… | βœ… | +| 🌀 Exposure level | βœ… | βœ… | +| πŸ“‘ Broadcast live image stream | βœ… | βœ… | +| πŸ§ͺ Image analysis (barcode scan & more.) | βœ… | βœ… | +| πŸ‘ Zoom | βœ… | βœ… | +| πŸ“Έ Device flash support | βœ… | βœ… | +| βŒ›οΈ Auto focus | βœ… | βœ… | +| πŸ“² Live switching camera | βœ… | βœ… | +| πŸ˜΅β€πŸ’« Camera rotation stream | βœ… | βœ… | +| 🀐 Background auto stop | βœ… | βœ… | +| πŸ”€ Sensor type switching | ⛔️ | βœ… | +| πŸͺž Enable/disable front camera mirroring | βœ… | βœ… | --- @@ -100,7 +112,6 @@ dependencies: Add these on `ios/Runner/Info.plist`: ```xml - NSCameraUsageDescription Your own description @@ -119,14 +130,16 @@ Change the minimum SDK version to 21 (or higher) in `android/app/build.gradle`: minSdkVersion 21 ``` -In order to be able to take pictures or record videos, you may need additional permissions depending -on the Android version and where you want to save them. -Read more about it in -the [official documentation](https://developer.android.com/training/data-storage). -> `WRITE_EXTERNAL_STORAGE` is not included in the plugin starting with version 1.4.0. +In order to be able to take pictures or record videos, you may need additional +permissions depending on the Android version and where you want to save them. +Read more about it in the +[official documentation](https://developer.android.com/training/data-storage). +> `WRITE_EXTERNAL_STORAGE` is not included in the plugin starting with version +> 1.4.0. -If you want to record videos with audio, add this permission to your `AndroidManifest.xml`: +If you want to record videos with audio, add this permission to your +`AndroidManifest.xml`: ```xml ``` -You may also want to save location of your pictures in exif metadata. In this case, add below -permissions: +You may also want to save location of your pictures in exif metadata. In this +case, add below permissions: ```xml ⚠️ Overriding Android dependencies -Some of the dependencies used by CamerAwesome can be overriden if you have a conflict. -Change these variables to define which version you want to use: +Some of the dependencies used by CamerAwesome can be overriden if you have a +conflict. Change these variables to define which version you want to use: ```gradle buildscript { @@ -172,9 +185,9 @@ buildscript { Only change these variables if you are sure of what you are doing. -For example, setting the Play Services Location version might help you when you have conflicts with -other plugins. -The below line shows an example of these conflicts: +For example, setting the Play Services Location version might help you when you +have conflicts with other plugins. The below line shows an example of these +conflicts: ``` java.lang.IncompatibleClassChangeError: Found interface com.google.android.gms.location.ActivityRecognitionClient, but class was expected @@ -192,8 +205,8 @@ import 'package:camerawesome/camerawesome_plugin.dart'; ## πŸ‘Œ Awesome built-in interface -Just use our builder.
-That's all you need to create a complete camera experience within your app. +Just use our builder.
That's all you need to create a complete camera +experience within your app. ```dart CameraAwesomeBuilder.awesome( @@ -219,7 +232,9 @@ Here is an example: ![Customized UI](docs/img/custom_awesome_ui.jpg) -Check the [full documentation](https://docs.page/Apparence-io/camera_awesome/getting_started/awesome-ui) to learn more. +Check the +[full documentation](https://docs.page/Apparence-io/camera_awesome/getting_started/awesome-ui) +to learn more. --- @@ -227,7 +242,8 @@ Check the [full documentation](https://docs.page/Apparence-io/camera_awesome/get If the `awesome()` factory is not enough, you can use `custom()` instead. -It provides a `builder` property that lets you create your own camera experience.
+It provides a `builder` property that lets you create your own camera +experience.
The camera preview will be visible behind what you will provide to the builder. @@ -240,7 +256,8 @@ CameraAwesomeBuilder.custom( ) ``` -> See more in [documentation](https://docs.page/Apparence-io/camera_awesome/getting_started/custom-ui) +> See more in +> [documentation](https://docs.page/Apparence-io/camera_awesome/getting_started/custom-ui) ### Working with the custom builder @@ -258,16 +275,16 @@ Depending on which state is our camera experience you will have access to some d #### How do CamerAwesome states work ? -Using the state you can do anything you need without having to think about the camera flow

+Using the state you can do anything you need without having to think about the +camera flow

- On app start we are in `PreparingCameraState`
-- Then depending on the initialCaptureMode you set you will be `PhotoCameraState` - or `VideoCameraState`
+- Then depending on the initialCaptureMode you set you will be + `PhotoCameraState` or `VideoCameraState`
- Starting a video will push a `VideoRecordingCameraState`
- Stopping the video will push back the `VideoCameraState`
-
- Also if you want to use some specific function you can use the when method so you can write like - this.
+
Also if you want to use some specific function you can use the when + method so you can write like this.
```dart state.when( @@ -277,12 +294,59 @@ state.when( ); ``` -> See more in [documentation](https://docs.page/Apparence-io/camera_awesome/getting_started/custom-ui) +> See more in +> [documentation](https://docs.page/Apparence-io/camera_awesome/getting_started/custom-ui)
--- +## 🐝 Listen to picture or video event + +Using the onMediaCaptureEvent you can listen to any media capture event and do +whatever you want with it. + +```dart +onMediaCaptureEvent: (event) { + switch ((event.status, event.isPicture, event.isVideo)) { + case (MediaCaptureStatus.capturing, true, false): + debugPrint('Capturing picture...'); + case (MediaCaptureStatus.success, true, false): + event.captureRequest.when( + single: (single) { + debugPrint('Picture saved: ${single.file?.path}'); + }, + multiple: (multiple) { + multiple.fileBySensor.forEach((key, value) { + debugPrint('multiple image taken: $key ${value?.path}'); + }); + }, + ); + case (MediaCaptureStatus.failure, true, false): + debugPrint('Failed to capture picture: ${event.exception}'); + case (MediaCaptureStatus.capturing, false, true): + debugPrint('Capturing video...'); + case (MediaCaptureStatus.success, false, true): + event.captureRequest.when( + single: (single) { + debugPrint('Video saved: ${single.file?.path}'); + }, + multiple: (multiple) { + multiple.fileBySensor.forEach((key, value) { + debugPrint('multiple video taken: $key ${value?.path}'); + }); + }, + ); + case (MediaCaptureStatus.failure, false, true): + debugPrint('Failed to capture video: ${event.exception}'); + default: + debugPrint('Unknown event: $event'); + } +}, +``` + +--- + ## πŸ”¬ Analysis mode Use this to achieve: @@ -295,15 +359,17 @@ Use this to achieve: ![Face AI](docs/img/face_ai.gif) -You can check examples using MLKit inside the `example` directory. -The above example is from `ai_analysis_faces.dart`. It detects faces and draw their contours. +You can check examples using MLKit inside the `example` directory. The above +example is from `ai_analysis_faces.dart`. It detects faces and draw their +contours. It's also possible to use MLKit to read barcodes: ![Barcode scanning](docs/img/barcode_overlay.gif) -Check `ai_analysis_barcode.dart` and `preview_overlay_example.dart` for examples or -the [documentation](https://docs.page/Apparence-io/camera_awesome/ai_with_mlkit/reading_barcodes). +Check `ai_analysis_barcode.dart` and `preview_overlay_example.dart` for examples +or the +[documentation](https://docs.page/Apparence-io/camera_awesome/ai_with_mlkit/reading_barcodes). ### How to use it @@ -325,23 +391,25 @@ CameraAwesomeBuilder.awesome( ) ``` -> MLkit recommends using nv21 format for Android.
-> bgra8888 is the iOS format -> For machine learning you don't need full-resolution images (720 or lower should be enough and makes computation easier) +> MLkit recommends using nv21 format for Android.
bgra8888 is the iOS +> format For machine learning you don't need full-resolution images (720 or +> lower should be enough and makes computation easier) -Learn more about the image analysis configuration in -the [documentation](https://docs.page/Apparence-io/camera_awesome/ai_with_mlkit/image_analysis_configuration) +Learn more about the image analysis configuration in the +[documentation](https://docs.page/Apparence-io/camera_awesome/ai_with_mlkit/image_analysis_configuration) . -Check also detailed explanations on how to use MLKit -to [read barcodes](https://docs.page/Apparence-io/camera_awesome/ai_with_mlkit/reading_barcodes) -and [detect faces](https://docs.page/Apparence-io/camera_awesome/ai_with_mlkit/detecting_faces). +Check also detailed explanations on how to use MLKit to +[read barcodes](https://docs.page/Apparence-io/camera_awesome/ai_with_mlkit/reading_barcodes) +and +[detect faces](https://docs.page/Apparence-io/camera_awesome/ai_with_mlkit/detecting_faces). -⚠️ On Android, some devices don't support video recording and image analysis at the same time. +⚠️ On Android, some devices don't support video recording and image analysis at +the same time. - If they don't, image analysis will be ignored. -- You can check if a device has this capability by - using `CameraCharacteristics .isVideoRecordingAndImageAnalysisSupported(Sensors.back)`. +- You can check if a device has this capability by using + `CameraCharacteristics .isVideoRecordingAndImageAnalysisSupported(Sensors.back)`. --- @@ -358,7 +426,8 @@ Through state you can access to a `SensorConfig` class. | `setBrightness` | change brightness level manually (better to let this auto) | | `setMirrorFrontCamera` | set mirroring for front camera | -All of these configurations are listenable through a stream so your UI can automatically get updated according to the actual configuration. +All of these configurations are listenable through a stream so your UI can +automatically get updated according to the actual configuration.
@@ -374,6 +443,7 @@ You can also choose to use a specific filter from the start: CameraAwesomeBuilder.awesome( // other params filter: AwesomeFilter.AddictiveRed, + availableFilters: ... ) ``` @@ -397,17 +467,21 @@ CameraAwesomeBuilder.custom( ) ``` -See all available filters in the [documentation](https://docs.page/Apparence-io/camera_awesome/widgets/awesome_filters). +See all available filters in the +[documentation](https://docs.page/Apparence-io/camera_awesome/widgets/awesome_filters). +> [!TIP] By default the awesome ui setup has a filter list but you can pass an +> empty list to remove it ## πŸ“· πŸ“· Concurrent cameras ![Concurrent cameras](docs/img/concurrent_cameras.gif) -> 🚧 Feature in beta 🚧 -> Any feedback is welcome! +> 🚧 Feature in beta 🚧 Any feedback is welcome! -In order to start using CamerAwesome with multiple cameras simulatenously, you need to define a `SensorConfig` that uses several sensors. You can use the `SensorConfig.multiple()` constructor for this: +In order to start using CamerAwesome with multiple cameras simulatenously, you +need to define a `SensorConfig` that uses several sensors. You can use the +`SensorConfig.multiple()` constructor for this: ```dart CameraAwesomeBuilder.awesome( @@ -423,12 +497,11 @@ CameraAwesomeBuilder.awesome( ) ``` -This feature is not supported by all devices and even when it is, there are limitations that you must be aware of. - -Check the details in the [dedicated documentation](https://docs.page/Apparence-io/camera_awesome/getting_started/multicam). - - +This feature is not supported by all devices and even when it is, there are +limitations that you must be aware of. +Check the details in the +[dedicated documentation](https://docs.page/Apparence-io/camera_awesome/getting_started/multicam).
diff --git a/README.zh.md b/README.zh.md index 27b2995d..f41495b5 100644 --- a/README.zh.md +++ b/README.zh.md @@ -16,6 +16,16 @@ /> + + ApparenceKit Flutter template to bootstrap your next app + + +This plugin is also available as a template in [ApparenceKit](https://apparencekit.dev).
+
# CamerAwesome @@ -381,7 +391,7 @@ CameraAwesomeBuilder.custom( ) ``` -ζŸ₯ηœ‹ [ζ–‡ζ‘£](https://docs.page/Apparence-io/camera_awesome/widgets/awesome_filters) δΈ­ηš„ζ‰€ζœ‰ε―η”¨θΏ‡ζ»€ε™¨. +ζŸ₯ηœ‹ [ζ–‡ζ‘£](https://doc.page/Apparence-io/camera_awesome/widgets/awesome_filters) δΈ­ηš„ζ‰€ζœ‰ε―η”¨θΏ‡ζ»€ε™¨.
@@ -391,3 +401,15 @@ CameraAwesomeBuilder.custom( width="100%" /> + +This plugin is also available as a template in [ApparenceKit](https://apparencekit.dev).
+ +
+ + + ApparenceKit Flutter template to bootstrap your next app + diff --git a/android/build.gradle b/android/build.gradle index a0f349d4..147ff9a9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -25,11 +25,11 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -def DEFAULT_PLAY_SERVICES_LOCATION_VERSION = "21.0.1" -def DEFAULT_EXIF_INTERFACE_VERSION = "1.3.5" +def DEFAULT_PLAY_SERVICES_LOCATION_VERSION = "21.3.0" +def DEFAULT_EXIF_INTERFACE_VERSION = "1.4.0" def DEFAULT_COMPILE_SDK_VERSION = 33 def DEFAULT_MIN_SDK_VERSION = 21 -def DEFAULT_MEDIA_VERSION = "1.6.0" +def DEFAULT_MEDIA_VERSION = "1.7.0" def getMajor(versionTab) { versionTab == null ? null : versionTab[0].split("\\-")[0] @@ -97,11 +97,12 @@ def compatibleVersion(prop, fallbackVersion, min = null, max = null) { } android { - compileSdkVersion compatibleVersion('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION).toInteger() + compileSdkVersion 34 namespace 'io.apparence.camerawesome' defaultConfig { - minSdkVersion compatibleVersion('minSdkVersion', DEFAULT_MIN_SDK_VERSION, 21).toInteger() + minSdkVersion 24 + targetSdk = 34 } lintOptions { disable 'InvalidPackage' @@ -126,21 +127,24 @@ android { dependencies { implementation 'io.reactivex.rxjava3:rxjava:3.0.4' implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' - implementation 'androidx.test:rules:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' + implementation 'androidx.test:rules:1.6.1' // implementation project(path: ':integration_test') - def compatPlayServicesLocationVersion = compatibleVersion('playServicesLocationVersion', DEFAULT_PLAY_SERVICES_LOCATION_VERSION) - implementation "com.google.android.gms:play-services-location:$compatPlayServicesLocationVersion" - def compatExifInterfaceVersion = compatibleVersion('compatExifInterfaceVersion', DEFAULT_EXIF_INTERFACE_VERSION) - implementation "androidx.exifinterface:exifinterface:$compatExifInterfaceVersion" + // def compatPlayServicesLocationVersion = compatibleVersion('playServicesLocationVersion', DEFAULT_PLAY_SERVICES_LOCATION_VERSION) + // implementation "com.google.android.gms:play-services-location:$compatPlayServicesLocationVersion" + // def compatExifInterfaceVersion = compatibleVersion('compatExifInterfaceVersion', DEFAULT_EXIF_INTERFACE_VERSION) + // implementation "androidx.exifinterface:exifinterface:$compatExifInterfaceVersion" + implementation "com.google.android.gms:play-services-location:$DEFAULT_PLAY_SERVICES_LOCATION_VERSION" + implementation "androidx.exifinterface:exifinterface:$DEFAULT_EXIF_INTERFACE_VERSION" testImplementation 'junit:junit:4.13.2' // Optional -- Mockito framework - testImplementation "org.mockito:mockito-core:4.8.0" + testImplementation "org.mockito:mockito-core:5.0.0" // Optional -- mockito-kotlin testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" + - def camerax_version = "1.3.0-alpha06" + def camerax_version = "1.4.2" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-lifecycle:${camerax_version}" @@ -149,8 +153,9 @@ dependencies { implementation "androidx.camera:camera-view:${camerax_version}" implementation "androidx.camera:camera-extensions:${camerax_version}" def compatMediaVersion = compatibleVersion('compatMediaVersion', DEFAULT_MEDIA_VERSION) - implementation "androidx.media:media:${compatMediaVersion}" + // implementation "androidx.media:media:${compatMediaVersion}" + implementation "androidx.media:media:$DEFAULT_MEDIA_VERSION" - implementation 'com.google.guava:guava:31.0.1-android' + implementation 'com.google.guava:guava:33.4.0-android' } \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2afc8e34..09523c0e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Wed Nov 16 21:50:57 CET 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index e30e90f7..558b9ddc 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/AnalysisImageConverter.kt b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/AnalysisImageConverter.kt index 104988b6..75e4eb58 100644 --- a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/AnalysisImageConverter.kt +++ b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/AnalysisImageConverter.kt @@ -192,6 +192,45 @@ class AnalysisImageConverter : AnalysisImageUtils { jpegQuality: Long, callback: (Result) -> Unit ) { - callback(Result.failure(Exception("BGRA 8888 conversion not implemented on Android"))) + try { + val width = bgra8888image.width.toInt() + val height = bgra8888image.height.toInt() + + // Create a bitmap with ARGB_8888 config (Android's internal format) + val bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888) + + // Convert BGRA to ARGB by swapping B and R channels + val bgraBuffer = ByteBuffer.wrap(bgra8888image.bytes) + val pixels = IntArray(width * height) + for (i in 0 until width * height) { + val b = bgraBuffer.get().toInt() and 0xFF + val g = bgraBuffer.get().toInt() and 0xFF + val r = bgraBuffer.get().toInt() and 0xFF + val a = bgraBuffer.get().toInt() and 0xFF + pixels[i] = (a shl 24) or (r shl 16) or (g shl 8) or b + } + bitmap.setPixels(pixels, 0, width, 0, 0, width, height) + + // Compress to JPEG + val outputStream = ByteArrayOutputStream() + bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, jpegQuality.toInt(), outputStream) + + // Clean up + bitmap.recycle() + + // Create new AnalysisImageWrapper with JPEG data + val jpegImage = AnalysisImageWrapper( + bytes = outputStream.toByteArray(), + width = bgra8888image.width, + height = bgra8888image.height, + rotation = bgra8888image.rotation, + format = AnalysisImageFormat.JPEG, + cropRect = bgra8888image.cropRect + ) + + callback(Result.success(jpegImage)) + } catch (e: Exception) { + callback(Result.failure(e)) + } } } \ No newline at end of file diff --git a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraAwesomeX.kt b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraAwesomeX.kt index c5a1686d..7405360e 100644 --- a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraAwesomeX.kt +++ b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraAwesomeX.kt @@ -155,6 +155,7 @@ class CameraAwesomeX : CameraInterface, FlutterPlugin, ActivityAware { currentCaptureMode = mode, enableImageStream = enableImageStream, videoOptions = videoOptions?.android, + videoRecordingQuality = videoOptions?.quality, onStreamReady = { state -> state.updateLifecycle(activity!!) }).apply { this.updateAspectRatio(aspectRatio) this.flashMode = FlashMode.valueOf(flashMode) @@ -170,8 +171,7 @@ class CameraAwesomeX : CameraInterface, FlutterPlugin, ActivityAware { if (zoom > 0) { // TODO Find a better way to set initial zoom than using a postDelayed Handler(Looper.getMainLooper()).postDelayed({ - (cameraState.concurrentCamera?.cameras?.firstOrNull() - ?: cameraState.previewCamera)?.cameraControl?.setLinearZoom(zoom.toFloat()) + cameraState.setZoom(zoom.toFloat()) }, 200) } } @@ -194,6 +194,7 @@ class CameraAwesomeX : CameraInterface, FlutterPlugin, ActivityAware { "YUV_420" -> OutputImageFormat.YUV_420_888 "NV21" -> OutputImageFormat.NV21 "JPEG" -> OutputImageFormat.JPEG + "BGRA8888" -> OutputImageFormat.RGBA_8888 else -> OutputImageFormat.NV21 }, executor(activity!!), width, @@ -599,7 +600,7 @@ class CameraAwesomeX : CameraInterface, FlutterPlugin, ActivityAware { } override fun setZoom(zoom: Double) { - cameraState.setLinearZoom(zoom.toFloat()) + cameraState.setZoom(zoom.toFloat()) } @SuppressLint("RestrictedApi") @@ -748,22 +749,18 @@ class CameraAwesomeX : CameraInterface, FlutterPlugin, ActivityAware { @SuppressLint("RestrictedApi") override fun getEffectivPreviewSize(index: Long): PreviewSize { - val res = cameraState.previews!![index.toInt()].resolutionInfo?.resolution - return if (res != null) { - val rota90 = 90 - val rota270 = 270 - when (cameraState.previews!![index.toInt()].resolutionInfo?.rotationDegrees) { - rota90, rota270 -> { - PreviewSize(res.height.toDouble(), res.width.toDouble()) - } - - else -> { - PreviewSize(res.width.toDouble(), res.height.toDouble()) - } - } - } else { - PreviewSize(0.0, 0.0) + val preview = cameraState.previews?.getOrNull(index.toInt()) ?: return PreviewSize(0.0, 0.0) + val resolutionInfo = preview.resolutionInfo ?: return PreviewSize(0.0, 0.0) + val res = resolutionInfo.resolution + + val rotation = resolutionInfo.rotationDegrees + val previewSize = when (rotation) { + 90, 270 -> PreviewSize(res.height.toDouble(), res.width.toDouble()) + else -> PreviewSize(res.width.toDouble(), res.height.toDouble()) } + + Log.d("CameraX", "Preview size: width=${previewSize.width}, height=${previewSize.height}, rotation=$rotation") + return previewSize } @SuppressLint("RestrictedApi") @@ -840,4 +837,4 @@ class CameraAwesomeX : CameraInterface, FlutterPlugin, ActivityAware { cameraPermissions.onCancel(null) } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraXState.kt b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraXState.kt index 358579c3..017f9695 100644 --- a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraXState.kt +++ b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/CameraXState.kt @@ -11,6 +11,9 @@ import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat import androidx.camera.camera2.internal.compat.quirk.CamcorderProfileResolutionQuirk import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.core.* +import androidx.camera.core.resolutionselector.AspectRatioStrategy +import androidx.camera.core.resolutionselector.ResolutionSelector +import androidx.camera.core.resolutionselector.ResolutionStrategy import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.* import androidx.core.content.ContextCompat @@ -47,6 +50,7 @@ data class CameraXState( var flashMode: FlashMode = FlashMode.NONE, val onStreamReady: (state: CameraXState) -> Unit, var mirrorFrontCamera: Boolean = false, + val videoRecordingQuality: VideoRecordingQuality?, val videoOptions: AndroidVideoOptions?, ) : EventChannel.StreamHandler, SensorOrientation { @@ -90,6 +94,7 @@ data class CameraXState( previews = mutableListOf() imageCaptures.clear() videoCaptures.clear() + val resolutionSelector = getResolutionSelector(aspectRatio ?: AspectRatio.RATIO_4_3) if (cameraProvider.isMultiCamSupported() && sensors.size > 1) { val singleCameraConfigs = mutableListOf() var isFirst = true @@ -127,23 +132,20 @@ data class CameraXState( val preview = if (aspectRatio != null) { Preview.Builder().setTargetAspectRatio(aspectRatio!!) - .setCameraSelector(cameraSelector).build() + .build() } else { - Preview.Builder().setCameraSelector(cameraSelector).build() + Preview.Builder().build() } - preview.setSurfaceProvider( - surfaceProvider(executor(activity), sensor.deviceId ?: "$index") - ) + useCaseGroupBuilder.addUseCase(preview) previews!!.add(preview) if (currentCaptureMode == CaptureModes.PHOTO) { - val imageCapture = ImageCapture.Builder().setCameraSelector(cameraSelector) + val imageCapture = ImageCapture.Builder() // .setJpegQuality(100) .apply { - //photoSize?.let { setTargetResolution(it) } if (rational.denominator != rational.numerator) { - setTargetAspectRatio(aspectRatio ?: AspectRatio.RATIO_4_3) + setResolutionSelector(resolutionSelector) } setFlashMode( @@ -197,10 +199,11 @@ data class CameraXState( if (currentCaptureMode != CaptureModes.ANALYSIS_ONLY) { previews!!.add( if (aspectRatio != null) { - Preview.Builder().setTargetAspectRatio(aspectRatio!!) - .setCameraSelector(cameraSelector).build() + Preview.Builder() + .setResolutionSelector(resolutionSelector) + .build() } else { - Preview.Builder().setCameraSelector(cameraSelector).build() + Preview.Builder().build() } ) @@ -211,12 +214,12 @@ data class CameraXState( } if (currentCaptureMode == CaptureModes.PHOTO) { - val imageCapture = ImageCapture.Builder().setCameraSelector(cameraSelector) + val imageCapture = ImageCapture.Builder() // .setJpegQuality(100) .apply { //photoSize?.let { setTargetResolution(it) } if (rational.denominator != rational.numerator) { - setTargetAspectRatio(aspectRatio ?: AspectRatio.RATIO_4_3) + setResolutionSelector(resolutionSelector) } setFlashMode( when (flashMode) { @@ -268,11 +271,30 @@ data class CameraXState( } } + private fun getResolutionSelector(aspectRatio: Int): ResolutionSelector { + val resolutionStrategy = when (aspectRatio) { + AspectRatio.RATIO_16_9 -> ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY + AspectRatio.RATIO_4_3 -> ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY + else -> ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY + } + + return ResolutionSelector.Builder() + .setAspectRatioStrategy( + when (aspectRatio) { + AspectRatio.RATIO_16_9 -> AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY + AspectRatio.RATIO_4_3 -> AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY + else -> AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY + } + ) + .setResolutionStrategy(resolutionStrategy) + .build() + } + private fun buildVideoCapture(videoOptions: AndroidVideoOptions?): VideoCapture { val recorderBuilder = Recorder.Builder() // Aspect ratio is handled by the setViewPort on the UseCaseGroup - if (videoOptions?.quality != null) { - val quality = when (videoOptions.quality) { + if (videoRecordingQuality != null) { + val quality = when (videoRecordingQuality) { VideoRecordingQuality.LOWEST -> Quality.LOWEST VideoRecordingQuality.SD -> Quality.SD VideoRecordingQuality.HD -> Quality.HD @@ -283,7 +305,7 @@ data class CameraXState( recorderBuilder.setQualitySelector( QualitySelector.from( quality, - if (videoOptions.fallbackStrategy == QualityFallbackStrategy.LOWER) FallbackStrategy.lowerQualityOrHigherThan( + if (videoOptions?.fallbackStrategy == QualityFallbackStrategy.LOWER) FallbackStrategy.lowerQualityOrHigherThan( quality ) else FallbackStrategy.higherQualityOrLowerThan(quality) @@ -304,6 +326,7 @@ data class CameraXState( // Log.d("SurfaceProviderCamX", "Creating surface provider for $cameraId") return Preview.SurfaceProvider { request: SurfaceRequest -> val resolution = request.resolution + //Log.d("CameraX", "surfaceProvider -> Preview size: width=${resolution.width}, height=${resolution.height}") val texture = textureEntries[cameraId]!!.surfaceTexture() texture.setDefaultBufferSize(resolution.width, resolution.height) val surface = Surface(texture) @@ -314,8 +337,13 @@ data class CameraXState( } } - fun setLinearZoom(zoom: Float) { - mainCameraControl.setLinearZoom(zoom) + fun setZoom(normalizedZoom: Float) { + val clampedZoom = normalizedZoom.coerceIn(0f, 1f) + val zoomState = mainCameraInfos.zoomState.value ?: return + val minZoomRatio = zoomState.minZoomRatio + val maxZoomRatio = zoomState.maxZoomRatio + val targetZoomRatio = minZoomRatio + (maxZoomRatio - minZoomRatio) * clampedZoom + mainCameraControl.setZoomRatio(targetZoomRatio) } fun startFocusAndMetering(autoFocusAction: FocusMeteringAction) { @@ -442,4 +470,4 @@ data class CameraXState( else -> Rational(3, 4) } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/ImageAnalysisBuilder.kt b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/ImageAnalysisBuilder.kt index 15f012b1..f2659c15 100644 --- a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/ImageAnalysisBuilder.kt +++ b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/ImageAnalysisBuilder.kt @@ -15,7 +15,7 @@ import kotlin.math.roundToInt import kotlin.math.roundToLong enum class OutputImageFormat { - JPEG, YUV_420_888, NV21, + JPEG, YUV_420_888, NV21, RGBA_8888 } class ImageAnalysisBuilder private constructor( @@ -62,10 +62,11 @@ class ImageAnalysisBuilder private constructor( @SuppressLint("RestrictedApi") fun build(): ImageAnalysis { + val outputImageFormat = if (format == OutputImageFormat.RGBA_8888) ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888 else ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888 countDownLatch.reset() val imageAnalysis = ImageAnalysis.Builder().setTargetResolution(Size(width, height)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888).build() + .setOutputImageFormat(outputImageFormat).build() imageAnalysis.setAnalyzer(Dispatchers.IO.asExecutor()) { imageProxy -> if (previewStreamSink == null) { return@setAnalyzer @@ -101,6 +102,12 @@ class ImageAnalysisBuilder private constructor( imageMap["cropRect"] = cropRect(imageProxy) executor.execute { previewStreamSink?.success(imageMap) } } + OutputImageFormat.RGBA_8888 -> { + val planes = imagePlanesAdapter(imageProxy) + val imageMap = imageProxyBaseAdapter(imageProxy) + imageMap["planes"] = planes + executor.execute { previewStreamSink?.success(imageMap) } + } } CoroutineScope(Dispatchers.IO).launch { maxFramesPerSecond?.let { diff --git a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/Pigeon.kt b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/Pigeon.kt index 73b27b11..340b7e95 100644 --- a/android/src/main/kotlin/com/apparence/camerawesome/cameraX/Pigeon.kt +++ b/android/src/main/kotlin/com/apparence/camerawesome/cameraX/Pigeon.kt @@ -266,6 +266,8 @@ data class PigeonSensor ( data class VideoOptions ( /** Enable audio while video recording */ val enableAudio: Boolean, + /** The quality of the video recording, defaults to [VideoRecordingQuality.highest]. */ + val quality: VideoRecordingQuality? = null, val android: AndroidVideoOptions? = null, val ios: CupertinoVideoOptions? = null @@ -274,18 +276,22 @@ data class VideoOptions ( @Suppress("UNCHECKED_CAST") fun fromList(list: List): VideoOptions { val enableAudio = list[0] as Boolean - val android: AndroidVideoOptions? = (list[1] as List?)?.let { + val quality: VideoRecordingQuality? = (list[1] as Int?)?.let { + VideoRecordingQuality.ofRaw(it) + } + val android: AndroidVideoOptions? = (list[2] as List?)?.let { AndroidVideoOptions.fromList(it) } - val ios: CupertinoVideoOptions? = (list[2] as List?)?.let { + val ios: CupertinoVideoOptions? = (list[3] as List?)?.let { CupertinoVideoOptions.fromList(it) } - return VideoOptions(enableAudio, android, ios) + return VideoOptions(enableAudio, quality, android, ios) } } fun toList(): List { return listOf( enableAudio, + quality?.raw, android?.toList(), ios?.toList(), ) @@ -299,8 +305,6 @@ data class AndroidVideoOptions ( * desired. */ val bitrate: Long? = null, - /** The quality of the video recording, defaults to [VideoRecordingQuality.highest]. */ - val quality: VideoRecordingQuality? = null, val fallbackStrategy: QualityFallbackStrategy? = null ) { @@ -308,19 +312,15 @@ data class AndroidVideoOptions ( @Suppress("UNCHECKED_CAST") fun fromList(list: List): AndroidVideoOptions { val bitrate = list[0].let { if (it is Int) it.toLong() else it as Long? } - val quality: VideoRecordingQuality? = (list[1] as Int?)?.let { - VideoRecordingQuality.ofRaw(it) - } - val fallbackStrategy: QualityFallbackStrategy? = (list[2] as Int?)?.let { + val fallbackStrategy: QualityFallbackStrategy? = (list[1] as Int?)?.let { QualityFallbackStrategy.ofRaw(it) } - return AndroidVideoOptions(bitrate, quality, fallbackStrategy) + return AndroidVideoOptions(bitrate, fallbackStrategy) } } fun toList(): List { return listOf( bitrate, - quality?.raw, fallbackStrategy?.raw, ) } diff --git a/docs/getting_started/awesome-ui.mdx b/docs/getting_started/awesome-ui.mdx index 9d2ced2c..7808c4a7 100644 --- a/docs/getting_started/awesome-ui.mdx +++ b/docs/getting_started/awesome-ui.mdx @@ -159,11 +159,11 @@ Example: CameraAwesomeBuilder.awesome( theme: AwesomeTheme( // Background color of the bottom actions - bottomActionsBackgroundColor: Colors.deepPurple.withOpacity(0.5), + bottomActionsBackgroundColor: Colors.deepPurple.withValues(alpha: 0.5), // Buttons theme buttonTheme: AwesomeButtonTheme( // Background color of the button - backgroundColor: Colors.deepPurple.withOpacity(0.5), + backgroundColor: Colors.deepPurple.withValues(alpha: 0.5), // Size of the icon iconSize: 32, // Padding around the icon @@ -178,7 +178,7 @@ CameraAwesomeBuilder.awesome( shape: const CircleBorder(), child: InkWell( splashColor: Colors.deepPurple, - highlightColor: Colors.deepPurpleAccent.withOpacity(0.5), + highlightColor: Colors.deepPurpleAccent.withValues(alpha: 0.5), onTap: onTap, child: child, ), @@ -231,9 +231,9 @@ CameraAwesomeBuilder.awesome( ... // Set an AwesomeTheme that you might reuse in your UI theme: AwesomeTheme( - bottomActionsBackgroundColor: Colors.cyan.withOpacity(0.5), + bottomActionsBackgroundColor: Colors.cyan.withValues(alpha: 0.5), buttonTheme: AwesomeButtonTheme( - backgroundColor: Colors.cyan.withOpacity(0.5), + backgroundColor: Colors.cyan.withValues(alpha: 0.5), iconSize: 20, foregroundColor: Colors.white, padding: const EdgeInsets.all(16), @@ -245,7 +245,7 @@ CameraAwesomeBuilder.awesome( shape: const CircleBorder(), child: InkWell( splashColor: Colors.cyan, - highlightColor: Colors.cyan.withOpacity(0.5), + highlightColor: Colors.cyan.withValues(alpha: 0.5), onTap: onTap, child: child, ), @@ -454,10 +454,10 @@ CameraAwesomeBuilder.awesome( // Alignment of the preview previewAlignment: Alignment.center, // Add your own decoration on top of the preview - previewDecoratorBuilder: (state, previewSize, previewRect) { + previewDecoratorBuilder: (state, preview) { // This will be shown above the preview (in a Stack) // It could be used in combination with MLKit to draw filters on faces for example - return PreviewDecorationWiget(previewRect); + return PreviewDecorationWiget(preview.rect); }, // Preview fit of the camera previewFit: CameraPreviewFit.fitWidth, @@ -494,11 +494,11 @@ CameraAwesomeBuilder.awesome( // CamerAwesome theme used to customize the built-in UI theme: AwesomeTheme( // Background color of the bottom actions - bottomActionsBackgroundColor: Colors.deepPurple.withOpacity(0.5), + bottomActionsBackgroundColor: Colors.deepPurple.withValues(alpha: 0.5), // Buttons theme buttonTheme: AwesomeButtonTheme( // Background color of the buttons - backgroundColor: Colors.deepPurple.withOpacity(0.5), + backgroundColor: Colors.deepPurple.withValues(alpha: 0.5), // Buttons icon size iconSize: 32, // Padding around icons @@ -513,7 +513,7 @@ CameraAwesomeBuilder.awesome( shape: const CircleBorder(), child: InkWell( splashColor: Colors.deepPurple, - highlightColor: Colors.deepPurpleAccent.withOpacity(0.5), + highlightColor: Colors.deepPurpleAccent.withValues(alpha: 0.5), onTap: onTap, child: child, ), @@ -526,6 +526,11 @@ CameraAwesomeBuilder.awesome( topActionsBuilder: (state) { return AwesomeTopActions(state: state); }, + // default filter + defaultFilter: AwesomeFilter.None, + // list of photo filters (default to awesome filter) + // put null or empty list for hiding filters + availableFilters: awesomePresetFiltersList, ) ``` diff --git a/docs/getting_started/custom-ui.mdx b/docs/getting_started/custom-ui.mdx index a82f5d0e..bc3c19f8 100644 --- a/docs/getting_started/custom-ui.mdx +++ b/docs/getting_started/custom-ui.mdx @@ -7,7 +7,7 @@ The camera preview will be visible behind what you will provide to this builder. ```dart CameraAwesomeBuilder.custom( saveConfig: SaveConfig.photoAndVideo(), - builder: (cameraState, previewSize, previewRect) { + builder: (cameraState, preview) { // Return your UI (a Widget) return cameraState.when( onPreparingCamera: (state) => const Center(child: CircularProgressIndicator()), diff --git a/docs/getting_started/multicam.mdx b/docs/getting_started/multicam.mdx index 827e9dfc..6eb7b271 100644 --- a/docs/getting_started/multicam.mdx +++ b/docs/getting_started/multicam.mdx @@ -93,8 +93,6 @@ Let's break it down: 4. Add an `onTap` callback. 5. Customize how you want the preview to be displayed using the `pictureInPictureBuilder`. This builder must display the `preview` widget. You may also use the `aspectRatio` of the preview to adjust the size of the widget. - - ## Get the list of sensors You can get the list of all the sensors available on iOS with: @@ -164,16 +162,6 @@ Concurrent camera video recording support is not ready yet. Sensor settinigs like `flashMode` or `aspectRatio` are only applied to the first sensor in the list (let's call it the main sensor). -### Picture-in-picture (lack of) customization - -As it's still a beta, the additional cameras are simply displayed in a picture-in-picture like floating window. - -It can be moved with a drag and drop, but you can resize it or change its look yet. - -Feel free to share what you'd like to do with it in a [new issue](https://github.com/Apparence-io/CamerAwesome/issues/new/choose)! - -Of course, you can also provide your own PR directly and we'll be happy to review it. - ### Sensors used on Android The sensors used are not necessarly the ones given in the list of sensors. diff --git a/docs/image_analysis/detecting_faces.mdx b/docs/image_analysis/detecting_faces.mdx index 2d7c02de..27900184 100644 --- a/docs/image_analysis/detecting_faces.mdx +++ b/docs/image_analysis/detecting_faces.mdx @@ -42,7 +42,7 @@ CameraAwesomeBuilder.previewOnly( androidOptions: const AndroidAnalysisOptions.nv21( width: 250, ), - maxFramesPerSecond: 20, + maxFramesPerSecond: 5, // depending on your phone performances ), // 5. builder: (state, previewSize, previewRect) { @@ -91,14 +91,14 @@ extension MLKitUtils on AnalysisImage { return when(nv21: (image) { // 3. return InputImage.fromBytes( - bytes: image.bytes, - inputImageData: InputImageData( - imageRotation: inputImageRotation, - inputImageFormat: InputImageFormat.nv21, - planeData: planeData, - size: image.size, - ), - ); + bytes: image.bytes, + metadata: InputImageMetadata( + rotation: inputImageRotation, + format: InputImageFormat.nv21, + size: image.size, + bytesPerRow: image.planes.first.bytesPerRow, + ), + ); }, bgra8888: (image) { // 4. final inputImageData = InputImageData( @@ -180,14 +180,12 @@ First, take a look at `_MyPreviewDecoratorWidget`: class _MyPreviewDecoratorWidget extends StatelessWidget { final CameraState cameraState; final Stream faceDetectionStream; - final PreviewSize previewSize; - final Rect previewRect; + final Preview preview; const _MyPreviewDecoratorWidget({ required this.cameraState, required this.faceDetectionStream, - required this.previewSize, - required this.previewRect, + required this.preview, }); @override @@ -205,13 +203,16 @@ class _MyPreviewDecoratorWidget extends StatelessWidget { stream: faceDetectionStream, builder: (_, faceModelSnapshot) { if (!faceModelSnapshot.hasData) return const SizedBox(); + // this is the transformation needed to convert the image to the preview + // Android mirrors the preview but the analysis image is not + final canvasTransformation = faceModelSnapshot.data!.img + ?.getCanvasTransformation(preview); // 3. return CustomPaint( painter: FaceDetectorPainter( model: faceModelSnapshot.requireData, - previewSize: previewSize, - previewRect: previewRect, - isBackCamera: snapshot.requireData.sensor == Sensors.back, + canvasTransformation: canvasTransformation, + preview: preview, ), ); }, @@ -234,113 +235,66 @@ Now let's see how our `FaceDetectorPainter` works by looking at its `paint()` me ``` dart @override void paint(Canvas canvas, Size size) { - final croppedSize = model.cropRect == null - ? model.absoluteImageSize - : Size( - // Width and height are inverted - model.cropRect!.size.height, - model.cropRect!.size.width, - ); - final ratioAnalysisToPreview = previewSize.width / croppedSize.width; - - // 1. - bool flipXY = false; - if (Platform.isAndroid) { - // Symmetry for Android since native image analysis is not mirrored but preview is - // It also handles device rotation - switch (model.imageRotation) { - case InputImageRotation.rotation0deg: - if (isBackCamera) { - flipXY = true; - canvas.scale(-1, 1); - canvas.translate(-size.width, 0); - } else { - flipXY = true; - canvas.scale(-1, -1); - canvas.translate(-size.width, -size.height); - } - break; - case InputImageRotation.rotation90deg: - if (isBackCamera) { - // No changes - } else { - canvas.scale(1, -1); - canvas.translate(0, -size.height); - } - break; - case InputImageRotation.rotation180deg: - if (isBackCamera) { - flipXY = true; - canvas.scale(1, -1); - canvas.translate(0, -size.height); - } else { - flipXY = true; - } - break; - default: - // 270 or null - if (isBackCamera) { - canvas.scale(-1, -1); - canvas.translate(-size.width, -size.height); - } else { - canvas.scale(-1, 1); - canvas.translate(-size.width, 0); - } + if (preview == null || model.img == null) { + return; } - } - - // 2. - for (final Face face in model.faces) { - // 3. - Map paths = { - for (var fct in FaceContourType.values) fct: Path() - }; - // 4. - face.contours.forEach((contourType, faceContour) { - if (faceContour != null) { - // 5. - paths[contourType]!.addPolygon( - faceContour.points - .map( - (element) => _croppedPosition( - element, - croppedSize: croppedSize, - painterSize: size, - ratio: ratioAnalysisToPreview, - flipXY: flipXY, - ), - ) - .toList(), - true); - // 6. - for (var element in faceContour.points) { - canvas.drawCircle( - _croppedPosition( - element, - croppedSize: croppedSize, - painterSize: size, - ratio: ratioAnalysisToPreview, - flipXY: flipXY, - ), - 4, - Paint()..color = Colors.blue, - ); + // 1 + // We apply the canvas transformation to the canvas so that the barcode + // rect is drawn in the correct orientation. (Android only) + if (canvasTransformation != null) { + canvas.save(); + canvas.applyTransformation(canvasTransformation!, size); + } + // 2 + for (final Face face in model.faces) { + // 3 + Map paths = { + for (var fct in FaceContourType.values) fct: Path() + }; + // 4 + face.contours.forEach((contourType, faceContour) { + if (faceContour != null) { + // 5 + paths[contourType]!.addPolygon( + faceContour.points + .map( + (element) => preview!.convertFromImage( + Offset(element.x.toDouble(), element.y.toDouble()), + model.img!, + ), + ) + .toList(), + true); + // 6 + for (var element in faceContour.points) { + var position = preview!.convertFromImage( + Offset(element.x.toDouble(), element.y.toDouble()), + model.img!, + ); + canvas.drawCircle( + position, + 4, + Paint()..color = Colors.blue, + ); + } } + }); + // 7 + paths.removeWhere((key, value) => value.getBounds().isEmpty); + // 8 + for (var p in paths.entries) { + canvas.drawPath( + p.value, + Paint() + ..color = Colors.orange + ..strokeWidth = 2 + ..style = PaintingStyle.stroke); } - }); - // 7. - paths.removeWhere((key, value) => value.getBounds().isEmpty); - - // 8. - for (var p in paths.entries) { - canvas.drawPath( - p.value, - Paint() - ..color = Colors.orange - ..strokeWidth = 2 - ..style = PaintingStyle.stroke); } - } + // if you want to draw again without canvas transformation, use this: + if (canvasTransformation != null) { + canvas.restore(); + } } ``` Here is a short break through of the above code: @@ -353,41 +307,15 @@ Here is a short break through of the above code: 7. Filter the contours not found. 8. Draw the contours that we found in orange. -You may have noticed the use of `_croppedPosition()` to position the elements on the canvas. -Here is its definition: -``` dart -Offset _croppedPosition( - Point element, { - required Size croppedSize, - required Size painterSize, - required double ratio, - required bool flipXY, -}) { - // 1. - num imageDiffX; - num imageDiffY; - if (Platform.isIOS) { - imageDiffX = model.absoluteImageSize.width - croppedSize.width; - imageDiffY = model.absoluteImageSize.height - croppedSize.height; - } else { - // 2. - imageDiffX = model.absoluteImageSize.height - croppedSize.width; - imageDiffY = model.absoluteImageSize.width - croppedSize.height; - } - return (Offset( - // 3. - (flipXY ? element.y : element.x).toDouble() - (imageDiffX / 2), - (flipXY ? element.x : element.y).toDouble() - (imageDiffY / 2), - ) * - ratio) // 4. - .translate( - // 5. - (painterSize.width - (croppedSize.width * ratio)) / 2, - (painterSize.height - (croppedSize.height * ratio)) / 2, - ); -} +## Canvas applyTransformation + +```dart +canvas applyTransformation(canvasTransformation!, size); ``` + +Here what it does + Let's explain what's going on: 1. The image from image analysis might not reflect what is seen on the camera preview due to a difference between their aspect ratio. For instance, image analysis could return a picture in 600x800 (ratio 3:4) while the camera preview could be at 1000x1000. diff --git a/docs/image_analysis/reading_barcodes.mdx b/docs/image_analysis/reading_barcodes.mdx index 81415d12..4e65edf1 100644 --- a/docs/image_analysis/reading_barcodes.mdx +++ b/docs/image_analysis/reading_barcodes.mdx @@ -5,7 +5,7 @@ First thing you need to do is adding MLKit's barcode scanner to your pubspec.yaml: ```yaml -google_mlkit_barcode_scanning: ^0.5.0 +google_mlkit_barcode_scanning: ^0.12.0 ``` You will need a `BarcodeScanner`. You can get one with: @@ -36,7 +36,7 @@ CameraAwesomeBuilder.previewOnly( // 3. onImageForAnalysis: (img) => _processImageBarcode(img), // 4. - builder: (cameraModeState, previewSize, previewRect) { + builder: (cameraModeState, preview) { return _BarcodeDisplayWidget( barcodesStream: _barcodesStream, scrollController: _scrollController, @@ -108,11 +108,11 @@ extension MLKitUtils on AnalysisImage { // 3. return InputImage.fromBytes( bytes: image.bytes, - inputImageData: InputImageData( - imageRotation: inputImageRotation, - inputImageFormat: InputImageFormat.nv21, - planeData: planeData, - size: image.size, + metadata: InputImageMetadata( + rotation: inputImageRotation, + format: InputImageFormat.nv21, + size: image.size, + bytesPerRow: image.planes.first.bytesPerRow, ), ); }, bgra8888: (image) { @@ -168,7 +168,7 @@ To do that, CamerAwesome provides an `AnalysisController` through `CameraState`: ```dart // 1. CameraAwesomeBuilder.custom( - builder: (cameraState, previewSize, previewRect) { + builder: (cameraState, preview) { // 2. print("Image analysis enabled: ${cameraState.analysisController?.enabled}"); @@ -225,11 +225,11 @@ You can use `CameraAwesomeBuilder.awesome()` to do that thanks to the `previewDe ```dart CameraAwesomeBuilder.awesome( - previewDecoratorBuilder: (state, previewSize, previewRect) { + previewDecoratorBuilder: (state, preview) { return BarcodePreviewOverlay( state: state, - previewSize: previewSize, - previewRect: previewRect, + previewSize: preview.size, + previewRect: preview.rect, barcodes: _barcodes, analysisImage: _image, ); @@ -377,119 +377,67 @@ Let's see the code: ```dart Future _detectBarcodeInArea(AnalysisImage img, List barcodes) async { - final Size imageSize = Size(img.width.toDouble(), img.height.toDouble()); - final croppedSize = img.cropRect == null - ? imageSize - : Size( - // Width and height of cropRect are inverted - img.cropRect!.size.height, - img.cropRect!.size.width, - ); - try { - final ratioAnalysisToPreview = - widget.previewSize.width / croppedSize.width; - - // 1. - bool flipXY = false; - _canvasScale = null; - _canvasTranslate = null; - if (Platform.isAndroid) { - switch (InputImageRotation.values.byName(img.rotation.name)) { - case InputImageRotation.rotation0deg: - if (widget.isBackCamera) { - flipXY = true; - _canvasScale = const Point(-1, 1); - _canvasTranslate = const Point(-1, 0); - } else { - flipXY = true; - _canvasScale = const Point(-1, -1); - _canvasTranslate = const Point(-1, -1); - } - break; - case InputImageRotation.rotation90deg: - if (widget.isBackCamera) { - // No changes - } else { - _canvasScale = const Point(1, -1); - _canvasTranslate = const Point(0, -1); - } - break; - case InputImageRotation.rotation180deg: - if (widget.isBackCamera) { - flipXY = true; - _canvasScale = const Point(1, -1); - _canvasTranslate = const Point(0, -1); - } else { - flipXY = true; - } - break; - default: - if (widget.isBackCamera) { - _canvasScale = const Point(-1, -1); - _canvasTranslate = const Point(-1, -1); - } else { - _canvasScale = const Point(-1, 1); - _canvasTranslate = const Point(-1, 0); - } - } - } - - // 2. String? barcodeRead; _barcodeInArea = null; + + // The canvas transformation is needed to display the barcode rect correctly on android + canvasTransformation = img.getCanvasTransformation(widget.preview); + for (Barcode barcode in barcodes) { - if (barcode.cornerPoints != null) { - // 3. - final topLeft = _croppedPosition( - barcode.cornerPoints![0], - analysisImageSize: imageSize, - croppedSize: croppedSize, - screenSize: _screenSize, - ratio: ratioAnalysisToPreview, - flipXY: flipXY, - ).translate(-widget.previewRect.left, -widget.previewRect.top); - final bottomRight = _croppedPosition( - barcode.cornerPoints![2], - analysisImageSize: imageSize, - croppedSize: croppedSize, - screenSize: _screenSize, - ratio: ratioAnalysisToPreview, - flipXY: flipXY, - ).translate(-widget.previewRect.left, -widget.previewRect.top); - - barcodeRead = "[${barcode.format.name}]: ${barcode.rawValue}"; - // 4. - _barcodeRect = Rect.fromLTRB( - topLeft.dx, - topLeft.dy, - bottomRight.dx, - bottomRight.dy, - ); - - // 5 - if (_scanArea.contains( - _barcodeRect!.center.translate( - (_screenSize.width - widget.previewSize.width) / 2, - (_screenSize.height - widget.previewSize.height) / 2, - ), - )) { - _barcodeInArea = true; - break; - } else { - _barcodeInArea = false; - } + if (barcode.cornerPoints.isEmpty) { + continue; + } + + barcodeRead = "[${barcode.format.name}]: ${barcode.rawValue}"; + // For simplicity we consider the barcode to be a Rect. Due to + // perspective, it might not be in reality. You could build a Path + // from the 4 corner points instead. + final topLeftOffset = barcode.cornerPoints[0]; + final bottomRightOffset = barcode.cornerPoints[2]; + var topLeftOff = widget.preview.convertFromImage( + topLeftOffset.toOffset(), + img, + ); + var bottomRightOff = widget.preview.convertFromImage( + bottomRightOffset.toOffset(), + img, + ); + + _barcodeRect = Rect.fromLTRB( + topLeftOff.dx, + topLeftOff.dy, + bottomRightOff.dx, + bottomRightOff.dy, + ); + + // Approximately detect if the barcode is in the scan area by checking + // if the center of the barcode is in the scan area. + if (_scanArea.contains( + _barcodeRect!.center.translate( + (_screenSize.width - widget.preview.previewSize.width) / 2, + (_screenSize.height - widget.preview.previewSize.height) / 2, + ), + )) { + // Note: for a better detection, you should calculate the area of the + // intersection between the barcode and the scan area and compare it + // with the area of the barcode. If the intersection is greater than + // a certain percentage, then the barcode is in the scan area. + _barcodeInArea = true; + // Only handle one good barcode in this example + break; + } else { + _barcodeInArea = false; } if (_barcodeInArea != null && mounted) { - // 6. setState(() { _barcodeRead = barcodeRead; }); } } - } catch (error) { - debugPrint("...sending image resulted error $error"); + } catch (error, stacktrace) { + debugPrint("...sending image resulted error $error $stacktrace"); } } ``` @@ -501,6 +449,33 @@ Let's see the code: 5. We check if the center of the barcode is within the scan area. 6. If a barcode is within the scan area, we update the state to show it. +To help you converting an analysis image to the preview coordinates we provides functions. + +```dart +/// this method is used to convert a point from an image to the preview +/// according to the current preview size and the image size +/// also in case of Android, it will flip the point if required +Offset convertFromImage( + Offset point, + AnalysisImage img, +); +``` + +The AnalysisImage also provide you the required transformation +(It seems that the 180 / 270 cases are not working on all phones right now) +```dart +abstract class AnalysisImage { + ... + // Symmetry for Android since native image analysis is not mirrored but preview is + // It also handles device rotation + CanvasTransformation? getCanvasTransformation( + Preview preview, + ); +``` + +With those two functions you can just draw your points without doing any math. + + ### From MLKit coordinates to screen coordinates MLKit takes an `AnalysisImage` as input, and returns a list of `Barcode` objects. diff --git a/docs/img/apparence.png b/docs/img/apparence.png index f754e65b..6b9b9b26 100644 Binary files a/docs/img/apparence.png and b/docs/img/apparence.png differ diff --git a/docs/img/apparencekit_camera.png b/docs/img/apparencekit_camera.png new file mode 100644 index 00000000..6f78a987 Binary files /dev/null and b/docs/img/apparencekit_camera.png differ diff --git a/docs/img/concurrent_cameras.gif b/docs/img/concurrent_cameras.gif new file mode 100644 index 00000000..a448ed51 Binary files /dev/null and b/docs/img/concurrent_cameras.gif differ diff --git a/docs/img/flutter_template.png b/docs/img/flutter_template.png new file mode 100644 index 00000000..56c09942 Binary files /dev/null and b/docs/img/flutter_template.png differ diff --git a/docs/widgets/theming.mdx b/docs/widgets/theming.mdx index ccc6b723..5e4a5727 100644 --- a/docs/widgets/theming.mdx +++ b/docs/widgets/theming.mdx @@ -14,11 +14,11 @@ Example: CameraAwesomeBuilder.awesome( theme: AwesomeTheme( // Background color of the bottom actions - bottomActionsBackgroundColor: Colors.deepPurple.withOpacity(0.5), + bottomActionsBackgroundColor: Colors.deepPurple.withValues(alpha: 0.5), // Buttons theme buttonTheme: AwesomeButtonTheme( // Background color of the button - backgroundColor: Colors.deepPurple.withOpacity(0.5), + backgroundColor: Colors.deepPurple.withValues(alpha: 0.5), // Size of the icon iconSize: 32, // Padding around the icon @@ -33,7 +33,7 @@ CameraAwesomeBuilder.awesome( shape: const CircleBorder(), child: InkWell( splashColor: Colors.deepPurple, - highlightColor: Colors.deepPurpleAccent.withOpacity(0.5), + highlightColor: Colors.deepPurpleAccent.withValues(alpha: 0.5), onTap: onTap, child: child, ), diff --git a/example/.gitignore b/example/.gitignore index e07a6486..7d8cfec5 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -41,6 +41,7 @@ version analysis_benchmark.json + # packages file containing multi-root paths .packages.generated @@ -69,6 +70,8 @@ unlinked_spec.ds **/android/**/GeneratedPluginRegistrant.java **/android/key.properties *.jks +/app/.cxx/** + # iOS/XCode related **/ios/**/*.mode1v3 diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 00000000..b02a7e4b --- /dev/null +++ b/example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: android + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: ios + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: linux + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: macos + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: web + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: windows + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/android/.gitignore b/example/android/.gitignore index 070eed36..55afd919 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -5,10 +5,9 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java -google-services.json # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 88728b53..8fe67e61 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,78 +1,53 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 33 - ndkVersion flutter.ndkVersion + namespace = "com.example.camera_app" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.camera_app" + applicationId = "com.example.camera_app" // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion 21 - targetSdkVersion 33 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - - // Firbease test lab - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 24 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig = signingConfigs.debug } } } flutter { - source '../..' + source = "../.." } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.10" implementation 'com.google.mlkit:face-detection:16.1.5' implementation 'com.google.mlkit:vision-common:17.3.0' + testImplementation 'junit:junit:4.12' diff --git a/example/android/app/src/androidTest/java/com/example/camera_app/MainActivityTest.java b/example/android/app/src/androidTest/java/com/example/camera_app/MainActivityTest.java deleted file mode 100644 index af8236d4..00000000 --- a/example/android/app/src/androidTest/java/com/example/camera_app/MainActivityTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.camera_app; - -import org.junit.Rule; -import org.junit.runner.RunWith; -import pl.leancode.patrol.PatrolTestRule; -import pl.leancode.patrol.PatrolTestRunner; - -@RunWith(PatrolTestRunner.class) -public class MainActivityTest { - @Rule - public PatrolTestRule rule = new PatrolTestRule<>(MainActivity.class); -} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 4a34d7a4..399f6981 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/camera_app/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/camera_app/MainActivity.kt index b9cfef0e..7205e983 100644 --- a/example/android/app/src/main/kotlin/com/example/camera_app/MainActivity.kt +++ b/example/android/app/src/main/kotlin/com/example/camera_app/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.camera_app import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity: FlutterActivity() diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 4a34d7a4..399f6981 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,4 @@ - +