Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9454f3e4d1 | |||
| 7094f9850a | |||
| 7039cf2059 | |||
| 09a551635c | |||
| 72c7c81a42 | |||
| a7287e363c | |||
| 6156f4c897 | |||
| 29be38b88b | |||
| 69d99e1aea | |||
| af9e8158b8 | |||
| 4494d76bb1 | |||
| f1830853c3 | |||
| 75181ecb92 | |||
| 697f996d17 | |||
| 1783058ade | |||
| f78c324f23 | |||
| b1f6f491cd | |||
| 46fe442a4e | |||
| 27c9a7a910 | |||
| f05fc12039 | |||
| b37c3df8c9 | |||
| 353e8a326b | |||
| 9021507995 | |||
| 4a23a70565 | |||
| 9104a316ee | |||
| 33ce7fa483 | |||
| 45368b3691 | |||
| 65023340e5 | |||
| ede7e4cbbe | |||
| 3bbec9b6d8 | |||
| ec6cdf3d66 | |||
| cb4c8d1ee1 | |||
| 245d74fb46 | |||
| b67ee40be2 | |||
| 66c7489708 | |||
| 96fc39bd84 | |||
| c82b628b44 | |||
| 7ee03f1a8c | |||
| 816d4da6aa | |||
| 4fc20316d0 | |||
| 3b17c030f5 | |||
| 0c17e2bf29 | |||
| 8189847088 | |||
| 36670caf02 | |||
| 36d5a038ce | |||
| 7c5878259d | |||
| 4b6c0447d3 | |||
| 7a04a38872 | |||
| 18d6ae38ad | |||
| 767aae82fd | |||
| 61742958a0 | |||
| bc8c122323 | |||
| 982c7b8548 | |||
| 5abcbf7e54 | |||
| 4f78de91e0 | |||
| 1e247aab22 | |||
| 919e4a0262 | |||
| 0d4c51f6be | |||
| ea4e1bac6e | |||
| 9621cc986b | |||
| 5e1ed00cfe | |||
| 9a4fc6bb4e | |||
| 67b07dc1e8 | |||
| 962866f405 | |||
| 3f25da7449 | |||
| a6bb1a5c63 | |||
| 8db7e7f8b3 | |||
| c2a97f18f5 | |||
| 799395ae38 | |||
| a17e601428 | |||
| d89adb8134 | |||
| 9a8bbb1021 | |||
| b55ebc6c68 | |||
| 631d216b13 | |||
| a31a856b55 | |||
| ff265c5c73 | |||
| 78f5ad88c2 | |||
| 35e30c12fe | |||
| d0fec5f5b9 | |||
| 7233866fcc | |||
| c7e3c01b2f | |||
| 3de625f2cd | |||
| 72dcec0470 | |||
| 8104644cfb | |||
| 58ffc4f9ea | |||
| 31d48c6154 | |||
| db791d9259 | |||
| 82752f13c6 | |||
| 8214559d98 | |||
| 32e479cc7b | |||
| 262aa4ed9b | |||
| 762fae3d22 | |||
| d8c5cb6ada | |||
| 5125541274 | |||
| da4ca3a699 | |||
| 98dab42245 | |||
| e8dfac0fd6 | |||
| 4e2c91a7e9 | |||
| 3aba7f8803 | |||
| 4cf6d16baa | |||
| cf22a9a1f8 | |||
| 6781e349f9 | |||
| 08446ff533 | |||
| 9a45b8f400 | |||
| e8092902d2 | |||
| d679ff7454 | |||
| 4583b8d992 | |||
| 3da2615acd | |||
| 12c3c3a852 | |||
| 0458642f75 | |||
| cd6cebdb6c | |||
| 2513e42830 | |||
| 4ed72421aa | |||
| dddd81600a |
@@ -6,4 +6,6 @@
|
||||
build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.project
|
||||
.settings
|
||||
|
||||
|
||||
+13
-7
@@ -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
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
# 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.
|
||||
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.0.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.
|
||||
|
||||
+20
-17
@@ -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: 16,
|
||||
target : 27
|
||||
minimum: 14,
|
||||
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.1.0-alpha06"
|
||||
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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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
@@ -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,22 +1,19 @@
|
||||
package io.fotoapparat
|
||||
|
||||
import android.content.Context
|
||||
import android.support.annotation.FloatRange
|
||||
import io.fotoapparat.capability.Capabilities
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import androidx.annotation.FloatRange
|
||||
import io.fotoapparat.concurrent.CameraExecutor
|
||||
import io.fotoapparat.concurrent.CameraExecutor.Operation
|
||||
import io.fotoapparat.configuration.CameraConfiguration
|
||||
import io.fotoapparat.configuration.Configuration
|
||||
import io.fotoapparat.error.CameraErrorCallback
|
||||
import io.fotoapparat.error.onMainThread
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
import io.fotoapparat.hardware.Device
|
||||
import io.fotoapparat.hardware.display.Display
|
||||
import io.fotoapparat.hardware.execute
|
||||
import io.fotoapparat.hardware.executeTask
|
||||
import io.fotoapparat.hardware.orientation.OrientationSensor
|
||||
import io.fotoapparat.log.Logger
|
||||
import io.fotoapparat.log.none
|
||||
import io.fotoapparat.parameter.ScaleType
|
||||
import io.fotoapparat.parameter.camera.CameraParameters
|
||||
import io.fotoapparat.result.*
|
||||
import io.fotoapparat.routine.camera.bootStart
|
||||
import io.fotoapparat.routine.camera.shutDown
|
||||
@@ -27,12 +24,9 @@ import io.fotoapparat.routine.focus.focus
|
||||
import io.fotoapparat.routine.parameter.getCurrentParameters
|
||||
import io.fotoapparat.routine.photo.takePhoto
|
||||
import io.fotoapparat.routine.zoom.updateZoomLevel
|
||||
import io.fotoapparat.selector.back
|
||||
import io.fotoapparat.selector.external
|
||||
import io.fotoapparat.selector.firstAvailable
|
||||
import io.fotoapparat.selector.front
|
||||
import io.fotoapparat.selector.*
|
||||
import io.fotoapparat.view.CameraRenderer
|
||||
import java.util.concurrent.FutureTask
|
||||
import io.fotoapparat.view.FocalPointSelector
|
||||
|
||||
/**
|
||||
* Camera. Takes pictures.
|
||||
@@ -41,14 +35,16 @@ class Fotoapparat
|
||||
@JvmOverloads constructor(
|
||||
context: Context,
|
||||
view: CameraRenderer,
|
||||
lensPosition: Collection<LensPosition>.() -> LensPosition? = firstAvailable(
|
||||
focusView: FocalPointSelector? = null,
|
||||
lensPosition: LensPositionSelector = firstAvailable(
|
||||
back(),
|
||||
front(),
|
||||
external()
|
||||
),
|
||||
scaleType: ScaleType = ScaleType.CenterCrop,
|
||||
cameraConfiguration: CameraConfiguration = CameraConfiguration.default(),
|
||||
cameraErrorCallback: ((CameraException) -> Unit) = {},
|
||||
cameraErrorCallback: CameraErrorCallback = {},
|
||||
private val executor: CameraExecutor = EXECUTOR,
|
||||
private val logger: Logger = none()
|
||||
) {
|
||||
|
||||
@@ -58,17 +54,21 @@ class Fotoapparat
|
||||
|
||||
private val device = Device(
|
||||
cameraRenderer = view,
|
||||
focusPointSelector = focusView,
|
||||
logger = logger,
|
||||
display = display,
|
||||
scaleType = scaleType,
|
||||
initialLensPositionSelector = lensPosition,
|
||||
initialConfiguration = cameraConfiguration
|
||||
initialConfiguration = cameraConfiguration,
|
||||
executor = executor
|
||||
)
|
||||
|
||||
private val orientationSensor = OrientationSensor(
|
||||
context = context,
|
||||
device = device
|
||||
)
|
||||
private val orientationSensor by lazy {
|
||||
OrientationSensor(
|
||||
context = context,
|
||||
device = device
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
logger.recordMethod()
|
||||
@@ -82,12 +82,12 @@ class Fotoapparat
|
||||
fun start() {
|
||||
logger.recordMethod()
|
||||
|
||||
execute {
|
||||
executor.execute(Operation {
|
||||
device.bootStart(
|
||||
orientationSensor = orientationSensor,
|
||||
mainThreadErrorCallback = mainThreadErrorCallback
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,11 +98,12 @@ class Fotoapparat
|
||||
fun stop() {
|
||||
logger.recordMethod()
|
||||
|
||||
execute {
|
||||
executor.cancelTasks()
|
||||
executor.execute(Operation {
|
||||
device.shutDown(
|
||||
orientationSensor = orientationSensor
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,13 +114,12 @@ class Fotoapparat
|
||||
fun takePicture(): PhotoResult {
|
||||
logger.recordMethod()
|
||||
|
||||
val takePictureTask = FutureTask<Photo> {
|
||||
device.takePhoto()
|
||||
}
|
||||
val future = executor.execute(Operation(
|
||||
cancellable = true,
|
||||
function = device::takePhoto
|
||||
))
|
||||
|
||||
executeTask(takePictureTask)
|
||||
|
||||
return PhotoResult.fromFuture(takePictureTask, logger)
|
||||
return PhotoResult.fromFuture(future, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,13 +130,12 @@ class Fotoapparat
|
||||
fun getCapabilities(): CapabilitiesResult {
|
||||
logger.recordMethod()
|
||||
|
||||
val getCapabilitiesTask = FutureTask<Capabilities> {
|
||||
device.getCapabilities()
|
||||
}
|
||||
val future = executor.execute(Operation(
|
||||
cancellable = true,
|
||||
function = device::getCapabilities
|
||||
))
|
||||
|
||||
executeTask(getCapabilitiesTask)
|
||||
|
||||
return PendingResult.fromFuture(getCapabilitiesTask, logger)
|
||||
return PendingResult.fromFuture(future, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,13 +146,12 @@ class Fotoapparat
|
||||
fun getCurrentParameters(): ParametersResult {
|
||||
logger.recordMethod()
|
||||
|
||||
val getCameraParametersTask = FutureTask<CameraParameters> {
|
||||
device.getCurrentParameters()
|
||||
}
|
||||
val future = executor.execute(Operation(
|
||||
cancellable = true,
|
||||
function = device::getCurrentParameters
|
||||
))
|
||||
|
||||
executeTask(getCameraParametersTask)
|
||||
|
||||
return PendingResult.fromFuture(getCameraParametersTask, logger)
|
||||
return PendingResult.fromFuture(future, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,11 +159,12 @@ class Fotoapparat
|
||||
*
|
||||
* @throws IllegalStateException If the current camera has not started.
|
||||
*/
|
||||
fun updateConfiguration(newConfiguration: Configuration) = execute {
|
||||
logger.recordMethod()
|
||||
fun updateConfiguration(newConfiguration: Configuration) = executor.execute(
|
||||
Operation(cancellable = true) {
|
||||
logger.recordMethod()
|
||||
|
||||
device.updateDeviceConfiguration(newConfiguration)
|
||||
}
|
||||
device.updateDeviceConfiguration(newConfiguration)
|
||||
})
|
||||
|
||||
/**
|
||||
* Asynchronously updates zoom level of the camera.
|
||||
@@ -174,24 +173,23 @@ class Fotoapparat
|
||||
* @param zoomLevel Zoom level of the camera. A value between 0 and 1.
|
||||
* @throws IllegalStateException If the current camera has not started.
|
||||
*/
|
||||
fun setZoom(@FloatRange(from = 0.0, to = 1.0) zoomLevel: Float) = execute {
|
||||
logger.recordMethod()
|
||||
fun setZoom(@FloatRange(from = 0.0, to = 1.0) zoomLevel: Float) = executor.execute(
|
||||
Operation(cancellable = true) {
|
||||
logger.recordMethod()
|
||||
|
||||
device.updateZoomLevel(
|
||||
zoomLevel = zoomLevel
|
||||
)
|
||||
}
|
||||
device.updateZoomLevel(
|
||||
zoomLevel = zoomLevel
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Performs auto focus. If it is not available or not enabled, does nothing.
|
||||
*
|
||||
* @see Fotoapparat.focus
|
||||
*/
|
||||
fun autoFocus(): Fotoapparat {
|
||||
fun autoFocus(): Fotoapparat = apply {
|
||||
logger.recordMethod()
|
||||
focus()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,12 +202,12 @@ class Fotoapparat
|
||||
fun focus(): PendingResult<FocusResult> {
|
||||
logger.recordMethod()
|
||||
|
||||
val focusTask = FutureTask<FocusResult> {
|
||||
device.focus()
|
||||
}
|
||||
executeTask(focusTask)
|
||||
val future = executor.execute(Operation(
|
||||
cancellable = true,
|
||||
function = device::focus
|
||||
))
|
||||
|
||||
return PendingResult.fromFuture(focusTask, logger)
|
||||
return PendingResult.fromFuture(future, logger)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,31 +215,34 @@ class Fotoapparat
|
||||
* stopped automatically and new will start.
|
||||
*/
|
||||
fun switchTo(
|
||||
lensPosition: Collection<LensPosition>.() -> LensPosition?,
|
||||
lensPosition: LensPositionSelector,
|
||||
cameraConfiguration: CameraConfiguration
|
||||
) {
|
||||
logger.recordMethod()
|
||||
execute {
|
||||
|
||||
executor.execute(Operation(cancellable = true) {
|
||||
device.switchCamera(
|
||||
orientationSensor = orientationSensor,
|
||||
newLensPositionSelector = lensPosition,
|
||||
newConfiguration = cameraConfiguration,
|
||||
mainThreadErrorCallback = mainThreadErrorCallback
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if selected lens position is available. `false` if it is not available.
|
||||
*/
|
||||
fun isAvailable(
|
||||
selector: (Collection<LensPosition>) -> LensPosition?
|
||||
): Boolean {
|
||||
return device.canSelectCamera(selector)
|
||||
}
|
||||
selector: LensPositionSelector
|
||||
): Boolean = device.canSelectCamera(selector)
|
||||
|
||||
companion object {
|
||||
|
||||
private val EXECUTOR = CameraExecutor()
|
||||
|
||||
@JvmStatic
|
||||
fun with(context: Context) = FotoapparatBuilder(context)
|
||||
fun with(context: Context): FotoapparatBuilder = FotoapparatBuilder(context)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
package io.fotoapparat
|
||||
|
||||
import android.content.Context
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import io.fotoapparat.configuration.CameraConfiguration
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
import io.fotoapparat.error.CameraErrorCallback
|
||||
import io.fotoapparat.error.CameraErrorListener
|
||||
import io.fotoapparat.log.Logger
|
||||
import io.fotoapparat.log.none
|
||||
import io.fotoapparat.parameter.*
|
||||
import io.fotoapparat.parameter.ScaleType
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.selector.back
|
||||
import io.fotoapparat.selector.external
|
||||
import io.fotoapparat.selector.firstAvailable
|
||||
import io.fotoapparat.selector.front
|
||||
import io.fotoapparat.selector.*
|
||||
import io.fotoapparat.util.FrameProcessor
|
||||
import io.fotoapparat.view.CameraRenderer
|
||||
import io.fotoapparat.view.CameraView
|
||||
import io.fotoapparat.view.FocusView
|
||||
import io.fotoapparat.preview.FrameProcessor as FrameProcessorJava
|
||||
|
||||
/**
|
||||
* Builder for [Fotoapparat].
|
||||
*/
|
||||
class FotoapparatBuilder internal constructor(private var context: Context) {
|
||||
|
||||
internal var lensPositionSelector: Iterable<LensPosition>.() -> LensPosition? = firstAvailable(
|
||||
internal var lensPositionSelector: LensPositionSelector = firstAvailable(
|
||||
back(),
|
||||
front(),
|
||||
external()
|
||||
)
|
||||
internal var cameraErrorCallback: (CameraException) -> Unit = {}
|
||||
internal var frameProcessor: ((Frame) -> Unit?)? = null
|
||||
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()
|
||||
|
||||
@@ -36,125 +36,142 @@ class FotoapparatBuilder internal constructor(private var context: Context) {
|
||||
/**
|
||||
* @param selector camera sensor position from list of available positions.
|
||||
*/
|
||||
fun lensPosition(selector: Iterable<LensPosition>.() -> LensPosition?): FotoapparatBuilder {
|
||||
lensPositionSelector = selector
|
||||
return this
|
||||
}
|
||||
fun lensPosition(selector: LensPositionSelector): FotoapparatBuilder =
|
||||
apply { lensPositionSelector = selector }
|
||||
|
||||
/**
|
||||
* @param scaleType of preview inside the view.
|
||||
*/
|
||||
fun previewScaleType(scaleType: ScaleType): FotoapparatBuilder {
|
||||
this.scaleType = scaleType
|
||||
return this
|
||||
}
|
||||
fun previewScaleType(scaleType: ScaleType): FotoapparatBuilder =
|
||||
apply { this.scaleType = scaleType }
|
||||
|
||||
/**
|
||||
* @param selector selects resolution of the photo (in pixels) from list of available resolutions.
|
||||
*/
|
||||
fun photoResolution(selector: Iterable<Resolution>.() -> Resolution?): FotoapparatBuilder {
|
||||
fun photoResolution(selector: ResolutionSelector): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
pictureResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects size of preview stream (in pixels) from list of available resolutions.
|
||||
*/
|
||||
fun previewResolution(selector: Iterable<Resolution>.() -> Resolution?): FotoapparatBuilder {
|
||||
fun previewResolution(selector: ResolutionSelector): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
previewResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param selector selects focus mode from list of available modes.
|
||||
*/
|
||||
fun focusMode(selector: Iterable<FocusMode>.() -> FocusMode?): FotoapparatBuilder {
|
||||
fun focusMode(selector: FocusModeSelector): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
focusMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects flash mode from list of available modes.
|
||||
*/
|
||||
fun flash(selector: Iterable<Flash>.() -> Flash?): FotoapparatBuilder {
|
||||
fun flash(selector: FlashSelector): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
flashMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects preview FPS range from list of available ranges.
|
||||
*/
|
||||
fun previewFpsRange(selector: Iterable<FpsRange>.() -> FpsRange?): FotoapparatBuilder {
|
||||
fun previewFpsRange(selector: FpsRangeSelector): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
previewFpsRange = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector selects ISO value from range of available values.
|
||||
*/
|
||||
fun sensorSensitivity(selector: Iterable<Int>.() -> Int?): FotoapparatBuilder {
|
||||
fun sensorSensitivity(selector: SensorSensitivitySelector): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
sensorSensitivity = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector of the Jpeg picture quality.
|
||||
*/
|
||||
fun jpegQuality(selector: IntRange.() -> Int?): FotoapparatBuilder {
|
||||
fun jpegQuality(selector: QualitySelector): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
jpegQuality = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 FrameProcessor
|
||||
* @see FrameProcessorJava
|
||||
*/
|
||||
fun frameProcessor(frameProcessor: (Frame) -> Unit): FotoapparatBuilder {
|
||||
this.frameProcessor = frameProcessor
|
||||
return this
|
||||
fun frameProcessor(frameProcessor: FrameProcessor): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
frameProcessor = frameProcessor
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param frameProcessor receives preview frames for processing.
|
||||
* @see FrameProcessorJava
|
||||
*/
|
||||
fun frameProcessor(frameProcessor: FrameProcessorJava?): FotoapparatBuilder = apply {
|
||||
configuration = configuration.copy(
|
||||
frameProcessor = frameProcessor?.let { it::process }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param logger logger which will print logs. No logger is set by default.
|
||||
* @see io.fotoapparat.log.Loggers
|
||||
*/
|
||||
fun logger(logger: Logger): FotoapparatBuilder {
|
||||
this.logger = logger
|
||||
return this
|
||||
}
|
||||
fun logger(logger: Logger): FotoapparatBuilder =
|
||||
apply { this.logger = logger }
|
||||
|
||||
/**
|
||||
* @param callback which will be notified when camera error happens in Fotoapparat.
|
||||
* @see CameraErrorCallback
|
||||
* @see CameraErrorListener
|
||||
*/
|
||||
fun cameraErrorCallback(callback: (CameraException) -> Unit): FotoapparatBuilder {
|
||||
cameraErrorCallback = callback
|
||||
return this
|
||||
}
|
||||
fun cameraErrorCallback(callback: CameraErrorListener): FotoapparatBuilder =
|
||||
apply { cameraErrorCallback = { callback.onError(it) } }
|
||||
|
||||
/**
|
||||
* @param callback which will be notified when camera error happens in Fotoapparat.
|
||||
* @see CameraErrorListener
|
||||
*/
|
||||
fun cameraErrorCallback(callback: CameraErrorCallback): FotoapparatBuilder =
|
||||
apply { cameraErrorCallback = callback }
|
||||
|
||||
/**
|
||||
* @param renderer view which will draw the stream from the camera.
|
||||
* @see CameraView
|
||||
*/
|
||||
fun into(renderer: CameraRenderer): FotoapparatBuilder {
|
||||
this.renderer = renderer
|
||||
return this
|
||||
}
|
||||
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].
|
||||
@@ -174,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,
|
||||
@@ -182,4 +200,4 @@ class FotoapparatBuilder internal constructor(private var context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,11 +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>,
|
||||
@@ -33,11 +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,12 +14,15 @@ 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,
|
||||
antiBandingModes = supportedAutoBandingModes.extract { it.toAntiBandingMode() },
|
||||
exposureCompensationRange = exposureCompensationRange,
|
||||
antiBandingModes = supportedAutoBandingModes.extract(String::toAntiBandingMode),
|
||||
sensorSensitivities = sensorSensitivities.toSet(),
|
||||
previewFpsRanges = supportedPreviewFpsRanges.extract { it.toFpsRange() },
|
||||
pictureResolutions = pictureResolutions.mapSizes(),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package io.fotoapparat.characteristic
|
||||
|
||||
import android.hardware.Camera
|
||||
import io.fotoapparat.hardware.orientation.toOrientation
|
||||
|
||||
/**
|
||||
* Returns the [Characteristics] for the given `cameraId`.
|
||||
@@ -10,9 +11,11 @@ import android.hardware.Camera
|
||||
internal fun getCharacteristics(cameraId: Int): Characteristics {
|
||||
val info = Camera.CameraInfo()
|
||||
Camera.getCameraInfo(cameraId, info)
|
||||
val lensPosition = info.facing.toLensPosition()
|
||||
return Characteristics(
|
||||
cameraId,
|
||||
info.facing.toLensPosition(),
|
||||
info.orientation
|
||||
cameraId = cameraId,
|
||||
lensPosition = lensPosition,
|
||||
cameraOrientation = info.orientation.toOrientation(),
|
||||
isMirrored = lensPosition == LensPosition.Front
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package io.fotoapparat.characteristic
|
||||
|
||||
import io.fotoapparat.hardware.orientation.Orientation
|
||||
|
||||
/**
|
||||
* A set of information about the camera.
|
||||
*/
|
||||
internal data class Characteristics(
|
||||
val cameraId: Int,
|
||||
val lensPosition: LensPosition,
|
||||
val orientation: Int
|
||||
val cameraOrientation: Orientation,
|
||||
val isMirrored: Boolean
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
+18
-14
@@ -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.
|
||||
*
|
||||
@@ -12,13 +17,13 @@ import io.fotoapparat.exception.camera.CameraException
|
||||
* @return [LensPosition] from the given lens position code id.
|
||||
* `null` if position code id is not supported.
|
||||
*/
|
||||
internal fun Int.toLensPosition(): LensPosition {
|
||||
return when (this) {
|
||||
Camera.CameraInfo.CAMERA_FACING_FRONT -> LensPosition.Front
|
||||
Camera.CameraInfo.CAMERA_FACING_BACK -> LensPosition.Back
|
||||
else -> throw IllegalArgumentException("Lens position $this is not supported.")
|
||||
}
|
||||
}
|
||||
internal fun Int.toLensPosition(): LensPosition =
|
||||
when (this) {
|
||||
Camera.CameraInfo.CAMERA_FACING_FRONT -> LensPosition.Front
|
||||
Camera.CameraInfo.CAMERA_FACING_BACK -> LensPosition.Back
|
||||
CAMERA_FACING_EXTERNAL -> LensPosition.External
|
||||
else -> throw IllegalArgumentException("Lens position $this is not supported.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps between [LensPosition] and Camera v1 code id.
|
||||
@@ -26,10 +31,9 @@ internal fun Int.toLensPosition(): LensPosition {
|
||||
* @receiver [LensPosition]
|
||||
* @return code of the camera as in [Camera.CameraInfo].
|
||||
*/
|
||||
fun LensPosition.toCameraId(): Int {
|
||||
return (0 until Camera.getNumberOfCameras())
|
||||
.find {
|
||||
this == getCharacteristics(it).lensPosition
|
||||
}
|
||||
?: throw CameraException("Device has no camera for the desired lens position(s).")
|
||||
}
|
||||
fun LensPosition.toCameraId(): Int =
|
||||
(0 until Camera.getNumberOfCameras())
|
||||
.find { cameraId ->
|
||||
this == getCharacteristics(cameraId).lensPosition
|
||||
}
|
||||
?: throw CameraException("Device has no camera for the desired lens position(s).")
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package io.fotoapparat.concurrent
|
||||
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
|
||||
/**
|
||||
* Executes camera related operations using a dedicated thread and provides a control over the queue
|
||||
* of those operations.
|
||||
*
|
||||
* This class should be accessed only from a one thread at a time.
|
||||
*/
|
||||
class CameraExecutor(
|
||||
private val executor: ExecutorService = Executors.newSingleThreadExecutor()
|
||||
) {
|
||||
|
||||
private val cancellableTasksQueue = LinkedList<Future<*>>()
|
||||
|
||||
/**
|
||||
* Adds operation to the serial execution queue.
|
||||
*/
|
||||
fun <T> execute(operation: Operation<T>): Future<T> {
|
||||
val future = executor.submit(Callable {
|
||||
operation.function()
|
||||
})
|
||||
|
||||
if (operation.cancellable) {
|
||||
cancellableTasksQueue += future
|
||||
}
|
||||
|
||||
cleanUpCancelledTasks()
|
||||
|
||||
return future
|
||||
}
|
||||
|
||||
private fun cleanUpCancelledTasks() {
|
||||
cancellableTasksQueue.removeAll {
|
||||
!it.isPending
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all cancellable tasks in the queue. Non-cancellable tasks would still be executed.
|
||||
*
|
||||
* After this operation is completed the executor is still in a valid state and can be used
|
||||
* further.
|
||||
*/
|
||||
fun cancelTasks() {
|
||||
cancellableTasksQueue
|
||||
.filter { it.isPending }
|
||||
.forEach {
|
||||
it.cancel(true)
|
||||
}
|
||||
cancellableTasksQueue.clear()
|
||||
}
|
||||
|
||||
private val Future<*>.isPending get() = !isCancelled && !isDone
|
||||
|
||||
/**
|
||||
* Operation to be executed.
|
||||
*/
|
||||
data class Operation<out T>(
|
||||
|
||||
/**
|
||||
* `true` if operation could be cancelled. `false` if operation can not be cancelled.
|
||||
*/
|
||||
val cancellable: Boolean = false,
|
||||
|
||||
/**
|
||||
* Body of the operation.
|
||||
*/
|
||||
val function: () -> T
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.fotoapparat.concurrent
|
||||
|
||||
import android.os.Looper
|
||||
|
||||
/**
|
||||
* Throws [IllegalThreadStateException] if called from main thread.
|
||||
*/
|
||||
fun ensureBackgroundThread() {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
throw IllegalThreadStateException("Operation should not run from main thread.")
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,36 @@
|
||||
package io.fotoapparat.configuration
|
||||
|
||||
import io.fotoapparat.parameter.*
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.preview.FrameProcessor
|
||||
import io.fotoapparat.selector.*
|
||||
|
||||
import io.fotoapparat.util.FrameProcessor
|
||||
import io.fotoapparat.preview.FrameProcessor as FrameProcessorJava
|
||||
|
||||
private const val DEFAULT_JPEG_QUALITY = 90
|
||||
private const val DEFAULT_EXPOSURE_COMPENSATION = 0
|
||||
|
||||
/**
|
||||
* A camera configuration which has all it's selectors defined.
|
||||
*/
|
||||
data class CameraConfiguration(
|
||||
override val flashMode: Iterable<Flash>.() -> Flash? = off(),
|
||||
override val focusMode: Iterable<FocusMode>.() -> FocusMode? = firstAvailable(
|
||||
override val flashMode: FlashSelector = off(),
|
||||
override val focusMode: FocusModeSelector = firstAvailable(
|
||||
continuousFocusPicture(),
|
||||
autoFocus(),
|
||||
fixed()
|
||||
fixed(),
|
||||
infinity()
|
||||
),
|
||||
override val jpegQuality: (IntRange) -> Int? = manualJpegQuality(DEFAULT_JPEG_QUALITY),
|
||||
override val frameProcessor: (Frame) -> Unit = {},
|
||||
override val previewFpsRange: Iterable<FpsRange>.() -> FpsRange? = highestFps(),
|
||||
override val antiBandingMode: Iterable<AntiBandingMode>.() -> AntiBandingMode? = firstAvailable(
|
||||
override val jpegQuality: QualitySelector = manualJpegQuality(DEFAULT_JPEG_QUALITY),
|
||||
override val exposureCompensation: ExposureSelector = manualExposure(DEFAULT_EXPOSURE_COMPENSATION),
|
||||
override val frameProcessor: FrameProcessor? = null,
|
||||
override val previewFpsRange: FpsRangeSelector = highestFps(),
|
||||
override val antiBandingMode: AntiBandingModeSelector = firstAvailable(
|
||||
auto(),
|
||||
hz50(),
|
||||
hz60(),
|
||||
none()
|
||||
),
|
||||
override val sensorSensitivity: (Iterable<Int>.() -> Int?)? = null,
|
||||
override val pictureResolution: Iterable<Resolution>.() -> Resolution? = highestResolution(),
|
||||
override val previewResolution: Iterable<Resolution>.() -> Resolution? = highestResolution()
|
||||
override val sensorSensitivity: SensorSensitivitySelector? = null,
|
||||
override val pictureResolution: ResolutionSelector = highestResolution(),
|
||||
override val previewResolution: ResolutionSelector = highestResolution()
|
||||
) : Configuration {
|
||||
|
||||
/**
|
||||
@@ -39,67 +40,64 @@ data class CameraConfiguration(
|
||||
|
||||
private var cameraConfiguration: CameraConfiguration = default()
|
||||
|
||||
fun flash(selector: (Iterable<Flash>.() -> Flash?)): Builder {
|
||||
fun flash(selector: FlashSelector): Builder = apply {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
flashMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun focusMode(selector: (Iterable<FocusMode>.() -> FocusMode?)): Builder {
|
||||
fun focusMode(selector: FocusModeSelector): Builder = apply {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
focusMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun previewFpsRange(selector: (Iterable<FpsRange>.() -> FpsRange?)): Builder {
|
||||
fun previewFpsRange(selector: FpsRangeSelector): Builder = apply {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
previewFpsRange = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun sensorSensitivity(selector: (Iterable<Int>.() -> Int?)): Builder {
|
||||
fun exposureCompensation(selector: ExposureSelector): Builder = apply {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
sensorSensitivity = selector
|
||||
exposureCompensation = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun antiBandingMode(selector: Iterable<AntiBandingMode>.() -> AntiBandingMode?): Builder {
|
||||
fun antiBandingMode(selector: AntiBandingModeSelector): Builder = apply {
|
||||
cameraConfiguration.copy(
|
||||
antiBandingMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun jpegQuality(selector: IntRange.() -> Int?): Builder {
|
||||
fun jpegQuality(selector: QualitySelector): Builder = apply {
|
||||
cameraConfiguration.copy(
|
||||
jpegQuality = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun previewResolution(selector: (Iterable<Resolution>.() -> Resolution?)): Builder {
|
||||
fun sensorSensitivity(selector: SensorSensitivitySelector): Builder = apply {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
sensorSensitivity = selector
|
||||
)
|
||||
}
|
||||
|
||||
fun previewResolution(selector: ResolutionSelector): Builder = apply {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
previewResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun photoResolution(selector: (Iterable<Resolution>.() -> Resolution?)): Builder {
|
||||
fun photoResolution(selector: ResolutionSelector): Builder = apply {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
pictureResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun frameProcessor(frameProcessor: FrameProcessor): Builder {
|
||||
fun frameProcessor(frameProcessor: FrameProcessorJava?): Builder = apply {
|
||||
cameraConfiguration = cameraConfiguration.copy(
|
||||
frameProcessor = { frameProcessor.process(it) }
|
||||
frameProcessor = frameProcessor?.let { it::process }
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,18 +113,18 @@ data class CameraConfiguration(
|
||||
* Alias for [CameraConfiguration.default]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun standard() = default()
|
||||
fun standard(): CameraConfiguration = default()
|
||||
|
||||
/**
|
||||
* Default [CameraConfiguration].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun default() = CameraConfiguration()
|
||||
fun default(): CameraConfiguration = CameraConfiguration()
|
||||
|
||||
/**
|
||||
* Creates a new [CameraConfiguration.Builder].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun builder() = CameraConfiguration.Builder()
|
||||
fun builder(): Builder = Builder()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package io.fotoapparat.configuration
|
||||
|
||||
import io.fotoapparat.parameter.*
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.selector.*
|
||||
import io.fotoapparat.util.FrameProcessor
|
||||
|
||||
interface Configuration {
|
||||
val flashMode: (Iterable<Flash>.() -> Flash?)?
|
||||
val focusMode: (Iterable<FocusMode>.() -> FocusMode?)?
|
||||
val jpegQuality: ((IntRange) -> Int?)?
|
||||
val frameProcessor: ((Frame) -> Unit)?
|
||||
val previewFpsRange: (Iterable<FpsRange>.() -> FpsRange?)?
|
||||
val antiBandingMode: (Iterable<AntiBandingMode>.() -> AntiBandingMode?)?
|
||||
val sensorSensitivity: (Iterable<Int>.() -> Int?)?
|
||||
val previewResolution: (Iterable<Resolution>.() -> Resolution?)?
|
||||
val pictureResolution: (Iterable<Resolution>.() -> Resolution?)?
|
||||
val flashMode: FlashSelector?
|
||||
val focusMode: FocusModeSelector?
|
||||
val jpegQuality: QualitySelector?
|
||||
val exposureCompensation: ExposureSelector?
|
||||
val frameProcessor: FrameProcessor?
|
||||
val previewFpsRange: FpsRangeSelector?
|
||||
val antiBandingMode: AntiBandingModeSelector?
|
||||
val sensorSensitivity: SensorSensitivitySelector?
|
||||
val previewResolution: ResolutionSelector?
|
||||
val pictureResolution: ResolutionSelector?
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
package io.fotoapparat.configuration
|
||||
|
||||
import io.fotoapparat.parameter.*
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.selector.*
|
||||
import io.fotoapparat.util.FrameProcessor
|
||||
|
||||
/**
|
||||
* A camera update configuration.
|
||||
*/
|
||||
data class UpdateConfiguration(
|
||||
override val flashMode: (Iterable<Flash>.() -> Flash?)? = null,
|
||||
override val focusMode: (Iterable<FocusMode>.() -> FocusMode?)? = null,
|
||||
override val jpegQuality: ((IntRange) -> Int?)? = null,
|
||||
override val frameProcessor: ((Frame) -> Unit)? = null,
|
||||
override val previewFpsRange: (Iterable<FpsRange>.() -> FpsRange?)? = null,
|
||||
override val antiBandingMode: (Iterable<AntiBandingMode>.() -> AntiBandingMode?)? = null,
|
||||
override val sensorSensitivity: (Iterable<Int>.() -> Int?)? = null,
|
||||
override val previewResolution: (Iterable<Resolution>.() -> Resolution?)? = null,
|
||||
override val pictureResolution: (Iterable<Resolution>.() -> Resolution?)? = null
|
||||
override val flashMode: FlashSelector? = null,
|
||||
override val focusMode: FocusModeSelector? = null,
|
||||
override val jpegQuality: QualitySelector? = null,
|
||||
override val exposureCompensation: ExposureSelector? = null,
|
||||
override val frameProcessor: FrameProcessor? = null,
|
||||
override val previewFpsRange: FpsRangeSelector? = null,
|
||||
override val antiBandingMode: AntiBandingModeSelector? = null,
|
||||
override val sensorSensitivity: SensorSensitivitySelector? = null,
|
||||
override val previewResolution: ResolutionSelector? = null,
|
||||
override val pictureResolution: ResolutionSelector? = null
|
||||
) : Configuration {
|
||||
|
||||
/**
|
||||
@@ -25,67 +26,64 @@ data class UpdateConfiguration(
|
||||
|
||||
private var configuration = UpdateConfiguration()
|
||||
|
||||
fun flash(selector: Iterable<Flash>.() -> Flash?): Builder {
|
||||
configuration.copy(
|
||||
fun flash(selector: FlashSelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
flashMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun focusMode(selector: Iterable<FocusMode>.() -> FocusMode?): Builder {
|
||||
configuration.copy(
|
||||
fun focusMode(selector: FocusModeSelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
focusMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun previewFpsRange(selector: Iterable<FpsRange>.() -> FpsRange?): Builder {
|
||||
configuration.copy(
|
||||
fun previewFpsRange(selector: FpsRangeSelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
previewFpsRange = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun sensorSensitivity(selector: Iterable<Int>.() -> Int?): Builder {
|
||||
configuration.copy(
|
||||
fun sensorSensitivity(selector: SensorSensitivitySelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
sensorSensitivity = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun antiBandingMode(selector: Iterable<AntiBandingMode>.() -> AntiBandingMode?): Builder {
|
||||
configuration.copy(
|
||||
fun antiBandingMode(selector: AntiBandingModeSelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
antiBandingMode = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun jpegQuality(selector: IntRange.() -> Int?): Builder {
|
||||
configuration.copy(
|
||||
fun jpegQuality(selector: QualitySelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
jpegQuality = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun previewResolution(selector: Iterable<Resolution>.() -> Resolution?): Builder {
|
||||
configuration.copy(
|
||||
fun exposureCompensation(selector: ExposureSelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
exposureCompensation = selector
|
||||
)
|
||||
}
|
||||
|
||||
fun previewResolution(selector: ResolutionSelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
previewResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun photoResolution(selector: Iterable<Resolution>.() -> Resolution?): Builder {
|
||||
configuration.copy(
|
||||
fun photoResolution(selector: ResolutionSelector): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
pictureResolution = selector
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun frameProcessor(frameProcessor: (Frame) -> Unit): Builder {
|
||||
configuration.copy(
|
||||
fun frameProcessor(frameProcessor: FrameProcessor?): Builder = apply {
|
||||
configuration = configuration.copy(
|
||||
frameProcessor = frameProcessor
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,6 +98,6 @@ data class UpdateConfiguration(
|
||||
* Creates a new [UpdateConfiguration.Builder].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun builder() = UpdateConfiguration.Builder()
|
||||
fun builder(): Builder = Builder()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.fotoapparat.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
|
||||
/**
|
||||
* A [ConflatedBroadcastChannel] which exposes a [getValue] which will [await] for at least one value.
|
||||
*/
|
||||
@ExperimentalCoroutinesApi
|
||||
internal class AwaitBroadcastChannel<T>(
|
||||
private val channel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel(),
|
||||
private val deferred: CompletableDeferred<Boolean> = CompletableDeferred()
|
||||
) : BroadcastChannel<T> by channel, Deferred<Boolean> by deferred {
|
||||
|
||||
/**
|
||||
* The most recently sent element to this channel.
|
||||
*/
|
||||
suspend fun getValue(): T {
|
||||
deferred.await()
|
||||
return channel.value
|
||||
}
|
||||
|
||||
override fun offer(element: T): Boolean {
|
||||
deferred.complete(true)
|
||||
return channel.offer(element)
|
||||
}
|
||||
|
||||
override suspend fun send(element: T) {
|
||||
deferred.complete(true)
|
||||
channel.send(element)
|
||||
}
|
||||
|
||||
override fun cancel(cause: CancellationException?) {
|
||||
channel.cancel(cause)
|
||||
deferred.cancel(cause)
|
||||
}
|
||||
|
||||
override fun cancel(cause: Throwable?): Boolean {
|
||||
deferred.cancel(cause?.message ?:"", cause)
|
||||
return channel.close(cause)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.fotoapparat.error
|
||||
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
|
||||
/**
|
||||
* Notified when an camera error happens within Fotoapparat.
|
||||
*
|
||||
* This method is always called from the main thread.
|
||||
*/
|
||||
interface CameraErrorListener {
|
||||
|
||||
/**
|
||||
* Notified when a camera error happens within Fotoapparat.
|
||||
*/
|
||||
fun onError(e: CameraException)
|
||||
}
|
||||
@@ -4,13 +4,15 @@ import android.os.Looper
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
import io.fotoapparat.hardware.executeMainThread
|
||||
|
||||
typealias CameraErrorCallback = (CameraException) -> Unit
|
||||
|
||||
/**
|
||||
* @return CameraErrorCallback which will always move execution to the main thread.
|
||||
*/
|
||||
fun ((CameraException) -> Unit).onMainThread(): (CameraException) -> Unit = { cameraException ->
|
||||
fun CameraErrorCallback.onMainThread(): CameraErrorCallback = { cameraException ->
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
this(cameraException)
|
||||
} else {
|
||||
executeMainThread { this(cameraException) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.fotoapparat.exception.camera
|
||||
|
||||
/**
|
||||
* Thrown when the preview surface didn't become available.
|
||||
*/
|
||||
class UnavailableSurfaceException : CameraException(
|
||||
"No preview surface became available before CameraView got detached from window. " +
|
||||
"Camera didn't start. You may ignore this exception."
|
||||
)
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.fotoapparat.exif
|
||||
|
||||
import io.fotoapparat.result.Photo
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -15,5 +14,5 @@ internal interface ExifOrientationWriter {
|
||||
* @param photo Photo stored in the file.
|
||||
* @throws FileSaveException If writing has failed.
|
||||
*/
|
||||
fun writeExifOrientation(file: File, photo: Photo)
|
||||
fun writeExifOrientation(file: File, rotationDegrees: Int)
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package io.fotoapparat.exif
|
||||
|
||||
import android.media.ExifInterface
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import io.fotoapparat.exception.FileSaveException
|
||||
import io.fotoapparat.result.Photo
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
@@ -11,16 +10,16 @@ import java.io.IOException
|
||||
*/
|
||||
internal object ExifWriter : ExifOrientationWriter {
|
||||
|
||||
|
||||
@Throws(FileSaveException::class)
|
||||
override fun writeExifOrientation(file: File, photo: Photo) {
|
||||
override fun writeExifOrientation(file: File, rotationDegrees: Int) {
|
||||
try {
|
||||
val exifInterface = ExifInterface(file.path)
|
||||
exifInterface.setAttribute(
|
||||
ExifInterface.TAG_ORIENTATION,
|
||||
toExifOrientation(photo.rotationDegrees).toString()
|
||||
)
|
||||
exifInterface.saveAttributes()
|
||||
ExifInterface(file.path).apply {
|
||||
setAttribute(
|
||||
ExifInterface.TAG_ORIENTATION,
|
||||
toExifOrientation(rotationDegrees).toString()
|
||||
)
|
||||
saveAttributes()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw FileSaveException(e)
|
||||
}
|
||||
|
||||
@@ -4,25 +4,30 @@ 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
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import io.fotoapparat.characteristic.toCameraId
|
||||
import io.fotoapparat.coroutines.AwaitBroadcastChannel
|
||||
import io.fotoapparat.exception.camera.CameraException
|
||||
import io.fotoapparat.hardware.orientation.computeDisplayOrientation
|
||||
import io.fotoapparat.hardware.orientation.computeImageOrientation
|
||||
import io.fotoapparat.hardware.metering.FocalRequest
|
||||
import io.fotoapparat.hardware.metering.convert.toFocusAreas
|
||||
import io.fotoapparat.hardware.orientation.*
|
||||
import io.fotoapparat.log.Logger
|
||||
import io.fotoapparat.parameter.FocusMode
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
import io.fotoapparat.parameter.camera.CameraParameters
|
||||
import io.fotoapparat.parameter.camera.apply.applyNewParameters
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.parameter.camera.apply.applyInto
|
||||
import io.fotoapparat.parameter.camera.convert.toCode
|
||||
import io.fotoapparat.preview.PreviewStream
|
||||
import io.fotoapparat.result.FocusResult
|
||||
import io.fotoapparat.result.Photo
|
||||
import io.fotoapparat.util.FrameProcessor
|
||||
import io.fotoapparat.util.lineSeparator
|
||||
import io.fotoapparat.view.Preview
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -38,15 +43,16 @@ internal open class CameraDevice(
|
||||
val characteristics: Characteristics
|
||||
) {
|
||||
|
||||
private lateinit var cameraParameters: CameraParameters
|
||||
private val capabilities = CompletableDeferred<Capabilities>()
|
||||
private val cameraParameters = AwaitBroadcastChannel<CameraParameters>()
|
||||
private lateinit var previewStream: PreviewStream
|
||||
private lateinit var capabilities: Capabilities
|
||||
private lateinit var surface: Surface
|
||||
private lateinit var camera: Camera
|
||||
|
||||
private var cachedZoomParameters: Camera.Parameters? = null
|
||||
private var displayRotation = 0
|
||||
var imageRotation = 0
|
||||
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.
|
||||
@@ -59,7 +65,7 @@ internal open class CameraDevice(
|
||||
|
||||
try {
|
||||
camera = Camera.open(cameraId)
|
||||
capabilities = camera.getCapabilities()
|
||||
capabilities.complete(camera.getCapabilities())
|
||||
previewStream = PreviewStream(camera)
|
||||
} catch (e: RuntimeException) {
|
||||
throw CameraException(
|
||||
@@ -74,7 +80,7 @@ internal open class CameraDevice(
|
||||
*/
|
||||
open fun close() {
|
||||
logger.recordMethod()
|
||||
|
||||
surface.release()
|
||||
camera.release()
|
||||
}
|
||||
|
||||
@@ -130,44 +136,46 @@ internal open class CameraDevice(
|
||||
open fun takePhoto(): Photo {
|
||||
logger.recordMethod()
|
||||
|
||||
return camera.takePhoto(imageRotation)
|
||||
return camera.takePhoto(imageOrientation.degrees)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [Capabilities] of the camera.
|
||||
*/
|
||||
open fun getCapabilities(): Capabilities {
|
||||
open suspend fun getCapabilities(): Capabilities {
|
||||
logger.recordMethod()
|
||||
|
||||
return capabilities
|
||||
return capabilities.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [CameraParameters] used.
|
||||
*/
|
||||
open fun getParameters(): CameraParameters {
|
||||
open suspend fun getParameters(): CameraParameters {
|
||||
logger.recordMethod()
|
||||
|
||||
return cameraParameters
|
||||
return cameraParameters.getValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the desired camera parameters.
|
||||
*/
|
||||
open fun updateParameters(cameraParameters: CameraParameters) {
|
||||
open suspend fun updateParameters(cameraParameters: CameraParameters) {
|
||||
logger.recordMethod()
|
||||
|
||||
this.cameraParameters = cameraParameters
|
||||
this.cameraParameters.send(cameraParameters)
|
||||
|
||||
logger.log("New camera parameters are: $cameraParameters")
|
||||
|
||||
camera.updateParameters(cameraParameters)
|
||||
cameraParameters.applyInto(cachedCameraParameters ?: camera.parameters)
|
||||
.cacheLocally()
|
||||
.setInCamera()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the frame processor.
|
||||
*/
|
||||
open fun updateFrameProcessor(frameProcessor: ((Frame) -> Unit)?) {
|
||||
open fun updateFrameProcessor(frameProcessor: FrameProcessor?) {
|
||||
logger.recordMethod()
|
||||
|
||||
previewStream.updateProcessorSafely(frameProcessor)
|
||||
@@ -176,23 +184,41 @@ internal open class CameraDevice(
|
||||
/**
|
||||
* Sets the current orientation of the display.
|
||||
*/
|
||||
open fun setDisplayOrientation(degrees: Int) {
|
||||
open fun setDisplayOrientation(orientationState: OrientationState) {
|
||||
logger.recordMethod()
|
||||
|
||||
imageRotation = computeImageOrientation(
|
||||
degrees = degrees,
|
||||
characteristics = characteristics
|
||||
imageOrientation = computeImageOrientation(
|
||||
deviceOrientation = orientationState.deviceOrientation,
|
||||
cameraOrientation = characteristics.cameraOrientation,
|
||||
cameraIsMirrored = characteristics.isMirrored
|
||||
)
|
||||
|
||||
displayRotation = computeDisplayOrientation(
|
||||
degrees = degrees,
|
||||
characteristics = characteristics
|
||||
displayOrientation = computeDisplayOrientation(
|
||||
screenOrientation = orientationState.screenOrientation,
|
||||
cameraOrientation = characteristics.cameraOrientation,
|
||||
cameraIsMirrored = characteristics.isMirrored
|
||||
)
|
||||
|
||||
logger.log("Image Rotation is: $imageRotation. Display rotation is: $displayRotation")
|
||||
previewOrientation = computePreviewOrientation(
|
||||
screenOrientation = orientationState.screenOrientation,
|
||||
cameraOrientation = characteristics.cameraOrientation,
|
||||
cameraIsMirrored = characteristics.isMirrored
|
||||
)
|
||||
|
||||
previewStream.frameOrientation = imageRotation
|
||||
camera.setDisplayOrientation(displayRotation)
|
||||
logger.log("Orientations: $lineSeparator" +
|
||||
"Screen orientation (preview) is: ${orientationState.screenOrientation}. " + lineSeparator +
|
||||
"Camera sensor orientation is always at: ${characteristics.cameraOrientation}. " + lineSeparator +
|
||||
"Camera is " + if (characteristics.isMirrored) "mirrored." else "not mirrored."
|
||||
)
|
||||
|
||||
logger.log("Orientation adjustments: $lineSeparator" +
|
||||
"Image orientation will be adjusted by: ${imageOrientation.degrees} degrees. " + lineSeparator +
|
||||
"Display orientation will be adjusted by: ${displayOrientation.degrees} degrees. " + lineSeparator +
|
||||
"Preview orientation will be adjusted by: ${previewOrientation.degrees} degrees."
|
||||
)
|
||||
|
||||
previewStream.frameOrientation = previewOrientation
|
||||
camera.setDisplayOrientation(displayOrientation.degrees)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,9 +242,30 @@ internal open class CameraDevice(
|
||||
return camera.focusSafely()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the point where the focus & exposure metering will happen.
|
||||
*/
|
||||
open suspend fun setFocalPoint(focalRequest: FocalRequest) {
|
||||
logger.recordMethod()
|
||||
|
||||
if (capabilities.await().canSetFocusingAreas()) {
|
||||
camera.updateFocusingAreas(focalRequest)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the point where the focus & exposure will happen.
|
||||
*/
|
||||
open fun clearFocalPoint() {
|
||||
logger.recordMethod()
|
||||
|
||||
camera.clearFocusingAreas()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired surface on which the camera's preview will be displayed.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
open fun setDisplaySurface(preview: Preview) {
|
||||
logger.recordMethod()
|
||||
|
||||
@@ -240,14 +287,13 @@ internal open class CameraDevice(
|
||||
open fun getPreviewResolution(): Resolution {
|
||||
logger.recordMethod()
|
||||
|
||||
val previewResolution = camera.getPreviewResolution(imageRotation)
|
||||
val previewResolution = camera.getPreviewResolution(previewOrientation)
|
||||
|
||||
logger.log("Preview resolution is: $previewResolution")
|
||||
|
||||
return previewResolution
|
||||
}
|
||||
|
||||
|
||||
private fun setZoomSafely(@FloatRange(from = 0.0, to = 1.0) level: Float) {
|
||||
try {
|
||||
setZoomUnsafe(level)
|
||||
@@ -257,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 {
|
||||
@@ -286,6 +338,36 @@ internal open class CameraDevice(
|
||||
|
||||
return FocusResult.Focused
|
||||
}
|
||||
|
||||
private suspend fun Camera.updateFocusingAreas(focalRequest: FocalRequest) {
|
||||
val focusingAreas = focalRequest.toFocusAreas(
|
||||
displayOrientationDegrees = displayOrientation.degrees,
|
||||
cameraIsMirrored = characteristics.isMirrored
|
||||
)
|
||||
|
||||
parameters = parameters.apply {
|
||||
with(capabilities.await()) {
|
||||
if (maxMeteringAreas > 0) {
|
||||
meteringAreas = focusingAreas
|
||||
}
|
||||
|
||||
if (maxFocusAreas > 0) {
|
||||
if (focusModes.contains(FocusMode.Auto)) {
|
||||
focusMode = FocusMode.Auto.toCode()
|
||||
}
|
||||
focusAreas = focusingAreas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Camera.clearFocusingAreas() {
|
||||
parameters = parameters.apply {
|
||||
meteringAreas = null
|
||||
focusAreas = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private const val AUTOFOCUS_TIMEOUT_SECONDS = 3L
|
||||
@@ -312,69 +394,31 @@ private fun Camera.takePhoto(imageRotation: Int): Photo {
|
||||
return photoReference.get()
|
||||
}
|
||||
|
||||
private fun computeImageOrientation(
|
||||
degrees: Int,
|
||||
characteristics: Characteristics
|
||||
) = computeImageOrientation(
|
||||
screenRotationDegrees = degrees,
|
||||
cameraRotationDegrees = characteristics.orientation,
|
||||
cameraIsMirrored = characteristics.lensPosition == LensPosition.Front
|
||||
)
|
||||
|
||||
private fun computeDisplayOrientation(
|
||||
degrees: Int,
|
||||
characteristics: Characteristics
|
||||
) = computeDisplayOrientation(
|
||||
screenRotationDegrees = degrees,
|
||||
cameraRotationDegrees = characteristics.orientation,
|
||||
cameraIsMirrored = characteristics.lensPosition == LensPosition.Front
|
||||
)
|
||||
|
||||
private fun Camera.updateParameters(newParameters: CameraParameters) {
|
||||
parameters = parameters.applyNewParameters(newParameters)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun Camera.setDisplaySurface(
|
||||
preview: Preview
|
||||
): Surface = when (preview) {
|
||||
is Preview.Texture -> preview.surfaceTexture
|
||||
.also {
|
||||
setPreviewTexture(it)
|
||||
}
|
||||
.let {
|
||||
Surface(it)
|
||||
}
|
||||
.also(this::setPreviewTexture)
|
||||
.let(::Surface)
|
||||
|
||||
is Preview.Surface -> preview.surfaceHolder
|
||||
.also {
|
||||
setPreviewDisplay(it)
|
||||
}
|
||||
.also(this::setPreviewDisplay)
|
||||
.surface
|
||||
}
|
||||
|
||||
private fun Camera.getPreviewResolution(imageRotation: Int): Resolution {
|
||||
val previewSize = parameters.previewSize
|
||||
|
||||
val size = PreviewSize(
|
||||
previewSize.width,
|
||||
previewSize.height
|
||||
)
|
||||
|
||||
return size.run {
|
||||
when (imageRotation) {
|
||||
0, 180 -> this
|
||||
else -> flipDimensions()
|
||||
}
|
||||
}
|
||||
private fun Camera.getPreviewResolution(previewOrientation: Orientation): Resolution {
|
||||
return parameters.previewSize
|
||||
.run {
|
||||
PreviewSize(width, height)
|
||||
}
|
||||
.run {
|
||||
when (previewOrientation) {
|
||||
is Orientation.Vertical -> this
|
||||
is Orientation.Horizontal -> flipDimensions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun PreviewStream.updateProcessorSafely(frameProcessor: ((Frame) -> Unit)?) {
|
||||
clearProcessors()
|
||||
when (frameProcessor) {
|
||||
null -> stop()
|
||||
else -> {
|
||||
addProcessor(frameProcessor)
|
||||
start()
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun Capabilities.canSetFocusingAreas(): Boolean =
|
||||
maxMeteringAreas > 0 || maxFocusAreas > 0
|
||||
|
||||
@@ -3,32 +3,35 @@
|
||||
package io.fotoapparat.hardware
|
||||
|
||||
import android.hardware.Camera
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import io.fotoapparat.characteristic.getCharacteristics
|
||||
import io.fotoapparat.concurrent.CameraExecutor
|
||||
import io.fotoapparat.configuration.CameraConfiguration
|
||||
import io.fotoapparat.configuration.Configuration
|
||||
import io.fotoapparat.exception.camera.UnsupportedLensException
|
||||
import io.fotoapparat.hardware.display.Display
|
||||
import io.fotoapparat.hardware.orientation.Orientation
|
||||
import io.fotoapparat.log.Logger
|
||||
import io.fotoapparat.parameter.ScaleType
|
||||
import io.fotoapparat.parameter.camera.CameraParameters
|
||||
import io.fotoapparat.parameter.camera.provide.getCameraParameters
|
||||
import io.fotoapparat.preview.Frame
|
||||
import io.fotoapparat.selector.LensPositionSelector
|
||||
import io.fotoapparat.util.FrameProcessor
|
||||
import io.fotoapparat.view.CameraRenderer
|
||||
import kotlinx.coroutines.experimental.CompletableDeferred
|
||||
|
||||
import io.fotoapparat.view.FocalPointSelector
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
|
||||
/**
|
||||
* Phone.
|
||||
*/
|
||||
internal open class Device(
|
||||
private val logger: Logger,
|
||||
internal open val logger: Logger,
|
||||
private val display: Display,
|
||||
open val scaleType: ScaleType,
|
||||
open val cameraRenderer: CameraRenderer,
|
||||
internal open val scaleType: ScaleType,
|
||||
internal open val cameraRenderer: CameraRenderer,
|
||||
internal val focusPointSelector: FocalPointSelector?,
|
||||
internal val executor: CameraExecutor,
|
||||
numberOfCameras: Int = Camera.getNumberOfCameras(),
|
||||
initialConfiguration: CameraConfiguration,
|
||||
initialLensPositionSelector: Collection<LensPosition>.() -> LensPosition?
|
||||
initialConfiguration: CameraConfiguration, initialLensPositionSelector: LensPositionSelector
|
||||
) {
|
||||
|
||||
private val cameras = (0 until numberOfCameras).map { cameraId ->
|
||||
@@ -38,7 +41,7 @@ internal open class Device(
|
||||
)
|
||||
}
|
||||
|
||||
private var lensPositionSelector: Collection<LensPosition>.() -> LensPosition? = initialLensPositionSelector
|
||||
private var lensPositionSelector: LensPositionSelector = initialLensPositionSelector
|
||||
private var selectedCameraDevice = CompletableDeferred<CameraDevice>()
|
||||
private var savedConfiguration = CameraConfiguration.default()
|
||||
|
||||
@@ -50,7 +53,7 @@ internal open class Device(
|
||||
/**
|
||||
* Selects a camera.
|
||||
*/
|
||||
open fun canSelectCamera(lensPositionSelector: (Collection<LensPosition>) -> LensPosition?): Boolean {
|
||||
open fun canSelectCamera(lensPositionSelector: LensPositionSelector): Boolean {
|
||||
val selectedCameraDevice = selectCamera(
|
||||
availableCameras = cameras,
|
||||
lensPositionSelector = lensPositionSelector
|
||||
@@ -68,9 +71,7 @@ internal open class Device(
|
||||
availableCameras = cameras,
|
||||
lensPositionSelector = lensPositionSelector
|
||||
)
|
||||
?.let {
|
||||
selectedCameraDevice.complete(it)
|
||||
}
|
||||
?.let(selectedCameraDevice::complete)
|
||||
?: selectedCameraDevice.completeExceptionally(UnsupportedLensException())
|
||||
}
|
||||
|
||||
@@ -84,9 +85,7 @@ internal open class Device(
|
||||
/**
|
||||
* Waits and returns the selected camera.
|
||||
*/
|
||||
open suspend fun awaitSelectedCamera(): CameraDevice {
|
||||
return selectedCameraDevice.await()
|
||||
}
|
||||
open suspend fun awaitSelectedCamera(): CameraDevice = selectedCameraDevice.await()
|
||||
|
||||
/**
|
||||
* Returns the selected camera.
|
||||
@@ -94,12 +93,10 @@ internal open class Device(
|
||||
* @throws IllegalStateException If no camera has been yet selected.
|
||||
* @throws UnsupportedLensException If no camera could get selected.
|
||||
*/
|
||||
open fun getSelectedCamera(): CameraDevice {
|
||||
return try {
|
||||
selectedCameraDevice.getCompleted()
|
||||
} catch (e: IllegalStateException) {
|
||||
throw IllegalStateException("Camera has not started!")
|
||||
}
|
||||
open fun getSelectedCamera(): CameraDevice = try {
|
||||
selectedCameraDevice.getCompleted()
|
||||
} catch (e: IllegalStateException) {
|
||||
throw IllegalStateException("Camera has not started!")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,16 +105,16 @@ internal open class Device(
|
||||
open fun hasSelectedCamera() = selectedCameraDevice.isCompleted
|
||||
|
||||
/**
|
||||
* @return rotation of the screen in degrees.
|
||||
* @return Orientation of the screen.
|
||||
*/
|
||||
open fun getScreenRotation(): Int {
|
||||
return display.getRotation()
|
||||
open fun getScreenOrientation(): Orientation {
|
||||
return display.getOrientation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the desired from the user camera lens position.
|
||||
*/
|
||||
open fun updateLensPositionSelector(newLensPosition: Collection<LensPosition>.() -> LensPosition?) {
|
||||
open fun updateLensPositionSelector(newLensPosition: LensPositionSelector) {
|
||||
logger.recordMethod()
|
||||
|
||||
lensPositionSelector = newLensPosition
|
||||
@@ -138,27 +135,26 @@ internal open class Device(
|
||||
/**
|
||||
* @return The desired from the user selectors.
|
||||
*/
|
||||
open fun getConfiguration(): CameraConfiguration {
|
||||
return savedConfiguration
|
||||
}
|
||||
open fun getConfiguration(): CameraConfiguration = savedConfiguration
|
||||
|
||||
open fun getCameraParameters(cameraDevice: CameraDevice): CameraParameters {
|
||||
return getCameraParameters(
|
||||
cameraConfiguration = savedConfiguration,
|
||||
capabilities = cameraDevice.getCapabilities()
|
||||
)
|
||||
}
|
||||
/**
|
||||
* @return The selected [CameraParameters] for the given [CameraDevice].
|
||||
*/
|
||||
open suspend fun getCameraParameters(cameraDevice: CameraDevice): CameraParameters =
|
||||
getCameraParameters(
|
||||
cameraConfiguration = savedConfiguration,
|
||||
capabilities = cameraDevice.getCapabilities()
|
||||
)
|
||||
|
||||
open fun getFrameProcessor(): (Frame) -> Unit {
|
||||
return savedConfiguration.frameProcessor
|
||||
}
|
||||
/**
|
||||
* @return The frame processor.
|
||||
*/
|
||||
open fun getFrameProcessor(): FrameProcessor? = savedConfiguration.frameProcessor
|
||||
|
||||
/**
|
||||
* @return The desired from the user camera lens position.
|
||||
*/
|
||||
open fun getLensPositionSelector(): Collection<LensPosition>.() -> LensPosition? {
|
||||
return lensPositionSelector
|
||||
}
|
||||
open fun getLensPositionSelector(): LensPositionSelector = lensPositionSelector
|
||||
|
||||
}
|
||||
|
||||
@@ -171,11 +167,16 @@ 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 ?: savedConfiguration.sensorSensitivity,
|
||||
pictureResolution = newConfiguration.pictureResolution ?: savedConfiguration.pictureResolution,
|
||||
previewResolution = newConfiguration.previewResolution ?: savedConfiguration.previewResolution
|
||||
sensorSensitivity = newConfiguration.sensorSensitivity
|
||||
?: savedConfiguration.sensorSensitivity,
|
||||
pictureResolution = newConfiguration.pictureResolution
|
||||
?: savedConfiguration.pictureResolution,
|
||||
previewResolution = newConfiguration.previewResolution
|
||||
?: savedConfiguration.previewResolution
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -183,11 +184,11 @@ internal fun updateConfiguration(
|
||||
*/
|
||||
internal fun selectCamera(
|
||||
availableCameras: List<CameraDevice>,
|
||||
lensPositionSelector: Collection<LensPosition>.() -> LensPosition?
|
||||
lensPositionSelector: LensPositionSelector
|
||||
): CameraDevice? {
|
||||
|
||||
val lensPositions = availableCameras.map { it.characteristics.lensPosition }.toSet()
|
||||
val desiredPosition = lensPositionSelector(lensPositions)
|
||||
|
||||
return availableCameras.find { it.characteristics.lensPosition == desiredPosition }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,9 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
private var taskExecutor = Executors.newSingleThreadExecutor()
|
||||
get() = field.takeUnless { it.isShutdown } ?: Executors.newSingleThreadExecutor().also { field = it }
|
||||
|
||||
private val cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
private val loggingExecutor = Executors.newSingleThreadExecutor()
|
||||
private val mainThreadHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
/**
|
||||
* [java.util.concurrent.Executor] operating the [io.fotoapparat.result.PendingResult].
|
||||
*/
|
||||
@@ -20,23 +16,6 @@ internal val pendingResultExecutor = Executors.newSingleThreadExecutor()
|
||||
*/
|
||||
internal val frameProcessingExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
/**
|
||||
* Shuts down all pending camera tasks.
|
||||
*/
|
||||
internal fun shutdownPendingTasks() {
|
||||
taskExecutor.shutdownNow()
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a camera task.
|
||||
*/
|
||||
internal fun executeTask(function: Runnable) = taskExecutor.execute(function)
|
||||
|
||||
/**
|
||||
* Executes a camera operation.
|
||||
*/
|
||||
internal fun execute(function: () -> Unit) = cameraExecutor.execute(function)
|
||||
|
||||
/**
|
||||
* Executes an operation in the main thread.
|
||||
*/
|
||||
|
||||
@@ -3,23 +3,28 @@ package io.fotoapparat.hardware.display
|
||||
import android.content.Context
|
||||
import android.view.Surface
|
||||
import android.view.WindowManager
|
||||
import io.fotoapparat.hardware.orientation.Orientation
|
||||
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.Landscape
|
||||
import io.fotoapparat.hardware.orientation.Orientation.Horizontal.ReverseLandscape
|
||||
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
|
||||
import io.fotoapparat.hardware.orientation.Orientation.Vertical.ReversePortrait
|
||||
|
||||
/**
|
||||
* A phone's display.
|
||||
*/
|
||||
open internal class Display(context: Context) {
|
||||
internal open class Display(context: Context) {
|
||||
|
||||
private val display = context.getDisplay()
|
||||
|
||||
/**
|
||||
* Returns the rotation of the screen from its "natural" orientation in degrees.
|
||||
* Returns the orientation of the screen.
|
||||
*/
|
||||
open fun getRotation(): Int = when (display.rotation) {
|
||||
Surface.ROTATION_90 -> 90
|
||||
Surface.ROTATION_180 -> 180
|
||||
Surface.ROTATION_270 -> 270
|
||||
Surface.ROTATION_0 -> 0
|
||||
else -> 0
|
||||
open fun getOrientation(): Orientation = when (display.rotation) {
|
||||
Surface.ROTATION_0 -> Portrait
|
||||
Surface.ROTATION_90 -> Landscape
|
||||
Surface.ROTATION_180 -> ReversePortrait
|
||||
Surface.ROTATION_270 -> ReverseLandscape
|
||||
else -> Portrait
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.fotoapparat.hardware.metering
|
||||
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
|
||||
/**
|
||||
* The request to focus camera at a particular point.
|
||||
*/
|
||||
data class FocalRequest(
|
||||
|
||||
/**
|
||||
* The point where when user would like to focus.
|
||||
*/
|
||||
val point: PointF,
|
||||
|
||||
/**
|
||||
* Resolution of the preview
|
||||
*/
|
||||
val previewResolution: Resolution
|
||||
)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.fotoapparat.hardware.metering
|
||||
|
||||
/**
|
||||
* A point in arbitrary scale.
|
||||
*/
|
||||
data class PointF(
|
||||
val x: Float,
|
||||
val y: Float
|
||||
)
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package io.fotoapparat.hardware.metering.convert
|
||||
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Rect
|
||||
import android.hardware.Camera
|
||||
import io.fotoapparat.hardware.metering.FocalRequest
|
||||
import io.fotoapparat.hardware.metering.PointF
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
|
||||
/**
|
||||
* The weight of the camera area.
|
||||
* Only 1will be used, so max weight, `1000`, is used.
|
||||
*/
|
||||
private const val WEIGHT = 1000
|
||||
|
||||
/**
|
||||
* From -1000 to 1000
|
||||
*
|
||||
* @see Camera.Area
|
||||
*/
|
||||
private const val CAMERA_BOUNDS_RANGE = 2000f
|
||||
|
||||
/**
|
||||
* The half dimension size of a focus area.
|
||||
*
|
||||
* The camera will focus on a maximum area of `(2 * this) ^ 2`.
|
||||
*/
|
||||
private const val FOCUS_AREA_HALF_SIZE = 50
|
||||
|
||||
/**
|
||||
* Converts a [FocalRequest] to a list of [Camera.Area] that the camera should focus at.
|
||||
*/
|
||||
internal fun FocalRequest.toFocusAreas(
|
||||
displayOrientationDegrees: Int,
|
||||
cameraIsMirrored: Boolean
|
||||
): List<Camera.Area> = listOf(Camera.Area(
|
||||
focusBounds(
|
||||
displayOrientationDegrees = displayOrientationDegrees.toFloat(),
|
||||
cameraIsMirrored = cameraIsMirrored
|
||||
),
|
||||
WEIGHT
|
||||
))
|
||||
|
||||
private fun FocalRequest.focusBounds(
|
||||
displayOrientationDegrees: Float,
|
||||
cameraIsMirrored: Boolean
|
||||
): Rect = point
|
||||
.adjustPointToCameraPreview(
|
||||
previewResolution,
|
||||
displayOrientationDegrees,
|
||||
cameraIsMirrored
|
||||
)
|
||||
.toBoundsRect()
|
||||
|
||||
private fun PointF.adjustPointToCameraPreview(
|
||||
visibleResolution: Resolution,
|
||||
displayOrientationDegrees: Float,
|
||||
cameraIsMirrored: Boolean
|
||||
): PointF = Matrix()
|
||||
.configMatrix(
|
||||
visibleResolution,
|
||||
displayOrientationDegrees,
|
||||
cameraIsMirrored
|
||||
)
|
||||
.run {
|
||||
floatArrayOf(x, y).also {
|
||||
mapPoints(it)
|
||||
}
|
||||
}
|
||||
.let {
|
||||
PointF(
|
||||
it[0].verifyInBounds(),
|
||||
it[1].verifyInBounds()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The point value should mainly be adjusted by `2000 * value - 1000`
|
||||
* in order to be in `-1000..1000` range.
|
||||
*/
|
||||
private fun Matrix.configMatrix(
|
||||
visibleResolution: Resolution,
|
||||
displayOrientationDegrees: Float,
|
||||
cameraIsMirrored: Boolean
|
||||
) = apply {
|
||||
postScale(
|
||||
CAMERA_BOUNDS_RANGE / visibleResolution.width.toFloat(),
|
||||
CAMERA_BOUNDS_RANGE / visibleResolution.height.toFloat()
|
||||
)
|
||||
postTranslate(
|
||||
-CAMERA_BOUNDS_RANGE / 2,
|
||||
-CAMERA_BOUNDS_RANGE / 2
|
||||
)
|
||||
postRotate(-displayOrientationDegrees)
|
||||
postScale(
|
||||
if (cameraIsMirrored) -1f else 1f,
|
||||
1f
|
||||
)
|
||||
}
|
||||
|
||||
private fun Float.verifyInBounds(): Float {
|
||||
return takeIf { it in -1000f..1000f }
|
||||
?: throw IllegalArgumentException("Point value should be between -1000.0 and 1000.0. Was $this")
|
||||
}
|
||||
|
||||
private fun PointF.toBoundsRect() = Rect(
|
||||
(x - FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
|
||||
(y - FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
|
||||
(x + FOCUS_AREA_HALF_SIZE).ensureAreaBound(),
|
||||
(y + FOCUS_AREA_HALF_SIZE).ensureAreaBound()
|
||||
)
|
||||
|
||||
private fun Float.ensureAreaBound() = clamp(-1000f, 1000f).toInt()
|
||||
|
||||
private fun Float.clamp(min: Float, max: Float): Float = Math.max(min, Math.min(this, max))
|
||||
@@ -1,61 +1,64 @@
|
||||
package io.fotoapparat.hardware.orientation
|
||||
|
||||
typealias DeviceOrientation = Orientation
|
||||
typealias ScreenOrientation = Orientation
|
||||
|
||||
/**
|
||||
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
|
||||
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
|
||||
* orientation.
|
||||
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case for
|
||||
* front cameras). `false` if it is not mirrored.
|
||||
*
|
||||
* @return clockwise rotation of the image relatively to current device orientation.
|
||||
* The device orientation.
|
||||
*/
|
||||
internal fun computeImageOrientation(
|
||||
screenRotationDegrees: Int,
|
||||
cameraRotationDegrees: Int,
|
||||
cameraIsMirrored: Boolean
|
||||
): Int {
|
||||
val rotation = if (cameraIsMirrored) {
|
||||
-(screenRotationDegrees + cameraRotationDegrees)
|
||||
} else {
|
||||
screenRotationDegrees - cameraRotationDegrees
|
||||
sealed class Orientation(
|
||||
val degrees: Int
|
||||
) {
|
||||
|
||||
/**
|
||||
* A vertical device orientation.
|
||||
*/
|
||||
sealed class Vertical(degrees: Int) : Orientation(degrees) {
|
||||
|
||||
/**
|
||||
* A vertical, normal orientation.
|
||||
*/
|
||||
object Portrait : Vertical(0) {
|
||||
override fun toString(): String = "Orientation.Vertical.Portrait"
|
||||
}
|
||||
|
||||
/**
|
||||
* A reversed (flipped phone) orientation.
|
||||
*/
|
||||
object ReversePortrait : Vertical(180) {
|
||||
override fun toString(): String = "Orientation.Vertical.ReversePortrait"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return (rotation + 720) % 360
|
||||
}
|
||||
/**
|
||||
* A horizontal device orientation.
|
||||
*/
|
||||
sealed class Horizontal(degrees: Int) : Orientation(degrees) {
|
||||
|
||||
/**
|
||||
* @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
|
||||
* @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
|
||||
* orientation.
|
||||
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case for
|
||||
* front cameras). `false` if it is not mirrored.
|
||||
*
|
||||
* @return display orientation in which user will see the output camera in a correct rotation.
|
||||
*/
|
||||
internal fun computeDisplayOrientation(
|
||||
screenRotationDegrees: Int,
|
||||
cameraRotationDegrees: Int,
|
||||
cameraIsMirrored: Boolean
|
||||
): Int {
|
||||
var degrees = toClosestRightAngle(screenRotationDegrees)
|
||||
/**
|
||||
* A 90 degrees clockwise from "normal", orientation.
|
||||
*/
|
||||
object Landscape : Horizontal(90) {
|
||||
override fun toString(): String = "Orientation.Horizontal.Landscape"
|
||||
}
|
||||
|
||||
/**
|
||||
* A 90 degrees counter-clockwise from "normal", orientation.
|
||||
*/
|
||||
object ReverseLandscape : Horizontal(270) {
|
||||
override fun toString(): String = "Orientation.Horizontal.ReverseLandscape"
|
||||
}
|
||||
|
||||
return if (cameraIsMirrored) {
|
||||
degrees = (cameraRotationDegrees + degrees) % 360
|
||||
(360 - degrees) % 360
|
||||
} else {
|
||||
(cameraRotationDegrees - degrees + 360) % 360
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return closest right angle to given value. That is: 0, 90, 180, 270.
|
||||
*/
|
||||
internal fun toClosestRightAngle(degrees: Int): Int {
|
||||
val roundUp = degrees % 90 > 45
|
||||
|
||||
val roundAppModifier = if (roundUp) 1 else 0
|
||||
|
||||
return (degrees / 90 + roundAppModifier) * 90 % 360
|
||||
internal fun Int.toOrientation(): Orientation {
|
||||
return when (this) {
|
||||
0, 360 -> Orientation.Vertical.Portrait
|
||||
90 -> Orientation.Horizontal.Landscape
|
||||
180 -> Orientation.Vertical.ReversePortrait
|
||||
270 -> Orientation.Horizontal.ReverseLandscape
|
||||
else -> throw IllegalArgumentException("Cannot convert $this to absolute Orientation.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package io.fotoapparat.hardware.orientation
|
||||
|
||||
/**
|
||||
* @param screenOrientation Orientation of the display.
|
||||
* @param cameraOrientation Orientation of the camera sensor.
|
||||
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case
|
||||
* for front cameras). `false` if it is not mirrored.
|
||||
*
|
||||
* @return rotation of the image relatively to current device orientation.
|
||||
*/
|
||||
fun computePreviewOrientation(
|
||||
screenOrientation: Orientation,
|
||||
cameraOrientation: Orientation,
|
||||
cameraIsMirrored: Boolean
|
||||
): Orientation {
|
||||
val mirroredCameraModifier = if (cameraIsMirrored) -1 else 1
|
||||
|
||||
val rotation = (720
|
||||
+ mirroredCameraModifier * screenOrientation.degrees
|
||||
- cameraOrientation.degrees
|
||||
) % 360
|
||||
|
||||
return rotation.toOrientation()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param deviceOrientation Orientation of the device.
|
||||
* @param cameraOrientation Orientation of the camera sensor.
|
||||
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case
|
||||
* for front cameras). `false` if it is not mirrored.
|
||||
*
|
||||
* @return clockwise rotation of the image relatively to current device orientation.
|
||||
*/
|
||||
internal fun computeImageOrientation(
|
||||
deviceOrientation: Orientation,
|
||||
cameraOrientation: Orientation,
|
||||
cameraIsMirrored: Boolean
|
||||
): Orientation {
|
||||
val screenOrientationDegrees = deviceOrientation.degrees
|
||||
val cameraRotationDegrees = cameraOrientation.degrees
|
||||
|
||||
val rotation = if (cameraIsMirrored) {
|
||||
360 - (cameraRotationDegrees - screenOrientationDegrees + 360) % 360
|
||||
} else {
|
||||
360 - (cameraRotationDegrees + screenOrientationDegrees) % 360
|
||||
}
|
||||
|
||||
return rotation.toOrientation()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param screenOrientation Orientation of the display.
|
||||
* @param cameraOrientation Orientation of the camera sensor.
|
||||
* @param cameraIsMirrored `true` if camera is mirrored (typically that is the case for
|
||||
* front cameras). `false` if it is not mirrored.
|
||||
*
|
||||
* @return display orientation in which user will see the output camera in a correct rotation.
|
||||
*/
|
||||
internal fun computeDisplayOrientation(
|
||||
screenOrientation: Orientation,
|
||||
cameraOrientation: Orientation,
|
||||
cameraIsMirrored: Boolean
|
||||
): Orientation {
|
||||
val screenOrientationDegrees = screenOrientation.degrees
|
||||
val cameraRotationDegrees = cameraOrientation.degrees
|
||||
|
||||
val rotation = if (cameraIsMirrored) {
|
||||
(360 - (cameraRotationDegrees + screenOrientationDegrees) % 360) % 360
|
||||
} else {
|
||||
(cameraRotationDegrees - screenOrientationDegrees + 360) % 360
|
||||
}
|
||||
|
||||
return rotation.toOrientation()
|
||||
}
|
||||
|
||||
+31
-19
@@ -2,43 +2,55 @@ package io.fotoapparat.hardware.orientation
|
||||
|
||||
import android.content.Context
|
||||
import io.fotoapparat.hardware.Device
|
||||
|
||||
import io.fotoapparat.hardware.orientation.Orientation.Vertical.Portrait
|
||||
|
||||
/**
|
||||
* Monitors orientation of the device.
|
||||
*/
|
||||
open internal class OrientationSensor(
|
||||
internal open class OrientationSensor(
|
||||
private val rotationListener: RotationListener,
|
||||
private val device: Device
|
||||
) {
|
||||
|
||||
constructor(context: Context,
|
||||
device: Device
|
||||
private lateinit var listener: (OrientationState) -> Unit
|
||||
private val onOrientationChanged: (DeviceRotationDegrees) -> Unit = { deviceRotation ->
|
||||
deviceRotation.toClosestRightAngle()
|
||||
.toOrientation()
|
||||
.let { deviceOrientation ->
|
||||
val screenOrientation = device.getScreenOrientation()
|
||||
|
||||
val newState = OrientationState(
|
||||
deviceOrientation = deviceOrientation,
|
||||
screenOrientation = screenOrientation
|
||||
)
|
||||
|
||||
if (newState != lastKnownOrientationState) {
|
||||
lastKnownOrientationState = newState
|
||||
listener(newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
open var lastKnownOrientationState: OrientationState = OrientationState(
|
||||
deviceOrientation = Portrait,
|
||||
screenOrientation = device.getScreenOrientation()
|
||||
)
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
device: Device
|
||||
) : this(
|
||||
RotationListener(context),
|
||||
device
|
||||
)
|
||||
|
||||
private val onOrientationChanged = {
|
||||
device.getScreenRotation().let {
|
||||
if (it != lastKnownRotation) {
|
||||
listener(it)
|
||||
lastKnownRotation = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
rotationListener.orientationChanged = onOrientationChanged
|
||||
}
|
||||
|
||||
private lateinit var listener: (Int) -> Unit
|
||||
private var lastKnownRotation: Int = 0
|
||||
|
||||
/**
|
||||
* Starts monitoring device's orientation.
|
||||
* Starts monitoring device's orientation state.
|
||||
*/
|
||||
open fun start(listener: (Int) -> Unit) {
|
||||
open fun start(listener: (OrientationState) -> Unit) {
|
||||
this.listener = listener
|
||||
rotationListener.enable()
|
||||
}
|
||||
@@ -50,4 +62,4 @@ open internal class OrientationSensor(
|
||||
rotationListener.disable()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.fotoapparat.hardware.orientation
|
||||
|
||||
/**
|
||||
* Phone orientation states.
|
||||
*/
|
||||
data class OrientationState(
|
||||
/**
|
||||
* The current orientation the device is being hold.
|
||||
*/
|
||||
val deviceOrientation: DeviceOrientation,
|
||||
|
||||
/**
|
||||
* The current orientation of the screen.
|
||||
*/
|
||||
val screenOrientation: ScreenOrientation
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.fotoapparat.hardware.orientation
|
||||
|
||||
typealias DeviceRotationDegrees = Int
|
||||
|
||||
/**
|
||||
* @return closest right angle to given value. That is: 0, 90, 180, 270.
|
||||
*/
|
||||
internal fun Int.toClosestRightAngle(): Int {
|
||||
val roundUp = this % 90 > 45
|
||||
|
||||
val roundAppModifier = if (roundUp) 1 else 0
|
||||
|
||||
return (this / 90 + roundAppModifier) * 90 % 360
|
||||
}
|
||||
@@ -3,20 +3,19 @@ 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.
|
||||
*/
|
||||
open internal class RotationListener(
|
||||
internal open class RotationListener(
|
||||
context: Context
|
||||
) : OrientationEventListener(context) {
|
||||
|
||||
lateinit var orientationChanged: () -> Unit
|
||||
lateinit var orientationChanged: (DeviceRotationDegrees) -> Unit
|
||||
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
override fun onOrientationChanged(orientation: DeviceRotationDegrees) {
|
||||
if (canDetectOrientation()) {
|
||||
orientationChanged()
|
||||
orientationChanged(orientation)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ import java.io.IOException
|
||||
*/
|
||||
class FileLogger(private val file: File) : Logger {
|
||||
|
||||
private val writer: FileWriter by lazy {
|
||||
FileWriter(file)
|
||||
}
|
||||
private val writer: FileWriter by lazy { FileWriter(file) }
|
||||
|
||||
override fun log(message: String) {
|
||||
try {
|
||||
|
||||
@@ -19,11 +19,7 @@ fun logcat(): Logger = LogcatLogger()
|
||||
*
|
||||
* Note: if file is not writable, no errors will be produced.
|
||||
*/
|
||||
fun fileLogger(file: File): Logger {
|
||||
return BackgroundThreadLogger(
|
||||
FileLogger(file)
|
||||
)
|
||||
}
|
||||
fun fileLogger(file: File): Logger = BackgroundThreadLogger(FileLogger(file))
|
||||
|
||||
/**
|
||||
* @return logger which prints logs to file located at `context.getExternalFilesDir("logs")`.
|
||||
|
||||
@@ -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,41 +1,33 @@
|
||||
package io.fotoapparat.parameter
|
||||
|
||||
import android.support.annotation.IntRange
|
||||
import androidx.annotation.IntRange
|
||||
|
||||
/**
|
||||
* Resolution. Units in pixels.
|
||||
*/
|
||||
data class Resolution(
|
||||
|
||||
@JvmField
|
||||
@IntRange(from = 0L, to = Long.MAX_VALUE)
|
||||
val width: Int,
|
||||
@JvmField
|
||||
@IntRange(from = 0L, to = Long.MAX_VALUE)
|
||||
val height: Int
|
||||
@[JvmField IntRange(from = 0L)] val width: Int,
|
||||
@[JvmField IntRange(from = 0L)] val height: Int
|
||||
) : Parameter {
|
||||
|
||||
/**
|
||||
* The total area this [Resolution] is covering.
|
||||
*/
|
||||
val area by lazy {
|
||||
width * height
|
||||
}
|
||||
val area: Int get() = width * height
|
||||
|
||||
/**
|
||||
* The aspect ratio for this size. [Float.NaN] if invalid dimensions.
|
||||
*/
|
||||
val aspectRatio 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(height, width)
|
||||
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,26 @@ 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
|
||||
*/
|
||||
val maxNumFocusAreas by lazy {
|
||||
cameraParameters.maxNumFocusAreas
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Camera.Parameters.getMaxNumMeteringAreas
|
||||
*/
|
||||
val maxNumMeteringAreas by lazy {
|
||||
cameraParameters.maxNumMeteringAreas
|
||||
}
|
||||
}
|
||||
|
||||
private val supportedSensitivitiesKeys = listOf("iso-values", "iso-mode-values", "iso-speed-values", "nv-picture-iso-values")
|
||||
|
||||
@@ -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() +
|
||||
"sensorSensitivity:" + sensorSensitivity.wrap() +
|
||||
"pictureResolution:" + pictureResolution.wrap() +
|
||||
"previewResolution:" + previewResolution.wrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -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()
|
||||
}
|
||||
|
||||
+15
-17
@@ -11,15 +11,14 @@ import io.fotoapparat.parameter.AntiBandingMode
|
||||
* @receiver Code of the anti banding mode as in [Camera.Parameters].
|
||||
* @return The [io.fotoapparat.Fotoapparat]'s camera [AntiBandingMode]. `null` if camera code is not supported.
|
||||
*/
|
||||
fun String.toAntiBandingMode(): AntiBandingMode? {
|
||||
return when (this) {
|
||||
Camera.Parameters.ANTIBANDING_AUTO -> AntiBandingMode.Auto
|
||||
Camera.Parameters.ANTIBANDING_50HZ -> AntiBandingMode.HZ50
|
||||
Camera.Parameters.ANTIBANDING_60HZ -> AntiBandingMode.HZ60
|
||||
Camera.Parameters.ANTIBANDING_OFF -> AntiBandingMode.None
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
fun String.toAntiBandingMode(): AntiBandingMode? =
|
||||
when (this) {
|
||||
Camera.Parameters.ANTIBANDING_AUTO -> AntiBandingMode.Auto
|
||||
Camera.Parameters.ANTIBANDING_50HZ -> AntiBandingMode.HZ50
|
||||
Camera.Parameters.ANTIBANDING_60HZ -> AntiBandingMode.HZ60
|
||||
Camera.Parameters.ANTIBANDING_OFF -> AntiBandingMode.None
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a [AntiBandingMode] to a antiBandingMode mode code as in [Camera.Parameters].
|
||||
@@ -27,11 +26,10 @@ fun String.toAntiBandingMode(): AntiBandingMode? {
|
||||
* @receiver The [io.fotoapparat.Fotoapparat]'s camera [AntiBandingMode].
|
||||
* @return anti banding mode code as in [Camera.Parameters].
|
||||
*/
|
||||
fun AntiBandingMode.toCode(): String {
|
||||
return when (this) {
|
||||
AntiBandingMode.Auto -> Camera.Parameters.ANTIBANDING_AUTO
|
||||
AntiBandingMode.HZ50 -> Camera.Parameters.ANTIBANDING_50HZ
|
||||
AntiBandingMode.HZ60 -> Camera.Parameters.ANTIBANDING_60HZ
|
||||
AntiBandingMode.None -> Camera.Parameters.ANTIBANDING_OFF
|
||||
}
|
||||
}
|
||||
fun AntiBandingMode.toCode(): String =
|
||||
when (this) {
|
||||
AntiBandingMode.Auto -> Camera.Parameters.ANTIBANDING_AUTO
|
||||
AntiBandingMode.HZ50 -> Camera.Parameters.ANTIBANDING_50HZ
|
||||
AntiBandingMode.HZ60 -> Camera.Parameters.ANTIBANDING_60HZ
|
||||
AntiBandingMode.None -> Camera.Parameters.ANTIBANDING_OFF
|
||||
}
|
||||
+17
-19
@@ -11,16 +11,15 @@ import io.fotoapparat.parameter.Flash
|
||||
* @receiver Code of flash mode as in [Camera.Parameters].
|
||||
* @return [Flash] from given camera code. `null` if camera code is not supported.
|
||||
*/
|
||||
internal fun String.toFlash(): Flash? {
|
||||
return when (this) {
|
||||
Camera.Parameters.FLASH_MODE_ON -> Flash.On
|
||||
Camera.Parameters.FLASH_MODE_OFF -> Flash.Off
|
||||
Camera.Parameters.FLASH_MODE_AUTO -> Flash.Auto
|
||||
Camera.Parameters.FLASH_MODE_TORCH -> Flash.Torch
|
||||
Camera.Parameters.FLASH_MODE_RED_EYE -> Flash.AutoRedEye
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
internal fun String.toFlash(): Flash? =
|
||||
when (this) {
|
||||
Camera.Parameters.FLASH_MODE_ON -> Flash.On
|
||||
Camera.Parameters.FLASH_MODE_OFF -> Flash.Off
|
||||
Camera.Parameters.FLASH_MODE_AUTO -> Flash.Auto
|
||||
Camera.Parameters.FLASH_MODE_TORCH -> Flash.Torch
|
||||
Camera.Parameters.FLASH_MODE_RED_EYE -> Flash.AutoRedEye
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps between [Flash] and Camera v1 flash codes.
|
||||
@@ -28,12 +27,11 @@ internal fun String.toFlash(): Flash? {
|
||||
* @receiver Flash mode.
|
||||
* @return code of the flash mode as in [Camera.Parameters].
|
||||
*/
|
||||
internal fun Flash.toCode(): String {
|
||||
return when (this) {
|
||||
Flash.On -> Camera.Parameters.FLASH_MODE_ON
|
||||
Flash.Off -> Camera.Parameters.FLASH_MODE_OFF
|
||||
Flash.Auto -> Camera.Parameters.FLASH_MODE_AUTO
|
||||
Flash.Torch -> Camera.Parameters.FLASH_MODE_TORCH
|
||||
Flash.AutoRedEye -> Camera.Parameters.FLASH_MODE_RED_EYE
|
||||
}
|
||||
}
|
||||
internal fun Flash.toCode(): String =
|
||||
when (this) {
|
||||
Flash.On -> Camera.Parameters.FLASH_MODE_ON
|
||||
Flash.Off -> Camera.Parameters.FLASH_MODE_OFF
|
||||
Flash.Auto -> Camera.Parameters.FLASH_MODE_AUTO
|
||||
Flash.Torch -> Camera.Parameters.FLASH_MODE_TORCH
|
||||
Flash.AutoRedEye -> Camera.Parameters.FLASH_MODE_RED_EYE
|
||||
}
|
||||
+21
-23
@@ -12,18 +12,17 @@ import io.fotoapparat.parameter.FocusMode
|
||||
* @receiver Code of focus mode as in [Camera.Parameters].
|
||||
* @return [FocusMode] from given camera code. `null` if camera code is not supported.
|
||||
*/
|
||||
internal fun String.toFocusMode(): FocusMode? {
|
||||
return when (this) {
|
||||
Camera.Parameters.FOCUS_MODE_EDOF -> FocusMode.Edof
|
||||
Camera.Parameters.FOCUS_MODE_AUTO -> FocusMode.Auto
|
||||
Camera.Parameters.FOCUS_MODE_MACRO -> FocusMode.Macro
|
||||
Camera.Parameters.FOCUS_MODE_FIXED -> FocusMode.Fixed
|
||||
Camera.Parameters.FOCUS_MODE_INFINITY -> FocusMode.Infinity
|
||||
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO -> FocusMode.ContinuousFocusVideo
|
||||
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE -> FocusMode.ContinuousFocusPicture
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
internal fun String.toFocusMode(): FocusMode? =
|
||||
when (this) {
|
||||
Camera.Parameters.FOCUS_MODE_EDOF -> FocusMode.Edof
|
||||
Camera.Parameters.FOCUS_MODE_AUTO -> FocusMode.Auto
|
||||
Camera.Parameters.FOCUS_MODE_MACRO -> FocusMode.Macro
|
||||
Camera.Parameters.FOCUS_MODE_FIXED -> FocusMode.Fixed
|
||||
Camera.Parameters.FOCUS_MODE_INFINITY -> FocusMode.Infinity
|
||||
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO -> FocusMode.ContinuousFocusVideo
|
||||
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE -> FocusMode.ContinuousFocusPicture
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps between [FocusMode] and Camera v1 focus codes.
|
||||
@@ -31,14 +30,13 @@ internal fun String.toFocusMode(): FocusMode? {
|
||||
* @receiver FocusMode mode.
|
||||
* @return code of the focus mode as in [Camera.Parameters].
|
||||
*/
|
||||
internal fun FocusMode.toCode(): String {
|
||||
return when (this) {
|
||||
FocusMode.Edof -> Camera.Parameters.FOCUS_MODE_EDOF
|
||||
FocusMode.Auto -> Camera.Parameters.FOCUS_MODE_AUTO
|
||||
FocusMode.Macro -> Camera.Parameters.FOCUS_MODE_MACRO
|
||||
FocusMode.Fixed -> Camera.Parameters.FOCUS_MODE_FIXED
|
||||
FocusMode.Infinity -> Camera.Parameters.FOCUS_MODE_INFINITY
|
||||
FocusMode.ContinuousFocusVideo -> Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO
|
||||
FocusMode.ContinuousFocusPicture -> Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
|
||||
}
|
||||
}
|
||||
internal fun FocusMode.toCode(): String =
|
||||
when (this) {
|
||||
FocusMode.Edof -> Camera.Parameters.FOCUS_MODE_EDOF
|
||||
FocusMode.Auto -> Camera.Parameters.FOCUS_MODE_AUTO
|
||||
FocusMode.Macro -> Camera.Parameters.FOCUS_MODE_MACRO
|
||||
FocusMode.Fixed -> Camera.Parameters.FOCUS_MODE_FIXED
|
||||
FocusMode.Infinity -> Camera.Parameters.FOCUS_MODE_INFINITY
|
||||
FocusMode.ContinuousFocusVideo -> Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO
|
||||
FocusMode.ContinuousFocusPicture -> Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
|
||||
}
|
||||
+5
-6
@@ -8,9 +8,8 @@ import io.fotoapparat.parameter.FpsRange
|
||||
/**
|
||||
* Converts a [IntArray] into a [FpsRange].
|
||||
*/
|
||||
internal fun IntArray.toFpsRange(): FpsRange {
|
||||
return FpsRange(
|
||||
this[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
|
||||
this[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]
|
||||
)
|
||||
}
|
||||
internal fun IntArray.toFpsRange(): FpsRange =
|
||||
FpsRange(
|
||||
this[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
|
||||
this[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]
|
||||
)
|
||||
|
||||
+1
-6
@@ -8,9 +8,4 @@ import io.fotoapparat.parameter.Resolution
|
||||
/**
|
||||
* Converts [Camera.Size] to [Resolution].
|
||||
*/
|
||||
fun Camera.Size.toResolution(): Resolution {
|
||||
return Resolution(
|
||||
width,
|
||||
height
|
||||
)
|
||||
}
|
||||
fun Camera.Size.toResolution(): Resolution = Resolution(width, height)
|
||||
|
||||
+35
-41
@@ -8,10 +8,7 @@ import io.fotoapparat.hardware.CameraDevice
|
||||
import io.fotoapparat.parameter.Parameter
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
import io.fotoapparat.parameter.camera.CameraParameters
|
||||
import io.fotoapparat.selector.aspectRatio
|
||||
import io.fotoapparat.selector.filtered
|
||||
import io.fotoapparat.selector.firstAvailable
|
||||
|
||||
import io.fotoapparat.selector.*
|
||||
|
||||
/**
|
||||
* @return [CameraParameters] which will be used by [CameraDevice].
|
||||
@@ -34,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,
|
||||
@@ -46,7 +44,7 @@ internal fun getCameraParameters(
|
||||
|
||||
private fun validPreviewSizeSelector(
|
||||
resolution: Resolution,
|
||||
original: Iterable<Resolution>.() -> Resolution?
|
||||
original: ResolutionSelector
|
||||
) = firstAvailable(
|
||||
filtered(
|
||||
selector = aspectRatio(
|
||||
@@ -60,30 +58,28 @@ private fun validPreviewSizeSelector(
|
||||
original
|
||||
)
|
||||
|
||||
private infix fun <T> (Collection<T>.() -> T?)?.selectOptionalFrom(supportedParameters: Set<T>): T? {
|
||||
return this?.run { this(supportedParameters) }
|
||||
}
|
||||
private infix fun <T> (Collection<T>.() -> T?)?.selectOptionalFrom(supportedParameters: Set<T>): T? =
|
||||
this?.run { this(supportedParameters) }
|
||||
|
||||
private inline infix fun <reified T : Parameter> (Collection<T>.() -> T?).selectFrom(supportedParameters: Set<T>): T {
|
||||
return this(supportedParameters)
|
||||
.ensureSelected(supportedParameters)
|
||||
.ensureInCollection(supportedParameters)
|
||||
}
|
||||
private inline infix fun <reified T : Parameter> (Collection<T>.() -> T?).selectFrom(
|
||||
supportedParameters: Set<T>
|
||||
): T = this(supportedParameters)
|
||||
.ensureSelected(supportedParameters)
|
||||
.ensureInCollection(supportedParameters)
|
||||
|
||||
|
||||
private infix fun (IntRange.() -> Int?).selectFrom(supportedParameters: IntRange): Int {
|
||||
return this(supportedParameters)
|
||||
.ensureSelected(
|
||||
supportedParameters = supportedParameters,
|
||||
configurationName = "Jpeg quality"
|
||||
)
|
||||
.ensureInCollection(supportedParameters)
|
||||
}
|
||||
private infix fun QualitySelector.selectFrom(supportedParameters: IntRange): Int =
|
||||
this(supportedParameters)
|
||||
.ensureSelected(
|
||||
supportedParameters = supportedParameters,
|
||||
configurationName = "Jpeg quality"
|
||||
)
|
||||
.ensureInCollection(supportedParameters)
|
||||
|
||||
private inline fun <reified Param : Parameter> Param.ensureInCollection(supportedParameters: Set<Param>): Param {
|
||||
return if (supportedParameters.contains(this)) {
|
||||
this
|
||||
} else {
|
||||
private inline fun <reified Param : Parameter> Param.ensureInCollection(
|
||||
supportedParameters: Set<Param>
|
||||
): Param = apply {
|
||||
if (this !in supportedParameters) {
|
||||
throw InvalidConfigurationException(
|
||||
value = this,
|
||||
klass = Param::class.java,
|
||||
@@ -92,10 +88,10 @@ private inline fun <reified Param : Parameter> Param.ensureInCollection(supporte
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified Param : Comparable<Param>> Param.ensureInCollection(supportedRange: ClosedRange<Param>): Param {
|
||||
return if (supportedRange.contains(this)) {
|
||||
this
|
||||
} else {
|
||||
private inline fun <reified Param : Comparable<Param>> Param.ensureInCollection(
|
||||
supportedRange: ClosedRange<Param>
|
||||
): Param = apply {
|
||||
if (this !in supportedRange) {
|
||||
throw InvalidConfigurationException(
|
||||
value = this,
|
||||
klass = Param::class.java,
|
||||
@@ -105,19 +101,17 @@ private inline fun <reified Param : Comparable<Param>> Param.ensureInCollection(
|
||||
}
|
||||
|
||||
|
||||
private inline fun <reified Param : Parameter> Param?.ensureSelected(supportedParameters: Collection<Parameter>): Param {
|
||||
return this ?: throw UnsupportedConfigurationException(
|
||||
klass = Param::class.java,
|
||||
supportedParameters = supportedParameters
|
||||
)
|
||||
}
|
||||
private inline fun <reified Param : Parameter> Param?.ensureSelected(
|
||||
supportedParameters: Collection<Parameter>
|
||||
): Param = this ?: throw UnsupportedConfigurationException(
|
||||
klass = Param::class.java,
|
||||
supportedParameters = supportedParameters
|
||||
)
|
||||
|
||||
private inline fun <reified Param : Comparable<Param>> Param?.ensureSelected(
|
||||
supportedParameters: ClosedRange<Param>,
|
||||
configurationName: String
|
||||
): Param {
|
||||
return this ?: throw UnsupportedConfigurationException(
|
||||
configurationName = configurationName,
|
||||
supportedRange = supportedParameters
|
||||
)
|
||||
}
|
||||
): Param = this ?: throw UnsupportedConfigurationException(
|
||||
configurationName = configurationName,
|
||||
supportedRange = supportedParameters
|
||||
)
|
||||
|
||||
@@ -10,14 +10,12 @@ import android.hardware.Camera
|
||||
* Empty list of values will be returned
|
||||
*/
|
||||
internal fun Camera.Parameters.extractRawCameraValues(keys: List<String>): List<String> {
|
||||
keys.forEach {
|
||||
getValuesForKey(key = it)
|
||||
?.let { return it }
|
||||
keys.forEach { key ->
|
||||
getValuesForKey(key)?.let { params -> return params }
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun Camera.Parameters.getValuesForKey(key: String): List<String>? {
|
||||
return get(key)?.split(regex = ",".toRegex())
|
||||
}
|
||||
private fun Camera.Parameters.getValuesForKey(key: String): List<String>? =
|
||||
get(key)?.split(regex = ",".toRegex())
|
||||
@@ -2,9 +2,9 @@ package io.fotoapparat.preview
|
||||
|
||||
/**
|
||||
* Performs processing on preview frames.
|
||||
* <p>
|
||||
*
|
||||
* Frame processors are called from worker thread (aka non-UI thread). After
|
||||
* {@link #processFrame(Frame)} completes the frame is returned back to the pool where it is reused
|
||||
* [.processFrame] completes the frame is returned back to the pool where it is reused
|
||||
* afterwards. This means that implementations should take special care to not do any operations on
|
||||
* frame after method completes.
|
||||
*/
|
||||
@@ -14,7 +14,7 @@ interface FrameProcessor {
|
||||
* Performs processing on preview frames. Read class description for more details.
|
||||
*
|
||||
* @param frame frame of the preview. Do not cache it as it will eventually be reused by the
|
||||
* camera.
|
||||
* camera.
|
||||
*/
|
||||
fun process(frame: Frame)
|
||||
}
|
||||
@@ -5,7 +5,9 @@ package io.fotoapparat.preview
|
||||
import android.graphics.ImageFormat
|
||||
import android.hardware.Camera
|
||||
import io.fotoapparat.hardware.frameProcessingExecutor
|
||||
import io.fotoapparat.hardware.orientation.Orientation
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
import io.fotoapparat.util.FrameProcessor
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@@ -13,19 +15,19 @@ import java.util.*
|
||||
*/
|
||||
internal class PreviewStream(private val camera: Camera) {
|
||||
|
||||
private val frameProcessors = LinkedHashSet<(Frame) -> Unit>()
|
||||
private val frameProcessors = LinkedHashSet<FrameProcessor>()
|
||||
|
||||
private var previewResolution: Resolution? = null
|
||||
|
||||
/**
|
||||
* CW rotation of frames in degrees.
|
||||
* CW orientation.
|
||||
*/
|
||||
var frameOrientation = 0
|
||||
var frameOrientation: Orientation = Orientation.Vertical.Portrait
|
||||
|
||||
/**
|
||||
* Clears all processors.
|
||||
*/
|
||||
fun clearProcessors() {
|
||||
private fun clearProcessors() {
|
||||
synchronized(frameProcessors) {
|
||||
frameProcessors.clear()
|
||||
}
|
||||
@@ -34,7 +36,7 @@ internal class PreviewStream(private val camera: Camera) {
|
||||
/**
|
||||
* Registers new processor. If processor was already added before, does nothing.
|
||||
*/
|
||||
fun addProcessor(processor: (Frame) -> Unit) {
|
||||
private fun addProcessor(processor: FrameProcessor) {
|
||||
synchronized(frameProcessors) {
|
||||
frameProcessors.add(processor)
|
||||
}
|
||||
@@ -43,7 +45,7 @@ internal class PreviewStream(private val camera: Camera) {
|
||||
/**
|
||||
* Starts preview stream. After preview is started frame processors will start receiving frames.
|
||||
*/
|
||||
fun start() {
|
||||
private fun start() {
|
||||
camera.addFrameToBuffer()
|
||||
|
||||
camera.setPreviewCallbackWithBuffer { data, _ -> dispatchFrameOnBackgroundThread(data) }
|
||||
@@ -52,10 +54,23 @@ internal class PreviewStream(private val camera: Camera) {
|
||||
/**
|
||||
* Stops preview stream.
|
||||
*/
|
||||
fun stop() {
|
||||
private fun stop() {
|
||||
camera.setPreviewCallbackWithBuffer(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the frame processor safely.
|
||||
*/
|
||||
fun updateProcessorSafely(frameProcessor: FrameProcessor?) {
|
||||
clearProcessors()
|
||||
if (frameProcessor == null) {
|
||||
stop()
|
||||
} else {
|
||||
addProcessor(frameProcessor)
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Camera.addFrameToBuffer() {
|
||||
addCallbackBuffer(parameters.allocateBuffer())
|
||||
}
|
||||
@@ -85,19 +100,19 @@ internal class PreviewStream(private val camera: Camera) {
|
||||
val frame = Frame(
|
||||
size = previewResolution,
|
||||
image = image,
|
||||
rotation = frameOrientation
|
||||
rotation = frameOrientation.degrees
|
||||
)
|
||||
|
||||
frameProcessors.forEach {
|
||||
it(frame)
|
||||
it.invoke(frame)
|
||||
}
|
||||
|
||||
returnFrameToBuffer(frame)
|
||||
}
|
||||
|
||||
private fun ensurePreviewSizeAvailable(): Resolution {
|
||||
return previewResolution ?: throw IllegalStateException("previewSize is null. Frame was not added?")
|
||||
}
|
||||
private fun ensurePreviewSizeAvailable(): Resolution =
|
||||
previewResolution
|
||||
?: throw IllegalStateException("previewSize is null. Frame was not added?")
|
||||
|
||||
private fun returnFrameToBuffer(frame: Frame) {
|
||||
camera.addCallbackBuffer(
|
||||
@@ -107,9 +122,8 @@ internal class PreviewStream(private val camera: Camera) {
|
||||
|
||||
}
|
||||
|
||||
private fun Camera.Size.bytesPerFrame(): Int {
|
||||
return width * height * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8
|
||||
}
|
||||
private fun Camera.Size.bytesPerFrame(): Int =
|
||||
width * height * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8
|
||||
|
||||
private fun Camera.Parameters.ensureNv21Format() {
|
||||
if (previewFormat != ImageFormat.NV21) {
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
package io.fotoapparat.result
|
||||
|
||||
import io.fotoapparat.capability.Capabilities
|
||||
import io.fotoapparat.exception.RecoverableRuntimeException
|
||||
import io.fotoapparat.concurrent.ensureBackgroundThread
|
||||
import io.fotoapparat.exception.UnableToDecodeBitmapException
|
||||
import io.fotoapparat.hardware.executeMainThread
|
||||
import io.fotoapparat.hardware.pendingResultExecutor
|
||||
@@ -9,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.
|
||||
@@ -22,13 +21,8 @@ internal constructor(
|
||||
) {
|
||||
private val resultUnsafe: T
|
||||
get() {
|
||||
return try {
|
||||
future.get()
|
||||
} catch (e: InterruptedException) {
|
||||
throw RecoverableRuntimeException(e)
|
||||
} catch (e: ExecutionException) {
|
||||
throw RecoverableRuntimeException(e)
|
||||
}
|
||||
ensureBackgroundThread()
|
||||
return future.get()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,9 +33,7 @@ internal constructor(
|
||||
* @return [PendingResult] of another type.
|
||||
*/
|
||||
fun <R> transform(transformer: (T) -> R): PendingResult<R> {
|
||||
val transformTask = FutureTask(Callable<R> {
|
||||
transformer(future.get())
|
||||
})
|
||||
val transformTask = FutureTask { transformer(future.get()) }
|
||||
|
||||
executor.execute(transformTask)
|
||||
|
||||
@@ -60,9 +52,7 @@ internal constructor(
|
||||
* @throws InterruptedException if the thread has been interrupted.
|
||||
*/
|
||||
@Throws(ExecutionException::class, InterruptedException::class)
|
||||
fun await(): T {
|
||||
return future.get()
|
||||
}
|
||||
fun await(): T = future.get()
|
||||
|
||||
/**
|
||||
* Adapts the resulting object to a different type.
|
||||
@@ -71,9 +61,7 @@ internal constructor(
|
||||
* type.
|
||||
* @return result adapted to a new type.
|
||||
*/
|
||||
fun <R> adapt(adapter: (Future<T>) -> R): R {
|
||||
return adapter(future)
|
||||
}
|
||||
fun <R> adapt(adapter: (Future<T>) -> R): R = adapter(future)
|
||||
|
||||
/**
|
||||
* Notifies given callback as soon as result is available. Callback will always be notified on
|
||||
@@ -84,12 +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")
|
||||
} catch (e: RecoverableRuntimeException) {
|
||||
logger.log("Couldn't deliver pending result.")
|
||||
callback(null)
|
||||
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.")
|
||||
notifyOnMainThread {
|
||||
callback(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ internal constructor(
|
||||
* Alias for [PendingResult.whenAvailable] for java.
|
||||
*/
|
||||
fun whenDone(callback: WhenDoneListener<T>) {
|
||||
whenAvailable { callback.whenDone(it) }
|
||||
whenAvailable(callback::whenDone)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -117,9 +117,9 @@ internal constructor(
|
||||
|
||||
}
|
||||
|
||||
private fun <T> T.notifyCallbackOnMainThread(callback: (T) -> Unit) {
|
||||
private fun notifyOnMainThread(function: () -> Unit) {
|
||||
executeMainThread {
|
||||
callback(this)
|
||||
function()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.fotoapparat.result
|
||||
|
||||
import java.util.*
|
||||
import android.graphics.BitmapFactory
|
||||
import java.util.Arrays
|
||||
|
||||
/**
|
||||
* Taken photo.
|
||||
@@ -23,6 +24,25 @@ data class Photo(
|
||||
val rotationDegrees: Int
|
||||
) {
|
||||
|
||||
/**
|
||||
* The height of the photo.
|
||||
*/
|
||||
val height: Int
|
||||
get() = decodedBounds.outHeight
|
||||
|
||||
/**
|
||||
* The width of the photo.
|
||||
*/
|
||||
val width: Int
|
||||
get() = decodedBounds.outWidth
|
||||
|
||||
private val decodedBounds by lazy {
|
||||
BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
BitmapFactory.decodeByteArray(encodedImage, 0, encodedImage.size, this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
@@ -41,17 +61,14 @@ data class Photo(
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
override fun toString(): String =
|
||||
"Photo(encodedImage=ByteArray(${encodedImage.size}) rotationDegrees=$rotationDegrees)"
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* @return empty [Photo].
|
||||
*/
|
||||
internal fun empty(): Photo {
|
||||
return Photo(
|
||||
encodedImage = ByteArray(0),
|
||||
rotationDegrees = 0
|
||||
)
|
||||
}
|
||||
internal fun empty(): Photo = Photo(encodedImage = ByteArray(0), rotationDegrees = 0)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import android.graphics.Bitmap
|
||||
import io.fotoapparat.exception.FileSaveException
|
||||
import io.fotoapparat.exif.ExifWriter
|
||||
import io.fotoapparat.log.Logger
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
import io.fotoapparat.result.transformer.BitmapPhotoTransformer
|
||||
import io.fotoapparat.result.transformer.ResolutionTransformer
|
||||
import io.fotoapparat.result.transformer.SaveToFileTransformer
|
||||
import io.fotoapparat.result.transformer.originalResolution
|
||||
import java.io.File
|
||||
@@ -25,9 +25,10 @@ class PhotoResult internal constructor(private val pendingResult: PendingResult<
|
||||
* future.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun toBitmap(sizeTransformer: (Resolution) -> Resolution = originalResolution()): PendingResult<BitmapPhoto> {
|
||||
return pendingResult.transform(BitmapPhotoTransformer(sizeTransformer))
|
||||
}
|
||||
fun toBitmap(
|
||||
sizeTransformer: ResolutionTransformer = originalResolution()
|
||||
): PendingResult<BitmapPhoto> =
|
||||
pendingResult.transform(BitmapPhotoTransformer(sizeTransformer))
|
||||
|
||||
/**
|
||||
* Saves result to file.
|
||||
@@ -35,19 +36,16 @@ class PhotoResult internal constructor(private val pendingResult: PendingResult<
|
||||
* @return pending operation which completes when photo is saved to file.
|
||||
* @throws FileSaveException If the file cannot be saved.
|
||||
*/
|
||||
fun saveToFile(file: File): PendingResult<Unit> {
|
||||
return pendingResult.transform(SaveToFileTransformer(
|
||||
file = file,
|
||||
exifOrientationWriter = ExifWriter
|
||||
))
|
||||
}
|
||||
fun saveToFile(file: File): PendingResult<Unit> =
|
||||
pendingResult.transform(SaveToFileTransformer(
|
||||
file = file,
|
||||
exifOrientationWriter = ExifWriter
|
||||
))
|
||||
|
||||
/**
|
||||
* @return result as [PendingResult].
|
||||
*/
|
||||
fun toPendingResult(): PendingResult<Photo> {
|
||||
return pendingResult
|
||||
}
|
||||
fun toPendingResult(): PendingResult<Photo> = pendingResult
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
+23
-12
@@ -11,7 +11,7 @@ import io.fotoapparat.result.Photo
|
||||
* Creates [BitmapPhoto] out of [Photo].
|
||||
*/
|
||||
internal class BitmapPhotoTransformer(
|
||||
private val sizeTransformer: (Resolution) -> Resolution
|
||||
private val sizeTransformer: ResolutionTransformer
|
||||
) : (Photo) -> BitmapPhoto {
|
||||
|
||||
override fun invoke(input: Photo): BitmapPhoto {
|
||||
@@ -23,9 +23,8 @@ internal class BitmapPhotoTransformer(
|
||||
desiredResolution = desiredResolution
|
||||
)
|
||||
|
||||
val decodedBitmap = input.decodeBitmap(scaleFactor)
|
||||
|
||||
decodedBitmap ?: throw UnableToDecodeBitmapException()
|
||||
val decodedBitmap = input.decodeBitmap(scaleFactor, originalResolution, desiredResolution)
|
||||
?: throw UnableToDecodeBitmapException()
|
||||
|
||||
val bitmap = if (decodedBitmap.width == desiredResolution.width && decodedBitmap.height == desiredResolution.height) {
|
||||
decodedBitmap
|
||||
@@ -46,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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,9 +90,7 @@ private fun Photo.readResolution(): Resolution {
|
||||
private fun computeScaleFactor(
|
||||
originalResolution: Resolution,
|
||||
desiredResolution: Resolution
|
||||
): Float {
|
||||
return Math.min(
|
||||
originalResolution.width / desiredResolution.width.toFloat(),
|
||||
originalResolution.height / desiredResolution.height.toFloat()
|
||||
)
|
||||
}
|
||||
): Float = Math.min(
|
||||
originalResolution.width / desiredResolution.width.toFloat(),
|
||||
originalResolution.height / desiredResolution.height.toFloat()
|
||||
)
|
||||
+4
-5
@@ -2,21 +2,20 @@ package io.fotoapparat.result.transformer
|
||||
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
|
||||
typealias ResolutionTransformer = (Resolution) -> Resolution
|
||||
|
||||
/**
|
||||
* @return Transforming function which always returns the same size as it receives.
|
||||
*/
|
||||
fun originalResolution(): (Resolution) -> Resolution = {
|
||||
it
|
||||
}
|
||||
fun originalResolution(): ResolutionTransformer = { it }
|
||||
|
||||
/**
|
||||
* @param scaleFactor scale factor which would be applied to image.
|
||||
* @return Transforming function which returns size scaled by given factor.
|
||||
*/
|
||||
fun scaled(scaleFactor: Float): (Resolution) -> Resolution = { input ->
|
||||
fun scaled(scaleFactor: Float): ResolutionTransformer = { input ->
|
||||
Resolution(
|
||||
width = (input.width * scaleFactor).toInt(),
|
||||
height = (input.height * scaleFactor).toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ internal class SaveToFileTransformer(
|
||||
try {
|
||||
saveImage(input, outputStream)
|
||||
|
||||
exifOrientationWriter.writeExifOrientation(file, input)
|
||||
exifOrientationWriter.writeExifOrientation(file, input.rotationDegrees)
|
||||
} catch (e: IOException) {
|
||||
throw FileSaveException(e)
|
||||
}
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
package io.fotoapparat.routine.camera
|
||||
|
||||
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.OrientationSensor
|
||||
import io.fotoapparat.routine.focus.focusOnPoint
|
||||
import io.fotoapparat.routine.orientation.startOrientationMonitoring
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Starts the camera from idle.
|
||||
*/
|
||||
internal fun Device.bootStart(
|
||||
orientationSensor: OrientationSensor,
|
||||
mainThreadErrorCallback: (CameraException) -> Unit
|
||||
mainThreadErrorCallback: CameraErrorCallback
|
||||
) {
|
||||
if (hasSelectedCamera()) {
|
||||
throw IllegalStateException("Camera has already started!")
|
||||
}
|
||||
|
||||
try {
|
||||
start()
|
||||
start(
|
||||
orientationSensor = orientationSensor
|
||||
)
|
||||
startOrientationMonitoring(
|
||||
orientationSensor = orientationSensor
|
||||
)
|
||||
@@ -29,29 +35,45 @@ internal fun Device.bootStart(
|
||||
/**
|
||||
* Starts the camera.
|
||||
*/
|
||||
internal fun Device.start() {
|
||||
internal fun Device.start(orientationSensor: OrientationSensor) {
|
||||
selectCamera()
|
||||
|
||||
val cameraDevice = getSelectedCamera()
|
||||
val cameraDevice = getSelectedCamera().apply {
|
||||
open()
|
||||
|
||||
cameraDevice.open()
|
||||
updateCameraConfiguration(
|
||||
cameraDevice = this
|
||||
)
|
||||
setDisplayOrientation(orientationSensor.lastKnownOrientationState)
|
||||
}
|
||||
|
||||
updateCameraConfiguration(
|
||||
cameraDevice = cameraDevice
|
||||
)
|
||||
val previewResolution = cameraDevice.getPreviewResolution()
|
||||
|
||||
cameraDevice.setDisplayOrientation(
|
||||
degrees = getScreenRotation()
|
||||
)
|
||||
cameraRenderer.apply {
|
||||
setScaleType(
|
||||
scaleType = scaleType
|
||||
)
|
||||
|
||||
cameraRenderer.setScaleType(
|
||||
scaleType = scaleType
|
||||
)
|
||||
cameraRenderer.setPreviewResolution(
|
||||
resolution = cameraDevice.getPreviewResolution()
|
||||
)
|
||||
cameraDevice.setDisplaySurface(
|
||||
preview = cameraRenderer.getPreview()
|
||||
)
|
||||
cameraDevice.startPreview()
|
||||
setPreviewResolution(
|
||||
resolution = previewResolution
|
||||
)
|
||||
}
|
||||
|
||||
focusPointSelector?.setFocalPointListener { focalRequest ->
|
||||
executor.execute(Operation(cancellable = true) {
|
||||
focusOnPoint(focalRequest)
|
||||
})
|
||||
}
|
||||
|
||||
with(cameraDevice) {
|
||||
try {
|
||||
setDisplaySurface(
|
||||
preview = cameraRenderer.getPreview()
|
||||
)
|
||||
|
||||
startPreview()
|
||||
} catch (e: IOException) {
|
||||
logger.log("Can't start preview because of the exception: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,15 @@ package io.fotoapparat.routine.camera
|
||||
import io.fotoapparat.hardware.CameraDevice
|
||||
import io.fotoapparat.hardware.Device
|
||||
import io.fotoapparat.hardware.orientation.OrientationSensor
|
||||
import io.fotoapparat.hardware.shutdownPendingTasks
|
||||
import io.fotoapparat.routine.orientation.stopMonitoring
|
||||
|
||||
|
||||
/**
|
||||
* Stops the camera completely.
|
||||
*/
|
||||
internal fun Device.shutDown(
|
||||
orientationSensor: OrientationSensor
|
||||
) {
|
||||
|
||||
shutdownPendingTasks()
|
||||
|
||||
focusPointSelector?.setFocalPointListener { }
|
||||
orientationSensor.stopMonitoring()
|
||||
|
||||
val cameraDevice = getSelectedCamera()
|
||||
@@ -32,4 +28,4 @@ internal fun Device.stop(cameraDevice: CameraDevice) {
|
||||
cameraDevice.close()
|
||||
|
||||
clearSelectedCamera()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package io.fotoapparat.routine.camera
|
||||
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
import io.fotoapparat.configuration.CameraConfiguration
|
||||
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
|
||||
|
||||
/**
|
||||
* Switches to a new [LensPosition] camera. Will do nothing if [LensPosition] is same.
|
||||
@@ -12,9 +15,10 @@ import io.fotoapparat.hardware.Device
|
||||
* Will restart preview automatically if existing camera has started its preview.
|
||||
*/
|
||||
internal fun Device.switchCamera(
|
||||
newLensPositionSelector: Collection<LensPosition>.() -> LensPosition?,
|
||||
newLensPositionSelector: LensPositionSelector,
|
||||
newConfiguration: CameraConfiguration,
|
||||
mainThreadErrorCallback: (CameraException) -> Unit
|
||||
mainThreadErrorCallback: CameraErrorCallback,
|
||||
orientationSensor: OrientationSensor
|
||||
) {
|
||||
val oldCameraDevice = try {
|
||||
getSelectedCamera()
|
||||
@@ -31,6 +35,7 @@ internal fun Device.switchCamera(
|
||||
|
||||
restartPreview(
|
||||
oldCameraDevice,
|
||||
orientationSensor,
|
||||
mainThreadErrorCallback
|
||||
)
|
||||
}
|
||||
@@ -41,12 +46,13 @@ internal fun Device.switchCamera(
|
||||
*/
|
||||
internal fun Device.restartPreview(
|
||||
oldCameraDevice: CameraDevice,
|
||||
mainThreadErrorCallback: (CameraException) -> Unit
|
||||
orientationSensor: OrientationSensor,
|
||||
mainThreadErrorCallback: CameraErrorCallback
|
||||
) {
|
||||
stop(oldCameraDevice)
|
||||
|
||||
try {
|
||||
start()
|
||||
start(orientationSensor)
|
||||
} catch (e: CameraException) {
|
||||
mainThreadErrorCallback(e)
|
||||
}
|
||||
|
||||
+3
-3
@@ -3,7 +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.runBlocking
|
||||
|
||||
/**
|
||||
* Updates [Device] configuration.
|
||||
@@ -21,7 +21,7 @@ internal fun Device.updateDeviceConfiguration(newConfiguration: Configuration) {
|
||||
*/
|
||||
internal fun Device.updateCameraConfiguration(
|
||||
cameraDevice: CameraDevice
|
||||
) {
|
||||
) = runBlocking {
|
||||
val cameraParameters = getCameraParameters(cameraDevice)
|
||||
val frameProcessor = getFrameProcessor()
|
||||
|
||||
@@ -32,4 +32,4 @@ internal fun Device.updateCameraConfiguration(
|
||||
cameraDevice.updateFrameProcessor(
|
||||
frameProcessor = frameProcessor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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].
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.fotoapparat.routine.focus
|
||||
|
||||
import io.fotoapparat.hardware.Device
|
||||
import io.fotoapparat.hardware.metering.FocalRequest
|
||||
import io.fotoapparat.result.FocusResult
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
* Focuses the camera on a particular point.
|
||||
*/
|
||||
internal fun Device.focusOnPoint(focalRequest: FocalRequest): FocusResult = runBlocking {
|
||||
awaitSelectedCamera()
|
||||
.run {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -1,8 +1,9 @@
|
||||
package io.fotoapparat.routine.orientation
|
||||
|
||||
import io.fotoapparat.concurrent.CameraExecutor.Operation
|
||||
import io.fotoapparat.hardware.Device
|
||||
import io.fotoapparat.hardware.execute
|
||||
import io.fotoapparat.hardware.orientation.OrientationSensor
|
||||
import io.fotoapparat.hardware.orientation.OrientationState
|
||||
|
||||
/**
|
||||
* Starts orientation monitoring routine.
|
||||
@@ -10,10 +11,10 @@ import io.fotoapparat.hardware.orientation.OrientationSensor
|
||||
internal fun Device.startOrientationMonitoring(
|
||||
orientationSensor: OrientationSensor
|
||||
) {
|
||||
orientationSensor.start { degrees ->
|
||||
execute {
|
||||
orientationSensor.start { orientationState: OrientationState ->
|
||||
executor.execute(Operation(cancellable = true) {
|
||||
val cameraDevice = getSelectedCamera()
|
||||
cameraDevice.setDisplayOrientation(degrees)
|
||||
}
|
||||
cameraDevice.setDisplayOrientation(orientationState)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -20,7 +20,6 @@ internal fun Device.takePhoto(): Photo = runBlocking {
|
||||
private fun CameraDevice.startPreviewSafely() {
|
||||
try {
|
||||
startPreview()
|
||||
} catch (e: CameraException) {
|
||||
//Nothing
|
||||
} catch (ignore: CameraException) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,13 +17,13 @@ internal fun Device.updateZoomLevel(
|
||||
zoomLevel.ensureInBounds()
|
||||
val cameraDevice = awaitSelectedCamera()
|
||||
|
||||
if (cameraDevice.getCapabilities().canZoom) {
|
||||
if (cameraDevice.getCapabilities().zoom is Zoom.VariableZoom) {
|
||||
cameraDevice.setZoom(zoomLevel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Float.ensureInBounds() {
|
||||
if (this < 0f || this > 1f) {
|
||||
if (this !in 0f..1f) {
|
||||
throw LevelOutOfRangeException(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,36 +2,30 @@ package io.fotoapparat.selector
|
||||
|
||||
import io.fotoapparat.parameter.AntiBandingMode
|
||||
|
||||
typealias AntiBandingModeSelector = Iterable<AntiBandingMode>.() -> AntiBandingMode?
|
||||
|
||||
/**
|
||||
* @return Selector function which provides an auto anti banding mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun auto(): Iterable<AntiBandingMode>.() -> AntiBandingMode? {
|
||||
return single(AntiBandingMode.Auto)
|
||||
}
|
||||
fun auto(): AntiBandingModeSelector = single(AntiBandingMode.Auto)
|
||||
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a 50hz banding mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun hz50(): Iterable<AntiBandingMode>.() -> AntiBandingMode? {
|
||||
return single(AntiBandingMode.HZ50)
|
||||
}
|
||||
fun hz50(): AntiBandingModeSelector = single(AntiBandingMode.HZ50)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a 60hz anti banding mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun hz60(): Iterable<AntiBandingMode>.() -> AntiBandingMode? {
|
||||
return single(AntiBandingMode.HZ60)
|
||||
}
|
||||
fun hz60(): AntiBandingModeSelector = single(AntiBandingMode.HZ60)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a disabled anti banding mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun none(): Iterable<AntiBandingMode>.() -> AntiBandingMode? {
|
||||
return single(AntiBandingMode.None)
|
||||
}
|
||||
fun none(): AntiBandingModeSelector = single(AntiBandingMode.None)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.fotoapparat.selector
|
||||
|
||||
import android.support.annotation.FloatRange
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
import androidx.annotation.FloatRange
|
||||
|
||||
/**
|
||||
* @param selector Input selector
|
||||
@@ -11,15 +10,13 @@ import io.fotoapparat.parameter.Resolution
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun standardRatio(
|
||||
selector: Iterable<Resolution>.() -> Resolution?,
|
||||
selector: ResolutionSelector,
|
||||
@FloatRange(from = 0.0, to = 1.0) tolerance: Double = 0.0
|
||||
): Iterable<Resolution>.() -> Resolution? {
|
||||
return aspectRatio(
|
||||
aspectRatio = 4f / 3f,
|
||||
selector = selector,
|
||||
tolerance = tolerance
|
||||
)
|
||||
}
|
||||
): ResolutionSelector = aspectRatio(
|
||||
aspectRatio = 4f / 3f,
|
||||
selector = selector,
|
||||
tolerance = tolerance
|
||||
)
|
||||
|
||||
/**
|
||||
* @param selector Input sizes, selected by provided selector function
|
||||
@@ -29,15 +26,13 @@ fun standardRatio(
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun wideRatio(
|
||||
selector: Iterable<Resolution>.() -> Resolution?,
|
||||
selector: ResolutionSelector,
|
||||
@FloatRange(from = 0.0, to = 1.0) tolerance: Double = 0.0
|
||||
): Iterable<Resolution>.() -> Resolution? {
|
||||
return aspectRatio(
|
||||
aspectRatio = 16f / 9f,
|
||||
selector = selector,
|
||||
tolerance = tolerance
|
||||
)
|
||||
}
|
||||
): ResolutionSelector = aspectRatio(
|
||||
aspectRatio = 16f / 9f,
|
||||
selector = selector,
|
||||
tolerance = tolerance
|
||||
)
|
||||
|
||||
/**
|
||||
* Select sizes with desired aspect ratio. This selector can
|
||||
@@ -51,10 +46,10 @@ fun wideRatio(
|
||||
@JvmOverloads
|
||||
fun aspectRatio(
|
||||
aspectRatio: Float,
|
||||
selector: Iterable<Resolution>.() -> Resolution?,
|
||||
selector: ResolutionSelector,
|
||||
@FloatRange(from = 0.0, to = 1.0) tolerance: Double = 0.0
|
||||
): Iterable<Resolution>.() -> Resolution? {
|
||||
if (tolerance < 0.0 || tolerance > 1.0) {
|
||||
): ResolutionSelector {
|
||||
if (tolerance !in 0.0..1.0) {
|
||||
throw IllegalArgumentException("Tolerance must be between 0.0 and 1.0.")
|
||||
}
|
||||
|
||||
|
||||
@@ -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,45 +2,36 @@ package io.fotoapparat.selector
|
||||
|
||||
import io.fotoapparat.parameter.Flash
|
||||
|
||||
typealias FlashSelector = Iterable<Flash>.() -> Flash?
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a disabled flash firing mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun off(): Iterable<Flash>.() -> Flash? {
|
||||
return single(Flash.Off)
|
||||
}
|
||||
fun off(): FlashSelector = single(Flash.Off)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a forced on flash firing mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun on(): Iterable<Flash>.() -> Flash? {
|
||||
return single(Flash.On)
|
||||
}
|
||||
fun on(): FlashSelector = single(Flash.On)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides an auto flash firing mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun autoFlash(): Iterable<Flash>.() -> Flash? {
|
||||
return single(Flash.Auto)
|
||||
}
|
||||
fun autoFlash(): FlashSelector = single(Flash.Auto)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides an auto flash firing mode with red eye
|
||||
* reduction if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun autoRedEye(): Iterable<Flash>.() -> Flash? {
|
||||
return single(Flash.AutoRedEye)
|
||||
}
|
||||
fun autoRedEye(): FlashSelector = single(Flash.AutoRedEye)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a torch (continuous on) flash firing mode if
|
||||
* available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun torch(): Iterable<Flash>.() -> Flash? {
|
||||
return single(Flash.Torch)
|
||||
}
|
||||
fun torch(): FlashSelector = single(Flash.Torch)
|
||||
@@ -2,63 +2,50 @@ package io.fotoapparat.selector
|
||||
|
||||
import io.fotoapparat.parameter.FocusMode
|
||||
|
||||
typealias FocusModeSelector = Iterable<FocusMode>.() -> FocusMode?
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a non-adjustable focus mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun fixed(): Iterable<FocusMode>.() -> FocusMode? {
|
||||
return single(FocusMode.Fixed)
|
||||
}
|
||||
fun fixed(): FocusModeSelector = single(FocusMode.Fixed)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a focus mode targeting infinity if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun infinity(): Iterable<FocusMode>.() -> FocusMode? {
|
||||
return single(FocusMode.Infinity)
|
||||
}
|
||||
fun infinity(): FocusModeSelector = single(FocusMode.Infinity)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a macro focus mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun macro(): Iterable<FocusMode>.() -> FocusMode? {
|
||||
return single(FocusMode.Macro)
|
||||
}
|
||||
fun macro(): FocusModeSelector = single(FocusMode.Macro)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides an auto focus mode if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun autoFocus(): Iterable<FocusMode>.() -> FocusMode? {
|
||||
return single(FocusMode.Auto)
|
||||
}
|
||||
fun autoFocus(): FocusModeSelector = single(FocusMode.Auto)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a focus mode which constantly tries to stay
|
||||
* in focus if available. The speed of focus change is aggressive.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun continuousFocusPicture(): Iterable<FocusMode>.() -> FocusMode? {
|
||||
return single(FocusMode.ContinuousFocusPicture)
|
||||
}
|
||||
fun continuousFocusPicture(): FocusModeSelector = single(FocusMode.ContinuousFocusPicture)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a focus mode which constantly tries to stay
|
||||
* in focus if available. The speed of focus change is smooth.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun continuousFocusVideo(): Iterable<FocusMode>.() -> FocusMode? {
|
||||
return single(FocusMode.ContinuousFocusVideo)
|
||||
}
|
||||
fun continuousFocusVideo(): FocusModeSelector = single(FocusMode.ContinuousFocusVideo)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides a focus mode which will produce images with
|
||||
* an extended depth of field if available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun edof(): Iterable<FocusMode>.() -> FocusMode? {
|
||||
return single(FocusMode.Edof)
|
||||
}
|
||||
fun edof(): FocusModeSelector = single(FocusMode.Edof)
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package io.fotoapparat.selector
|
||||
|
||||
typealias QualitySelector = IntRange.() -> Int?
|
||||
|
||||
/**
|
||||
* @param quality The specified Jpeq quality value
|
||||
* @return Selector function which selects the specified Jpeq quality value.
|
||||
*/
|
||||
fun manualJpegQuality(quality: Int): IntRange.() -> Int? = single(quality)
|
||||
fun manualJpegQuality(quality: Int): QualitySelector = single(quality)
|
||||
|
||||
/**
|
||||
* @return Selector function which always provides the highest quality.
|
||||
*/
|
||||
fun highestQuality(): IntRange.() -> Int? = highest()
|
||||
fun highestQuality(): QualitySelector = highest()
|
||||
|
||||
/**
|
||||
* @return Selector function which always provides the lowest quality.
|
||||
*/
|
||||
fun lowestQuality(): IntRange.() -> Int? = lowest()
|
||||
fun lowestQuality(): QualitySelector = lowest()
|
||||
@@ -2,27 +2,22 @@ package io.fotoapparat.selector
|
||||
|
||||
import io.fotoapparat.characteristic.LensPosition
|
||||
|
||||
typealias LensPositionSelector = Iterable<LensPosition>.() -> LensPosition?
|
||||
|
||||
/**
|
||||
* @return Selector function which provides the front camera if it is available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun front(): Iterable<LensPosition>.() -> LensPosition? {
|
||||
return single(LensPosition.Front)
|
||||
}
|
||||
fun front(): LensPositionSelector = single(LensPosition.Front)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides the back camera if it is available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun back(): Iterable<LensPosition>.() -> LensPosition? {
|
||||
return single(LensPosition.Back)
|
||||
}
|
||||
fun back(): LensPositionSelector = single(LensPosition.Back)
|
||||
|
||||
/**
|
||||
* @return Selector function which provides the external camera if it is available.
|
||||
* Otherwise provides `null`.
|
||||
*/
|
||||
fun external(): Iterable<LensPosition>.() -> LensPosition? {
|
||||
return single(LensPosition.External)
|
||||
}
|
||||
fun external(): LensPositionSelector = single(LensPosition.External)
|
||||
|
||||
@@ -3,18 +3,18 @@ package io.fotoapparat.selector
|
||||
import io.fotoapparat.parameter.FpsRange
|
||||
import io.fotoapparat.util.CompareFpsRangeByBounds
|
||||
|
||||
typealias FpsRangeSelector = Iterable<FpsRange>.() -> FpsRange?
|
||||
|
||||
/**
|
||||
* @param fps The specified FPS
|
||||
* @return Selector function which selects FPS range that contains the specified FPS.
|
||||
* Prefers fixed rates over non fixed ones.
|
||||
*/
|
||||
fun containsFps(fps: Float): Iterable<FpsRange>.() -> FpsRange? = firstAvailable(
|
||||
fun containsFps(fps: Float): FpsRangeSelector = firstAvailable(
|
||||
exactFixedFps(fps),
|
||||
filtered(
|
||||
selector = highestNonFixedFps(),
|
||||
predicate = {
|
||||
it.contains(fps.toFpsIntRepresentation())
|
||||
}
|
||||
predicate = { range -> fps.toFpsIntRepresentation() in range }
|
||||
)
|
||||
)
|
||||
|
||||
@@ -22,18 +22,16 @@ fun containsFps(fps: Float): Iterable<FpsRange>.() -> FpsRange? = firstAvailable
|
||||
* @param fps The specified FPS
|
||||
* @return Selector function which selects FPS range that contains only the specified FPS.
|
||||
*/
|
||||
fun exactFixedFps(fps: Float): Iterable<FpsRange>.() -> FpsRange? = filtered(
|
||||
fun exactFixedFps(fps: Float): FpsRangeSelector = filtered(
|
||||
selector = highestFixedFps(),
|
||||
predicate = {
|
||||
it.min == fps.toFpsIntRepresentation()
|
||||
}
|
||||
predicate = { it.min == fps.toFpsIntRepresentation() }
|
||||
)
|
||||
|
||||
/**
|
||||
* @return Selector function which selects FPS range with max FPS.
|
||||
* Prefers non fixed rates over fixed ones.
|
||||
*/
|
||||
fun highestFps(): Iterable<FpsRange>.() -> FpsRange? = firstAvailable(
|
||||
fun highestFps(): FpsRangeSelector = firstAvailable(
|
||||
highestNonFixedFps(),
|
||||
highestFixedFps()
|
||||
)
|
||||
@@ -41,7 +39,7 @@ fun highestFps(): Iterable<FpsRange>.() -> FpsRange? = firstAvailable(
|
||||
/**
|
||||
* @return Selector function which selects FPS range with max FPS and non fixed rate.
|
||||
*/
|
||||
fun highestNonFixedFps(): Iterable<FpsRange>.() -> FpsRange? = filtered(
|
||||
fun highestNonFixedFps(): FpsRangeSelector = filtered(
|
||||
selector = highestRangeFps(),
|
||||
predicate = { !it.isFixed }
|
||||
)
|
||||
@@ -49,7 +47,7 @@ fun highestNonFixedFps(): Iterable<FpsRange>.() -> FpsRange? = filtered(
|
||||
/**
|
||||
* @return Selector function which selects FPS range with max FPS and fixed rate.
|
||||
*/
|
||||
fun highestFixedFps(): Iterable<FpsRange>.() -> FpsRange? = filtered(
|
||||
fun highestFixedFps(): FpsRangeSelector = filtered(
|
||||
selector = highestRangeFps(),
|
||||
predicate = { it.isFixed }
|
||||
)
|
||||
@@ -58,7 +56,7 @@ fun highestFixedFps(): Iterable<FpsRange>.() -> FpsRange? = filtered(
|
||||
* @return Selector function which selects FPS range with min FPS.
|
||||
* Prefers non fixed rates over fixed ones.
|
||||
*/
|
||||
fun lowestFps(): Iterable<FpsRange>.() -> FpsRange? = firstAvailable(
|
||||
fun lowestFps(): FpsRangeSelector = firstAvailable(
|
||||
lowestNonFixedFps(),
|
||||
lowestFixedFps()
|
||||
)
|
||||
@@ -66,29 +64,21 @@ fun lowestFps(): Iterable<FpsRange>.() -> FpsRange? = firstAvailable(
|
||||
/**
|
||||
* @return Selector function which selects FPS range with min FPS and non fixed rate.
|
||||
*/
|
||||
fun lowestNonFixedFps(): Iterable<FpsRange>.() -> FpsRange? {
|
||||
return filtered(
|
||||
selector = lowestRangeFps(),
|
||||
predicate = { !it.isFixed }
|
||||
)
|
||||
}
|
||||
fun lowestNonFixedFps(): FpsRangeSelector = filtered(
|
||||
selector = lowestRangeFps(),
|
||||
predicate = { !it.isFixed }
|
||||
)
|
||||
|
||||
/**
|
||||
* @return Selector function which selects FPS range with min FPS and fixed rate.
|
||||
*/
|
||||
fun lowestFixedFps(): Iterable<FpsRange>.() -> FpsRange? {
|
||||
return filtered(
|
||||
selector = lowestRangeFps(),
|
||||
predicate = { it.isFixed }
|
||||
)
|
||||
}
|
||||
fun lowestFixedFps(): FpsRangeSelector = filtered(
|
||||
selector = lowestRangeFps(),
|
||||
predicate = { it.isFixed }
|
||||
)
|
||||
|
||||
private fun highestRangeFps(): Iterable<FpsRange>.() -> FpsRange? = {
|
||||
maxWith(CompareFpsRangeByBounds)
|
||||
}
|
||||
private fun highestRangeFps(): FpsRangeSelector = { maxWith(CompareFpsRangeByBounds) }
|
||||
|
||||
private fun lowestRangeFps(): Iterable<FpsRange>.() -> FpsRange? = {
|
||||
minWith(CompareFpsRangeByBounds)
|
||||
}
|
||||
private fun lowestRangeFps(): FpsRangeSelector = { minWith(CompareFpsRangeByBounds) }
|
||||
|
||||
private fun Float.toFpsIntRepresentation() = (this * 1000).toInt()
|
||||
@@ -2,16 +2,14 @@ package io.fotoapparat.selector
|
||||
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
|
||||
typealias ResolutionSelector = Iterable<Resolution>.() -> Resolution?
|
||||
|
||||
/**
|
||||
* @return Selector function which always provides the biggest resolution.
|
||||
*/
|
||||
fun highestResolution(): Iterable<Resolution>.() -> Resolution? = {
|
||||
maxBy { it.area }
|
||||
}
|
||||
fun highestResolution(): ResolutionSelector = { maxBy(Resolution::area) }
|
||||
|
||||
/**
|
||||
* @return Selector function which always provides the smallest resolution.
|
||||
*/
|
||||
fun lowestResolution(): Iterable<Resolution>.() -> Resolution? = {
|
||||
minBy { it.area }
|
||||
}
|
||||
fun lowestResolution(): ResolutionSelector = { minBy(Resolution::area) }
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.fotoapparat.selector
|
||||
|
||||
|
||||
/**
|
||||
* @return Selector function which always returns `null`.
|
||||
*/
|
||||
@@ -20,22 +19,19 @@ fun <T> single(preference: T): Iterable<T>.() -> T? = {
|
||||
/**
|
||||
* @return Selector function which selects highest [Comparable] value.
|
||||
*/
|
||||
fun <T : Comparable<T>> highest(): Iterable<T>.() -> T? = {
|
||||
max()
|
||||
}
|
||||
fun <T : Comparable<T>> highest(): Iterable<T>.() -> T? = Iterable<T>::max
|
||||
|
||||
/**
|
||||
* @return Selector function which selects lowest [Comparable] value.
|
||||
*/
|
||||
fun <T : Comparable<T>> lowest(): Iterable<T>.() -> T? = {
|
||||
min()
|
||||
}
|
||||
fun <T : Comparable<T>> lowest(): Iterable<T>.() -> T? = Iterable<T>::min
|
||||
|
||||
/**
|
||||
* @param functions functions in order of importance.
|
||||
* @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,19 +1,20 @@
|
||||
package io.fotoapparat.selector
|
||||
|
||||
typealias SensorSensitivitySelector = Iterable<Int>.() -> Int?
|
||||
|
||||
/**
|
||||
* @param iso The specified ISO value
|
||||
* @return Selector function which selects the specified ISO value. If there
|
||||
* is no specified value - selects default or previous ISO value.
|
||||
*/
|
||||
fun manualSensorSensitivity(iso: Int): Iterable<Int>.() -> Int? = single(iso)
|
||||
fun manualSensorSensitivity(iso: Int): SensorSensitivitySelector = single(iso)
|
||||
|
||||
/**
|
||||
* @return Selector function which selects highest ISO value.
|
||||
*/
|
||||
fun highestSensorSensitivity(): Iterable<Int>.() -> Int? = highest()
|
||||
fun highestSensorSensitivity(): SensorSensitivitySelector = highest()
|
||||
|
||||
/**
|
||||
* @return Selector function which selects lowest ISO value.
|
||||
*/
|
||||
fun lowestSensorSensitivity(): Iterable<Int>.() -> Int? = lowest()
|
||||
fun lowestSensorSensitivity(): SensorSensitivitySelector = lowest()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.fotoapparat.util
|
||||
|
||||
|
||||
/**
|
||||
* System line separator.
|
||||
*/
|
||||
@@ -14,4 +13,4 @@ internal fun Set<Any>.wrap(): String = "${this.map { lineSeparator + "\t\t" + it
|
||||
/**
|
||||
* Prints a item in logcat with left margin and appends a line separator
|
||||
*/
|
||||
internal fun Any.wrap() = "\t\t" + this + lineSeparator
|
||||
internal fun Any?.wrap(): String = (this ?: "null").let { "\t\t" + it + lineSeparator }
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.fotoapparat.util
|
||||
|
||||
import io.fotoapparat.preview.Frame
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -1,16 +1,17 @@
|
||||
package io.fotoapparat.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.graphics.SurfaceTexture
|
||||
import android.util.AttributeSet
|
||||
import android.view.TextureView
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import io.fotoapparat.exception.camera.UnavailableSurfaceException
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
import io.fotoapparat.parameter.ScaleType
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
|
||||
/**
|
||||
* Uses [android.view.TextureView] as an output for camera.
|
||||
*/
|
||||
@@ -63,7 +64,7 @@ class CameraView
|
||||
|
||||
private fun getPreviewAfterLatch(): Preview.Texture {
|
||||
textureLatch.await()
|
||||
return surfaceTexture?.toPreview() ?: throw InterruptedException("No surface became available.")
|
||||
return surfaceTexture?.toPreview() ?: throw UnavailableSurfaceException()
|
||||
}
|
||||
|
||||
private fun TextureView.tryInitialize() = surfaceTexture ?: null.also {
|
||||
@@ -75,13 +76,13 @@ class CameraView
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun ViewGroup.layoutTextureView(
|
||||
previewResolution: Resolution?,
|
||||
scaleType: ScaleType?
|
||||
) = when (scaleType) {
|
||||
ScaleType.CenterInside -> previewResolution?.centerInside(this)
|
||||
ScaleType.CenterCrop -> previewResolution?.centerCrop(this)
|
||||
ScaleType.TopCrop -> previewResolution?.topCrop(this)
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -97,12 +98,14 @@ private fun Resolution.centerInside(view: ViewGroup) {
|
||||
val extraX = Math.max(0, view.measuredWidth - width)
|
||||
val extraY = Math.max(0, view.measuredHeight - height)
|
||||
|
||||
view.getChildAt(0).layout(
|
||||
val rect = Rect(
|
||||
extraX / 2,
|
||||
extraY / 2,
|
||||
width + extraX / 2,
|
||||
height + extraY / 2
|
||||
)
|
||||
|
||||
view.layoutChildrenAt(rect)
|
||||
}
|
||||
|
||||
private fun Resolution.centerCrop(view: ViewGroup) {
|
||||
@@ -117,10 +120,45 @@ private fun Resolution.centerCrop(view: ViewGroup) {
|
||||
val extraX = Math.max(0, width - view.measuredWidth)
|
||||
val extraY = Math.max(0, height - view.measuredHeight)
|
||||
|
||||
view.getChildAt(0).layout(
|
||||
val rect = Rect(
|
||||
-extraX / 2,
|
||||
-extraY / 2,
|
||||
width - extraX / 2,
|
||||
height - extraY / 2
|
||||
)
|
||||
|
||||
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(
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right,
|
||||
rect.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.fotoapparat.view
|
||||
|
||||
import android.animation.AnimatorInflater.loadAnimator
|
||||
import android.animation.AnimatorSet
|
||||
import android.content.Context
|
||||
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.
|
||||
*/
|
||||
internal class FeedbackCircleView
|
||||
@JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val inner = ImageView(context, attrs, defStyleAttr).apply {
|
||||
setImageResource(R.drawable.focus_inner)
|
||||
layoutParams = context.resources.run {
|
||||
ViewGroup.LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
alpha = 0f
|
||||
}
|
||||
|
||||
private val outer = ImageView(context, attrs, defStyleAttr).apply {
|
||||
setImageResource(R.drawable.focus_outer)
|
||||
layoutParams = context.resources.run {
|
||||
ViewGroup.LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
alpha = 0f
|
||||
}
|
||||
|
||||
private val animatorSet = AnimatorSet().apply {
|
||||
playTogether(
|
||||
newAnimator(R.animator.focus_outer, outer),
|
||||
newAnimator(R.animator.focus_inner, inner)
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
layoutParams = context.resources.run {
|
||||
ViewGroup.LayoutParams(
|
||||
getDimensionPixelSize(R.dimen.focus_diameter),
|
||||
getDimensionPixelSize(R.dimen.focus_diameter)
|
||||
)
|
||||
}
|
||||
clipToPadding = false
|
||||
clipChildren = false
|
||||
|
||||
addView(outer)
|
||||
addView(inner)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Displays the view at the given coordinates.
|
||||
*/
|
||||
internal fun showAt(x: Float, y: Float) {
|
||||
translationX = x
|
||||
translationY = y
|
||||
|
||||
animatorSet.cancel()
|
||||
animatorSet.start()
|
||||
}
|
||||
|
||||
|
||||
private fun newAnimator(@AnimatorRes id: Int, target: View) = loadAnimator(context, id).apply {
|
||||
setTarget(target)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.fotoapparat.view
|
||||
|
||||
import io.fotoapparat.hardware.metering.FocalRequest
|
||||
|
||||
/**
|
||||
* Selects the a point where the camera should focus at.
|
||||
*/
|
||||
interface FocalPointSelector {
|
||||
|
||||
/**
|
||||
* Sets a listener to be called when a [FocalRequest] has been selected.
|
||||
*/
|
||||
fun setFocalPointListener(listener: (FocalRequest) -> Unit)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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
|
||||
import io.fotoapparat.parameter.Resolution
|
||||
|
||||
/**
|
||||
* A view which is metering the focus & exposure of the camera to specific areas, if possible.
|
||||
*
|
||||
* If the camera doesn't support focus metering on specific area this will only display a visual feedback.
|
||||
*/
|
||||
open class FocusView
|
||||
@JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr), FocalPointSelector {
|
||||
|
||||
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
|
||||
addView(visualFeedbackCircle)
|
||||
}
|
||||
|
||||
override fun setFocalPointListener(listener: (FocalRequest) -> Unit) {
|
||||
focusMeteringListener = listener
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
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 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)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:ordering="sequentially">
|
||||
|
||||
<set android:ordering="together">
|
||||
<objectAnimator
|
||||
android:duration="300"
|
||||
android:propertyName="alpha"
|
||||
android:startOffset="0"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1" />
|
||||
<objectAnimator
|
||||
android:duration="300"
|
||||
android:propertyName="scaleX"
|
||||
android:startOffset="0"
|
||||
android:valueFrom="0.2"
|
||||
android:valueTo="1" />
|
||||
<objectAnimator
|
||||
android:duration="300"
|
||||
android:propertyName="scaleY"
|
||||
android:startOffset="0"
|
||||
android:valueFrom="0.2"
|
||||
android:valueTo="1" />
|
||||
</set>
|
||||
|
||||
<objectAnimator
|
||||
android:duration="400"
|
||||
android:propertyName="alpha"
|
||||
android:startOffset="0"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0" />
|
||||
</set>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:ordering="sequentially">
|
||||
|
||||
<set android:ordering="together">
|
||||
<objectAnimator
|
||||
android:duration="300"
|
||||
android:propertyName="alpha"
|
||||
android:startOffset="0"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1" />
|
||||
<objectAnimator
|
||||
android:duration="300"
|
||||
android:propertyName="scaleX"
|
||||
android:startOffset="0"
|
||||
android:valueFrom="1.5"
|
||||
android:valueTo="1" />
|
||||
<objectAnimator
|
||||
android:duration="300"
|
||||
android:propertyName="scaleY"
|
||||
android:startOffset="0"
|
||||
android:valueFrom="1.5"
|
||||
android:valueTo="1" />
|
||||
</set>
|
||||
|
||||
<objectAnimator
|
||||
android:duration="400"
|
||||
android:propertyName="alpha"
|
||||
android:startOffset="500"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0" />
|
||||
</set>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<solid
|
||||
android:width="1dp"
|
||||
android:color="#60ffffff" />
|
||||
|
||||
</shape>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user