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 @@
/>
+
+
+
+
+This plugin is also available as a template in
+[ApparenceKit](https://apparencekit.dev).
+
# CamerAwesome
@@ -32,10 +43,11 @@
-[](https://github.com/Apparence-io/CamerAwesome/blob/master/README.md) [](https://github.com/Apparence-io/CamerAwesome/blob/master/README.zh.md)
+[](https://github.com/Apparence-io/CamerAwesome/blob/master/README.md)
+[](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
-
NSCameraUsageDescriptionYour 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:

-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:

-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:

-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

-> π§ 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 @@
/>
+
+
+
+
+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).
+
+
+
+
+
+
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 @@
-
+