mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2026-02-03 00:06:46 +08:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20aa744b55 | ||
|
|
d6af2098c1 | ||
|
|
b76b5ae670 | ||
|
|
f2a41e4b5e | ||
|
|
85a986f589 | ||
|
|
4c2c567fd8 | ||
|
|
fe6dc72a75 | ||
|
|
ae2acd9ab2 | ||
|
|
f6e8548381 | ||
|
|
e7a3d70fe9 | ||
|
|
91d6e10b29 | ||
|
|
6fb63fede5 | ||
|
|
c9bab1f94c | ||
|
|
5393a28191 | ||
|
|
2a7520112e | ||
|
|
552b806347 | ||
|
|
61a963d6fb | ||
|
|
3be1802c9e | ||
|
|
19a44608b8 | ||
|
|
3f995dd3de | ||
|
|
f1fb8d210e | ||
|
|
f3a704b59d | ||
|
|
4ae4414856 | ||
|
|
61ba9cca4b | ||
|
|
ab5c957434 | ||
|
|
031919be5b | ||
|
|
0fbddb8364 | ||
|
|
ab1281fb25 | ||
|
|
77f4683ca8 | ||
|
|
706a2efe20 | ||
|
|
ba278d2495 | ||
|
|
fc0678f970 | ||
|
|
464c37eb50 | ||
|
|
99dff47962 | ||
|
|
9b0d17ec85 | ||
|
|
76eb49da95 | ||
|
|
f9fa6ccedb | ||
|
|
39a5889c3b | ||
|
|
2d3997b2e7 | ||
|
|
e0f6d33876 | ||
|
|
ff03f474da | ||
|
|
bf105f9832 | ||
|
|
7574e66315 | ||
|
|
2437c40fe7 | ||
|
|
ccd59e66ba | ||
|
|
6f4fef8479 | ||
|
|
34e85810c8 |
202
LICENSE
202
LICENSE
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
27
README.md
27
README.md
@@ -1,5 +1,24 @@
|
||||
cordova-plugin-camera
|
||||
==========================
|
||||
To install this plugin, follow the [Command-line Interface Guide](http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-line%20Interface).
|
||||
<!---
|
||||
license: 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
|
||||
|
||||
If you are not using the Cordova Command-line Interface, follow [Using Plugman to Manage Plugins](http://cordova.apache.org/docs/en/edge/plugin_ref_plugman.md.html).
|
||||
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.
|
||||
-->
|
||||
|
||||
# org.apache.cordova.camera
|
||||
|
||||
Plugin documentation: [doc/index.md](doc/index.md)
|
||||
|
||||
This is `dev` - the deprecated development branch of this plugin; development of this plugin has moved to the `master` branch
|
||||
|
||||
@@ -1,57 +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.
|
||||
#
|
||||
-->
|
||||
# Release Notes
|
||||
|
||||
### 0.2.1 (Sept 5, 2013)
|
||||
* [CB-4656] Don't add line-breaks to base64-encoded images (Fixes type=DataURI)
|
||||
* [CB-4432] copyright notice change
|
||||
|
||||
### 0.2.3 (Sept 25, 2013)
|
||||
* CB-4889 bumping&resetting version
|
||||
* CB-4889 forgot index.html
|
||||
* CB-4889 renaming core inside cameraProxy
|
||||
* [Windows8] commandProxy has moved
|
||||
* [Windows8] commandProxy has moved
|
||||
* added Camera API for FirefoxOS
|
||||
* Rename CHANGELOG.md -> RELEASENOTES.md
|
||||
* [CB-4823] Fix XCode 5 camera plugin warnings
|
||||
* Fix compiler warnings
|
||||
* [CB-4765] Move ExifHelper.java into Camera Plugin
|
||||
* [CB-4764] Remove reference to DirectoryManager from CameraLauncher
|
||||
* [CB-4763] Use a copy of FileHelper.java within camera-plugin.
|
||||
* [CB-4752] Incremented plugin version on dev branch.
|
||||
* CB-4633: We really should close cursors. It's just the right thing to do.
|
||||
* No longer causes a stack trace, but it doesn't cause the error to be called.
|
||||
* CB-4889 renaming org.apache.cordova.core.camera to org.apache.cordova.camera
|
||||
|
||||
### 0.2.4 (Oct 28, 2013)
|
||||
* CB-5128: added repo + issue tag to plugin.xml for camera plugin
|
||||
* CB-4958 - iOS - Camera plugin should not show the status bar
|
||||
* [CB-4919] updated plugin.xml for FxOS
|
||||
* [CB-4915] Incremented plugin version on dev branch.
|
||||
|
||||
### 0.2.5 (Dec 4, 2013)
|
||||
* fix camera for firefox os
|
||||
* getPicture via web activities
|
||||
* [ubuntu] specify policy_group
|
||||
* add ubuntu platform
|
||||
* 1. User Agent detection now detects AmazonWebView. 2. Change to use amazon-fireos as the platform if user agent string contains 'cordova-amazon-fireos'
|
||||
* Added amazon-fireos platform.
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
license: 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.
|
||||
---
|
||||
|
||||
camera.cleanup
|
||||
=================
|
||||
|
||||
Cleans up the image files that were taken by the camera, that were stored in a temporary storage location.
|
||||
|
||||
navigator.camera.cleanup( cameraSuccess, cameraError );
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Cleans up the image files stored in the temporary storage location, when the function `camera.getPicture` is used with `Camera.sourceType = Camera.PictureSourceType.CAMERA` and `Camera.destinationType = Camera.DestinationType.FILE_URI`
|
||||
|
||||
|
||||
Supported Platforms
|
||||
-------------------
|
||||
|
||||
- iOS
|
||||
|
||||
|
||||
Example
|
||||
-------------
|
||||
|
||||
navigator.camera.cleanup(onSuccess, onFail);
|
||||
|
||||
function onSuccess() {
|
||||
console.log("Camera cleanup success.")
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
---
|
||||
license: 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.
|
||||
---
|
||||
|
||||
camera.getPicture
|
||||
=================
|
||||
|
||||
Takes a photo using the camera or retrieves a photo from the device's album.
|
||||
The image is passed to the success callback as a base64 encoded `String` or as the URI of an image file.
|
||||
The method itself returns a CameraPopoverHandle object, which can be used to reposition the file selection popover.
|
||||
|
||||
navigator.camera.getPicture( cameraSuccess, cameraError, [ cameraOptions ] );
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Function `camera.getPicture` opens the device's default camera application so that the user can take a picture (if `Camera.sourceType = Camera.PictureSourceType.CAMERA`, which is the default). Once the photo is taken, the camera application closes and your application is restored.
|
||||
|
||||
If `Camera.sourceType = Camera.PictureSourceType.PHOTOLIBRARY` or `Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a photo chooser dialog is shown, from which a photo from the album can be selected. A `CameraPopoverHandle` object, which can be used to reposition the photo chooser dialog (eg. when the device orientation changes) is returned by `camera.getPicture`.
|
||||
|
||||
The return value will be sent to the `cameraSuccess` function, in one of the following formats, depending on the `cameraOptions` you specify:
|
||||
|
||||
- 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 _(see example below)_
|
||||
- Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc)
|
||||
- Post the data to a remote server
|
||||
|
||||
__Note:__ The image quality of pictures taken using the camera on newer devices is quite good, and images from the Photo Album will not be downscaled to a lower quality, even if a quality parameter is specified. ___Encoding such images using Base64 has caused memory issues on many newer devices. Therefore, using FILE\_URI as the 'Camera.destinationType' is highly recommended.___
|
||||
|
||||
Supported Platforms
|
||||
-------------------
|
||||
|
||||
- Android
|
||||
- Blackberry WebWorks (OS 5.0 and higher)
|
||||
- iOS
|
||||
- Windows Phone 7 and 8
|
||||
- Bada 1.2
|
||||
- webOS
|
||||
- Tizen
|
||||
- Windows 8
|
||||
|
||||
iOS Quirks
|
||||
----------
|
||||
|
||||
Including a JavaScript alert() in either of the callback functions can cause problems. Wrap the alert in a setTimeout() to allow the iOS image picker or popover to fully close before the alert is displayed:
|
||||
|
||||
setTimeout(function() {
|
||||
// do your thing here!
|
||||
}, 0);
|
||||
|
||||
Windows Phone 7 Quirks
|
||||
----------------------
|
||||
|
||||
Invoking the native camera application while your device is connected
|
||||
via Zune will not work, and the error callback will be triggered.
|
||||
|
||||
Tizen Quirks
|
||||
----------------------
|
||||
|
||||
Only 'destinationType: Camera.DestinationType.FILE_URI' and 'sourceType: Camera.PictureSourceType.PHOTOLIBRARY' are supported.
|
||||
|
||||
Quick Example
|
||||
-------------
|
||||
|
||||
Take photo and retrieve Base64-encoded image:
|
||||
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
|
||||
destinationType: Camera.DestinationType.DATA_URL
|
||||
});
|
||||
|
||||
function onSuccess(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = "data:image/jpeg;base64," + imageData;
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
|
||||
Take photo and retrieve image file location:
|
||||
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
|
||||
destinationType: Camera.DestinationType.FILE_URI });
|
||||
|
||||
function onSuccess(imageURI) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = imageURI;
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
|
||||
|
||||
Full Example
|
||||
------------
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Capture Photo</title>
|
||||
|
||||
<script type="text/javascript" charset="utf-8" src="cordova-x.x.x.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
var pictureSource; // picture source
|
||||
var destinationType; // sets the format of returned value
|
||||
|
||||
// Wait for Cordova to connect with the device
|
||||
//
|
||||
document.addEventListener("deviceready",onDeviceReady,false);
|
||||
|
||||
// Cordova is ready to be used!
|
||||
//
|
||||
function onDeviceReady() {
|
||||
pictureSource=navigator.camera.PictureSourceType;
|
||||
destinationType=navigator.camera.DestinationType;
|
||||
}
|
||||
|
||||
// Called when a photo is successfully retrieved
|
||||
//
|
||||
function onPhotoDataSuccess(imageData) {
|
||||
// Uncomment to view the base64 encoded image data
|
||||
// console.log(imageData);
|
||||
|
||||
// Get image handle
|
||||
//
|
||||
var smallImage = document.getElementById('smallImage');
|
||||
|
||||
// Unhide image elements
|
||||
//
|
||||
smallImage.style.display = 'block';
|
||||
|
||||
// Show the captured photo
|
||||
// The inline CSS rules are used to resize the image
|
||||
//
|
||||
smallImage.src = "data:image/jpeg;base64," + imageData;
|
||||
}
|
||||
|
||||
// Called when a photo is successfully retrieved
|
||||
//
|
||||
function onPhotoURISuccess(imageURI) {
|
||||
// Uncomment to view the image file URI
|
||||
// console.log(imageURI);
|
||||
|
||||
// Get image handle
|
||||
//
|
||||
var largeImage = document.getElementById('largeImage');
|
||||
|
||||
// Unhide image elements
|
||||
//
|
||||
largeImage.style.display = 'block';
|
||||
|
||||
// Show the captured photo
|
||||
// The inline CSS rules are used to resize the image
|
||||
//
|
||||
largeImage.src = imageURI;
|
||||
}
|
||||
|
||||
// A button will call this function
|
||||
//
|
||||
function capturePhoto() {
|
||||
// Take picture using device camera and retrieve image as base64-encoded string
|
||||
navigator.camera.getPicture(onPhotoDataSuccess, onFail, { quality: 50,
|
||||
destinationType: destinationType.DATA_URL });
|
||||
}
|
||||
|
||||
// A button will call this function
|
||||
//
|
||||
function capturePhotoEdit() {
|
||||
// Take picture using device camera, allow edit, and retrieve image as base64-encoded string
|
||||
navigator.camera.getPicture(onPhotoDataSuccess, onFail, { quality: 20, allowEdit: true,
|
||||
destinationType: destinationType.DATA_URL });
|
||||
}
|
||||
|
||||
// A button will call this function
|
||||
//
|
||||
function getPhoto(source) {
|
||||
// Retrieve image file location from specified source
|
||||
navigator.camera.getPicture(onPhotoURISuccess, onFail, { quality: 50,
|
||||
destinationType: destinationType.FILE_URI,
|
||||
sourceType: source });
|
||||
}
|
||||
|
||||
// Called if something bad happens.
|
||||
//
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button onclick="capturePhoto();">Capture Photo</button> <br>
|
||||
<button onclick="capturePhotoEdit();">Capture Editable Photo</button> <br>
|
||||
<button onclick="getPhoto(pictureSource.PHOTOLIBRARY);">From Photo Library</button><br>
|
||||
<button onclick="getPhoto(pictureSource.SAVEDPHOTOALBUM);">From Photo Album</button><br>
|
||||
<img style="display:none;width:60px;height:60px;" id="smallImage" src="" />
|
||||
<img style="display:none;" id="largeImage" src="" />
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,97 +0,0 @@
|
||||
---
|
||||
license: 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.
|
||||
---
|
||||
|
||||
Camera
|
||||
======
|
||||
|
||||
> The `camera` object provides access to the device's default camera application.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
- camera.getPicture
|
||||
- camera.cleanup
|
||||
|
||||
Permissions
|
||||
-----------
|
||||
|
||||
### Android
|
||||
|
||||
#### app/res/xml/config.xml
|
||||
|
||||
<plugin name="Camera" value="org.apache.cordova.CameraLauncher" />
|
||||
|
||||
#### app/AndroidManifest
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
### Bada
|
||||
|
||||
#### manifest.xml
|
||||
|
||||
<Privilege>
|
||||
<Name>CAMERA</Name>
|
||||
</Privilege>
|
||||
<Privilege>
|
||||
<Name>RECORDING</Name>
|
||||
</Privilege>
|
||||
|
||||
### BlackBerry WebWorks
|
||||
|
||||
#### www/plugins.xml
|
||||
|
||||
<plugin name="Camera" value="org.apache.cordova.camera.Camera" />
|
||||
|
||||
#### www/config.xml
|
||||
|
||||
<feature id="blackberry.media.camera" />
|
||||
|
||||
<rim:permissions>
|
||||
<rim:permit>use_camera</rim:permit>
|
||||
</rim:permissions>
|
||||
|
||||
### iOS
|
||||
|
||||
#### config.xml
|
||||
|
||||
<plugin name="Camera" value="CDVCamera" />
|
||||
|
||||
### webOS
|
||||
|
||||
No permissions are required.
|
||||
|
||||
### Windows Phone
|
||||
|
||||
#### Properties/WPAppManifest.xml
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="ID_CAP_ISV_CAMERA" />
|
||||
<Capability Name="ID_HW_FRONTCAMERA" />
|
||||
</Capabilities>
|
||||
|
||||
Reference: [Application Manifest for Windows Phone](http://msdn.microsoft.com/en-us/library/ff769509%28v=vs.92%29.aspx)
|
||||
|
||||
### Tizen
|
||||
|
||||
#### config.xml
|
||||
|
||||
<feature name="http://tizen.org/api/application" required="true"/>
|
||||
<feature name="http://tizen.org/api/application.launch" required="true"/>
|
||||
|
||||
Reference: [Application Manifest for Tizen Web Application](https://developer.tizen.org/help/topic/org.tizen.help.gs/Creating%20a%20Project.html?path=0_1_1_3#8814682_CreatingaProject-EditingconfigxmlFeatures)
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
license: 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.
|
||||
---
|
||||
|
||||
CameraPopoverHandle
|
||||
===================
|
||||
|
||||
A handle to the popover dialog created by `camera.getPicture`.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
- __setPosition:__ Set the position of the popover.
|
||||
|
||||
Supported Platforms
|
||||
-------------------
|
||||
|
||||
- iOS
|
||||
|
||||
setPosition
|
||||
-----------
|
||||
|
||||
Set the position of the popover.
|
||||
|
||||
__Parameters:__
|
||||
- cameraPopoverOptions - the CameraPopoverOptions specifying the new position
|
||||
|
||||
Quick Example
|
||||
-------------
|
||||
|
||||
var cameraPopoverOptions = new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
|
||||
cameraPopoverHandle.setPosition(cameraPopoverOptions);
|
||||
|
||||
Full Example
|
||||
------------
|
||||
|
||||
function onSuccess(imageData) {
|
||||
// Do stuff with the image!
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed to get the picture: ' + message);
|
||||
}
|
||||
|
||||
var cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
|
||||
{ destinationType: Camera.DestinationType.FILE_URI,
|
||||
sourceType: Camera.PictureSourceType.PHOTOLIBRARY });
|
||||
|
||||
// Reposition the popover if the orientation changes.
|
||||
window.onorientationchange = function() {
|
||||
var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0);
|
||||
cameraPopoverHandle.setPosition(cameraPopoverOptions);
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
license: 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.
|
||||
---
|
||||
|
||||
CameraPopoverOptions
|
||||
====================
|
||||
|
||||
Parameters only used by iOS to specify the anchor element location and arrow direction of popover used on iPad when selecting images from the library or album.
|
||||
|
||||
{ x : 0,
|
||||
y : 32,
|
||||
width : 320,
|
||||
height : 480,
|
||||
arrowDir : Camera.PopoverArrowDirection.ARROW_ANY
|
||||
};
|
||||
|
||||
CameraPopoverOptions
|
||||
--------------------
|
||||
|
||||
- __x:__ x pixel coordinate of element on the screen to anchor popover onto. (`Number`)
|
||||
|
||||
- __y:__ y pixel coordinate of element on the screen to anchor popover onto. (`Number`)
|
||||
|
||||
- __width:__ width, in pixels, of the element on the screen to anchor popover onto. (`Number`)
|
||||
|
||||
- __height:__ height, in pixels, of the element on the screen to anchor popover onto. (`Number`)
|
||||
|
||||
- __arrowDir:__ Direction the arrow on the popover should point. Defined in Camera.PopoverArrowDirection (`Number`)
|
||||
|
||||
Camera.PopoverArrowDirection = {
|
||||
ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants
|
||||
ARROW_DOWN : 2,
|
||||
ARROW_LEFT : 4,
|
||||
ARROW_RIGHT : 8,
|
||||
ARROW_ANY : 15
|
||||
};
|
||||
|
||||
Note that the size of the popover may change to adjust to the direction of the arrow and orientation of the screen. Make sure to account for orientation changes when specifying the anchor element location.
|
||||
|
||||
Quick Example
|
||||
-------------
|
||||
|
||||
var popover = new CameraPopoverOptions(300,300,100,100,Camera.PopoverArrowDirection.ARROW_ANY);
|
||||
var options = { quality: 50, destinationType: Camera.DestinationType.DATA_URL,sourceType: Camera.PictureSource.SAVEDPHOTOALBUM, popoverOptions : popover };
|
||||
|
||||
navigator.camera.getPicture(onSuccess, onFail, options);
|
||||
|
||||
function onSuccess(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = "data:image/jpeg;base64," + imageData;
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
license: 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.
|
||||
---
|
||||
|
||||
cameraError
|
||||
===========
|
||||
|
||||
onError callback function that provides an error message.
|
||||
|
||||
function(message) {
|
||||
// Show a helpful message
|
||||
}
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
- __message:__ The message is provided by the device's native code. (`String`)
|
||||
@@ -1,136 +0,0 @@
|
||||
---
|
||||
license: 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.
|
||||
---
|
||||
|
||||
cameraOptions
|
||||
=============
|
||||
|
||||
Optional parameters to customize the camera settings.
|
||||
|
||||
{ quality : 75,
|
||||
destinationType : Camera.DestinationType.DATA_URL,
|
||||
sourceType : Camera.PictureSourceType.CAMERA,
|
||||
allowEdit : true,
|
||||
encodingType: Camera.EncodingType.JPEG,
|
||||
targetWidth: 100,
|
||||
targetHeight: 100,
|
||||
popoverOptions: CameraPopoverOptions,
|
||||
saveToPhotoAlbum: false };
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
- __quality:__ Quality of saved image. Range is [0, 100]. (`Number`)
|
||||
|
||||
- __destinationType:__ Choose the format of the return value. Defined in navigator.camera.DestinationType (`Number`)
|
||||
|
||||
Camera.DestinationType = {
|
||||
DATA_URL : 0, // Return image as base64 encoded string
|
||||
FILE_URI : 1, // Return image file URI
|
||||
NATIVE_URI : 2 // Return image native URI (eg. assets-library:// on iOS or content:// on Android)
|
||||
};
|
||||
|
||||
- __sourceType:__ Set the source of the picture. Defined in nagivator.camera.PictureSourceType (`Number`)
|
||||
|
||||
Camera.PictureSourceType = {
|
||||
PHOTOLIBRARY : 0,
|
||||
CAMERA : 1,
|
||||
SAVEDPHOTOALBUM : 2
|
||||
};
|
||||
|
||||
- __allowEdit:__ Allow simple editing of image before selection. (`Boolean`)
|
||||
|
||||
- __encodingType:__ Choose the encoding of the returned image file. Defined in navigator.camera.EncodingType (`Number`)
|
||||
|
||||
Camera.EncodingType = {
|
||||
JPEG : 0, // Return JPEG encoded image
|
||||
PNG : 1 // Return PNG encoded image
|
||||
};
|
||||
|
||||
- __targetWidth:__ Width in pixels to scale image. Must be used with targetHeight. Aspect ratio is maintained. (`Number`)
|
||||
- __targetHeight:__ Height in pixels to scale image. Must be used with targetWidth. Aspect ratio is maintained. (`Number`)
|
||||
|
||||
- __mediaType:__ Set the type of media to select from. Only works when PictureSourceType is PHOTOLIBRARY or SAVEDPHOTOALBUM. Defined in nagivator.camera.MediaType (`Number`)
|
||||
|
||||
Camera.MediaType = {
|
||||
PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
|
||||
VIDEO: 1, // allow selection of video only, WILL ALWAYS RETURN FILE_URI
|
||||
ALLMEDIA : 2 // allow selection from all media types
|
||||
};
|
||||
|
||||
- __correctOrientation:__ Rotate the image to correct for the orientation of the device during capture. (`Boolean`)
|
||||
- __saveToPhotoAlbum:__ Save the image to the photo album on the device after capture. (`Boolean`)
|
||||
- __popoverOptions:__ iOS only options to specify popover location in iPad. Defined in CameraPopoverOptions.
|
||||
- __cameraDirection:__ Choose the camera to use (front- or back-facing). Defined in navigator.camera.Direction (`Number`)
|
||||
|
||||
Camera.Direction = {
|
||||
BACK : 0, // Use the back-facing camera
|
||||
FRONT : 1 // Use the front-facing camera
|
||||
};
|
||||
|
||||
Android Quirks
|
||||
--------------
|
||||
|
||||
- Ignores the `allowEdit` parameter.
|
||||
- Camera.PictureSourceType.PHOTOLIBRARY and Camera.PictureSourceType.SAVEDPHOTOALBUM both display the same photo album.
|
||||
|
||||
BlackBerry Quirks
|
||||
-----------------
|
||||
|
||||
- Ignores the `quality` parameter.
|
||||
- Ignores the `sourceType` parameter.
|
||||
- Ignores the `allowEdit` parameter.
|
||||
- Application must have key injection permissions to close native Camera application after photo is taken.
|
||||
- Using Large image sizes may result in inability to encode image on later model devices with high resolution cameras (e.g. Torch 9800).
|
||||
- Camera.MediaType is not supported.
|
||||
- Ignores the `correctOrientation` parameter.
|
||||
- Ignores the `cameraDirection` parameter.
|
||||
|
||||
webOS Quirks
|
||||
-----------
|
||||
|
||||
- Ignores the `quality` parameter.
|
||||
- Ignores the `sourceType` parameter.
|
||||
- Ignores the `allowEdit` parameter.
|
||||
- Camera.MediaType is not supported.
|
||||
- Ignores the `correctOrientation` parameter.
|
||||
- Ignores the `saveToPhotoAlbum` parameter.
|
||||
- Ignores the `cameraDirection` parameter.
|
||||
|
||||
iOS Quirks
|
||||
--------------
|
||||
|
||||
- Set `quality` below 50 to avoid memory error on some devices.
|
||||
- When `destinationType.FILE_URI` is used, photos are saved in the application's temporary directory.
|
||||
|
||||
Windows Phone 7 and 8 Quirks
|
||||
--------------
|
||||
|
||||
- Ignores the `allowEdit` parameter.
|
||||
- Ignores the `correctOrientation` parameter.
|
||||
- Ignores the `cameraDirection` parameter.
|
||||
|
||||
Bada 1.2 Quirks
|
||||
--------------
|
||||
- options not supported
|
||||
- always returns a FILE URI
|
||||
|
||||
Tizen Quirks
|
||||
--------------
|
||||
- options not supported
|
||||
- always returns a FILE URI
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
license: 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.
|
||||
---
|
||||
|
||||
cameraSuccess
|
||||
=============
|
||||
|
||||
onSuccess callback function that provides the image data.
|
||||
|
||||
function(imageData) {
|
||||
// Do something with the image
|
||||
}
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
- __imageData:__ Base64 encoding of the image data, OR the image file URI, depending on `cameraOptions` used. (`String`)
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
// Show image
|
||||
//
|
||||
function cameraCallback(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = "data:image/jpeg;base64," + imageData;
|
||||
}
|
||||
212
plugin.xml
212
plugin.xml
@@ -1,212 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
id="org.apache.cordova.camera"
|
||||
version="0.2.5">
|
||||
<name>Camera</name>
|
||||
<description>Cordova Camera Plugin</description>
|
||||
<license>Apache 2.0</license>
|
||||
<keywords>cordova,camera</keywords>
|
||||
<repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-camera.git</repo>
|
||||
<issue>https://issues.apache.org/jira/browse/CB/component/12320645</issue>
|
||||
|
||||
<js-module src="www/CameraConstants.js" name="Camera">
|
||||
<clobbers target="Camera" />
|
||||
</js-module>
|
||||
|
||||
<js-module src="www/CameraPopoverOptions.js" name="CameraPopoverOptions">
|
||||
<clobbers target="CameraPopoverOptions" />
|
||||
</js-module>
|
||||
|
||||
|
||||
|
||||
<js-module src="www/Camera.js" name="camera">
|
||||
<clobbers target="navigator.camera" />
|
||||
</js-module>
|
||||
|
||||
<!-- firefoxos -->
|
||||
<platform name="firefoxos">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<param name="firefoxos-package" value="Camera" />
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<js-module src="src/firefoxos/camera.js" name="camera-impl">
|
||||
<runs />
|
||||
</js-module>
|
||||
</platform>
|
||||
<!-- android -->
|
||||
<platform name="android">
|
||||
<config-file target="res/xml/config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<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>
|
||||
|
||||
<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" />
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
|
||||
</platform>
|
||||
|
||||
<!-- amazon-fireos -->
|
||||
<platform name="amazon-fireos">
|
||||
<config-file target="res/xml/config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<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>
|
||||
|
||||
<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" />
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
|
||||
</platform>
|
||||
|
||||
<!-- ubuntu -->
|
||||
<platform name="ubuntu">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<param policy_group="camera" policy_version="1" />
|
||||
</feature>
|
||||
</config-file>
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
<header-file src="src/ubuntu/camera.h" />
|
||||
<source-file src="src/ubuntu/camera.cpp" />
|
||||
|
||||
<resource-file src="src/ubuntu/back.png" />
|
||||
<resource-file src="src/ubuntu/CaptureWidget.qml" />
|
||||
<resource-file src="src/ubuntu/shoot.png" />
|
||||
<resource-file src="src/ubuntu/toolbar-left.png" />
|
||||
<resource-file src="src/ubuntu/toolbar-middle.png" />
|
||||
<resource-file src="src/ubuntu/toolbar-right.png" />
|
||||
</platform>
|
||||
|
||||
<!-- ios -->
|
||||
<platform name="ios">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<param name="ios-package" value="CDVCamera" />
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<js-module src="www/ios/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
|
||||
<header-file src="src/ios/CDVCamera.h" />
|
||||
<source-file src="src/ios/CDVCamera.m" />
|
||||
<header-file src="src/ios/CDVJpegHeaderWriter.h" />
|
||||
<source-file src="src/ios/CDVJpegHeaderWriter.m" />
|
||||
<header-file src="src/ios/CDVExif.h" />
|
||||
<framework src="ImageIO.framework" weak="true" />
|
||||
<framework src="CoreLocation.framework" />
|
||||
<framework src="CoreGraphics.framework" />
|
||||
<framework src="AssetsLibrary.framework" />
|
||||
<framework src="MobileCoreServices.framework" />
|
||||
</platform>
|
||||
|
||||
<!-- blackberry10 -->
|
||||
<platform name="blackberry10">
|
||||
<source-file src="src/blackberry10/index.js" target-dir="Camera" />
|
||||
<config-file target="www/config.xml" parent="/widget">
|
||||
<feature name="Camera" value="Camera"/>
|
||||
</config-file>
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- wp7 -->
|
||||
<platform name="wp7">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<param name="wp-package" value="Camera"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App/Capabilities">
|
||||
<Capability Name="ID_CAP_ISV_CAMERA" />
|
||||
<Capability Name="ID_CAP_MEDIALIB" />
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/wp/Camera.cs" />
|
||||
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- wp8 -->
|
||||
<platform name="wp8">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<param name="wp-package" value="Camera"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App/Capabilities">
|
||||
<Capability Name="ID_CAP_ISV_CAMERA" />
|
||||
<Capability Name="ID_CAP_MEDIALIB_PHOTO"/>
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/wp/Camera.cs" />
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- windows8 -->
|
||||
<platform name="windows8">
|
||||
|
||||
<dependency id="org.apache.cordova.file" />
|
||||
<config-file target="package.appxmanifest" parent="/Package/Capabilities">
|
||||
<Capability Name="picturesLibrary" />
|
||||
<DeviceCapability Name="webcam" />
|
||||
</config-file>
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
<js-module src="src/windows8/CameraProxy.js" name="CameraProxy">
|
||||
<merges target="" />
|
||||
</js-module>
|
||||
|
||||
</platform>
|
||||
|
||||
<!-- firefoxos -->
|
||||
<platform name="firefoxos">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="Camera">
|
||||
<param name="firefoxos-package" value="Camera" />
|
||||
</feature>
|
||||
</config-file>
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
<js-module src="src/firefoxos/CameraProxy.js" name="CameraProxy">
|
||||
<runs />
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
</plugin>
|
||||
@@ -1,819 +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.
|
||||
*/
|
||||
package org.apache.cordova.camera;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.apache.cordova.LOG;
|
||||
import org.apache.cordova.PluginResult;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class launches the camera view, allows the user to take a picture, closes the camera view,
|
||||
* and returns the captured image. When the camera view is closed, the screen displayed before
|
||||
* the camera view was shown is redisplayed.
|
||||
*/
|
||||
public class CameraLauncher extends CordovaPlugin implements MediaScannerConnectionClient {
|
||||
|
||||
private static final int DATA_URL = 0; // Return base64 encoded string
|
||||
private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android)
|
||||
private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI
|
||||
|
||||
private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
|
||||
private static final int CAMERA = 1; // Take picture from camera
|
||||
private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture library (same as PHOTOLIBRARY for Android)
|
||||
|
||||
private static final int PICTURE = 0; // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
|
||||
private static final int VIDEO = 1; // allow selection of video only, ONLY RETURNS URL
|
||||
private static final int ALLMEDIA = 2; // allow selection from all media types
|
||||
|
||||
private static final int JPEG = 0; // Take a picture of type JPEG
|
||||
private static final int PNG = 1; // Take a picture of type PNG
|
||||
private static final String GET_PICTURE = "Get Picture";
|
||||
private static final String GET_VIDEO = "Get Video";
|
||||
private static final String GET_All = "Get All";
|
||||
|
||||
private static final String LOG_TAG = "CameraLauncher";
|
||||
|
||||
private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
|
||||
private int targetWidth; // desired width of the image
|
||||
private int targetHeight; // desired height of the image
|
||||
private Uri imageUri; // Uri of captured image
|
||||
private int encodingType; // Type of encoding to use
|
||||
private int mediaType; // What type of media to retrieve
|
||||
private boolean saveToPhotoAlbum; // Should the picture be saved to the device's photo album
|
||||
private boolean correctOrientation; // Should the pictures orientation be corrected
|
||||
//private boolean allowEdit; // Should we allow the user to crop the image. UNUSED.
|
||||
|
||||
public CallbackContext callbackContext;
|
||||
private int numPics;
|
||||
|
||||
private MediaScannerConnection conn; // Used to update gallery app with newly-written files
|
||||
private Uri scanMe; // Uri of image to be added to content store
|
||||
|
||||
/**
|
||||
* Executes the request and returns PluginResult.
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args JSONArry of arguments for the plugin.
|
||||
* @param callbackContext The callback id used when calling back into JavaScript.
|
||||
* @return A PluginResult object with a status and message.
|
||||
*/
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||
this.callbackContext = callbackContext;
|
||||
|
||||
if (action.equals("takePicture")) {
|
||||
int srcType = CAMERA;
|
||||
int destType = FILE_URI;
|
||||
this.saveToPhotoAlbum = false;
|
||||
this.targetHeight = 0;
|
||||
this.targetWidth = 0;
|
||||
this.encodingType = JPEG;
|
||||
this.mediaType = PICTURE;
|
||||
this.mQuality = 80;
|
||||
|
||||
this.mQuality = args.getInt(0);
|
||||
destType = args.getInt(1);
|
||||
srcType = args.getInt(2);
|
||||
this.targetWidth = args.getInt(3);
|
||||
this.targetHeight = args.getInt(4);
|
||||
this.encodingType = args.getInt(5);
|
||||
this.mediaType = args.getInt(6);
|
||||
//this.allowEdit = args.getBoolean(7); // This field is unused.
|
||||
this.correctOrientation = args.getBoolean(8);
|
||||
this.saveToPhotoAlbum = args.getBoolean(9);
|
||||
|
||||
// If the user specifies a 0 or smaller width/height
|
||||
// make it -1 so later comparisons succeed
|
||||
if (this.targetWidth < 1) {
|
||||
this.targetWidth = -1;
|
||||
}
|
||||
if (this.targetHeight < 1) {
|
||||
this.targetHeight = -1;
|
||||
}
|
||||
|
||||
try {
|
||||
if (srcType == CAMERA) {
|
||||
this.takePicture(destType, encodingType);
|
||||
}
|
||||
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
|
||||
this.getImage(srcType, destType);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
callbackContext.error("Illegal Argument Exception");
|
||||
PluginResult r = new PluginResult(PluginResult.Status.ERROR);
|
||||
callbackContext.sendPluginResult(r);
|
||||
return true;
|
||||
}
|
||||
|
||||
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
|
||||
r.setKeepCallback(true);
|
||||
callbackContext.sendPluginResult(r);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// LOCAL METHODS
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
private String getTempDirectoryPath() {
|
||||
File cache = null;
|
||||
|
||||
// SD Card Mounted
|
||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
cache = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
|
||||
"/Android/data/" + cordova.getActivity().getPackageName() + "/cache/");
|
||||
}
|
||||
// Use internal storage
|
||||
else {
|
||||
cache = cordova.getActivity().getCacheDir();
|
||||
}
|
||||
|
||||
// Create the cache directory if it doesn't exist
|
||||
cache.mkdirs();
|
||||
return cache.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a picture with the camera.
|
||||
* When an image is captured or the camera view is cancelled, the result is returned
|
||||
* in CordovaActivity.onActivityResult, which forwards the result to this.onActivityResult.
|
||||
*
|
||||
* The image can either be returned as a base64 string or a URI that points to the file.
|
||||
* To display base64 string in an img tag, set the source to:
|
||||
* img.src="data:image/jpeg;base64,"+result;
|
||||
* or to display URI in an img tag
|
||||
* img.src=result;
|
||||
*
|
||||
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
|
||||
* @param returnType Set the type of image to return.
|
||||
*/
|
||||
public void takePicture(int returnType, int encodingType) {
|
||||
// Save the number of images currently on disk for later
|
||||
this.numPics = queryImgDB(whichContentStore()).getCount();
|
||||
|
||||
// Display camera
|
||||
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
|
||||
|
||||
// Specify file so that large image is captured and returned
|
||||
File photo = createCaptureFile(encodingType);
|
||||
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
|
||||
this.imageUri = Uri.fromFile(photo);
|
||||
|
||||
if (this.cordova != null) {
|
||||
this.cordova.startActivityForResult((CordovaPlugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
|
||||
}
|
||||
// else
|
||||
// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file in the applications temporary directory based upon the supplied encoding.
|
||||
*
|
||||
* @param encodingType of the image to be taken
|
||||
* @return a File object pointing to the temporary picture
|
||||
*/
|
||||
private File createCaptureFile(int encodingType) {
|
||||
File photo = null;
|
||||
if (encodingType == JPEG) {
|
||||
photo = new File(getTempDirectoryPath(), ".Pic.jpg");
|
||||
} else if (encodingType == PNG) {
|
||||
photo = new File(getTempDirectoryPath(), ".Pic.png");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid Encoding Type: " + encodingType);
|
||||
}
|
||||
return photo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image from photo library.
|
||||
*
|
||||
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
|
||||
* @param srcType The album to get image from.
|
||||
* @param returnType Set the type of image to return.
|
||||
*/
|
||||
// TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do!
|
||||
public void getImage(int srcType, int returnType) {
|
||||
Intent intent = new Intent();
|
||||
String title = GET_PICTURE;
|
||||
if (this.mediaType == PICTURE) {
|
||||
intent.setType("image/*");
|
||||
}
|
||||
else if (this.mediaType == VIDEO) {
|
||||
intent.setType("video/*");
|
||||
title = GET_VIDEO;
|
||||
}
|
||||
else if (this.mediaType == ALLMEDIA) {
|
||||
// I wanted to make the type 'image/*, video/*' but this does not work on all versions
|
||||
// of android so I had to go with the wildcard search.
|
||||
intent.setType("*/*");
|
||||
title = GET_All;
|
||||
}
|
||||
|
||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
if (this.cordova != null) {
|
||||
this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent,
|
||||
new String(title)), (srcType + 1) * 16 + returnType + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the camera view exits.
|
||||
*
|
||||
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||
* allowing you to identify who this result came from.
|
||||
* @param resultCode The integer result code returned by the child activity through its setResult().
|
||||
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
|
||||
*/
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
|
||||
// Get src and dest types from request code
|
||||
int srcType = (requestCode / 16) - 1;
|
||||
int destType = (requestCode % 16) - 1;
|
||||
int rotate = 0;
|
||||
|
||||
// If CAMERA
|
||||
if (srcType == CAMERA) {
|
||||
// If image available
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
try {
|
||||
// Create an ExifHelper to save the exif data that is lost during compression
|
||||
ExifHelper exif = new ExifHelper();
|
||||
try {
|
||||
if (this.encodingType == JPEG) {
|
||||
exif.createInFile(getTempDirectoryPath() + "/.Pic.jpg");
|
||||
exif.readExifData();
|
||||
rotate = exif.getOrientation();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Bitmap bitmap = null;
|
||||
Uri uri = null;
|
||||
|
||||
// If sending base64 image back
|
||||
if (destType == DATA_URL) {
|
||||
bitmap = getScaledBitmap(FileHelper.stripFileProtocol(imageUri.toString()));
|
||||
if (bitmap == null) {
|
||||
// Try to get the bitmap from intent.
|
||||
bitmap = (Bitmap)intent.getExtras().get("data");
|
||||
}
|
||||
|
||||
// Double-check the bitmap.
|
||||
if (bitmap == null) {
|
||||
Log.d(LOG_TAG, "I either have a null image path or bitmap");
|
||||
this.failPicture("Unable to create bitmap!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (rotate != 0 && this.correctOrientation) {
|
||||
bitmap = getRotatedBitmap(rotate, bitmap, exif);
|
||||
}
|
||||
|
||||
this.processPicture(bitmap);
|
||||
checkForDuplicateImage(DATA_URL);
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI || destType == NATIVE_URI) {
|
||||
if (this.saveToPhotoAlbum) {
|
||||
Uri inputUri = getUriFromMediaStore();
|
||||
//Just because we have a media URI doesn't mean we have a real file, we need to make it
|
||||
uri = Uri.fromFile(new File(FileHelper.getRealPath(inputUri, this.cordova)));
|
||||
} else {
|
||||
uri = Uri.fromFile(new File(getTempDirectoryPath(), System.currentTimeMillis() + ".jpg"));
|
||||
}
|
||||
|
||||
if (uri == null) {
|
||||
this.failPicture("Error capturing image - no media storage found.");
|
||||
}
|
||||
|
||||
// If all this is true we shouldn't compress the image.
|
||||
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 &&
|
||||
!this.correctOrientation) {
|
||||
writeUncompressedImage(uri);
|
||||
|
||||
this.callbackContext.success(uri.toString());
|
||||
} else {
|
||||
bitmap = getScaledBitmap(FileHelper.stripFileProtocol(imageUri.toString()));
|
||||
|
||||
if (rotate != 0 && this.correctOrientation) {
|
||||
bitmap = getRotatedBitmap(rotate, bitmap, exif);
|
||||
}
|
||||
|
||||
// Add compressed version of captured image to returned media store Uri
|
||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
||||
os.close();
|
||||
|
||||
// Restore exif data to file
|
||||
if (this.encodingType == JPEG) {
|
||||
String exifPath;
|
||||
if (this.saveToPhotoAlbum) {
|
||||
exifPath = FileHelper.getRealPath(uri, this.cordova);
|
||||
} else {
|
||||
exifPath = uri.getPath();
|
||||
}
|
||||
exif.createOutFile(exifPath);
|
||||
exif.writeExifData();
|
||||
}
|
||||
|
||||
}
|
||||
// Send Uri back to JavaScript for viewing image
|
||||
this.callbackContext.success(uri.toString());
|
||||
}
|
||||
|
||||
this.cleanup(FILE_URI, this.imageUri, uri, bitmap);
|
||||
bitmap = null;
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error capturing image.");
|
||||
}
|
||||
}
|
||||
|
||||
// If cancelled
|
||||
else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
this.failPicture("Camera cancelled.");
|
||||
}
|
||||
|
||||
// If something else
|
||||
else {
|
||||
this.failPicture("Did not complete!");
|
||||
}
|
||||
}
|
||||
|
||||
// If retrieving photo from library
|
||||
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri uri = intent.getData();
|
||||
|
||||
// If you ask for video or all media type you will automatically get back a file URI
|
||||
// and there will be no attempt to resize any returned data
|
||||
if (this.mediaType != PICTURE) {
|
||||
this.callbackContext.success(uri.toString());
|
||||
}
|
||||
else {
|
||||
// This is a special case to just return the path as no scaling,
|
||||
// rotating, nor compressing needs to be done
|
||||
if (this.targetHeight == -1 && this.targetWidth == -1 &&
|
||||
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) {
|
||||
this.callbackContext.success(uri.toString());
|
||||
} else {
|
||||
String uriString = uri.toString();
|
||||
// Get the path to the image. Makes loading so much easier.
|
||||
String mimeType = FileHelper.getMimeType(uriString, this.cordova);
|
||||
// If we don't have a valid image so quit.
|
||||
if (!("image/jpeg".equalsIgnoreCase(mimeType) || "image/png".equalsIgnoreCase(mimeType))) {
|
||||
Log.d(LOG_TAG, "I either have a null image path or bitmap");
|
||||
this.failPicture("Unable to retrieve path to picture!");
|
||||
return;
|
||||
}
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
bitmap = getScaledBitmap(uriString);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (bitmap == null) {
|
||||
Log.d(LOG_TAG, "I either have a null image path or bitmap");
|
||||
this.failPicture("Unable to create bitmap!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.correctOrientation) {
|
||||
rotate = getImageOrientation(uri);
|
||||
if (rotate != 0) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setRotate(rotate);
|
||||
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
}
|
||||
|
||||
// If sending base64 image back
|
||||
if (destType == DATA_URL) {
|
||||
this.processPicture(bitmap);
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI || destType == NATIVE_URI) {
|
||||
// Do we need to scale the returned file
|
||||
if (this.targetHeight > 0 && this.targetWidth > 0) {
|
||||
try {
|
||||
// Create an ExifHelper to save the exif data that is lost during compression
|
||||
String resizePath = getTempDirectoryPath() + "/resize.jpg";
|
||||
// Some content: URIs do not map to file paths (e.g. picasa).
|
||||
String realPath = FileHelper.getRealPath(uri, this.cordova);
|
||||
ExifHelper exif = new ExifHelper();
|
||||
if (realPath != null && this.encodingType == JPEG) {
|
||||
try {
|
||||
exif.createInFile(realPath);
|
||||
exif.readExifData();
|
||||
rotate = exif.getOrientation();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream os = new FileOutputStream(resizePath);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
||||
os.close();
|
||||
|
||||
// Restore exif data to file
|
||||
if (realPath != null && this.encodingType == JPEG) {
|
||||
exif.createOutFile(resizePath);
|
||||
exif.writeExifData();
|
||||
}
|
||||
|
||||
// The resized image is cached by the app in order to get around this and not have to delete you
|
||||
// application cache I'm adding the current system time to the end of the file url.
|
||||
this.callbackContext.success("file://" + resizePath + "?" + System.currentTimeMillis());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error retrieving image.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.callbackContext.success(uri.toString());
|
||||
}
|
||||
}
|
||||
if (bitmap != null) {
|
||||
bitmap.recycle();
|
||||
bitmap = null;
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
this.failPicture("Selection cancelled.");
|
||||
}
|
||||
else {
|
||||
this.failPicture("Selection did not complete!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getImageOrientation(Uri uri) {
|
||||
String[] cols = { MediaStore.Images.Media.ORIENTATION };
|
||||
Cursor cursor = cordova.getActivity().getContentResolver().query(uri,
|
||||
cols, null, null, null);
|
||||
int rotate = 0;
|
||||
if (cursor != null) {
|
||||
cursor.moveToPosition(0);
|
||||
rotate = cursor.getInt(0);
|
||||
cursor.close();
|
||||
}
|
||||
return rotate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out if the bitmap should be rotated. For instance if the picture was taken in
|
||||
* portrait mode
|
||||
*
|
||||
* @param rotate
|
||||
* @param bitmap
|
||||
* @return rotated bitmap
|
||||
*/
|
||||
private Bitmap getRotatedBitmap(int rotate, Bitmap bitmap, ExifHelper exif) {
|
||||
Matrix matrix = new Matrix();
|
||||
if (rotate == 180) {
|
||||
matrix.setRotate(rotate);
|
||||
} else {
|
||||
matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
|
||||
}
|
||||
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
exif.resetOrientation();
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the special case where the default width, height and quality are unchanged
|
||||
* we just write the file out to disk saving the expensive Bitmap.compress function.
|
||||
*
|
||||
* @param uri
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private void writeUncompressedImage(Uri uri) throws FileNotFoundException,
|
||||
IOException {
|
||||
FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(imageUri.toString()));
|
||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
fis.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create entry in media store for image
|
||||
*
|
||||
* @return uri
|
||||
*/
|
||||
private Uri getUriFromMediaStore() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
|
||||
Uri uri;
|
||||
try {
|
||||
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.d(LOG_TAG, "Can't write to external media storage.");
|
||||
try {
|
||||
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
LOG.d(LOG_TAG, "Can't write to internal media storage.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a scaled bitmap based on the target width and height
|
||||
*
|
||||
* @param imagePath
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private Bitmap getScaledBitmap(String imageUrl) throws IOException {
|
||||
// If no new width or height were specified return the original bitmap
|
||||
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
|
||||
return BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova));
|
||||
}
|
||||
|
||||
// figure out the original width and height of the image
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova), null, options);
|
||||
|
||||
//CB-2292: WTF? Why is the width null?
|
||||
if(options.outWidth == 0 || options.outHeight == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// determine the correct aspect ratio
|
||||
int[] widthHeight = calculateAspectRatio(options.outWidth, options.outHeight);
|
||||
|
||||
// Load in the smallest bitmap possible that is closest to the size we want
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight);
|
||||
Bitmap unscaledBitmap = BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova), null, options);
|
||||
if (unscaledBitmap == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain the aspect ratio so the resulting image does not look smooshed
|
||||
*
|
||||
* @param origWidth
|
||||
* @param origHeight
|
||||
* @return
|
||||
*/
|
||||
public int[] calculateAspectRatio(int origWidth, int origHeight) {
|
||||
int newWidth = this.targetWidth;
|
||||
int newHeight = this.targetHeight;
|
||||
|
||||
// If no new width or height were specified return the original bitmap
|
||||
if (newWidth <= 0 && newHeight <= 0) {
|
||||
newWidth = origWidth;
|
||||
newHeight = origHeight;
|
||||
}
|
||||
// Only the width was specified
|
||||
else if (newWidth > 0 && newHeight <= 0) {
|
||||
newHeight = (newWidth * origHeight) / origWidth;
|
||||
}
|
||||
// only the height was specified
|
||||
else if (newWidth <= 0 && newHeight > 0) {
|
||||
newWidth = (newHeight * origWidth) / origHeight;
|
||||
}
|
||||
// If the user specified both a positive width and height
|
||||
// (potentially different aspect ratio) then the width or height is
|
||||
// scaled so that the image fits while maintaining aspect ratio.
|
||||
// Alternatively, the specified width and height could have been
|
||||
// kept and Bitmap.SCALE_TO_FIT specified when scaling, but this
|
||||
// would result in whitespace in the new image.
|
||||
else {
|
||||
double newRatio = newWidth / (double) newHeight;
|
||||
double origRatio = origWidth / (double) origHeight;
|
||||
|
||||
if (origRatio > newRatio) {
|
||||
newHeight = (newWidth * origHeight) / origWidth;
|
||||
} else if (origRatio < newRatio) {
|
||||
newWidth = (newHeight * origWidth) / origHeight;
|
||||
}
|
||||
}
|
||||
|
||||
int[] retval = new int[2];
|
||||
retval[0] = newWidth;
|
||||
retval[1] = newHeight;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out what ratio we can load our image into memory at while still being bigger than
|
||||
* our desired width and height
|
||||
*
|
||||
* @param srcWidth
|
||||
* @param srcHeight
|
||||
* @param dstWidth
|
||||
* @param dstHeight
|
||||
* @return
|
||||
*/
|
||||
public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
|
||||
final float srcAspect = (float)srcWidth / (float)srcHeight;
|
||||
final float dstAspect = (float)dstWidth / (float)dstHeight;
|
||||
|
||||
if (srcAspect > dstAspect) {
|
||||
return srcWidth / dstWidth;
|
||||
} else {
|
||||
return srcHeight / dstHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cursor that can be used to determine how many images we have.
|
||||
*
|
||||
* @return a cursor
|
||||
*/
|
||||
private Cursor queryImgDB(Uri contentStore) {
|
||||
return this.cordova.getActivity().getContentResolver().query(
|
||||
contentStore,
|
||||
new String[] { MediaStore.Images.Media._ID },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up after picture taking. Checking for duplicates and that kind of stuff.
|
||||
* @param newImage
|
||||
*/
|
||||
private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
|
||||
if (bitmap != null) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
|
||||
// Clean up initial camera-written image file.
|
||||
(new File(FileHelper.stripFileProtocol(oldImage.toString()))).delete();
|
||||
|
||||
checkForDuplicateImage(imageType);
|
||||
// Scan for the gallery to update pic refs in gallery
|
||||
if (this.saveToPhotoAlbum && newImage != null) {
|
||||
this.scanForGallery(newImage);
|
||||
}
|
||||
|
||||
System.gc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to find out if we are in a situation where the Camera Intent adds to images
|
||||
* to the content store. If we are using a FILE_URI and the number of images in the DB
|
||||
* increases by 2 we have a duplicate, when using a DATA_URL the number is 1.
|
||||
*
|
||||
* @param type FILE_URI or DATA_URL
|
||||
*/
|
||||
private void checkForDuplicateImage(int type) {
|
||||
int diff = 1;
|
||||
Uri contentStore = whichContentStore();
|
||||
Cursor cursor = queryImgDB(contentStore);
|
||||
int currentNumOfImages = cursor.getCount();
|
||||
|
||||
if (type == FILE_URI && this.saveToPhotoAlbum) {
|
||||
diff = 2;
|
||||
}
|
||||
|
||||
// delete the duplicate file if the difference is 2 for file URI or 1 for Data URL
|
||||
if ((currentNumOfImages - numPics) == diff) {
|
||||
cursor.moveToLast();
|
||||
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
|
||||
if (diff == 2) {
|
||||
id--;
|
||||
}
|
||||
Uri uri = Uri.parse(contentStore + "/" + id);
|
||||
this.cordova.getActivity().getContentResolver().delete(uri, null, null);
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we are storing the images in internal or external storage
|
||||
* @return Uri
|
||||
*/
|
||||
private Uri whichContentStore() {
|
||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
return android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else {
|
||||
return android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript.
|
||||
*
|
||||
* @param bitmap
|
||||
*/
|
||||
public void processPicture(Bitmap bitmap) {
|
||||
ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream();
|
||||
try {
|
||||
if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) {
|
||||
byte[] code = jpeg_data.toByteArray();
|
||||
byte[] output = Base64.encode(code, Base64.NO_WRAP);
|
||||
String js_out = new String(output);
|
||||
this.callbackContext.success(js_out);
|
||||
js_out = null;
|
||||
output = null;
|
||||
code = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
this.failPicture("Error compressing image.");
|
||||
}
|
||||
jpeg_data = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send error message to JavaScript.
|
||||
*
|
||||
* @param err
|
||||
*/
|
||||
public void failPicture(String err) {
|
||||
this.callbackContext.error(err);
|
||||
}
|
||||
|
||||
private void scanForGallery(Uri newImage) {
|
||||
this.scanMe = newImage;
|
||||
if(this.conn != null) {
|
||||
this.conn.disconnect();
|
||||
}
|
||||
this.conn = new MediaScannerConnection(this.cordova.getActivity().getApplicationContext(), this);
|
||||
conn.connect();
|
||||
}
|
||||
|
||||
public void onMediaScannerConnected() {
|
||||
try{
|
||||
this.conn.scanFile(this.scanMe.toString(), "image/*");
|
||||
} catch (java.lang.IllegalStateException e){
|
||||
LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void onScanCompleted(String path, Uri uri) {
|
||||
this.conn.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -1,185 +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.
|
||||
*/
|
||||
package org.apache.cordova.camera;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import android.media.ExifInterface;
|
||||
|
||||
public class ExifHelper {
|
||||
private String aperture = null;
|
||||
private String datetime = null;
|
||||
private String exposureTime = null;
|
||||
private String flash = null;
|
||||
private String focalLength = null;
|
||||
private String gpsAltitude = null;
|
||||
private String gpsAltitudeRef = null;
|
||||
private String gpsDateStamp = null;
|
||||
private String gpsLatitude = null;
|
||||
private String gpsLatitudeRef = null;
|
||||
private String gpsLongitude = null;
|
||||
private String gpsLongitudeRef = null;
|
||||
private String gpsProcessingMethod = null;
|
||||
private String gpsTimestamp = null;
|
||||
private String iso = null;
|
||||
private String make = null;
|
||||
private String model = null;
|
||||
private String orientation = null;
|
||||
private String whiteBalance = null;
|
||||
|
||||
private ExifInterface inFile = null;
|
||||
private ExifInterface outFile = null;
|
||||
|
||||
/**
|
||||
* The file before it is compressed
|
||||
*
|
||||
* @param filePath
|
||||
* @throws IOException
|
||||
*/
|
||||
public void createInFile(String filePath) throws IOException {
|
||||
this.inFile = new ExifInterface(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* The file after it has been compressed
|
||||
*
|
||||
* @param filePath
|
||||
* @throws IOException
|
||||
*/
|
||||
public void createOutFile(String filePath) throws IOException {
|
||||
this.outFile = new ExifInterface(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all the EXIF data from the input file.
|
||||
*/
|
||||
public void readExifData() {
|
||||
this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE);
|
||||
this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
|
||||
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
|
||||
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
|
||||
this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
|
||||
this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
|
||||
this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF);
|
||||
this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
|
||||
this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
||||
this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||
this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
||||
this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||
this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);
|
||||
this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
|
||||
this.iso = inFile.getAttribute(ExifInterface.TAG_ISO);
|
||||
this.make = inFile.getAttribute(ExifInterface.TAG_MAKE);
|
||||
this.model = inFile.getAttribute(ExifInterface.TAG_MODEL);
|
||||
this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION);
|
||||
this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the previously stored EXIF data to the output file.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeExifData() throws IOException {
|
||||
// Don't try to write to a null file
|
||||
if (this.outFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.aperture != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture);
|
||||
}
|
||||
if (this.datetime != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);
|
||||
}
|
||||
if (this.exposureTime != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime);
|
||||
}
|
||||
if (this.flash != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash);
|
||||
}
|
||||
if (this.focalLength != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength);
|
||||
}
|
||||
if (this.gpsAltitude != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude);
|
||||
}
|
||||
if (this.gpsAltitudeRef != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef);
|
||||
}
|
||||
if (this.gpsDateStamp != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp);
|
||||
}
|
||||
if (this.gpsLatitude != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude);
|
||||
}
|
||||
if (this.gpsLatitudeRef != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef);
|
||||
}
|
||||
if (this.gpsLongitude != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude);
|
||||
}
|
||||
if (this.gpsLongitudeRef != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef);
|
||||
}
|
||||
if (this.gpsProcessingMethod != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod);
|
||||
}
|
||||
if (this.gpsTimestamp != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp);
|
||||
}
|
||||
if (this.iso != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso);
|
||||
}
|
||||
if (this.make != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make);
|
||||
}
|
||||
if (this.model != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model);
|
||||
}
|
||||
if (this.orientation != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation);
|
||||
}
|
||||
if (this.whiteBalance != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance);
|
||||
}
|
||||
|
||||
this.outFile.saveAttributes();
|
||||
}
|
||||
|
||||
public int getOrientation() {
|
||||
int o = Integer.parseInt(this.orientation);
|
||||
|
||||
if (o == ExifInterface.ORIENTATION_NORMAL) {
|
||||
return 0;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_90) {
|
||||
return 90;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_180) {
|
||||
return 180;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_270) {
|
||||
return 270;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void resetOrientation() {
|
||||
this.orientation = "" + ExifInterface.ORIENTATION_NORMAL;
|
||||
}
|
||||
}
|
||||
@@ -1,158 +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.
|
||||
*/
|
||||
package org.apache.cordova.camera;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
public class FileHelper {
|
||||
private static final String LOG_TAG = "FileUtils";
|
||||
private static final String _DATA = "_data";
|
||||
|
||||
/**
|
||||
* 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 cordova the current application context
|
||||
* @return the full path to the file
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static String getRealPath(String uriString, CordovaInterface cordova) {
|
||||
String realPath = null;
|
||||
|
||||
if (uriString.startsWith("content://")) {
|
||||
String[] proj = { _DATA };
|
||||
Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
|
||||
int column_index = cursor.getColumnIndexOrThrow(_DATA);
|
||||
cursor.moveToFirst();
|
||||
realPath = cursor.getString(column_index);
|
||||
if (realPath == null) {
|
||||
LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
|
||||
}
|
||||
} else if (uriString.startsWith("file://")) {
|
||||
realPath = uriString.substring(7);
|
||||
if (realPath.startsWith("/android_asset/")) {
|
||||
LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
|
||||
realPath = null;
|
||||
}
|
||||
} else {
|
||||
realPath = uriString;
|
||||
}
|
||||
|
||||
return realPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 cordova the current application context
|
||||
* @return the full path to the file
|
||||
*/
|
||||
public static String getRealPath(Uri uri, CordovaInterface cordova) {
|
||||
return FileHelper.getRealPath(uri.toString(), cordova);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input stream based on given URI string.
|
||||
*
|
||||
* @param uriString the URI string from which to obtain the input stream
|
||||
* @param cordova the current application context
|
||||
* @return an input stream into the data at the given URI or null if given an invalid URI string
|
||||
* @throws IOException
|
||||
*/
|
||||
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
|
||||
if (uriString.startsWith("content")) {
|
||||
Uri uri = Uri.parse(uriString);
|
||||
return cordova.getActivity().getContentResolver().openInputStream(uri);
|
||||
} else if (uriString.startsWith("file://")) {
|
||||
int question = uriString.indexOf("?");
|
||||
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);
|
||||
return cordova.getActivity().getAssets().open(relativePath);
|
||||
} else {
|
||||
return new FileInputStream(getRealPath(uriString, cordova));
|
||||
}
|
||||
} else {
|
||||
return new FileInputStream(getRealPath(uriString, cordova));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the "file://" prefix from the given URI string, if applicable.
|
||||
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
|
||||
*
|
||||
* @param uriString the URI string to operate on
|
||||
* @return a path without the "file://" prefix
|
||||
*/
|
||||
public static String stripFileProtocol(String uriString) {
|
||||
if (uriString.startsWith("file://")) {
|
||||
uriString = uriString.substring(7);
|
||||
}
|
||||
return uriString;
|
||||
}
|
||||
|
||||
public static String getMimeTypeForExtension(String path) {
|
||||
String extension = path;
|
||||
int lastDot = extension.lastIndexOf('.');
|
||||
if (lastDot != -1) {
|
||||
extension = extension.substring(lastDot + 1);
|
||||
}
|
||||
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
|
||||
extension = extension.toLowerCase(Locale.getDefault());
|
||||
if (extension.equals("3ga")) {
|
||||
return "audio/3gpp";
|
||||
}
|
||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime type of the data specified by the given URI string.
|
||||
*
|
||||
* @param uriString the URI string of the data
|
||||
* @return the mime type of the specified data
|
||||
*/
|
||||
public static String getMimeType(String uriString, CordovaInterface cordova) {
|
||||
String mimeType = null;
|
||||
|
||||
Uri uri = Uri.parse(uriString);
|
||||
if (uriString.startsWith("content://")) {
|
||||
mimeType = cordova.getActivity().getContentResolver().getType(uri);
|
||||
} else {
|
||||
mimeType = getMimeTypeForExtension(uri.getPath());
|
||||
}
|
||||
|
||||
return mimeType;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +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.
|
||||
*
|
||||
*/
|
||||
var PictureSourceType = {
|
||||
PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
|
||||
CAMERA : 1, // Take picture from camera
|
||||
SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android)
|
||||
},
|
||||
DestinationType = {
|
||||
DATA_URL: 0, // Return base64 encoded string
|
||||
FILE_URI: 1, // Return file uri (content://media/external/images/media/2 for Android)
|
||||
NATIVE_URI: 2 // Return native uri (eg. asset-library://... for iOS)
|
||||
};
|
||||
|
||||
function encodeBase64(filePath, callback) {
|
||||
var sandbox = window.qnx.webplatform.getController().setFileSystemSandbox, // save original sandbox value
|
||||
errorHandler = function (err) {
|
||||
var msg = "An error occured: ";
|
||||
|
||||
switch (err.code) {
|
||||
case FileError.NOT_FOUND_ERR:
|
||||
msg += "File or directory not found";
|
||||
break;
|
||||
|
||||
case FileError.NOT_READABLE_ERR:
|
||||
msg += "File or directory not readable";
|
||||
break;
|
||||
|
||||
case FileError.PATH_EXISTS_ERR:
|
||||
msg += "File or directory already exists";
|
||||
break;
|
||||
|
||||
case FileError.TYPE_MISMATCH_ERR:
|
||||
msg += "Invalid file type";
|
||||
break;
|
||||
|
||||
default:
|
||||
msg += "Unknown Error";
|
||||
break;
|
||||
};
|
||||
|
||||
// set it back to original value
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
|
||||
callback(msg);
|
||||
},
|
||||
gotFile = function (fileEntry) {
|
||||
fileEntry.file(function (file) {
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onloadend = function (e) {
|
||||
// set it back to original value
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = sandbox;
|
||||
callback(this.result);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}, errorHandler);
|
||||
},
|
||||
onInitFs = function (fs) {
|
||||
window.qnx.webplatform.getController().setFileSystemSandbox = false;
|
||||
fs.root.getFile(filePath, {create: false}, gotFile, errorHandler);
|
||||
};
|
||||
|
||||
window.webkitRequestFileSystem(window.TEMPORARY, 10 * 1024 * 1024, onInitFs, errorHandler); // set size to 10MB max
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
takePicture: function (success, fail, args, env) {
|
||||
var destinationType = JSON.parse(decodeURIComponent(args[1])),
|
||||
sourceType = JSON.parse(decodeURIComponent(args[2])),
|
||||
result = new PluginResult(args, env),
|
||||
done = function (data) {
|
||||
if (destinationType === DestinationType.FILE_URI) {
|
||||
data = "file://" + data;
|
||||
result.callbackOk(data, false);
|
||||
} else {
|
||||
encodeBase64(data, function (data) {
|
||||
if (/^data:/.test(data)) {
|
||||
data = data.slice(data.indexOf(",") + 1);
|
||||
result.callbackOk(data, false);
|
||||
} else {
|
||||
result.callbackError(data, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel = function (reason) {
|
||||
result.callbackError(reason, false);
|
||||
},
|
||||
invoked = function (error) {
|
||||
if (error) {
|
||||
result.callbackError(error, false);
|
||||
}
|
||||
};
|
||||
|
||||
switch(sourceType) {
|
||||
case PictureSourceType.CAMERA:
|
||||
window.qnx.webplatform.getApplication().cards.camera.open("photo", done, cancel, invoked);
|
||||
break;
|
||||
|
||||
case PictureSourceType.PHOTOLIBRARY:
|
||||
case PictureSourceType.SAVEDPHOTOALBUM:
|
||||
window.qnx.webplatform.getApplication().cards.filePicker.open({
|
||||
mode: "Picker",
|
||||
type: ["picture"]
|
||||
}, done, cancel, invoked);
|
||||
break;
|
||||
}
|
||||
|
||||
result.noResult(true);
|
||||
}
|
||||
};
|
||||
@@ -1,51 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
function takePicture(success, error, opts) {
|
||||
var pick = new MozActivity({
|
||||
name: "pick",
|
||||
data: {
|
||||
type: ["image/*"]
|
||||
}
|
||||
});
|
||||
|
||||
pick.onerror = error || function() {};
|
||||
|
||||
pick.onsuccess = function() {
|
||||
// image is returned as Blob in this.result.blob
|
||||
// we need to call success with url or base64 encoded image
|
||||
if (opts && opts.destinationType == 0) {
|
||||
// TODO: base64
|
||||
return;
|
||||
}
|
||||
if (!opts || !opts.destinationType || opts.destinationType > 0) {
|
||||
// url
|
||||
return success(window.URL.createObjectURL(this.result.blob));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
takePicture: takePicture,
|
||||
cleanup: function(){}
|
||||
};
|
||||
|
||||
require("cordova/firefoxos/commandProxy").add("Camera", module.exports);
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
var firefoxos = require('cordova/platform');
|
||||
|
||||
function getPicture(cameraSuccess, cameraError, cameraOptions) {
|
||||
cameraError = cameraError || function(){};
|
||||
var pick = new MozActivity({
|
||||
name: "pick",
|
||||
data: {
|
||||
type: ["image/png", "image/jpg", "image/jpeg"]
|
||||
}
|
||||
});
|
||||
pick.onerror = cameraError;
|
||||
pick.onsuccess = function() {
|
||||
// image is returned as Blob in this.result.blob
|
||||
// we need to call cameraSuccess with url or base64 encoded image
|
||||
if (cameraOptions && cameraOptions.destinationType == 0) {
|
||||
// TODO: base64
|
||||
return;
|
||||
}
|
||||
if (!cameraOptions || !cameraOptions.destinationTyoe || cameraOptions.destinationType > 0) {
|
||||
// url
|
||||
return cameraSuccess(window.URL.createObjectURL(this.result.blob));
|
||||
}
|
||||
};
|
||||
}
|
||||
var Camera = {
|
||||
takePicture: getPicture,
|
||||
cleanup: function(){}
|
||||
};
|
||||
|
||||
firefoxos.registerPlugin('Camera', Camera);
|
||||
|
||||
@@ -1,102 +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 <Foundation/Foundation.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <Cordova/CDVPlugin.h>
|
||||
|
||||
enum CDVDestinationType {
|
||||
DestinationTypeDataUrl = 0,
|
||||
DestinationTypeFileUri,
|
||||
DestinationTypeNativeUri
|
||||
};
|
||||
typedef NSUInteger CDVDestinationType;
|
||||
|
||||
enum CDVEncodingType {
|
||||
EncodingTypeJPEG = 0,
|
||||
EncodingTypePNG
|
||||
};
|
||||
typedef NSUInteger CDVEncodingType;
|
||||
|
||||
enum CDVMediaType {
|
||||
MediaTypePicture = 0,
|
||||
MediaTypeVideo,
|
||||
MediaTypeAll
|
||||
};
|
||||
typedef NSUInteger CDVMediaType;
|
||||
|
||||
@interface CDVCameraPicker : UIImagePickerController
|
||||
{}
|
||||
|
||||
@property (assign) NSInteger quality;
|
||||
@property (copy) NSString* callbackId;
|
||||
@property (copy) NSString* postUrl;
|
||||
@property (nonatomic) enum CDVDestinationType returnType;
|
||||
@property (nonatomic) enum CDVEncodingType encodingType;
|
||||
@property (strong) UIPopoverController* popoverController;
|
||||
@property (assign) CGSize targetSize;
|
||||
@property (assign) bool correctOrientation;
|
||||
@property (assign) bool saveToPhotoAlbum;
|
||||
@property (assign) bool cropToSize;
|
||||
@property (strong) UIWebView* webView;
|
||||
@property (assign) BOOL popoverSupported;
|
||||
|
||||
@end
|
||||
|
||||
// ======================================================================= //
|
||||
|
||||
@interface CDVCamera : CDVPlugin <UIImagePickerControllerDelegate,
|
||||
UINavigationControllerDelegate,
|
||||
UIPopoverControllerDelegate,
|
||||
CLLocationManagerDelegate>
|
||||
{}
|
||||
|
||||
@property (strong) CDVCameraPicker* pickerController;
|
||||
@property (strong) NSMutableDictionary *metadata;
|
||||
@property (strong, nonatomic) CLLocationManager *locationManager;
|
||||
@property (strong) NSData* data;
|
||||
|
||||
/*
|
||||
* getPicture
|
||||
*
|
||||
* arguments:
|
||||
* 1: this is the javascript function that will be called with the results, the first parameter passed to the
|
||||
* javascript function is the picture as a Base64 encoded string
|
||||
* 2: this is the javascript function to be called if there was an error
|
||||
* options:
|
||||
* quality: integer between 1 and 100
|
||||
*/
|
||||
- (void)takePicture:(CDVInvokedUrlCommand*)command;
|
||||
- (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url;
|
||||
- (void)cleanup:(CDVInvokedUrlCommand*)command;
|
||||
- (void)repositionPopover:(CDVInvokedUrlCommand*)command;
|
||||
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info;
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo;
|
||||
- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker;
|
||||
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
|
||||
- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize;
|
||||
- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize;
|
||||
- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage;
|
||||
|
||||
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation;
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
|
||||
|
||||
@end
|
||||
@@ -1,756 +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"
|
||||
#import "CDVJpegHeaderWriter.h"
|
||||
#import <Cordova/NSArray+Comparisons.h>
|
||||
#import <Cordova/NSData+Base64.h>
|
||||
#import <Cordova/NSDictionary+Extensions.h>
|
||||
#import <ImageIO/CGImageProperties.h>
|
||||
#import <AssetsLibrary/ALAssetRepresentation.h>
|
||||
#import <ImageIO/CGImageSource.h>
|
||||
#import <ImageIO/CGImageProperties.h>
|
||||
#import <ImageIO/CGImageDestination.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
|
||||
#define CDV_PHOTO_PREFIX @"cdv_photo_"
|
||||
|
||||
static NSSet* org_apache_cordova_validArrowDirections;
|
||||
|
||||
@interface CDVCamera ()
|
||||
|
||||
@property (readwrite, assign) BOOL hasPendingOperation;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CDVCamera
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
org_apache_cordova_validArrowDirections = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:UIPopoverArrowDirectionUp], [NSNumber numberWithInt:UIPopoverArrowDirectionDown], [NSNumber numberWithInt:UIPopoverArrowDirectionLeft], [NSNumber numberWithInt:UIPopoverArrowDirectionRight], [NSNumber numberWithInt:UIPopoverArrowDirectionAny], nil];
|
||||
}
|
||||
|
||||
@synthesize hasPendingOperation, pickerController, locationManager;
|
||||
|
||||
- (BOOL)popoverSupported
|
||||
{
|
||||
return (NSClassFromString(@"UIPopoverController") != nil) &&
|
||||
(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
|
||||
}
|
||||
|
||||
/* takePicture arguments:
|
||||
* INDEX ARGUMENT
|
||||
* 0 quality
|
||||
* 1 destination type
|
||||
* 2 source type
|
||||
* 3 targetWidth
|
||||
* 4 targetHeight
|
||||
* 5 encodingType
|
||||
* 6 mediaType
|
||||
* 7 allowsEdit
|
||||
* 8 correctOrientation
|
||||
* 9 saveToPhotoAlbum
|
||||
* 10 popoverOptions
|
||||
* 11 cameraDirection
|
||||
*/
|
||||
- (void)takePicture:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
NSString* callbackId = command.callbackId;
|
||||
NSArray* arguments = command.arguments;
|
||||
|
||||
self.hasPendingOperation = NO;
|
||||
|
||||
NSString* sourceTypeString = [arguments objectAtIndex:2];
|
||||
UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; // default
|
||||
if (sourceTypeString != nil) {
|
||||
sourceType = (UIImagePickerControllerSourceType)[sourceTypeString intValue];
|
||||
}
|
||||
|
||||
bool hasCamera = [UIImagePickerController isSourceTypeAvailable:sourceType];
|
||||
if (!hasCamera) {
|
||||
NSLog(@"Camera.getPicture: source type %d not available.", sourceType);
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no camera available"];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
return;
|
||||
}
|
||||
|
||||
bool allowEdit = [[arguments objectAtIndex:7] boolValue];
|
||||
NSNumber* targetWidth = [arguments objectAtIndex:3];
|
||||
NSNumber* targetHeight = [arguments objectAtIndex:4];
|
||||
NSNumber* mediaValue = [arguments objectAtIndex:6];
|
||||
CDVMediaType mediaType = (mediaValue) ? [mediaValue intValue] : MediaTypePicture;
|
||||
|
||||
CGSize targetSize = CGSizeMake(0, 0);
|
||||
if ((targetWidth != nil) && (targetHeight != nil)) {
|
||||
targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]);
|
||||
}
|
||||
|
||||
// If a popover is already open, close it; we only want one at a time.
|
||||
if (([[self pickerController] popoverController] != nil) && [[[self pickerController] popoverController] isPopoverVisible]) {
|
||||
[[[self pickerController] popoverController] dismissPopoverAnimated:YES];
|
||||
[[[self pickerController] popoverController] setDelegate:nil];
|
||||
[[self pickerController] setPopoverController:nil];
|
||||
}
|
||||
|
||||
CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init];
|
||||
self.pickerController = cameraPicker;
|
||||
|
||||
cameraPicker.delegate = self;
|
||||
cameraPicker.sourceType = sourceType;
|
||||
cameraPicker.allowsEditing = allowEdit; // THIS IS ALL IT TAKES FOR CROPPING - jm
|
||||
cameraPicker.callbackId = callbackId;
|
||||
cameraPicker.targetSize = targetSize;
|
||||
cameraPicker.cropToSize = NO;
|
||||
// we need to capture this state for memory warnings that dealloc this object
|
||||
cameraPicker.webView = self.webView;
|
||||
cameraPicker.popoverSupported = [self popoverSupported];
|
||||
|
||||
cameraPicker.correctOrientation = [[arguments objectAtIndex:8] boolValue];
|
||||
cameraPicker.saveToPhotoAlbum = [[arguments objectAtIndex:9] boolValue];
|
||||
|
||||
cameraPicker.encodingType = ([arguments objectAtIndex:5]) ? [[arguments objectAtIndex:5] intValue] : EncodingTypeJPEG;
|
||||
|
||||
cameraPicker.quality = ([arguments objectAtIndex:0]) ? [[arguments objectAtIndex:0] intValue] : 50;
|
||||
cameraPicker.returnType = ([arguments objectAtIndex:1]) ? [[arguments objectAtIndex:1] intValue] : DestinationTypeFileUri;
|
||||
|
||||
if (sourceType == UIImagePickerControllerSourceTypeCamera) {
|
||||
// We only allow taking pictures (no video) in this API.
|
||||
cameraPicker.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil];
|
||||
|
||||
// We can only set the camera device if we're actually using the camera.
|
||||
NSNumber* cameraDirection = [command argumentAtIndex:11 withDefault:[NSNumber numberWithInteger:UIImagePickerControllerCameraDeviceRear]];
|
||||
cameraPicker.cameraDevice = (UIImagePickerControllerCameraDevice)[cameraDirection intValue];
|
||||
} else if (mediaType == MediaTypeAll) {
|
||||
cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType];
|
||||
} else {
|
||||
NSArray* mediaArray = [NSArray arrayWithObjects:(NSString*)(mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage), nil];
|
||||
cameraPicker.mediaTypes = mediaArray;
|
||||
}
|
||||
|
||||
if ([self popoverSupported] && (sourceType != UIImagePickerControllerSourceTypeCamera)) {
|
||||
if (cameraPicker.popoverController == nil) {
|
||||
cameraPicker.popoverController = [[NSClassFromString(@"UIPopoverController")alloc] initWithContentViewController:cameraPicker];
|
||||
}
|
||||
NSDictionary* options = [command.arguments objectAtIndex:10 withDefault:nil];
|
||||
[self displayPopover:options];
|
||||
} else {
|
||||
SEL selector = NSSelectorFromString(@"presentViewController:animated:completion:");
|
||||
if ([self.viewController respondsToSelector:selector]) {
|
||||
[self.viewController presentViewController:cameraPicker animated:YES completion:nil];
|
||||
} else {
|
||||
// deprecated as of iOS >= 6.0
|
||||
[self.viewController presentModalViewController:cameraPicker animated:YES];
|
||||
}
|
||||
}
|
||||
self.hasPendingOperation = YES;
|
||||
}
|
||||
|
||||
- (void)repositionPopover:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
NSDictionary* options = [command.arguments objectAtIndex:0 withDefault:nil];
|
||||
|
||||
[self displayPopover:options];
|
||||
}
|
||||
|
||||
- (void)displayPopover:(NSDictionary*)options
|
||||
{
|
||||
int x = 0;
|
||||
int y = 32;
|
||||
int width = 320;
|
||||
int height = 480;
|
||||
UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
|
||||
|
||||
if (options) {
|
||||
x = [options integerValueForKey:@"x" defaultValue:0];
|
||||
y = [options integerValueForKey:@"y" defaultValue:32];
|
||||
width = [options integerValueForKey:@"width" defaultValue:320];
|
||||
height = [options integerValueForKey:@"height" defaultValue:480];
|
||||
arrowDirection = [options integerValueForKey:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny];
|
||||
if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithInt:arrowDirection]]) {
|
||||
arrowDirection = UIPopoverArrowDirectionAny;
|
||||
}
|
||||
}
|
||||
|
||||
[[[self pickerController] popoverController] setDelegate:self];
|
||||
[[[self pickerController] popoverController] presentPopoverFromRect:CGRectMake(x, y, width, height)
|
||||
inView:[self.webView superview]
|
||||
permittedArrowDirections:arrowDirection
|
||||
animated:YES];
|
||||
}
|
||||
|
||||
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
|
||||
{
|
||||
if([navigationController isKindOfClass:[UIImagePickerController class]]){
|
||||
UIImagePickerController * cameraPicker = (UIImagePickerController*)navigationController;
|
||||
|
||||
if(![cameraPicker.mediaTypes containsObject:(NSString*) kUTTypeImage]){
|
||||
[viewController.navigationItem setTitle:NSLocalizedString(@"Videos title", nil)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cleanup:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
// empty the tmp directory
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
NSError* err = nil;
|
||||
BOOL hasErrors = NO;
|
||||
|
||||
// clear contents of NSTemporaryDirectory
|
||||
NSString* tempDirectoryPath = NSTemporaryDirectory();
|
||||
NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath];
|
||||
NSString* fileName = nil;
|
||||
BOOL result;
|
||||
|
||||
while ((fileName = [directoryEnumerator nextObject])) {
|
||||
// only delete the files we created
|
||||
if (![fileName hasPrefix:CDV_PHOTO_PREFIX]) {
|
||||
continue;
|
||||
}
|
||||
NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
|
||||
result = [fileMgr removeItemAtPath:filePath error:&err];
|
||||
if (!result && err) {
|
||||
NSLog(@"Failed to delete: %@ (error: %@)", filePath, err);
|
||||
hasErrors = YES;
|
||||
}
|
||||
}
|
||||
|
||||
CDVPluginResult* pluginResult;
|
||||
if (hasErrors) {
|
||||
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"One or more files failed to be deleted."];
|
||||
} else {
|
||||
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
||||
}
|
||||
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
- (void)popoverControllerDidDismissPopover:(id)popoverController
|
||||
{
|
||||
// [ self imagePickerControllerDidCancel:self.pickerController ]; '
|
||||
UIPopoverController* pc = (UIPopoverController*)popoverController;
|
||||
|
||||
[pc dismissPopoverAnimated:YES];
|
||||
pc.delegate = nil;
|
||||
if (self.pickerController && self.pickerController.callbackId && self.pickerController.popoverController) {
|
||||
self.pickerController.popoverController = nil;
|
||||
NSString* callbackId = self.pickerController.callbackId;
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
|
||||
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
}
|
||||
self.hasPendingOperation = NO;
|
||||
}
|
||||
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
|
||||
{
|
||||
CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
|
||||
|
||||
if (cameraPicker.popoverSupported && (cameraPicker.popoverController != nil)) {
|
||||
[cameraPicker.popoverController dismissPopoverAnimated:YES];
|
||||
cameraPicker.popoverController.delegate = nil;
|
||||
cameraPicker.popoverController = nil;
|
||||
} else {
|
||||
if ([cameraPicker respondsToSelector:@selector(presentingViewController)]) {
|
||||
[[cameraPicker presentingViewController] dismissModalViewControllerAnimated:YES];
|
||||
} else {
|
||||
[[cameraPicker parentViewController] dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
CDVPluginResult* result = nil;
|
||||
|
||||
NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
|
||||
// IMAGE TYPE
|
||||
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
|
||||
if (cameraPicker.returnType == DestinationTypeNativeUri) {
|
||||
NSString* nativeUri = [(NSURL*)[info objectForKey:UIImagePickerControllerReferenceURL] absoluteString];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri];
|
||||
} else {
|
||||
// get the image
|
||||
UIImage* image = nil;
|
||||
if (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) {
|
||||
image = [info objectForKey:UIImagePickerControllerEditedImage];
|
||||
} else {
|
||||
image = [info objectForKey:UIImagePickerControllerOriginalImage];
|
||||
}
|
||||
|
||||
if (cameraPicker.correctOrientation) {
|
||||
image = [self imageCorrectedForCaptureOrientation:image];
|
||||
}
|
||||
|
||||
UIImage* scaledImage = nil;
|
||||
|
||||
if ((cameraPicker.targetSize.width > 0) && (cameraPicker.targetSize.height > 0)) {
|
||||
// if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping
|
||||
if (cameraPicker.cropToSize) {
|
||||
scaledImage = [self imageByScalingAndCroppingForSize:image toSize:cameraPicker.targetSize];
|
||||
} else {
|
||||
scaledImage = [self imageByScalingNotCroppingForSize:image toSize:cameraPicker.targetSize];
|
||||
}
|
||||
}
|
||||
|
||||
NSData* data = nil;
|
||||
// returnedImage is the image that is returned to caller and (optionally) saved to photo album
|
||||
UIImage* returnedImage = (scaledImage == nil ? image : scaledImage);
|
||||
|
||||
if (cameraPicker.encodingType == EncodingTypePNG) {
|
||||
data = UIImagePNGRepresentation(returnedImage);
|
||||
} else if ((cameraPicker.allowsEditing==false) && (cameraPicker.targetSize.width <= 0) && (cameraPicker.targetSize.height <= 0) && (cameraPicker.correctOrientation==false)){
|
||||
// use image unedited as requested , don't resize
|
||||
data = UIImageJPEGRepresentation(returnedImage, 1.0);
|
||||
} else {
|
||||
data = UIImageJPEGRepresentation(returnedImage, cameraPicker.quality / 100.0f);
|
||||
|
||||
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];
|
||||
|
||||
[[self locationManager] startUpdatingLocation];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cameraPicker.saveToPhotoAlbum) {
|
||||
ALAssetsLibrary *library = [ALAssetsLibrary new];
|
||||
[library writeImageToSavedPhotosAlbum:returnedImage.CGImage orientation:(ALAssetOrientation)(returnedImage.imageOrientation) completionBlock:nil];
|
||||
}
|
||||
|
||||
if (cameraPicker.returnType == DestinationTypeFileUri) {
|
||||
// write to temp directory and return URI
|
||||
// get the temp directory path
|
||||
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
|
||||
NSError* err = nil;
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe
|
||||
// generate unique file name
|
||||
NSString* filePath;
|
||||
|
||||
int i = 1;
|
||||
do {
|
||||
filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, cameraPicker.encodingType == EncodingTypePNG ? @"png":@"jpg"];
|
||||
} while ([fileMgr fileExistsAtPath:filePath]);
|
||||
|
||||
// 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:[[NSURL fileURLWithPath:filePath] absoluteString]];
|
||||
}
|
||||
} else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[data base64EncodedString]];
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOT IMAGE TYPE (MOVIE)
|
||||
else {
|
||||
NSString* moviePath = [[info objectForKey:UIImagePickerControllerMediaURL] absoluteString];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
|
||||
}
|
||||
|
||||
if (result) {
|
||||
[self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
|
||||
}
|
||||
|
||||
self.hasPendingOperation = NO;
|
||||
self.pickerController = nil;
|
||||
}
|
||||
|
||||
// older api calls newer didFinishPickingMediaWithInfo
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo
|
||||
{
|
||||
NSDictionary* imageInfo = [NSDictionary dictionaryWithObject:image forKey:UIImagePickerControllerOriginalImage];
|
||||
|
||||
[self imagePickerController:picker didFinishPickingMediaWithInfo:imageInfo];
|
||||
}
|
||||
|
||||
- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker
|
||||
{
|
||||
CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
|
||||
|
||||
if ([cameraPicker respondsToSelector:@selector(presentingViewController)]) {
|
||||
[[cameraPicker presentingViewController] dismissModalViewControllerAnimated:YES];
|
||||
} else {
|
||||
[[cameraPicker parentViewController] dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
// popoverControllerDidDismissPopover:(id)popoverController is called if popover is cancelled
|
||||
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
|
||||
[self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId];
|
||||
|
||||
self.hasPendingOperation = NO;
|
||||
self.pickerController = nil;
|
||||
}
|
||||
|
||||
- (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize
|
||||
{
|
||||
UIImage* sourceImage = anImage;
|
||||
UIImage* newImage = nil;
|
||||
CGSize imageSize = sourceImage.size;
|
||||
CGFloat width = imageSize.width;
|
||||
CGFloat height = imageSize.height;
|
||||
CGFloat targetWidth = targetSize.width;
|
||||
CGFloat targetHeight = targetSize.height;
|
||||
CGFloat scaleFactor = 0.0;
|
||||
CGFloat scaledWidth = targetWidth;
|
||||
CGFloat scaledHeight = targetHeight;
|
||||
CGPoint thumbnailPoint = CGPointMake(0.0, 0.0);
|
||||
|
||||
if (CGSizeEqualToSize(imageSize, targetSize) == NO) {
|
||||
CGFloat widthFactor = targetWidth / width;
|
||||
CGFloat heightFactor = targetHeight / height;
|
||||
|
||||
if (widthFactor > heightFactor) {
|
||||
scaleFactor = widthFactor; // scale to fit height
|
||||
} else {
|
||||
scaleFactor = heightFactor; // scale to fit width
|
||||
}
|
||||
scaledWidth = width * scaleFactor;
|
||||
scaledHeight = height * scaleFactor;
|
||||
|
||||
// center the image
|
||||
if (widthFactor > heightFactor) {
|
||||
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
|
||||
} else if (widthFactor < heightFactor) {
|
||||
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
UIGraphicsBeginImageContext(targetSize); // this will crop
|
||||
|
||||
CGRect thumbnailRect = CGRectZero;
|
||||
thumbnailRect.origin = thumbnailPoint;
|
||||
thumbnailRect.size.width = scaledWidth;
|
||||
thumbnailRect.size.height = scaledHeight;
|
||||
|
||||
[sourceImage drawInRect:thumbnailRect];
|
||||
|
||||
newImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
if (newImage == nil) {
|
||||
NSLog(@"could not scale image");
|
||||
}
|
||||
|
||||
// pop the context to get back to the default
|
||||
UIGraphicsEndImageContext();
|
||||
return newImage;
|
||||
}
|
||||
|
||||
- (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage
|
||||
{
|
||||
float rotation_radians = 0;
|
||||
bool perpendicular = false;
|
||||
|
||||
switch ([anImage imageOrientation]) {
|
||||
case UIImageOrientationUp :
|
||||
rotation_radians = 0.0;
|
||||
break;
|
||||
|
||||
case UIImageOrientationDown:
|
||||
rotation_radians = M_PI; // don't be scared of radians, if you're reading this, you're good at math
|
||||
break;
|
||||
|
||||
case UIImageOrientationRight:
|
||||
rotation_radians = M_PI_2;
|
||||
perpendicular = true;
|
||||
break;
|
||||
|
||||
case UIImageOrientationLeft:
|
||||
rotation_radians = -M_PI_2;
|
||||
perpendicular = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
UIGraphicsBeginImageContext(CGSizeMake(anImage.size.width, anImage.size.height));
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
// Rotate around the center point
|
||||
CGContextTranslateCTM(context, anImage.size.width / 2, anImage.size.height / 2);
|
||||
CGContextRotateCTM(context, rotation_radians);
|
||||
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
float width = perpendicular ? anImage.size.height : anImage.size.width;
|
||||
float height = perpendicular ? anImage.size.width : anImage.size.height;
|
||||
CGContextDrawImage(context, CGRectMake(-width / 2, -height / 2, width, height), [anImage CGImage]);
|
||||
|
||||
// Move the origin back since the rotation might've change it (if its 90 degrees)
|
||||
if (perpendicular) {
|
||||
CGContextTranslateCTM(context, -anImage.size.height / 2, -anImage.size.width / 2);
|
||||
}
|
||||
|
||||
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return newImage;
|
||||
}
|
||||
|
||||
- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize
|
||||
{
|
||||
UIImage* sourceImage = anImage;
|
||||
UIImage* newImage = nil;
|
||||
CGSize imageSize = sourceImage.size;
|
||||
CGFloat width = imageSize.width;
|
||||
CGFloat height = imageSize.height;
|
||||
CGFloat targetWidth = frameSize.width;
|
||||
CGFloat targetHeight = frameSize.height;
|
||||
CGFloat scaleFactor = 0.0;
|
||||
CGSize scaledSize = frameSize;
|
||||
|
||||
if (CGSizeEqualToSize(imageSize, frameSize) == NO) {
|
||||
CGFloat widthFactor = targetWidth / width;
|
||||
CGFloat heightFactor = targetHeight / height;
|
||||
|
||||
// opposite comparison to imageByScalingAndCroppingForSize in order to contain the image within the given bounds
|
||||
if (widthFactor > heightFactor) {
|
||||
scaleFactor = heightFactor; // scale to fit height
|
||||
} else {
|
||||
scaleFactor = widthFactor; // scale to fit width
|
||||
}
|
||||
scaledSize = CGSizeMake(MIN(width * scaleFactor, targetWidth), MIN(height * scaleFactor, targetHeight));
|
||||
}
|
||||
|
||||
UIGraphicsBeginImageContext(scaledSize); // this will resize
|
||||
|
||||
[sourceImage drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
|
||||
|
||||
newImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
if (newImage == nil) {
|
||||
NSLog(@"could not scale image");
|
||||
}
|
||||
|
||||
// pop the context to get back to the default
|
||||
UIGraphicsEndImageContext();
|
||||
return newImage;
|
||||
}
|
||||
|
||||
- (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url
|
||||
{
|
||||
self.hasPendingOperation = YES;
|
||||
|
||||
NSString* boundary = @"----BOUNDARY_IS_I";
|
||||
|
||||
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
|
||||
NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
|
||||
[req setValue:contentType forHTTPHeaderField:@"Content-type"];
|
||||
|
||||
NSData* imageData = UIImagePNGRepresentation(anImage);
|
||||
|
||||
// adding the body
|
||||
NSMutableData* postBody = [NSMutableData data];
|
||||
|
||||
// first parameter an image
|
||||
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"upload\"; filename=\"%@\"\r\n", filename] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[postBody appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding : NSUTF8StringEncoding]];
|
||||
[postBody appendData:imageData];
|
||||
|
||||
// // second parameter information
|
||||
// [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
// [postBody appendData:[@"Content-Disposition: form-data; name=\"some_other_name\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
// [postBody appendData:[@"some_other_value" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
// [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r \n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
[req setHTTPBody:postBody];
|
||||
|
||||
NSURLResponse* response;
|
||||
NSError* error;
|
||||
[NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
|
||||
|
||||
// NSData* result = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
|
||||
// NSString * resultStr = [[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding] autorelease];
|
||||
|
||||
self.hasPendingOperation = NO;
|
||||
}
|
||||
|
||||
|
||||
- (CLLocationManager *)locationManager {
|
||||
|
||||
if (locationManager != nil) {
|
||||
return locationManager;
|
||||
}
|
||||
|
||||
locationManager = [[CLLocationManager alloc] init];
|
||||
[locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];
|
||||
[locationManager setDelegate:self];
|
||||
|
||||
return locationManager;
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
|
||||
{
|
||||
if (locationManager != nil) {
|
||||
[self.locationManager stopUpdatingLocation];
|
||||
self.locationManager = nil;
|
||||
|
||||
NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init];
|
||||
|
||||
CLLocationDegrees latitude = newLocation.coordinate.latitude;
|
||||
CLLocationDegrees longitude = newLocation.coordinate.longitude;
|
||||
|
||||
// latitude
|
||||
if (latitude < 0.0) {
|
||||
latitude = latitude * -1.0f;
|
||||
[GPSDictionary setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
|
||||
} else {
|
||||
[GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
|
||||
}
|
||||
[GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
|
||||
|
||||
// longitude
|
||||
if (longitude < 0.0) {
|
||||
longitude = longitude * -1.0f;
|
||||
[GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
|
||||
}
|
||||
else {
|
||||
[GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
|
||||
}
|
||||
[GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];
|
||||
|
||||
// altitude
|
||||
CGFloat altitude = newLocation.altitude;
|
||||
if (!isnan(altitude)){
|
||||
if (altitude < 0) {
|
||||
altitude = -altitude;
|
||||
[GPSDictionary setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
|
||||
} else {
|
||||
[GPSDictionary setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
|
||||
}
|
||||
[GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
|
||||
}
|
||||
|
||||
// Time and date
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
[formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
|
||||
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
|
||||
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
|
||||
[formatter setDateFormat:@"yyyy:MM:dd"];
|
||||
[GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
|
||||
|
||||
[self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
|
||||
[self imagePickerControllerReturnImageResult];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
|
||||
if (locationManager != nil) {
|
||||
[self.locationManager stopUpdatingLocation];
|
||||
self.locationManager = nil;
|
||||
|
||||
[self imagePickerControllerReturnImageResult];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)imagePickerControllerReturnImageResult
|
||||
{
|
||||
CDVPluginResult* result = nil;
|
||||
|
||||
if (self.metadata) {
|
||||
CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge_retained CFDataRef)self.data, NULL);
|
||||
CFStringRef sourceType = CGImageSourceGetType(sourceImage);
|
||||
|
||||
CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL);
|
||||
CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata);
|
||||
CGImageDestinationFinalize(destinationImage);
|
||||
|
||||
CFRelease(sourceImage);
|
||||
CFRelease(destinationImage);
|
||||
}
|
||||
|
||||
if (self.pickerController.saveToPhotoAlbum) {
|
||||
ALAssetsLibrary *library = [ALAssetsLibrary new];
|
||||
[library writeImageDataToSavedPhotosAlbum:self.data metadata:self.metadata completionBlock:nil];
|
||||
}
|
||||
|
||||
if (self.pickerController.returnType == DestinationTypeFileUri) {
|
||||
// write to temp directory and return URI
|
||||
// get the temp directory path
|
||||
NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
|
||||
NSError* err = nil;
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe
|
||||
// generate unique file name
|
||||
NSString* filePath;
|
||||
|
||||
int i = 1;
|
||||
do {
|
||||
filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, self.pickerController.encodingType == EncodingTypePNG ? @"png":@"jpg"];
|
||||
} while ([fileMgr fileExistsAtPath:filePath]);
|
||||
|
||||
// save file
|
||||
if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
|
||||
}
|
||||
else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[self.data base64EncodedString]];
|
||||
}
|
||||
if (result) {
|
||||
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
|
||||
}
|
||||
|
||||
if (result) {
|
||||
[self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId];
|
||||
}
|
||||
|
||||
self.hasPendingOperation = NO;
|
||||
self.pickerController = nil;
|
||||
self.data = nil;
|
||||
self.metadata = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CDVCameraPicker
|
||||
|
||||
@synthesize quality, postUrl;
|
||||
@synthesize returnType;
|
||||
@synthesize callbackId;
|
||||
@synthesize popoverController;
|
||||
@synthesize targetSize;
|
||||
@synthesize correctOrientation;
|
||||
@synthesize saveToPhotoAlbum;
|
||||
@synthesize encodingType;
|
||||
@synthesize cropToSize;
|
||||
@synthesize webView;
|
||||
@synthesize popoverSupported;
|
||||
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UIViewController*)childViewControllerForStatusBarHidden {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
|
||||
if ([self respondsToSelector:sel]) {
|
||||
[self performSelector:sel withObject:nil afterDelay:0];
|
||||
}
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +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.
|
||||
*/
|
||||
|
||||
#ifndef CordovaLib_ExifData_h
|
||||
#define CordovaLib_ExifData_h
|
||||
|
||||
// exif data types
|
||||
typedef enum exifDataTypes {
|
||||
EDT_UBYTE = 1, // 8 bit unsigned integer
|
||||
EDT_ASCII_STRING, // 8 bits containing 7 bit ASCII code, null terminated
|
||||
EDT_USHORT, // 16 bit unsigned integer
|
||||
EDT_ULONG, // 32 bit unsigned integer
|
||||
EDT_URATIONAL, // 2 longs, first is numerator and second is denominator
|
||||
EDT_SBYTE,
|
||||
EDT_UNDEFINED, // 8 bits
|
||||
EDT_SSHORT,
|
||||
EDT_SLONG, // 32bit signed integer (2's complement)
|
||||
EDT_SRATIONAL, // 2 SLONGS, first long is numerator, second is denominator
|
||||
EDT_SINGLEFLOAT,
|
||||
EDT_DOUBLEFLOAT
|
||||
} ExifDataTypes;
|
||||
|
||||
// maps integer code for exif data types to width in bytes
|
||||
static const int DataTypeToWidth[] = {1,1,2,4,8,1,1,2,4,8,4,8};
|
||||
|
||||
static const int RECURSE_HORIZON = 8;
|
||||
#endif
|
||||
@@ -1,62 +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 <Foundation/Foundation.h>
|
||||
|
||||
@interface CDVJpegHeaderWriter : NSObject {
|
||||
NSDictionary * SubIFDTagFormatDict;
|
||||
NSDictionary * IFD0TagFormatDict;
|
||||
}
|
||||
|
||||
- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata
|
||||
withExifBlock: (NSString*) exifstr;
|
||||
- (NSString*) createExifAPP1 : (NSDictionary*) datadict;
|
||||
- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb
|
||||
withPlaces: (NSNumber*) width;
|
||||
- (NSString*) formatNumberWithLeadingZeroes: (NSNumber*) numb
|
||||
withPlaces: (NSNumber*) places;
|
||||
- (NSString*) decimalToUnsignedRational: (NSNumber*) numb
|
||||
withResultNumerator: (NSNumber**) numerator
|
||||
withResultDenominator: (NSNumber**) denominator;
|
||||
- (void) continuedFraction: (double) val
|
||||
withFractionList: (NSMutableArray*) fractionlist
|
||||
withHorizon: (int) horizon;
|
||||
//- (void) expandContinuedFraction: (NSArray*) fractionlist;
|
||||
- (void) splitDouble: (double) val
|
||||
withIntComponent: (int*) rightside
|
||||
withFloatRemainder: (double*) leftside;
|
||||
- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator
|
||||
withDenominator: (NSNumber*) denominator
|
||||
asSigned: (Boolean) signedFlag;
|
||||
- (NSString*) hexStringFromData : (NSData*) data;
|
||||
- (NSNumber*) numericFromHexString : (NSString *) hexstring;
|
||||
|
||||
/*
|
||||
- (void) readExifMetaData : (NSData*) imgdata;
|
||||
- (void) spliceImageData : (NSData*) imgdata withExifData: (NSDictionary*) exifdata;
|
||||
- (void) locateExifMetaData : (NSData*) imgdata;
|
||||
- (NSString*) createExifAPP1 : (NSDictionary*) datadict;
|
||||
- (void) createExifDataString : (NSDictionary*) datadict;
|
||||
- (NSString*) createDataElement : (NSString*) element
|
||||
withElementData: (NSString*) data
|
||||
withExternalDataBlock: (NSDictionary*) memblock;
|
||||
- (NSString*) hexStringFromData : (NSData*) data;
|
||||
- (NSNumber*) numericFromHexString : (NSString *) hexstring;
|
||||
*/
|
||||
@end
|
||||
@@ -1,547 +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 "CDVJpegHeaderWriter.h"
|
||||
#include "CDVExif.h"
|
||||
|
||||
/* macros for tag info shorthand:
|
||||
tagno : tag number
|
||||
typecode : data type
|
||||
components : number of components
|
||||
appendString (TAGINF_W_APPEND only) : string to append to data
|
||||
Exif date data format include an extra 0x00 to the end of the data
|
||||
*/
|
||||
#define TAGINF(tagno, typecode, components) [NSArray arrayWithObjects: tagno, typecode, components, nil]
|
||||
#define TAGINF_W_APPEND(tagno, typecode, components, appendString) [NSArray arrayWithObjects: tagno, typecode, components, appendString, nil]
|
||||
|
||||
const uint mJpegId = 0xffd8; // JPEG format marker
|
||||
const uint mExifMarker = 0xffe1; // APP1 jpeg header marker
|
||||
const uint mExif = 0x45786966; // ASCII 'Exif', first characters of valid exif header after size
|
||||
const uint mMotorallaByteAlign = 0x4d4d; // 'MM', motorola byte align, msb first or 'sane'
|
||||
const uint mIntelByteAlgin = 0x4949; // 'II', Intel byte align, lsb first or 'batshit crazy reverso world'
|
||||
const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a(MM) or 0x2a00(II), tiff version number
|
||||
|
||||
|
||||
@implementation CDVJpegHeaderWriter
|
||||
|
||||
- (id) init {
|
||||
self = [super init];
|
||||
// supported tags for exif IFD
|
||||
IFD0TagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys:
|
||||
// TAGINF(@"010e", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"ImageDescription",
|
||||
TAGINF_W_APPEND(@"0132", [NSNumber numberWithInt:EDT_ASCII_STRING], @20, @"00"), @"DateTime",
|
||||
TAGINF(@"010f", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Make",
|
||||
TAGINF(@"0110", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Model",
|
||||
TAGINF(@"0131", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Software",
|
||||
TAGINF(@"011a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"XResolution",
|
||||
TAGINF(@"011b", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"YResolution",
|
||||
// currently supplied outside of Exif data block by UIImagePickerControllerMediaMetadata, this is set manually in CDVCamera.m
|
||||
/* TAGINF(@"0112", [NSNumber numberWithInt:EDT_USHORT], @1), @"Orientation",
|
||||
|
||||
// rest of the tags are supported by exif spec, but are not specified by UIImagePickerControllerMediaMedadata
|
||||
// should camera hardware supply these values in future versions, or if they can be derived, ImageHeaderWriter will include them gracefully
|
||||
TAGINF(@"0128", [NSNumber numberWithInt:EDT_USHORT], @1), @"ResolutionUnit",
|
||||
TAGINF(@"013e", [NSNumber numberWithInt:EDT_URATIONAL], @2), @"WhitePoint",
|
||||
TAGINF(@"013f", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"PrimaryChromaticities",
|
||||
TAGINF(@"0211", [NSNumber numberWithInt:EDT_URATIONAL], @3), @"YCbCrCoefficients",
|
||||
TAGINF(@"0213", [NSNumber numberWithInt:EDT_USHORT], @1), @"YCbCrPositioning",
|
||||
TAGINF(@"0214", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"ReferenceBlackWhite",
|
||||
TAGINF(@"8298", [NSNumber numberWithInt:EDT_URATIONAL], @0), @"Copyright",
|
||||
|
||||
// offset to exif subifd, we determine this dynamically based on the size of the main exif IFD
|
||||
TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",*/
|
||||
nil];
|
||||
|
||||
|
||||
// supported tages for exif subIFD
|
||||
SubIFDTagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys:
|
||||
//TAGINF(@"9000", [NSNumber numberWithInt:], @), @"ExifVersion",
|
||||
//TAGINF(@"9202",[NSNumber numberWithInt:EDT_URATIONAL],@1), @"ApertureValue",
|
||||
//TAGINF(@"9203",[NSNumber numberWithInt:EDT_SRATIONAL],@1), @"BrightnessValue",
|
||||
TAGINF(@"a001",[NSNumber numberWithInt:EDT_USHORT],@1), @"ColorSpace",
|
||||
TAGINF_W_APPEND(@"9004",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeDigitized",
|
||||
TAGINF_W_APPEND(@"9003",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeOriginal",
|
||||
TAGINF(@"a402", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureMode",
|
||||
TAGINF(@"8822", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureProgram",
|
||||
//TAGINF(@"829a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"ExposureTime",
|
||||
//TAGINF(@"829d", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FNumber",
|
||||
TAGINF(@"9209", [NSNumber numberWithInt:EDT_USHORT], @1), @"Flash",
|
||||
// FocalLengthIn35mmFilm
|
||||
TAGINF(@"a405", [NSNumber numberWithInt:EDT_USHORT], @1), @"FocalLenIn35mmFilm",
|
||||
//TAGINF(@"920a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FocalLength",
|
||||
//TAGINF(@"8827", [NSNumber numberWithInt:EDT_USHORT], @2), @"ISOSpeedRatings",
|
||||
TAGINF(@"9207", [NSNumber numberWithInt:EDT_USHORT],@1), @"MeteringMode",
|
||||
// specific to compressed data
|
||||
TAGINF(@"a002", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelXDimension",
|
||||
TAGINF(@"a003", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelYDimension",
|
||||
// data type undefined, but this is a DSC camera, so value is always 1, treat as ushort
|
||||
TAGINF(@"a301", [NSNumber numberWithInt:EDT_USHORT],@1), @"SceneType",
|
||||
TAGINF(@"a217",[NSNumber numberWithInt:EDT_USHORT],@1), @"SensingMethod",
|
||||
//TAGINF(@"9201", [NSNumber numberWithInt:EDT_SRATIONAL], @1), @"ShutterSpeedValue",
|
||||
// specifies location of main subject in scene (x,y,wdith,height) expressed before rotation processing
|
||||
//TAGINF(@"9214", [NSNumber numberWithInt:EDT_USHORT], @4), @"SubjectArea",
|
||||
TAGINF(@"a403", [NSNumber numberWithInt:EDT_USHORT], @1), @"WhiteBalance",
|
||||
nil];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata withExifBlock: (NSString*) exifstr {
|
||||
|
||||
CDVJpegHeaderWriter * exifWriter = [[CDVJpegHeaderWriter alloc] init];
|
||||
|
||||
NSMutableData * exifdata = [NSMutableData dataWithCapacity: [exifstr length]/2];
|
||||
int idx;
|
||||
for (idx = 0; idx+1 < [exifstr length]; idx+=2) {
|
||||
NSRange range = NSMakeRange(idx, 2);
|
||||
NSString* hexStr = [exifstr substringWithRange:range];
|
||||
NSScanner* scanner = [NSScanner scannerWithString:hexStr];
|
||||
unsigned int intValue;
|
||||
[scanner scanHexInt:&intValue];
|
||||
[exifdata appendBytes:&intValue length:1];
|
||||
}
|
||||
|
||||
NSMutableData * ddata = [NSMutableData dataWithCapacity: [jpegdata length]];
|
||||
NSMakeRange(0,4);
|
||||
int loc = 0;
|
||||
bool done = false;
|
||||
// read the jpeg data until we encounter the app1==0xFFE1 marker
|
||||
while (loc+1 < [jpegdata length]) {
|
||||
NSData * blag = [jpegdata subdataWithRange: NSMakeRange(loc,2)];
|
||||
if( [[blag description] isEqualToString : @"<ffe1>"]) {
|
||||
// read the APP1 block size bits
|
||||
NSString * the = [exifWriter hexStringFromData:[jpegdata subdataWithRange: NSMakeRange(loc+2,2)]];
|
||||
NSNumber * app1width = [exifWriter numericFromHexString:the];
|
||||
//consume the original app1 block
|
||||
[ddata appendData:exifdata];
|
||||
// advance our loc marker past app1
|
||||
loc += [app1width intValue] + 2;
|
||||
done = true;
|
||||
} else {
|
||||
if(!done) {
|
||||
[ddata appendData:blag];
|
||||
loc += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// copy the remaining data
|
||||
[ddata appendData:[jpegdata subdataWithRange: NSMakeRange(loc,[jpegdata length]-loc)]];
|
||||
return ddata;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create the Exif data block as a hex string
|
||||
* jpeg uses Application Markers (APP's) as markers for application data
|
||||
* APP1 is the application marker reserved for exif data
|
||||
*
|
||||
* (NSDictionary*) datadict - with subdictionaries marked '{TIFF}' and '{EXIF}' as returned by imagePickerController with a valid
|
||||
* didFinishPickingMediaWithInfo data dict, under key @"UIImagePickerControllerMediaMetadata"
|
||||
*
|
||||
* the following constructs a hex string to Exif specifications, and is therefore brittle
|
||||
* altering the order of arguments to the string constructors, modifying field sizes or formats,
|
||||
* and any other minor change will likely prevent the exif data from being read
|
||||
*/
|
||||
- (NSString*) createExifAPP1 : (NSDictionary*) datadict {
|
||||
NSMutableString * app1; // holds finalized product
|
||||
NSString * exifIFD; // exif information file directory
|
||||
NSString * subExifIFD; // subexif information file directory
|
||||
|
||||
// FFE1 is the hex APP1 marker code, and will allow client apps to read the data
|
||||
NSString * app1marker = @"ffe1";
|
||||
// SSSS size, to be determined
|
||||
// EXIF ascii characters followed by 2bytes of zeros
|
||||
NSString * exifmarker = @"457869660000";
|
||||
// Tiff header: 4d4d is motorolla byte align (big endian), 002a is hex for 42
|
||||
NSString * tiffheader = @"4d4d002a";
|
||||
//first IFD offset from the Tiff header to IFD0. Since we are writing it, we know it's address 0x08
|
||||
NSString * ifd0offset = @"00000008";
|
||||
// current offset to next data area
|
||||
int currentDataOffset = 0;
|
||||
|
||||
//data labeled as TIFF in UIImagePickerControllerMediaMetaData is part of the EXIF IFD0 portion of APP1
|
||||
exifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{TIFF}"] withFormatDict: IFD0TagFormatDict isIFD0:YES currentDataOffset:¤tDataOffset];
|
||||
|
||||
//data labeled as EXIF in UIImagePickerControllerMediaMetaData is part of the EXIF Sub IFD portion of APP1
|
||||
subExifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{Exif}"] withFormatDict: SubIFDTagFormatDict isIFD0:NO currentDataOffset:¤tDataOffset];
|
||||
/*
|
||||
NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",exifIFD,[exifIFD length]);
|
||||
|
||||
NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",subExifIFD,[subExifIFD length]);
|
||||
*/
|
||||
// construct the complete app1 data block
|
||||
app1 = [[NSMutableString alloc] initWithFormat: @"%@%04x%@%@%@%@%@",
|
||||
app1marker,
|
||||
16 + ([exifIFD length]/2) + ([subExifIFD length]/2) /*16+[exifIFD length]/2*/,
|
||||
exifmarker,
|
||||
tiffheader,
|
||||
ifd0offset,
|
||||
exifIFD,
|
||||
subExifIFD];
|
||||
|
||||
return app1;
|
||||
}
|
||||
|
||||
// returns hex string representing a valid exif information file directory constructed from the datadict and formatdict
|
||||
- (NSString*) createExifIFDFromDict : (NSDictionary*) datadict
|
||||
withFormatDict : (NSDictionary*) formatdict
|
||||
isIFD0 : (BOOL) ifd0flag
|
||||
currentDataOffset : (int*) dataoffset {
|
||||
NSArray * datakeys = [datadict allKeys]; // all known data keys
|
||||
NSArray * knownkeys = [formatdict allKeys]; // only keys in knowkeys are considered for entry in this IFD
|
||||
NSMutableArray * ifdblock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // all ifd entries
|
||||
NSMutableArray * ifddatablock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // data block entries
|
||||
// ifd0flag = NO; // ifd0 requires a special flag and has offset to next ifd appended to end
|
||||
|
||||
// iterate through known provided data keys
|
||||
for (int i = 0; i < [datakeys count]; i++) {
|
||||
NSString * key = [datakeys objectAtIndex:i];
|
||||
// don't muck about with unknown keys
|
||||
if ([knownkeys indexOfObject: key] != NSNotFound) {
|
||||
// create new IFD entry
|
||||
NSString * entry = [self createIFDElement: key
|
||||
withFormat: [formatdict objectForKey:key]
|
||||
withElementData: [datadict objectForKey:key]];
|
||||
// create the IFD entry's data block
|
||||
NSString * data = [self createIFDElementDataWithFormat: [formatdict objectForKey:key]
|
||||
withData: [datadict objectForKey:key]];
|
||||
if (entry) {
|
||||
[ifdblock addObject:entry];
|
||||
if(!data) {
|
||||
[ifdblock addObject:@""];
|
||||
} else {
|
||||
[ifddatablock addObject:data];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableString * exifstr = [[NSMutableString alloc] initWithCapacity: [ifdblock count] * 24];
|
||||
NSMutableString * dbstr = [[NSMutableString alloc] initWithCapacity: 100];
|
||||
|
||||
int addr=*dataoffset; // current offset/address in datablock
|
||||
if (ifd0flag) {
|
||||
// calculate offset to datablock based on ifd file entry count
|
||||
addr += 14+(12*([ifddatablock count]+1)); // +1 for tag 0x8769, exifsubifd offset
|
||||
} else {
|
||||
// current offset + numSubIFDs (2-bytes) + 12*numSubIFDs + endMarker (4-bytes)
|
||||
addr += 2+(12*[ifddatablock count])+4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < [ifdblock count]; i++) {
|
||||
NSString * entry = [ifdblock objectAtIndex:i];
|
||||
NSString * data = [ifddatablock objectAtIndex:i];
|
||||
|
||||
// check if the data fits into 4 bytes
|
||||
if( [data length] <= 8) {
|
||||
// concatenate the entry and the (4byte) data entry into the final IFD entry and append to exif ifd string
|
||||
[exifstr appendFormat : @"%@%@", entry, data];
|
||||
} else {
|
||||
[exifstr appendFormat : @"%@%08x", entry, addr];
|
||||
[dbstr appendFormat: @"%@", data];
|
||||
addr+= [data length] / 2;
|
||||
/*
|
||||
NSLog(@"=====data-length[%i]=======",[data length]);
|
||||
NSLog(@"addr-offset[%i]",addr);
|
||||
NSLog(@"entry[%@]",entry);
|
||||
NSLog(@"data[%@]",data);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// calculate IFD0 terminal offset tags, currently ExifSubIFD
|
||||
int entrycount = [ifdblock count];
|
||||
if (ifd0flag) {
|
||||
// 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header
|
||||
NSNumber * offset = [NSNumber numberWithInt:[exifstr length] / 2 + [dbstr length] / 2 + 18+8];
|
||||
|
||||
[self appendExifOffsetTagTo: exifstr
|
||||
withOffset : offset];
|
||||
entrycount++;
|
||||
}
|
||||
*dataoffset = addr;
|
||||
return [[NSString alloc] initWithFormat: @"%04x%@%@%@",
|
||||
entrycount,
|
||||
exifstr,
|
||||
@"00000000", // offset to next IFD, 0 since there is none
|
||||
dbstr]; // lastly, the datablock
|
||||
}
|
||||
|
||||
// Creates an exif formatted exif information file directory entry
|
||||
- (NSString*) createIFDElement: (NSString*) elementName withFormat: (NSArray*) formtemplate withElementData: (NSString*) data {
|
||||
//NSArray * fielddata = [formatdict objectForKey: elementName];// format data of desired field
|
||||
if (formtemplate) {
|
||||
// format string @"%@%@%@%@", tag number, data format, components, value
|
||||
NSNumber * dataformat = [formtemplate objectAtIndex:1];
|
||||
NSNumber * components = [formtemplate objectAtIndex:2];
|
||||
if([components intValue] == 0) {
|
||||
components = [NSNumber numberWithInt: [data length] * DataTypeToWidth[[dataformat intValue]-1]];
|
||||
}
|
||||
|
||||
return [[NSString alloc] initWithFormat: @"%@%@%08x",
|
||||
[formtemplate objectAtIndex:0], // the field code
|
||||
[self formatNumberWithLeadingZeroes: dataformat withPlaces: @4], // the data type code
|
||||
[components intValue]]; // number of components
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* appends exif IFD0 tag 8769 "ExifOffset" to the string provided
|
||||
* (NSMutableString*) str - string you wish to append the 8769 tag to: APP1 or IFD0 hex data string
|
||||
* // TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",
|
||||
*/
|
||||
- (void) appendExifOffsetTagTo: (NSMutableString*) str withOffset : (NSNumber*) offset {
|
||||
NSArray * format = TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1);
|
||||
|
||||
NSString * entry = [self createIFDElement: @"ExifOffset"
|
||||
withFormat: format
|
||||
withElementData: [offset stringValue]];
|
||||
|
||||
NSString * data = [self createIFDElementDataWithFormat: format
|
||||
withData: [offset stringValue]];
|
||||
[str appendFormat:@"%@%@", entry, data];
|
||||
}
|
||||
|
||||
// formats the Information File Directory Data to exif format
|
||||
- (NSString*) createIFDElementDataWithFormat: (NSArray*) dataformat withData: (NSString*) data {
|
||||
NSMutableString * datastr = nil;
|
||||
NSNumber * tmp = nil;
|
||||
NSNumber * formatcode = [dataformat objectAtIndex:1];
|
||||
NSUInteger formatItemsCount = [dataformat count];
|
||||
NSNumber * num = @0;
|
||||
NSNumber * denom = @0;
|
||||
|
||||
switch ([formatcode intValue]) {
|
||||
case EDT_UBYTE:
|
||||
break;
|
||||
case EDT_ASCII_STRING:
|
||||
datastr = [[NSMutableString alloc] init];
|
||||
for (int i = 0; i < [data length]; i++) {
|
||||
[datastr appendFormat:@"%02x",[data characterAtIndex:i]];
|
||||
}
|
||||
if (formatItemsCount > 3) {
|
||||
// We have additional data to append.
|
||||
// currently used by Date format to append final 0x00 but can be used by other data types as well in the future
|
||||
[datastr appendString:[dataformat objectAtIndex:3]];
|
||||
}
|
||||
if ([datastr length] < 8) {
|
||||
NSString * format = [NSString stringWithFormat:@"%%0%dd", 8 - [datastr length]];
|
||||
[datastr appendFormat:format,0];
|
||||
}
|
||||
return datastr;
|
||||
case EDT_USHORT:
|
||||
return [[NSString alloc] initWithFormat : @"%@%@",
|
||||
[self formattedHexStringFromDecimalNumber: [NSNumber numberWithInt: [data intValue]] withPlaces: @4],
|
||||
@"0000"];
|
||||
case EDT_ULONG:
|
||||
tmp = [NSNumber numberWithUnsignedLong:[data intValue]];
|
||||
return [NSString stringWithFormat : @"%@",
|
||||
[self formattedHexStringFromDecimalNumber: tmp withPlaces: @8]];
|
||||
case EDT_URATIONAL:
|
||||
return [self decimalToUnsignedRational: [NSNumber numberWithDouble:[data doubleValue]]
|
||||
withResultNumerator: &num
|
||||
withResultDenominator: &denom];
|
||||
case EDT_SBYTE:
|
||||
|
||||
break;
|
||||
case EDT_UNDEFINED:
|
||||
break; // 8 bits
|
||||
case EDT_SSHORT:
|
||||
break;
|
||||
case EDT_SLONG:
|
||||
break; // 32bit signed integer (2's complement)
|
||||
case EDT_SRATIONAL:
|
||||
break; // 2 SLONGS, first long is numerator, second is denominator
|
||||
case EDT_SINGLEFLOAT:
|
||||
break;
|
||||
case EDT_DOUBLEFLOAT:
|
||||
break;
|
||||
}
|
||||
return datastr;
|
||||
}
|
||||
|
||||
//======================================================================================================================
|
||||
// Utility Methods
|
||||
//======================================================================================================================
|
||||
|
||||
// creates a formatted little endian hex string from a number and width specifier
|
||||
- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb withPlaces: (NSNumber*) width {
|
||||
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:[width intValue]];
|
||||
NSString * formatstr = [[NSString alloc] initWithFormat: @"%%%@%dx", @"0", [width intValue]];
|
||||
[str appendFormat:formatstr, [numb intValue]];
|
||||
return str;
|
||||
}
|
||||
|
||||
// format number as string with leading 0's
|
||||
- (NSString*) formatNumberWithLeadingZeroes: (NSNumber *) numb withPlaces: (NSNumber *) places {
|
||||
NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
|
||||
NSString *formatstr = [@"" stringByPaddingToLength:[places unsignedIntegerValue] withString:@"0" startingAtIndex:0];
|
||||
[formatter setPositiveFormat:formatstr];
|
||||
return [formatter stringFromNumber:numb];
|
||||
}
|
||||
|
||||
// approximate a decimal with a rational by method of continued fraction
|
||||
// can be collasped into decimalToUnsignedRational after testing
|
||||
- (void) decimalToRational: (NSNumber *) numb
|
||||
withResultNumerator: (NSNumber**) numerator
|
||||
withResultDenominator: (NSNumber**) denominator {
|
||||
NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
|
||||
|
||||
[self continuedFraction: [numb doubleValue]
|
||||
withFractionList: fractionlist
|
||||
withHorizon: 8];
|
||||
|
||||
// simplify complex fraction represented by partial fraction list
|
||||
[self expandContinuedFraction: fractionlist
|
||||
withResultNumerator: numerator
|
||||
withResultDenominator: denominator];
|
||||
|
||||
}
|
||||
|
||||
// approximate a decimal with an unsigned rational by method of continued fraction
|
||||
- (NSString*) decimalToUnsignedRational: (NSNumber *) numb
|
||||
withResultNumerator: (NSNumber**) numerator
|
||||
withResultDenominator: (NSNumber**) denominator {
|
||||
NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8];
|
||||
|
||||
// generate partial fraction list
|
||||
[self continuedFraction: [numb doubleValue]
|
||||
withFractionList: fractionlist
|
||||
withHorizon: 8];
|
||||
|
||||
// simplify complex fraction represented by partial fraction list
|
||||
[self expandContinuedFraction: fractionlist
|
||||
withResultNumerator: numerator
|
||||
withResultDenominator: denominator];
|
||||
|
||||
return [self formatFractionList: fractionlist];
|
||||
}
|
||||
|
||||
// recursive implementation of decimal approximation by continued fraction
|
||||
- (void) continuedFraction: (double) val
|
||||
withFractionList: (NSMutableArray*) fractionlist
|
||||
withHorizon: (int) horizon {
|
||||
int whole;
|
||||
double remainder;
|
||||
// 1. split term
|
||||
[self splitDouble: val withIntComponent: &whole withFloatRemainder: &remainder];
|
||||
[fractionlist addObject: [NSNumber numberWithInt:whole]];
|
||||
|
||||
// 2. calculate reciprocal of remainder
|
||||
if (!remainder) return; // early exit, exact fraction found, avoids recip/0
|
||||
double recip = 1 / remainder;
|
||||
|
||||
// 3. exit condition
|
||||
if ([fractionlist count] > horizon) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. recurse
|
||||
[self continuedFraction:recip withFractionList: fractionlist withHorizon: horizon];
|
||||
|
||||
}
|
||||
|
||||
// expand continued fraction list, creating a single level rational approximation
|
||||
-(void) expandContinuedFraction: (NSArray*) fractionlist
|
||||
withResultNumerator: (NSNumber**) numerator
|
||||
withResultDenominator: (NSNumber**) denominator {
|
||||
int i = 0;
|
||||
int den = 0;
|
||||
int num = 0;
|
||||
if ([fractionlist count] == 1) {
|
||||
*numerator = [NSNumber numberWithInt:[[fractionlist objectAtIndex:0] intValue]];
|
||||
*denominator = @1;
|
||||
return;
|
||||
}
|
||||
|
||||
//begin at the end of the list
|
||||
i = [fractionlist count] - 1;
|
||||
num = 1;
|
||||
den = [[fractionlist objectAtIndex:i] intValue];
|
||||
|
||||
while (i > 0) {
|
||||
int t = [[fractionlist objectAtIndex: i-1] intValue];
|
||||
num = t * den + num;
|
||||
if (i==1) {
|
||||
break;
|
||||
} else {
|
||||
t = num;
|
||||
num = den;
|
||||
den = t;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
// set result parameters values
|
||||
*numerator = [NSNumber numberWithInt: num];
|
||||
*denominator = [NSNumber numberWithInt: den];
|
||||
}
|
||||
|
||||
// formats expanded fraction list to string matching exif specification
|
||||
- (NSString*) formatFractionList: (NSArray *) fractionlist {
|
||||
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
|
||||
|
||||
if ([fractionlist count] == 1){
|
||||
[str appendFormat: @"%08x00000001", [[fractionlist objectAtIndex:0] intValue]];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// format rational as
|
||||
- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator withDenominator: (NSNumber*) denominator asSigned: (Boolean) signedFlag {
|
||||
NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16];
|
||||
if (signedFlag) {
|
||||
long num = [numerator longValue];
|
||||
long den = [denominator longValue];
|
||||
[str appendFormat: @"%08lx%08lx", num >= 0 ? num : ~ABS(num) + 1, num >= 0 ? den : ~ABS(den) + 1];
|
||||
} else {
|
||||
[str appendFormat: @"%08lx%08lx", [numerator unsignedLongValue], [denominator unsignedLongValue]];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// split a floating point number into two integer values representing the left and right side of the decimal
|
||||
- (void) splitDouble: (double) val withIntComponent: (int*) rightside withFloatRemainder: (double*) leftside {
|
||||
*rightside = val; // convert numb to int representation, which truncates the decimal portion
|
||||
*leftside = val - *rightside;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
- (NSString*) hexStringFromData : (NSData*) data {
|
||||
//overflow detection
|
||||
const unsigned char *dataBuffer = [data bytes];
|
||||
return [[NSString alloc] initWithFormat: @"%02x%02x",
|
||||
(unsigned char)dataBuffer[0],
|
||||
(unsigned char)dataBuffer[1]];
|
||||
}
|
||||
|
||||
// convert a hex string to a number
|
||||
- (NSNumber*) numericFromHexString : (NSString *) hexstring {
|
||||
NSScanner * scan = NULL;
|
||||
unsigned int numbuf= 0;
|
||||
|
||||
scan = [NSScanner scannerWithString:hexstring];
|
||||
[scan scanHexInt:&numbuf];
|
||||
return [NSNumber numberWithInt:numbuf];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2013 Canonical Ltd.
|
||||
*
|
||||
* 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 QtQuick 2.0
|
||||
import QtMultimedia 5.0
|
||||
|
||||
Rectangle {
|
||||
property string shootImagePath: "shoot.png"
|
||||
function isSuffix(str, suffix) {
|
||||
return String(str).substr(String(str).length - suffix.length) == suffix
|
||||
}
|
||||
|
||||
id: ui
|
||||
color: "#252423"
|
||||
anchors.fill: parent
|
||||
|
||||
Camera {
|
||||
objectName: "camera"
|
||||
id: camera
|
||||
onError: {
|
||||
console.log(errorString);
|
||||
}
|
||||
videoRecorder.audioBitRate: 128000
|
||||
videoRecorder.mediaContainer: "mp4"
|
||||
imageCapture {
|
||||
onImageSaved: {
|
||||
root.exec("Camera", "onImageSaved", [path]);
|
||||
ui.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
VideoOutput {
|
||||
id: output
|
||||
source: camera
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: shootButton.height
|
||||
BorderImage {
|
||||
id: leftBackground
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: middle.left
|
||||
anchors.topMargin: units.dp(2)
|
||||
anchors.bottomMargin: units.dp(2)
|
||||
source: "toolbar-left.png"
|
||||
Image {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: parent.iconSpacing
|
||||
source: "back.png"
|
||||
width: units.gu(6)
|
||||
height: units.gu(5)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.exec("Camera", "cancel");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BorderImage {
|
||||
id: middle
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: shootButton.height + units.gu(1)
|
||||
width: shootButton.width
|
||||
source: "toolbar-middle.png"
|
||||
Image {
|
||||
id: shootButton
|
||||
width: units.gu(8)
|
||||
height: width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
source: shootImagePath
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
camera.imageCapture.capture();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BorderImage {
|
||||
id: rightBackground
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: middle.right
|
||||
anchors.topMargin: units.dp(2)
|
||||
anchors.bottomMargin: units.dp(2)
|
||||
source: "toolbar-right.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,143 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "camera.h"
|
||||
#include <cordova.h>
|
||||
|
||||
#include <QCameraViewfinder>
|
||||
#include <QCameraImageCapture>
|
||||
#include <QGraphicsObject>
|
||||
#include <QCloseEvent>
|
||||
#include <QQuickItem>
|
||||
|
||||
const char code[] = "\
|
||||
var component, object; \
|
||||
function createObject() { \
|
||||
component = Qt.createComponent(%1); \
|
||||
if (component.status == Component.Ready) \
|
||||
finishCreation(); \
|
||||
else \
|
||||
component.statusChanged.connect(finishCreation); \
|
||||
} \
|
||||
function finishCreation() { \
|
||||
CordovaWrapper.object = component.createObject(root, \
|
||||
{root: root, cordova: cordova}); \
|
||||
} \
|
||||
createObject()";
|
||||
|
||||
|
||||
Camera::Camera(Cordova *cordova):
|
||||
CPlugin(cordova),
|
||||
_lastScId(0),
|
||||
_lastEcId(0) {
|
||||
}
|
||||
|
||||
bool Camera::preprocessImage(QString &path) {
|
||||
bool convertToPNG = (*_options.find("encodingType")).toInt() == Camera::PNG;
|
||||
int quality = (*_options.find("quality")).toInt();
|
||||
int width = (*_options.find("targetWidth")).toInt();
|
||||
int height = (*_options.find("targetHeight")).toInt();
|
||||
|
||||
QImage image(path);
|
||||
if (width <= 0)
|
||||
width = image.width();
|
||||
if (height <= 0)
|
||||
height = image.height();
|
||||
image = image.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
QFile oldImage(path);
|
||||
QTemporaryFile newImage;
|
||||
|
||||
const char *type;
|
||||
if (convertToPNG) {
|
||||
newImage.setFileTemplate("imgXXXXXX.png");
|
||||
type = "png";
|
||||
} else {
|
||||
newImage.setFileTemplate("imgXXXXXX.jpg");
|
||||
type = "jpg";
|
||||
}
|
||||
|
||||
newImage.open();
|
||||
newImage.setAutoRemove(false);
|
||||
image.save(newImage.fileName(), type, quality);
|
||||
|
||||
path = newImage.fileName();
|
||||
oldImage.remove();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Camera::onImageSaved(QString path) {
|
||||
bool dataURL = _options.find("destinationType")->toInt() == Camera::DATA_URL;
|
||||
|
||||
QString cbParams;
|
||||
if (preprocessImage(path)) {
|
||||
QString absolutePath = QFileInfo(path).absoluteFilePath();
|
||||
if (dataURL) {
|
||||
QFile image(absolutePath);
|
||||
image.open(QIODevice::ReadOnly);
|
||||
QByteArray content = image.readAll().toBase64();
|
||||
cbParams = QString("\"%1\"").arg(content.data());
|
||||
image.remove();
|
||||
} else {
|
||||
cbParams = CordovaInternal::format(QUrl::fromLocalFile(absolutePath).toString());
|
||||
}
|
||||
}
|
||||
|
||||
this->callback(_lastScId, cbParams);
|
||||
|
||||
_lastEcId = _lastScId = 0;
|
||||
}
|
||||
|
||||
void Camera::takePicture(int scId, int ecId, int quality, int destinationType, int/*sourceType*/, int targetWidth, int targetHeight, int encodingType,
|
||||
int/*mediaType*/, bool/*allowEdit*/, bool/*correctOrientation*/, bool/*saveToPhotoAlbum*/, const QVariantMap &/*popoverOptions*/, int/*cameraDirection*/) {
|
||||
if (_camera.isNull()) {
|
||||
_camera = QSharedPointer<QCamera>(new QCamera());
|
||||
}
|
||||
|
||||
if (((_lastScId || _lastEcId) && (_lastScId != scId && _lastEcId != ecId)) || !_camera->isAvailable() || _camera->lockStatus() != QCamera::Unlocked) {
|
||||
this->cb(_lastEcId, "Device is busy");
|
||||
return;
|
||||
}
|
||||
|
||||
_options.clear();
|
||||
_options.insert("quality", quality);
|
||||
_options.insert("destinationType", destinationType);
|
||||
_options.insert("targetWidth", targetWidth);
|
||||
_options.insert("targetHeight", targetHeight);
|
||||
_options.insert("encodingType", encodingType);
|
||||
|
||||
_lastScId = scId;
|
||||
_lastEcId = ecId;
|
||||
|
||||
QString path = m_cordova->get_app_dir() + "/../qml/CaptureWidget.qml";
|
||||
|
||||
// TODO: relative url
|
||||
QString qml = QString(code).arg(CordovaInternal::format(path));
|
||||
m_cordova->execQML(qml);
|
||||
}
|
||||
|
||||
void Camera::cancel() {
|
||||
m_cordova->execQML("CordovaWrapper.object.destroy()");
|
||||
this->cb(_lastEcId, "canceled");
|
||||
|
||||
_lastEcId = _lastScId = 0;
|
||||
}
|
||||
@@ -1,76 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CAMERA_H
|
||||
#define CAMERA_H
|
||||
|
||||
#include <cplugin.h>
|
||||
|
||||
#include <QtCore>
|
||||
#include <QQuickView>
|
||||
#include <QCamera>
|
||||
#include <QtMultimediaWidgets/QCameraViewfinder>
|
||||
#include <QCameraImageCapture>
|
||||
|
||||
class Camera: public CPlugin {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Camera(Cordova *cordova);
|
||||
|
||||
virtual const QString fullName() override {
|
||||
return Camera::fullID();
|
||||
}
|
||||
|
||||
virtual const QString shortName() override {
|
||||
return "Camera";
|
||||
}
|
||||
|
||||
static const QString fullID() {
|
||||
return "Camera";
|
||||
}
|
||||
|
||||
public slots:
|
||||
void takePicture(int scId, int ecId, int quality, int destinationType, int/*sourceType*/, int targetWidth, int targetHeight, int encodingType,
|
||||
int/*mediaType*/, bool/*allowEdit*/, bool/*correctOrientation*/, bool/*saveToPhotoAlbum*/, const QVariantMap &popoverOptions, int cameraDirection);
|
||||
void cancel();
|
||||
|
||||
void onImageSaved(QString path);
|
||||
|
||||
private:
|
||||
bool preprocessImage(QString &path);
|
||||
|
||||
int _lastScId;
|
||||
int _lastEcId;
|
||||
QSharedPointer<QCamera> _camera;
|
||||
|
||||
QVariantMap _options;
|
||||
protected:
|
||||
enum DestinationType {
|
||||
DATA_URL = 0,
|
||||
FILE_URI = 1
|
||||
};
|
||||
enum EncodingType {
|
||||
JPEG = 0,
|
||||
PNG = 1
|
||||
};
|
||||
};
|
||||
|
||||
#endif // CAMERA_H
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,341 +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 */
|
||||
|
||||
|
||||
var cordova = require('cordova'),
|
||||
Camera = require('./Camera'),
|
||||
FileEntry = require('org.apache.cordova.file.FileEntry'),
|
||||
FileError = require('org.apache.cordova.file.FileError'),
|
||||
FileReader = require('org.apache.cordova.file.FileReader');
|
||||
|
||||
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
|
||||
|
||||
takePicture: function (successCallback, errorCallback, args) {
|
||||
var encodingType = args[5];
|
||||
var targetWidth = args[3];
|
||||
var targetHeight = args[4];
|
||||
var sourceType = args[2];
|
||||
var destinationType = args[1];
|
||||
var mediaType = args[6];
|
||||
var saveToPhotoAlbum = args[9];
|
||||
|
||||
var pkg = Windows.ApplicationModel.Package.current;
|
||||
var packageId = pkg.installedLocation;
|
||||
|
||||
var fail = function (fileError) {
|
||||
errorCallback("FileError, code:" + fileError.code);
|
||||
};
|
||||
|
||||
// resize method :)
|
||||
var resizeImage = function (file) {
|
||||
var tempPhotoFileName = "";
|
||||
if (encodingType == Camera.EncodingType.PNG) {
|
||||
tempPhotoFileName = "camera_cordova_temp_return.png";
|
||||
} else {
|
||||
tempPhotoFileName = "camera_cordova_temp_return.jpg";
|
||||
}
|
||||
var imgObj = new Image();
|
||||
var success = function (fileEntry) {
|
||||
var successCB = function (filePhoto) {
|
||||
var fileType = file.contentType,
|
||||
reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
var image = new Image();
|
||||
image.src = reader.result;
|
||||
image.onload = function () {
|
||||
var imageWidth = targetWidth,
|
||||
imageHeight = targetHeight;
|
||||
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 _blob = canvas.msToBlob();
|
||||
var _stream = _blob.msDetachStream();
|
||||
Windows.Storage.StorageFolder.getFolderFromPathAsync(packageId.path).done(function (storageFolder) {
|
||||
storageFolder.createFileAsync(tempPhotoFileName, Windows.Storage.CreationCollisionOption.generateUniqueName).done(function (file) {
|
||||
file.openAsync(Windows.Storage.FileAccessMode.readWrite).done(function (fileStream) {
|
||||
Windows.Storage.Streams.RandomAccessStream.copyAndCloseAsync(_stream, fileStream).done(function () {
|
||||
var _imageUrl = URL.createObjectURL(file);
|
||||
successCallback(_imageUrl);
|
||||
}, function () { errorCallback("Resize picture error."); });
|
||||
}, function () { errorCallback("Resize picture error."); });
|
||||
}, function () { errorCallback("Resize picture error."); });
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
reader.readAsDataURL(filePhoto);
|
||||
};
|
||||
|
||||
var failCB = function () {
|
||||
errorCallback("File not found.");
|
||||
};
|
||||
fileEntry.file(successCB, failCB);
|
||||
};
|
||||
|
||||
Windows.Storage.StorageFolder.getFolderFromPathAsync(packageId.path).done(function (storageFolder) {
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).then(function (storageFile) {
|
||||
success(new FileEntry(storageFile.name, storageFile.path));
|
||||
}, function () {
|
||||
fail(FileError.INVALID_MODIFICATION_ERR);
|
||||
}, function () {
|
||||
errorCallback("Folder not access.");
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// because of asynchronous method, so let the successCallback be called in it.
|
||||
var resizeImageBase64 = function (file) {
|
||||
var imgObj = new Image();
|
||||
var success = function (fileEntry) {
|
||||
var successCB = function (filePhoto) {
|
||||
var fileType = file.contentType,
|
||||
reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
var image = new Image();
|
||||
image.src = reader.result;
|
||||
|
||||
image.onload = function () {
|
||||
var imageWidth = targetWidth,
|
||||
imageHeight = targetHeight;
|
||||
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(fileType);
|
||||
|
||||
// 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);
|
||||
};
|
||||
};
|
||||
|
||||
reader.readAsDataURL(filePhoto);
|
||||
|
||||
};
|
||||
var failCB = function () {
|
||||
errorCallback("File not found.");
|
||||
};
|
||||
fileEntry.file(successCB, failCB);
|
||||
};
|
||||
|
||||
Windows.Storage.StorageFolder.getFolderFromPathAsync(packageId.path).done(function (storageFolder) {
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).then(function (storageFile) {
|
||||
success(new FileEntry(storageFile.name, storageFile.path));
|
||||
}, function () {
|
||||
fail(FileError.INVALID_MODIFICATION_ERR);
|
||||
}, function () {
|
||||
errorCallback("Folder not access.");
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
if (sourceType != Camera.PictureSourceType.CAMERA) {
|
||||
var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
|
||||
fileOpenPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
|
||||
if (mediaType == Camera.MediaType.PICTURE) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
|
||||
} else if (mediaType == Camera.MediaType.VIDEO) {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll([".avi", ".flv", ".asx", ".asf", ".mov", ".mp4", ".mpg", ".rm", ".srt", ".swf", ".wmv", ".vob"]);
|
||||
} else {
|
||||
fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
|
||||
}
|
||||
|
||||
fileOpenPicker.pickSingleFileAsync().then(function (file) {
|
||||
if (file) {
|
||||
if (destinationType == Camera.DestinationType.FILE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(file);
|
||||
} else {
|
||||
Windows.Storage.StorageFolder.getFolderFromPathAsync(packageId.path).done(function (storageFolder) {
|
||||
file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).then(function (storageFile) {
|
||||
var _imageUrl = URL.createObjectURL(storageFile);
|
||||
successCallback(_imageUrl);
|
||||
}, function () {
|
||||
fail(FileError.INVALID_MODIFICATION_ERR);
|
||||
}, function () {
|
||||
errorCallback("Folder not access.");
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(file);
|
||||
} else {
|
||||
Windows.Storage.FileIO.readBufferAsync(file).done(function (buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
errorCallback("User didn't choose a file.");
|
||||
}
|
||||
}, function () {
|
||||
errorCallback("User didn't choose a file.");
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
var cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI();
|
||||
cameraCaptureUI.photoSettings.allowCropping = true;
|
||||
var allowCrop = !!args[7];
|
||||
if (!allowCrop) {
|
||||
cameraCaptureUI.photoSettings.allowCropping = false;
|
||||
}
|
||||
|
||||
if (encodingType == Camera.EncodingType.PNG) {
|
||||
cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.png;
|
||||
} else {
|
||||
cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.jpeg;
|
||||
}
|
||||
// decide which max pixels should be supported by targetWidth or targetHeight.
|
||||
if (targetWidth >= 1280 || targetHeight >= 960) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.large3M;
|
||||
} else if (targetWidth >= 1024 || targetHeight >= 768) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.mediumXga;
|
||||
} else if (targetWidth >= 800 || targetHeight >= 600) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.mediumXga;
|
||||
} else if (targetWidth >= 640 || targetHeight >= 480) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.smallVga;
|
||||
} else if (targetWidth >= 320 || targetHeight >= 240) {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.verySmallQvga;
|
||||
} else {
|
||||
cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.highestAvailable;
|
||||
}
|
||||
|
||||
cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).then(function (picture) {
|
||||
if (picture) {
|
||||
// save to photo album successCallback
|
||||
var success = function (fileEntry) {
|
||||
if (destinationType == Camera.DestinationType.FILE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(picture);
|
||||
} else {
|
||||
Windows.Storage.StorageFolder.getFolderFromPathAsync(packageId.path).done(function (storageFolder) {
|
||||
picture.copyAsync(storageFolder, picture.name, Windows.Storage.NameCollisionOption.replaceExisting).then(function (storageFile) {
|
||||
var _imageUrl = URL.createObjectURL(storageFile);
|
||||
successCallback(_imageUrl);
|
||||
}, function () {
|
||||
fail(FileError.INVALID_MODIFICATION_ERR);
|
||||
}, function () {
|
||||
errorCallback("Folder not access.");
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(picture);
|
||||
} else {
|
||||
Windows.Storage.FileIO.readBufferAsync(picture).done(function (buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
// save to photo album errorCallback
|
||||
var fail = function () {
|
||||
//errorCallback("FileError, code:" + fileError.code);
|
||||
errorCallback("Save fail.");
|
||||
};
|
||||
|
||||
if (saveToPhotoAlbum) {
|
||||
Windows.Storage.StorageFile.getFileFromPathAsync(picture.path).then(function (storageFile) {
|
||||
storageFile.copyAsync(Windows.Storage.KnownFolders.picturesLibrary, picture.name, Windows.Storage.NameCollisionOption.generateUniqueName).then(function (storageFile) {
|
||||
success(storageFile);
|
||||
}, function () {
|
||||
fail();
|
||||
});
|
||||
});
|
||||
//var directory = new DirectoryEntry("Pictures", parentPath);
|
||||
//new FileEntry(picture.name, picture.path).copyTo(directory, null, success, fail);
|
||||
} else {
|
||||
if (destinationType == Camera.DestinationType.FILE_URI) {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImage(picture);
|
||||
} else {
|
||||
Windows.Storage.StorageFolder.getFolderFromPathAsync(packageId.path).done(function (storageFolder) {
|
||||
picture.copyAsync(storageFolder, picture.name, Windows.Storage.NameCollisionOption.replaceExisting).then(function (storageFile) {
|
||||
var _imageUrl = URL.createObjectURL(storageFile);
|
||||
successCallback(_imageUrl);
|
||||
}, function () {
|
||||
fail(FileError.INVALID_MODIFICATION_ERR);
|
||||
}, function () {
|
||||
errorCallback("Folder not access.");
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (targetHeight > 0 && targetWidth > 0) {
|
||||
resizeImageBase64(picture);
|
||||
} else {
|
||||
Windows.Storage.FileIO.readBufferAsync(picture).done(function (buffer) {
|
||||
var strBase64 = Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
|
||||
successCallback(strBase64);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorCallback("User didn't capture a photo.");
|
||||
}
|
||||
}, function () {
|
||||
errorCallback("Fail to capture a photo.");
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
require("cordova/windows8/commandProxy").add("Camera",module.exports);
|
||||
490
src/wp/Camera.cs
490
src/wp/Camera.cs
@@ -1,490 +0,0 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Phone.Tasks;
|
||||
using System.Runtime.Serialization;
|
||||
using System.IO;
|
||||
using System.IO.IsolatedStorage;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Phone;
|
||||
using Microsoft.Xna.Framework.Media;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace WPCordovaClassLib.Cordova.Commands
|
||||
{
|
||||
public class Camera : BaseCommand
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Return base64 encoded string
|
||||
/// </summary>
|
||||
private const int DATA_URL = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Return file uri
|
||||
/// </summary>
|
||||
private const int FILE_URI = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Choose image from picture library
|
||||
/// </summary>
|
||||
private const int PHOTOLIBRARY = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Take picture from camera
|
||||
/// </summary>
|
||||
|
||||
private const int CAMERA = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Choose image from picture library
|
||||
/// </summary>
|
||||
private const int SAVEDPHOTOALBUM = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Take a picture of type JPEG
|
||||
/// </summary>
|
||||
private const int JPEG = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Take a picture of type PNG
|
||||
/// </summary>
|
||||
private const int PNG = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Folder to store captured images
|
||||
/// </summary>
|
||||
private const string isoFolder = "CapturedImagesCache";
|
||||
|
||||
/// <summary>
|
||||
/// Represents captureImage action options.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class CameraOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Source to getPicture from.
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "sourceType")]
|
||||
public int PictureSourceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Format of image that returned from getPicture.
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "destinationType")]
|
||||
public int DestinationType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Quality of saved image
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "quality")]
|
||||
public int Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether or not the image is also added to the device photo album.
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "saveToPhotoAlbum")]
|
||||
public bool SaveToPhotoAlbum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ignored
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "correctOrientation")]
|
||||
public bool CorrectOrientation { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ignored
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "allowEdit")]
|
||||
public bool AllowEdit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Height in pixels to scale image
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "encodingType")]
|
||||
public int EncodingType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Height in pixels to scale image
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "mediaType")]
|
||||
public int MediaType { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Height in pixels to scale image
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "targetHeight")]
|
||||
public int TargetHeight { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Width in pixels to scale image
|
||||
/// </summary>
|
||||
[DataMember(IsRequired = false, Name = "targetWidth")]
|
||||
public int TargetWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates options object with default parameters
|
||||
/// </summary>
|
||||
public CameraOptions()
|
||||
{
|
||||
this.SetDefaultValues(new StreamingContext());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes default values for class fields.
|
||||
/// Implemented in separate method because default constructor is not invoked during deserialization.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
[OnDeserializing()]
|
||||
public void SetDefaultValues(StreamingContext context)
|
||||
{
|
||||
PictureSourceType = CAMERA;
|
||||
DestinationType = FILE_URI;
|
||||
Quality = 80;
|
||||
TargetHeight = -1;
|
||||
TargetWidth = -1;
|
||||
SaveToPhotoAlbum = false;
|
||||
CorrectOrientation = true;
|
||||
AllowEdit = false;
|
||||
MediaType = -1;
|
||||
EncodingType = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to open photo library
|
||||
/// </summary>
|
||||
PhotoChooserTask photoChooserTask;
|
||||
|
||||
/// <summary>
|
||||
/// Used to open camera application
|
||||
/// </summary>
|
||||
CameraCaptureTask cameraTask;
|
||||
|
||||
/// <summary>
|
||||
/// Camera options
|
||||
/// </summary>
|
||||
CameraOptions cameraOptions;
|
||||
|
||||
public void takePicture(string options)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
|
||||
// ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType",
|
||||
// "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ]
|
||||
this.cameraOptions = new CameraOptions();
|
||||
this.cameraOptions.Quality = int.Parse(args[0]);
|
||||
this.cameraOptions.DestinationType = int.Parse(args[1]);
|
||||
this.cameraOptions.PictureSourceType = int.Parse(args[2]);
|
||||
this.cameraOptions.TargetWidth = int.Parse(args[3]);
|
||||
this.cameraOptions.TargetHeight = int.Parse(args[4]);
|
||||
this.cameraOptions.EncodingType = int.Parse(args[5]);
|
||||
this.cameraOptions.MediaType = int.Parse(args[6]);
|
||||
this.cameraOptions.AllowEdit = bool.Parse(args[7]);
|
||||
this.cameraOptions.CorrectOrientation = bool.Parse(args[8]);
|
||||
this.cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]);
|
||||
|
||||
//this.cameraOptions = String.IsNullOrEmpty(options) ?
|
||||
// new CameraOptions() : JSON.JsonHelper.Deserialize<CameraOptions>(options);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO Check if all the options are acceptable
|
||||
|
||||
|
||||
if (cameraOptions.PictureSourceType == CAMERA)
|
||||
{
|
||||
cameraTask = new CameraCaptureTask();
|
||||
cameraTask.Completed += onCameraTaskCompleted;
|
||||
cameraTask.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM))
|
||||
{
|
||||
photoChooserTask = new PhotoChooserTask();
|
||||
photoChooserTask.Completed += onPickerTaskCompleted;
|
||||
photoChooserTask.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void onCameraTaskCompleted(object sender, PhotoResult e)
|
||||
{
|
||||
if (e.Error != null)
|
||||
{
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.TaskResult)
|
||||
{
|
||||
case TaskResult.OK:
|
||||
try
|
||||
{
|
||||
string imagePathOrContent = string.Empty;
|
||||
|
||||
if (cameraOptions.DestinationType == FILE_URI)
|
||||
{
|
||||
// Save image in media library
|
||||
if (cameraOptions.SaveToPhotoAlbum)
|
||||
{
|
||||
MediaLibrary library = new MediaLibrary();
|
||||
Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ...
|
||||
}
|
||||
|
||||
int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto);
|
||||
int newAngle = 0;
|
||||
switch (orient)
|
||||
{
|
||||
case ImageExifOrientation.LandscapeLeft:
|
||||
newAngle = 90;
|
||||
break;
|
||||
case ImageExifOrientation.PortraitUpsideDown:
|
||||
newAngle = 180;
|
||||
break;
|
||||
case ImageExifOrientation.LandscapeRight:
|
||||
newAngle = 270;
|
||||
break;
|
||||
case ImageExifOrientation.Portrait:
|
||||
default: break; // 0 default already set
|
||||
}
|
||||
|
||||
Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle);
|
||||
|
||||
// we should return stream position back after saving stream to media library
|
||||
rotImageStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
WriteableBitmap image = PictureDecoder.DecodeJpeg(rotImageStream);
|
||||
|
||||
imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName));
|
||||
|
||||
|
||||
}
|
||||
else if (cameraOptions.DestinationType == DATA_URL)
|
||||
{
|
||||
imagePathOrContent = this.GetImageContent(e.ChosenPhoto);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: shouldn't this happen before we launch the camera-picker?
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType"));
|
||||
return;
|
||||
}
|
||||
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent));
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image."));
|
||||
}
|
||||
break;
|
||||
|
||||
case TaskResult.Cancel:
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled."));
|
||||
break;
|
||||
|
||||
default:
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!"));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void onPickerTaskCompleted(object sender, PhotoResult e)
|
||||
{
|
||||
if (e.Error != null)
|
||||
{
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.TaskResult)
|
||||
{
|
||||
case TaskResult.OK:
|
||||
try
|
||||
{
|
||||
string imagePathOrContent = string.Empty;
|
||||
|
||||
if (cameraOptions.DestinationType == FILE_URI)
|
||||
{
|
||||
WriteableBitmap image = PictureDecoder.DecodeJpeg(e.ChosenPhoto);
|
||||
imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName));
|
||||
}
|
||||
else if (cameraOptions.DestinationType == DATA_URL)
|
||||
{
|
||||
imagePathOrContent = this.GetImageContent(e.ChosenPhoto);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: shouldn't this happen before we launch the camera-picker?
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType"));
|
||||
return;
|
||||
}
|
||||
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent));
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image."));
|
||||
}
|
||||
break;
|
||||
|
||||
case TaskResult.Cancel:
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled."));
|
||||
break;
|
||||
|
||||
default:
|
||||
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns image content in a form of base64 string
|
||||
/// </summary>
|
||||
/// <param name="stream">Image stream</param>
|
||||
/// <returns>Base64 representation of the image</returns>
|
||||
private string GetImageContent(Stream stream)
|
||||
{
|
||||
int streamLength = (int)stream.Length;
|
||||
byte[] fileData = new byte[streamLength + 1];
|
||||
stream.Read(fileData, 0, streamLength);
|
||||
|
||||
//use photo's actual width & height if user doesn't provide width & height
|
||||
if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0)
|
||||
{
|
||||
stream.Close();
|
||||
return Convert.ToBase64String(fileData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// resize photo
|
||||
byte[] resizedFile = ResizePhoto(stream, fileData);
|
||||
stream.Close();
|
||||
return Convert.ToBase64String(resizedFile);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resize image
|
||||
/// </summary>
|
||||
/// <param name="stream">Image stream</param>
|
||||
/// <param name="fileData">File data</param>
|
||||
/// <returns>resized image</returns>
|
||||
private byte[] ResizePhoto(Stream stream, byte[] fileData)
|
||||
{
|
||||
int streamLength = (int)stream.Length;
|
||||
int intResult = 0;
|
||||
|
||||
byte[] resizedFile;
|
||||
|
||||
stream.Read(fileData, 0, streamLength);
|
||||
|
||||
BitmapImage objBitmap = new BitmapImage();
|
||||
MemoryStream objBitmapStream = new MemoryStream(fileData);
|
||||
MemoryStream objBitmapStreamResized = new MemoryStream();
|
||||
WriteableBitmap objWB;
|
||||
objBitmap.SetSource(stream);
|
||||
objWB = new WriteableBitmap(objBitmap);
|
||||
|
||||
// resize the photo with user defined TargetWidth & TargetHeight
|
||||
Extensions.SaveJpeg(objWB, objBitmapStreamResized, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality);
|
||||
|
||||
//Convert the resized stream to a byte array.
|
||||
streamLength = (int)objBitmapStreamResized.Length;
|
||||
resizedFile = new Byte[streamLength]; //-1
|
||||
objBitmapStreamResized.Position = 0;
|
||||
//for some reason we have to set Position to zero, but we don't have to earlier when we get the bytes from the chosen photo...
|
||||
intResult = objBitmapStreamResized.Read(resizedFile, 0, streamLength);
|
||||
|
||||
return resizedFile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves captured image in isolated storage
|
||||
/// </summary>
|
||||
/// <param name="imageFileName">image file name</param>
|
||||
/// <returns>Image path</returns>
|
||||
private string SaveImageToLocalStorage(WriteableBitmap image, string imageFileName)
|
||||
{
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
throw new ArgumentNullException("imageBytes");
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
|
||||
|
||||
if (!isoFile.DirectoryExists(isoFolder))
|
||||
{
|
||||
isoFile.CreateDirectory(isoFolder);
|
||||
}
|
||||
|
||||
string filePath = System.IO.Path.Combine("///" + isoFolder + "/", imageFileName);
|
||||
|
||||
using (var stream = isoFile.CreateFile(filePath))
|
||||
{
|
||||
// resize image if Height and Width defined via options
|
||||
if (cameraOptions.TargetHeight > 0 && cameraOptions.TargetWidth > 0)
|
||||
{
|
||||
image.SaveJpeg(stream, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.SaveJpeg(stream, image.PixelWidth, image.PixelHeight, 0, cameraOptions.Quality);
|
||||
}
|
||||
}
|
||||
|
||||
return new Uri(filePath, UriKind.Relative).ToString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//TODO: log or do something else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
jasmine.HtmlReporter = function(_doc) {
|
||||
var self = this;
|
||||
var doc = _doc || window.document;
|
||||
|
||||
var reporterView;
|
||||
|
||||
var dom = {};
|
||||
|
||||
// Jasmine Reporter Public Interface
|
||||
self.logRunningSpecs = false;
|
||||
|
||||
self.reportRunnerStarting = function(runner) {
|
||||
var specs = runner.specs() || [];
|
||||
|
||||
if (specs.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
createReporterDom(runner.env.versionString());
|
||||
doc.body.appendChild(dom.reporter);
|
||||
|
||||
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
|
||||
reporterView.addSpecs(specs, self.specFilter);
|
||||
};
|
||||
|
||||
self.reportRunnerResults = function(runner) {
|
||||
reporterView && reporterView.complete();
|
||||
};
|
||||
|
||||
self.reportSuiteResults = function(suite) {
|
||||
reporterView.suiteComplete(suite);
|
||||
};
|
||||
|
||||
self.reportSpecStarting = function(spec) {
|
||||
if (self.logRunningSpecs) {
|
||||
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
self.reportSpecResults = function(spec) {
|
||||
reporterView.specComplete(spec);
|
||||
};
|
||||
|
||||
self.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.specFilter = function(spec) {
|
||||
if (!focusedSpecName()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return spec.getFullName().indexOf(focusedSpecName()) === 0;
|
||||
};
|
||||
|
||||
return self;
|
||||
|
||||
function focusedSpecName() {
|
||||
var specName;
|
||||
|
||||
(function memoizeFocusedSpec() {
|
||||
if (specName) {
|
||||
return;
|
||||
}
|
||||
|
||||
var paramMap = [];
|
||||
var params = doc.location.search.substring(1).split('&');
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
specName = paramMap.spec;
|
||||
})();
|
||||
|
||||
return specName;
|
||||
}
|
||||
|
||||
function createReporterDom(version) {
|
||||
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
|
||||
dom.banner = self.createDom('div', { className: 'banner' },
|
||||
self.createDom('span', { className: 'title' }, "Jasmine "),
|
||||
self.createDom('span', { className: 'version' }, version)),
|
||||
|
||||
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
|
||||
dom.alert = self.createDom('div', {className: 'alert'}),
|
||||
dom.results = self.createDom('div', {className: 'results'},
|
||||
dom.summary = self.createDom('div', { className: 'summary' }),
|
||||
dom.details = self.createDom('div', { id: 'details' }))
|
||||
);
|
||||
}
|
||||
};
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
|
||||
@@ -1,60 +0,0 @@
|
||||
jasmine.HtmlReporterHelpers = {};
|
||||
|
||||
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) {
|
||||
el.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
|
||||
var results = child.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
|
||||
var parentDiv = this.dom.summary;
|
||||
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
|
||||
var parent = child[parentSuite];
|
||||
|
||||
if (parent) {
|
||||
if (typeof this.views.suites[parent.id] == 'undefined') {
|
||||
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
|
||||
}
|
||||
parentDiv = this.views.suites[parent.id].element;
|
||||
}
|
||||
|
||||
parentDiv.appendChild(childElement);
|
||||
};
|
||||
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
|
||||
for(var fn in jasmine.HtmlReporterHelpers) {
|
||||
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
jasmine.HtmlReporter.ReporterView = function(dom) {
|
||||
this.startedAt = new Date();
|
||||
this.runningSpecCount = 0;
|
||||
this.completeSpecCount = 0;
|
||||
this.passedCount = 0;
|
||||
this.failedCount = 0;
|
||||
this.skippedCount = 0;
|
||||
|
||||
this.createResultsMenu = function() {
|
||||
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
|
||||
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
|
||||
' | ',
|
||||
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
|
||||
|
||||
this.summaryMenuItem.onclick = function() {
|
||||
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
|
||||
};
|
||||
|
||||
this.detailsMenuItem.onclick = function() {
|
||||
showDetails();
|
||||
};
|
||||
};
|
||||
|
||||
this.addSpecs = function(specs, specFilter) {
|
||||
this.totalSpecCount = specs.length;
|
||||
|
||||
this.views = {
|
||||
specs: {},
|
||||
suites: {}
|
||||
};
|
||||
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
var spec = specs[i];
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
|
||||
if (specFilter(spec)) {
|
||||
this.runningSpecCount++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.specComplete = function(spec) {
|
||||
this.completeSpecCount++;
|
||||
|
||||
if (isUndefined(this.views.specs[spec.id])) {
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
|
||||
}
|
||||
|
||||
var specView = this.views.specs[spec.id];
|
||||
|
||||
switch (specView.status()) {
|
||||
case 'passed':
|
||||
this.passedCount++;
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.failedCount++;
|
||||
break;
|
||||
|
||||
case 'skipped':
|
||||
this.skippedCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
specView.refresh();
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
this.suiteComplete = function(suite) {
|
||||
var suiteView = this.views.suites[suite.id];
|
||||
if (isUndefined(suiteView)) {
|
||||
return;
|
||||
}
|
||||
suiteView.refresh();
|
||||
};
|
||||
|
||||
this.refresh = function() {
|
||||
|
||||
if (isUndefined(this.resultsMenu)) {
|
||||
this.createResultsMenu();
|
||||
}
|
||||
|
||||
// currently running UI
|
||||
if (isUndefined(this.runningAlert)) {
|
||||
this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
|
||||
dom.alert.appendChild(this.runningAlert);
|
||||
}
|
||||
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
|
||||
|
||||
// skipped specs UI
|
||||
if (isUndefined(this.skippedAlert)) {
|
||||
this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
|
||||
}
|
||||
|
||||
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.skippedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.skippedAlert);
|
||||
}
|
||||
|
||||
// passing specs UI
|
||||
if (isUndefined(this.passedAlert)) {
|
||||
this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
|
||||
}
|
||||
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
|
||||
|
||||
// failing specs UI
|
||||
if (isUndefined(this.failedAlert)) {
|
||||
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
|
||||
}
|
||||
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
|
||||
|
||||
if (this.failedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.failedAlert);
|
||||
dom.alert.appendChild(this.resultsMenu);
|
||||
}
|
||||
|
||||
// summary info
|
||||
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
|
||||
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
|
||||
};
|
||||
|
||||
this.complete = function() {
|
||||
dom.alert.removeChild(this.runningAlert);
|
||||
|
||||
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.failedCount === 0) {
|
||||
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
|
||||
} else {
|
||||
showDetails();
|
||||
}
|
||||
|
||||
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function showDetails() {
|
||||
if (dom.reporter.className.search(/showDetails/) === -1) {
|
||||
dom.reporter.className += " showDetails";
|
||||
}
|
||||
}
|
||||
|
||||
function isUndefined(obj) {
|
||||
return typeof obj === 'undefined';
|
||||
}
|
||||
|
||||
function isDefined(obj) {
|
||||
return !isUndefined(obj);
|
||||
}
|
||||
|
||||
function specPluralizedFor(count) {
|
||||
var str = count + " spec";
|
||||
if (count > 1) {
|
||||
str += "s"
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
|
||||
this.spec = spec;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.symbol = this.createDom('li', { className: 'pending' });
|
||||
this.dom.symbolSummary.appendChild(this.symbol);
|
||||
|
||||
this.summary = this.createDom('div', { className: 'specSummary' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.description)
|
||||
);
|
||||
|
||||
this.detail = this.createDom('div', { className: 'specDetail' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.getFullName())
|
||||
);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.spec);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
|
||||
this.symbol.className = this.status();
|
||||
|
||||
switch (this.status()) {
|
||||
case 'skipped':
|
||||
break;
|
||||
|
||||
case 'passed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
this.appendFailureDetail();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
|
||||
this.summary.className += ' ' + this.status();
|
||||
this.appendToSummary(this.spec, this.summary);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
|
||||
this.detail.className += ' ' + this.status();
|
||||
|
||||
var resultItems = this.spec.results().getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
this.detail.appendChild(messagesDiv);
|
||||
this.dom.details.appendChild(this.detail);
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);
|
||||
@@ -1,22 +0,0 @@
|
||||
jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
|
||||
this.suite = suite;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.element = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
|
||||
);
|
||||
|
||||
this.appendToSummary(this.suite, this.element);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.suite);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
|
||||
this.element.className += " " + this.status();
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
/* @deprecated Use jasmine.HtmlReporter instead
|
||||
*/
|
||||
jasmine.TrivialReporter = function(doc) {
|
||||
this.document = doc || document;
|
||||
this.suiteDivs = {};
|
||||
this.logRunningSpecs = false;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) { el.appendChild(child); }
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
||||
var showPassed, showSkipped;
|
||||
|
||||
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
|
||||
this.createDom('div', { className: 'banner' },
|
||||
this.createDom('div', { className: 'logo' },
|
||||
this.createDom('span', { className: 'title' }, "Jasmine"),
|
||||
this.createDom('span', { className: 'version' }, runner.env.versionString())),
|
||||
this.createDom('div', { className: 'options' },
|
||||
"Show ",
|
||||
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
|
||||
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
|
||||
)
|
||||
),
|
||||
|
||||
this.runnerDiv = this.createDom('div', { className: 'runner running' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
|
||||
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
|
||||
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
|
||||
);
|
||||
|
||||
this.document.body.appendChild(this.outerDiv);
|
||||
|
||||
var suites = runner.suites();
|
||||
for (var i = 0; i < suites.length; i++) {
|
||||
var suite = suites[i];
|
||||
var suiteDiv = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
|
||||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
|
||||
this.suiteDivs[suite.id] = suiteDiv;
|
||||
var parentDiv = this.outerDiv;
|
||||
if (suite.parentSuite) {
|
||||
parentDiv = this.suiteDivs[suite.parentSuite.id];
|
||||
}
|
||||
parentDiv.appendChild(suiteDiv);
|
||||
}
|
||||
|
||||
this.startedAt = new Date();
|
||||
|
||||
var self = this;
|
||||
showPassed.onclick = function(evt) {
|
||||
if (showPassed.checked) {
|
||||
self.outerDiv.className += ' show-passed';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
|
||||
}
|
||||
};
|
||||
|
||||
showSkipped.onclick = function(evt) {
|
||||
if (showSkipped.checked) {
|
||||
self.outerDiv.className += ' show-skipped';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
|
||||
var results = runner.results();
|
||||
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
|
||||
this.runnerDiv.setAttribute("class", className);
|
||||
//do it twice for IE
|
||||
this.runnerDiv.setAttribute("className", className);
|
||||
var specs = runner.specs();
|
||||
var specCount = 0;
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
if (this.specFilter(specs[i])) {
|
||||
specCount++;
|
||||
}
|
||||
}
|
||||
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
|
||||
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
|
||||
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
|
||||
|
||||
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
|
||||
var results = suite.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.totalCount === 0) { // todo: change this to check results.skipped
|
||||
status = 'skipped';
|
||||
}
|
||||
this.suiteDivs[suite.id].className += " " + status;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
|
||||
if (this.logRunningSpecs) {
|
||||
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
|
||||
var results = spec.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
var specDiv = this.createDom('div', { className: 'spec ' + status },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(spec.getFullName()),
|
||||
title: spec.getFullName()
|
||||
}, spec.description));
|
||||
|
||||
|
||||
var resultItems = results.getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
specDiv.appendChild(messagesDiv);
|
||||
}
|
||||
|
||||
this.suiteDivs[spec.suite.id].appendChild(specDiv);
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.getLocation = function() {
|
||||
return this.document.location;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
|
||||
var paramMap = {};
|
||||
var params = this.getLocation().search.substring(1).split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
if (!paramMap.spec) {
|
||||
return true;
|
||||
}
|
||||
return spec.getFullName().indexOf(paramMap.spec) === 0;
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, height=device-height, user-scalable=yes, initial-scale=1.0;" />
|
||||
|
||||
<title>Cordova API Specs</title>
|
||||
|
||||
<link rel="stylesheet" href="../master.css" type="text/css" media="screen" title="no title" charset="utf-8">
|
||||
<script type="text/javascript" src="../cordova-incl.js"></script>
|
||||
</head>
|
||||
<body id="stage" class="theme">
|
||||
<h1>Cordova API Specs</h1>
|
||||
|
||||
<a href="pages/all.html" class="btn large" style="width:100%;">Run All Tests</a>
|
||||
<a href="pages/accelerometer.html" class="btn large" style="width:100%;">Run Accelerometer Tests</a>
|
||||
<a href="pages/battery.html" class="btn large" style="width:100%;">Run Battery Tests</a>
|
||||
<a href="pages/camera.html" class="btn large" style="width:100%;">Run Camera Tests</a>
|
||||
<a href="pages/capture.html" class="btn large" style="width:100%;">Run Capture Tests</a>
|
||||
<a href="pages/compass.html" class="btn large" style="width:100%;">Run Compass Tests</a>
|
||||
<a href="pages/contacts.html" class="btn large" style="width:100%;">Run Contacts Tests</a>
|
||||
<a href="pages/datauri.html" class="btn large" style="width:100%;">Run Data URI Tests</a>
|
||||
<a href="pages/device.html" class="btn large" style="width:100%;">Run Device Tests</a>
|
||||
<a href="pages/file.html" class="btn large" style="width:100%;">Run File Tests</a>
|
||||
<a href="pages/filetransfer.html" class="btn large" style="width:100%;">Run FileTransfer Tests</a>
|
||||
<a href="pages/geolocation.html" class="btn large" style="width:100%;">Run Geolocation Tests</a>
|
||||
<a href="pages/globalization.html" class="btn large" style="width:100%;">Run Globalization Tests</a>
|
||||
<a href="pages/media.html" class="btn large" style="width:100%;">Run Media Tests</a>
|
||||
<a href="pages/network.html" class="btn large" style="width:100%;">Run Network Tests</a>
|
||||
<a href="pages/notification.html" class="btn large" style="width:100%;">Run Notification Tests</a>
|
||||
<a href="pages/platform.html" class="btn large" style="width:100%;">Run Platform Tests</a>
|
||||
<a href="pages/storage.html" class="btn large" style="width:100%;">Run Storage Tests</a>
|
||||
<a href="pages/bridge.html" class="btn large" style="width:100%;">Run Bridge Tests</a>
|
||||
|
||||
<h2> </h2><div class="backBtn" onclick="backHome();">Back</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,81 +0,0 @@
|
||||
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
|
||||
|
||||
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
|
||||
#HTMLReporter a { text-decoration: none; }
|
||||
#HTMLReporter a:hover { text-decoration: underline; }
|
||||
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
|
||||
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
|
||||
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#HTMLReporter .version { color: #aaaaaa; }
|
||||
#HTMLReporter .banner { margin-top: 14px; }
|
||||
#HTMLReporter .duration { color: #aaaaaa; float: right; }
|
||||
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
|
||||
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
|
||||
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
|
||||
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
|
||||
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
|
||||
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
|
||||
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
|
||||
#HTMLReporter .runningAlert { background-color: #666666; }
|
||||
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
|
||||
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
|
||||
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
|
||||
#HTMLReporter .passingAlert { background-color: #a6b779; }
|
||||
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
|
||||
#HTMLReporter .failingAlert { background-color: #cf867e; }
|
||||
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
|
||||
#HTMLReporter .results { margin-top: 14px; }
|
||||
#HTMLReporter #details { display: none; }
|
||||
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .summary { display: none; }
|
||||
#HTMLReporter.showDetails #details { display: block; }
|
||||
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter .summary { margin-top: 14px; }
|
||||
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
|
||||
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
|
||||
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
|
||||
#HTMLReporter .description + .suite { margin-top: 0; }
|
||||
#HTMLReporter .suite { margin-top: 14px; }
|
||||
#HTMLReporter .suite a { color: #333333; }
|
||||
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
|
||||
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
|
||||
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
|
||||
#HTMLReporter .resultMessage span.result { display: block; }
|
||||
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
|
||||
|
||||
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
|
||||
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
|
||||
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
|
||||
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
|
||||
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
|
||||
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
|
||||
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
|
||||
#TrivialReporter .runner.running { background-color: yellow; }
|
||||
#TrivialReporter .options { text-align: right; font-size: .8em; }
|
||||
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
|
||||
#TrivialReporter .suite .suite { margin: 5px; }
|
||||
#TrivialReporter .suite.passed { background-color: #dfd; }
|
||||
#TrivialReporter .suite.failed { background-color: #fdd; }
|
||||
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
|
||||
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
|
||||
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
|
||||
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
|
||||
#TrivialReporter .spec.skipped { background-color: #bbb; }
|
||||
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
|
||||
#TrivialReporter .passed { background-color: #cfc; display: none; }
|
||||
#TrivialReporter .failed { background-color: #fbb; }
|
||||
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
|
||||
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
|
||||
#TrivialReporter .resultMessage .mismatch { color: black; }
|
||||
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
|
||||
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
|
||||
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
|
||||
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
|
||||
File diff suppressed because it is too large
Load Diff
BIN
test/autotest/pages/.DS_Store
vendored
BIN
test/autotest/pages/.DS_Store
vendored
Binary file not shown.
@@ -1,71 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Cordova: Camera API Specs</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, height=device-height, user-scalable=yes, initial-scale=1.0;" />
|
||||
|
||||
<!-- Load jasmine -->
|
||||
<link href="../jasmine.css" rel="stylesheet"/>
|
||||
<script type="text/javascript" src="../jasmine.js"></script>
|
||||
<script type="text/javascript" src="../html/HtmlReporterHelpers.js"></script>
|
||||
<script type="text/javascript" src="../html/HtmlReporter.js"></script>
|
||||
<script type="text/javascript" src="../html/ReporterView.js"></script>
|
||||
<script type="text/javascript" src="../html/SpecView.js"></script>
|
||||
<script type="text/javascript" src="../html/SuiteView.js"></script>
|
||||
<script type="text/javascript" src="../html/TrivialReporter.js"></script>
|
||||
|
||||
<!-- Source -->
|
||||
<script type="text/javascript" src="../../cordova-incl.js"></script>
|
||||
|
||||
<!-- Load Test Runner -->
|
||||
<script type="text/javascript" src="../test-runner.js"></script>
|
||||
|
||||
<!-- Tests -->
|
||||
<script type="text/javascript" src="../tests/camera.tests.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('deviceready', function () {
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 1000;
|
||||
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return htmlReporter.specFilter(spec);
|
||||
};
|
||||
|
||||
jasmineEnv.execute();
|
||||
}, false);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a href="javascript:" class="backBtn" onclick="backHome();">Back</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,62 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
if (window.sessionStorage != null) {
|
||||
window.sessionStorage.clear();
|
||||
}
|
||||
|
||||
// Timeout is 2 seconds to allow physical devices enough
|
||||
// time to query the response. This is important for some
|
||||
// Android devices.
|
||||
var Tests = function() {};
|
||||
Tests.TEST_TIMEOUT = 7500;
|
||||
|
||||
// Creates a spy that will fail if called.
|
||||
function createDoNotCallSpy(name, opt_extraMessage) {
|
||||
return jasmine.createSpy().andCallFake(function() {
|
||||
var errorMessage = name + ' should not have been called.';
|
||||
if (arguments.length) {
|
||||
errorMessage += ' Got args: ' + JSON.stringify(arguments);
|
||||
}
|
||||
if (opt_extraMessage) {
|
||||
errorMessage += '\n' + opt_extraMessage;
|
||||
}
|
||||
expect(false).toBe(true, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
// Waits for any of the given spys to be called.
|
||||
// Last param may be a custom timeout duration.
|
||||
function waitsForAny() {
|
||||
var spys = [].slice.call(arguments);
|
||||
var timeout = Tests.TEST_TIMEOUT;
|
||||
if (typeof spys[spys.length - 1] == 'number') {
|
||||
timeout = spys.pop();
|
||||
}
|
||||
waitsFor(function() {
|
||||
for (var i = 0; i < spys.length; ++i) {
|
||||
if (spys[i].wasCalled) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, "Expecting callbacks to be called.", timeout);
|
||||
}
|
||||
BIN
test/autotest/tests/.DS_Store
vendored
BIN
test/autotest/tests/.DS_Store
vendored
Binary file not shown.
@@ -1,70 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('Camera (navigator.camera)', function () {
|
||||
it("should exist", function() {
|
||||
expect(navigator.camera).toBeDefined();
|
||||
});
|
||||
|
||||
it("should contain a getPicture function", function() {
|
||||
expect(navigator.camera.getPicture).toBeDefined();
|
||||
expect(typeof navigator.camera.getPicture == 'function').toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Camera Constants (window.Camera + navigator.camera)', function () {
|
||||
it("camera.spec.1 window.Camera should exist", function() {
|
||||
expect(window.Camera).toBeDefined();
|
||||
});
|
||||
|
||||
it("camera.spec.2 should contain three DestinationType constants", function() {
|
||||
expect(Camera.DestinationType.DATA_URL).toBe(0);
|
||||
expect(Camera.DestinationType.FILE_URI).toBe(1);
|
||||
expect(Camera.DestinationType.NATIVE_URI).toBe(2);
|
||||
expect(navigator.camera.DestinationType.DATA_URL).toBe(0);
|
||||
expect(navigator.camera.DestinationType.FILE_URI).toBe(1);
|
||||
expect(navigator.camera.DestinationType.NATIVE_URI).toBe(2);
|
||||
});
|
||||
|
||||
it("camera.spec.3 should contain two EncodingType constants", function() {
|
||||
expect(Camera.EncodingType.JPEG).toBe(0);
|
||||
expect(Camera.EncodingType.PNG).toBe(1);
|
||||
expect(navigator.camera.EncodingType.JPEG).toBe(0);
|
||||
expect(navigator.camera.EncodingType.PNG).toBe(1);
|
||||
});
|
||||
|
||||
it("camera.spec.4 should contain three MediaType constants", function() {
|
||||
expect(Camera.MediaType.PICTURE).toBe(0);
|
||||
expect(Camera.MediaType.VIDEO).toBe(1);
|
||||
expect(Camera.MediaType.ALLMEDIA).toBe(2);
|
||||
expect(navigator.camera.MediaType.PICTURE).toBe(0);
|
||||
expect(navigator.camera.MediaType.VIDEO).toBe(1);
|
||||
expect(navigator.camera.MediaType.ALLMEDIA).toBe(2);
|
||||
});
|
||||
it("camera.spec.5 should contain three PictureSourceType constants", function() {
|
||||
expect(Camera.PictureSourceType.PHOTOLIBRARY).toBe(0);
|
||||
expect(Camera.PictureSourceType.CAMERA).toBe(1);
|
||||
expect(Camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2);
|
||||
expect(navigator.camera.PictureSourceType.PHOTOLIBRARY).toBe(0);
|
||||
expect(navigator.camera.PictureSourceType.CAMERA).toBe(1);
|
||||
expect(navigator.camera.PictureSourceType.SAVEDPHOTOALBUM).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,394 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width,height=device-height,user-scalable=no,maximum-scale=1.0,initial-scale=1.0" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8"> <!-- ISO-8859-1 -->
|
||||
<title>Cordova Mobile Spec</title>
|
||||
<link rel="stylesheet" href="../master.css" type="text/css" media="screen" title="no title" charset="utf-8">
|
||||
<script type="text/javascript" charset="utf-8" src="../cordova-incl.js"></script>
|
||||
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
var deviceReady = false;
|
||||
var platformId = null;
|
||||
var CameraPopoverOptions = null;
|
||||
var pictureUrl = null;
|
||||
var fileObj = null;
|
||||
var fileEntry = null;
|
||||
var pageStartTime = +new Date();
|
||||
|
||||
//default camera options
|
||||
var camQualityDefault = ['quality value', 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];
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Camera
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
function log(value) {
|
||||
console.log(value);
|
||||
document.getElementById('camera_status').textContent += (new Date() - pageStartTime) / 1000 + ': ' + value + '\n';
|
||||
}
|
||||
|
||||
function clearStatus() {
|
||||
document.getElementById('camera_status').innerHTML = '';
|
||||
document.getElementById('camera_image').src = 'about:blank';
|
||||
var canvas = document.getElementById('canvas');
|
||||
canvas.width = canvas.height = 1;
|
||||
pictureUrl = null;
|
||||
fileObj = null;
|
||||
fileEntry = null;
|
||||
}
|
||||
|
||||
function setPicture(url, callback) {
|
||||
try {
|
||||
window.atob(url);
|
||||
// if we got here it is a base64 string (DATA_URL)
|
||||
url = "data:image/jpeg;base64," + url;
|
||||
} catch (e) {
|
||||
// not DATA_URL
|
||||
log('URL: ' + url.slice(0, 100));
|
||||
}
|
||||
|
||||
pictureUrl = url;
|
||||
var img = document.getElementById('camera_image');
|
||||
var startTime = new Date();
|
||||
img.src = url;
|
||||
img.onloadend = function() {
|
||||
log('Image tag load time: ' + (new Date() - startTime));
|
||||
callback && callback();
|
||||
};
|
||||
}
|
||||
|
||||
function onGetPictureError(e) {
|
||||
log('Error getting picture: ' + e.code);
|
||||
}
|
||||
|
||||
function getPictureWin(data) {
|
||||
setPicture(data);
|
||||
// TODO: Fix resolveLocalFileSystemURI to work with native-uri.
|
||||
if (pictureUrl.indexOf('file:') == 0) {
|
||||
resolveLocalFileSystemURI(data, function(e) {
|
||||
fileEntry = e;
|
||||
logCallback('resolveLocalFileSystemURI()', true)(e);
|
||||
}, logCallback('resolveLocalFileSystemURI()', false));
|
||||
} else if (pictureUrl.indexOf('data:image/jpeg;base64' == 0)) {
|
||||
// do nothing
|
||||
} else {
|
||||
var path = pictureUrl.replace(/^file:\/\/(localhost)?/, '').replace(/%20/g, ' ');
|
||||
fileEntry = new FileEntry('image_name.png', path);
|
||||
}
|
||||
}
|
||||
|
||||
function getPicture() {
|
||||
clearStatus();
|
||||
var options = extractOptions();
|
||||
log('Getting picture with options: ' + JSON.stringify(options));
|
||||
var 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);
|
||||
popoverHandle.setPosition(newPopoverOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function uploadImage() {
|
||||
var ft = new FileTransfer(),
|
||||
uploadcomplete=0,
|
||||
progress = 0,
|
||||
options = new FileUploadOptions();
|
||||
options.fileKey="photo";
|
||||
options.fileName='test.jpg';
|
||||
options.mimeType="image/jpeg";
|
||||
ft.onprogress = function(progressEvent) {
|
||||
log('progress: ' + progressEvent.loaded + ' of ' + progressEvent.total);
|
||||
};
|
||||
var server = "http://cordova-filetransfer.jitsu.com";
|
||||
|
||||
ft.upload(pictureUrl, server + '/upload', win, fail, options);
|
||||
function win(information_back){
|
||||
log('upload complete');
|
||||
}
|
||||
function fail(message) {
|
||||
log('upload failed: ' + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function logCallback(apiName, success) {
|
||||
return function() {
|
||||
log('Call to ' + apiName + (success ? ' success: ' : ' failed: ') + JSON.stringify([].slice.call(arguments)));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Select image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.getMetadata, FileEntry.setMetadata, FileEntry.getParent, FileEntry.file, and FileReader.readAsDataURL.
|
||||
*/
|
||||
function readFile() {
|
||||
function onFileReadAsDataURL(evt) {
|
||||
var img = document.getElementById('camera_image');
|
||||
img.style.visibility = "visible";
|
||||
img.style.display = "block";
|
||||
img.src = evt.target.result;
|
||||
log("FileReader.readAsDataURL success");
|
||||
};
|
||||
|
||||
function onFileReceived(file) {
|
||||
log('Got file: ' + JSON.stringify(file));
|
||||
fileObj = file;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
log('FileReader.readAsDataURL() - length = ' + reader.result.length);
|
||||
};
|
||||
reader.onerror = logCallback('FileReader.readAsDataURL', false);
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
// Test out onFileReceived when the file object was set via a native <input> elements.
|
||||
if (fileObj) {
|
||||
onFileReceived(fileObj);
|
||||
} else {
|
||||
fileEntry.file(onFileReceived, logCallback('FileEntry.file', false));
|
||||
}
|
||||
}
|
||||
function getFileInfo() {
|
||||
// Test FileEntry API here.
|
||||
fileEntry.getMetadata(logCallback('FileEntry.getMetadata', true), logCallback('FileEntry.getMetadata', false));
|
||||
fileEntry.setMetadata(logCallback('FileEntry.setMetadata', true), logCallback('FileEntry.setMetadata', false), { "com.apple.MobileBackup": 1 });
|
||||
fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false));
|
||||
fileEntry.getParent(logCallback('FileEntry.getParent', true), logCallback('FileEntry.getParent', false));
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.copyTo and FileEntry.moveTo.
|
||||
*/
|
||||
function copyImage() {
|
||||
var onFileSystemReceived = function(fileSystem) {
|
||||
var destDirEntry = fileSystem.root;
|
||||
|
||||
// Test FileEntry API here.
|
||||
fileEntry.copyTo(destDirEntry, 'copied_file.png', logCallback('FileEntry.copyTo', true), logCallback('FileEntry.copyTo', false));
|
||||
fileEntry.moveTo(destDirEntry, 'moved_file.png', logCallback('FileEntry.moveTo', true), logCallback('FileEntry.moveTo', false));
|
||||
};
|
||||
|
||||
window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, onFileSystemReceived, null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Write image to library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.createWriter, FileWriter.write, and FileWriter.truncate.
|
||||
*/
|
||||
function writeImage() {
|
||||
var onFileWriterReceived = function(fileWriter) {
|
||||
fileWriter.onwrite = logCallback('FileWriter.write', true);
|
||||
fileWriter.onerror = logCallback('FileWriter.write', false);
|
||||
fileWriter.write("some text!");
|
||||
};
|
||||
|
||||
var onFileTruncateWriterReceived = function(fileWriter) {
|
||||
fileWriter.onwrite = logCallback('FileWriter.truncate', true);
|
||||
fileWriter.onerror = logCallback('FileWriter.truncate', false);
|
||||
fileWriter.truncate(10);
|
||||
};
|
||||
|
||||
fileEntry.createWriter(onFileWriterReceived, logCallback('FileEntry.createWriter', false));
|
||||
fileEntry.createWriter(onFileTruncateWriterReceived, null);
|
||||
};
|
||||
|
||||
function displayImageUsingCanvas() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var img = document.getElementById('camera_image');
|
||||
var w = img.width;
|
||||
var h = img.height;
|
||||
h = 100 / w * h;
|
||||
w = 100;
|
||||
canvas.width = w;
|
||||
canvas.height= h;
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(img, 0, 0, w, h);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove image from library using a NATIVE_URI destination type
|
||||
* This calls FileEntry.remove.
|
||||
*/
|
||||
function removeImage() {
|
||||
fileEntry.remove(logCallback('FileEntry.remove', true), logCallback('FileEntry.remove', false));
|
||||
};
|
||||
|
||||
function testInputTag(inputEl) {
|
||||
clearStatus();
|
||||
// iOS 6 likes to dead-lock in the onchange context if you
|
||||
// do any alerts or try to remote-debug.
|
||||
window.setTimeout(function() {
|
||||
testNativeFile2(inputEl);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
function testNativeFile2(inputEl) {
|
||||
if (!inputEl.value) {
|
||||
alert('No file selected.');
|
||||
return;
|
||||
}
|
||||
fileObj = inputEl.files[0];
|
||||
if (!fileObj) {
|
||||
alert('Got value but no file.');
|
||||
return;
|
||||
}
|
||||
var URLApi = window.URL || window.webkitURL;
|
||||
if (URLApi) {
|
||||
var blobURL = URLApi.createObjectURL(fileObj);
|
||||
if (blobURL) {
|
||||
setPicture(blobURL, function() {
|
||||
URLApi.revokeObjectURL(blobURL);
|
||||
});
|
||||
} else {
|
||||
log('URL.createObjectURL returned null');
|
||||
}
|
||||
} else {
|
||||
log('URL.createObjectURL() not supported.');
|
||||
}
|
||||
}
|
||||
|
||||
function extractOptions() {
|
||||
var els = document.querySelectorAll('#image-options select');
|
||||
var ret = {};
|
||||
for (var i = 0, el; el = els[i]; ++i) {
|
||||
var value = el.value;
|
||||
if (value === '') continue;
|
||||
if (el.isBool) {
|
||||
ret[el.keyName] = !!value;
|
||||
} else {
|
||||
ret[el.keyName] = +value;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function createOptionsEl(name, values, selectionDefault) {
|
||||
var container = document.createElement('div');
|
||||
container.style.display = 'inline-block';
|
||||
container.appendChild(document.createTextNode(name + ': '));
|
||||
var select = document.createElement('select');
|
||||
select.keyName = name;
|
||||
container.appendChild(select);
|
||||
|
||||
// if we didn't get a default value, insert the blank <default> entry
|
||||
if (selectionDefault == undefined) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.text = '<default>';
|
||||
select.appendChild(opt);
|
||||
}
|
||||
|
||||
select.isBool = typeof values == 'boolean';
|
||||
if (select.isBool) {
|
||||
values = {'true': 1, 'false': 0};
|
||||
}
|
||||
|
||||
for (var k in values) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = values[k];
|
||||
opt.textContent = k;
|
||||
if (selectionDefault) {
|
||||
if (selectionDefault[0] == k) {
|
||||
opt.selected = true;
|
||||
}
|
||||
}
|
||||
select.appendChild(opt);
|
||||
}
|
||||
var optionsDiv = document.getElementById('image-options');
|
||||
optionsDiv.appendChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when page has finished loading.
|
||||
*/
|
||||
function init() {
|
||||
document.addEventListener("deviceready", function() {
|
||||
deviceReady = true;
|
||||
platformId = cordova.require('cordova/platform').id;
|
||||
CameraPopoverOptions = cordova.require('org.apache.cordova.camera.CameraPopoverOptions');
|
||||
console.log("Device="+device.platform+" "+device.version);
|
||||
createOptionsEl('sourceType', Camera.PictureSourceType, camPictureSourceTypeDefault);
|
||||
createOptionsEl('destinationType', Camera.DestinationType, camDestinationTypeDefault);
|
||||
createOptionsEl('encodingType', Camera.EncodingType, camEncodingTypeDefault);
|
||||
createOptionsEl('mediaType', Camera.MediaType, camMediaTypeDefault);
|
||||
createOptionsEl('quality', {'0': 0, '50': 50, '80': 80, '100': 100}, camQualityDefault);
|
||||
createOptionsEl('targetWidth', {'50': 50, '200': 200, '800': 800, '2048': 2048});
|
||||
createOptionsEl('targetHeight', {'50': 50, '200': 200, '800': 800, '2048': 2048});
|
||||
createOptionsEl('allowEdit', true, camAllowEditDefault);
|
||||
createOptionsEl('correctOrientation', true, camCorrectOrientationDefault);
|
||||
createOptionsEl('saveToPhotoAlbum', true, camSaveToPhotoAlbumDefault);
|
||||
createOptionsEl('cameraDirection', Camera.Direction);
|
||||
|
||||
}, false);
|
||||
window.setTimeout(function() {
|
||||
if (!deviceReady) {
|
||||
alert("Error: Apache Cordova did not initialize. Demo will not run correctly.");
|
||||
}
|
||||
},1000);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body onload="init();" id="stage" class="theme">
|
||||
|
||||
<h1>Camera</h1>
|
||||
<div id="info" style="white-space: pre-wrap">
|
||||
<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>
|
||||
<h2>Cordova Camera API</h2>
|
||||
<div id="image-options"></div>
|
||||
<div class="btn large" onclick="getPicture();">camera.getPicture()</div>
|
||||
<h2>Native File Inputs</h2>
|
||||
<div>input type=file <input type="file" onchange="testInputTag(this)"></div>
|
||||
<div>capture=camera <input type="file" accept="image/*;capture=camera" onchange="testInputTag(this)"></div>
|
||||
<div>capture=camcorder <input type="file" accept="video/*;capture=camcorder" onchange="testInputTag(this)"></div>
|
||||
<div>capture=microphone <input type="file" accept="audio/*;capture=microphone" onchange="testInputTag(this)"></div>
|
||||
<h2>Actions</h2>
|
||||
<div class="btn large" onclick="getFileInfo();">Get File Metadata</div>
|
||||
<div class="btn large" onclick="readFile();">Read with FileReader</div>
|
||||
<div class="btn large" onclick="copyImage();">Copy Image</div>
|
||||
<div class="btn large" onclick="writeImage();">Write Image</div>
|
||||
<div class="btn large" onclick="uploadImage();">Upload Image</div>
|
||||
<div class="btn large" onclick="displayImageUsingCanvas();">Draw Using Canvas</div>
|
||||
<div class="btn large" onclick="removeImage();">Remove Image</div>
|
||||
<h2> </h2><div class="backBtn" onclick="backHome();">Back</div>
|
||||
</body>
|
||||
</html>
|
||||
72
test/cordova-incl.js
vendored
72
test/cordova-incl.js
vendored
@@ -1,72 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
var PLAT;
|
||||
if (/cordova-amazon-fireos/.exec(navigator.userAgent)) {
|
||||
PLAT = 'amazon-fireos';
|
||||
}else if (/Android/.exec(navigator.userAgent)) {
|
||||
PLAT = 'android';
|
||||
} else if (/(iPad)|(iPhone)|(iPod)/.exec(navigator.userAgent)) {
|
||||
PLAT = 'ios';
|
||||
} else if (/(BB10)|(PlayBook)|(BlackBerry)/.exec(navigator.userAgent)) {
|
||||
PLAT = 'blackberry';
|
||||
}
|
||||
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var currentPath = scripts[scripts.length - 1].src;
|
||||
var platformCordovaPath = currentPath.replace("cordova-incl.js", "cordova." + PLAT + ".js");
|
||||
var normalCordovaPath = currentPath.replace("cordova-incl.js", "cordova.js");
|
||||
var cordovaPath = normalCordovaPath;
|
||||
|
||||
if (PLAT) {
|
||||
// XHR to local file is an error on some platforms, windowsphone for one
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", platformCordovaPath, false);
|
||||
xhr.onreadystatechange = function() {
|
||||
|
||||
if (this.readyState == this.DONE && this.responseText.length > 0) {
|
||||
if(parseInt(this.status) >= 400){
|
||||
cordovaPath = normalCordovaPath;
|
||||
}else{
|
||||
cordovaPath = platformCordovaPath;
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
catch(e){
|
||||
cordovaPath = normalCordovaPath;
|
||||
} // access denied!
|
||||
}
|
||||
|
||||
if (!window._doNotWriteCordovaScript) {
|
||||
document.write('<script type="text/javascript" charset="utf-8" src="' + cordovaPath + '"></script>');
|
||||
}
|
||||
|
||||
function backHome() {
|
||||
if (window.device && device.platform && (device.platform.toLowerCase() == 'android' || device.platform.toLowerCase() == 'amazon-fireos')) {
|
||||
navigator.app.backHistory();
|
||||
}
|
||||
else {
|
||||
window.history.go(-1);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width,height=device-height,user-scalable=no,initial-scale=1.0" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Cordova Mobile Spec</title>
|
||||
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8">
|
||||
<script type="text/javascript" charset="utf-8" src="cordova-incl.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="main.js"></script>
|
||||
|
||||
</head>
|
||||
<body onload="init();" id="stage" class="theme">
|
||||
<h1>Apache Cordova Tests</h1>
|
||||
<div id="info">
|
||||
<h4>Platform: <span id="platform"> </span></h4>
|
||||
<h4>Version: <span id="version"> </span></h4>
|
||||
<h4>UUID: <span id="uuid"> </span></h4>
|
||||
<h4>Name: <span id="name"> </span></h4>
|
||||
<h4>Model: <span id="model"> </span></h4>
|
||||
<h4>Width: <span id="width"> </span>, Height: <span id="height">
|
||||
</span>, Color Depth: <span id="colorDepth"></span></h4>
|
||||
<h4>User-Agent: <span id="user-agent"> </span></h4>
|
||||
</div>
|
||||
<a href="autotest/index.html" class="btn large">Automatic Test</a>
|
||||
<a href="accelerometer/index.html" class="btn large">Accelerometer</a>
|
||||
<a href="audio/index.html" class="btn large">Audio Play/Record</a>
|
||||
<a href="battery/index.html" class="btn large">Battery</a>
|
||||
<a href="camera/index.html" class="btn large">Camera</a>
|
||||
<a href="compass/index.html" class="btn large">Compass</a>
|
||||
<a href="contacts/index.html" class="btn large">Contacts</a>
|
||||
<a href="events/index.html" class="btn large">Events</a>
|
||||
<a href="location/index.html" class="btn large">Location</a>
|
||||
<a href="lazyloadjs/index.html" class="btn large">Lazy Loading of cordova-incl.js</a>
|
||||
<a href="misc/index.html" class="btn large">Misc Content</a>
|
||||
<a href="network/index.html" class="btn large">Network</a>
|
||||
<a href="notification/index.html" class="btn large">Notification</a>
|
||||
<a href="splashscreen/index.html" class="btn large">Splashscreen</a>
|
||||
<a href="sql/index.html" class="btn large">Web SQL</a>
|
||||
<a href="storage/index.html" class="btn large">Local Storage</a>
|
||||
<a href="benchmarks/index.html" class="btn large">Benchmarks</a>
|
||||
<a href="inappbrowser/index.html" class="btn large">In App Browser</a>
|
||||
</body>
|
||||
</html>
|
||||
163
test/main.js
163
test/main.js
@@ -1,163 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
var deviceInfo = function() {
|
||||
document.getElementById("platform").innerHTML = device.platform;
|
||||
document.getElementById("version").innerHTML = device.version;
|
||||
document.getElementById("uuid").innerHTML = device.uuid;
|
||||
document.getElementById("name").innerHTML = device.name;
|
||||
document.getElementById("model").innerHTML = device.model;
|
||||
document.getElementById("width").innerHTML = screen.width;
|
||||
document.getElementById("height").innerHTML = screen.height;
|
||||
document.getElementById("colorDepth").innerHTML = screen.colorDepth;
|
||||
};
|
||||
|
||||
var getLocation = function() {
|
||||
var suc = function(p) {
|
||||
alert(p.coords.latitude + " " + p.coords.longitude);
|
||||
};
|
||||
var locFail = function() {
|
||||
};
|
||||
navigator.geolocation.getCurrentPosition(suc, locFail);
|
||||
};
|
||||
|
||||
var beep = function() {
|
||||
navigator.notification.beep(2);
|
||||
};
|
||||
|
||||
var vibrate = function() {
|
||||
navigator.notification.vibrate(0);
|
||||
};
|
||||
|
||||
function roundNumber(num) {
|
||||
var dec = 3;
|
||||
var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
|
||||
return result;
|
||||
}
|
||||
|
||||
var accelerationWatch = null;
|
||||
|
||||
function updateAcceleration(a) {
|
||||
document.getElementById('x').innerHTML = roundNumber(a.x);
|
||||
document.getElementById('y').innerHTML = roundNumber(a.y);
|
||||
document.getElementById('z').innerHTML = roundNumber(a.z);
|
||||
}
|
||||
|
||||
var toggleAccel = function() {
|
||||
if (accelerationWatch !== null) {
|
||||
navigator.accelerometer.clearWatch(accelerationWatch);
|
||||
updateAcceleration({
|
||||
x : "",
|
||||
y : "",
|
||||
z : ""
|
||||
});
|
||||
accelerationWatch = null;
|
||||
} else {
|
||||
var options = {};
|
||||
options.frequency = 1000;
|
||||
accelerationWatch = navigator.accelerometer.watchAcceleration(
|
||||
updateAcceleration, function(ex) {
|
||||
alert("accel fail (" + ex.name + ": " + ex.message + ")");
|
||||
}, options);
|
||||
}
|
||||
};
|
||||
|
||||
var preventBehavior = function(e) {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
function dump_pic(data) {
|
||||
var viewport = document.getElementById('viewport');
|
||||
console.log(data);
|
||||
viewport.style.display = "";
|
||||
viewport.style.position = "absolute";
|
||||
viewport.style.top = "10px";
|
||||
viewport.style.left = "10px";
|
||||
document.getElementById("test_img").src = "data:image/jpeg;base64," + data;
|
||||
}
|
||||
|
||||
function fail(msg) {
|
||||
alert(msg);
|
||||
}
|
||||
|
||||
function show_pic() {
|
||||
navigator.camera.getPicture(dump_pic, fail, {
|
||||
quality : 50
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
var viewport = document.getElementById('viewport');
|
||||
viewport.style.position = "relative";
|
||||
viewport.style.display = "none";
|
||||
}
|
||||
|
||||
// This is just to do this.
|
||||
function readFile() {
|
||||
navigator.file.read('/sdcard/cordova.txt', fail, fail);
|
||||
}
|
||||
|
||||
function writeFile() {
|
||||
navigator.file.write('foo.txt', "This is a test of writing to a file",
|
||||
fail, fail);
|
||||
}
|
||||
|
||||
function contacts_success(contacts) {
|
||||
alert(contacts.length
|
||||
+ ' contacts returned.'
|
||||
+ (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted)
|
||||
: ''));
|
||||
}
|
||||
|
||||
function get_contacts() {
|
||||
var obj = new ContactFindOptions();
|
||||
obj.filter = "";
|
||||
obj.multiple = true;
|
||||
obj.limit = 5;
|
||||
navigator.service.contacts.find(
|
||||
[ "displayName", "name" ], contacts_success,
|
||||
fail, obj);
|
||||
}
|
||||
|
||||
var networkReachableCallback = function(reachability) {
|
||||
// There is no consistency on the format of reachability
|
||||
var networkState = reachability.code || reachability;
|
||||
|
||||
var currentState = {};
|
||||
currentState[NetworkStatus.NOT_REACHABLE] = 'No network connection';
|
||||
currentState[NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK] = 'Carrier data connection';
|
||||
currentState[NetworkStatus.REACHABLE_VIA_WIFI_NETWORK] = 'WiFi connection';
|
||||
|
||||
confirm("Connection type:\n" + currentState[networkState]);
|
||||
};
|
||||
|
||||
function check_network() {
|
||||
navigator.network.isReachable("www.mobiledevelopersolutions.com",
|
||||
networkReachableCallback, {});
|
||||
}
|
||||
|
||||
function init() {
|
||||
// the next line makes it impossible to see Contacts on the HTC Evo since it
|
||||
// doesn't have a scroll button
|
||||
// document.addEventListener("touchmove", preventBehavior, false);
|
||||
document.addEventListener("deviceready", deviceInfo, true);
|
||||
document.getElementById("user-agent").textContent = navigator.userAgent;
|
||||
}
|
||||
164
test/master.css
164
test/master.css
@@ -1,164 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
body {
|
||||
background:#222 none repeat scroll 0 0;
|
||||
color:#666;
|
||||
font-family:Helvetica;
|
||||
font-size:72%;
|
||||
line-height:1.5em;
|
||||
margin:0;
|
||||
border-top:1px solid #393939;
|
||||
}
|
||||
|
||||
#info{
|
||||
background:#ffa;
|
||||
border: 1px solid #ffd324;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
clear:both;
|
||||
margin:15px 6px 0;
|
||||
min-width:295px;
|
||||
max-width:97%;
|
||||
padding:4px 0px 2px 10px;
|
||||
word-wrap:break-word;
|
||||
margin-bottom:10px;
|
||||
display:inline-block;
|
||||
min-height: 160px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
#info > h4{
|
||||
font-size:.95em;
|
||||
margin:5px 0;
|
||||
}
|
||||
|
||||
#stage.theme{
|
||||
padding-top:3px;
|
||||
}
|
||||
|
||||
/* Definition List */
|
||||
#stage.theme > dl{
|
||||
padding-top:10px;
|
||||
clear:both;
|
||||
margin:0;
|
||||
list-style-type:none;
|
||||
padding-left:10px;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
#stage.theme > dl > dt{
|
||||
font-weight:bold;
|
||||
float:left;
|
||||
margin-left:5px;
|
||||
}
|
||||
|
||||
#stage.theme > dl > dd{
|
||||
width:45px;
|
||||
float:left;
|
||||
color:#a87;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
/* Content Styling */
|
||||
#stage.theme > h1, #stage.theme > h2, #stage.theme > p{
|
||||
margin:1em 0 .5em 13px;
|
||||
}
|
||||
|
||||
#stage.theme > h1{
|
||||
color:#eee;
|
||||
font-size:1.6em;
|
||||
text-align:center;
|
||||
margin:0;
|
||||
margin-top:15px;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#stage.theme > h2{
|
||||
clear:both;
|
||||
margin:0;
|
||||
padding:3px;
|
||||
font-size:1em;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
/* Stage Buttons */
|
||||
#stage.theme .btn{
|
||||
border: 1px solid #555;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-align:center;
|
||||
display:inline-block;
|
||||
background:#444;
|
||||
width:150px;
|
||||
color:#9ab;
|
||||
font-size:1.1em;
|
||||
text-decoration:none;
|
||||
padding:1.2em 0;
|
||||
margin:3px 0px 3px 5px;
|
||||
}
|
||||
|
||||
#stage.theme .large{
|
||||
width:308px;
|
||||
padding:1.2em 0;
|
||||
}
|
||||
|
||||
#stage.theme .wide{
|
||||
width:100%;
|
||||
padding:1.2em 0;
|
||||
}
|
||||
|
||||
#stage.theme .backBtn{
|
||||
border: 1px solid #555;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-align:center;
|
||||
display:block;
|
||||
float:right;
|
||||
background:#666;
|
||||
width:75px;
|
||||
color:#9ab;
|
||||
font-size:1.1em;
|
||||
text-decoration:none;
|
||||
padding:1.2em 0;
|
||||
margin:3px 5px 3px 5px;
|
||||
}
|
||||
|
||||
#stage.theme .input{
|
||||
border: 1px solid #555;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-align:center;
|
||||
display:block;
|
||||
float:light;
|
||||
background:#888;
|
||||
color:#9cd;
|
||||
font-size:1.1em;
|
||||
text-decoration:none;
|
||||
padding:1.2em 0;
|
||||
margin:3px 0px 3px 5px;
|
||||
}
|
||||
|
||||
#stage.theme .numeric{
|
||||
width:100%;
|
||||
}
|
||||
@@ -1,75 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
var argscheck = require('cordova/argscheck'),
|
||||
exec = require('cordova/exec'),
|
||||
Camera = require('./Camera');
|
||||
// XXX: commented out
|
||||
//CameraPopoverHandle = require('./CameraPopoverHandle');
|
||||
|
||||
var cameraExport = {};
|
||||
|
||||
// Tack on the Camera Constants to the base camera plugin.
|
||||
for (var key in Camera) {
|
||||
cameraExport[key] = Camera[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a picture from source defined by "options.sourceType", and returns the
|
||||
* image as defined by the "options.destinationType" option.
|
||||
|
||||
* The defaults are sourceType=CAMERA and destinationType=FILE_URI.
|
||||
*
|
||||
* @param {Function} successCallback
|
||||
* @param {Function} errorCallback
|
||||
* @param {Object} options
|
||||
*/
|
||||
cameraExport.getPicture = function(successCallback, errorCallback, options) {
|
||||
argscheck.checkArgs('fFO', 'Camera.getPicture', arguments);
|
||||
options = options || {};
|
||||
var 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);
|
||||
|
||||
var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
|
||||
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
|
||||
|
||||
exec(successCallback, errorCallback, "Camera", "takePicture", args);
|
||||
// XXX: commented out
|
||||
//return new CameraPopoverHandle();
|
||||
};
|
||||
|
||||
cameraExport.cleanup = function(successCallback, errorCallback) {
|
||||
exec(successCallback, errorCallback, "Camera", "cleanup", []);
|
||||
};
|
||||
|
||||
module.exports = cameraExport;
|
||||
@@ -1,53 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
DestinationType:{
|
||||
DATA_URL: 0, // Return base64 encoded string
|
||||
FILE_URI: 1, // Return file uri (content://media/external/images/media/2 for Android)
|
||||
NATIVE_URI: 2 // Return native uri (eg. asset-library://... for iOS)
|
||||
},
|
||||
EncodingType:{
|
||||
JPEG: 0, // Return JPEG encoded image
|
||||
PNG: 1 // Return PNG encoded image
|
||||
},
|
||||
MediaType:{
|
||||
PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
|
||||
VIDEO: 1, // allow selection of video only, ONLY RETURNS URL
|
||||
ALLMEDIA : 2 // allow selection from all media types
|
||||
},
|
||||
PictureSourceType:{
|
||||
PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
|
||||
CAMERA : 1, // Take picture from camera
|
||||
SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android)
|
||||
},
|
||||
PopoverArrowDirection:{
|
||||
ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants to specify arrow location on popover
|
||||
ARROW_DOWN : 2,
|
||||
ARROW_LEFT : 4,
|
||||
ARROW_RIGHT : 8,
|
||||
ARROW_ANY : 15
|
||||
},
|
||||
Direction:{
|
||||
BACK: 0,
|
||||
FRONT: 1
|
||||
}
|
||||
};
|
||||
@@ -1,33 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
|
||||
/**
|
||||
* A handle to an image picker popover.
|
||||
*/
|
||||
var CameraPopoverHandle = function() {
|
||||
this.setPosition = function(popoverOptions) {
|
||||
console.log('CameraPopoverHandle.setPosition is only supported on iOS.');
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = CameraPopoverHandle;
|
||||
@@ -1,37 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
var Camera = require('./Camera');
|
||||
|
||||
/**
|
||||
* Encapsulates options for iOS Popover image picker
|
||||
*/
|
||||
var CameraPopoverOptions = function(x,y,width,height,arrowDir){
|
||||
// information of rectangle that popover should be anchored to
|
||||
this.x = x || 0;
|
||||
this.y = y || 32;
|
||||
this.width = width || 320;
|
||||
this.height = height || 480;
|
||||
// The direction of the popover arrow
|
||||
this.arrowDir = arrowDir || Camera.PopoverArrowDirection.ARROW_ANY;
|
||||
};
|
||||
|
||||
module.exports = CameraPopoverOptions;
|
||||
@@ -1,34 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
|
||||
/**
|
||||
* A handle to an image picker popover.
|
||||
*/
|
||||
var CameraPopoverHandle = function() {
|
||||
this.setPosition = function(popoverOptions) {
|
||||
var args = [ popoverOptions ];
|
||||
exec(null, null, "Camera", "repositionPopover", args);
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = CameraPopoverHandle;
|
||||
Reference in New Issue
Block a user