Compare commits

..

178 Commits

Author SHA1 Message Date
Ali Asadi 9454f3e4d1 Add TopCrop option to ScaleType (#372) 2021-12-10 10:53:46 +01:00
Ilia Voitcekhovskii 7094f9850a Fix unused inSampleSize and add scaling to decoding step (#359)
* Fix bitmap decoding to use inSampleSize

* Add scaling (by max side) while decoding bitmap
2021-12-10 10:51:01 +01:00
John 7039cf2059 Pinch to zoom (#388)
* Update gradle and libs

* Implemented pinch to zoom in example app
2021-12-10 10:50:19 +01:00
zakimiiiii 09a551635c Modified file to correct name (#378) 2019-07-02 14:15:09 +02:00
Dionysis 72c7c81a42 Release v2.7.0 2019-03-02 16:44:27 +01:00
Dionysis a7287e363c Remove wrong imports 2019-03-02 16:41:39 +01:00
Dionysis 6156f4c897 Merge remote-tracking branch 'origin/master' 2019-03-02 16:40:48 +01:00
Dionysis 29be38b88b Fixes #191 2019-03-02 16:40:38 +01:00
Dionysis 69d99e1aea Fixes #191 2019-03-02 16:40:12 +01:00
Nicklas Ansman Giertz af9e8158b8 Remove lazy delegates where they aren't needed (#349) 2019-03-02 14:21:53 +01:00
Dionysis 4494d76bb1 Merge branch 'master' of github.com:RedApparat/Fotoapparat 2019-03-02 14:20:11 +01:00
Dionysis f1830853c3 Merge branch 'ansman-fix/pending-result-notify' 2019-03-02 14:19:32 +01:00
Dionysis 75181ecb92 Merge branch 'fix/pending-result-notify' of https://github.com/ansman/Fotoapparat into ansman-fix/pending-result-notify 2019-03-02 14:19:13 +01:00
Nicklas Ansman Giertz 697f996d17 Make Photo.toString not include the entire byte array (#351) 2019-03-02 14:14:31 +01:00
Dionysis Lorentzos 1783058ade Fixes orientation issues (#354)
* fix: preview orientation in sensorLandscape

* Fix orientation issues.

Fixes #262
Fixes #260
Fixes #253
Fixes #193
2019-03-02 14:13:18 +01:00
Alaeri f78c324f23 fix: preview orientation in sensorLandscape (#352) 2019-03-02 14:10:50 +01:00
Nicklas Ansman Giertz b1f6f491cd Make all the configuration objects have nicer toString values (#348) 2019-03-02 12:32:19 +01:00
Nicklas Ansman Giertz 46fe442a4e Always notify the callback in PendingResult
Previously it wouldn't be notified in some cases and in some cases
it would be notified off the main thread.
2019-02-21 13:06:30 -05:00
Wolfgang Schreurs 27c9a7a910 Set source and target compatability to Java 1.8 for library and dependencies (#343) 2019-02-20 18:33:08 +01:00
Nicklas Ansman Giertz f05fc12039 Fix a crash when trying to access with size of a Photo (#347)
* Fix a crash when trying to access the size of a Photo
2019-02-20 18:32:01 +01:00
Santi Aguilera b37c3df8c9 Remove app_name from library (#334) 2019-01-24 21:02:29 +01:00
Dick Lucas 353e8a326b Update gradle version (#338)
Using this library in our project forces us to use Gradle version 3.2.1 in order to match Fotoapparat. This is because if they are not matching, it causes an issue generating the R file.
2019-01-24 21:01:51 +01:00
Dionysis Lorentzos 9021507995 Auto upload on tags (#317)
* Deploy on tags

* Bump version to 2.6.1
2018-11-08 16:25:48 +01:00
dionysis 4a23a70565 Bump version to 2.6.0 2018-11-06 14:54:48 +01:00
Dionysis Lorentzos 9104a316ee Update README.md 2018-11-06 14:43:32 +01:00
Dionysis Lorentzos 33ce7fa483 Remove kotlin.experimental.coroutines 'enable' 2018-11-06 14:29:05 +01:00
Rafael Toledo 45368b3691 Migrates project to AndroidX and Kotlin 1.3 2018-11-06 14:29:05 +01:00
Dmitry Zaytsev 65023340e5 Update build tools (#307)
* Update build tools

* Updated Travis confi
2018-10-13 11:07:25 +02:00
Dima Zaytsev ede7e4cbbe Updated build tools 2018-10-13 10:41:52 +02:00
Dmitry Zaytsev 3bbec9b6d8 Removed mention of Videoapparat 2018-10-11 00:13:53 +02:00
Dima Zaytsev ec6cdf3d66 Bumped version to 2.5.0 2018-10-11 00:05:17 +02:00
Paul Woitaschek cb4c8d1ee1 Updated Coroutines (#306)
* Updated gradle to 4.10.2

* Updated kotlin to 1.2.71

* Updated coroutines to 0.30.2

* Overwrite cancel and delegate to the channel.

* Inline the cancel call.

* Handle the cancellation of the deferred value too.
2018-10-11 00:02:50 +02:00
Dima Zaytsev 245d74fb46 Reusing artifact version variable. Bumped version to 2.4.0 2018-09-09 22:10:17 +02:00
Ivan Martinović b67ee40be2 FocusView fix - focus is now initiated on tap instead on ACTION_DOWN event type (#292) 2018-09-09 21:59:18 +02:00
Ivan Martinović 66c7489708 Upgrade Zoom.VariableZoom to contain list of zoom ratios supported by camera (#291) 2018-09-09 21:57:45 +02:00
Dima Zaytsev 96fc39bd84 Updated maven gradle plugin 2018-08-13 23:47:03 +02:00
Dima Zaytsev c82b628b44 Fixed build issues in 2.3.2. Updated the version 2018-08-13 23:39:59 +02:00
Dima Zaytsev 7ee03f1a8c Updated README 2018-08-13 22:50:12 +02:00
Dima Zaytsev 816d4da6aa Updated version to 2.3.2 2018-08-13 22:49:10 +02:00
Dmitry Zaytsev 4fc20316d0 Using null FrameProcessor by default to avoid resource consumption (#284) 2018-08-13 22:45:24 +02:00
Dima Zaytsev 3b17c030f5 Fixed Bintray configuration for RxJava 1/2 adapters. Updated README. 2018-08-13 21:28:42 +02:00
Dima Zaytsev 0c17e2bf29 Fixed configuration to make it suitable for upload to jCenter 2018-08-10 16:19:03 +02:00
Dima Zaytsev 8189847088 Added Bintray configuration 2018-08-08 21:14:53 +02:00
dionysis 36670caf02 v2.3.1 2018-07-05 21:47:59 +02:00
Ahmed Hegazy 36d5a038ce Remove configureondemand gradle configuration. Fixes #269 2018-07-05 16:20:07 +02:00
dionysis 7c5878259d Fix failing configuration 2018-07-04 15:34:01 +02:00
dionysis 4b6c0447d3 Release v2.3.0 2018-07-04 14:54:23 +02:00
dionysis 7a04a38872 Update dependencies 2018-07-04 14:54:11 +02:00
Dionysis Lorentzos 18d6ae38ad Update README.md 2018-07-03 12:18:14 +02:00
Dmitry Zaytsev 767aae82fd Merge pull request #237 from radosdesign/master
CameraDevice: Release surface when closing camera device.
2018-05-29 19:57:34 +02:00
radoslav.dodek 61742958a0 CameraDevice: Release surface when closing camera device. 2018-04-03 20:20:38 +02:00
Dmitry Zaytsev bc8c122323 Merge pull request #219 from kphil/patch-1
Update README.md
2018-03-26 14:24:51 +02:00
Sergejs Luhmirins 982c7b8548 Add special handling for external lens positions (Fixes #224) 2018-03-14 09:39:27 +01:00
Rajat Kumar Gupta 5abcbf7e54 Update README.md 2018-03-10 15:15:42 +01:00
kphil 4f78de91e0 Update README.md 2018-03-09 18:17:27 +01:00
Diederik 1e247aab22 Review feedback changes.
Missed annotation mistakenly taken out.
2018-02-19 21:21:32 +01:00
Diederik 919e4a0262 Review feedback changes. 2018-02-19 21:21:32 +01:00
Diederik Hattingh 0d4c51f6be Added focus view to java example 2018-02-19 21:21:32 +01:00
Pavel Strelchenko ea4e1bac6e Add docs + remove min zoom level as it is always 0. 2018-02-17 12:03:41 +01:00
Pavel Strelchenko 9621cc986b Replace "maxZoom" and "canZoom" parameters with single "zoom" value, which contains object with min and max zoom level, if requested camera supports zoom, or "FixedZoom" object otherwise. 2018-02-17 12:03:41 +01:00
Pavel Strelchenko 5e1ed00cfe Add "max zoom" value into camera capabilities. 2018-02-17 12:03:41 +01:00
Dionysis Lorentzos 9a4fc6bb4e Update README.md 2018-02-06 23:50:36 +01:00
Dionysis Lorentzos 67b07dc1e8 Update README.md 2018-02-06 20:02:23 +01:00
Dionysis Lorentzos 962866f405 Update README.md 2018-02-06 20:00:50 +01:00
Dionysis Lorentzos 3f25da7449 Update README.md 2018-02-06 19:59:51 +01:00
Dionysis Lorentzos a6bb1a5c63 Update README.md 2018-02-02 17:44:30 +01:00
AndrewCKing 8db7e7f8b3 Fix naming conventions for exposure 2018-02-02 11:35:34 +01:00
AndrewCKing c2a97f18f5 Support exposure compensation 2018-02-02 11:35:34 +01:00
Dionysis 799395ae38 Use cache parameters because they are less expensive 2018-02-02 11:25:47 +01:00
Dionysis a17e601428 No need to cache the parameters. This will actually create more issues 2018-02-02 11:25:47 +01:00
Dionysis d89adb8134 Fixes #196 2018-02-01 14:21:38 +01:00
Dionysis Lorentzos 9a8bbb1021 Update README.md 2018-01-20 22:17:00 +01:00
Dmitry Zaytsev b55ebc6c68 Catching IOException when assigning display surface 2018-01-20 22:04:31 +01:00
Dmitry Zaytsev 631d216b13 Merge pull request #187 from Fotoapparat/revert_callbacks
Add callback for java only users in builder
2018-01-20 21:08:59 +01:00
Dmitry Zaytsev a31a856b55 Merge pull request #188 from Fotoapparat/fix/camera-threading
Using only one thread for operating with camera
2018-01-20 20:53:52 +01:00
Dmitry Zaytsev ff265c5c73 Merge branch 'master' of github.com:Fotoapparat/Fotoapparat into fix/camera-threading 2018-01-20 18:41:50 +01:00
Dmitry Zaytsev 78f5ad88c2 Making sure that task queue does not remain polluted with tasks. 2018-01-20 18:39:47 +01:00
Dmitry Zaytsev 35e30c12fe Properly canceling tasks. 2018-01-20 18:33:26 +01:00
Dmitry Zaytsev d0fec5f5b9 Using CameraExecutor in routines 2018-01-20 12:54:51 +01:00
Dmitry Zaytsev 7233866fcc Implemented CameraExecutor 2018-01-20 12:39:57 +01:00
Dmitry Zaytsev c7e3c01b2f Removed task executor 2018-01-20 12:22:57 +01:00
Dionysis 3de625f2cd Revert changes 2018-01-19 19:45:55 +01:00
Dionysis 72dcec0470 Unassign focus listener on stop. Fixes #185 2018-01-19 17:58:43 +01:00
Dionysis 8104644cfb Add callback for java only users in builder 2018-01-19 17:56:46 +01:00
Dionysis 58ffc4f9ea Add forgotten var assignments. Fixes #183 2018-01-19 17:04:06 +01:00
Dmitry Zaytsev 31d48c6154 Merge branch 'master' of github.com:Fotoapparat/Fotoapparat 2018-01-18 18:26:30 +01:00
Dmitry Zaytsev db791d9259 Using stable version of gradle plugin 2018-01-18 18:26:21 +01:00
Dionysis Lorentzos 82752f13c6 Merge pull request #180 from Fotoapparat/feature/rotation
Even if the screen orientation is locked, the device will render corr…
2018-01-18 17:21:10 +01:00
Dionysis 8214559d98 Improve logs in pending result 2018-01-18 12:55:52 +01:00
Dionysis 32e479cc7b Add infinity in default camera configuration 2018-01-18 12:31:47 +01:00
Dionysis 262aa4ed9b Merge branch 'master' into feature/rotation
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/hardware/CameraDevice.kt
#	fotoapparat/src/main/java/io/fotoapparat/hardware/Device.kt
#	fotoapparat/src/main/java/io/fotoapparat/preview/PreviewStream.kt
2018-01-11 23:06:32 +01:00
Dionysis 762fae3d22 Even if the screen orientation is locked, the device will render correctly the image rotation. 2018-01-11 23:01:36 +01:00
Dionysis Lorentzos d8c5cb6ada Merge pull request #176 from dewarder/fix-codestyle
Add typealiases. Codestyle improvements.
2018-01-11 21:20:50 +01:00
Artem Hluhovskyi 5125541274 Merge branch 'master' of https://github.com/Fotoapparat/Fotoapparat into fix-codestyle
# Conflicts:
#	fotoapparat/src/main/java/io/fotoapparat/exif/ExifWriter.kt
2018-01-11 18:48:25 +02:00
Artem Hluhovskyi da4ca3a699 Clean up some methods with apply() 2018-01-11 18:03:26 +02:00
Sandi Barisic 98dab42245 Accept rotation in degrees instead of Photos in ExifOrientationHelper 2018-01-11 15:37:57 +01:00
Artem Hluhovskyi e8dfac0fd6 Add typealiases for selectors. Clean up. 2018-01-11 14:41:39 +02:00
Dionysis Lorentzos 4e2c91a7e9 Update README.md 2018-01-10 12:45:31 +01:00
pb 3aba7f8803 Revert minimum SDK version to 14. 2018-01-10 12:41:58 +01:00
Dionysis 4cf6d16baa Bump tag 2018-01-08 22:19:47 +01:00
Dionysis cf22a9a1f8 Fix merge failure 2018-01-08 22:13:24 +01:00
Dionysis 6781e349f9 Bump version 2018-01-08 21:48:48 +01:00
Dionysis Lorentzos 08446ff533 Merge pull request #171 from Fotoapparat/manual_focus
Add focus on tap
2018-01-08 21:47:18 +01:00
Dionysis Lorentzos 9a45b8f400 Merge pull request #172 from Fotoapparat/capabilities_null
Wait capabilities + parameters to be created before obtaining them.
2018-01-08 15:34:58 +01:00
Dionysis e8092902d2 Add kdocs 2018-01-08 00:07:58 +01:00
Dionysis d679ff7454 Wait capabilities + parameters to be created before obtaining them. 2018-01-08 00:06:01 +01:00
Dionysis Lorentzos 4583b8d992 Merge branch 'master' into manual_focus 2018-01-07 23:27:31 +01:00
Dionysis 3da2615acd Add capture button 2018-01-07 23:23:16 +01:00
Dionysis 12c3c3a852 Fix logs 2018-01-07 23:06:21 +01:00
Dionysis 0458642f75 Add focus support 2018-01-07 23:01:27 +01:00
Dionysis cd6cebdb6c Photo has helping functionality to get the dimensions. Fixes #137 2018-01-03 02:56:39 +02:00
Dionysis 2513e42830 Notify exception from the callback instead of crashing the app. Fixes #164, #165 2018-01-03 02:37:49 +02:00
Dionysis 4ed72421aa Bump version 2018-01-03 01:59:40 +02:00
Dionysis dddd81600a Link frame processor in builder with FA 2018-01-03 01:57:46 +02:00
Dionysis Lorentzos 2c80a903c1 Merge pull request #160 from Fotoapparat/v2
🎄 V2 🎄
2017-12-31 18:07:07 +02:00
Dionysis 3f6ecbd37e Update readme and version 2017-12-31 17:18:32 +02:00
Dionysis d9114d4824 Clean 2017-12-31 17:08:38 +02:00
Dionysis 1768d73e59 Clean 2017-12-31 15:25:58 +02:00
Dionysis 51aa9eb9dc Revert compile + update versions 2017-12-31 15:19:27 +02:00
Dionysis 1cfbd2216f Bump gradle 2017-12-31 14:30:07 +02:00
Dionysis bde4c27f8b Remove ci warnings 2017-12-30 18:39:44 +02:00
Dionysis f4b60a445a Bump version 2017-12-30 17:59:14 +02:00
Dionysis 64186ec825 Use api in gradle 2017-12-30 17:58:43 +02:00
Dionysis 96f9add77d Bump coroutines version 2017-12-30 17:37:49 +02:00
Dionysis b6a6f56e38 Bump version 2017-12-30 16:58:59 +02:00
Dionysis a694830c1c Fix issue when Samsung phones have additional focus modes than the android api ones. 2017-12-30 16:58:28 +02:00
Dionysis 352a96a13c Tasks will be retroactive actions to the currently selected camera 2017-12-30 03:07:11 +02:00
Dionysis 33c5054150 Bump version 2017-12-24 21:11:26 +02:00
Dionysis ba02497fbd Update travis 2017-12-24 21:11:03 +02:00
Dionysis 95196e046b Shutdown tasks if we have a stop() operation of FA 2017-12-24 20:54:08 +02:00
Dionysis cb00abc810 Update tools 2017-12-24 17:41:38 +02:00
Dionysis 5e9a6afff7 Fix crash that SurfaceTexture is no longer available 2017-12-24 17:41:33 +02:00
Dionysis 75d20c2f37 Resolve todos 2017-12-20 12:59:15 +02:00
Dionysis d0da61bfb8 Update version 2017-12-20 12:50:47 +02:00
Dionysis 92f099500d Accept license if needed 2017-12-20 12:41:19 +02:00
Dionysis 15b4f35555 Remove 2017-12-19 18:03:47 +02:00
Dionysis c1b293da58 Ahhh travis... 2017-12-19 17:59:38 +02:00
Dionysis 09cca52111 Get latest android tools 2017-12-19 17:41:05 +02:00
Dionysis 9d6f4cb9e8 Try accept travis licenses 2017-12-19 16:51:25 +02:00
Dionysis 2ea813a257 Update build tools 2017-12-19 16:38:00 +02:00
Dionysis 8dbe53d261 Update travis file 2017-12-19 16:23:55 +02:00
Dionysis 4bd05b679f Improve sample 2017-12-19 01:00:35 +02:00
Dionysis 01dc60df24 Fix orientation crash 2017-12-19 00:41:49 +02:00
Dionysis 2b05601405 Improve sample app 2017-12-18 23:57:06 +02:00
Dionysis 1ca2462f07 Support anti banding mode 2017-12-18 19:21:41 +02:00
Dionysis e553699335 Merge branch 'master' into v2
# Conflicts:
#	README.md
#	fotoapparat/src/main/java/io/fotoapparat/Fotoapparat.java
#	fotoapparat/src/main/java/io/fotoapparat/FotoapparatBuilder.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/Capabilities.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/CameraParametersDecorator.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/ParametersConverter.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v1/capabilities/FocusCapability.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/CapabilitiesFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/hardware/v2/capabilities/Characteristics.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/Parameters.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/factory/ParametersFactory.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/provider/InitialParametersProvider.java
#	fotoapparat/src/main/java/io/fotoapparat/parameter/update/UpdateRequest.java
#	fotoapparat/src/main/java/io/fotoapparat/routine/parameter/UpdateParametersRoutine.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatBuilderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/FotoapparatTest.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v1/CapabilitiesFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v2/Camera2Test.java
#	fotoapparat/src/test/java/io/fotoapparat/hardware/v2/capabilities/CapabilitiesFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/factory/ParametersFactoryTest.java
#	fotoapparat/src/test/java/io/fotoapparat/parameter/provider/InitialParametersProviderTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/parameter/UpdateParametersRoutineTest.java
#	fotoapparat/src/test/java/io/fotoapparat/routine/zoom/UpdateZoomLevelRoutineTest.java
#	fotoapparat/src/test/java/io/fotoapparat/task/GetCapabilitiesTaskTest.java
#	sample/src/main/java/io/fotoapparat/sample/MainActivity.java
2017-12-18 18:48:07 +02:00
Dionysis Lorentzos 1d6d2d0977 Merge pull request #158 from simone-gasparini/154_add_antibanding
Added anti banding parameter
2017-12-18 17:43:01 +01:00
Simone Gasparini 94268a339a Added anti banding parameter. Test fixes. 2017-12-18 13:51:10 +01:00
Simone Gasparini 03170f6b26 Added anti banding parameter 2017-12-18 13:06:04 +01:00
Dionysis fe719e8bf3 Fix tests 2017-12-18 13:45:26 +02:00
Dionysis d7db6fa196 Support jpeg quality 2017-12-18 02:54:19 +02:00
Dionysis 29cc23e047 Advertise kotlin 2017-12-17 18:44:10 +02:00
Dionysis 89a172dbb1 new line 2017-12-17 18:42:30 +02:00
Dionysis e4acda6aea Fix link 2017-12-17 18:41:57 +02:00
Dionysis 3fc55404df Update Readme 2017-12-17 18:41:04 +02:00
Dionysis 16fb6bd94c Update Readme 2017-12-17 18:39:26 +02:00
Dionysis df59b3996e Update Readme 2017-12-17 18:36:59 +02:00
Dionysis b8e43e836a Update Readme 2017-12-17 18:36:39 +02:00
Dionysis 6e15327f6c Update Readme 2017-12-17 18:35:44 +02:00
Dionysis 6aef37b3f8 Make file to kotlin 2017-12-17 17:52:59 +02:00
Dionysis 21e4b89bf7 Update adapters to kotlin + add flowable support 2017-12-17 17:51:47 +02:00
Dionysis e3f0aa7421 Rename tag 2017-12-17 16:47:54 +02:00
Dionysis 35062675d9 Implement can select camera 2017-12-14 00:07:54 +01:00
Dionysis aff03c17c8 Implement samples in both kt & java. Add builders for helping java users 2017-12-13 23:34:18 +01:00
Dionysis b4e6db0a04 Add fileLogger() in loggers 2017-12-13 21:09:28 +01:00
Dionysis 7517702165 Init FA v2 2017-12-11 22:39:00 +01:00
Dionysis 56d32eee60 Update build gradle 2017-12-11 20:58:08 +01:00
Dionysis 132736e4f7 Remove duplicate tests 2017-12-10 21:04:40 +01:00
Dionysis 2ac387d6ff Migrate build scripts 2017-12-10 20:51:57 +01:00
Dmitry Zaitsev f4ff8b91b9 Merge pull request #147 from ezaquarii/is-started
Added Fotoapparat.isStarted() getter
2017-12-07 07:03:38 +01:00
Krzysztof Narkiewicz 7b047501a6 Added Fotoapparat.isStarted() getter 2017-12-07 05:30:22 +00:00
Dionysis 80f43fcf63 Allow null fps range and sensor sensitivity (ISO) 2017-12-06 22:25:06 +01:00
Dionysis b7417650c6 Fix null check 2017-12-06 22:06:36 +01:00
Dmitry Zaitsev e7e264f522 Merge pull request #143 from Fotoapparat/prevent_wrong_selectors
Selectors cannot be trusted, therefore we ensure their values are correct
2017-12-04 16:22:20 +01:00
Dionysis e501d0d25f Add more parameter factory tests 2017-12-04 15:18:38 +01:00
Dionysis 39765cdb07 Fix/add parameter factory tests 2017-12-04 15:12:04 +01:00
Dionysis 71480f4fa8 Rename method 2017-12-04 14:52:37 +01:00
Dionysis a6028d56f3 Selectors cannot be trusted, therefore we ensure their values are correct 2017-12-02 14:21:11 +01:00
445 changed files with 10215 additions and 17598 deletions
+2
View File
@@ -6,4 +6,6 @@
build
/captures
.externalNativeBuild
.project
.settings
+15 -5
View File
@@ -3,11 +3,21 @@ jdk: oraclejdk8
android:
components:
- tools
- platform-tools
- build-tools-25.0.2
- android-25
- extra-android-m2repository
- tools
- platform-tools
- tools
- build-tools-28.0.3
- android-28
- extra-android-m2repository
before_install:
- yes | sdkmanager "platforms;android-28"
script:
- ./gradlew build test
deploy:
provider: script
script: ./gradlew bintrayUpload
on:
tags: true
+91 -89
View File
@@ -1,33 +1,33 @@
# Fotoapparat
![Build status](https://travis-ci.org/Fotoapparat/Fotoapparat.svg?branch=master)
![Build status](https://travis-ci.org/RedApparat/Fotoapparat.svg?branch=master)
![ ](sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
Camera API in Android is hard. Having 2 different API for new and old Camera does not make things any easier. But fret not, that is your lucky day! After several years of working with Camera we came up with Fotoapparat.
Camera API in Android is hard. Having 2 different API for new and old Camera does not make things any easier. But fret not, that is your lucky day! After several years of working with Camera, we came up with Fotoapparat.
What it provides:
- Camera API which does not allow you to shoot yourself in the foot.
- Simple yet powerful parameters customization.
- Standalone custom `CameraView` which can be integrated into any `Activity`.
- Fixes and workarounds for device-specific problems.
- Last, but not least, non 0% test coverage.
- Both Kotlin and Java friendly configurations.
- Last, but not least, non 0% test coverage.
Taking picture becomes as simple as:
```java
Fotoapparat fotoapparat = Fotoapparat
.with(context)
.into(cameraView)
.build();
```kotlin
val fotoapparat = Fotoapparat(
context = this,
view = cameraView
)
fotoapparat.start()
fotoapparat.start();
fotoapparat
.takePicture()
.saveToFile(someFile);
.saveToFile(someFile)
```
## How it works
@@ -45,109 +45,115 @@ Add `CameraView` to your layout
### Step Two
Configure `Fotoapparat` instance
Configure `Fotoapparat` instance.
```java
Fotoapparat
.with(context)
.into(cameraView) // view which will draw the camera preview
.previewScaleType(ScaleType.CENTER_CROP) // we want the preview to fill the view
.photoSize(biggestSize()) // we want to have the biggest photo possible
.lensPosition(back()) // we want back camera
.focusMode(firstAvailable( // (optional) use the first focus mode which is supported by device
continuousFocus(),
autoFocus(), // in case if continuous focus is not available on device, auto focus will be used
fixed() // if even auto focus is not available - fixed focus mode will be used
))
.flash(firstAvailable( // (optional) similar to how it is done for focus mode, this time for flash
autoRedEye(),
autoFlash(),
torch()
))
.frameProcessor(myFrameProcessor) // (optional) receives each frame from preview stream
.logger(loggers( // (optional) we want to log camera events in 2 places at once
logcat(), // ... in logcat
fileLogger(this) // ... and to file
))
.build();
```kotlin
Fotoapparat(
context = this,
view = cameraView, // view which will draw the camera preview
scaleType = ScaleType.CenterCrop, // (optional) we want the preview to fill the view
lensPosition = back(), // (optional) we want back camera
cameraConfiguration = configuration, // (optional) define an advanced configuration
logger = loggers( // (optional) we want to log camera events in 2 places at once
logcat(), // ... in logcat
fileLogger(this) // ... and to file
),
cameraErrorCallback = { error -> } // (optional) log fatal errors
)
```
Check the [wiki for the `configuration` options e.g. change iso](https://github.com/Fotoapparat/Fotoapparat/wiki/Configuration-Kotlin)
Are you using Java only? See our [wiki for the java-friendly configuration](https://github.com/Fotoapparat/Fotoapparat/wiki/Configuration-Java).
### Step Three
Call `start()` and `stop()`. No rocket science here.
```java
@Override
protected void onStart() {
super.onStart();
fotoapparat.start();
```kotlin
override fun onStart() {
super.onStart()
fotoapparat.start()
}
@Override
protected void onStop() {
super.onStop();
fotoapparat.stop();
override fun onStop() {
super.onStop()
fotoapparat.stop()
}
```
### Take picture
### Take a picture
Finally we are ready to take picture. You have various options.
Finally, we are ready to take a picture. You have various options.
```kotlin
val photoResult = fotoapparat.takePicture()
```java
PhotoResult photoResult = fotoapparat.takePicture();
// Asynchronously saves photo to file
photoResult.saveToFile(someFile);
// Asynchronously converts photo to bitmap and returns result on main thread
photoResult.saveToFile(someFile)
// Asynchronously converts photo to bitmap and returns the result on the main thread
photoResult
.toBitmap()
.whenAvailable(new PendingResult.Callback<BitmapPhoto>() {
@Override
public void onResult(BitmapPhoto result) {
ImageView imageView = (ImageView) findViewById(R.id.result);
.whenAvailable { bitmapPhoto ->
val imageView = (ImageView) findViewById(R.id.result)
imageView.setImageBitmap(result.bitmap);
imageView.setRotation(-result.rotationDegrees);
}
});
// Of course you can also get a photo in a blocking way. Do not do it on main thread though.
BitmapPhoto result = photoResult.toBitmap().await();
// Convert asynchronous events to RxJava 1.x/2.x types. See /fotoapparat-adapters/ module
imageView.setImageBitmap(bitmapPhoto.bitmap)
imageView.setRotation(-bitmapPhoto.rotationDegrees)
}
// Of course, you can also get a photo in a blocking way. Do not do it on the main thread though.
val result = photoResult.toBitmap().await()
// Convert asynchronous events to RxJava 1.x/2.x types.
// See /fotoapparat-adapters/ module
photoResult
.toBitmap()
.adapt(SingleAdapter.<BitmapPhoto>toSingle())
.subscribe(bitmapPhoto -> {
});
.toSingle()
.subscribe { bitmapPhoto ->
}
```
## Update parameters
It is also possible to update some parameters after `Fotoapparat` was already started.
```java
fotoapparat.updateParameters(
UpdateRequest.builder()
.flash(
isTurnedOn
? torch()
: off()
)
.build()
```kotlin
fotoapparat.updateConfiguration(
UpdateConfiguration(
flashMode = if (isChecked) torch() else off()
// ...
// all the parameters available in CameraConfiguration
)
)
```
Or alternatively, you may provide updates on an existing full configuration.
```kotlin
val configuration = CameraConfiguration(
// A full configuration
// ...
)
fotoapparat.updateConfiguration(
configuration.copy(
flashMode = if (isChecked) torch() else off()
// all the parameters available in CameraConfiguration
)
)
```
## Switch cameras
In order to switch between multiple instances of Fotoapparat, for example an instance using the device's front camera and another using the back, `FotoapparatSwitcher` can be used:
In order to switch between cameras, `Fotoapparat.switchTo()` can be used with the new desired `lensPosition` and its `cameraConfiguration`.
```java
FotoapparatSwitcher fotoapparatSwitcher = FotoapparatSwitcher.withDefault(fotoapparatFront);
fotoapparatSwitcher.switchTo(fotoapparatBack);
```kotlin
fotoapparat.switchTo(
lensPosition = front(),
cameraConfiguration = newConfigurationForFrontCamera
)
```
## Set up
@@ -155,11 +161,7 @@ fotoapparatSwitcher.switchTo(fotoapparatBack);
Add dependency to your `build.gradle`
```groovy
repositories {
maven { url 'https://jitpack.io' }
}
compile 'io.fotoapparat.fotoapparat:library:1.5.0'
implementation 'io.fotoapparat:fotoapparat:2.7.0'
```
Camera permission will be automatically added to your `AndroidManifest.xml`. Do not forget to request this permission on Marshmallow and higher.
+38 -6
View File
@@ -1,18 +1,53 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
subprojects {
ext {
artifactVersion = '2.7.0'
}
}
buildscript {
ext {
versions = [
kotlin : '1.3.50',
code : 1,
name : '1.0.0',
sdk : [
minimum: 14,
target : 28
],
android: [
buildTools: '28.0.3',
appcompat : '1.1.0',
annotation : '1.1.0',
exifinterface : '1.0.0'
],
rx : [
rxJava1: '1.3.8',
rxJava2: '2.2.12'
],
test : [
junit : '4.12',
mockito: '2.23.0'
]
]
}
repositories {
jcenter()
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:${project.androidBuildToolsVersion}"
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
@@ -21,6 +56,3 @@ task clean(type: Delete) {
delete rootProject.buildDir
}
task wrapper(type: Wrapper) {
gradleVersion = project.gradleVersion
}
+100
View File
@@ -0,0 +1,100 @@
apply plugin: 'com.jfrog.bintray'
apply plugin: 'com.github.dcendents.android-maven'
ext {
bintrayOrganisation = 'fotoapparat'
bintrayRepo = 'fotoapparat'
publishedGroupId = 'io.fotoapparat'
siteUrl = 'https://github.com/RedApparat/Fotoapparat'
gitUrl = 'https://github.com/RedApparat/Fotoapparat.git'
developerId = 'fotoapparat'
developerName = 'Fotoapparat'
developerEmail = 'dmitry.zaicew@gmail.com'
licenseName = 'The Apache Software License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
allLicenses = ["Apache-2.0"]
}
group = publishedGroupId
version = libraryVersion
install {
repositories.mavenInstaller {
pom.project {
packaging 'aar'
groupId publishedGroupId
artifactId artifact
name libraryName
description libraryDescription
url siteUrl
licenses {
license {
name licenseName
url licenseUrl
}
}
developers {
developer {
id developerId
name developerName
email developerEmail
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
task sourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
failOnError = false
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
bintray {
user = project.properties["bintray.user"] ?: System.getenv('BINTRAY_USER')
key = project.properties["bintray.apikey"] ?: System.getenv('BINTRAY_API_KEY')
configurations = ['archives']
pkg {
userOrg = bintrayOrganisation
repo = bintrayRepo
name = bintrayName
desc = libraryDescription
websiteUrl = siteUrl
vcsUrl = gitUrl
licenses = allLicenses
dryRun = false
publish = true
override = false
publicDownloadNumbers = true
version {
desc = libraryDescription
}
}
}
+21
View File
@@ -5,6 +5,18 @@ The child modules contained herein are additional adapters for other popular exe
To use, supply an instance of your desired adapter when performing a task of a PendingResult.
Kotlin:
```java
fotoapparat.takePicture()
.toBitmap()
.toObservable()
.subscribe { bitmapPhoto ->
// Do something with the photo
}
```
Java:
```java
fotoapparat.takePicture()
.toBitmap()
@@ -16,3 +28,12 @@ fotoapparat.takePicture()
}
});
```
Supported types:
* `Observable<T>` : RxJava 1/2
* `Flowable<T>` : RxJava 2
* `Single<T>` : RxJava 1/2
* `Completable` : RxJava 1/2
+27 -9
View File
@@ -1,23 +1,41 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
}
archivesBaseName = 'adapter-rxjava'
archivesBaseName = 'adapter-rxjava'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
compile project(':fotoapparat')
provided "io.reactivex:rxjava:${project.rxjava1Version}"
compileOnly project(':fotoapparat')
compileOnly "io.reactivex:rxjava:${versions.rx.rxJava1}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
testImplementation "io.reactivex:rxjava:${versions.rx.rxJava1}"
testImplementation "junit:junit:${versions.test.junit}"
}
testCompile "junit:junit:${project.junitVersion}"
}
ext {
bintrayName = 'adapter-rxjava'
libraryName = 'Fotoapparat Adapters - RxJava'
artifact = 'adapter-rxjava'
libraryVersion = artifactVersion
libraryDescription = 'RxJava1 adapter for Fotoapparat.'
}
apply from: '../../deploy.gradle'
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Completable;
/**
* Adapter for {@link Completable}.
*/
public class CompletableAdapter<T> implements Adapter<T, Completable> {
private CompletableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Completable}.
*/
public static <R> CompletableAdapter<R> toCompletable() {
return new CompletableAdapter<>();
}
@Override
public Completable adapt(Future<T> future) {
return Completable.fromFuture(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Completable
import java.util.concurrent.Future
/**
* Adapter for [Completable].
*/
object CompletableAdapter {
/**
* @return Adapter which adapts result to [Completable].
*/
@JvmStatic
fun <T> toCompletable(): Function1<Future<T>, Completable> {
return { future -> Completable.fromFuture(future) }
}
}
/**
* @return A [Completable] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toCompletable(): Completable {
return adapt { future -> Completable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Observable;
/**
* Adapter for {@link Observable}.
*/
public class ObservableAdapter<T> implements Adapter<T, Observable<T>> {
private ObservableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Observable}.
*/
public static <R> ObservableAdapter<R> toObservable() {
return new ObservableAdapter<>();
}
@Override
public Observable<T> adapt(Future<T> future) {
return Observable.from(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Observable
import java.util.concurrent.Future
/**
* Adapter for [Observable].
*/
object ObservableAdapter {
/**
* @return Adapter which adapts result to [Observable].
*/
@JvmStatic
fun <T> toObservable(): Function1<Future<T>, Observable<T>> {
return { future -> Observable.from(future) }
}
}
/**
* @return A [Observable] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toObservable(): Observable<T> {
return adapt { future -> Observable.from(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import rx.Single;
/**
* Adapter for {@link Single}.
*/
public class SingleAdapter<T> implements Adapter<T, Single<T>> {
private SingleAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Single}.
*/
public static <R> SingleAdapter<R> toSingle() {
return new SingleAdapter<>();
}
@Override
public Single<T> adapt(Future<T> future) {
return Single.from(future);
}
}
@@ -0,0 +1,27 @@
package io.fotoapparat.result.adapter.rxjava
import io.fotoapparat.result.PendingResult
import rx.Single
import java.util.concurrent.Future
/**
* Adapter for [Single].
*/
object SingleAdapter {
/**
* @return Adapter which adapts result to [Single].
*/
@JvmStatic
fun <T> toSingle(): (Future<T>) -> Single<T> {
return { future -> Single.from(future) }
}
}
/**
* @return A [Single] from the given [PendingResult].
*/
fun <T> PendingResult<T>.toSingle(): Single<T> {
return adapt { future -> Single.from(future) }
}
@@ -1,58 +0,0 @@
package io.fotoapparat.result.adapter.rxjava;
import android.support.annotation.NonNull;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Simply returns the value of the callable.
*/
public class CallableFuture<T> implements Future<T> {
private final CountDownLatch latch = new CountDownLatch(1);
private final Callable<T> callable;
public CallableFuture(Callable<T> callable) {
this.callable = callable;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return latch.getCount() == 0;
}
@Override
public T get() throws InterruptedException, ExecutionException {
return callCallable();
}
@Override
public T get(long timeout,
@NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return callCallable();
}
private T callCallable() throws ExecutionException {
latch.countDown();
try {
return callable.call();
} catch (Exception e) {
throw new ExecutionException(e);
}
}
}
@@ -0,0 +1,44 @@
package io.fotoapparat.result.adapter.rxjava
import java.util.concurrent.*
/**
* Simply returns the value of the callable.
*/
class CallableFuture<T>(private val callable: Callable<T>) : Future<T> {
private val latch = CountDownLatch(1)
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
return false
}
override fun isCancelled(): Boolean {
return false
}
override fun isDone(): Boolean {
return latch.count == 0L
}
@Throws(InterruptedException::class, ExecutionException::class)
override fun get(): T {
return callCallable()
}
@Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
override fun get(timeout: Long, unit: TimeUnit): T {
return callCallable()
}
@Throws(ExecutionException::class)
private fun callCallable(): T {
latch.countDown()
try {
return callable.call()
} catch (e: Exception) {
throw ExecutionException(e)
}
}
}
@@ -24,7 +24,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -24,7 +24,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -24,7 +24,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
@@ -45,7 +45,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(subscriber);
// Then
+1
View File
@@ -5,6 +5,7 @@ An `Adapter` for adapting RxJava 2.x types.
Available types:
* `Observable<T>`
* `Flowable<T>`
* `Single<T>`
* `Completable`
+27 -9
View File
@@ -1,23 +1,41 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
}
archivesBaseName = 'adapter-rxjava2'
archivesBaseName = 'adapter-rxjava2'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
compile project(':fotoapparat')
provided "io.reactivex.rxjava2:rxjava:${project.rxjava2Version}"
compileOnly project(':fotoapparat')
compileOnly "io.reactivex.rxjava2:rxjava:${versions.rx.rxJava2}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
testImplementation "io.reactivex.rxjava2:rxjava:${versions.rx.rxJava2}"
testImplementation "junit:junit:${versions.test.junit}"
}
testCompile "junit:junit:${project.junitVersion}"
}
ext {
bintrayName = 'adapter-rxjava2'
libraryName = 'Fotoapparat Adapters - RxJava2'
artifact = 'adapter-rxjava2'
libraryVersion = artifactVersion
libraryDescription = 'RxJava2 adapter for Fotoapparat.'
}
apply from: '../../deploy.gradle'
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Completable;
/**
* Adapter for {@link Completable}.
*/
public class CompletableAdapter<T> implements Adapter<T, Completable> {
private CompletableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Completable}.
*/
public static <R> CompletableAdapter<R> toCompletable() {
return new CompletableAdapter<>();
}
@Override
public Completable adapt(Future<T> future) {
return Completable.fromFuture(future);
}
}
@@ -0,0 +1,31 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Completable
import java.util.concurrent.Future
/**
* Adapter for [Completable].
*/
object CompletableAdapter {
/**
* @return Adapter which adapts result to [Completable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <R> toCompletable(): (Future<R>) -> Completable {
return { future -> Completable.fromFuture(future) }
}
}
/**
* @return A [Completable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toCompletable(): Completable {
return adapt { future -> Completable.fromFuture(future) }
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Flowable
import java.util.concurrent.Future
/**
* Adapter for [Flowable].
*/
object FlowableAdapter {
/**
* @return Adapter which adapts result to [Flowable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toFlowable(): Function1<Future<T>, Flowable<T>> {
return { future -> Flowable.fromFuture(future) }
}
}
/**
* @return A [Flowable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toFlowable(): Flowable<T> {
return adapt { future -> Flowable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Observable;
/**
* Adapter for {@link Observable}.
*/
public class ObservableAdapter<T> implements Adapter<T, Observable<T>> {
private ObservableAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Observable}.
*/
public static <R> ObservableAdapter<R> toObservable() {
return new ObservableAdapter<>();
}
@Override
public Observable<T> adapt(Future<T> future) {
return Observable.fromFuture(future);
}
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Observable
import java.util.concurrent.Future
/**
* Adapter for [Observable].
*/
object ObservableAdapter {
/**
* @return Adapter which adapts result to [Observable].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toObservable(): Function1<Future<T>, Observable<T>> {
return { future -> Observable.fromFuture(future) }
}
}
/**
* @return A [Observable] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toObservable(): Observable<T> {
return adapt { future -> Observable.fromFuture(future) }
}
@@ -1,27 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import java.util.concurrent.Future;
import io.fotoapparat.result.adapter.Adapter;
import io.reactivex.Single;
/**
* Adapter for {@link Single}.
*/
public class SingleAdapter<T> implements Adapter<T, Single<T>> {
private SingleAdapter() {
}
/**
* @return {@link Adapter} which adapts result to {@link Single}.
*/
public static <R> SingleAdapter<R> toSingle() {
return new SingleAdapter<>();
}
@Override
public Single<T> adapt(Future<T> future) {
return Single.fromFuture(future);
}
}
@@ -0,0 +1,30 @@
package io.fotoapparat.result.adapter.rxjava2
import android.annotation.SuppressLint
import io.fotoapparat.result.PendingResult
import io.reactivex.Single
import java.util.concurrent.Future
/**
* Adapter for [Single].
*/
object SingleAdapter {
/**
* @return Adapter which adapts result to [Single].
*/
@JvmStatic
@SuppressLint("CheckResult")
fun <T> toSingle(): (Future<T>) -> Single<T> {
return { future -> Single.fromFuture(future) }
}
}
/**
* @return A [Single] from the given [PendingResult].
*/
@SuppressLint("CheckResult")
fun <T> PendingResult<T>.toSingle(): Single<T> {
return adapt { future -> Single.fromFuture(future) }
}
@@ -1,58 +0,0 @@
package io.fotoapparat.result.adapter.rxjava2;
import android.support.annotation.NonNull;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Simply returns the value of the callable.
*/
public class CallableFuture<T> implements Future<T> {
private final CountDownLatch latch = new CountDownLatch(1);
private final Callable<T> callable;
public CallableFuture(Callable<T> callable) {
this.callable = callable;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return latch.getCount() == 0;
}
@Override
public T get() throws InterruptedException, ExecutionException {
return callCallable();
}
@Override
public T get(long timeout,
@NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return callCallable();
}
private T callCallable() throws ExecutionException {
latch.countDown();
try {
return callable.call();
} catch (Exception e) {
throw new ExecutionException(e);
}
}
}
@@ -0,0 +1,44 @@
package io.fotoapparat.result.adapter.rxjava2
import java.util.concurrent.*
/**
* Simply returns the value of the callable.
*/
class CallableFuture<T>(private val callable: Callable<T>) : Future<T> {
private val latch = CountDownLatch(1)
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
return false
}
override fun isCancelled(): Boolean {
return false
}
override fun isDone(): Boolean {
return latch.count == 0L
}
@Throws(InterruptedException::class, ExecutionException::class)
override fun get(): T {
return callCallable()
}
@Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
override fun get(timeout: Long, unit: TimeUnit): T {
return callCallable()
}
@Throws(ExecutionException::class)
private fun callCallable(): T {
latch.countDown()
try {
return callable.call()
} catch (e: Exception) {
throw ExecutionException(e)
}
}
}
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class CompletableAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class CompletableAdapterTest {
// When
CompletableAdapter.<String>toCompletable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -0,0 +1,55 @@
package io.fotoapparat.result.adapter.rxjava2;
import org.junit.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import io.reactivex.subscribers.TestSubscriber;
public class FlowableAdapterTest {
private TestSubscriber<String> observer = new TestSubscriber<>();
@Test
public void completed() throws Exception {
// Given
Future<String> future = new CallableFuture<>(new Callable<String>() {
@Override
public String call() throws Exception {
return "Hello";
}
});
// When
FlowableAdapter.<String>toFlowable()
.invoke(future)
.subscribe(observer);
// Then
observer.assertValue("Hello");
observer.assertNoErrors();
}
@Test
public void error() throws Exception {
// Given
Future<String> future = new CallableFuture<>(new Callable<String>() {
@Override
public String call() throws Exception {
throw new RuntimeException("What a failure");
}
});
// When
FlowableAdapter.<String>toFlowable()
.invoke(future)
.subscribe(observer);
// Then
observer.assertNoValues();
observer.assertError(ExecutionException.class);
}
}
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class ObservableAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class ObservableAdapterTest {
// When
ObservableAdapter.<String>toObservable()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -10,7 +10,7 @@ import io.reactivex.observers.TestObserver;
public class SingleAdapterTest {
private TestObserver<Object> observer = new TestObserver<>();
private TestObserver<String> observer = new TestObserver<>();
@Test
public void completed() throws Exception {
@@ -24,7 +24,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
@@ -44,7 +44,7 @@ public class SingleAdapterTest {
// When
SingleAdapter.<String>toSingle()
.adapt(future)
.invoke(future)
.subscribe(observer);
// Then
+28 -11
View File
@@ -1,17 +1,16 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
android {
buildToolsVersion project.buildToolsVersion
compileSdkVersion Integer.parseInt(project.compileSdkVersion)
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
defaultConfig {
minSdkVersion Integer.parseInt(project.minSdkVersion)
targetSdkVersion Integer.parseInt(project.targetSdkVersion)
archivesBaseName = 'library'
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
}
buildTypes {
@@ -23,12 +22,30 @@ android {
testOptions {
unitTests.returnDefaultValues = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
compile "com.android.support:support-annotations:${project.appCompatVersion}"
implementation "androidx.annotation:annotation:${versions.android.annotation}"
implementation "androidx.exifinterface:exifinterface:${versions.android.exifinterface}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
testImplementation "junit:junit:${versions.test.junit}"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${versions.kotlin}"
testImplementation "org.mockito:mockito-core:${versions.test.mockito}"
}
testCompile "junit:junit:${project.junitVersion}"
testCompile "org.mockito:mockito-core:${project.mockitoVersion}"
testCompile 'commons-io:commons-io:2.5'
}
ext {
bintrayName = 'fotoapparat'
libraryName = 'Fotoapparat'
artifact = 'fotoapparat'
libraryVersion = artifactVersion
libraryDescription = 'Camera library for Android with a more user-friendly API.'
}
apply from: '../deploy.gradle'
@@ -1,342 +0,0 @@
package io.fotoapparat;
import android.content.Context;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import io.fotoapparat.error.Callbacks;
import io.fotoapparat.error.CameraErrorCallback;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.orientation.OrientationSensor;
import io.fotoapparat.hardware.orientation.RotationListener;
import io.fotoapparat.hardware.orientation.ScreenOrientationProvider;
import io.fotoapparat.parameter.provider.CapabilitiesProvider;
import io.fotoapparat.parameter.provider.CurrentParametersProvider;
import io.fotoapparat.parameter.provider.InitialParametersProvider;
import io.fotoapparat.parameter.provider.InitialParametersValidator;
import io.fotoapparat.parameter.update.UpdateRequest;
import io.fotoapparat.result.CapabilitiesResult;
import io.fotoapparat.result.FocusResult;
import io.fotoapparat.result.ParametersResult;
import io.fotoapparat.result.PendingResult;
import io.fotoapparat.result.PhotoResult;
import io.fotoapparat.routine.CheckAvailabilityRoutine;
import io.fotoapparat.routine.ConfigurePreviewStreamRoutine;
import io.fotoapparat.routine.StartCameraRoutine;
import io.fotoapparat.routine.StopCameraRoutine;
import io.fotoapparat.routine.UpdateOrientationRoutine;
import io.fotoapparat.routine.focus.AutoFocusRoutine;
import io.fotoapparat.routine.parameter.UpdateParametersRoutine;
import io.fotoapparat.routine.picture.TakePictureRoutine;
import io.fotoapparat.routine.zoom.UpdateZoomLevelRoutine;
/**
* Camera. Takes pictures.
*/
public class Fotoapparat {
private static final Executor SERIAL_EXECUTOR = Executors.newSingleThreadExecutor();
private final StartCameraRoutine startCameraRoutine;
private final StopCameraRoutine stopCameraRoutine;
private final UpdateOrientationRoutine updateOrientationRoutine;
private final ConfigurePreviewStreamRoutine configurePreviewStreamRoutine;
private final CapabilitiesProvider capabilitiesProvider;
private final CurrentParametersProvider currentParametersProvider;
private final TakePictureRoutine takePictureRoutine;
private final AutoFocusRoutine autoFocusRoutine;
private final CheckAvailabilityRoutine checkAvailabilityRoutine;
private final UpdateParametersRoutine updateParametersRoutine;
private final UpdateZoomLevelRoutine updateZoomLevelRoutine;
private final Executor executor;
private boolean started = false;
Fotoapparat(StartCameraRoutine startCameraRoutine,
StopCameraRoutine stopCameraRoutine,
UpdateOrientationRoutine updateOrientationRoutine,
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine,
CapabilitiesProvider capabilitiesProvider,
CurrentParametersProvider parametersProvider,
TakePictureRoutine takePictureRoutine,
AutoFocusRoutine autoFocusRoutine,
CheckAvailabilityRoutine checkAvailabilityRoutine,
UpdateParametersRoutine updateParametersRoutine,
UpdateZoomLevelRoutine updateZoomLevelRoutine,
Executor executor) {
this.startCameraRoutine = startCameraRoutine;
this.stopCameraRoutine = stopCameraRoutine;
this.updateOrientationRoutine = updateOrientationRoutine;
this.configurePreviewStreamRoutine = configurePreviewStreamRoutine;
this.capabilitiesProvider = capabilitiesProvider;
this.currentParametersProvider = parametersProvider;
this.takePictureRoutine = takePictureRoutine;
this.autoFocusRoutine = autoFocusRoutine;
this.checkAvailabilityRoutine = checkAvailabilityRoutine;
this.updateParametersRoutine = updateParametersRoutine;
this.updateZoomLevelRoutine = updateZoomLevelRoutine;
this.executor = executor;
}
public static FotoapparatBuilder with(Context context) {
if (context == null) {
throw new IllegalStateException("Context is null.");
}
return new FotoapparatBuilder(context);
}
static Fotoapparat create(FotoapparatBuilder builder) {
CameraErrorCallback cameraErrorCallback = Callbacks.onMainThread(
builder.cameraErrorCallback
);
CameraDevice cameraDevice = builder.cameraProvider.get(builder.logger);
ScreenOrientationProvider screenOrientationProvider = new ScreenOrientationProvider(builder.context);
RotationListener rotationListener = new RotationListener(builder.context);
InitialParametersValidator parametersValidator = new InitialParametersValidator();
InitialParametersProvider initialParametersProvider = new InitialParametersProvider(
cameraDevice,
builder.photoSizeSelector,
builder.previewSizeSelector,
builder.focusModeSelector,
builder.flashSelector,
builder.previewFpsRangeSelector,
builder.sensorSensitivitySelector,
builder.jpegQuality,
parametersValidator
);
StartCameraRoutine startCameraRoutine = new StartCameraRoutine(
cameraDevice,
builder.renderer,
builder.scaleType,
builder.lensPositionSelector,
screenOrientationProvider,
initialParametersProvider,
cameraErrorCallback
);
StopCameraRoutine stopCameraRoutine = new StopCameraRoutine(cameraDevice);
OrientationSensor orientationSensor = new OrientationSensor(
rotationListener,
screenOrientationProvider
);
UpdateOrientationRoutine updateOrientationRoutine = new UpdateOrientationRoutine(
cameraDevice,
orientationSensor,
SERIAL_EXECUTOR,
builder.logger
);
ConfigurePreviewStreamRoutine configurePreviewStreamRoutine = new ConfigurePreviewStreamRoutine(
cameraDevice,
builder.frameProcessor
);
CapabilitiesProvider capabilitiesProvider = new CapabilitiesProvider(
cameraDevice,
SERIAL_EXECUTOR
);
CurrentParametersProvider currentParametersProvider = new CurrentParametersProvider(
cameraDevice,
SERIAL_EXECUTOR
);
TakePictureRoutine takePictureRoutine = new TakePictureRoutine(
cameraDevice,
SERIAL_EXECUTOR
);
AutoFocusRoutine autoFocusRoutine = new AutoFocusRoutine(
cameraDevice,
SERIAL_EXECUTOR
);
CheckAvailabilityRoutine checkAvailabilityRoutine = new CheckAvailabilityRoutine(
cameraDevice,
builder.lensPositionSelector
);
UpdateParametersRoutine updateParametersRoutine = new UpdateParametersRoutine(
cameraDevice
);
UpdateZoomLevelRoutine updateZoomLevelRoutine = new UpdateZoomLevelRoutine(
cameraDevice
);
return new Fotoapparat(
startCameraRoutine,
stopCameraRoutine,
updateOrientationRoutine,
configurePreviewStreamRoutine,
capabilitiesProvider,
currentParametersProvider,
takePictureRoutine,
autoFocusRoutine,
checkAvailabilityRoutine,
updateParametersRoutine,
updateZoomLevelRoutine,
SERIAL_EXECUTOR
);
}
/**
* @return {@code true} if camera for this {@link Fotoapparat} is available. {@code false} if
* it is not available.
*/
public boolean isAvailable() {
return checkAvailabilityRoutine.isAvailable();
}
/**
* Provides camera capabilities asynchronously, returns immediately.
*
* @return {@link CapabilitiesResult} which will deliver result asynchronously.
*/
public CapabilitiesResult getCapabilities() {
ensureStarted();
return capabilitiesProvider.getCapabilities();
}
/**
* Provides current camera parameters asynchronously, returns immediately.
*
* @return {@link ParametersResult} which will deliver result asynchronously.
*/
public ParametersResult getCurrentParameters() {
ensureStarted();
return currentParametersProvider.getParameters();
}
/**
* Takes picture. Returns immediately.
*
* @return {@link PhotoResult} which will deliver result asynchronously.
*/
public PhotoResult takePicture() {
ensureStarted();
return takePictureRoutine.takePicture();
}
/**
* Performs auto focus. If it is not available or not enabled, does nothing.
*/
public Fotoapparat autoFocus() {
focus();
return this;
}
/**
* Attempts to focus the camera asynchronously.
*
* @return the pending result of focus operation which will deliver result asynchronously.
*/
public PendingResult<FocusResult> focus() {
ensureStarted();
return autoFocusRoutine.autoFocus();
}
/**
* Asynchronously updates parameters of the camera. Must be called only after {@link #start()}.
*/
public void updateParameters(@NonNull final UpdateRequest updateRequest) {
ensureStarted();
executor.execute(new Runnable() {
@Override
public void run() {
updateParametersRoutine.updateParameters(updateRequest);
}
});
}
/**
* Asynchronously updates zoom level of the camera. Must be called only after {@link #start()}.
* <p>
* If zoom is not supported by the device - does nothing.
*
* @param zoomLevel zoom level of the camera. A value between 0 and 1.
*/
public void setZoom(@FloatRange(from = 0f, to = 1f) final float zoomLevel) {
ensureStarted();
executor.execute(new Runnable() {
@Override
public void run() {
updateZoomLevelRoutine.updateZoomLevel(zoomLevel);
}
});
}
/**
* Starts camera.
*
* @throws IllegalStateException if camera was already started.
*/
public void start() {
ensureNotStarted();
started = true;
startCamera();
configurePreviewStream();
updateOrientationRoutine.start();
}
/**
* Stops camera.
*
* @throws IllegalStateException if camera is not started.
*/
public void stop() {
ensureStarted();
started = false;
updateOrientationRoutine.stop();
stopCamera();
}
private void startCamera() {
executor.execute(
startCameraRoutine
);
}
private void stopCamera() {
executor.execute(
stopCameraRoutine
);
}
private void configurePreviewStream() {
executor.execute(
configurePreviewStreamRoutine
);
}
private void ensureStarted() {
if (!started) {
throw new IllegalStateException("Camera is not started!");
}
}
private void ensureNotStarted() {
if (started) {
throw new IllegalStateException("Camera is already started!");
}
}
}
@@ -0,0 +1,248 @@
package io.fotoapparat
import android.content.Context
import androidx.annotation.FloatRange
import io.fotoapparat.concurrent.CameraExecutor
import io.fotoapparat.concurrent.CameraExecutor.Operation
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.configuration.Configuration
import io.fotoapparat.error.CameraErrorCallback
import io.fotoapparat.error.onMainThread
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.display.Display
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.log.Logger
import io.fotoapparat.log.none
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.result.*
import io.fotoapparat.routine.camera.bootStart
import io.fotoapparat.routine.camera.shutDown
import io.fotoapparat.routine.camera.switchCamera
import io.fotoapparat.routine.camera.updateDeviceConfiguration
import io.fotoapparat.routine.capability.getCapabilities
import io.fotoapparat.routine.focus.focus
import io.fotoapparat.routine.parameter.getCurrentParameters
import io.fotoapparat.routine.photo.takePhoto
import io.fotoapparat.routine.zoom.updateZoomLevel
import io.fotoapparat.selector.*
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.FocalPointSelector
/**
* Camera. Takes pictures.
*/
class Fotoapparat
@JvmOverloads constructor(
context: Context,
view: CameraRenderer,
focusView: FocalPointSelector? = null,
lensPosition: LensPositionSelector = firstAvailable(
back(),
front(),
external()
),
scaleType: ScaleType = ScaleType.CenterCrop,
cameraConfiguration: CameraConfiguration = CameraConfiguration.default(),
cameraErrorCallback: CameraErrorCallback = {},
private val executor: CameraExecutor = EXECUTOR,
private val logger: Logger = none()
) {
private val mainThreadErrorCallback = cameraErrorCallback.onMainThread()
private val display = Display(context)
private val device = Device(
cameraRenderer = view,
focusPointSelector = focusView,
logger = logger,
display = display,
scaleType = scaleType,
initialLensPositionSelector = lensPosition,
initialConfiguration = cameraConfiguration,
executor = executor
)
private val orientationSensor by lazy {
OrientationSensor(
context = context,
device = device
)
}
init {
logger.recordMethod()
}
/**
* Starts camera.
*
* @throws IllegalStateException If the camera has already started.
*/
fun start() {
logger.recordMethod()
executor.execute(Operation {
device.bootStart(
orientationSensor = orientationSensor,
mainThreadErrorCallback = mainThreadErrorCallback
)
})
}
/**
* Stops camera.
*
* @throws IllegalStateException If the camera has not started.
*/
fun stop() {
logger.recordMethod()
executor.cancelTasks()
executor.execute(Operation {
device.shutDown(
orientationSensor = orientationSensor
)
})
}
/**
* Takes picture, returns immediately.
*
* @return [PhotoResult] which will deliver result asynchronously.
*/
fun takePicture(): PhotoResult {
logger.recordMethod()
val future = executor.execute(Operation(
cancellable = true,
function = device::takePhoto
))
return PhotoResult.fromFuture(future, logger)
}
/**
* Provides camera capabilities asynchronously, returns immediately.
*
* @return [CapabilitiesResult] which will deliver result asynchronously.
*/
fun getCapabilities(): CapabilitiesResult {
logger.recordMethod()
val future = executor.execute(Operation(
cancellable = true,
function = device::getCapabilities
))
return PendingResult.fromFuture(future, logger)
}
/**
* Provides current camera parameters asynchronously, returns immediately.
*
* @return [ParametersResult] which will deliver result asynchronously.
*/
fun getCurrentParameters(): ParametersResult {
logger.recordMethod()
val future = executor.execute(Operation(
cancellable = true,
function = device::getCurrentParameters
))
return PendingResult.fromFuture(future, logger)
}
/**
* Updates current configuration.
*
* @throws IllegalStateException If the current camera has not started.
*/
fun updateConfiguration(newConfiguration: Configuration) = executor.execute(
Operation(cancellable = true) {
logger.recordMethod()
device.updateDeviceConfiguration(newConfiguration)
})
/**
* Asynchronously updates zoom level of the camera.
* If zoom is not supported by the device - does nothing.
*
* @param zoomLevel Zoom level of the camera. A value between 0 and 1.
* @throws IllegalStateException If the current camera has not started.
*/
fun setZoom(@FloatRange(from = 0.0, to = 1.0) zoomLevel: Float) = executor.execute(
Operation(cancellable = true) {
logger.recordMethod()
device.updateZoomLevel(
zoomLevel = zoomLevel
)
})
/**
* Performs auto focus. If it is not available or not enabled, does nothing.
*
* @see Fotoapparat.focus
*/
fun autoFocus(): Fotoapparat = apply {
logger.recordMethod()
focus()
}
/**
* Attempts to focus the camera asynchronously.
*
* @return the pending result of focus operation which will deliver result asynchronously.
*
* @see Fotoapparat.autoFocus
*/
fun focus(): PendingResult<FocusResult> {
logger.recordMethod()
val future = executor.execute(Operation(
cancellable = true,
function = device::focus
))
return PendingResult.fromFuture(future, logger)
}
/**
* Switches to another camera. If previous camera has already started then it will be
* stopped automatically and new will start.
*/
fun switchTo(
lensPosition: LensPositionSelector,
cameraConfiguration: CameraConfiguration
) {
logger.recordMethod()
executor.execute(Operation(cancellable = true) {
device.switchCamera(
orientationSensor = orientationSensor,
newLensPositionSelector = lensPosition,
newConfiguration = cameraConfiguration,
mainThreadErrorCallback = mainThreadErrorCallback
)
})
}
/**
* @return `true` if selected lens position is available. `false` if it is not available.
*/
fun isAvailable(
selector: LensPositionSelector
): Boolean = device.canSelectCamera(selector)
companion object {
private val EXECUTOR = CameraExecutor()
@JvmStatic
fun with(context: Context): FotoapparatBuilder = FotoapparatBuilder(context)
}
}
@@ -1,220 +0,0 @@
package io.fotoapparat;
import android.content.Context;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import io.fotoapparat.error.CameraErrorCallback;
import java.util.Collection;
import io.fotoapparat.hardware.CameraDevice;
import io.fotoapparat.hardware.provider.CameraProvider;
import io.fotoapparat.log.Logger;
import io.fotoapparat.log.Loggers;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.ScaleType;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.parameter.range.Range;
import io.fotoapparat.parameter.selector.FlashSelectors;
import io.fotoapparat.parameter.selector.SelectorFunction;
import io.fotoapparat.parameter.selector.Selectors;
import io.fotoapparat.preview.FrameProcessor;
import io.fotoapparat.view.CameraRenderer;
import io.fotoapparat.view.CameraView;
import static io.fotoapparat.hardware.provider.CameraProviders.v1;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.autoFocus;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.continuousFocus;
import static io.fotoapparat.parameter.selector.FocusModeSelectors.fixed;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.back;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.external;
import static io.fotoapparat.parameter.selector.LensPositionSelectors.front;
import static io.fotoapparat.parameter.selector.Selectors.firstAvailable;
import static io.fotoapparat.parameter.selector.SizeSelectors.biggestSize;
/**
* Builder for {@link Fotoapparat}.
*/
public class FotoapparatBuilder {
Context context;
CameraProvider cameraProvider = v1();
CameraRenderer renderer;
SelectorFunction<Collection<LensPosition>, LensPosition> lensPositionSelector = firstAvailable(
back(),
front(),
external()
);
SelectorFunction<Collection<Size>, Size> photoSizeSelector = biggestSize();
SelectorFunction<Collection<Size>, Size> previewSizeSelector = biggestSize();
SelectorFunction<Collection<FocusMode>, FocusMode> focusModeSelector = firstAvailable(
continuousFocus(),
autoFocus(),
fixed()
);
SelectorFunction<Collection<Flash>, Flash> flashSelector = FlashSelectors.off();
SelectorFunction<Collection<Range<Integer>>, Range<Integer>> previewFpsRangeSelector = Selectors.nothing();
SelectorFunction<Range<Integer>, Integer> sensorSensitivitySelector = Selectors.nothing();
Integer jpegQuality;
ScaleType scaleType = ScaleType.CENTER_CROP;
FrameProcessor frameProcessor = null;
Logger logger = Loggers.none();
CameraErrorCallback cameraErrorCallback = CameraErrorCallback.NULL;
FotoapparatBuilder(@NonNull Context context) {
this.context = context;
}
/**
* @param cameraProvider decides which {@link CameraDevice} to use.
*/
public FotoapparatBuilder cameraProvider(@NonNull CameraProvider cameraProvider) {
this.cameraProvider = cameraProvider;
return this;
}
/**
* @param selector selects size of the photo (in pixels) from list of available sizes.
*/
public FotoapparatBuilder photoSize(@NonNull SelectorFunction<Collection<Size>, Size> selector) {
photoSizeSelector = selector;
return this;
}
/**
* @param selector selects size of preview stream (in pixels) from list of available sizes.
*/
public FotoapparatBuilder previewSize(@NonNull SelectorFunction<Collection<Size>, Size> selector) {
previewSizeSelector = selector;
return this;
}
/**
* @param scaleType of preview inside the view.
*/
public FotoapparatBuilder previewScaleType(ScaleType scaleType) {
this.scaleType = scaleType;
return this;
}
/**
* @param selector selects focus mode from list of available modes.
*/
public FotoapparatBuilder focusMode(@NonNull SelectorFunction<Collection<FocusMode>, FocusMode> selector) {
focusModeSelector = selector;
return this;
}
/**
* @param selector selects flash mode from list of available modes.
*/
public FotoapparatBuilder flash(@NonNull SelectorFunction<Collection<Flash>, Flash> selector) {
flashSelector = selector;
return this;
}
/**
* @param selector camera sensor position from list of available positions.
*/
public FotoapparatBuilder lensPosition(@NonNull SelectorFunction<Collection<LensPosition>, LensPosition> selector) {
lensPositionSelector = selector;
return this;
}
/**
* @param selector selects preview FPS range from list of available ranges.
*/
public FotoapparatBuilder previewFpsRange(@NonNull SelectorFunction<Collection<Range<Integer>>, Range<Integer>> selector) {
previewFpsRangeSelector = selector;
return this;
}
/**
* @param selector selects ISO value from range of available values.
*/
public FotoapparatBuilder sensorSensitivity(@NonNull SelectorFunction<Range<Integer>, Integer> selector) {
sensorSensitivitySelector = selector;
return this;
}
/**
* @param jpegQuality of the picture (1-100)
*/
public FotoapparatBuilder jpegQuality(@IntRange(from=0,to=100) @NonNull Integer jpegQuality) {
this.jpegQuality = jpegQuality;
return this;
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessor
*/
public FotoapparatBuilder frameProcessor(@NonNull FrameProcessor frameProcessor) {
this.frameProcessor = frameProcessor;
return this;
}
/**
* @param logger logger which will print logs. No logger is set by default.
* @see Loggers
*/
public FotoapparatBuilder logger(@NonNull Logger logger) {
this.logger = logger;
return this;
}
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorCallback
*/
public FotoapparatBuilder cameraErrorCallback(@NonNull CameraErrorCallback callback) {
this.cameraErrorCallback = callback;
return this;
}
/**
* @param renderer view which will draw the stream from the camera.
* @see CameraView
*/
public FotoapparatBuilder into(@NonNull CameraRenderer renderer) {
this.renderer = renderer;
return this;
}
/**
* @return set up instance of {@link Fotoapparat}.
* @throws IllegalStateException if some mandatory parameters are not specified.
*/
public Fotoapparat build() {
validate();
return Fotoapparat.create(this);
}
private void validate() {
if (cameraProvider == null) {
throw new IllegalStateException("CameraProvider is mandatory.");
}
if (renderer == null) {
throw new IllegalStateException("CameraRenderer is mandatory.");
}
if (lensPositionSelector == null) {
throw new IllegalStateException("LensPosition selector is mandatory.");
}
if (photoSizeSelector == null) {
throw new IllegalStateException("Photo size selector is mandatory.");
}
}
}
@@ -0,0 +1,203 @@
package io.fotoapparat
import android.content.Context
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.error.CameraErrorCallback
import io.fotoapparat.error.CameraErrorListener
import io.fotoapparat.log.Logger
import io.fotoapparat.log.none
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.preview.Frame
import io.fotoapparat.selector.*
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.CameraView
import io.fotoapparat.view.FocusView
import io.fotoapparat.preview.FrameProcessor as FrameProcessorJava
/**
* Builder for [Fotoapparat].
*/
class FotoapparatBuilder internal constructor(private var context: Context) {
internal var lensPositionSelector: LensPositionSelector = firstAvailable(
back(),
front(),
external()
)
internal var cameraErrorCallback: CameraErrorCallback = {}
internal var renderer: CameraRenderer? = null
internal var focusView: FocusView? = null
internal var scaleType: ScaleType = ScaleType.CenterCrop
internal var logger: Logger = none()
internal var configuration = CameraConfiguration.default()
/**
* @param selector camera sensor position from list of available positions.
*/
fun lensPosition(selector: LensPositionSelector): FotoapparatBuilder =
apply { lensPositionSelector = selector }
/**
* @param scaleType of preview inside the view.
*/
fun previewScaleType(scaleType: ScaleType): FotoapparatBuilder =
apply { this.scaleType = scaleType }
/**
* @param selector selects resolution of the photo (in pixels) from list of available resolutions.
*/
fun photoResolution(selector: ResolutionSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
pictureResolution = selector
)
}
/**
* @param selector selects size of preview stream (in pixels) from list of available resolutions.
*/
fun previewResolution(selector: ResolutionSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
previewResolution = selector
)
}
/**
* @param selector selects focus mode from list of available modes.
*/
fun focusMode(selector: FocusModeSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
focusMode = selector
)
}
/**
* @param selector selects flash mode from list of available modes.
*/
fun flash(selector: FlashSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
flashMode = selector
)
}
/**
* @param selector selects preview FPS range from list of available ranges.
*/
fun previewFpsRange(selector: FpsRangeSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
previewFpsRange = selector
)
}
/**
* @param selector selects ISO value from range of available values.
*/
fun sensorSensitivity(selector: SensorSensitivitySelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
sensorSensitivity = selector
)
}
/**
* @param selector of the Jpeg picture quality.
*/
fun jpegQuality(selector: QualitySelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
jpegQuality = selector
)
}
/**
* @param selector selects exposure compensation value from available range.
*/
fun exposureCompensation(selector: ExposureSelector): FotoapparatBuilder = apply {
configuration = configuration.copy(
exposureCompensation = selector
)
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessorJava
*/
fun frameProcessor(frameProcessor: FrameProcessor): FotoapparatBuilder = apply {
configuration = configuration.copy(
frameProcessor = frameProcessor
)
}
/**
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessorJava
*/
fun frameProcessor(frameProcessor: FrameProcessorJava?): FotoapparatBuilder = apply {
configuration = configuration.copy(
frameProcessor = frameProcessor?.let { it::process }
)
}
/**
* @param logger logger which will print logs. No logger is set by default.
* @see io.fotoapparat.log.Loggers
*/
fun logger(logger: Logger): FotoapparatBuilder =
apply { this.logger = logger }
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorListener
*/
fun cameraErrorCallback(callback: CameraErrorListener): FotoapparatBuilder =
apply { cameraErrorCallback = { callback.onError(it) } }
/**
* @param callback which will be notified when camera error happens in Fotoapparat.
* @see CameraErrorListener
*/
fun cameraErrorCallback(callback: CameraErrorCallback): FotoapparatBuilder =
apply { cameraErrorCallback = callback }
/**
* @param renderer view which will draw the stream from the camera.
* @see CameraView
*/
fun into(renderer: CameraRenderer): FotoapparatBuilder =
apply { this.renderer = renderer }
/**
* @param focusView view which will be used for touch to focus.
* @see FocusView
*/
fun focusView(focusView: FocusView): FotoapparatBuilder =
apply { this.focusView = focusView }
/**
* @return set up instance of [Fotoapparat].
* @throws IllegalStateException if some mandatory parameters are not specified.
*/
fun build() = buildInternal(
renderer = renderer
)
private fun buildInternal(
renderer: CameraRenderer?
): Fotoapparat {
if (renderer == null) {
throw IllegalStateException("CameraRenderer is mandatory.")
}
return Fotoapparat(
context = context,
view = renderer,
focusView = focusView,
lensPosition = lensPositionSelector,
cameraConfiguration = configuration,
scaleType = scaleType,
cameraErrorCallback = cameraErrorCallback,
logger = logger
)
}
}
@@ -1,77 +0,0 @@
package io.fotoapparat;
import android.support.annotation.NonNull;
/**
* Switches between different instances of {@link Fotoapparat}. Convenient when you want to allow
* user to switch between different cameras or configurations.
* <p>
* This class is not thread safe. Consider using it from a single thread.
*/
public class FotoapparatSwitcher {
@NonNull
private Fotoapparat fotoapparat;
private boolean started = false;
private FotoapparatSwitcher(@NonNull Fotoapparat fotoapparat) {
this.fotoapparat = fotoapparat;
}
/**
* @return {@link FotoapparatSwitcher} with given {@link Fotoapparat} used by default.
*/
public static FotoapparatSwitcher withDefault(@NonNull Fotoapparat fotoapparat) {
return new FotoapparatSwitcher(fotoapparat);
}
/**
* Starts {@link Fotoapparat} associated with this switcher. Every new {@link Fotoapparat} will
* be started automatically until {@link #stop()} is called.
*
* @throws IllegalStateException if switcher is already started.
*/
public void start() {
fotoapparat.start();
started = true;
}
/**
* Stops currently used {@link Fotoapparat}.
*
* @throws IllegalStateException if switcher is already stopped.
*/
public void stop() {
fotoapparat.stop();
started = false;
}
/**
* Switches to another {@link Fotoapparat}. If switcher is already started then previously used
* {@link Fotoapparat} will be stopped automatically and new {@link Fotoapparat} will be
* started.
*
* @param fotoapparat new {@link Fotoapparat} to use.
* @throws NullPointerException if given {@link Fotoapparat} is {@code null}.
*/
public void switchTo(@NonNull Fotoapparat fotoapparat) {
if (started) {
this.fotoapparat.stop();
fotoapparat.start();
}
this.fotoapparat = fotoapparat;
}
/**
* @return currently used instance of {@link Fotoapparat}.
*/
@NonNull
public Fotoapparat getCurrentFotoapparat() {
return fotoapparat;
}
}
@@ -0,0 +1,59 @@
package io.fotoapparat.capability
import io.fotoapparat.parameter.*
import io.fotoapparat.util.lineSeparator
import io.fotoapparat.util.wrap
/**
* Capabilities of camera hardware.
*
* Sensor sensitivities is not guaranteed to always contain a value.
*/
data class Capabilities(
val zoom: Zoom,
val flashModes: Set<Flash>,
val focusModes: Set<FocusMode>,
val canSmoothZoom: Boolean,
val maxFocusAreas: Int,
val maxMeteringAreas: Int,
val jpegQualityRange: IntRange,
val exposureCompensationRange: IntRange,
val previewFpsRanges: Set<FpsRange>,
val antiBandingModes: Set<AntiBandingMode>,
val pictureResolutions: Set<Resolution>,
val previewResolutions: Set<Resolution>,
val sensorSensitivities: Set<Int>
) {
init {
flashModes.ensureNotEmpty()
focusModes.ensureNotEmpty()
antiBandingModes.ensureNotEmpty()
previewFpsRanges.ensureNotEmpty()
pictureResolutions.ensureNotEmpty()
previewResolutions.ensureNotEmpty()
}
override fun toString(): String {
return "Capabilities" + lineSeparator +
"zoom:" + zoom.wrap() +
"flashModes:" + flashModes.wrap() +
"focusModes:" + focusModes.wrap() +
"canSmoothZoom:" + canSmoothZoom.wrap() +
"maxFocusAreas:" + maxFocusAreas.wrap() +
"maxMeteringAreas:" + maxMeteringAreas.wrap() +
"jpegQualityRange:" + jpegQualityRange.wrap() +
"exposureCompensationRange:" + exposureCompensationRange.wrap() +
"antiBandingModes:" + antiBandingModes.wrap() +
"previewFpsRanges:" + previewFpsRanges.wrap() +
"pictureResolutions:" + pictureResolutions.wrap() +
"previewResolutions:" + previewResolutions.wrap() +
"sensorSensitivities:" + sensorSensitivities.wrap()
}
}
private inline fun <reified E> Set<E>.ensureNotEmpty() {
if (isEmpty()) {
throw IllegalArgumentException("Capabilities cannot have an empty Set<${E::class.java.simpleName}>.")
}
}
@@ -0,0 +1,35 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.capability.provide
import android.hardware.Camera
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.parameter.SupportedParameters
import io.fotoapparat.parameter.camera.convert.*
/**
* Returns the [io.fotoapparat.capability.Capabilities] of the given [Camera].
*/
internal fun Camera.getCapabilities() = SupportedParameters(parameters).getCapabilities()
private fun SupportedParameters.getCapabilities(): Capabilities {
return Capabilities(
zoom = supportedZoom,
flashModes = flashModes.extract { it.toFlash() },
focusModes = focusModes.extract { it.toFocusMode() },
maxFocusAreas = maxNumFocusAreas,
canSmoothZoom = supportedSmoothZoom,
maxMeteringAreas = maxNumMeteringAreas,
jpegQualityRange = jpegQualityRange,
exposureCompensationRange = exposureCompensationRange,
antiBandingModes = supportedAutoBandingModes.extract(String::toAntiBandingMode),
sensorSensitivities = sensorSensitivities.toSet(),
previewFpsRanges = supportedPreviewFpsRanges.extract { it.toFpsRange() },
pictureResolutions = pictureResolutions.mapSizes(),
previewResolutions = previewResolutions.mapSizes()
)
}
private fun <Parameter : Any, Code> List<Code>.extract(converter: (Code) -> Parameter?) = mapNotNull { converter(it) }.toSet()
private fun Collection<Camera.Size>.mapSizes() = map { it.toResolution() }.toSet()
@@ -0,0 +1,21 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.characteristic
import android.hardware.Camera
import io.fotoapparat.hardware.orientation.toOrientation
/**
* Returns the [Characteristics] for the given `cameraId`.
*/
internal fun getCharacteristics(cameraId: Int): Characteristics {
val info = Camera.CameraInfo()
Camera.getCameraInfo(cameraId, info)
val lensPosition = info.facing.toLensPosition()
return Characteristics(
cameraId = cameraId,
lensPosition = lensPosition,
cameraOrientation = info.orientation.toOrientation(),
isMirrored = lensPosition == LensPosition.Front
)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.characteristic
/**
* A camera characteristic.
*/
interface Characteristic
@@ -0,0 +1,13 @@
package io.fotoapparat.characteristic
import io.fotoapparat.hardware.orientation.Orientation
/**
* A set of information about the camera.
*/
internal data class Characteristics(
val cameraId: Int,
val lensPosition: LensPosition,
val cameraOrientation: Orientation,
val isMirrored: Boolean
)
@@ -0,0 +1,29 @@
package io.fotoapparat.characteristic
/**
* The camera position relatively to the screen of the device.
*/
sealed class LensPosition : Characteristic {
/**
* The back camera.
*/
object Back : LensPosition() {
override fun toString(): String = "LensPosition.Back"
}
/**
* The front camera.
*/
object Front : LensPosition() {
override fun toString(): String = "LensPosition.Front"
}
/**
* An external camera.
*/
object External : LensPosition() {
override fun toString(): String = "LensPosition.External"
}
}
@@ -0,0 +1,39 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.characteristic
import android.hardware.Camera
import io.fotoapparat.exception.camera.CameraException
/**
* On some devices with double cameras v1 still returns external lens position (as in v2).
*/
private const val CAMERA_FACING_EXTERNAL = 2
/**
* Maps between [LensPosition] and Camera v1 lens position code id.
*
* @receiver Camera facing info id.
* @return [LensPosition] from the given lens position code id.
* `null` if position code id is not supported.
*/
internal fun Int.toLensPosition(): LensPosition =
when (this) {
Camera.CameraInfo.CAMERA_FACING_FRONT -> LensPosition.Front
Camera.CameraInfo.CAMERA_FACING_BACK -> LensPosition.Back
CAMERA_FACING_EXTERNAL -> LensPosition.External
else -> throw IllegalArgumentException("Lens position $this is not supported.")
}
/**
* Maps between [LensPosition] and Camera v1 code id.
*
* @receiver [LensPosition]
* @return code of the camera as in [Camera.CameraInfo].
*/
fun LensPosition.toCameraId(): Int =
(0 until Camera.getNumberOfCameras())
.find { cameraId ->
this == getCharacteristics(cameraId).lensPosition
}
?: throw CameraException("Device has no camera for the desired lens position(s).")
@@ -0,0 +1,78 @@
package io.fotoapparat.concurrent
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
/**
* Executes camera related operations using a dedicated thread and provides a control over the queue
* of those operations.
*
* This class should be accessed only from a one thread at a time.
*/
class CameraExecutor(
private val executor: ExecutorService = Executors.newSingleThreadExecutor()
) {
private val cancellableTasksQueue = LinkedList<Future<*>>()
/**
* Adds operation to the serial execution queue.
*/
fun <T> execute(operation: Operation<T>): Future<T> {
val future = executor.submit(Callable {
operation.function()
})
if (operation.cancellable) {
cancellableTasksQueue += future
}
cleanUpCancelledTasks()
return future
}
private fun cleanUpCancelledTasks() {
cancellableTasksQueue.removeAll {
!it.isPending
}
}
/**
* Cancels all cancellable tasks in the queue. Non-cancellable tasks would still be executed.
*
* After this operation is completed the executor is still in a valid state and can be used
* further.
*/
fun cancelTasks() {
cancellableTasksQueue
.filter { it.isPending }
.forEach {
it.cancel(true)
}
cancellableTasksQueue.clear()
}
private val Future<*>.isPending get() = !isCancelled && !isDone
/**
* Operation to be executed.
*/
data class Operation<out T>(
/**
* `true` if operation could be cancelled. `false` if operation can not be cancelled.
*/
val cancellable: Boolean = false,
/**
* Body of the operation.
*/
val function: () -> T
)
}
@@ -0,0 +1,12 @@
package io.fotoapparat.concurrent
import android.os.Looper
/**
* Throws [IllegalThreadStateException] if called from main thread.
*/
fun ensureBackgroundThread() {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw IllegalThreadStateException("Operation should not run from main thread.")
}
}
@@ -0,0 +1,130 @@
package io.fotoapparat.configuration
import io.fotoapparat.selector.*
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.preview.FrameProcessor as FrameProcessorJava
private const val DEFAULT_JPEG_QUALITY = 90
private const val DEFAULT_EXPOSURE_COMPENSATION = 0
/**
* A camera configuration which has all it's selectors defined.
*/
data class CameraConfiguration(
override val flashMode: FlashSelector = off(),
override val focusMode: FocusModeSelector = firstAvailable(
continuousFocusPicture(),
autoFocus(),
fixed(),
infinity()
),
override val jpegQuality: QualitySelector = manualJpegQuality(DEFAULT_JPEG_QUALITY),
override val exposureCompensation: ExposureSelector = manualExposure(DEFAULT_EXPOSURE_COMPENSATION),
override val frameProcessor: FrameProcessor? = null,
override val previewFpsRange: FpsRangeSelector = highestFps(),
override val antiBandingMode: AntiBandingModeSelector = firstAvailable(
auto(),
hz50(),
hz60(),
none()
),
override val sensorSensitivity: SensorSensitivitySelector? = null,
override val pictureResolution: ResolutionSelector = highestResolution(),
override val previewResolution: ResolutionSelector = highestResolution()
) : Configuration {
/**
* Builder for [CameraConfiguration].
*/
class Builder internal constructor() {
private var cameraConfiguration: CameraConfiguration = default()
fun flash(selector: FlashSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
flashMode = selector
)
}
fun focusMode(selector: FocusModeSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
focusMode = selector
)
}
fun previewFpsRange(selector: FpsRangeSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
previewFpsRange = selector
)
}
fun exposureCompensation(selector: ExposureSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
exposureCompensation = selector
)
}
fun antiBandingMode(selector: AntiBandingModeSelector): Builder = apply {
cameraConfiguration.copy(
antiBandingMode = selector
)
}
fun jpegQuality(selector: QualitySelector): Builder = apply {
cameraConfiguration.copy(
jpegQuality = selector
)
}
fun sensorSensitivity(selector: SensorSensitivitySelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
sensorSensitivity = selector
)
}
fun previewResolution(selector: ResolutionSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
previewResolution = selector
)
}
fun photoResolution(selector: ResolutionSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
pictureResolution = selector
)
}
fun frameProcessor(frameProcessor: FrameProcessorJava?): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
frameProcessor = frameProcessor?.let { it::process }
)
}
/**
* Builds a new [CameraConfiguration].
*/
fun build(): CameraConfiguration = cameraConfiguration
}
companion object {
/**
* Alias for [CameraConfiguration.default]
*/
@JvmStatic
fun standard(): CameraConfiguration = default()
/**
* Default [CameraConfiguration].
*/
@JvmStatic
fun default(): CameraConfiguration = CameraConfiguration()
/**
* Creates a new [CameraConfiguration.Builder].
*/
@JvmStatic
fun builder(): Builder = Builder()
}
}
@@ -0,0 +1,17 @@
package io.fotoapparat.configuration
import io.fotoapparat.selector.*
import io.fotoapparat.util.FrameProcessor
interface Configuration {
val flashMode: FlashSelector?
val focusMode: FocusModeSelector?
val jpegQuality: QualitySelector?
val exposureCompensation: ExposureSelector?
val frameProcessor: FrameProcessor?
val previewFpsRange: FpsRangeSelector?
val antiBandingMode: AntiBandingModeSelector?
val sensorSensitivity: SensorSensitivitySelector?
val previewResolution: ResolutionSelector?
val pictureResolution: ResolutionSelector?
}
@@ -0,0 +1,103 @@
package io.fotoapparat.configuration
import io.fotoapparat.selector.*
import io.fotoapparat.util.FrameProcessor
/**
* A camera update configuration.
*/
data class UpdateConfiguration(
override val flashMode: FlashSelector? = null,
override val focusMode: FocusModeSelector? = null,
override val jpegQuality: QualitySelector? = null,
override val exposureCompensation: ExposureSelector? = null,
override val frameProcessor: FrameProcessor? = null,
override val previewFpsRange: FpsRangeSelector? = null,
override val antiBandingMode: AntiBandingModeSelector? = null,
override val sensorSensitivity: SensorSensitivitySelector? = null,
override val previewResolution: ResolutionSelector? = null,
override val pictureResolution: ResolutionSelector? = null
) : Configuration {
/**
* Builder for [UpdateConfiguration].
*/
class Builder internal constructor() {
private var configuration = UpdateConfiguration()
fun flash(selector: FlashSelector): Builder = apply {
configuration = configuration.copy(
flashMode = selector
)
}
fun focusMode(selector: FocusModeSelector): Builder = apply {
configuration = configuration.copy(
focusMode = selector
)
}
fun previewFpsRange(selector: FpsRangeSelector): Builder = apply {
configuration = configuration.copy(
previewFpsRange = selector
)
}
fun sensorSensitivity(selector: SensorSensitivitySelector): Builder = apply {
configuration = configuration.copy(
sensorSensitivity = selector
)
}
fun antiBandingMode(selector: AntiBandingModeSelector): Builder = apply {
configuration = configuration.copy(
antiBandingMode = selector
)
}
fun jpegQuality(selector: QualitySelector): Builder = apply {
configuration = configuration.copy(
jpegQuality = selector
)
}
fun exposureCompensation(selector: ExposureSelector): Builder = apply {
configuration = configuration.copy(
exposureCompensation = selector
)
}
fun previewResolution(selector: ResolutionSelector): Builder = apply {
configuration = configuration.copy(
previewResolution = selector
)
}
fun photoResolution(selector: ResolutionSelector): Builder = apply {
configuration = configuration.copy(
pictureResolution = selector
)
}
fun frameProcessor(frameProcessor: FrameProcessor?): Builder = apply {
configuration = configuration.copy(
frameProcessor = frameProcessor
)
}
/**
* Builds a new [UpdateConfiguration].
*/
fun build(): UpdateConfiguration = configuration
}
companion object {
/**
* Creates a new [UpdateConfiguration.Builder].
*/
@JvmStatic
fun builder(): Builder = Builder()
}
}
@@ -0,0 +1,43 @@
package io.fotoapparat.coroutines
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
/**
* A [ConflatedBroadcastChannel] which exposes a [getValue] which will [await] for at least one value.
*/
@ExperimentalCoroutinesApi
internal class AwaitBroadcastChannel<T>(
private val channel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel(),
private val deferred: CompletableDeferred<Boolean> = CompletableDeferred()
) : BroadcastChannel<T> by channel, Deferred<Boolean> by deferred {
/**
* The most recently sent element to this channel.
*/
suspend fun getValue(): T {
deferred.await()
return channel.value
}
override fun offer(element: T): Boolean {
deferred.complete(true)
return channel.offer(element)
}
override suspend fun send(element: T) {
deferred.complete(true)
channel.send(element)
}
override fun cancel(cause: CancellationException?) {
channel.cancel(cause)
deferred.cancel(cause)
}
override fun cancel(cause: Throwable?): Boolean {
deferred.cancel(cause?.message ?:"", cause)
return channel.close(cause)
}
}
@@ -1,36 +0,0 @@
package io.fotoapparat.error;
import android.os.Handler;
import android.os.Looper;
import io.fotoapparat.hardware.CameraException;
/**
* Factory methods for callbacks.
*/
public class Callbacks {
private static final Handler MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
/**
* @return CameraErrorCallback which will always move execution to the main thread.
*/
public static CameraErrorCallback onMainThread(final CameraErrorCallback original) {
return new CameraErrorCallback() {
@Override
public void onError(final CameraException e) {
if (Looper.myLooper() == Looper.getMainLooper()) {
original.onError(e);
} else {
MAIN_THREAD_HANDLER.post(new Runnable() {
@Override
public void run() {
original.onError(e);
}
});
}
}
};
}
}
@@ -1,27 +0,0 @@
package io.fotoapparat.error;
import io.fotoapparat.hardware.CameraException;
/**
* Notified when an camera error happens within Fotoapparat.
* <p>
* This method is always called from the main thread.
*/
public interface CameraErrorCallback {
/**
* No-op implementation of {@link CameraErrorCallback}.
*/
CameraErrorCallback NULL = new CameraErrorCallback() {
@Override
public void onError(CameraException e) {
// Do nothing
}
};
/**
* Notified when a camera error happens within Fotoapparat.
*/
void onError(CameraException e);
}
@@ -0,0 +1,16 @@
package io.fotoapparat.error
import io.fotoapparat.exception.camera.CameraException
/**
* Notified when an camera error happens within Fotoapparat.
*
* This method is always called from the main thread.
*/
interface CameraErrorListener {
/**
* Notified when a camera error happens within Fotoapparat.
*/
fun onError(e: CameraException)
}
@@ -0,0 +1,18 @@
package io.fotoapparat.error
import android.os.Looper
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.executeMainThread
typealias CameraErrorCallback = (CameraException) -> Unit
/**
* @return CameraErrorCallback which will always move execution to the main thread.
*/
fun CameraErrorCallback.onMainThread(): CameraErrorCallback = { cameraException ->
if (Looper.myLooper() == Looper.getMainLooper()) {
this(cameraException)
} else {
executeMainThread { this(cameraException) }
}
}
@@ -0,0 +1,6 @@
package io.fotoapparat.exception
/**
* Thrown when there is a problem while saving the file.
*/
class FileSaveException(cause: Throwable) : RuntimeException(cause)
@@ -0,0 +1,6 @@
package io.fotoapparat.exception
/**
* Thrown when zoom level is outside of [0..1] range.
*/
class LevelOutOfRangeException(zoomLevel: Float) : RuntimeException(zoomLevel.toString() + " is out of range [0..1]")
@@ -0,0 +1,11 @@
package io.fotoapparat.exception
/**
* Exception which is not caused by developer and can be either ignored or recovered from.
*/
open class RecoverableRuntimeException : RuntimeException {
constructor(message: String) : super(message)
constructor(throwable: Throwable) : super(throwable)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.exception
/**
* Thrown when it is not possible to decode bitmap from byte array.
*/
class UnableToDecodeBitmapException : RecoverableRuntimeException("Unable to decode bitmap")
@@ -0,0 +1,12 @@
package io.fotoapparat.exception.camera
/**
* A generic camera exception.
*/
open class CameraException(
message: String,
cause: Throwable? = null
) : RuntimeException(
message,
cause
)
@@ -0,0 +1,30 @@
package io.fotoapparat.exception.camera
import io.fotoapparat.parameter.Parameter
/**
* Thrown to indicate that a selected [Parameter] is not in the supported set.
*
* e.g. Camera supports flash `on`, `off` & `auto` and you ask for a `tomato`.
*/
internal class InvalidConfigurationException : CameraException {
constructor(
value: Any,
klass: Class<out Parameter>,
supportedParameters: Collection<Parameter>
) : super(
"${klass.simpleName} configuration selector selected value $value. " +
"However it's not in the supported set of values. " +
"Supported parameters: $supportedParameters"
)
constructor(
value: Any,
klass: Class<out Comparable<*>>,
supportedRange: ClosedRange<*>
) : super(
"${klass.simpleName} configuration selector selected value $value. " +
"However it's not in the supported set of values. " +
"Supported parameters from: ${supportedRange.start} to ${supportedRange.endInclusive}."
)
}
@@ -0,0 +1,9 @@
package io.fotoapparat.exception.camera
/**
* Thrown when the preview surface didn't become available.
*/
class UnavailableSurfaceException : CameraException(
"No preview surface became available before CameraView got detached from window. " +
"Camera didn't start. You may ignore this exception."
)
@@ -0,0 +1,25 @@
package io.fotoapparat.exception.camera
import io.fotoapparat.parameter.Parameter
/**
* Thrown to indicate that a configuration selector couldn't select a value.
*/
internal class UnsupportedConfigurationException : CameraException {
constructor(
klass: Class<out Parameter>,
supportedParameters: Collection<Parameter>
) : super(
"${klass.simpleName} configuration selector couldn't select a value. " +
"Supported parameters: $supportedParameters"
)
constructor(
configurationName: String,
supportedRange: ClosedRange<*>
) : super(
"$configurationName configuration selector couldn't select a value. " +
"Supported parameters from: ${supportedRange.start} to ${supportedRange.endInclusive}."
)
}
@@ -0,0 +1,6 @@
package io.fotoapparat.exception.camera
/**
* Thrown to indicate that the device has no camera for the desired lens position(s).
*/
class UnsupportedLensException : CameraException("Device has no camera for the desired lens position(s).")
@@ -0,0 +1,18 @@
package io.fotoapparat.exif
import java.io.File
/**
* Writes Exif orientation attributes.
*/
internal interface ExifOrientationWriter {
/**
* Writes EXIF orientation tag into a file, overwriting it if it already exists.
*
* @param file File of the image.
* @param photo Photo stored in the file.
* @throws FileSaveException If writing has failed.
*/
fun writeExifOrientation(file: File, rotationDegrees: Int)
}
@@ -0,0 +1,39 @@
package io.fotoapparat.exif
import androidx.exifinterface.media.ExifInterface
import io.fotoapparat.exception.FileSaveException
import java.io.File
import java.io.IOException
/**
* Writes Exif attributes.
*/
internal object ExifWriter : ExifOrientationWriter {
@Throws(FileSaveException::class)
override fun writeExifOrientation(file: File, rotationDegrees: Int) {
try {
ExifInterface(file.path).apply {
setAttribute(
ExifInterface.TAG_ORIENTATION,
toExifOrientation(rotationDegrees).toString()
)
saveAttributes()
}
} catch (e: IOException) {
throw FileSaveException(e)
}
}
private fun toExifOrientation(rotationDegrees: Int): Int {
val compensationRotationDegrees = (360 - rotationDegrees) % 360
return when (compensationRotationDegrees) {
90 -> ExifInterface.ORIENTATION_ROTATE_90
180 -> ExifInterface.ORIENTATION_ROTATE_180
270 -> ExifInterface.ORIENTATION_ROTATE_270
else -> ExifInterface.ORIENTATION_NORMAL
}
}
}
@@ -1,83 +0,0 @@
package io.fotoapparat.hardware;
import android.support.annotation.FloatRange;
import java.util.List;
import io.fotoapparat.hardware.operators.AutoFocusOperator;
import io.fotoapparat.hardware.operators.CapabilitiesOperator;
import io.fotoapparat.hardware.operators.CaptureOperator;
import io.fotoapparat.hardware.operators.ConnectionOperator;
import io.fotoapparat.hardware.operators.ExposureMeasurementOperator;
import io.fotoapparat.hardware.operators.OrientationOperator;
import io.fotoapparat.hardware.operators.ParametersOperator;
import io.fotoapparat.hardware.operators.PreviewOperator;
import io.fotoapparat.hardware.operators.PreviewStreamOperator;
import io.fotoapparat.hardware.operators.RendererParametersOperator;
import io.fotoapparat.hardware.operators.SurfaceOperator;
import io.fotoapparat.hardware.operators.ZoomOperator;
import io.fotoapparat.hardware.provider.AvailableLensPositionsProvider;
import io.fotoapparat.lens.FocusResult;
import io.fotoapparat.parameter.LensPosition;
import io.fotoapparat.parameter.Parameters;
import io.fotoapparat.parameter.RendererParameters;
import io.fotoapparat.photo.Photo;
import io.fotoapparat.preview.PreviewStream;
/**
* Abstraction for camera hardware.
*/
public interface CameraDevice extends CaptureOperator,
PreviewOperator, CapabilitiesOperator, OrientationOperator, ParametersOperator,
ConnectionOperator, SurfaceOperator, PreviewStreamOperator, RendererParametersOperator,
ExposureMeasurementOperator, AutoFocusOperator, AvailableLensPositionsProvider,
ZoomOperator {
@Override
void open(LensPosition lensPosition);
@Override
void close();
@Override
void startPreview();
@Override
void stopPreview();
@Override
void setDisplaySurface(Object displaySurface);
@Override
void setDisplayOrientation(int degrees);
@Override
void updateParameters(Parameters parameters);
@Override
Capabilities getCapabilities();
@Override
FocusResult autoFocus();
@Override
void measureExposure();
@Override
Photo takePicture();
@Override
PreviewStream getPreviewStream();
@Override
RendererParameters getRendererParameters();
@Override
List<LensPosition> getAvailableLensPositions();
@Override
void setZoom(@FloatRange(from = 0f, to = 1f) float level);
Parameters getCurrentParameters();
}
@@ -0,0 +1,424 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware
import android.hardware.Camera
import android.media.MediaRecorder
import android.view.Surface
import androidx.annotation.FloatRange
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.capability.provide.getCapabilities
import io.fotoapparat.characteristic.Characteristics
import io.fotoapparat.characteristic.toCameraId
import io.fotoapparat.coroutines.AwaitBroadcastChannel
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.metering.FocalRequest
import io.fotoapparat.hardware.metering.convert.toFocusAreas
import io.fotoapparat.hardware.orientation.*
import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.FocusMode
import io.fotoapparat.parameter.Resolution
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.parameter.camera.apply.applyInto
import io.fotoapparat.parameter.camera.convert.toCode
import io.fotoapparat.preview.PreviewStream
import io.fotoapparat.result.FocusResult
import io.fotoapparat.result.Photo
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.util.lineSeparator
import io.fotoapparat.view.Preview
import kotlinx.coroutines.CompletableDeferred
import java.io.IOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
typealias PreviewSize = io.fotoapparat.parameter.Resolution
/**
* Camera.
*/
internal open class CameraDevice(
private val logger: Logger,
val characteristics: Characteristics
) {
private val capabilities = CompletableDeferred<Capabilities>()
private val cameraParameters = AwaitBroadcastChannel<CameraParameters>()
private lateinit var previewStream: PreviewStream
private lateinit var surface: Surface
private lateinit var camera: Camera
private var cachedCameraParameters: Camera.Parameters? = null
private lateinit var displayOrientation: Orientation
private lateinit var imageOrientation: Orientation
private lateinit var previewOrientation: Orientation
/**
* Opens a connection to a camera.
*/
open fun open() {
logger.recordMethod()
val lensPosition = characteristics.lensPosition
val cameraId = lensPosition.toCameraId()
try {
camera = Camera.open(cameraId)
capabilities.complete(camera.getCapabilities())
previewStream = PreviewStream(camera)
} catch (e: RuntimeException) {
throw CameraException(
message = "Failed to open camera with lens position: $lensPosition and id: $cameraId",
cause = e
)
}
}
/**
* Closes the connection to a camera.
*/
open fun close() {
logger.recordMethod()
surface.release()
camera.release()
}
/**
* Starts preview.
*/
open fun startPreview() {
logger.recordMethod()
try {
camera.startPreview()
} catch (e: RuntimeException) {
throw CameraException(
message = "Failed to start preview for camera with lens " +
"position: ${characteristics.lensPosition} and id: ${characteristics.cameraId}",
cause = e
)
}
}
/**
* Stops preview.
*/
open fun stopPreview() {
logger.recordMethod()
camera.stopPreview()
}
/**
* Unlock camera.
*/
open fun unlock() {
logger.recordMethod()
camera.unlock()
}
/**
* Lock camera.
*/
open fun lock() {
logger.recordMethod()
camera.lock()
}
/**
* Invokes a still photo capture action.
*
* @return The captured photo.
*/
open fun takePhoto(): Photo {
logger.recordMethod()
return camera.takePhoto(imageOrientation.degrees)
}
/**
* Returns the [Capabilities] of the camera.
*/
open suspend fun getCapabilities(): Capabilities {
logger.recordMethod()
return capabilities.await()
}
/**
* Returns the [CameraParameters] used.
*/
open suspend fun getParameters(): CameraParameters {
logger.recordMethod()
return cameraParameters.getValue()
}
/**
* Updates the desired camera parameters.
*/
open suspend fun updateParameters(cameraParameters: CameraParameters) {
logger.recordMethod()
this.cameraParameters.send(cameraParameters)
logger.log("New camera parameters are: $cameraParameters")
cameraParameters.applyInto(cachedCameraParameters ?: camera.parameters)
.cacheLocally()
.setInCamera()
}
/**
* Updates the frame processor.
*/
open fun updateFrameProcessor(frameProcessor: FrameProcessor?) {
logger.recordMethod()
previewStream.updateProcessorSafely(frameProcessor)
}
/**
* Sets the current orientation of the display.
*/
open fun setDisplayOrientation(orientationState: OrientationState) {
logger.recordMethod()
imageOrientation = computeImageOrientation(
deviceOrientation = orientationState.deviceOrientation,
cameraOrientation = characteristics.cameraOrientation,
cameraIsMirrored = characteristics.isMirrored
)
displayOrientation = computeDisplayOrientation(
screenOrientation = orientationState.screenOrientation,
cameraOrientation = characteristics.cameraOrientation,
cameraIsMirrored = characteristics.isMirrored
)
previewOrientation = computePreviewOrientation(
screenOrientation = orientationState.screenOrientation,
cameraOrientation = characteristics.cameraOrientation,
cameraIsMirrored = characteristics.isMirrored
)
logger.log("Orientations: $lineSeparator" +
"Screen orientation (preview) is: ${orientationState.screenOrientation}. " + lineSeparator +
"Camera sensor orientation is always at: ${characteristics.cameraOrientation}. " + lineSeparator +
"Camera is " + if (characteristics.isMirrored) "mirrored." else "not mirrored."
)
logger.log("Orientation adjustments: $lineSeparator" +
"Image orientation will be adjusted by: ${imageOrientation.degrees} degrees. " + lineSeparator +
"Display orientation will be adjusted by: ${displayOrientation.degrees} degrees. " + lineSeparator +
"Preview orientation will be adjusted by: ${previewOrientation.degrees} degrees."
)
previewStream.frameOrientation = previewOrientation
camera.setDisplayOrientation(displayOrientation.degrees)
}
/**
* Changes zoom level of the camera. Must be called only if zoom is supported.
*
* @param level normalized zoom level. Value in range [0..1].
*/
open fun setZoom(@FloatRange(from = 0.0, to = 1.0) level: Float) {
logger.recordMethod()
setZoomSafely(level)
}
/**
* Performs auto focus. This is a blocking operation which returns the result of the operation
* when auto focus completes.
*/
open fun autoFocus(): FocusResult {
logger.recordMethod()
return camera.focusSafely()
}
/**
* Sets the point where the focus & exposure metering will happen.
*/
open suspend fun setFocalPoint(focalRequest: FocalRequest) {
logger.recordMethod()
if (capabilities.await().canSetFocusingAreas()) {
camera.updateFocusingAreas(focalRequest)
}
}
/**
* Clears the point where the focus & exposure will happen.
*/
open fun clearFocalPoint() {
logger.recordMethod()
camera.clearFocusingAreas()
}
/**
* Sets the desired surface on which the camera's preview will be displayed.
*/
@Throws(IOException::class)
open fun setDisplaySurface(preview: Preview) {
logger.recordMethod()
surface = camera.setDisplaySurface(preview)
}
/**
* Attaches the camera to the [MediaRecorder].
*/
open fun attachRecordingCamera(mediaRecorder: MediaRecorder) {
logger.recordMethod()
mediaRecorder.setCamera(camera)
}
/**
* Returns the [Resolution] of the displayed preview.
*/
open fun getPreviewResolution(): Resolution {
logger.recordMethod()
val previewResolution = camera.getPreviewResolution(previewOrientation)
logger.log("Preview resolution is: $previewResolution")
return previewResolution
}
private fun setZoomSafely(@FloatRange(from = 0.0, to = 1.0) level: Float) {
try {
setZoomUnsafe(level)
} catch (e: Exception) {
logger.log("Unable to change zoom level to " + level + " e: " + e.message)
}
}
private fun setZoomUnsafe(@FloatRange(from = 0.0, to = 1.0) level: Float) {
(cachedCameraParameters ?: camera.parameters)
.apply {
zoom = (maxZoom * level).toInt()
}
.cacheLocally()
.setInCamera()
}
private fun Camera.Parameters.cacheLocally() = apply {
cachedCameraParameters = this
}
private fun Camera.Parameters.setInCamera() = apply {
camera.parameters = this
}
private fun Camera.focusSafely(): FocusResult {
val latch = CountDownLatch(1)
try {
autoFocus { _, _ -> latch.countDown() }
} catch (e: Exception) {
logger.log("Failed to perform autofocus using device ${characteristics.cameraId} e: ${e.message}")
return FocusResult.UnableToFocus
}
try {
latch.await(AUTOFOCUS_TIMEOUT_SECONDS, TimeUnit.SECONDS)
} catch (e: InterruptedException) {
// Do nothing
}
return FocusResult.Focused
}
private suspend fun Camera.updateFocusingAreas(focalRequest: FocalRequest) {
val focusingAreas = focalRequest.toFocusAreas(
displayOrientationDegrees = displayOrientation.degrees,
cameraIsMirrored = characteristics.isMirrored
)
parameters = parameters.apply {
with(capabilities.await()) {
if (maxMeteringAreas > 0) {
meteringAreas = focusingAreas
}
if (maxFocusAreas > 0) {
if (focusModes.contains(FocusMode.Auto)) {
focusMode = FocusMode.Auto.toCode()
}
focusAreas = focusingAreas
}
}
}
}
private fun Camera.clearFocusingAreas() {
parameters = parameters.apply {
meteringAreas = null
focusAreas = null
}
}
}
private const val AUTOFOCUS_TIMEOUT_SECONDS = 3L
private fun Camera.takePhoto(imageRotation: Int): Photo {
val latch = CountDownLatch(1)
val photoReference = AtomicReference<Photo>()
takePicture(
null,
null,
null,
Camera.PictureCallback { data, _ ->
photoReference.set(
Photo(data, imageRotation)
)
latch.countDown()
}
)
latch.await()
return photoReference.get()
}
@Throws(IOException::class)
private fun Camera.setDisplaySurface(
preview: Preview
): Surface = when (preview) {
is Preview.Texture -> preview.surfaceTexture
.also(this::setPreviewTexture)
.let(::Surface)
is Preview.Surface -> preview.surfaceHolder
.also(this::setPreviewDisplay)
.surface
}
private fun Camera.getPreviewResolution(previewOrientation: Orientation): Resolution {
return parameters.previewSize
.run {
PreviewSize(width, height)
}
.run {
when (previewOrientation) {
is Orientation.Vertical -> this
is Orientation.Horizontal -> flipDimensions()
}
}
}
private fun Capabilities.canSetFocusingAreas(): Boolean =
maxMeteringAreas > 0 || maxFocusAreas > 0
@@ -1,20 +0,0 @@
package io.fotoapparat.hardware;
/**
* A generic camera exception.
*/
public class CameraException extends RuntimeException {
public CameraException(Exception e) {
super(e);
}
public CameraException(String message) {
super(message);
}
public CameraException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,155 +0,0 @@
package io.fotoapparat.hardware;
import android.support.annotation.NonNull;
import java.util.Collections;
import java.util.Set;
import io.fotoapparat.parameter.Flash;
import io.fotoapparat.parameter.FocusMode;
import io.fotoapparat.parameter.Size;
import io.fotoapparat.parameter.range.Range;
import io.fotoapparat.parameter.range.Ranges;
/**
* Capabilities of camera hardware.
*/
public class Capabilities {
@NonNull
private final Set<Size> photoSizes;
@NonNull
private final Set<Size> previewSizes;
@NonNull
private final Set<FocusMode> focusModes;
@NonNull
private final Set<Flash> flashModes;
@NonNull
private final Set<Range<Integer>> previewFpsRanges;
@NonNull
private final Range<Integer> sensorSensitivityRange;
private final boolean zoomSupported;
public Capabilities(@NonNull Set<Size> photoSizes,
@NonNull Set<Size> previewSizes,
@NonNull Set<FocusMode> focusModes,
@NonNull Set<Flash> flashModes,
@NonNull Set<Range<Integer>> previewFpsRanges,
@NonNull Range<Integer> sensorSensitivityRange,
boolean zoomSupported) {
this.photoSizes = photoSizes;
this.previewSizes = previewSizes;
this.focusModes = focusModes;
this.flashModes = flashModes;
this.previewFpsRanges = previewFpsRanges;
this.sensorSensitivityRange = sensorSensitivityRange;
this.zoomSupported = zoomSupported;
}
/**
* @return Empty {@link Capabilities}.
*/
public static Capabilities empty() {
return new Capabilities(
Collections.<Size>emptySet(),
Collections.<Size>emptySet(),
Collections.<FocusMode>emptySet(),
Collections.<Flash>emptySet(),
Collections.<Range<Integer>>emptySet(),
Ranges.<Integer>emptyRange(),
false
);
}
/**
* @return list of supported picture sizes.
*/
public Set<Size> supportedPictureSizes() {
return photoSizes;
}
/**
* @return list of supported preview sizes;
*/
public Set<Size> supportedPreviewSizes() {
return previewSizes;
}
/**
* @return list of supported focus modes.
*/
public Set<FocusMode> supportedFocusModes() {
return focusModes;
}
/**
* @return list of supported flash firing modes.
*/
public Set<Flash> supportedFlashModes() {
return flashModes;
}
/**
* @return list of supported preview fps ranges.
*/
public Set<Range<Integer>> supportedPreviewFpsRanges() {
return previewFpsRanges;
}
/**
* @return supported range of the sensor's sensitivity.
*/
public Range<Integer> supportedSensorSensitivityRange() {
return sensorSensitivityRange;
}
/**
* @return {@code true} if zoom feature is supported. {@code false} if it is not supported.
*/
public boolean isZoomSupported() {
return zoomSupported;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Capabilities)) return false;
Capabilities that = (Capabilities) o;
return zoomSupported == that.zoomSupported
&& photoSizes.equals(that.photoSizes)
&& previewSizes.equals(that.previewSizes)
&& focusModes.equals(that.focusModes)
&& flashModes.equals(that.flashModes)
&& previewFpsRanges.equals(that.previewFpsRanges)
&& sensorSensitivityRange.equals(that.sensorSensitivityRange);
}
@Override
public int hashCode() {
int result = photoSizes.hashCode();
result = 31 * result + previewSizes.hashCode();
result = 31 * result + focusModes.hashCode();
result = 31 * result + flashModes.hashCode();
result = 31 * result + previewFpsRanges.hashCode();
result = 31 * result + sensorSensitivityRange.hashCode();
result = 31 * result + (isZoomSupported() ? 1 : 0);
return result;
}
@Override
public String toString() {
return "Capabilities{" +
"photoSizes=" + photoSizes +
", previewSizes=" + previewSizes +
", focusModes=" + focusModes +
", flashModes=" + flashModes +
", previewFpsRanges=" + previewFpsRanges +
", supportedSensorSensitivityRange=" + sensorSensitivityRange +
", zoomSupported=" + zoomSupported +
'}';
}
}
@@ -0,0 +1,194 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware
import android.hardware.Camera
import io.fotoapparat.characteristic.getCharacteristics
import io.fotoapparat.concurrent.CameraExecutor
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.configuration.Configuration
import io.fotoapparat.exception.camera.UnsupportedLensException
import io.fotoapparat.hardware.display.Display
import io.fotoapparat.hardware.orientation.Orientation
import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.parameter.camera.provide.getCameraParameters
import io.fotoapparat.selector.LensPositionSelector
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.FocalPointSelector
import kotlinx.coroutines.CompletableDeferred
/**
* Phone.
*/
internal open class Device(
internal open val logger: Logger,
private val display: Display,
internal open val scaleType: ScaleType,
internal open val cameraRenderer: CameraRenderer,
internal val focusPointSelector: FocalPointSelector?,
internal val executor: CameraExecutor,
numberOfCameras: Int = Camera.getNumberOfCameras(),
initialConfiguration: CameraConfiguration, initialLensPositionSelector: LensPositionSelector
) {
private val cameras = (0 until numberOfCameras).map { cameraId ->
CameraDevice(
logger = logger,
characteristics = getCharacteristics(cameraId)
)
}
private var lensPositionSelector: LensPositionSelector = initialLensPositionSelector
private var selectedCameraDevice = CompletableDeferred<CameraDevice>()
private var savedConfiguration = CameraConfiguration.default()
init {
updateLensPositionSelector(initialLensPositionSelector)
savedConfiguration = initialConfiguration
}
/**
* Selects a camera.
*/
open fun canSelectCamera(lensPositionSelector: LensPositionSelector): Boolean {
val selectedCameraDevice = selectCamera(
availableCameras = cameras,
lensPositionSelector = lensPositionSelector
)
return selectedCameraDevice != null
}
/**
* Selects a camera. Will do nothing if camera cannot be selected.
*/
open fun selectCamera() {
logger.recordMethod()
selectCamera(
availableCameras = cameras,
lensPositionSelector = lensPositionSelector
)
?.let(selectedCameraDevice::complete)
?: selectedCameraDevice.completeExceptionally(UnsupportedLensException())
}
/**
* Clears the selected camera.
*/
open fun clearSelectedCamera() {
selectedCameraDevice = CompletableDeferred()
}
/**
* Waits and returns the selected camera.
*/
open suspend fun awaitSelectedCamera(): CameraDevice = selectedCameraDevice.await()
/**
* Returns the selected camera.
*
* @throws IllegalStateException If no camera has been yet selected.
* @throws UnsupportedLensException If no camera could get selected.
*/
open fun getSelectedCamera(): CameraDevice = try {
selectedCameraDevice.getCompleted()
} catch (e: IllegalStateException) {
throw IllegalStateException("Camera has not started!")
}
/**
* @return `true` if a camera has been selected.
*/
open fun hasSelectedCamera() = selectedCameraDevice.isCompleted
/**
* @return Orientation of the screen.
*/
open fun getScreenOrientation(): Orientation {
return display.getOrientation()
}
/**
* Updates the desired from the user camera lens position.
*/
open fun updateLensPositionSelector(newLensPosition: LensPositionSelector) {
logger.recordMethod()
lensPositionSelector = newLensPosition
}
/**
* Updates the desired from the user selectors.
*/
open fun updateConfiguration(newConfiguration: Configuration) {
logger.recordMethod()
savedConfiguration = updateConfiguration(
savedConfiguration = savedConfiguration,
newConfiguration = newConfiguration
)
}
/**
* @return The desired from the user selectors.
*/
open fun getConfiguration(): CameraConfiguration = savedConfiguration
/**
* @return The selected [CameraParameters] for the given [CameraDevice].
*/
open suspend fun getCameraParameters(cameraDevice: CameraDevice): CameraParameters =
getCameraParameters(
cameraConfiguration = savedConfiguration,
capabilities = cameraDevice.getCapabilities()
)
/**
* @return The frame processor.
*/
open fun getFrameProcessor(): FrameProcessor? = savedConfiguration.frameProcessor
/**
* @return The desired from the user camera lens position.
*/
open fun getLensPositionSelector(): LensPositionSelector = lensPositionSelector
}
/**
* Updates the device's configuration.
*/
internal fun updateConfiguration(
savedConfiguration: CameraConfiguration,
newConfiguration: Configuration
) = CameraConfiguration(
flashMode = newConfiguration.flashMode ?: savedConfiguration.flashMode,
focusMode = newConfiguration.focusMode ?: savedConfiguration.focusMode,
exposureCompensation = newConfiguration.exposureCompensation
?: savedConfiguration.exposureCompensation,
frameProcessor = newConfiguration.frameProcessor ?: savedConfiguration.frameProcessor,
previewFpsRange = newConfiguration.previewFpsRange ?: savedConfiguration.previewFpsRange,
sensorSensitivity = newConfiguration.sensorSensitivity
?: savedConfiguration.sensorSensitivity,
pictureResolution = newConfiguration.pictureResolution
?: savedConfiguration.pictureResolution,
previewResolution = newConfiguration.previewResolution
?: savedConfiguration.previewResolution
)
/**
* Selects a camera from the set of available ones.
*/
internal fun selectCamera(
availableCameras: List<CameraDevice>,
lensPositionSelector: LensPositionSelector
): CameraDevice? {
val lensPositions = availableCameras.map { it.characteristics.lensPosition }.toSet()
val desiredPosition = lensPositionSelector(lensPositions)
return availableCameras.find { it.characteristics.lensPosition == desiredPosition }
}
@@ -0,0 +1,27 @@
package io.fotoapparat.hardware
import android.os.Handler
import android.os.Looper
import java.util.concurrent.Executors
private val loggingExecutor = Executors.newSingleThreadExecutor()
private val mainThreadHandler = Handler(Looper.getMainLooper())
/**
* [java.util.concurrent.Executor] operating the [io.fotoapparat.result.PendingResult].
*/
internal val pendingResultExecutor = Executors.newSingleThreadExecutor()
/**
* [java.util.concurrent.Executor] operating the [io.fotoapparat.preview.PreviewStream].
*/
internal val frameProcessingExecutor = Executors.newSingleThreadExecutor()
/**
* Executes an operation in the main thread.
*/
internal fun executeLoggingThread(function: () -> Unit) = loggingExecutor.execute { function() }
/**
* Executes an operation in the main thread.
*/
internal fun executeMainThread(function: () -> Unit) = mainThreadHandler.post { function() }
@@ -0,0 +1,32 @@
package io.fotoapparat.hardware.display
import android.content.Context
import android.view.Surface
import android.view.WindowManager
import io.fotoapparat.hardware.orientation.Orientation
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.Landscape
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.ReverseLandscape
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
import io.fotoapparat.hardware.orientation.Orientation.Vertical.ReversePortrait
/**
* A phone's display.
*/
internal open class Display(context: Context) {
private val display = context.getDisplay()
/**
* Returns the orientation of the screen.
*/
open fun getOrientation(): Orientation = when (display.rotation) {
Surface.ROTATION_0 -> Portrait
Surface.ROTATION_90 -> Landscape
Surface.ROTATION_180 -> ReversePortrait
Surface.ROTATION_270 -> ReverseLandscape
else -> Portrait
}
}
private fun Context.getDisplay() = (getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
@@ -0,0 +1,20 @@
package io.fotoapparat.hardware.metering
import io.fotoapparat.parameter.Resolution
/**
* The request to focus camera at a particular point.
*/
data class FocalRequest(
/**
* The point where when user would like to focus.
*/
val point: PointF,
/**
* Resolution of the preview
*/
val previewResolution: Resolution
)
@@ -0,0 +1,9 @@
package io.fotoapparat.hardware.metering
/**
* A point in arbitrary scale.
*/
data class PointF(
val x: Float,
val y: Float
)
@@ -0,0 +1,117 @@
@file:Suppress("DEPRECATION")
package io.fotoapparat.hardware.metering.convert
import android.graphics.Matrix
import android.graphics.Rect
import android.hardware.Camera
import io.fotoapparat.hardware.metering.FocalRequest
import io.fotoapparat.hardware.metering.PointF
import io.fotoapparat.parameter.Resolution
/**
* The weight of the camera area.
* Only 1will be used, so max weight, `1000`, is used.
*/
private const val WEIGHT = 1000
/**
* From -1000 to 1000
*
* @see Camera.Area
*/
private const val CAMERA_BOUNDS_RANGE = 2000f
/**
* The half dimension size of a focus area.
*
* The camera will focus on a maximum area of `(2 * this) ^ 2`.
*/
private const val FOCUS_AREA_HALF_SIZE = 50
/**
* Converts a [FocalRequest] to a list of [Camera.Area] that the camera should focus at.
*/
internal fun FocalRequest.toFocusAreas(
displayOrientationDegrees: Int,
cameraIsMirrored: Boolean
): List<Camera.Area> = listOf(Camera.Area(
focusBounds(
displayOrientationDegrees = displayOrientationDegrees.toFloat(),
cameraIsMirrored = cameraIsMirrored
),
WEIGHT
))
private fun FocalRequest.focusBounds(
displayOrientationDegrees: Float,
cameraIsMirrored: Boolean
): Rect = point
.adjustPointToCameraPreview(
previewResolution,
displayOrientationDegrees,
cameraIsMirrored
)
.toBoundsRect()
private fun PointF.adjustPointToCameraPreview(
visibleResolution: Resolution,
displayOrientationDegrees: Float,
cameraIsMirrored: Boolean
): PointF = Matrix()
.configMatrix(
visibleResolution,
displayOrientationDegrees,
cameraIsMirrored
)
.run {
floatArrayOf(x, y).also {
mapPoints(it)
}
}
.let {
PointF(
it[0].verifyInBounds(),
it[1].verifyInBounds()
)
}
/**
* The point value should mainly be adjusted by `2000 * value - 1000`
* in order to be in `-1000..1000` range.
*/
private fun Matrix.configMatrix(
visibleResolution: Resolution,
displayOrientationDegrees: Float,
cameraIsMirrored: Boolean
) = apply {
postScale(
CAMERA_BOUNDS_RANGE / visibleResolution.width.toFloat(),
CAMERA_BOUNDS_RANGE / visibleResolution.height.toFloat()
)
postTranslate(
-CAMERA_BOUNDS_RANGE / 2,
-CAMERA_BOUNDS_RANGE / 2
)
postRotate(-displayOrientationDegrees)
postScale(
if (cameraIsMirrored) -1f else 1f,
1f
)
}
private fun Float.verifyInBounds(): Float {
return takeIf { it in -1000f..1000f }
?: throw IllegalArgumentException("Point value should be between -1000.0 and 1000.0. Was $this")
}
private fun PointF.toBoundsRect() = Rect(
(x - FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
(y - FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
(x + FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
(y + FOCUS_AREA_HALF_SIZE).ensureAreaBound()
)
private fun Float.ensureAreaBound() = clamp(-1000f, 1000f).toInt()
private fun Float.clamp(min: Float, max: Float): Float = Math.max(min, Math.min(this, max))
@@ -1,16 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.lens.FocusResult;
/**
* Performs auto focus.
*/
public interface AutoFocusOperator {
/**
* Performs auto focus. This is a blocking operation which returns the result of the operation
* when auto focus completes.
*/
FocusResult autoFocus();
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.hardware.Capabilities;
/**
* An interface which indicates that the class can
* provide the camera capabilities.
*/
public interface CapabilitiesOperator {
/**
* Returns the {@link Capabilities} of the opened camera.
*
* @return The {@link Capabilities} of the camera.
*/
Capabilities getCapabilities();
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.photo.Photo;
/**
* An interface which indicates that the class can
* capture still pictures.
*/
public interface CaptureOperator {
/**
* Invokes a still picture capture action.
*
* @return The captured photo.
*/
Photo takePicture();
}
@@ -1,23 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.LensPosition;
/**
* An interface which indicates that the class can
* open and close connections to a camera.
*/
public interface ConnectionOperator {
/**
* Opens a connection to a camera.
*
* @param lensPosition The camera position relatively to the screen of the device,
* which will determine which camera to open.
*/
void open(LensPosition lensPosition);
/**
* Closes the connection to a camera.
*/
void close();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* Measures the exposure.
*/
public interface ExposureMeasurementOperator {
/**
* Measures the exposure. This is a blocking operation which returns when measurement completes.
*/
void measureExposure();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* handle the orientation updates.
*/
public interface OrientationOperator {
/**
* Sets the current orientation of the display.
*/
void setDisplayOrientation(int degrees);
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.Parameters;
/**
* An interface which indicates that the class can
* support parameter updates.
*/
public interface ParametersOperator {
/**
* Updates the desired parameters for the preview and the photo capture actions.
*/
void updateParameters(Parameters parameters);
}
@@ -1,21 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* start and stop the capture preview.
*/
public interface PreviewOperator {
/**
* Starts the preview to the surface.
* <p>
* {@link #stopPreview()} should be called
* to stop the operation.
*/
void startPreview();
/**
* Stops the preview from the surface.
*/
void stopPreview();
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.preview.PreviewStream;
/**
* Provides {@link PreviewStream} controlled by camera.
*/
public interface PreviewStreamOperator {
/**
* @return {@link PreviewStream} associated with camera.
*/
PreviewStream getPreviewStream();
}
@@ -1,15 +0,0 @@
package io.fotoapparat.hardware.operators;
import io.fotoapparat.parameter.RendererParameters;
/**
* Builds {@link RendererParameters}.
*/
public interface RendererParametersOperator {
/**
* @return {@link RendererParameters}.
*/
RendererParameters getRendererParameters();
}
@@ -1,13 +0,0 @@
package io.fotoapparat.hardware.operators;
/**
* An interface which indicates that the class can
* set a preview surface.
*/
public interface SurfaceOperator {
/**
* Sets the desired surface on which the camera preview will be displayed.
*/
void setDisplaySurface(Object displaySurface);
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.operators;
import android.support.annotation.FloatRange;
/**
* Modifies zoom level of the camera.
*/
public interface ZoomOperator {
/**
* Changes zoom level of the camera. Must be called only if zoom is supported.
*
* @param level normalized zoom level. Value in range [0..1].
*/
void setZoom(@FloatRange(from = 0f, to = 1f) float level);
}
@@ -0,0 +1,64 @@
package io.fotoapparat.hardware.orientation
typealias DeviceOrientation = Orientation
typealias ScreenOrientation = Orientation
/**
* The device orientation.
*/
sealed class Orientation(
val degrees: Int
) {
/**
* A vertical device orientation.
*/
sealed class Vertical(degrees: Int) : Orientation(degrees) {
/**
* A vertical, normal orientation.
*/
object Portrait : Vertical(0) {
override fun toString(): String = "Orientation.Vertical.Portrait"
}
/**
* A reversed (flipped phone) orientation.
*/
object ReversePortrait : Vertical(180) {
override fun toString(): String = "Orientation.Vertical.ReversePortrait"
}
}
/**
* A horizontal device orientation.
*/
sealed class Horizontal(degrees: Int) : Orientation(degrees) {
/**
* A 90 degrees clockwise from "normal", orientation.
*/
object Landscape : Horizontal(90) {
override fun toString(): String = "Orientation.Horizontal.Landscape"
}
/**
* A 90 degrees counter-clockwise from "normal", orientation.
*/
object ReverseLandscape : Horizontal(270) {
override fun toString(): String = "Orientation.Horizontal.ReverseLandscape"
}
}
}
internal fun Int.toOrientation(): Orientation {
return when (this) {
0, 360 -> Orientation.Vertical.Portrait
90 -> Orientation.Horizontal.Landscape
180 -> Orientation.Vertical.ReversePortrait
270 -> Orientation.Horizontal.ReverseLandscape
else -> throw IllegalArgumentException("Cannot convert $this to absolute Orientation.")
}
}
@@ -0,0 +1,75 @@
package io.fotoapparat.hardware.orientation
/**
* @param screenOrientation Orientation of the display.
* @param cameraOrientation Orientation of the camera sensor.
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case
* for front cameras). `false` if it is not mirrored.
*
* @return rotation of the image relatively to current device orientation.
*/
fun computePreviewOrientation(
screenOrientation: Orientation,
cameraOrientation: Orientation,
cameraIsMirrored: Boolean
): Orientation {
val mirroredCameraModifier = if (cameraIsMirrored) -1 else 1
val rotation = (720
+ mirroredCameraModifier * screenOrientation.degrees
- cameraOrientation.degrees
) % 360
return rotation.toOrientation()
}
/**
* @param deviceOrientation Orientation of the device.
* @param cameraOrientation Orientation of the camera sensor.
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case
* for front cameras). `false` if it is not mirrored.
*
* @return clockwise rotation of the image relatively to current device orientation.
*/
internal fun computeImageOrientation(
deviceOrientation: Orientation,
cameraOrientation: Orientation,
cameraIsMirrored: Boolean
): Orientation {
val screenOrientationDegrees = deviceOrientation.degrees
val cameraRotationDegrees = cameraOrientation.degrees
val rotation = if (cameraIsMirrored) {
360 - (cameraRotationDegrees - screenOrientationDegrees + 360) % 360
} else {
360 - (cameraRotationDegrees + screenOrientationDegrees) % 360
}
return rotation.toOrientation()
}
/**
* @param screenOrientation Orientation of the display.
* @param cameraOrientation Orientation of the camera sensor.
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case for
* front cameras). `false` if it is not mirrored.
*
* @return display orientation in which user will see the output camera in a correct rotation.
*/
internal fun computeDisplayOrientation(
screenOrientation: Orientation,
cameraOrientation: Orientation,
cameraIsMirrored: Boolean
): Orientation {
val screenOrientationDegrees = screenOrientation.degrees
val cameraRotationDegrees = cameraOrientation.degrees
val rotation = if (cameraIsMirrored) {
(360 - (cameraRotationDegrees + screenOrientationDegrees) % 360) % 360
} else {
(cameraRotationDegrees - screenOrientationDegrees + 360) % 360
}
return rotation.toOrientation()
}
@@ -1,61 +0,0 @@
package io.fotoapparat.hardware.orientation;
import android.support.annotation.NonNull;
/**
* Monitors orientation of the device.
*/
public class OrientationSensor implements RotationListener.Listener {
private final RotationListener rotationListener;
private final ScreenOrientationProvider screenOrientationProvider;
private int lastKnownRotation;
private Listener listener;
public OrientationSensor(@NonNull final RotationListener rotationListener,
@NonNull final ScreenOrientationProvider screenOrientationProvider) {
this.rotationListener = rotationListener;
this.screenOrientationProvider = screenOrientationProvider;
rotationListener.setRotationListener(this);
}
/**
* Starts monitoring device's orientation.
*/
public void start(Listener listener) {
this.listener = listener;
rotationListener.enable();
}
/**
* Stops monitoring device's orientation.
*/
public void stop() {
rotationListener.disable();
listener = null;
}
@Override
public void onRotationChanged() {
if (listener != null) {
int rotation = screenOrientationProvider.getScreenRotation();
if (rotation != lastKnownRotation) {
listener.onOrientationChanged(rotation);
lastKnownRotation = rotation;
}
}
}
/**
* Notified when orientation of the device is updated.
*/
public interface Listener {
/**
* Called when orientation of the device is updated.
*/
void onOrientationChanged(int degrees);
}
}
@@ -0,0 +1,65 @@
package io.fotoapparat.hardware.orientation
import android.content.Context
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
/**
* Monitors orientation of the device.
*/
internal open class OrientationSensor(
private val rotationListener: RotationListener,
private val device: Device
) {
private lateinit var listener: (OrientationState) -> Unit
private val onOrientationChanged: (DeviceRotationDegrees) -> Unit = { deviceRotation ->
deviceRotation.toClosestRightAngle()
.toOrientation()
.let { deviceOrientation ->
val screenOrientation = device.getScreenOrientation()
val newState = OrientationState(
deviceOrientation = deviceOrientation,
screenOrientation = screenOrientation
)
if (newState != lastKnownOrientationState) {
lastKnownOrientationState = newState
listener(newState)
}
}
}
open var lastKnownOrientationState: OrientationState = OrientationState(
deviceOrientation = Portrait,
screenOrientation = device.getScreenOrientation()
)
constructor(
context: Context,
device: Device
) : this(
RotationListener(context),
device
)
init {
rotationListener.orientationChanged = onOrientationChanged
}
/**
* Starts monitoring device's orientation state.
*/
open fun start(listener: (OrientationState) -> Unit) {
this.listener = listener
rotationListener.enable()
}
/**
* Stops monitoring device's orientation.
*/
open fun stop() {
rotationListener.disable()
}
}
@@ -0,0 +1,16 @@
package io.fotoapparat.hardware.orientation
/**
* Phone orientation states.
*/
data class OrientationState(
/**
* The current orientation the device is being hold.
*/
val deviceOrientation: DeviceOrientation,
/**
* The current orientation of the screen.
*/
val screenOrientation: ScreenOrientation
)
@@ -1,64 +0,0 @@
package io.fotoapparat.hardware.orientation;
/**
* Utilities for working with device orientation.
*/
public class OrientationUtils {
/**
* @return closest right angle to given value. That is: 0, 90, 180, 270.
*/
public static int toClosestRightAngle(int degrees) {
boolean roundUp = degrees % 90 > 45;
int roundAppModifier = roundUp ? 1 : 0;
return (((degrees / 90) + roundAppModifier) * 90) % 360;
}
/**
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
* orientation.
* @param cameraIsMirrored {@code true} if camera is mirrored (typically that is the case
* for front cameras). {@code false} if it is not mirrored.
* @return display orientation in which user will see the output camera in a correct rotation.
*/
public static int computeDisplayOrientation(int screenRotationDegrees,
int cameraRotationDegrees,
boolean cameraIsMirrored) {
int degrees = OrientationUtils.toClosestRightAngle(screenRotationDegrees);
if (cameraIsMirrored) {
degrees = (cameraRotationDegrees + degrees) % 360;
degrees = (360 - degrees) % 360;
} else {
degrees = (cameraRotationDegrees - degrees + 360) % 360;
}
return degrees;
}
/**
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
* orientation.
* @param cameraIsMirrored {@code true} if camera is mirrored (typically that is the case
* for front cameras). {@code false} if it is not mirrored.
* @return clockwise rotation of the image relatively to current device orientation.
*/
public static int computeImageOrientation(int screenRotationDegrees,
int cameraRotationDegrees,
boolean cameraIsMirrored) {
int rotation;
if (cameraIsMirrored) {
rotation = -(screenRotationDegrees + cameraRotationDegrees);
} else {
rotation = screenRotationDegrees - cameraRotationDegrees;
}
return (rotation + 360 + 360) % 360;
}
}
@@ -0,0 +1,14 @@
package io.fotoapparat.hardware.orientation
typealias DeviceRotationDegrees = Int
/**
* @return closest right angle to given value. That is: 0, 90, 180, 270.
*/
internal fun Int.toClosestRightAngle(): Int {
val roundUp = this % 90 > 45
val roundAppModifier = if (roundUp) 1 else 0
return (this / 90 + roundAppModifier) * 90 % 360
}
@@ -1,44 +0,0 @@
package io.fotoapparat.hardware.orientation;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.OrientationEventListener;
/**
* Wrapper around {@link OrientationEventListener} to notify when the device's rotation has changed.
*/
public class RotationListener extends OrientationEventListener {
private Listener listener;
public RotationListener(Context context) {
super(context);
}
@Override
public void onOrientationChanged(int orientation) {
if (listener != null && canDetectOrientation()) {
listener.onRotationChanged();
}
}
/**
* Sets a listener to this class to notify future rotation events.
*
* @param listener The new listener
*/
void setRotationListener(@NonNull Listener listener) {
this.listener = listener;
}
/**
* Notified when the rotation of the device is updated.
*/
interface Listener {
/**
* Called when the rotation of the device has changed.
*/
void onRotationChanged();
}
}
@@ -0,0 +1,21 @@
package io.fotoapparat.hardware.orientation
import android.content.Context
import android.view.OrientationEventListener
/**
* Wrapper around [OrientationEventListener] to notify when the device's rotation has changed.
*/
internal open class RotationListener(
context: Context
) : OrientationEventListener(context) {
lateinit var orientationChanged: (DeviceRotationDegrees) -> Unit
override fun onOrientationChanged(orientation: DeviceRotationDegrees) {
if (canDetectOrientation()) {
orientationChanged(orientation)
}
}
}
@@ -1,39 +0,0 @@
package io.fotoapparat.hardware.orientation;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
/**
* Provides orientation of the screen.
*/
public class ScreenOrientationProvider {
private final Display display;
public ScreenOrientationProvider(@NonNull Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
display = windowManager.getDefaultDisplay();
}
/**
* @return rotation of the screen in degrees.
*/
public int getScreenRotation() {
int rotation = display.getRotation();
switch (rotation) {
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
case Surface.ROTATION_0:
default:
return 0;
}
}
}
@@ -1,17 +0,0 @@
package io.fotoapparat.hardware.provider;
import java.util.List;
import io.fotoapparat.parameter.LensPosition;
/**
* Provides list of {@link LensPosition} which are available on the device.
*/
public interface AvailableLensPositionsProvider {
/**
* @return list of {@link LensPosition} which are available on the device.
*/
List<LensPosition> getAvailableLensPositions();
}

Some files were not shown because too many files have changed in this diff Show More