mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2025-04-17 06:33:20 +08:00
Compare commits
66 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8864262022 | ||
![]() |
b002b48735 | ||
![]() |
979155ee98 | ||
![]() |
7f33ef4add | ||
![]() |
4c9dc10512 | ||
![]() |
de468b6f63 | ||
![]() |
48c4cdd47f | ||
![]() |
7d159cf3c9 | ||
![]() |
c208754c08 | ||
![]() |
0d86764b90 | ||
![]() |
7adccc8ee9 | ||
![]() |
53795454f4 | ||
![]() |
2eaa9a3972 | ||
![]() |
a672c31efb | ||
![]() |
16325102c7 | ||
![]() |
36bf8e7331 | ||
![]() |
feb7643bc3 | ||
![]() |
44480300d9 | ||
![]() |
faa4615ee0 | ||
![]() |
0a4bfe1a74 | ||
![]() |
49438dee6d | ||
![]() |
f38aba2b59 | ||
![]() |
3b73186b91 | ||
![]() |
40a5db10c7 | ||
![]() |
9eecbaa1af | ||
![]() |
24839eb71f | ||
![]() |
64bd32d641 | ||
![]() |
0796f784c1 | ||
![]() |
20293f3d64 | ||
![]() |
8cb34e1175 | ||
![]() |
505ccefb4c | ||
![]() |
61a6e9bb44 | ||
![]() |
c2eb21d201 | ||
![]() |
23642f09b5 | ||
![]() |
84166f6355 | ||
![]() |
2c09ade500 | ||
![]() |
827bb611ee | ||
![]() |
d0545c879f | ||
![]() |
d0d46c151c | ||
![]() |
a18fda7ddf | ||
![]() |
3e548770b7 | ||
![]() |
4608f8ef80 | ||
![]() |
53223c3df2 | ||
![]() |
bf12b39d18 | ||
![]() |
ed216ce714 | ||
![]() |
879712028a | ||
![]() |
204234b1b9 | ||
![]() |
0fba37cac3 | ||
![]() |
5b8263732a | ||
![]() |
869f02da1a | ||
![]() |
e9db20e381 | ||
![]() |
c7971d9f63 | ||
![]() |
3112e5fb15 | ||
![]() |
c56a255fe8 | ||
![]() |
0227cdcf14 | ||
![]() |
75bf807261 | ||
![]() |
abfbbd35d5 | ||
![]() |
59cf76d1da | ||
![]() |
4ee90a84f3 | ||
![]() |
5587bec320 | ||
![]() |
d523142cf2 | ||
![]() |
0f6435ec73 | ||
![]() |
e81e02b21f | ||
![]() |
1e20a9bd07 | ||
![]() |
d356030070 | ||
![]() |
7bc311fba9 |
@ -1,33 +0,0 @@
|
||||
# appveyor file
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
max_jobs: 1
|
||||
|
||||
shallow_clone: true
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf true
|
||||
|
||||
image:
|
||||
- Visual Studio 2017
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "10"
|
||||
- nodejs_version: "12"
|
||||
- nodejs_version: "14"
|
||||
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- node --version
|
||||
- npm install -g github:apache/cordova-paramedic
|
||||
- npm install -g cordova
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- cordova-paramedic --config pr\windows-10-store --plugin . --justBuild
|
24
.asf.yaml
24
.asf.yaml
@ -15,6 +15,30 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
github:
|
||||
description: Apache Cordova Plugin camera
|
||||
homepage: https://cordova.apache.org/
|
||||
|
||||
labels:
|
||||
- cordova
|
||||
- mobile
|
||||
- javascript
|
||||
- nodejs
|
||||
- hacktoberfest
|
||||
- java
|
||||
- objective-c
|
||||
- cordova-plugin
|
||||
|
||||
features:
|
||||
wiki: false
|
||||
issues: true
|
||||
projects: true
|
||||
|
||||
enabled_merge_buttons:
|
||||
squash: true
|
||||
merge: false
|
||||
rebase: false
|
||||
|
||||
notifications:
|
||||
commits: commits@cordova.apache.org
|
||||
issues: issues@cordova.apache.org
|
||||
|
7
.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md
vendored
7
.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md
vendored
@ -13,7 +13,8 @@ For usage and support questions, please check out the resources below. Thanks!
|
||||
|
||||
You can get answers to your usage and support questions about **Apache Cordova** on:
|
||||
|
||||
* Slack Community Chat: https://cordova.slack.com (you can sign-up at http://slack.cordova.io/)
|
||||
* GitHub Discussions: https://github.com/apache/cordova/discussions
|
||||
* Slack Community Chat: https://cordova.slack.com (you can sign-up at https://s.apache.org/cordova-slack)
|
||||
* StackOverflow: https://stackoverflow.com/questions/tagged/cordova using the tag `cordova`
|
||||
|
||||
---
|
||||
@ -22,6 +23,4 @@ If you are using a tool that uses Cordova internally, like e.g. Ionic, check the
|
||||
|
||||
* **Ionic Framework**
|
||||
* [Ionic Community Forum](https://forum.ionicframework.com/)
|
||||
* [Ionic Worldwide Slack](https://ionicworldwide.herokuapp.com/)
|
||||
* **PhoneGap**
|
||||
* [PhoneGap Developer Community](https://forums.adobe.com/community/phonegap)
|
||||
* [Ionic Discord](https://ionic.link/discord)
|
161
.github/workflows/android.yml
vendored
Normal file
161
.github/workflows/android.yml
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
|
||||
name: Android Testsuite
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '.eslint*'
|
||||
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '.eslint*'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Android ${{ matrix.versions.android }} Test
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
# hoist configurations to top that are expected to be updated
|
||||
env:
|
||||
# Storing a copy of the repo
|
||||
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
node-version: 20
|
||||
|
||||
# These are the default Java configurations used by most tests.
|
||||
# To customize these options, add "java-distro" or "java-version" to the strategy matrix with its overriding value.
|
||||
default_java-distro: temurin
|
||||
default_java-version: 17
|
||||
|
||||
# These are the default Android System Image configurations used by most tests.
|
||||
# To customize these options, add "system-image-arch" or "system-image-target" to the strategy matrix with its overriding value.
|
||||
default_system-image-arch: x86_64
|
||||
default_system-image-target: google_apis # Most system images have a google_api option. Set this as default.
|
||||
|
||||
# configurations for each testing strategy (test matrix)
|
||||
strategy:
|
||||
matrix:
|
||||
versions:
|
||||
- android: 7
|
||||
android-api: 24
|
||||
|
||||
- android: 7.1
|
||||
android-api: 25
|
||||
|
||||
- android: 8
|
||||
android-api: 26
|
||||
|
||||
- android: 8.1
|
||||
android-api: 27
|
||||
system-image-arch: x86
|
||||
|
||||
- android: 9
|
||||
android-api: 28
|
||||
|
||||
- android: 10
|
||||
android-api: 29
|
||||
|
||||
- android: 11
|
||||
android-api: 30
|
||||
|
||||
- android: 12
|
||||
android-api: 31
|
||||
|
||||
- android: 12L
|
||||
android-api: 32
|
||||
|
||||
- android: 13
|
||||
android-api: 33
|
||||
|
||||
- android: 14
|
||||
android-api: 34
|
||||
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.node-version }}
|
||||
- uses: actions/setup-java@v4
|
||||
env:
|
||||
java-version: ${{ matrix.versions.java-version == '' && env.default_java-version || matrix.versions.java-version }}
|
||||
java-distro: ${{ matrix.versions.java-distro == '' && env.default_java-distro || matrix.versions.java-distro }}
|
||||
with:
|
||||
distribution: ${{ env.java-distro }}
|
||||
java-version: ${{ env.java-version }}
|
||||
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Run Environment Information
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
java -version
|
||||
|
||||
- name: Run npm install
|
||||
run: |
|
||||
export PATH="/usr/local/lib/android/sdk/platform-tools":$PATH
|
||||
export JAVA_HOME=$JAVA_HOME_11_X64
|
||||
npm i -g cordova@latest
|
||||
npm ci
|
||||
|
||||
- name: Run paramedic install
|
||||
if: ${{ endswith(env.repo, '/cordova-paramedic') != true }}
|
||||
run: npm i -g github:apache/cordova-paramedic
|
||||
|
||||
- uses: reactivecircus/android-emulator-runner@v2
|
||||
env:
|
||||
system-image-arch: ${{ matrix.versions.system-image-arch == '' && env.default_system-image-arch || matrix.versions.system-image-arch }}
|
||||
system-image-target: ${{ matrix.versions.system-image-target == '' && env.default_system-image-target || matrix.versions.system-image-target }}
|
||||
with:
|
||||
api-level: ${{ matrix.versions.android-api }}
|
||||
target: ${{ env.system-image-target }}
|
||||
arch: ${{ env.system-image-arch }}
|
||||
force-avd-creation: false
|
||||
disable-animations: false
|
||||
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim
|
||||
script: echo "Pregenerate the AVD before running Paramedic"
|
||||
|
||||
- name: Run paramedic tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
env:
|
||||
system-image-arch: ${{ matrix.versions.system-image-arch == '' && env.default_system-image-arch || matrix.versions.system-image-arch }}
|
||||
system-image-target: ${{ matrix.versions.system-image-target == '' && env.default_system-image-target || matrix.versions.system-image-target }}
|
||||
test_config: 'android-${{ matrix.versions.android }}.config.json'
|
||||
# Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed.
|
||||
test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }}
|
||||
paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }}
|
||||
with:
|
||||
api-level: ${{ matrix.versions.android-api }}
|
||||
target: ${{ env.system-image-target }}
|
||||
arch: ${{ env.system-image-arch }}
|
||||
force-avd-creation: false
|
||||
disable-animations: false
|
||||
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim
|
||||
script: ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }}
|
73
.github/workflows/chrome.yml
vendored
Normal file
73
.github/workflows/chrome.yml
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
|
||||
name: Chrome Testsuite
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '.eslint*'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '.eslint*'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Chrome Latest Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# hoist configurations to top that are expected to be updated
|
||||
env:
|
||||
# Storing a copy of the repo
|
||||
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
node-version: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.node-version }}
|
||||
|
||||
- name: Run install xvfb
|
||||
run: sudo apt-get install xvfb
|
||||
|
||||
- name: Run Environment Information
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Run npm install
|
||||
run: |
|
||||
npm i -g cordova@latest
|
||||
npm ci
|
||||
|
||||
- name: Run paramedic install
|
||||
if: ${{ endswith(env.repo, '/cordova-paramedic') != true }}
|
||||
run: npm i -g github:apache/cordova-paramedic
|
||||
|
||||
- name: Run paramedic tests
|
||||
env:
|
||||
test_config: 'browser.config.json'
|
||||
# Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed.
|
||||
test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }}
|
||||
paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }}
|
||||
run: xvfb-run --auto-servernum ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }}
|
97
.github/workflows/ios.yml
vendored
Normal file
97
.github/workflows/ios.yml
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
|
||||
name: iOS Testsuite
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '.eslint*'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '.eslint*'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: iOS ${{ matrix.versions.ios-version }} Test
|
||||
runs-on: ${{ matrix.versions.os-version }}
|
||||
continue-on-error: true
|
||||
|
||||
# hoist configurations to top that are expected to be updated
|
||||
env:
|
||||
# Storing a copy of the repo
|
||||
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
node-version: 20
|
||||
|
||||
# > Starting April 26, 2021, all iOS and iPadOS apps submitted to the App Store must be built with Xcode 12 and the iOS 14 SDK.
|
||||
# Because of Apple's requirement, listed above, We will only be using the latest Xcode release for testing.
|
||||
# To customize these options, add "xcode-version" to the strategy matrix with its overriding value.
|
||||
default_xcode-version: latest-stable
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
versions:
|
||||
- os-version: macos-12
|
||||
ios-version: 15.x
|
||||
xcode-version: 13.x
|
||||
|
||||
- os-version: macos-14
|
||||
ios-version: 16.x
|
||||
xcode-version: 14.x
|
||||
|
||||
- os-version: macos-14
|
||||
ios-version: 17.x
|
||||
xcode-version: 15.x
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.node-version }}
|
||||
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
|
||||
env:
|
||||
xcode-version: ${{ matrix.versions.xcode-version == '' && env.default_xcode-version || matrix.versions.xcode-version }}
|
||||
with:
|
||||
xcode-version: ${{ env.xcode-version }}
|
||||
|
||||
- name: Run Environment Information
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
xcodebuild -version
|
||||
|
||||
- name: Run npm install
|
||||
run: |
|
||||
npm i -g cordova@latest ios-deploy@latest
|
||||
npm ci
|
||||
|
||||
- name: Run paramedic install
|
||||
if: ${{ endswith(env.repo, '/cordova-paramedic') != true }}
|
||||
run: npm i -g github:apache/cordova-paramedic
|
||||
|
||||
- name: Run paramedic tests
|
||||
env:
|
||||
test_config: 'ios-${{ matrix.versions.ios-version }}.config.json'
|
||||
# Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed.
|
||||
test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }}
|
||||
paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }}
|
||||
run: ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }}
|
56
.github/workflows/lint.yml
vendored
Normal file
56
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
|
||||
name: Lint Test
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**.js'
|
||||
- '.eslint*'
|
||||
- '.github/workflow/lint.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.js'
|
||||
- '.eslint*'
|
||||
- '.github/workflow/lint.yml'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint Test
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
node-version: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.node-version }}
|
||||
|
||||
- name: Run Environment Information
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Run npm install
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Run lint test
|
||||
run: |
|
||||
npm run lint
|
13
.github/workflows/release-notify.yml
vendored
Normal file
13
.github/workflows/release-notify.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: Close issue asking for release
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
action-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: niklasmerz/release-notify@master
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
@ -1,3 +1,2 @@
|
||||
.*
|
||||
appveyor.yml
|
||||
tests
|
||||
|
118
.travis.yml
118
.travis.yml
@ -1,118 +0,0 @@
|
||||
# This Travis configuration file is built after a Cordova Paramedic
|
||||
# specific template with minimal modifications and adaptations:
|
||||
# https://github.com/apache/cordova-paramedic/blob/master/.travis.yml
|
||||
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
jwt:
|
||||
# SAUCE_ACCESS_KEY
|
||||
secure: QivPLlqTVvOo3TJeHxuBOfxU6lho1I0IxQ3b68yntkEQQJko6kzleXHfgjf0a8aw8m38E3+fxaBWF1bGyucGwOLDWY8Ddt2P2xg44zdXH5EXHd9oIqAgngIdzLvUtH3Db2TbQEtIGOkrnNR2STovjqB7vHGLASQrgs4oL7r32/s=
|
||||
|
||||
env:
|
||||
global:
|
||||
- SAUCE_USERNAME=snay
|
||||
- TRAVIS_NODE_VERSION=12
|
||||
- ANDROID_API_LEVEL=29
|
||||
- ANDROID_BUILD_TOOLS_VERSION=29.0.2
|
||||
|
||||
language: node_js
|
||||
node_js: 14
|
||||
|
||||
# yaml anchor/alias: https://medium.com/@tommyvn/travis-yml-dry-with-anchors-8b6a3ac1b027
|
||||
|
||||
_ios: &_ios
|
||||
os: osx
|
||||
osx_image: xcode11.6
|
||||
|
||||
_android: &_android
|
||||
language: android
|
||||
os: linux
|
||||
jdk: oraclejdk8
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- build-tools-$ANDROID_BUILD_TOOLS_VERSION
|
||||
- android-$ANDROID_API_LEVEL
|
||||
licenses:
|
||||
- "android-sdk-preview-license-.+"
|
||||
- "android-sdk-license-.+"
|
||||
- "google-gdk-license-.+"
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# additional tests
|
||||
- env: ADDITIONAL_TESTS_DIR=./tests/ios
|
||||
os: osx
|
||||
osx_image: xcode11.5
|
||||
|
||||
# local tests, without saucelabs
|
||||
- env: PLATFORM=local/browser
|
||||
<<: *_ios
|
||||
- env: PLATFORM=local/ios-10.0
|
||||
<<: *_ios
|
||||
|
||||
# many tests with saucelabs
|
||||
- env: PLATFORM=browser-chrome
|
||||
- env: PLATFORM=browser-firefox
|
||||
- env: PLATFORM=browser-safari
|
||||
- env: PLATFORM=browser-edge
|
||||
|
||||
- env: PLATFORM=ios-11.3
|
||||
<<: *_ios
|
||||
- env: PLATFORM=ios-12.0
|
||||
<<: *_ios
|
||||
- env: PLATFORM=ios-12.2
|
||||
<<: *_ios
|
||||
|
||||
- env: PLATFORM=android-5.1
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-6.0
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-7.0
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-7.1
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-8.0
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-8.1
|
||||
<<: *_android
|
||||
- env: PLATFORM=android-9.0
|
||||
<<: *_android
|
||||
|
||||
before_install:
|
||||
# manually install Node for `language: android`
|
||||
- if [[ "$PLATFORM" =~ android ]]; then nvm install $TRAVIS_NODE_VERSION; fi
|
||||
- node --version
|
||||
- if [[ "$PLATFORM" =~ android ]]; then gradle --version; fi
|
||||
- if [[ "$PLATFORM" =~ ios ]]; then npm install -g ios-deploy; fi
|
||||
- npm install -g cordova
|
||||
# install paramedic if not running on paramedic repo
|
||||
- if ! [[ "$TRAVIS_REPO_SLUG" =~ cordova-paramedic ]]; then npm install -g github:apache/cordova-paramedic; fi
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
- |
|
||||
if [[ "$TRAVIS_REPO_SLUG" =~ cordova-paramedic ]]; then
|
||||
# when used in the cordova-paramedic repo
|
||||
TEST_COMMAND="npm run eslint"
|
||||
PARAMEDIC_PLUGIN_TO_TEST="./spec/testable-plugin/"
|
||||
PARAMEDIC_COMMAND="node main.js"
|
||||
else
|
||||
# when used in any other (plugin) repo
|
||||
TEST_COMMAND="npm test"
|
||||
PARAMEDIC_PLUGIN_TO_TEST=$(pwd)
|
||||
PARAMEDIC_COMMAND="cordova-paramedic"
|
||||
fi
|
||||
- PARAMEDIC_BUILDNAME=travis-$TRAVIS_REPO_SLUG-$TRAVIS_JOB_NUMBER
|
||||
|
||||
script:
|
||||
- $TEST_COMMAND
|
||||
- |
|
||||
if [[ "$ADDITIONAL_TESTS_DIR" != "" ]];
|
||||
then cd $ADDITIONAL_TESTS_DIR && npm install && npm test;
|
||||
else
|
||||
$PARAMEDIC_COMMAND --config ./pr/$PLATFORM --plugin $PARAMEDIC_PLUGIN_TO_TEST --buildName $PARAMEDIC_BUILDNAME;
|
||||
fi
|
292
README.md
292
README.md
@ -21,12 +21,10 @@ description: Take pictures with the device camera.
|
||||
# under the License.
|
||||
-->
|
||||
|
||||
|AppVeyor|Travis CI|
|
||||
|:-:|:-:|
|
||||
|[](https://ci.appveyor.com/project/ApacheSoftwareFoundation/cordova-plugin-camera)|[](https://travis-ci.org/apache/cordova-plugin-camera)|
|
||||
|
||||
# cordova-plugin-camera
|
||||
|
||||
[](https://github.com/apache/cordova-plugin-camera/actions/workflows/android.yml) [](https://github.com/apache/cordova-plugin-camera/actions/workflows/chrome.yml) [](https://github.com/apache/cordova-plugin-camera/actions/workflows/ios.yml) [](https://github.com/apache/cordova-plugin-camera/actions/workflows/lint.yml)
|
||||
|
||||
This plugin defines a global `navigator.camera` object, which provides an API for taking pictures and for choosing images from
|
||||
the system's image library.
|
||||
|
||||
@ -40,16 +38,20 @@ Although the object is attached to the global scoped `navigator`, it is not avai
|
||||
|
||||
## Installation
|
||||
|
||||
This requires cordova 5.0+
|
||||
|
||||
cordova plugin add cordova-plugin-camera
|
||||
Older versions of cordova can still install via the __deprecated__ id
|
||||
|
||||
cordova plugin add org.apache.cordova.camera
|
||||
It is also possible to install via repo url directly ( unstable )
|
||||
|
||||
cordova plugin add https://github.com/apache/cordova-plugin-camera.git
|
||||
|
||||
## Plugin variables
|
||||
|
||||
The plugin uses the `ANDROIDX_CORE_VERSION` variable to configure `androidx.core:core` dependency. This allows to avoid conflicts with other plugins that have the dependency hardcoded.
|
||||
If no value is passed, it will use `1.6.+` as the default value.
|
||||
|
||||
The variable is configured on install time
|
||||
|
||||
cordova plugin add cordova-plugin-camera --variable ANDROIDX_CORE_VERSION=1.8.0
|
||||
|
||||
## How to Contribute
|
||||
|
||||
@ -133,8 +135,31 @@ To add these entries into the `info.plist`, you can use the `edit-config` tag in
|
||||
|
||||
### camera.getPicture(successCallback, errorCallback, options)
|
||||
Takes a photo using the camera, or retrieves a photo from the device's
|
||||
image gallery. The image is passed to the success callback as a
|
||||
Base64-encoded `String`, or as the URI for the image file.
|
||||
image gallery. The result is provided in the first parameter of the `successCallback` as a string.
|
||||
|
||||
As of v8.0.0, the result is formatted as URIs. The scheme will vary depending on settings and platform.
|
||||
|
||||
|Platform|Destination Type|Format|
|
||||
|---|---|---|
|
||||
|Android|FILE_URI|An URI scheme such as `file://...` or `content://...`|
|
||||
||DATA_URL|Base 64 encoded with the proper data URI header|
|
||||
|iOS|FILE_URI|`file://` schemed paths|
|
||||
||DATA_URL|Base 64 encoded with the proper data URI header|
|
||||
|Browser|FILE_URI|Not supported|
|
||||
||DATA_URL|Base 64 encoded with the proper data URI header|
|
||||
|
||||
v7 and earlier versions, the return format is as follows:
|
||||
|
||||
|Platform|Destination Type|Format|
|
||||
|---|---|---|
|
||||
|Android|FILE_URI|Raw file path (unprefixed)|
|
||||
||DATA_URL|Base 64 encoded, without the `data:` prefix
|
||||
|iOS|FILE_URI|`file://` schemed paths|
|
||||
||DATA_URL|Base 64 encoded, without the `data:` prefix
|
||||
|Browser|FILE_URI|Not supported|
|
||||
||DATA_URL|Base 64 encoded, without the `data:` prefix|
|
||||
|
||||
For this reason, upgrading to v8 is strongly recommended as it greatly streamlines the return data.
|
||||
|
||||
The `camera.getPicture` function opens the device's default camera
|
||||
application that allows users to snap pictures by default - this behavior occurs,
|
||||
@ -147,16 +172,10 @@ that allows users to select an existing image.
|
||||
|
||||
The return value is sent to the [`cameraSuccess`](#module_camera.onSuccess) callback function, in
|
||||
one of the following formats, depending on the specified
|
||||
`cameraOptions`:
|
||||
`cameraOptions`. You can do whatever you want with content:
|
||||
|
||||
- A `String` containing the Base64-encoded photo image.
|
||||
- A `String` representing the image file location on local storage (default).
|
||||
|
||||
You can do whatever you want with the encoded image or URI, for
|
||||
example:
|
||||
|
||||
- Render the image in an `<img>` tag, as in the example below
|
||||
- Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)
|
||||
- Render the content in an `<img>` or `<video>` tag
|
||||
- Copy the data to a persistent location
|
||||
- Post the data to a remote server
|
||||
|
||||
__NOTE__: Photo resolution on newer devices is quite good. Photos
|
||||
@ -165,17 +184,41 @@ quality, even if a `quality` parameter is specified. To avoid common
|
||||
memory problems, set `Camera.destinationType` to `FILE_URI` rather
|
||||
than `DATA_URL`.
|
||||
|
||||
__NOTE__: To use `saveToPhotoAlbum` option on Android 9 (API 28) and lower, the `WRITE_EXTERNAL_STORAGE` permission must be declared.
|
||||
|
||||
To do this, add the following in your `config.xml`:
|
||||
|
||||
```xml
|
||||
<config-file target="AndroidManifest.xml" parent="/*" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||
</config-file>
|
||||
```
|
||||
|
||||
Android 10 (API 29) and later devices does not require `WRITE_EXTERNAL_STORAGE` permission. If your application only supports Android 10 or later, then this step is not necessary.
|
||||
|
||||
#### FILE_URI Usage
|
||||
|
||||
When `FILE_URI` is used, the returned path is not directly usable. The file path needs to be resolved into
|
||||
a DOM-usable URL using the [Cordova File Plugin](https://github.com/apache/cordova-plugin-file).
|
||||
|
||||
Additionally, the file URIs returned is a temporary read access grant. The OS reserves the right to revoke permission to access the resource, which typically occurs after the app has been closed. For images captured using the camera, the image is stored in a temporary location which can be cleared at any time, usually after the app exits. It's the application's decision to decide how the content should be used depending on their use cases.
|
||||
|
||||
For persistent access to the content, the resource should be copied to your app's storage container. An example use case is an app allowing an user to select a profile picture from their gallery or camera. The application will need
|
||||
consistent access to that resource, so it's not suitable to retain the temporary access path. So the appplication should copy the resource to a persistent location.
|
||||
|
||||
For use cases that involve temporary use, it is valid and safe to use the temporary file path to display the content. An example of this could be an image editing application, rendering the data into a canvas.
|
||||
|
||||
__NOTE__: The returned schemes is an implementation detail. Do not assume that it will always be a `file://` URI.
|
||||
|
||||
__Supported Platforms__
|
||||
|
||||
- Android
|
||||
- Browser
|
||||
- iOS
|
||||
- Windows
|
||||
- OSX
|
||||
|
||||
More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
|
||||
|
||||
**Kind**: static method of <code>[camera](#module_camera)</code>
|
||||
**Kind**: static method of <code>[camera](#module_camera)</code>
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
@ -183,7 +226,7 @@ More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPict
|
||||
| errorCallback | <code>[onError](#module_camera.onError)</code> | |
|
||||
| options | <code>[CameraOptions](#module_camera.CameraOptions)</code> | CameraOptions |
|
||||
|
||||
**Example**
|
||||
**Example**
|
||||
```js
|
||||
navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
|
||||
```
|
||||
@ -199,8 +242,8 @@ __Supported Platforms__
|
||||
|
||||
- iOS
|
||||
|
||||
**Kind**: static method of <code>[camera](#module_camera)</code>
|
||||
**Example**
|
||||
**Kind**: static method of <code>[camera](#module_camera)</code>
|
||||
**Example**
|
||||
```js
|
||||
navigator.camera.cleanup(onSuccess, onFail);
|
||||
|
||||
@ -217,7 +260,7 @@ function onFail(message) {
|
||||
### camera.onError : <code>function</code>
|
||||
Callback function that provides an error message.
|
||||
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
@ -228,19 +271,26 @@ Callback function that provides an error message.
|
||||
### camera.onSuccess : <code>function</code>
|
||||
Callback function that provides the image data.
|
||||
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| imageData | <code>string</code> | Base64 encoding of the image data, _or_ the image file URI, depending on [`cameraOptions`](#module_camera.CameraOptions) in effect. |
|
||||
| imageData | <code>string</code> | Data URI, _or_ the image file URI, depending on [`cameraOptions`](#module_camera.CameraOptions) in effect. |
|
||||
|
||||
**Example**
|
||||
**Example**
|
||||
```js
|
||||
// Show image
|
||||
//
|
||||
// Show image captured with FILE_URI
|
||||
function cameraCallback(imageData) {
|
||||
window.resolveLocalFileSystemURL(uri, (entry) => {
|
||||
let image = document.getElementById('myImage');
|
||||
image.src = entry.toURL();
|
||||
}, onError);
|
||||
}
|
||||
|
||||
// Show image captured with DATA_URL
|
||||
function cameraCallback(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = "data:image/jpeg;base64," + imageData;
|
||||
image.src = imageData;
|
||||
}
|
||||
```
|
||||
<a name="module_camera.CameraOptions"></a>
|
||||
@ -249,7 +299,7 @@ function cameraCallback(imageData) {
|
||||
Optional parameters to customize the camera settings.
|
||||
* [Quirks](#CameraOptions-quirks)
|
||||
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@ -257,13 +307,13 @@ Optional parameters to customize the camera settings.
|
||||
| quality | <code>number</code> | <code>50</code> | Quality of the saved image, expressed as a range of 0-100, where 100 is typically full resolution with no loss from file compression. (Note that information about the camera's resolution is unavailable.) |
|
||||
| destinationType | <code>[DestinationType](#module_Camera.DestinationType)</code> | <code>FILE_URI</code> | Choose the format of the return value. |
|
||||
| sourceType | <code>[PictureSourceType](#module_Camera.PictureSourceType)</code> | <code>CAMERA</code> | Set the source of the picture. |
|
||||
| allowEdit | <code>Boolean</code> | <code>false</code> | Allow simple editing of image before selection. |
|
||||
| ~~allowEdit~~ | <code>Boolean</code> | <code>false</code> | **Deprecated**. Allow simple editing of image before selection. |
|
||||
| encodingType | <code>[EncodingType](#module_Camera.EncodingType)</code> | <code>JPEG</code> | Choose the returned image file's encoding. |
|
||||
| targetWidth | <code>number</code> | | Width in pixels to scale image. Must be used with `targetHeight`. Aspect ratio remains constant. |
|
||||
| targetHeight | <code>number</code> | | Height in pixels to scale image. Must be used with `targetWidth`. Aspect ratio remains constant. |
|
||||
| mediaType | <code>[MediaType](#module_Camera.MediaType)</code> | <code>PICTURE</code> | Set the type of media to select from. Only works when `PictureSourceType` is `PHOTOLIBRARY` or `SAVEDPHOTOALBUM`. |
|
||||
| correctOrientation | <code>Boolean</code> | | Rotate the image to correct for the orientation of the device during capture. |
|
||||
| saveToPhotoAlbum | <code>Boolean</code> | | Save the image to the photo album on the device after capture. |
|
||||
| saveToPhotoAlbum | <code>Boolean</code> | | Save the image to the photo album on the device after capture.<br />See [Android Quirks](#cameragetpicturesuccesscallback-errorcallback-options). |
|
||||
| popoverOptions | <code>[CameraPopoverOptions](#module_CameraPopoverOptions)</code> | | iOS-only options that specify popover location in iPad. |
|
||||
| cameraDirection | <code>[Direction](#module_Camera.Direction)</code> | <code>BACK</code> | Choose the camera to use (front- or back-facing). |
|
||||
|
||||
@ -277,18 +327,18 @@ Optional parameters to customize the camera settings.
|
||||
### Camera.DestinationType : <code>enum</code>
|
||||
Defines the output format of `Camera.getPicture` call.
|
||||
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| DATA_URL | <code>number</code> | <code>0</code> | Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI if possible |
|
||||
| DATA_URL | <code>number</code> | <code>0</code> | Return data uri. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI if possible |
|
||||
| FILE_URI | <code>number</code> | <code>1</code> | Return file uri (content://media/external/images/media/2 for Android) |
|
||||
|
||||
<a name="module_Camera.EncodingType"></a>
|
||||
|
||||
### Camera.EncodingType : <code>enum</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@ -299,7 +349,7 @@ Defines the output format of `Camera.getPicture` call.
|
||||
<a name="module_Camera.MediaType"></a>
|
||||
|
||||
### Camera.MediaType : <code>enum</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@ -313,7 +363,7 @@ Defines the output format of `Camera.getPicture` call.
|
||||
### Camera.PictureSourceType : <code>enum</code>
|
||||
Defines the output format of `Camera.getPicture` call.
|
||||
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@ -327,21 +377,21 @@ Defines the output format of `Camera.getPicture` call.
|
||||
### Camera.PopoverArrowDirection : <code>enum</code>
|
||||
Matches iOS UIPopoverArrowDirection constants to specify arrow location on popover.
|
||||
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default |
|
||||
| --- | --- | --- |
|
||||
| ARROW_UP | <code>number</code> | <code>1</code> |
|
||||
| ARROW_DOWN | <code>number</code> | <code>2</code> |
|
||||
| ARROW_LEFT | <code>number</code> | <code>4</code> |
|
||||
| ARROW_RIGHT | <code>number</code> | <code>8</code> |
|
||||
| ARROW_ANY | <code>number</code> | <code>15</code> |
|
||||
| ARROW_UP | <code>number</code> | <code>1</code> |
|
||||
| ARROW_DOWN | <code>number</code> | <code>2</code> |
|
||||
| ARROW_LEFT | <code>number</code> | <code>4</code> |
|
||||
| ARROW_RIGHT | <code>number</code> | <code>8</code> |
|
||||
| ARROW_ANY | <code>number</code> | <code>15</code> |
|
||||
|
||||
<a name="module_Camera.Direction"></a>
|
||||
|
||||
### Camera.Direction : <code>enum</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@ -384,7 +434,7 @@ __Supported Platforms__
|
||||
|
||||
- iOS
|
||||
|
||||
**Example**
|
||||
**Example**
|
||||
```js
|
||||
navigator.camera.getPicture(onSuccess, onFail,
|
||||
{
|
||||
@ -409,38 +459,46 @@ window.onorientationchange = function() {
|
||||
|
||||
Take a photo and retrieve the image's file location:
|
||||
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
|
||||
destinationType: Camera.DestinationType.FILE_URI });
|
||||
```javascript
|
||||
// Don't forget to install cordova-plugin-file for resolveLocalFileSystemURL!
|
||||
|
||||
function onSuccess(imageURI) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = imageURI;
|
||||
}
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
|
||||
destinationType: Camera.DestinationType.FILE_URI });
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
function onSuccess(imageURI) {
|
||||
window.resolveLocalFileSystemURL(uri, (entry) => {
|
||||
let img = document.getElementById('image');
|
||||
img.src = entry.toURL();
|
||||
}, onFail);
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
```
|
||||
|
||||
Take a photo and retrieve it as a Base64-encoded image:
|
||||
|
||||
/**
|
||||
* Warning: Using DATA_URL is not recommended! The DATA_URL destination
|
||||
* type is very memory intensive, even with a low quality setting. Using it
|
||||
* can result in out of memory errors and application crashes. Use FILE_URI
|
||||
* instead.
|
||||
*/
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 25,
|
||||
destinationType: Camera.DestinationType.DATA_URL
|
||||
});
|
||||
```javascript
|
||||
/**
|
||||
* Warning: Using DATA_URL is not recommended! The DATA_URL destination
|
||||
* type is very memory intensive, even with a low quality setting. Using it
|
||||
* can result in out of memory errors and application crashes. Use FILE_URI
|
||||
* instead.
|
||||
*/
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 25,
|
||||
destinationType: Camera.DestinationType.DATA_URL
|
||||
});
|
||||
|
||||
function onSuccess(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = "data:image/jpeg;base64," + imageData;
|
||||
}
|
||||
function onSuccess(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = imageData;
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
```
|
||||
|
||||
#### Preferences (iOS)
|
||||
|
||||
@ -461,7 +519,7 @@ successful.
|
||||
|
||||
#### Browser Quirks
|
||||
|
||||
Can only return photos as Base64-encoded image.
|
||||
Can only return photos as data URI image.
|
||||
|
||||
#### iOS Quirks
|
||||
|
||||
@ -470,19 +528,11 @@ can cause problems. Wrap the alert within a `setTimeout()` to allow
|
||||
the iOS image picker or popover to fully close before the alert
|
||||
displays:
|
||||
|
||||
setTimeout(function() {
|
||||
// do your thing here!
|
||||
}, 0);
|
||||
|
||||
#### Windows quirks
|
||||
|
||||
On Windows Phone 8.1 using `SAVEDPHOTOALBUM` or `PHOTOLIBRARY` as a source type causes application to suspend until file picker returns the selected image and
|
||||
then restore with start page as defined in app's `config.xml`. In case when `camera.getPicture` was called from different page, this will lead to reloading
|
||||
start page from scratch and success and error callbacks will never be called.
|
||||
|
||||
To avoid this we suggest using SPA pattern or call `camera.getPicture` only from your app's start page.
|
||||
|
||||
More information about Windows Phone 8.1 picker APIs is here: [How to continue your Windows Phone app after calling a file picker](https://msdn.microsoft.com/en-us/library/windows/apps/dn720490.aspx)
|
||||
```javascript
|
||||
setTimeout(function() {
|
||||
// do your thing here!
|
||||
}, 0);
|
||||
```
|
||||
|
||||
## `CameraOptions` Errata <a name="CameraOptions-quirks"></a>
|
||||
|
||||
@ -496,11 +546,6 @@ More information about Windows Phone 8.1 picker APIs is here: [How to continue y
|
||||
|
||||
- Ignores the `encodingType` parameter if the image is unedited (i.e. `quality` is 100, `correctOrientation` is false, and no `targetHeight` or `targetWidth` are specified). The `CAMERA` source will always return the JPEG file given by the native camera and the `PHOTOLIBRARY` and `SAVEDPHOTOALBUM` sources will return the selected file in its existing encoding.
|
||||
|
||||
#### iOS Quirks
|
||||
|
||||
- When using `destinationType.FILE_URI`, photos are saved in the application's temporary directory. The contents of the application's temporary directory is deleted when the application ends.
|
||||
|
||||
|
||||
[android_lifecycle]: http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html
|
||||
|
||||
## Sample: Take Pictures, Select Pictures from the Picture Library, and Get Thumbnails <a name="sample"></a>
|
||||
@ -569,12 +614,6 @@ function displayImage(imgUri) {
|
||||
}
|
||||
```
|
||||
|
||||
To display the image on some platforms, you might need to include the main part of the URI in the Content-Security-Policy `<meta>` element in index.html. For example, on Windows 10, you can include `ms-appdata:` in your `<meta>` element. Here is an example.
|
||||
|
||||
```html
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: ms-appdata: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
|
||||
```
|
||||
|
||||
## Take a Picture and Return Thumbnails (Resize the Picture) <a name="getThumbnails"></a>
|
||||
|
||||
To get smaller images, you can return a resized image by passing both `targetHeight` and `targetWidth` values with your CameraOptions object. In this example, you resize the returned image to fit in a 100px by 100px box (the aspect ratio is maintained, so 100px is either the height or width, whichever is greater in the source).
|
||||
@ -655,9 +694,9 @@ function openFilePicker(selection) {
|
||||
|
||||
## Take a picture and get a FileEntry Object <a name="convert"></a>
|
||||
|
||||
If you want to do something like copy the image to another location, or upload it somewhere using the FileTransfer plugin, you need to get a FileEntry object for the returned picture. To do that, call `window.resolveLocalFileSystemURL` on the file URI returned by the Camera app. If you need to use a FileEntry object, set the `destinationType` to `Camera.DestinationType.FILE_URI` in your CameraOptions object (this is also the default value).
|
||||
If you want to do something like copy the image to another location, or upload it somewhere, an `FileEntry` is needed for the returned picture. To do this, call `window.resolveLocalFileSystemURL` on the file URI returned by the Camera app. If you need to use a FileEntry object, set the `destinationType` to `Camera.DestinationType.FILE_URI` in your CameraOptions object (this is also the default value).
|
||||
|
||||
>*Note* You need the [File plugin](https://www.npmjs.com/package/cordova-plugin-file) to call `window.resolveLocalFileSystemURL`.
|
||||
__NOTE:__ You need the [File plugin](https://www.npmjs.com/package/cordova-plugin-file) to call `window.resolveLocalFileSystemURL`.
|
||||
|
||||
Here is the call to `window.resolveLocalFileSystemURL`. The image URI is passed to this function from the success callback of `getPicture`. The success handler of `resolveLocalFileSystemURL` receives the FileEntry object.
|
||||
|
||||
@ -665,39 +704,26 @@ Here is the call to `window.resolveLocalFileSystemURL`. The image URI is passed
|
||||
function getFileEntry(imgUri) {
|
||||
window.resolveLocalFileSystemURL(imgUri, function success(fileEntry) {
|
||||
|
||||
// Do something with the FileEntry object, like write to it, upload it, etc.
|
||||
// writeFile(fileEntry, imgUri);
|
||||
console.log("got file: " + fileEntry.fullPath);
|
||||
// displayFileData(fileEntry.nativeURL, "Native URL");
|
||||
// Example 1: Copy to app data directory
|
||||
window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function (dataDirectoryEntry) {
|
||||
fileEntry.copyTo(dataDirectoryEntry, "profilePic", onSuccess, onError);
|
||||
}, onError);
|
||||
|
||||
}, function () {
|
||||
// If don't get the FileEntry (which may happen when testing
|
||||
// on some emulators), copy to a new FileEntry.
|
||||
createNewFileEntry(imgUri);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
In the example shown in the preceding code, you call the app's `createNewFileEntry` function if you don't get a valid FileEntry object. The image URI returned from the Camera app should result in a valid FileEntry, but platform behavior on some emulators may be different for files returned from the file picker.
|
||||
|
||||
>*Note* To see an example of writing to a FileEntry, see the [File plugin README](https://www.npmjs.com/package/cordova-plugin-file).
|
||||
|
||||
The code shown here creates a file in your app's cache (in sandboxed storage) named `tempFile.jpeg`. With the new FileEntry object, you can copy the image to the file or do something else like upload it.
|
||||
|
||||
```js
|
||||
function createNewFileEntry(imgUri) {
|
||||
window.resolveLocalFileSystemURL(cordova.file.cacheDirectory, function success(dirEntry) {
|
||||
|
||||
// JPEG file
|
||||
dirEntry.getFile("tempFile.jpeg", { create: true, exclusive: false }, function (fileEntry) {
|
||||
|
||||
// Do something with it, like write to it, upload it, etc.
|
||||
// writeFile(fileEntry, imgUri);
|
||||
console.log("got file: " + fileEntry.fullPath);
|
||||
// displayFileData(fileEntry.fullPath, "File copied to");
|
||||
|
||||
}, onErrorCreateFile);
|
||||
|
||||
}, onErrorResolveUrl);
|
||||
// Example 2: Upload it!
|
||||
fileEntry.file(function (file) {
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onloadend = function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'https://myserver.com/upload');
|
||||
xhr.onload = function () {
|
||||
// All done!
|
||||
};
|
||||
xhr.send(this.result);
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
}, onError);
|
||||
}, onError);
|
||||
}
|
||||
```
|
||||
|
@ -20,6 +20,103 @@
|
||||
-->
|
||||
# Release Notes
|
||||
|
||||
### 8.0.0 (Oct 30, 2024)
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
* [GH-889](https://github.com/apache/cordova-plugin-camera/pull/889) fix(android): Remove media permissions to make complaint with **Android** 14 requirements (#889)
|
||||
* [GH-902](https://github.com/apache/cordova-plugin-camera/pull/902) fix(android): return content uris when possible when selecting from gallery (#902)
|
||||
* [GH-909](https://github.com/apache/cordova-plugin-camera/pull/909) refactor(android): Make WRITE_EXTERNAL_STORAGE optional (#909)
|
||||
* [GH-910](https://github.com/apache/cordova-plugin-camera/pull/910) fix(android): Return data uris as an URI (#910)
|
||||
* [GH-911](https://github.com/apache/cordova-plugin-camera/pull/911) fix(ios): Sync camera API return to match **Android** changes (#911)
|
||||
* [GH-912](https://github.com/apache/cordova-plugin-camera/pull/912) fix(browser): Make data uri be returned as actual URI strings (#912)
|
||||
|
||||
**Fixes**:
|
||||
|
||||
* [GH-901](https://github.com/apache/cordova-plugin-camera/pull/901) fix(android): Isolate provider access to a subdirectory (#901)
|
||||
* [GH-915](https://github.com/apache/cordova-plugin-camera/pull/903) fix(android): Improper serialization of image uri in save instance state (#903)
|
||||
* [GH-904](https://github.com/apache/cordova-plugin-camera/pull/904) fix(android): Use VERSION_CODES instead of hard-coded API literals (#904)
|
||||
* [GH-915](https://github.com/apache/cordova-plugin-camera/pull/905) fix(android): improper cache path construction during image manipulation (#905)
|
||||
* [GH-906](https://github.com/apache/cordova-plugin-camera/pull/906) refactor(android): replace image path usage with image uris (#906)
|
||||
* [GH-915](https://github.com/apache/cordova-plugin-camera/pull/907) refactor(android): remove query img usage (#907)
|
||||
* [GH-915](https://github.com/apache/cordova-plugin-camera/pull/915) fix!: Remove WRITE_EXTERNAL_PERMISSION (#915)
|
||||
|
||||
**CI**:
|
||||
|
||||
* [GH-890](https://github.com/apache/cordova-plugin-camera/pull/890) ci(android): Update **Android** CI to be compatible with `cordova-android`@13 (#890)
|
||||
* [GH-895](https://github.com/apache/cordova-plugin-camera/pull/895) ci: sync workflow with paramedic (#895)
|
||||
|
||||
**Documentation**:
|
||||
|
||||
* [GH-913](https://github.com/apache/cordova-plugin-camera/pull/913) docs: Revisions for v8 public API changes with the return string formats of getPicture (#913)
|
||||
|
||||
**Other**:
|
||||
|
||||
* [GH-898](https://github.com/apache/cordova-plugin-camera/pull/898) chore: Update eslint config to 5.1.0 (#898)
|
||||
* [GH-914](https://github.com/apache/cordova-plugin-camera/pull/914) deprecation: allowEdit (#914)
|
||||
|
||||
### 7.0.0 (Sep 06, 2023)
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
* [GH-848](https://github.com/apache/cordova-plugin-camera/pull/848) fix!: remove deprecated platforms
|
||||
* [GH-844](https://github.com/apache/cordova-plugin-camera/pull/844) feat(android)!: Android 13 support
|
||||
|
||||
**Fixes:**
|
||||
|
||||
* [GH-827](https://github.com/apache/cordova-plugin-camera/pull/827) fix(android): set `applicationId`
|
||||
* [GH-810](https://github.com/apache/cordova-plugin-camera/pull/810) fix(browser): use `navigator.mediaDevices.getUserMedia`
|
||||
* [GH-712](https://github.com/apache/cordova-plugin-camera/pull/712) fix(ios): preserving `EXIF` data
|
||||
* [GH-780](https://github.com/apache/cordova-plugin-camera/pull/780) fix(android): update queries in `plugin.xml`
|
||||
|
||||
**Chores, Dependencies, Docs:**
|
||||
|
||||
* [GH-850](https://github.com/apache/cordova-plugin-camera/pull/850) chore: remove windows/osx from `plugin.xml`
|
||||
* [GH-849](https://github.com/apache/cordova-plugin-camera/pull/849) chore: Update `SUPPORT_QUESTION.md` template
|
||||
* [GH-831](https://github.com/apache/cordova-plugin-camera/pull/831) chore(android): Cleanup obsolete `BuildConfig` comments
|
||||
* [GH-846](https://github.com/apache/cordova-plugin-camera/pull/846) dep(dev)!: bump `@cordova/eslint-config@5.0`
|
||||
* [GH-800](https://github.com/apache/cordova-plugin-camera/pull/800) dep(npm): bump package-lock v2 w/ rebuild
|
||||
* [GH-808](https://github.com/apache/cordova-plugin-camera/pull/808) docs(README): Document `ANDROIDX_CORE_VERSION` variable
|
||||
|
||||
**CI:**
|
||||
|
||||
* [GH-851](https://github.com/apache/cordova-plugin-camera/pull/851) ci(gh-action): sync with `paramedic` configs
|
||||
* [GH-835](https://github.com/apache/cordova-plugin-camera/pull/835) ci(android): Drop API 22 & 31. Added API 24 & 33
|
||||
* [GH-804](https://github.com/apache/cordova-plugin-camera/pull/804) ci: sync workflow with `paramedic`
|
||||
* [GH-798](https://github.com/apache/cordova-plugin-camera/pull/798) ci(android): update java requirement for `cordova-android@11`
|
||||
* [GH-770](https://github.com/apache/cordova-plugin-camera/pull/770) ci(ios): update workflow w/ iOS 15
|
||||
* [GH-766](https://github.com/apache/cordova-plugin-camera/pull/766) ci: remove old ci workflow
|
||||
* [GH-765](https://github.com/apache/cordova-plugin-camera/pull/765) ci: add action-badge
|
||||
* [GH-764](https://github.com/apache/cordova-plugin-camera/pull/764) ci: remove `travis` & `appveyor`
|
||||
* [GH-762](https://github.com/apache/cordova-plugin-camera/pull/762) ci: add `gh-actions` workflows
|
||||
|
||||
### 6.0.0 (Aug 19, 2021)
|
||||
|
||||
**Feature:**
|
||||
|
||||
* [GH-751](https://github.com/apache/cordova-plugin-camera/pull/751) feat(android)!: support **AndroidX**
|
||||
* [GH-750](https://github.com/apache/cordova-plugin-camera/pull/750) feat(android): bump `cordova-android` requirements for `10.x`
|
||||
* [GH-731](https://github.com/apache/cordova-plugin-camera/pull/731) feat(android): encode `heic` format to `EncodingType` for webview display [#711](https://github.com/apache/cordova-plugin-camera/issues/711)
|
||||
* [GH-684](https://github.com/apache/cordova-plugin-camera/pull/684) feat(android): `sdk-30` package visibility support
|
||||
|
||||
**Fix:**
|
||||
|
||||
* [GH-687](https://github.com/apache/cordova-plugin-camera/pull/687) fix(android): return exception message (where it exists)
|
||||
* [GH-585](https://github.com/apache/cordova-plugin-camera/pull/585) fix(android): file path correction if `Uri` authority is `FileProvider`
|
||||
|
||||
**Chore & CI:**
|
||||
|
||||
* [GH-749](https://github.com/apache/cordova-plugin-camera/pull/749) chore: bump plugin version for next major
|
||||
* [GH-654](https://github.com/apache/cordova-plugin-camera/pull/654) chore: add release notify action
|
||||
* [GH-745](https://github.com/apache/cordova-plugin-camera/pull/745) ci(gh-action): added workflow to run tests
|
||||
|
||||
### 5.0.3 (Aug 04, 2021)
|
||||
|
||||
* [GH-754](https://github.com/apache/cordova-plugin-camera/pull/754) chore: rebuilt `package-lock.json`
|
||||
* [GH-748](https://github.com/apache/cordova-plugin-camera/pull/748) fix: incorrect version in `package-lock`
|
||||
* [GH-747](https://github.com/apache/cordova-plugin-camera/pull/747) chore: set the 5.x versions locked to `cordova-android` `<10.0.0`
|
||||
* [GH-729](https://github.com/apache/cordova-plugin-camera/pull/729) chore(asf): Update GitHub repo metadata
|
||||
|
||||
### 5.0.2 (May 11, 2021)
|
||||
* [GH-728](https://github.com/apache/cordova-plugin-camera/pull/728) plugin release preparation - audit fix
|
||||
* [GH-700](https://github.com/apache/cordova-plugin-camera/pull/700) Bugfix [issue 665](https://github.com/apache/cordova-plugin-camera/issues/665) - app crashes after taking a picture due to a bug in the camera plugin when app is resumed
|
||||
|
4612
package-lock.json
generated
4612
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera",
|
||||
"version": "5.0.2",
|
||||
"version": "8.0.1-dev",
|
||||
"description": "Cordova Camera Plugin",
|
||||
"types": "./types/index.d.ts",
|
||||
"cordova": {
|
||||
@ -8,9 +8,7 @@
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios",
|
||||
"browser",
|
||||
"windows",
|
||||
"osx"
|
||||
"browser"
|
||||
]
|
||||
},
|
||||
"repository": "github:apache/cordova-plugin-camera",
|
||||
@ -21,9 +19,7 @@
|
||||
"ecosystem:cordova",
|
||||
"cordova-android",
|
||||
"cordova-ios",
|
||||
"cordova-browser",
|
||||
"cordova-windows",
|
||||
"cordova-osx"
|
||||
"cordova-browser"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run lint",
|
||||
@ -45,12 +41,32 @@
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"5.0.4-dev": {
|
||||
"cordova-android": "<10.0.0",
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"6.0.0": {
|
||||
"cordova-android": ">=10.0.0",
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"7.0.0": {
|
||||
"cordova-android": ">=12.0.0",
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"8.0.0": {
|
||||
"cordova-android": ">=12.0.0",
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"9.0.0": {
|
||||
"cordova": ">100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cordova/eslint-config": "^3.0.0"
|
||||
"@cordova/eslint-config": "^5.1.0"
|
||||
}
|
||||
}
|
||||
|
62
plugin.xml
62
plugin.xml
@ -21,7 +21,7 @@
|
||||
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
id="cordova-plugin-camera"
|
||||
version="5.0.2">
|
||||
version="8.0.1-dev">
|
||||
<name>Camera</name>
|
||||
<description>Cordova Camera Plugin</description>
|
||||
<license>Apache 2.0</license>
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
<engines>
|
||||
<engine name="cordova" version=">=9.0.0"/>
|
||||
<engine name="cordova-android" version=">=9.0.0" />
|
||||
<engine name="cordova-android" version=">=12.0.0" />
|
||||
<engine name="cordova-ios" version=">=5.1.0" />
|
||||
</engines>
|
||||
|
||||
@ -54,9 +54,6 @@
|
||||
<param name="android-package" value="org.apache.cordova.camera.CameraLauncher"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
<config-file target="AndroidManifest.xml" parent="/*">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
</config-file>
|
||||
<config-file target="AndroidManifest.xml" parent="application">
|
||||
<provider
|
||||
android:name="org.apache.cordova.camera.FileProvider"
|
||||
@ -69,6 +66,22 @@
|
||||
</provider>
|
||||
</config-file>
|
||||
|
||||
<config-file target="AndroidManifest.xml" parent="queries">
|
||||
<intent>
|
||||
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PICK" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="com.android.camera.action.CROP" />
|
||||
<data android:scheme="content" android:mimeType="image/*"/>
|
||||
</intent>
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/android/CameraLauncher.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/ExifHelper.java" target-dir="src/org/apache/cordova/camera" />
|
||||
@ -76,13 +89,12 @@
|
||||
<source-file src="src/android/GalleryPathVO.java" target-dir="src/org/apache/cordova/camera" />
|
||||
<source-file src="src/android/xml/camera_provider_paths.xml" target-dir="res/xml" />
|
||||
|
||||
<preference name="ANDROIDX_CORE_VERSION" default="1.6.+"/>
|
||||
<framework src="androidx.core:core:$ANDROIDX_CORE_VERSION" />
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
|
||||
<preference name="ANDROID_SUPPORT_V4_VERSION" default="27.+"/>
|
||||
<framework src="com.android.support:support-v4:$ANDROID_SUPPORT_V4_VERSION"/>
|
||||
|
||||
</platform>
|
||||
|
||||
<!-- ios -->
|
||||
@ -128,36 +140,4 @@
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- windows -->
|
||||
<platform name="windows">
|
||||
<config-file target="package.appxmanifest" parent="/Package/Capabilities">
|
||||
<DeviceCapability Name="webcam" />
|
||||
</config-file>
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
<js-module src="src/windows/CameraProxy.js" name="CameraProxy">
|
||||
<runs />
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- osx -->
|
||||
<platform name="osx">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<param name="osx-package" value="CDVCamera"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
|
||||
<header-file src="src/osx/CDVCamera.h" />
|
||||
<source-file src="src/osx/CDVCamera.m" />
|
||||
|
||||
<framework src="Quartz.framework" />
|
||||
<framework src="AppKit.framework" />
|
||||
</platform>
|
||||
|
||||
</plugin>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
||||
package org.apache.cordova.camera;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import android.media.ExifInterface;
|
||||
|
||||
@ -56,6 +57,16 @@ public class ExifHelper {
|
||||
this.inFile = new ExifInterface(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* The file before it is compressed
|
||||
*
|
||||
* @param input
|
||||
* @throws IOException
|
||||
*/
|
||||
public void createInFile(InputStream input) throws IOException {
|
||||
this.inFile = new ExifInterface(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* The file after it has been compressed
|
||||
*
|
||||
|
@ -19,7 +19,6 @@ package org.apache.cordova.camera;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.CursorLoader;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
@ -29,8 +28,8 @@ import android.provider.MediaStore;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -44,7 +43,7 @@ public class FileHelper {
|
||||
* Returns the real path of the given URI string.
|
||||
* If the given URI string represents a content:// URI, the real path is retrieved from the media store.
|
||||
*
|
||||
* @param uriString the URI string of the audio/image/video
|
||||
* @param uri the URI of the audio/image/video
|
||||
* @param cordova the current application context
|
||||
* @return the full path to the file
|
||||
*/
|
||||
@ -57,7 +56,7 @@ public class FileHelper {
|
||||
* Returns the real path of the given URI.
|
||||
* If the given URI is a content:// URI, the real path is retrieved from the media store.
|
||||
*
|
||||
* @param uri the URI of the audio/image/video
|
||||
* @param uriString the URI string from which to obtain the input stream
|
||||
* @param cordova the current application context
|
||||
* @return the full path to the file
|
||||
*/
|
||||
@ -132,6 +131,9 @@ public class FileHelper {
|
||||
if (isGooglePhotosUri(uri))
|
||||
return uri.getLastPathSegment();
|
||||
|
||||
if (isFileProviderUri(context, uri))
|
||||
return getFileProviderPath(context, uri);
|
||||
|
||||
return getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
@ -142,22 +144,6 @@ public class FileHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
|
||||
String[] proj = { MediaStore.Images.Media.DATA };
|
||||
String result = null;
|
||||
|
||||
try {
|
||||
Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
|
||||
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||
cursor.moveToFirst();
|
||||
result = cursor.getString(column_index);
|
||||
|
||||
} catch (Exception e) {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input stream based on given URI string.
|
||||
*
|
||||
@ -177,6 +163,7 @@ public class FileHelper {
|
||||
if (question > -1) {
|
||||
uriString = uriString.substring(0, question);
|
||||
}
|
||||
|
||||
if (uriString.startsWith("file:///android_asset/")) {
|
||||
Uri uri = Uri.parse(uriString);
|
||||
String relativePath = uri.getPath().substring(15);
|
||||
@ -206,6 +193,7 @@ public class FileHelper {
|
||||
* @return a path without the "file://" prefix
|
||||
*/
|
||||
public static String stripFileProtocol(String uriString) {
|
||||
|
||||
if (uriString.startsWith("file://")) {
|
||||
uriString = uriString.substring(7);
|
||||
}
|
||||
@ -225,7 +213,7 @@ public class FileHelper {
|
||||
}
|
||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the mime type of the data specified by the given URI string.
|
||||
*
|
||||
@ -233,7 +221,7 @@ public class FileHelper {
|
||||
* @return the mime type of the specified data
|
||||
*/
|
||||
public static String getMimeType(String uriString, CordovaInterface cordova) {
|
||||
String mimeType = null;
|
||||
String mimeType;
|
||||
|
||||
Uri uri = Uri.parse(uriString);
|
||||
if (uriString.startsWith("content://")) {
|
||||
@ -316,4 +304,28 @@ public class FileHelper {
|
||||
public static boolean isGooglePhotosUri(Uri uri) {
|
||||
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context The Application context
|
||||
* @param uri The Uri is checked by functions
|
||||
* @return Whether the Uri authority is FileProvider
|
||||
*/
|
||||
public static boolean isFileProviderUri(final Context context, final Uri uri) {
|
||||
final String packageName = context.getPackageName();
|
||||
final String authority = new StringBuilder(packageName).append(".provider").toString();
|
||||
return authority.equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context The Application context
|
||||
* @param uri The Uri is checked by functions
|
||||
* @return File path or null if file is missing
|
||||
*/
|
||||
public static String getFileProviderPath(final Context context, final Uri uri)
|
||||
{
|
||||
final File appDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||
final File file = new File(appDir, uri.getLastPathSegment());
|
||||
return file.exists() ? file.toString(): null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,4 +18,4 @@
|
||||
*/
|
||||
package org.apache.cordova.camera;
|
||||
|
||||
public class FileProvider extends android.support.v4.content.FileProvider {}
|
||||
public class FileProvider extends androidx.core.content.FileProvider {}
|
||||
|
@ -17,5 +17,5 @@
|
||||
-->
|
||||
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="cache_files" path="." />
|
||||
</paths>
|
||||
<cache-path name="cache_files" path="org.apache.cordova.camera/" />
|
||||
</paths>
|
||||
|
@ -19,13 +19,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
|
||||
const HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
|
||||
|
||||
function takePicture (success, error, opts) {
|
||||
if (opts && opts[2] === 1) {
|
||||
capture(success, error, opts);
|
||||
} else {
|
||||
var input = document.createElement('input');
|
||||
const input = document.createElement('input');
|
||||
input.style.position = 'relative';
|
||||
input.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX;
|
||||
input.className = 'cordova-camera-select';
|
||||
@ -33,13 +33,13 @@ function takePicture (success, error, opts) {
|
||||
input.name = 'files[]';
|
||||
|
||||
input.onchange = function (inputEvent) {
|
||||
var reader = new FileReader(); /* eslint no-undef : 0 */
|
||||
const reader = new FileReader(); /* eslint no-undef : 0 */
|
||||
reader.onload = function (readerEvent) {
|
||||
input.parentNode.removeChild(input);
|
||||
|
||||
var imageData = readerEvent.target.result;
|
||||
const imageData = readerEvent.target.result;
|
||||
|
||||
return success(imageData.substr(imageData.indexOf(',') + 1));
|
||||
return success(imageData);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(inputEvent.target.files[0]);
|
||||
@ -50,16 +50,16 @@ function takePicture (success, error, opts) {
|
||||
}
|
||||
|
||||
function capture (success, errorCallback, opts) {
|
||||
var localMediaStream;
|
||||
var targetWidth = opts[3];
|
||||
var targetHeight = opts[4];
|
||||
let localMediaStream;
|
||||
let targetWidth = opts[3];
|
||||
let targetHeight = opts[4];
|
||||
|
||||
targetWidth = targetWidth === -1 ? 320 : targetWidth;
|
||||
targetHeight = targetHeight === -1 ? 240 : targetHeight;
|
||||
|
||||
var video = document.createElement('video');
|
||||
var button = document.createElement('button');
|
||||
var parent = document.createElement('div');
|
||||
const video = document.createElement('video');
|
||||
const button = document.createElement('button');
|
||||
const parent = document.createElement('div');
|
||||
parent.style.position = 'relative';
|
||||
parent.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX;
|
||||
parent.className = 'cordova-camera-capture';
|
||||
@ -72,14 +72,13 @@ function capture (success, errorCallback, opts) {
|
||||
|
||||
button.onclick = function () {
|
||||
// create a canvas and capture a frame from video stream
|
||||
var canvas = document.createElement('canvas');
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = targetWidth;
|
||||
canvas.height = targetHeight;
|
||||
canvas.getContext('2d').drawImage(video, 0, 0, targetWidth, targetHeight);
|
||||
|
||||
// convert image stored in canvas to base64 encoded image
|
||||
var imageData = canvas.toDataURL('image/png');
|
||||
imageData = imageData.replace('data:image/png;base64,', '');
|
||||
const imageData = canvas.toDataURL('image/png');
|
||||
|
||||
// stop video stream, remove video and button.
|
||||
// Note that MediaStream.stop() is deprecated as of Chrome 47.
|
||||
@ -100,7 +99,7 @@ function capture (success, errorCallback, opts) {
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia;
|
||||
|
||||
var successCallback = function (stream) {
|
||||
const successCallback = function (stream) {
|
||||
localMediaStream = stream;
|
||||
if ('srcObject' in video) {
|
||||
video.srcObject = localMediaStream;
|
||||
@ -111,7 +110,11 @@ function capture (success, errorCallback, opts) {
|
||||
document.body.appendChild(parent);
|
||||
};
|
||||
|
||||
if (navigator.getUserMedia) {
|
||||
if (navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
|
||||
.then(successCallback)
|
||||
.catch(errorCallback);
|
||||
} else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia({ video: true, audio: false }, successCallback, errorCallback);
|
||||
} else {
|
||||
alert('Browser does not support camera :(');
|
||||
@ -119,7 +122,7 @@ function capture (success, errorCallback, opts) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
takePicture: takePicture,
|
||||
takePicture,
|
||||
cleanup: function () {}
|
||||
};
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
#import <ImageIO/CGImageDestination.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <objc/message.h>
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
#ifndef __CORDOVA_4_0_0
|
||||
#import <Cordova/NSData+Base64.h>
|
||||
@ -57,6 +58,9 @@ static NSString* toBase64(NSData* data) {
|
||||
}
|
||||
}
|
||||
|
||||
static NSString* MIME_PNG = @"image/png";
|
||||
static NSString* MIME_JPEG = @"image/jpeg";
|
||||
|
||||
@implementation CDVPictureOptions
|
||||
|
||||
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command
|
||||
@ -159,7 +163,7 @@ static NSString* toBase64(NSData* data) {
|
||||
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera) {
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
|
||||
{
|
||||
if(!granted)
|
||||
if (!granted)
|
||||
{
|
||||
// Denied; show an alert
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@ -174,11 +178,32 @@ static NSString* toBase64(NSData* data) {
|
||||
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
} else {
|
||||
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
|
||||
});
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
|
||||
[weakSelf options:pictureOptions requestPhotoPermissions:^(BOOL granted) {
|
||||
if (!granted) {
|
||||
// Denied; show an alert
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] message:NSLocalizedString(@"Access to the camera roll has been prohibited; please enable it in the Settings to continue.", nil) preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[weakSelf sendNoPermissionResult:command.callbackId];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Settings", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
|
||||
[weakSelf sendNoPermissionResult:command.callbackId];
|
||||
}]];
|
||||
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
@ -350,16 +375,50 @@ static NSString* toBase64(NSData* data) {
|
||||
self.hasPendingOperation = NO;
|
||||
}
|
||||
|
||||
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
|
||||
- (NSString*) getMimeForEncoding:(CDVEncodingType) encoding {
|
||||
switch (encoding) {
|
||||
case EncodingTypePNG: return MIME_PNG;
|
||||
case EncodingTypeJPEG:
|
||||
default:
|
||||
return MIME_JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString*) formatAsDataURI:(NSData*) data withMIME:(NSString*) mime {
|
||||
NSString* base64 = toBase64(data);
|
||||
|
||||
if (base64 == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"data:%@;base64,%@", mime, base64];
|
||||
}
|
||||
|
||||
- (NSString*) processImageAsDataUri:(UIImage*) image info:(NSDictionary*) info options:(CDVPictureOptions*) options
|
||||
{
|
||||
NSString* mime = nil;
|
||||
NSData* data = [self processImage: image info: info options: options outMime: &mime];
|
||||
|
||||
return [self formatAsDataURI: data withMIME: mime];
|
||||
}
|
||||
|
||||
- (NSData*) processImage:(UIImage*) image info:(NSDictionary*) info options:(CDVPictureOptions*) options
|
||||
{
|
||||
return [self processImage:image info: info options: options outMime: nil];
|
||||
}
|
||||
|
||||
- (NSData*) processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options outMime:(NSString**) outMime
|
||||
{
|
||||
NSData* data = nil;
|
||||
|
||||
switch (options.encodingType) {
|
||||
case EncodingTypePNG:
|
||||
data = UIImagePNGRepresentation(image);
|
||||
if (outMime != nil) *outMime = MIME_PNG;
|
||||
break;
|
||||
case EncodingTypeJPEG:
|
||||
{
|
||||
if (outMime != nil) *outMime = MIME_JPEG;
|
||||
if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO) && (([options.quality integerValue] == 100) || (options.sourceType != UIImagePickerControllerSourceTypeCamera))){
|
||||
// use image unedited as requested , don't resize
|
||||
data = UIImageJPEGRepresentation(image, 1.0);
|
||||
@ -367,33 +426,133 @@ static NSString* toBase64(NSData* data) {
|
||||
data = UIImageJPEGRepresentation(image, [options.quality floatValue] / 100.0f);
|
||||
}
|
||||
|
||||
if (options.usesGeolocation) {
|
||||
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
|
||||
if (pickerController.sourceType == UIImagePickerControllerSourceTypeCamera) {
|
||||
if (options.usesGeolocation) {
|
||||
NSDictionary* controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"];
|
||||
if (controllerMetadata) {
|
||||
self.data = data;
|
||||
self.metadata = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
|
||||
if (EXIFDictionary) {
|
||||
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
|
||||
}
|
||||
|
||||
if (IsAtLeastiOSVersion(@"8.0")) {
|
||||
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
|
||||
}
|
||||
[[self locationManager] startUpdatingLocation];
|
||||
}
|
||||
data = nil;
|
||||
}
|
||||
} else if (pickerController.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
|
||||
PHAsset* asset = [info objectForKey:@"UIImagePickerControllerPHAsset"];
|
||||
NSDictionary* controllerMetadata = [self getImageMetadataFromAsset:asset];
|
||||
|
||||
self.data = data;
|
||||
if (controllerMetadata) {
|
||||
self.data = data;
|
||||
self.metadata = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSMutableDictionary* EXIFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyExifDictionary]mutableCopy];
|
||||
if (EXIFDictionary) {
|
||||
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
|
||||
}
|
||||
|
||||
if (IsAtLeastiOSVersion(@"8.0")) {
|
||||
[[self locationManager] performSelector:NSSelectorFromString(@"requestWhenInUseAuthorization") withObject:nil afterDelay:0];
|
||||
NSMutableDictionary* TIFFDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyTIFFDictionary
|
||||
]mutableCopy];
|
||||
if (TIFFDictionary) {
|
||||
[self.metadata setObject:TIFFDictionary forKey:(NSString*)kCGImagePropertyTIFFDictionary];
|
||||
}
|
||||
NSMutableDictionary* GPSDictionary = [[controllerMetadata objectForKey:(NSString*)kCGImagePropertyGPSDictionary
|
||||
]mutableCopy];
|
||||
if (GPSDictionary) {
|
||||
[self.metadata setObject:GPSDictionary forKey:(NSString*)kCGImagePropertyGPSDictionary
|
||||
];
|
||||
}
|
||||
[[self locationManager] startUpdatingLocation];
|
||||
}
|
||||
data = nil;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------
|
||||
-- get the metadata of the image from a PHAsset
|
||||
-------------------------------------------------------------- */
|
||||
- (NSDictionary*)getImageMetadataFromAsset:(PHAsset*)asset {
|
||||
|
||||
if(asset == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// get photo info from this asset
|
||||
__block NSDictionary *dict = nil;
|
||||
PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
|
||||
imageRequestOptions.synchronous = YES;
|
||||
[[PHImageManager defaultManager]
|
||||
requestImageDataForAsset:asset
|
||||
options:imageRequestOptions
|
||||
resultHandler: ^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
|
||||
dict = [self convertImageMetadata:imageData]; // as this imageData is in NSData format so we need a method to convert this NSData into NSDictionary
|
||||
}];
|
||||
return dict;
|
||||
}
|
||||
|
||||
-(NSDictionary*)convertImageMetadata:(NSData*)imageData {
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
|
||||
if (imageSource) {
|
||||
NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
|
||||
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
|
||||
if (imageProperties) {
|
||||
NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
|
||||
CFRelease(imageProperties);
|
||||
CFRelease(imageSource);
|
||||
NSLog(@"Metadata of selected image%@", metadata);// image metadata after converting NSData into NSDictionary
|
||||
return metadata;
|
||||
}
|
||||
CFRelease(imageSource);
|
||||
}
|
||||
|
||||
NSLog(@"Can't read image metadata");
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)options:(CDVPictureOptions*)options requestPhotoPermissions:(void (^)(BOOL auth))completion
|
||||
{
|
||||
if((unsigned long)options.sourceType == 1){
|
||||
completion(YES);
|
||||
}
|
||||
else{
|
||||
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
|
||||
|
||||
switch (status) {
|
||||
case PHAuthorizationStatusAuthorized:
|
||||
completion(YES);
|
||||
break;
|
||||
case PHAuthorizationStatusNotDetermined: {
|
||||
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus authorizationStatus) {
|
||||
if (authorizationStatus == PHAuthorizationStatusAuthorized) {
|
||||
completion(YES);
|
||||
} else {
|
||||
completion(NO);
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
completion(NO);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (NSString*)tempFilePath:(NSString*)extension
|
||||
{
|
||||
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
|
||||
@ -443,9 +602,9 @@ static NSString* toBase64(NSData* data) {
|
||||
case DestinationTypeDataUrl:
|
||||
{
|
||||
image = [self retrieveImage:info options:options];
|
||||
NSData* data = [self processImage:image info:info options:options];
|
||||
NSString* data = [self processImageAsDataUri:image info:info options:options];
|
||||
if (data) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(data)];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: data];
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -453,18 +612,50 @@ static NSString* toBase64(NSData* data) {
|
||||
{
|
||||
image = [self retrieveImage:info options:options];
|
||||
NSData* data = [self processImage:image info:info options:options];
|
||||
|
||||
if (data) {
|
||||
if (pickerController.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
|
||||
NSMutableData *imageDataWithExif = [NSMutableData data];
|
||||
if (self.metadata) {
|
||||
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)self.data, NULL);
|
||||
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
|
||||
|
||||
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
|
||||
NSString* filePath = [self tempFilePath:extension];
|
||||
NSError* err = nil;
|
||||
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWithExif, sourceType, 1, NULL);
|
||||
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
|
||||
CGImageDestinationFinalize(destinationImage);
|
||||
|
||||
// save file
|
||||
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
|
||||
} else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
|
||||
CFRelease(sourceImage);
|
||||
CFRelease(destinationImage);
|
||||
} else {
|
||||
imageDataWithExif = [self.data mutableCopy];
|
||||
}
|
||||
|
||||
NSError* err = nil;
|
||||
NSString* extension = self.pickerController.pictureOptions.encodingType == EncodingTypePNG ? @"png":@"jpg";
|
||||
NSString* filePath = [self tempFilePath:extension];
|
||||
|
||||
// save file
|
||||
if (![imageDataWithExif writeToFile:filePath options:NSAtomicWrite error:&err]) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
|
||||
}
|
||||
else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
|
||||
}
|
||||
|
||||
} else if (pickerController.sourceType != UIImagePickerControllerSourceTypeCamera || !options.usesGeolocation) {
|
||||
// No need to save file if usesGeolocation is true since it will be saved after the location is tracked
|
||||
NSString* extension = options.encodingType == EncodingTypePNG? @"png" : @"jpg";
|
||||
NSString* filePath = [self tempFilePath:extension];
|
||||
NSError* err = nil;
|
||||
|
||||
// save file
|
||||
if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
|
||||
} else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[self urlTransformer:[NSURL fileURLWithPath:filePath]] absoluteString]];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -649,25 +840,31 @@ static NSString* toBase64(NSData* data) {
|
||||
{
|
||||
CDVPictureOptions* options = self.pickerController.pictureOptions;
|
||||
CDVPluginResult* result = nil;
|
||||
|
||||
NSMutableData *imageDataWithExif = [NSMutableData data];
|
||||
|
||||
if (self.metadata) {
|
||||
NSData* dataCopy = [self.data mutableCopy];
|
||||
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge CFDataRef)dataCopy, NULL);
|
||||
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
|
||||
|
||||
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
|
||||
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataWithExif, sourceType, 1, NULL);
|
||||
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
|
||||
CGImageDestinationFinalize(destinationImage);
|
||||
|
||||
dataCopy = nil;
|
||||
CFRelease(sourceImage);
|
||||
CFRelease(destinationImage);
|
||||
} else {
|
||||
imageDataWithExif = [self.data mutableCopy];
|
||||
}
|
||||
|
||||
switch (options.destinationType) {
|
||||
case DestinationTypeDataUrl:
|
||||
{
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(self.data)];
|
||||
NSString* mime = [self getMimeForEncoding: self.pickerController.pictureOptions.encodingType];
|
||||
NSString* uri = [self formatAsDataURI: self.data withMIME: mime];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: uri];
|
||||
}
|
||||
break;
|
||||
default: // DestinationTypeFileUri
|
||||
@ -695,7 +892,7 @@ static NSString* toBase64(NSData* data) {
|
||||
self.pickerController = nil;
|
||||
self.data = nil;
|
||||
self.metadata = nil;
|
||||
|
||||
imageDataWithExif = nil;
|
||||
if (options.saveToPhotoAlbum) {
|
||||
UIImageWriteToSavedPhotosAlbum([[UIImage alloc] initWithData:self.data], nil, nil, nil);
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
#import <Quartz/Quartz.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <Cordova/CDVPlugin.h>
|
||||
|
||||
|
||||
|
||||
enum CDVDestinationType {
|
||||
DestinationTypeDataUrl = 0,
|
||||
DestinationTypeFileUri
|
||||
};
|
||||
typedef NSUInteger CDVDestinationType;
|
||||
|
||||
enum CDVSourceType {
|
||||
SourceTypePhotoLibrary = 0,
|
||||
SourceTypeCamera,
|
||||
SourceTypePhotoAlbum
|
||||
};
|
||||
typedef NSUInteger CDVSourceType;
|
||||
|
||||
enum CDVEncodingType {
|
||||
EncodingTypeJPEG = 0,
|
||||
EncodingTypePNG
|
||||
};
|
||||
typedef NSUInteger CDVEncodingType;
|
||||
|
||||
enum CDVMediaType {
|
||||
MediaTypePicture = 0,
|
||||
MediaTypeVideo,
|
||||
MediaTypeAll
|
||||
};
|
||||
typedef NSUInteger CDVMediaType;
|
||||
|
||||
|
||||
// ======================================================================= //
|
||||
|
||||
|
||||
@interface CDVPictureOptions : NSObject
|
||||
|
||||
@property (strong) NSNumber *quality;
|
||||
@property (assign) CDVDestinationType destinationType;
|
||||
@property (assign) CDVSourceType sourceType;
|
||||
@property (assign) CGSize targetSize;
|
||||
@property (assign) CDVEncodingType encodingType;
|
||||
@property (assign) CDVMediaType mediaType;
|
||||
@property (assign) BOOL allowsEditing;
|
||||
@property (assign) BOOL correctOrientation;
|
||||
@property (assign) BOOL saveToPhotoAlbum;
|
||||
|
||||
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand *)command;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// ======================================================================= //
|
||||
|
||||
|
||||
@interface CDVCamera : CDVPlugin
|
||||
|
||||
- (void)takePicture:(CDVInvokedUrlCommand *)command;
|
||||
- (void)cleanup:(CDVInvokedUrlCommand *)command;
|
||||
|
||||
@end
|
@ -1,258 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
#import "CDVCamera.h"
|
||||
|
||||
|
||||
@implementation CDVPictureOptions
|
||||
|
||||
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command {
|
||||
CDVPictureOptions *pictureOptions = [[CDVPictureOptions alloc] init];
|
||||
|
||||
pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)];
|
||||
pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue];
|
||||
pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(SourceTypeCamera)] unsignedIntegerValue];
|
||||
|
||||
NSNumber *targetWidth = [command argumentAtIndex:3 withDefault:nil];
|
||||
NSNumber *targetHeight = [command argumentAtIndex:4 withDefault:nil];
|
||||
pictureOptions.targetSize = CGSizeMake(0, 0);
|
||||
if ((targetWidth != nil) && (targetHeight != nil)) {
|
||||
pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
|
||||
}
|
||||
|
||||
pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue];
|
||||
pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue];
|
||||
pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
|
||||
pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
|
||||
pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
|
||||
|
||||
return pictureOptions;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// ======================================================================= //
|
||||
|
||||
|
||||
@implementation CDVCamera
|
||||
|
||||
/*!
|
||||
Static array that stores the temporary created files allowing to delete them when calling navigator.camera.cleanup(...)
|
||||
*/
|
||||
static NSMutableArray *cleanUpFiles;
|
||||
|
||||
+ (void)initialize {
|
||||
cleanUpFiles = [NSMutableArray array];
|
||||
}
|
||||
|
||||
- (void)takePicture:(CDVInvokedUrlCommand *)command {
|
||||
CDVPictureOptions *pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
if (pictureOptions.sourceType == SourceTypeCamera) {
|
||||
[self takePictureFromCamera:command withOptions:pictureOptions];
|
||||
} else {
|
||||
[self takePictureFromFile:command withOptions:pictureOptions];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cleanup:(CDVInvokedUrlCommand*)command {
|
||||
[self.commandDelegate runInBackground:^{
|
||||
if (cleanUpFiles.count > 0) {
|
||||
for (int i=0; i<cleanUpFiles.count; i++) {
|
||||
NSString *path = [cleanUpFiles objectAtIndex:i];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
}
|
||||
|
||||
[cleanUpFiles removeAllObjects];
|
||||
|
||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Camera
|
||||
|
||||
/*!
|
||||
Takes a picture from the iSight camera using the default OS dialog.
|
||||
@see https://developer.apple.com/documentation/quartz/ikpicturetaker
|
||||
*/
|
||||
- (void)takePictureFromCamera:(CDVInvokedUrlCommand *)command withOptions:(CDVPictureOptions *)pictureOptions {
|
||||
IKPictureTaker *pictureTaker = [IKPictureTaker pictureTaker];
|
||||
[pictureTaker setValue:[NSNumber numberWithBool:YES] forKey:IKPictureTakerAllowsVideoCaptureKey];
|
||||
[pictureTaker setValue:[NSNumber numberWithBool:NO] forKey:IKPictureTakerAllowsFileChoosingKey];
|
||||
[pictureTaker setValue:[NSNumber numberWithBool:pictureOptions.allowsEditing] forKey:IKPictureTakerShowEffectsKey];
|
||||
[pictureTaker setValue:[NSNumber numberWithBool:pictureOptions.allowsEditing] forKey:IKPictureTakerAllowsEditingKey];
|
||||
|
||||
NSDictionary *contextInfo = @{ @"command": command, @"pictureOptions" : pictureOptions};
|
||||
[pictureTaker beginPictureTakerSheetForWindow:self.viewController.contentView.window withDelegate:self didEndSelector:@selector(pictureTakerDidEnd:returnCode:contextInfo:) contextInfo:(void *)CFBridgingRetain(contextInfo)];
|
||||
|
||||
}
|
||||
|
||||
- (void)pictureTakerDidEnd:(IKPictureTaker *)pictureTaker returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||
if (returnCode == NSOKButton) {
|
||||
NSDictionary *contextInfoDictionary = (NSDictionary *)CFBridgingRelease(contextInfo);
|
||||
CDVInvokedUrlCommand *command = [contextInfoDictionary valueForKey:@"command"];
|
||||
CDVPictureOptions *pictureOptions = [contextInfoDictionary valueForKey:@"pictureOptions"];
|
||||
|
||||
[self returnImage:pictureTaker.outputImage command:command options:pictureOptions];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - File
|
||||
|
||||
/*!
|
||||
Allows to select an image or video using the system native dialog.
|
||||
*/
|
||||
- (void)takePictureFromFile:(CDVInvokedUrlCommand *)command withOptions:(CDVPictureOptions *)pictureOptions {
|
||||
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
||||
openPanel.canChooseFiles = YES;
|
||||
openPanel.canChooseDirectories = NO;
|
||||
openPanel.canCreateDirectories = YES;
|
||||
openPanel.allowsMultipleSelection = NO;
|
||||
|
||||
NSMutableArray *allowedTypes = [NSMutableArray array];
|
||||
if (pictureOptions.mediaType == MediaTypePicture || pictureOptions.mediaType == MediaTypeAll) {
|
||||
[allowedTypes addObjectsFromArray:[NSImage imageTypes]];
|
||||
}
|
||||
if (pictureOptions.mediaType == MediaTypeVideo || pictureOptions.mediaType == MediaTypeAll) {
|
||||
[allowedTypes addObjectsFromArray:@[(NSString *)kUTTypeMovie]];
|
||||
}
|
||||
[openPanel setAllowedFileTypes:allowedTypes];
|
||||
|
||||
[openPanel beginSheetModalForWindow:self.viewController.contentView.window completionHandler:^(NSInteger result) {
|
||||
if (result == NSOKButton) {
|
||||
NSURL *fileURL = [openPanel.URLs objectAtIndex:0];
|
||||
|
||||
if ([self fileIsImage:fileURL]) {
|
||||
NSImage *image = [[NSImage alloc] initWithContentsOfFile:fileURL.path];
|
||||
[self returnImage:image command:command options:pictureOptions];
|
||||
} else {
|
||||
if (pictureOptions.destinationType == DestinationTypeDataUrl) {
|
||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Camera.DestinationType.DATA_URL is only available with image files"];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
||||
} else {
|
||||
[self returnUri:fileURL.path command:command options:pictureOptions];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Common
|
||||
|
||||
/*!
|
||||
Returns to JavaScript a URI.
|
||||
Called when Camera.DestinationType.FILE_URI.
|
||||
*/
|
||||
- (void)returnUri:(NSString *)path command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
|
||||
NSString *protocol = (pictureOptions.destinationType == DestinationTypeFileUri) ? @"file://" : @"";
|
||||
NSString *uri = [NSString stringWithFormat:@"%@%@", protocol, path];
|
||||
|
||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:uri];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns to JavaScript a base64 encoded image.
|
||||
Called when Camera.DestinationType.DATA_URL.
|
||||
*/
|
||||
- (void)returnImage:(NSImage *)image command:(CDVInvokedUrlCommand *)command options:(CDVPictureOptions *)pictureOptions {
|
||||
[self.commandDelegate runInBackground:^{
|
||||
NSData *processedImageData = [self processImage:image options:pictureOptions];
|
||||
|
||||
if (pictureOptions.destinationType == DestinationTypeDataUrl) {
|
||||
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[processedImageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
||||
} else {
|
||||
NSString *tempFilePath = [self uniqueImageName:pictureOptions];
|
||||
[processedImageData writeToFile:tempFilePath atomically:YES];
|
||||
[cleanUpFiles addObject:tempFilePath];
|
||||
[self returnUri:tempFilePath command:command options:pictureOptions];
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
Top level method to apply the size and quality required changes to an image.
|
||||
*/
|
||||
- (NSData *)processImage:(NSImage *)image options:(CDVPictureOptions *)pictureOptions {
|
||||
NSImage *sourceImage = image;
|
||||
if (pictureOptions.targetSize.width > 0 && pictureOptions.targetSize.height > 0) {
|
||||
sourceImage = [self resizeImage:sourceImage toSize:pictureOptions.targetSize];
|
||||
}
|
||||
|
||||
CGImageRef cgRef = [sourceImage CGImageForProposedRect:NULL context:nil hints:nil];
|
||||
NSBitmapImageRep *imageRepresentation = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
|
||||
|
||||
NSData *data = (pictureOptions.encodingType == EncodingTypeJPEG)
|
||||
? [imageRepresentation representationUsingType:NSJPEGFileType properties:@{NSImageCompressionFactor: [NSNumber numberWithFloat:pictureOptions.quality.floatValue/100.f]}]
|
||||
: [imageRepresentation representationUsingType:NSPNGFileType properties:@{NSImageCompressionFactor: @1.0}];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/*!
|
||||
Auxiliar method to resize an image.
|
||||
*/
|
||||
- (NSImage *)resizeImage:(NSImage *)image toSize:(CGSize)newSize {
|
||||
CGFloat aspectWidth = newSize.width / image.size.width;
|
||||
CGFloat aspectHeight = newSize.height / image.size.height;
|
||||
CGFloat aspectRatio = MIN(aspectWidth, aspectHeight);
|
||||
|
||||
CGSize scaledSize = NSMakeSize(image.size.width*aspectRatio, image.size.height*aspectRatio);
|
||||
|
||||
NSImage *smallImage = [[NSImage alloc] initWithSize: scaledSize];
|
||||
[smallImage lockFocus];
|
||||
[image setSize: scaledSize];
|
||||
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
|
||||
[image drawAtPoint:NSZeroPoint fromRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height) operation:NSCompositeCopy fraction:1.0];
|
||||
[smallImage unlockFocus];
|
||||
return smallImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Auxiliar method to know if a given file is an image or not.
|
||||
*/
|
||||
- (BOOL)fileIsImage:(NSURL *)fileURL {
|
||||
NSString *type;
|
||||
BOOL isImage = NO;
|
||||
|
||||
if ([fileURL getResourceValue:&type forKey:NSURLTypeIdentifierKey error:nil]) {
|
||||
isImage = [[NSImage imageTypes] containsObject:type];
|
||||
}
|
||||
|
||||
return isImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Auxiliar method that generates an unique filename for an image in the temporary directory.
|
||||
*/
|
||||
- (NSString *)uniqueImageName:(CDVPictureOptions *)pictureOptions {
|
||||
NSString *tempDir = NSTemporaryDirectory();
|
||||
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] ;
|
||||
NSString *extension = (pictureOptions.encodingType == EncodingTypeJPEG) ? @"jpeg" : @"png";
|
||||
NSString *uniqueFileName = [NSString stringWithFormat:@"%@%@.%@", tempDir, guid, extension];
|
||||
return uniqueFileName;
|
||||
}
|
||||
|
||||
@end
|
@ -1,861 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/* global Windows:true, URL:true, module:true, require:true, WinJS:true */
|
||||
|
||||
var Camera = require('./Camera');
|
||||
|
||||
var getAppData = function () {
|
||||
return Windows.Storage.ApplicationData.current;
|
||||
};
|
||||
var encodeToBase64String = function (buffer) {
|
||||
return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
};
|
||||
var OptUnique = Windows.Storage.CreationCollisionOption.generateUniqueName;
|
||||
var CapMSType = Windows.Media.Capture.MediaStreamType;
|
||||
var webUIApp = Windows.UI.WebUI.WebUIApplication;
|
||||
var fileIO = Windows.Storage.FileIO;
|
||||
var pickerLocId = Windows.Storage.Pickers.PickerLocationId;
|
||||
|
||||
module.exports = {
|
||||
|
||||
// args will contain :
|
||||
// ... it is an array, so be careful
|
||||
// 0 quality:50,
|
||||
// 1 destinationType:Camera.DestinationType.FILE_URI,
|
||||
// 2 sourceType:Camera.PictureSourceType.CAMERA,
|
||||
// 3 targetWidth:-1,
|
||||
// 4 targetHeight:-1,
|
||||
// 5 encodingType:Camera.EncodingType.JPEG,
|
||||
// 6 mediaType:Camera.MediaType.PICTURE,
|
||||
// 7 allowEdit:false,
|
||||
// 8 correctOrientation:false,
|
||||
// 9 saveToPhotoAlbum:false,
|
||||
// 10 popoverOptions:null
|
||||
// 11 cameraDirection:0
|
||||
|
||||
takePicture: function (successCallback, errorCallback, args) {
|
||||
var sourceType = args[2];
|
||||
|
||||
if (sourceType !== Camera.PictureSourceType.CAMERA) {
|
||||
takePictureFromFile(successCallback, errorCallback, args);
|
||||
} else {
|
||||
takePictureFromCamera(successCallback, errorCallback, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/apps/ff462087(v=vs.105).aspx
|
||||
var windowsVideoContainers = ['.avi', '.flv', '.asx', '.asf', '.mov', '.mp4', '.mpg', '.rm', '.srt', '.swf', '.wmv', '.vob'];
|
||||
var windowsPhoneVideoContainers = ['.avi', '.3gp', '.3g2', '.wmv', '.3gp', '.3g2', '.mp4', '.m4v'];
|
||||
|
||||
// Default aspect ratio 1.78 (16:9 hd video standard)
|
||||
var DEFAULT_ASPECT_RATIO = '1.8';
|
||||
|
||||
// Highest possible z-index supported across browsers. Anything used above is converted to this value.
|
||||
var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
|
||||
|
||||
// Resize method
|
||||
function resizeImage (successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
|
||||
var tempPhotoFileName = '';
|
||||
var targetContentType = '';
|
||||
|
||||
if (encodingType === Camera.EncodingType.PNG) {
|
||||
tempPhotoFileName = 'camera_cordova_temp_return.png';
|
||||
targetContentType = 'image/png';
|
||||
} else {
|
||||
tempPhotoFileName = 'camera_cordova_temp_return.jpg';
|
||||
targetContentType = 'image/jpeg';
|
||||
}
|
||||
|
||||
var storageFolder = getAppData().localFolder;
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting)
|
||||
.then(function (storageFile) {
|
||||
return fileIO.readBufferAsync(storageFile);
|
||||
})
|
||||
.then(function (buffer) {
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
var imageData = 'data:' + file.contentType + ';base64,' + strBase64;
|
||||
var image = new Image(); /* eslint no-undef : 0 */
|
||||
image.src = imageData;
|
||||
image.onload = function () {
|
||||
var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
|
||||
var imageWidth = ratio * this.width;
|
||||
var imageHeight = ratio * this.height;
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
var storageFileName;
|
||||
|
||||
canvas.width = imageWidth;
|
||||
canvas.height = imageHeight;
|
||||
|
||||
canvas.getContext('2d').drawImage(this, 0, 0, imageWidth, imageHeight);
|
||||
|
||||
var fileContent = canvas.toDataURL(targetContentType).split(',')[1];
|
||||
|
||||
var storageFolder = getAppData().localFolder;
|
||||
|
||||
storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
|
||||
.then(function (storagefile) {
|
||||
var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
|
||||
storageFileName = storagefile.name;
|
||||
return fileIO.writeBufferAsync(storagefile, content);
|
||||
})
|
||||
.done(function () {
|
||||
successCallback('ms-appdata:///local/' + storageFileName);
|
||||
}, errorCallback);
|
||||
};
|
||||
})
|
||||
.done(null, function (err) {
|
||||
errorCallback(err);
|
||||
});
|
||||
}
|
||||
|
||||
// Because of asynchronous method, so let the successCallback be called in it.
|
||||
function resizeImageBase64 (successCallback, errorCallback, file, targetWidth, targetHeight) {
|
||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
var imageData = 'data:' + file.contentType + ';base64,' + strBase64;
|
||||
|
||||
var image = new Image(); /* eslint no-undef : 0 */
|
||||
image.src = imageData;
|
||||
|
||||
image.onload = function () {
|
||||
var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
|
||||
var imageWidth = ratio * this.width;
|
||||
var imageHeight = ratio * this.height;
|
||||
var canvas = document.createElement('canvas');
|
||||
|
||||
canvas.width = imageWidth;
|
||||
canvas.height = imageHeight;
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
|
||||
|
||||
// The resized file ready for upload
|
||||
var finalFile = canvas.toDataURL(file.contentType);
|
||||
|
||||
// Remove the prefix such as "data:" + contentType + ";base64," , in order to meet the Cordova API.
|
||||
var arr = finalFile.split(',');
|
||||
var newStr = finalFile.substr(arr[0].length + 1);
|
||||
successCallback(newStr);
|
||||
};
|
||||
}, function (err) { errorCallback(err); });
|
||||
}
|
||||
|
||||
function takePictureFromFile (successCallback, errorCallback, args) {
|
||||
// Detect Windows Phone
|
||||
if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
|
||||
takePictureFromFileWP(successCallback, errorCallback, args);
|
||||
} else {
|
||||
takePictureFromFileWindows(successCallback, errorCallback, args);
|
||||
}
|
||||
}
|
||||
|
||||
function takePictureFromFileWP (successCallback, errorCallback, args) {
|
||||
var mediaType = args[6];
|
||||
var destinationType = args[1];
|
||||
var targetWidth = args[3];
|
||||
var targetHeight = args[4];
|
||||
var encodingType = args[5];
|
||||
|
||||
/*
|
||||
Need to add and remove an event listener to catch activation state
|
||||
Using FileOpenPicker will suspend the app and it's required to catch the PickSingleFileAndContinue
|
||||
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
|
||||
*/
|
||||
var filePickerActivationHandler = function (eventArgs) {
|
||||
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickFileContinuation) {
|
||||
var file = eventArgs.files[0];
|
||||
if (!file) {
|
||||
errorCallback("User didn't choose a file.");
|
||||
webUIApp.removeEventListener('activated', filePickerActivationHandler);
|
||||
return;
|
||||
}
|
||||
if (destinationType === Camera.DestinationType.FILE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
|
||||
} else {
|
||||
var storageFolder = getAppData().localFolder;
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
}, function () {
|
||||
errorCallback("Can't access localStorage folder.");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
|
||||
} else {
|
||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
}, errorCallback);
|
||||
}
|
||||
}
|
||||
webUIApp.removeEventListener('activated', filePickerActivationHandler);
|
||||
}
|
||||
};
|
||||
|
||||
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
|
||||
if (mediaType === Camera.MediaType.PICTURE) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']);
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
|
||||
} else if (mediaType === Camera.MediaType.VIDEO) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers);
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
|
||||
} else {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(['*']);
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
|
||||
}
|
||||
|
||||
webUIApp.addEventListener('activated', filePickerActivationHandler);
|
||||
fileOpenPicker.pickSingleFileAndContinue();
|
||||
}
|
||||
|
||||
function takePictureFromFileWindows (successCallback, errorCallback, args) {
|
||||
var mediaType = args[6];
|
||||
var destinationType = args[1];
|
||||
var targetWidth = args[3];
|
||||
var targetHeight = args[4];
|
||||
var encodingType = args[5];
|
||||
|
||||
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
|
||||
if (mediaType === Camera.MediaType.PICTURE) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']);
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
|
||||
} else if (mediaType === Camera.MediaType.VIDEO) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers);
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
|
||||
} else {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(['*']);
|
||||
fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
|
||||
}
|
||||
|
||||
fileOpenPicker.pickSingleFileAsync().done(function (file) {
|
||||
if (!file) {
|
||||
errorCallback("User didn't choose a file.");
|
||||
return;
|
||||
}
|
||||
if (destinationType === Camera.DestinationType.FILE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
|
||||
} else {
|
||||
var storageFolder = getAppData().localFolder;
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
|
||||
successCallback(URL.createObjectURL(storageFile));
|
||||
}, function () {
|
||||
errorCallback("Can't access localStorage folder.");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
|
||||
} else {
|
||||
fileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
}, errorCallback);
|
||||
}
|
||||
}
|
||||
}, function () {
|
||||
errorCallback("User didn't choose a file.");
|
||||
});
|
||||
}
|
||||
|
||||
function takePictureFromCamera (successCallback, errorCallback, args) {
|
||||
// Check if necessary API available
|
||||
if (!Windows.Media.Capture.CameraCaptureUI) {
|
||||
takePictureFromCameraWP(successCallback, errorCallback, args);
|
||||
} else {
|
||||
takePictureFromCameraWindows(successCallback, errorCallback, args);
|
||||
}
|
||||
}
|
||||
|
||||
function takePictureFromCameraWP (successCallback, errorCallback, args) {
|
||||
// We are running on WP8.1 which lacks CameraCaptureUI class
|
||||
// so we need to use MediaCapture class instead and implement custom UI for camera
|
||||
var destinationType = args[1];
|
||||
var targetWidth = args[3];
|
||||
var targetHeight = args[4];
|
||||
var encodingType = args[5];
|
||||
var saveToPhotoAlbum = args[9];
|
||||
var cameraDirection = args[11];
|
||||
var capturePreview = null;
|
||||
var cameraCaptureButton = null;
|
||||
var cameraCancelButton = null;
|
||||
var capture = null;
|
||||
var captureSettings = null;
|
||||
var CaptureNS = Windows.Media.Capture;
|
||||
var sensor = null;
|
||||
|
||||
function createCameraUI () {
|
||||
// create style for take and cancel buttons
|
||||
var buttonStyle = 'width:45%;padding: 10px 16px;font-size: 18px;line-height: 1.3333333;color: #333;background-color: #fff;border-color: #ccc; border: 1px solid transparent;border-radius: 6px; display: block; margin: 20px; z-index: 1000;border-color: #adadad;';
|
||||
|
||||
// Create fullscreen preview
|
||||
// z-order style element for capturePreview and cameraCancelButton elts
|
||||
// is necessary to avoid overriding by another page elements, -1 sometimes is not enough
|
||||
capturePreview = document.createElement('video');
|
||||
capturePreview.style.cssText = 'position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: ' + (HIGHEST_POSSIBLE_Z_INDEX - 1) + ';';
|
||||
|
||||
// Create capture button
|
||||
cameraCaptureButton = document.createElement('button');
|
||||
cameraCaptureButton.innerText = 'Take';
|
||||
cameraCaptureButton.style.cssText = buttonStyle + 'position: fixed; left: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';';
|
||||
|
||||
// Create cancel button
|
||||
cameraCancelButton = document.createElement('button');
|
||||
cameraCancelButton.innerText = 'Cancel';
|
||||
cameraCancelButton.style.cssText = buttonStyle + 'position: fixed; right: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';';
|
||||
|
||||
capture = new CaptureNS.MediaCapture();
|
||||
|
||||
captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
|
||||
captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video;
|
||||
}
|
||||
|
||||
function continueVideoOnFocus () {
|
||||
// if preview is defined it would be stuck, play it
|
||||
if (capturePreview) {
|
||||
capturePreview.play();
|
||||
}
|
||||
}
|
||||
|
||||
function startCameraPreview () {
|
||||
// Search for available camera devices
|
||||
// This is necessary to detect which camera (front or back) we should use
|
||||
var DeviceEnum = Windows.Devices.Enumeration;
|
||||
var expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;
|
||||
|
||||
// Add focus event handler to capture the event when user suspends the app and comes back while the preview is on
|
||||
window.addEventListener('focus', continueVideoOnFocus);
|
||||
|
||||
DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
|
||||
if (devices.length <= 0) {
|
||||
destroyCameraPreview();
|
||||
errorCallback('Camera not found');
|
||||
return;
|
||||
}
|
||||
|
||||
devices.forEach(function (currDev) {
|
||||
if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel === expectedPanel) {
|
||||
captureSettings.videoDeviceId = currDev.id;
|
||||
}
|
||||
});
|
||||
|
||||
captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.photo;
|
||||
|
||||
return capture.initializeAsync(captureSettings);
|
||||
}).then(function () {
|
||||
// create focus control if available
|
||||
var VideoDeviceController = capture.videoDeviceController;
|
||||
var FocusControl = VideoDeviceController.focusControl;
|
||||
|
||||
if (FocusControl.supported === true) {
|
||||
capturePreview.addEventListener('click', function () {
|
||||
// Make sure function isn't called again before previous focus is completed
|
||||
if (this.getAttribute('clicked') === '1') {
|
||||
return false;
|
||||
} else {
|
||||
this.setAttribute('clicked', '1');
|
||||
}
|
||||
var preset = Windows.Media.Devices.FocusPreset.autoNormal;
|
||||
var parent = this;
|
||||
FocusControl.setPresetAsync(preset).done(function () {
|
||||
// set the clicked attribute back to '0' to allow focus again
|
||||
parent.setAttribute('clicked', '0');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
|
||||
capturePreview.msZoom = true;
|
||||
capturePreview.src = URL.createObjectURL(capture);
|
||||
capturePreview.play();
|
||||
|
||||
// Bind events to controls
|
||||
sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault();
|
||||
if (sensor !== null) {
|
||||
sensor.addEventListener('orientationchanged', onOrientationChange);
|
||||
}
|
||||
|
||||
// add click events to capture and cancel buttons
|
||||
cameraCaptureButton.addEventListener('click', onCameraCaptureButtonClick);
|
||||
cameraCancelButton.addEventListener('click', onCameraCancelButtonClick);
|
||||
|
||||
// Change default orientation
|
||||
if (sensor) {
|
||||
setPreviewRotation(sensor.getCurrentOrientation());
|
||||
} else {
|
||||
setPreviewRotation(Windows.Graphics.Display.DisplayInformation.getForCurrentView().currentOrientation);
|
||||
}
|
||||
|
||||
// Get available aspect ratios
|
||||
var aspectRatios = getAspectRatios(capture);
|
||||
|
||||
// Couldn't find a good ratio
|
||||
if (aspectRatios.length === 0) {
|
||||
destroyCameraPreview();
|
||||
errorCallback('There\'s not a good aspect ratio available');
|
||||
return;
|
||||
}
|
||||
|
||||
// add elements to body
|
||||
document.body.appendChild(capturePreview);
|
||||
document.body.appendChild(cameraCaptureButton);
|
||||
document.body.appendChild(cameraCancelButton);
|
||||
|
||||
if (aspectRatios.indexOf(DEFAULT_ASPECT_RATIO) > -1) {
|
||||
return setAspectRatio(capture, DEFAULT_ASPECT_RATIO);
|
||||
} else {
|
||||
// Doesn't support 16:9 - pick next best
|
||||
return setAspectRatio(capture, aspectRatios[0]);
|
||||
}
|
||||
}).done(null, function (err) {
|
||||
destroyCameraPreview();
|
||||
errorCallback('Camera intitialization error ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
function destroyCameraPreview () {
|
||||
// If sensor is available, remove event listener
|
||||
if (sensor !== null) {
|
||||
sensor.removeEventListener('orientationchanged', onOrientationChange);
|
||||
}
|
||||
|
||||
// Pause and dispose preview element
|
||||
capturePreview.pause();
|
||||
capturePreview.src = null;
|
||||
|
||||
// Remove event listeners from buttons
|
||||
cameraCaptureButton.removeEventListener('click', onCameraCaptureButtonClick);
|
||||
cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick);
|
||||
|
||||
// Remove the focus event handler
|
||||
window.removeEventListener('focus', continueVideoOnFocus);
|
||||
|
||||
// Remove elements
|
||||
[capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) {
|
||||
if (elem /* && elem in document.body.childNodes */) {
|
||||
document.body.removeChild(elem);
|
||||
}
|
||||
});
|
||||
|
||||
// Stop and dispose media capture manager
|
||||
if (capture) {
|
||||
capture.stopRecordAsync();
|
||||
capture = null;
|
||||
}
|
||||
}
|
||||
|
||||
function captureAction () {
|
||||
var encodingProperties;
|
||||
var fileName;
|
||||
var tempFolder = getAppData().temporaryFolder;
|
||||
|
||||
if (encodingType === Camera.EncodingType.PNG) {
|
||||
fileName = 'photo.png';
|
||||
encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createPng();
|
||||
} else {
|
||||
fileName = 'photo.jpg';
|
||||
encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg();
|
||||
}
|
||||
|
||||
tempFolder.createFileAsync(fileName, OptUnique)
|
||||
.then(function (tempCapturedFile) {
|
||||
return new WinJS.Promise(function (complete) {
|
||||
var photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||
var finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
|
||||
capture.capturePhotoToStreamAsync(encodingProperties, photoStream)
|
||||
.then(function () {
|
||||
return Windows.Graphics.Imaging.BitmapDecoder.createAsync(photoStream);
|
||||
})
|
||||
.then(function (dec) {
|
||||
finalStream.size = 0; // BitmapEncoder requires the output stream to be empty
|
||||
return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(finalStream, dec);
|
||||
})
|
||||
.then(function (enc) {
|
||||
// We need to rotate the photo wrt sensor orientation
|
||||
enc.bitmapTransform.rotation = orientationToRotation(sensor.getCurrentOrientation());
|
||||
return enc.flushAsync();
|
||||
})
|
||||
.then(function () {
|
||||
return tempCapturedFile.openAsync(Windows.Storage.FileAccessMode.readWrite);
|
||||
})
|
||||
.then(function (fileStream) {
|
||||
return Windows.Storage.Streams.RandomAccessStream.copyAndCloseAsync(finalStream, fileStream);
|
||||
})
|
||||
.done(function () {
|
||||
photoStream.close();
|
||||
finalStream.close();
|
||||
complete(tempCapturedFile);
|
||||
}, function () {
|
||||
photoStream.close();
|
||||
finalStream.close();
|
||||
throw new Error('An error has occured while capturing the photo.');
|
||||
});
|
||||
});
|
||||
})
|
||||
.done(function (capturedFile) {
|
||||
destroyCameraPreview();
|
||||
savePhoto(capturedFile, {
|
||||
destinationType: destinationType,
|
||||
targetHeight: targetHeight,
|
||||
targetWidth: targetWidth,
|
||||
encodingType: encodingType,
|
||||
saveToPhotoAlbum: saveToPhotoAlbum
|
||||
}, successCallback, errorCallback);
|
||||
}, function (err) {
|
||||
destroyCameraPreview();
|
||||
errorCallback(err);
|
||||
});
|
||||
}
|
||||
|
||||
function getAspectRatios (capture) {
|
||||
var videoDeviceController = capture.videoDeviceController;
|
||||
var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
|
||||
return (element.width / element.height).toFixed(1);
|
||||
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
|
||||
|
||||
var videoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord).map(function (element) {
|
||||
return (element.width / element.height).toFixed(1);
|
||||
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
|
||||
|
||||
var videoPreviewAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview).map(function (element) {
|
||||
return (element.width / element.height).toFixed(1);
|
||||
}).filter(function (element, index, array) { return (index === array.indexOf(element)); });
|
||||
|
||||
var allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios);
|
||||
|
||||
var aspectObj = allAspectRatios.reduce(function (map, item) {
|
||||
if (!map[item]) {
|
||||
map[item] = 0;
|
||||
}
|
||||
map[item]++;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
return Object.keys(aspectObj).filter(function (k) {
|
||||
return aspectObj[k] === 3;
|
||||
});
|
||||
}
|
||||
|
||||
function setAspectRatio (capture, aspect) {
|
||||
// Max photo resolution with desired aspect ratio
|
||||
var videoDeviceController = capture.videoDeviceController;
|
||||
var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
|
||||
.filter(function (elem) {
|
||||
return ((elem.width / elem.height).toFixed(1) === aspect);
|
||||
})
|
||||
.reduce(function (prop1, prop2) {
|
||||
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
|
||||
});
|
||||
|
||||
// Max video resolution with desired aspect ratio
|
||||
var videoRecordResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord)
|
||||
.filter(function (elem) {
|
||||
return ((elem.width / elem.height).toFixed(1) === aspect);
|
||||
})
|
||||
.reduce(function (prop1, prop2) {
|
||||
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
|
||||
});
|
||||
|
||||
// Max video preview resolution with desired aspect ratio
|
||||
var videoPreviewResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview)
|
||||
.filter(function (elem) {
|
||||
return ((elem.width / elem.height).toFixed(1) === aspect);
|
||||
})
|
||||
.reduce(function (prop1, prop2) {
|
||||
return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
|
||||
});
|
||||
|
||||
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.photo, photoResolution)
|
||||
.then(function () {
|
||||
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoPreview, videoPreviewResolution);
|
||||
})
|
||||
.then(function () {
|
||||
return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When Capture button is clicked, try to capture a picture and return
|
||||
*/
|
||||
function onCameraCaptureButtonClick () {
|
||||
// Make sure user can't click more than once
|
||||
if (this.getAttribute('clicked') === '1') {
|
||||
return false;
|
||||
} else {
|
||||
this.setAttribute('clicked', '1');
|
||||
}
|
||||
captureAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* When Cancel button is clicked, destroy camera preview and return with error callback
|
||||
*/
|
||||
function onCameraCancelButtonClick () {
|
||||
// Make sure user can't click more than once
|
||||
if (this.getAttribute('clicked') === '1') {
|
||||
return false;
|
||||
} else {
|
||||
this.setAttribute('clicked', '1');
|
||||
}
|
||||
destroyCameraPreview();
|
||||
errorCallback('no image selected');
|
||||
}
|
||||
|
||||
/**
|
||||
* When the phone orientation change, get the event and change camera preview rotation
|
||||
* @param {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
|
||||
*/
|
||||
function onOrientationChange (e) {
|
||||
setPreviewRotation(e.orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts SimpleOrientation to a VideoRotation to remove difference between camera sensor orientation
|
||||
* and video orientation
|
||||
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
|
||||
* @return {number} - Windows.Media.Capture.VideoRotation
|
||||
*/
|
||||
function orientationToRotation (orientation) {
|
||||
// VideoRotation enumerable and BitmapRotation enumerable have the same values
|
||||
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx
|
||||
// https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx
|
||||
|
||||
switch (orientation) {
|
||||
// portrait
|
||||
case Windows.Devices.Sensors.SimpleOrientation.notRotated:
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// landscape
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise:
|
||||
return Windows.Media.Capture.VideoRotation.none;
|
||||
// portrait-flipped (not supported by WinPhone Apps)
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise:
|
||||
// Falling back to portrait default
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
// landscape-flipped
|
||||
case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise:
|
||||
return Windows.Media.Capture.VideoRotation.clockwise180Degrees;
|
||||
// faceup & facedown
|
||||
default:
|
||||
// Falling back to portrait default
|
||||
return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the current MediaCapture's video
|
||||
* @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
|
||||
*/
|
||||
function setPreviewRotation (orientation) {
|
||||
capture.setPreviewRotation(orientationToRotation(orientation));
|
||||
}
|
||||
|
||||
try {
|
||||
createCameraUI();
|
||||
startCameraPreview();
|
||||
} catch (ex) {
|
||||
errorCallback(ex);
|
||||
}
|
||||
}
|
||||
|
||||
function takePictureFromCameraWindows (successCallback, errorCallback, args) {
|
||||
var destinationType = args[1];
|
||||
var targetWidth = args[3];
|
||||
var targetHeight = args[4];
|
||||
var encodingType = args[5];
|
||||
var allowCrop = !!args[7];
|
||||
var saveToPhotoAlbum = args[9];
|
||||
var WMCapture = Windows.Media.Capture;
|
||||
var cameraCaptureUI = new WMCapture.CameraCaptureUI();
|
||||
|
||||
cameraCaptureUI.photoSettings.allowCropping = allowCrop;
|
||||
|
||||
if (encodingType === Camera.EncodingType.PNG) {
|
||||
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png;
|
||||
} else {
|
||||
cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg;
|
||||
}
|
||||
|
||||
// decide which max pixels should be supported by targetWidth or targetHeight.
|
||||
var maxRes = null;
|
||||
var UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
|
||||
var totalPixels = targetWidth * targetHeight;
|
||||
|
||||
if (targetWidth === -1 && targetHeight === -1) {
|
||||
maxRes = UIMaxRes.highestAvailable;
|
||||
// Temp fix for CB-10539
|
||||
/* else if (totalPixels <= 320 * 240) {
|
||||
maxRes = UIMaxRes.verySmallQvga;
|
||||
} */
|
||||
} else if (totalPixels <= 640 * 480) {
|
||||
maxRes = UIMaxRes.smallVga;
|
||||
} else if (totalPixels <= 1024 * 768) {
|
||||
maxRes = UIMaxRes.mediumXga;
|
||||
} else if (totalPixels <= 3 * 1000 * 1000) {
|
||||
maxRes = UIMaxRes.large3M;
|
||||
} else if (totalPixels <= 5 * 1000 * 1000) {
|
||||
maxRes = UIMaxRes.veryLarge5M;
|
||||
} else {
|
||||
maxRes = UIMaxRes.highestAvailable;
|
||||
}
|
||||
|
||||
cameraCaptureUI.photoSettings.maxResolution = maxRes;
|
||||
|
||||
var cameraPicture;
|
||||
|
||||
// define focus handler for windows phone 10.0
|
||||
var savePhotoOnFocus = function () {
|
||||
window.removeEventListener('focus', savePhotoOnFocus);
|
||||
// call only when the app is in focus again
|
||||
savePhoto(cameraPicture, {
|
||||
destinationType: destinationType,
|
||||
targetHeight: targetHeight,
|
||||
targetWidth: targetWidth,
|
||||
encodingType: encodingType,
|
||||
saveToPhotoAlbum: saveToPhotoAlbum
|
||||
}, successCallback, errorCallback);
|
||||
};
|
||||
|
||||
// if windows phone 10, add and delete focus eventHandler to capture the focus back from cameraUI to app
|
||||
if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) {
|
||||
window.addEventListener('focus', savePhotoOnFocus);
|
||||
}
|
||||
|
||||
cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) {
|
||||
if (!picture) {
|
||||
errorCallback("User didn't capture a photo.");
|
||||
// Remove the focus handler if present
|
||||
window.removeEventListener('focus', savePhotoOnFocus);
|
||||
return;
|
||||
}
|
||||
cameraPicture = picture;
|
||||
|
||||
// If not windows 10, call savePhoto() now. If windows 10, wait for the app to be in focus again
|
||||
if (navigator.appVersion.indexOf('Windows Phone 10.0') < 0) {
|
||||
savePhoto(cameraPicture, {
|
||||
destinationType: destinationType,
|
||||
targetHeight: targetHeight,
|
||||
targetWidth: targetWidth,
|
||||
encodingType: encodingType,
|
||||
saveToPhotoAlbum: saveToPhotoAlbum
|
||||
}, successCallback, errorCallback);
|
||||
}
|
||||
}, function () {
|
||||
errorCallback('Fail to capture a photo.');
|
||||
window.removeEventListener('focus', savePhotoOnFocus);
|
||||
});
|
||||
}
|
||||
|
||||
function savePhoto (picture, options, successCallback, errorCallback) {
|
||||
// success callback for capture operation
|
||||
var success = function (picture) {
|
||||
if (options.destinationType === Camera.DestinationType.FILE_URI) {
|
||||
if (options.targetHeight > 0 && options.targetWidth > 0) {
|
||||
resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType);
|
||||
} else {
|
||||
// CB-11714: check if target content-type is PNG to just rename as *.jpg since camera is captured as JPEG
|
||||
if (options.encodingType === Camera.EncodingType.PNG) {
|
||||
picture.name = picture.name.replace(/\.png$/, '.jpg');
|
||||
}
|
||||
|
||||
picture.copyAsync(getAppData().localFolder, picture.name, OptUnique).done(function (copiedFile) {
|
||||
successCallback('ms-appdata:///local/' + copiedFile.name);
|
||||
}, errorCallback);
|
||||
}
|
||||
} else {
|
||||
if (options.targetHeight > 0 && options.targetWidth > 0) {
|
||||
resizeImageBase64(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight);
|
||||
} else {
|
||||
fileIO.readBufferAsync(picture).done(function (buffer) {
|
||||
var strBase64 = encodeToBase64String(buffer);
|
||||
picture.deleteAsync().done(function () {
|
||||
successCallback(strBase64);
|
||||
}, function (err) {
|
||||
errorCallback(err);
|
||||
});
|
||||
}, errorCallback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.saveToPhotoAlbum) {
|
||||
success(picture);
|
||||
} else {
|
||||
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
var saveFile = function (file) {
|
||||
if (file) {
|
||||
// Prevent updates to the remote version of the file until we're done
|
||||
Windows.Storage.CachedFileManager.deferUpdates(file);
|
||||
picture.moveAndReplaceAsync(file)
|
||||
.then(function () {
|
||||
// Let Windows know that we're finished changing the file so
|
||||
// the other app can update the remote version of the file.
|
||||
return Windows.Storage.CachedFileManager.completeUpdatesAsync(file);
|
||||
})
|
||||
.done(function (updateStatus) {
|
||||
if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
|
||||
success(picture);
|
||||
} else {
|
||||
errorCallback('File update status is not complete.');
|
||||
}
|
||||
}, errorCallback);
|
||||
} else {
|
||||
errorCallback('Failed to select a file.');
|
||||
}
|
||||
};
|
||||
savePicker.suggestedStartLocation = pickerLocId.picturesLibrary;
|
||||
|
||||
if (options.encodingType === Camera.EncodingType.PNG) {
|
||||
savePicker.fileTypeChoices.insert('PNG', ['.png']);
|
||||
savePicker.suggestedFileName = 'photo.png';
|
||||
} else {
|
||||
savePicker.fileTypeChoices.insert('JPEG', ['.jpg']);
|
||||
savePicker.suggestedFileName = 'photo.jpg';
|
||||
}
|
||||
|
||||
// If Windows Phone 8.1 use pickSaveFileAndContinue()
|
||||
if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
|
||||
/*
|
||||
Need to add and remove an event listener to catch activation state
|
||||
Using FileSavePicker will suspend the app and it's required to catch the pickSaveFileContinuation
|
||||
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
|
||||
*/
|
||||
var fileSaveHandler = function (eventArgs) {
|
||||
if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) {
|
||||
var file = eventArgs.file;
|
||||
saveFile(file);
|
||||
webUIApp.removeEventListener('activated', fileSaveHandler);
|
||||
}
|
||||
};
|
||||
webUIApp.addEventListener('activated', fileSaveHandler);
|
||||
savePicker.pickSaveFileAndContinue();
|
||||
} else {
|
||||
savePicker.pickSaveFileAsync()
|
||||
.done(saveFile, errorCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require('cordova/exec/proxy').add('Camera', module.exports);
|
13
tests/package-lock.json
generated
Normal file
13
tests/package-lock.json
generated
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera-tests",
|
||||
"version": "8.0.1-dev",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cordova-plugin-camera-tests",
|
||||
"version": "8.0.1-dev",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera-tests",
|
||||
"version": "5.0.2",
|
||||
"version": "8.0.1-dev",
|
||||
"description": "",
|
||||
"cordova": {
|
||||
"id": "cordova-plugin-camera-tests",
|
||||
|
@ -22,7 +22,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:rim="http://www.blackberry.com/ns/widgets"
|
||||
id="cordova-plugin-camera-tests"
|
||||
version="5.0.2">
|
||||
version="8.0.1-dev">
|
||||
<name>Cordova Camera Plugin Tests</name>
|
||||
<license>Apache 2.0</license>
|
||||
|
||||
|
120
tests/tests.js
120
tests/tests.js
@ -19,7 +19,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem, MSApp */
|
||||
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem */
|
||||
/* eslint-env jasmine */
|
||||
|
||||
exports.defineAutoTests = function () {
|
||||
@ -78,20 +78,20 @@ exports.defineAutoTests = function () {
|
||||
/******************************************************************************/
|
||||
|
||||
exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
var pictureUrl = null;
|
||||
var fileObj = null;
|
||||
var fileEntry = null;
|
||||
var pageStartTime = +new Date();
|
||||
let pictureUrl = null;
|
||||
let fileObj = null;
|
||||
let fileEntry = null;
|
||||
const pageStartTime = +new Date();
|
||||
|
||||
// default camera options
|
||||
var camQualityDefault = ['50', 50];
|
||||
var camDestinationTypeDefault = ['FILE_URI', 1];
|
||||
var camPictureSourceTypeDefault = ['CAMERA', 1];
|
||||
var camAllowEditDefault = ['allowEdit', false];
|
||||
var camEncodingTypeDefault = ['JPEG', 0];
|
||||
var camMediaTypeDefault = ['mediaType', 0];
|
||||
var camCorrectOrientationDefault = ['correctOrientation', false];
|
||||
var camSaveToPhotoAlbumDefault = ['saveToPhotoAlbum', true];
|
||||
const camQualityDefault = ['50', 50];
|
||||
const camDestinationTypeDefault = ['FILE_URI', 1];
|
||||
const camPictureSourceTypeDefault = ['CAMERA', 1];
|
||||
const camAllowEditDefault = ['allowEdit', false];
|
||||
const camEncodingTypeDefault = ['JPEG', 0];
|
||||
const camMediaTypeDefault = ['mediaType', 0];
|
||||
const camCorrectOrientationDefault = ['correctOrientation', false];
|
||||
const camSaveToPhotoAlbumDefault = ['saveToPhotoAlbum', true];
|
||||
|
||||
function log (value) {
|
||||
console.log(value);
|
||||
@ -101,7 +101,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
function clearStatus () {
|
||||
document.getElementById('camera_status').innerHTML = '';
|
||||
document.getElementById('camera_image').src = 'about:blank';
|
||||
var canvas = document.getElementById('canvas');
|
||||
const canvas = document.getElementById('canvas');
|
||||
canvas.width = canvas.height = 1;
|
||||
pictureUrl = null;
|
||||
fileObj = null;
|
||||
@ -119,8 +119,8 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
log('URL: "' + url.slice(0, 90) + '"');
|
||||
|
||||
pictureUrl = url;
|
||||
var img = document.getElementById('camera_image');
|
||||
var startTime = new Date();
|
||||
const img = document.getElementById('camera_image');
|
||||
const startTime = new Date();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
log('Img size: ' + img.naturalWidth + 'x' + img.naturalHeight);
|
||||
@ -147,20 +147,20 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
} else if (pictureUrl.indexOf('data:image/jpeg;base64') === 0) {
|
||||
// do nothing
|
||||
} else {
|
||||
var path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' ');
|
||||
const path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' ');
|
||||
fileEntry = new FileEntry('image_name.png', path);
|
||||
}
|
||||
}
|
||||
|
||||
function getPicture () {
|
||||
clearStatus();
|
||||
var options = extractOptions();
|
||||
const options = extractOptions();
|
||||
log('Getting picture with options: ' + JSON.stringify(options));
|
||||
var popoverHandle = navigator.camera.getPicture(getPictureWin, onGetPictureError, options);
|
||||
const popoverHandle = navigator.camera.getPicture(getPictureWin, onGetPictureError, options);
|
||||
|
||||
// Reposition the popover if the orientation changes.
|
||||
window.onorientationchange = function () {
|
||||
var newPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0, 300, 400);
|
||||
const newPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0, 300, 400);
|
||||
popoverHandle.setPosition(newPopoverOptions);
|
||||
};
|
||||
}
|
||||
@ -177,7 +177,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
*/
|
||||
function readFile () {
|
||||
function onFileReadAsDataURL (evt) {
|
||||
var img = document.getElementById('camera_image');
|
||||
const img = document.getElementById('camera_image');
|
||||
img.style.visibility = 'visible';
|
||||
img.style.display = 'block';
|
||||
img.src = evt.target.result;
|
||||
@ -188,7 +188,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
log('Got file: ' + JSON.stringify(file));
|
||||
fileObj = file;
|
||||
/* eslint-disable no-undef */
|
||||
var reader = new FileReader();
|
||||
const reader = new FileReader();
|
||||
/* eslint-enable no-undef */
|
||||
reader.onload = function () {
|
||||
log('FileReader.readAsDataURL() - length = ' + reader.result.length);
|
||||
@ -219,9 +219,9 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
* This calls FileEntry.copyTo and FileEntry.moveTo.
|
||||
*/
|
||||
function copyImage () {
|
||||
var onFileSystemReceived = function (fileSystem) {
|
||||
var destDirEntry = fileSystem.root;
|
||||
var origName = fileEntry.name;
|
||||
const onFileSystemReceived = function (fileSystem) {
|
||||
const destDirEntry = fileSystem.root;
|
||||
const origName = fileEntry.name;
|
||||
|
||||
// Test FileEntry API here.
|
||||
fileEntry.copyTo(destDirEntry, 'copied_file.png', logCallback('FileEntry.copyTo', true), logCallback('FileEntry.copyTo', false));
|
||||
@ -253,13 +253,13 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
* This calls FileEntry.createWriter, FileWriter.write, and FileWriter.truncate.
|
||||
*/
|
||||
function writeImage () {
|
||||
var onFileWriterReceived = function (fileWriter) {
|
||||
const onFileWriterReceived = function (fileWriter) {
|
||||
fileWriter.onwrite = logCallback('FileWriter.write', true);
|
||||
fileWriter.onerror = logCallback('FileWriter.write', false);
|
||||
fileWriter.write('some text!');
|
||||
};
|
||||
|
||||
var onFileTruncateWriterReceived = function (fileWriter) {
|
||||
const onFileTruncateWriterReceived = function (fileWriter) {
|
||||
fileWriter.onwrite = logCallback('FileWriter.truncate', true);
|
||||
fileWriter.onerror = logCallback('FileWriter.truncate', false);
|
||||
fileWriter.truncate(10);
|
||||
@ -270,15 +270,15 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
|
||||
function displayImageUsingCanvas () {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var img = document.getElementById('camera_image');
|
||||
var w = img.width;
|
||||
var h = img.height;
|
||||
const canvas = document.getElementById('canvas');
|
||||
const img = document.getElementById('camera_image');
|
||||
let w = img.width;
|
||||
let h = img.height;
|
||||
h = 100 / w * h;
|
||||
w = 100;
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
var context = canvas.getContext('2d');
|
||||
const context = canvas.getContext('2d');
|
||||
context.drawImage(img, 0, 0, w, h);
|
||||
}
|
||||
|
||||
@ -311,9 +311,9 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
return;
|
||||
}
|
||||
/* eslint-enable no-undef */
|
||||
var URLApi = window.URL || window.webkitURL;
|
||||
const URLApi = window.URL || window.webkitURL;
|
||||
if (URLApi) {
|
||||
var blobURL = URLApi.createObjectURL(fileObj);
|
||||
const blobURL = URLApi.createObjectURL(fileObj);
|
||||
if (blobURL) {
|
||||
setPicture(blobURL, function () {
|
||||
URLApi.revokeObjectURL(blobURL);
|
||||
@ -327,11 +327,11 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
|
||||
function extractOptions () {
|
||||
var els = document.querySelectorAll('#image-options select');
|
||||
var ret = {};
|
||||
const els = document.querySelectorAll('#image-options select');
|
||||
const ret = {};
|
||||
/* eslint-disable no-cond-assign */
|
||||
for (var i = 0, el; el = els[i]; ++i) {
|
||||
var value = el.value;
|
||||
for (let i = 0, el; el = els[i]; ++i) {
|
||||
let value = el.value;
|
||||
if (value === '') continue;
|
||||
value = +value;
|
||||
|
||||
@ -346,20 +346,20 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
}
|
||||
|
||||
function createOptionsEl (name, values, selectionDefault) {
|
||||
var openDiv = '<div style="display: inline-block">' + name + ': ';
|
||||
var select = '<select name=' + name + ' id="' + name + '">';
|
||||
const openDiv = '<div style="display: inline-block">' + name + ': ';
|
||||
const select = '<select name=' + name + ' id="' + name + '">';
|
||||
|
||||
var defaultOption = '';
|
||||
let defaultOption = '';
|
||||
if (selectionDefault === undefined) {
|
||||
defaultOption = '<option value="">default</option>';
|
||||
}
|
||||
|
||||
var options = '';
|
||||
let options = '';
|
||||
if (typeof values === 'boolean') {
|
||||
values = { true: 1, false: 0 };
|
||||
}
|
||||
for (var k in values) {
|
||||
var isSelected = '';
|
||||
for (const k in values) {
|
||||
let isSelected = '';
|
||||
if (selectionDefault) {
|
||||
if (selectionDefault[0] === k) {
|
||||
isSelected = 'selected';
|
||||
@ -368,20 +368,20 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
options += '<option value="' + values[k] + '" ' + isSelected + '>' + k + '</option>';
|
||||
}
|
||||
|
||||
var closeDiv = '</select></div>';
|
||||
const closeDiv = '</select></div>';
|
||||
|
||||
return openDiv + select + defaultOption + options + closeDiv;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var info_div = '<h1>Camera</h1>' +
|
||||
const info_div = '<h1>Camera</h1>' +
|
||||
'<div id="info">' +
|
||||
'<b>Status:</b> <div id="camera_status"></div>' +
|
||||
'img: <img width="100" id="camera_image">' +
|
||||
'canvas: <canvas id="canvas" width="1" height="1"></canvas>' +
|
||||
'</div>';
|
||||
var options_div = '<h2>Cordova Camera API Options</h2>' +
|
||||
const options_div = '<h2>Cordova Camera API Options</h2>' +
|
||||
'<div id="image-options">' +
|
||||
createOptionsEl('sourceType', Camera.PictureSourceType, camPictureSourceTypeDefault) +
|
||||
createOptionsEl('destinationType', Camera.DestinationType, camDestinationTypeDefault) +
|
||||
@ -395,8 +395,8 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
createOptionsEl('saveToPhotoAlbum', true, camSaveToPhotoAlbumDefault) +
|
||||
createOptionsEl('cameraDirection', Camera.Direction) +
|
||||
'</div>';
|
||||
var getpicture_div = '<div id="getpicture"></div>';
|
||||
var test_procedure = '<h4>Recommended Test Procedure</h4>' +
|
||||
const getpicture_div = '<div id="getpicture"></div>';
|
||||
const test_procedure = '<h4>Recommended Test Procedure</h4>' +
|
||||
'Options not specified should be the default value' +
|
||||
'<br>Status box should update with image and info whenever an image is taken or selected from library' +
|
||||
'</p><div style="background:#B0C4DE;border:1px solid #FFA07A;margin:15px 6px 0px;min-width:295px;max-width:97%;padding:4px 0px 2px 10px;min-height:160px;max-height:200px;overflow:auto">' +
|
||||
@ -410,13 +410,13 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
'</p><li>sourceType=CAMERA<br>targetWidth & targetHeight=50<br>allowEdit=false<br>Do Get File Metadata test below and take note of size<br>Repeat test but with width and height=800. Size should be significantly larger.</li>' +
|
||||
'</p><li>quality=0<br>targetWidth & targetHeight=default<br>allowEdit=false<br>Do Get File Metadata test below and take note of size<br>Repeat test but with quality=80. Size should be significantly larger.</li>' +
|
||||
'</ol></div>';
|
||||
var inputs_div = '<h2>Native File Inputs</h2>' +
|
||||
const inputs_div = '<h2>Native File Inputs</h2>' +
|
||||
'For the following tests, status box should update with file selected' +
|
||||
'</p><div>input type=file <input type="file" class="testInputTag"></div>' +
|
||||
'<div>capture=camera <input type="file" accept="image/*;capture=camera" class="testInputTag"></div>' +
|
||||
'<div>capture=camcorder <input type="file" accept="video/*;capture=camcorder" class="testInputTag"></div>' +
|
||||
'<div>capture=microphone <input type="file" accept="audio/*;capture=microphone" class="testInputTag"></div>';
|
||||
var actions_div = '<h2>Actions</h2>' +
|
||||
const actions_div = '<h2>Actions</h2>' +
|
||||
'For the following tests, ensure that an image is set in status box' +
|
||||
'</p><div id="metadata"></div>' +
|
||||
'Expected result: Get metadata about file selected.<br>Status box will show, along with the metadata, "Call to FileEntry.getMetadata success, Call to FileEntry.setMetadata success, Call to FileEntry.getParent success"' +
|
||||
@ -433,22 +433,14 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
'</p><div id="remove"></div>' +
|
||||
'Expected result: Remove image from library.<br>Status box will show "FileEntry.remove success:["OK"]';
|
||||
|
||||
// We need to wrap this code due to Windows security restrictions
|
||||
// see http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences for details
|
||||
if (window.MSApp && window.MSApp.execUnsafeLocalFunction) {
|
||||
MSApp.execUnsafeLocalFunction(function () {
|
||||
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
|
||||
});
|
||||
} else {
|
||||
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
|
||||
}
|
||||
contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div;
|
||||
|
||||
var elements = document.getElementsByClassName('testInputTag');
|
||||
var listener = function (e) {
|
||||
const elements = document.getElementsByClassName('testInputTag');
|
||||
const listener = function (e) {
|
||||
testInputTag(e.target);
|
||||
};
|
||||
for (var i = 0; i < elements.length; ++i) {
|
||||
var item = elements[i];
|
||||
for (let i = 0; i < elements.length; ++i) {
|
||||
const item = elements[i];
|
||||
item.addEventListener('change', listener, false);
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
var argscheck = require('cordova/argscheck');
|
||||
var exec = require('cordova/exec');
|
||||
var Camera = require('./Camera');
|
||||
const argscheck = require('cordova/argscheck');
|
||||
const exec = require('cordova/exec');
|
||||
const Camera = require('./Camera');
|
||||
// XXX: commented out
|
||||
// CameraPopoverHandle = require('./CameraPopoverHandle');
|
||||
|
||||
@ -32,10 +32,10 @@ var Camera = require('./Camera');
|
||||
/**
|
||||
* @exports camera
|
||||
*/
|
||||
var cameraExport = {};
|
||||
const cameraExport = {};
|
||||
|
||||
// Tack on the Camera Constants to the base camera plugin.
|
||||
for (var key in Camera) {
|
||||
for (const key in Camera) {
|
||||
cameraExport[key] = Camera[key];
|
||||
}
|
||||
|
||||
@ -114,14 +114,8 @@ for (var key in Camera) {
|
||||
* __Supported Platforms__
|
||||
*
|
||||
* - Android
|
||||
* - BlackBerry
|
||||
* - Browser
|
||||
* - Firefox
|
||||
* - FireOS
|
||||
* - iOS
|
||||
* - Windows
|
||||
* - WP8
|
||||
* - Ubuntu
|
||||
*
|
||||
* More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
|
||||
*
|
||||
@ -134,22 +128,26 @@ for (var key in Camera) {
|
||||
cameraExport.getPicture = function (successCallback, errorCallback, options) {
|
||||
argscheck.checkArgs('fFO', 'Camera.getPicture', arguments);
|
||||
options = options || {};
|
||||
var getValue = argscheck.getValue;
|
||||
const getValue = argscheck.getValue;
|
||||
|
||||
var quality = getValue(options.quality, 50);
|
||||
var destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI);
|
||||
var sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA);
|
||||
var targetWidth = getValue(options.targetWidth, -1);
|
||||
var targetHeight = getValue(options.targetHeight, -1);
|
||||
var encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG);
|
||||
var mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE);
|
||||
var allowEdit = !!options.allowEdit;
|
||||
var correctOrientation = !!options.correctOrientation;
|
||||
var saveToPhotoAlbum = !!options.saveToPhotoAlbum;
|
||||
var popoverOptions = getValue(options.popoverOptions, null);
|
||||
var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK);
|
||||
const quality = getValue(options.quality, 50);
|
||||
const destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI);
|
||||
const sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA);
|
||||
const targetWidth = getValue(options.targetWidth, -1);
|
||||
const targetHeight = getValue(options.targetHeight, -1);
|
||||
const encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG);
|
||||
const mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE);
|
||||
const allowEdit = !!options.allowEdit;
|
||||
const correctOrientation = !!options.correctOrientation;
|
||||
const saveToPhotoAlbum = !!options.saveToPhotoAlbum;
|
||||
const popoverOptions = getValue(options.popoverOptions, null);
|
||||
const cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK);
|
||||
|
||||
var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
|
||||
if (allowEdit) {
|
||||
console.warn('allowEdit is deprecated. It does not work reliably on all platforms. Utilise a dedicated image editing library instead. allowEdit functionality is scheduled to be removed in a future release.');
|
||||
}
|
||||
|
||||
const args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
|
||||
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
|
||||
|
||||
exec(successCallback, errorCallback, 'Camera', 'takePicture', args);
|
||||
|
@ -23,7 +23,7 @@
|
||||
* @ignore in favour of iOS' one
|
||||
* A handle to an image picker popover.
|
||||
*/
|
||||
var CameraPopoverHandle = function () {
|
||||
const CameraPopoverHandle = function () {
|
||||
this.setPosition = function (popoverOptions) {
|
||||
console.log('CameraPopoverHandle.setPosition is only supported on iOS.');
|
||||
};
|
||||
|
@ -19,7 +19,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
var Camera = require('./Camera');
|
||||
const Camera = require('./Camera');
|
||||
|
||||
/**
|
||||
* @namespace navigator
|
||||
@ -42,7 +42,7 @@ var Camera = require('./Camera');
|
||||
* @param {Number} [popoverWidth=0] - width of the popover (0 or not specified will use apple's default width).
|
||||
* @param {Number} [popoverHeight=0] - height of the popover (0 or not specified will use apple's default height).
|
||||
*/
|
||||
var CameraPopoverOptions = function (x, y, width, height, arrowDir, popoverWidth, popoverHeight) {
|
||||
const CameraPopoverOptions = function (x, y, width, height, arrowDir, popoverWidth, popoverHeight) {
|
||||
// information of rectangle that popover should be anchored to
|
||||
this.x = x || 0;
|
||||
this.y = y || 32;
|
||||
|
@ -19,7 +19,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
const exec = require('cordova/exec');
|
||||
|
||||
/**
|
||||
* @namespace navigator
|
||||
@ -48,7 +48,7 @@ var exec = require('cordova/exec');
|
||||
* }
|
||||
* @module CameraPopoverHandle
|
||||
*/
|
||||
var CameraPopoverHandle = function () {
|
||||
const CameraPopoverHandle = function () {
|
||||
/**
|
||||
* Can be used to reposition the image selection dialog,
|
||||
* for example, when the device orientation changes.
|
||||
@ -58,7 +58,7 @@ var CameraPopoverHandle = function () {
|
||||
* @param {module:CameraPopoverOptions} popoverOptions
|
||||
*/
|
||||
this.setPosition = function (popoverOptions) {
|
||||
var args = [popoverOptions];
|
||||
const args = [popoverOptions];
|
||||
exec(null, null, 'Camera', 'repositionPopover', args);
|
||||
};
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user