Compare commits

..

71 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
94 changed files with 1019 additions and 468 deletions
+2
View File
@@ -6,4 +6,6 @@
build
/captures
.externalNativeBuild
.project
.settings
+13 -7
View File
@@ -3,15 +3,21 @@ jdk: oraclejdk8
android:
components:
- tools
- platform-tools
- tools
- build-tools-27.0.3
- android-27
- extra-android-m2repository
- tools
- platform-tools
- tools
- build-tools-28.0.3
- android-28
- extra-android-m2repository
before_install:
- yes | sdkmanager "platforms;android-27"
- yes | sdkmanager "platforms;android-28"
script:
- ./gradlew build test
deploy:
provider: script
script: ./gradlew bintrayUpload
on:
tags: true
+28 -33
View File
@@ -1,20 +1,19 @@
# 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.
- Both Kotlin and Java friendly configurations.
- Last, but not least, non 0% test coverage.
- Last, but not least, non 0% test coverage.
Taking picture becomes as simple as:
@@ -23,9 +22,9 @@ val fotoapparat = Fotoapparat(
context = this,
view = cameraView
)
fotoapparat.start()
fotoapparat
.takePicture()
.saveToFile(someFile)
@@ -48,7 +47,7 @@ Add `CameraView` to your layout
Configure `Fotoapparat` instance.
```kotlin
```kotlin
Fotoapparat(
context = this,
view = cameraView, // view which will draw the camera preview
@@ -61,12 +60,12 @@ Fotoapparat(
),
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.
@@ -76,43 +75,43 @@ override fun onStart() {
super.onStart()
fotoapparat.start()
}
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()
// Asynchronously saves photo to file
photoResult.saveToFile(someFile)
// Asynchronously converts photo to bitmap and returns result on main thread
// Asynchronously converts photo to bitmap and returns the result on the main thread
photoResult
.toBitmap()
.whenAvailable { bitmapPhoto ->
val imageView = (ImageView) findViewById(R.id.result)
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 main thread though.
// 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
// Convert asynchronous events to RxJava 1.x/2.x types.
// See /fotoapparat-adapters/ module
photoResult
.toBitmap()
.toSingle()
.subscribe { bitmapPhoto ->
.subscribe { bitmapPhoto ->
}
```
@@ -125,23 +124,23 @@ fotoapparat.updateConfiguration(
UpdateConfiguration(
flashMode = if (isChecked) torch() else off()
// ...
// all the parameters available in CameraConfiguration
// all the parameters available in CameraConfiguration
)
)
```
Or alternatively you may provide updates on an existing full configuration.
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
// all the parameters available in CameraConfiguration
)
)
```
@@ -162,11 +161,7 @@ fotoapparat.switchTo(
Add dependency to your `build.gradle`
```groovy
repositories {
maven { url 'https://jitpack.io' }
}
compile 'io.fotoapparat.fotoapparat:library:2.2.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.
+19 -16
View File
@@ -1,30 +1,34 @@
// 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 = [
gradle : '4.4.1',
kotlin : '1.2.10',
kotlin : '1.3.50',
code : 1,
name : '1.0.0',
sdk : [
minimum: 14,
target : 27
target : 28
],
android: [
buildTools: '27.0.3',
support : '27.0.2'
buildTools: '28.0.3',
appcompat : '1.1.0',
annotation : '1.1.0',
exifinterface : '1.0.0'
],
rx : [
rxJava1: '1.2.9',
rxJava2: '2.0.8'
rxJava1: '1.3.8',
rxJava2: '2.2.12'
],
test : [
junit : '4.12',
mockito: '2.13.0'
],
util : [
commons: '2.5'
mockito: '2.23.0'
]
]
}
@@ -33,9 +37,11 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
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'
}
}
@@ -50,6 +56,3 @@ task clean(type: Delete) {
delete rootProject.buildDir
}
task wrapper(type: Wrapper) {
gradleVersion = versions.gradle
}
+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
}
}
}
+18 -4
View File
@@ -11,17 +11,31 @@ android {
defaultConfig {
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 {
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}"
}
}
ext {
bintrayName = 'adapter-rxjava'
libraryName = 'Fotoapparat Adapters - RxJava'
artifact = 'adapter-rxjava'
libraryVersion = artifactVersion
libraryDescription = 'RxJava1 adapter for Fotoapparat.'
}
apply from: '../../deploy.gradle'
+18 -4
View File
@@ -11,17 +11,31 @@ android {
defaultConfig {
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 {
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}"
}
}
ext {
bintrayName = 'adapter-rxjava2'
libraryName = 'Fotoapparat Adapters - RxJava2'
artifact = 'adapter-rxjava2'
libraryVersion = artifactVersion
libraryDescription = 'RxJava2 adapter for Fotoapparat.'
}
apply from: '../../deploy.gradle'
+21 -10
View File
@@ -4,8 +4,6 @@ apply plugin: 'kotlin-android'
group = 'io.fotoapparat'
kotlin.experimental.coroutines 'enable'
android {
buildToolsVersion versions.android.buildTools
compileSdkVersion versions.sdk.target
@@ -13,8 +11,6 @@ android {
defaultConfig {
minSdkVersion versions.sdk.minimum
targetSdkVersion versions.sdk.target
archivesBaseName = 'library'
}
buildTypes {
@@ -26,15 +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:${versions.android.support}"
compile "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.21'
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}"
testImplementation "commons-io:commons-io:${versions.util.commons}"
}
}
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,7 +1,7 @@
package io.fotoapparat
import android.content.Context
import android.support.annotation.FloatRange
import androidx.annotation.FloatRange
import io.fotoapparat.concurrent.CameraExecutor
import io.fotoapparat.concurrent.CameraExecutor.Operation
import io.fotoapparat.configuration.CameraConfiguration
@@ -63,10 +63,12 @@ class Fotoapparat
executor = executor
)
private val orientationSensor = OrientationSensor(
context = context,
device = device
)
private val orientationSensor by lazy {
OrientationSensor(
context = context,
device = device
)
}
init {
logger.recordMethod()
@@ -220,6 +222,7 @@ class Fotoapparat
executor.execute(Operation(cancellable = true) {
device.switchCamera(
orientationSensor = orientationSensor,
newLensPositionSelector = lensPosition,
newConfiguration = cameraConfiguration,
mainThreadErrorCallback = mainThreadErrorCallback
@@ -7,10 +7,12 @@ 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
/**
@@ -25,6 +27,7 @@ class FotoapparatBuilder internal constructor(private var context: Context) {
)
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()
@@ -106,6 +109,15 @@ class FotoapparatBuilder internal constructor(private var context: Context) {
)
}
/**
* @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
@@ -120,9 +132,9 @@ class FotoapparatBuilder internal constructor(private var context: Context) {
* @param frameProcessor receives preview frames for processing.
* @see FrameProcessorJava
*/
fun frameProcessor(frameProcessor: FrameProcessorJava): FotoapparatBuilder = apply {
fun frameProcessor(frameProcessor: FrameProcessorJava?): FotoapparatBuilder = apply {
configuration = configuration.copy(
frameProcessor = { frameProcessor.process(it) }
frameProcessor = frameProcessor?.let { it::process }
)
}
@@ -154,6 +166,13 @@ class FotoapparatBuilder internal constructor(private var context: Context) {
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.
@@ -172,6 +191,7 @@ class FotoapparatBuilder internal constructor(private var context: Context) {
return Fotoapparat(
context = context,
view = renderer,
focusView = focusView,
lensPosition = lensPositionSelector,
cameraConfiguration = configuration,
scaleType = scaleType,
@@ -10,13 +10,14 @@ import io.fotoapparat.util.wrap
* Sensor sensitivities is not guaranteed to always contain a value.
*/
data class Capabilities(
val canZoom: Boolean,
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>,
@@ -35,13 +36,14 @@ data class Capabilities(
override fun toString(): String {
return "Capabilities" + lineSeparator +
"canZoom:" + canZoom.wrap() +
"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() +
@@ -14,13 +14,14 @@ internal fun Camera.getCapabilities() = SupportedParameters(parameters).getCapab
private fun SupportedParameters.getCapabilities(): Capabilities {
return Capabilities(
canZoom = supportedZoom,
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() },
@@ -8,16 +8,22 @@ sealed class LensPosition : Characteristic {
/**
* The back camera.
*/
object Back : LensPosition()
object Back : LensPosition() {
override fun toString(): String = "LensPosition.Back"
}
/**
* The front camera.
*/
object Front : LensPosition()
object Front : LensPosition() {
override fun toString(): String = "LensPosition.Front"
}
/**
* An external camera.
*/
object External : LensPosition()
object External : LensPosition() {
override fun toString(): String = "LensPosition.External"
}
}
@@ -5,6 +5,11 @@ 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.
*
@@ -16,6 +21,7 @@ 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.")
}
@@ -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.")
}
}
@@ -4,8 +4,8 @@ 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.
@@ -19,7 +19,8 @@ data class CameraConfiguration(
infinity()
),
override val jpegQuality: QualitySelector = manualJpegQuality(DEFAULT_JPEG_QUALITY),
override val frameProcessor: FrameProcessor = {},
override val exposureCompensation: ExposureSelector = manualExposure(DEFAULT_EXPOSURE_COMPENSATION),
override val frameProcessor: FrameProcessor? = null,
override val previewFpsRange: FpsRangeSelector = highestFps(),
override val antiBandingMode: AntiBandingModeSelector = firstAvailable(
auto(),
@@ -57,9 +58,9 @@ data class CameraConfiguration(
)
}
fun sensorSensitivity(selector: SensorSensitivitySelector): Builder = apply {
fun exposureCompensation(selector: ExposureSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
sensorSensitivity = selector
exposureCompensation = selector
)
}
@@ -75,6 +76,12 @@ data class CameraConfiguration(
)
}
fun sensorSensitivity(selector: SensorSensitivitySelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
sensorSensitivity = selector
)
}
fun previewResolution(selector: ResolutionSelector): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
previewResolution = selector
@@ -87,9 +94,9 @@ data class CameraConfiguration(
)
}
fun frameProcessor(frameProcessor: FrameProcessorJava): Builder = apply {
fun frameProcessor(frameProcessor: FrameProcessorJava?): Builder = apply {
cameraConfiguration = cameraConfiguration.copy(
frameProcessor = frameProcessor::process
frameProcessor = frameProcessor?.let { it::process }
)
}
@@ -7,6 +7,7 @@ interface Configuration {
val flashMode: FlashSelector?
val focusMode: FocusModeSelector?
val jpegQuality: QualitySelector?
val exposureCompensation: ExposureSelector?
val frameProcessor: FrameProcessor?
val previewFpsRange: FpsRangeSelector?
val antiBandingMode: AntiBandingModeSelector?
@@ -10,6 +10,7 @@ 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,
@@ -61,6 +62,12 @@ data class UpdateConfiguration(
)
}
fun exposureCompensation(selector: ExposureSelector): Builder = apply {
configuration = configuration.copy(
exposureCompensation = selector
)
}
fun previewResolution(selector: ResolutionSelector): Builder = apply {
configuration = configuration.copy(
previewResolution = selector
@@ -73,7 +80,7 @@ data class UpdateConfiguration(
)
}
fun frameProcessor(frameProcessor: FrameProcessor): Builder = apply {
fun frameProcessor(frameProcessor: FrameProcessor?): Builder = apply {
configuration = configuration.copy(
frameProcessor = frameProcessor
)
@@ -1,13 +1,13 @@
package io.fotoapparat.coroutines
import kotlinx.coroutines.experimental.CompletableDeferred
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.channels.BroadcastChannel
import kotlinx.coroutines.experimental.channels.ConflatedBroadcastChannel
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()
@@ -30,4 +30,14 @@ internal class AwaitBroadcastChannel<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)
}
}
@@ -4,7 +4,6 @@ import android.os.Looper
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.executeMainThread
typealias CameraErrorCallback = (CameraException) -> Unit
/**
@@ -16,4 +15,4 @@ fun CameraErrorCallback.onMainThread(): CameraErrorCallback = { cameraException
} else {
executeMainThread { this(cameraException) }
}
}
}
@@ -1,6 +1,6 @@
package io.fotoapparat.exif
import android.media.ExifInterface
import androidx.exifinterface.media.ExifInterface
import io.fotoapparat.exception.FileSaveException
import java.io.File
import java.io.IOException
@@ -4,8 +4,8 @@ package io.fotoapparat.hardware
import android.hardware.Camera
import android.media.MediaRecorder
import android.support.annotation.FloatRange
import android.view.Surface
import androidx.annotation.FloatRange
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.capability.provide.getCapabilities
import io.fotoapparat.characteristic.Characteristics
@@ -15,12 +15,11 @@ 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.hardware.orientation.Orientation.Vertical.Portrait
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.applyNewParameters
import io.fotoapparat.parameter.camera.apply.applyInto
import io.fotoapparat.parameter.camera.convert.toCode
import io.fotoapparat.preview.PreviewStream
import io.fotoapparat.result.FocusResult
@@ -28,13 +27,12 @@ import io.fotoapparat.result.Photo
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.util.lineSeparator
import io.fotoapparat.view.Preview
import kotlinx.coroutines.experimental.CompletableDeferred
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
/**
@@ -51,10 +49,10 @@ internal open class CameraDevice(
private lateinit var surface: Surface
private lateinit var camera: Camera
private var cachedZoomParameters: Camera.Parameters? = null
private var displayOrientation: Orientation = Portrait
private var imageOrientation: Orientation = Portrait
private var previewOrientation: Orientation = Portrait
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.
@@ -82,7 +80,7 @@ internal open class CameraDevice(
*/
open fun close() {
logger.recordMethod()
surface.release()
camera.release()
}
@@ -169,7 +167,9 @@ internal open class CameraDevice(
logger.log("New camera parameters are: $cameraParameters")
camera.updateParameters(cameraParameters)
cameraParameters.applyInto(cachedCameraParameters ?: camera.parameters)
.cacheLocally()
.setInCamera()
}
/**
@@ -205,9 +205,16 @@ internal open class CameraDevice(
cameraIsMirrored = characteristics.isMirrored
)
logger.log("Image orientation is: $imageOrientation. " + lineSeparator +
"Display orientation is: $displayOrientation. " + lineSeparator +
"Preview orientation is: $previewOrientation."
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
@@ -287,7 +294,6 @@ internal open class CameraDevice(
return previewResolution
}
private fun setZoomSafely(@FloatRange(from = 0.0, to = 1.0) level: Float) {
try {
setZoomUnsafe(level)
@@ -297,14 +303,20 @@ internal open class CameraDevice(
}
private fun setZoomUnsafe(@FloatRange(from = 0.0, to = 1.0) level: Float) {
(cachedZoomParameters ?: camera.parameters)
(cachedCameraParameters ?: camera.parameters)
.apply {
zoom = (maxZoom * level).toInt()
}
.let {
cachedZoomParameters = it
camera.parameters = it
}
.cacheLocally()
.setInCamera()
}
private fun Camera.Parameters.cacheLocally() = apply {
cachedCameraParameters = this
}
private fun Camera.Parameters.setInCamera() = apply {
camera.parameters = this
}
private fun Camera.focusSafely(): FocusResult {
@@ -351,16 +363,13 @@ internal open class CameraDevice(
private fun Camera.clearFocusingAreas() {
parameters = parameters.apply {
with(capabilities) {
meteringAreas = null
focusAreas = null
}
meteringAreas = null
focusAreas = null
}
}
}
private const val AUTOFOCUS_TIMEOUT_SECONDS = 3L
private fun Camera.takePhoto(imageRotation: Int): Photo {
@@ -385,10 +394,6 @@ private fun Camera.takePhoto(imageRotation: Int): Photo {
return photoReference.get()
}
private fun Camera.updateParameters(newParameters: CameraParameters) {
parameters = parameters.applyNewParameters(newParameters)
}
@Throws(IOException::class)
private fun Camera.setDisplaySurface(
preview: Preview
@@ -18,8 +18,7 @@ import io.fotoapparat.selector.LensPositionSelector
import io.fotoapparat.util.FrameProcessor
import io.fotoapparat.view.CameraRenderer
import io.fotoapparat.view.FocalPointSelector
import kotlinx.coroutines.experimental.CompletableDeferred
import kotlinx.coroutines.CompletableDeferred
/**
* Phone.
@@ -150,12 +149,13 @@ internal open class Device(
/**
* @return The frame processor.
*/
open fun getFrameProcessor(): FrameProcessor = savedConfiguration.frameProcessor
open fun getFrameProcessor(): FrameProcessor? = savedConfiguration.frameProcessor
/**
* @return The desired from the user camera lens position.
*/
open fun getLensPositionSelector(): LensPositionSelector = lensPositionSelector
}
/**
@@ -167,6 +167,8 @@ internal fun updateConfiguration(
) = 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
@@ -189,4 +191,4 @@ internal fun selectCamera(
val desiredPosition = lensPositionSelector(lensPositions)
return availableCameras.find { it.characteristics.lensPosition == desiredPosition }
}
}
@@ -4,7 +4,6 @@ import android.os.Handler
import android.os.Looper
import java.util.concurrent.Executors
private val loggingExecutor = Executors.newSingleThreadExecutor()
private val mainThreadHandler = Handler(Looper.getMainLooper())
@@ -1,6 +1,5 @@
package io.fotoapparat.hardware.metering
/**
* A point in arbitrary scale.
*/
@@ -18,12 +18,16 @@ sealed class Orientation(
/**
* A vertical, normal orientation.
*/
object Portrait : Vertical(0)
object Portrait : Vertical(0) {
override fun toString(): String = "Orientation.Vertical.Portrait"
}
/**
* A reversed (flipped phone) orientation.
*/
object ReversePortrait : Vertical(180)
object ReversePortrait : Vertical(180) {
override fun toString(): String = "Orientation.Vertical.ReversePortrait"
}
}
@@ -35,12 +39,16 @@ sealed class Orientation(
/**
* A 90 degrees clockwise from "normal", orientation.
*/
object Landscape : Horizontal(90)
object Landscape : Horizontal(90) {
override fun toString(): String = "Orientation.Horizontal.Landscape"
}
/**
* A 90 degrees counter-clockwise from "normal", orientation.
*/
object ReverseLandscape : Horizontal(270)
object ReverseLandscape : Horizontal(270) {
override fun toString(): String = "Orientation.Horizontal.ReverseLandscape"
}
}
}
@@ -4,7 +4,6 @@ import android.content.Context
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
/**
* Monitors orientation of the device.
*/
@@ -13,23 +12,28 @@ internal open class OrientationSensor(
private val device: Device
) {
private lateinit var listener: (OrientationState) -> Unit
private val onOrientationChanged: (DeviceRotationDegrees) -> Unit = { deviceRotation ->
deviceRotation.toClosestRightAngle()
.toOrientation()
.takeIf { it != lastKnownDeviceOrientation }
?.let {
val state = OrientationState(
deviceOrientation = it,
screenOrientation = device.getScreenOrientation()
.let { deviceOrientation ->
val screenOrientation = device.getScreenOrientation()
val newState = OrientationState(
deviceOrientation = deviceOrientation,
screenOrientation = screenOrientation
)
lastKnownDeviceOrientation = state.deviceOrientation
listener(state)
if (newState != lastKnownOrientationState) {
lastKnownOrientationState = newState
listener(newState)
}
}
}
private lateinit var listener: (OrientationState) -> Unit
private var lastKnownDeviceOrientation: Orientation = Portrait
open var lastKnownOrientationState: OrientationState = OrientationState(
deviceOrientation = Portrait,
screenOrientation = device.getScreenOrientation()
)
constructor(
context: Context,
@@ -58,4 +62,4 @@ internal open class OrientationSensor(
rotationListener.disable()
}
}
}
@@ -3,7 +3,6 @@ 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.
*/
@@ -19,4 +18,4 @@ internal open class RotationListener(
}
}
}
}
@@ -1,6 +1,5 @@
package io.fotoapparat.log
/**
* Logger which delegates messages to multiple other loggers.
*/
@@ -10,4 +9,4 @@ internal class CompositeLogger(private val loggers: List<Logger>) : Logger {
loggers.forEach { it.log(message) }
}
}
}
@@ -8,21 +8,29 @@ sealed class AntiBandingMode : Parameter {
/**
* Auto adjust. This should be the default.
*/
object Auto : AntiBandingMode()
object Auto : AntiBandingMode() {
override fun toString(): String = "AntiBandingMode.Auto"
}
/**
* Anti Banding is set to 50Hz light frequency.
*/
object HZ50 : AntiBandingMode()
object HZ50 : AntiBandingMode() {
override fun toString(): String = "AntiBandingMode.HZ50"
}
/**
* Anti Banding is set to 60Hz light frequency.
*/
object HZ60 : AntiBandingMode()
object HZ60 : AntiBandingMode() {
override fun toString(): String = "AntiBandingMode.HZ60"
}
/**
* Anti Banding is not supported or ignored.
*/
object None : AntiBandingMode()
object None : AntiBandingMode() {
override fun toString(): String = "AntiBandingMode.None"
}
}
@@ -8,27 +8,37 @@ sealed class Flash : Parameter {
/**
* Camera flash will not fire.
*/
object Off : Flash()
object Off : Flash() {
override fun toString(): String = "Flash.Off"
}
/**
* Camera flash will always fire regardless light conditions.
*/
object On : Flash()
object On : Flash() {
override fun toString(): String = "Flash.On"
}
/**
* Camera flash will fire only in low light conditions.
*/
object Auto : Flash()
object Auto : Flash() {
override fun toString(): String = "Flash.Auto"
}
/**
* If deemed necessary by the camera device, a red eye reduction flash will fire during the
* precapture sequence in low light conditions.
*/
object AutoRedEye : Flash()
object AutoRedEye : Flash() {
override fun toString(): String = "Flash.AutoRedEye"
}
/**
* Transition flash to continuously on.
*/
object Torch : Flash()
object Torch : Flash() {
override fun toString(): String = "Flash.Torch"
}
}
@@ -8,40 +8,54 @@ sealed class FocusMode : Parameter {
/**
* Focus is not adjustable. It is always used by devices which do not support auto-focus.
*/
object Fixed : FocusMode()
object Fixed : FocusMode() {
override fun toString(): String = "FocusMode.Fixed"
}
/**
* Camera is focused at infinity.
*/
object Infinity : FocusMode()
object Infinity : FocusMode() {
override fun toString(): String = "FocusMode.Infinity"
}
/**
* Macro focus mode.
*/
object Macro : FocusMode()
object Macro : FocusMode() {
override fun toString(): String = "FocusMode.Macro"
}
/**
* Auto focus. Camera is trying to focus automatically when manually requested.
*/
object Auto : FocusMode()
object Auto : FocusMode() {
override fun toString(): String = "FocusMode.Auto"
}
/**
* Camera is constantly trying to stay in focus.
*
* The speed of focus change is more aggressive than [ContinuousFocusVideo].
*/
object ContinuousFocusPicture : FocusMode()
object ContinuousFocusPicture : FocusMode() {
override fun toString(): String = "FocusMode.ContinuousFocusPicture"
}
/**
* Camera is constantly trying to stay in focus.
*
* The speed of focus change is smoother than [ContinuousFocusPicture].
*/
object ContinuousFocusVideo : FocusMode()
object ContinuousFocusVideo : FocusMode() {
override fun toString(): String = "FocusMode.ContinuousFocusVideo"
}
/**
* The camera device will produce images with an extended depth of field.
*/
object Edof : FocusMode()
object Edof : FocusMode() {
override fun toString(): String = "FocusMode.Edof"
}
}
@@ -26,7 +26,5 @@ data class FpsRange(
/**
* `true` if the current range is fixed (min == max).
*/
val isFixed by lazy {
max == min
}
val isFixed get() = max == min
}
@@ -1,6 +1,6 @@
package io.fotoapparat.parameter
import android.support.annotation.IntRange
import androidx.annotation.IntRange
/**
* Resolution. Units in pixels.
@@ -13,22 +13,21 @@ data class Resolution(
/**
* The total area this [Resolution] is covering.
*/
val area: Int by lazy { width * height }
val area: Int get() = width * height
/**
* The aspect ratio for this size. [Float.NaN] if invalid dimensions.
*/
val aspectRatio: Float by lazy {
when {
val aspectRatio: Float
get() = when {
width == 0 -> Float.NaN
height == 0 -> Float.NaN
else -> width.toFloat() / height
}
}
/**
* @return new instance of [Resolution] with width and height being swapped.
*/
fun flipDimensions(): Resolution = Resolution(height, width)
}
}
@@ -15,6 +15,13 @@ enum class ScaleType {
* The preview will be scaled so as its one dimensions will be equal and the other one equal or
* smaller than the corresponding dimension of the view
*/
CenterInside
CenterInside,
/**
* The preview will be scaled so as its one dimensions will be equal and the other one equal or
* larger than the corresponding dimension of the view with focus on the top part
*/
TopCrop,
}
@@ -56,10 +56,11 @@ internal class SupportedParameters(
}
/**
* @see Camera.Parameters.isZoomSupported
* @return [io.fotoapparat.parameter.Zoom.FixedZoom] if [Camera.Parameters.isZoomSupported] returns false,
* and [io.fotoapparat.parameter.Zoom.VariableZoom] with max zoom level otherwise.
*/
val supportedZoom by lazy {
cameraParameters.isZoomSupported
if (cameraParameters.isZoomSupported) Zoom.VariableZoom(cameraParameters.maxZoom, cameraParameters.zoomRatios) else Zoom.FixedZoom
}
/**
@@ -83,6 +84,13 @@ internal class SupportedParameters(
IntRange(0, 100)
}
/**
* @return A [IntRange] of exposure compensation values supported by the camera.
*/
val exposureCompensationRange by lazy {
IntRange(cameraParameters.minExposureCompensation, cameraParameters.maxExposureCompensation)
}
/**
* @see Camera.Parameters.getMaxNumFocusAreas
*/
@@ -0,0 +1,25 @@
package io.fotoapparat.parameter
/**
* Zoom modes which camera can have.
*/
sealed class Zoom {
/**
* The camera can only support one, fixed zoom level.
*/
object FixedZoom : Zoom() {
override fun toString(): String = "Zoom.FixedZoom"
}
/**
* The camera can only support a variable zoom level between (and including) 0 and [maxZoom] values.
* [zoomRatios] is a list of all zoom values. Ratios are in 1/100 increments. For example, zoom of
* 2.7x is returned as 270. The number of elements is [maxZoom] + 1. List is sorted from small to
* large. First element is always 100. The last element is the zoom ratio of the maximum zoom value.
*/
data class VariableZoom(val maxZoom: Int, val zoomRatios: List<Int>) : Zoom() {
override fun toString(): String = "Zoom.VariableZoom(maxZoom=$maxZoom, zoomRatios=$zoomRatios)"
}
}
@@ -5,7 +5,6 @@ import io.fotoapparat.parameter.*
import io.fotoapparat.util.lineSeparator
import io.fotoapparat.util.wrap
/**
* Parameters of [CameraDevice].
*/
@@ -13,6 +12,7 @@ data class CameraParameters(
val flashMode: Flash,
val focusMode: FocusMode,
val jpegQuality: Int,
val exposureCompensation: Int,
val previewFpsRange: FpsRange,
val antiBandingMode: AntiBandingMode,
val sensorSensitivity: Int?,
@@ -24,10 +24,11 @@ data class CameraParameters(
"flashMode:" + flashMode.wrap() +
"focusMode:" + focusMode.wrap() +
"jpegQuality:" + jpegQuality.wrap() +
"exposureCompensation:" + exposureCompensation.wrap() +
"previewFpsRange:" + previewFpsRange.wrap() +
"antiBandingMode:" + antiBandingMode.wrap() +
"sensorSensitivity:" + sensorSensitivity.wrap() +
"pictureResolution:" + pictureResolution.wrap() +
"previewResolution:" + previewResolution.wrap()
}
}
}
@@ -12,19 +12,20 @@ import io.fotoapparat.parameter.camera.convert.toCode
* Applies a new set of [CameraParameters] to existing [Camera.Parameters].
*
* @receiver The existing [Camera.Parameters]
* @param newParameters A new set of [CameraParameters].
* @param parameters A new set of [CameraParameters].
*
* @return Same [Camera.Parameters] object which was passed, but filled with new parameters.
*/
internal fun Camera.Parameters.applyNewParameters(newParameters: CameraParameters): Camera.Parameters {
newParameters tryApplyInto this
return this
internal fun CameraParameters.applyInto(parameters: Camera.Parameters): Camera.Parameters {
this tryApplyInto parameters
return parameters
}
private infix fun CameraParameters.tryApplyInto(parameters: Camera.Parameters) {
flashMode applyInto parameters
focusMode applyInto parameters
jpegQuality applyInto parameters
jpegQuality applyJpegQualityInto parameters
exposureCompensation applyExposureCompensationInto parameters
antiBandingMode applyInto parameters
previewFpsRange applyInto parameters
previewResolution applyPreviewInto parameters
@@ -40,10 +41,14 @@ private infix fun FocusMode.applyInto(parameters: Camera.Parameters) {
parameters.focusMode = toCode()
}
private infix fun Int.applyInto(parameters: Camera.Parameters) {
private infix fun Int.applyJpegQualityInto(parameters: Camera.Parameters) {
parameters.jpegQuality = this
}
private infix fun Int.applyExposureCompensationInto(parameters: Camera.Parameters) {
parameters.exposureCompensation = this
}
private infix fun AntiBandingMode.applyInto(parameters: Camera.Parameters) {
parameters.antibanding = toCode()
}
@@ -10,7 +10,6 @@ import io.fotoapparat.parameter.Resolution
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.selector.*
/**
* @return [CameraParameters] which will be used by [CameraDevice].
*/
@@ -32,6 +31,7 @@ internal fun getCameraParameters(
flashMode = flashMode selectFrom flashModes,
focusMode = focusMode selectFrom focusModes,
jpegQuality = jpegQuality selectFrom jpegQualityRange,
exposureCompensation = exposureCompensation selectFrom exposureCompensationRange,
previewFpsRange = previewFpsRange selectFrom previewFpsRanges,
antiBandingMode = antiBandingMode selectFrom antiBandingModes,
pictureResolution = selectedPictureResolution,
@@ -8,11 +8,15 @@ sealed class FocusResult {
/**
* Camera is unable to focus for some reason.
*/
object UnableToFocus : FocusResult()
object UnableToFocus : FocusResult() {
override fun toString(): String = "FocusResult.UnableToFocus"
}
/**
* Camera is focused successfully.
*/
object Focused : FocusResult()
object Focused : FocusResult() {
override fun toString(): String = "FocusResult.Focused"
}
}
@@ -1,6 +1,7 @@
package io.fotoapparat.result
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.concurrent.ensureBackgroundThread
import io.fotoapparat.exception.UnableToDecodeBitmapException
import io.fotoapparat.hardware.executeMainThread
import io.fotoapparat.hardware.pendingResultExecutor
@@ -8,7 +9,6 @@ import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.camera.CameraParameters
import java.util.concurrent.*
/**
* Result which might not be readily available at the given moment but will be available in the
* future.
@@ -20,7 +20,10 @@ internal constructor(
private val executor: Executor
) {
private val resultUnsafe: T
get() = future.get()
get() {
ensureBackgroundThread()
return future.get()
}
/**
* Transforms result from one type to another.
@@ -69,16 +72,24 @@ internal constructor(
fun whenAvailable(callback: (T?) -> Unit) {
executor.execute {
try {
resultUnsafe.notifyCallbackOnMainThread(callback)
val result = resultUnsafe
notifyOnMainThread {
callback(result)
}
} catch (e: UnableToDecodeBitmapException) {
logger.log("Couldn't decode bitmap from byte array")
notifyOnMainThread {
callback(null)
}
} catch (e: InterruptedException) {
logger.log("Couldn't deliver pending result: Camera stopped before delivering result.")
} catch (e: CancellationException) {
logger.log("Couldn't deliver pending result: Camera operation was cancelled.")
} catch (e: ExecutionException) {
logger.log("Couldn't deliver pending result: Operation failed internally.")
callback(null)
notifyOnMainThread {
callback(null)
}
}
}
}
@@ -106,9 +117,9 @@ internal constructor(
}
private fun <T> T.notifyCallbackOnMainThread(callback: (T) -> Unit) {
private fun notifyOnMainThread(function: () -> Unit) {
executeMainThread {
callback(this)
function()
}
}
@@ -1,7 +1,7 @@
package io.fotoapparat.result
import android.graphics.BitmapFactory
import java.util.*
import java.util.Arrays
/**
* Taken photo.
@@ -27,22 +27,20 @@ data class Photo(
/**
* The height of the photo.
*/
val height by lazy { decodedBounds.height }
val height: Int
get() = decodedBounds.outHeight
/**
* The width of the photo.
*/
val width by lazy { decodedBounds.width }
val width: Int
get() = decodedBounds.outWidth
private val decodedBounds by lazy {
BitmapFactory.decodeByteArray(
encodedImage,
0,
encodedImage.size,
BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
)
BitmapFactory.Options().apply {
inJustDecodeBounds = true
BitmapFactory.decodeByteArray(encodedImage, 0, encodedImage.size, this)
}
}
override fun equals(other: Any?): Boolean {
@@ -63,14 +61,14 @@ data class Photo(
return result
}
override fun toString(): String =
"Photo(encodedImage=ByteArray(${encodedImage.size}) rotationDegrees=$rotationDegrees)"
companion object {
private val EMPTY by lazy { Photo(encodedImage = ByteArray(0), rotationDegrees = 0) }
/**
* @return empty [Photo].
*/
internal fun empty(): Photo = EMPTY
internal fun empty(): Photo = Photo(encodedImage = ByteArray(0), rotationDegrees = 0)
}
}
}
@@ -23,7 +23,8 @@ internal class BitmapPhotoTransformer(
desiredResolution = desiredResolution
)
val decodedBitmap = input.decodeBitmap(scaleFactor) ?: throw UnableToDecodeBitmapException()
val decodedBitmap = input.decodeBitmap(scaleFactor, originalResolution, desiredResolution)
?: throw UnableToDecodeBitmapException()
val bitmap = if (decodedBitmap.width == desiredResolution.width && decodedBitmap.height == desiredResolution.height) {
decodedBitmap
@@ -44,14 +45,28 @@ internal class BitmapPhotoTransformer(
}
private fun Photo.decodeBitmap(scaleFactor: Float): Bitmap? {
private fun Photo.decodeBitmap(
scaleFactor: Float,
originalResolution: Resolution,
desiredResolution: Resolution
): Bitmap? {
val options = BitmapFactory.Options()
options.inSampleSize = scaleFactor.toInt()
options.inScaled = true
if (desiredResolution.width > desiredResolution.height) {
options.inDensity = originalResolution.width
options.inTargetDensity = desiredResolution.width * options.inSampleSize
} else {
options.inDensity = originalResolution.height
options.inTargetDensity = desiredResolution.height * options.inSampleSize
}
return BitmapFactory.decodeByteArray(
encodedImage,
0,
encodedImage.size
encodedImage.size,
options
)
}
@@ -2,7 +2,6 @@ package io.fotoapparat.result.transformer
import io.fotoapparat.parameter.Resolution
typealias ResolutionTransformer = (Resolution) -> Resolution
/**
@@ -19,4 +18,4 @@ fun scaled(scaleFactor: Float): ResolutionTransformer = { input ->
width = (input.width * scaleFactor).toInt(),
height = (input.height * scaleFactor).toInt()
)
}
}
@@ -4,9 +4,7 @@ import io.fotoapparat.concurrent.CameraExecutor.Operation
import io.fotoapparat.error.CameraErrorCallback
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.Orientation
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.hardware.orientation.OrientationState
import io.fotoapparat.routine.focus.focusOnPoint
import io.fotoapparat.routine.orientation.startOrientationMonitoring
import java.io.IOException
@@ -23,7 +21,9 @@ internal fun Device.bootStart(
}
try {
start()
start(
orientationSensor = orientationSensor
)
startOrientationMonitoring(
orientationSensor = orientationSensor
)
@@ -35,7 +35,7 @@ internal fun Device.bootStart(
/**
* Starts the camera.
*/
internal fun Device.start() {
internal fun Device.start(orientationSensor: OrientationSensor) {
selectCamera()
val cameraDevice = getSelectedCamera().apply {
@@ -44,12 +44,7 @@ internal fun Device.start() {
updateCameraConfiguration(
cameraDevice = this
)
setDisplayOrientation(
orientationState = OrientationState(
deviceOrientation = Orientation.Vertical.Portrait,
screenOrientation = getScreenOrientation()
)
)
setDisplayOrientation(orientationSensor.lastKnownOrientationState)
}
val previewResolution = cameraDevice.getPreviewResolution()
@@ -5,7 +5,6 @@ import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.routine.orientation.stopMonitoring
/**
* Stops the camera completely.
*/
@@ -29,4 +28,4 @@ internal fun Device.stop(cameraDevice: CameraDevice) {
cameraDevice.close()
clearSelectedCamera()
}
}
@@ -6,6 +6,7 @@ import io.fotoapparat.error.CameraErrorCallback
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.CameraDevice
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.selector.LensPositionSelector
/**
@@ -16,7 +17,8 @@ import io.fotoapparat.selector.LensPositionSelector
internal fun Device.switchCamera(
newLensPositionSelector: LensPositionSelector,
newConfiguration: CameraConfiguration,
mainThreadErrorCallback: CameraErrorCallback
mainThreadErrorCallback: CameraErrorCallback,
orientationSensor: OrientationSensor
) {
val oldCameraDevice = try {
getSelectedCamera()
@@ -33,6 +35,7 @@ internal fun Device.switchCamera(
restartPreview(
oldCameraDevice,
orientationSensor,
mainThreadErrorCallback
)
}
@@ -43,12 +46,13 @@ internal fun Device.switchCamera(
*/
internal fun Device.restartPreview(
oldCameraDevice: CameraDevice,
orientationSensor: OrientationSensor,
mainThreadErrorCallback: CameraErrorCallback
) {
stop(oldCameraDevice)
try {
start()
start(orientationSensor)
} catch (e: CameraException) {
mainThreadErrorCallback(e)
}
@@ -3,8 +3,7 @@ package io.fotoapparat.routine.camera
import io.fotoapparat.configuration.Configuration
import io.fotoapparat.hardware.CameraDevice
import io.fotoapparat.hardware.Device
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
/**
* Updates [Device] configuration.
@@ -33,4 +32,4 @@ internal fun Device.updateCameraConfiguration(
cameraDevice.updateFrameProcessor(
frameProcessor = frameProcessor
)
}
}
@@ -2,7 +2,7 @@ package io.fotoapparat.routine.capability
import io.fotoapparat.capability.Capabilities
import io.fotoapparat.hardware.Device
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
/**
* Returns the camera [Capabilities].
@@ -3,7 +3,7 @@ package io.fotoapparat.routine.focus
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.metering.FocalRequest
import io.fotoapparat.result.FocusResult
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
/**
* Focuses the camera on a particular point.
@@ -14,4 +14,4 @@ internal fun Device.focusOnPoint(focalRequest: FocalRequest): FocusResult = runB
setFocalPoint(focalRequest)
autoFocus()
}
}
}
@@ -2,7 +2,7 @@ package io.fotoapparat.routine.focus
import io.fotoapparat.hardware.Device
import io.fotoapparat.result.FocusResult
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
/**
* Focuses the camera.
@@ -11,4 +11,4 @@ internal fun Device.focus(): FocusResult = runBlocking {
val cameraDevice = awaitSelectedCamera()
cameraDevice.autoFocus()
}
}
@@ -2,7 +2,7 @@ package io.fotoapparat.routine.parameter
import io.fotoapparat.hardware.Device
import io.fotoapparat.parameter.camera.CameraParameters
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
/**
* Returns the current [CameraParameters].
@@ -11,4 +11,4 @@ internal fun Device.getCurrentParameters(): CameraParameters = runBlocking {
val cameraDevice = awaitSelectedCamera()
cameraDevice.getParameters()
}
}
@@ -4,7 +4,7 @@ import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.CameraDevice
import io.fotoapparat.hardware.Device
import io.fotoapparat.result.Photo
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
/**
* Takes a photo.
@@ -1,10 +1,10 @@
package io.fotoapparat.routine.zoom
import android.support.annotation.FloatRange
import androidx.annotation.FloatRange
import io.fotoapparat.exception.LevelOutOfRangeException
import io.fotoapparat.hardware.Device
import kotlinx.coroutines.experimental.runBlocking
import io.fotoapparat.parameter.Zoom
import kotlinx.coroutines.runBlocking
/**
* Updates zoom level of the camera. If zoom is not supported - does nothing.
@@ -17,7 +17,7 @@ internal fun Device.updateZoomLevel(
zoomLevel.ensureInBounds()
val cameraDevice = awaitSelectedCamera()
if (cameraDevice.getCapabilities().canZoom) {
if (cameraDevice.getCapabilities().zoom is Zoom.VariableZoom) {
cameraDevice.setZoom(zoomLevel)
}
}
@@ -26,4 +26,4 @@ private fun Float.ensureInBounds() {
if (this !in 0f..1f) {
throw LevelOutOfRangeException(this)
}
}
}
@@ -2,7 +2,6 @@ package io.fotoapparat.selector
import io.fotoapparat.parameter.AntiBandingMode
typealias AntiBandingModeSelector = Iterable<AntiBandingMode>.() -> AntiBandingMode?
/**
@@ -1,6 +1,6 @@
package io.fotoapparat.selector
import android.support.annotation.FloatRange
import androidx.annotation.FloatRange
/**
* @param selector Input selector
@@ -0,0 +1,24 @@
package io.fotoapparat.selector
typealias ExposureSelector = IntRange.() -> Int?
/**
* @param exposure The specified exposure compensation value
* @return Selector function which selects the specified exposure compensation value.
*/
fun manualExposure(exposure: Int): ExposureSelector = single(exposure)
/**
* @return Selector function which always provides the highest exposure.
*/
fun highestExposure(): ExposureSelector = highest()
/**
* @return Selector function which always provides the lowest exposure.
*/
fun lowestExposure(): ExposureSelector = lowest()
/**
* @return Selector function which always provides the default exposure.
*/
fun defaultExposure(): ExposureSelector = single(0)
@@ -2,7 +2,6 @@ package io.fotoapparat.selector
import io.fotoapparat.parameter.FocusMode
typealias FocusModeSelector = Iterable<FocusMode>.() -> FocusMode?
/**
@@ -2,7 +2,6 @@ package io.fotoapparat.selector
import io.fotoapparat.characteristic.LensPosition
typealias LensPositionSelector = Iterable<LensPosition>.() -> LensPosition?
/**
@@ -21,4 +20,4 @@ fun back(): LensPositionSelector = single(LensPosition.Back)
* @return Selector function which provides the external camera if it is available.
* Otherwise provides `null`.
*/
fun external(): LensPositionSelector = single(LensPosition.External)
fun external(): LensPositionSelector = single(LensPosition.External)
@@ -1,6 +1,5 @@
package io.fotoapparat.selector
/**
* @return Selector function which always returns `null`.
*/
@@ -32,6 +31,7 @@ fun <T : Comparable<T>> lowest(): Iterable<T>.() -> T? = Iterable<T>::min
* @return Selector function which returns first non-null result from given selectors.
* If there are no non-null results, returns `null`.
*/
@SafeVarargs
fun <Input, Output> firstAvailable(
vararg functions: Input.() -> Output?
): Input.() -> Output? = {
@@ -1,6 +1,5 @@
package io.fotoapparat.selector
typealias SensorSensitivitySelector = Iterable<Int>.() -> Int?
/**
@@ -18,4 +17,4 @@ fun highestSensorSensitivity(): SensorSensitivitySelector = highest()
/**
* @return Selector function which selects lowest ISO value.
*/
fun lowestSensorSensitivity(): SensorSensitivitySelector = lowest()
fun lowestSensorSensitivity(): SensorSensitivitySelector = lowest()
@@ -1,6 +1,5 @@
package io.fotoapparat.util
/**
* System line separator.
*/
@@ -2,4 +2,15 @@ package io.fotoapparat.util
import io.fotoapparat.preview.Frame
typealias FrameProcessor = (Frame) -> Unit
/**
* Performs processing on preview frames.
*
* This function is called from worker thread (aka non-UI thread). After
* the function completes the frame is returned back to the pool where it is reused
* afterwards. Thus, implementations should take special care to not do any operations on
* frame after method completes.
*
* @param frame frame of the preview. Do not cache it as it will eventually be reused by the
* camera.
*/
typealias FrameProcessor = (frame: Frame) -> Unit
@@ -12,7 +12,6 @@ import io.fotoapparat.parameter.Resolution
import io.fotoapparat.parameter.ScaleType
import java.util.concurrent.CountDownLatch
/**
* Uses [android.view.TextureView] as an output for camera.
*/
@@ -83,6 +82,7 @@ private fun ViewGroup.layoutTextureView(
) = when (scaleType) {
ScaleType.CenterInside -> previewResolution?.centerInside(this)
ScaleType.CenterCrop -> previewResolution?.centerCrop(this)
ScaleType.TopCrop -> previewResolution?.topCrop(this)
else -> null
}
@@ -130,6 +130,28 @@ private fun Resolution.centerCrop(view: ViewGroup) {
view.layoutChildrenAt(rect)
}
private fun Resolution.topCrop(view: ViewGroup) {
val scale = Math.max(
view.measuredWidth / width.toFloat(),
view.measuredHeight / height.toFloat()
)
val width = (width * scale).toInt()
val height = (height * scale).toInt()
val extraX = Math.max(0, width - view.measuredWidth)
val rect = Rect(
-extraX / 2,
0,
width - extraX / 2,
height
)
view.layoutChildrenAt(rect)
}
private fun ViewGroup.layoutChildrenAt(rect: Rect) {
(0 until childCount).forEach {
getChildAt(it).layout(
@@ -3,15 +3,14 @@ package io.fotoapparat.view
import android.animation.AnimatorInflater.loadAnimator
import android.animation.AnimatorSet
import android.content.Context
import android.support.annotation.AnimatorRes
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.annotation.AnimatorRes
import io.fotoapparat.R
/**
* A circle which gives feedback to the user about a click.
*/
@@ -81,4 +80,4 @@ internal class FeedbackCircleView
private fun newAnimator(@AnimatorRes id: Int, target: View) = loadAnimator(context, id).apply {
setTarget(target)
}
}
}
@@ -3,7 +3,9 @@ package io.fotoapparat.view
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.widget.FrameLayout
import io.fotoapparat.hardware.metering.FocalRequest
import io.fotoapparat.hardware.metering.PointF
@@ -14,7 +16,7 @@ import io.fotoapparat.parameter.Resolution
*
* If the camera doesn't support focus metering on specific area this will only display a visual feedback.
*/
class FocusView
open class FocusView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -24,6 +26,15 @@ class FocusView
private val visualFeedbackCircle = FeedbackCircleView(context, attrs, defStyleAttr)
private var focusMeteringListener: ((FocalRequest) -> Unit)? = null
var scaleListener: ((Float) -> Unit)? = null
var ptrListener: ((Int) -> Unit)? = null
private var mPtrCount: Int = 0
set(value) {
field = value
ptrListener?.invoke(value)
}
init {
clipToPadding = false
clipChildren = false
@@ -36,27 +47,50 @@ class FocusView
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
focusMeteringListener?.let {
it(FocalRequest(
point = PointF(
x = event.x,
y = event.y
),
previewResolution = Resolution(
width = width,
height = height
)
))
visualFeedbackCircle.showAt(
x = event.x - visualFeedbackCircle.width / 2,
y = event.y - visualFeedbackCircle.height / 2
)
performClick()
return true
}
tapDetector.onTouchEvent(event)
scaleDetector.onTouchEvent(event)
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_POINTER_DOWN -> mPtrCount++
MotionEvent.ACTION_POINTER_UP -> mPtrCount--
MotionEvent.ACTION_DOWN -> mPtrCount++
MotionEvent.ACTION_UP -> mPtrCount--
}
return super.onTouchEvent(event)
return true
}
private val gestureDetectorListener = object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean {
return focusMeteringListener
?.let {
it(FocalRequest(
point = PointF(
x = e.x,
y = e.y),
previewResolution = Resolution(
width = width,
height = height)))
visualFeedbackCircle.showAt(
x = e.x - visualFeedbackCircle.width / 2,
y = e.y - visualFeedbackCircle.height / 2)
performClick()
true
}
?: super.onSingleTapUp(e)
}
}
private val tapDetector = GestureDetector(context, gestureDetectorListener)
private val scaleGestureDetector = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
return scaleListener
?.let {
it(detector.scaleFactor)
true
}?: super.onScale(detector)
}
}
private val scaleDetector = ScaleGestureDetector(context, scaleGestureDetector)
}
@@ -1,3 +0,0 @@
<resources>
<string name="app_name">Fotoapparat</string>
</resources>
@@ -237,12 +237,12 @@ class FotoapparatBuilderTest {
}
@Test
fun `frameProcessor empty by default`() {
fun `frameProcessor null by default`() {
// When
val builder = builderWithMandatoryArguments()
// Then
assertNotNull(builder.configuration.frameProcessor)
assertNull(builder.configuration.frameProcessor)
}
@Test
@@ -2,6 +2,7 @@ package io.fotoapparat.hardware.orientation
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.Landscape
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
import io.fotoapparat.hardware.orientation.Orientation.Vertical.ReversePortrait
import io.fotoapparat.test.willReturn
import org.junit.Before
@@ -25,6 +26,7 @@ internal class OrientationSensorTest {
@Before
fun setUp() {
device.getScreenOrientation() willReturn Portrait
testee = OrientationSensor(
rotationListener,
device
@@ -36,7 +38,7 @@ internal class OrientationSensorTest {
// Given
// When
testee.start({})
testee.start {}
testee.stop()
// Then
@@ -1,11 +1,9 @@
package io.fotoapparat.log
import org.apache.commons.io.FileUtils.readFileToString
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.File
import java.nio.charset.Charset
import kotlin.test.assertEquals
internal class FileLoggerTest {
@@ -29,7 +27,7 @@ internal class FileLoggerTest {
// Then
assertEquals(
"message\nmessage\n",
readFileToString(file, Charset.defaultCharset())
file.readText()
)
}
@@ -11,16 +11,16 @@ import io.fotoapparat.selector.single
import org.junit.Test
import kotlin.test.assertEquals
internal class CameraParametersProviderTest {
val resolution = Resolution(10, 10)
val fpsRange = FpsRange(20000, 20000)
val jpegQuality = 80
val exposureCompensation = 5
val iso = 100
val capabilities = Capabilities(
canZoom = false,
zoom = Zoom.FixedZoom,
flashModes = setOf(Flash.AutoRedEye),
focusModes = setOf(FocusMode.Fixed),
canSmoothZoom = false,
@@ -29,6 +29,7 @@ internal class CameraParametersProviderTest {
previewFpsRanges = setOf(fpsRange),
antiBandingModes = setOf(AntiBandingMode.None),
jpegQualityRange = IntRange(0, 100),
exposureCompensationRange = IntRange(-20, 20),
pictureResolutions = setOf(resolution),
previewResolutions = setOf(resolution),
sensorSensitivities = setOf(iso)
@@ -38,6 +39,7 @@ internal class CameraParametersProviderTest {
flashMode = single(Flash.AutoRedEye),
focusMode = single(FocusMode.Fixed),
jpegQuality = single(jpegQuality),
exposureCompensation = single(exposureCompensation),
frameProcessor = {},
antiBandingMode = single(AntiBandingMode.None),
previewFpsRange = single(fpsRange),
@@ -62,6 +64,7 @@ internal class CameraParametersProviderTest {
flashMode = Flash.AutoRedEye,
focusMode = FocusMode.Fixed,
jpegQuality = jpegQuality,
exposureCompensation = exposureCompensation,
previewFpsRange = fpsRange,
antiBandingMode = AntiBandingMode.None,
pictureResolution = resolution,
@@ -171,6 +174,23 @@ internal class CameraParametersProviderTest {
// throw exception
}
@Test(expected = UnsupportedConfigurationException::class)
fun `Select no exposure compensation`() {
// Given
val definedConfiguration = definedConfiguration.copy(
exposureCompensation = nothing()
)
// When
getCameraParameters(
capabilities = capabilities,
cameraConfiguration = definedConfiguration
)
// Then
// throw exception
}
@Test(expected = UnsupportedConfigurationException::class)
fun `Select no antibanding mode`() {
// Given
@@ -325,6 +345,26 @@ internal class CameraParametersProviderTest {
// throw exception
}
@Test(expected = InvalidConfigurationException::class)
fun `Select exposure compensation which is not supported`() {
// Given
val capabilities = capabilities.copy(
exposureCompensationRange = IntRange(-20, 20)
)
val definedConfiguration = definedConfiguration.copy(
exposureCompensation = { 100 }
)
// When
getCameraParameters(
capabilities = capabilities,
cameraConfiguration = definedConfiguration
)
// Then
// throw exception
}
@Test(expected = InvalidConfigurationException::class)
fun `Select anti banding mode which is not supported`() {
// Given
@@ -344,4 +384,4 @@ internal class CameraParametersProviderTest {
// Then
// throw exception
}
}
}
@@ -4,10 +4,7 @@ import android.graphics.SurfaceTexture
import io.fotoapparat.exception.camera.CameraException
import io.fotoapparat.hardware.CameraDevice
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.Landscape
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.hardware.orientation.OrientationState
import io.fotoapparat.log.Logger
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.test.testResolution
@@ -27,7 +24,6 @@ import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@RunWith(MockitoJUnitRunner::class)
internal class StartRoutineTest {
@@ -56,7 +52,6 @@ internal class StartRoutineTest {
// Given
device.apply {
getSelectedCamera() willReturn cameraDevice
getScreenOrientation() willReturn Landscape
cameraRenderer willReturn cameraViewRenderer
scaleType willReturn ScaleType.CenterCrop
}
@@ -64,7 +59,7 @@ internal class StartRoutineTest {
cameraViewRenderer.getPreview() willReturn preview
// When
device.start()
device.start(orientationSensor)
// Then
val inOrder = inOrder(
@@ -77,10 +72,7 @@ internal class StartRoutineTest {
verify(device).selectCamera()
verify(cameraDevice).open()
verify(device).updateCameraConfiguration(cameraDevice)
verify(cameraDevice).setDisplayOrientation(OrientationState(
Portrait,
Landscape
))
verify(cameraDevice).setDisplayOrientation(orientationSensor.lastKnownOrientationState)
verify(cameraViewRenderer).setScaleType(ScaleType.CenterCrop)
verify(cameraViewRenderer).setPreviewResolution(testResolution)
verify(cameraDevice).setDisplaySurface(preview)
@@ -93,7 +85,6 @@ internal class StartRoutineTest {
// Given
device.apply {
getSelectedCamera() willReturn cameraDevice
getScreenOrientation() willReturn Landscape
cameraRenderer willReturn cameraViewRenderer
scaleType willReturn ScaleType.CenterCrop
logger willReturn mockLogger
@@ -103,7 +94,7 @@ internal class StartRoutineTest {
cameraViewRenderer.getPreview() willReturn preview
// When
device.start()
device.start(orientationSensor)
// Then
verify(cameraDevice, never()).startPreview()
@@ -116,8 +107,8 @@ internal class StartRoutineTest {
// When
device.bootStart(
orientationSensor,
{}
orientationSensor = orientationSensor,
mainThreadErrorCallback = {}
)
// Then
@@ -132,8 +123,8 @@ internal class StartRoutineTest {
// When
device.bootStart(
orientationSensor,
{ hasErrors.set(true) }
orientationSensor = orientationSensor,
mainThreadErrorCallback = { hasErrors.set(true) }
)
// Then
@@ -148,7 +139,6 @@ internal class StartRoutineTest {
device.apply {
hasSelectedCamera() willReturn false
getSelectedCamera() willReturn cameraDevice
getScreenOrientation() willReturn Landscape
cameraRenderer willReturn cameraViewRenderer
scaleType willReturn ScaleType.CenterCrop
}
@@ -157,14 +147,14 @@ internal class StartRoutineTest {
// When
device.bootStart(
orientationSensor,
{ hasErrors = true }
orientationSensor = orientationSensor,
mainThreadErrorCallback = { hasErrors = true }
)
// Then
assertFalse(hasErrors)
verify(device).start()
verify(device).start(orientationSensor)
}
}
@@ -4,11 +4,10 @@ import io.fotoapparat.characteristic.LensPosition
import io.fotoapparat.error.CameraErrorCallback
import io.fotoapparat.hardware.CameraDevice
import io.fotoapparat.hardware.Device
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.Landscape
import io.fotoapparat.hardware.orientation.OrientationSensor
import io.fotoapparat.test.testConfiguration
import io.fotoapparat.test.willReturn
import io.fotoapparat.view.CameraRenderer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.BDDMockito.given
@@ -27,14 +26,11 @@ internal class SwitchCameraRoutineTest {
lateinit var device: Device
@Mock
lateinit var cameraRenderer: CameraRenderer
@Mock
lateinit var orientationSensor: OrientationSensor
private val mainThreadErrorCallback: CameraErrorCallback = {}
@Before
fun setUp() {
device.getScreenOrientation() willReturn Landscape
}
@Test
fun `Switch camera, not started`() {
// Given
@@ -48,13 +44,14 @@ internal class SwitchCameraRoutineTest {
device.switchCamera(
newLensPositionSelector = lensPositionSelector,
newConfiguration = testConfiguration,
mainThreadErrorCallback = mainThreadErrorCallback
mainThreadErrorCallback = mainThreadErrorCallback,
orientationSensor = orientationSensor
)
// Then
verify(device).updateLensPositionSelector(lensPositionSelector)
verify(device).updateConfiguration(testConfiguration)
verify(device, never()).restartPreview(oldCameraDevice, mainThreadErrorCallback)
verify(device, never()).restartPreview(oldCameraDevice, orientationSensor, mainThreadErrorCallback)
}
@Test
@@ -69,7 +66,8 @@ internal class SwitchCameraRoutineTest {
device.switchCamera(
newLensPositionSelector = lensPositionSelector,
newConfiguration = testConfiguration,
mainThreadErrorCallback = mainThreadErrorCallback
mainThreadErrorCallback = mainThreadErrorCallback,
orientationSensor = orientationSensor
)
// Then
@@ -80,7 +78,7 @@ internal class SwitchCameraRoutineTest {
inOrder.apply {
verify(device, never()).updateLensPositionSelector(lensPositionSelector)
verify(device, never()).updateConfiguration(testConfiguration)
verify(device, never()).restartPreview(oldCameraDevice, mainThreadErrorCallback)
verify(device, never()).restartPreview(oldCameraDevice, orientationSensor, mainThreadErrorCallback)
}
}
@@ -96,7 +94,8 @@ internal class SwitchCameraRoutineTest {
device.switchCamera(
newLensPositionSelector = lensPositionSelector,
newConfiguration = testConfiguration,
mainThreadErrorCallback = mainThreadErrorCallback
mainThreadErrorCallback = mainThreadErrorCallback,
orientationSensor = orientationSensor
)
// Then
@@ -107,7 +106,7 @@ internal class SwitchCameraRoutineTest {
inOrder.apply {
verify(device).updateLensPositionSelector(lensPositionSelector)
verify(device).updateConfiguration(testConfiguration)
verify(device).restartPreview(oldCameraDevice, mainThreadErrorCallback)
verify(device).restartPreview(oldCameraDevice, orientationSensor, mainThreadErrorCallback)
}
}
@@ -120,6 +119,7 @@ internal class SwitchCameraRoutineTest {
// When
device.restartPreview(
oldCameraDevice = oldCameraDevice,
orientationSensor = orientationSensor,
mainThreadErrorCallback = mainThreadErrorCallback
)
@@ -128,7 +128,7 @@ internal class SwitchCameraRoutineTest {
inOrder.apply {
verify(device).stop(oldCameraDevice)
verify(device).start()
verify(device).start(orientationSensor)
}
}
@@ -141,6 +141,7 @@ internal class SwitchCameraRoutineTest {
// When
device.restartPreview(
oldCameraDevice = oldCameraDevice,
orientationSensor = orientationSensor,
mainThreadErrorCallback = mainThreadErrorCallback
)
@@ -148,7 +149,7 @@ internal class SwitchCameraRoutineTest {
val inOrder = inOrder(device)
inOrder.apply {
verify(device).stop(oldCameraDevice)
verify(device).start()
verify(device).start(orientationSensor)
}
}
@@ -6,7 +6,7 @@ import io.fotoapparat.hardware.Device
import io.fotoapparat.test.testCameraParameters
import io.fotoapparat.test.testFrameProcessor
import io.fotoapparat.test.willReturn
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -59,4 +59,4 @@ internal class UpdateCameraConfigurationRoutineTest {
inOrder.verify(cameraDevice).updateParameters(testCameraParameters)
inOrder.verify(cameraDevice).updateFrameProcessor(testFrameProcessor)
}
}
}
@@ -4,7 +4,7 @@ import io.fotoapparat.hardware.CameraDevice
import io.fotoapparat.hardware.Device
import io.fotoapparat.test.testCapabilities
import io.fotoapparat.test.willReturn
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -35,4 +35,4 @@ internal class GetCapabilitiesRoutineTest {
)
}
}
}
@@ -7,7 +7,7 @@ import io.fotoapparat.hardware.metering.PointF
import io.fotoapparat.parameter.Resolution
import io.fotoapparat.result.FocusResult
import io.fotoapparat.test.willReturn
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -70,4 +70,4 @@ internal class FocusRoutineTest {
actual = focusResult
)
}
}
}
@@ -4,7 +4,7 @@ import io.fotoapparat.hardware.CameraDevice
import io.fotoapparat.hardware.Device
import io.fotoapparat.test.testCameraParameters
import io.fotoapparat.test.willReturn
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -34,4 +34,4 @@ internal class GetParametersRoutineTest {
actual = currentParameters
)
}
}
}
@@ -6,7 +6,7 @@ import io.fotoapparat.hardware.Device
import io.fotoapparat.result.Photo
import io.fotoapparat.test.willReturn
import io.fotoapparat.test.willThrow
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -68,4 +68,4 @@ internal class TakePhotoRoutineTest {
actual = result
)
}
}
}
@@ -3,9 +3,10 @@ package io.fotoapparat.routine.zoom
import io.fotoapparat.exception.LevelOutOfRangeException
import io.fotoapparat.hardware.CameraDevice
import io.fotoapparat.hardware.Device
import io.fotoapparat.parameter.Zoom
import io.fotoapparat.test.testCapabilities
import io.fotoapparat.test.willReturn
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyFloat
@@ -45,7 +46,7 @@ internal class UpdateZoomLevelRoutineTest {
// Given
device.awaitSelectedCamera() willReturn cameraDevice
cameraDevice.getCapabilities() willReturn testCapabilities.copy(
canZoom = true
zoom = Zoom.VariableZoom(3, listOf(100, 150, 200, 250))
)
// When
@@ -60,7 +61,7 @@ internal class UpdateZoomLevelRoutineTest {
// Given
device.awaitSelectedCamera() willReturn cameraDevice
cameraDevice.getCapabilities() willReturn testCapabilities.copy(
canZoom = false
zoom = Zoom.FixedZoom
)
// When
@@ -70,4 +71,4 @@ internal class UpdateZoomLevelRoutineTest {
verify(cameraDevice, never()).setZoom(anyFloat())
}
}
}
@@ -4,7 +4,6 @@ import io.fotoapparat.capability.Capabilities
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.parameter.*
import io.fotoapparat.parameter.camera.CameraParameters
import io.fotoapparat.preview.Frame
import io.fotoapparat.selector.single
import io.fotoapparat.util.FrameProcessor
@@ -28,6 +27,11 @@ val testIso = 100
*/
val jpegQuality = 80
/**
* Test object for camera exposure compensation.
*/
val exposureCompensation = 5
/**
* Test object for [CameraConfiguration].
*/
@@ -45,13 +49,14 @@ internal val testConfiguration = CameraConfiguration(
* Test object for [Capabilities].
*/
val testCapabilities = Capabilities(
canZoom = false,
zoom = Zoom.VariableZoom(3, listOf(100, 150, 200, 250)),
flashModes = setOf(Flash.AutoRedEye),
focusModes = setOf(FocusMode.Fixed),
canSmoothZoom = false,
maxFocusAreas = 100,
maxMeteringAreas = 100,
jpegQualityRange = IntRange(0, 100),
exposureCompensationRange = IntRange(-20, 20),
antiBandingModes = setOf(AntiBandingMode.None),
previewFpsRanges = setOf(testFpsRange),
pictureResolutions = setOf(testResolution),
@@ -66,6 +71,7 @@ val testCameraParameters = CameraParameters(
flashMode = Flash.AutoRedEye,
focusMode = FocusMode.Fixed,
jpegQuality = jpegQuality,
exposureCompensation = exposureCompensation,
antiBandingMode = AntiBandingMode.None,
previewFpsRange = testFpsRange,
sensorSensitivity = testIso,
+2 -1
View File
@@ -2,4 +2,5 @@
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx1536m
org.gradle.configureondemand=true
android.useAndroidX=true
android.enableJetifier=true
Binary file not shown.
+1 -2
View File
@@ -1,6 +1,5 @@
#Mon Dec 04 00:49:03 CET 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
Vendored
+56 -44
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@@ -6,42 +6,6 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -60,6 +24,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -85,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -150,11 +154,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
+4 -10
View File
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
+1 -2
View File
@@ -25,6 +25,5 @@ android {
dependencies {
implementation project(':fotoapparat')
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
implementation "com.android.support:appcompat-v7:${versions.android.support}"
implementation "androidx.appcompat:appcompat:${versions.android.appcompat}"
}
+8 -5
View File
@@ -1,20 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="io.fotoapparat.sample"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.fotoapparat.sample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<!--Set android:name to ".ActivityJava" for java example-->
<activity
android:name=".MainActivity"
android:screenOrientation="fullSensor">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
@@ -1,33 +1,39 @@
package io.fotoapparat.sample;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SwitchCompat;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import io.fotoapparat.Fotoapparat;
import io.fotoapparat.capability.Capabilities;
import io.fotoapparat.configuration.CameraConfiguration;
import io.fotoapparat.configuration.UpdateConfiguration;
import io.fotoapparat.error.CameraErrorListener;
import io.fotoapparat.exception.camera.CameraException;
import io.fotoapparat.parameter.ScaleType;
import io.fotoapparat.parameter.Zoom;
import io.fotoapparat.preview.Frame;
import io.fotoapparat.preview.FrameProcessor;
import io.fotoapparat.result.BitmapPhoto;
import io.fotoapparat.result.PhotoResult;
import io.fotoapparat.result.WhenDoneListener;
import io.fotoapparat.view.CameraView;
import io.fotoapparat.view.FocusView;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import static io.fotoapparat.log.LoggersKt.fileLogger;
import static io.fotoapparat.log.LoggersKt.logcat;
@@ -55,9 +61,16 @@ public class ActivityJava extends AppCompatActivity {
private final PermissionsDelegate permissionsDelegate = new PermissionsDelegate(this);
private boolean hasCameraPermission;
private CameraView cameraView;
private FocusView focusView;
private TextView zoomLvl;
private ImageView switchCamera;
private View capture;
private Fotoapparat fotoapparat;
private Zoom.VariableZoom cameraZoom;
private float curZoom = 0f;
boolean activeCameraBack = true;
private CameraConfiguration cameraConfiguration = CameraConfiguration
.builder()
@@ -86,6 +99,10 @@ public class ActivityJava extends AppCompatActivity {
setContentView(R.layout.activity_main);
cameraView = findViewById(R.id.cameraView);
focusView = findViewById(R.id.focusView);
capture = findViewById(R.id.capture);
zoomLvl = findViewById(R.id.zoomLvl);
switchCamera = findViewById(R.id.switchCamera);
hasCameraPermission = permissionsDelegate.hasCameraPermission();
if (hasCameraPermission) {
@@ -97,16 +114,15 @@ public class ActivityJava extends AppCompatActivity {
fotoapparat = createFotoapparat();
takePictureOnClick();
focusOnLongClick();
switchCameraOnClick();
toggleTorchOnSwitch();
zoomSeekBar();
}
private Fotoapparat createFotoapparat() {
return Fotoapparat
.with(this)
.into(cameraView)
.focusView(focusView)
.previewScaleType(ScaleType.CenterCrop)
.lensPosition(back())
.frameProcessor(new SampleFrameProcessor())
@@ -123,15 +139,66 @@ public class ActivityJava extends AppCompatActivity {
.build();
}
private void zoomSeekBar() {
SeekBar seekBar = findViewById(R.id.zoomSeekBar);
seekBar.setOnSeekBarChangeListener(new OnProgressChanged() {
private void adjustViewsVisibility() {
fotoapparat.getCapabilities().whenAvailable(new Function1<Capabilities, Unit>() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
fotoapparat.setZoom(progress / (float) seekBar.getMax());
public Unit invoke(Capabilities capabilities) {
Zoom zoom = capabilities != null ? capabilities.getZoom() : null;
if(zoom instanceof Zoom.VariableZoom){
cameraZoom = (Zoom.VariableZoom) zoom;
focusView.setScaleListener(new Function1<Float, Unit>() {
@Override
public Unit invoke(Float aFloat) {
scaleZoom(aFloat);
return null;
}
});
focusView.setPtrListener(new Function1<Integer, Unit>() {
@Override
public Unit invoke(Integer integer) {
pointerChanged(integer);
return null;
}
});
} else {
zoomLvl.setVisibility(View.GONE);
focusView.setScaleListener(null);
focusView.setPtrListener(null);
}
return null;
}
});
if (fotoapparat.isAvailable(front())){
switchCamera.setVisibility(View.VISIBLE);
} else {
switchCamera.setVisibility(View.GONE);
}
}
private void scaleZoom(float scaleFactor){
float plusZoom = 0;
if (scaleFactor < 1) plusZoom = -1 * (1 - scaleFactor);
else plusZoom = scaleFactor - 1;
float newZoom = curZoom + plusZoom;
if (newZoom < 0 || newZoom > 1) return;
curZoom = newZoom;
fotoapparat.setZoom(curZoom);
int progress = Math.round (cameraZoom.getMaxZoom() * curZoom);
int value = cameraZoom.getZoomRatios().get(progress);
float roundedValue = (float)(Math.round(((float)value) / 10f)) / 10f;
zoomLvl.setVisibility(View.VISIBLE);
zoomLvl.setText(String.format(Locale.getDefault(), "%.1f×", roundedValue));
}
private void pointerChanged(int fingerCount){
if(fingerCount == 0) {
zoomLvl.setVisibility(View.GONE);
}
}
private void switchCameraOnClick() {
@@ -169,27 +236,18 @@ public class ActivityJava extends AppCompatActivity {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activeCameraBack = !activeCameraBack;
fotoapparat.switchTo(
front(),
activeCameraBack ? back() : front(),
cameraConfiguration
);
}
});
}
private void focusOnLongClick() {
cameraView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
fotoapparat.autoFocus();
return true;
adjustViewsVisibility();
}
});
}
private void takePictureOnClick() {
cameraView.setOnClickListener(new View.OnClickListener() {
capture.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
takePicture();
@@ -227,6 +285,7 @@ public class ActivityJava extends AppCompatActivity {
super.onStart();
if (hasCameraPermission) {
fotoapparat.start();
adjustViewsVisibility();
}
}
@@ -246,6 +305,7 @@ public class ActivityJava extends AppCompatActivity {
if (permissionsDelegate.resultGranted(requestCode, permissions, grantResults)) {
hasCameraPermission = true;
fotoapparat.start();
adjustViewsVisibility();
cameraView.setVisibility(View.VISIBLE);
}
}
@@ -1,22 +1,22 @@
package io.fotoapparat.sample
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import io.fotoapparat.Fotoapparat
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.configuration.UpdateConfiguration
import io.fotoapparat.log.logcat
import io.fotoapparat.parameter.Flash
import io.fotoapparat.parameter.Zoom
import io.fotoapparat.result.transformer.scaled
import io.fotoapparat.selector.*
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() {
@@ -26,7 +26,9 @@ class MainActivity : AppCompatActivity() {
private var activeCamera: Camera = Camera.Back
private lateinit var fotoapparat: Fotoapparat
private lateinit var cameraZoom: Zoom.VariableZoom
private var curZoom: Float = 0f
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -51,15 +53,10 @@ class MainActivity : AppCompatActivity() {
)
capture onClick takePicture()
zoomSeekBar onProgressChanged updateZoom()
switchCamera onClick changeCamera()
torchSwitch onCheckedChanged toggleFlash()
}
private fun updateZoom(): (SeekBar, Int) -> Unit = { seekBar: SeekBar, progress: Int ->
fotoapparat.setZoom(progress / seekBar.max.toFloat())
}
private fun takePicture(): () -> Unit = {
val photoResult = fotoapparat
.autoFocus()
@@ -100,7 +97,6 @@ class MainActivity : AppCompatActivity() {
adjustViewsVisibility()
zoomSeekBar.progress = 0
torchSwitch.isChecked = false
Log.i(LOGGING_TAG, "New camera position: ${if (activeCamera is Camera.Back) "back" else "front"}")
@@ -153,7 +149,18 @@ class MainActivity : AppCompatActivity() {
.whenAvailable { capabilities ->
capabilities
?.let {
zoomSeekBar.visibility = if (it.canZoom) View.VISIBLE else View.GONE
(it.zoom as? Zoom.VariableZoom)
?.let {
cameraZoom = it
focusView.scaleListener = this::scaleZoom
focusView.ptrListener = this::pointerChanged
}
?: run {
zoomLvl?.visibility = View.GONE
focusView.scaleListener = null
focusView.ptrListener = null
}
torchSwitch.visibility = if (it.flashModes.contains(Flash.Torch)) View.VISIBLE else View.GONE
}
?: Log.e(LOGGING_TAG, "Couldn't obtain capabilities.")
@@ -162,6 +169,28 @@ class MainActivity : AppCompatActivity() {
switchCamera.visibility = if (fotoapparat.isAvailable(front())) View.VISIBLE else View.GONE
}
//When zooming slowly, the values are approximately 0.9 ~ 1.1
private fun scaleZoom(scaleFactor: Float) {
//convert to -0.1 ~ 0.1
val plusZoom = if (scaleFactor < 1) -1 * (1 - scaleFactor) else scaleFactor - 1
val newZoom = curZoom + plusZoom
if (newZoom < 0 || newZoom > 1) return
curZoom = newZoom
fotoapparat.setZoom(curZoom)
val progress = (cameraZoom.maxZoom * curZoom).roundToInt()
val value = cameraZoom.zoomRatios[progress]
val roundedValue = ((value.toFloat()) / 10).roundToInt().toFloat() / 10
zoomLvl.visibility = View.VISIBLE
zoomLvl.text = String.format("%.1f×", roundedValue)
}
private fun pointerChanged(fingerCount: Int){
if(fingerCount == 0) {
zoomLvl?.visibility = View.GONE
}
}
}
private const val LOGGING_TAG = "Fotoapparat Example"
@@ -3,10 +3,11 @@ package io.fotoapparat.sample;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.View;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
class PermissionsDelegate {
private static final int REQUEST_CODE = 10;
@@ -1,10 +1,9 @@
package io.fotoapparat.sample
import android.support.v7.widget.SwitchCompat
import android.view.View
import android.widget.CompoundButton
import android.widget.SeekBar
import androidx.appcompat.widget.SwitchCompat
internal infix fun SwitchCompat.onCheckedChanged(function: (CompoundButton, Boolean) -> Unit) {
setOnCheckedChangeListener(function)
@@ -14,10 +13,10 @@ internal infix fun View.onClick(function: () -> Unit) {
setOnClickListener { function() }
}
internal infix fun SeekBar.onProgressChanged(updateZoom: (SeekBar, Int) -> Unit) {
internal infix fun SeekBar.onProgressChanged(zoomUpdated: () -> Unit) {
setOnSeekBarChangeListener(object : OnProgressChanged() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
updateZoom(seekBar, progress)
zoomUpdated()
}
})
}
}
+12 -11
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.FitWindowsFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.appcompat.widget.FitWindowsFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -35,7 +35,6 @@
tools:ignore="HardcodedText"
tools:visibility="visible" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -46,7 +45,7 @@
android:layout_height="wrap_content"
android:layout_gravity="top">
<android.support.v7.widget.SwitchCompat
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/torchSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -54,13 +53,6 @@
android:padding="20dp"
tools:ignore="RtlHardcoded" />
<SeekBar
android:id="@+id/zoomSeekBar"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:max="30" />
<ImageView
android:id="@+id/switchCamera"
android:layout_width="wrap_content"
@@ -72,6 +64,15 @@
</FrameLayout>
<TextView
android:id="@+id/zoomLvl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#FFF"
android:textSize="20sp"
tools:text="2.4" />
<ImageView
android:id="@+id/result"
android:layout_width="150dp"
@@ -89,4 +90,4 @@
tools:ignore="ContentDescription" />
</FrameLayout>
</android.support.v7.widget.FitWindowsFrameLayout>
</androidx.appcompat.widget.FitWindowsFrameLayout>