init
This commit is contained in:
commit
73aee09d9c
92
.circleci/config.yml
Normal file
92
.circleci/config.yml
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
version: 2
|
||||||
|
executorType: docker
|
||||||
|
jobs:
|
||||||
|
build-app:
|
||||||
|
resource_class: medium
|
||||||
|
environment:
|
||||||
|
- GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError"'
|
||||||
|
- REACT_NATIVE_MAX_WORKERS: 2
|
||||||
|
- ANDROID_BUILD_TOOLS_VERSION: '28.0.3'
|
||||||
|
working_directory: ~/app
|
||||||
|
docker:
|
||||||
|
- image: reactnativecommunity/react-native-android
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- v1-npm-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||||
|
- v1-npm
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: yarn install --ignore-engines
|
||||||
|
- save_cache:
|
||||||
|
key: v1-npm
|
||||||
|
paths:
|
||||||
|
- node_modules/
|
||||||
|
- save_cache:
|
||||||
|
key: v1-npm-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||||
|
paths:
|
||||||
|
- node_modules/
|
||||||
|
- run:
|
||||||
|
name: Lint
|
||||||
|
command: yarn lint
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- v1-gradle-{{ checksum "android/gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "examples/basic/android/gradle/wrapper/gradle-wrapper.properties" }}
|
||||||
|
- v1-gradle-wrapper
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- v1-gradle-cache-{{ checksum "android/build.gradle" }}-{{ checksum "examples/basic/android/build.gradle" }}
|
||||||
|
- v1-gradle-cache
|
||||||
|
- run:
|
||||||
|
name: Run Checks
|
||||||
|
command: |
|
||||||
|
cd android
|
||||||
|
chmod +x ./gradlew && ./gradlew check
|
||||||
|
- store_artifacts:
|
||||||
|
path: android/build/reports
|
||||||
|
- run:
|
||||||
|
name: Run Yarn to Generate react.gradle
|
||||||
|
command: cd examples/basic/android && yarn
|
||||||
|
- run:
|
||||||
|
name: Build Sample App
|
||||||
|
command: |
|
||||||
|
cd examples/basic/android && chmod +x ./gradlew && ./gradlew clean && ./gradlew build
|
||||||
|
- store_artifacts:
|
||||||
|
path: examples/basic/android/app/build/reports
|
||||||
|
destination: app
|
||||||
|
- save_cache:
|
||||||
|
key: v1-gradle-wrapper-{{ checksum "examples/basic/android/gradle/wrapper/gradle-wrapper.properties" }}
|
||||||
|
paths:
|
||||||
|
- ~/.gradle/wrapper
|
||||||
|
- save_cache:
|
||||||
|
key: v1-gradle-cache-{{ checksum "examples/basic/android/build.gradle" }}
|
||||||
|
paths:
|
||||||
|
- ~/.gradle/caches
|
||||||
|
- deploy:
|
||||||
|
command: |
|
||||||
|
if [ "${CIRCLE_BRANCH}" == "master" ]; then
|
||||||
|
yarn ci:publish
|
||||||
|
fi
|
||||||
|
deploy-docs:
|
||||||
|
working_directory: ~/app
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:8.11.1
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Deploying to GitHub Pages
|
||||||
|
command: |
|
||||||
|
git config --global user.email "${GH_EMAIL}@users.noreply.github.com"
|
||||||
|
git config --global user.name "${GH_NAME}"
|
||||||
|
echo "machine github.com login $GH_NAME password $GH_TOKEN_DOCS" > ~/.netrc
|
||||||
|
cd website && yarn install && GIT_USER=${GH_NAME} yarn run publish-gh-pages
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build-and-deploy-docs:
|
||||||
|
jobs:
|
||||||
|
- build-app
|
||||||
|
- deploy-docs:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: master
|
||||||
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*/node_modules
|
||||||
|
*.log
|
||||||
59
.eslintrc
Normal file
59
.eslintrc
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"jest": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"plugins": ["react", "react-native", "flowtype", "import"],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"modules": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:import/errors"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"comma-dangle": [2, "always-multiline"],
|
||||||
|
"quotes": [2, "single", { "allowTemplateLiterals": true }],
|
||||||
|
"react/prop-types": 0,
|
||||||
|
"no-case-declarations": 0,
|
||||||
|
"react/jsx-no-bind": 0,
|
||||||
|
"react/display-name": 0,
|
||||||
|
"new-cap": 0,
|
||||||
|
"react-native/no-unused-styles": 2,
|
||||||
|
"react-native/split-platform-components": 0,
|
||||||
|
"react-native/no-inline-styles": 0,
|
||||||
|
"react-native/no-color-literals": 0,
|
||||||
|
"no-unexpected-multiline": 0,
|
||||||
|
"no-class-assign": 1,
|
||||||
|
"no-console": 2,
|
||||||
|
"object-curly-spacing": [1, "always"],
|
||||||
|
"flowtype/define-flow-type": 1,
|
||||||
|
"flowtype/use-flow-type": 1,
|
||||||
|
"import/first": 2,
|
||||||
|
"import/default": 0,
|
||||||
|
"no-unused-vars": ["error", { "ignoreRestSiblings": true }],
|
||||||
|
"import/named": 0,
|
||||||
|
"import/namespace": [2, { "allowComputed": true }],
|
||||||
|
"no-extra-boolean-cast": 0,
|
||||||
|
"import/no-duplicates": 2,
|
||||||
|
"react/no-deprecated": 0
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"node": {
|
||||||
|
"extensions": [".js", ".android.js", ".ios.js", ".json"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"__DEV__": true
|
||||||
|
}
|
||||||
|
}
|
||||||
12
.flowconfig
Normal file
12
.flowconfig
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[ignore]
|
||||||
|
.*/node_modules/.*
|
||||||
|
|
||||||
|
[include]
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
|
||||||
|
[options]
|
||||||
|
|
||||||
|
[strict]
|
||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Disable git large files for now!
|
||||||
|
# RNCameraExample/ios/Frameworks/FaceDetector/Frameworks/frameworks/FaceDetector.framework/FaceDetector filter=lfs diff=lfs merge=lfs -text
|
||||||
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: react-native-camera
|
||||||
|
tidelift: npm/react-native-camera
|
||||||
|
custom: # Replace with a single custom sponsorship URL
|
||||||
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
---
|
||||||
|
|
||||||
|
# Bug Report
|
||||||
|
|
||||||
|
**To Do First**
|
||||||
|
|
||||||
|
- [ ] Did you try latest release?
|
||||||
|
- [ ] Did you try master?
|
||||||
|
- [ ] Did you look for existing matching issues?
|
||||||
|
|
||||||
|
**Platforms**
|
||||||
|
|
||||||
|
<!--Comment in the related ones-->
|
||||||
|
<!--Android-->
|
||||||
|
<!--iOS-->
|
||||||
|
|
||||||
|
**Versions**
|
||||||
|
|
||||||
|
<!--Please add the used versions/branches or leave blank and comment in the optionals if used-->
|
||||||
|
|
||||||
|
- Android:
|
||||||
|
- iOS:
|
||||||
|
- react-native-camera:
|
||||||
|
- react-native:
|
||||||
|
- react:
|
||||||
|
<!---react-navigation:-->
|
||||||
|
|
||||||
|
**Description/Current Behaviour**
|
||||||
|
|
||||||
|
<!--place your bug description below-->
|
||||||
|
|
||||||
|
**Expected Behaviour**
|
||||||
|
|
||||||
|
<!--place your expected behaviour below-->
|
||||||
|
|
||||||
|
**Steps to Reproduce**
|
||||||
|
|
||||||
|
<!--describe how to produce the error below-->
|
||||||
|
|
||||||
|
<!--**Does it work with Expo Camera?**-->
|
||||||
|
<!--Check usage with Expo and comment in this section- https://github.com/react-native-community/react-native-camera/blob/master/docs/Expo_Usage.md
|
||||||
|
You should open an issue there as well, so we can cooperate in a solution.-->
|
||||||
|
|
||||||
|
**Additionals**
|
||||||
|
|
||||||
|
<!--place screenshots/suggestions and other additional infos below-->
|
||||||
|
|
||||||
|
> Love react-native-camera? Please consider supporting our collective: 👉 https://opencollective.com/react-native-camera/donate
|
||||||
|
> Want this issue to be resolved faster? Please consider adding a bounty to it https://issuehunt.io/repos/33218414
|
||||||
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature-Request
|
||||||
|
|
||||||
|
**Describe the Feature**
|
||||||
|
<!--describe the requested Feature-->
|
||||||
|
|
||||||
|
**Possible Implementations**
|
||||||
|
<!--describe how to implement the feature-->
|
||||||
|
|
||||||
|
**Related Issues**
|
||||||
|
<!--link related issues here-->
|
||||||
|
|
||||||
|
> Love react-native-camera? Please consider supporting our collective: 👉 https://opencollective.com/react-native-camera/donate
|
||||||
|
> Want this feature to be resolved faster? Please consider adding a bounty to it https://issuehunt.io/repos/33218414
|
||||||
21
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: Ask your question
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Question
|
||||||
|
|
||||||
|
**To Do First**
|
||||||
|
- [ ] Take a look in the [README](https://github.com/react-native-community/react-native-camera/blob/master/README.md)
|
||||||
|
- [ ] Take a look in the [docs](https://github.com/react-native-community/react-native-camera/blob/master/docs/RNCamera.md)
|
||||||
|
- [ ] Take a look in the [QA](https://github.com/react-native-community/react-native-camera/blob/master/docs/QA.md)
|
||||||
|
|
||||||
|
**Ask your Question**
|
||||||
|
<!--ask your question-->
|
||||||
|
|
||||||
|
**Tags**
|
||||||
|
<!--add some related tags to your question-->
|
||||||
|
|
||||||
|
> Love react-native-camera? Please consider supporting our collective: 👉 https://opencollective.com/react-native-camera/donate
|
||||||
|
> Want this issue to be resolved faster? Please consider adding a bounty to it https://issuehunt.io/repos/33218414
|
||||||
45
.github/stale.yml
vendored
Normal file
45
.github/stale.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Configuration for probot-stale based on: https://github.com/facebook/react-native/blob/master/.github/stale.yml
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||||
|
daysUntilClose: 7
|
||||||
|
|
||||||
|
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||||
|
exemptLabels:
|
||||||
|
- pinned
|
||||||
|
- security
|
||||||
|
- For Discussion
|
||||||
|
- semantic-release
|
||||||
|
- Needs revision
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a project (defaults to false)
|
||||||
|
exemptProjects: false
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
|
exemptMilestones: false
|
||||||
|
|
||||||
|
# Set to true to ignore issues with an assignee (defaults to false)
|
||||||
|
exemptAssignees: false
|
||||||
|
|
||||||
|
# Label to use when marking as stale
|
||||||
|
staleLabel: stale
|
||||||
|
|
||||||
|
# Comment to post when marking as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions. You may also mark this issue as a "discussion" and i
|
||||||
|
will leave this open.
|
||||||
|
|
||||||
|
# Comment to post when closing a stale Issue or Pull Request.
|
||||||
|
closeComment: >
|
||||||
|
Closing this issue after a prolonged period of inactivity. Fell free to reopen
|
||||||
|
this issue, if this still affecting you.
|
||||||
|
|
||||||
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
|
limitPerRun: 30
|
||||||
|
|
||||||
|
# Limit to only `issues` or `pulls`
|
||||||
|
only: issues
|
||||||
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# OSX
|
||||||
|
#
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Xcode
|
||||||
|
#
|
||||||
|
build/
|
||||||
|
*.pbxuser
|
||||||
|
!default.pbxuser
|
||||||
|
*.mode1v3
|
||||||
|
!default.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
!default.mode2v3
|
||||||
|
*.perspectivev3
|
||||||
|
!default.perspectivev3
|
||||||
|
xcuserdata
|
||||||
|
*.xccheckout
|
||||||
|
*.moved-aside
|
||||||
|
DerivedData
|
||||||
|
*.hmap
|
||||||
|
*.ipa
|
||||||
|
*.xcuserstate
|
||||||
|
project.xcworkspace
|
||||||
|
|
||||||
|
# Android/IJ
|
||||||
|
#
|
||||||
|
*.iml
|
||||||
|
.idea
|
||||||
|
.gradle
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# node.js
|
||||||
|
#
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# BUCK
|
||||||
|
buck-out/
|
||||||
|
\.buckd/
|
||||||
|
android/app/libs
|
||||||
|
android/keystores/debug.keystore
|
||||||
|
package-json.lock
|
||||||
|
|
||||||
|
# vscode
|
||||||
|
.vscode
|
||||||
|
examples/mlkit/android/app/google-services.json
|
||||||
|
examples/mlkit/ios/Pods
|
||||||
|
examples/mlkit/ios/mlkit/GoogleService-Info.plist
|
||||||
7
.npmignore
Normal file
7
.npmignore
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
/.github
|
||||||
|
/examples
|
||||||
|
circle.yml
|
||||||
|
commitlint.config.js
|
||||||
|
/android/build
|
||||||
|
/website
|
||||||
|
/docs
|
||||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
316
CHANGELOG.md
Normal file
316
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
[**THE CHANGELOG OF FURTHER VERSIONS (STARTING WITH 1.4.0) IS MAINTAINED WITH GITHUB RELEASES AND CAN BE FOUND HERE**](https://github.com/react-native-community/react-native-camera/releases)
|
||||||
|
|
||||||
|
|
||||||
|
#### 1.3.1-9 (2018-10-24)
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* **package:** bump to 1.3.0 ([501d4ad8](https://github.com/react-native-community/react-native-camera/commit/501d4ad8deb013f36abda18794fbf58c04bd190b))
|
||||||
|
* **yarn:** update yarn.lock ([fafe4c11](https://github.com/react-native-community/react-native-camera/commit/fafe4c119230a7378ab18ea1dd0634d8eb55a538))
|
||||||
|
|
||||||
|
##### Documentation Changes
|
||||||
|
|
||||||
|
* add slide-up zoom recipe ([737a5a2e](https://github.com/react-native-community/react-native-camera/commit/737a5a2ef51d52b5a82b2b16972ebe0df40a2fd4))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* Do not rely in to jcenter but google ([#1874](https://github.com/react-native-community/react-native-camera/pull/1874)) ([92615246](https://github.com/react-native-community/react-native-camera/commit/9261524609ac4d9f29a33fb94847ef78a50b38f3))
|
||||||
|
* Error retrieving camcorder profile params ([#1835](https://github.com/react-native-community/react-native-camera/pull/1835)) ([8de827e6](https://github.com/react-native-community/react-native-camera/commit/8de827e6c2a027b30668c108b147fb02affb8a35))
|
||||||
|
* **build:** fix no face detection project ([a7a7abf6](https://github.com/react-native-community/react-native-camera/commit/a7a7abf648e5f2c2f4b6f7e11bc3ed9669916a24))
|
||||||
|
|
||||||
|
##### Reverts
|
||||||
|
|
||||||
|
* check if face or text detector libraries are included independently ([#1882](https://github.com/react-native-community/react-native-camera/pull/1882)) ([9efd7554](https://github.com/react-native-community/react-native-camera/commit/9efd7554586deed6d8e59cce579b21479365f984))
|
||||||
|
|
||||||
|
### 1.3.0-8 (2018-09-26)
|
||||||
|
|
||||||
|
##### Build System / Dependencies
|
||||||
|
|
||||||
|
* **package:** 1.2.0 ([da8e79cd](https://github.com/react-native-community/react-native-camera/commit/da8e79cd7aa2d62fb1209892c83c440b8f80e0b6))
|
||||||
|
* **change-log:** 1.2.0 ([1da60b2f](https://github.com/react-native-community/react-native-camera/commit/1da60b2feb260748a3da268486717d854193e7b6))
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* **ts:** add doNotSave to ts type definitions ([f0c18b7c](https://github.com/react-native-community/react-native-camera/commit/f0c18b7c2074bb88a92df526525dcf8845afd854))
|
||||||
|
* **update:** update package.json packages ([#1739](https://github.com/react-native-community/react-native-camera/pull/1739)) ([843cbf4f](https://github.com/react-native-community/react-native-camera/commit/843cbf4ff685a3f211f3b5c31613943d1672bb88))
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* adds autoFocusPointOfInterest to iOS ([39cc29de](https://github.com/react-native-community/react-native-camera/commit/39cc29deca9c1de9bb4b1e3a9f31f07b32f7ddc6))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **rn-camera:**
|
||||||
|
* bump platform version to 9.0. closes [#1806](https://github.com/react-native-community/react-native-camera/pull/1806) ([bdbc2564](https://github.com/react-native-community/react-native-camera/commit/bdbc2564ba113e1316306333febb1763b1003aaf))
|
||||||
|
* use `componentDidMount` instead of `componentWillMount`. closes [#1809](https://github.com/react-native-community/react-native-camera/pull/1809) closes [#1760](https://github.com/react-native-community/react-native-camera/pull/1760) ([2d311ff1](https://github.com/react-native-community/react-native-camera/commit/2d311ff1866977d77d6b0018dbc2a7a2ee511040))
|
||||||
|
* added some missing typescript declarations ([#1792](https://github.com/react-native-community/react-native-camera/pull/1792)) ([a5c67376](https://github.com/react-native-community/react-native-camera/commit/a5c67376af4bdddef163480441cc773004b86471))
|
||||||
|
* android base64 string format for consistency with ios ([#1776](https://github.com/react-native-community/react-native-camera/pull/1776)) ([d4b4ee11](https://github.com/react-native-community/react-native-camera/commit/d4b4ee116861919d0ba70f3edea774d1ab252182))
|
||||||
|
* update docs and add check for focus mode support ([ca5a12b4](https://github.com/react-native-community/react-native-camera/commit/ca5a12b400c599e6ec76d60214c3b479891f4d02))
|
||||||
|
|
||||||
|
### 1.2.0-7 (2018-08-09)
|
||||||
|
|
||||||
|
##### Build System / Dependencies
|
||||||
|
|
||||||
|
* **change-log:** v1.1.5-2 ([e49e35a0](https://github.com/react-native-community/react-native-camera/commit/e49e35a085b1793cc8692d2c1600eb2e14ffbe75))
|
||||||
|
|
||||||
|
##### Documentation Changes
|
||||||
|
|
||||||
|
* **expo:** explain how to migrate to and from expo camera module ([#1605](https://github.com/react-native-community/react-native-camera/pull/1605)) ([4a9322cb](https://github.com/react-native-community/react-native-camera/commit/4a9322cb8b7d455fc28f7e67a15bff2fd9d7ea3e))
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* **preview:**
|
||||||
|
* add android code ([497a7039](https://github.com/react-native-community/react-native-camera/commit/497a703964e925b6e3e62e39a54a9734a7ed6c40))
|
||||||
|
* add new props to JS ([9bf9a2e3](https://github.com/react-native-community/react-native-camera/commit/9bf9a2e3162b919d98cab104029250394b2dd3a8))
|
||||||
|
* add preview methods and more fixes ([b9fb708f](https://github.com/react-native-community/react-native-camera/commit/b9fb708ffc3fd6865191ce6e2bd0a2404a9c657c))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **rn-camera:**
|
||||||
|
* fix codec backwards compat ([91f5bf45](https://github.com/react-native-community/react-native-camera/commit/91f5bf45672a8b83253ed17c3f90eee64b0f07bf))
|
||||||
|
* fix types, conversions and casts ([83d0618e](https://github.com/react-native-community/react-native-camera/commit/83d0618e988656dfd9a216b85394ceb5f3a05e9b))
|
||||||
|
* **picture-size:**
|
||||||
|
* create None default value ([ad87c8e3](https://github.com/react-native-community/react-native-camera/commit/ad87c8e3421f2ff1836674a01cb86deb619cdc4e))
|
||||||
|
* export method and change default value ([9efb7f14](https://github.com/react-native-community/react-native-camera/commit/9efb7f141f8970ad160c852fa837427a79f3d0dc))
|
||||||
|
|
||||||
|
##### Other Changes
|
||||||
|
|
||||||
|
* Implement video stabilization mode property for ios ([#1606](https://github.com/react-native-community/react-native-camera/pull/1606)) ([a090faa0](https://github.com/react-native-community/react-native-camera/commit/a090faa09b417afd41af3739ec2b895de9dca6b6))
|
||||||
|
|
||||||
|
#### 1.1.5-2 (2018-06-14)
|
||||||
|
|
||||||
|
##### Build System / Dependencies
|
||||||
|
|
||||||
|
* **change-log:**
|
||||||
|
* v1.1.4-6 ([86bf1d28](https://github.com/react-native-community/react-native-camera/commit/86bf1d284baf64caa94e3815c9ebed5b0e662369))
|
||||||
|
* v1.1.3-5 ([98b18950](https://github.com/react-native-community/react-native-camera/commit/98b1895038ccf47f94d7d27811f3540d3847feb7))
|
||||||
|
* v1.1.2-4 ([4f6b213d](https://github.com/react-native-community/react-native-camera/commit/4f6b213dc63e7ae96c77a1cf1627c14fcda99a94))
|
||||||
|
* v1.1.1-3 ([821a1b24](https://github.com/react-native-community/react-native-camera/commit/821a1b24e6251ad2a9ba9087c9a427a3b20d0778))
|
||||||
|
* v1.1.0 ([01e6c843](https://github.com/react-native-community/react-native-camera/commit/01e6c8434d87f4723feff7fec568028bfb140cb5))
|
||||||
|
* v1.1.0-2 ([deb42144](https://github.com/react-native-community/react-native-camera/commit/deb42144769c3ccc2e593d5dbf586abab244f219))
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* **cameraview:**
|
||||||
|
* integrate google's cameraview directly on rncamera? ([d11ed319](https://github.com/react-native-community/react-native-camera/commit/d11ed31917c26df151b4fb46ab166d2921a9ac99))
|
||||||
|
* update camera view ([501ffe83](https://github.com/react-native-community/react-native-camera/commit/501ffe8336b9d8bc9743c1ed803fe20b77f2c270))
|
||||||
|
* **lint:**
|
||||||
|
* more lint checks ([3bb9a648](https://github.com/react-native-community/react-native-camera/commit/3bb9a6484af306ac66083dd05ac6c46de542f3b4))
|
||||||
|
* fix some warnings ([7967e2fb](https://github.com/react-native-community/react-native-camera/commit/7967e2fbce44b15a77ae0cbddf76f0b37fc530ba))
|
||||||
|
* fix lint to make ci work ([919d07b1](https://github.com/react-native-community/react-native-camera/commit/919d07b162f4a39a2454bebdb387224e21a4ba7a))
|
||||||
|
* **package:** enforce no errors on lint and update packages ([00f4f4c1](https://github.com/react-native-community/react-native-camera/commit/00f4f4c13714a9d4e03a2cd76f2b19de7a78cfe4))
|
||||||
|
* **gms:** change default gms to 12.0.0 ([94c8968b](https://github.com/react-native-community/react-native-camera/commit/94c8968b2633cfa4e16d1e4275eb831065232014))
|
||||||
|
|
||||||
|
##### Documentation Changes
|
||||||
|
|
||||||
|
* **expo:** explain how to migrate to and from expo camera module ([#1605](https://github.com/react-native-community/react-native-camera/pull/1605)) ([4a9322cb](https://github.com/react-native-community/react-native-camera/commit/4a9322cb8b7d455fc28f7e67a15bff2fd9d7ea3e))
|
||||||
|
* **recipes:** add some recipes ([ef5c2fef](https://github.com/react-native-community/react-native-camera/commit/ef5c2fef14530110b0c5aec3a044ca27dcfa8d72))
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* **preview:**
|
||||||
|
* add android code ([497a7039](https://github.com/react-native-community/react-native-camera/commit/497a703964e925b6e3e62e39a54a9734a7ed6c40))
|
||||||
|
* add new props to JS ([9bf9a2e3](https://github.com/react-native-community/react-native-camera/commit/9bf9a2e3162b919d98cab104029250394b2dd3a8))
|
||||||
|
* add preview methods and more fixes ([b9fb708f](https://github.com/react-native-community/react-native-camera/commit/b9fb708ffc3fd6865191ce6e2bd0a2404a9c657c))
|
||||||
|
* **types:**
|
||||||
|
* add types for [#1547](https://github.com/react-native-community/react-native-camera/pull/1547) ([#1548](https://github.com/react-native-community/react-native-camera/pull/1548)) ([3ce3c80d](https://github.com/react-native-community/react-native-camera/commit/3ce3c80db670cc05dead7636d70dc8fc911a2c6b))
|
||||||
|
* add types for [#1523](https://github.com/react-native-community/react-native-camera/pull/1523) ([f61004de](https://github.com/react-native-community/react-native-camera/commit/f61004de623a2011e99a6a8092048b513025f5ed))
|
||||||
|
* add types for [#1518](https://github.com/react-native-community/react-native-camera/pull/1518) (FaCC) ([842dc1cb](https://github.com/react-native-community/react-native-camera/commit/842dc1cb581bd28653549dee86f70c2ff5d65ee2))
|
||||||
|
* add types for [#1441](https://github.com/react-native-community/react-native-camera/pull/1441) ([be3e0ebf](https://github.com/react-native-community/react-native-camera/commit/be3e0ebfb8ff42a48211b55054325548cd304694))
|
||||||
|
* add types for [#1428](https://github.com/react-native-community/react-native-camera/pull/1428) ([6cc3d89b](https://github.com/react-native-community/react-native-camera/commit/6cc3d89bec2a55b31c2e7c4f0e597eafc8c31323))
|
||||||
|
* add types for text detection feature ([c0ace2e9](https://github.com/react-native-community/react-native-camera/commit/c0ace2e94c47a9122a386bcbe99911182da80744))
|
||||||
|
* **rn-camera:** use and export constants ([c8c6fdea](https://github.com/react-native-community/react-native-camera/commit/c8c6fdea0bf15de60c638f504f38dcb9ac80a3e4))
|
||||||
|
* **rn_camera:** add function as children ([45cc8f25](https://github.com/react-native-community/react-native-camera/commit/45cc8f25d2de71b9eee29e1fe14e2f4f3d2feee9))
|
||||||
|
* **ci:** add first circleci lint and check script ([ee385eec](https://github.com/react-native-community/react-native-camera/commit/ee385eec05b9be5e1f96524206e50aa96085ce19))
|
||||||
|
* **android:** make android gradle check work ([1c7f231a](https://github.com/react-native-community/react-native-camera/commit/1c7f231af460127bebf1f9970367bf64987de34b))
|
||||||
|
* **play-sound:** play sound on capture (android) ([69242183](https://github.com/react-native-community/react-native-camera/commit/69242183cc65460040795b866095f34090a9598d))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **rn-camera:**
|
||||||
|
* fix codec backwards compat ([91f5bf45](https://github.com/react-native-community/react-native-camera/commit/91f5bf45672a8b83253ed17c3f90eee64b0f07bf))
|
||||||
|
* fix types, conversions and casts ([83d0618e](https://github.com/react-native-community/react-native-camera/commit/83d0618e988656dfd9a216b85394ceb5f3a05e9b))
|
||||||
|
* inject correct status ([858cc4c9](https://github.com/react-native-community/react-native-camera/commit/858cc4c9c8fd456390b274ee4cfddb62fee198ee))
|
||||||
|
* **picture-size:**
|
||||||
|
* create None default value ([ad87c8e3](https://github.com/react-native-community/react-native-camera/commit/ad87c8e3421f2ff1836674a01cb86deb619cdc4e))
|
||||||
|
* export method and change default value ([9efb7f14](https://github.com/react-native-community/react-native-camera/commit/9efb7f141f8970ad160c852fa837427a79f3d0dc))
|
||||||
|
* **cache:** store video recordings in same directory as photos ([bba84a98](https://github.com/react-native-community/react-native-camera/commit/bba84a983446c25f76aa77793f49d4252cd63ea3))
|
||||||
|
* **rn_camera:** improve naming ([3811d82c](https://github.com/react-native-community/react-native-camera/commit/3811d82c75ceedc27b8aa5550e352159d5daf2b8))
|
||||||
|
* **search-paths:** remove unnecessary search paths and add missing one ([dee298b4](https://github.com/react-native-community/react-native-camera/commit/dee298b4fefca4659468fd43e914fd1c970ca930))
|
||||||
|
* **styles:** place style sheet above everything,prevent undefined styles ([01501892](https://github.com/react-native-community/react-native-camera/commit/01501892b5711db765cc367a24ba7c3233678791))
|
||||||
|
* **warnings:** remove inline styles ([716c4e38](https://github.com/react-native-community/react-native-camera/commit/716c4e389da45fd7d240a8b4acf60a620fa2c372))
|
||||||
|
* **barcode:** better name google variables and correct init ([38e96ed2](https://github.com/react-native-community/react-native-camera/commit/38e96ed24d6b59e108a0ac175eefff22d7b33c27))
|
||||||
|
* **Android:** image stretched instead of cropped ([73eb5fd2](https://github.com/react-native-community/react-native-camera/commit/73eb5fd272c28a6369705d30379dcabae3429301))
|
||||||
|
* **barcode-prop:** fix default value and add more values ([2c87b44b](https://github.com/react-native-community/react-native-camera/commit/2c87b44b1660f44e9f2bc8e7fce207c872933806))
|
||||||
|
* **docs:**
|
||||||
|
* move skipProcessing to 'Supported options' ([8054200f](https://github.com/react-native-community/react-native-camera/commit/8054200f81a754ae2d29532b636f55331e996703))
|
||||||
|
* Header on the wrong position ([589a0819](https://github.com/react-native-community/react-native-camera/commit/589a08192930f96aa4f7cf255aa4ac0adfd31a12))
|
||||||
|
* **types:** fix types for [#1402](https://github.com/react-native-community/react-native-camera/pull/1402) ([26f9a1e5](https://github.com/react-native-community/react-native-camera/commit/26f9a1e53b3f3b21b86f28d27236849995e7baf9))
|
||||||
|
* **ios:** add video output early to avoid underexposed beginning ([9ef5b29a](https://github.com/react-native-community/react-native-camera/commit/9ef5b29ad5d66f0e6d52e504dab00b862148c60f))
|
||||||
|
|
||||||
|
##### Other Changes
|
||||||
|
|
||||||
|
* Implement video stabilization mode property for ios ([#1606](https://github.com/react-native-community/react-native-camera/pull/1606)) ([a090faa0](https://github.com/react-native-community/react-native-camera/commit/a090faa09b417afd41af3739ec2b895de9dca6b6))
|
||||||
|
* Fix java.lang.ArrayIndexOutOfBoundsException with image rotation ([6ce014d3](https://github.com/react-native-community/react-native-camera/commit/6ce014d3ca3805f908fbdcd30da9b982de3bc2da))
|
||||||
|
|
||||||
|
#### 1.1.4-6 (2018-05-21)
|
||||||
|
|
||||||
|
#### 1.1.3-5 (2018-05-18)
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* **types:**
|
||||||
|
* add types for [#1547](https://github.com/react-native-community/react-native-camera/pull/1547) ([#1548](https://github.com/react-native-community/react-native-camera/pull/1548)) ([3ce3c80d](https://github.com/react-native-community/react-native-camera/commit/3ce3c80db670cc05dead7636d70dc8fc911a2c6b))
|
||||||
|
* add types for [#1523](https://github.com/react-native-community/react-native-camera/pull/1523) ([f61004de](https://github.com/react-native-community/react-native-camera/commit/f61004de623a2011e99a6a8092048b513025f5ed))
|
||||||
|
* add types for [#1518](https://github.com/react-native-community/react-native-camera/pull/1518) (FaCC) ([842dc1cb](https://github.com/react-native-community/react-native-camera/commit/842dc1cb581bd28653549dee86f70c2ff5d65ee2))
|
||||||
|
* **rn-camera:** use and export constants ([c8c6fdea](https://github.com/react-native-community/react-native-camera/commit/c8c6fdea0bf15de60c638f504f38dcb9ac80a3e4))
|
||||||
|
* **rn_camera:** add function as children ([45cc8f25](https://github.com/react-native-community/react-native-camera/commit/45cc8f25d2de71b9eee29e1fe14e2f4f3d2feee9))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **rn-camera:** inject correct status ([858cc4c9](https://github.com/react-native-community/react-native-camera/commit/858cc4c9c8fd456390b274ee4cfddb62fee198ee))
|
||||||
|
* **cache:** store video recordings in same directory as photos ([bba84a98](https://github.com/react-native-community/react-native-camera/commit/bba84a983446c25f76aa77793f49d4252cd63ea3))
|
||||||
|
* **rn_camera:** improve naming ([3811d82c](https://github.com/react-native-community/react-native-camera/commit/3811d82c75ceedc27b8aa5550e352159d5daf2b8))
|
||||||
|
|
||||||
|
##### Other Changes
|
||||||
|
|
||||||
|
* Fix java.lang.ArrayIndexOutOfBoundsException with image rotation ([6ce014d3](https://github.com/react-native-community/react-native-camera/commit/6ce014d3ca3805f908fbdcd30da9b982de3bc2da))
|
||||||
|
|
||||||
|
#### 1.1.2-4 (2018-04-25)
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* **cameraview:** integrate google's cameraview directly on rncamera? ([d11ed319](https://github.com/react-native-community/react-native-camera/commit/d11ed31917c26df151b4fb46ab166d2921a9ac99))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **search-paths:** remove unnecessary search paths and add missing one ([dee298b4](https://github.com/react-native-community/react-native-camera/commit/dee298b4fefca4659468fd43e914fd1c970ca930))
|
||||||
|
|
||||||
|
#### 1.1.1-3 (2018-04-15)
|
||||||
|
|
||||||
|
##### Build System / Dependencies
|
||||||
|
|
||||||
|
* **change-log:** v1.1.0 ([01e6c843](https://github.com/react-native-community/react-native-camera/commit/01e6c8434d87f4723feff7fec568028bfb140cb5))
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* **lint:**
|
||||||
|
* more lint checks ([3bb9a648](https://github.com/react-native-community/react-native-camera/commit/3bb9a6484af306ac66083dd05ac6c46de542f3b4))
|
||||||
|
* fix some warnings ([7967e2fb](https://github.com/react-native-community/react-native-camera/commit/7967e2fbce44b15a77ae0cbddf76f0b37fc530ba))
|
||||||
|
* fix lint to make ci work ([919d07b1](https://github.com/react-native-community/react-native-camera/commit/919d07b162f4a39a2454bebdb387224e21a4ba7a))
|
||||||
|
* **package:** enforce no errors on lint and update packages ([00f4f4c1](https://github.com/react-native-community/react-native-camera/commit/00f4f4c13714a9d4e03a2cd76f2b19de7a78cfe4))
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* **ci:** add first circleci lint and check script ([ee385eec](https://github.com/react-native-community/react-native-camera/commit/ee385eec05b9be5e1f96524206e50aa96085ce19))
|
||||||
|
* **android:** make android gradle check work ([1c7f231a](https://github.com/react-native-community/react-native-camera/commit/1c7f231af460127bebf1f9970367bf64987de34b))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **styles:** place style sheet above everything,prevent undefined styles ([01501892](https://github.com/react-native-community/react-native-camera/commit/01501892b5711db765cc367a24ba7c3233678791))
|
||||||
|
* **warnings:** remove inline styles ([716c4e38](https://github.com/react-native-community/react-native-camera/commit/716c4e389da45fd7d240a8b4acf60a620fa2c372))
|
||||||
|
|
||||||
|
### 1.1.0-2 (2018-04-15)
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* **gms:** change default gms to 12.0.0 ([94c8968b](https://github.com/react-native-community/react-native-camera/commit/94c8968b2633cfa4e16d1e4275eb831065232014))
|
||||||
|
* **cameraview:** update camera view ([501ffe83](https://github.com/react-native-community/react-native-camera/commit/501ffe8336b9d8bc9743c1ed803fe20b77f2c270))
|
||||||
|
|
||||||
|
##### Documentation Changes
|
||||||
|
|
||||||
|
* **recipes:** add some recipes ([ef5c2fef](https://github.com/react-native-community/react-native-camera/commit/ef5c2fef14530110b0c5aec3a044ca27dcfa8d72))
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* **types:**
|
||||||
|
* add types for [#1441](https://github.com/react-native-community/react-native-camera/pull/1441) ([be3e0ebf](https://github.com/react-native-community/react-native-camera/commit/be3e0ebfb8ff42a48211b55054325548cd304694))
|
||||||
|
* add types for [#1428](https://github.com/react-native-community/react-native-camera/pull/1428) ([6cc3d89b](https://github.com/react-native-community/react-native-camera/commit/6cc3d89bec2a55b31c2e7c4f0e597eafc8c31323))
|
||||||
|
* add types for text detection feature ([c0ace2e9](https://github.com/react-native-community/react-native-camera/commit/c0ace2e94c47a9122a386bcbe99911182da80744))
|
||||||
|
* **play-sound:** play sound on capture (android) ([69242183](https://github.com/react-native-community/react-native-camera/commit/69242183cc65460040795b866095f34090a9598d))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **barcode:** better name google variables and correct init ([38e96ed2](https://github.com/react-native-community/react-native-camera/commit/38e96ed24d6b59e108a0ac175eefff22d7b33c27))
|
||||||
|
* **Android:** image stretched instead of cropped ([73eb5fd2](https://github.com/react-native-community/react-native-camera/commit/73eb5fd272c28a6369705d30379dcabae3429301))
|
||||||
|
* **barcode-prop:** fix default value and add more values ([2c87b44b](https://github.com/react-native-community/react-native-camera/commit/2c87b44b1660f44e9f2bc8e7fce207c872933806))
|
||||||
|
* **docs:**
|
||||||
|
* move skipProcessing to 'Supported options' ([8054200f](https://github.com/react-native-community/react-native-camera/commit/8054200f81a754ae2d29532b636f55331e996703))
|
||||||
|
* Header on the wrong position ([589a0819](https://github.com/react-native-community/react-native-camera/commit/589a08192930f96aa4f7cf255aa4ac0adfd31a12))
|
||||||
|
* **types:** fix types for [#1402](https://github.com/react-native-community/react-native-camera/pull/1402) ([26f9a1e5](https://github.com/react-native-community/react-native-camera/commit/26f9a1e53b3f3b21b86f28d27236849995e7baf9))
|
||||||
|
* **ios:** add video output early to avoid underexposed beginning ([9ef5b29a](https://github.com/react-native-community/react-native-camera/commit/9ef5b29ad5d66f0e6d52e504dab00b862148c60f))
|
||||||
|
|
||||||
|
#### 1.0.3-1 (2018-03-24)
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* restored original CameraModule.java ([7bea109e](https://github.com/react-native-community/react-native-camera/commit/7bea109e47a5b7302069f9774a4c7fb2d1652275))
|
||||||
|
|
||||||
|
##### Documentation Changes
|
||||||
|
|
||||||
|
* **rncamera:**
|
||||||
|
* specifying onTextRecognized callback prototype ([48611212](https://github.com/react-native-community/react-native-camera/commit/48611212f56eed8d9594693c84fe3f00cbb8448b))
|
||||||
|
* docs for text recognition usage ([68639b82](https://github.com/react-native-community/react-native-camera/commit/68639b82ed98ef53ac1a0cc1762c35c5941b61b6))
|
||||||
|
* **codec:** document ios codec option ([2b9d8db2](https://github.com/react-native-community/react-native-camera/commit/2b9d8db21389af624fd7ee3fe0eafa8348a3b776))
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* **chore:** try to automate changelog ([cc5f6e62](https://github.com/react-native-community/react-native-camera/commit/cc5f6e62eb78a7de884a3b770eaa12c03a626721))
|
||||||
|
* **android:**
|
||||||
|
* integrating Google Vision's text recognition ([fcaa9452](https://github.com/react-native-community/react-native-camera/commit/fcaa9452865247ba8aa63e6fd323bd86ea0f7401))
|
||||||
|
|
||||||
|
* **Android:**
|
||||||
|
* **types:** update types for video recording codec ([f9252254](https://github.com/react-native-community/react-native-camera/commit/f925225484ca1599652039b612fc7deba635de6f))
|
||||||
|
* **rn-camera:** add codec option for ios ([c0d5aabf](https://github.com/react-native-community/react-native-camera/commit/c0d5aabf0b32f71326ff153d31e3cb5c588062da))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **typo:** fix typo on package.json ([706278d8](https://github.com/react-native-community/react-native-camera/commit/706278d807edac5bc9eb606e29b3326790d7816c))
|
||||||
|
* **textrecognition:** height of text block ([01e763b1](https://github.com/react-native-community/react-native-camera/commit/01e763b1430cdb65d82c78c08a5215da65706e6d))
|
||||||
|
* issue [#1246](https://github.com/react-native-community/react-native-camera/pull/1246) - torch will be disabled when starting the record ([8c696017](https://github.com/react-native-community/react-native-camera/commit/8c6960178922492bf49fc44fbab25b638209dc4e))
|
||||||
|
* **ios-project:** fix path to parent's ios project ([4496c321](https://github.com/react-native-community/react-native-camera/commit/4496c3217195853a36c261415f126140ddebbcc4))
|
||||||
|
|
||||||
|
#### 1.0.2 (2018-03-10)
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* **flow:** add missing types to Picture options ([6bff4d93](https://github.com/react-native-community/react-native-camera/commit/6bff4d935ac421f4aea395c58f5916df78cdae0a))
|
||||||
|
* **types:** add new keys to TakePictureOptions ([cc272036](https://github.com/react-native-community/react-native-camera/commit/cc272036581f68dbdce1b596644a158a42c471dc))
|
||||||
|
* **face-detector:** make face detection stoppage smoother ([3b3c38dd](https://github.com/react-native-community/react-native-camera/commit/3b3c38dd7d08edd1dad3b6c7fb944515fcb1e9c4))
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* **types:**
|
||||||
|
* add FaceDetector declarations ([ba218750](https://github.com/react-native-community/react-native-camera/commit/ba21875001df2e260feb87d71411ff89fe6942ea))
|
||||||
|
* add TypeScript definition files ([a94bad5e](https://github.com/react-native-community/react-native-camera/commit/a94bad5e3739927dd50b850f68ed57a59f782e99))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **types:**
|
||||||
|
* fix onBarCodeRead type ([a9947b47](https://github.com/react-native-community/react-native-camera/commit/a9947b47d569227ed6b83ef2988a8cbd3e6b7b41))
|
||||||
|
* fix definition for RNCameraProps.ratio ([4d1616c5](https://github.com/react-native-community/react-native-camera/commit/4d1616c57a059127db07f52ca18a8b092ba559ad))
|
||||||
|
* **android-camera:** revert to old camera api ([8d9c06ad](https://github.com/react-native-community/react-native-camera/commit/8d9c06ad903b40abc8bef67927d4621c494aeb3b))
|
||||||
|
|
||||||
|
#### 1.0.1 (2018-02-14)
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* **release-script:** add script to package json ([b0503dc8](https://github.com/react-native-community/react-native-camera/commit/b0503dc8aefc1d2a992c1778e00c5d0f8dfd6901))
|
||||||
|
* **changelog:** add changelog script ([d2263937](https://github.com/react-native-community/react-native-camera/commit/d226393783748f973cc99032343fc55e45828717))
|
||||||
|
* **mirror:** add option to give "mirrorImage" flag to takePicture. ([0b6f0abd](https://github.com/react-native-community/react-native-camera/commit/0b6f0abda07b8a9ff3daa1722a254087f30eec08))
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* **focusWarning:** fix focus depth warning being shown for ios. ([79698b81](https://github.com/react-native-community/react-native-camera/commit/79698b815b44507037a6e89fda40b5c505703c00))
|
||||||
|
* **imports:** delete some useless imports which may cause problems ([a5b9f7e7](https://github.com/react-native-community/react-native-camera/commit/a5b9f7e717bc11aad9a8e5d9e9a449ad7fd9c9fa))
|
||||||
|
|
||||||
|
### master
|
||||||
|
|
||||||
|
### 1.0.0
|
||||||
|
- RNCamera as main camera implementation for both iOS and Android (base on expo module)
|
||||||
|
- FaceDetector feature for both iOS and Android (based on expo module)
|
||||||
|
- RCTCamera deprecated
|
||||||
|
|
||||||
|
### 0.13.0
|
||||||
|
- added RNCamera implementation for android
|
||||||
|
- added FaceDetector for android
|
||||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM node:8.11.4
|
||||||
|
|
||||||
|
WORKDIR /app/website
|
||||||
|
|
||||||
|
EXPOSE 3000 35729
|
||||||
|
COPY ./docs /app/docs
|
||||||
|
COPY ./website /app/website
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
CMD ["yarn", "start"]
|
||||||
22
LICENSE
Normal file
22
LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Loch Wansbrough
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
2
README.md
Normal file
2
README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
This is edit of [https://react-native-community.github.io/react-native-camera/] for indivisual purpose
|
||||||
246
THIRD-PARTY-LICENSES
Normal file
246
THIRD-PARTY-LICENSES
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
expo/expo
|
||||||
|
https://github.com/expo/expo
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
BSD License
|
||||||
|
|
||||||
|
For Exponent software
|
||||||
|
|
||||||
|
Copyright (c) 2015-present, 650 Industries, Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the names 650 Industries, Exponent, nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
google/cameraview
|
||||||
|
https://github.com/google/cameraview
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
6
android/.classpath
Normal file
6
android/.classpath
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
|
</classpath>
|
||||||
23
android/.project
Normal file
23
android/.project
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>react-native-camera</name>
|
||||||
|
<comment>Project react-native-camera created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
2
android/.settings/org.eclipse.buildship.core.prefs
Normal file
2
android/.settings/org.eclipse.buildship.core.prefs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
connection.project.dir=../../../android
|
||||||
|
eclipse.preferences.version=1
|
||||||
90
android/build.gradle
Normal file
90
android/build.gradle
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
def safeExtGet(prop, fallback) {
|
||||||
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
// The Android Gradle plugin is only required when opening the android folder stand-alone.
|
||||||
|
// This avoids unnecessary downloads and potential conflicts when the library is included as a
|
||||||
|
// module dependency in an application project.
|
||||||
|
if (project == rootProject) {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
//noinspection GradleDependency
|
||||||
|
classpath("com.android.tools.build:gradle:3.5.2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion safeExtGet('compileSdkVersion', 28)
|
||||||
|
buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion safeExtGet('minSdkVersion', 16)
|
||||||
|
targetSdkVersion safeExtGet('targetSdkVersion', 28)
|
||||||
|
}
|
||||||
|
|
||||||
|
flavorDimensions "react-native-camera"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
general {
|
||||||
|
dimension "react-native-camera"
|
||||||
|
}
|
||||||
|
mlkit {
|
||||||
|
dimension "react-native-camera"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java.srcDirs = ['src/main/java']
|
||||||
|
}
|
||||||
|
general {
|
||||||
|
java.srcDirs = ['src/general/java']
|
||||||
|
}
|
||||||
|
mlkit {
|
||||||
|
java.srcDirs = ['src/mlkit/java']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
warning 'InvalidPackage'
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/androidx.exifinterface_exifinterface.version'
|
||||||
|
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
|
maven {
|
||||||
|
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||||
|
url "$rootDir/../node_modules/react-native/android"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
def googlePlayServicesVisionVersion = safeExtGet('googlePlayServicesVisionVersion', safeExtGet('googlePlayServicesVersion', '17.0.2'))
|
||||||
|
|
||||||
|
implementation 'com.facebook.react:react-native:+'
|
||||||
|
implementation "com.google.zxing:core:3.3.3"
|
||||||
|
implementation "com.drewnoakes:metadata-extractor:2.11.0"
|
||||||
|
generalImplementation "com.google.android.gms:play-services-vision:$googlePlayServicesVisionVersion"
|
||||||
|
implementation "androidx.exifinterface:exifinterface:1.0.0"
|
||||||
|
implementation "androidx.annotation:annotation:1.0.0"
|
||||||
|
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||||
|
mlkitImplementation "com.google.firebase:firebase-ml-vision:${safeExtGet('firebase-ml-vision', '19.0.3')}"
|
||||||
|
mlkitImplementation "com.google.firebase:firebase-ml-vision-face-model:${safeExtGet('firebase-ml-vision-face-model', '17.0.2')}"
|
||||||
|
}
|
||||||
2
android/gradle.properties
Normal file
2
android/gradle.properties
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.useAndroidX=true
|
||||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#Wed Jan 23 23:35:17 CST 2019
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
||||||
160
android/gradlew
vendored
Normal file
160
android/gradlew
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
90
android/gradlew.bat
vendored
Normal file
90
android/gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windowz variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
goto execute
|
||||||
|
|
||||||
|
:4NT_args
|
||||||
|
@rem Get arguments from the 4NT Shell from JP Software
|
||||||
|
set CMD_LINE_ARGS=%$
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package org.reactnative.barcodedetector;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import com.google.android.gms.vision.barcode.Barcode;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BarcodeFormatUtils {
|
||||||
|
|
||||||
|
public static final SparseArray<String> FORMATS;
|
||||||
|
public static final Map<String, Integer> REVERSE_FORMATS;
|
||||||
|
|
||||||
|
private static final String UNKNOWN_FORMAT_STRING = "UNKNOWN_FORMAT";
|
||||||
|
private static final int UNKNOWN_FORMAT_INT = -1;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Initialize integer to string map
|
||||||
|
SparseArray<String> map = new SparseArray<>();
|
||||||
|
map.put(Barcode.CODE_128, "CODE_128");
|
||||||
|
map.put(Barcode.CODE_39, "CODE_39");
|
||||||
|
map.put(Barcode.CODE_93, "CODE_93");
|
||||||
|
map.put(Barcode.CODABAR, "CODABAR");
|
||||||
|
map.put(Barcode.DATA_MATRIX, "DATA_MATRIX");
|
||||||
|
map.put(Barcode.EAN_13, "EAN_13");
|
||||||
|
map.put(Barcode.EAN_8, "EAN_8");
|
||||||
|
map.put(Barcode.ITF, "ITF");
|
||||||
|
map.put(Barcode.QR_CODE, "QR_CODE");
|
||||||
|
map.put(Barcode.UPC_A, "UPC_A");
|
||||||
|
map.put(Barcode.UPC_E, "UPC_E");
|
||||||
|
map.put(Barcode.PDF417, "PDF417");
|
||||||
|
map.put(Barcode.AZTEC, "AZTEC");
|
||||||
|
map.put(Barcode.ALL_FORMATS, "ALL");
|
||||||
|
map.put(Barcode.CALENDAR_EVENT, "CALENDAR_EVENT");
|
||||||
|
map.put(Barcode.CONTACT_INFO, "CONTACT_INFO");
|
||||||
|
map.put(Barcode.DRIVER_LICENSE, "DRIVER_LICENSE");
|
||||||
|
map.put(Barcode.EMAIL, "EMAIL");
|
||||||
|
map.put(Barcode.GEO, "GEO");
|
||||||
|
map.put(Barcode.ISBN, "ISBN");
|
||||||
|
map.put(Barcode.PHONE, "PHONE");
|
||||||
|
map.put(Barcode.PRODUCT, "PRODUCT");
|
||||||
|
map.put(Barcode.SMS, "SMS");
|
||||||
|
map.put(Barcode.TEXT, "TEXT");
|
||||||
|
map.put(Barcode.UPC_A, "UPC_A");
|
||||||
|
map.put(Barcode.URL, "URL");
|
||||||
|
map.put(Barcode.WIFI, "WIFI");
|
||||||
|
map.put(-1, "None");
|
||||||
|
FORMATS = map;
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize string to integer map
|
||||||
|
Map<String, Integer> rmap = new HashMap<>();
|
||||||
|
for (int i = 0; i < map.size(); i++) {
|
||||||
|
rmap.put(map.valueAt(i), map.keyAt(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
REVERSE_FORMATS = Collections.unmodifiableMap(rmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String get(int format) {
|
||||||
|
return FORMATS.get(format, UNKNOWN_FORMAT_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int get(String format) {
|
||||||
|
if (REVERSE_FORMATS.containsKey(format)) {
|
||||||
|
return REVERSE_FORMATS.get(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN_FORMAT_INT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.reactnative.barcodedetector;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import com.google.android.gms.vision.barcode.Barcode;
|
||||||
|
import com.google.android.gms.vision.barcode.BarcodeDetector;
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
import org.reactnative.frame.RNFrame;
|
||||||
|
|
||||||
|
public class RNBarcodeDetector {
|
||||||
|
|
||||||
|
public static int NORMAL_MODE = 0;
|
||||||
|
public static int ALTERNATE_MODE = 1;
|
||||||
|
public static int INVERTED_MODE = 2;
|
||||||
|
public static int ALL_FORMATS = Barcode.ALL_FORMATS;
|
||||||
|
|
||||||
|
private BarcodeDetector mBarcodeDetector = null;
|
||||||
|
private ImageDimensions mPreviousDimensions;
|
||||||
|
private BarcodeDetector.Builder mBuilder;
|
||||||
|
|
||||||
|
private int mBarcodeType = Barcode.ALL_FORMATS;
|
||||||
|
|
||||||
|
public RNBarcodeDetector(Context context) {
|
||||||
|
mBuilder = new BarcodeDetector.Builder(context)
|
||||||
|
.setBarcodeFormats(mBarcodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
|
||||||
|
public boolean isOperational() {
|
||||||
|
if (mBarcodeDetector == null) {
|
||||||
|
createBarcodeDetector();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mBarcodeDetector.isOperational();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SparseArray<Barcode> detect(RNFrame frame) {
|
||||||
|
// If the frame has different dimensions, create another barcode detector.
|
||||||
|
// Otherwise we will most likely get nasty "inconsistent image dimensions" error from detector
|
||||||
|
// and no barcode will be detected.
|
||||||
|
if (!frame.getDimensions().equals(mPreviousDimensions)) {
|
||||||
|
releaseBarcodeDetector();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mBarcodeDetector == null) {
|
||||||
|
createBarcodeDetector();
|
||||||
|
mPreviousDimensions = frame.getDimensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mBarcodeDetector.detect(frame.getFrame());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarcodeType(int barcodeType) {
|
||||||
|
if (barcodeType != mBarcodeType) {
|
||||||
|
release();
|
||||||
|
mBuilder.setBarcodeFormats(barcodeType);
|
||||||
|
mBarcodeType = barcodeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
releaseBarcodeDetector();
|
||||||
|
mPreviousDimensions = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifecycle methods
|
||||||
|
|
||||||
|
private void releaseBarcodeDetector() {
|
||||||
|
if (mBarcodeDetector != null) {
|
||||||
|
mBarcodeDetector.release();
|
||||||
|
mBarcodeDetector = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createBarcodeDetector() {
|
||||||
|
mBarcodeDetector = mBuilder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.google.android.gms.vision.barcode.Barcode;
|
||||||
|
|
||||||
|
import org.reactnative.barcodedetector.BarcodeFormatUtils;
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
import org.reactnative.frame.RNFrame;
|
||||||
|
import org.reactnative.frame.RNFrameFactory;
|
||||||
|
import org.reactnative.barcodedetector.RNBarcodeDetector;
|
||||||
|
|
||||||
|
public class BarcodeDetectorAsyncTask extends android.os.AsyncTask<Void, Void, SparseArray<Barcode>> {
|
||||||
|
|
||||||
|
private byte[] mImageData;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private int mRotation;
|
||||||
|
private RNBarcodeDetector mBarcodeDetector;
|
||||||
|
private BarcodeDetectorAsyncTaskDelegate mDelegate;
|
||||||
|
private double mScaleX;
|
||||||
|
private double mScaleY;
|
||||||
|
private ImageDimensions mImageDimensions;
|
||||||
|
private int mPaddingLeft;
|
||||||
|
private int mPaddingTop;
|
||||||
|
|
||||||
|
public BarcodeDetectorAsyncTask(
|
||||||
|
BarcodeDetectorAsyncTaskDelegate delegate,
|
||||||
|
RNBarcodeDetector barcodeDetector,
|
||||||
|
byte[] imageData,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rotation,
|
||||||
|
float density,
|
||||||
|
int facing,
|
||||||
|
int viewWidth,
|
||||||
|
int viewHeight,
|
||||||
|
int viewPaddingLeft,
|
||||||
|
int viewPaddingTop
|
||||||
|
) {
|
||||||
|
mImageData = imageData;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
mRotation = rotation;
|
||||||
|
mDelegate = delegate;
|
||||||
|
mBarcodeDetector = barcodeDetector;
|
||||||
|
mImageDimensions = new ImageDimensions(width, height, rotation, facing);
|
||||||
|
mScaleX = (double) (viewWidth) / (mImageDimensions.getWidth() * density);
|
||||||
|
mScaleY = (double) (viewHeight) / (mImageDimensions.getHeight() * density);
|
||||||
|
mPaddingLeft = viewPaddingLeft;
|
||||||
|
mPaddingTop = viewPaddingTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SparseArray<Barcode> doInBackground(Void... ignored) {
|
||||||
|
if (isCancelled() || mDelegate == null || mBarcodeDetector == null || !mBarcodeDetector.isOperational()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNFrame frame = RNFrameFactory.buildFrame(mImageData, mWidth, mHeight, mRotation);
|
||||||
|
return mBarcodeDetector.detect(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(SparseArray<Barcode> barcodes) {
|
||||||
|
super.onPostExecute(barcodes);
|
||||||
|
|
||||||
|
if (barcodes == null) {
|
||||||
|
mDelegate.onBarcodeDetectionError(mBarcodeDetector);
|
||||||
|
} else {
|
||||||
|
if (barcodes.size() > 0) {
|
||||||
|
mDelegate.onBarcodesDetected(serializeEventData(barcodes));
|
||||||
|
}
|
||||||
|
mDelegate.onBarcodeDetectingTaskCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableArray serializeEventData(SparseArray<Barcode> barcodes) {
|
||||||
|
WritableArray barcodesList = Arguments.createArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < barcodes.size(); i++) {
|
||||||
|
Barcode barcode = barcodes.valueAt(i);
|
||||||
|
WritableMap serializedBarcode = Arguments.createMap();
|
||||||
|
|
||||||
|
serializedBarcode.putString("data", barcode.displayValue);
|
||||||
|
serializedBarcode.putString("rawData", barcode.rawValue);
|
||||||
|
serializedBarcode.putString("type", BarcodeFormatUtils.get(barcode.format));
|
||||||
|
serializedBarcode.putMap("bounds", processBounds(barcode.getBoundingBox()));
|
||||||
|
barcodesList.pushMap(serializedBarcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return barcodesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap processBounds(Rect frame) {
|
||||||
|
WritableMap origin = Arguments.createMap();
|
||||||
|
int x = frame.left;
|
||||||
|
int y = frame.top;
|
||||||
|
|
||||||
|
if (frame.left < mWidth / 2) {
|
||||||
|
x = x + mPaddingLeft / 2;
|
||||||
|
} else if (frame.left > mWidth /2) {
|
||||||
|
x = x - mPaddingLeft / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.top < mHeight / 2) {
|
||||||
|
y = y + mPaddingTop / 2;
|
||||||
|
} else if (frame.top > mHeight / 2) {
|
||||||
|
y = y - mPaddingTop / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
origin.putDouble("x", x * mScaleX);
|
||||||
|
origin.putDouble("y", y * mScaleY);
|
||||||
|
|
||||||
|
WritableMap size = Arguments.createMap();
|
||||||
|
size.putDouble("width", frame.width() * mScaleX);
|
||||||
|
size.putDouble("height", frame.height() * mScaleY);
|
||||||
|
|
||||||
|
WritableMap bounds = Arguments.createMap();
|
||||||
|
bounds.putMap("origin", origin);
|
||||||
|
bounds.putMap("size", size);
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.google.android.cameraview.CameraView;
|
||||||
|
import com.google.android.gms.vision.face.Face;
|
||||||
|
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
import org.reactnative.facedetector.FaceDetectorUtils;
|
||||||
|
import org.reactnative.frame.RNFrame;
|
||||||
|
import org.reactnative.frame.RNFrameFactory;
|
||||||
|
import org.reactnative.facedetector.RNFaceDetector;
|
||||||
|
|
||||||
|
public class FaceDetectorAsyncTask extends android.os.AsyncTask<Void, Void, SparseArray<Face>> {
|
||||||
|
private byte[] mImageData;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private int mRotation;
|
||||||
|
private RNFaceDetector mFaceDetector;
|
||||||
|
private FaceDetectorAsyncTaskDelegate mDelegate;
|
||||||
|
private ImageDimensions mImageDimensions;
|
||||||
|
private double mScaleX;
|
||||||
|
private double mScaleY;
|
||||||
|
private int mPaddingLeft;
|
||||||
|
private int mPaddingTop;
|
||||||
|
|
||||||
|
public FaceDetectorAsyncTask(
|
||||||
|
FaceDetectorAsyncTaskDelegate delegate,
|
||||||
|
RNFaceDetector faceDetector,
|
||||||
|
byte[] imageData,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rotation,
|
||||||
|
float density,
|
||||||
|
int facing,
|
||||||
|
int viewWidth,
|
||||||
|
int viewHeight,
|
||||||
|
int viewPaddingLeft,
|
||||||
|
int viewPaddingTop
|
||||||
|
) {
|
||||||
|
mImageData = imageData;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
mRotation = rotation;
|
||||||
|
mDelegate = delegate;
|
||||||
|
mFaceDetector = faceDetector;
|
||||||
|
mImageDimensions = new ImageDimensions(width, height, rotation, facing);
|
||||||
|
mScaleX = (double) (viewWidth) / (mImageDimensions.getWidth() * density);
|
||||||
|
mScaleY = (double) (viewHeight) / (mImageDimensions.getHeight() * density);
|
||||||
|
mPaddingLeft = viewPaddingLeft;
|
||||||
|
mPaddingTop = viewPaddingTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SparseArray<Face> doInBackground(Void... ignored) {
|
||||||
|
if (isCancelled() || mDelegate == null || mFaceDetector == null || !mFaceDetector.isOperational()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNFrame frame = RNFrameFactory.buildFrame(mImageData, mWidth, mHeight, mRotation);
|
||||||
|
return mFaceDetector.detect(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(SparseArray<Face> faces) {
|
||||||
|
super.onPostExecute(faces);
|
||||||
|
|
||||||
|
if (faces == null) {
|
||||||
|
mDelegate.onFaceDetectionError(mFaceDetector);
|
||||||
|
} else {
|
||||||
|
if (faces.size() > 0) {
|
||||||
|
mDelegate.onFacesDetected(serializeEventData(faces));
|
||||||
|
}
|
||||||
|
mDelegate.onFaceDetectingTaskCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableArray serializeEventData(SparseArray<Face> faces) {
|
||||||
|
WritableArray facesList = Arguments.createArray();
|
||||||
|
|
||||||
|
for(int i = 0; i < faces.size(); i++) {
|
||||||
|
Face face = faces.valueAt(i);
|
||||||
|
WritableMap serializedFace = FaceDetectorUtils.serializeFace(face, mScaleX, mScaleY, mWidth, mHeight, mPaddingLeft, mPaddingTop);
|
||||||
|
if (mImageDimensions.getFacing() == CameraView.FACING_FRONT) {
|
||||||
|
serializedFace = FaceDetectorUtils.rotateFaceX(serializedFace, mImageDimensions.getWidth(), mScaleX);
|
||||||
|
} else {
|
||||||
|
serializedFace = FaceDetectorUtils.changeAnglesDirection(serializedFace);
|
||||||
|
}
|
||||||
|
facesList.pushMap(serializedFace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return facesList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
|
import com.google.android.cameraview.CameraView;
|
||||||
|
import com.google.android.gms.vision.text.Line;
|
||||||
|
import com.google.android.gms.vision.text.Text;
|
||||||
|
import com.google.android.gms.vision.text.TextBlock;
|
||||||
|
import com.google.android.gms.vision.text.TextRecognizer;
|
||||||
|
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
import org.reactnative.facedetector.FaceDetectorUtils;
|
||||||
|
import org.reactnative.frame.RNFrame;
|
||||||
|
import org.reactnative.frame.RNFrameFactory;
|
||||||
|
|
||||||
|
|
||||||
|
public class TextRecognizerAsyncTask extends android.os.AsyncTask<Void, Void, SparseArray<TextBlock>> {
|
||||||
|
|
||||||
|
private TextRecognizerAsyncTaskDelegate mDelegate;
|
||||||
|
private ThemedReactContext mThemedReactContext;
|
||||||
|
private TextRecognizer mTextRecognizer;
|
||||||
|
private byte[] mImageData;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private int mRotation;
|
||||||
|
private ImageDimensions mImageDimensions;
|
||||||
|
private double mScaleX;
|
||||||
|
private double mScaleY;
|
||||||
|
private int mPaddingLeft;
|
||||||
|
private int mPaddingTop;
|
||||||
|
|
||||||
|
public TextRecognizerAsyncTask(
|
||||||
|
TextRecognizerAsyncTaskDelegate delegate,
|
||||||
|
ThemedReactContext themedReactContext,
|
||||||
|
byte[] imageData,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rotation,
|
||||||
|
float density,
|
||||||
|
int facing,
|
||||||
|
int viewWidth,
|
||||||
|
int viewHeight,
|
||||||
|
int viewPaddingLeft,
|
||||||
|
int viewPaddingTop
|
||||||
|
) {
|
||||||
|
mDelegate = delegate;
|
||||||
|
mThemedReactContext = themedReactContext;
|
||||||
|
mImageData = imageData;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
mRotation = rotation;
|
||||||
|
mImageDimensions = new ImageDimensions(width, height, rotation, facing);
|
||||||
|
mScaleX = (double) (viewWidth) / (mImageDimensions.getWidth() * density);
|
||||||
|
mScaleY = (double) (viewHeight) / (mImageDimensions.getHeight() * density);
|
||||||
|
mPaddingLeft = viewPaddingLeft;
|
||||||
|
mPaddingTop = viewPaddingTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SparseArray<TextBlock> doInBackground(Void... ignored) {
|
||||||
|
if (isCancelled() || mDelegate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mTextRecognizer = new TextRecognizer.Builder(mThemedReactContext).build();
|
||||||
|
RNFrame frame = RNFrameFactory.buildFrame(mImageData, mWidth, mHeight, mRotation);
|
||||||
|
return mTextRecognizer.detect(frame.getFrame());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(SparseArray<TextBlock> textBlocks) {
|
||||||
|
super.onPostExecute(textBlocks);
|
||||||
|
if (mTextRecognizer != null) {
|
||||||
|
mTextRecognizer.release();
|
||||||
|
}
|
||||||
|
if (textBlocks != null) {
|
||||||
|
WritableArray textBlocksList = Arguments.createArray();
|
||||||
|
for (int i = 0; i < textBlocks.size(); ++i) {
|
||||||
|
TextBlock textBlock = textBlocks.valueAt(i);
|
||||||
|
WritableMap serializedTextBlock = serializeText(textBlock);
|
||||||
|
if (mImageDimensions.getFacing() == CameraView.FACING_FRONT) {
|
||||||
|
serializedTextBlock = rotateTextX(serializedTextBlock);
|
||||||
|
}
|
||||||
|
textBlocksList.pushMap(serializedTextBlock);
|
||||||
|
}
|
||||||
|
mDelegate.onTextRecognized(textBlocksList);
|
||||||
|
}
|
||||||
|
mDelegate.onTextRecognizerTaskCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeText(Text text) {
|
||||||
|
WritableMap encodedText = Arguments.createMap();
|
||||||
|
|
||||||
|
WritableArray components = Arguments.createArray();
|
||||||
|
for (Text component : text.getComponents()) {
|
||||||
|
components.pushMap(serializeText(component));
|
||||||
|
}
|
||||||
|
encodedText.putArray("components", components);
|
||||||
|
|
||||||
|
encodedText.putString("value", text.getValue());
|
||||||
|
|
||||||
|
int x = text.getBoundingBox().left;
|
||||||
|
int y = text.getBoundingBox().top;
|
||||||
|
|
||||||
|
if (text.getBoundingBox().left < mWidth / 2) {
|
||||||
|
x = x + mPaddingLeft / 2;
|
||||||
|
} else if (text.getBoundingBox().left > mWidth /2) {
|
||||||
|
x = x - mPaddingLeft / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.getBoundingBox().height() < mHeight / 2) {
|
||||||
|
y = y + mPaddingTop / 2;
|
||||||
|
} else if (text.getBoundingBox().height() > mHeight / 2) {
|
||||||
|
y = y - mPaddingTop / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
WritableMap origin = Arguments.createMap();
|
||||||
|
origin.putDouble("x", x * this.mScaleX);
|
||||||
|
origin.putDouble("y", y * this.mScaleY);
|
||||||
|
|
||||||
|
WritableMap size = Arguments.createMap();
|
||||||
|
size.putDouble("width", text.getBoundingBox().width() * this.mScaleX);
|
||||||
|
size.putDouble("height", text.getBoundingBox().height() * this.mScaleY);
|
||||||
|
|
||||||
|
WritableMap bounds = Arguments.createMap();
|
||||||
|
bounds.putMap("origin", origin);
|
||||||
|
bounds.putMap("size", size);
|
||||||
|
|
||||||
|
encodedText.putMap("bounds", bounds);
|
||||||
|
|
||||||
|
String type_;
|
||||||
|
if (text instanceof TextBlock) {
|
||||||
|
type_ = "block";
|
||||||
|
} else if (text instanceof Line) {
|
||||||
|
type_ = "line";
|
||||||
|
} else /*if (text instanceof Element)*/ {
|
||||||
|
type_ = "element";
|
||||||
|
}
|
||||||
|
encodedText.putString("type", type_);
|
||||||
|
|
||||||
|
return encodedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap rotateTextX(WritableMap text) {
|
||||||
|
ReadableMap faceBounds = text.getMap("bounds");
|
||||||
|
|
||||||
|
ReadableMap oldOrigin = faceBounds.getMap("origin");
|
||||||
|
WritableMap mirroredOrigin = FaceDetectorUtils.positionMirroredHorizontally(
|
||||||
|
oldOrigin, mImageDimensions.getWidth(), mScaleX);
|
||||||
|
|
||||||
|
double translateX = -faceBounds.getMap("size").getDouble("width");
|
||||||
|
WritableMap translatedMirroredOrigin = FaceDetectorUtils.positionTranslatedHorizontally(mirroredOrigin, translateX);
|
||||||
|
|
||||||
|
WritableMap newBounds = Arguments.createMap();
|
||||||
|
newBounds.merge(faceBounds);
|
||||||
|
newBounds.putMap("origin", translatedMirroredOrigin);
|
||||||
|
|
||||||
|
text.putMap("bounds", newBounds);
|
||||||
|
|
||||||
|
ReadableArray oldComponents = text.getArray("components");
|
||||||
|
WritableArray newComponents = Arguments.createArray();
|
||||||
|
for (int i = 0; i < oldComponents.size(); ++i) {
|
||||||
|
WritableMap component = Arguments.createMap();
|
||||||
|
component.merge(oldComponents.getMap(i));
|
||||||
|
rotateTextX(component);
|
||||||
|
newComponents.pushMap(component);
|
||||||
|
}
|
||||||
|
text.putArray("components", newComponents);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package org.reactnative.facedetector;
|
||||||
|
|
||||||
|
import org.reactnative.facedetector.tasks.FileFaceDetectionAsyncTask;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class FaceDetectorModule extends ReactContextBaseJavaModule {
|
||||||
|
private static final String TAG = "RNFaceDetector";
|
||||||
|
// private ScopedContext mScopedContext;
|
||||||
|
private static ReactApplicationContext mScopedContext;
|
||||||
|
|
||||||
|
public FaceDetectorModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
mScopedContext = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("Mode", getFaceDetectionModeConstants());
|
||||||
|
put("Landmarks", getFaceDetectionLandmarksConstants());
|
||||||
|
put("Classifications", getFaceDetectionClassificationsConstants());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFaceDetectionModeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("fast", RNFaceDetector.FAST_MODE);
|
||||||
|
put("accurate", RNFaceDetector.ACCURATE_MODE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFaceDetectionClassificationsConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("all", RNFaceDetector.ALL_CLASSIFICATIONS);
|
||||||
|
put("none", RNFaceDetector.NO_CLASSIFICATIONS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFaceDetectionLandmarksConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("all", RNFaceDetector.ALL_LANDMARKS);
|
||||||
|
put("none", RNFaceDetector.NO_LANDMARKS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void detectFaces(ReadableMap options, final Promise promise) {
|
||||||
|
new FileFaceDetectionAsyncTask(mScopedContext, options, promise).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
package org.reactnative.facedetector;
|
||||||
|
|
||||||
|
import android.graphics.PointF;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.google.android.gms.vision.face.Face;
|
||||||
|
import com.google.android.gms.vision.face.Landmark;
|
||||||
|
|
||||||
|
public class FaceDetectorUtils {
|
||||||
|
// All the landmarks reported by Google Mobile Vision in constants' order.
|
||||||
|
// https://developers.google.com/android/reference/com/google/android/gms/vision/face/Landmark
|
||||||
|
private static final String[] landmarkNames = {
|
||||||
|
"bottomMouthPosition", "leftCheekPosition", "leftEarPosition", "leftEarTipPosition",
|
||||||
|
"leftEyePosition", "leftMouthPosition", "noseBasePosition", "rightCheekPosition",
|
||||||
|
"rightEarPosition", "rightEarTipPosition", "rightEyePosition", "rightMouthPosition"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static WritableMap serializeFace(Face face) {
|
||||||
|
return serializeFace(face, 1, 1, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap serializeFace(Face face, double scaleX, double scaleY, int width, int height, int paddingLeft, int paddingTop) {
|
||||||
|
WritableMap encodedFace = Arguments.createMap();
|
||||||
|
|
||||||
|
encodedFace.putInt("faceID", face.getId());
|
||||||
|
encodedFace.putDouble("rollAngle", face.getEulerZ());
|
||||||
|
encodedFace.putDouble("yawAngle", face.getEulerY());
|
||||||
|
|
||||||
|
if (face.getIsSmilingProbability() >= 0) {
|
||||||
|
encodedFace.putDouble("smilingProbability", face.getIsSmilingProbability());
|
||||||
|
}
|
||||||
|
if (face.getIsLeftEyeOpenProbability() >= 0) {
|
||||||
|
encodedFace.putDouble("leftEyeOpenProbability", face.getIsLeftEyeOpenProbability());
|
||||||
|
}
|
||||||
|
if (face.getIsRightEyeOpenProbability() >= 0) {
|
||||||
|
encodedFace.putDouble("rightEyeOpenProbability", face.getIsRightEyeOpenProbability());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Landmark landmark : face.getLandmarks()) {
|
||||||
|
encodedFace.putMap(landmarkNames[landmark.getType()], mapFromPoint(landmark.getPosition(), scaleX, scaleY, width, height, paddingLeft, paddingTop));
|
||||||
|
}
|
||||||
|
|
||||||
|
WritableMap origin = Arguments.createMap();
|
||||||
|
Float x = face.getPosition().x;
|
||||||
|
Float y = face.getPosition().y;
|
||||||
|
if (face.getPosition().x < width / 2) {
|
||||||
|
x = x + paddingLeft / 2;
|
||||||
|
} else if (face.getPosition().x > width / 2) {
|
||||||
|
x = x - paddingLeft / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (face.getPosition().y < height / 2) {
|
||||||
|
y = y + paddingTop / 2;
|
||||||
|
} else if (face.getPosition().y > height / 2) {
|
||||||
|
y = y - paddingTop / 2;
|
||||||
|
}
|
||||||
|
origin.putDouble("x", x * scaleX);
|
||||||
|
origin.putDouble("y", y * scaleY);
|
||||||
|
|
||||||
|
WritableMap size = Arguments.createMap();
|
||||||
|
size.putDouble("width", face.getWidth() * scaleX);
|
||||||
|
size.putDouble("height", face.getHeight() * scaleY);
|
||||||
|
|
||||||
|
WritableMap bounds = Arguments.createMap();
|
||||||
|
bounds.putMap("origin", origin);
|
||||||
|
bounds.putMap("size", size);
|
||||||
|
|
||||||
|
encodedFace.putMap("bounds", bounds);
|
||||||
|
|
||||||
|
return encodedFace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap rotateFaceX(WritableMap face, int sourceWidth, double scaleX) {
|
||||||
|
ReadableMap faceBounds = face.getMap("bounds");
|
||||||
|
|
||||||
|
ReadableMap oldOrigin = faceBounds.getMap("origin");
|
||||||
|
WritableMap mirroredOrigin = positionMirroredHorizontally(oldOrigin, sourceWidth, scaleX);
|
||||||
|
|
||||||
|
double translateX = -faceBounds.getMap("size").getDouble("width");
|
||||||
|
WritableMap translatedMirroredOrigin = positionTranslatedHorizontally(mirroredOrigin, translateX);
|
||||||
|
|
||||||
|
WritableMap newBounds = Arguments.createMap();
|
||||||
|
newBounds.merge(faceBounds);
|
||||||
|
newBounds.putMap("origin", translatedMirroredOrigin);
|
||||||
|
|
||||||
|
for (String landmarkName : landmarkNames) {
|
||||||
|
ReadableMap landmark = face.hasKey(landmarkName) ? face.getMap(landmarkName) : null;
|
||||||
|
if (landmark != null) {
|
||||||
|
WritableMap mirroredPosition = positionMirroredHorizontally(landmark, sourceWidth, scaleX);
|
||||||
|
face.putMap(landmarkName, mirroredPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
face.putMap("bounds", newBounds);
|
||||||
|
|
||||||
|
return face;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap changeAnglesDirection(WritableMap face) {
|
||||||
|
face.putDouble("rollAngle", (-face.getDouble("rollAngle") + 360) % 360);
|
||||||
|
face.putDouble("yawAngle", (-face.getDouble("yawAngle") + 360) % 360);
|
||||||
|
return face;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap mapFromPoint(PointF point, double scaleX, double scaleY, int width, int height, int paddingLeft, int paddingTop) {
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
Float x = point.x;
|
||||||
|
Float y = point.y;
|
||||||
|
if (point.x < width / 2) {
|
||||||
|
x = (x + paddingLeft / 2);
|
||||||
|
} else if (point.x > width / 2) {
|
||||||
|
x = (x - paddingLeft / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point.y < height / 2) {
|
||||||
|
y = (y + paddingTop / 2);
|
||||||
|
} else if (point.y > height / 2) {
|
||||||
|
y = (y - paddingTop / 2);
|
||||||
|
}
|
||||||
|
map.putDouble("x", point.x * scaleX);
|
||||||
|
map.putDouble("y", point.y * scaleY);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap positionTranslatedHorizontally(ReadableMap position, double translateX) {
|
||||||
|
WritableMap newPosition = Arguments.createMap();
|
||||||
|
newPosition.merge(position);
|
||||||
|
newPosition.putDouble("x", position.getDouble("x") + translateX);
|
||||||
|
return newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap positionMirroredHorizontally(ReadableMap position, int containerWidth, double scaleX) {
|
||||||
|
WritableMap newPosition = Arguments.createMap();
|
||||||
|
newPosition.merge(position);
|
||||||
|
newPosition.putDouble("x", valueMirroredHorizontally(position.getDouble("x"), containerWidth, scaleX));
|
||||||
|
return newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double valueMirroredHorizontally(double elementX, int containerWidth, double scaleX) {
|
||||||
|
double originalX = elementX / scaleX;
|
||||||
|
double mirroredX = containerWidth - originalX;
|
||||||
|
return mirroredX * scaleX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
package org.reactnative.facedetector;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
import com.google.android.gms.vision.face.Face;
|
||||||
|
import com.google.android.gms.vision.face.FaceDetector;
|
||||||
|
import org.reactnative.frame.RNFrame;
|
||||||
|
|
||||||
|
public class RNFaceDetector {
|
||||||
|
public static int ALL_CLASSIFICATIONS = FaceDetector.ALL_CLASSIFICATIONS;
|
||||||
|
public static int NO_CLASSIFICATIONS = FaceDetector.NO_CLASSIFICATIONS;
|
||||||
|
public static int ALL_LANDMARKS = FaceDetector.ALL_LANDMARKS;
|
||||||
|
public static int NO_LANDMARKS = FaceDetector.NO_LANDMARKS;
|
||||||
|
public static int ACCURATE_MODE = FaceDetector.ACCURATE_MODE;
|
||||||
|
public static int FAST_MODE = FaceDetector.FAST_MODE;
|
||||||
|
|
||||||
|
private FaceDetector mFaceDetector = null;
|
||||||
|
private ImageDimensions mPreviousDimensions;
|
||||||
|
private FaceDetector.Builder mBuilder = null;
|
||||||
|
|
||||||
|
private int mClassificationType = NO_CLASSIFICATIONS;
|
||||||
|
private int mLandmarkType = NO_LANDMARKS;
|
||||||
|
private float mMinFaceSize = 0.15f;
|
||||||
|
private int mMode = FAST_MODE;
|
||||||
|
|
||||||
|
public RNFaceDetector(Context context) {
|
||||||
|
mBuilder = new FaceDetector.Builder(context);
|
||||||
|
mBuilder.setMinFaceSize(mMinFaceSize);
|
||||||
|
mBuilder.setMode(mMode);
|
||||||
|
mBuilder.setLandmarkType(mLandmarkType);
|
||||||
|
mBuilder.setClassificationType(mClassificationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
|
||||||
|
public boolean isOperational() {
|
||||||
|
if (mFaceDetector == null) {
|
||||||
|
createFaceDetector();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFaceDetector.isOperational();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SparseArray<Face> detect(RNFrame frame) {
|
||||||
|
// If the frame has different dimensions, create another face detector.
|
||||||
|
// Otherwise we will get nasty "inconsistent image dimensions" error from detector
|
||||||
|
// and no face will be detected.
|
||||||
|
if (!frame.getDimensions().equals(mPreviousDimensions)) {
|
||||||
|
releaseFaceDetector();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mFaceDetector == null) {
|
||||||
|
createFaceDetector();
|
||||||
|
mPreviousDimensions = frame.getDimensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFaceDetector.detect(frame.getFrame());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTracking(boolean trackingEnabled) {
|
||||||
|
release();
|
||||||
|
mBuilder.setTrackingEnabled(trackingEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClassificationType(int classificationType) {
|
||||||
|
if (classificationType != mClassificationType) {
|
||||||
|
release();
|
||||||
|
mBuilder.setClassificationType(classificationType);
|
||||||
|
mClassificationType = classificationType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLandmarkType(int landmarkType) {
|
||||||
|
if (landmarkType != mLandmarkType) {
|
||||||
|
release();
|
||||||
|
mBuilder.setLandmarkType(landmarkType);
|
||||||
|
mLandmarkType = landmarkType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(int mode) {
|
||||||
|
if (mode != mMode) {
|
||||||
|
release();
|
||||||
|
mBuilder.setMode(mode);
|
||||||
|
mMode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
releaseFaceDetector();
|
||||||
|
mPreviousDimensions = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifecycle methods
|
||||||
|
|
||||||
|
private void releaseFaceDetector() {
|
||||||
|
if (mFaceDetector != null) {
|
||||||
|
mFaceDetector.release();
|
||||||
|
mFaceDetector = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createFaceDetector() {
|
||||||
|
mFaceDetector = mBuilder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package org.reactnative.facedetector.tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import org.reactnative.facedetector.RNFaceDetector;
|
||||||
|
import org.reactnative.frame.RNFrame;
|
||||||
|
import org.reactnative.frame.RNFrameFactory;
|
||||||
|
import org.reactnative.facedetector.FaceDetectorUtils;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.google.android.gms.vision.face.Face;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class FileFaceDetectionAsyncTask extends AsyncTask<Void, Void, SparseArray<Face>> {
|
||||||
|
private static final String ERROR_TAG = "E_FACE_DETECTION_FAILED";
|
||||||
|
|
||||||
|
private static final String MODE_OPTION_KEY = "mode";
|
||||||
|
private static final String DETECT_LANDMARKS_OPTION_KEY = "detectLandmarks";
|
||||||
|
private static final String RUN_CLASSIFICATIONS_OPTION_KEY = "runClassifications";
|
||||||
|
|
||||||
|
private String mUri;
|
||||||
|
private String mPath;
|
||||||
|
private Promise mPromise;
|
||||||
|
private int mWidth = 0;
|
||||||
|
private int mHeight = 0;
|
||||||
|
private Context mContext;
|
||||||
|
private ReadableMap mOptions;
|
||||||
|
private int mOrientation = ExifInterface.ORIENTATION_UNDEFINED;
|
||||||
|
private RNFaceDetector mRNFaceDetector;
|
||||||
|
|
||||||
|
public FileFaceDetectionAsyncTask(Context context, ReadableMap options, Promise promise) {
|
||||||
|
mUri = options.getString("uri");
|
||||||
|
mPromise = promise;
|
||||||
|
mOptions = options;
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
if (mUri == null) {
|
||||||
|
mPromise.reject(ERROR_TAG, "You have to provide an URI of an image.");
|
||||||
|
cancel(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uri = Uri.parse(mUri);
|
||||||
|
mPath = uri.getPath();
|
||||||
|
|
||||||
|
if (mPath == null) {
|
||||||
|
mPromise.reject(ERROR_TAG, "Invalid URI provided: `" + mUri + "`.");
|
||||||
|
cancel(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to check if the requested image is in a directory safely accessible by our app.
|
||||||
|
boolean fileIsInSafeDirectories =
|
||||||
|
mPath.startsWith(mContext.getCacheDir().getPath()) || mPath.startsWith(mContext.getFilesDir().getPath());
|
||||||
|
|
||||||
|
if (!fileIsInSafeDirectories) {
|
||||||
|
mPromise.reject(ERROR_TAG, "The image has to be in the local app's directories.");
|
||||||
|
cancel(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!new File(mPath).exists()) {
|
||||||
|
mPromise.reject(ERROR_TAG, "The file does not exist. Given path: `" + mPath + "`.");
|
||||||
|
cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SparseArray<Face> doInBackground(Void... voids) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mRNFaceDetector = detectorForOptions(mOptions, mContext);
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeFile(mPath);
|
||||||
|
mWidth = bitmap.getWidth();
|
||||||
|
mHeight = bitmap.getHeight();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ExifInterface exif = new ExifInterface(mPath);
|
||||||
|
mOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(ERROR_TAG, "Reading orientation from file `" + mPath + "` failed.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
RNFrame frame = RNFrameFactory.buildFrame(bitmap);
|
||||||
|
return mRNFaceDetector.detect(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(SparseArray<Face> faces) {
|
||||||
|
super.onPostExecute(faces);
|
||||||
|
WritableMap result = Arguments.createMap();
|
||||||
|
WritableArray facesArray = Arguments.createArray();
|
||||||
|
|
||||||
|
for(int i = 0; i < faces.size(); i++) {
|
||||||
|
Face face = faces.valueAt(i);
|
||||||
|
WritableMap encodedFace = FaceDetectorUtils.serializeFace(face);
|
||||||
|
encodedFace.putDouble("yawAngle", (-encodedFace.getDouble("yawAngle") + 360) % 360);
|
||||||
|
encodedFace.putDouble("rollAngle", (-encodedFace.getDouble("rollAngle") + 360) % 360);
|
||||||
|
facesArray.pushMap(encodedFace);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.putArray("faces", facesArray);
|
||||||
|
|
||||||
|
WritableMap image = Arguments.createMap();
|
||||||
|
image.putInt("width", mWidth);
|
||||||
|
image.putInt("height", mHeight);
|
||||||
|
image.putInt("orientation", mOrientation);
|
||||||
|
image.putString("uri", mUri);
|
||||||
|
result.putMap("image", image);
|
||||||
|
|
||||||
|
mRNFaceDetector.release();
|
||||||
|
mPromise.resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RNFaceDetector detectorForOptions(ReadableMap options, Context context) {
|
||||||
|
RNFaceDetector detector = new RNFaceDetector(context);
|
||||||
|
detector.setTracking(false);
|
||||||
|
|
||||||
|
if(options.hasKey(MODE_OPTION_KEY)) {
|
||||||
|
detector.setMode(options.getInt(MODE_OPTION_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.hasKey(RUN_CLASSIFICATIONS_OPTION_KEY)) {
|
||||||
|
detector.setClassificationType(options.getInt(RUN_CLASSIFICATIONS_OPTION_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.hasKey(DETECT_LANDMARKS_OPTION_KEY)) {
|
||||||
|
detector.setLandmarkType(options.getInt(DETECT_LANDMARKS_OPTION_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
return detector;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
android/src/main/AndroidManifest.xml
Normal file
6
android/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.reactnative.camera">
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.collection.SparseArrayCompat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable class for describing proportional relationship between width and height.
|
||||||
|
*/
|
||||||
|
public class AspectRatio implements Comparable<AspectRatio>, Parcelable {
|
||||||
|
|
||||||
|
private final static SparseArrayCompat<SparseArrayCompat<AspectRatio>> sCache
|
||||||
|
= new SparseArrayCompat<>(16);
|
||||||
|
|
||||||
|
private final int mX;
|
||||||
|
private final int mY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values.
|
||||||
|
* The values {@code x} and {@code} will be reduced by their greatest common divider.
|
||||||
|
*
|
||||||
|
* @param x The width
|
||||||
|
* @param y The height
|
||||||
|
* @return An instance of {@link AspectRatio}
|
||||||
|
*/
|
||||||
|
public static AspectRatio of(int x, int y) {
|
||||||
|
int gcd = gcd(x, y);
|
||||||
|
x /= gcd;
|
||||||
|
y /= gcd;
|
||||||
|
SparseArrayCompat<AspectRatio> arrayX = sCache.get(x);
|
||||||
|
if (arrayX == null) {
|
||||||
|
AspectRatio ratio = new AspectRatio(x, y);
|
||||||
|
arrayX = new SparseArrayCompat<>();
|
||||||
|
arrayX.put(y, ratio);
|
||||||
|
sCache.put(x, arrayX);
|
||||||
|
return ratio;
|
||||||
|
} else {
|
||||||
|
AspectRatio ratio = arrayX.get(y);
|
||||||
|
if (ratio == null) {
|
||||||
|
ratio = new AspectRatio(x, y);
|
||||||
|
arrayX.put(y, ratio);
|
||||||
|
}
|
||||||
|
return ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an {@link AspectRatio} from a {@link String} formatted like "4:3".
|
||||||
|
*
|
||||||
|
* @param s The string representation of the aspect ratio
|
||||||
|
* @return The aspect ratio
|
||||||
|
* @throws IllegalArgumentException when the format is incorrect.
|
||||||
|
*/
|
||||||
|
public static AspectRatio parse(String s) {
|
||||||
|
int position = s.indexOf(':');
|
||||||
|
if (position == -1) {
|
||||||
|
throw new IllegalArgumentException("Malformed aspect ratio: " + s);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int x = Integer.parseInt(s.substring(0, position));
|
||||||
|
int y = Integer.parseInt(s.substring(position + 1));
|
||||||
|
return AspectRatio.of(x, y);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Malformed aspect ratio: " + s, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AspectRatio(int x, int y) {
|
||||||
|
mX = x;
|
||||||
|
mY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return mX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return mY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(Size size) {
|
||||||
|
int gcd = gcd(size.getWidth(), size.getHeight());
|
||||||
|
int x = size.getWidth() / gcd;
|
||||||
|
int y = size.getHeight() / gcd;
|
||||||
|
return mX == x && mY == y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o instanceof AspectRatio) {
|
||||||
|
AspectRatio ratio = (AspectRatio) o;
|
||||||
|
return mX == ratio.mX && mY == ratio.mY;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return mX + ":" + mY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float toFloat() {
|
||||||
|
return (float) mX / mY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// assuming most sizes are <2^16, doing a rotate will give us perfect hashing
|
||||||
|
return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NonNull AspectRatio another) {
|
||||||
|
if (equals(another)) {
|
||||||
|
return 0;
|
||||||
|
} else if (toFloat() - another.toFloat() > 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The inverse of this {@link AspectRatio}.
|
||||||
|
*/
|
||||||
|
public AspectRatio inverse() {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
return AspectRatio.of(mY, mX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int gcd(int a, int b) {
|
||||||
|
while (b != 0) {
|
||||||
|
int c = b;
|
||||||
|
b = a % b;
|
||||||
|
a = c;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(mX);
|
||||||
|
dest.writeInt(mY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<AspectRatio> CREATOR
|
||||||
|
= new Parcelable.Creator<AspectRatio>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AspectRatio createFromParcel(Parcel source) {
|
||||||
|
int x = source.readInt();
|
||||||
|
int y = source.readInt();
|
||||||
|
return AspectRatio.of(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AspectRatio[] newArray(int size) {
|
||||||
|
return new AspectRatio[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
1582
android/src/main/java/com/google/android/cameraview/Camera1.java
Normal file
1582
android/src/main/java/com/google/android/cameraview/Camera1.java
Normal file
File diff suppressed because it is too large
Load Diff
1558
android/src/main/java/com/google/android/cameraview/Camera2.java
Normal file
1558
android/src/main/java/com/google/android/cameraview/Camera2.java
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.ImageFormat;
|
||||||
|
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
|
||||||
|
@TargetApi(23)
|
||||||
|
class Camera2Api23 extends Camera2 {
|
||||||
|
|
||||||
|
Camera2Api23(Callback callback, PreviewImpl preview, Context context, Handler bgHandler) {
|
||||||
|
super(callback, preview, context, bgHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void collectPictureSizes(SizeMap sizes, StreamConfigurationMap map) {
|
||||||
|
// Try to get hi-res output sizes
|
||||||
|
android.util.Size[] outputSizes = map.getHighResolutionOutputSizes(ImageFormat.JPEG);
|
||||||
|
if (outputSizes != null) {
|
||||||
|
for (android.util.Size size : map.getHighResolutionOutputSizes(ImageFormat.JPEG)) {
|
||||||
|
sizes.add(new Size(size.getWidth(), size.getHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sizes.isEmpty()) {
|
||||||
|
super.collectPictureSizes(sizes, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,872 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.hardware.Camera;
|
||||||
|
import android.media.CamcorderProfile;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.os.ParcelableCompat;
|
||||||
|
import androidx.core.os.ParcelableCompatCreatorCallbacks;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
|
public class CameraView extends FrameLayout {
|
||||||
|
|
||||||
|
/** The camera device faces the opposite direction as the device's screen. */
|
||||||
|
public static final int FACING_BACK = Constants.FACING_BACK;
|
||||||
|
|
||||||
|
/** The camera device faces the same direction as the device's screen. */
|
||||||
|
public static final int FACING_FRONT = Constants.FACING_FRONT;
|
||||||
|
|
||||||
|
/** Direction the camera faces relative to device screen. */
|
||||||
|
@IntDef({FACING_BACK, FACING_FRONT})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface Facing {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Flash will not be fired. */
|
||||||
|
public static final int FLASH_OFF = Constants.FLASH_OFF;
|
||||||
|
|
||||||
|
/** Flash will always be fired during snapshot. */
|
||||||
|
public static final int FLASH_ON = Constants.FLASH_ON;
|
||||||
|
|
||||||
|
/** Constant emission of light during preview, auto-focus and snapshot. */
|
||||||
|
public static final int FLASH_TORCH = Constants.FLASH_TORCH;
|
||||||
|
|
||||||
|
/** Flash will be fired automatically when required. */
|
||||||
|
public static final int FLASH_AUTO = Constants.FLASH_AUTO;
|
||||||
|
|
||||||
|
/** Flash will be fired in red-eye reduction mode. */
|
||||||
|
public static final int FLASH_RED_EYE = Constants.FLASH_RED_EYE;
|
||||||
|
|
||||||
|
/** The mode for for the camera device's flash control */
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE})
|
||||||
|
public @interface Flash {
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraViewImpl mImpl;
|
||||||
|
|
||||||
|
private final CallbackBridge mCallbacks;
|
||||||
|
|
||||||
|
private boolean mAdjustViewBounds;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
private final DisplayOrientationDetector mDisplayOrientationDetector;
|
||||||
|
|
||||||
|
protected HandlerThread mBgThread;
|
||||||
|
protected Handler mBgHandler;
|
||||||
|
|
||||||
|
|
||||||
|
public CameraView(Context context, boolean fallbackToOldApi) {
|
||||||
|
this(context, null, fallbackToOldApi);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CameraView(Context context, AttributeSet attrs, boolean fallbackToOldApi) {
|
||||||
|
this(context, attrs, 0, fallbackToOldApi);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WrongConstant")
|
||||||
|
public CameraView(Context context, AttributeSet attrs, int defStyleAttr, boolean fallbackToOldApi) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
|
||||||
|
// bg hanadler for non UI heavy work
|
||||||
|
mBgThread = new HandlerThread("RNCamera-Handler-Thread");
|
||||||
|
mBgThread.start();
|
||||||
|
mBgHandler = new Handler(mBgThread.getLooper());
|
||||||
|
|
||||||
|
|
||||||
|
if (isInEditMode()){
|
||||||
|
mCallbacks = null;
|
||||||
|
mDisplayOrientationDetector = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mAdjustViewBounds = true;
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
|
// Internal setup
|
||||||
|
final PreviewImpl preview = createPreviewImpl(context);
|
||||||
|
mCallbacks = new CallbackBridge();
|
||||||
|
if (fallbackToOldApi || Build.VERSION.SDK_INT < 21 || Camera2.isLegacy(context)) {
|
||||||
|
mImpl = new Camera1(mCallbacks, preview, mBgHandler);
|
||||||
|
} else if (Build.VERSION.SDK_INT < 23) {
|
||||||
|
mImpl = new Camera2(mCallbacks, preview, context, mBgHandler);
|
||||||
|
} else {
|
||||||
|
mImpl = new Camera2Api23(mCallbacks, preview, context, mBgHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display orientation detector
|
||||||
|
mDisplayOrientationDetector = new DisplayOrientationDetector(context) {
|
||||||
|
@Override
|
||||||
|
public void onDisplayOrientationChanged(int displayOrientation, int deviceOrientation) {
|
||||||
|
mImpl.setDisplayOrientation(displayOrientation);
|
||||||
|
mImpl.setDeviceOrientation(deviceOrientation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanup(){
|
||||||
|
if(mBgThread != null){
|
||||||
|
if(Build.VERSION.SDK_INT < 18){
|
||||||
|
mBgThread.quit();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
mBgThread.quitSafely();
|
||||||
|
}
|
||||||
|
|
||||||
|
mBgThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private PreviewImpl createPreviewImpl(Context context) {
|
||||||
|
PreviewImpl preview;
|
||||||
|
if (Build.VERSION.SDK_INT < 14) {
|
||||||
|
preview = new SurfaceViewPreview(context, this);
|
||||||
|
} else {
|
||||||
|
preview = new TextureViewPreview(context, this);
|
||||||
|
}
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
if (!isInEditMode()) {
|
||||||
|
mDisplayOrientationDetector.enable(ViewCompat.getDisplay(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
if (!isInEditMode()) {
|
||||||
|
mDisplayOrientationDetector.disable();
|
||||||
|
}
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
if (isInEditMode()){
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handle android:adjustViewBounds
|
||||||
|
if (mAdjustViewBounds) {
|
||||||
|
if (!isCameraOpened()) {
|
||||||
|
mCallbacks.reserveRequestLayoutOnOpen();
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||||
|
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||||
|
if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
|
||||||
|
final AspectRatio ratio = getAspectRatio();
|
||||||
|
assert ratio != null;
|
||||||
|
int height = (int) (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat());
|
||||||
|
if (heightMode == MeasureSpec.AT_MOST) {
|
||||||
|
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
|
||||||
|
}
|
||||||
|
super.onMeasure(widthMeasureSpec,
|
||||||
|
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
|
||||||
|
} else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
|
||||||
|
final AspectRatio ratio = getAspectRatio();
|
||||||
|
assert ratio != null;
|
||||||
|
int width = (int) (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat());
|
||||||
|
if (widthMode == MeasureSpec.AT_MOST) {
|
||||||
|
width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec));
|
||||||
|
}
|
||||||
|
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||||
|
heightMeasureSpec);
|
||||||
|
} else {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
// Measure the TextureView
|
||||||
|
int width = getMeasuredWidth();
|
||||||
|
int height = getMeasuredHeight();
|
||||||
|
AspectRatio ratio = getAspectRatio();
|
||||||
|
if (mDisplayOrientationDetector.getLastKnownDisplayOrientation() % 180 == 0) {
|
||||||
|
ratio = ratio.inverse();
|
||||||
|
}
|
||||||
|
assert ratio != null;
|
||||||
|
if (height < width * ratio.getY() / ratio.getX()) {
|
||||||
|
mImpl.getView().measure(
|
||||||
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(width * ratio.getY() / ratio.getX(),
|
||||||
|
MeasureSpec.EXACTLY));
|
||||||
|
} else {
|
||||||
|
mImpl.getView().measure(
|
||||||
|
MeasureSpec.makeMeasureSpec(height * ratio.getX() / ratio.getY(),
|
||||||
|
MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Parcelable onSaveInstanceState() {
|
||||||
|
SavedState state = new SavedState(super.onSaveInstanceState());
|
||||||
|
state.facing = getFacing();
|
||||||
|
state.cameraId = getCameraId();
|
||||||
|
state.ratio = getAspectRatio();
|
||||||
|
state.autoFocus = getAutoFocus();
|
||||||
|
state.flash = getFlash();
|
||||||
|
state.exposure = getExposureCompensation();
|
||||||
|
state.focusDepth = getFocusDepth();
|
||||||
|
state.zoom = getZoom();
|
||||||
|
state.whiteBalance = getWhiteBalance();
|
||||||
|
state.playSoundOnCapture = getPlaySoundOnCapture();
|
||||||
|
state.scanning = getScanning();
|
||||||
|
state.pictureSize = getPictureSize();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(Parcelable state) {
|
||||||
|
if (!(state instanceof SavedState)) {
|
||||||
|
super.onRestoreInstanceState(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SavedState ss = (SavedState) state;
|
||||||
|
super.onRestoreInstanceState(ss.getSuperState());
|
||||||
|
setFacing(ss.facing);
|
||||||
|
setCameraId(ss.cameraId);
|
||||||
|
setAspectRatio(ss.ratio);
|
||||||
|
setAutoFocus(ss.autoFocus);
|
||||||
|
setFlash(ss.flash);
|
||||||
|
setExposureCompensation(ss.exposure);
|
||||||
|
setFocusDepth(ss.focusDepth);
|
||||||
|
setZoom(ss.zoom);
|
||||||
|
setWhiteBalance(ss.whiteBalance);
|
||||||
|
setPlaySoundOnCapture(ss.playSoundOnCapture);
|
||||||
|
setScanning(ss.scanning);
|
||||||
|
setPictureSize(ss.pictureSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsingCamera2Api(boolean useCamera2) {
|
||||||
|
if (Build.VERSION.SDK_INT < 21) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean wasOpened = isCameraOpened();
|
||||||
|
Parcelable state = onSaveInstanceState();
|
||||||
|
|
||||||
|
if (useCamera2 && !Camera2.isLegacy(mContext)) {
|
||||||
|
if (wasOpened) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT < 23) {
|
||||||
|
mImpl = new Camera2(mCallbacks, mImpl.mPreview, mContext, mBgHandler);
|
||||||
|
} else {
|
||||||
|
mImpl = new Camera2Api23(mCallbacks, mImpl.mPreview, mContext, mBgHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRestoreInstanceState(state);
|
||||||
|
} else {
|
||||||
|
if (mImpl instanceof Camera1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasOpened) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
mImpl = new Camera1(mCallbacks, mImpl.mPreview, mBgHandler);
|
||||||
|
}
|
||||||
|
if(wasOpened){
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a camera device and start showing camera preview. This is typically called from
|
||||||
|
* {@link Activity#onResume()}.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
mImpl.start();
|
||||||
|
|
||||||
|
// this fallback is no longer needed and was too buggy/slow
|
||||||
|
// if (!mImpl.start()) {
|
||||||
|
// if (mImpl.getView() != null) {
|
||||||
|
// this.removeView(mImpl.getView());
|
||||||
|
// }
|
||||||
|
// //store the state and restore this state after fall back to Camera1
|
||||||
|
// Parcelable state = onSaveInstanceState();
|
||||||
|
// // Camera2 uses legacy hardware layer; fall back to Camera1
|
||||||
|
// mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()), mBgHandler);
|
||||||
|
// onRestoreInstanceState(state);
|
||||||
|
// mImpl.start();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop camera preview and close the device. This is typically called from
|
||||||
|
* {@link Activity#onPause()}.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
mImpl.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the camera is opened.
|
||||||
|
*/
|
||||||
|
public boolean isCameraOpened() {
|
||||||
|
return mImpl.isCameraOpened();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new callback.
|
||||||
|
*
|
||||||
|
* @param callback The {@link Callback} to add.
|
||||||
|
* @see #removeCallback(Callback)
|
||||||
|
*/
|
||||||
|
public void addCallback(@NonNull Callback callback) {
|
||||||
|
mCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a callback.
|
||||||
|
*
|
||||||
|
* @param callback The {@link Callback} to remove.
|
||||||
|
* @see #addCallback(Callback)
|
||||||
|
*/
|
||||||
|
public void removeCallback(@NonNull Callback callback) {
|
||||||
|
mCallbacks.remove(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param adjustViewBounds {@code true} if you want the CameraView to adjust its bounds to
|
||||||
|
* preserve the aspect ratio of camera.
|
||||||
|
* @see #getAdjustViewBounds()
|
||||||
|
*/
|
||||||
|
public void setAdjustViewBounds(boolean adjustViewBounds) {
|
||||||
|
if (mAdjustViewBounds != adjustViewBounds) {
|
||||||
|
mAdjustViewBounds = adjustViewBounds;
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True when this CameraView is adjusting its bounds to preserve the aspect ratio of
|
||||||
|
* camera.
|
||||||
|
* @see #setAdjustViewBounds(boolean)
|
||||||
|
*/
|
||||||
|
public boolean getAdjustViewBounds() {
|
||||||
|
return mAdjustViewBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getView() {
|
||||||
|
if (mImpl != null) {
|
||||||
|
return mImpl.getView();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chooses camera by the direction it faces.
|
||||||
|
*
|
||||||
|
* @param facing The camera facing. Must be either {@link #FACING_BACK} or
|
||||||
|
* {@link #FACING_FRONT}.
|
||||||
|
*/
|
||||||
|
public void setFacing(@Facing int facing) {
|
||||||
|
mImpl.setFacing(facing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the direction that the current camera faces.
|
||||||
|
*
|
||||||
|
* @return The camera facing.
|
||||||
|
*/
|
||||||
|
@Facing
|
||||||
|
public int getFacing() {
|
||||||
|
//noinspection WrongConstant
|
||||||
|
return mImpl.getFacing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chooses camera by its camera iD
|
||||||
|
*
|
||||||
|
* @param id The camera ID
|
||||||
|
*/
|
||||||
|
public void setCameraId(String id) {
|
||||||
|
mImpl.setCameraId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the currently set camera ID
|
||||||
|
*
|
||||||
|
* @return The camera facing.
|
||||||
|
*/
|
||||||
|
public String getCameraId() {
|
||||||
|
return mImpl.getCameraId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the aspect ratios supported by the current camera.
|
||||||
|
*/
|
||||||
|
public Set<AspectRatio> getSupportedAspectRatios() {
|
||||||
|
return mImpl.getSupportedAspectRatios();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the camera IDs supported by the phone as a String
|
||||||
|
*/
|
||||||
|
public List<Properties> getCameraIds() {
|
||||||
|
return mImpl.getCameraIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the aspect ratio of camera.
|
||||||
|
*
|
||||||
|
* @param ratio The {@link AspectRatio} to be set.
|
||||||
|
*/
|
||||||
|
public void setAspectRatio(@NonNull AspectRatio ratio) {
|
||||||
|
if (mImpl.setAspectRatio(ratio)) {
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current aspect ratio of camera.
|
||||||
|
*
|
||||||
|
* @return The current {@link AspectRatio}. Can be {@code null} if no camera is opened yet.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public AspectRatio getAspectRatio() {
|
||||||
|
return mImpl.getAspectRatio();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the picture sizes for particular ratio supported by the current camera.
|
||||||
|
*
|
||||||
|
* @param ratio {@link AspectRatio} for which the available image sizes will be returned.
|
||||||
|
*/
|
||||||
|
public SortedSet<Size> getAvailablePictureSizes(@NonNull AspectRatio ratio) {
|
||||||
|
return mImpl.getAvailablePictureSizes(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of taken pictures.
|
||||||
|
*
|
||||||
|
* @param size The {@link Size} to be set.
|
||||||
|
*/
|
||||||
|
public void setPictureSize(@NonNull Size size) {
|
||||||
|
mImpl.setPictureSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of pictures that will be taken.
|
||||||
|
*/
|
||||||
|
public Size getPictureSize() {
|
||||||
|
return mImpl.getPictureSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the continuous auto-focus mode. When the current camera doesn't support
|
||||||
|
* auto-focus, calling this method will be ignored.
|
||||||
|
*
|
||||||
|
* @param autoFocus {@code true} to enable continuous auto-focus mode. {@code false} to
|
||||||
|
* disable it.
|
||||||
|
*/
|
||||||
|
public void setAutoFocus(boolean autoFocus) {
|
||||||
|
mImpl.setAutoFocus(autoFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the continuous auto-focus mode is enabled.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the continuous auto-focus mode is enabled. {@code false} if it is
|
||||||
|
* disabled, or if it is not supported by the current camera.
|
||||||
|
*/
|
||||||
|
public boolean getAutoFocus() {
|
||||||
|
return mImpl.getAutoFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the flash mode.
|
||||||
|
*
|
||||||
|
* @param flash The desired flash mode.
|
||||||
|
*/
|
||||||
|
public void setFlash(@Flash int flash) {
|
||||||
|
mImpl.setFlash(flash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current flash mode.
|
||||||
|
*
|
||||||
|
* @return The current flash mode.
|
||||||
|
*/
|
||||||
|
@Flash
|
||||||
|
public int getFlash() {
|
||||||
|
//noinspection WrongConstant
|
||||||
|
return mImpl.getFlash();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExposureCompensation(float exposure) {
|
||||||
|
mImpl.setExposureCompensation(exposure);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getExposureCompensation() {
|
||||||
|
return mImpl.getExposureCompensation();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the camera orientation relative to the devices native orientation.
|
||||||
|
*
|
||||||
|
* @return The orientation of the camera.
|
||||||
|
*/
|
||||||
|
public int getCameraOrientation() {
|
||||||
|
return mImpl.getCameraOrientation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the auto focus point.
|
||||||
|
*
|
||||||
|
* @param x sets the x coordinate for camera auto focus
|
||||||
|
* @param y sets the y coordinate for camera auto focus
|
||||||
|
*/
|
||||||
|
public void setAutoFocusPointOfInterest(float x, float y) {
|
||||||
|
mImpl.setFocusArea(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFocusDepth(float value) {
|
||||||
|
mImpl.setFocusDepth(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getFocusDepth() { return mImpl.getFocusDepth(); }
|
||||||
|
|
||||||
|
public void setZoom(float zoom) {
|
||||||
|
mImpl.setZoom(zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getZoom() {
|
||||||
|
return mImpl.getZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWhiteBalance(int whiteBalance) {
|
||||||
|
mImpl.setWhiteBalance(whiteBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWhiteBalance() {
|
||||||
|
return mImpl.getWhiteBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaySoundOnCapture(boolean playSoundOnCapture) {
|
||||||
|
mImpl.setPlaySoundOnCapture(playSoundOnCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getPlaySoundOnCapture() {
|
||||||
|
return mImpl.getPlaySoundOnCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScanning(boolean isScanning) { mImpl.setScanning(isScanning);}
|
||||||
|
|
||||||
|
public boolean getScanning() { return mImpl.getScanning(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a picture. The result will be returned to
|
||||||
|
* {@link Callback#onPictureTaken(CameraView, byte[], int)}.
|
||||||
|
*/
|
||||||
|
public void takePicture(ReadableMap options) {
|
||||||
|
mImpl.takePicture(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record a video and save it to file. The result will be returned to
|
||||||
|
* {@link Callback#onVideoRecorded(CameraView, String, int, int)}.
|
||||||
|
* @param path Path to file that video will be saved to.
|
||||||
|
* @param maxDuration Maximum duration of the recording, in seconds.
|
||||||
|
* @param maxFileSize Maximum recording file size, in bytes.
|
||||||
|
* @param profile Quality profile of the recording.
|
||||||
|
*
|
||||||
|
* fires {@link Callback#onRecordingStart(CameraView, String, int, int)} and {@link Callback#onRecordingEnd(CameraView)}.
|
||||||
|
*/
|
||||||
|
public boolean record(String path, int maxDuration, int maxFileSize,
|
||||||
|
boolean recordAudio, CamcorderProfile profile, int orientation) {
|
||||||
|
return mImpl.record(path, maxDuration, maxFileSize, recordAudio, profile, orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopRecording() {
|
||||||
|
mImpl.stopRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resumePreview() {
|
||||||
|
mImpl.resumePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pausePreview() {
|
||||||
|
mImpl.pausePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreviewTexture(SurfaceTexture surfaceTexture) {
|
||||||
|
mImpl.setPreviewTexture(surfaceTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size getPreviewSize() {
|
||||||
|
return mImpl.getPreviewSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CallbackBridge implements CameraViewImpl.Callback {
|
||||||
|
|
||||||
|
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean mRequestLayoutOnOpen;
|
||||||
|
|
||||||
|
CallbackBridge() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Callback callback) {
|
||||||
|
mCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(Callback callback) {
|
||||||
|
mCallbacks.remove(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCameraOpened() {
|
||||||
|
if (mRequestLayoutOnOpen) {
|
||||||
|
mRequestLayoutOnOpen = false;
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
for (Callback callback : mCallbacks) {
|
||||||
|
callback.onCameraOpened(CameraView.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCameraClosed() {
|
||||||
|
for (Callback callback : mCallbacks) {
|
||||||
|
callback.onCameraClosed(CameraView.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureTaken(byte[] data, int deviceOrientation) {
|
||||||
|
for (Callback callback : mCallbacks) {
|
||||||
|
callback.onPictureTaken(CameraView.this, data, deviceOrientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRecordingStart(String path, int videoOrientation, int deviceOrientation) {
|
||||||
|
for (Callback callback : mCallbacks) {
|
||||||
|
callback.onRecordingStart(CameraView.this, path, videoOrientation, deviceOrientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRecordingEnd() {
|
||||||
|
for (Callback callback : mCallbacks) {
|
||||||
|
callback.onRecordingEnd(CameraView.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoRecorded(String path, int videoOrientation, int deviceOrientation) {
|
||||||
|
for (Callback callback : mCallbacks) {
|
||||||
|
callback.onVideoRecorded(CameraView.this, path, videoOrientation, deviceOrientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFramePreview(byte[] data, int width, int height, int orientation) {
|
||||||
|
for (Callback callback : mCallbacks) {
|
||||||
|
callback.onFramePreview(CameraView.this, data, width, height, orientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMountError() {
|
||||||
|
for (Callback callback : mCallbacks) {
|
||||||
|
callback.onMountError(CameraView.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reserveRequestLayoutOnOpen() {
|
||||||
|
mRequestLayoutOnOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class SavedState extends BaseSavedState {
|
||||||
|
|
||||||
|
@Facing
|
||||||
|
int facing;
|
||||||
|
|
||||||
|
String cameraId;
|
||||||
|
|
||||||
|
AspectRatio ratio;
|
||||||
|
|
||||||
|
boolean autoFocus;
|
||||||
|
|
||||||
|
@Flash
|
||||||
|
int flash;
|
||||||
|
|
||||||
|
float exposure;
|
||||||
|
|
||||||
|
float focusDepth;
|
||||||
|
|
||||||
|
float zoom;
|
||||||
|
|
||||||
|
int whiteBalance;
|
||||||
|
|
||||||
|
boolean playSoundOnCapture;
|
||||||
|
|
||||||
|
boolean scanning;
|
||||||
|
|
||||||
|
Size pictureSize;
|
||||||
|
|
||||||
|
@SuppressWarnings("WrongConstant")
|
||||||
|
public SavedState(Parcel source, ClassLoader loader) {
|
||||||
|
super(source);
|
||||||
|
facing = source.readInt();
|
||||||
|
cameraId = source.readString();
|
||||||
|
ratio = source.readParcelable(loader);
|
||||||
|
autoFocus = source.readByte() != 0;
|
||||||
|
flash = source.readInt();
|
||||||
|
exposure = source.readFloat();
|
||||||
|
focusDepth = source.readFloat();
|
||||||
|
zoom = source.readFloat();
|
||||||
|
whiteBalance = source.readInt();
|
||||||
|
playSoundOnCapture = source.readByte() != 0;
|
||||||
|
scanning = source.readByte() != 0;
|
||||||
|
pictureSize = source.readParcelable(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SavedState(Parcelable superState) {
|
||||||
|
super(superState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
|
super.writeToParcel(out, flags);
|
||||||
|
out.writeInt(facing);
|
||||||
|
out.writeString(cameraId);
|
||||||
|
out.writeParcelable(ratio, 0);
|
||||||
|
out.writeByte((byte) (autoFocus ? 1 : 0));
|
||||||
|
out.writeInt(flash);
|
||||||
|
out.writeFloat(exposure);
|
||||||
|
out.writeFloat(focusDepth);
|
||||||
|
out.writeFloat(zoom);
|
||||||
|
out.writeInt(whiteBalance);
|
||||||
|
out.writeByte((byte) (playSoundOnCapture ? 1 : 0));
|
||||||
|
out.writeByte((byte) (scanning ? 1 : 0));
|
||||||
|
out.writeParcelable(pictureSize, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<SavedState> CREATOR
|
||||||
|
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
|
||||||
|
return new SavedState(in, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SavedState[] newArray(int size) {
|
||||||
|
return new SavedState[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for monitoring events about {@link CameraView}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnusedParameters")
|
||||||
|
public abstract static class Callback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when camera is opened.
|
||||||
|
*
|
||||||
|
* @param cameraView The associated {@link CameraView}.
|
||||||
|
*/
|
||||||
|
public void onCameraOpened(CameraView cameraView) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when camera is closed.
|
||||||
|
*
|
||||||
|
* @param cameraView The associated {@link CameraView}.
|
||||||
|
*/
|
||||||
|
public void onCameraClosed(CameraView cameraView) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a picture is taken.
|
||||||
|
*
|
||||||
|
* @param cameraView The associated {@link CameraView}.
|
||||||
|
* @param data JPEG data.
|
||||||
|
*/
|
||||||
|
public void onPictureTaken(CameraView cameraView, byte[] data, int deviceOrientation) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a video recording starts
|
||||||
|
*
|
||||||
|
* @param cameraView The associated {@link CameraView}.
|
||||||
|
* @param path Path to recoredd video file.
|
||||||
|
*/
|
||||||
|
public void onRecordingStart(CameraView cameraView, String path, int videoOrientation, int deviceOrientation) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a video recording ends, but before video is saved/processed.
|
||||||
|
*
|
||||||
|
* @param cameraView The associated {@link CameraView}.
|
||||||
|
* @param path Path to recoredd video file.
|
||||||
|
*/
|
||||||
|
public void onRecordingEnd(CameraView cameraView){}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a video is recorded.
|
||||||
|
*
|
||||||
|
* @param cameraView The associated {@link CameraView}.
|
||||||
|
* @param path Path to recoredd video file.
|
||||||
|
*/
|
||||||
|
public void onVideoRecorded(CameraView cameraView, String path, int videoOrientation, int deviceOrientation) {}
|
||||||
|
|
||||||
|
public void onFramePreview(CameraView cameraView, byte[] data, int width, int height, int orientation) {}
|
||||||
|
|
||||||
|
public void onMountError(CameraView cameraView) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.media.CamcorderProfile;
|
||||||
|
import android.view.View;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
|
|
||||||
|
abstract class CameraViewImpl {
|
||||||
|
|
||||||
|
protected final Callback mCallback;
|
||||||
|
protected final PreviewImpl mPreview;
|
||||||
|
|
||||||
|
// Background handler that the implementation an use to run heavy tasks in background
|
||||||
|
// in a thread/looper provided by the view.
|
||||||
|
// Most calls should not require this since the view will already schedule it
|
||||||
|
// on the bg thread. However, the implementation might need to do some heavy work
|
||||||
|
// by itself.
|
||||||
|
protected final Handler mBgHandler;
|
||||||
|
|
||||||
|
CameraViewImpl(Callback callback, PreviewImpl preview, Handler bgHandler) {
|
||||||
|
mCallback = callback;
|
||||||
|
mPreview = preview;
|
||||||
|
mBgHandler = bgHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
View getView() {
|
||||||
|
return mPreview.getView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the implementation was able to start the camera session.
|
||||||
|
*/
|
||||||
|
abstract boolean start();
|
||||||
|
|
||||||
|
abstract void stop();
|
||||||
|
|
||||||
|
abstract boolean isCameraOpened();
|
||||||
|
|
||||||
|
abstract void setFacing(int facing);
|
||||||
|
|
||||||
|
abstract int getFacing();
|
||||||
|
|
||||||
|
abstract void setCameraId(String id);
|
||||||
|
|
||||||
|
abstract String getCameraId();
|
||||||
|
|
||||||
|
abstract Set<AspectRatio> getSupportedAspectRatios();
|
||||||
|
|
||||||
|
abstract List<Properties> getCameraIds();
|
||||||
|
|
||||||
|
abstract SortedSet<Size> getAvailablePictureSizes(AspectRatio ratio);
|
||||||
|
|
||||||
|
abstract void setPictureSize(Size size);
|
||||||
|
|
||||||
|
abstract Size getPictureSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the aspect ratio was changed.
|
||||||
|
*/
|
||||||
|
abstract boolean setAspectRatio(AspectRatio ratio);
|
||||||
|
|
||||||
|
abstract AspectRatio getAspectRatio();
|
||||||
|
|
||||||
|
abstract void setAutoFocus(boolean autoFocus);
|
||||||
|
|
||||||
|
abstract boolean getAutoFocus();
|
||||||
|
|
||||||
|
abstract void setFlash(int flash);
|
||||||
|
|
||||||
|
abstract int getFlash();
|
||||||
|
|
||||||
|
abstract void setExposureCompensation(float exposure);
|
||||||
|
|
||||||
|
abstract float getExposureCompensation();
|
||||||
|
|
||||||
|
abstract void takePicture(ReadableMap options);
|
||||||
|
|
||||||
|
abstract boolean record(String path, int maxDuration, int maxFileSize,
|
||||||
|
boolean recordAudio, CamcorderProfile profile, int orientation);
|
||||||
|
|
||||||
|
abstract void stopRecording();
|
||||||
|
|
||||||
|
abstract int getCameraOrientation();
|
||||||
|
|
||||||
|
abstract void setDisplayOrientation(int displayOrientation);
|
||||||
|
|
||||||
|
abstract void setDeviceOrientation(int deviceOrientation);
|
||||||
|
|
||||||
|
abstract void setFocusArea(float x, float y);
|
||||||
|
|
||||||
|
abstract void setFocusDepth(float value);
|
||||||
|
|
||||||
|
abstract float getFocusDepth();
|
||||||
|
|
||||||
|
abstract void setZoom(float zoom);
|
||||||
|
|
||||||
|
abstract float getZoom();
|
||||||
|
|
||||||
|
abstract void setWhiteBalance(int whiteBalance);
|
||||||
|
|
||||||
|
abstract int getWhiteBalance();
|
||||||
|
|
||||||
|
abstract void setPlaySoundOnCapture(boolean playSoundOnCapture);
|
||||||
|
|
||||||
|
abstract boolean getPlaySoundOnCapture();
|
||||||
|
|
||||||
|
abstract void setScanning(boolean isScanning);
|
||||||
|
|
||||||
|
abstract boolean getScanning();
|
||||||
|
|
||||||
|
abstract public void resumePreview();
|
||||||
|
|
||||||
|
abstract public void pausePreview();
|
||||||
|
|
||||||
|
abstract public void setPreviewTexture(SurfaceTexture surfaceTexture);
|
||||||
|
|
||||||
|
abstract public Size getPreviewSize();
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
|
||||||
|
void onCameraOpened();
|
||||||
|
|
||||||
|
void onCameraClosed();
|
||||||
|
|
||||||
|
void onPictureTaken(byte[] data, int deviceOrientation);
|
||||||
|
|
||||||
|
void onVideoRecorded(String path, int videoOrientation, int deviceOrientation);
|
||||||
|
|
||||||
|
void onRecordingStart(String path, int videoOrientation, int deviceOrientation);
|
||||||
|
|
||||||
|
void onRecordingEnd();
|
||||||
|
|
||||||
|
void onFramePreview(byte[] data, int width, int height, int orientation);
|
||||||
|
|
||||||
|
void onMountError();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
public interface Constants {
|
||||||
|
|
||||||
|
AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3);
|
||||||
|
|
||||||
|
int FACING_BACK = 0;
|
||||||
|
int FACING_FRONT = 1;
|
||||||
|
|
||||||
|
int FLASH_OFF = 0;
|
||||||
|
int FLASH_ON = 1;
|
||||||
|
int FLASH_TORCH = 2;
|
||||||
|
int FLASH_AUTO = 3;
|
||||||
|
int FLASH_RED_EYE = 4;
|
||||||
|
|
||||||
|
int LANDSCAPE_90 = 90;
|
||||||
|
int LANDSCAPE_270 = 270;
|
||||||
|
|
||||||
|
int WB_AUTO = 0;
|
||||||
|
int WB_CLOUDY = 1;
|
||||||
|
int WB_SUNNY = 2;
|
||||||
|
int WB_SHADOW = 3;
|
||||||
|
int WB_FLUORESCENT = 4;
|
||||||
|
int WB_INCANDESCENT = 5;
|
||||||
|
|
||||||
|
int ORIENTATION_AUTO = 0;
|
||||||
|
int ORIENTATION_UP = 1;
|
||||||
|
int ORIENTATION_DOWN = 2;
|
||||||
|
int ORIENTATION_LEFT = 3;
|
||||||
|
int ORIENTATION_RIGHT = 4;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.SparseIntArray;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.OrientationEventListener;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors the value returned from {@link Display#getRotation()}.
|
||||||
|
*/
|
||||||
|
abstract class DisplayOrientationDetector {
|
||||||
|
|
||||||
|
private final OrientationEventListener mOrientationEventListener;
|
||||||
|
|
||||||
|
/** Mapping from Surface.Rotation_n to degrees. */
|
||||||
|
static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();
|
||||||
|
|
||||||
|
static {
|
||||||
|
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);
|
||||||
|
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);
|
||||||
|
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);
|
||||||
|
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
|
||||||
|
}
|
||||||
|
|
||||||
|
Display mDisplay;
|
||||||
|
|
||||||
|
private int mLastKnownDisplayOrientation = 0;
|
||||||
|
|
||||||
|
private int mLastKnownDeviceOrientation = 0;
|
||||||
|
|
||||||
|
public DisplayOrientationDetector(Context context) {
|
||||||
|
mOrientationEventListener = new OrientationEventListener(context) {
|
||||||
|
|
||||||
|
/** This is either Surface.Rotation_0, _90, _180, _270, or -1 (invalid). */
|
||||||
|
private int mLastKnownRotation = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOrientationChanged(int orientation) {
|
||||||
|
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN ||
|
||||||
|
mDisplay == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean hasChanged = false;
|
||||||
|
|
||||||
|
/** set device orientation */
|
||||||
|
final int deviceOrientation;
|
||||||
|
if (orientation > 315 || orientation < 45) {
|
||||||
|
deviceOrientation = 0;
|
||||||
|
} else if (orientation > 45 && orientation < 135) {
|
||||||
|
deviceOrientation = 90;
|
||||||
|
} else if (orientation > 135 && orientation < 225) {
|
||||||
|
deviceOrientation = 180;
|
||||||
|
} else if (orientation > 225 && orientation < 315) {
|
||||||
|
deviceOrientation = 270;
|
||||||
|
} else {
|
||||||
|
deviceOrientation = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mLastKnownDeviceOrientation != deviceOrientation) {
|
||||||
|
mLastKnownDeviceOrientation = deviceOrientation;
|
||||||
|
hasChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** set screen orientation */
|
||||||
|
final int rotation = mDisplay.getRotation();
|
||||||
|
if (mLastKnownRotation != rotation) {
|
||||||
|
mLastKnownRotation = rotation;
|
||||||
|
hasChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanged) {
|
||||||
|
dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(rotation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enable(Display display) {
|
||||||
|
mDisplay = display;
|
||||||
|
mOrientationEventListener.enable();
|
||||||
|
// Immediately dispatch the first callback
|
||||||
|
dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(display.getRotation()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disable() {
|
||||||
|
mOrientationEventListener.disable();
|
||||||
|
mDisplay = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastKnownDisplayOrientation() {
|
||||||
|
return mLastKnownDisplayOrientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatchOnDisplayOrientationChanged(int displayOrientation) {
|
||||||
|
mLastKnownDisplayOrientation = displayOrientation;
|
||||||
|
onDisplayOrientationChanged(displayOrientation, mLastKnownDeviceOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when display orientation is changed.
|
||||||
|
*
|
||||||
|
* @param displayOrientation One of 0, 90, 180, and 270.
|
||||||
|
* @param deviceOrientation One of 0, 90, 180, and 270.
|
||||||
|
*/
|
||||||
|
public abstract void onDisplayOrientationChanged(int displayOrientation, int deviceOrientation);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates all the operations related to camera preview in a backward-compatible manner.
|
||||||
|
*/
|
||||||
|
abstract class PreviewImpl {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
void onSurfaceChanged();
|
||||||
|
|
||||||
|
void onSurfaceDestroyed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Callback mCallback;
|
||||||
|
|
||||||
|
private int mWidth;
|
||||||
|
|
||||||
|
private int mHeight;
|
||||||
|
|
||||||
|
void setCallback(Callback callback) {
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract Surface getSurface();
|
||||||
|
|
||||||
|
abstract View getView();
|
||||||
|
|
||||||
|
abstract Class getOutputClass();
|
||||||
|
|
||||||
|
abstract void setDisplayOrientation(int displayOrientation);
|
||||||
|
|
||||||
|
abstract boolean isReady();
|
||||||
|
|
||||||
|
protected void dispatchSurfaceChanged() {
|
||||||
|
mCallback.onSurfaceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void dispatchSurfaceDestroyed() {
|
||||||
|
mCallback.onSurfaceDestroyed();
|
||||||
|
}
|
||||||
|
|
||||||
|
SurfaceHolder getSurfaceHolder() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object getSurfaceTexture() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBufferSize(int width, int height) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSize(int width, int height) {
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWidth() {
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getHeight() {
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
119
android/src/main/java/com/google/android/cameraview/Size.java
Normal file
119
android/src/main/java/com/google/android/cameraview/Size.java
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable class for describing width and height dimensions in pixels.
|
||||||
|
*/
|
||||||
|
public class Size implements Comparable<Size>, Parcelable {
|
||||||
|
|
||||||
|
private final int mWidth;
|
||||||
|
private final int mHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new immutable Size instance.
|
||||||
|
*
|
||||||
|
* @param width The width of the size, in pixels
|
||||||
|
* @param height The height of the size, in pixels
|
||||||
|
*/
|
||||||
|
public Size(int width, int height) {
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Size parse(String s) {
|
||||||
|
int position = s.indexOf('x');
|
||||||
|
if (position == -1) {
|
||||||
|
throw new IllegalArgumentException("Malformed size: " + s);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int width = Integer.parseInt(s.substring(0, position));
|
||||||
|
int height = Integer.parseInt(s.substring(position + 1));
|
||||||
|
return new Size(width, height);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Malformed size: " + s, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o instanceof Size) {
|
||||||
|
Size size = (Size) o;
|
||||||
|
return mWidth == size.mWidth && mHeight == size.mHeight;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return mWidth + "x" + mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// assuming most sizes are <2^16, doing a rotate will give us perfect hashing
|
||||||
|
return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NonNull Size another) {
|
||||||
|
return mWidth * mHeight - another.mWidth * another.mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(mWidth);
|
||||||
|
dest.writeInt(mHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<Size> CREATOR = new Parcelable.Creator<Size>() {
|
||||||
|
@Override
|
||||||
|
public Size createFromParcel(Parcel source) {
|
||||||
|
int width = source.readInt();
|
||||||
|
int height = source.readInt();
|
||||||
|
return new Size(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Size[] newArray(int size) {
|
||||||
|
return new Size[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import androidx.collection.ArrayMap;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection class that automatically groups {@link Size}s by their {@link AspectRatio}s.
|
||||||
|
*/
|
||||||
|
class SizeMap {
|
||||||
|
|
||||||
|
private final ArrayMap<AspectRatio, SortedSet<Size>> mRatios = new ArrayMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new {@link Size} to this collection.
|
||||||
|
*
|
||||||
|
* @param size The size to add.
|
||||||
|
* @return {@code true} if it is added, {@code false} if it already exists and is not added.
|
||||||
|
*/
|
||||||
|
public boolean add(Size size) {
|
||||||
|
for (AspectRatio ratio : mRatios.keySet()) {
|
||||||
|
if (ratio.matches(size)) {
|
||||||
|
final SortedSet<Size> sizes = mRatios.get(ratio);
|
||||||
|
if (sizes.contains(size)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
sizes.add(size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// None of the existing ratio matches the provided size; add a new key
|
||||||
|
SortedSet<Size> sizes = new TreeSet<>();
|
||||||
|
sizes.add(size);
|
||||||
|
mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified aspect ratio and all sizes associated with it.
|
||||||
|
*
|
||||||
|
* @param ratio The aspect ratio to be removed.
|
||||||
|
*/
|
||||||
|
public void remove(AspectRatio ratio) {
|
||||||
|
mRatios.remove(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<AspectRatio> ratios() {
|
||||||
|
return mRatios.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
SortedSet<Size> sizes(AspectRatio ratio) {
|
||||||
|
return mRatios.get(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
mRatios.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
return mRatios.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.reactnative.camera.R;
|
||||||
|
|
||||||
|
class SurfaceViewPreview extends PreviewImpl {
|
||||||
|
|
||||||
|
final SurfaceView mSurfaceView;
|
||||||
|
|
||||||
|
SurfaceViewPreview(Context context, ViewGroup parent) {
|
||||||
|
final View view = View.inflate(context, R.layout.surface_view, parent);
|
||||||
|
mSurfaceView = (SurfaceView) view.findViewById(R.id.surface_view);
|
||||||
|
final SurfaceHolder holder = mSurfaceView.getHolder();
|
||||||
|
//noinspection deprecation
|
||||||
|
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
||||||
|
holder.addCallback(new SurfaceHolder.Callback() {
|
||||||
|
@Override
|
||||||
|
public void surfaceCreated(SurfaceHolder h) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceChanged(SurfaceHolder h, int format, int width, int height) {
|
||||||
|
setSize(width, height);
|
||||||
|
if (!ViewCompat.isInLayout(mSurfaceView)) {
|
||||||
|
dispatchSurfaceChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder h) {
|
||||||
|
setSize(0, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Surface getSurface() {
|
||||||
|
return getSurfaceHolder().getSurface();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SurfaceHolder getSurfaceHolder() {
|
||||||
|
return mSurfaceView.getHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
View getView() {
|
||||||
|
return mSurfaceView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Class getOutputClass() {
|
||||||
|
return SurfaceHolder.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setDisplayOrientation(int displayOrientation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isReady() {
|
||||||
|
return getWidth() != 0 && getHeight() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.cameraview;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.TextureView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.reactnative.camera.R;
|
||||||
|
|
||||||
|
@TargetApi(14)
|
||||||
|
class TextureViewPreview extends PreviewImpl {
|
||||||
|
|
||||||
|
private final TextureView mTextureView;
|
||||||
|
|
||||||
|
private int mDisplayOrientation;
|
||||||
|
|
||||||
|
TextureViewPreview(Context context, ViewGroup parent) {
|
||||||
|
final View view = View.inflate(context, R.layout.texture_view, parent);
|
||||||
|
mTextureView = (TextureView) view.findViewById(R.id.texture_view);
|
||||||
|
mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||||
|
setSize(width, height);
|
||||||
|
configureTransform();
|
||||||
|
dispatchSurfaceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||||||
|
setSize(width, height);
|
||||||
|
configureTransform();
|
||||||
|
dispatchSurfaceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||||
|
setSize(0, 0);
|
||||||
|
dispatchSurfaceDestroyed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is called only from Camera2.
|
||||||
|
@TargetApi(15)
|
||||||
|
@Override
|
||||||
|
void setBufferSize(int width, int height) {
|
||||||
|
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Surface getSurface() {
|
||||||
|
return new Surface(mTextureView.getSurfaceTexture());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SurfaceTexture getSurfaceTexture() {
|
||||||
|
return mTextureView.getSurfaceTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
View getView() {
|
||||||
|
return mTextureView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Class getOutputClass() {
|
||||||
|
return SurfaceTexture.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setDisplayOrientation(int displayOrientation) {
|
||||||
|
mDisplayOrientation = displayOrientation;
|
||||||
|
configureTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isReady() {
|
||||||
|
return mTextureView.getSurfaceTexture() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the transform matrix for TextureView based on {@link #mDisplayOrientation} and
|
||||||
|
* the surface size.
|
||||||
|
*/
|
||||||
|
void configureTransform() {
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
if (mDisplayOrientation % 180 == 90) {
|
||||||
|
final int width = getWidth();
|
||||||
|
final int height = getHeight();
|
||||||
|
// Rotate the camera preview when the screen is landscape.
|
||||||
|
matrix.setPolyToPoly(
|
||||||
|
new float[]{
|
||||||
|
0.f, 0.f, // top left
|
||||||
|
width, 0.f, // top right
|
||||||
|
0.f, height, // bottom left
|
||||||
|
width, height, // bottom right
|
||||||
|
}, 0,
|
||||||
|
mDisplayOrientation == 90 ?
|
||||||
|
// Clockwise
|
||||||
|
new float[]{
|
||||||
|
0.f, height, // top left
|
||||||
|
0.f, 0.f, // top right
|
||||||
|
width, height, // bottom left
|
||||||
|
width, 0.f, // bottom right
|
||||||
|
} : // mDisplayOrientation == 270
|
||||||
|
// Counter-clockwise
|
||||||
|
new float[]{
|
||||||
|
width, 0.f, // top left
|
||||||
|
width, height, // top right
|
||||||
|
0.f, 0.f, // bottom left
|
||||||
|
0.f, height, // bottom right
|
||||||
|
}, 0,
|
||||||
|
4);
|
||||||
|
} else if (mDisplayOrientation == 180) {
|
||||||
|
matrix.postRotate(180, getWidth() / 2, getHeight() / 2);
|
||||||
|
}
|
||||||
|
mTextureView.setTransform(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,327 @@
|
||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.drew.imaging.ImageMetadataReader;
|
||||||
|
import com.drew.imaging.ImageProcessingException;
|
||||||
|
import com.drew.metadata.Directory;
|
||||||
|
import com.drew.metadata.Metadata;
|
||||||
|
import com.drew.metadata.MetadataException;
|
||||||
|
import com.drew.metadata.Tag;
|
||||||
|
import com.drew.metadata.exif.ExifIFD0Directory;
|
||||||
|
import com.drew.metadata.exif.ExifSubIFDDirectory;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class MutableImage {
|
||||||
|
private static final String TAG = "RNCamera";
|
||||||
|
|
||||||
|
private final byte[] originalImageData;
|
||||||
|
private Bitmap currentRepresentation;
|
||||||
|
private Metadata originalImageMetaData;
|
||||||
|
private boolean hasBeenReoriented = false;
|
||||||
|
|
||||||
|
public MutableImage(byte[] originalImageData) {
|
||||||
|
this.originalImageData = originalImageData;
|
||||||
|
this.currentRepresentation = toBitmap(originalImageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return this.currentRepresentation.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return this.currentRepresentation.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mirrorImage() throws ImageMutationFailedException {
|
||||||
|
Matrix m = new Matrix();
|
||||||
|
|
||||||
|
m.preScale(-1, 1);
|
||||||
|
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(
|
||||||
|
currentRepresentation,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
getWidth(),
|
||||||
|
getHeight(),
|
||||||
|
m,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (bitmap == null)
|
||||||
|
throw new ImageMutationFailedException("failed to mirror");
|
||||||
|
|
||||||
|
this.currentRepresentation = bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fixOrientation() throws ImageMutationFailedException {
|
||||||
|
try {
|
||||||
|
Metadata metadata = originalImageMetaData();
|
||||||
|
|
||||||
|
ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
||||||
|
if (exifIFD0Directory == null) {
|
||||||
|
return;
|
||||||
|
} else if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
|
||||||
|
int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
|
||||||
|
if(exifOrientation != 1) {
|
||||||
|
rotate(exifOrientation);
|
||||||
|
exifIFD0Directory.setInt(ExifIFD0Directory.TAG_ORIENTATION, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ImageProcessingException | IOException | MetadataException e) {
|
||||||
|
throw new ImageMutationFailedException("failed to fix orientation", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cropToPreview(double previewRatio) throws IllegalArgumentException {
|
||||||
|
int pictureWidth = getWidth(), pictureHeight = getHeight();
|
||||||
|
int targetPictureWidth, targetPictureHeight;
|
||||||
|
|
||||||
|
if (previewRatio * pictureHeight > pictureWidth) {
|
||||||
|
targetPictureWidth = pictureWidth;
|
||||||
|
targetPictureHeight = (int) (pictureWidth / previewRatio);
|
||||||
|
} else {
|
||||||
|
targetPictureHeight = pictureHeight;
|
||||||
|
targetPictureWidth = (int) (pictureHeight * previewRatio);
|
||||||
|
}
|
||||||
|
this.currentRepresentation = Bitmap.createBitmap(
|
||||||
|
this.currentRepresentation,
|
||||||
|
(pictureWidth - targetPictureWidth) / 2,
|
||||||
|
(pictureHeight - targetPictureHeight) / 2,
|
||||||
|
targetPictureWidth,
|
||||||
|
targetPictureHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
//see http://www.impulseadventure.com/photo/exif-orientation.html
|
||||||
|
private void rotate(int exifOrientation) throws ImageMutationFailedException {
|
||||||
|
final Matrix bitmapMatrix = new Matrix();
|
||||||
|
switch (exifOrientation) {
|
||||||
|
case 1:
|
||||||
|
return;//no rotation required
|
||||||
|
case 2:
|
||||||
|
bitmapMatrix.postScale(-1, 1);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
bitmapMatrix.postRotate(180);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
bitmapMatrix.postRotate(180);
|
||||||
|
bitmapMatrix.postScale(-1, 1);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
bitmapMatrix.postRotate(90);
|
||||||
|
bitmapMatrix.postScale(-1, 1);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
bitmapMatrix.postRotate(90);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
bitmapMatrix.postRotate(270);
|
||||||
|
bitmapMatrix.postScale(-1, 1);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
bitmapMatrix.postRotate(270);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap transformedBitmap = Bitmap.createBitmap(
|
||||||
|
currentRepresentation,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
getWidth(),
|
||||||
|
getHeight(),
|
||||||
|
bitmapMatrix,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (transformedBitmap == null)
|
||||||
|
throw new ImageMutationFailedException("failed to rotate");
|
||||||
|
|
||||||
|
this.currentRepresentation = transformedBitmap;
|
||||||
|
this.hasBeenReoriented = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Bitmap toBitmap(byte[] data) {
|
||||||
|
try {
|
||||||
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
|
||||||
|
Bitmap photo = BitmapFactory.decodeStream(inputStream);
|
||||||
|
inputStream.close();
|
||||||
|
return photo;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Will not happen", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toBase64(int jpegQualityPercent) {
|
||||||
|
return Base64.encodeToString(toJpeg(currentRepresentation, jpegQualityPercent), Base64.NO_WRAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeDataToFile(File file, ReadableMap options, int jpegQualityPercent) throws IOException {
|
||||||
|
FileOutputStream fos = new FileOutputStream(file);
|
||||||
|
fos.write(toJpeg(currentRepresentation, jpegQualityPercent));
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ExifInterface exif = new ExifInterface(file.getAbsolutePath());
|
||||||
|
|
||||||
|
// copy original exif data to the output exif...
|
||||||
|
for (Directory directory : originalImageMetaData().getDirectories()) {
|
||||||
|
for (Tag tag : directory.getTags()) {
|
||||||
|
int tagType = tag.getTagType();
|
||||||
|
Object object = directory.getObject(tagType);
|
||||||
|
exif.setAttribute(tag.getTagName(), object.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add missing exif data from a sub directory
|
||||||
|
ExifSubIFDDirectory directory = originalImageMetaData()
|
||||||
|
.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
|
||||||
|
for (Tag tag : directory.getTags()) {
|
||||||
|
int tagType = tag.getTagType();
|
||||||
|
// As some of exif data does not follow naming of the ExifInterface the names need
|
||||||
|
// to be transformed into Upper camel case format.
|
||||||
|
String tagName = tag.getTagName().replaceAll(" ", "");
|
||||||
|
Object object = directory.getObject(tagType);
|
||||||
|
if (tagName.equals(ExifInterface.TAG_EXPOSURE_TIME)) {
|
||||||
|
exif.setAttribute(tagName, convertExposureTimeToDoubleFormat(object.toString()));
|
||||||
|
} else {
|
||||||
|
exif.setAttribute(tagName, object.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLocationExifData(options, exif);
|
||||||
|
|
||||||
|
if(hasBeenReoriented)
|
||||||
|
rewriteOrientation(exif);
|
||||||
|
|
||||||
|
exif.saveAttributes();
|
||||||
|
} catch (ImageProcessingException | IOException e) {
|
||||||
|
Log.e(TAG, "failed to save exif data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reformats exposure time value to match ExifInterface format. Example 1/11 -> 0.0909
|
||||||
|
// Even the value is formatted as double it is returned as a String because exif.setAttribute requires it.
|
||||||
|
private String convertExposureTimeToDoubleFormat(String exposureTime) {
|
||||||
|
if(!exposureTime.contains("/"))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
String exposureFractions[]= exposureTime.split("/");
|
||||||
|
double divider = Double.parseDouble(exposureFractions[1]);
|
||||||
|
double exposureTimeAsDouble = 1.0f / divider;
|
||||||
|
return Double.toString(exposureTimeAsDouble);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rewriteOrientation(ExifInterface exif) {
|
||||||
|
exif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(ExifInterface.ORIENTATION_NORMAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLocationExifData(ReadableMap options, ExifInterface exif) {
|
||||||
|
if(!options.hasKey("metadata"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ReadableMap metadata = options.getMap("metadata");
|
||||||
|
if (!metadata.hasKey("location"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ReadableMap location = metadata.getMap("location");
|
||||||
|
if(!location.hasKey("coords"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ReadableMap coords = location.getMap("coords");
|
||||||
|
double latitude = coords.getDouble("latitude");
|
||||||
|
double longitude = coords.getDouble("longitude");
|
||||||
|
|
||||||
|
GPS.writeExifData(latitude, longitude, exif);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Couldn't write location data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Metadata originalImageMetaData() throws ImageProcessingException, IOException {
|
||||||
|
if(this.originalImageMetaData == null) {//this is expensive, don't do it more than once
|
||||||
|
originalImageMetaData = ImageMetadataReader.readMetadata(
|
||||||
|
new BufferedInputStream(new ByteArrayInputStream(originalImageData)),
|
||||||
|
originalImageData.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return originalImageMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] toJpeg(Bitmap bitmap, int quality) throws OutOfMemoryError {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
outputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "problem compressing jpeg", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ImageMutationFailedException extends Exception {
|
||||||
|
public ImageMutationFailedException(String detailMessage, Throwable throwable) {
|
||||||
|
super(detailMessage, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageMutationFailedException(String detailMessage) {
|
||||||
|
super(detailMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GPS {
|
||||||
|
public static void writeExifData(double latitude, double longitude, ExifInterface exif) throws IOException {
|
||||||
|
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, toDegreeMinuteSecods(latitude));
|
||||||
|
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, latitudeRef(latitude));
|
||||||
|
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, toDegreeMinuteSecods(longitude));
|
||||||
|
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, longitudeRef(longitude));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String latitudeRef(double latitude) {
|
||||||
|
return latitude < 0.0d ? "S" : "N";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String longitudeRef(double longitude) {
|
||||||
|
return longitude < 0.0d ? "W" : "E";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toDegreeMinuteSecods(double latitude) {
|
||||||
|
latitude = Math.abs(latitude);
|
||||||
|
int degree = (int) latitude;
|
||||||
|
latitude *= 60;
|
||||||
|
latitude -= (degree * 60.0d);
|
||||||
|
int minute = (int) latitude;
|
||||||
|
latitude *= 60;
|
||||||
|
latitude -= (minute * 60.0d);
|
||||||
|
int second = (int) (latitude * 1000.0d);
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append(degree);
|
||||||
|
sb.append("/1,");
|
||||||
|
sb.append(minute);
|
||||||
|
sb.append("/1,");
|
||||||
|
sb.append(second);
|
||||||
|
sb.append("/1000,");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
536
android/src/main/java/com/lwansbrough/RCTCamera/RCTCamera.java
Normal file
536
android/src/main/java/com/lwansbrough/RCTCamera/RCTCamera.java
Normal file
|
|
@ -0,0 +1,536 @@
|
||||||
|
/**
|
||||||
|
* Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.hardware.Camera;
|
||||||
|
import android.media.CamcorderProfile;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.lang.Math;
|
||||||
|
|
||||||
|
public class RCTCamera {
|
||||||
|
private static RCTCamera ourInstance;
|
||||||
|
private final HashMap<Integer, CameraInfoWrapper> _cameraInfos;
|
||||||
|
private final HashMap<Integer, Integer> _cameraTypeToIndex;
|
||||||
|
private final Map<Number, Camera> _cameras;
|
||||||
|
private static final Resolution RESOLUTION_480P = new Resolution(853, 480); // 480p shoots for a 16:9 HD aspect ratio, but can otherwise fall back/down to any other supported camera sizes, such as 800x480 or 720x480, if (any) present. See getSupportedPictureSizes/getSupportedVideoSizes below.
|
||||||
|
private static final Resolution RESOLUTION_720P = new Resolution(1280, 720);
|
||||||
|
private static final Resolution RESOLUTION_1080P = new Resolution(1920, 1080);
|
||||||
|
private boolean _barcodeScannerEnabled = false;
|
||||||
|
private List<String> _barCodeTypes = null;
|
||||||
|
private int _orientation = -1;
|
||||||
|
private int _actualDeviceOrientation = 0;
|
||||||
|
private int _adjustedDeviceOrientation = 0;
|
||||||
|
|
||||||
|
public static RCTCamera getInstance() {
|
||||||
|
return ourInstance;
|
||||||
|
}
|
||||||
|
public static void createInstance(int deviceOrientation) {
|
||||||
|
ourInstance = new RCTCamera(deviceOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized Camera acquireCameraInstance(int type) {
|
||||||
|
if (null == _cameras.get(type) && null != _cameraTypeToIndex.get(type)) {
|
||||||
|
try {
|
||||||
|
Camera camera = Camera.open(_cameraTypeToIndex.get(type));
|
||||||
|
_cameras.put(type, camera);
|
||||||
|
adjustPreviewLayout(type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("RCTCamera", "acquireCameraInstance failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _cameras.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseCameraInstance(int type) {
|
||||||
|
// Release seems async and creates race conditions. Remove from map first before releasing.
|
||||||
|
Camera releasingCamera = _cameras.get(type);
|
||||||
|
if (null != releasingCamera) {
|
||||||
|
_cameras.remove(type);
|
||||||
|
releasingCamera.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPreviewWidth(int type) {
|
||||||
|
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||||
|
if (null == cameraInfo) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return cameraInfo.previewWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPreviewHeight(int type) {
|
||||||
|
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||||
|
if (null == cameraInfo) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return cameraInfo.previewHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPreviewVisibleHeight(int type) {
|
||||||
|
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||||
|
if (null == cameraInfo) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return cameraInfo.previewVisibleHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPreviewVisibleWidth(int type) {
|
||||||
|
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||||
|
if (null == cameraInfo) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return cameraInfo.previewVisibleWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Camera.Size getBestSize(List<Camera.Size> supportedSizes, int maxWidth, int maxHeight) {
|
||||||
|
Camera.Size bestSize = null;
|
||||||
|
for (Camera.Size size : supportedSizes) {
|
||||||
|
if (size.width > maxWidth || size.height > maxHeight) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestSize == null) {
|
||||||
|
bestSize = size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int resultArea = bestSize.width * bestSize.height;
|
||||||
|
int newArea = size.width * size.height;
|
||||||
|
|
||||||
|
if (newArea > resultArea) {
|
||||||
|
bestSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Camera.Size getSmallestSize(List<Camera.Size> supportedSizes) {
|
||||||
|
Camera.Size smallestSize = null;
|
||||||
|
for (Camera.Size size : supportedSizes) {
|
||||||
|
if (smallestSize == null) {
|
||||||
|
smallestSize = size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int resultArea = smallestSize.width * smallestSize.height;
|
||||||
|
int newArea = size.width * size.height;
|
||||||
|
|
||||||
|
if (newArea < resultArea) {
|
||||||
|
smallestSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return smallestSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Camera.Size getClosestSize(List<Camera.Size> supportedSizes, int matchWidth, int matchHeight) {
|
||||||
|
Camera.Size closestSize = null;
|
||||||
|
for (Camera.Size size : supportedSizes) {
|
||||||
|
if (closestSize == null) {
|
||||||
|
closestSize = size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentDelta = Math.sqrt(Math.pow(closestSize.width - matchWidth,2) + Math.pow(closestSize.height - matchHeight,2));
|
||||||
|
double newDelta = Math.sqrt(Math.pow(size.width - matchWidth,2) + Math.pow(size.height - matchHeight,2));
|
||||||
|
|
||||||
|
if (newDelta < currentDelta) {
|
||||||
|
closestSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Camera.Size> getSupportedVideoSizes(Camera camera) {
|
||||||
|
Camera.Parameters params = camera.getParameters();
|
||||||
|
// defer to preview instead of params.getSupportedVideoSizes() http://bit.ly/1rxOsq0
|
||||||
|
// but prefer SupportedVideoSizes!
|
||||||
|
List<Camera.Size> sizes = params.getSupportedVideoSizes();
|
||||||
|
if (sizes != null) {
|
||||||
|
return sizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video sizes may be null, which indicates that all the supported
|
||||||
|
// preview sizes are supported for video recording.
|
||||||
|
return params.getSupportedPreviewSizes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOrientation() {
|
||||||
|
return _orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrientation(int orientation) {
|
||||||
|
if (_orientation == orientation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_orientation = orientation;
|
||||||
|
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_FRONT);
|
||||||
|
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBarcodeScannerEnabled() {
|
||||||
|
return _barcodeScannerEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) {
|
||||||
|
_barcodeScannerEnabled = barcodeScannerEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getBarCodeTypes() {
|
||||||
|
return _barCodeTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarCodeTypes(List<String> barCodeTypes) {
|
||||||
|
_barCodeTypes = barCodeTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getActualDeviceOrientation() {
|
||||||
|
return _actualDeviceOrientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdjustedDeviceOrientation(int orientation) {
|
||||||
|
_adjustedDeviceOrientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAdjustedDeviceOrientation() {
|
||||||
|
return _adjustedDeviceOrientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActualDeviceOrientation(int actualDeviceOrientation) {
|
||||||
|
_actualDeviceOrientation = actualDeviceOrientation;
|
||||||
|
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_FRONT);
|
||||||
|
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaptureMode(final int cameraType, final int captureMode) {
|
||||||
|
Camera camera = _cameras.get(cameraType);
|
||||||
|
if (camera == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set (video) recording hint based on camera type. For video recording, setting
|
||||||
|
// this hint can help reduce the time it takes to start recording.
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
parameters.setRecordingHint(captureMode == RCTCameraModule.RCT_CAMERA_CAPTURE_MODE_VIDEO);
|
||||||
|
try{
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCamera", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaptureQuality(int cameraType, String captureQuality) {
|
||||||
|
Camera camera = this.acquireCameraInstance(cameraType);
|
||||||
|
if (camera == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
Camera.Size pictureSize = null;
|
||||||
|
List<Camera.Size> supportedSizes = parameters.getSupportedPictureSizes();
|
||||||
|
switch (captureQuality) {
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_LOW:
|
||||||
|
pictureSize = getSmallestSize(supportedSizes);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_MEDIUM:
|
||||||
|
pictureSize = supportedSizes.get(supportedSizes.size() / 2);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_HIGH:
|
||||||
|
pictureSize = getBestSize(parameters.getSupportedPictureSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_PREVIEW:
|
||||||
|
Camera.Size optimalPreviewSize = getBestSize(parameters.getSupportedPreviewSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
pictureSize = getClosestSize(parameters.getSupportedPictureSizes(), optimalPreviewSize.width, optimalPreviewSize.height);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_480P:
|
||||||
|
pictureSize = getBestSize(supportedSizes, RESOLUTION_480P.width, RESOLUTION_480P.height);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_720P:
|
||||||
|
pictureSize = getBestSize(supportedSizes, RESOLUTION_720P.width, RESOLUTION_720P.height);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_1080P:
|
||||||
|
pictureSize = getBestSize(supportedSizes, RESOLUTION_1080P.width, RESOLUTION_1080P.height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pictureSize != null) {
|
||||||
|
parameters.setPictureSize(pictureSize.width, pictureSize.height);
|
||||||
|
try{
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCamera", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CamcorderProfile setCaptureVideoQuality(int cameraType, String captureQuality) {
|
||||||
|
Camera camera = this.acquireCameraInstance(cameraType);
|
||||||
|
if (camera == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.Size videoSize = null;
|
||||||
|
List<Camera.Size> supportedSizes = getSupportedVideoSizes(camera);
|
||||||
|
CamcorderProfile cm = null;
|
||||||
|
switch (captureQuality) {
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_LOW:
|
||||||
|
videoSize = getSmallestSize(supportedSizes);
|
||||||
|
cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_480P);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_MEDIUM:
|
||||||
|
videoSize = supportedSizes.get(supportedSizes.size() / 2);
|
||||||
|
cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_720P);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_HIGH:
|
||||||
|
videoSize = getBestSize(supportedSizes, Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_HIGH);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_480P:
|
||||||
|
videoSize = getBestSize(supportedSizes, RESOLUTION_480P.width, RESOLUTION_480P.height);
|
||||||
|
cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_480P);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_720P:
|
||||||
|
videoSize = getBestSize(supportedSizes, RESOLUTION_720P.width, RESOLUTION_720P.height);
|
||||||
|
cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_720P);
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_1080P:
|
||||||
|
videoSize = getBestSize(supportedSizes, RESOLUTION_1080P.width, RESOLUTION_1080P.height);
|
||||||
|
cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_1080P);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cm == null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoSize != null) {
|
||||||
|
cm.videoFrameHeight = videoSize.height;
|
||||||
|
cm.videoFrameWidth = videoSize.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTorchMode(int cameraType, int torchMode) {
|
||||||
|
Camera camera = this.acquireCameraInstance(cameraType);
|
||||||
|
if (null == camera) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
String value = parameters.getFlashMode();
|
||||||
|
switch (torchMode) {
|
||||||
|
case RCTCameraModule.RCT_CAMERA_TORCH_MODE_ON:
|
||||||
|
value = Camera.Parameters.FLASH_MODE_TORCH;
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_TORCH_MODE_OFF:
|
||||||
|
value = Camera.Parameters.FLASH_MODE_OFF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> flashModes = parameters.getSupportedFlashModes();
|
||||||
|
if (flashModes != null && flashModes.contains(value)) {
|
||||||
|
parameters.setFlashMode(value);
|
||||||
|
try{
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCamera", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlashMode(int cameraType, int flashMode) {
|
||||||
|
Camera camera = this.acquireCameraInstance(cameraType);
|
||||||
|
if (null == camera) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
String value = parameters.getFlashMode();
|
||||||
|
switch (flashMode) {
|
||||||
|
case RCTCameraModule.RCT_CAMERA_FLASH_MODE_AUTO:
|
||||||
|
value = Camera.Parameters.FLASH_MODE_AUTO;
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_FLASH_MODE_ON:
|
||||||
|
value = Camera.Parameters.FLASH_MODE_ON;
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_FLASH_MODE_OFF:
|
||||||
|
value = Camera.Parameters.FLASH_MODE_OFF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
List<String> flashModes = parameters.getSupportedFlashModes();
|
||||||
|
if (flashModes != null && flashModes.contains(value)) {
|
||||||
|
parameters.setFlashMode(value);
|
||||||
|
try{
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCamera", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoom(int cameraType, int zoom) {
|
||||||
|
Camera camera = this.acquireCameraInstance(cameraType);
|
||||||
|
if (null == camera) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
int maxZoom = parameters.getMaxZoom();
|
||||||
|
if (parameters.isZoomSupported()) {
|
||||||
|
if (zoom >=0 && zoom < maxZoom) {
|
||||||
|
parameters.setZoom(zoom);
|
||||||
|
try{
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCamera", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void adjustCameraRotationToDeviceOrientation(int type, int deviceOrientation) {
|
||||||
|
Camera camera = _cameras.get(type);
|
||||||
|
if (null == camera) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||||
|
int rotation;
|
||||||
|
int orientation = cameraInfo.info.orientation;
|
||||||
|
if (cameraInfo.info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||||
|
rotation = (orientation + deviceOrientation * 90) % 360;
|
||||||
|
} else {
|
||||||
|
rotation = (orientation - deviceOrientation * 90 + 360) % 360;
|
||||||
|
}
|
||||||
|
cameraInfo.rotation = rotation;
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
parameters.setRotation(cameraInfo.rotation);
|
||||||
|
|
||||||
|
try {
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void adjustPreviewLayout(int type) {
|
||||||
|
Camera camera = _cameras.get(type);
|
||||||
|
if (null == camera) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||||
|
int displayRotation;
|
||||||
|
int rotation;
|
||||||
|
int orientation = cameraInfo.info.orientation;
|
||||||
|
if (cameraInfo.info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||||
|
rotation = (orientation + _actualDeviceOrientation * 90) % 360;
|
||||||
|
displayRotation = (720 - orientation - _actualDeviceOrientation * 90) % 360;
|
||||||
|
} else {
|
||||||
|
rotation = (orientation - _actualDeviceOrientation * 90 + 360) % 360;
|
||||||
|
displayRotation = rotation;
|
||||||
|
}
|
||||||
|
cameraInfo.rotation = rotation;
|
||||||
|
// TODO: take in account the _orientation prop
|
||||||
|
|
||||||
|
setAdjustedDeviceOrientation(rotation);
|
||||||
|
camera.setDisplayOrientation(displayRotation);
|
||||||
|
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
parameters.setRotation(cameraInfo.rotation);
|
||||||
|
|
||||||
|
// set preview size
|
||||||
|
// defaults to highest resolution available
|
||||||
|
Camera.Size optimalPreviewSize = getBestSize(parameters.getSupportedPreviewSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
int width = optimalPreviewSize.width;
|
||||||
|
int height = optimalPreviewSize.height;
|
||||||
|
|
||||||
|
parameters.setPreviewSize(width, height);
|
||||||
|
try {
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cameraInfo.rotation == 0 || cameraInfo.rotation == 180) {
|
||||||
|
cameraInfo.previewWidth = width;
|
||||||
|
cameraInfo.previewHeight = height;
|
||||||
|
} else {
|
||||||
|
cameraInfo.previewWidth = height;
|
||||||
|
cameraInfo.previewHeight = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreviewVisibleSize(int type, int width, int height) {
|
||||||
|
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||||
|
if (cameraInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraInfo.previewVisibleWidth = width;
|
||||||
|
cameraInfo.previewVisibleHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RCTCamera(int deviceOrientation) {
|
||||||
|
_cameras = new HashMap<>();
|
||||||
|
_cameraInfos = new HashMap<>();
|
||||||
|
_cameraTypeToIndex = new HashMap<>();
|
||||||
|
|
||||||
|
_actualDeviceOrientation = deviceOrientation;
|
||||||
|
|
||||||
|
// map camera types to camera indexes and collect cameras properties
|
||||||
|
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
|
||||||
|
Camera.CameraInfo info = new Camera.CameraInfo();
|
||||||
|
Camera.getCameraInfo(i, info);
|
||||||
|
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT && _cameraInfos.get(RCTCameraModule.RCT_CAMERA_TYPE_FRONT) == null) {
|
||||||
|
_cameraInfos.put(RCTCameraModule.RCT_CAMERA_TYPE_FRONT, new CameraInfoWrapper(info));
|
||||||
|
_cameraTypeToIndex.put(RCTCameraModule.RCT_CAMERA_TYPE_FRONT, i);
|
||||||
|
acquireCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_FRONT);
|
||||||
|
releaseCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_FRONT);
|
||||||
|
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK && _cameraInfos.get(RCTCameraModule.RCT_CAMERA_TYPE_BACK) == null) {
|
||||||
|
_cameraInfos.put(RCTCameraModule.RCT_CAMERA_TYPE_BACK, new CameraInfoWrapper(info));
|
||||||
|
_cameraTypeToIndex.put(RCTCameraModule.RCT_CAMERA_TYPE_BACK, i);
|
||||||
|
acquireCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||||
|
releaseCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CameraInfoWrapper {
|
||||||
|
public final Camera.CameraInfo info;
|
||||||
|
public int rotation = 0;
|
||||||
|
public int previewWidth = -1;
|
||||||
|
public int previewHeight = -1;
|
||||||
|
public int previewVisibleWidth = -1;
|
||||||
|
public int previewVisibleHeight = -1;
|
||||||
|
|
||||||
|
public CameraInfoWrapper(Camera.CameraInfo info) {
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Resolution {
|
||||||
|
public int width;
|
||||||
|
public int height;
|
||||||
|
|
||||||
|
public Resolution(final int width, final int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,871 @@
|
||||||
|
/**
|
||||||
|
* Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16.
|
||||||
|
* Android video recording support by Marc Johnson (me@marc.mn) 4/2016
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.hardware.Camera;
|
||||||
|
import android.media.*;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.LifecycleEventListener;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.bridge.WritableNativeMap;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||||
|
implements MediaRecorder.OnInfoListener, MediaRecorder.OnErrorListener, LifecycleEventListener {
|
||||||
|
private static final String TAG = "RCTCameraModule";
|
||||||
|
|
||||||
|
public static final int RCT_CAMERA_ASPECT_FILL = 0;
|
||||||
|
public static final int RCT_CAMERA_ASPECT_FIT = 1;
|
||||||
|
public static final int RCT_CAMERA_ASPECT_STRETCH = 2;
|
||||||
|
public static final int RCT_CAMERA_CAPTURE_MODE_STILL = 0;
|
||||||
|
public static final int RCT_CAMERA_CAPTURE_MODE_VIDEO = 1;
|
||||||
|
public static final int RCT_CAMERA_CAPTURE_TARGET_MEMORY = 0;
|
||||||
|
public static final int RCT_CAMERA_CAPTURE_TARGET_DISK = 1;
|
||||||
|
public static final int RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL = 2;
|
||||||
|
public static final int RCT_CAMERA_CAPTURE_TARGET_TEMP = 3;
|
||||||
|
public static final int RCT_CAMERA_ORIENTATION_AUTO = Integer.MAX_VALUE;
|
||||||
|
public static final int RCT_CAMERA_ORIENTATION_PORTRAIT = Surface.ROTATION_0;
|
||||||
|
public static final int RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN = Surface.ROTATION_180;
|
||||||
|
public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT = Surface.ROTATION_90;
|
||||||
|
public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT = Surface.ROTATION_270;
|
||||||
|
public static final int RCT_CAMERA_TYPE_FRONT = 1;
|
||||||
|
public static final int RCT_CAMERA_TYPE_BACK = 2;
|
||||||
|
public static final int RCT_CAMERA_FLASH_MODE_OFF = 0;
|
||||||
|
public static final int RCT_CAMERA_FLASH_MODE_ON = 1;
|
||||||
|
public static final int RCT_CAMERA_FLASH_MODE_AUTO = 2;
|
||||||
|
public static final int RCT_CAMERA_TORCH_MODE_OFF = 0;
|
||||||
|
public static final int RCT_CAMERA_TORCH_MODE_ON = 1;
|
||||||
|
public static final int RCT_CAMERA_TORCH_MODE_AUTO = 2;
|
||||||
|
public static final String RCT_CAMERA_CAPTURE_QUALITY_PREVIEW = "preview";
|
||||||
|
public static final String RCT_CAMERA_CAPTURE_QUALITY_HIGH = "high";
|
||||||
|
public static final String RCT_CAMERA_CAPTURE_QUALITY_MEDIUM = "medium";
|
||||||
|
public static final String RCT_CAMERA_CAPTURE_QUALITY_LOW = "low";
|
||||||
|
public static final String RCT_CAMERA_CAPTURE_QUALITY_1080P = "1080p";
|
||||||
|
public static final String RCT_CAMERA_CAPTURE_QUALITY_720P = "720p";
|
||||||
|
public static final String RCT_CAMERA_CAPTURE_QUALITY_480P = "480p";
|
||||||
|
public static final int MEDIA_TYPE_IMAGE = 1;
|
||||||
|
public static final int MEDIA_TYPE_VIDEO = 2;
|
||||||
|
|
||||||
|
private static ReactApplicationContext _reactContext;
|
||||||
|
private RCTSensorOrientationChecker _sensorOrientationChecker;
|
||||||
|
|
||||||
|
private MediaRecorder mMediaRecorder;
|
||||||
|
private long MRStartTime;
|
||||||
|
private File mVideoFile;
|
||||||
|
private Camera mCamera = null;
|
||||||
|
private Promise mRecordingPromise = null;
|
||||||
|
private ReadableMap mRecordingOptions;
|
||||||
|
private Boolean mSafeToCapture = true;
|
||||||
|
|
||||||
|
public RCTCameraModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
_reactContext = reactContext;
|
||||||
|
_sensorOrientationChecker = new RCTSensorOrientationChecker(_reactContext);
|
||||||
|
_reactContext.addLifecycleEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReactApplicationContext getReactContextSingleton() {
|
||||||
|
return _reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked on new MediaRecorder info.
|
||||||
|
*
|
||||||
|
* See https://developer.android.com/reference/android/media/MediaRecorder.OnInfoListener.html
|
||||||
|
* for more information.
|
||||||
|
*
|
||||||
|
* @param mr MediaRecorder instance for which this callback is being invoked.
|
||||||
|
* @param what Type of info we have received.
|
||||||
|
* @param extra Extra code, specific to the info type.
|
||||||
|
*/
|
||||||
|
public void onInfo(MediaRecorder mr, int what, int extra) {
|
||||||
|
if ( what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
|
||||||
|
what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
|
||||||
|
if (mRecordingPromise != null) {
|
||||||
|
releaseMediaRecorder(); // release the MediaRecorder object and resolve promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked when a MediaRecorder instance encounters an error while recording.
|
||||||
|
*
|
||||||
|
* See https://developer.android.com/reference/android/media/MediaRecorder.OnErrorListener.html
|
||||||
|
* for more information.
|
||||||
|
*
|
||||||
|
* @param mr MediaRecorder instance for which this callback is being invoked.
|
||||||
|
* @param what Type of error that has occurred.
|
||||||
|
* @param extra Extra code, specific to the error type.
|
||||||
|
*/
|
||||||
|
public void onError(MediaRecorder mr, int what, int extra) {
|
||||||
|
// On any error, release the MediaRecorder object and resolve promise. In particular, this
|
||||||
|
// prevents leaving the camera in an unrecoverable state if we crash in the middle of
|
||||||
|
// recording.
|
||||||
|
if (mRecordingPromise != null) {
|
||||||
|
releaseMediaRecorder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "RCTCameraModule";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("Aspect", getAspectConstants());
|
||||||
|
put("BarCodeType", getBarCodeConstants());
|
||||||
|
put("Type", getTypeConstants());
|
||||||
|
put("CaptureQuality", getCaptureQualityConstants());
|
||||||
|
put("CaptureMode", getCaptureModeConstants());
|
||||||
|
put("CaptureTarget", getCaptureTargetConstants());
|
||||||
|
put("Orientation", getOrientationConstants());
|
||||||
|
put("FlashMode", getFlashModeConstants());
|
||||||
|
put("TorchMode", getTorchModeConstants());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getAspectConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("stretch", RCT_CAMERA_ASPECT_STRETCH);
|
||||||
|
put("fit", RCT_CAMERA_ASPECT_FIT);
|
||||||
|
put("fill", RCT_CAMERA_ASPECT_FILL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getBarCodeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
// @TODO add barcode types
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getTypeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("front", RCT_CAMERA_TYPE_FRONT);
|
||||||
|
put("back", RCT_CAMERA_TYPE_BACK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getCaptureQualityConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("low", RCT_CAMERA_CAPTURE_QUALITY_LOW);
|
||||||
|
put("medium", RCT_CAMERA_CAPTURE_QUALITY_MEDIUM);
|
||||||
|
put("high", RCT_CAMERA_CAPTURE_QUALITY_HIGH);
|
||||||
|
put("photo", RCT_CAMERA_CAPTURE_QUALITY_HIGH);
|
||||||
|
put("preview", RCT_CAMERA_CAPTURE_QUALITY_PREVIEW);
|
||||||
|
put("480p", RCT_CAMERA_CAPTURE_QUALITY_480P);
|
||||||
|
put("720p", RCT_CAMERA_CAPTURE_QUALITY_720P);
|
||||||
|
put("1080p", RCT_CAMERA_CAPTURE_QUALITY_1080P);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getCaptureModeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("still", RCT_CAMERA_CAPTURE_MODE_STILL);
|
||||||
|
put("video", RCT_CAMERA_CAPTURE_MODE_VIDEO);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getCaptureTargetConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("memory", RCT_CAMERA_CAPTURE_TARGET_MEMORY);
|
||||||
|
put("disk", RCT_CAMERA_CAPTURE_TARGET_DISK);
|
||||||
|
put("cameraRoll", RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL);
|
||||||
|
put("temp", RCT_CAMERA_CAPTURE_TARGET_TEMP);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getOrientationConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("auto", RCT_CAMERA_ORIENTATION_AUTO);
|
||||||
|
put("landscapeLeft", RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT);
|
||||||
|
put("landscapeRight", RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT);
|
||||||
|
put("portrait", RCT_CAMERA_ORIENTATION_PORTRAIT);
|
||||||
|
put("portraitUpsideDown", RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFlashModeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("off", RCT_CAMERA_FLASH_MODE_OFF);
|
||||||
|
put("on", RCT_CAMERA_FLASH_MODE_ON);
|
||||||
|
put("auto", RCT_CAMERA_FLASH_MODE_AUTO);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getTorchModeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("off", RCT_CAMERA_TORCH_MODE_OFF);
|
||||||
|
put("on", RCT_CAMERA_TORCH_MODE_ON);
|
||||||
|
put("auto", RCT_CAMERA_TORCH_MODE_AUTO);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare media recorder for video capture.
|
||||||
|
*
|
||||||
|
* See "Capturing Videos" at https://developer.android.com/guide/topics/media/camera.html for
|
||||||
|
* a guideline of steps and more information in general.
|
||||||
|
*
|
||||||
|
* @param options Options.
|
||||||
|
* @return Throwable; null if no errors.
|
||||||
|
*/
|
||||||
|
private Throwable prepareMediaRecorder(ReadableMap options, int deviceOrientation) {
|
||||||
|
// Prepare CamcorderProfile instance, setting essential options.
|
||||||
|
CamcorderProfile cm = RCTCamera.getInstance().setCaptureVideoQuality(options.getInt("type"), options.getString("quality"));
|
||||||
|
if (cm == null) {
|
||||||
|
return new RuntimeException("CamcorderProfile not found in prepareMediaRecorder.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock camera to make available for MediaRecorder. Note that this statement must be
|
||||||
|
// executed before calling setCamera when configuring the MediaRecorder instance.
|
||||||
|
mCamera.unlock();
|
||||||
|
|
||||||
|
// Create new MediaRecorder instance.
|
||||||
|
mMediaRecorder = new MediaRecorder();
|
||||||
|
|
||||||
|
// Attach callback to handle maxDuration (@see onInfo method in this file).
|
||||||
|
mMediaRecorder.setOnInfoListener(this);
|
||||||
|
// Attach error listener (@see onError method in this file).
|
||||||
|
mMediaRecorder.setOnErrorListener(this);
|
||||||
|
|
||||||
|
// Set camera.
|
||||||
|
mMediaRecorder.setCamera(mCamera);
|
||||||
|
|
||||||
|
// Set AV sources.
|
||||||
|
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
|
||||||
|
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
|
||||||
|
|
||||||
|
// Adjust for orientation.
|
||||||
|
// mMediaRecorder.setOrientationHint(RCTCamera.getInstance().getAdjustedDeviceOrientation());
|
||||||
|
switch (deviceOrientation) {
|
||||||
|
case 0: mMediaRecorder.setOrientationHint(90);
|
||||||
|
break;
|
||||||
|
case 1: mMediaRecorder.setOrientationHint(0);
|
||||||
|
break;
|
||||||
|
case 2: mMediaRecorder.setOrientationHint(270);
|
||||||
|
break;
|
||||||
|
case 3: mMediaRecorder.setOrientationHint(180);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set video output format and encoding using CamcorderProfile.
|
||||||
|
cm.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
|
||||||
|
mMediaRecorder.setProfile(cm);
|
||||||
|
|
||||||
|
// Set video output file.
|
||||||
|
mVideoFile = null;
|
||||||
|
switch (options.getInt("target")) {
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_MEMORY:
|
||||||
|
mVideoFile = getTempMediaFile(MEDIA_TYPE_VIDEO); // temporarily
|
||||||
|
break;
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL:
|
||||||
|
mVideoFile = getOutputCameraRollFile(MEDIA_TYPE_VIDEO);
|
||||||
|
break;
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_TEMP:
|
||||||
|
mVideoFile = getTempMediaFile(MEDIA_TYPE_VIDEO);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_DISK:
|
||||||
|
mVideoFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mVideoFile == null) {
|
||||||
|
return new RuntimeException("Error while preparing output file in prepareMediaRecorder.");
|
||||||
|
}
|
||||||
|
mMediaRecorder.setOutputFile(mVideoFile.getPath());
|
||||||
|
|
||||||
|
if (options.hasKey("totalSeconds")) {
|
||||||
|
int totalSeconds = options.getInt("totalSeconds");
|
||||||
|
mMediaRecorder.setMaxDuration(totalSeconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.hasKey("maxFileSize")) {
|
||||||
|
int maxFileSize = options.getInt("maxFileSize");
|
||||||
|
mMediaRecorder.setMaxFileSize(maxFileSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the MediaRecorder instance with the provided configuration settings.
|
||||||
|
try {
|
||||||
|
mMediaRecorder.prepare();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "Media recorder prepare error.", ex);
|
||||||
|
releaseMediaRecorder();
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void record(final ReadableMap options, final Promise promise, final int deviceOrientation) {
|
||||||
|
if (mRecordingPromise != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCamera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type"));
|
||||||
|
if (mCamera == null) {
|
||||||
|
promise.reject(new RuntimeException("No camera found."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Throwable prepareError = prepareMediaRecorder(options, deviceOrientation);
|
||||||
|
if (prepareError != null) {
|
||||||
|
promise.reject(prepareError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mMediaRecorder.start();
|
||||||
|
MRStartTime = System.currentTimeMillis();
|
||||||
|
mRecordingOptions = options;
|
||||||
|
mRecordingPromise = promise; // only got here if mediaRecorder started
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "Media recorder start error.", ex);
|
||||||
|
promise.reject(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release media recorder following video capture (or failure to start recording session).
|
||||||
|
*
|
||||||
|
* See "Capturing Videos" at https://developer.android.com/guide/topics/media/camera.html for
|
||||||
|
* a guideline of steps and more information in general.
|
||||||
|
*/
|
||||||
|
private void releaseMediaRecorder() {
|
||||||
|
// Must record at least a second or MediaRecorder throws exceptions on some platforms
|
||||||
|
long duration = System.currentTimeMillis() - MRStartTime;
|
||||||
|
if (duration < 1500) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1500 - duration);
|
||||||
|
} catch(InterruptedException ex) {
|
||||||
|
Log.e(TAG, "releaseMediaRecorder thread sleep error.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release actual MediaRecorder instance.
|
||||||
|
if (mMediaRecorder != null) {
|
||||||
|
// Stop recording video.
|
||||||
|
try {
|
||||||
|
mMediaRecorder.stop(); // stop the recording
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
Log.e(TAG, "Media recorder stop error.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally, remove the configuration settings from the recorder.
|
||||||
|
mMediaRecorder.reset();
|
||||||
|
|
||||||
|
// Release the MediaRecorder.
|
||||||
|
mMediaRecorder.release();
|
||||||
|
|
||||||
|
// Reset variable.
|
||||||
|
mMediaRecorder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock the camera so that future MediaRecorder sessions can use it by calling
|
||||||
|
// Camera.lock(). Note this is not required on Android 4.0+ unless the
|
||||||
|
// MediaRecorder.prepare() call fails.
|
||||||
|
if (mCamera != null) {
|
||||||
|
mCamera.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRecordingPromise == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File f = new File(mVideoFile.getPath());
|
||||||
|
if (!f.exists()) {
|
||||||
|
mRecordingPromise.reject(new RuntimeException("There is nothing recorded."));
|
||||||
|
mRecordingPromise = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.setReadable(true, false); // so mediaplayer can play it
|
||||||
|
f.setWritable(true, false); // so can clean it up
|
||||||
|
|
||||||
|
WritableMap response = new WritableNativeMap();
|
||||||
|
switch (mRecordingOptions.getInt("target")) {
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_MEMORY:
|
||||||
|
byte[] encoded = convertFileToByteArray(mVideoFile);
|
||||||
|
response.putString("data", new String(encoded, Base64.NO_WRAP));
|
||||||
|
mRecordingPromise.resolve(response);
|
||||||
|
f.delete();
|
||||||
|
break;
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL:
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(MediaStore.Video.Media.DATA, mVideoFile.getPath());
|
||||||
|
values.put(MediaStore.Video.Media.TITLE, mRecordingOptions.hasKey("title") ? mRecordingOptions.getString("title") : "video");
|
||||||
|
|
||||||
|
if (mRecordingOptions.hasKey("description")) {
|
||||||
|
values.put(MediaStore.Video.Media.DESCRIPTION, mRecordingOptions.hasKey("description"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRecordingOptions.hasKey("latitude")) {
|
||||||
|
values.put(MediaStore.Video.Media.LATITUDE, mRecordingOptions.getString("latitude"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRecordingOptions.hasKey("longitude")) {
|
||||||
|
values.put(MediaStore.Video.Media.LONGITUDE, mRecordingOptions.getString("longitude"));
|
||||||
|
}
|
||||||
|
|
||||||
|
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
|
||||||
|
_reactContext.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
|
||||||
|
addToMediaStore(mVideoFile.getAbsolutePath());
|
||||||
|
response.putString("path", Uri.fromFile(mVideoFile).toString());
|
||||||
|
mRecordingPromise.resolve(response);
|
||||||
|
break;
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_TEMP:
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_DISK:
|
||||||
|
response.putString("path", Uri.fromFile(mVideoFile).toString());
|
||||||
|
mRecordingPromise.resolve(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
mRecordingPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] convertFileToByteArray(File f)
|
||||||
|
{
|
||||||
|
byte[] byteArray = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InputStream inputStream = new FileInputStream(f);
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
byte[] b = new byte[1024*8];
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
while ((bytesRead = inputStream.read(b)) != -1) {
|
||||||
|
bos.write(b, 0, bytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
byteArray = bos.toByteArray();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return byteArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void capture(final ReadableMap options, final Promise promise) {
|
||||||
|
if (RCTCamera.getInstance() == null) {
|
||||||
|
promise.reject("Camera is not ready yet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int orientation = options.hasKey("orientation") ? options.getInt("orientation") : RCTCamera.getInstance().getOrientation();
|
||||||
|
if (orientation == RCT_CAMERA_ORIENTATION_AUTO) {
|
||||||
|
_sensorOrientationChecker.onResume();
|
||||||
|
_sensorOrientationChecker.registerOrientationListener(new RCTSensorOrientationListener() {
|
||||||
|
@Override
|
||||||
|
public void orientationEvent() {
|
||||||
|
int deviceOrientation = _sensorOrientationChecker.getOrientation();
|
||||||
|
_sensorOrientationChecker.unregisterOrientationListener();
|
||||||
|
_sensorOrientationChecker.onPause();
|
||||||
|
captureWithOrientation(options, promise, deviceOrientation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
captureWithOrientation(options, promise, orientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void captureWithOrientation(final ReadableMap options, final Promise promise, int deviceOrientation) {
|
||||||
|
final Camera camera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type"));
|
||||||
|
if (null == camera) {
|
||||||
|
promise.reject("No camera found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.getInt("mode") == RCT_CAMERA_CAPTURE_MODE_VIDEO) {
|
||||||
|
record(options, promise, deviceOrientation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RCTCamera.getInstance().setCaptureQuality(options.getInt("type"), options.getString("quality"));
|
||||||
|
|
||||||
|
if (options.hasKey("playSoundOnCapture") && options.getBoolean("playSoundOnCapture")) {
|
||||||
|
MediaActionSound sound = new MediaActionSound();
|
||||||
|
sound.play(MediaActionSound.SHUTTER_CLICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.hasKey("quality")) {
|
||||||
|
RCTCamera.getInstance().setCaptureQuality(options.getInt("type"), options.getString("quality"));
|
||||||
|
}
|
||||||
|
|
||||||
|
RCTCamera.getInstance().adjustCameraRotationToDeviceOrientation(options.getInt("type"), deviceOrientation);
|
||||||
|
camera.setPreviewCallback(null);
|
||||||
|
|
||||||
|
Camera.PictureCallback captureCallback = new Camera.PictureCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPictureTaken(final byte[] data, Camera camera) {
|
||||||
|
camera.stopPreview();
|
||||||
|
camera.startPreview();
|
||||||
|
|
||||||
|
AsyncTask.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
processImage(new MutableImage(data), options, promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mSafeToCapture = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() {
|
||||||
|
@Override
|
||||||
|
public void onShutter() {
|
||||||
|
try {
|
||||||
|
camera.setPreviewCallback(null);
|
||||||
|
camera.setPreviewTexture(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(mSafeToCapture) {
|
||||||
|
try {
|
||||||
|
camera.takePicture(shutterCallback, null, captureCallback);
|
||||||
|
mSafeToCapture = false;
|
||||||
|
} catch(RuntimeException ex) {
|
||||||
|
Log.e(TAG, "Couldn't capture photo.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* synchronized in order to prevent the user crashing the app by taking many photos and them all being processed
|
||||||
|
* concurrently which would blow the memory (esp on smaller devices), and slow things down.
|
||||||
|
*/
|
||||||
|
private synchronized void processImage(MutableImage mutableImage, ReadableMap options, Promise promise) {
|
||||||
|
boolean shouldFixOrientation = options.hasKey("fixOrientation") && options.getBoolean("fixOrientation");
|
||||||
|
if(shouldFixOrientation) {
|
||||||
|
try {
|
||||||
|
mutableImage.fixOrientation();
|
||||||
|
} catch (MutableImage.ImageMutationFailedException e) {
|
||||||
|
promise.reject("Error fixing orientation image", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean needsReorient = false;
|
||||||
|
double previewRatio, pictureRatio = (double) mutableImage.getWidth() / (double) mutableImage.getHeight();
|
||||||
|
try {
|
||||||
|
int type = options.getInt("type");
|
||||||
|
previewRatio = (double) RCTCamera.getInstance().getPreviewVisibleWidth(type) / (double) RCTCamera.getInstance().getPreviewVisibleHeight(type);
|
||||||
|
needsReorient = (previewRatio > 1) != (pictureRatio > 1);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
previewRatio = pictureRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean shouldCropToPreview = options.hasKey("cropToPreview") && options.getBoolean("cropToPreview");
|
||||||
|
if (shouldCropToPreview) {
|
||||||
|
try {
|
||||||
|
mutableImage.cropToPreview(needsReorient ? 1.0 / previewRatio : previewRatio);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
promise.reject("Error cropping image to preview", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean shouldMirror = options.hasKey("mirrorImage") && options.getBoolean("mirrorImage");
|
||||||
|
if (shouldMirror) {
|
||||||
|
try {
|
||||||
|
mutableImage.mirrorImage();
|
||||||
|
} catch (MutableImage.ImageMutationFailedException e) {
|
||||||
|
promise.reject("Error mirroring image", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int jpegQualityPercent = 80;
|
||||||
|
if(options.hasKey("jpegQuality")) {
|
||||||
|
jpegQualityPercent = options.getInt("jpegQuality");
|
||||||
|
}
|
||||||
|
|
||||||
|
int imgWidth = (needsReorient) ? mutableImage.getHeight() : mutableImage.getWidth();
|
||||||
|
int imgHeight = (needsReorient) ? mutableImage.getWidth() : mutableImage.getHeight();
|
||||||
|
|
||||||
|
switch (options.getInt("target")) {
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_MEMORY:
|
||||||
|
String encoded = mutableImage.toBase64(jpegQualityPercent);
|
||||||
|
WritableMap response = new WritableNativeMap();
|
||||||
|
response.putString("data", encoded);
|
||||||
|
response.putInt("width", imgWidth);
|
||||||
|
response.putInt("height", imgHeight);
|
||||||
|
promise.resolve(response);
|
||||||
|
break;
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: {
|
||||||
|
File cameraRollFile = getOutputCameraRollFile(MEDIA_TYPE_IMAGE);
|
||||||
|
if (cameraRollFile == null) {
|
||||||
|
promise.reject("Error creating media file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mutableImage.writeDataToFile(cameraRollFile, options, jpegQualityPercent);
|
||||||
|
} catch (IOException | NullPointerException e) {
|
||||||
|
promise.reject("failed to save image file", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToMediaStore(cameraRollFile.getAbsolutePath());
|
||||||
|
|
||||||
|
resolveImage(cameraRollFile, imgWidth, imgHeight, promise, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_DISK: {
|
||||||
|
File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
|
||||||
|
if (pictureFile == null) {
|
||||||
|
promise.reject("Error creating media file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mutableImage.writeDataToFile(pictureFile, options, jpegQualityPercent);
|
||||||
|
} catch (IOException e) {
|
||||||
|
promise.reject("failed to save image file", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveImage(pictureFile, imgWidth, imgHeight, promise, false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_TEMP: {
|
||||||
|
File tempFile = getTempMediaFile(MEDIA_TYPE_IMAGE);
|
||||||
|
if (tempFile == null) {
|
||||||
|
promise.reject("Error creating media file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mutableImage.writeDataToFile(tempFile, options, jpegQualityPercent);
|
||||||
|
} catch (IOException e) {
|
||||||
|
promise.reject("failed to save image file", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveImage(tempFile, imgWidth, imgHeight, promise, false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void stopCapture(final Promise promise) {
|
||||||
|
if (mRecordingPromise != null) {
|
||||||
|
releaseMediaRecorder(); // release the MediaRecorder object
|
||||||
|
promise.resolve("Finished recording.");
|
||||||
|
} else {
|
||||||
|
promise.resolve("Not recording.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void hasFlash(ReadableMap options, final Promise promise) {
|
||||||
|
Camera camera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type"));
|
||||||
|
if (null == camera) {
|
||||||
|
promise.reject("No camera found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> flashModes = camera.getParameters().getSupportedFlashModes();
|
||||||
|
promise.resolve(null != flashModes && !flashModes.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void setZoom(ReadableMap options, int zoom) {
|
||||||
|
RCTCamera instance = RCTCamera.getInstance();
|
||||||
|
if (instance == null) return;
|
||||||
|
|
||||||
|
Camera camera = instance.acquireCameraInstance(options.getInt("type"));
|
||||||
|
if (camera == null) return;
|
||||||
|
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
int maxZoom = parameters.getMaxZoom();
|
||||||
|
if (parameters.isZoomSupported()) {
|
||||||
|
if (zoom >=0 && zoom < maxZoom) {
|
||||||
|
parameters.setZoom(zoom);
|
||||||
|
try{
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCameraModule", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getOutputMediaFile(int type) {
|
||||||
|
// Get environment directory type id from requested media type.
|
||||||
|
String environmentDirectoryType;
|
||||||
|
if (type == MEDIA_TYPE_IMAGE) {
|
||||||
|
environmentDirectoryType = Environment.DIRECTORY_PICTURES;
|
||||||
|
} else if (type == MEDIA_TYPE_VIDEO) {
|
||||||
|
environmentDirectoryType = Environment.DIRECTORY_MOVIES;
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unsupported media type:" + type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOutputFile(
|
||||||
|
type,
|
||||||
|
Environment.getExternalStoragePublicDirectory(environmentDirectoryType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getOutputCameraRollFile(int type) {
|
||||||
|
return getOutputFile(
|
||||||
|
type,
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getOutputFile(int type, File storageDir) {
|
||||||
|
// Create the storage directory if it does not exist
|
||||||
|
if (!storageDir.exists()) {
|
||||||
|
if (!storageDir.mkdirs()) {
|
||||||
|
Log.e(TAG, "failed to create directory:" + storageDir.getAbsolutePath());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a media file name
|
||||||
|
String fileName = String.format("%s", new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()));
|
||||||
|
|
||||||
|
if (type == MEDIA_TYPE_IMAGE) {
|
||||||
|
fileName = String.format("IMG_%s.jpg", fileName);
|
||||||
|
} else if (type == MEDIA_TYPE_VIDEO) {
|
||||||
|
fileName = String.format("VID_%s.mp4", fileName);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unsupported media type:" + type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new File(String.format("%s%s%s", storageDir.getPath(), File.separator, fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getTempMediaFile(int type) {
|
||||||
|
try {
|
||||||
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||||
|
File outputDir = _reactContext.getCacheDir();
|
||||||
|
File outputFile;
|
||||||
|
|
||||||
|
if (type == MEDIA_TYPE_IMAGE) {
|
||||||
|
outputFile = File.createTempFile("IMG_" + timeStamp, ".jpg", outputDir);
|
||||||
|
} else if (type == MEDIA_TYPE_VIDEO) {
|
||||||
|
outputFile = File.createTempFile("VID_" + timeStamp, ".mp4", outputDir);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unsupported media type:" + type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return outputFile;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToMediaStore(String path) {
|
||||||
|
MediaScannerConnection.scanFile(_reactContext, new String[] { path }, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LifecycleEventListener overrides
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onHostResume() {
|
||||||
|
mSafeToCapture = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHostPause() {
|
||||||
|
// On pause, we stop any pending recording session
|
||||||
|
if (mRecordingPromise != null) {
|
||||||
|
releaseMediaRecorder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHostDestroy() {
|
||||||
|
// ... do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveImage(final File imageFile, final int imgWidth, final int imgHeight, final Promise promise, boolean addToMediaStore) {
|
||||||
|
final WritableMap response = new WritableNativeMap();
|
||||||
|
response.putString("path", Uri.fromFile(imageFile).toString());
|
||||||
|
response.putInt("width", imgWidth);
|
||||||
|
response.putInt("height", imgHeight);
|
||||||
|
|
||||||
|
if(addToMediaStore) {
|
||||||
|
// borrowed from react-native CameraRollManager, it finds and returns the 'internal'
|
||||||
|
// representation of the image uri that was just saved.
|
||||||
|
// e.g. content://media/external/images/media/123
|
||||||
|
MediaScannerConnection.scanFile(
|
||||||
|
_reactContext,
|
||||||
|
new String[]{imageFile.getAbsolutePath()},
|
||||||
|
null,
|
||||||
|
new MediaScannerConnection.OnScanCompletedListener() {
|
||||||
|
@Override
|
||||||
|
public void onScanCompleted(String path, Uri uri) {
|
||||||
|
if (uri != null) {
|
||||||
|
response.putString("mediaUri", uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise.resolve(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.hardware.Camera;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
public class RCTCameraUtils {
|
||||||
|
private static final int FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH = 100;
|
||||||
|
private static final int FOCUS_AREA_WEIGHT = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes a Camera.Area corresponding to the new focus area to focus the camera on. This is
|
||||||
|
* done by deriving a square around the center of a MotionEvent pointer (with side length equal
|
||||||
|
* to FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH), then transforming this rectangle's/square's
|
||||||
|
* coordinates into the (-1000, 1000) coordinate system used for camera focus areas.
|
||||||
|
*
|
||||||
|
* Also note that we operate on RectF instances for the most part, to avoid any integer
|
||||||
|
* division rounding errors going forward. We only round at the very end for playing into
|
||||||
|
* the final focus areas list.
|
||||||
|
*
|
||||||
|
* @throws RuntimeException if unable to compute valid intersection between MotionEvent region
|
||||||
|
* and SurfaceTexture region.
|
||||||
|
*/
|
||||||
|
protected static Camera.Area computeFocusAreaFromMotionEvent(final MotionEvent event, final int surfaceTextureWidth, final int surfaceTextureHeight) {
|
||||||
|
// Get position of first touch pointer.
|
||||||
|
final int pointerId = event.getPointerId(0);
|
||||||
|
final int pointerIndex = event.findPointerIndex(pointerId);
|
||||||
|
final float centerX = event.getX(pointerIndex);
|
||||||
|
final float centerY = event.getY(pointerIndex);
|
||||||
|
|
||||||
|
// Build event rect. Note that coordinates increase right and down, such that left <= right
|
||||||
|
// and top <= bottom.
|
||||||
|
final RectF eventRect = new RectF(
|
||||||
|
centerX - FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH, // left
|
||||||
|
centerY - FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH, // top
|
||||||
|
centerX + FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH, // right
|
||||||
|
centerY + FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH // bottom
|
||||||
|
);
|
||||||
|
|
||||||
|
// Intersect this rect with the rect corresponding to the full area of the parent surface
|
||||||
|
// texture, making sure we are not placing any amount of the eventRect outside the parent
|
||||||
|
// surface's area.
|
||||||
|
final RectF surfaceTextureRect = new RectF(
|
||||||
|
(float) 0, // left
|
||||||
|
(float) 0, // top
|
||||||
|
(float) surfaceTextureWidth, // right
|
||||||
|
(float) surfaceTextureHeight // bottom
|
||||||
|
);
|
||||||
|
final boolean intersectSuccess = eventRect.intersect(surfaceTextureRect);
|
||||||
|
if (!intersectSuccess) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"MotionEvent rect does not intersect with SurfaceTexture rect; unable to " +
|
||||||
|
"compute focus area"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform into (-1000, 1000) focus area coordinate system. See
|
||||||
|
// https://developer.android.com/reference/android/hardware/Camera.Area.html.
|
||||||
|
// Note that if this is ever changed to a Rect instead of RectF, be cautious of integer
|
||||||
|
// division rounding!
|
||||||
|
final RectF focusAreaRect = new RectF(
|
||||||
|
(eventRect.left / surfaceTextureWidth) * 2000 - 1000, // left
|
||||||
|
(eventRect.top / surfaceTextureHeight) * 2000 - 1000, // top
|
||||||
|
(eventRect.right / surfaceTextureWidth) * 2000 - 1000, // right
|
||||||
|
(eventRect.bottom / surfaceTextureHeight) * 2000 - 1000 // bottom
|
||||||
|
);
|
||||||
|
Rect focusAreaRectRounded = new Rect();
|
||||||
|
focusAreaRect.round(focusAreaRectRounded);
|
||||||
|
return new Camera.Area(focusAreaRectRounded, FOCUS_AREA_WEIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
/**
|
||||||
|
* Created by Fabrice Armisen (farmisen@gmail.com) on 1/3/16.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.view.OrientationEventListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RCTCameraView extends ViewGroup {
|
||||||
|
private final OrientationEventListener _orientationListener;
|
||||||
|
private final Context _context;
|
||||||
|
private RCTCameraViewFinder _viewFinder = null;
|
||||||
|
private int _actualDeviceOrientation = -1;
|
||||||
|
private int _aspect = RCTCameraModule.RCT_CAMERA_ASPECT_FIT;
|
||||||
|
private int _captureMode = RCTCameraModule.RCT_CAMERA_CAPTURE_MODE_STILL;
|
||||||
|
private String _captureQuality = "high";
|
||||||
|
private int _torchMode = -1;
|
||||||
|
private int _flashMode = -1;
|
||||||
|
private int _zoom = 0;
|
||||||
|
private boolean _clearWindowBackground = false;
|
||||||
|
|
||||||
|
public RCTCameraView(Context context) {
|
||||||
|
super(context);
|
||||||
|
this._context = context;
|
||||||
|
RCTCamera.createInstance(getDeviceOrientation(context));
|
||||||
|
|
||||||
|
_orientationListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
|
||||||
|
@Override
|
||||||
|
public void onOrientationChanged(int orientation) {
|
||||||
|
if (setActualDeviceOrientation(_context)) {
|
||||||
|
layoutViewFinder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_orientationListener.canDetectOrientation()) {
|
||||||
|
_orientationListener.enable();
|
||||||
|
} else {
|
||||||
|
_orientationListener.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
|
layoutViewFinder(left, top, right, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewAdded(View child) {
|
||||||
|
if (this._viewFinder == child) return;
|
||||||
|
// remove and readd view to make sure it is in the back.
|
||||||
|
// @TODO figure out why there was a z order issue in the first place and fix accordingly.
|
||||||
|
this.removeView(this._viewFinder);
|
||||||
|
this.addView(this._viewFinder, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAspect(int aspect) {
|
||||||
|
this._aspect = aspect;
|
||||||
|
layoutViewFinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCameraType(final int type) {
|
||||||
|
if (null != this._viewFinder) {
|
||||||
|
this._viewFinder.setCameraType(type);
|
||||||
|
RCTCamera.getInstance().adjustPreviewLayout(type);
|
||||||
|
} else {
|
||||||
|
_viewFinder = new RCTCameraViewFinder(_context, type);
|
||||||
|
if (-1 != this._flashMode) {
|
||||||
|
_viewFinder.setFlashMode(this._flashMode);
|
||||||
|
}
|
||||||
|
if (-1 != this._torchMode) {
|
||||||
|
_viewFinder.setTorchMode(this._torchMode);
|
||||||
|
}
|
||||||
|
if (0 != this._zoom) {
|
||||||
|
_viewFinder.setZoom(this._zoom);
|
||||||
|
}
|
||||||
|
_viewFinder.setClearWindowBackground(this._clearWindowBackground);
|
||||||
|
addView(_viewFinder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaptureMode(final int captureMode) {
|
||||||
|
this._captureMode = captureMode;
|
||||||
|
if (this._viewFinder != null) {
|
||||||
|
this._viewFinder.setCaptureMode(captureMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaptureQuality(String captureQuality) {
|
||||||
|
this._captureQuality = captureQuality;
|
||||||
|
if (this._viewFinder != null) {
|
||||||
|
this._viewFinder.setCaptureQuality(captureQuality);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTorchMode(int torchMode) {
|
||||||
|
this._torchMode = torchMode;
|
||||||
|
if (this._viewFinder != null) {
|
||||||
|
this._viewFinder.setTorchMode(torchMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlashMode(int flashMode) {
|
||||||
|
this._flashMode = flashMode;
|
||||||
|
if (this._viewFinder != null) {
|
||||||
|
this._viewFinder.setFlashMode(flashMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoom(int zoom) {
|
||||||
|
this._zoom = zoom;
|
||||||
|
if (this._viewFinder != null) {
|
||||||
|
this._viewFinder.setZoom(zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrientation(int orientation) {
|
||||||
|
RCTCamera.getInstance().setOrientation(orientation);
|
||||||
|
if (this._viewFinder != null) {
|
||||||
|
layoutViewFinder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) {
|
||||||
|
RCTCamera.getInstance().setBarcodeScannerEnabled(barcodeScannerEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarCodeTypes(List<String> types) {
|
||||||
|
RCTCamera.getInstance().setBarCodeTypes(types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClearWindowBackground(boolean clearWindowBackground) {
|
||||||
|
this._clearWindowBackground = clearWindowBackground;
|
||||||
|
if (this._viewFinder != null) {
|
||||||
|
this._viewFinder.setClearWindowBackground(clearWindowBackground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopPreview() {
|
||||||
|
if (_viewFinder == null) return;
|
||||||
|
_viewFinder.stopPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startPreview() {
|
||||||
|
if (_viewFinder == null) return;
|
||||||
|
_viewFinder.startPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setActualDeviceOrientation(Context context) {
|
||||||
|
int actualDeviceOrientation = getDeviceOrientation(context);
|
||||||
|
if (_actualDeviceOrientation != actualDeviceOrientation) {
|
||||||
|
_actualDeviceOrientation = actualDeviceOrientation;
|
||||||
|
RCTCamera.getInstance().setActualDeviceOrientation(_actualDeviceOrientation);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDeviceOrientation(Context context) {
|
||||||
|
return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getOrientation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void layoutViewFinder() {
|
||||||
|
layoutViewFinder(this.getLeft(), this.getTop(), this.getRight(), this.getBottom());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void layoutViewFinder(int left, int top, int right, int bottom) {
|
||||||
|
if (null == _viewFinder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float width = right - left;
|
||||||
|
float height = bottom - top;
|
||||||
|
int viewfinderWidth;
|
||||||
|
int viewfinderHeight;
|
||||||
|
double ratio;
|
||||||
|
switch (this._aspect) {
|
||||||
|
case RCTCameraModule.RCT_CAMERA_ASPECT_FIT:
|
||||||
|
ratio = this._viewFinder.getRatio();
|
||||||
|
if (ratio * height > width) {
|
||||||
|
viewfinderHeight = (int) (width / ratio);
|
||||||
|
viewfinderWidth = (int) width;
|
||||||
|
} else {
|
||||||
|
viewfinderWidth = (int) (ratio * height);
|
||||||
|
viewfinderHeight = (int) height;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RCTCameraModule.RCT_CAMERA_ASPECT_FILL:
|
||||||
|
ratio = this._viewFinder.getRatio();
|
||||||
|
if (ratio * height < width) {
|
||||||
|
viewfinderHeight = (int) (width / ratio);
|
||||||
|
viewfinderWidth = (int) width;
|
||||||
|
} else {
|
||||||
|
viewfinderWidth = (int) (ratio * height);
|
||||||
|
viewfinderHeight = (int) height;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
viewfinderWidth = (int) width;
|
||||||
|
viewfinderHeight = (int) height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int viewFinderPaddingX = (int) ((width - viewfinderWidth) / 2);
|
||||||
|
int viewFinderPaddingY = (int) ((height - viewfinderHeight) / 2);
|
||||||
|
|
||||||
|
RCTCamera.getInstance().setPreviewVisibleSize(_viewFinder.getCameraType(), (int) width, (int) height);
|
||||||
|
|
||||||
|
this._viewFinder.layout(viewFinderPaddingX, viewFinderPaddingY, viewFinderPaddingX + viewfinderWidth, viewFinderPaddingY + viewfinderHeight);
|
||||||
|
this.postInvalidate(this.getLeft(), this.getTop(), this.getRight(), this.getBottom());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,566 @@
|
||||||
|
/**
|
||||||
|
* Created by Fabrice Armisen (farmisen@gmail.com) on 1/3/16.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.hardware.Camera;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.TextureView;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReactContext;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.BinaryBitmap;
|
||||||
|
import com.google.zxing.DecodeHintType;
|
||||||
|
import com.google.zxing.MultiFormatReader;
|
||||||
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.ResultPoint;
|
||||||
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
|
||||||
|
class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceTextureListener, Camera.PreviewCallback {
|
||||||
|
private int _cameraType;
|
||||||
|
private int _captureMode;
|
||||||
|
private SurfaceTexture _surfaceTexture;
|
||||||
|
private int _surfaceTextureWidth;
|
||||||
|
private int _surfaceTextureHeight;
|
||||||
|
private boolean _isStarting;
|
||||||
|
private boolean _isStopping;
|
||||||
|
private Camera _camera;
|
||||||
|
private boolean _clearWindowBackground = false;
|
||||||
|
private float mFingerSpacing;
|
||||||
|
|
||||||
|
// concurrency lock for barcode scanner to avoid flooding the runtime
|
||||||
|
public static volatile boolean barcodeScannerTaskLock = false;
|
||||||
|
|
||||||
|
// reader instance for the barcode scanner
|
||||||
|
private final MultiFormatReader _multiFormatReader = new MultiFormatReader();
|
||||||
|
|
||||||
|
public RCTCameraViewFinder(Context context, int type) {
|
||||||
|
super(context);
|
||||||
|
this.setSurfaceTextureListener(this);
|
||||||
|
this._cameraType = type;
|
||||||
|
this.initBarcodeReader(RCTCamera.getInstance().getBarCodeTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||||
|
_surfaceTexture = surface;
|
||||||
|
_surfaceTextureWidth = width;
|
||||||
|
_surfaceTextureHeight = height;
|
||||||
|
startCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||||||
|
_surfaceTextureWidth = width;
|
||||||
|
_surfaceTextureHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||||
|
_surfaceTexture = null;
|
||||||
|
_surfaceTextureWidth = 0;
|
||||||
|
_surfaceTextureHeight = 0;
|
||||||
|
stopCamera();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCameraType() {
|
||||||
|
return _cameraType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRatio() {
|
||||||
|
int width = RCTCamera.getInstance().getPreviewWidth(this._cameraType);
|
||||||
|
int height = RCTCamera.getInstance().getPreviewHeight(this._cameraType);
|
||||||
|
return ((float) width) / ((float) height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCameraType(final int type) {
|
||||||
|
if (this._cameraType == type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
stopPreview();
|
||||||
|
_cameraType = type;
|
||||||
|
startPreview();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaptureMode(final int captureMode) {
|
||||||
|
RCTCamera.getInstance().setCaptureMode(_cameraType, captureMode);
|
||||||
|
this._captureMode = captureMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaptureQuality(String captureQuality) {
|
||||||
|
RCTCamera.getInstance().setCaptureQuality(_cameraType, captureQuality);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTorchMode(int torchMode) {
|
||||||
|
RCTCamera.getInstance().setTorchMode(_cameraType, torchMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlashMode(int flashMode) {
|
||||||
|
RCTCamera.getInstance().setFlashMode(_cameraType, flashMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClearWindowBackground(boolean clearWindowBackground) {
|
||||||
|
this._clearWindowBackground = clearWindowBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoom(int zoom) {
|
||||||
|
RCTCamera.getInstance().setZoom(_cameraType, zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startPreview() {
|
||||||
|
if (_surfaceTexture != null) {
|
||||||
|
startCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopPreview() {
|
||||||
|
if (_camera != null) {
|
||||||
|
stopCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized private void startCamera() {
|
||||||
|
if (!_isStarting) {
|
||||||
|
_isStarting = true;
|
||||||
|
try {
|
||||||
|
_camera = RCTCamera.getInstance().acquireCameraInstance(_cameraType);
|
||||||
|
Camera.Parameters parameters = _camera.getParameters();
|
||||||
|
|
||||||
|
final boolean isCaptureModeStill = (_captureMode == RCTCameraModule.RCT_CAMERA_CAPTURE_MODE_STILL);
|
||||||
|
final boolean isCaptureModeVideo = (_captureMode == RCTCameraModule.RCT_CAMERA_CAPTURE_MODE_VIDEO);
|
||||||
|
if (!isCaptureModeStill && !isCaptureModeVideo) {
|
||||||
|
throw new RuntimeException("Unsupported capture mode:" + _captureMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set auto-focus. Try to set to continuous picture/video, and fall back to general
|
||||||
|
// auto if available.
|
||||||
|
List<String> focusModes = parameters.getSupportedFocusModes();
|
||||||
|
if (isCaptureModeStill && focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
||||||
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
||||||
|
} else if (isCaptureModeVideo && focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
|
||||||
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
|
||||||
|
} else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
|
||||||
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set picture size
|
||||||
|
// defaults to max available size
|
||||||
|
List<Camera.Size> supportedSizes;
|
||||||
|
if (isCaptureModeStill) {
|
||||||
|
supportedSizes = parameters.getSupportedPictureSizes();
|
||||||
|
} else if (isCaptureModeVideo) {
|
||||||
|
supportedSizes = RCTCamera.getInstance().getSupportedVideoSizes(_camera);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unsupported capture mode:" + _captureMode);
|
||||||
|
}
|
||||||
|
Camera.Size optimalPictureSize = RCTCamera.getInstance().getBestSize(
|
||||||
|
supportedSizes,
|
||||||
|
Integer.MAX_VALUE,
|
||||||
|
Integer.MAX_VALUE
|
||||||
|
);
|
||||||
|
parameters.setPictureSize(optimalPictureSize.width, optimalPictureSize.height);
|
||||||
|
|
||||||
|
try{
|
||||||
|
_camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCameraViewFinder", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
_camera.setPreviewTexture(_surfaceTexture);
|
||||||
|
_camera.startPreview();
|
||||||
|
// clear window background if needed
|
||||||
|
if (_clearWindowBackground) {
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity != null)
|
||||||
|
activity.getWindow().setBackgroundDrawable(null);
|
||||||
|
}
|
||||||
|
// send previews to `onPreviewFrame`
|
||||||
|
_camera.setPreviewCallback(this);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
stopCamera();
|
||||||
|
} finally {
|
||||||
|
_isStarting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized private void stopCamera() {
|
||||||
|
if (!_isStopping) {
|
||||||
|
_isStopping = true;
|
||||||
|
try {
|
||||||
|
if (_camera != null) {
|
||||||
|
_camera.stopPreview();
|
||||||
|
// stop sending previews to `onPreviewFrame`
|
||||||
|
_camera.setPreviewCallback(null);
|
||||||
|
RCTCamera.getInstance().releaseCameraInstance(_cameraType);
|
||||||
|
_camera = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
_isStopping = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Activity getActivity() {
|
||||||
|
Context context = getContext();
|
||||||
|
while (context instanceof ContextWrapper) {
|
||||||
|
if (context instanceof Activity) {
|
||||||
|
return (Activity)context;
|
||||||
|
}
|
||||||
|
context = ((ContextWrapper)context).getBaseContext();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse barcodes as BarcodeFormat constants.
|
||||||
|
*
|
||||||
|
* Supports all iOS codes except [code39mod43, itf14]
|
||||||
|
*
|
||||||
|
* Additionally supports [codabar, maxicode, rss14, rssexpanded, upca, upceanextension]
|
||||||
|
*/
|
||||||
|
private BarcodeFormat parseBarCodeString(String c) {
|
||||||
|
if ("aztec".equals(c)) {
|
||||||
|
return BarcodeFormat.AZTEC;
|
||||||
|
} else if ("ean13".equals(c)) {
|
||||||
|
return BarcodeFormat.EAN_13;
|
||||||
|
} else if ("ean8".equals(c)) {
|
||||||
|
return BarcodeFormat.EAN_8;
|
||||||
|
} else if ("qr".equals(c)) {
|
||||||
|
return BarcodeFormat.QR_CODE;
|
||||||
|
} else if ("pdf417".equals(c)) {
|
||||||
|
return BarcodeFormat.PDF_417;
|
||||||
|
} else if ("upce".equals(c)) {
|
||||||
|
return BarcodeFormat.UPC_E;
|
||||||
|
} else if ("datamatrix".equals(c)) {
|
||||||
|
return BarcodeFormat.DATA_MATRIX;
|
||||||
|
} else if ("code39".equals(c)) {
|
||||||
|
return BarcodeFormat.CODE_39;
|
||||||
|
} else if ("code93".equals(c)) {
|
||||||
|
return BarcodeFormat.CODE_93;
|
||||||
|
} else if ("interleaved2of5".equals(c)) {
|
||||||
|
return BarcodeFormat.ITF;
|
||||||
|
} else if ("codabar".equals(c)) {
|
||||||
|
return BarcodeFormat.CODABAR;
|
||||||
|
} else if ("code128".equals(c)) {
|
||||||
|
return BarcodeFormat.CODE_128;
|
||||||
|
} else if ("maxicode".equals(c)) {
|
||||||
|
return BarcodeFormat.MAXICODE;
|
||||||
|
} else if ("rss14".equals(c)) {
|
||||||
|
return BarcodeFormat.RSS_14;
|
||||||
|
} else if ("rssexpanded".equals(c)) {
|
||||||
|
return BarcodeFormat.RSS_EXPANDED;
|
||||||
|
} else if ("upca".equals(c)) {
|
||||||
|
return BarcodeFormat.UPC_A;
|
||||||
|
} else if ("upceanextension".equals(c)) {
|
||||||
|
return BarcodeFormat.UPC_EAN_EXTENSION;
|
||||||
|
} else {
|
||||||
|
android.util.Log.v("RCTCamera", "Unsupported code.. [" + c + "]");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the barcode decoder.
|
||||||
|
*/
|
||||||
|
private void initBarcodeReader(List<String> barCodeTypes) {
|
||||||
|
EnumMap<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
|
||||||
|
EnumSet<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
|
||||||
|
|
||||||
|
if (barCodeTypes != null) {
|
||||||
|
for (String code : barCodeTypes) {
|
||||||
|
BarcodeFormat format = parseBarCodeString(code);
|
||||||
|
if (format != null) {
|
||||||
|
decodeFormats.add(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
|
||||||
|
_multiFormatReader.setHints(hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a barcode reader task if
|
||||||
|
* - the barcode scanner is enabled (has a onBarCodeRead function)
|
||||||
|
* - one isn't already running
|
||||||
|
*
|
||||||
|
* See {Camera.PreviewCallback}
|
||||||
|
*/
|
||||||
|
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||||
|
if (RCTCamera.getInstance().isBarcodeScannerEnabled() && !RCTCameraViewFinder.barcodeScannerTaskLock) {
|
||||||
|
RCTCameraViewFinder.barcodeScannerTaskLock = true;
|
||||||
|
new ReaderAsyncTask(camera, data).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReaderAsyncTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
private byte[] imageData;
|
||||||
|
private final Camera camera;
|
||||||
|
|
||||||
|
ReaderAsyncTask(Camera camera, byte[] imageData) {
|
||||||
|
this.camera = camera;
|
||||||
|
this.imageData = imageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result getBarcode(int width, int height, boolean inverse) {
|
||||||
|
try{
|
||||||
|
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(imageData, width, height, 0, 0, width, height, false);
|
||||||
|
BinaryBitmap bitmap;
|
||||||
|
if (inverse) {
|
||||||
|
bitmap = new BinaryBitmap(new HybridBinarizer(source.invert()));
|
||||||
|
} else {
|
||||||
|
bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
}
|
||||||
|
return _multiFormatReader.decodeWithState(bitmap);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// meh
|
||||||
|
} finally {
|
||||||
|
_multiFormatReader.reset();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result getBarcodeAnyOrientation() {
|
||||||
|
Camera.Size size = camera.getParameters().getPreviewSize();
|
||||||
|
|
||||||
|
int width = size.width;
|
||||||
|
int height = size.height;
|
||||||
|
Result result = getBarcode(width, height, false);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// inverse
|
||||||
|
result = getBarcode(width, height, true);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// rotate
|
||||||
|
rotateImage(width, height);
|
||||||
|
width = size.height;
|
||||||
|
height = size.width;
|
||||||
|
result = getBarcode(width, height, false);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return getBarcode(width, height, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotateImage(int width, int height) {
|
||||||
|
byte[] rotated = new byte[imageData.length];
|
||||||
|
for (int y = 0; y < width; y++) {
|
||||||
|
for (int x = 0; x < height; x++) {
|
||||||
|
int sourceIx = x + y * height;
|
||||||
|
int destIx = x * width + width - y - 1;
|
||||||
|
if (sourceIx >= 0 && sourceIx < imageData.length && destIx >= 0 && destIx < imageData.length) {
|
||||||
|
rotated[destIx] = imageData[sourceIx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageData = rotated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... ignored) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// rotate for zxing if orientation is portrait
|
||||||
|
Result result = getBarcodeAnyOrientation();
|
||||||
|
if (result == null){
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactContext reactContext = RCTCameraModule.getReactContextSingleton();
|
||||||
|
WritableMap event = Arguments.createMap();
|
||||||
|
WritableArray resultPoints = Arguments.createArray();
|
||||||
|
ResultPoint[] points = result.getResultPoints();
|
||||||
|
|
||||||
|
if(points != null) {
|
||||||
|
for (ResultPoint point : points) {
|
||||||
|
WritableMap newPoint = Arguments.createMap();
|
||||||
|
newPoint.putString("x", String.valueOf(point.getX()));
|
||||||
|
newPoint.putString("y", String.valueOf(point.getY()));
|
||||||
|
resultPoints.pushMap(newPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.putArray("bounds", resultPoints);
|
||||||
|
event.putString("data", result.getText());
|
||||||
|
event.putString("type", result.getBarcodeFormat().toString());
|
||||||
|
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("CameraBarCodeReadAndroid", event);
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// meh
|
||||||
|
} finally {
|
||||||
|
_multiFormatReader.reset();
|
||||||
|
RCTCameraViewFinder.barcodeScannerTaskLock = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
// Fast swiping and touching while component is being loaded can cause _camera to be null.
|
||||||
|
if (_camera == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the pointer ID
|
||||||
|
Camera.Parameters params = _camera.getParameters();
|
||||||
|
int action = event.getAction();
|
||||||
|
|
||||||
|
|
||||||
|
if (event.getPointerCount() > 1) {
|
||||||
|
// handle multi-touch events
|
||||||
|
if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||||
|
mFingerSpacing = getFingerSpacing(event);
|
||||||
|
} else if (action == MotionEvent.ACTION_MOVE && params.isZoomSupported()) {
|
||||||
|
_camera.cancelAutoFocus();
|
||||||
|
handleZoom(event, params);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// handle single touch events
|
||||||
|
if (action == MotionEvent.ACTION_UP) {
|
||||||
|
handleFocus(event, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleZoom(MotionEvent event, Camera.Parameters params) {
|
||||||
|
int maxZoom = params.getMaxZoom();
|
||||||
|
int zoom = params.getZoom();
|
||||||
|
float newDist = getFingerSpacing(event);
|
||||||
|
if (newDist > mFingerSpacing) {
|
||||||
|
//zoom in
|
||||||
|
if (zoom < maxZoom)
|
||||||
|
zoom++;
|
||||||
|
} else if (newDist < mFingerSpacing) {
|
||||||
|
//zoom out
|
||||||
|
if (zoom > 0)
|
||||||
|
zoom--;
|
||||||
|
}
|
||||||
|
mFingerSpacing = newDist;
|
||||||
|
params.setZoom(zoom);
|
||||||
|
try{
|
||||||
|
_camera.setParameters(params);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCameraViewFinder", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles setting focus to the location of the event.
|
||||||
|
*
|
||||||
|
* Note that this will override the focus mode on the camera to FOCUS_MODE_AUTO if available,
|
||||||
|
* even if this was previously something else (such as FOCUS_MODE_CONTINUOUS_*; see also
|
||||||
|
* {@link #startCamera()}. However, this makes sense - after the user has initiated any
|
||||||
|
* specific focus intent, we shouldn't be refocusing and overriding their request!
|
||||||
|
*/
|
||||||
|
public void handleFocus(MotionEvent event, Camera.Parameters params) {
|
||||||
|
List<String> supportedFocusModes = params.getSupportedFocusModes();
|
||||||
|
if (supportedFocusModes != null && supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
|
||||||
|
// Ensure focus areas are enabled. If max num focus areas is 0, then focus area is not
|
||||||
|
// supported, so we cannot do anything here.
|
||||||
|
if (params.getMaxNumFocusAreas() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel any previous focus actions.
|
||||||
|
_camera.cancelAutoFocus();
|
||||||
|
|
||||||
|
// Compute focus area rect.
|
||||||
|
Camera.Area focusAreaFromMotionEvent;
|
||||||
|
try {
|
||||||
|
focusAreaFromMotionEvent = RCTCameraUtils.computeFocusAreaFromMotionEvent(event, _surfaceTextureWidth, _surfaceTextureHeight);
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set focus mode to auto.
|
||||||
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
|
||||||
|
// Set focus area.
|
||||||
|
final ArrayList<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
|
||||||
|
focusAreas.add(focusAreaFromMotionEvent);
|
||||||
|
params.setFocusAreas(focusAreas);
|
||||||
|
|
||||||
|
// Also set metering area if enabled. If max num metering areas is 0, then metering area
|
||||||
|
// is not supported. We can usually safely omit this anyway, though.
|
||||||
|
if (params.getMaxNumMeteringAreas() > 0) {
|
||||||
|
params.setMeteringAreas(focusAreas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set parameters before starting auto-focus.
|
||||||
|
try{
|
||||||
|
_camera.setParameters(params);
|
||||||
|
}
|
||||||
|
catch(RuntimeException e ) {
|
||||||
|
Log.e("RCTCameraViewFinder", "setParameters failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start auto-focus now that focus area has been set. If successful, then can cancel
|
||||||
|
// it afterwards. Wrap in try-catch to avoid crashing on merely autoFocus fails.
|
||||||
|
try {
|
||||||
|
_camera.autoFocus(new Camera.AutoFocusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAutoFocus(boolean success, Camera camera) {
|
||||||
|
if (success) {
|
||||||
|
camera.cancelAutoFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Determine the space between the first two fingers */
|
||||||
|
private float getFingerSpacing(MotionEvent event) {
|
||||||
|
float x = event.getX(0) - event.getX(1);
|
||||||
|
float y = event.getY(0) - event.getY(1);
|
||||||
|
return (float) Math.sqrt(x * x + y * y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.common.MapBuilder;
|
||||||
|
import com.facebook.react.uimanager.*;
|
||||||
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class RCTCameraViewManager extends ViewGroupManager<RCTCameraView> {
|
||||||
|
private static final String REACT_CLASS = "RCTCamera";
|
||||||
|
|
||||||
|
public static final int COMMAND_STOP_PREVIEW = 1;
|
||||||
|
public static final int COMMAND_START_PREVIEW = 2;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return REACT_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RCTCameraView createViewInstance(ThemedReactContext context) {
|
||||||
|
return new RCTCameraView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Integer> getCommandsMap() {
|
||||||
|
return MapBuilder.of(
|
||||||
|
"stopPreview",
|
||||||
|
COMMAND_STOP_PREVIEW,
|
||||||
|
"startPreview",
|
||||||
|
COMMAND_START_PREVIEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveCommand(RCTCameraView view, int commandType, @Nullable ReadableArray args) {
|
||||||
|
if (view == null) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
switch (commandType) {
|
||||||
|
case COMMAND_STOP_PREVIEW: {
|
||||||
|
view.stopPreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case COMMAND_START_PREVIEW: {
|
||||||
|
view.startPreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Unsupported command %d received by %s.", commandType, getClass().getSimpleName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "aspect")
|
||||||
|
public void setAspect(RCTCameraView view, int aspect) {
|
||||||
|
view.setAspect(aspect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "captureMode")
|
||||||
|
public void setCaptureMode(RCTCameraView view, final int captureMode) {
|
||||||
|
// Note that this in practice only performs any additional setup necessary for each mode;
|
||||||
|
// the actual indication to capture a still or record a video when capture() is called is
|
||||||
|
// still ultimately decided upon by what it in the options sent to capture().
|
||||||
|
view.setCaptureMode(captureMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "captureTarget")
|
||||||
|
public void setCaptureTarget(RCTCameraView view, int captureTarget) {
|
||||||
|
// No reason to handle this props value here since it's passed again to the RCTCameraModule capture method
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "type")
|
||||||
|
public void setType(RCTCameraView view, int type) {
|
||||||
|
view.setCameraType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "captureQuality")
|
||||||
|
public void setCaptureQuality(RCTCameraView view, String captureQuality) {
|
||||||
|
view.setCaptureQuality(captureQuality);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "torchMode")
|
||||||
|
public void setTorchMode(RCTCameraView view, int torchMode) {
|
||||||
|
view.setTorchMode(torchMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "flashMode")
|
||||||
|
public void setFlashMode(RCTCameraView view, int flashMode) {
|
||||||
|
view.setFlashMode(flashMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "zoom")
|
||||||
|
public void setZoom(RCTCameraView view, int zoom) {
|
||||||
|
view.setZoom(zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "orientation")
|
||||||
|
public void setOrientation(RCTCameraView view, int orientation) {
|
||||||
|
view.setOrientation(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "captureAudio")
|
||||||
|
public void setCaptureAudio(RCTCameraView view, boolean captureAudio) {
|
||||||
|
// TODO - implement video mode
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "barcodeScannerEnabled")
|
||||||
|
public void setBarcodeScannerEnabled(RCTCameraView view, boolean barcodeScannerEnabled) {
|
||||||
|
view.setBarcodeScannerEnabled(barcodeScannerEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "barCodeTypes")
|
||||||
|
public void setBarCodeTypes(RCTCameraView view, ReadableArray barCodeTypes) {
|
||||||
|
if (barCodeTypes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> result = new ArrayList<String>(barCodeTypes.size());
|
||||||
|
for (int i = 0; i < barCodeTypes.size(); i++) {
|
||||||
|
result.add(barCodeTypes.getString(i));
|
||||||
|
}
|
||||||
|
view.setBarCodeTypes(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "clearWindowBackground")
|
||||||
|
public void setClearWindowBackground(RCTCameraView view, boolean clearWindowBackground) {
|
||||||
|
view.setClearWindowBackground(clearWindowBackground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* Created by rpopovici on 23/03/16.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
|
||||||
|
interface RCTSensorOrientationListener {
|
||||||
|
void orientationEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RCTSensorOrientationChecker {
|
||||||
|
|
||||||
|
int mOrientation = 0;
|
||||||
|
private SensorEventListener mSensorEventListener;
|
||||||
|
private SensorManager mSensorManager;
|
||||||
|
private RCTSensorOrientationListener mListener = null;
|
||||||
|
|
||||||
|
public RCTSensorOrientationChecker( ReactApplicationContext reactContext) {
|
||||||
|
mSensorEventListener = new Listener();
|
||||||
|
mSensorManager = (SensorManager) reactContext.getSystemService(Context.SENSOR_SERVICE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call on activity onResume()
|
||||||
|
*/
|
||||||
|
public void onResume() {
|
||||||
|
mSensorManager.registerListener(mSensorEventListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call on activity onPause()
|
||||||
|
*/
|
||||||
|
public void onPause() {
|
||||||
|
mSensorManager.unregisterListener(mSensorEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Listener implements SensorEventListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSensorChanged(SensorEvent event) {
|
||||||
|
float x = event.values[0];
|
||||||
|
float y = event.values[1];
|
||||||
|
|
||||||
|
if (x<5 && x>-5 && y > 5)
|
||||||
|
mOrientation = Surface.ROTATION_0; // portrait
|
||||||
|
else if (x<-5 && y<5 && y>-5)
|
||||||
|
mOrientation = Surface.ROTATION_270; // right
|
||||||
|
else if (x<5 && x>-5 && y<-5)
|
||||||
|
mOrientation = Surface.ROTATION_180; // upside down
|
||||||
|
else if (x>5 && y<5 && y>-5)
|
||||||
|
mOrientation = Surface.ROTATION_90; // left
|
||||||
|
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.orientationEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOrientation() {
|
||||||
|
return mOrientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerOrientationListener(RCTSensorOrientationListener listener) {
|
||||||
|
this.mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterOrientationListener() {
|
||||||
|
mListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
425
android/src/main/java/org/reactnative/camera/CameraModule.java
Normal file
425
android/src/main/java/org/reactnative/camera/CameraModule.java
Normal file
|
|
@ -0,0 +1,425 @@
|
||||||
|
package org.reactnative.camera;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.*;
|
||||||
|
import com.facebook.react.common.build.ReactBuildConfig;
|
||||||
|
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
||||||
|
import com.facebook.react.uimanager.UIBlock;
|
||||||
|
import com.facebook.react.uimanager.UIManagerModule;
|
||||||
|
import com.google.android.cameraview.AspectRatio;
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import org.reactnative.barcodedetector.BarcodeFormatUtils;
|
||||||
|
import org.reactnative.camera.utils.ScopedContext;
|
||||||
|
import org.reactnative.facedetector.RNFaceDetector;
|
||||||
|
import com.google.android.cameraview.Size;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
|
|
||||||
|
public class CameraModule extends ReactContextBaseJavaModule {
|
||||||
|
private static final String TAG = "CameraModule";
|
||||||
|
|
||||||
|
private ScopedContext mScopedContext;
|
||||||
|
static final int VIDEO_2160P = 0;
|
||||||
|
static final int VIDEO_1080P = 1;
|
||||||
|
static final int VIDEO_720P = 2;
|
||||||
|
static final int VIDEO_480P = 3;
|
||||||
|
static final int VIDEO_4x3 = 4;
|
||||||
|
|
||||||
|
static final int GOOGLE_VISION_BARCODE_MODE_NORMAL = 0;
|
||||||
|
static final int GOOGLE_VISION_BARCODE_MODE_ALTERNATE = 1;
|
||||||
|
static final int GOOGLE_VISION_BARCODE_MODE_INVERTED = 2;
|
||||||
|
|
||||||
|
public static final Map<String, Object> VALID_BARCODE_TYPES =
|
||||||
|
Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("aztec", BarcodeFormat.AZTEC.toString());
|
||||||
|
put("ean13", BarcodeFormat.EAN_13.toString());
|
||||||
|
put("ean8", BarcodeFormat.EAN_8.toString());
|
||||||
|
put("qr", BarcodeFormat.QR_CODE.toString());
|
||||||
|
put("pdf417", BarcodeFormat.PDF_417.toString());
|
||||||
|
put("upc_e", BarcodeFormat.UPC_E.toString());
|
||||||
|
put("datamatrix", BarcodeFormat.DATA_MATRIX.toString());
|
||||||
|
put("code39", BarcodeFormat.CODE_39.toString());
|
||||||
|
put("code93", BarcodeFormat.CODE_93.toString());
|
||||||
|
put("interleaved2of5", BarcodeFormat.ITF.toString());
|
||||||
|
put("codabar", BarcodeFormat.CODABAR.toString());
|
||||||
|
put("code128", BarcodeFormat.CODE_128.toString());
|
||||||
|
put("maxicode", BarcodeFormat.MAXICODE.toString());
|
||||||
|
put("rss14", BarcodeFormat.RSS_14.toString());
|
||||||
|
put("rssexpanded", BarcodeFormat.RSS_EXPANDED.toString());
|
||||||
|
put("upc_a", BarcodeFormat.UPC_A.toString());
|
||||||
|
put("upc_ean", BarcodeFormat.UPC_EAN_EXTENSION.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public CameraModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
mScopedContext = new ScopedContext(reactContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopedContext getScopedContext() {
|
||||||
|
return mScopedContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "RNCameraModule";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("Type", getTypeConstants());
|
||||||
|
put("FlashMode", getFlashModeConstants());
|
||||||
|
put("AutoFocus", getAutoFocusConstants());
|
||||||
|
put("WhiteBalance", getWhiteBalanceConstants());
|
||||||
|
put("VideoQuality", getVideoQualityConstants());
|
||||||
|
put("BarCodeType", getBarCodeConstants());
|
||||||
|
put("FaceDetection", Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("Mode", getFaceDetectionModeConstants());
|
||||||
|
put("Landmarks", getFaceDetectionLandmarksConstants());
|
||||||
|
put("Classifications", getFaceDetectionClassificationsConstants());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFaceDetectionModeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("fast", RNFaceDetector.FAST_MODE);
|
||||||
|
put("accurate", RNFaceDetector.ACCURATE_MODE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFaceDetectionClassificationsConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("all", RNFaceDetector.ALL_CLASSIFICATIONS);
|
||||||
|
put("none", RNFaceDetector.NO_CLASSIFICATIONS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFaceDetectionLandmarksConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("all", RNFaceDetector.ALL_LANDMARKS);
|
||||||
|
put("none", RNFaceDetector.NO_LANDMARKS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
put("GoogleVisionBarcodeDetection", Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("BarcodeType", BarcodeFormatUtils.REVERSE_FORMATS);
|
||||||
|
put("BarcodeMode", getGoogleVisionBarcodeModeConstants());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
put("Orientation", Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("auto", Constants.ORIENTATION_AUTO);
|
||||||
|
put("portrait", Constants.ORIENTATION_UP);
|
||||||
|
put("portraitUpsideDown", Constants.ORIENTATION_DOWN);
|
||||||
|
put("landscapeLeft", Constants.ORIENTATION_LEFT);
|
||||||
|
put("landscapeRight", Constants.ORIENTATION_RIGHT);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getTypeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("front", Constants.FACING_FRONT);
|
||||||
|
put("back", Constants.FACING_BACK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFlashModeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("off", Constants.FLASH_OFF);
|
||||||
|
put("on", Constants.FLASH_ON);
|
||||||
|
put("auto", Constants.FLASH_AUTO);
|
||||||
|
put("torch", Constants.FLASH_TORCH);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getAutoFocusConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("on", true);
|
||||||
|
put("off", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getWhiteBalanceConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("auto", Constants.WB_AUTO);
|
||||||
|
put("cloudy", Constants.WB_CLOUDY);
|
||||||
|
put("sunny", Constants.WB_SUNNY);
|
||||||
|
put("shadow", Constants.WB_SHADOW);
|
||||||
|
put("fluorescent", Constants.WB_FLUORESCENT);
|
||||||
|
put("incandescent", Constants.WB_INCANDESCENT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getVideoQualityConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("2160p", VIDEO_2160P);
|
||||||
|
put("1080p", VIDEO_1080P);
|
||||||
|
put("720p", VIDEO_720P);
|
||||||
|
put("480p", VIDEO_480P);
|
||||||
|
put("4:3", VIDEO_4x3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getGoogleVisionBarcodeModeConstants() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||||
|
{
|
||||||
|
put("NORMAL", GOOGLE_VISION_BARCODE_MODE_NORMAL);
|
||||||
|
put("ALTERNATE", GOOGLE_VISION_BARCODE_MODE_ALTERNATE);
|
||||||
|
put("INVERTED", GOOGLE_VISION_BARCODE_MODE_INVERTED);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getBarCodeConstants() {
|
||||||
|
return VALID_BARCODE_TYPES;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void pausePreview(final int viewTag) {
|
||||||
|
final ReactApplicationContext context = getReactApplicationContext();
|
||||||
|
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||||
|
uiManager.addUIBlock(new UIBlock() {
|
||||||
|
@Override
|
||||||
|
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||||
|
final RNCameraView cameraView;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||||
|
if (cameraView.isCameraOpened()) {
|
||||||
|
cameraView.pausePreview();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void resumePreview(final int viewTag) {
|
||||||
|
final ReactApplicationContext context = getReactApplicationContext();
|
||||||
|
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||||
|
uiManager.addUIBlock(new UIBlock() {
|
||||||
|
@Override
|
||||||
|
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||||
|
final RNCameraView cameraView;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||||
|
if (cameraView.isCameraOpened()) {
|
||||||
|
cameraView.resumePreview();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void takePicture(final ReadableMap options, final int viewTag, final Promise promise) {
|
||||||
|
final ReactApplicationContext context = getReactApplicationContext();
|
||||||
|
final File cacheDirectory = mScopedContext.getCacheDirectory();
|
||||||
|
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||||
|
uiManager.addUIBlock(new UIBlock() {
|
||||||
|
@Override
|
||||||
|
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||||
|
RNCameraView cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||||
|
try {
|
||||||
|
if (cameraView.isCameraOpened()) {
|
||||||
|
cameraView.takePicture(options, promise, cacheDirectory);
|
||||||
|
} else {
|
||||||
|
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
promise.reject("E_TAKE_PICTURE_FAILED", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void record(final ReadableMap options, final int viewTag, final Promise promise) {
|
||||||
|
final ReactApplicationContext context = getReactApplicationContext();
|
||||||
|
final File cacheDirectory = mScopedContext.getCacheDirectory();
|
||||||
|
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||||
|
|
||||||
|
uiManager.addUIBlock(new UIBlock() {
|
||||||
|
@Override
|
||||||
|
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||||
|
final RNCameraView cameraView;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||||
|
if (cameraView.isCameraOpened()) {
|
||||||
|
cameraView.record(options, promise, cacheDirectory);
|
||||||
|
} else {
|
||||||
|
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
promise.reject("E_CAPTURE_FAILED", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void stopRecording(final int viewTag) {
|
||||||
|
final ReactApplicationContext context = getReactApplicationContext();
|
||||||
|
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||||
|
uiManager.addUIBlock(new UIBlock() {
|
||||||
|
@Override
|
||||||
|
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||||
|
final RNCameraView cameraView;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||||
|
if (cameraView.isCameraOpened()) {
|
||||||
|
cameraView.stopRecording();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getSupportedRatios(final int viewTag, final Promise promise) {
|
||||||
|
final ReactApplicationContext context = getReactApplicationContext();
|
||||||
|
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||||
|
uiManager.addUIBlock(new UIBlock() {
|
||||||
|
@Override
|
||||||
|
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||||
|
final RNCameraView cameraView;
|
||||||
|
try {
|
||||||
|
cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||||
|
WritableArray result = Arguments.createArray();
|
||||||
|
if (cameraView.isCameraOpened()) {
|
||||||
|
Set<AspectRatio> ratios = cameraView.getSupportedAspectRatios();
|
||||||
|
for (AspectRatio ratio : ratios) {
|
||||||
|
result.pushString(ratio.toString());
|
||||||
|
}
|
||||||
|
promise.resolve(result);
|
||||||
|
} else {
|
||||||
|
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getCameraIds(final int viewTag, final Promise promise) {
|
||||||
|
final ReactApplicationContext context = getReactApplicationContext();
|
||||||
|
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||||
|
uiManager.addUIBlock(new UIBlock() {
|
||||||
|
@Override
|
||||||
|
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||||
|
final RNCameraView cameraView;
|
||||||
|
try {
|
||||||
|
cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||||
|
WritableArray result = Arguments.createArray();
|
||||||
|
List<Properties> ids = cameraView.getCameraIds();
|
||||||
|
for (Properties p : ids) {
|
||||||
|
WritableMap m = new WritableNativeMap();
|
||||||
|
m.putString("id", p.getProperty("id"));
|
||||||
|
m.putInt("type", Integer.valueOf(p.getProperty("type")));
|
||||||
|
result.pushMap(m);
|
||||||
|
}
|
||||||
|
promise.resolve(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
promise.reject("E_CAMERA_FAILED", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getAvailablePictureSizes(final String ratio, final int viewTag, final Promise promise) {
|
||||||
|
final ReactApplicationContext context = getReactApplicationContext();
|
||||||
|
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||||
|
uiManager.addUIBlock(new UIBlock() {
|
||||||
|
@Override
|
||||||
|
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||||
|
final RNCameraView cameraView;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||||
|
WritableArray result = Arguments.createArray();
|
||||||
|
if (cameraView.isCameraOpened()) {
|
||||||
|
SortedSet<Size> sizes = cameraView.getAvailablePictureSizes(AspectRatio.parse(ratio));
|
||||||
|
for (Size size : sizes) {
|
||||||
|
result.pushString(size.toString());
|
||||||
|
}
|
||||||
|
promise.resolve(result);
|
||||||
|
} else {
|
||||||
|
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
promise.reject("E_CAMERA_BAD_VIEWTAG", "getAvailablePictureSizesAsync: Expected a Camera component");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void checkIfRecordAudioPermissionsAreDefined(final Promise promise) {
|
||||||
|
try {
|
||||||
|
PackageInfo info = getCurrentActivity().getPackageManager().getPackageInfo(getReactApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||||
|
if (info.requestedPermissions != null) {
|
||||||
|
for (String p : info.requestedPermissions) {
|
||||||
|
if (p.equals(Manifest.permission.RECORD_AUDIO)) {
|
||||||
|
promise.resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
promise.resolve(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
package org.reactnative.camera;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.common.MapBuilder;
|
||||||
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
|
import com.facebook.react.uimanager.ViewGroupManager;
|
||||||
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
|
import com.google.android.cameraview.AspectRatio;
|
||||||
|
import com.google.android.cameraview.Size;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CameraViewManager extends ViewGroupManager<RNCameraView> {
|
||||||
|
public enum Events {
|
||||||
|
EVENT_CAMERA_READY("onCameraReady"),
|
||||||
|
EVENT_PREVIEW_COLOR("onPreviewColor"),
|
||||||
|
EVENT_ON_MOUNT_ERROR("onMountError"),
|
||||||
|
EVENT_ON_BAR_CODE_READ("onBarCodeRead"),
|
||||||
|
EVENT_ON_FACES_DETECTED("onFacesDetected"),
|
||||||
|
EVENT_ON_BARCODES_DETECTED("onGoogleVisionBarcodesDetected"),
|
||||||
|
EVENT_ON_FACE_DETECTION_ERROR("onFaceDetectionError"),
|
||||||
|
EVENT_ON_BARCODE_DETECTION_ERROR("onGoogleVisionBarcodeDetectionError"),
|
||||||
|
EVENT_ON_TEXT_RECOGNIZED("onTextRecognized"),
|
||||||
|
EVENT_ON_PICTURE_TAKEN("onPictureTaken"),
|
||||||
|
EVENT_ON_PICTURE_SAVED("onPictureSaved"),
|
||||||
|
EVENT_ON_RECORDING_START("onRecordingStart"),
|
||||||
|
EVENT_ON_RECORDING_END("onRecordingEnd");
|
||||||
|
|
||||||
|
private final String mName;
|
||||||
|
|
||||||
|
Events(final String name) {
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String REACT_CLASS = "RNCamera";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDropViewInstance(RNCameraView view) {
|
||||||
|
view.onHostDestroy();
|
||||||
|
super.onDropViewInstance(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return REACT_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RNCameraView createViewInstance(ThemedReactContext themedReactContext) {
|
||||||
|
return new RNCameraView(themedReactContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
||||||
|
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
|
||||||
|
for (Events event : Events.values()) {
|
||||||
|
builder.put(event.toString(), MapBuilder.of("registrationName", event.toString()));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "type")
|
||||||
|
public void setType(RNCameraView view, int type) {
|
||||||
|
view.setFacing(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "cameraId")
|
||||||
|
public void setCameraId(RNCameraView view, String id) {
|
||||||
|
view.setCameraId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "ratio")
|
||||||
|
public void setRatio(RNCameraView view, String ratio) {
|
||||||
|
view.setAspectRatio(AspectRatio.parse(ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "flashMode")
|
||||||
|
public void setFlashMode(RNCameraView view, int torchMode) {
|
||||||
|
view.setFlash(torchMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "exposure")
|
||||||
|
public void setExposureCompensation(RNCameraView view, float exposure){
|
||||||
|
view.setExposureCompensation(exposure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "autoFocus")
|
||||||
|
public void setAutoFocus(RNCameraView view, boolean autoFocus) {
|
||||||
|
view.setAutoFocus(autoFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "focusDepth")
|
||||||
|
public void setFocusDepth(RNCameraView view, float depth) {
|
||||||
|
view.setFocusDepth(depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "autoFocusPointOfInterest")
|
||||||
|
public void setAutoFocusPointOfInterest(RNCameraView view, ReadableMap coordinates) {
|
||||||
|
if(coordinates != null){
|
||||||
|
float x = (float) coordinates.getDouble("x");
|
||||||
|
float y = (float) coordinates.getDouble("y");
|
||||||
|
view.setAutoFocusPointOfInterest(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "zoom")
|
||||||
|
public void setZoom(RNCameraView view, float zoom) {
|
||||||
|
view.setZoom(zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "whiteBalance")
|
||||||
|
public void setWhiteBalance(RNCameraView view, int whiteBalance) {
|
||||||
|
view.setWhiteBalance(whiteBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "pictureSize")
|
||||||
|
public void setPictureSize(RNCameraView view, String size) {
|
||||||
|
view.setPictureSize(size.equals("None") ? null : Size.parse(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "playSoundOnCapture")
|
||||||
|
public void setPlaySoundOnCapture(RNCameraView view, boolean playSoundOnCapture) {
|
||||||
|
view.setPlaySoundOnCapture(playSoundOnCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "barCodeTypes")
|
||||||
|
public void setBarCodeTypes(RNCameraView view, ReadableArray barCodeTypes) {
|
||||||
|
if (barCodeTypes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> result = new ArrayList<>(barCodeTypes.size());
|
||||||
|
for (int i = 0; i < barCodeTypes.size(); i++) {
|
||||||
|
result.add(barCodeTypes.getString(i));
|
||||||
|
}
|
||||||
|
view.setBarCodeTypes(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "barCodeScannerEnabled")
|
||||||
|
public void setBarCodeScanning(RNCameraView view, boolean barCodeScannerEnabled) {
|
||||||
|
view.setShouldScanBarCodes(barCodeScannerEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "useCamera2Api")
|
||||||
|
public void setUseCamera2Api(RNCameraView view, boolean useCamera2Api) {
|
||||||
|
view.setUsingCamera2Api(useCamera2Api);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "faceDetectorEnabled")
|
||||||
|
public void setFaceDetecting(RNCameraView view, boolean faceDetectorEnabled) {
|
||||||
|
view.setShouldDetectFaces(faceDetectorEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "faceDetectionMode")
|
||||||
|
public void setFaceDetectionMode(RNCameraView view, int mode) {
|
||||||
|
view.setFaceDetectionMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "faceDetectionLandmarks")
|
||||||
|
public void setFaceDetectionLandmarks(RNCameraView view, int landmarks) {
|
||||||
|
view.setFaceDetectionLandmarks(landmarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "faceDetectionClassifications")
|
||||||
|
public void setFaceDetectionClassifications(RNCameraView view, int classifications) {
|
||||||
|
view.setFaceDetectionClassifications(classifications);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "trackingEnabled")
|
||||||
|
public void setTracking(RNCameraView view, boolean trackingEnabled) {
|
||||||
|
view.setTracking(trackingEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "googleVisionBarcodeDetectorEnabled")
|
||||||
|
public void setGoogleVisionBarcodeDetecting(RNCameraView view, boolean googleBarcodeDetectorEnabled) {
|
||||||
|
view.setShouldGoogleDetectBarcodes(googleBarcodeDetectorEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "googleVisionBarcodeType")
|
||||||
|
public void setGoogleVisionBarcodeType(RNCameraView view, int barcodeType) {
|
||||||
|
view.setGoogleVisionBarcodeType(barcodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "googleVisionBarcodeMode")
|
||||||
|
public void setGoogleVisionBarcodeMode(RNCameraView view, int barcodeMode) {
|
||||||
|
view.setGoogleVisionBarcodeMode(barcodeMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "textRecognizerEnabled")
|
||||||
|
public void setTextRecognizing(RNCameraView view, boolean textRecognizerEnabled) {
|
||||||
|
view.setShouldRecognizeText(textRecognizerEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**---limit scan area addition---**/
|
||||||
|
@ReactProp(name = "rectOfInterest")
|
||||||
|
public void setRectOfInterest(RNCameraView view, ReadableMap coordinates) {
|
||||||
|
if(coordinates != null){
|
||||||
|
float x = (float) coordinates.getDouble("x");
|
||||||
|
float y = (float) coordinates.getDouble("y");
|
||||||
|
float width = (float) coordinates.getDouble("width");
|
||||||
|
float height = (float) coordinates.getDouble("height");
|
||||||
|
view.setRectOfInterest(x, y, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "cameraViewDimensions")
|
||||||
|
public void setCameraViewDimensions(RNCameraView view, ReadableMap dimensions) {
|
||||||
|
if(dimensions != null){
|
||||||
|
int cameraViewWidth = (int) dimensions.getDouble("width");
|
||||||
|
int cameraViewHeight = (int) dimensions.getDouble("height");
|
||||||
|
view.setCameraViewDimensions(cameraViewWidth, cameraViewHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**---limit scan area addition---**/
|
||||||
|
}
|
||||||
48
android/src/main/java/org/reactnative/camera/Constants.java
Normal file
48
android/src/main/java/org/reactnative/camera/Constants.java
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.reactnative.camera;
|
||||||
|
|
||||||
|
import com.google.android.cameraview.AspectRatio;
|
||||||
|
|
||||||
|
public interface Constants {
|
||||||
|
|
||||||
|
AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3);
|
||||||
|
|
||||||
|
int FACING_BACK = 0;
|
||||||
|
int FACING_FRONT = 1;
|
||||||
|
|
||||||
|
int FLASH_OFF = 0;
|
||||||
|
int FLASH_ON = 1;
|
||||||
|
int FLASH_TORCH = 2;
|
||||||
|
int FLASH_AUTO = 3;
|
||||||
|
int FLASH_RED_EYE = 4;
|
||||||
|
|
||||||
|
int LANDSCAPE_90 = 90;
|
||||||
|
int LANDSCAPE_270 = 270;
|
||||||
|
|
||||||
|
int WB_AUTO = 0;
|
||||||
|
int WB_CLOUDY = 1;
|
||||||
|
int WB_SUNNY = 2;
|
||||||
|
int WB_SHADOW = 3;
|
||||||
|
int WB_FLUORESCENT = 4;
|
||||||
|
int WB_INCANDESCENT = 5;
|
||||||
|
|
||||||
|
int ORIENTATION_AUTO = 0;
|
||||||
|
int ORIENTATION_UP = 1;
|
||||||
|
int ORIENTATION_DOWN = 2;
|
||||||
|
int ORIENTATION_LEFT = 3;
|
||||||
|
int ORIENTATION_RIGHT = 4;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.reactnative.camera;
|
||||||
|
|
||||||
|
import com.facebook.react.ReactPackage;
|
||||||
|
import com.facebook.react.bridge.JavaScriptModule;
|
||||||
|
import com.facebook.react.bridge.NativeModule;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.uimanager.ViewManager;
|
||||||
|
import com.lwansbrough.RCTCamera.RCTCameraModule;
|
||||||
|
import com.lwansbrough.RCTCamera.RCTCameraViewManager;
|
||||||
|
|
||||||
|
import org.reactnative.facedetector.FaceDetectorModule;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by jgfidelis on 02/02/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RNCameraPackage implements ReactPackage {
|
||||||
|
@Override
|
||||||
|
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
|
||||||
|
return Arrays.<NativeModule>asList(
|
||||||
|
new RCTCameraModule(reactApplicationContext),
|
||||||
|
new CameraModule(reactApplicationContext),
|
||||||
|
new FaceDetectorModule(reactApplicationContext)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated in RN 0.47
|
||||||
|
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
|
||||||
|
return Arrays.<ViewManager>asList(
|
||||||
|
new RCTCameraViewManager(),
|
||||||
|
new CameraViewManager()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
614
android/src/main/java/org/reactnative/camera/RNCameraView.java
Normal file
614
android/src/main/java/org/reactnative/camera/RNCameraView.java
Normal file
|
|
@ -0,0 +1,614 @@
|
||||||
|
package org.reactnative.camera;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.YuvImage;
|
||||||
|
import android.media.CamcorderProfile;
|
||||||
|
import android.os.Build;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import com.facebook.react.bridge.*;
|
||||||
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
|
import com.google.android.cameraview.CameraView;
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.DecodeHintType;
|
||||||
|
import com.google.zxing.MultiFormatReader;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import org.reactnative.barcodedetector.RNBarcodeDetector;
|
||||||
|
import org.reactnative.camera.tasks.*;
|
||||||
|
import org.reactnative.camera.utils.RNFileUtils;
|
||||||
|
import org.reactnative.facedetector.RNFaceDetector;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
|
public class RNCameraView extends CameraView implements LifecycleEventListener, BarCodeScannerAsyncTaskDelegate, FaceDetectorAsyncTaskDelegate,
|
||||||
|
BarcodeDetectorAsyncTaskDelegate, TextRecognizerAsyncTaskDelegate, PictureSavedDelegate {
|
||||||
|
private ThemedReactContext mThemedReactContext;
|
||||||
|
private Queue<Promise> mPictureTakenPromises = new ConcurrentLinkedQueue<>();
|
||||||
|
private Map<Promise, ReadableMap> mPictureTakenOptions = new ConcurrentHashMap<>();
|
||||||
|
private Map<Promise, File> mPictureTakenDirectories = new ConcurrentHashMap<>();
|
||||||
|
private Promise mVideoRecordedPromise;
|
||||||
|
private List<String> mBarCodeTypes = null;
|
||||||
|
|
||||||
|
private boolean mIsPaused = false;
|
||||||
|
private boolean mIsNew = true;
|
||||||
|
private boolean invertImageData = false;
|
||||||
|
private Boolean mIsRecording = false;
|
||||||
|
private Boolean mIsRecordingInterrupted = false;
|
||||||
|
|
||||||
|
// Concurrency lock for scanners to avoid flooding the runtime
|
||||||
|
public volatile boolean barCodeScannerTaskLock = false;
|
||||||
|
public volatile boolean faceDetectorTaskLock = false;
|
||||||
|
public volatile boolean googleBarcodeDetectorTaskLock = false;
|
||||||
|
public volatile boolean textRecognizerTaskLock = false;
|
||||||
|
|
||||||
|
// Scanning-related properties
|
||||||
|
private MultiFormatReader mMultiFormatReader;
|
||||||
|
private RNFaceDetector mFaceDetector;
|
||||||
|
private RNBarcodeDetector mGoogleBarcodeDetector;
|
||||||
|
private boolean mShouldDetectFaces = false;
|
||||||
|
private boolean mShouldGoogleDetectBarcodes = false;
|
||||||
|
private boolean mShouldScanBarCodes = false;
|
||||||
|
private boolean mShouldRecognizeText = false;
|
||||||
|
private int mFaceDetectorMode = RNFaceDetector.FAST_MODE;
|
||||||
|
private int mFaceDetectionLandmarks = RNFaceDetector.NO_LANDMARKS;
|
||||||
|
private int mFaceDetectionClassifications = RNFaceDetector.NO_CLASSIFICATIONS;
|
||||||
|
private int mGoogleVisionBarCodeType = RNBarcodeDetector.ALL_FORMATS;
|
||||||
|
private int mGoogleVisionBarCodeMode = RNBarcodeDetector.NORMAL_MODE;
|
||||||
|
private boolean mTrackingEnabled = true;
|
||||||
|
private int mPaddingX;
|
||||||
|
private int mPaddingY;
|
||||||
|
|
||||||
|
// Limit Android Scan Area
|
||||||
|
private boolean mLimitScanArea = false;
|
||||||
|
private float mScanAreaX = 0.0f;
|
||||||
|
private float mScanAreaY = 0.0f;
|
||||||
|
private float mScanAreaWidth = 0.0f;
|
||||||
|
private float mScanAreaHeight = 0.0f;
|
||||||
|
private int mCameraViewWidth = 0;
|
||||||
|
private int mCameraViewHeight = 0;
|
||||||
|
|
||||||
|
public RNCameraView(ThemedReactContext themedReactContext) {
|
||||||
|
super(themedReactContext, true);
|
||||||
|
mThemedReactContext = themedReactContext;
|
||||||
|
themedReactContext.addLifecycleEventListener(this);
|
||||||
|
|
||||||
|
addCallback(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onCameraOpened(CameraView cameraView) {
|
||||||
|
RNCameraViewHelper.emitCameraReadyEvent(cameraView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMountError(CameraView cameraView) {
|
||||||
|
RNCameraViewHelper.emitMountErrorEvent(cameraView, "Camera view threw an error - component could not be rendered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureTaken(CameraView cameraView, final byte[] data, int deviceOrientation) {
|
||||||
|
Promise promise = mPictureTakenPromises.poll();
|
||||||
|
ReadableMap options = mPictureTakenOptions.remove(promise);
|
||||||
|
if (options.hasKey("fastMode") && options.getBoolean("fastMode")) {
|
||||||
|
promise.resolve(null);
|
||||||
|
}
|
||||||
|
final File cacheDirectory = mPictureTakenDirectories.remove(promise);
|
||||||
|
if(Build.VERSION.SDK_INT >= 11/*HONEYCOMB*/) {
|
||||||
|
new ResolveTakenPictureAsyncTask(data, promise, options, cacheDirectory, deviceOrientation, RNCameraView.this)
|
||||||
|
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
} else {
|
||||||
|
new ResolveTakenPictureAsyncTask(data, promise, options, cacheDirectory, deviceOrientation, RNCameraView.this)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
RNCameraViewHelper.emitPictureTakenEvent(cameraView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRecordingStart(CameraView cameraView, String path, int videoOrientation, int deviceOrientation) {
|
||||||
|
WritableMap result = Arguments.createMap();
|
||||||
|
result.putInt("videoOrientation", videoOrientation);
|
||||||
|
result.putInt("deviceOrientation", deviceOrientation);
|
||||||
|
result.putString("uri", RNFileUtils.uriFromFile(new File(path)).toString());
|
||||||
|
RNCameraViewHelper.emitRecordingStartEvent(cameraView, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRecordingEnd(CameraView cameraView) {
|
||||||
|
RNCameraViewHelper.emitRecordingEndEvent(cameraView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoRecorded(CameraView cameraView, String path, int videoOrientation, int deviceOrientation) {
|
||||||
|
if (mVideoRecordedPromise != null) {
|
||||||
|
if (path != null) {
|
||||||
|
WritableMap result = Arguments.createMap();
|
||||||
|
result.putBoolean("isRecordingInterrupted", mIsRecordingInterrupted);
|
||||||
|
result.putInt("videoOrientation", videoOrientation);
|
||||||
|
result.putInt("deviceOrientation", deviceOrientation);
|
||||||
|
result.putString("uri", RNFileUtils.uriFromFile(new File(path)).toString());
|
||||||
|
mVideoRecordedPromise.resolve(result);
|
||||||
|
} else {
|
||||||
|
mVideoRecordedPromise.reject("E_RECORDING", "Couldn't stop recording - there is none in progress");
|
||||||
|
}
|
||||||
|
mIsRecording = false;
|
||||||
|
mIsRecordingInterrupted = false;
|
||||||
|
mVideoRecordedPromise = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFramePreview(CameraView cameraView, byte[] data, int width, int height, int rotation) {
|
||||||
|
int correctRotation = RNCameraViewHelper.getCorrectCameraRotation(rotation, getFacing(), getCameraOrientation());
|
||||||
|
boolean willCallBarCodeTask = mShouldScanBarCodes && !barCodeScannerTaskLock && cameraView instanceof BarCodeScannerAsyncTaskDelegate;
|
||||||
|
boolean willCallFaceTask = mShouldDetectFaces && !faceDetectorTaskLock && cameraView instanceof FaceDetectorAsyncTaskDelegate;
|
||||||
|
boolean willCallGoogleBarcodeTask = mShouldGoogleDetectBarcodes && !googleBarcodeDetectorTaskLock && cameraView instanceof BarcodeDetectorAsyncTaskDelegate;
|
||||||
|
boolean willCallTextTask = mShouldRecognizeText && !textRecognizerTaskLock && cameraView instanceof TextRecognizerAsyncTaskDelegate;
|
||||||
|
boolean willCallPreviewColor = true;
|
||||||
|
if (!willCallBarCodeTask && !willCallFaceTask && !willCallGoogleBarcodeTask && !willCallTextTask && !willCallPreviewColor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length < (1.5 * width * height)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(willCallPreviewColor){
|
||||||
|
|
||||||
|
YuvImage yuvImage = new YuvImage(data, 17, width, height, null);
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 80, byteArrayOutputStream);
|
||||||
|
byte[] byteArray = byteArrayOutputStream.toByteArray();
|
||||||
|
Bitmap decodeByteArray = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
|
||||||
|
int w = decodeByteArray.getWidth() / 2;
|
||||||
|
int h = decodeByteArray.getHeight() / 2;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
int r = 0;
|
||||||
|
int g = 0;
|
||||||
|
int b = 0;
|
||||||
|
for(int i = w -1; i <= w + 1; i++){
|
||||||
|
for(int j = h - 1; j <= h +1; j++){
|
||||||
|
count ++;
|
||||||
|
int c = decodeByteArray.getPixel(i, j);
|
||||||
|
r += (c >> 16) & 0xff;
|
||||||
|
g += (c >> 8) & 0xff;
|
||||||
|
b += c & 0xff;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r/count;
|
||||||
|
g = g/count;
|
||||||
|
b = b/count;
|
||||||
|
RNCameraViewHelper.emitPreviewColorEvent(cameraView, (r<<16) + (g << 8) + b, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willCallBarCodeTask) {
|
||||||
|
barCodeScannerTaskLock = true;
|
||||||
|
BarCodeScannerAsyncTaskDelegate delegate = (BarCodeScannerAsyncTaskDelegate) cameraView;
|
||||||
|
new BarCodeScannerAsyncTask(delegate, mMultiFormatReader, data, width, height, mLimitScanArea, mScanAreaX, mScanAreaY, mScanAreaWidth, mScanAreaHeight, mCameraViewWidth, mCameraViewHeight, getAspectRatio().toFloat()).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willCallFaceTask) {
|
||||||
|
faceDetectorTaskLock = true;
|
||||||
|
FaceDetectorAsyncTaskDelegate delegate = (FaceDetectorAsyncTaskDelegate) cameraView;
|
||||||
|
new FaceDetectorAsyncTask(delegate, mFaceDetector, data, width, height, correctRotation, getResources().getDisplayMetrics().density, getFacing(), getWidth(), getHeight(), mPaddingX, mPaddingY).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willCallGoogleBarcodeTask) {
|
||||||
|
googleBarcodeDetectorTaskLock = true;
|
||||||
|
if (mGoogleVisionBarCodeMode == RNBarcodeDetector.NORMAL_MODE) {
|
||||||
|
invertImageData = false;
|
||||||
|
} else if (mGoogleVisionBarCodeMode == RNBarcodeDetector.ALTERNATE_MODE) {
|
||||||
|
invertImageData = !invertImageData;
|
||||||
|
} else if (mGoogleVisionBarCodeMode == RNBarcodeDetector.INVERTED_MODE) {
|
||||||
|
invertImageData = true;
|
||||||
|
}
|
||||||
|
if (invertImageData) {
|
||||||
|
for (int y = 0; y < data.length; y++) {
|
||||||
|
data[y] = (byte) ~data[y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BarcodeDetectorAsyncTaskDelegate delegate = (BarcodeDetectorAsyncTaskDelegate) cameraView;
|
||||||
|
new BarcodeDetectorAsyncTask(delegate, mGoogleBarcodeDetector, data, width, height, correctRotation, getResources().getDisplayMetrics().density, getFacing(), getWidth(), getHeight(), mPaddingX, mPaddingY).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willCallTextTask) {
|
||||||
|
textRecognizerTaskLock = true;
|
||||||
|
TextRecognizerAsyncTaskDelegate delegate = (TextRecognizerAsyncTaskDelegate) cameraView;
|
||||||
|
new TextRecognizerAsyncTask(delegate, mThemedReactContext, data, width, height, correctRotation, getResources().getDisplayMetrics().density, getFacing(), getWidth(), getHeight(), mPaddingX, mPaddingY).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
|
View preview = getView();
|
||||||
|
if (null == preview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float width = right - left;
|
||||||
|
float height = bottom - top;
|
||||||
|
float ratio = getAspectRatio().toFloat();
|
||||||
|
int orientation = getResources().getConfiguration().orientation;
|
||||||
|
int correctHeight;
|
||||||
|
int correctWidth;
|
||||||
|
this.setBackgroundColor(Color.BLACK);
|
||||||
|
if (orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
if (ratio * height < width) {
|
||||||
|
correctHeight = (int) (width / ratio);
|
||||||
|
correctWidth = (int) width;
|
||||||
|
} else {
|
||||||
|
correctWidth = (int) (height * ratio);
|
||||||
|
correctHeight = (int) height;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ratio * width > height) {
|
||||||
|
correctHeight = (int) (width * ratio);
|
||||||
|
correctWidth = (int) width;
|
||||||
|
} else {
|
||||||
|
correctWidth = (int) (height / ratio);
|
||||||
|
correctHeight = (int) height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int paddingX = (int) ((width - correctWidth) / 2);
|
||||||
|
int paddingY = (int) ((height - correctHeight) / 2);
|
||||||
|
mPaddingX = paddingX;
|
||||||
|
mPaddingY = paddingY;
|
||||||
|
preview.layout(paddingX, paddingY, correctWidth + paddingX, correctHeight + paddingY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("all")
|
||||||
|
@Override
|
||||||
|
public void requestLayout() {
|
||||||
|
// React handles this for us, so we don't need to call super.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarCodeTypes(List<String> barCodeTypes) {
|
||||||
|
mBarCodeTypes = barCodeTypes;
|
||||||
|
initBarcodeReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void takePicture(final ReadableMap options, final Promise promise, final File cacheDirectory) {
|
||||||
|
mBgHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mPictureTakenPromises.add(promise);
|
||||||
|
mPictureTakenOptions.put(promise, options);
|
||||||
|
mPictureTakenDirectories.put(promise, cacheDirectory);
|
||||||
|
|
||||||
|
try {
|
||||||
|
RNCameraView.super.takePicture(options);
|
||||||
|
} catch (Exception e) {
|
||||||
|
mPictureTakenPromises.remove(promise);
|
||||||
|
mPictureTakenOptions.remove(promise);
|
||||||
|
mPictureTakenDirectories.remove(promise);
|
||||||
|
|
||||||
|
promise.reject("E_TAKE_PICTURE_FAILED", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureSaved(WritableMap response) {
|
||||||
|
RNCameraViewHelper.emitPictureSavedEvent(this, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void record(final ReadableMap options, final Promise promise, final File cacheDirectory) {
|
||||||
|
mBgHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
String path = options.hasKey("path") ? options.getString("path") : RNFileUtils.getOutputFilePath(cacheDirectory, ".mp4");
|
||||||
|
int maxDuration = options.hasKey("maxDuration") ? options.getInt("maxDuration") : -1;
|
||||||
|
int maxFileSize = options.hasKey("maxFileSize") ? options.getInt("maxFileSize") : -1;
|
||||||
|
|
||||||
|
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||||
|
if (options.hasKey("quality")) {
|
||||||
|
profile = RNCameraViewHelper.getCamcorderProfile(options.getInt("quality"));
|
||||||
|
}
|
||||||
|
if (options.hasKey("videoBitrate")) {
|
||||||
|
profile.videoBitRate = options.getInt("videoBitrate");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean recordAudio = true;
|
||||||
|
if (options.hasKey("mute")) {
|
||||||
|
recordAudio = !options.getBoolean("mute");
|
||||||
|
}
|
||||||
|
|
||||||
|
int orientation = Constants.ORIENTATION_AUTO;
|
||||||
|
if (options.hasKey("orientation")) {
|
||||||
|
orientation = options.getInt("orientation");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RNCameraView.super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile, orientation)) {
|
||||||
|
mIsRecording = true;
|
||||||
|
mVideoRecordedPromise = promise;
|
||||||
|
} else {
|
||||||
|
promise.reject("E_RECORDING_FAILED", "Starting video recording failed. Another recording might be in progress.");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
promise.reject("E_RECORDING_FAILED", "Starting video recording failed - could not create video file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the barcode decoder.
|
||||||
|
* Supports all iOS codes except [code138, code39mod43, itf14]
|
||||||
|
* Additionally supports [codabar, code128, maxicode, rss14, rssexpanded, upc_a, upc_ean]
|
||||||
|
*/
|
||||||
|
private void initBarcodeReader() {
|
||||||
|
mMultiFormatReader = new MultiFormatReader();
|
||||||
|
EnumMap<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
|
||||||
|
EnumSet<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
|
||||||
|
|
||||||
|
if (mBarCodeTypes != null) {
|
||||||
|
for (String code : mBarCodeTypes) {
|
||||||
|
String formatString = (String) CameraModule.VALID_BARCODE_TYPES.get(code);
|
||||||
|
if (formatString != null) {
|
||||||
|
decodeFormats.add(BarcodeFormat.valueOf(formatString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
|
||||||
|
mMultiFormatReader.setHints(hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShouldScanBarCodes(boolean shouldScanBarCodes) {
|
||||||
|
if (shouldScanBarCodes && mMultiFormatReader == null) {
|
||||||
|
initBarcodeReader();
|
||||||
|
}
|
||||||
|
this.mShouldScanBarCodes = shouldScanBarCodes;
|
||||||
|
setScanning(mShouldDetectFaces || mShouldGoogleDetectBarcodes || mShouldScanBarCodes || mShouldRecognizeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBarCodeRead(Result barCode, int width, int height) {
|
||||||
|
String barCodeType = barCode.getBarcodeFormat().toString();
|
||||||
|
if (!mShouldScanBarCodes || !mBarCodeTypes.contains(barCodeType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNCameraViewHelper.emitBarCodeReadEvent(this, barCode, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBarCodeScanningTaskCompleted() {
|
||||||
|
barCodeScannerTaskLock = false;
|
||||||
|
if(mMultiFormatReader != null) {
|
||||||
|
mMultiFormatReader.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit Scan Area
|
||||||
|
public void setRectOfInterest(float x, float y, float width, float height) {
|
||||||
|
this.mLimitScanArea = true;
|
||||||
|
this.mScanAreaX = x;
|
||||||
|
this.mScanAreaY = y;
|
||||||
|
this.mScanAreaWidth = width;
|
||||||
|
this.mScanAreaHeight = height;
|
||||||
|
}
|
||||||
|
public void setCameraViewDimensions(int width, int height) {
|
||||||
|
this.mCameraViewWidth = width;
|
||||||
|
this.mCameraViewHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial setup of the face detector
|
||||||
|
*/
|
||||||
|
private void setupFaceDetector() {
|
||||||
|
mFaceDetector = new RNFaceDetector(mThemedReactContext);
|
||||||
|
mFaceDetector.setMode(mFaceDetectorMode);
|
||||||
|
mFaceDetector.setLandmarkType(mFaceDetectionLandmarks);
|
||||||
|
mFaceDetector.setClassificationType(mFaceDetectionClassifications);
|
||||||
|
mFaceDetector.setTracking(mTrackingEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaceDetectionLandmarks(int landmarks) {
|
||||||
|
mFaceDetectionLandmarks = landmarks;
|
||||||
|
if (mFaceDetector != null) {
|
||||||
|
mFaceDetector.setLandmarkType(landmarks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaceDetectionClassifications(int classifications) {
|
||||||
|
mFaceDetectionClassifications = classifications;
|
||||||
|
if (mFaceDetector != null) {
|
||||||
|
mFaceDetector.setClassificationType(classifications);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaceDetectionMode(int mode) {
|
||||||
|
mFaceDetectorMode = mode;
|
||||||
|
if (mFaceDetector != null) {
|
||||||
|
mFaceDetector.setMode(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTracking(boolean trackingEnabled) {
|
||||||
|
mTrackingEnabled = trackingEnabled;
|
||||||
|
if (mFaceDetector != null) {
|
||||||
|
mFaceDetector.setTracking(trackingEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShouldDetectFaces(boolean shouldDetectFaces) {
|
||||||
|
if (shouldDetectFaces && mFaceDetector == null) {
|
||||||
|
setupFaceDetector();
|
||||||
|
}
|
||||||
|
this.mShouldDetectFaces = shouldDetectFaces;
|
||||||
|
setScanning(mShouldDetectFaces || mShouldGoogleDetectBarcodes || mShouldScanBarCodes || mShouldRecognizeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFacesDetected(WritableArray data) {
|
||||||
|
if (!mShouldDetectFaces) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNCameraViewHelper.emitFacesDetectedEvent(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFaceDetectionError(RNFaceDetector faceDetector) {
|
||||||
|
if (!mShouldDetectFaces) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNCameraViewHelper.emitFaceDetectionErrorEvent(this, faceDetector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFaceDetectingTaskCompleted() {
|
||||||
|
faceDetectorTaskLock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial setup of the barcode detector
|
||||||
|
*/
|
||||||
|
private void setupBarcodeDetector() {
|
||||||
|
mGoogleBarcodeDetector = new RNBarcodeDetector(mThemedReactContext);
|
||||||
|
mGoogleBarcodeDetector.setBarcodeType(mGoogleVisionBarCodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShouldGoogleDetectBarcodes(boolean shouldDetectBarcodes) {
|
||||||
|
if (shouldDetectBarcodes && mGoogleBarcodeDetector == null) {
|
||||||
|
setupBarcodeDetector();
|
||||||
|
}
|
||||||
|
this.mShouldGoogleDetectBarcodes = shouldDetectBarcodes;
|
||||||
|
setScanning(mShouldDetectFaces || mShouldGoogleDetectBarcodes || mShouldScanBarCodes || mShouldRecognizeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGoogleVisionBarcodeType(int barcodeType) {
|
||||||
|
mGoogleVisionBarCodeType = barcodeType;
|
||||||
|
if (mGoogleBarcodeDetector != null) {
|
||||||
|
mGoogleBarcodeDetector.setBarcodeType(barcodeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGoogleVisionBarcodeMode(int barcodeMode) {
|
||||||
|
mGoogleVisionBarCodeMode = barcodeMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBarcodesDetected(WritableArray barcodesDetected) {
|
||||||
|
if (!mShouldGoogleDetectBarcodes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RNCameraViewHelper.emitBarcodesDetectedEvent(this, barcodesDetected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBarcodeDetectionError(RNBarcodeDetector barcodeDetector) {
|
||||||
|
if (!mShouldGoogleDetectBarcodes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNCameraViewHelper.emitBarcodeDetectionErrorEvent(this, barcodeDetector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBarcodeDetectingTaskCompleted() {
|
||||||
|
googleBarcodeDetectorTaskLock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Text recognition
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void setShouldRecognizeText(boolean shouldRecognizeText) {
|
||||||
|
this.mShouldRecognizeText = shouldRecognizeText;
|
||||||
|
setScanning(mShouldDetectFaces || mShouldGoogleDetectBarcodes || mShouldScanBarCodes || mShouldRecognizeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTextRecognized(WritableArray serializedData) {
|
||||||
|
if (!mShouldRecognizeText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNCameraViewHelper.emitTextRecognizedEvent(this, serializedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextRecognizerTaskCompleted() {
|
||||||
|
textRecognizerTaskLock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* End Text Recognition */
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHostResume() {
|
||||||
|
if (hasCameraPermissions()) {
|
||||||
|
mBgHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if ((mIsPaused && !isCameraOpened()) || mIsNew) {
|
||||||
|
mIsPaused = false;
|
||||||
|
mIsNew = false;
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
RNCameraViewHelper.emitMountErrorEvent(this, "Camera permissions not granted - component could not be rendered.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHostPause() {
|
||||||
|
if (mIsRecording) {
|
||||||
|
mIsRecordingInterrupted = true;
|
||||||
|
}
|
||||||
|
if (!mIsPaused && isCameraOpened()) {
|
||||||
|
mIsPaused = true;
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHostDestroy() {
|
||||||
|
if (mFaceDetector != null) {
|
||||||
|
mFaceDetector.release();
|
||||||
|
}
|
||||||
|
if (mGoogleBarcodeDetector != null) {
|
||||||
|
mGoogleBarcodeDetector.release();
|
||||||
|
}
|
||||||
|
mMultiFormatReader = null;
|
||||||
|
mThemedReactContext.removeLifecycleEventListener(this);
|
||||||
|
|
||||||
|
// camera release can be quite expensive. Run in on bg handler
|
||||||
|
// and cleanup last once everything has finished
|
||||||
|
mBgHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
stop();
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCameraPermissions() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
int result = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA);
|
||||||
|
return result == PackageManager.PERMISSION_GRANTED;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,477 @@
|
||||||
|
package org.reactnative.camera;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.media.CamcorderProfile;
|
||||||
|
import android.os.Build;
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReactContext;
|
||||||
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.uimanager.UIManagerModule;
|
||||||
|
import com.google.android.cameraview.CameraView;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import org.reactnative.camera.events.*;
|
||||||
|
import org.reactnative.barcodedetector.RNBarcodeDetector;
|
||||||
|
import org.reactnative.facedetector.RNFaceDetector;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class RNCameraViewHelper {
|
||||||
|
|
||||||
|
public static final String[][] exifTags = new String[][]{
|
||||||
|
{"string", ExifInterface.TAG_ARTIST},
|
||||||
|
{"int", ExifInterface.TAG_BITS_PER_SAMPLE},
|
||||||
|
{"int", ExifInterface.TAG_COMPRESSION},
|
||||||
|
{"string", ExifInterface.TAG_COPYRIGHT},
|
||||||
|
{"string", ExifInterface.TAG_DATETIME},
|
||||||
|
{"string", ExifInterface.TAG_IMAGE_DESCRIPTION},
|
||||||
|
{"int", ExifInterface.TAG_IMAGE_LENGTH},
|
||||||
|
{"int", ExifInterface.TAG_IMAGE_WIDTH},
|
||||||
|
{"int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT},
|
||||||
|
{"int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH},
|
||||||
|
{"string", ExifInterface.TAG_MAKE},
|
||||||
|
{"string", ExifInterface.TAG_MODEL},
|
||||||
|
{"int", ExifInterface.TAG_ORIENTATION},
|
||||||
|
{"int", ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION},
|
||||||
|
{"int", ExifInterface.TAG_PLANAR_CONFIGURATION},
|
||||||
|
{"double", ExifInterface.TAG_PRIMARY_CHROMATICITIES},
|
||||||
|
{"double", ExifInterface.TAG_REFERENCE_BLACK_WHITE},
|
||||||
|
{"int", ExifInterface.TAG_RESOLUTION_UNIT},
|
||||||
|
{"int", ExifInterface.TAG_ROWS_PER_STRIP},
|
||||||
|
{"int", ExifInterface.TAG_SAMPLES_PER_PIXEL},
|
||||||
|
{"string", ExifInterface.TAG_SOFTWARE},
|
||||||
|
{"int", ExifInterface.TAG_STRIP_BYTE_COUNTS},
|
||||||
|
{"int", ExifInterface.TAG_STRIP_OFFSETS},
|
||||||
|
{"int", ExifInterface.TAG_TRANSFER_FUNCTION},
|
||||||
|
{"double", ExifInterface.TAG_WHITE_POINT},
|
||||||
|
{"double", ExifInterface.TAG_X_RESOLUTION},
|
||||||
|
{"double", ExifInterface.TAG_Y_CB_CR_COEFFICIENTS},
|
||||||
|
{"int", ExifInterface.TAG_Y_CB_CR_POSITIONING},
|
||||||
|
{"int", ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING},
|
||||||
|
{"double", ExifInterface.TAG_Y_RESOLUTION},
|
||||||
|
{"double", ExifInterface.TAG_APERTURE_VALUE},
|
||||||
|
{"double", ExifInterface.TAG_BRIGHTNESS_VALUE},
|
||||||
|
{"string", ExifInterface.TAG_CFA_PATTERN},
|
||||||
|
{"int", ExifInterface.TAG_COLOR_SPACE},
|
||||||
|
{"string", ExifInterface.TAG_COMPONENTS_CONFIGURATION},
|
||||||
|
{"double", ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL},
|
||||||
|
{"int", ExifInterface.TAG_CONTRAST},
|
||||||
|
{"int", ExifInterface.TAG_CUSTOM_RENDERED},
|
||||||
|
{"string", ExifInterface.TAG_DATETIME_DIGITIZED},
|
||||||
|
{"string", ExifInterface.TAG_DATETIME_ORIGINAL},
|
||||||
|
{"string", ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION},
|
||||||
|
{"double", ExifInterface.TAG_DIGITAL_ZOOM_RATIO},
|
||||||
|
{"string", ExifInterface.TAG_EXIF_VERSION},
|
||||||
|
{"double", ExifInterface.TAG_EXPOSURE_BIAS_VALUE},
|
||||||
|
{"double", ExifInterface.TAG_EXPOSURE_INDEX},
|
||||||
|
{"int", ExifInterface.TAG_EXPOSURE_MODE},
|
||||||
|
{"int", ExifInterface.TAG_EXPOSURE_PROGRAM},
|
||||||
|
{"double", ExifInterface.TAG_EXPOSURE_TIME},
|
||||||
|
{"double", ExifInterface.TAG_F_NUMBER},
|
||||||
|
{"string", ExifInterface.TAG_FILE_SOURCE},
|
||||||
|
{"int", ExifInterface.TAG_FLASH},
|
||||||
|
{"double", ExifInterface.TAG_FLASH_ENERGY},
|
||||||
|
{"string", ExifInterface.TAG_FLASHPIX_VERSION},
|
||||||
|
{"double", ExifInterface.TAG_FOCAL_LENGTH},
|
||||||
|
{"int", ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM},
|
||||||
|
{"int", ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT},
|
||||||
|
{"double", ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION},
|
||||||
|
{"double", ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION},
|
||||||
|
{"int", ExifInterface.TAG_GAIN_CONTROL},
|
||||||
|
{"int", ExifInterface.TAG_ISO_SPEED_RATINGS},
|
||||||
|
{"string", ExifInterface.TAG_IMAGE_UNIQUE_ID},
|
||||||
|
{"int", ExifInterface.TAG_LIGHT_SOURCE},
|
||||||
|
{"string", ExifInterface.TAG_MAKER_NOTE},
|
||||||
|
{"double", ExifInterface.TAG_MAX_APERTURE_VALUE},
|
||||||
|
{"int", ExifInterface.TAG_METERING_MODE},
|
||||||
|
{"int", ExifInterface.TAG_NEW_SUBFILE_TYPE},
|
||||||
|
{"string", ExifInterface.TAG_OECF},
|
||||||
|
{"int", ExifInterface.TAG_PIXEL_X_DIMENSION},
|
||||||
|
{"int", ExifInterface.TAG_PIXEL_Y_DIMENSION},
|
||||||
|
{"string", ExifInterface.TAG_RELATED_SOUND_FILE},
|
||||||
|
{"int", ExifInterface.TAG_SATURATION},
|
||||||
|
{"int", ExifInterface.TAG_SCENE_CAPTURE_TYPE},
|
||||||
|
{"string", ExifInterface.TAG_SCENE_TYPE},
|
||||||
|
{"int", ExifInterface.TAG_SENSING_METHOD},
|
||||||
|
{"int", ExifInterface.TAG_SHARPNESS},
|
||||||
|
{"double", ExifInterface.TAG_SHUTTER_SPEED_VALUE},
|
||||||
|
{"string", ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE},
|
||||||
|
{"string", ExifInterface.TAG_SPECTRAL_SENSITIVITY},
|
||||||
|
{"int", ExifInterface.TAG_SUBFILE_TYPE},
|
||||||
|
{"string", ExifInterface.TAG_SUBSEC_TIME},
|
||||||
|
{"string", ExifInterface.TAG_SUBSEC_TIME_DIGITIZED},
|
||||||
|
{"string", ExifInterface.TAG_SUBSEC_TIME_ORIGINAL},
|
||||||
|
{"int", ExifInterface.TAG_SUBJECT_AREA},
|
||||||
|
{"double", ExifInterface.TAG_SUBJECT_DISTANCE},
|
||||||
|
{"int", ExifInterface.TAG_SUBJECT_DISTANCE_RANGE},
|
||||||
|
{"int", ExifInterface.TAG_SUBJECT_LOCATION},
|
||||||
|
{"string", ExifInterface.TAG_USER_COMMENT},
|
||||||
|
{"int", ExifInterface.TAG_WHITE_BALANCE},
|
||||||
|
{"int", ExifInterface.TAG_GPS_ALTITUDE_REF},
|
||||||
|
{"string", ExifInterface.TAG_GPS_AREA_INFORMATION},
|
||||||
|
{"double", ExifInterface.TAG_GPS_DOP},
|
||||||
|
{"string", ExifInterface.TAG_GPS_DATESTAMP},
|
||||||
|
{"double", ExifInterface.TAG_GPS_DEST_BEARING},
|
||||||
|
{"string", ExifInterface.TAG_GPS_DEST_BEARING_REF},
|
||||||
|
{"double", ExifInterface.TAG_GPS_DEST_DISTANCE},
|
||||||
|
{"string", ExifInterface.TAG_GPS_DEST_DISTANCE_REF},
|
||||||
|
{"double", ExifInterface.TAG_GPS_DEST_LATITUDE},
|
||||||
|
{"string", ExifInterface.TAG_GPS_DEST_LATITUDE_REF},
|
||||||
|
{"double", ExifInterface.TAG_GPS_DEST_LONGITUDE},
|
||||||
|
{"string", ExifInterface.TAG_GPS_DEST_LONGITUDE_REF},
|
||||||
|
{"int", ExifInterface.TAG_GPS_DIFFERENTIAL},
|
||||||
|
{"double", ExifInterface.TAG_GPS_IMG_DIRECTION},
|
||||||
|
{"string", ExifInterface.TAG_GPS_IMG_DIRECTION_REF},
|
||||||
|
{"string", ExifInterface.TAG_GPS_LATITUDE_REF},
|
||||||
|
{"string", ExifInterface.TAG_GPS_LONGITUDE_REF},
|
||||||
|
{"string", ExifInterface.TAG_GPS_MAP_DATUM},
|
||||||
|
{"string", ExifInterface.TAG_GPS_MEASURE_MODE},
|
||||||
|
{"string", ExifInterface.TAG_GPS_PROCESSING_METHOD},
|
||||||
|
{"string", ExifInterface.TAG_GPS_SATELLITES},
|
||||||
|
{"double", ExifInterface.TAG_GPS_SPEED},
|
||||||
|
{"string", ExifInterface.TAG_GPS_SPEED_REF},
|
||||||
|
{"string", ExifInterface.TAG_GPS_STATUS},
|
||||||
|
{"string", ExifInterface.TAG_GPS_TIMESTAMP},
|
||||||
|
{"double", ExifInterface.TAG_GPS_TRACK},
|
||||||
|
{"string", ExifInterface.TAG_GPS_TRACK_REF},
|
||||||
|
{"string", ExifInterface.TAG_GPS_VERSION_ID},
|
||||||
|
{"string", ExifInterface.TAG_INTEROPERABILITY_INDEX},
|
||||||
|
{"int", ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH},
|
||||||
|
{"int", ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH},
|
||||||
|
{"int", ExifInterface.TAG_DNG_VERSION},
|
||||||
|
{"int", ExifInterface.TAG_DEFAULT_CROP_SIZE},
|
||||||
|
{"int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_START},
|
||||||
|
{"int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH},
|
||||||
|
{"int", ExifInterface.TAG_ORF_ASPECT_FRAME},
|
||||||
|
{"int", ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER},
|
||||||
|
{"int", ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER},
|
||||||
|
{"int", ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER},
|
||||||
|
{"int", ExifInterface.TAG_RW2_SENSOR_TOP_BORDER},
|
||||||
|
{"int", ExifInterface.TAG_RW2_ISO},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run all events on native modules queue thread since they might be fired
|
||||||
|
// from other non RN threads.
|
||||||
|
|
||||||
|
|
||||||
|
// Mount error event
|
||||||
|
|
||||||
|
public static void emitMountErrorEvent(final ViewGroup view, final String error) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
CameraMountErrorEvent event = CameraMountErrorEvent.obtain(view.getId(), error);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camera ready event
|
||||||
|
|
||||||
|
public static void emitCameraReadyEvent(final ViewGroup view) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
CameraReadyEvent event = CameraReadyEvent.obtain(view.getId());
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preview Color event
|
||||||
|
public static void emitPreviewColorEvent(final ViewGroup view, final int color, final int r, final int g, final int b){
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
PreviewColorEvent event = PreviewColorEvent.obtain(view.getId(), color, r, g, b);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Picture saved event
|
||||||
|
|
||||||
|
public static void emitPictureSavedEvent(final ViewGroup view, final WritableMap response) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
PictureSavedEvent event = PictureSavedEvent.obtain(view.getId(), response);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Picture taken event
|
||||||
|
|
||||||
|
public static void emitPictureTakenEvent(final ViewGroup view) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
PictureTakenEvent event = PictureTakenEvent.obtain(view.getId());
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// video recording start/end events
|
||||||
|
|
||||||
|
public static void emitRecordingStartEvent(final ViewGroup view, final WritableMap response) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
RecordingStartEvent event = RecordingStartEvent.obtain(view.getId(), response);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void emitRecordingEndEvent(final ViewGroup view) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
RecordingEndEvent event = RecordingEndEvent.obtain(view.getId());
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Face detection events
|
||||||
|
|
||||||
|
public static void emitFacesDetectedEvent(final ViewGroup view, final WritableArray data) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
FacesDetectedEvent event = FacesDetectedEvent.obtain(view.getId(), data);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void emitFaceDetectionErrorEvent(final ViewGroup view, final RNFaceDetector faceDetector) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
FaceDetectionErrorEvent event = FaceDetectionErrorEvent.obtain(view.getId(), faceDetector);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Barcode detection events
|
||||||
|
|
||||||
|
public static void emitBarcodesDetectedEvent(final ViewGroup view, final WritableArray barcodes) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BarcodesDetectedEvent event = BarcodesDetectedEvent.obtain(view.getId(), barcodes);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void emitBarcodeDetectionErrorEvent(final ViewGroup view, final RNBarcodeDetector barcodeDetector) {
|
||||||
|
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BarcodeDetectionErrorEvent event = BarcodeDetectionErrorEvent.obtain(view.getId(), barcodeDetector);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bar code read event
|
||||||
|
|
||||||
|
public static void emitBarCodeReadEvent(final ViewGroup view, final Result barCode, final int width, final int height) {
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BarCodeReadEvent event = BarCodeReadEvent.obtain(view.getId(), barCode, width, height);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text recognition event
|
||||||
|
|
||||||
|
public static void emitTextRecognizedEvent(final ViewGroup view, final WritableArray data) {
|
||||||
|
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||||
|
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
TextRecognizedEvent event = TextRecognizedEvent.obtain(view.getId(), data);
|
||||||
|
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
|
||||||
|
public static int getCorrectCameraRotation(int rotation, int facing, int cameraOrientation) {
|
||||||
|
if (facing == CameraView.FACING_FRONT) {
|
||||||
|
// Tested the below line and there's no need to do the mirror calculation
|
||||||
|
return (cameraOrientation + rotation) % 360;
|
||||||
|
} else {
|
||||||
|
final int landscapeFlip = rotationIsLandscape(rotation) ? 180 : 0;
|
||||||
|
return (cameraOrientation - rotation + landscapeFlip) % 360;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean rotationIsLandscape(int rotation) {
|
||||||
|
return (rotation == Constants.LANDSCAPE_90 ||
|
||||||
|
rotation == Constants.LANDSCAPE_270);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getCamcorderProfileQualityFromCameraModuleConstant(int quality) {
|
||||||
|
switch (quality) {
|
||||||
|
case CameraModule.VIDEO_2160P:
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
return CamcorderProfile.QUALITY_2160P;
|
||||||
|
}
|
||||||
|
case CameraModule.VIDEO_1080P:
|
||||||
|
return CamcorderProfile.QUALITY_1080P;
|
||||||
|
case CameraModule.VIDEO_720P:
|
||||||
|
return CamcorderProfile.QUALITY_720P;
|
||||||
|
case CameraModule.VIDEO_480P:
|
||||||
|
return CamcorderProfile.QUALITY_480P;
|
||||||
|
case CameraModule.VIDEO_4x3:
|
||||||
|
return CamcorderProfile.QUALITY_480P;
|
||||||
|
}
|
||||||
|
return CamcorderProfile.QUALITY_HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CamcorderProfile getCamcorderProfile(int quality) {
|
||||||
|
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||||
|
int camcorderQuality = getCamcorderProfileQualityFromCameraModuleConstant(quality);
|
||||||
|
if (CamcorderProfile.hasProfile(camcorderQuality)) {
|
||||||
|
profile = CamcorderProfile.get(camcorderQuality);
|
||||||
|
if (quality == CameraModule.VIDEO_4x3) {
|
||||||
|
profile.videoFrameWidth = 640;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap getExifData(ExifInterface exifInterface) {
|
||||||
|
WritableMap exifMap = Arguments.createMap();
|
||||||
|
for (String[] tagInfo : exifTags) {
|
||||||
|
String name = tagInfo[1];
|
||||||
|
if (exifInterface.getAttribute(name) != null) {
|
||||||
|
String type = tagInfo[0];
|
||||||
|
switch (type) {
|
||||||
|
case "string":
|
||||||
|
exifMap.putString(name, exifInterface.getAttribute(name));
|
||||||
|
break;
|
||||||
|
case "int":
|
||||||
|
exifMap.putInt(name, exifInterface.getAttributeInt(name, 0));
|
||||||
|
break;
|
||||||
|
case "double":
|
||||||
|
exifMap.putDouble(name, exifInterface.getAttributeDouble(name, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double[] latLong = exifInterface.getLatLong();
|
||||||
|
if (latLong != null) {
|
||||||
|
exifMap.putDouble(ExifInterface.TAG_GPS_LATITUDE, latLong[0]);
|
||||||
|
exifMap.putDouble(ExifInterface.TAG_GPS_LONGITUDE, latLong[1]);
|
||||||
|
exifMap.putDouble(ExifInterface.TAG_GPS_ALTITUDE, exifInterface.getAltitude(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return exifMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setExifData(ExifInterface exifInterface, ReadableMap exifMap) {
|
||||||
|
for (String[] tagInfo : exifTags) {
|
||||||
|
String name = tagInfo[1];
|
||||||
|
if (exifMap.hasKey(name)) {
|
||||||
|
String type = tagInfo[0];
|
||||||
|
switch (type) {
|
||||||
|
case "string":
|
||||||
|
exifInterface.setAttribute(name, exifMap.getString(name));
|
||||||
|
break;
|
||||||
|
case "int":
|
||||||
|
exifInterface.setAttribute(name, Integer.toString(exifMap.getInt(name)));
|
||||||
|
exifMap.getInt(name);
|
||||||
|
break;
|
||||||
|
case "double":
|
||||||
|
exifInterface.setAttribute(name, Double.toString(exifMap.getDouble(name)));
|
||||||
|
exifMap.getDouble(name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exifMap.hasKey(ExifInterface.TAG_GPS_LATITUDE) && exifMap.hasKey(ExifInterface.TAG_GPS_LONGITUDE)) {
|
||||||
|
exifInterface.setLatLong(exifMap.getDouble(ExifInterface.TAG_GPS_LATITUDE),
|
||||||
|
exifMap.getDouble(ExifInterface.TAG_GPS_LONGITUDE));
|
||||||
|
}
|
||||||
|
if(exifMap.hasKey(ExifInterface.TAG_GPS_ALTITUDE)){
|
||||||
|
exifInterface.setAltitude(exifMap.getDouble(ExifInterface.TAG_GPS_ALTITUDE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clears exif values in place
|
||||||
|
public static void clearExifData(ExifInterface exifInterface) {
|
||||||
|
for (String[] tagInfo : exifTags) {
|
||||||
|
exifInterface.setAttribute(tagInfo[1], null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are not part of our tag list, remove by hand
|
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE, null);
|
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, null);
|
||||||
|
exifInterface.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap generateSimulatorPhoto(int width, int height) {
|
||||||
|
Bitmap fakePhoto = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(fakePhoto);
|
||||||
|
Paint background = new Paint();
|
||||||
|
background.setColor(Color.BLACK);
|
||||||
|
canvas.drawRect(0, 0, width, height, background);
|
||||||
|
Paint textPaint = new Paint();
|
||||||
|
textPaint.setColor(Color.YELLOW);
|
||||||
|
textPaint.setTextSize(35);
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd G '->' HH:mm:ss z");
|
||||||
|
canvas.drawText(simpleDateFormat.format(calendar.getTime()), width * 0.1f, height * 0.2f, textPaint);
|
||||||
|
canvas.drawText(simpleDateFormat.format(calendar.getTime()), width * 0.2f, height * 0.4f, textPaint);
|
||||||
|
canvas.drawText(simpleDateFormat.format(calendar.getTime()), width * 0.3f, height * 0.6f, textPaint);
|
||||||
|
canvas.drawText(simpleDateFormat.format(calendar.getTime()), width * 0.4f, height * 0.8f, textPaint);
|
||||||
|
|
||||||
|
return fakePhoto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.ResultPoint;
|
||||||
|
|
||||||
|
import java.util.Formatter;
|
||||||
|
|
||||||
|
public class BarCodeReadEvent extends Event<BarCodeReadEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<BarCodeReadEvent> EVENTS_POOL =
|
||||||
|
new Pools.SynchronizedPool<>(3);
|
||||||
|
|
||||||
|
private Result mBarCode;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
|
||||||
|
private BarCodeReadEvent() {}
|
||||||
|
|
||||||
|
public static BarCodeReadEvent obtain(int viewTag, Result barCode, int width, int height) {
|
||||||
|
BarCodeReadEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new BarCodeReadEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, barCode, width, height);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, Result barCode, int width, int height) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mBarCode = barCode;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We want every distinct barcode to be reported to the JS listener.
|
||||||
|
* If we return some static value as a coalescing key there may be two barcode events
|
||||||
|
* containing two different barcodes waiting to be transmitted to JS
|
||||||
|
* that would get coalesced (because both of them would have the same coalescing key).
|
||||||
|
* So let's differentiate them with a hash of the contents (mod short's max value).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
int hashCode = mBarCode.getText().hashCode() % Short.MAX_VALUE;
|
||||||
|
return (short) hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_BAR_CODE_READ.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
WritableMap event = Arguments.createMap();
|
||||||
|
WritableMap eventOrigin = Arguments.createMap();
|
||||||
|
|
||||||
|
event.putInt("target", getViewTag());
|
||||||
|
event.putString("data", mBarCode.getText());
|
||||||
|
|
||||||
|
byte[] rawBytes = mBarCode.getRawBytes();
|
||||||
|
if (rawBytes != null && rawBytes.length > 0) {
|
||||||
|
Formatter formatter = new Formatter();
|
||||||
|
for (byte b : rawBytes) {
|
||||||
|
formatter.format("%02x", b);
|
||||||
|
}
|
||||||
|
event.putString("rawData", formatter.toString());
|
||||||
|
formatter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
event.putString("type", mBarCode.getBarcodeFormat().toString());
|
||||||
|
WritableArray resultPoints = Arguments.createArray();
|
||||||
|
ResultPoint[] points = mBarCode.getResultPoints();
|
||||||
|
for (ResultPoint point: points) {
|
||||||
|
if(point!=null) {
|
||||||
|
WritableMap newPoint = Arguments.createMap();
|
||||||
|
newPoint.putString("x", String.valueOf(point.getX()));
|
||||||
|
newPoint.putString("y", String.valueOf(point.getY()));
|
||||||
|
resultPoints.pushMap(newPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventOrigin.putArray("origin", resultPoints);
|
||||||
|
eventOrigin.putInt("height", mHeight);
|
||||||
|
eventOrigin.putInt("width", mWidth);
|
||||||
|
event.putMap("bounds", eventOrigin);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import org.reactnative.barcodedetector.RNBarcodeDetector;
|
||||||
|
|
||||||
|
public class BarcodeDetectionErrorEvent extends Event<BarcodeDetectionErrorEvent> {
|
||||||
|
|
||||||
|
private static final Pools.SynchronizedPool<BarcodeDetectionErrorEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||||
|
private RNBarcodeDetector mBarcodeDetector;
|
||||||
|
|
||||||
|
private BarcodeDetectionErrorEvent() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarcodeDetectionErrorEvent obtain(int viewTag, RNBarcodeDetector barcodeDetector) {
|
||||||
|
BarcodeDetectionErrorEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new BarcodeDetectionErrorEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, barcodeDetector);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, RNBarcodeDetector faceDetector) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mBarcodeDetector = faceDetector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_BARCODE_DETECTION_ERROR.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
map.putBoolean("isOperational", mBarcodeDetector != null && mBarcodeDetector.isOperational());
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
|
||||||
|
public class BarcodesDetectedEvent extends Event<BarcodesDetectedEvent> {
|
||||||
|
|
||||||
|
private static final Pools.SynchronizedPool<BarcodesDetectedEvent> EVENTS_POOL =
|
||||||
|
new Pools.SynchronizedPool<>(3);
|
||||||
|
|
||||||
|
private WritableArray mBarcodes;
|
||||||
|
|
||||||
|
private BarcodesDetectedEvent() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarcodesDetectedEvent obtain(
|
||||||
|
int viewTag,
|
||||||
|
WritableArray barcodes
|
||||||
|
) {
|
||||||
|
BarcodesDetectedEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new BarcodesDetectedEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, barcodes);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(
|
||||||
|
int viewTag,
|
||||||
|
WritableArray barcodes
|
||||||
|
) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mBarcodes = barcodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* note(@sjchmiela)
|
||||||
|
* Should the events about detected barcodes coalesce, the best strategy will be
|
||||||
|
* to ensure that events with different barcodes count are always being transmitted.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
if (mBarcodes.size() > Short.MAX_VALUE) {
|
||||||
|
return Short.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (short) mBarcodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_BARCODES_DETECTED.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
WritableMap event = Arguments.createMap();
|
||||||
|
event.putString("type", "barcode");
|
||||||
|
event.putArray("barcodes", mBarcodes);
|
||||||
|
event.putInt("target", getViewTag());
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
|
||||||
|
public class CameraMountErrorEvent extends Event<CameraMountErrorEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<CameraMountErrorEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||||
|
private String mError;
|
||||||
|
|
||||||
|
private CameraMountErrorEvent() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CameraMountErrorEvent obtain(int viewTag, String error) {
|
||||||
|
CameraMountErrorEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new CameraMountErrorEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, error);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, String error) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mError = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_MOUNT_ERROR.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
WritableMap arguments = Arguments.createMap();
|
||||||
|
arguments.putString("message", mError);
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
public class CameraReadyEvent extends Event<CameraReadyEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<CameraReadyEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||||
|
private CameraReadyEvent() {}
|
||||||
|
|
||||||
|
public static CameraReadyEvent obtain(int viewTag) {
|
||||||
|
CameraReadyEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new CameraReadyEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_CAMERA_READY.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
return Arguments.createMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import org.reactnative.facedetector.RNFaceDetector;
|
||||||
|
|
||||||
|
public class FaceDetectionErrorEvent extends Event<FaceDetectionErrorEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<FaceDetectionErrorEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||||
|
private RNFaceDetector mFaceDetector;
|
||||||
|
|
||||||
|
private FaceDetectionErrorEvent() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FaceDetectionErrorEvent obtain(int viewTag, RNFaceDetector faceDetector) {
|
||||||
|
FaceDetectionErrorEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new FaceDetectionErrorEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, faceDetector);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, RNFaceDetector faceDetector) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mFaceDetector = faceDetector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_FACE_DETECTION_ERROR.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
map.putBoolean("isOperational", mFaceDetector != null && mFaceDetector.isOperational());
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
public class FacesDetectedEvent extends Event<FacesDetectedEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<FacesDetectedEvent> EVENTS_POOL =
|
||||||
|
new Pools.SynchronizedPool<>(3);
|
||||||
|
|
||||||
|
private WritableArray mData;
|
||||||
|
|
||||||
|
private FacesDetectedEvent() {}
|
||||||
|
|
||||||
|
public static FacesDetectedEvent obtain(int viewTag, WritableArray data) {
|
||||||
|
FacesDetectedEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new FacesDetectedEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, data);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, WritableArray data) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* note(@sjchmiela)
|
||||||
|
* Should the events about detected faces coalesce, the best strategy will be
|
||||||
|
* to ensure that events with different faces count are always being transmitted.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
if (mData.size() > Short.MAX_VALUE) {
|
||||||
|
return Short.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (short) mData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_FACES_DETECTED.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
WritableMap event = Arguments.createMap();
|
||||||
|
event.putString("type", "face");
|
||||||
|
event.putArray("faces", mData);
|
||||||
|
event.putInt("target", getViewTag());
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
|
||||||
|
public class PictureSavedEvent extends Event<PictureSavedEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<PictureSavedEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(5);
|
||||||
|
private PictureSavedEvent() {}
|
||||||
|
|
||||||
|
private WritableMap mResponse;
|
||||||
|
|
||||||
|
public static PictureSavedEvent obtain(int viewTag, WritableMap response) {
|
||||||
|
PictureSavedEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new PictureSavedEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, response);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, WritableMap response) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mResponse = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
int hashCode = mResponse.getMap("data").getString("uri").hashCode() % Short.MAX_VALUE;
|
||||||
|
return (short) hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_PICTURE_SAVED.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
public class PictureTakenEvent extends Event<PictureTakenEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<PictureTakenEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||||
|
private PictureTakenEvent() {}
|
||||||
|
|
||||||
|
public static PictureTakenEvent obtain(int viewTag) {
|
||||||
|
PictureTakenEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new PictureTakenEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_PICTURE_TAKEN.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
return Arguments.createMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
public class PreviewColorEvent extends Event<PreviewColorEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<PreviewColorEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||||
|
private PreviewColorEvent() {}
|
||||||
|
private int color;
|
||||||
|
private int r, g, b;
|
||||||
|
|
||||||
|
public static PreviewColorEvent obtain(int viewTag, int color, int r, int g, int b) {
|
||||||
|
PreviewColorEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new PreviewColorEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, color, r, g, b);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, int color, int r, int g, int b) {
|
||||||
|
super.init(viewTag);
|
||||||
|
this.color = color;
|
||||||
|
this.r = r;
|
||||||
|
this.g = g;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_PREVIEW_COLOR.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
map.putInt("color", color);
|
||||||
|
map.putInt("r", r);
|
||||||
|
map.putInt("g", g);
|
||||||
|
map.putInt("b", b);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
public class RecordingEndEvent extends Event<RecordingEndEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<RecordingEndEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||||
|
private RecordingEndEvent() {}
|
||||||
|
|
||||||
|
public static RecordingEndEvent obtain(int viewTag) {
|
||||||
|
RecordingEndEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new RecordingEndEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getCoalescingKey() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_RECORDING_END.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeEventData() {
|
||||||
|
return Arguments.createMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
public class RecordingStartEvent extends Event<RecordingStartEvent> {
|
||||||
|
private static final Pools.SynchronizedPool<RecordingStartEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||||
|
private RecordingStartEvent() {}
|
||||||
|
|
||||||
|
private WritableMap mResponse;
|
||||||
|
|
||||||
|
public static RecordingStartEvent obtain(int viewTag, WritableMap response) {
|
||||||
|
RecordingStartEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new RecordingStartEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, response);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, WritableMap response) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mResponse = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public short getCoalescingKey() {
|
||||||
|
// int hashCode = mResponse.getString("uri").hashCode() % Short.MAX_VALUE;
|
||||||
|
// return (short) hashCode;
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_RECORDING_START.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package org.reactnative.camera.events;
|
||||||
|
|
||||||
|
import androidx.core.util.Pools;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.events.Event;
|
||||||
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
import org.reactnative.camera.CameraViewManager;
|
||||||
|
|
||||||
|
|
||||||
|
public class TextRecognizedEvent extends Event<TextRecognizedEvent> {
|
||||||
|
|
||||||
|
private static final Pools.SynchronizedPool<TextRecognizedEvent> EVENTS_POOL =
|
||||||
|
new Pools.SynchronizedPool<>(3);
|
||||||
|
|
||||||
|
private WritableArray mData;
|
||||||
|
|
||||||
|
private TextRecognizedEvent() {}
|
||||||
|
|
||||||
|
public static TextRecognizedEvent obtain(int viewTag, WritableArray data) {
|
||||||
|
TextRecognizedEvent event = EVENTS_POOL.acquire();
|
||||||
|
if (event == null) {
|
||||||
|
event = new TextRecognizedEvent();
|
||||||
|
}
|
||||||
|
event.init(viewTag, data);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int viewTag, WritableArray data) {
|
||||||
|
super.init(viewTag);
|
||||||
|
mData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventName() {
|
||||||
|
return CameraViewManager.Events.EVENT_ON_TEXT_RECOGNIZED.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||||
|
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), createEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap createEvent() {
|
||||||
|
WritableMap event = Arguments.createMap();
|
||||||
|
event.putString("type", "textBlock");
|
||||||
|
event.putArray("textBlocks", mData);
|
||||||
|
event.putInt("target", getViewTag());
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import com.google.zxing.BinaryBitmap;
|
||||||
|
import com.google.zxing.MultiFormatReader;
|
||||||
|
import com.google.zxing.NotFoundException;
|
||||||
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
|
||||||
|
public class BarCodeScannerAsyncTask extends android.os.AsyncTask<Void, Void, Result> {
|
||||||
|
private byte[] mImageData;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private BarCodeScannerAsyncTaskDelegate mDelegate;
|
||||||
|
private final MultiFormatReader mMultiFormatReader;
|
||||||
|
private boolean mLimitScanArea;
|
||||||
|
private float mScanAreaX;
|
||||||
|
private float mScanAreaY;
|
||||||
|
private float mScanAreaWidth;
|
||||||
|
private float mScanAreaHeight;
|
||||||
|
private int mCameraViewWidth;
|
||||||
|
private int mCameraViewHeight;
|
||||||
|
private float mRatio;
|
||||||
|
|
||||||
|
// note(sjchmiela): From my short research it's ok to ignore rotation of the image.
|
||||||
|
public BarCodeScannerAsyncTask(
|
||||||
|
BarCodeScannerAsyncTaskDelegate delegate,
|
||||||
|
MultiFormatReader multiFormatReader,
|
||||||
|
byte[] imageData,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
boolean limitScanArea,
|
||||||
|
float scanAreaX,
|
||||||
|
float scanAreaY,
|
||||||
|
float scanAreaWidth,
|
||||||
|
float scanAreaHeight,
|
||||||
|
int cameraViewWidth,
|
||||||
|
int cameraViewHeight,
|
||||||
|
float ratio
|
||||||
|
) {
|
||||||
|
mImageData = imageData;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
mDelegate = delegate;
|
||||||
|
mMultiFormatReader = multiFormatReader;
|
||||||
|
mLimitScanArea = limitScanArea;
|
||||||
|
mScanAreaX = scanAreaX;
|
||||||
|
mScanAreaY = scanAreaY;
|
||||||
|
mScanAreaWidth = scanAreaWidth;
|
||||||
|
mScanAreaHeight = scanAreaHeight;
|
||||||
|
mCameraViewWidth = cameraViewWidth;
|
||||||
|
mCameraViewHeight = cameraViewHeight;
|
||||||
|
mRatio = ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result doInBackground(Void... ignored) {
|
||||||
|
if (isCancelled() || mDelegate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result result = null;
|
||||||
|
/**
|
||||||
|
* mCameraViewWidth and mCameraViewHeight are obtained from portait orientation
|
||||||
|
* mWidth and mHeight are measured with landscape orientation with Home button to the right
|
||||||
|
* adjustedCamViewWidth is the adjusted width fromt the Aspect ratio setting
|
||||||
|
*/
|
||||||
|
int adjustedCamViewWidth = (int) (mCameraViewHeight / mRatio);
|
||||||
|
float adjustedScanY = (((adjustedCamViewWidth - mCameraViewWidth) / 2) + (mScanAreaY * mCameraViewWidth)) / adjustedCamViewWidth;
|
||||||
|
|
||||||
|
int left = (int) (mScanAreaX * mWidth);
|
||||||
|
int top = (int) (adjustedScanY * mHeight);
|
||||||
|
int scanWidth = (int) (mScanAreaWidth * mWidth);
|
||||||
|
int scanHeight = (int) (((mScanAreaHeight * mCameraViewWidth) / adjustedCamViewWidth) * mHeight);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BinaryBitmap bitmap = generateBitmapFromImageData(
|
||||||
|
mImageData,
|
||||||
|
mWidth,
|
||||||
|
mHeight,
|
||||||
|
false,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
scanWidth,
|
||||||
|
scanHeight
|
||||||
|
);
|
||||||
|
result = mMultiFormatReader.decodeWithState(bitmap);
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
BinaryBitmap bitmap = generateBitmapFromImageData(
|
||||||
|
rotateImage(mImageData,mWidth, mHeight),
|
||||||
|
mHeight,
|
||||||
|
mWidth,
|
||||||
|
false,
|
||||||
|
mHeight - scanHeight - top,
|
||||||
|
left,
|
||||||
|
scanHeight,
|
||||||
|
scanWidth
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
result = mMultiFormatReader.decodeWithState(bitmap);
|
||||||
|
} catch (NotFoundException e1) {
|
||||||
|
BinaryBitmap invertedBitmap = generateBitmapFromImageData(
|
||||||
|
mImageData,
|
||||||
|
mWidth,
|
||||||
|
mHeight,
|
||||||
|
true,
|
||||||
|
mWidth - scanWidth - left,
|
||||||
|
mHeight - scanHeight - top,
|
||||||
|
scanWidth,
|
||||||
|
scanHeight
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
result = mMultiFormatReader.decodeWithState(invertedBitmap);
|
||||||
|
} catch (NotFoundException e2) {
|
||||||
|
BinaryBitmap invertedRotatedBitmap = generateBitmapFromImageData(
|
||||||
|
rotateImage(mImageData,mWidth, mHeight),
|
||||||
|
mHeight,
|
||||||
|
mWidth,
|
||||||
|
true,
|
||||||
|
top,
|
||||||
|
mWidth - scanWidth - left,
|
||||||
|
scanHeight,
|
||||||
|
scanWidth
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
result = mMultiFormatReader.decodeWithState(invertedRotatedBitmap);
|
||||||
|
} catch (NotFoundException e3) {
|
||||||
|
//no barcode Found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
private byte[] rotateImage(byte[]imageData,int width, int height) {
|
||||||
|
byte[] rotated = new byte[imageData.length];
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
rotated[x * height + height - y - 1] = imageData[x + y * width];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rotated;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Result result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
if (result != null) {
|
||||||
|
mDelegate.onBarCodeRead(result, mWidth, mHeight);
|
||||||
|
}
|
||||||
|
mDelegate.onBarCodeScanningTaskCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BinaryBitmap generateBitmapFromImageData(byte[] imageData, int width, int height, boolean inverse, int left, int top, int sWidth, int sHeight) {
|
||||||
|
PlanarYUVLuminanceSource source;
|
||||||
|
if (mLimitScanArea) {
|
||||||
|
source = new PlanarYUVLuminanceSource(
|
||||||
|
imageData, // byte[] yuvData
|
||||||
|
width, // int dataWidth
|
||||||
|
height, // int dataHeight
|
||||||
|
left, // int left
|
||||||
|
top, // int top
|
||||||
|
sWidth, // int width
|
||||||
|
sHeight, // int height
|
||||||
|
false // boolean reverseHorizontal
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
source = new PlanarYUVLuminanceSource(
|
||||||
|
imageData, // byte[] yuvData
|
||||||
|
width, // int dataWidth
|
||||||
|
height, // int dataHeight
|
||||||
|
0, // int left
|
||||||
|
0, // int top
|
||||||
|
width, // int width
|
||||||
|
height, // int height
|
||||||
|
false // boolean reverseHorizontal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (inverse) {
|
||||||
|
return new BinaryBitmap(new HybridBinarizer(source.invert()));
|
||||||
|
} else {
|
||||||
|
return new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
|
||||||
|
public interface BarCodeScannerAsyncTaskDelegate {
|
||||||
|
void onBarCodeRead(Result barCode, int width, int height);
|
||||||
|
void onBarCodeScanningTaskCompleted();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import org.reactnative.barcodedetector.RNBarcodeDetector;
|
||||||
|
|
||||||
|
public interface BarcodeDetectorAsyncTaskDelegate {
|
||||||
|
|
||||||
|
void onBarcodesDetected(WritableArray barcodes);
|
||||||
|
|
||||||
|
void onBarcodeDetectionError(RNBarcodeDetector barcodeDetector);
|
||||||
|
|
||||||
|
void onBarcodeDetectingTaskCompleted();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import org.reactnative.facedetector.RNFaceDetector;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
|
||||||
|
public interface FaceDetectorAsyncTaskDelegate {
|
||||||
|
void onFacesDetected(WritableArray faces);
|
||||||
|
void onFaceDetectionError(RNFaceDetector faceDetector);
|
||||||
|
void onFaceDetectingTaskCompleted();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
|
||||||
|
public interface PictureSavedDelegate {
|
||||||
|
void onPictureSaved(WritableMap response);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,360 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import org.reactnative.camera.RNCameraViewHelper;
|
||||||
|
import org.reactnative.camera.utils.RNFileUtils;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.ReadableType;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ResolveTakenPictureAsyncTask extends AsyncTask<Void, Void, WritableMap> {
|
||||||
|
private static final String ERROR_TAG = "E_TAKING_PICTURE_FAILED";
|
||||||
|
private Promise mPromise;
|
||||||
|
private Bitmap mBitmap;
|
||||||
|
private byte[] mImageData;
|
||||||
|
private ReadableMap mOptions;
|
||||||
|
private File mCacheDirectory;
|
||||||
|
private int mDeviceOrientation;
|
||||||
|
private PictureSavedDelegate mPictureSavedDelegate;
|
||||||
|
|
||||||
|
public ResolveTakenPictureAsyncTask(byte[] imageData, Promise promise, ReadableMap options, File cacheDirectory, int deviceOrientation, PictureSavedDelegate delegate) {
|
||||||
|
mPromise = promise;
|
||||||
|
mOptions = options;
|
||||||
|
mImageData = imageData;
|
||||||
|
mCacheDirectory = cacheDirectory;
|
||||||
|
mDeviceOrientation = deviceOrientation;
|
||||||
|
mPictureSavedDelegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getQuality() {
|
||||||
|
return (int) (mOptions.getDouble("quality") * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loads bitmap only if necessary
|
||||||
|
private void loadBitmap() throws IOException {
|
||||||
|
if(mBitmap == null){
|
||||||
|
mBitmap = BitmapFactory.decodeByteArray(mImageData, 0, mImageData.length);
|
||||||
|
}
|
||||||
|
if(mBitmap == null){
|
||||||
|
throw new IOException("Failed to decode Image Bitmap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WritableMap doInBackground(Void... voids) {
|
||||||
|
WritableMap response = Arguments.createMap();
|
||||||
|
ByteArrayInputStream inputStream = null;
|
||||||
|
ExifInterface exifInterface = null;
|
||||||
|
WritableMap exifData = null;
|
||||||
|
ReadableMap exifExtraData = null;
|
||||||
|
|
||||||
|
boolean orientationChanged = false;
|
||||||
|
|
||||||
|
response.putInt("deviceOrientation", mDeviceOrientation);
|
||||||
|
response.putInt("pictureOrientation", mOptions.hasKey("orientation") ? mOptions.getInt("orientation") : mDeviceOrientation);
|
||||||
|
|
||||||
|
|
||||||
|
try{
|
||||||
|
// this replaces the skipProcessing flag, we will process only if needed, and in
|
||||||
|
// an orderly manner, so that skipProcessing is the default behaviour if no options are given
|
||||||
|
// and this behaves more like the iOS version.
|
||||||
|
// We will load all data lazily only when needed.
|
||||||
|
|
||||||
|
// this should not incurr in any overhead if not read/used
|
||||||
|
inputStream = new ByteArrayInputStream(mImageData);
|
||||||
|
|
||||||
|
|
||||||
|
// Rotate the bitmap to the proper orientation if requested
|
||||||
|
if(mOptions.hasKey("fixOrientation") && mOptions.getBoolean("fixOrientation")){
|
||||||
|
|
||||||
|
exifInterface = new ExifInterface(inputStream);
|
||||||
|
|
||||||
|
// Get orientation of the image from mImageData via inputStream
|
||||||
|
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
|
||||||
|
|
||||||
|
if(orientation != ExifInterface.ORIENTATION_UNDEFINED){
|
||||||
|
loadBitmap();
|
||||||
|
mBitmap = rotateBitmap(mBitmap, getImageRotation(orientation));
|
||||||
|
orientationChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mOptions.hasKey("width")) {
|
||||||
|
loadBitmap();
|
||||||
|
mBitmap = resizeBitmap(mBitmap, mOptions.getInt("width"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mOptions.hasKey("mirrorImage") && mOptions.getBoolean("mirrorImage")) {
|
||||||
|
loadBitmap();
|
||||||
|
mBitmap = flipHorizontally(mBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// EXIF code - we will adjust exif info later if we manipulated the bitmap
|
||||||
|
boolean writeExifToResponse = mOptions.hasKey("exif") && mOptions.getBoolean("exif");
|
||||||
|
|
||||||
|
// default to true if not provided so it is consistent with iOS and with what happens if no
|
||||||
|
// processing is done and the image is saved as is.
|
||||||
|
boolean writeExifToFile = true;
|
||||||
|
|
||||||
|
if (mOptions.hasKey("writeExif")) {
|
||||||
|
switch (mOptions.getType("writeExif")) {
|
||||||
|
case Boolean:
|
||||||
|
writeExifToFile = mOptions.getBoolean("writeExif");
|
||||||
|
break;
|
||||||
|
case Map:
|
||||||
|
exifExtraData = mOptions.getMap("writeExif");
|
||||||
|
writeExifToFile = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Exif data if needed
|
||||||
|
if (writeExifToResponse || writeExifToFile) {
|
||||||
|
|
||||||
|
// if we manipulated the image, or need to add extra data, or need to add it to the response,
|
||||||
|
// then we need to load the actual exif data.
|
||||||
|
// Otherwise we can just use w/e exif data we have right now in our byte array
|
||||||
|
if(mBitmap != null || exifExtraData != null || writeExifToResponse){
|
||||||
|
if(exifInterface == null){
|
||||||
|
exifInterface = new ExifInterface(inputStream);
|
||||||
|
}
|
||||||
|
exifData = RNCameraViewHelper.getExifData(exifInterface);
|
||||||
|
|
||||||
|
if(exifExtraData != null){
|
||||||
|
exifData.merge(exifExtraData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we did anything to the bitmap, adjust exif
|
||||||
|
if(mBitmap != null){
|
||||||
|
exifData.putInt("width", mBitmap.getWidth());
|
||||||
|
exifData.putInt("height", mBitmap.getHeight());
|
||||||
|
|
||||||
|
if(orientationChanged){
|
||||||
|
exifData.putInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Exif data to the response if requested
|
||||||
|
if (writeExifToResponse) {
|
||||||
|
response.putMap("exif", exifData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// final processing
|
||||||
|
// Based on whether or not we loaded the full bitmap into memory, final processing differs
|
||||||
|
if(mBitmap == null){
|
||||||
|
|
||||||
|
// set response dimensions. If we haven't read our bitmap, get it efficiently
|
||||||
|
// without loading the actual bitmap into memory
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeByteArray(mImageData, 0, mImageData.length, options);
|
||||||
|
if(options != null){
|
||||||
|
response.putInt("width", options.outWidth);
|
||||||
|
response.putInt("height", options.outHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// save to file if requested
|
||||||
|
if (!mOptions.hasKey("doNotSave") || !mOptions.getBoolean("doNotSave")) {
|
||||||
|
|
||||||
|
// Prepare file output
|
||||||
|
File imageFile = new File(RNFileUtils.getOutputFilePath(mCacheDirectory, ".jpg"));
|
||||||
|
imageFile.createNewFile();
|
||||||
|
FileOutputStream fOut = new FileOutputStream(imageFile);
|
||||||
|
|
||||||
|
// Save byte array (it is already a JPEG)
|
||||||
|
fOut.write(mImageData);
|
||||||
|
fOut.flush();
|
||||||
|
fOut.close();
|
||||||
|
|
||||||
|
// update exif data if needed.
|
||||||
|
// Since we didn't modify the image, we only update if we have extra exif info
|
||||||
|
if (writeExifToFile && exifExtraData != null) {
|
||||||
|
ExifInterface fileExifInterface = new ExifInterface(imageFile.getAbsolutePath());
|
||||||
|
RNCameraViewHelper.setExifData(fileExifInterface, exifExtraData);
|
||||||
|
fileExifInterface.saveAttributes();
|
||||||
|
}
|
||||||
|
else if (!writeExifToFile){
|
||||||
|
// if we were requested to NOT store exif, we actually need to
|
||||||
|
// clear the exif tags
|
||||||
|
ExifInterface fileExifInterface = new ExifInterface(imageFile.getAbsolutePath());
|
||||||
|
RNCameraViewHelper.clearExifData(fileExifInterface);
|
||||||
|
fileExifInterface.saveAttributes();
|
||||||
|
}
|
||||||
|
// else: exif is unmodified, no need to update anything
|
||||||
|
|
||||||
|
// Return file system URI
|
||||||
|
String fileUri = Uri.fromFile(imageFile).toString();
|
||||||
|
response.putString("uri", fileUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mOptions.hasKey("base64") && mOptions.getBoolean("base64")) {
|
||||||
|
response.putString("base64", Base64.encodeToString(mImageData, Base64.NO_WRAP));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
|
||||||
|
// get response dimensions right from the bitmap if we have it
|
||||||
|
response.putInt("width", mBitmap.getWidth());
|
||||||
|
response.putInt("height", mBitmap.getHeight());
|
||||||
|
|
||||||
|
// Cache compressed image in imageStream
|
||||||
|
ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
|
||||||
|
mBitmap.compress(Bitmap.CompressFormat.JPEG, getQuality(), imageStream);
|
||||||
|
|
||||||
|
|
||||||
|
// Write compressed image to file in cache directory unless otherwise specified
|
||||||
|
if (!mOptions.hasKey("doNotSave") || !mOptions.getBoolean("doNotSave")) {
|
||||||
|
String filePath = writeStreamToFile(imageStream);
|
||||||
|
|
||||||
|
// since we lost any exif data on bitmap creation, we only need
|
||||||
|
// to add it if requested
|
||||||
|
if (writeExifToFile && exifData != null) {
|
||||||
|
ExifInterface fileExifInterface = new ExifInterface(filePath);
|
||||||
|
RNCameraViewHelper.setExifData(fileExifInterface, exifData);
|
||||||
|
fileExifInterface.saveAttributes();
|
||||||
|
}
|
||||||
|
File imageFile = new File(filePath);
|
||||||
|
String fileUri = Uri.fromFile(imageFile).toString();
|
||||||
|
response.putString("uri", fileUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write base64-encoded image to the response if requested
|
||||||
|
if (mOptions.hasKey("base64") && mOptions.getBoolean("base64")) {
|
||||||
|
response.putString("base64", Base64.encodeToString(imageStream.toByteArray(), Base64.NO_WRAP));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Resources.NotFoundException e) {
|
||||||
|
mPromise.reject(ERROR_TAG, "Documents directory of the app could not be found.", e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
mPromise.reject(ERROR_TAG, "An unknown I/O exception has occurred.", e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
if (inputStream != null) {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap rotateBitmap(Bitmap source, int angle) {
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.postRotate(angle);
|
||||||
|
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap resizeBitmap(Bitmap bm, int newWidth) {
|
||||||
|
int width = bm.getWidth();
|
||||||
|
int height = bm.getHeight();
|
||||||
|
float scaleRatio = (float) newWidth / (float) width;
|
||||||
|
|
||||||
|
return Bitmap.createScaledBitmap(bm, newWidth, (int) (height * scaleRatio), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap flipHorizontally(Bitmap source) {
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.preScale(-1.0f, 1.0f);
|
||||||
|
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rotation degrees from Exif orientation enum
|
||||||
|
|
||||||
|
private int getImageRotation(int orientation) {
|
||||||
|
int rotationDegrees = 0;
|
||||||
|
switch (orientation) {
|
||||||
|
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||||
|
rotationDegrees = 90;
|
||||||
|
break;
|
||||||
|
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||||
|
rotationDegrees = 180;
|
||||||
|
break;
|
||||||
|
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||||
|
rotationDegrees = 270;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return rotationDegrees;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String writeStreamToFile(ByteArrayOutputStream inputStream) throws IOException {
|
||||||
|
String outputPath = null;
|
||||||
|
IOException exception = null;
|
||||||
|
FileOutputStream outputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
outputPath = RNFileUtils.getOutputFilePath(mCacheDirectory, ".jpg");
|
||||||
|
outputStream = new FileOutputStream(outputPath);
|
||||||
|
inputStream.writeTo(outputStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
exception = e;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (outputStream != null) {
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(WritableMap response) {
|
||||||
|
super.onPostExecute(response);
|
||||||
|
|
||||||
|
// If the response is not null everything went well and we can resolve the promise.
|
||||||
|
if (response != null) {
|
||||||
|
if (mOptions.hasKey("fastMode") && mOptions.getBoolean("fastMode")) {
|
||||||
|
WritableMap wrapper = Arguments.createMap();
|
||||||
|
wrapper.putInt("id", mOptions.getInt("id"));
|
||||||
|
wrapper.putMap("data", response);
|
||||||
|
mPictureSavedDelegate.onPictureSaved(wrapper);
|
||||||
|
} else {
|
||||||
|
mPromise.resolve(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
|
||||||
|
public interface TextRecognizerAsyncTaskDelegate {
|
||||||
|
void onTextRecognized(WritableArray serializedData);
|
||||||
|
void onTextRecognizerTaskCompleted();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package org.reactnative.camera.utils;
|
||||||
|
|
||||||
|
public class ImageDimensions {
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private int mFacing;
|
||||||
|
private int mRotation;
|
||||||
|
|
||||||
|
public ImageDimensions(int width, int height) {
|
||||||
|
this(width, height, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageDimensions(int width, int height, int rotation) {
|
||||||
|
this(width, height, rotation, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageDimensions(int width, int height, int rotation, int facing) {
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
mFacing = facing;
|
||||||
|
mRotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLandscape() {
|
||||||
|
return mRotation % 180 == 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
if (isLandscape()) {
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
if (isLandscape()) {
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRotation() {
|
||||||
|
return mRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFacing() {
|
||||||
|
return mFacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof ImageDimensions) {
|
||||||
|
ImageDimensions otherDimensions = (ImageDimensions) obj;
|
||||||
|
return (otherDimensions.getWidth() == getWidth() &&
|
||||||
|
otherDimensions.getHeight() == getHeight() &&
|
||||||
|
otherDimensions.getFacing() == getFacing() &&
|
||||||
|
otherDimensions.getRotation() == getRotation());
|
||||||
|
} else {
|
||||||
|
return super.equals(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package org.reactnative.camera.utils;
|
||||||
|
|
||||||
|
|
||||||
|
public class ObjectUtils {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replacement for Objects.equals that is only available after Android API 19
|
||||||
|
*/
|
||||||
|
public static boolean equals(Object o1, Object o2) {
|
||||||
|
if (o1 == null && o2 == null) return true;
|
||||||
|
if (o1 == null) return false;
|
||||||
|
return o1.equals(o2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.reactnative.camera.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by jgfidelis on 23/01/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RNFileUtils {
|
||||||
|
|
||||||
|
public static File ensureDirExists(File dir) throws IOException {
|
||||||
|
if (!(dir.isDirectory() || dir.mkdirs())) {
|
||||||
|
throw new IOException("Couldn't create directory '" + dir + "'");
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getOutputFilePath(File directory, String extension) throws IOException {
|
||||||
|
ensureDirExists(directory);
|
||||||
|
String filename = UUID.randomUUID().toString();
|
||||||
|
return directory + File.separator + filename + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri uriFromFile(File file) {
|
||||||
|
return Uri.fromFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.reactnative.camera.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by jgfidelis on 23/01/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ScopedContext {
|
||||||
|
|
||||||
|
private File cacheDirectory = null;
|
||||||
|
|
||||||
|
public ScopedContext(Context context) {
|
||||||
|
createCacheDirectory(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createCacheDirectory(Context context) {
|
||||||
|
cacheDirectory = new File(context.getCacheDir() + "/Camera/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getCacheDirectory() {
|
||||||
|
return cacheDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
android/src/main/java/org/reactnative/frame/RNFrame.java
Normal file
28
android/src/main/java/org/reactnative/frame/RNFrame.java
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.reactnative.frame;
|
||||||
|
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
import com.google.android.gms.vision.Frame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around Frame allowing us to track Frame dimensions.
|
||||||
|
* Tracking dimensions is used in RNFaceDetector and RNBarcodeDetector to provide painless FaceDetector/BarcodeDetector recreation
|
||||||
|
* when image dimensions change.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RNFrame {
|
||||||
|
private Frame mFrame;
|
||||||
|
private ImageDimensions mDimensions;
|
||||||
|
|
||||||
|
public RNFrame(Frame frame, ImageDimensions dimensions) {
|
||||||
|
mFrame = frame;
|
||||||
|
mDimensions = dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Frame getFrame() {
|
||||||
|
return mFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageDimensions getDimensions() {
|
||||||
|
return mDimensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.reactnative.frame;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.ImageFormat;
|
||||||
|
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
import com.google.android.gms.vision.Frame;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class RNFrameFactory {
|
||||||
|
public static RNFrame buildFrame(byte[] bitmapData, int width, int height, int rotation) {
|
||||||
|
Frame.Builder builder = new Frame.Builder();
|
||||||
|
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bitmapData);
|
||||||
|
builder.setImageData(byteBuffer, width, height, ImageFormat.NV21);
|
||||||
|
|
||||||
|
switch (rotation) {
|
||||||
|
case 90:
|
||||||
|
builder.setRotation(Frame.ROTATION_90);
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
builder.setRotation(Frame.ROTATION_180);
|
||||||
|
break;
|
||||||
|
case 270:
|
||||||
|
builder.setRotation(Frame.ROTATION_270);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
builder.setRotation(Frame.ROTATION_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDimensions dimensions = new ImageDimensions(width, height, rotation);
|
||||||
|
|
||||||
|
return new RNFrame(builder.build(), dimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RNFrame buildFrame(Bitmap bitmap) {
|
||||||
|
Frame.Builder builder = new Frame.Builder();
|
||||||
|
builder.setBitmap(bitmap);
|
||||||
|
ImageDimensions dimensions = new ImageDimensions(bitmap.getWidth(), bitmap.getHeight());
|
||||||
|
return new RNFrame(builder.build(), dimensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
android/src/main/res/layout-v14/texture_view.xml
Normal file
23
android/src/main/res/layout-v14/texture_view.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 The Android Open Source Project
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<TextureView
|
||||||
|
android:id="@+id/texture_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</merge>
|
||||||
23
android/src/main/res/layout/surface_view.xml
Normal file
23
android/src/main/res/layout/surface_view.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 The Android Open Source Project
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<SurfaceView
|
||||||
|
android:id="@+id/surface_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</merge>
|
||||||
58
android/src/main/res/values/attrs.xml
Normal file
58
android/src/main/res/values/attrs.xml
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 The Android Open Source Project
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="CameraView">
|
||||||
|
<!--
|
||||||
|
Set this to true if you want the CameraView to adjust its bounds to preserve the aspect
|
||||||
|
ratio of its camera preview.
|
||||||
|
-->
|
||||||
|
<attr name="android:adjustViewBounds"/>
|
||||||
|
<!-- Direction the camera faces relative to device screen. -->
|
||||||
|
<attr name="facing" format="enum">
|
||||||
|
<!-- The camera device faces the opposite direction as the device's screen. -->
|
||||||
|
<enum name="back" value="0"/>
|
||||||
|
<!-- The camera device faces the same direction as the device's screen. -->
|
||||||
|
<enum name="front" value="1"/>
|
||||||
|
</attr>
|
||||||
|
<!-- Aspect ratio of camera preview and pictures. -->
|
||||||
|
<attr name="aspectRatio" format="string"/>
|
||||||
|
<!-- Continuous auto focus mode. -->
|
||||||
|
<attr name="autoFocus" format="boolean"/>
|
||||||
|
<!-- The flash mode. -->
|
||||||
|
<attr name="flash" format="enum">
|
||||||
|
<!-- Flash will not be fired. -->
|
||||||
|
<enum name="off" value="0"/>
|
||||||
|
<!--
|
||||||
|
Flash will always be fired during snapshot.
|
||||||
|
The flash may also be fired during preview or auto-focus depending on the driver.
|
||||||
|
-->
|
||||||
|
<enum name="on" value="1"/>
|
||||||
|
<!--
|
||||||
|
Constant emission of light during preview, auto-focus and snapshot.
|
||||||
|
This can also be used for video recording.
|
||||||
|
-->
|
||||||
|
<enum name="torch" value="2"/>
|
||||||
|
<!--
|
||||||
|
Flash will be fired automatically when required.
|
||||||
|
The flash may be fired during preview, auto-focus, or snapshot depending on the
|
||||||
|
driver.
|
||||||
|
-->
|
||||||
|
<enum name="auto" value="3"/>
|
||||||
|
<!--
|
||||||
|
Flash will be fired in red-eye reduction mode.
|
||||||
|
-->
|
||||||
|
<enum name="redEye" value="4"/>
|
||||||
|
</attr>
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
||||||
21
android/src/main/res/values/public.xml
Normal file
21
android/src/main/res/values/public.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 The Android Open Source Project
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<public name="facing" type="attr"/>
|
||||||
|
<public name="aspectRatio" type="attr"/>
|
||||||
|
<public name="autoFocus" type="attr"/>
|
||||||
|
<public name="flash" type="attr"/>
|
||||||
|
|
||||||
|
<public name="Widget.CameraView" type="style"/>
|
||||||
|
</resources>
|
||||||
24
android/src/main/res/values/styles.xml
Normal file
24
android/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 The Android Open Source Project
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Widget.CameraView" parent="android:Widget">
|
||||||
|
<item name="android:adjustViewBounds">false</item>
|
||||||
|
<item name="facing">back</item>
|
||||||
|
<item name="aspectRatio">4:3</item>
|
||||||
|
<item name="autoFocus">true</item>
|
||||||
|
<item name="flash">auto</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package org.reactnative.barcodedetector;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BarcodeFormatUtils {
|
||||||
|
|
||||||
|
public static final SparseArray<String> FORMATS;
|
||||||
|
public static final Map<String, Integer> REVERSE_FORMATS;
|
||||||
|
public static final SparseArray<String> TYPES;
|
||||||
|
public static final Map<String, Integer> REVERSE_TYPES;
|
||||||
|
|
||||||
|
private static final int UNKNOWN_FORMAT_INT = FirebaseVisionBarcode.FORMAT_UNKNOWN;
|
||||||
|
|
||||||
|
private static final String UNKNOWN_TYPE_STRING = "UNKNOWN_TYPE";
|
||||||
|
private static final String UNKNOWN_FORMAT_STRING = "UNKNOWN_FORMAT";
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Initialize integer to string map
|
||||||
|
SparseArray<String> map = new SparseArray<>();
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_CODE_128, "CODE_128");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_CODE_39, "CODE_39");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_CODE_93, "CODE_93");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_CODABAR, "CODABAR");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_DATA_MATRIX, "DATA_MATRIX");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_EAN_13, "EAN_13");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_EAN_8, "EAN_8");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_ITF, "ITF");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_QR_CODE, "QR_CODE");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_UPC_A, "UPC_A");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_UPC_E, "UPC_E");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_PDF417, "PDF417");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_AZTEC, "AZTEC");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_ALL_FORMATS, "ALL");
|
||||||
|
map.put(FirebaseVisionBarcode.FORMAT_UPC_A, "UPC_A");
|
||||||
|
map.put(-1, "None");
|
||||||
|
FORMATS = map;
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize string to integer map
|
||||||
|
Map<String, Integer> rmap = new HashMap<>();
|
||||||
|
for (int i = 0; i < map.size(); i++) {
|
||||||
|
rmap.put(map.valueAt(i), map.keyAt(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
REVERSE_FORMATS = Collections.unmodifiableMap(rmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Initialize integer to string map
|
||||||
|
SparseArray<String> map = new SparseArray<>();
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_CALENDAR_EVENT, "CALENDAR_EVENT");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_CONTACT_INFO, "CONTACT_INFO");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_DRIVER_LICENSE, "DRIVER_LICENSE");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_EMAIL, "EMAIL");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_GEO, "GEO");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_ISBN, "ISBN");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_PHONE, "PHONE");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_PRODUCT, "PRODUCT");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_SMS, "SMS");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_TEXT, "TEXT");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_URL, "URL");
|
||||||
|
map.put(FirebaseVisionBarcode.TYPE_WIFI, "WIFI");
|
||||||
|
map.put(-1, "None");
|
||||||
|
TYPES = map;
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize string to integer map
|
||||||
|
Map<String, Integer> rmap = new HashMap<>();
|
||||||
|
for (int i = 0; i < map.size(); i++) {
|
||||||
|
rmap.put(map.valueAt(i), map.keyAt(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
REVERSE_TYPES = Collections.unmodifiableMap(rmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String get(int format) {
|
||||||
|
return TYPES.get(format, UNKNOWN_TYPE_STRING);
|
||||||
|
}
|
||||||
|
public static String getFormat(int format) {
|
||||||
|
return FORMATS.get(format, UNKNOWN_FORMAT_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int get(String format) {
|
||||||
|
if (REVERSE_FORMATS.containsKey(format)) {
|
||||||
|
return REVERSE_FORMATS.get(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN_FORMAT_INT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.reactnative.barcodedetector;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.firebase.ml.vision.FirebaseVision;
|
||||||
|
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode;
|
||||||
|
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetector;
|
||||||
|
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetectorOptions;
|
||||||
|
|
||||||
|
|
||||||
|
public class RNBarcodeDetector {
|
||||||
|
|
||||||
|
public static int NORMAL_MODE = 0;
|
||||||
|
public static int ALTERNATE_MODE = 1;
|
||||||
|
public static int INVERTED_MODE = 2;
|
||||||
|
public static int ALL_FORMATS = FirebaseVisionBarcode.FORMAT_ALL_FORMATS;
|
||||||
|
|
||||||
|
private FirebaseVisionBarcodeDetector mBarcodeDetector = null;
|
||||||
|
private FirebaseVisionBarcodeDetectorOptions.Builder mBuilder;
|
||||||
|
|
||||||
|
private int mBarcodeType = FirebaseVisionBarcode.FORMAT_ALL_FORMATS;
|
||||||
|
|
||||||
|
public RNBarcodeDetector(Context context) {
|
||||||
|
mBuilder = new FirebaseVisionBarcodeDetectorOptions.Builder().setBarcodeFormats(mBarcodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOperational() {
|
||||||
|
// Legacy api from GMV
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FirebaseVisionBarcodeDetector getDetector() {
|
||||||
|
|
||||||
|
if (mBarcodeDetector == null) {
|
||||||
|
createBarcodeDetector();
|
||||||
|
}
|
||||||
|
return mBarcodeDetector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBarcodeType(int barcodeType) {
|
||||||
|
if (barcodeType != mBarcodeType) {
|
||||||
|
release();
|
||||||
|
mBuilder.setBarcodeFormats(barcodeType);
|
||||||
|
mBarcodeType = barcodeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
if (mBarcodeDetector != null) {
|
||||||
|
try {
|
||||||
|
mBarcodeDetector.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("RNCamera", "Attempt to close BarcodeDetector failed");
|
||||||
|
}
|
||||||
|
mBarcodeDetector = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createBarcodeDetector() {
|
||||||
|
FirebaseVisionBarcodeDetectorOptions options = mBuilder.build();
|
||||||
|
mBarcodeDetector = FirebaseVision.getInstance()
|
||||||
|
.getVisionBarcodeDetector(options);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,354 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
//import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.google.android.gms.tasks.OnFailureListener;
|
||||||
|
import com.google.android.gms.tasks.OnSuccessListener;
|
||||||
|
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode;
|
||||||
|
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetector;
|
||||||
|
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
|
||||||
|
import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata;
|
||||||
|
|
||||||
|
import org.reactnative.barcodedetector.BarcodeFormatUtils;
|
||||||
|
import org.reactnative.barcodedetector.RNBarcodeDetector;
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BarcodeDetectorAsyncTask extends android.os.AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private byte[] mImageData;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private int mRotation;
|
||||||
|
private RNBarcodeDetector mBarcodeDetector;
|
||||||
|
private BarcodeDetectorAsyncTaskDelegate mDelegate;
|
||||||
|
private double mScaleX;
|
||||||
|
private double mScaleY;
|
||||||
|
private ImageDimensions mImageDimensions;
|
||||||
|
private int mPaddingLeft;
|
||||||
|
private int mPaddingTop;
|
||||||
|
private String TAG = "RNCamera";
|
||||||
|
|
||||||
|
public BarcodeDetectorAsyncTask(
|
||||||
|
BarcodeDetectorAsyncTaskDelegate delegate,
|
||||||
|
RNBarcodeDetector barcodeDetector,
|
||||||
|
byte[] imageData,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rotation,
|
||||||
|
float density,
|
||||||
|
int facing,
|
||||||
|
int viewWidth,
|
||||||
|
int viewHeight,
|
||||||
|
int viewPaddingLeft,
|
||||||
|
int viewPaddingTop
|
||||||
|
) {
|
||||||
|
mImageData = imageData;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
mRotation = rotation;
|
||||||
|
mDelegate = delegate;
|
||||||
|
mBarcodeDetector = barcodeDetector;
|
||||||
|
mImageDimensions = new ImageDimensions(width, height, rotation, facing);
|
||||||
|
mScaleX = (double) (viewWidth) / (mImageDimensions.getWidth() * density);
|
||||||
|
mScaleY = 1 / density;
|
||||||
|
mPaddingLeft = viewPaddingLeft;
|
||||||
|
mPaddingTop = viewPaddingTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... ignored) {
|
||||||
|
if (isCancelled() || mDelegate == null || mBarcodeDetector == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
|
||||||
|
.setWidth(mWidth)
|
||||||
|
.setHeight(mHeight)
|
||||||
|
.setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_YV12)
|
||||||
|
.setRotation(getFirebaseRotation())
|
||||||
|
.build();
|
||||||
|
FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(mImageData, metadata);
|
||||||
|
|
||||||
|
FirebaseVisionBarcodeDetector barcode = mBarcodeDetector.getDetector();
|
||||||
|
barcode.detectInImage(image)
|
||||||
|
.addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
|
||||||
|
WritableArray serializedBarcodes = serializeEventData(barcodes);
|
||||||
|
mDelegate.onBarcodesDetected(serializedBarcodes);
|
||||||
|
mDelegate.onBarcodeDetectingTaskCompleted();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addOnFailureListener(new OnFailureListener() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
Log.e(TAG, "Text recognition task failed" + e);
|
||||||
|
mDelegate.onBarcodeDetectingTaskCompleted();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getFirebaseRotation(){
|
||||||
|
int result;
|
||||||
|
switch (mRotation) {
|
||||||
|
case 0:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_0;
|
||||||
|
break;
|
||||||
|
case 90:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_90;
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_180;
|
||||||
|
break;
|
||||||
|
case -90:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_270;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_0;
|
||||||
|
Log.e(TAG, "Bad rotation value: " + mRotation);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private WritableArray serializeEventData(List<FirebaseVisionBarcode> barcodes) {
|
||||||
|
WritableArray barcodesList = Arguments.createArray();
|
||||||
|
|
||||||
|
for (FirebaseVisionBarcode barcode: barcodes) {
|
||||||
|
// TODO implement position and data from all barcode types
|
||||||
|
Rect bounds = barcode.getBoundingBox();
|
||||||
|
// Point[] corners = barcode.getCornerPoints();
|
||||||
|
|
||||||
|
String rawValue = barcode.getRawValue();
|
||||||
|
|
||||||
|
int valueType = barcode.getValueType();
|
||||||
|
int valueFormat = barcode.getFormat();
|
||||||
|
|
||||||
|
WritableMap serializedBarcode = Arguments.createMap();
|
||||||
|
|
||||||
|
switch (valueType) {
|
||||||
|
case FirebaseVisionBarcode.TYPE_WIFI:
|
||||||
|
String ssid = barcode.getWifi().getSsid();
|
||||||
|
String password = barcode.getWifi().getPassword();
|
||||||
|
int type = barcode.getWifi().getEncryptionType();
|
||||||
|
String typeString = "UNKNOWN";
|
||||||
|
switch (type) {
|
||||||
|
case FirebaseVisionBarcode.WiFi.TYPE_OPEN:
|
||||||
|
typeString = "Open";
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.WiFi.TYPE_WEP:
|
||||||
|
typeString = "WEP";
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.WiFi.TYPE_WPA:
|
||||||
|
typeString = "WPA";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
serializedBarcode.putString("encryptionType", typeString);
|
||||||
|
serializedBarcode.putString("password", password);
|
||||||
|
serializedBarcode.putString("ssid", ssid);
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.TYPE_URL:
|
||||||
|
String title = barcode.getUrl().getTitle();
|
||||||
|
String url = barcode.getUrl().getUrl();
|
||||||
|
serializedBarcode.putString("url", url);
|
||||||
|
serializedBarcode.putString("title", title);
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.TYPE_SMS:
|
||||||
|
String message = barcode.getSms().getMessage();
|
||||||
|
String phoneNumber = barcode.getSms().getPhoneNumber();
|
||||||
|
serializedBarcode.putString("message", message);
|
||||||
|
serializedBarcode.putString("title", phoneNumber);
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.TYPE_PHONE:
|
||||||
|
String number = barcode.getPhone().getNumber();
|
||||||
|
int typePhone = barcode.getPhone().getType();
|
||||||
|
serializedBarcode.putString("number", number);
|
||||||
|
String typeStringPhone = getPhoneType(typePhone);
|
||||||
|
serializedBarcode.putString("phoneType", typeStringPhone);
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.TYPE_CALENDAR_EVENT:
|
||||||
|
serializedBarcode.putString("description", barcode.getCalendarEvent().getDescription());
|
||||||
|
serializedBarcode.putString("location", barcode.getCalendarEvent().getLocation());
|
||||||
|
serializedBarcode.putString("organizer", barcode.getCalendarEvent().getOrganizer());
|
||||||
|
serializedBarcode.putString("status", barcode.getCalendarEvent().getStatus());
|
||||||
|
serializedBarcode.putString("summary", barcode.getCalendarEvent().getSummary());
|
||||||
|
FirebaseVisionBarcode.CalendarDateTime start = barcode.getCalendarEvent().getStart();
|
||||||
|
FirebaseVisionBarcode.CalendarDateTime end = barcode.getCalendarEvent().getEnd();
|
||||||
|
if (start != null) {
|
||||||
|
serializedBarcode.putString("start", start.getRawValue());
|
||||||
|
}
|
||||||
|
if (end != null) {
|
||||||
|
serializedBarcode.putString("end", start.getRawValue());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.TYPE_DRIVER_LICENSE:
|
||||||
|
serializedBarcode.putString("addressCity", barcode.getDriverLicense().getAddressCity());
|
||||||
|
serializedBarcode.putString("addressState", barcode.getDriverLicense().getAddressState());
|
||||||
|
serializedBarcode.putString("addressStreet", barcode.getDriverLicense().getAddressStreet());
|
||||||
|
serializedBarcode.putString("addressZip", barcode.getDriverLicense().getAddressZip());
|
||||||
|
serializedBarcode.putString("birthDate", barcode.getDriverLicense().getBirthDate());
|
||||||
|
serializedBarcode.putString("documentType", barcode.getDriverLicense().getDocumentType());
|
||||||
|
serializedBarcode.putString("expiryDate", barcode.getDriverLicense().getExpiryDate());
|
||||||
|
serializedBarcode.putString("firstName", barcode.getDriverLicense().getFirstName());
|
||||||
|
serializedBarcode.putString("middleName", barcode.getDriverLicense().getMiddleName());
|
||||||
|
serializedBarcode.putString("lastName", barcode.getDriverLicense().getLastName());
|
||||||
|
serializedBarcode.putString("gender", barcode.getDriverLicense().getGender());
|
||||||
|
serializedBarcode.putString("issueDate", barcode.getDriverLicense().getIssueDate());
|
||||||
|
serializedBarcode.putString("issuingCountry", barcode.getDriverLicense().getIssuingCountry());
|
||||||
|
serializedBarcode.putString("licenseNumber", barcode.getDriverLicense().getLicenseNumber());
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.TYPE_GEO:
|
||||||
|
serializedBarcode.putDouble("latitude", barcode.getGeoPoint().getLat());
|
||||||
|
serializedBarcode.putDouble("longitude", barcode.getGeoPoint().getLng());
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.TYPE_CONTACT_INFO:
|
||||||
|
serializedBarcode.putString("organization", barcode.getContactInfo().getOrganization());
|
||||||
|
serializedBarcode.putString("title", barcode.getContactInfo().getTitle());
|
||||||
|
FirebaseVisionBarcode.PersonName name = barcode.getContactInfo().getName();
|
||||||
|
if (name != null) {
|
||||||
|
serializedBarcode.putString("firstName", name.getFirst());
|
||||||
|
serializedBarcode.putString("lastName", name.getLast());
|
||||||
|
serializedBarcode.putString("middleName", name.getMiddle());
|
||||||
|
serializedBarcode.putString("formattedName", name.getFormattedName());
|
||||||
|
serializedBarcode.putString("prefix", name.getPrefix());
|
||||||
|
serializedBarcode.putString("pronunciation", name.getPronunciation());
|
||||||
|
serializedBarcode.putString("suffix", name.getSuffix());
|
||||||
|
}
|
||||||
|
List<FirebaseVisionBarcode.Phone> phones = barcode.getContactInfo().getPhones();
|
||||||
|
WritableArray phonesList = Arguments.createArray();
|
||||||
|
for (FirebaseVisionBarcode.Phone phone : phones) {
|
||||||
|
WritableMap phoneObject = Arguments.createMap();
|
||||||
|
phoneObject.putString("number", phone.getNumber());
|
||||||
|
phoneObject.putString("phoneType", getPhoneType(phone.getType()));
|
||||||
|
phonesList.pushMap(phoneObject);
|
||||||
|
}
|
||||||
|
serializedBarcode.putArray("phones", phonesList);
|
||||||
|
List<FirebaseVisionBarcode.Address> addresses = barcode.getContactInfo().getAddresses();
|
||||||
|
WritableArray addressesList = Arguments.createArray();
|
||||||
|
for (FirebaseVisionBarcode.Address address : addresses) {
|
||||||
|
WritableMap addressesData = Arguments.createMap();
|
||||||
|
WritableArray addressesLinesList = Arguments.createArray();
|
||||||
|
String[] addressesLines = address.getAddressLines();
|
||||||
|
for (String line : addressesLines) {
|
||||||
|
addressesLinesList.pushString(line);
|
||||||
|
}
|
||||||
|
addressesData.putArray("addressLines", addressesLinesList);
|
||||||
|
|
||||||
|
int addressType = address.getType();
|
||||||
|
String addressTypeString = "UNKNOWN";
|
||||||
|
switch(addressType) {
|
||||||
|
case FirebaseVisionBarcode.Address.TYPE_WORK:
|
||||||
|
addressTypeString = "Work";
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.Address.TYPE_HOME:
|
||||||
|
addressTypeString = "Home";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
addressesData.putString("addressType", addressTypeString);
|
||||||
|
addressesList.pushMap(addressesData);
|
||||||
|
}
|
||||||
|
serializedBarcode.putArray("addresses", addressesList);
|
||||||
|
List<FirebaseVisionBarcode.Email> emails = barcode.getContactInfo().getEmails();
|
||||||
|
WritableArray emailsList = Arguments.createArray();
|
||||||
|
for (FirebaseVisionBarcode.Email email : emails) {
|
||||||
|
WritableMap emailData = processEmail(email);
|
||||||
|
emailsList.pushMap(emailData);
|
||||||
|
}
|
||||||
|
serializedBarcode.putArray("emails", emailsList);
|
||||||
|
String[] urls = barcode.getContactInfo().getUrls();
|
||||||
|
WritableArray urlsList = Arguments.createArray();
|
||||||
|
for (String urlContact : urls) {
|
||||||
|
urlsList.pushString(urlContact);
|
||||||
|
}
|
||||||
|
serializedBarcode.putArray("urls", urlsList);
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.TYPE_EMAIL:
|
||||||
|
WritableMap emailData = processEmail(barcode.getEmail());
|
||||||
|
serializedBarcode.putMap("email", emailData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedBarcode.putString("data", barcode.getDisplayValue());
|
||||||
|
serializedBarcode.putString("dataRaw", rawValue);
|
||||||
|
serializedBarcode.putString("type", BarcodeFormatUtils.get(valueType));
|
||||||
|
serializedBarcode.putString("format", BarcodeFormatUtils.getFormat(valueFormat));
|
||||||
|
serializedBarcode.putMap("bounds", processBounds(bounds));
|
||||||
|
barcodesList.pushMap(serializedBarcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return barcodesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap processEmail(FirebaseVisionBarcode.Email email) {
|
||||||
|
WritableMap emailData = Arguments.createMap();
|
||||||
|
emailData.putString("address", email.getAddress());
|
||||||
|
emailData.putString("body", email.getBody());
|
||||||
|
emailData.putString("subject", email.getSubject());
|
||||||
|
int emailType = email.getType();
|
||||||
|
String emailTypeString = "UNKNOWN";
|
||||||
|
switch (emailType) {
|
||||||
|
case FirebaseVisionBarcode.Email.TYPE_WORK:
|
||||||
|
emailTypeString = "Work";
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.Email.TYPE_HOME:
|
||||||
|
emailTypeString = "Home";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
emailData.putString("emailType", emailTypeString);
|
||||||
|
return emailData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPhoneType(int typePhone) {
|
||||||
|
String typeStringPhone = "UNKNOWN";
|
||||||
|
switch(typePhone) {
|
||||||
|
case FirebaseVisionBarcode.Phone.TYPE_WORK:
|
||||||
|
typeStringPhone = "Work";
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.Phone.TYPE_HOME:
|
||||||
|
typeStringPhone = "Home";
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.Phone.TYPE_FAX:
|
||||||
|
typeStringPhone = "Fax";
|
||||||
|
break;
|
||||||
|
case FirebaseVisionBarcode.Phone.TYPE_MOBILE:
|
||||||
|
typeStringPhone = "Mobile";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return typeStringPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap processBounds(Rect frame) {
|
||||||
|
WritableMap origin = Arguments.createMap();
|
||||||
|
int x = frame.left;
|
||||||
|
int y = frame.top;
|
||||||
|
|
||||||
|
if (frame.left < mWidth / 2) {
|
||||||
|
x = x + mPaddingLeft / 2;
|
||||||
|
} else if (frame.left > mWidth /2) {
|
||||||
|
x = x - mPaddingLeft / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
y = y + mPaddingTop;
|
||||||
|
|
||||||
|
origin.putDouble("x", x * mScaleX);
|
||||||
|
origin.putDouble("y", y * mScaleY);
|
||||||
|
|
||||||
|
WritableMap size = Arguments.createMap();
|
||||||
|
size.putDouble("width", frame.width() * mScaleX);
|
||||||
|
size.putDouble("height", frame.height() * mScaleY);
|
||||||
|
|
||||||
|
WritableMap bounds = Arguments.createMap();
|
||||||
|
bounds.putMap("origin", origin);
|
||||||
|
bounds.putMap("size", size);
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.google.android.cameraview.CameraView;
|
||||||
|
import com.google.android.gms.tasks.OnFailureListener;
|
||||||
|
import com.google.android.gms.tasks.OnSuccessListener;
|
||||||
|
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
|
||||||
|
import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata;
|
||||||
|
import com.google.firebase.ml.vision.face.FirebaseVisionFace;
|
||||||
|
import com.google.firebase.ml.vision.face.FirebaseVisionFaceDetector;
|
||||||
|
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
import org.reactnative.facedetector.FaceDetectorUtils;
|
||||||
|
import org.reactnative.facedetector.RNFaceDetector;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FaceDetectorAsyncTask extends android.os.AsyncTask<Void, Void, Void> {
|
||||||
|
private byte[] mImageData;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private int mRotation;
|
||||||
|
private RNFaceDetector mFaceDetector;
|
||||||
|
private FaceDetectorAsyncTaskDelegate mDelegate;
|
||||||
|
private double mScaleX;
|
||||||
|
private double mScaleY;
|
||||||
|
private ImageDimensions mImageDimensions;
|
||||||
|
private int mPaddingLeft;
|
||||||
|
private int mPaddingTop;
|
||||||
|
private String TAG = "RNCamera";
|
||||||
|
|
||||||
|
public FaceDetectorAsyncTask(
|
||||||
|
FaceDetectorAsyncTaskDelegate delegate,
|
||||||
|
RNFaceDetector faceDetector,
|
||||||
|
byte[] imageData,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rotation,
|
||||||
|
float density,
|
||||||
|
int facing,
|
||||||
|
int viewWidth,
|
||||||
|
int viewHeight,
|
||||||
|
int viewPaddingLeft,
|
||||||
|
int viewPaddingTop
|
||||||
|
) {
|
||||||
|
mImageData = imageData;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
mRotation = rotation;
|
||||||
|
mDelegate = delegate;
|
||||||
|
mFaceDetector = faceDetector;
|
||||||
|
mImageDimensions = new ImageDimensions(width, height, rotation, facing);
|
||||||
|
mScaleX = (double) (viewWidth) / (mImageDimensions.getWidth() * density);
|
||||||
|
mScaleY = (double) (viewHeight) / (mImageDimensions.getHeight() * density);
|
||||||
|
mPaddingLeft = viewPaddingLeft;
|
||||||
|
mPaddingTop = viewPaddingTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... ignored) {
|
||||||
|
if (isCancelled() || mDelegate == null || mFaceDetector == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
|
||||||
|
.setWidth(mWidth)
|
||||||
|
.setHeight(mHeight)
|
||||||
|
.setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_YV12)
|
||||||
|
.setRotation(getFirebaseRotation())
|
||||||
|
.build();
|
||||||
|
FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(mImageData, metadata);
|
||||||
|
|
||||||
|
FirebaseVisionFaceDetector detector = mFaceDetector.getDetector();
|
||||||
|
detector.detectInImage(image)
|
||||||
|
.addOnSuccessListener(
|
||||||
|
new OnSuccessListener<List<FirebaseVisionFace>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<FirebaseVisionFace> faces) {
|
||||||
|
WritableArray facesList = serializeEventData(faces);
|
||||||
|
mDelegate.onFacesDetected(facesList);
|
||||||
|
mDelegate.onFaceDetectingTaskCompleted();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addOnFailureListener(
|
||||||
|
new OnFailureListener() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
Log.e(TAG, "Text recognition task failed" + e);
|
||||||
|
mDelegate.onFaceDetectingTaskCompleted();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getFirebaseRotation(){
|
||||||
|
int result;
|
||||||
|
switch (mRotation) {
|
||||||
|
case 0:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_0;
|
||||||
|
break;
|
||||||
|
case 90:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_90;
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_180;
|
||||||
|
break;
|
||||||
|
case 270:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_270;
|
||||||
|
break;
|
||||||
|
case -90:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_270;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_0;
|
||||||
|
Log.e(TAG, "Bad rotation value: " + mRotation);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableArray serializeEventData(List<FirebaseVisionFace> faces) {
|
||||||
|
WritableArray facesList = Arguments.createArray();
|
||||||
|
|
||||||
|
for (FirebaseVisionFace face : faces) {
|
||||||
|
WritableMap serializedFace = FaceDetectorUtils.serializeFace(face, mScaleX, mScaleY, mWidth, mHeight, mPaddingLeft, mPaddingTop);
|
||||||
|
if (mImageDimensions.getFacing() == CameraView.FACING_FRONT) {
|
||||||
|
serializedFace = FaceDetectorUtils.rotateFaceX(serializedFace, mImageDimensions.getWidth(), mScaleX);
|
||||||
|
} else {
|
||||||
|
serializedFace = FaceDetectorUtils.changeAnglesDirection(serializedFace);
|
||||||
|
}
|
||||||
|
facesList.pushMap(serializedFace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return facesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
package org.reactnative.camera.tasks;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
|
|
||||||
|
import com.google.android.cameraview.CameraView;
|
||||||
|
import com.google.android.gms.tasks.OnFailureListener;
|
||||||
|
import com.google.android.gms.tasks.OnSuccessListener;
|
||||||
|
import com.google.firebase.ml.vision.FirebaseVision;
|
||||||
|
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
|
||||||
|
import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata;
|
||||||
|
import com.google.firebase.ml.vision.text.FirebaseVisionText;
|
||||||
|
import com.google.firebase.ml.vision.text.FirebaseVisionTextRecognizer;
|
||||||
|
|
||||||
|
import org.reactnative.camera.utils.ImageDimensions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class TextRecognizerAsyncTask extends android.os.AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private TextRecognizerAsyncTaskDelegate mDelegate;
|
||||||
|
private ThemedReactContext mThemedReactContext;
|
||||||
|
private byte[] mImageData;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private int mRotation;
|
||||||
|
private double mScaleX;
|
||||||
|
private double mScaleY;
|
||||||
|
private ImageDimensions mImageDimensions;
|
||||||
|
private int mPaddingLeft;
|
||||||
|
private int mPaddingTop;
|
||||||
|
private String TAG = "RNCamera";
|
||||||
|
|
||||||
|
public TextRecognizerAsyncTask(
|
||||||
|
TextRecognizerAsyncTaskDelegate delegate,
|
||||||
|
ThemedReactContext themedReactContext,
|
||||||
|
byte[] imageData,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rotation,
|
||||||
|
float density,
|
||||||
|
int facing,
|
||||||
|
int viewWidth,
|
||||||
|
int viewHeight,
|
||||||
|
int viewPaddingLeft,
|
||||||
|
int viewPaddingTop
|
||||||
|
) {
|
||||||
|
mDelegate = delegate;
|
||||||
|
mImageData = imageData;
|
||||||
|
mWidth = width;
|
||||||
|
mHeight = height;
|
||||||
|
mRotation = rotation;
|
||||||
|
mImageDimensions = new ImageDimensions(width, height, rotation, facing);
|
||||||
|
mScaleX = (double) (viewWidth) / (mImageDimensions.getWidth() * density);
|
||||||
|
mScaleY = (double) (viewHeight) / (mImageDimensions.getHeight() * density);
|
||||||
|
mPaddingLeft = viewPaddingLeft;
|
||||||
|
mPaddingTop = viewPaddingTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... ignored) {
|
||||||
|
if (isCancelled() || mDelegate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
|
||||||
|
.setWidth(mWidth)
|
||||||
|
.setHeight(mHeight)
|
||||||
|
.setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_YV12)
|
||||||
|
.setRotation(getFirebaseRotation())
|
||||||
|
.build();
|
||||||
|
FirebaseVisionTextRecognizer detector = FirebaseVision.getInstance().getOnDeviceTextRecognizer();
|
||||||
|
|
||||||
|
FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(mImageData, metadata);
|
||||||
|
detector.processImage(image)
|
||||||
|
.addOnSuccessListener(new OnSuccessListener<FirebaseVisionText>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(FirebaseVisionText firebaseVisionText) {
|
||||||
|
List<FirebaseVisionText.TextBlock> textBlocks = firebaseVisionText.getTextBlocks();
|
||||||
|
WritableArray serializedData = serializeEventData(textBlocks);
|
||||||
|
mDelegate.onTextRecognized(serializedData);
|
||||||
|
mDelegate.onTextRecognizerTaskCompleted();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addOnFailureListener(
|
||||||
|
new OnFailureListener() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
Log.e(TAG, "Text recognition task failed" + e);
|
||||||
|
mDelegate.onTextRecognizerTaskCompleted();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getFirebaseRotation(){
|
||||||
|
int result;
|
||||||
|
switch (mRotation) {
|
||||||
|
case 0:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_0;
|
||||||
|
break;
|
||||||
|
case 90:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_90;
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_180;
|
||||||
|
break;
|
||||||
|
case -90:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_270;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = FirebaseVisionImageMetadata.ROTATION_0;
|
||||||
|
Log.e(TAG, "Bad rotation value: " + mRotation);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableArray serializeEventData(List<FirebaseVisionText.TextBlock> textBlocks) {
|
||||||
|
WritableArray textBlocksList = Arguments.createArray();
|
||||||
|
for (FirebaseVisionText.TextBlock block: textBlocks) {
|
||||||
|
WritableMap serializedTextBlock = serializeBloc(block);
|
||||||
|
if (mImageDimensions.getFacing() == CameraView.FACING_FRONT) {
|
||||||
|
serializedTextBlock = rotateTextX(serializedTextBlock);
|
||||||
|
}
|
||||||
|
textBlocksList.pushMap(serializedTextBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textBlocksList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeBloc(FirebaseVisionText.TextBlock block) {
|
||||||
|
WritableMap encodedText = Arguments.createMap();
|
||||||
|
WritableArray lines = Arguments.createArray();
|
||||||
|
for (FirebaseVisionText.Line line : block.getLines()) {
|
||||||
|
lines.pushMap(serializeLine(line));
|
||||||
|
}
|
||||||
|
encodedText.putArray("components", lines);
|
||||||
|
|
||||||
|
encodedText.putString("value", block.getText());
|
||||||
|
|
||||||
|
WritableMap bounds = processBounds(block.getBoundingBox());
|
||||||
|
|
||||||
|
encodedText.putMap("bounds", bounds);
|
||||||
|
|
||||||
|
encodedText.putString("type", "block");
|
||||||
|
return encodedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeLine(FirebaseVisionText.Line line) {
|
||||||
|
WritableMap encodedText = Arguments.createMap();
|
||||||
|
WritableArray lines = Arguments.createArray();
|
||||||
|
for (FirebaseVisionText.Element element : line.getElements()) {
|
||||||
|
lines.pushMap(serializeElement(element));
|
||||||
|
}
|
||||||
|
encodedText.putArray("components", lines);
|
||||||
|
|
||||||
|
encodedText.putString("value", line.getText());
|
||||||
|
|
||||||
|
WritableMap bounds = processBounds(line.getBoundingBox());
|
||||||
|
|
||||||
|
encodedText.putMap("bounds", bounds);
|
||||||
|
|
||||||
|
encodedText.putString("type", "line");
|
||||||
|
return encodedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap serializeElement(FirebaseVisionText.Element element) {
|
||||||
|
WritableMap encodedText = Arguments.createMap();
|
||||||
|
|
||||||
|
encodedText.putString("value", element.getText());
|
||||||
|
|
||||||
|
WritableMap bounds = processBounds(element.getBoundingBox());
|
||||||
|
|
||||||
|
encodedText.putMap("bounds", bounds);
|
||||||
|
|
||||||
|
encodedText.putString("type", "element");
|
||||||
|
return encodedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap processBounds(Rect frame) {
|
||||||
|
WritableMap origin = Arguments.createMap();
|
||||||
|
int x = frame.left;
|
||||||
|
int y = frame.top;
|
||||||
|
|
||||||
|
if (frame.left < mWidth / 2) {
|
||||||
|
x = x + mPaddingLeft / 2;
|
||||||
|
} else if (frame.left > mWidth /2) {
|
||||||
|
x = x - mPaddingLeft / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.top < mHeight / 2) {
|
||||||
|
y = y + mPaddingTop / 2;
|
||||||
|
} else if (frame.top > mHeight / 2) {
|
||||||
|
y = y - mPaddingTop / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
origin.putDouble("x", x * mScaleX);
|
||||||
|
origin.putDouble("y", y * mScaleY);
|
||||||
|
|
||||||
|
WritableMap size = Arguments.createMap();
|
||||||
|
size.putDouble("width", frame.width() * mScaleX);
|
||||||
|
size.putDouble("height", frame.height() * mScaleY);
|
||||||
|
|
||||||
|
WritableMap bounds = Arguments.createMap();
|
||||||
|
bounds.putMap("origin", origin);
|
||||||
|
bounds.putMap("size", size);
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WritableMap rotateTextX(WritableMap text) {
|
||||||
|
ReadableMap faceBounds = text.getMap("bounds");
|
||||||
|
|
||||||
|
ReadableMap oldOrigin = faceBounds.getMap("origin");
|
||||||
|
WritableMap mirroredOrigin = positionMirroredHorizontally(
|
||||||
|
oldOrigin, mImageDimensions.getWidth(), mScaleX);
|
||||||
|
|
||||||
|
double translateX = -faceBounds.getMap("size").getDouble("width");
|
||||||
|
WritableMap translatedMirroredOrigin = positionTranslatedHorizontally(mirroredOrigin, translateX);
|
||||||
|
|
||||||
|
WritableMap newBounds = Arguments.createMap();
|
||||||
|
newBounds.merge(faceBounds);
|
||||||
|
newBounds.putMap("origin", translatedMirroredOrigin);
|
||||||
|
|
||||||
|
text.putMap("bounds", newBounds);
|
||||||
|
|
||||||
|
ReadableArray oldComponents = text.getArray("components");
|
||||||
|
WritableArray newComponents = Arguments.createArray();
|
||||||
|
for (int i = 0; i < oldComponents.size(); ++i) {
|
||||||
|
WritableMap component = Arguments.createMap();
|
||||||
|
component.merge(oldComponents.getMap(i));
|
||||||
|
rotateTextX(component);
|
||||||
|
newComponents.pushMap(component);
|
||||||
|
}
|
||||||
|
text.putArray("components", newComponents);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap positionTranslatedHorizontally(ReadableMap position, double translateX) {
|
||||||
|
WritableMap newPosition = Arguments.createMap();
|
||||||
|
newPosition.merge(position);
|
||||||
|
newPosition.putDouble("x", position.getDouble("x") + translateX);
|
||||||
|
return newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WritableMap positionMirroredHorizontally(ReadableMap position, int containerWidth, double scaleX) {
|
||||||
|
WritableMap newPosition = Arguments.createMap();
|
||||||
|
newPosition.merge(position);
|
||||||
|
newPosition.putDouble("x", valueMirroredHorizontally(position.getDouble("x"), containerWidth, scaleX));
|
||||||
|
return newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double valueMirroredHorizontally(double elementX, int containerWidth, double scaleX) {
|
||||||
|
double originalX = elementX / scaleX;
|
||||||
|
double mirroredX = containerWidth - originalX;
|
||||||
|
return mirroredX * scaleX;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user