Compare commits
No commits in common. "gh-pages" and "master" have entirely different histories.
103
.circleci/config.yml
Normal file
103
.circleci/config.yml
Normal file
@ -0,0 +1,103 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
react-native: react-native-community/react-native@4.4.0
|
||||
|
||||
commands:
|
||||
checkout-attach-workspace:
|
||||
description: "Checkout and attach workspace"
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
|
||||
jobs:
|
||||
install:
|
||||
executor: react-native/linux_js
|
||||
steps:
|
||||
- checkout-attach-workspace
|
||||
- react-native/yarn_install
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
lint:
|
||||
executor: react-native/linux_android
|
||||
steps:
|
||||
- checkout-attach-workspace
|
||||
- run:
|
||||
name: Lint
|
||||
command: yarn lint
|
||||
|
||||
build-app:
|
||||
executor: react-native/linux_android
|
||||
steps:
|
||||
- checkout-attach-workspace
|
||||
- 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
|
||||
- react-native/android_build:
|
||||
project_path: ./examples/basic/android
|
||||
|
||||
deploy-docs:
|
||||
executor: react-native/linux_js
|
||||
steps:
|
||||
- checkout-attach-workspace
|
||||
- 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
|
||||
|
||||
publish-version:
|
||||
executor: react-native/linux_js
|
||||
steps:
|
||||
- checkout-attach-workspace
|
||||
- run:
|
||||
name: Run semantic-release
|
||||
command: yarn ci:publish
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
develop:
|
||||
jobs:
|
||||
- install:
|
||||
filters:
|
||||
branches:
|
||||
ignore: master
|
||||
|
||||
- lint:
|
||||
requires:
|
||||
- install
|
||||
|
||||
- build-app:
|
||||
requires:
|
||||
- install
|
||||
|
||||
release:
|
||||
jobs:
|
||||
- install:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
|
||||
- deploy-docs:
|
||||
requires:
|
||||
- install
|
||||
|
||||
- publish-version:
|
||||
requires:
|
||||
- install
|
||||
|
||||
|
||||
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
*/node_modules
|
||||
*.log
|
||||
1
.eslintIgnore
Normal file
1
.eslintIgnore
Normal file
@ -0,0 +1 @@
|
||||
tests
|
||||
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
|
||||
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
# 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
|
||||
|
||||
!debug.keystore
|
||||
/ios/Pods/
|
||||
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.
|
||||
|
||||
149
README.md
Normal file
149
README.md
Normal file
@ -0,0 +1,149 @@
|
||||
# React Native Camera [](#backers) [](#sponsors) [](http://badge.fury.io/js/react-native-camera) [](https://www.npmjs.com/package/react-native-camera)
|
||||
|
||||
[Looking for Maintainers](https://github.com/react-native-community/react-native-camera/issues/3000)
|
||||
|
||||
We are looking for maintainers for this package, or to deprecated this in favor of expo-camera, it nobody want to maintain this
|
||||
|
||||
## Docs
|
||||
Follow our docs here [https://react-native-camera.github.io/react-native-camera/](https://react-native-camera.github.io/react-native-camera/)
|
||||
|
||||
## Sponsors
|
||||
|
||||
If you use this library on your commercial/personal projects, you can help us by funding the work on specific issues that you choose by using IssueHunt.io!
|
||||
|
||||
This gives you the power to prioritize our work and support the project contributors. Moreover it'll guarantee the project will be updated and maintained in the long run.
|
||||
|
||||
[](https://issuehunt.io/repos/33218414)
|
||||
|
||||
## react-native-camera for enterprise
|
||||
|
||||
Available as part of the Tidelift Subscription
|
||||
|
||||
The maintainers of react-native-camera and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-react-native-camera?utm_source=npm-react-native-camera&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
|
||||
|
||||
## Open Collective
|
||||
|
||||
You can also fund this project using open collective
|
||||
|
||||
### Backers
|
||||
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/react-native-camera#backer)]
|
||||
|
||||
<a href="https://opencollective.com/react-native-camera/backer/0/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/1/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/2/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/3/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/4/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/5/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/6/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/7/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/8/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/9/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/10/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/11/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/12/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/13/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/14/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/15/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/16/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/17/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/18/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/19/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/20/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/21/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/22/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/23/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/24/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/25/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/26/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/27/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/28/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/backer/29/website" target="_blank"><img src="https://opencollective.com/react-native-camera/backer/29/avatar.svg"></a>
|
||||
|
||||
### Sponsors
|
||||
|
||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/react-native-camera#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/0/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/1/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/2/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/3/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/4/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/5/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/6/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/7/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/8/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/react-native-camera/sponsor/9/website" target="_blank"><img src="https://opencollective.com/react-native-camera/sponsor/9/avatar.svg"></a>
|
||||
|
||||
The comprehensive camera module for React Native.
|
||||
|
||||
Supports:
|
||||
|
||||
- photographs.
|
||||
- videos
|
||||
- face detection (Android & iOS only)
|
||||
- barcode scanning
|
||||
- text recognition (optional installation for iOS using CocoaPods)
|
||||
|
||||
### Example import
|
||||
|
||||
```jsx
|
||||
import { RNCamera, FaceDetector } from 'react-native-camera';
|
||||
```
|
||||
|
||||
#### How to use master branch?
|
||||
|
||||
We recommend using the releases from npm, however if you need some features that are not published on npm yet you can install react-native-camera from git.
|
||||
|
||||
**yarn**: `yarn add react-native-camera@git+https://git@github.com/react-native-community/react-native-camera.git`
|
||||
|
||||
**npm**: `npm install --save react-native-camera@git+https://git@github.com/react-native-community/react-native-camera.git`
|
||||
|
||||
### Contributing
|
||||
|
||||
- Pull Requests are welcome, if you open a pull request we will do our best to get to it in a timely manner
|
||||
- Pull Request Reviews are even more welcome! we need help testing, reviewing, and updating open PRs
|
||||
- If you are interested in contributing more actively, please contact me (same username on Twitter, Facebook, etc.) Thanks!
|
||||
- We are now on [Open Collective](https://opencollective.com/react-native-camera#sponsor)! Contributions are appreciated and will be used to fund core contributors. [more details](#open-collective)
|
||||
- If you want to help us coding, join Expo slack https://slack.expo.io/, so we can chat over there. (#react-native-camera)
|
||||
|
||||
##### Permissions
|
||||
|
||||
To use the camera,
|
||||
|
||||
1) On Android you must ask for camera permission:
|
||||
|
||||
```java
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
```
|
||||
|
||||
To enable `video recording` feature you have to add the following code to the `AndroidManifest.xml`:
|
||||
|
||||
```java
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
```
|
||||
|
||||

|
||||
|
||||
2) On iOS, you must update Info.plist with a usage description for camera
|
||||
|
||||
```xml
|
||||
...
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Your own description of the purpose</string>
|
||||
...
|
||||
|
||||
```
|
||||
For more information on installation, please refer to [installation requirements](./docs/installation.md#requirements).
|
||||
|
||||
For general introduction, please take a look into this [RNCamera](./docs/RNCamera.md).
|
||||
|
||||
## Security contact information
|
||||
|
||||
To report a security vulnerability, please use the
|
||||
|
||||
[Tidelift security contact](https://tidelift.com/security).
|
||||
|
||||
Tidelift will coordinate the fix and disclosure.
|
||||
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.
|
||||
79
android/build.gradle
Normal file
79
android/build.gradle
Normal file
@ -0,0 +1,79 @@
|
||||
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.6.3")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
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'))
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation 'com.facebook.react:react-native:+' // From node_modules
|
||||
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.
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
188
android/gradlew
vendored
Executable file
188
android/gradlew
vendored
Executable file
@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# 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
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# 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
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
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" -a "$nonstop" = "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 or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
100
android/gradlew.bat
vendored
Normal file
100
android/gradlew.bat
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@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
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@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="-Xmx64m" "-Xms64m"
|
||||
|
||||
@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 Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_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=%*
|
||||
|
||||
: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,127 @@
|
||||
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), mWidth, mHeight, mImageData);
|
||||
}
|
||||
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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
1674
android/src/main/java/com/google/android/cameraview/Camera1.java
Normal file
1674
android/src/main/java/com/google/android/cameraview/Camera1.java
Normal file
File diff suppressed because it is too large
Load Diff
1588
android/src/main/java/com/google/android/cameraview/Camera2.java
Normal file
1588
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,884 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
public ArrayList<int[]> getSupportedPreviewFpsRange() {
|
||||
return mImpl.getSupportedPreviewFpsRange();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, int fps) {
|
||||
return mImpl.record(path, maxDuration, maxFileSize, recordAudio, profile, orientation, fps);
|
||||
}
|
||||
|
||||
public void stopRecording() {
|
||||
mImpl.stopRecording();
|
||||
}
|
||||
|
||||
public void pauseRecording() {
|
||||
mImpl.pauseRecording();
|
||||
}
|
||||
|
||||
public void resumeRecording() {
|
||||
mImpl.resumeRecording();
|
||||
}
|
||||
|
||||
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,169 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
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, int fps);
|
||||
|
||||
abstract void stopRecording();
|
||||
|
||||
abstract void pauseRecording();
|
||||
|
||||
abstract void resumeRecording();
|
||||
|
||||
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 public ArrayList<int[]> getSupportedPreviewFpsRange();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
495
android/src/main/java/org/reactnative/camera/CameraModule.java
Normal file
495
android/src/main/java/org/reactnative/camera/CameraModule.java
Normal file
@ -0,0 +1,495 @@
|
||||
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.ArrayList;
|
||||
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 pauseRecording(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.pauseRecording();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void resumeRecording(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.resumeRecording();
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getSupportedPreviewFpsRange(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();
|
||||
ArrayList<int[]> ranges = cameraView.getSupportedPreviewFpsRange();
|
||||
for (int[] range : ranges) {
|
||||
WritableMap m = new WritableNativeMap();
|
||||
m.putInt("MAXIMUM_FPS", range[0]);
|
||||
m.putInt("MINIMUM_FPS", range[1]);
|
||||
result.pushMap(m);
|
||||
}
|
||||
promise.resolve(result);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,241 @@
|
||||
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_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"),
|
||||
EVENT_ON_TOUCH("onTouch");
|
||||
|
||||
|
||||
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 = "useNativeZoom")
|
||||
public void setUseNativeZoom(RNCameraView view, boolean useNativeZoom) {
|
||||
view.setUseNativeZoom(useNativeZoom);
|
||||
}
|
||||
@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 = "detectedImageInEvent")
|
||||
public void setDetectedImageInEvent(RNCameraView view, boolean detectedImageInEvent) {
|
||||
view.setDetectedImageInEvent(detectedImageInEvent);
|
||||
}
|
||||
|
||||
@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 = "touchDetectorEnabled")
|
||||
public void setTouchDetectorEnabled(RNCameraView view, boolean touchDetectorEnabled) {
|
||||
view.setShouldDetectTouches(touchDetectorEnabled);
|
||||
}
|
||||
|
||||
@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()
|
||||
);
|
||||
}
|
||||
}
|
||||
710
android/src/main/java/org/reactnative/camera/RNCameraView.java
Normal file
710
android/src/main/java/org/reactnative/camera/RNCameraView.java
Normal file
@ -0,0 +1,710 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.YuvImage;
|
||||
import android.media.CamcorderProfile;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
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 mDetectedImageInEvent = false;
|
||||
|
||||
private ScaleGestureDetector mScaleGestureDetector;
|
||||
private GestureDetector mGestureDetector;
|
||||
|
||||
|
||||
private boolean mIsPaused = false;
|
||||
private boolean mIsNew = true;
|
||||
private boolean invertImageData = false;
|
||||
private Boolean mIsRecording = false;
|
||||
private Boolean mIsRecordingInterrupted = false;
|
||||
private boolean mUseNativeZoom=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 boolean mShouldDetectTouches = 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;
|
||||
if (!willCallBarCodeTask && !willCallFaceTask && !willCallGoogleBarcodeTask && !willCallTextTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length < (1.5 * width * height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 setDetectedImageInEvent(boolean detectedImageInEvent) {
|
||||
this.mDetectedImageInEvent = detectedImageInEvent;
|
||||
}
|
||||
|
||||
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;
|
||||
int fps = options.hasKey("fps") ? options.getInt("fps") : -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, fps)) {
|
||||
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, byte[] imageData) {
|
||||
String barCodeType = barCode.getBarcodeFormat().toString();
|
||||
if (!mShouldScanBarCodes || !mBarCodeTypes.contains(barCodeType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] compressedImage;
|
||||
if (mDetectedImageInEvent) {
|
||||
try {
|
||||
// https://stackoverflow.com/a/32793908/122441
|
||||
final YuvImage yuvImage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
|
||||
final ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
|
||||
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, imageStream);
|
||||
compressedImage = imageStream.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(String.format("Error decoding imageData from NV21 format (%d bytes)", imageData.length), e);
|
||||
}
|
||||
} else {
|
||||
compressedImage = null;
|
||||
}
|
||||
|
||||
RNCameraViewHelper.emitBarCodeReadEvent(this, barCode, width, height, compressedImage);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
public void setShouldDetectTouches(boolean shouldDetectTouches) {
|
||||
if(!mShouldDetectTouches && shouldDetectTouches){
|
||||
mGestureDetector=new GestureDetector(mThemedReactContext,onGestureListener);
|
||||
}else{
|
||||
mGestureDetector=null;
|
||||
}
|
||||
this.mShouldDetectTouches = shouldDetectTouches;
|
||||
}
|
||||
|
||||
public void setUseNativeZoom(boolean useNativeZoom){
|
||||
if(!mUseNativeZoom && useNativeZoom){
|
||||
mScaleGestureDetector = new ScaleGestureDetector(mThemedReactContext,onScaleGestureListener);
|
||||
}else{
|
||||
mScaleGestureDetector=null;
|
||||
}
|
||||
mUseNativeZoom=useNativeZoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if(mUseNativeZoom) {
|
||||
mScaleGestureDetector.onTouchEvent(event);
|
||||
}
|
||||
if(mShouldDetectTouches){
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, int width, int height, byte[] imageData) {
|
||||
if (!mShouldGoogleDetectBarcodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See discussion in https://github.com/react-native-community/react-native-camera/issues/2786
|
||||
final byte[] compressedImage;
|
||||
if (mDetectedImageInEvent) {
|
||||
try {
|
||||
// https://stackoverflow.com/a/32793908/122441
|
||||
final YuvImage yuvImage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
|
||||
final ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
|
||||
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, imageStream);
|
||||
compressedImage = imageStream.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(String.format("Error decoding imageData from NV21 format (%d bytes)", imageData.length), e);
|
||||
}
|
||||
} else {
|
||||
compressedImage = null;
|
||||
}
|
||||
|
||||
RNCameraViewHelper.emitBarcodesDetectedEvent(this, barcodesDetected, compressedImage);
|
||||
}
|
||||
|
||||
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 void onZoom(float scale){
|
||||
float currentZoom=getZoom();
|
||||
float nextZoom=currentZoom+(scale-1.0f);
|
||||
|
||||
if(nextZoom > currentZoom){
|
||||
setZoom(Math.min(nextZoom,1.0f));
|
||||
}else{
|
||||
setZoom(Math.max(nextZoom,0.0f));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
private int scalePosition(float raw){
|
||||
Resources resources = getResources();
|
||||
Configuration config = resources.getConfiguration();
|
||||
DisplayMetrics dm = resources.getDisplayMetrics();
|
||||
return (int)(raw/ dm.density);
|
||||
}
|
||||
private GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener(){
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
RNCameraViewHelper.emitTouchEvent(RNCameraView.this,false,scalePosition(e.getX()),scalePosition(e.getY()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
RNCameraViewHelper.emitTouchEvent(RNCameraView.this,true,scalePosition(e.getX()),scalePosition(e.getY()));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
private ScaleGestureDetector.OnScaleGestureListener onScaleGestureListener = new ScaleGestureDetector.OnScaleGestureListener() {
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
|
||||
onZoom(scaleGestureDetector.getScaleFactor());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
|
||||
onZoom(scaleGestureDetector.getScaleFactor());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@ -0,0 +1,474 @@
|
||||
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.view.ViewGroup;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Touch event
|
||||
public static void emitTouchEvent(final ViewGroup view, final boolean isDoubleTap, final int x, final int y) {
|
||||
|
||||
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TouchEvent event = TouchEvent.obtain(view.getId(), isDoubleTap, x, y);
|
||||
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 byte[] compressedImage) {
|
||||
|
||||
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BarcodesDetectedEvent event = BarcodesDetectedEvent.obtain(view.getId(), barcodes, compressedImage);
|
||||
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 byte[] compressedImage) {
|
||||
final ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.runOnNativeModulesQueueThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BarCodeReadEvent event = BarCodeReadEvent.obtain(view.getId(), barCode, width, height, compressedImage);
|
||||
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,107 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
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 byte[] mCompressedImage;
|
||||
|
||||
private BarCodeReadEvent() {}
|
||||
|
||||
public static BarCodeReadEvent obtain(int viewTag, Result barCode, int width, int height, byte[] compressedImage) {
|
||||
BarCodeReadEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new BarCodeReadEvent();
|
||||
}
|
||||
event.init(viewTag, barCode, width, height, compressedImage);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(int viewTag, Result barCode, int width, int height, byte[] compressedImage) {
|
||||
super.init(viewTag);
|
||||
mBarCode = barCode;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mCompressedImage = compressedImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (mCompressedImage != null) {
|
||||
event.putString("image", Base64.encodeToString(mCompressedImage, Base64.NO_WRAP));
|
||||
}
|
||||
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,80 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
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 BarcodesDetectedEvent extends Event<BarcodesDetectedEvent> {
|
||||
|
||||
private static final Pools.SynchronizedPool<BarcodesDetectedEvent> EVENTS_POOL =
|
||||
new Pools.SynchronizedPool<>(3);
|
||||
|
||||
private WritableArray mBarcodes;
|
||||
private byte[] mCompressedImage;
|
||||
|
||||
private BarcodesDetectedEvent() {
|
||||
}
|
||||
|
||||
public static BarcodesDetectedEvent obtain(
|
||||
int viewTag,
|
||||
WritableArray barcodes,
|
||||
byte[] compressedImage) {
|
||||
BarcodesDetectedEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new BarcodesDetectedEvent();
|
||||
}
|
||||
event.init(viewTag, barcodes, compressedImage);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(
|
||||
int viewTag,
|
||||
WritableArray barcodes,
|
||||
byte[] compressedImage) {
|
||||
super.init(viewTag);
|
||||
mBarcodes = barcodes;
|
||||
mCompressedImage = compressedImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
if (mCompressedImage != null) {
|
||||
event.putString("image", Base64.encodeToString(mCompressedImage, Base64.NO_WRAP));
|
||||
}
|
||||
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,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,69 @@
|
||||
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 TouchEvent extends Event<TouchEvent> {
|
||||
private static final Pools.SynchronizedPool<TouchEvent> EVENTS_POOL =
|
||||
new Pools.SynchronizedPool<>(3);
|
||||
|
||||
private int mX;
|
||||
private int mY;
|
||||
private boolean mIsDoubleTap;
|
||||
|
||||
private TouchEvent() {}
|
||||
|
||||
public static TouchEvent obtain(int viewTag, boolean isDoubleTap, int x, int y) {
|
||||
TouchEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new TouchEvent();
|
||||
}
|
||||
event.init(viewTag, isDoubleTap, x, y);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(int viewTag, boolean isDoubleTap, int x, int y) {
|
||||
super.init(viewTag);
|
||||
mX = x;
|
||||
mY = y;
|
||||
mIsDoubleTap=isDoubleTap;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_TOUCH.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableMap event = Arguments.createMap();
|
||||
|
||||
event.putInt("target", getViewTag());
|
||||
|
||||
WritableMap touchOrigin = Arguments.createMap();
|
||||
touchOrigin.putInt("x", mX);
|
||||
touchOrigin.putInt("y",mY);
|
||||
|
||||
event.putBoolean("isDoubleTap", mIsDoubleTap);
|
||||
event.putMap("touchOrigin", touchOrigin);
|
||||
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 from 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, mImageData);
|
||||
}
|
||||
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, byte[] imageData);
|
||||
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, int width, int height, byte[] imageData);
|
||||
|
||||
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,369 @@
|
||||
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(getImagePath());
|
||||
|
||||
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 getImagePath() throws IOException{
|
||||
if(mOptions.hasKey("path")){
|
||||
return mOptions.getString("path");
|
||||
}
|
||||
return RNFileUtils.getOutputFilePath(mCacheDirectory, ".jpg");
|
||||
}
|
||||
|
||||
private String writeStreamToFile(ByteArrayOutputStream inputStream) throws IOException {
|
||||
String outputPath = null;
|
||||
IOException exception = null;
|
||||
FileOutputStream outputStream = null;
|
||||
|
||||
try {
|
||||
outputPath = getImagePath();
|
||||
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, mWidth, mHeight, mImageData);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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,166 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.firebase.ml.vision.common.FirebaseVisionPoint;
|
||||
import com.google.firebase.ml.vision.face.FirebaseVisionFace;
|
||||
import com.google.firebase.ml.vision.face.FirebaseVisionFaceLandmark;
|
||||
|
||||
public class FaceDetectorUtils {
|
||||
private static final String[] landmarkNames = {
|
||||
"bottomMouthPosition", "leftCheekPosition", "leftEarPosition",
|
||||
"leftEyePosition", "leftMouthPosition", "noseBasePosition", "rightCheekPosition",
|
||||
"rightEarPosition", "rightEyePosition", "rightMouthPosition"
|
||||
};
|
||||
|
||||
public static WritableMap serializeFace(FirebaseVisionFace face) {
|
||||
return serializeFace(face, 1, 1, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static WritableMap serializeFace(FirebaseVisionFace face, double scaleX, double scaleY, int width, int height, int paddingLeft, int paddingTop) {
|
||||
WritableMap encodedFace = Arguments.createMap();
|
||||
|
||||
int id = 0;
|
||||
// If face tracking was enabled:
|
||||
if (face.getTrackingId() != FirebaseVisionFace.INVALID_ID) {
|
||||
id = face.getTrackingId();
|
||||
}
|
||||
|
||||
|
||||
encodedFace.putInt("faceID", id);
|
||||
encodedFace.putDouble("rollAngle", face.getHeadEulerAngleZ());
|
||||
encodedFace.putDouble("yawAngle", face.getHeadEulerAngleY());
|
||||
|
||||
// If classification was enabled:
|
||||
if (face.getSmilingProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
|
||||
encodedFace.putDouble("smilingProbability", face.getSmilingProbability());
|
||||
}
|
||||
if (face.getLeftEyeOpenProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
|
||||
encodedFace.putDouble("leftEyeOpenProbability", face.getLeftEyeOpenProbability());
|
||||
}
|
||||
if (face.getRightEyeOpenProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
|
||||
encodedFace.putDouble("rightEyeOpenProbability", face.getRightEyeOpenProbability());
|
||||
}
|
||||
int[] landmarks = {
|
||||
FirebaseVisionFaceLandmark.MOUTH_BOTTOM,
|
||||
FirebaseVisionFaceLandmark.LEFT_CHEEK,
|
||||
FirebaseVisionFaceLandmark.LEFT_EAR,
|
||||
FirebaseVisionFaceLandmark.LEFT_EYE,
|
||||
FirebaseVisionFaceLandmark.MOUTH_LEFT,
|
||||
FirebaseVisionFaceLandmark.NOSE_BASE,
|
||||
FirebaseVisionFaceLandmark.RIGHT_CHEEK,
|
||||
FirebaseVisionFaceLandmark.RIGHT_EAR,
|
||||
FirebaseVisionFaceLandmark.RIGHT_EYE,
|
||||
FirebaseVisionFaceLandmark.MOUTH_RIGHT};
|
||||
|
||||
for (int i = 0; i < landmarks.length; ++i) {
|
||||
FirebaseVisionFaceLandmark landmark = face.getLandmark(landmarks[i]);
|
||||
if (landmark != null) {
|
||||
encodedFace.putMap(landmarkNames[i], mapFromPoint(landmark.getPosition(), scaleX, scaleY, width, height, paddingLeft, paddingTop));
|
||||
}
|
||||
}
|
||||
|
||||
WritableMap origin = Arguments.createMap();
|
||||
Float x = face.getBoundingBox().exactCenterX() - (face.getBoundingBox().width() / 2 );
|
||||
Float y = face.getBoundingBox().exactCenterY() - (face.getBoundingBox().height() / 2);
|
||||
if (face.getBoundingBox().exactCenterX() < width / 2) {
|
||||
x = x + paddingLeft / 2;
|
||||
} else if (face.getBoundingBox().exactCenterX() > width / 2) {
|
||||
x = x - paddingLeft / 2;
|
||||
}
|
||||
|
||||
if (face.getBoundingBox().exactCenterY() < height / 2) {
|
||||
y = y + paddingTop / 2;
|
||||
} else if (face.getBoundingBox().exactCenterY() > height / 2) {
|
||||
y = y - paddingTop / 2;
|
||||
}
|
||||
|
||||
origin.putDouble("x", x * scaleX);
|
||||
origin.putDouble("y", y * scaleY);
|
||||
|
||||
WritableMap size = Arguments.createMap();
|
||||
size.putDouble("width", face.getBoundingBox().width() * scaleX);
|
||||
size.putDouble("height", face.getBoundingBox().height() * 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(FirebaseVisionPoint point, double scaleX, double scaleY, int width, int height, int paddingLeft, int paddingTop) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
Float x = point.getX();
|
||||
Float y = point.getY();
|
||||
if (point.getX() < width / 2) {
|
||||
x = (x + paddingLeft / 2);
|
||||
} else if (point.getX() > width / 2) {
|
||||
x = (x - paddingLeft / 2);
|
||||
}
|
||||
|
||||
if (point.getY() < height / 2) {
|
||||
y = (y + paddingTop / 2);
|
||||
} else if (point.getY() > height / 2) {
|
||||
y = (y - paddingTop / 2);
|
||||
}
|
||||
map.putDouble("x", x * scaleX);
|
||||
map.putDouble("y", 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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user