Compare commits
40 Commits
Author | SHA1 | Date |
---|---|---|
DeweyReed | e26ac950be | 2 years ago |
Dmitry Naymushin | ae3f3da0e1 | 2 years ago |
Steve Xu | 82ea49f6e2 | 3 years ago |
Mattia Iavarone | 0057981408 | 3 years ago |
Mattia Iavarone | 399844321b | 3 years ago |
Mattia Iavarone | c2e02923de | 3 years ago |
Mattia Iavarone | 994b1dd434 | 3 years ago |
Jan Šmuk | ceb0eca394 | 4 years ago |
Ryan Loebs | bde88a946e | 4 years ago |
Ryan Loebs | f608ec3918 | 4 years ago |
Mattia Iavarone | a219073468 | 4 years ago |
Ezequiel Adrián | d89491195c | 4 years ago |
Michael | 5d10277d7b | 4 years ago |
Mattia Iavarone | 0f6ab20c3e | 4 years ago |
Billy Sutomo | a9fcd6506e | 4 years ago |
Philippe Banwarth | 6aa2d3a489 | 4 years ago |
Mattia Iavarone | 40ace54c19 | 4 years ago |
Mattia Iavarone | 2429cc114b | 4 years ago |
Mattia Iavarone | 0001ab7a54 | 4 years ago |
Mattia Iavarone | e3fcef286f | 4 years ago |
Mattia Iavarone | 8207e67679 | 4 years ago |
thijsonline | daf7a0bf44 | 4 years ago |
Steve | 170d87ed39 | 4 years ago |
Mattia Iavarone | a7cf726636 | 4 years ago |
Mattia Iavarone | 610f511a76 | 4 years ago |
Mattia Iavarone | a7c18324a5 | 4 years ago |
Alexander | 66c37373bd | 4 years ago |
Alexander | 8455c23fb1 | 4 years ago |
jeffreyfjohnson | b5921ec6bf | 4 years ago |
Ryan McClarnon | 4364ec7c3a | 5 years ago |
Mattia Iavarone | edbade24a9 | 5 years ago |
Alexander | 5530c011fa | 5 years ago |
Mattia Iavarone | 778cdedc00 | 5 years ago |
dependabot[bot] | fa26b2a654 | 5 years ago |
Mattia Iavarone | 124aee6908 | 5 years ago |
Alexander | 218b27319d | 5 years ago |
Alexander | 858495afde | 5 years ago |
Mattia Iavarone | aa95d19902 | 5 years ago |
Mattia Iavarone | 968d181af4 | 5 years ago |
dependabot[bot] | 7f23c309ab | 5 years ago |
@ -1,10 +1,14 @@ |
|||||||
### Before you go |
### Before you go |
||||||
Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first. |
Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first so that |
||||||
If the edited files were covered by tests, updated tests are required for merging. Please look into the tests folders and make sure you cover new code. |
we can discuss the best approach to address the problem. Without a reference issue and discussion, |
||||||
|
unfortunately, this PR will likely be ignored. |
||||||
|
|
||||||
- Fixes ... (*issue number*) |
If the edited files were covered by tests, updated tests are required for merging. |
||||||
|
Please look into the tests folders and make sure you cover new code. |
||||||
|
|
||||||
|
- Fixes: ... (*issue number*) |
||||||
- Tests: ... (*yes/no*) |
- Tests: ... (*yes/no*) |
||||||
- Docs updated: ... (*yes/no*) |
- Docs updated: ... (*yes/no*) |
||||||
|
|
||||||
### Solution |
### Solution |
||||||
If applicable, briefly describe how the issue was addressed. |
If applicable, describe briefly how the issue was addressed. |
||||||
|
@ -0,0 +1,25 @@ |
|||||||
|
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions |
||||||
|
# Renaming ? Change the README badge. |
||||||
|
name: Snapshot |
||||||
|
on: |
||||||
|
push: |
||||||
|
branches: |
||||||
|
- main |
||||||
|
jobs: |
||||||
|
SNAPSHOT: |
||||||
|
name: Publish Snapshot |
||||||
|
runs-on: ubuntu-latest |
||||||
|
env: |
||||||
|
SIGNING_KEY: ${{ secrets.SIGNING_KEY }} |
||||||
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} |
||||||
|
SONATYPE_USER: ${{ secrets.SONATYPE_USER }} |
||||||
|
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} |
||||||
|
steps: |
||||||
|
- uses: actions/checkout@v2 |
||||||
|
- uses: actions/setup-java@v2 |
||||||
|
with: |
||||||
|
java-version: 11 |
||||||
|
distribution: temurin |
||||||
|
cache: gradle |
||||||
|
- name: Publish sonatype snapshot |
||||||
|
run: ./gradlew publishToSonatypeSnapshot |
@ -0,0 +1,23 @@ |
|||||||
|
<component name="ProjectRunConfigurationManager"> |
||||||
|
<configuration default="false" name="runAndroidTests" type="GradleRunConfiguration" factoryName="Gradle"> |
||||||
|
<ExternalSystemSettings> |
||||||
|
<option name="executionName" /> |
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$/cameraview" /> |
||||||
|
<option name="externalSystemIdString" value="GRADLE" /> |
||||||
|
<option name="scriptParameters" value="" /> |
||||||
|
<option name="taskDescriptions"> |
||||||
|
<list /> |
||||||
|
</option> |
||||||
|
<option name="taskNames"> |
||||||
|
<list> |
||||||
|
<option value="runAndroidTests" /> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
<option name="vmOptions" value="" /> |
||||||
|
</ExternalSystemSettings> |
||||||
|
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess> |
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> |
||||||
|
<DebugAllEnabled>false</DebugAllEnabled> |
||||||
|
<method v="2" /> |
||||||
|
</configuration> |
||||||
|
</component> |
@ -0,0 +1,23 @@ |
|||||||
|
<component name="ProjectRunConfigurationManager"> |
||||||
|
<configuration default="false" name="runUnitTests" type="GradleRunConfiguration" factoryName="Gradle"> |
||||||
|
<ExternalSystemSettings> |
||||||
|
<option name="executionName" /> |
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$/cameraview" /> |
||||||
|
<option name="externalSystemIdString" value="GRADLE" /> |
||||||
|
<option name="scriptParameters" value="" /> |
||||||
|
<option name="taskDescriptions"> |
||||||
|
<list /> |
||||||
|
</option> |
||||||
|
<option name="taskNames"> |
||||||
|
<list> |
||||||
|
<option value="runUnitTests" /> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
<option name="vmOptions" value="" /> |
||||||
|
</ExternalSystemSettings> |
||||||
|
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess> |
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> |
||||||
|
<DebugAllEnabled>false</DebugAllEnabled> |
||||||
|
<method v="2" /> |
||||||
|
</configuration> |
||||||
|
</component> |
@ -1,30 +0,0 @@ |
|||||||
buildscript { |
|
||||||
repositories { |
|
||||||
jcenter() |
|
||||||
google() |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
classpath 'com.android.tools.build:gradle:3.5.3' |
|
||||||
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' |
|
||||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
allprojects { |
|
||||||
repositories { |
|
||||||
jcenter() |
|
||||||
google() |
|
||||||
maven { url 'https://jitpack.io' } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
ext { |
|
||||||
compileSdkVersion = 29 |
|
||||||
minSdkVersion = 15 |
|
||||||
targetSdkVersion = 29 |
|
||||||
} |
|
||||||
|
|
||||||
task clean(type: Delete) { |
|
||||||
delete rootProject.buildDir |
|
||||||
} |
|
@ -0,0 +1,30 @@ |
|||||||
|
|
||||||
|
buildscript { |
||||||
|
|
||||||
|
extra["minSdkVersion"] = 15 |
||||||
|
extra["compileSdkVersion"] = 31 |
||||||
|
extra["targetSdkVersion"] = 31 |
||||||
|
|
||||||
|
repositories { |
||||||
|
google() |
||||||
|
mavenCentral() |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
classpath("com.android.tools.build:gradle:7.0.3") |
||||||
|
classpath("io.deepmedia.tools:publisher:0.6.0") |
||||||
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31") |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
allprojects { |
||||||
|
repositories { |
||||||
|
google() |
||||||
|
mavenCentral() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
tasks.register("clean", Delete::class) { |
||||||
|
delete(buildDir) |
||||||
|
} |
@ -1,276 +0,0 @@ |
|||||||
apply plugin: 'com.android.library' |
|
||||||
apply plugin: 'com.github.dcendents.android-maven' |
|
||||||
apply plugin: 'com.jfrog.bintray' |
|
||||||
|
|
||||||
// Required by bintray |
|
||||||
version = '2.6.1' |
|
||||||
group = 'com.otaliastudios' |
|
||||||
|
|
||||||
//region android dependencies |
|
||||||
|
|
||||||
android { |
|
||||||
compileSdkVersion rootProject.ext.compileSdkVersion |
|
||||||
|
|
||||||
defaultConfig { |
|
||||||
minSdkVersion rootProject.ext.minSdkVersion |
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion |
|
||||||
versionCode 1 |
|
||||||
versionName project.version |
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
|
||||||
testInstrumentationRunnerArgument "filter", "" + |
|
||||||
"com.otaliastudios.cameraview.tools.SdkExcludeFilter," + |
|
||||||
"com.otaliastudios.cameraview.tools.SdkIncludeFilter" |
|
||||||
} |
|
||||||
|
|
||||||
buildTypes { |
|
||||||
debug { |
|
||||||
testCoverageEnabled true |
|
||||||
} |
|
||||||
|
|
||||||
release { |
|
||||||
minifyEnabled false |
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
testImplementation 'junit:junit:4.12' |
|
||||||
testImplementation 'org.mockito:mockito-inline:2.28.2' |
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0' |
|
||||||
androidTestImplementation 'androidx.test:rules:1.2.0' |
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1' |
|
||||||
androidTestImplementation 'org.mockito:mockito-android:2.28.2' |
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' |
|
||||||
|
|
||||||
api 'androidx.exifinterface:exifinterface:1.1.0' |
|
||||||
api 'androidx.lifecycle:lifecycle-common:2.1.0' |
|
||||||
api 'com.google.android.gms:play-services-tasks:17.0.0' |
|
||||||
implementation 'androidx.annotation:annotation:1.1.0' |
|
||||||
|
|
||||||
api 'com.github.natario1:Egloo:refactor-SNAPSHOT' |
|
||||||
} |
|
||||||
|
|
||||||
configurations.all { |
|
||||||
resolutionStrategy.cacheChangingModulesFor 0, 'seconds' |
|
||||||
} |
|
||||||
|
|
||||||
//endregion |
|
||||||
|
|
||||||
//region bintray |
|
||||||
|
|
||||||
// install is a task defined by the Gradle Maven plugin, which is used to |
|
||||||
// publish a maven repo to a local repository. (we actually use the android version of the plugin, |
|
||||||
// com.github.dcendents.android-maven, to support AARs) |
|
||||||
// https://docs.gradle.org/current/userguide/maven_plugin.html#sec:maven_tasks |
|
||||||
install { |
|
||||||
// The repositories property is common to all tasks of type Upload and returns the repositories |
|
||||||
// into which we will upload data. https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Upload.html#org.gradle.api.tasks.Upload:repositories |
|
||||||
// It returns a RepositoryHandler: https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html |
|
||||||
repositories { |
|
||||||
// The maven plugin adds a mavenInstaller property to the RepositoryHandler which can be used to |
|
||||||
// add and configure a local maven repository cache. |
|
||||||
// https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html#N11785 |
|
||||||
mavenInstaller { |
|
||||||
// The object here extends PomFilterContainer so we can configure the pom file here. |
|
||||||
// https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/maven/PomFilterContainer.html#pom-groovy.lang.Closure- |
|
||||||
pom { |
|
||||||
// Now we are inside a MavenPom object that can be configured. We get the project and configure. |
|
||||||
// https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/maven/MavenPom.html |
|
||||||
project { |
|
||||||
name 'CameraView' |
|
||||||
description 'A well documented, high-level Android interface that makes capturing pictures ' + |
|
||||||
'and videos easy, addressing most of the common issues and needs.' |
|
||||||
url 'https://github.com/natario1/CameraView' |
|
||||||
packaging 'aar' |
|
||||||
groupId project.group |
|
||||||
artifactId 'cameraview' |
|
||||||
version project.version |
|
||||||
licenses { |
|
||||||
license { |
|
||||||
name 'The Apache Software License, Version 2.0' |
|
||||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt' |
|
||||||
} |
|
||||||
} |
|
||||||
scm { |
|
||||||
connection 'https://github.com/natario1/CameraView.git' |
|
||||||
developerConnection 'https://github.com/natario1/CameraView.git' |
|
||||||
url 'https://github.com/natario1/CameraView' |
|
||||||
|
|
||||||
} |
|
||||||
developers { |
|
||||||
developer { |
|
||||||
id = 'natario' |
|
||||||
name 'Mattia Iavarone' |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
def bintrayUser |
|
||||||
def bintrayKey |
|
||||||
def isCI = System.getenv("TRAVIS") |
|
||||||
if (isCI) { |
|
||||||
bintrayUser = System.getenv("BINTRAY_USER") |
|
||||||
bintrayKey = System.getenv("BINTRAY_KEY") |
|
||||||
} else { |
|
||||||
Properties props = new Properties() |
|
||||||
props.load(project.rootProject.file('local.properties').newDataInputStream()) |
|
||||||
bintrayUser = props.getProperty('bintray.user') |
|
||||||
bintrayKey = props.get('bintray.key') |
|
||||||
} |
|
||||||
|
|
||||||
bintray { |
|
||||||
// https://github.com/bintray/gradle-bintray-plugin |
|
||||||
user = bintrayUser |
|
||||||
key = bintrayKey |
|
||||||
configurations = ['archives'] |
|
||||||
pkg { |
|
||||||
repo = 'android' |
|
||||||
name = 'CameraView' |
|
||||||
licenses = ['Apache-2.0'] |
|
||||||
vcsUrl = 'https://github.com/natario1/CameraView.git' |
|
||||||
publish = true |
|
||||||
override = true |
|
||||||
version { |
|
||||||
name = project.version |
|
||||||
desc = 'CameraView v. '+project.version |
|
||||||
released = new Date() |
|
||||||
vcsTag = 'v'+project.version |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//endregion |
|
||||||
|
|
||||||
//region javadoc and sources |
|
||||||
|
|
||||||
// From official sample https://github.com/bintray/bintray-examples/blob/master/gradle-aar-example/build.gradle |
|
||||||
task sourcesJar(type: Jar) { |
|
||||||
archiveClassifier.set('sources') |
|
||||||
from android.sourceSets.main.java.sourceFiles |
|
||||||
} |
|
||||||
|
|
||||||
task javadoc(type: Javadoc) { |
|
||||||
source = android.sourceSets.main.java.srcDirs |
|
||||||
classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) |
|
||||||
classpath += project.files("${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar") |
|
||||||
project.android.libraryVariants.all { variant -> |
|
||||||
if (variant.name == 'release') { |
|
||||||
classpath += files(variant.javaCompile.classpath) |
|
||||||
} |
|
||||||
} |
|
||||||
exclude '**/BuildConfig.java' |
|
||||||
exclude '**/R.java' |
|
||||||
// This excludes our internal folder, which is nice, but also creates |
|
||||||
// errors anytime we reference excluded classes in public classes javadocs, |
|
||||||
// which is unfortunate. There might be a fix but I don't know any. |
|
||||||
// exclude '**/internal/**' |
|
||||||
} |
|
||||||
|
|
||||||
task javadocJar(type: Jar, dependsOn: javadoc) { |
|
||||||
archiveClassifier.set('javadoc') |
|
||||||
from javadoc.destinationDir |
|
||||||
} |
|
||||||
|
|
||||||
artifacts { |
|
||||||
archives javadocJar |
|
||||||
archives sourcesJar |
|
||||||
} |
|
||||||
|
|
||||||
//endregion |
|
||||||
|
|
||||||
//region code coverage |
|
||||||
|
|
||||||
// 1. running androidTests with connectedCheck will generate an .ec file |
|
||||||
// in build/outputs/code-coverage/connected, plus the XML result in |
|
||||||
// in build/reports/coverage/debug/report.xml . |
|
||||||
|
|
||||||
// 2. running unit tests with testDebugUnitTest will just generate the .exec file. |
|
||||||
// The JacocoReport task from the jacoco plugin can create the XML report out of it. |
|
||||||
|
|
||||||
// to have a unified report, we just pass both the .exec and the .ec file |
|
||||||
// to the jacoco task, so we get a unified XML report with total coverage. |
|
||||||
// Reference: https://medium.com/@rafael_toledo/setting-up-an-unified-coverage-report-in-android-with-jacoco-robolectric-and-espresso-ffe239aaf3fa |
|
||||||
|
|
||||||
apply plugin: 'jacoco' |
|
||||||
|
|
||||||
def reportsDirectory = "$buildDir/reports/" |
|
||||||
jacoco { |
|
||||||
toolVersion = "0.8.1" |
|
||||||
reportsDir = file(reportsDirectory) |
|
||||||
} |
|
||||||
|
|
||||||
task createCoverageReports() { |
|
||||||
dependsOn "testDebugUnitTest" |
|
||||||
dependsOn "connectedCheck" |
|
||||||
} |
|
||||||
|
|
||||||
task mergeCoverageReports(type: JacocoReport) { |
|
||||||
// Let this be called without running the tests. |
|
||||||
// dependsOn "createCoverageReports" |
|
||||||
// However, ensure we have the compiled .class files. |
|
||||||
dependsOn "compileDebugSources" |
|
||||||
|
|
||||||
// Merge unit tests and android tests data |
|
||||||
executionData = fileTree(dir: "$buildDir", includes: [ |
|
||||||
"jacoco/testDebugUnitTest.exec", // Unit tests |
|
||||||
"outputs/code_coverage/debugAndroidTest/connected/*coverage.ec" // Android tests |
|
||||||
]) |
|
||||||
|
|
||||||
// Sources |
|
||||||
sourceDirectories = files(android.sourceSets.main.java.sourceFiles) |
|
||||||
additionalSourceDirs = files([ // Add BuildConfig and R. |
|
||||||
"$buildDir/generated/source/buildConfig/debug", |
|
||||||
"$buildDir/generated/source/r/debug" |
|
||||||
]) |
|
||||||
|
|
||||||
// Classes (.class files) |
|
||||||
// Not everything in the filter relates to CameraView, |
|
||||||
// but let's keep a generic filter |
|
||||||
def classDir = "$buildDir/intermediates/javac/debug" |
|
||||||
def classFilter = [ |
|
||||||
'**/R.class', |
|
||||||
'**/R$*.class', |
|
||||||
'**/BuildConfig.*', |
|
||||||
'**/Manifest*.*', |
|
||||||
'android/**', |
|
||||||
'androidx/**', |
|
||||||
'com/google/**', |
|
||||||
'**/*$ViewInjector*.*', |
|
||||||
'**/Dagger*Component.class', |
|
||||||
'**/Dagger*Component$Builder.class', |
|
||||||
'**/*Module_*Factory.class', |
|
||||||
] |
|
||||||
if (isCI) { |
|
||||||
// All these classes are tested by the integration tests that we are not able to |
|
||||||
// run on the CI emulator. |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/engine/CameraEngine**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/engine/Camera1Engine**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/engine/Camera2Engine**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/engine/action/**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/engine/lock/**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/engine/meter/**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/picture/**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/video/**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/orchestrator/**.*') |
|
||||||
// classFilter.add('**/com/otaliastudios/cameraview/video/encoding/**.*') |
|
||||||
} |
|
||||||
// We don't test OpenGL filters. |
|
||||||
classFilter.add('**/com/otaliastudios/cameraview/filters/**.*') |
|
||||||
|
|
||||||
classDirectories = fileTree(dir: classDir, excludes: classFilter); |
|
||||||
|
|
||||||
reports.html.enabled = true |
|
||||||
reports.xml.enabled = true |
|
||||||
reports.xml.destination file("$reportsDirectory/mergedCoverageReport/report.xml") |
|
||||||
} |
|
||||||
|
|
||||||
//endregion |
|
||||||
|
|
||||||
// To deploy ./gradlew bintrayUpload |
|
||||||
|
|
@ -0,0 +1,137 @@ |
|||||||
|
import io.deepmedia.tools.publisher.common.License |
||||||
|
import io.deepmedia.tools.publisher.common.Release |
||||||
|
import io.deepmedia.tools.publisher.common.GithubScm |
||||||
|
|
||||||
|
plugins { |
||||||
|
id("com.android.library") |
||||||
|
id("kotlin-android") |
||||||
|
id("io.deepmedia.tools.publisher") |
||||||
|
id("jacoco") |
||||||
|
} |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdk = property("compileSdkVersion") as Int |
||||||
|
defaultConfig { |
||||||
|
minSdk = property("minSdkVersion") as Int |
||||||
|
targetSdk = property("targetSdkVersion") as Int |
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" |
||||||
|
testInstrumentationRunnerArguments["filter"] = "" + |
||||||
|
"com.otaliastudios.cameraview.tools.SdkExcludeFilter," + |
||||||
|
"com.otaliastudios.cameraview.tools.SdkIncludeFilter" |
||||||
|
} |
||||||
|
buildTypes["debug"].isTestCoverageEnabled = true |
||||||
|
buildTypes["release"].isMinifyEnabled = false |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
testImplementation("junit:junit:4.13.1") |
||||||
|
testImplementation("org.mockito:mockito-inline:2.28.2") |
||||||
|
|
||||||
|
androidTestImplementation("androidx.test:runner:1.4.0") |
||||||
|
androidTestImplementation("androidx.test:rules:1.4.0") |
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.1.3") |
||||||
|
androidTestImplementation("org.mockito:mockito-android:2.28.2") |
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") |
||||||
|
|
||||||
|
api("androidx.exifinterface:exifinterface:1.3.3") |
||||||
|
api("androidx.lifecycle:lifecycle-common:2.3.1") |
||||||
|
api("com.google.android.gms:play-services-tasks:17.2.1") |
||||||
|
implementation("androidx.annotation:annotation:1.2.0") |
||||||
|
implementation("com.otaliastudios.opengl:egloo:0.6.1") |
||||||
|
} |
||||||
|
|
||||||
|
// Publishing |
||||||
|
|
||||||
|
publisher { |
||||||
|
project.description = "A well documented, high-level Android interface that makes capturing " + |
||||||
|
"pictures and videos easy, addressing all of the common issues and needs. " + |
||||||
|
"Real-time filters, gestures, watermarks, frame processing, RAW, output of any size." |
||||||
|
project.artifact = "cameraview" |
||||||
|
project.group = "com.otaliastudios" |
||||||
|
project.url = "https://github.com/natario1/CameraView" |
||||||
|
project.scm = GithubScm("natario1", "CameraView") |
||||||
|
project.addLicense(License.APACHE_2_0) |
||||||
|
project.addDeveloper("natario1", "mat.iavarone@gmail.com") |
||||||
|
release.sources = Release.SOURCES_AUTO |
||||||
|
release.docs = Release.DOCS_AUTO |
||||||
|
release.version = "2.7.2" |
||||||
|
|
||||||
|
directory() |
||||||
|
|
||||||
|
sonatype { |
||||||
|
auth.user = "SONATYPE_USER" |
||||||
|
auth.password = "SONATYPE_PASSWORD" |
||||||
|
signing.key = "SIGNING_KEY" |
||||||
|
signing.password = "SIGNING_PASSWORD" |
||||||
|
} |
||||||
|
|
||||||
|
sonatype("snapshot") { |
||||||
|
repository = io.deepmedia.tools.publisher.sonatype.Sonatype.OSSRH_SNAPSHOT_1 |
||||||
|
release.version = "latest-SNAPSHOT" |
||||||
|
auth.user = "SONATYPE_USER" |
||||||
|
auth.password = "SONATYPE_PASSWORD" |
||||||
|
signing.key = "SIGNING_KEY" |
||||||
|
signing.password = "SIGNING_PASSWORD" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Code Coverage |
||||||
|
val buildDir = project.buildDir.absolutePath |
||||||
|
val coverageInputDir = "$buildDir/coverage_input" // changing? change github workflow |
||||||
|
val coverageOutputDir = "$buildDir/coverage_output" // changing? change github workflow |
||||||
|
|
||||||
|
// Run unit tests, with coverage enabled in the android { } configuration. |
||||||
|
// Output will be an .exec file in build/jacoco. |
||||||
|
tasks.register("runUnitTests") { // changing name? change github workflow |
||||||
|
dependsOn("testDebugUnitTest") |
||||||
|
doLast { |
||||||
|
copy { |
||||||
|
from("$buildDir/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec") |
||||||
|
into("$coverageInputDir/unit_tests") // changing? change github workflow |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Run android tests with coverage. |
||||||
|
tasks.register("runAndroidTests") { // changing name? change github workflow |
||||||
|
dependsOn("connectedDebugAndroidTest") |
||||||
|
doLast { |
||||||
|
copy { |
||||||
|
from("$buildDir/outputs/code_coverage/debugAndroidTest/connected") |
||||||
|
include("*coverage.ec") |
||||||
|
into("$coverageInputDir/android_tests") // changing? change github workflow |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Merge the two with a jacoco task. |
||||||
|
jacoco { toolVersion = "0.8.5" } |
||||||
|
tasks.register("computeCoverage", JacocoReport::class) { |
||||||
|
dependsOn("compileDebugSources") // Compile sources, needed below |
||||||
|
executionData.from(fileTree(coverageInputDir)) |
||||||
|
sourceDirectories.from(android.sourceSets["main"].java.srcDirs) |
||||||
|
additionalSourceDirs.from("$buildDir/generated/source/buildConfig/debug") |
||||||
|
additionalSourceDirs.from("$buildDir/generated/source/r/debug") |
||||||
|
classDirectories.from(fileTree("$buildDir/intermediates/javac/debug") { |
||||||
|
// Not everything here is relevant for CameraView, but let's keep it generic |
||||||
|
exclude( |
||||||
|
"**/R.class", |
||||||
|
"**/R$*.class", |
||||||
|
"**/BuildConfig.*", |
||||||
|
"**/Manifest*.*", |
||||||
|
"android/**", |
||||||
|
"androidx/**", |
||||||
|
"com/google/**", |
||||||
|
"**/*\$ViewInjector*.*", |
||||||
|
"**/Dagger*Component.class", |
||||||
|
"**/Dagger*Component\$Builder.class", |
||||||
|
"**/*Module_*Factory.class", |
||||||
|
// We don"t test OpenGL filters. |
||||||
|
"**/com/otaliastudios/cameraview/filters/**.*" |
||||||
|
) |
||||||
|
}) |
||||||
|
reports.html.required.set(true) |
||||||
|
reports.xml.required.set(true) |
||||||
|
reports.html.outputLocation.set(file("$coverageOutputDir/html")) |
||||||
|
reports.xml.outputLocation.set(file("$coverageOutputDir/xml/report.xml")) |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
package com.otaliastudios.cameraview.controls; |
||||||
|
|
||||||
|
|
||||||
|
import android.os.Build; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.RequiresApi; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.CameraView; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constants for selecting the encoder of audio recordings. |
||||||
|
* https://developer.android.com/guide/topics/media/media-formats.html#audio-formats
|
||||||
|
* |
||||||
|
* @see CameraView#setAudioCodec(AudioCodec) |
||||||
|
*/ |
||||||
|
public enum AudioCodec implements Control { |
||||||
|
|
||||||
|
/** |
||||||
|
* Let the device choose its codec. |
||||||
|
*/ |
||||||
|
DEVICE_DEFAULT(0), |
||||||
|
|
||||||
|
/** |
||||||
|
* The AAC codec. |
||||||
|
*/ |
||||||
|
AAC(1), |
||||||
|
|
||||||
|
/** |
||||||
|
* The HE_AAC codec. |
||||||
|
*/ |
||||||
|
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) |
||||||
|
HE_AAC(2), |
||||||
|
|
||||||
|
/** |
||||||
|
* The AAC_ELD codec. |
||||||
|
*/ |
||||||
|
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) |
||||||
|
AAC_ELD(3); |
||||||
|
|
||||||
|
static final AudioCodec DEFAULT = DEVICE_DEFAULT; |
||||||
|
|
||||||
|
private int value; |
||||||
|
|
||||||
|
AudioCodec(int value) { |
||||||
|
this.value = value; |
||||||
|
} |
||||||
|
|
||||||
|
int value() { |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
static AudioCodec fromValue(int value) { |
||||||
|
AudioCodec[] list = AudioCodec.values(); |
||||||
|
for (AudioCodec action : list) { |
||||||
|
if (action.value() == value) { |
||||||
|
return action; |
||||||
|
} |
||||||
|
} |
||||||
|
return DEFAULT; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
|
import android.os.Build; |
||||||
|
import android.util.Log; |
||||||
|
import android.util.Range; |
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.CameraLogger; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
@RequiresApi(21) |
||||||
|
public class FpsRangeValidator { |
||||||
|
|
||||||
|
private final static CameraLogger LOG = CameraLogger.create("FpsRangeValidator"); |
||||||
|
private final static Map<String, List<Range<Integer>>> sIssues = new HashMap<>(); |
||||||
|
|
||||||
|
static { |
||||||
|
sIssues.put("Google Pixel 4", Arrays.asList(new Range<>(15, 60))); |
||||||
|
sIssues.put("Google Pixel 4a", Arrays.asList(new Range<>(15, 60))); |
||||||
|
sIssues.put("Google Pixel 4 XL", Arrays.asList(new Range<>(15, 60))); |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean validate(Range<Integer> range) { |
||||||
|
LOG.i("Build.MODEL:", Build.MODEL, "Build.BRAND:", Build.BRAND, "Build.MANUFACTURER:", Build.MANUFACTURER); |
||||||
|
String descriptor = Build.MANUFACTURER + " " + Build.MODEL; |
||||||
|
List<Range<Integer>> ranges = sIssues.get(descriptor); |
||||||
|
if (ranges != null && ranges.contains(range)) { |
||||||
|
LOG.i("Dropping range:", range); |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package com.otaliastudios.cameraview.preview; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
/** |
||||||
|
* Base interface for previews that support renderer frame callbacks, |
||||||
|
* see {@link RendererFrameCallback}. |
||||||
|
*/ |
||||||
|
public interface RendererCameraPreview { |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a {@link RendererFrameCallback} to receive renderer frame events. |
||||||
|
* @param callback a callback |
||||||
|
*/ |
||||||
|
void addRendererFrameCallback(@NonNull final RendererFrameCallback callback); |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes a {@link RendererFrameCallback} that was previously added to receive renderer |
||||||
|
* frame events. |
||||||
|
* @param callback a callback |
||||||
|
*/ |
||||||
|
void removeRendererFrameCallback(@NonNull final RendererFrameCallback callback); |
||||||
|
} |
@ -1,27 +0,0 @@ |
|||||||
apply plugin: 'com.android.application' |
|
||||||
|
|
||||||
android { |
|
||||||
compileSdkVersion rootProject.ext.compileSdkVersion |
|
||||||
|
|
||||||
defaultConfig { |
|
||||||
applicationId "com.otaliastudios.cameraview.demo" |
|
||||||
minSdkVersion rootProject.ext.minSdkVersion |
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion |
|
||||||
versionCode 1 |
|
||||||
versionName "1.0" |
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
|
||||||
vectorDrawables.useSupportLibrary = true |
|
||||||
} |
|
||||||
buildTypes { |
|
||||||
release { |
|
||||||
minifyEnabled false |
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
implementation project(':cameraview') |
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0' |
|
||||||
implementation 'com.google.android.material:material:1.1.0-beta01' |
|
||||||
} |
|
@ -0,0 +1,23 @@ |
|||||||
|
plugins { |
||||||
|
id("com.android.application") |
||||||
|
id("kotlin-android") |
||||||
|
} |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdk = property("compileSdkVersion") as Int |
||||||
|
defaultConfig { |
||||||
|
applicationId = "com.otaliastudios.cameraview.demo" |
||||||
|
minSdk = property("minSdkVersion") as Int |
||||||
|
targetSdk = property("targetSdkVersion") as Int |
||||||
|
versionCode = 1 |
||||||
|
versionName = "1.0" |
||||||
|
vectorDrawables.useSupportLibrary = true |
||||||
|
} |
||||||
|
sourceSets["main"].java.srcDir("src/main/kotlin") |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
implementation(project(":cameraview")) |
||||||
|
implementation("androidx.appcompat:appcompat:1.3.1") |
||||||
|
implementation("com.google.android.material:material:1.4.0") |
||||||
|
} |
@ -1,417 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.demo; |
|
||||||
|
|
||||||
import android.animation.ValueAnimator; |
|
||||||
import android.content.Intent; |
|
||||||
import android.content.pm.PackageManager; |
|
||||||
import android.graphics.Bitmap; |
|
||||||
import android.graphics.BitmapFactory; |
|
||||||
import android.graphics.Color; |
|
||||||
import android.graphics.ImageFormat; |
|
||||||
import android.graphics.PointF; |
|
||||||
import android.graphics.Rect; |
|
||||||
import android.graphics.YuvImage; |
|
||||||
import android.os.Bundle; |
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior; |
|
||||||
|
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import androidx.appcompat.app.AppCompatActivity; |
|
||||||
|
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewGroup; |
|
||||||
import android.view.ViewTreeObserver; |
|
||||||
import android.widget.Toast; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraException; |
|
||||||
import com.otaliastudios.cameraview.CameraListener; |
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
|
||||||
import com.otaliastudios.cameraview.CameraOptions; |
|
||||||
import com.otaliastudios.cameraview.CameraView; |
|
||||||
import com.otaliastudios.cameraview.PictureResult; |
|
||||||
import com.otaliastudios.cameraview.controls.Mode; |
|
||||||
import com.otaliastudios.cameraview.VideoResult; |
|
||||||
import com.otaliastudios.cameraview.controls.Preview; |
|
||||||
import com.otaliastudios.cameraview.filter.Filters; |
|
||||||
import com.otaliastudios.cameraview.filter.MultiFilter; |
|
||||||
import com.otaliastudios.cameraview.filters.BrightnessFilter; |
|
||||||
import com.otaliastudios.cameraview.filters.DuotoneFilter; |
|
||||||
import com.otaliastudios.cameraview.frame.Frame; |
|
||||||
import com.otaliastudios.cameraview.frame.FrameProcessor; |
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream; |
|
||||||
import java.io.File; |
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
|
|
||||||
public class CameraActivity extends AppCompatActivity implements View.OnClickListener, OptionView.Callback { |
|
||||||
|
|
||||||
private final static CameraLogger LOG = CameraLogger.create("DemoApp"); |
|
||||||
private final static boolean USE_FRAME_PROCESSOR = true; |
|
||||||
private final static boolean DECODE_BITMAP = false; |
|
||||||
|
|
||||||
private CameraView camera; |
|
||||||
private ViewGroup controlPanel; |
|
||||||
private long mCaptureTime; |
|
||||||
|
|
||||||
private int mCurrentFilter = 0; |
|
||||||
private final Filters[] mAllFilters = Filters.values(); |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void onCreate(Bundle savedInstanceState) { |
|
||||||
super.onCreate(savedInstanceState); |
|
||||||
setContentView(R.layout.activity_camera); |
|
||||||
CameraLogger.setLogLevel(CameraLogger.LEVEL_VERBOSE); |
|
||||||
|
|
||||||
camera = findViewById(R.id.camera); |
|
||||||
camera.setLifecycleOwner(this); |
|
||||||
camera.addCameraListener(new Listener()); |
|
||||||
|
|
||||||
if (USE_FRAME_PROCESSOR) { |
|
||||||
camera.addFrameProcessor(new FrameProcessor() { |
|
||||||
private long lastTime = System.currentTimeMillis(); |
|
||||||
|
|
||||||
@Override |
|
||||||
public void process(@NonNull Frame frame) { |
|
||||||
long newTime = frame.getTime(); |
|
||||||
long delay = newTime - lastTime; |
|
||||||
lastTime = newTime; |
|
||||||
LOG.v("Frame delayMillis:", delay, "FPS:", 1000 / delay); |
|
||||||
if (DECODE_BITMAP) { |
|
||||||
if (frame.getFormat() == ImageFormat.NV21 |
|
||||||
&& frame.getDataClass() == byte[].class) { |
|
||||||
byte[] data = frame.getData(); |
|
||||||
YuvImage yuvImage = new YuvImage(data, |
|
||||||
frame.getFormat(), |
|
||||||
frame.getSize().getWidth(), |
|
||||||
frame.getSize().getHeight(), |
|
||||||
null); |
|
||||||
ByteArrayOutputStream jpegStream = new ByteArrayOutputStream(); |
|
||||||
yuvImage.compressToJpeg(new Rect(0, 0, |
|
||||||
frame.getSize().getWidth(), |
|
||||||
frame.getSize().getHeight()), 100, jpegStream); |
|
||||||
byte[] jpegByteArray = jpegStream.toByteArray(); |
|
||||||
Bitmap bitmap = BitmapFactory.decodeByteArray(jpegByteArray, |
|
||||||
0, jpegByteArray.length); |
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
bitmap.toString(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
findViewById(R.id.edit).setOnClickListener(this); |
|
||||||
findViewById(R.id.capturePicture).setOnClickListener(this); |
|
||||||
findViewById(R.id.capturePictureSnapshot).setOnClickListener(this); |
|
||||||
findViewById(R.id.captureVideo).setOnClickListener(this); |
|
||||||
findViewById(R.id.captureVideoSnapshot).setOnClickListener(this); |
|
||||||
findViewById(R.id.toggleCamera).setOnClickListener(this); |
|
||||||
findViewById(R.id.changeFilter).setOnClickListener(this); |
|
||||||
|
|
||||||
controlPanel = findViewById(R.id.controls); |
|
||||||
ViewGroup group = (ViewGroup) controlPanel.getChildAt(0); |
|
||||||
final View watermark = findViewById(R.id.watermark); |
|
||||||
|
|
||||||
List<Option<?>> options = Arrays.asList( |
|
||||||
// Layout
|
|
||||||
new Option.Width(), new Option.Height(), |
|
||||||
// Engine and preview
|
|
||||||
new Option.Mode(), new Option.Engine(), new Option.Preview(), |
|
||||||
// Some controls
|
|
||||||
new Option.Flash(), new Option.WhiteBalance(), new Option.Hdr(), |
|
||||||
new Option.PictureMetering(), new Option.PictureSnapshotMetering(), |
|
||||||
new Option.PictureFormat(), |
|
||||||
// Video recording
|
|
||||||
new Option.PreviewFrameRate(), new Option.VideoCodec(), new Option.Audio(), |
|
||||||
// Gestures
|
|
||||||
new Option.Pinch(), new Option.HorizontalScroll(), new Option.VerticalScroll(), |
|
||||||
new Option.Tap(), new Option.LongTap(), |
|
||||||
// Watermarks
|
|
||||||
new Option.OverlayInPreview(watermark), |
|
||||||
new Option.OverlayInPictureSnapshot(watermark), |
|
||||||
new Option.OverlayInVideoSnapshot(watermark), |
|
||||||
// Frame Processing
|
|
||||||
new Option.FrameProcessingFormat(), |
|
||||||
// Other
|
|
||||||
new Option.Grid(), new Option.GridColor(), new Option.UseDeviceOrientation() |
|
||||||
); |
|
||||||
List<Boolean> dividers = Arrays.asList( |
|
||||||
// Layout
|
|
||||||
false, true, |
|
||||||
// Engine and preview
|
|
||||||
false, false, true, |
|
||||||
// Some controls
|
|
||||||
false, false, false, false, false, true, |
|
||||||
// Video recording
|
|
||||||
false, false, true, |
|
||||||
// Gestures
|
|
||||||
false, false, false, false, true, |
|
||||||
// Watermarks
|
|
||||||
false, false, true, |
|
||||||
// Frame Processing
|
|
||||||
true, |
|
||||||
// Other
|
|
||||||
false, false, true |
|
||||||
); |
|
||||||
for (int i = 0; i < options.size(); i++) { |
|
||||||
OptionView view = new OptionView(this); |
|
||||||
//noinspection unchecked
|
|
||||||
view.setOption(options.get(i), this); |
|
||||||
view.setHasDivider(dividers.get(i)); |
|
||||||
group.addView(view, |
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT, |
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT); |
|
||||||
} |
|
||||||
|
|
||||||
controlPanel.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { |
|
||||||
@Override |
|
||||||
public void onGlobalLayout() { |
|
||||||
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel); |
|
||||||
b.setState(BottomSheetBehavior.STATE_HIDDEN); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
// Animate the watermark just to show we record the animation in video snapshots
|
|
||||||
ValueAnimator animator = ValueAnimator.ofFloat(1F, 0.8F); |
|
||||||
animator.setDuration(300); |
|
||||||
animator.setRepeatCount(ValueAnimator.INFINITE); |
|
||||||
animator.setRepeatMode(ValueAnimator.REVERSE); |
|
||||||
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
|
||||||
@Override |
|
||||||
public void onAnimationUpdate(ValueAnimator animation) { |
|
||||||
float scale = (float) animation.getAnimatedValue(); |
|
||||||
watermark.setScaleX(scale); |
|
||||||
watermark.setScaleY(scale); |
|
||||||
watermark.setRotation(watermark.getRotation() + 2); |
|
||||||
} |
|
||||||
}); |
|
||||||
animator.start(); |
|
||||||
} |
|
||||||
|
|
||||||
private void message(@NonNull String content, boolean important) { |
|
||||||
if (important) { |
|
||||||
LOG.w(content); |
|
||||||
Toast.makeText(this, content, Toast.LENGTH_LONG).show(); |
|
||||||
} else { |
|
||||||
LOG.i(content); |
|
||||||
Toast.makeText(this, content, Toast.LENGTH_SHORT).show(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private class Listener extends CameraListener { |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onCameraOpened(@NonNull CameraOptions options) { |
|
||||||
ViewGroup group = (ViewGroup) controlPanel.getChildAt(0); |
|
||||||
for (int i = 0; i < group.getChildCount(); i++) { |
|
||||||
OptionView view = (OptionView) group.getChildAt(i); |
|
||||||
view.onCameraOpened(camera, options); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onCameraError(@NonNull CameraException exception) { |
|
||||||
super.onCameraError(exception); |
|
||||||
message("Got CameraException #" + exception.getReason(), true); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onPictureTaken(@NonNull PictureResult result) { |
|
||||||
super.onPictureTaken(result); |
|
||||||
if (camera.isTakingVideo()) { |
|
||||||
message("Captured while taking video. Size=" + result.getSize(), false); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// This can happen if picture was taken with a gesture.
|
|
||||||
long callbackTime = System.currentTimeMillis(); |
|
||||||
if (mCaptureTime == 0) mCaptureTime = callbackTime - 300; |
|
||||||
LOG.w("onPictureTaken called! Launching activity. Delay:", callbackTime - mCaptureTime); |
|
||||||
PicturePreviewActivity.setPictureResult(result); |
|
||||||
Intent intent = new Intent(CameraActivity.this, PicturePreviewActivity.class); |
|
||||||
intent.putExtra("delay", callbackTime - mCaptureTime); |
|
||||||
startActivity(intent); |
|
||||||
mCaptureTime = 0; |
|
||||||
LOG.w("onPictureTaken called! Launched activity."); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onVideoTaken(@NonNull VideoResult result) { |
|
||||||
super.onVideoTaken(result); |
|
||||||
LOG.w("onVideoTaken called! Launching activity."); |
|
||||||
VideoPreviewActivity.setVideoResult(result); |
|
||||||
Intent intent = new Intent(CameraActivity.this, VideoPreviewActivity.class); |
|
||||||
startActivity(intent); |
|
||||||
LOG.w("onVideoTaken called! Launched activity."); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onVideoRecordingStart() { |
|
||||||
super.onVideoRecordingStart(); |
|
||||||
LOG.w("onVideoRecordingStart!"); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onVideoRecordingEnd() { |
|
||||||
super.onVideoRecordingEnd(); |
|
||||||
message("Video taken. Processing...", false); |
|
||||||
LOG.w("onVideoRecordingEnd!"); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers) { |
|
||||||
super.onExposureCorrectionChanged(newValue, bounds, fingers); |
|
||||||
message("Exposure correction:" + newValue, false); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onZoomChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers) { |
|
||||||
super.onZoomChanged(newValue, bounds, fingers); |
|
||||||
message("Zoom:" + newValue, false); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onClick(View view) { |
|
||||||
switch (view.getId()) { |
|
||||||
case R.id.edit: edit(); break; |
|
||||||
case R.id.capturePicture: capturePicture(); break; |
|
||||||
case R.id.capturePictureSnapshot: capturePictureSnapshot(); break; |
|
||||||
case R.id.captureVideo: captureVideo(); break; |
|
||||||
case R.id.captureVideoSnapshot: captureVideoSnapshot(); break; |
|
||||||
case R.id.toggleCamera: toggleCamera(); break; |
|
||||||
case R.id.changeFilter: changeCurrentFilter(); break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onBackPressed() { |
|
||||||
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel); |
|
||||||
if (b.getState() != BottomSheetBehavior.STATE_HIDDEN) { |
|
||||||
b.setState(BottomSheetBehavior.STATE_HIDDEN); |
|
||||||
return; |
|
||||||
} |
|
||||||
super.onBackPressed(); |
|
||||||
} |
|
||||||
|
|
||||||
private void edit() { |
|
||||||
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel); |
|
||||||
b.setState(BottomSheetBehavior.STATE_COLLAPSED); |
|
||||||
} |
|
||||||
|
|
||||||
private void capturePicture() { |
|
||||||
if (camera.getMode() == Mode.VIDEO) { |
|
||||||
message("Can't take HQ pictures while in VIDEO mode.", false); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (camera.isTakingPicture()) return; |
|
||||||
mCaptureTime = System.currentTimeMillis(); |
|
||||||
message("Capturing picture...", false); |
|
||||||
camera.takePicture(); |
|
||||||
} |
|
||||||
|
|
||||||
private void capturePictureSnapshot() { |
|
||||||
if (camera.isTakingPicture()) return; |
|
||||||
if (camera.getPreview() != Preview.GL_SURFACE) { |
|
||||||
message("Picture snapshots are only allowed with the GL_SURFACE preview.", true); |
|
||||||
return; |
|
||||||
} |
|
||||||
mCaptureTime = System.currentTimeMillis(); |
|
||||||
message("Capturing picture snapshot...", false); |
|
||||||
camera.takePictureSnapshot(); |
|
||||||
} |
|
||||||
|
|
||||||
private void captureVideo() { |
|
||||||
if (camera.getMode() == Mode.PICTURE) { |
|
||||||
message("Can't record HQ videos while in PICTURE mode.", false); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (camera.isTakingPicture() || camera.isTakingVideo()) return; |
|
||||||
message("Recording for 5 seconds...", true); |
|
||||||
camera.takeVideo(new File(getFilesDir(), "video.mp4"), 5000); |
|
||||||
} |
|
||||||
|
|
||||||
private void captureVideoSnapshot() { |
|
||||||
if (camera.isTakingVideo()) { |
|
||||||
message("Already taking video.", false); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (camera.getPreview() != Preview.GL_SURFACE) { |
|
||||||
message("Video snapshots are only allowed with the GL_SURFACE preview.", true); |
|
||||||
return; |
|
||||||
} |
|
||||||
message("Recording snapshot for 5 seconds...", true); |
|
||||||
camera.takeVideoSnapshot(new File(getFilesDir(), "video.mp4"), 5000); |
|
||||||
} |
|
||||||
|
|
||||||
private void toggleCamera() { |
|
||||||
if (camera.isTakingPicture() || camera.isTakingVideo()) return; |
|
||||||
switch (camera.toggleFacing()) { |
|
||||||
case BACK: |
|
||||||
message("Switched to back camera!", false); |
|
||||||
break; |
|
||||||
|
|
||||||
case FRONT: |
|
||||||
message("Switched to front camera!", false); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void changeCurrentFilter() { |
|
||||||
if (camera.getPreview() != Preview.GL_SURFACE) { |
|
||||||
message("Filters are supported only when preview is Preview.GL_SURFACE.", true); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (mCurrentFilter < mAllFilters.length - 1) { |
|
||||||
mCurrentFilter++; |
|
||||||
} else { |
|
||||||
mCurrentFilter = 0; |
|
||||||
} |
|
||||||
Filters filter = mAllFilters[mCurrentFilter]; |
|
||||||
message(filter.toString(), false); |
|
||||||
|
|
||||||
// Normal behavior:
|
|
||||||
camera.setFilter(filter.newInstance()); |
|
||||||
|
|
||||||
// To test MultiFilter:
|
|
||||||
// DuotoneFilter duotone = new DuotoneFilter();
|
|
||||||
// duotone.setFirstColor(Color.RED);
|
|
||||||
// duotone.setSecondColor(Color.GREEN);
|
|
||||||
// camera.setFilter(new MultiFilter(duotone, filter.newInstance()));
|
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public <T> boolean onValueChanged(@NonNull Option<T> option, @NonNull T value, @NonNull String name) { |
|
||||||
if ((option instanceof Option.Width || option instanceof Option.Height)) { |
|
||||||
Preview preview = camera.getPreview(); |
|
||||||
boolean wrapContent = (Integer) value == ViewGroup.LayoutParams.WRAP_CONTENT; |
|
||||||
if (preview == Preview.SURFACE && !wrapContent) { |
|
||||||
message("The SurfaceView preview does not support width or height changes. " + |
|
||||||
"The view will act as WRAP_CONTENT by default.", true); |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
option.set(camera, value); |
|
||||||
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel); |
|
||||||
b.setState(BottomSheetBehavior.STATE_HIDDEN); |
|
||||||
message("Changed " + option.getName() + " to " + name, false); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
//region Permissions
|
|
||||||
|
|
||||||
@Override |
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
|
||||||
boolean valid = true; |
|
||||||
for (int grantResult : grantResults) { |
|
||||||
valid = valid && grantResult == PackageManager.PERMISSION_GRANTED; |
|
||||||
} |
|
||||||
if (valid && !camera.isOpened()) { |
|
||||||
camera.open(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//endregion
|
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.demo; |
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import android.util.AttributeSet; |
|
||||||
import android.view.ViewGroup; |
|
||||||
import android.widget.LinearLayout; |
|
||||||
import android.widget.TextView; |
|
||||||
|
|
||||||
public class MessageView extends LinearLayout { |
|
||||||
|
|
||||||
private TextView message; |
|
||||||
private TextView title; |
|
||||||
|
|
||||||
public MessageView(Context context) { |
|
||||||
this(context, null); |
|
||||||
} |
|
||||||
|
|
||||||
public MessageView(Context context, @Nullable AttributeSet attrs) { |
|
||||||
this(context, attrs, 0); |
|
||||||
} |
|
||||||
|
|
||||||
public MessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { |
|
||||||
super(context, attrs, defStyleAttr); |
|
||||||
setOrientation(VERTICAL); |
|
||||||
inflate(context, R.layout.option_view, this); |
|
||||||
ViewGroup content = findViewById(R.id.content); |
|
||||||
inflate(context, R.layout.spinner_text, content); |
|
||||||
title = findViewById(R.id.title); |
|
||||||
message = (TextView) content.getChildAt(0); |
|
||||||
} |
|
||||||
|
|
||||||
public void setTitleAndMessage(String title, String message) { |
|
||||||
setTitle(title); |
|
||||||
setMessage(message); |
|
||||||
} |
|
||||||
|
|
||||||
public void setTitle(String title) { |
|
||||||
this.title.setText(title); |
|
||||||
} |
|
||||||
|
|
||||||
public void setMessage(String message) { |
|
||||||
this.message.setText(message); |
|
||||||
} |
|
||||||
} |
|
@ -1,599 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.demo; |
|
||||||
|
|
||||||
import android.graphics.Color; |
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.core.util.Pair; |
|
||||||
|
|
||||||
import android.graphics.ImageFormat; |
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewGroup; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraListener; |
|
||||||
import com.otaliastudios.cameraview.CameraOptions; |
|
||||||
import com.otaliastudios.cameraview.CameraView; |
|
||||||
import com.otaliastudios.cameraview.controls.Control; |
|
||||||
import com.otaliastudios.cameraview.gesture.Gesture; |
|
||||||
import com.otaliastudios.cameraview.gesture.GestureAction; |
|
||||||
import com.otaliastudios.cameraview.overlay.Overlay; |
|
||||||
import com.otaliastudios.cameraview.overlay.OverlayLayout; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.Collection; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
/** |
|
||||||
* Controls that we want to display in a ControlView. |
|
||||||
*/ |
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
public abstract class Option<T> { |
|
||||||
|
|
||||||
private String name; |
|
||||||
|
|
||||||
private Option(@NonNull String name) { |
|
||||||
this.name = name; |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
@NonNull |
|
||||||
public final String getName() { |
|
||||||
return name; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public abstract T get(@NonNull CameraView view); |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public abstract Collection<T> getAll(@NonNull CameraView view, @NonNull CameraOptions options); |
|
||||||
|
|
||||||
public abstract void set(@NonNull CameraView view, @NonNull T value); |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public String toString(@NonNull T value) { |
|
||||||
return String.valueOf(value).replace("_", " ").toLowerCase(); |
|
||||||
} |
|
||||||
|
|
||||||
public static class Width extends Option<Integer> { |
|
||||||
public Width() { |
|
||||||
super("Width"); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Integer> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
View root = (View) view.getParent(); |
|
||||||
List<Integer> list = new ArrayList<>(); |
|
||||||
int boundary = root.getWidth(); |
|
||||||
if (boundary == 0) boundary = 1000; |
|
||||||
int step = boundary / 10; |
|
||||||
list.add(ViewGroup.LayoutParams.WRAP_CONTENT); |
|
||||||
list.add(ViewGroup.LayoutParams.MATCH_PARENT); |
|
||||||
for (int i = step; i < boundary; i += step) { |
|
||||||
list.add(i); |
|
||||||
} |
|
||||||
return list; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Integer get(@NonNull CameraView view) { |
|
||||||
return view.getLayoutParams().width; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Integer value) { |
|
||||||
view.getLayoutParams().width = (int) value; |
|
||||||
view.setLayoutParams(view.getLayoutParams()); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public String toString(@NonNull Integer value) { |
|
||||||
if (value == ViewGroup.LayoutParams.MATCH_PARENT) return "match parent"; |
|
||||||
if (value == ViewGroup.LayoutParams.WRAP_CONTENT) return "wrap content"; |
|
||||||
return super.toString(value); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Height extends Option<Integer> { |
|
||||||
public Height() { |
|
||||||
super("Height"); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Integer> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
View root = (View) view.getParent(); |
|
||||||
ArrayList<Integer> list = new ArrayList<>(); |
|
||||||
int boundary = root.getHeight(); |
|
||||||
if (boundary == 0) boundary = 1000; |
|
||||||
int step = boundary / 10; |
|
||||||
list.add(ViewGroup.LayoutParams.WRAP_CONTENT); |
|
||||||
list.add(ViewGroup.LayoutParams.MATCH_PARENT); |
|
||||||
for (int i = step; i < boundary; i += step) { |
|
||||||
list.add(i); |
|
||||||
} |
|
||||||
return list; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Integer get(@NonNull CameraView view) { |
|
||||||
return view.getLayoutParams().height; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Integer value) { |
|
||||||
view.getLayoutParams().height = (int) value; |
|
||||||
view.setLayoutParams(view.getLayoutParams()); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public String toString(@NonNull Integer value) { |
|
||||||
if (value == ViewGroup.LayoutParams.MATCH_PARENT) return "match parent"; |
|
||||||
if (value == ViewGroup.LayoutParams.WRAP_CONTENT) return "wrap content"; |
|
||||||
return super.toString(value); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static abstract class ControlOption<T extends Control> extends Option<T> { |
|
||||||
private final Class<T> controlClass; |
|
||||||
|
|
||||||
ControlOption(@NonNull Class<T> controlClass, String name) { |
|
||||||
super(name); |
|
||||||
this.controlClass = controlClass; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull T value) { |
|
||||||
view.set(value); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public T get(@NonNull CameraView view) { |
|
||||||
return view.get(controlClass); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<T> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
return options.getSupportedControls(controlClass); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Mode extends ControlOption<com.otaliastudios.cameraview.controls.Mode> { |
|
||||||
public Mode() { |
|
||||||
super(com.otaliastudios.cameraview.controls.Mode.class, "Mode"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Engine extends ControlOption<com.otaliastudios.cameraview.controls.Engine> { |
|
||||||
public Engine() { |
|
||||||
super(com.otaliastudios.cameraview.controls.Engine.class, "Engine"); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(final @NonNull CameraView view, final @NonNull com.otaliastudios.cameraview.controls.Engine value) { |
|
||||||
boolean started = view.isOpened(); |
|
||||||
if (started) { |
|
||||||
view.addCameraListener(new CameraListener() { |
|
||||||
@Override |
|
||||||
public void onCameraClosed() { |
|
||||||
super.onCameraClosed(); |
|
||||||
view.removeCameraListener(this); |
|
||||||
view.setEngine(value); |
|
||||||
view.open(); |
|
||||||
} |
|
||||||
}); |
|
||||||
view.close(); |
|
||||||
} else { |
|
||||||
view.setEngine(value); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Preview extends ControlOption<com.otaliastudios.cameraview.controls.Preview> { |
|
||||||
public Preview() { |
|
||||||
super(com.otaliastudios.cameraview.controls.Preview.class, "Preview Surface"); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(final @NonNull CameraView view, final @NonNull com.otaliastudios.cameraview.controls.Preview value) { |
|
||||||
boolean opened = view.isOpened(); |
|
||||||
if (opened) { |
|
||||||
view.addCameraListener(new CameraListener() { |
|
||||||
@Override |
|
||||||
public void onCameraClosed() { |
|
||||||
super.onCameraClosed(); |
|
||||||
view.removeCameraListener(this); |
|
||||||
applyPreview(view, value); |
|
||||||
view.open(); |
|
||||||
} |
|
||||||
}); |
|
||||||
view.close(); |
|
||||||
} else { |
|
||||||
applyPreview(view, value); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// This is really tricky since the preview can only be changed when not attached to window.
|
|
||||||
private void applyPreview(@NonNull CameraView cameraView, |
|
||||||
@NonNull com.otaliastudios.cameraview.controls.Preview newPreview) { |
|
||||||
ViewGroup.LayoutParams params = cameraView.getLayoutParams(); |
|
||||||
ViewGroup parent = (ViewGroup) cameraView.getParent(); |
|
||||||
int index = 0; |
|
||||||
for (int i = 0; i < parent.getChildCount(); i++) { |
|
||||||
if (parent.getChildAt(i) == cameraView) { |
|
||||||
index = i; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
parent.removeView(cameraView); |
|
||||||
cameraView.setPreview(newPreview); |
|
||||||
parent.addView(cameraView, index, params); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Flash extends ControlOption<com.otaliastudios.cameraview.controls.Flash> { |
|
||||||
public Flash() { |
|
||||||
super(com.otaliastudios.cameraview.controls.Flash.class, "Flash"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class WhiteBalance extends ControlOption<com.otaliastudios.cameraview.controls.WhiteBalance> { |
|
||||||
public WhiteBalance() { |
|
||||||
super(com.otaliastudios.cameraview.controls.WhiteBalance.class, "White Balance"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Hdr extends ControlOption<com.otaliastudios.cameraview.controls.Hdr> { |
|
||||||
public Hdr() { |
|
||||||
super(com.otaliastudios.cameraview.controls.Hdr.class, "HDR"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class PictureMetering extends Option<Boolean> { |
|
||||||
|
|
||||||
public PictureMetering() { |
|
||||||
super("Picture Metering"); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Boolean get(@NonNull CameraView view) { |
|
||||||
return view.getPictureMetering(); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Boolean> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
return Arrays.asList(true, false); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Boolean value) { |
|
||||||
view.setPictureMetering(value); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class PictureSnapshotMetering extends Option<Boolean> { |
|
||||||
|
|
||||||
public PictureSnapshotMetering() { |
|
||||||
super("Picture Snapshot Metering"); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Boolean get(@NonNull CameraView view) { |
|
||||||
return view.getPictureSnapshotMetering(); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Boolean> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
return Arrays.asList(true, false); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Boolean value) { |
|
||||||
view.setPictureSnapshotMetering(value); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class VideoCodec extends ControlOption<com.otaliastudios.cameraview.controls.VideoCodec> { |
|
||||||
public VideoCodec() { |
|
||||||
super(com.otaliastudios.cameraview.controls.VideoCodec.class, "Video Codec"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Audio extends ControlOption<com.otaliastudios.cameraview.controls.Audio> { |
|
||||||
public Audio() { |
|
||||||
super(com.otaliastudios.cameraview.controls.Audio.class, "Audio"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static abstract class GestureOption extends Option<GestureAction> { |
|
||||||
private final Gesture gesture; |
|
||||||
private final GestureAction[] allActions = GestureAction.values(); |
|
||||||
|
|
||||||
GestureOption(@NonNull Gesture gesture, String name) { |
|
||||||
super(name); |
|
||||||
this.gesture = gesture; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull GestureAction value) { |
|
||||||
view.mapGesture(gesture, value); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public GestureAction get(@NonNull CameraView view) { |
|
||||||
return view.getGestureAction(gesture); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<GestureAction> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
List<GestureAction> list = new ArrayList<>(); |
|
||||||
for (GestureAction action : allActions) { |
|
||||||
if (gesture.isAssignableTo(action) && options.supports(action)) { |
|
||||||
list.add(action); |
|
||||||
} |
|
||||||
} |
|
||||||
return list; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Pinch extends GestureOption { |
|
||||||
public Pinch() { |
|
||||||
super(Gesture.PINCH, "Pinch"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class HorizontalScroll extends GestureOption { |
|
||||||
public HorizontalScroll() { |
|
||||||
super(Gesture.SCROLL_HORIZONTAL, "Horizontal Scroll"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class VerticalScroll extends GestureOption { |
|
||||||
public VerticalScroll() { |
|
||||||
super(Gesture.SCROLL_VERTICAL, "Vertical Scroll"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Tap extends GestureOption { |
|
||||||
public Tap() { |
|
||||||
super(Gesture.TAP, "Tap"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class LongTap extends GestureOption { |
|
||||||
public LongTap() { |
|
||||||
super(Gesture.LONG_TAP, "Long Tap"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static abstract class OverlayOption extends Option<Boolean> { |
|
||||||
private View overlay; |
|
||||||
private Overlay.Target target; |
|
||||||
|
|
||||||
OverlayOption(@NonNull Overlay.Target target, @NonNull String name, @NonNull View overlay) { |
|
||||||
super(name); |
|
||||||
this.overlay = overlay; |
|
||||||
this.target = target; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Boolean> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
return Arrays.asList(true, false); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Boolean get(@NonNull CameraView view) { |
|
||||||
OverlayLayout.LayoutParams params = (OverlayLayout.LayoutParams) overlay.getLayoutParams(); |
|
||||||
switch (target) { |
|
||||||
case PREVIEW: return params.drawOnPreview; |
|
||||||
case PICTURE_SNAPSHOT: return params.drawOnPictureSnapshot; |
|
||||||
case VIDEO_SNAPSHOT: return params.drawOnVideoSnapshot; |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Boolean value) { |
|
||||||
OverlayLayout.LayoutParams params = (OverlayLayout.LayoutParams) overlay.getLayoutParams(); |
|
||||||
switch (target) { |
|
||||||
case PREVIEW: params.drawOnPreview = value; break; |
|
||||||
case PICTURE_SNAPSHOT: params.drawOnPictureSnapshot = value; break; |
|
||||||
case VIDEO_SNAPSHOT: params.drawOnVideoSnapshot = value; break; |
|
||||||
} |
|
||||||
overlay.setLayoutParams(params); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class OverlayInPreview extends OverlayOption { |
|
||||||
public OverlayInPreview(@NonNull View overlay) { |
|
||||||
super(Overlay.Target.PREVIEW, "Overlay in Preview", overlay); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class OverlayInPictureSnapshot extends OverlayOption { |
|
||||||
public OverlayInPictureSnapshot(@NonNull View overlay) { |
|
||||||
super(Overlay.Target.PICTURE_SNAPSHOT, "Overlay in Picture Snapshot", overlay); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class OverlayInVideoSnapshot extends OverlayOption { |
|
||||||
public OverlayInVideoSnapshot(@NonNull View overlay) { |
|
||||||
super(Overlay.Target.VIDEO_SNAPSHOT, "Overlay in Video Snapshot", overlay); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Grid extends ControlOption<com.otaliastudios.cameraview.controls.Grid> { |
|
||||||
public Grid() { |
|
||||||
super(com.otaliastudios.cameraview.controls.Grid.class, "Grid Lines"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class GridColor extends Option<Pair<Integer, String>> { |
|
||||||
|
|
||||||
public GridColor() { |
|
||||||
super("Grid Color"); |
|
||||||
} |
|
||||||
|
|
||||||
private static final List<Pair<Integer, String>> ALL = Arrays.asList( |
|
||||||
new Pair<>(Color.argb(160, 255, 255, 255), "default"), |
|
||||||
new Pair<>(Color.WHITE, "white"), |
|
||||||
new Pair<>(Color.BLACK, "black"), |
|
||||||
new Pair<>(Color.YELLOW, "yellow") |
|
||||||
); |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Pair<Integer, String>> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
return ALL; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Pair<Integer, String> get(@NonNull CameraView view) { |
|
||||||
for (Pair<Integer, String> pair : ALL) { |
|
||||||
//noinspection ConstantConditions
|
|
||||||
if (pair.first == view.getGridColor()) { |
|
||||||
return pair; |
|
||||||
} |
|
||||||
} |
|
||||||
throw new RuntimeException("Could not find grid color"); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Pair<Integer, String> value) { |
|
||||||
//noinspection ConstantConditions
|
|
||||||
view.setGridColor(value.first); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public String toString(@NonNull Pair<Integer, String> value) { |
|
||||||
//noinspection ConstantConditions
|
|
||||||
return value.second; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class UseDeviceOrientation extends Option<Boolean> { |
|
||||||
public UseDeviceOrientation() { |
|
||||||
super("Use Device Orientation"); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Boolean> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
return Arrays.asList(true, false); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Boolean get(@NonNull CameraView view) { |
|
||||||
return view.getUseDeviceOrientation(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Boolean value) { |
|
||||||
view.setUseDeviceOrientation(value); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class PreviewFrameRate extends Option<Integer> { |
|
||||||
public PreviewFrameRate() { |
|
||||||
super("Preview FPS"); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Integer> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
float min = options.getPreviewFrameRateMinValue(); |
|
||||||
float max = options.getPreviewFrameRateMaxValue(); |
|
||||||
float delta = max - min; |
|
||||||
List<Integer> result = new ArrayList<>(); |
|
||||||
if (min == 0F && max == 0F) { |
|
||||||
return result; // empty list
|
|
||||||
} else if (delta < 0.005F) { |
|
||||||
result.add(Math.round(min)); |
|
||||||
return result; // single value
|
|
||||||
} else { |
|
||||||
final int steps = 3; |
|
||||||
final float step = delta / steps; |
|
||||||
for (int i = 0; i <= steps; i++) { |
|
||||||
result.add(Math.round(min)); |
|
||||||
min += step; |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Integer get(@NonNull CameraView view) { |
|
||||||
return Math.round(view.getPreviewFrameRate()); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Integer value) { |
|
||||||
view.setPreviewFrameRate((float) value); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class PictureFormat extends ControlOption<com.otaliastudios.cameraview.controls.PictureFormat> { |
|
||||||
public PictureFormat() { |
|
||||||
super(com.otaliastudios.cameraview.controls.PictureFormat.class, "Picture Format"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class FrameProcessingFormat extends Option<Integer> { |
|
||||||
|
|
||||||
FrameProcessingFormat() { |
|
||||||
super("Frame Processing Format"); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void set(@NonNull CameraView view, @NonNull Integer value) { |
|
||||||
view.setFrameProcessingFormat(value); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Integer get(@NonNull CameraView view) { |
|
||||||
return view.getFrameProcessingFormat(); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Collection<Integer> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { |
|
||||||
return options.getSupportedFrameProcessingFormats(); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public String toString(@NonNull Integer value) { |
|
||||||
switch (value) { |
|
||||||
case ImageFormat.NV21: return "NV21"; |
|
||||||
case ImageFormat.NV16: return "NV16"; |
|
||||||
case ImageFormat.JPEG: return "JPEG"; |
|
||||||
case ImageFormat.YUY2: return "YUY2"; |
|
||||||
case ImageFormat.YUV_420_888: return "YUV_420_888"; |
|
||||||
case ImageFormat.YUV_422_888: return "YUV_422_888"; |
|
||||||
case ImageFormat.YUV_444_888: return "YUV_444_888"; |
|
||||||
case ImageFormat.RAW10: return "RAW10"; |
|
||||||
case ImageFormat.RAW12: return "RAW12"; |
|
||||||
case ImageFormat.RAW_SENSOR: return "RAW_SENSOR"; |
|
||||||
} |
|
||||||
return super.toString(value); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,97 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.demo; |
|
||||||
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint; |
|
||||||
import android.content.Context; |
|
||||||
import android.util.Log; |
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewGroup; |
|
||||||
import android.widget.AdapterView; |
|
||||||
import android.widget.ArrayAdapter; |
|
||||||
import android.widget.LinearLayout; |
|
||||||
import android.widget.Spinner; |
|
||||||
import android.widget.TextView; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraOptions; |
|
||||||
import com.otaliastudios.cameraview.CameraView; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor") |
|
||||||
public class OptionView<Value> extends LinearLayout implements Spinner.OnItemSelectedListener { |
|
||||||
|
|
||||||
interface Callback { |
|
||||||
<T> boolean onValueChanged(@NonNull Option<T> option, @NonNull T value, @NonNull String name); |
|
||||||
} |
|
||||||
|
|
||||||
private Value value; |
|
||||||
private ArrayList<Value> values; |
|
||||||
private ArrayList<String> valuesStrings; |
|
||||||
private Option option; |
|
||||||
private Callback callback; |
|
||||||
private Spinner spinner; |
|
||||||
|
|
||||||
public OptionView(@NonNull Context context) { |
|
||||||
super(context); |
|
||||||
setOrientation(VERTICAL); |
|
||||||
inflate(context, R.layout.option_view, this); |
|
||||||
ViewGroup content = findViewById(R.id.content); |
|
||||||
spinner = new Spinner(context, Spinner.MODE_DROPDOWN); |
|
||||||
content.addView(spinner); |
|
||||||
} |
|
||||||
|
|
||||||
public void setHasDivider(boolean hasDivider) { |
|
||||||
View divider = findViewById(R.id.divider); |
|
||||||
divider.setVisibility(hasDivider ? View.VISIBLE : View.GONE); |
|
||||||
} |
|
||||||
|
|
||||||
public void setOption(@NonNull Option<Value> option, @NonNull Callback callback) { |
|
||||||
this.option = option; |
|
||||||
this.callback = callback; |
|
||||||
TextView title = findViewById(R.id.title); |
|
||||||
title.setText(option.getName()); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("all") |
|
||||||
public void onCameraOpened(CameraView view, CameraOptions options) { |
|
||||||
values = new ArrayList(option.getAll(view, options)); |
|
||||||
value = (Value) option.get(view); |
|
||||||
valuesStrings = new ArrayList<>(); |
|
||||||
for (Value value : values) { |
|
||||||
valuesStrings.add(option.toString(value)); |
|
||||||
} |
|
||||||
|
|
||||||
if (values.isEmpty()) { |
|
||||||
spinner.setOnItemSelectedListener(null); |
|
||||||
spinner.setEnabled(false); |
|
||||||
spinner.setAlpha(0.8f); |
|
||||||
spinner.setAdapter(new ArrayAdapter(getContext(), |
|
||||||
R.layout.spinner_text, new String[]{ "Not supported." })); |
|
||||||
spinner.setSelection(0, false); |
|
||||||
} else { |
|
||||||
spinner.setEnabled(true); |
|
||||||
spinner.setAlpha(1f); |
|
||||||
spinner.setAdapter(new ArrayAdapter(getContext(), |
|
||||||
R.layout.spinner_text, valuesStrings)); |
|
||||||
spinner.setSelection(values.indexOf(value), false); |
|
||||||
spinner.setOnItemSelectedListener(this); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { |
|
||||||
if (!values.get(i).equals(value)) { |
|
||||||
Log.e("ControlView", "curr: " + value + " new: " + values.get(i)); |
|
||||||
if (!callback.onValueChanged(option, values.get(i), valuesStrings.get(i))) { |
|
||||||
spinner.setSelection(values.indexOf(value)); // Go back.
|
|
||||||
} else { |
|
||||||
value = values.get(i); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onNothingSelected(AdapterView<?> adapterView) {} |
|
||||||
} |
|
@ -1,136 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.demo; |
|
||||||
|
|
||||||
import android.app.Activity; |
|
||||||
import android.content.Context; |
|
||||||
import android.content.Intent; |
|
||||||
import android.graphics.Bitmap; |
|
||||||
import android.graphics.BitmapFactory; |
|
||||||
import android.graphics.Color; |
|
||||||
import android.graphics.drawable.ColorDrawable; |
|
||||||
import android.net.Uri; |
|
||||||
import android.os.Bundle; |
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import androidx.appcompat.app.AppCompatActivity; |
|
||||||
import androidx.core.content.FileProvider; |
|
||||||
|
|
||||||
import android.util.Log; |
|
||||||
import android.view.Menu; |
|
||||||
import android.view.MenuItem; |
|
||||||
import android.widget.ImageView; |
|
||||||
import android.widget.Toast; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraUtils; |
|
||||||
import com.otaliastudios.cameraview.FileCallback; |
|
||||||
import com.otaliastudios.cameraview.size.AspectRatio; |
|
||||||
import com.otaliastudios.cameraview.BitmapCallback; |
|
||||||
import com.otaliastudios.cameraview.PictureResult; |
|
||||||
|
|
||||||
import java.io.File; |
|
||||||
import java.io.FileInputStream; |
|
||||||
import java.io.FileOutputStream; |
|
||||||
|
|
||||||
|
|
||||||
public class PicturePreviewActivity extends AppCompatActivity { |
|
||||||
|
|
||||||
private static PictureResult picture; |
|
||||||
|
|
||||||
public static void setPictureResult(@Nullable PictureResult pictureResult) { |
|
||||||
picture = pictureResult; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) { |
|
||||||
super.onCreate(savedInstanceState); |
|
||||||
setContentView(R.layout.activity_picture_preview); |
|
||||||
final PictureResult result = picture; |
|
||||||
if (result == null) { |
|
||||||
finish(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
final ImageView imageView = findViewById(R.id.image); |
|
||||||
final MessageView captureResolution = findViewById(R.id.nativeCaptureResolution); |
|
||||||
final MessageView captureLatency = findViewById(R.id.captureLatency); |
|
||||||
final MessageView exifRotation = findViewById(R.id.exifRotation); |
|
||||||
|
|
||||||
final long delay = getIntent().getLongExtra("delay", 0); |
|
||||||
AspectRatio ratio = AspectRatio.of(result.getSize()); |
|
||||||
captureLatency.setTitleAndMessage("Approx. latency", delay + " milliseconds"); |
|
||||||
captureResolution.setTitleAndMessage("Resolution", result.getSize() + " (" + ratio + ")"); |
|
||||||
exifRotation.setTitleAndMessage("EXIF rotation", result.getRotation() + ""); |
|
||||||
try { |
|
||||||
result.toBitmap(1000, 1000, new BitmapCallback() { |
|
||||||
@Override |
|
||||||
public void onBitmapReady(Bitmap bitmap) { |
|
||||||
imageView.setImageBitmap(bitmap); |
|
||||||
} |
|
||||||
}); |
|
||||||
} catch (UnsupportedOperationException e) { |
|
||||||
imageView.setImageDrawable(new ColorDrawable(Color.GREEN)); |
|
||||||
Toast.makeText(this, "Can't preview this format: " + picture.getFormat(), |
|
||||||
Toast.LENGTH_LONG).show(); |
|
||||||
} |
|
||||||
|
|
||||||
if (result.isSnapshot()) { |
|
||||||
// Log the real size for debugging reason.
|
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options(); |
|
||||||
options.inJustDecodeBounds = true; |
|
||||||
BitmapFactory.decodeByteArray(result.getData(), 0, result.getData().length, options); |
|
||||||
if (result.getRotation() % 180 != 0) { |
|
||||||
Log.e("PicturePreview", "The picture full size is " + result.getSize().getHeight() + "x" + result.getSize().getWidth()); |
|
||||||
} else { |
|
||||||
Log.e("PicturePreview", "The picture full size is " + result.getSize().getWidth() + "x" + result.getSize().getHeight()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void onDestroy() { |
|
||||||
super.onDestroy(); |
|
||||||
if (!isChangingConfigurations()) { |
|
||||||
setPictureResult(null); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean onCreateOptionsMenu(Menu menu) { |
|
||||||
getMenuInflater().inflate(R.menu.share, menu); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean onOptionsItemSelected(MenuItem item) { |
|
||||||
if (item.getItemId() == R.id.share) { |
|
||||||
Toast.makeText(this, "Sharing...", Toast.LENGTH_SHORT).show(); |
|
||||||
String extension; |
|
||||||
switch (picture.getFormat()) { |
|
||||||
case JPEG: extension = "jpg"; break; |
|
||||||
case DNG: extension = "dng"; break; |
|
||||||
default: throw new RuntimeException("Unknown format."); |
|
||||||
} |
|
||||||
File file = new File(getFilesDir(), "picture." + extension); |
|
||||||
CameraUtils.writeToFile(picture.getData(), file, new FileCallback() { |
|
||||||
@Override |
|
||||||
public void onFileReady(@Nullable File file) { |
|
||||||
if (file != null) { |
|
||||||
Context context = PicturePreviewActivity.this; |
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND); |
|
||||||
intent.setType("image/*"); |
|
||||||
Uri uri = FileProvider.getUriForFile(context, |
|
||||||
context.getPackageName() + ".provider", |
|
||||||
file); |
|
||||||
intent.putExtra(Intent.EXTRA_STREAM, uri); |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); |
|
||||||
startActivity(intent); |
|
||||||
} else { |
|
||||||
Toast.makeText(PicturePreviewActivity.this, |
|
||||||
"Error while writing file.", |
|
||||||
Toast.LENGTH_SHORT).show(); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
return true; |
|
||||||
} |
|
||||||
return super.onOptionsItemSelected(item); |
|
||||||
} |
|
||||||
} |
|
@ -1,134 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.demo; |
|
||||||
|
|
||||||
import android.app.Activity; |
|
||||||
import android.content.Context; |
|
||||||
import android.content.Intent; |
|
||||||
import android.media.MediaPlayer; |
|
||||||
import android.net.Uri; |
|
||||||
import android.os.Bundle; |
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import androidx.appcompat.app.AppCompatActivity; |
|
||||||
import androidx.core.content.FileProvider; |
|
||||||
|
|
||||||
import android.util.Log; |
|
||||||
import android.view.Menu; |
|
||||||
import android.view.MenuItem; |
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewGroup; |
|
||||||
import android.widget.MediaController; |
|
||||||
import android.widget.Toast; |
|
||||||
import android.widget.VideoView; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraUtils; |
|
||||||
import com.otaliastudios.cameraview.FileCallback; |
|
||||||
import com.otaliastudios.cameraview.VideoResult; |
|
||||||
import com.otaliastudios.cameraview.size.AspectRatio; |
|
||||||
|
|
||||||
import java.io.File; |
|
||||||
|
|
||||||
|
|
||||||
public class VideoPreviewActivity extends AppCompatActivity { |
|
||||||
|
|
||||||
private VideoView videoView; |
|
||||||
|
|
||||||
private static VideoResult videoResult; |
|
||||||
|
|
||||||
public static void setVideoResult(@Nullable VideoResult result) { |
|
||||||
videoResult = result; |
|
||||||
} |
|
||||||
@Override |
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) { |
|
||||||
super.onCreate(savedInstanceState); |
|
||||||
setContentView(R.layout.activity_video_preview); |
|
||||||
final VideoResult result = videoResult; |
|
||||||
if (result == null) { |
|
||||||
finish(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
videoView = findViewById(R.id.video); |
|
||||||
videoView.setOnClickListener(new View.OnClickListener() { |
|
||||||
@Override |
|
||||||
public void onClick(View view) { |
|
||||||
playVideo(); |
|
||||||
} |
|
||||||
}); |
|
||||||
final MessageView actualResolution = findViewById(R.id.actualResolution); |
|
||||||
final MessageView isSnapshot = findViewById(R.id.isSnapshot); |
|
||||||
final MessageView rotation = findViewById(R.id.rotation); |
|
||||||
final MessageView audio = findViewById(R.id.audio); |
|
||||||
final MessageView audioBitRate = findViewById(R.id.audioBitRate); |
|
||||||
final MessageView videoCodec = findViewById(R.id.videoCodec); |
|
||||||
final MessageView videoBitRate = findViewById(R.id.videoBitRate); |
|
||||||
final MessageView videoFrameRate = findViewById(R.id.videoFrameRate); |
|
||||||
|
|
||||||
AspectRatio ratio = AspectRatio.of(result.getSize()); |
|
||||||
actualResolution.setTitleAndMessage("Size", result.getSize() + " (" + ratio + ")"); |
|
||||||
isSnapshot.setTitleAndMessage("Snapshot", result.isSnapshot() + ""); |
|
||||||
rotation.setTitleAndMessage("Rotation", result.getRotation() + ""); |
|
||||||
audio.setTitleAndMessage("Audio", result.getAudio().name()); |
|
||||||
audioBitRate.setTitleAndMessage("Audio bit rate", result.getAudioBitRate() + " bits per sec."); |
|
||||||
videoCodec.setTitleAndMessage("VideoCodec", result.getVideoCodec().name()); |
|
||||||
videoBitRate.setTitleAndMessage("Video bit rate", result.getVideoBitRate() + " bits per sec."); |
|
||||||
videoFrameRate.setTitleAndMessage("Video frame rate", result.getVideoFrameRate() + " fps"); |
|
||||||
MediaController controller = new MediaController(this); |
|
||||||
controller.setAnchorView(videoView); |
|
||||||
controller.setMediaPlayer(videoView); |
|
||||||
videoView.setMediaController(controller); |
|
||||||
videoView.setVideoURI(Uri.fromFile(result.getFile())); |
|
||||||
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { |
|
||||||
@Override |
|
||||||
public void onPrepared(MediaPlayer mp) { |
|
||||||
ViewGroup.LayoutParams lp = videoView.getLayoutParams(); |
|
||||||
float videoWidth = mp.getVideoWidth(); |
|
||||||
float videoHeight = mp.getVideoHeight(); |
|
||||||
float viewWidth = videoView.getWidth(); |
|
||||||
lp.height = (int) (viewWidth * (videoHeight / videoWidth)); |
|
||||||
videoView.setLayoutParams(lp); |
|
||||||
playVideo(); |
|
||||||
|
|
||||||
if (result.isSnapshot()) { |
|
||||||
// Log the real size for debugging reason.
|
|
||||||
Log.e("VideoPreview", "The video full size is " + videoWidth + "x" + videoHeight); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
void playVideo() { |
|
||||||
if (!videoView.isPlaying()) { |
|
||||||
videoView.start(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void onDestroy() { |
|
||||||
super.onDestroy(); |
|
||||||
if (!isChangingConfigurations()) { |
|
||||||
setVideoResult(null); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean onCreateOptionsMenu(Menu menu) { |
|
||||||
getMenuInflater().inflate(R.menu.share, menu); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean onOptionsItemSelected(MenuItem item) { |
|
||||||
if (item.getItemId() == R.id.share) { |
|
||||||
Toast.makeText(this, "Sharing...", Toast.LENGTH_SHORT).show(); |
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND); |
|
||||||
intent.setType("video/*"); |
|
||||||
Uri uri = FileProvider.getUriForFile(this, |
|
||||||
this.getPackageName() + ".provider", |
|
||||||
videoResult.getFile()); |
|
||||||
intent.putExtra(Intent.EXTRA_STREAM, uri); |
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); |
|
||||||
startActivity(intent); |
|
||||||
return true; |
|
||||||
} |
|
||||||
return super.onOptionsItemSelected(item); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,344 @@ |
|||||||
|
package com.otaliastudios.cameraview.demo |
||||||
|
|
||||||
|
import android.animation.ValueAnimator |
||||||
|
import android.content.Intent |
||||||
|
import android.content.pm.PackageManager |
||||||
|
import android.content.pm.PackageManager.PERMISSION_GRANTED |
||||||
|
import android.graphics.* |
||||||
|
import android.os.Bundle |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT |
||||||
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT |
||||||
|
import android.widget.Toast |
||||||
|
import androidx.appcompat.app.AppCompatActivity |
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior |
||||||
|
import com.otaliastudios.cameraview.* |
||||||
|
import com.otaliastudios.cameraview.controls.Facing |
||||||
|
import com.otaliastudios.cameraview.controls.Mode |
||||||
|
import com.otaliastudios.cameraview.controls.Preview |
||||||
|
import com.otaliastudios.cameraview.filter.Filters |
||||||
|
import com.otaliastudios.cameraview.frame.Frame |
||||||
|
import com.otaliastudios.cameraview.frame.FrameProcessor |
||||||
|
import java.io.ByteArrayOutputStream |
||||||
|
import java.io.File |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
class CameraActivity : AppCompatActivity(), View.OnClickListener, OptionView.Callback { |
||||||
|
|
||||||
|
companion object { |
||||||
|
private val LOG = CameraLogger.create("DemoApp") |
||||||
|
private const val USE_FRAME_PROCESSOR = false |
||||||
|
private const val DECODE_BITMAP = false |
||||||
|
} |
||||||
|
|
||||||
|
private val camera: CameraView by lazy { findViewById(R.id.camera) } |
||||||
|
private val controlPanel: ViewGroup by lazy { findViewById(R.id.controls) } |
||||||
|
private var captureTime: Long = 0 |
||||||
|
|
||||||
|
private var currentFilter = 0 |
||||||
|
private val allFilters = Filters.values() |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_camera) |
||||||
|
CameraLogger.setLogLevel(CameraLogger.LEVEL_VERBOSE) |
||||||
|
camera.setLifecycleOwner(this) |
||||||
|
camera.addCameraListener(Listener()) |
||||||
|
if (USE_FRAME_PROCESSOR) { |
||||||
|
camera.addFrameProcessor(object : FrameProcessor { |
||||||
|
private var lastTime = System.currentTimeMillis() |
||||||
|
override fun process(frame: Frame) { |
||||||
|
val newTime = frame.time |
||||||
|
val delay = newTime - lastTime |
||||||
|
lastTime = newTime |
||||||
|
LOG.v("Frame delayMillis:", delay, "FPS:", 1000 / delay) |
||||||
|
if (DECODE_BITMAP) { |
||||||
|
if (frame.format == ImageFormat.NV21 |
||||||
|
&& frame.dataClass == ByteArray::class.java) { |
||||||
|
val data = frame.getData<ByteArray>() |
||||||
|
val yuvImage = YuvImage(data, |
||||||
|
frame.format, |
||||||
|
frame.size.width, |
||||||
|
frame.size.height, |
||||||
|
null) |
||||||
|
val jpegStream = ByteArrayOutputStream() |
||||||
|
yuvImage.compressToJpeg(Rect(0, 0, |
||||||
|
frame.size.width, |
||||||
|
frame.size.height), 100, jpegStream) |
||||||
|
val jpegByteArray = jpegStream.toByteArray() |
||||||
|
val bitmap = BitmapFactory.decodeByteArray(jpegByteArray, |
||||||
|
0, jpegByteArray.size) |
||||||
|
bitmap.toString() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
findViewById<View>(R.id.edit).setOnClickListener(this) |
||||||
|
findViewById<View>(R.id.capturePicture).setOnClickListener(this) |
||||||
|
findViewById<View>(R.id.capturePictureSnapshot).setOnClickListener(this) |
||||||
|
findViewById<View>(R.id.captureVideo).setOnClickListener(this) |
||||||
|
findViewById<View>(R.id.captureVideoSnapshot).setOnClickListener(this) |
||||||
|
findViewById<View>(R.id.toggleCamera).setOnClickListener(this) |
||||||
|
findViewById<View>(R.id.changeFilter).setOnClickListener(this) |
||||||
|
val group = controlPanel.getChildAt(0) as ViewGroup |
||||||
|
val watermark = findViewById<View>(R.id.watermark) |
||||||
|
val options: List<Option<*>> = listOf( |
||||||
|
// Layout |
||||||
|
Option.Width(), Option.Height(), |
||||||
|
// Engine and preview |
||||||
|
Option.Mode(), Option.Engine(), Option.Preview(), |
||||||
|
// Some controls |
||||||
|
Option.Flash(), Option.WhiteBalance(), Option.Hdr(), |
||||||
|
Option.PictureMetering(), Option.PictureSnapshotMetering(), |
||||||
|
Option.PictureFormat(), |
||||||
|
// Video recording |
||||||
|
Option.PreviewFrameRate(), Option.VideoCodec(), Option.Audio(), Option.AudioCodec(), |
||||||
|
// Gestures |
||||||
|
Option.Pinch(), Option.HorizontalScroll(), Option.VerticalScroll(), |
||||||
|
Option.Tap(), Option.LongTap(), |
||||||
|
// Watermarks |
||||||
|
Option.OverlayInPreview(watermark), |
||||||
|
Option.OverlayInPictureSnapshot(watermark), |
||||||
|
Option.OverlayInVideoSnapshot(watermark), |
||||||
|
// Frame Processing |
||||||
|
Option.FrameProcessingFormat(), |
||||||
|
// Other |
||||||
|
Option.Grid(), Option.GridColor(), Option.UseDeviceOrientation() |
||||||
|
) |
||||||
|
val dividers = listOf( |
||||||
|
// Layout |
||||||
|
false, true, |
||||||
|
// Engine and preview |
||||||
|
false, false, true, |
||||||
|
// Some controls |
||||||
|
false, false, false, false, false, true, |
||||||
|
// Video recording |
||||||
|
false, false, false, true, |
||||||
|
// Gestures |
||||||
|
false, false, false, false, true, |
||||||
|
// Watermarks |
||||||
|
false, false, true, |
||||||
|
// Frame Processing |
||||||
|
true, |
||||||
|
// Other |
||||||
|
false, false, true |
||||||
|
) |
||||||
|
for (i in options.indices) { |
||||||
|
val view = OptionView<Any>(this) |
||||||
|
view.setOption(options[i] as Option<Any>, this) |
||||||
|
view.setHasDivider(dividers[i]) |
||||||
|
group.addView(view, MATCH_PARENT, WRAP_CONTENT) |
||||||
|
} |
||||||
|
controlPanel.viewTreeObserver.addOnGlobalLayoutListener { |
||||||
|
BottomSheetBehavior.from(controlPanel).state = BottomSheetBehavior.STATE_HIDDEN |
||||||
|
} |
||||||
|
|
||||||
|
// Animate the watermark just to show we record the animation in video snapshots |
||||||
|
val animator = ValueAnimator.ofFloat(1f, 0.8f) |
||||||
|
animator.duration = 300 |
||||||
|
animator.repeatCount = ValueAnimator.INFINITE |
||||||
|
animator.repeatMode = ValueAnimator.REVERSE |
||||||
|
animator.addUpdateListener { animation -> |
||||||
|
val scale = animation.animatedValue as Float |
||||||
|
watermark.scaleX = scale |
||||||
|
watermark.scaleY = scale |
||||||
|
watermark.rotation = watermark.rotation + 2 |
||||||
|
} |
||||||
|
animator.start() |
||||||
|
} |
||||||
|
|
||||||
|
private fun message(content: String, important: Boolean) { |
||||||
|
if (important) { |
||||||
|
LOG.w(content) |
||||||
|
Toast.makeText(this, content, Toast.LENGTH_LONG).show() |
||||||
|
} else { |
||||||
|
LOG.i(content) |
||||||
|
Toast.makeText(this, content, Toast.LENGTH_SHORT).show() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private inner class Listener : CameraListener() { |
||||||
|
override fun onCameraOpened(options: CameraOptions) { |
||||||
|
val group = controlPanel.getChildAt(0) as ViewGroup |
||||||
|
for (i in 0 until group.childCount) { |
||||||
|
val view = group.getChildAt(i) as OptionView<*> |
||||||
|
view.onCameraOpened(camera, options) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCameraError(exception: CameraException) { |
||||||
|
super.onCameraError(exception) |
||||||
|
message("Got CameraException #" + exception.reason, true) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onPictureTaken(result: PictureResult) { |
||||||
|
super.onPictureTaken(result) |
||||||
|
if (camera.isTakingVideo) { |
||||||
|
message("Captured while taking video. Size=" + result.size, false) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// This can happen if picture was taken with a gesture. |
||||||
|
val callbackTime = System.currentTimeMillis() |
||||||
|
if (captureTime == 0L) captureTime = callbackTime - 300 |
||||||
|
LOG.w("onPictureTaken called! Launching activity. Delay:", callbackTime - captureTime) |
||||||
|
PicturePreviewActivity.pictureResult = result |
||||||
|
val intent = Intent(this@CameraActivity, PicturePreviewActivity::class.java) |
||||||
|
intent.putExtra("delay", callbackTime - captureTime) |
||||||
|
startActivity(intent) |
||||||
|
captureTime = 0 |
||||||
|
LOG.w("onPictureTaken called! Launched activity.") |
||||||
|
} |
||||||
|
|
||||||
|
override fun onVideoTaken(result: VideoResult) { |
||||||
|
super.onVideoTaken(result) |
||||||
|
LOG.w("onVideoTaken called! Launching activity.") |
||||||
|
VideoPreviewActivity.videoResult = result |
||||||
|
val intent = Intent(this@CameraActivity, VideoPreviewActivity::class.java) |
||||||
|
startActivity(intent) |
||||||
|
LOG.w("onVideoTaken called! Launched activity.") |
||||||
|
} |
||||||
|
|
||||||
|
override fun onVideoRecordingStart() { |
||||||
|
super.onVideoRecordingStart() |
||||||
|
LOG.w("onVideoRecordingStart!") |
||||||
|
} |
||||||
|
|
||||||
|
override fun onVideoRecordingEnd() { |
||||||
|
super.onVideoRecordingEnd() |
||||||
|
message("Video taken. Processing...", false) |
||||||
|
LOG.w("onVideoRecordingEnd!") |
||||||
|
} |
||||||
|
|
||||||
|
override fun onExposureCorrectionChanged(newValue: Float, bounds: FloatArray, fingers: Array<PointF>?) { |
||||||
|
super.onExposureCorrectionChanged(newValue, bounds, fingers) |
||||||
|
message("Exposure correction:$newValue", false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onZoomChanged(newValue: Float, bounds: FloatArray, fingers: Array<PointF>?) { |
||||||
|
super.onZoomChanged(newValue, bounds, fingers) |
||||||
|
message("Zoom:$newValue", false) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onClick(view: View) { |
||||||
|
when (view.id) { |
||||||
|
R.id.edit -> edit() |
||||||
|
R.id.capturePicture -> capturePicture() |
||||||
|
R.id.capturePictureSnapshot -> capturePictureSnapshot() |
||||||
|
R.id.captureVideo -> captureVideo() |
||||||
|
R.id.captureVideoSnapshot -> captureVideoSnapshot() |
||||||
|
R.id.toggleCamera -> toggleCamera() |
||||||
|
R.id.changeFilter -> changeCurrentFilter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onBackPressed() { |
||||||
|
val b = BottomSheetBehavior.from(controlPanel) |
||||||
|
if (b.state != BottomSheetBehavior.STATE_HIDDEN) { |
||||||
|
b.state = BottomSheetBehavior.STATE_HIDDEN |
||||||
|
return |
||||||
|
} |
||||||
|
super.onBackPressed() |
||||||
|
} |
||||||
|
|
||||||
|
private fun edit() { |
||||||
|
BottomSheetBehavior.from(controlPanel).state = BottomSheetBehavior.STATE_COLLAPSED |
||||||
|
} |
||||||
|
|
||||||
|
private fun capturePicture() { |
||||||
|
if (camera.mode == Mode.VIDEO) return run { |
||||||
|
message("Can't take HQ pictures while in VIDEO mode.", false) |
||||||
|
} |
||||||
|
if (camera.isTakingPicture) return |
||||||
|
captureTime = System.currentTimeMillis() |
||||||
|
message("Capturing picture...", false) |
||||||
|
camera.takePicture() |
||||||
|
} |
||||||
|
|
||||||
|
private fun capturePictureSnapshot() { |
||||||
|
if (camera.isTakingPicture) return |
||||||
|
if (camera.preview != Preview.GL_SURFACE) return run { |
||||||
|
message("Picture snapshots are only allowed with the GL_SURFACE preview.", true) |
||||||
|
} |
||||||
|
captureTime = System.currentTimeMillis() |
||||||
|
message("Capturing picture snapshot...", false) |
||||||
|
camera.takePictureSnapshot() |
||||||
|
} |
||||||
|
|
||||||
|
private fun captureVideo() { |
||||||
|
if (camera.mode == Mode.PICTURE) return run { |
||||||
|
message("Can't record HQ videos while in PICTURE mode.", false) |
||||||
|
} |
||||||
|
if (camera.isTakingPicture || camera.isTakingVideo) return |
||||||
|
message("Recording for 5 seconds...", true) |
||||||
|
camera.takeVideo(File(filesDir, "video.mp4"), 5000) |
||||||
|
} |
||||||
|
|
||||||
|
private fun captureVideoSnapshot() { |
||||||
|
if (camera.isTakingVideo) return run { |
||||||
|
message("Already taking video.", false) |
||||||
|
} |
||||||
|
if (camera.preview != Preview.GL_SURFACE) return run { |
||||||
|
message("Video snapshots are only allowed with the GL_SURFACE preview.", true) |
||||||
|
} |
||||||
|
message("Recording snapshot for 5 seconds...", true) |
||||||
|
camera.takeVideoSnapshot(File(filesDir, "video.mp4"), 5000) |
||||||
|
} |
||||||
|
|
||||||
|
private fun toggleCamera() { |
||||||
|
if (camera.isTakingPicture || camera.isTakingVideo) return |
||||||
|
when (camera.toggleFacing()) { |
||||||
|
Facing.BACK -> message("Switched to back camera!", false) |
||||||
|
Facing.FRONT -> message("Switched to front camera!", false) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun changeCurrentFilter() { |
||||||
|
if (camera.preview != Preview.GL_SURFACE) return run { |
||||||
|
message("Filters are supported only when preview is Preview.GL_SURFACE.", true) |
||||||
|
} |
||||||
|
if (currentFilter < allFilters.size - 1) { |
||||||
|
currentFilter++ |
||||||
|
} else { |
||||||
|
currentFilter = 0 |
||||||
|
} |
||||||
|
val filter = allFilters[currentFilter] |
||||||
|
message(filter.toString(), false) |
||||||
|
|
||||||
|
// Normal behavior: |
||||||
|
camera.filter = filter.newInstance() |
||||||
|
|
||||||
|
// To test MultiFilter: |
||||||
|
// DuotoneFilter duotone = new DuotoneFilter(); |
||||||
|
// duotone.setFirstColor(Color.RED); |
||||||
|
// duotone.setSecondColor(Color.GREEN); |
||||||
|
// camera.setFilter(new MultiFilter(duotone, filter.newInstance())); |
||||||
|
} |
||||||
|
|
||||||
|
override fun <T : Any> onValueChanged(option: Option<T>, value: T, name: String): Boolean { |
||||||
|
if (option is Option.Width || option is Option.Height) { |
||||||
|
val preview = camera.preview |
||||||
|
val wrapContent = value as Int == WRAP_CONTENT |
||||||
|
if (preview == Preview.SURFACE && !wrapContent) { |
||||||
|
message("The SurfaceView preview does not support width or height changes. " + |
||||||
|
"The view will act as WRAP_CONTENT by default.", true) |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
option.set(camera, value) |
||||||
|
BottomSheetBehavior.from(controlPanel).state = BottomSheetBehavior.STATE_HIDDEN |
||||||
|
message("Changed " + option.name + " to " + name, false) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) { |
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults) |
||||||
|
val valid = grantResults.all { it == PERMISSION_GRANTED } |
||||||
|
if (valid && !camera.isOpened) { |
||||||
|
camera.open() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
package com.otaliastudios.cameraview.demo |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.util.AttributeSet |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import android.widget.LinearLayout |
||||||
|
import android.widget.TextView |
||||||
|
|
||||||
|
class MessageView @JvmOverloads constructor( |
||||||
|
context: Context, |
||||||
|
attrs: AttributeSet? = null, |
||||||
|
defStyleAttr: Int = 0 |
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) { |
||||||
|
|
||||||
|
init { |
||||||
|
orientation = VERTICAL |
||||||
|
View.inflate(context, R.layout.option_view, this) |
||||||
|
val content = findViewById<ViewGroup>(R.id.content) |
||||||
|
View.inflate(context, R.layout.spinner_text, content) |
||||||
|
} |
||||||
|
|
||||||
|
private val message: TextView = findViewById<ViewGroup>(R.id.content).getChildAt(0) as TextView |
||||||
|
private val title: TextView = findViewById(R.id.title) |
||||||
|
|
||||||
|
fun setMessage(message: String) { this.message.text = message } |
||||||
|
|
||||||
|
fun setTitle(title: String) { this.title.text = title } |
||||||
|
|
||||||
|
fun setTitleAndMessage(title: String, message: String) { |
||||||
|
setTitle(title) |
||||||
|
setMessage(message) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,296 @@ |
|||||||
|
package com.otaliastudios.cameraview.demo |
||||||
|
|
||||||
|
import android.graphics.Color |
||||||
|
import android.graphics.ImageFormat |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT |
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.CameraListener |
||||||
|
import com.otaliastudios.cameraview.CameraOptions |
||||||
|
import com.otaliastudios.cameraview.CameraView |
||||||
|
import com.otaliastudios.cameraview.gesture.GestureAction |
||||||
|
import com.otaliastudios.cameraview.overlay.OverlayLayout |
||||||
|
import kotlin.math.roundToInt |
||||||
|
import kotlin.reflect.KClass |
||||||
|
|
||||||
|
/** |
||||||
|
* Controls that we want to display in a ControlView. |
||||||
|
*/ |
||||||
|
abstract class Option<T: Any>(val name: String) { |
||||||
|
|
||||||
|
abstract fun get(view: CameraView): T |
||||||
|
|
||||||
|
abstract fun getAll(view: CameraView, options: CameraOptions): Collection<T> |
||||||
|
|
||||||
|
abstract fun set(view: CameraView, value: T) |
||||||
|
|
||||||
|
open fun toString(value: T): String { |
||||||
|
return "$value".replace("_", "_").toLowerCase() |
||||||
|
} |
||||||
|
|
||||||
|
class Width : Option<Int>("Width") { |
||||||
|
override fun get(view: CameraView) = view.layoutParams.width |
||||||
|
|
||||||
|
override fun set(view: CameraView, value: Int) { |
||||||
|
view.layoutParams.width = value |
||||||
|
view.layoutParams = view.layoutParams |
||||||
|
} |
||||||
|
|
||||||
|
override fun getAll(view: CameraView, options: CameraOptions): Collection<Int> { |
||||||
|
val root = view.parent as View |
||||||
|
val boundary = root.width.takeIf { it > 0 } ?: 1000 |
||||||
|
val step = boundary / 10 |
||||||
|
val list = mutableListOf(WRAP_CONTENT, MATCH_PARENT) |
||||||
|
for (i in step until boundary step step) { list.add(i) } |
||||||
|
return list |
||||||
|
} |
||||||
|
|
||||||
|
override fun toString(value: Int): String { |
||||||
|
if (value == MATCH_PARENT) return "match parent" |
||||||
|
if (value == WRAP_CONTENT) return "wrap content" |
||||||
|
return super.toString(value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Height : Option<Int>("Height") { |
||||||
|
override fun get(view: CameraView) = view.layoutParams.height |
||||||
|
|
||||||
|
override fun set(view: CameraView, value: Int) { |
||||||
|
view.layoutParams.height = value |
||||||
|
view.layoutParams = view.layoutParams |
||||||
|
} |
||||||
|
|
||||||
|
override fun getAll(view: CameraView, options: CameraOptions): Collection<Int> { |
||||||
|
val root = view.parent as View |
||||||
|
val boundary = root.height.takeIf { it > 0 } ?: 1000 |
||||||
|
val step = boundary / 10 |
||||||
|
val list = mutableListOf(WRAP_CONTENT, MATCH_PARENT) |
||||||
|
for (i in step until boundary step step) { list.add(i) } |
||||||
|
return list |
||||||
|
} |
||||||
|
|
||||||
|
override fun toString(value: Int): String { |
||||||
|
if (value == MATCH_PARENT) return "match parent" |
||||||
|
if (value == WRAP_CONTENT) return "wrap content" |
||||||
|
return super.toString(value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract class Control<T: com.otaliastudios.cameraview.controls.Control>(private val kclass: KClass<T>, name: String) : Option<T>(name) { |
||||||
|
override fun set(view: CameraView, value: T) = view.set(value) |
||||||
|
override fun get(view: CameraView) = view.get(kclass.java) |
||||||
|
override fun getAll(view: CameraView, options: CameraOptions) |
||||||
|
= options.getSupportedControls(kclass.java) |
||||||
|
} |
||||||
|
|
||||||
|
class Mode : Control<com.otaliastudios.cameraview.controls.Mode>(com.otaliastudios.cameraview.controls.Mode::class, "Mode") |
||||||
|
|
||||||
|
class Engine : Control<com.otaliastudios.cameraview.controls.Engine>(com.otaliastudios.cameraview.controls.Engine::class, "Engine") { |
||||||
|
override fun set(view: CameraView, value: com.otaliastudios.cameraview.controls.Engine) { |
||||||
|
if (view.isOpened) { |
||||||
|
view.addCameraListener(object : CameraListener() { |
||||||
|
override fun onCameraClosed() { |
||||||
|
super.onCameraClosed() |
||||||
|
view.removeCameraListener(this) |
||||||
|
view.engine = value |
||||||
|
view.open() |
||||||
|
} |
||||||
|
}) |
||||||
|
view.close() |
||||||
|
} else { |
||||||
|
view.engine = value |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Preview : Control<com.otaliastudios.cameraview.controls.Preview>(com.otaliastudios.cameraview.controls.Preview::class, "Preview Surface") { |
||||||
|
override fun set(view: CameraView, value: com.otaliastudios.cameraview.controls.Preview) { |
||||||
|
if (view.isOpened) { |
||||||
|
view.addCameraListener(object : CameraListener() { |
||||||
|
override fun onCameraClosed() { |
||||||
|
super.onCameraClosed() |
||||||
|
view.removeCameraListener(this) |
||||||
|
applyPreview(view, value) |
||||||
|
view.open() |
||||||
|
} |
||||||
|
}) |
||||||
|
view.close() |
||||||
|
} else { |
||||||
|
applyPreview(view, value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// This is really tricky since the preview can only be changed when not attached to window. |
||||||
|
private fun applyPreview(view: CameraView, value: com.otaliastudios.cameraview.controls.Preview) { |
||||||
|
val params = view.layoutParams |
||||||
|
val parent = view.parent as ViewGroup |
||||||
|
val index = (0 until parent.childCount).first { parent.getChildAt(it) === view } |
||||||
|
parent.removeView(view) |
||||||
|
view.preview = value |
||||||
|
parent.addView(view, index, params) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Flash : Control<com.otaliastudios.cameraview.controls.Flash>(com.otaliastudios.cameraview.controls.Flash::class, "Flash") |
||||||
|
|
||||||
|
class WhiteBalance : Control<com.otaliastudios.cameraview.controls.WhiteBalance>(com.otaliastudios.cameraview.controls.WhiteBalance::class, "White Balance") |
||||||
|
|
||||||
|
class Hdr : Control<com.otaliastudios.cameraview.controls.Hdr>(com.otaliastudios.cameraview.controls.Hdr::class, "HDR") |
||||||
|
|
||||||
|
class PictureMetering : Option<Boolean>("Picture Metering") { |
||||||
|
override fun get(view: CameraView) = view.pictureMetering |
||||||
|
override fun getAll(view: CameraView, options: CameraOptions) = listOf(true, false) |
||||||
|
override fun set(view: CameraView, value: Boolean) { view.pictureMetering = value } |
||||||
|
} |
||||||
|
|
||||||
|
class PictureSnapshotMetering : Option<Boolean>("Picture Snapshot Metering") { |
||||||
|
override fun get(view: CameraView) = view.pictureSnapshotMetering |
||||||
|
override fun getAll(view: CameraView, options: CameraOptions) = listOf(true, false) |
||||||
|
override fun set(view: CameraView, value: Boolean) { view.pictureSnapshotMetering = value } |
||||||
|
} |
||||||
|
|
||||||
|
class VideoCodec : Control<com.otaliastudios.cameraview.controls.VideoCodec>(com.otaliastudios.cameraview.controls.VideoCodec::class, "Video Codec") |
||||||
|
|
||||||
|
class AudioCodec : Control<com.otaliastudios.cameraview.controls.AudioCodec>(com.otaliastudios.cameraview.controls.AudioCodec::class, "Audio Codec") |
||||||
|
|
||||||
|
class Audio : Control<com.otaliastudios.cameraview.controls.Audio>(com.otaliastudios.cameraview.controls.Audio::class, "Audio") |
||||||
|
|
||||||
|
abstract class Gesture(val gesture: com.otaliastudios.cameraview.gesture.Gesture, name: String) : Option<GestureAction>(name) { |
||||||
|
override fun set(view: CameraView, value: GestureAction) { |
||||||
|
view.mapGesture(gesture, value) |
||||||
|
} |
||||||
|
|
||||||
|
override fun get(view: CameraView): GestureAction { |
||||||
|
return view.getGestureAction(gesture) |
||||||
|
} |
||||||
|
|
||||||
|
override fun getAll(view: CameraView, options: CameraOptions): Collection<GestureAction> { |
||||||
|
return GestureAction.values().filter { |
||||||
|
gesture.isAssignableTo(it) && options.supports(it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Pinch : Gesture(com.otaliastudios.cameraview.gesture.Gesture.PINCH, "Pinch") |
||||||
|
|
||||||
|
class HorizontalScroll : Gesture(com.otaliastudios.cameraview.gesture.Gesture.SCROLL_HORIZONTAL, "Horizontal Scroll") |
||||||
|
|
||||||
|
class VerticalScroll : Gesture(com.otaliastudios.cameraview.gesture.Gesture.SCROLL_VERTICAL, "Vertical Scroll") |
||||||
|
|
||||||
|
class Tap : Gesture(com.otaliastudios.cameraview.gesture.Gesture.TAP, "Tap") |
||||||
|
|
||||||
|
class LongTap : Gesture(com.otaliastudios.cameraview.gesture.Gesture.LONG_TAP, "Long Tap") |
||||||
|
|
||||||
|
abstract class Overlay(private val overlay: View, private val target: com.otaliastudios.cameraview.overlay.Overlay.Target, name: String) : Option<Boolean>(name) { |
||||||
|
override fun getAll(view: CameraView, options: CameraOptions) = listOf(true, false) |
||||||
|
override fun get(view: CameraView): Boolean { |
||||||
|
val params = overlay.layoutParams as OverlayLayout.LayoutParams |
||||||
|
return when (target) { |
||||||
|
com.otaliastudios.cameraview.overlay.Overlay.Target.PREVIEW -> params.drawOnPreview |
||||||
|
com.otaliastudios.cameraview.overlay.Overlay.Target.PICTURE_SNAPSHOT -> params.drawOnPictureSnapshot |
||||||
|
com.otaliastudios.cameraview.overlay.Overlay.Target.VIDEO_SNAPSHOT -> params.drawOnVideoSnapshot |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun set(view: CameraView, value: Boolean) { |
||||||
|
val params = overlay.layoutParams as OverlayLayout.LayoutParams |
||||||
|
when (target) { |
||||||
|
com.otaliastudios.cameraview.overlay.Overlay.Target.PREVIEW -> params.drawOnPreview = value |
||||||
|
com.otaliastudios.cameraview.overlay.Overlay.Target.PICTURE_SNAPSHOT -> params.drawOnPictureSnapshot = value |
||||||
|
com.otaliastudios.cameraview.overlay.Overlay.Target.VIDEO_SNAPSHOT -> params.drawOnVideoSnapshot = value |
||||||
|
} |
||||||
|
overlay.layoutParams = params |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class OverlayInPreview(overlay: View) : Overlay(overlay, com.otaliastudios.cameraview.overlay.Overlay.Target.PREVIEW, "Overlay in Preview") |
||||||
|
|
||||||
|
class OverlayInPictureSnapshot(overlay: View) : Overlay(overlay, com.otaliastudios.cameraview.overlay.Overlay.Target.PICTURE_SNAPSHOT, "Overlay in Picture Snapshot") |
||||||
|
|
||||||
|
class OverlayInVideoSnapshot(overlay: View) : Overlay(overlay, com.otaliastudios.cameraview.overlay.Overlay.Target.VIDEO_SNAPSHOT, "Overlay in Video Snapshot") |
||||||
|
|
||||||
|
class Grid : Control<com.otaliastudios.cameraview.controls.Grid>(com.otaliastudios.cameraview.controls.Grid::class, "Grid Lines") |
||||||
|
|
||||||
|
class GridColor : Option<Pair<Int, String>>("Grid Color") { |
||||||
|
private val all = listOf( |
||||||
|
Color.argb(160, 255, 255, 255) to "default", |
||||||
|
Color.WHITE to "white", |
||||||
|
Color.BLACK to "black", |
||||||
|
Color.YELLOW to "yellow" |
||||||
|
) |
||||||
|
|
||||||
|
override fun getAll(view: CameraView, options: CameraOptions) = all |
||||||
|
|
||||||
|
override fun get(view: CameraView) = all.first { it.first == view.gridColor } |
||||||
|
|
||||||
|
override fun set(view: CameraView, value: Pair<Int, String>) { |
||||||
|
view.gridColor = value.first |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class UseDeviceOrientation : Option<Boolean>("Use Device Orientation") { |
||||||
|
override fun getAll(view: CameraView, options: CameraOptions) = listOf(true, false) |
||||||
|
override fun get(view: CameraView) = view.useDeviceOrientation |
||||||
|
override fun set(view: CameraView, value: Boolean) { |
||||||
|
view.useDeviceOrientation = value |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class PreviewFrameRate : Option<Int>("Preview FPS") { |
||||||
|
override fun get(view: CameraView) = view.previewFrameRate.roundToInt() |
||||||
|
|
||||||
|
override fun set(view: CameraView, value: Int) { |
||||||
|
view.previewFrameRate = value.toFloat() |
||||||
|
} |
||||||
|
|
||||||
|
override fun getAll(view: CameraView, options: CameraOptions): Collection<Int> { |
||||||
|
val min = options.previewFrameRateMinValue |
||||||
|
val max = options.previewFrameRateMaxValue |
||||||
|
val delta = max - min |
||||||
|
return when { |
||||||
|
min == 0F && max == 0F -> listOf() |
||||||
|
delta < 0.005F -> listOf(min.roundToInt()) |
||||||
|
else -> { |
||||||
|
val results = mutableListOf<Int>() |
||||||
|
var value = min |
||||||
|
for (i in 0 until 3) { |
||||||
|
results.add(value.roundToInt()) |
||||||
|
value += delta / 3 |
||||||
|
} |
||||||
|
results |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class PictureFormat : Control<com.otaliastudios.cameraview.controls.PictureFormat>(com.otaliastudios.cameraview.controls.PictureFormat::class, "Picture Format") |
||||||
|
|
||||||
|
class FrameProcessingFormat : Option<Int>("Frame Processing Format") { |
||||||
|
override fun set(view: CameraView, value: Int) { |
||||||
|
view.frameProcessingFormat = value |
||||||
|
} |
||||||
|
|
||||||
|
override fun get(view: CameraView) = view.frameProcessingFormat |
||||||
|
|
||||||
|
override fun getAll(view: CameraView, options: CameraOptions) |
||||||
|
= options.supportedFrameProcessingFormats |
||||||
|
|
||||||
|
override fun toString(value: Int): String { |
||||||
|
return when (value) { |
||||||
|
ImageFormat.NV21 -> "NV21" |
||||||
|
ImageFormat.NV16 -> "NV16" |
||||||
|
ImageFormat.JPEG -> "JPEG" |
||||||
|
ImageFormat.YUY2 -> "YUY2" |
||||||
|
ImageFormat.YUV_420_888 -> "YUV_420_888" |
||||||
|
ImageFormat.YUV_422_888 -> "YUV_422_888" |
||||||
|
ImageFormat.YUV_444_888 -> "YUV_444_888" |
||||||
|
ImageFormat.RAW10 -> "RAW10" |
||||||
|
ImageFormat.RAW12 -> "RAW12" |
||||||
|
ImageFormat.RAW_SENSOR -> "RAW_SENSOR" |
||||||
|
else -> super.toString(value) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
package com.otaliastudios.cameraview.demo |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.util.Log |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import android.widget.* |
||||||
|
import com.otaliastudios.cameraview.CameraOptions |
||||||
|
import com.otaliastudios.cameraview.CameraView |
||||||
|
|
||||||
|
class OptionView<Value: Any>(context: Context) : LinearLayout(context), AdapterView.OnItemSelectedListener { |
||||||
|
|
||||||
|
interface Callback { |
||||||
|
fun <T: Any> onValueChanged(option: Option<T>, value: T, name: String): Boolean |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var option: Option<Value> |
||||||
|
private lateinit var callback: Callback |
||||||
|
private lateinit var value: Value |
||||||
|
private lateinit var values: List<Value> |
||||||
|
private lateinit var valuesStrings: List<String> |
||||||
|
|
||||||
|
init { |
||||||
|
orientation = VERTICAL |
||||||
|
View.inflate(context, R.layout.option_view, this) |
||||||
|
} |
||||||
|
|
||||||
|
val spinner = Spinner(context, Spinner.MODE_DROPDOWN).also { |
||||||
|
val content = findViewById<ViewGroup>(R.id.content) |
||||||
|
content.addView(it) |
||||||
|
} |
||||||
|
|
||||||
|
fun setHasDivider(hasDivider: Boolean) { |
||||||
|
val divider = findViewById<View>(R.id.divider) |
||||||
|
divider.visibility = if (hasDivider) View.VISIBLE else View.GONE |
||||||
|
} |
||||||
|
|
||||||
|
fun setOption(option: Option<Value>, callback: Callback) { |
||||||
|
this.option = option |
||||||
|
this.callback = callback |
||||||
|
val title = findViewById<TextView>(R.id.title) |
||||||
|
title.text = option.name |
||||||
|
} |
||||||
|
|
||||||
|
fun onCameraOpened(view: CameraView, options: CameraOptions) { |
||||||
|
values = option.getAll(view, options).toList() |
||||||
|
value = option.get(view) |
||||||
|
valuesStrings = values.map { option.toString(it) } |
||||||
|
if (values.isEmpty()) { |
||||||
|
spinner.onItemSelectedListener = null |
||||||
|
spinner.isEnabled = false |
||||||
|
spinner.alpha = 0.8f |
||||||
|
spinner.adapter = ArrayAdapter(context, R.layout.spinner_text, arrayOf("Not supported.")) |
||||||
|
spinner.setSelection(0, false) |
||||||
|
} else { |
||||||
|
spinner.isEnabled = true |
||||||
|
spinner.alpha = 1f |
||||||
|
spinner.adapter = ArrayAdapter(context, R.layout.spinner_text, valuesStrings) |
||||||
|
spinner.setSelection(values.indexOf(value), false) |
||||||
|
spinner.onItemSelectedListener = this |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onItemSelected(adapterView: AdapterView<*>?, view: View?, i: Int, l: Long) { |
||||||
|
if (values[i] != value) { |
||||||
|
Log.e("ControlView", "curr: " + value + " new: " + values[i]) |
||||||
|
if (!callback.onValueChanged(option, values[i], valuesStrings[i])) { |
||||||
|
spinner.setSelection(values.indexOf(value)) // Go back. |
||||||
|
} else { |
||||||
|
value = values[i] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onNothingSelected(adapterView: AdapterView<*>?) {} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
package com.otaliastudios.cameraview.demo |
||||||
|
|
||||||
|
import android.content.Intent |
||||||
|
import android.graphics.BitmapFactory |
||||||
|
import android.graphics.Color |
||||||
|
import android.graphics.drawable.ColorDrawable |
||||||
|
import android.os.Bundle |
||||||
|
import android.util.Log |
||||||
|
import android.view.Menu |
||||||
|
import android.view.MenuItem |
||||||
|
import android.widget.ImageView |
||||||
|
import android.widget.Toast |
||||||
|
import androidx.appcompat.app.AppCompatActivity |
||||||
|
import androidx.core.content.FileProvider |
||||||
|
import com.otaliastudios.cameraview.CameraUtils |
||||||
|
import com.otaliastudios.cameraview.PictureResult |
||||||
|
import com.otaliastudios.cameraview.controls.PictureFormat |
||||||
|
import com.otaliastudios.cameraview.size.AspectRatio |
||||||
|
import java.io.File |
||||||
|
|
||||||
|
class PicturePreviewActivity : AppCompatActivity() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
var pictureResult: PictureResult? = null |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_picture_preview) |
||||||
|
val result = pictureResult ?: run { |
||||||
|
finish() |
||||||
|
return |
||||||
|
} |
||||||
|
val imageView = findViewById<ImageView>(R.id.image) |
||||||
|
val captureResolution = findViewById<MessageView>(R.id.nativeCaptureResolution) |
||||||
|
val captureLatency = findViewById<MessageView>(R.id.captureLatency) |
||||||
|
val exifRotation = findViewById<MessageView>(R.id.exifRotation) |
||||||
|
|
||||||
|
val delay = intent.getLongExtra("delay", 0) |
||||||
|
val ratio = AspectRatio.of(result.size) |
||||||
|
captureLatency.setTitleAndMessage("Approx. latency", "$delay milliseconds") |
||||||
|
captureResolution.setTitleAndMessage("Resolution", "${result.size} ($ratio)") |
||||||
|
exifRotation.setTitleAndMessage("EXIF rotation", result.rotation.toString()) |
||||||
|
|
||||||
|
try { |
||||||
|
result.toBitmap(1000, 1000) { bitmap -> imageView.setImageBitmap(bitmap) } |
||||||
|
} catch (e: UnsupportedOperationException) { |
||||||
|
imageView.setImageDrawable(ColorDrawable(Color.GREEN)) |
||||||
|
Toast.makeText(this, "Can't preview this format: " + result.format, Toast.LENGTH_LONG).show() |
||||||
|
} |
||||||
|
if (result.isSnapshot) { |
||||||
|
// Log the real size for debugging reason. |
||||||
|
val options = BitmapFactory.Options() |
||||||
|
options.inJustDecodeBounds = true |
||||||
|
BitmapFactory.decodeByteArray(result.data, 0, result.data.size, options) |
||||||
|
if (result.rotation % 180 != 0) { |
||||||
|
Log.e("PicturePreview", "The picture full size is ${result.size.height}x${result.size.width}") |
||||||
|
} else { |
||||||
|
Log.e("PicturePreview", "The picture full size is ${result.size.width}x${result.size.height}") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDestroy() { |
||||||
|
super.onDestroy() |
||||||
|
if (!isChangingConfigurations) { |
||||||
|
pictureResult = null |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean { |
||||||
|
menuInflater.inflate(R.menu.share, menu) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean { |
||||||
|
if (item.itemId == R.id.share) { |
||||||
|
Toast.makeText(this, "Sharing...", Toast.LENGTH_SHORT).show() |
||||||
|
val extension = when (requireNotNull(pictureResult).format) { |
||||||
|
PictureFormat.JPEG -> "jpg" |
||||||
|
PictureFormat.DNG -> "dng" |
||||||
|
else -> throw RuntimeException("Unknown format.") |
||||||
|
} |
||||||
|
val destFile = File(filesDir, "picture.$extension") |
||||||
|
CameraUtils.writeToFile(requireNotNull(pictureResult?.data), destFile) { file -> |
||||||
|
if (file != null) { |
||||||
|
val context = this@PicturePreviewActivity |
||||||
|
val intent = Intent(Intent.ACTION_SEND) |
||||||
|
intent.type = "image/*" |
||||||
|
val uri = FileProvider.getUriForFile(context, |
||||||
|
context.packageName + ".provider", file) |
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, uri) |
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) |
||||||
|
startActivity(intent) |
||||||
|
} else { |
||||||
|
Toast.makeText(this@PicturePreviewActivity, |
||||||
|
"Error while writing file.", |
||||||
|
Toast.LENGTH_SHORT).show() |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
return super.onOptionsItemSelected(item) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
package com.otaliastudios.cameraview.demo |
||||||
|
|
||||||
|
import android.content.Intent |
||||||
|
import android.net.Uri |
||||||
|
import android.os.Bundle |
||||||
|
import android.util.Log |
||||||
|
import android.view.Menu |
||||||
|
import android.view.MenuItem |
||||||
|
import android.widget.MediaController |
||||||
|
import android.widget.Toast |
||||||
|
import android.widget.VideoView |
||||||
|
import androidx.appcompat.app.AppCompatActivity |
||||||
|
import androidx.core.content.FileProvider |
||||||
|
import com.otaliastudios.cameraview.VideoResult |
||||||
|
import com.otaliastudios.cameraview.size.AspectRatio |
||||||
|
|
||||||
|
class VideoPreviewActivity : AppCompatActivity() { |
||||||
|
companion object { |
||||||
|
var videoResult: VideoResult? = null |
||||||
|
} |
||||||
|
|
||||||
|
private val videoView: VideoView by lazy { findViewById<VideoView>(R.id.video) } |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_video_preview) |
||||||
|
val result = videoResult ?: run { |
||||||
|
finish() |
||||||
|
return |
||||||
|
} |
||||||
|
videoView.setOnClickListener { playVideo() } |
||||||
|
val actualResolution = findViewById<MessageView>(R.id.actualResolution) |
||||||
|
val isSnapshot = findViewById<MessageView>(R.id.isSnapshot) |
||||||
|
val rotation = findViewById<MessageView>(R.id.rotation) |
||||||
|
val audio = findViewById<MessageView>(R.id.audio) |
||||||
|
val audioBitRate = findViewById<MessageView>(R.id.audioBitRate) |
||||||
|
val videoCodec = findViewById<MessageView>(R.id.videoCodec) |
||||||
|
val audioCodec = findViewById<MessageView>(R.id.audioCodec) |
||||||
|
val videoBitRate = findViewById<MessageView>(R.id.videoBitRate) |
||||||
|
val videoFrameRate = findViewById<MessageView>(R.id.videoFrameRate) |
||||||
|
|
||||||
|
val ratio = AspectRatio.of(result.size) |
||||||
|
actualResolution.setTitleAndMessage("Size", "${result.size} ($ratio)") |
||||||
|
isSnapshot.setTitleAndMessage("Snapshot", result.isSnapshot.toString()) |
||||||
|
rotation.setTitleAndMessage("Rotation", result.rotation.toString()) |
||||||
|
audio.setTitleAndMessage("Audio", result.audio.name) |
||||||
|
audioBitRate.setTitleAndMessage("Audio bit rate", "${result.audioBitRate} bits per sec.") |
||||||
|
videoCodec.setTitleAndMessage("VideoCodec", result.videoCodec.name) |
||||||
|
audioCodec.setTitleAndMessage("AudioCodec", result.audioCodec.name) |
||||||
|
videoBitRate.setTitleAndMessage("Video bit rate", "${result.videoBitRate} bits per sec.") |
||||||
|
videoFrameRate.setTitleAndMessage("Video frame rate", "${result.videoFrameRate} fps") |
||||||
|
|
||||||
|
val controller = MediaController(this) |
||||||
|
controller.setAnchorView(videoView) |
||||||
|
controller.setMediaPlayer(videoView) |
||||||
|
videoView.setMediaController(controller) |
||||||
|
videoView.setVideoURI(Uri.fromFile(result.file)) |
||||||
|
videoView.setOnPreparedListener { mp -> |
||||||
|
val lp = videoView.layoutParams |
||||||
|
val videoWidth = mp.videoWidth.toFloat() |
||||||
|
val videoHeight = mp.videoHeight.toFloat() |
||||||
|
val viewWidth = videoView.width.toFloat() |
||||||
|
lp.height = (viewWidth * (videoHeight / videoWidth)).toInt() |
||||||
|
videoView.layoutParams = lp |
||||||
|
playVideo() |
||||||
|
if (result.isSnapshot) { |
||||||
|
// Log the real size for debugging reason. |
||||||
|
Log.e("VideoPreview", "The video full size is " + videoWidth + "x" + videoHeight) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun playVideo() { |
||||||
|
if (!videoView.isPlaying) { |
||||||
|
videoView.start() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDestroy() { |
||||||
|
super.onDestroy() |
||||||
|
if (!isChangingConfigurations) { |
||||||
|
videoResult = null |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean { |
||||||
|
menuInflater.inflate(R.menu.share, menu) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean { |
||||||
|
if (item.itemId == R.id.share) { |
||||||
|
Toast.makeText(this, "Sharing...", Toast.LENGTH_SHORT).show() |
||||||
|
val intent = Intent(Intent.ACTION_SEND) |
||||||
|
intent.type = "video/*" |
||||||
|
val uri = FileProvider.getUriForFile(this, |
||||||
|
this.packageName + ".provider", |
||||||
|
videoResult!!.file) |
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, uri) |
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) |
||||||
|
startActivity(intent) |
||||||
|
return true |
||||||
|
} |
||||||
|
return super.onOptionsItemSelected(item) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,248 +0,0 @@ |
|||||||
GEM |
|
||||||
remote: https://rubygems.org/ |
|
||||||
specs: |
|
||||||
activesupport (4.2.10) |
|
||||||
i18n (~> 0.7) |
|
||||||
minitest (~> 5.1) |
|
||||||
thread_safe (~> 0.3, >= 0.3.4) |
|
||||||
tzinfo (~> 1.1) |
|
||||||
addressable (2.5.2) |
|
||||||
public_suffix (>= 2.0.2, < 4.0) |
|
||||||
coffee-script (2.4.1) |
|
||||||
coffee-script-source |
|
||||||
execjs |
|
||||||
coffee-script-source (1.11.1) |
|
||||||
colorator (1.1.0) |
|
||||||
commonmarker (0.17.13) |
|
||||||
ruby-enum (~> 0.5) |
|
||||||
concurrent-ruby (1.1.4) |
|
||||||
dnsruby (1.61.2) |
|
||||||
addressable (~> 2.5) |
|
||||||
em-websocket (0.5.1) |
|
||||||
eventmachine (>= 0.12.9) |
|
||||||
http_parser.rb (~> 0.6.0) |
|
||||||
ethon (0.11.0) |
|
||||||
ffi (>= 1.3.0) |
|
||||||
eventmachine (1.2.7) |
|
||||||
execjs (2.7.0) |
|
||||||
faraday (0.15.4) |
|
||||||
multipart-post (>= 1.2, < 3) |
|
||||||
ffi (1.9.25) |
|
||||||
forwardable-extended (2.6.0) |
|
||||||
gemoji (3.0.0) |
|
||||||
github-pages (193) |
|
||||||
activesupport (= 4.2.10) |
|
||||||
github-pages-health-check (= 1.8.1) |
|
||||||
jekyll (= 3.7.4) |
|
||||||
jekyll-avatar (= 0.6.0) |
|
||||||
jekyll-coffeescript (= 1.1.1) |
|
||||||
jekyll-commonmark-ghpages (= 0.1.5) |
|
||||||
jekyll-default-layout (= 0.1.4) |
|
||||||
jekyll-feed (= 0.11.0) |
|
||||||
jekyll-gist (= 1.5.0) |
|
||||||
jekyll-github-metadata (= 2.9.4) |
|
||||||
jekyll-mentions (= 1.4.1) |
|
||||||
jekyll-optional-front-matter (= 0.3.0) |
|
||||||
jekyll-paginate (= 1.1.0) |
|
||||||
jekyll-readme-index (= 0.2.0) |
|
||||||
jekyll-redirect-from (= 0.14.0) |
|
||||||
jekyll-relative-links (= 0.5.3) |
|
||||||
jekyll-remote-theme (= 0.3.1) |
|
||||||
jekyll-sass-converter (= 1.5.2) |
|
||||||
jekyll-seo-tag (= 2.5.0) |
|
||||||
jekyll-sitemap (= 1.2.0) |
|
||||||
jekyll-swiss (= 0.4.0) |
|
||||||
jekyll-theme-architect (= 0.1.1) |
|
||||||
jekyll-theme-cayman (= 0.1.1) |
|
||||||
jekyll-theme-dinky (= 0.1.1) |
|
||||||
jekyll-theme-hacker (= 0.1.1) |
|
||||||
jekyll-theme-leap-day (= 0.1.1) |
|
||||||
jekyll-theme-merlot (= 0.1.1) |
|
||||||
jekyll-theme-midnight (= 0.1.1) |
|
||||||
jekyll-theme-minimal (= 0.1.1) |
|
||||||
jekyll-theme-modernist (= 0.1.1) |
|
||||||
jekyll-theme-primer (= 0.5.3) |
|
||||||
jekyll-theme-slate (= 0.1.1) |
|
||||||
jekyll-theme-tactile (= 0.1.1) |
|
||||||
jekyll-theme-time-machine (= 0.1.1) |
|
||||||
jekyll-titles-from-headings (= 0.5.1) |
|
||||||
jemoji (= 0.10.1) |
|
||||||
kramdown (= 1.17.0) |
|
||||||
liquid (= 4.0.0) |
|
||||||
listen (= 3.1.5) |
|
||||||
mercenary (~> 0.3) |
|
||||||
minima (= 2.5.0) |
|
||||||
nokogiri (>= 1.8.2, < 2.0) |
|
||||||
rouge (= 2.2.1) |
|
||||||
terminal-table (~> 1.4) |
|
||||||
github-pages-health-check (1.8.1) |
|
||||||
addressable (~> 2.3) |
|
||||||
dnsruby (~> 1.60) |
|
||||||
octokit (~> 4.0) |
|
||||||
public_suffix (~> 2.0) |
|
||||||
typhoeus (~> 1.3) |
|
||||||
html-pipeline (2.9.1) |
|
||||||
activesupport (>= 2) |
|
||||||
nokogiri (>= 1.4) |
|
||||||
http_parser.rb (0.6.0) |
|
||||||
i18n (0.9.5) |
|
||||||
concurrent-ruby (~> 1.0) |
|
||||||
jekyll (3.7.4) |
|
||||||
addressable (~> 2.4) |
|
||||||
colorator (~> 1.0) |
|
||||||
em-websocket (~> 0.5) |
|
||||||
i18n (~> 0.7) |
|
||||||
jekyll-sass-converter (~> 1.0) |
|
||||||
jekyll-watch (~> 2.0) |
|
||||||
kramdown (~> 1.14) |
|
||||||
liquid (~> 4.0) |
|
||||||
mercenary (~> 0.3.3) |
|
||||||
pathutil (~> 0.9) |
|
||||||
rouge (>= 1.7, < 4) |
|
||||||
safe_yaml (~> 1.0) |
|
||||||
jekyll-avatar (0.6.0) |
|
||||||
jekyll (~> 3.0) |
|
||||||
jekyll-coffeescript (1.1.1) |
|
||||||
coffee-script (~> 2.2) |
|
||||||
coffee-script-source (~> 1.11.1) |
|
||||||
jekyll-commonmark (1.2.0) |
|
||||||
commonmarker (~> 0.14) |
|
||||||
jekyll (>= 3.0, < 4.0) |
|
||||||
jekyll-commonmark-ghpages (0.1.5) |
|
||||||
commonmarker (~> 0.17.6) |
|
||||||
jekyll-commonmark (~> 1) |
|
||||||
rouge (~> 2) |
|
||||||
jekyll-default-layout (0.1.4) |
|
||||||
jekyll (~> 3.0) |
|
||||||
jekyll-feed (0.11.0) |
|
||||||
jekyll (~> 3.3) |
|
||||||
jekyll-gist (1.5.0) |
|
||||||
octokit (~> 4.2) |
|
||||||
jekyll-github-metadata (2.9.4) |
|
||||||
jekyll (~> 3.1) |
|
||||||
octokit (~> 4.0, != 4.4.0) |
|
||||||
jekyll-mentions (1.4.1) |
|
||||||
html-pipeline (~> 2.3) |
|
||||||
jekyll (~> 3.0) |
|
||||||
jekyll-optional-front-matter (0.3.0) |
|
||||||
jekyll (~> 3.0) |
|
||||||
jekyll-paginate (1.1.0) |
|
||||||
jekyll-readme-index (0.2.0) |
|
||||||
jekyll (~> 3.0) |
|
||||||
jekyll-redirect-from (0.14.0) |
|
||||||
jekyll (~> 3.3) |
|
||||||
jekyll-relative-links (0.5.3) |
|
||||||
jekyll (~> 3.3) |
|
||||||
jekyll-remote-theme (0.3.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
rubyzip (>= 1.2.1, < 3.0) |
|
||||||
jekyll-sass-converter (1.5.2) |
|
||||||
sass (~> 3.4) |
|
||||||
jekyll-seo-tag (2.5.0) |
|
||||||
jekyll (~> 3.3) |
|
||||||
jekyll-sitemap (1.2.0) |
|
||||||
jekyll (~> 3.3) |
|
||||||
jekyll-swiss (0.4.0) |
|
||||||
jekyll-theme-architect (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-cayman (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-dinky (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-hacker (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-leap-day (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-merlot (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-midnight (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-minimal (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-modernist (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-primer (0.5.3) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-github-metadata (~> 2.9) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-slate (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-tactile (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-theme-time-machine (0.1.1) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-seo-tag (~> 2.0) |
|
||||||
jekyll-titles-from-headings (0.5.1) |
|
||||||
jekyll (~> 3.3) |
|
||||||
jekyll-watch (2.1.2) |
|
||||||
listen (~> 3.0) |
|
||||||
jemoji (0.10.1) |
|
||||||
gemoji (~> 3.0) |
|
||||||
html-pipeline (~> 2.2) |
|
||||||
jekyll (~> 3.0) |
|
||||||
kramdown (1.17.0) |
|
||||||
liquid (4.0.0) |
|
||||||
listen (3.1.5) |
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4) |
|
||||||
rb-inotify (~> 0.9, >= 0.9.7) |
|
||||||
ruby_dep (~> 1.2) |
|
||||||
mercenary (0.3.6) |
|
||||||
mini_portile2 (2.4.0) |
|
||||||
minima (2.5.0) |
|
||||||
jekyll (~> 3.5) |
|
||||||
jekyll-feed (~> 0.9) |
|
||||||
jekyll-seo-tag (~> 2.1) |
|
||||||
minitest (5.11.3) |
|
||||||
multipart-post (2.0.0) |
|
||||||
nokogiri (1.10.4) |
|
||||||
mini_portile2 (~> 2.4.0) |
|
||||||
octokit (4.13.0) |
|
||||||
sawyer (~> 0.8.0, >= 0.5.3) |
|
||||||
pathutil (0.16.2) |
|
||||||
forwardable-extended (~> 2.6) |
|
||||||
public_suffix (2.0.5) |
|
||||||
rb-fsevent (0.10.3) |
|
||||||
rb-inotify (0.10.0) |
|
||||||
ffi (~> 1.0) |
|
||||||
rouge (2.2.1) |
|
||||||
ruby-enum (0.7.2) |
|
||||||
i18n |
|
||||||
ruby_dep (1.5.0) |
|
||||||
rubyzip (1.2.2) |
|
||||||
safe_yaml (1.0.4) |
|
||||||
sass (3.7.2) |
|
||||||
sass-listen (~> 4.0.0) |
|
||||||
sass-listen (4.0.0) |
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4) |
|
||||||
rb-inotify (~> 0.9, >= 0.9.7) |
|
||||||
sawyer (0.8.1) |
|
||||||
addressable (>= 2.3.5, < 2.6) |
|
||||||
faraday (~> 0.8, < 1.0) |
|
||||||
terminal-table (1.8.0) |
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1) |
|
||||||
thread_safe (0.3.6) |
|
||||||
typhoeus (1.3.1) |
|
||||||
ethon (>= 0.9.0) |
|
||||||
tzinfo (1.2.5) |
|
||||||
thread_safe (~> 0.1) |
|
||||||
unicode-display_width (1.4.0) |
|
||||||
|
|
||||||
PLATFORMS |
|
||||||
ruby |
|
||||||
|
|
||||||
DEPENDENCIES |
|
||||||
github-pages |
|
||||||
|
|
||||||
BUNDLED WITH |
|
||||||
1.17.2 |
|
@ -0,0 +1,95 @@ |
|||||||
|
--- |
||||||
|
layout: page |
||||||
|
title: "FAQs" |
||||||
|
description: "Frequently asked questions" |
||||||
|
order: 4 |
||||||
|
disqus: 1 |
||||||
|
--- |
||||||
|
|
||||||
|
### Usage |
||||||
|
|
||||||
|
##### Q: Why is front camera flipped horizontally when using takePicture() or takeVideo() ? |
||||||
|
|
||||||
|
A: It's actually not flipped - if you show your left hand, the person in the picture will show its left hand as well, |
||||||
|
so this is the accurate representation of reality. |
||||||
|
|
||||||
|
However, if you want to flip the result horizontally to match the preview, |
||||||
|
you can do so by using the [snapshot APIs](../docs/capturing-media), which will respect what is shown in the preview. |
||||||
|
|
||||||
|
##### Q: Can I use filters / overlays / cropping with takePicture() instead of takePictureSnapshot() ? |
||||||
|
|
||||||
|
A: No, these features are only available with the snapshot API. |
||||||
|
|
||||||
|
##### Q: Can I use filters / overlays / cropping with takeVideo() instead of takeVideoSnapshot() ? |
||||||
|
|
||||||
|
A: No, these features are only available with the snapshot API. |
||||||
|
|
||||||
|
##### Q: How can I improve takePictureSnapshot() quality? |
||||||
|
|
||||||
|
A: The picture quality can be controlled in two ways: |
||||||
|
- By [changing the snapshot size](../docs/snapshot-size) |
||||||
|
- By [enabling metering](../docs/metering#picture-metering). |
||||||
|
|
||||||
|
##### Q: How can I improve takeVideoSnapshot() quality? |
||||||
|
|
||||||
|
A: The video quality can be controlled as follows: |
||||||
|
- By changing the [snapshot size](../docs/snapshot-size) |
||||||
|
- By changing the [snapshot framerate](../docs/controls#cameraPreviewFrameRate) (carefully: high values can cause dark preview) |
||||||
|
- By changing the [video bitrate](../docs/controls#cameraVideoBitRate) |
||||||
|
- By changing the [audio bitrate](../docs/controls#cameraAudioBitRate) |
||||||
|
|
||||||
|
##### Q: How can I reduce the picture size? |
||||||
|
|
||||||
|
A: The only control here is the picture size. |
||||||
|
- When using `takePicture()`, change the [capture size](../docs/capture-size) |
||||||
|
- When using `takePictureSnapshot()`, change the [snapshot size](../docs/snapshot-size) |
||||||
|
|
||||||
|
##### Q: How can I reduce the video size? |
||||||
|
|
||||||
|
A: By using video controls, for instance: |
||||||
|
- Change the [video bitrate](../docs/controls#cameraVideoBitRate) |
||||||
|
- Change the [audio bitrate](../docs/controls#cameraAudioBitRate) |
||||||
|
- When using `takeVideo()`, change the [capture size](../docs/capture-size) |
||||||
|
- When using `takeVideoSnapshot()`, change the [snapshot size](../docs/snapshot-size) |
||||||
|
|
||||||
|
##### Q: Why is my preview / snapshot dark? |
||||||
|
|
||||||
|
A: This is often caused by bad [framerate](../docs/controls#cameraPreviewFrameRate). Try using |
||||||
|
a lower value, so that there's more time for frame exposure. |
||||||
|
|
||||||
|
### Project Management |
||||||
|
|
||||||
|
##### Q: I have found a bug with Camera1, can you fix it? |
||||||
|
|
||||||
|
A: No, we will not address Camera1 bugs anymore - development is focused on Camera2. However, if you find a solution, |
||||||
|
feel free to open a GitHub issue or pull requests to discuss. |
||||||
|
|
||||||
|
##### Q: I have found a bug with my device XYZ, can you fix it? |
||||||
|
|
||||||
|
A: No. Unless it's a device that we physically own, there is very little chance that a device-specific issues |
||||||
|
can be solved by the maintainers. We encourage you to investigate on your own and get back to us |
||||||
|
with a clear understanding of the problem and the solution. |
||||||
|
|
||||||
|
##### Q: Why don't you review / comment on my GitHub issue? |
||||||
|
|
||||||
|
A: Either because the issue did not respect the provided template, or because I don't have time. |
||||||
|
If you are sure about the template, you can get private support by [sponsoring the project](../extra/donate). |
||||||
|
|
||||||
|
##### Q: Why don't you review / comment on my GitHub pull requests? |
||||||
|
|
||||||
|
A: Either because the pull requests did not respect the provided template, or because I don't have time. |
||||||
|
If you are sure about the template, you can get private support by [sponsoring the project](../extra/donate). |
||||||
|
|
||||||
|
##### Q: When will you do a new release? |
||||||
|
|
||||||
|
A: We don't have a release schedule. New releases happen when there's enough changes to justify one, |
||||||
|
and maintainers have had time to execute and publish the release. You can speed things up by |
||||||
|
[sponsoring the project](../extra/donate) or pull snapshots from [jitpack.io](https://jitpack.io): |
||||||
|
|
||||||
|
```groovy |
||||||
|
implementation 'com.github.natario1:CameraView:main-SNAPSHOT' |
||||||
|
implementation 'com.github.natario1:CameraView:<commit hash>' |
||||||
|
``` |
||||||
|
|
||||||
|
Check their website for more information about how to set things up. |
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue