Compare commits
	
		
			1 Commits 
		
	
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | b6947c6267 | 6 years ago | 
| @ -1,14 +1,10 @@ | ||||
| ### Before you go | ||||
| Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first so that | ||||
| we can discuss the best approach to address the problem. Without a reference issue and discussion,  | ||||
| unfortunately, this PR will likely be ignored. | ||||
| Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first. | ||||
| 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. | ||||
| 
 | ||||
| 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*) | ||||
| - Fixes ... (*issue number*) | ||||
| - Tests: ... (*yes/no*) | ||||
| - Docs updated: ... (*yes/no*) | ||||
| 
 | ||||
| ### Solution | ||||
| If applicable, describe briefly how the issue was addressed. | ||||
| If applicable, briefly describe how the issue was addressed. | ||||
|  | ||||
| @ -1,25 +0,0 @@ | ||||
| # 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 | ||||
| @ -1,23 +0,0 @@ | ||||
| <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> | ||||
| @ -1,23 +0,0 @@ | ||||
| <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> | ||||
| @ -0,0 +1,29 @@ | ||||
| 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() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ext { | ||||
|     compileSdkVersion = 29 | ||||
|     minSdkVersion = 15 | ||||
|     targetSdkVersion = 29 | ||||
| } | ||||
| 
 | ||||
| task clean(type: Delete) { | ||||
|     delete rootProject.buildDir | ||||
| } | ||||
| @ -1,30 +0,0 @@ | ||||
| 
 | ||||
| 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) | ||||
| } | ||||
| @ -0,0 +1,270 @@ | ||||
| apply plugin: 'com.android.library' | ||||
| apply plugin: 'com.github.dcendents.android-maven' | ||||
| apply plugin: 'com.jfrog.bintray' | ||||
| 
 | ||||
| // Required by bintray | ||||
| version = '2.6.0' | ||||
| 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' | ||||
| } | ||||
| 
 | ||||
| //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) { | ||||
|     classifier = '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) { | ||||
|     classifier = '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 | ||||
| 
 | ||||
| @ -1,137 +0,0 @@ | ||||
| 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")) | ||||
| } | ||||
| @ -1,10 +1,9 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| 
 | ||||
| import android.graphics.Rect; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.CropHelper; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| @ -1,10 +1,9 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| 
 | ||||
| import com.google.android.gms.tasks.Task; | ||||
| import com.google.android.gms.tasks.Tasks; | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.internal.WorkerHandler; | ||||
| import com.otaliastudios.cameraview.tools.Op; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| @ -1,63 +0,0 @@ | ||||
| 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; | ||||
|     } | ||||
| } | ||||
| @ -1,40 +0,0 @@ | ||||
| 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; | ||||
|     } | ||||
| } | ||||
| @ -1,97 +0,0 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import android.opengl.GLES11Ext; | ||||
| import android.opengl.GLES20; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| import com.otaliastudios.cameraview.filter.Filter; | ||||
| import com.otaliastudios.cameraview.filter.NoFilter; | ||||
| import com.otaliastudios.opengl.core.Egloo; | ||||
| import com.otaliastudios.opengl.program.GlProgram; | ||||
| import com.otaliastudios.opengl.texture.GlTexture; | ||||
| 
 | ||||
| 
 | ||||
| public class GlTextureDrawer { | ||||
| 
 | ||||
|     private final static String TAG = GlTextureDrawer.class.getSimpleName(); | ||||
|     private final static CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     private final static int TEXTURE_TARGET = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; | ||||
|     private final static int TEXTURE_UNIT = GLES20.GL_TEXTURE0; | ||||
| 
 | ||||
|     private final GlTexture mTexture; | ||||
|     private float[] mTextureTransform = Egloo.IDENTITY_MATRIX.clone(); | ||||
| 
 | ||||
|     @NonNull | ||||
|     private Filter mFilter = new NoFilter(); | ||||
|     private Filter mPendingFilter = null; | ||||
|     private int mProgramHandle = -1; | ||||
| 
 | ||||
|     @SuppressWarnings("unused") | ||||
|     public GlTextureDrawer() { | ||||
|         this(new GlTexture(TEXTURE_UNIT, TEXTURE_TARGET)); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unused") | ||||
|     public GlTextureDrawer(int textureId) { | ||||
|         this(new GlTexture(TEXTURE_UNIT, TEXTURE_TARGET, textureId)); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     public GlTextureDrawer(@NonNull GlTexture texture) { | ||||
|         mTexture = texture; | ||||
|     } | ||||
| 
 | ||||
|     public void setFilter(@NonNull Filter filter) { | ||||
|         mPendingFilter = filter; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public GlTexture getTexture() { | ||||
|         return mTexture; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public float[] getTextureTransform() { | ||||
|         return mTextureTransform; | ||||
|     } | ||||
| 
 | ||||
|     public void setTextureTransform(@NonNull float[] textureTransform) { | ||||
|         mTextureTransform = textureTransform; | ||||
|     } | ||||
| 
 | ||||
|     public void draw(final long timestampUs) { | ||||
|         if (mPendingFilter != null) { | ||||
|             release(); | ||||
|             mFilter = mPendingFilter; | ||||
|             mPendingFilter = null; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if (mProgramHandle == -1) { | ||||
|             mProgramHandle = GlProgram.create( | ||||
|                     mFilter.getVertexShader(), | ||||
|                     mFilter.getFragmentShader()); | ||||
|             mFilter.onCreate(mProgramHandle); | ||||
|             Egloo.checkGlError("program creation"); | ||||
|         } | ||||
| 
 | ||||
|         GLES20.glUseProgram(mProgramHandle); | ||||
|         Egloo.checkGlError("glUseProgram(handle)"); | ||||
|         mTexture.bind(); | ||||
|         mFilter.draw(timestampUs, mTextureTransform); | ||||
|         mTexture.unbind(); | ||||
|         GLES20.glUseProgram(0); | ||||
|         Egloo.checkGlError("glUseProgram(0)"); | ||||
|     } | ||||
| 
 | ||||
|     public void release() { | ||||
|         if (mProgramHandle == -1) return; | ||||
|         mFilter.onDestroy(); | ||||
|         GLES20.glDeleteProgram(mProgramHandle); | ||||
|         mProgramHandle = -1; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,96 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import android.opengl.GLES20; | ||||
| import android.opengl.Matrix; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| 
 | ||||
| import java.nio.ByteBuffer; | ||||
| import java.nio.ByteOrder; | ||||
| import java.nio.FloatBuffer; | ||||
| 
 | ||||
| public class GlUtils { | ||||
| 
 | ||||
|     private final static String TAG = GlUtils.class.getSimpleName(); | ||||
|     private final static CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     // Identity matrix for general use.
 | ||||
|     public static final float[] IDENTITY_MATRIX = new float[16]; | ||||
|     static { | ||||
|         Matrix.setIdentityM(IDENTITY_MATRIX, 0); | ||||
|     } | ||||
| 
 | ||||
|     public static void checkError(@NonNull String opName) { | ||||
|         int error = GLES20.glGetError(); | ||||
|         if (error != GLES20.GL_NO_ERROR) { | ||||
|             String message = LOG.e("Error during", opName, "glError 0x", | ||||
|                     Integer.toHexString(error)); | ||||
|             throw new RuntimeException(message); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static void checkLocation(int location, @NonNull String name) { | ||||
|         if (location < 0) { | ||||
|             String message = LOG.e("Unable to locate", name, "in program"); | ||||
|             throw new RuntimeException(message); | ||||
|         } | ||||
|     } | ||||
|     // Compiles the given shader, returns a handle.
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     public static int loadShader(int shaderType, @NonNull String source) { | ||||
|         int shader = GLES20.glCreateShader(shaderType); | ||||
|         checkError("glCreateShader type=" + shaderType); | ||||
|         GLES20.glShaderSource(shader, source); | ||||
|         GLES20.glCompileShader(shader); | ||||
|         int[] compiled = new int[1]; | ||||
|         GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); | ||||
|         if (compiled[0] == 0) { | ||||
|             LOG.e("Could not compile shader", shaderType, ":", | ||||
|                     GLES20.glGetShaderInfoLog(shader)); | ||||
|             GLES20.glDeleteShader(shader); | ||||
|             shader = 0; | ||||
|         } | ||||
|         return shader; | ||||
|     } | ||||
| 
 | ||||
|     // Creates a program with given vertex shader and pixel shader.
 | ||||
|     public static int createProgram(@NonNull String vertexSource, @NonNull String fragmentSource) { | ||||
|         int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); | ||||
|         if (vertexShader == 0) return 0; | ||||
|         int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); | ||||
|         if (pixelShader == 0) return 0; | ||||
| 
 | ||||
|         int program = GLES20.glCreateProgram(); | ||||
|         checkError("glCreateProgram"); | ||||
|         if (program == 0) { | ||||
|             LOG.e("Could not create program"); | ||||
|         } | ||||
|         GLES20.glAttachShader(program, vertexShader); | ||||
|         checkError("glAttachShader"); | ||||
|         GLES20.glAttachShader(program, pixelShader); | ||||
|         checkError("glAttachShader"); | ||||
|         GLES20.glLinkProgram(program); | ||||
|         int[] linkStatus = new int[1]; | ||||
|         GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); | ||||
|         if (linkStatus[0] != GLES20.GL_TRUE) { | ||||
|             LOG.e("Could not link program:", GLES20.glGetProgramInfoLog(program)); | ||||
|             GLES20.glDeleteProgram(program); | ||||
|             program = 0; | ||||
|         } | ||||
|         return program; | ||||
|     } | ||||
| 
 | ||||
|     // Allocates a direct float buffer, and populates it with the float array data.
 | ||||
|     public static FloatBuffer floatBuffer(@NonNull float[] coords) { | ||||
|         // Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
 | ||||
|         ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4); | ||||
|         bb.order(ByteOrder.nativeOrder()); | ||||
|         FloatBuffer fb = bb.asFloatBuffer(); | ||||
|         fb.put(coords); | ||||
|         fb.position(0); | ||||
|         return fb; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,244 @@ | ||||
| /* | ||||
|  * Copyright 2013 Google Inc. All rights reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.otaliastudios.cameraview.internal.egl; | ||||
| 
 | ||||
| import android.graphics.Bitmap; | ||||
| import android.opengl.EGL14; | ||||
| import android.opengl.EGLSurface; | ||||
| import android.opengl.GLES20; | ||||
| import android.os.Build; | ||||
| import androidx.annotation.RequiresApi; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| import com.otaliastudios.cameraview.internal.GlUtils; | ||||
| 
 | ||||
| import java.io.BufferedOutputStream; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.File; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.nio.ByteBuffer; | ||||
| import java.nio.ByteOrder; | ||||
| 
 | ||||
| /** | ||||
|  * Common base class for EGL surfaces. | ||||
|  * <p> | ||||
|  * There can be multiple surfaces associated with a single context. | ||||
|  */ | ||||
| @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) | ||||
| public class EglBaseSurface { | ||||
|     protected static final String TAG = EglBaseSurface.class.getSimpleName(); | ||||
|     private final static CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     // EglCore object we're associated with.  It may be associated with multiple surfaces.
 | ||||
|     protected EglCore mEglCore; | ||||
| 
 | ||||
|     private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; | ||||
|     private int mWidth = -1; | ||||
|     private int mHeight = -1; | ||||
| 
 | ||||
|     public EglBaseSurface(EglCore eglCore) { | ||||
|         mEglCore = eglCore; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a window surface. | ||||
|      * <p> | ||||
|      * @param surface May be a Surface or SurfaceTexture. | ||||
|      */ | ||||
|     public void createWindowSurface(Object surface) { | ||||
|         if (mEGLSurface != EGL14.EGL_NO_SURFACE) { | ||||
|             throw new IllegalStateException("surface already created"); | ||||
|         } | ||||
|         mEGLSurface = mEglCore.createWindowSurface(surface); | ||||
| 
 | ||||
|         // Don't cache width/height here, because the size of the underlying surface can change
 | ||||
|         // out from under us (see e.g. HardwareScalerActivity).
 | ||||
|         //mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
 | ||||
|         //mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates an off-screen surface. | ||||
|      */ | ||||
|     public void createOffscreenSurface(int width, int height) { | ||||
|         if (mEGLSurface != EGL14.EGL_NO_SURFACE) { | ||||
|             throw new IllegalStateException("surface already created"); | ||||
|         } | ||||
|         mEGLSurface = mEglCore.createOffscreenSurface(width, height); | ||||
|         mWidth = width; | ||||
|         mHeight = height; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the surface's width, in pixels. | ||||
|      * <p> | ||||
|      * If this is called on a window surface, and the underlying surface is in the process | ||||
|      * of changing size, we may not see the new size right away (e.g. in the "surfaceChanged" | ||||
|      * callback).  The size should match after the next buffer swap. | ||||
|      */ | ||||
|     public int getWidth() { | ||||
|         if (mWidth < 0) { | ||||
|             return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); | ||||
|         } else { | ||||
|             return mWidth; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the surface's height, in pixels. | ||||
|      */ | ||||
|     public int getHeight() { | ||||
|         if (mHeight < 0) { | ||||
|             return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); | ||||
|         } else { | ||||
|             return mHeight; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Release the EGL surface. | ||||
|      */ | ||||
|     public void releaseEglSurface() { | ||||
|         mEglCore.releaseSurface(mEGLSurface); | ||||
|         mEGLSurface = EGL14.EGL_NO_SURFACE; | ||||
|         mWidth = mHeight = -1; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Makes our EGL context and surface current. | ||||
|      */ | ||||
|     public void makeCurrent() { | ||||
|         mEglCore.makeCurrent(mEGLSurface); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Makes our EGL context and surface current for drawing, using the supplied surface | ||||
|      * for reading. | ||||
|      */ | ||||
|     public void makeCurrentReadFrom(EglBaseSurface readSurface) { | ||||
|         mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Calls eglSwapBuffers.  Use this to "publish" the current frame. | ||||
|      * | ||||
|      * @return false on failure | ||||
|      */ | ||||
|     public boolean swapBuffers() { | ||||
|         boolean result = mEglCore.swapBuffers(mEGLSurface); | ||||
|         if (!result) { | ||||
|             Log.d(TAG, "WARNING: swapBuffers() failed"); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sends the presentation time stamp to EGL. | ||||
|      * https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
 | ||||
|      * | ||||
|      * @param nsecs Timestamp, in nanoseconds. | ||||
|      */ | ||||
|     public void setPresentationTime(long nsecs) { | ||||
|         mEglCore.setPresentationTime(mEGLSurface, nsecs); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Saves the EGL surface to a file. | ||||
|      * <p> | ||||
|      * Expects that this object's EGL surface is current. | ||||
|      */ | ||||
|     public void saveFrameToFile(File file) throws IOException { | ||||
|         if (!mEglCore.isCurrent(mEGLSurface)) { | ||||
|             throw new RuntimeException("Expected EGL context/surface is not current"); | ||||
|         } | ||||
| 
 | ||||
|         // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
 | ||||
|         // data (i.e. a byte of red, followed by a byte of green...).  While the Bitmap
 | ||||
|         // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
 | ||||
|         // Bitmap "copy pixels" method wants the same format GL provides.
 | ||||
|         //
 | ||||
|         // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
 | ||||
|         // here often.
 | ||||
|         //
 | ||||
|         // Making this even more interesting is the upside-down nature of GL, which means
 | ||||
|         // our output will look upside down relative to what appears on screen if the
 | ||||
|         // typical GL conventions are used.
 | ||||
| 
 | ||||
|         String filename = file.toString(); | ||||
| 
 | ||||
|         int width = getWidth(); | ||||
|         int height = getHeight(); | ||||
|         ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); | ||||
|         buf.order(ByteOrder.LITTLE_ENDIAN); | ||||
|         GLES20.glReadPixels(0, 0, width, height, | ||||
|                 GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); | ||||
|         GlUtils.checkError("glReadPixels"); | ||||
|         buf.rewind(); | ||||
| 
 | ||||
|         BufferedOutputStream bos = null; | ||||
|         try { | ||||
|             bos = new BufferedOutputStream(new FileOutputStream(filename)); | ||||
|             Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | ||||
|             bmp.copyPixelsFromBuffer(buf); | ||||
|             bmp.compress(Bitmap.CompressFormat.PNG, 90, bos); | ||||
|             bmp.recycle(); | ||||
|         } finally { | ||||
|             if (bos != null) bos.close(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Saves the EGL surface to given format. | ||||
|      * <p> | ||||
|      * Expects that this object's EGL surface is current. | ||||
|      */ | ||||
|     public byte[] saveFrameTo(Bitmap.CompressFormat compressFormat) { | ||||
|         if (!mEglCore.isCurrent(mEGLSurface)) { | ||||
|             throw new RuntimeException("Expected EGL context/surface is not current"); | ||||
|         } | ||||
| 
 | ||||
|         // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
 | ||||
|         // data (i.e. a byte of red, followed by a byte of green...).  While the Bitmap
 | ||||
|         // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
 | ||||
|         // Bitmap "copy pixels" method wants the same format GL provides.
 | ||||
|         //
 | ||||
|         // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
 | ||||
|         // here often.
 | ||||
|         //
 | ||||
|         // Making this even more interesting is the upside-down nature of GL, which means
 | ||||
|         // our output will look upside down relative to what appears on screen if the
 | ||||
|         // typical GL conventions are used.
 | ||||
| 
 | ||||
|         int width = getWidth(); | ||||
|         int height = getHeight(); | ||||
|         ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); | ||||
|         buf.order(ByteOrder.LITTLE_ENDIAN); | ||||
|         GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, | ||||
|                 buf); | ||||
|         GlUtils.checkError("glReadPixels"); | ||||
|         buf.rewind(); | ||||
| 
 | ||||
|         ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.array().length); | ||||
|         Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | ||||
|         bmp.copyPixelsFromBuffer(buf); | ||||
|         bmp.compress(compressFormat, 90, bos); | ||||
|         bmp.recycle(); | ||||
|         return bos.toByteArray(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,364 @@ | ||||
| /* | ||||
|  * Copyright 2013 Google Inc. All rights reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.otaliastudios.cameraview.internal.egl; | ||||
| 
 | ||||
| import android.graphics.SurfaceTexture; | ||||
| import android.opengl.EGL14; | ||||
| import android.opengl.EGLConfig; | ||||
| import android.opengl.EGLContext; | ||||
| import android.opengl.EGLDisplay; | ||||
| import android.opengl.EGLExt; | ||||
| import android.opengl.EGLSurface; | ||||
| import android.os.Build; | ||||
| import androidx.annotation.RequiresApi; | ||||
| import android.util.Log; | ||||
| import android.view.Surface; | ||||
| 
 | ||||
| /** | ||||
|  * -- from grafika -- | ||||
|  * | ||||
|  * Core EGL state (display, context, config). | ||||
|  * <p> | ||||
|  * The EGLContext must only be attached to one thread at a time.  This class is not thread-safe. | ||||
|  */ | ||||
| @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) | ||||
| public final class EglCore { | ||||
|     private static final String TAG = EglCore.class.getSimpleName(); | ||||
| 
 | ||||
|     /** | ||||
|      * Constructor flag: surface must be recordable.  This discourages EGL from using a | ||||
|      * pixel format that cannot be converted efficiently to something usable by the video | ||||
|      * encoder. | ||||
|      */ | ||||
|     public static final int FLAG_RECORDABLE = 0x01; | ||||
| 
 | ||||
|     /** | ||||
|      * Constructor flag: ask for GLES3, fall back to GLES2 if not available.  Without this | ||||
|      * flag, GLES2 is used. | ||||
|      */ | ||||
|     public static final int FLAG_TRY_GLES3 = 0x02; | ||||
| 
 | ||||
|     // Android-specific extension.
 | ||||
|     private static final int EGL_RECORDABLE_ANDROID = 0x3142; | ||||
| 
 | ||||
|     private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; | ||||
|     private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; | ||||
|     private EGLConfig mEGLConfig = null; | ||||
|     private int mGlVersion = -1; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Prepares EGL display and context. | ||||
|      * <p> | ||||
|      * Equivalent to EglCore(null, 0). | ||||
|      */ | ||||
|     public EglCore() { | ||||
|         this(null, 0); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Prepares EGL display and context. | ||||
|      * <p> | ||||
|      * @param sharedContext The context to share, or null if sharing is not desired. | ||||
|      * @param flags Configuration bit flags, e.g. FLAG_RECORDABLE. | ||||
|      */ | ||||
|     public EglCore(EGLContext sharedContext, int flags) { | ||||
|         if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { | ||||
|             throw new RuntimeException("EGL already set up"); | ||||
|         } | ||||
| 
 | ||||
|         if (sharedContext == null) { | ||||
|             sharedContext = EGL14.EGL_NO_CONTEXT; | ||||
|         } | ||||
| 
 | ||||
|         mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); | ||||
|         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { | ||||
|             throw new RuntimeException("unable to get EGL14 display"); | ||||
|         } | ||||
|         int[] version = new int[2]; | ||||
|         if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { | ||||
|             mEGLDisplay = null; | ||||
|             throw new RuntimeException("unable to initialize EGL14"); | ||||
|         } | ||||
| 
 | ||||
|         // Try to get a GLES3 context, if requested.
 | ||||
|         if ((flags & FLAG_TRY_GLES3) != 0) { | ||||
|             //Log.d(TAG, "Trying GLES 3");
 | ||||
|             EGLConfig config = getConfig(flags, 3); | ||||
|             if (config != null) { | ||||
|                 int[] attrib3_list = { | ||||
|                         EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, | ||||
|                         EGL14.EGL_NONE | ||||
|                 }; | ||||
|                 EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, | ||||
|                         attrib3_list, 0); | ||||
| 
 | ||||
|                 if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { | ||||
|                     //Log.d(TAG, "Got GLES 3 config");
 | ||||
|                     mEGLConfig = config; | ||||
|                     mEGLContext = context; | ||||
|                     mGlVersion = 3; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (mEGLContext == EGL14.EGL_NO_CONTEXT) {  // GLES 2 only, or GLES 3 attempt failed
 | ||||
|             //Log.d(TAG, "Trying GLES 2");
 | ||||
|             EGLConfig config = getConfig(flags, 2); | ||||
|             if (config == null) { | ||||
|                 throw new RuntimeException("Unable to find a suitable EGLConfig"); | ||||
|             } | ||||
|             int[] attrib2_list = { | ||||
|                     EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, | ||||
|                     EGL14.EGL_NONE | ||||
|             }; | ||||
|             EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, | ||||
|                     attrib2_list, 0); | ||||
|             checkEglError("eglCreateContext"); | ||||
|             mEGLConfig = config; | ||||
|             mEGLContext = context; | ||||
|             mGlVersion = 2; | ||||
|         } | ||||
| 
 | ||||
|         // Confirm with query.
 | ||||
|         int[] values = new int[1]; | ||||
|         EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, | ||||
|                 values, 0); | ||||
|         // Log.d(TAG, "EGLContext created, client version " + values[0]);
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Finds a suitable EGLConfig. | ||||
|      * | ||||
|      * @param flags Bit flags from constructor. | ||||
|      * @param version Must be 2 or 3. | ||||
|      */ | ||||
|     private EGLConfig getConfig(int flags, int version) { | ||||
|         int renderableType = EGL14.EGL_OPENGL_ES2_BIT; | ||||
|         if (version >= 3) { | ||||
|             renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; | ||||
|         } | ||||
| 
 | ||||
|         // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
 | ||||
|         // doesn't really help.  It can also lead to a huge performance hit on glReadPixels()
 | ||||
|         // when reading into a GL_RGBA buffer.
 | ||||
|         int[] attribList = { | ||||
|                 EGL14.EGL_RED_SIZE, 8, | ||||
|                 EGL14.EGL_GREEN_SIZE, 8, | ||||
|                 EGL14.EGL_BLUE_SIZE, 8, | ||||
|                 EGL14.EGL_ALPHA_SIZE, 8, | ||||
|                 //EGL14.EGL_DEPTH_SIZE, 16,
 | ||||
|                 //EGL14.EGL_STENCIL_SIZE, 8,
 | ||||
|                 EGL14.EGL_RENDERABLE_TYPE, renderableType, | ||||
|                 EGL14.EGL_NONE, 0,      // placeholder for recordable [@-3]
 | ||||
|                 EGL14.EGL_NONE | ||||
|         }; | ||||
|         if ((flags & FLAG_RECORDABLE) != 0) { | ||||
|             attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID; | ||||
|             attribList[attribList.length - 2] = 1; | ||||
|         } | ||||
|         EGLConfig[] configs = new EGLConfig[1]; | ||||
|         int[] numConfigs = new int[1]; | ||||
|         if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, | ||||
|                 configs.length, numConfigs, 0)) { | ||||
|             Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); | ||||
|             return null; | ||||
|         } | ||||
|         return configs[0]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Discards all resources held by this class, notably the EGL context.  This must be | ||||
|      * called from the thread where the context was created. | ||||
|      * <p> | ||||
|      * On completion, no context will be current. | ||||
|      */ | ||||
|     public void release() { | ||||
|         if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { | ||||
|             // Android is unusual in that it uses a reference-counted EGLDisplay.  So for
 | ||||
|             // every eglInitialize() we need an eglTerminate().
 | ||||
|             EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, | ||||
|                     EGL14.EGL_NO_CONTEXT); | ||||
|             EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); | ||||
|             EGL14.eglReleaseThread(); | ||||
|             EGL14.eglTerminate(mEGLDisplay); | ||||
|         } | ||||
| 
 | ||||
|         mEGLDisplay = EGL14.EGL_NO_DISPLAY; | ||||
|         mEGLContext = EGL14.EGL_NO_CONTEXT; | ||||
|         mEGLConfig = null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void finalize() throws Throwable { | ||||
|         try { | ||||
|             if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { | ||||
|                 // We're limited here -- finalizers don't run on the thread that holds
 | ||||
|                 // the EGL state, so if a surface or context is still current on another
 | ||||
|                 // thread we can't fully release it here.  Exceptions thrown from here
 | ||||
|                 // are quietly discarded.  Complain in the log file.
 | ||||
|                 Log.w(TAG, "WARNING: EglCore was not explicitly released! " + | ||||
|                         "State may be leaked"); | ||||
|                 release(); | ||||
|             } | ||||
|         } finally { | ||||
|             super.finalize(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Destroys the specified surface.  Note the EGLSurface won't actually be destroyed if it's | ||||
|      * still current in a context. | ||||
|      */ | ||||
|     public void releaseSurface(EGLSurface eglSurface) { | ||||
|         EGL14.eglDestroySurface(mEGLDisplay, eglSurface); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates an EGL surface associated with a Surface. | ||||
|      * <p> | ||||
|      * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute. | ||||
|      */ | ||||
|     public EGLSurface createWindowSurface(Object surface) { | ||||
|         if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) { | ||||
|             throw new RuntimeException("invalid surface: " + surface); | ||||
|         } | ||||
| 
 | ||||
|         // Create a window surface, and attach it to the Surface we received.
 | ||||
|         int[] surfaceAttribs = { | ||||
|                 EGL14.EGL_NONE | ||||
|         }; | ||||
|         EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, | ||||
|                 surfaceAttribs, 0); | ||||
|         checkEglError("eglCreateWindowSurface"); | ||||
|         if (eglSurface == null) { | ||||
|             throw new RuntimeException("surface was null"); | ||||
|         } | ||||
|         return eglSurface; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates an EGL surface associated with an offscreen buffer. | ||||
|      */ | ||||
|     public EGLSurface createOffscreenSurface(int width, int height) { | ||||
|         int[] surfaceAttribs = { | ||||
|                 EGL14.EGL_WIDTH, width, | ||||
|                 EGL14.EGL_HEIGHT, height, | ||||
|                 EGL14.EGL_NONE | ||||
|         }; | ||||
|         EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, | ||||
|                 surfaceAttribs, 0); | ||||
|         checkEglError("eglCreatePbufferSurface"); | ||||
|         if (eglSurface == null) { | ||||
|             throw new RuntimeException("surface was null"); | ||||
|         } | ||||
|         return eglSurface; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Makes our EGL context current, using the supplied surface for both "draw" and "read". | ||||
|      */ | ||||
|     public void makeCurrent(EGLSurface eglSurface) { | ||||
|         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { | ||||
|             // called makeCurrent() before create?
 | ||||
|             // Log.d(TAG, "NOTE: makeCurrent w/o display");
 | ||||
|         } | ||||
|         if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) { | ||||
|             throw new RuntimeException("eglMakeCurrent failed"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Makes our EGL context current, using the supplied "draw" and "read" surfaces. | ||||
|      */ | ||||
|     public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { | ||||
|         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { | ||||
|             // called makeCurrent() before create?
 | ||||
|             Log.d(TAG, "NOTE: makeCurrent w/o display"); | ||||
|         } | ||||
|         if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) { | ||||
|             throw new RuntimeException("eglMakeCurrent(draw,read) failed"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Makes no context current. | ||||
|      */ | ||||
|     public void makeNothingCurrent() { | ||||
|         if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, | ||||
|                 EGL14.EGL_NO_CONTEXT)) { | ||||
|             throw new RuntimeException("eglMakeCurrent failed"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Calls eglSwapBuffers.  Use this to "publish" the current frame. | ||||
|      * | ||||
|      * @return false on failure | ||||
|      */ | ||||
|     public boolean swapBuffers(EGLSurface eglSurface) { | ||||
|         return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds. | ||||
|      * https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
 | ||||
|      */ | ||||
|     public void setPresentationTime(EGLSurface eglSurface, long nsecs) { | ||||
|         EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if our context and the specified surface are current. | ||||
|      */ | ||||
|     public boolean isCurrent(EGLSurface eglSurface) { | ||||
|         return mEGLContext.equals(EGL14.eglGetCurrentContext()) && | ||||
|             eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Performs a simple surface query. | ||||
|      */ | ||||
|     public int querySurface(EGLSurface eglSurface, int what) { | ||||
|         int[] value = new int[1]; | ||||
|         EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0); | ||||
|         return value[0]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Queries a string value. | ||||
|      */ | ||||
|     public String queryString(int what) { | ||||
|         return EGL14.eglQueryString(mEGLDisplay, what); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the GLES version this context is configured for (currently 2 or 3). | ||||
|      */ | ||||
|     public int getGlVersion() { | ||||
|         return mGlVersion; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Checks for EGL errors.  Throws an exception if an error has been raised. | ||||
|      */ | ||||
|     private void checkEglError(String msg) { | ||||
|         int error; | ||||
|         if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { | ||||
|             throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,103 @@ | ||||
| package com.otaliastudios.cameraview.internal.egl; | ||||
| 
 | ||||
| 
 | ||||
| import android.opengl.GLES11Ext; | ||||
| import android.opengl.GLES20; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| import com.otaliastudios.cameraview.filter.Filter; | ||||
| import com.otaliastudios.cameraview.filter.NoFilter; | ||||
| import com.otaliastudios.cameraview.internal.GlUtils; | ||||
| 
 | ||||
| 
 | ||||
| public class EglViewport { | ||||
| 
 | ||||
|     private final static CameraLogger LOG = CameraLogger.create(EglViewport.class.getSimpleName()); | ||||
| 
 | ||||
|     private int mProgramHandle = -1; | ||||
|     private int mTextureTarget; | ||||
|     private int mTextureUnit; | ||||
| 
 | ||||
|     private Filter mFilter; | ||||
|     private Filter mPendingFilter; | ||||
| 
 | ||||
|     public EglViewport() { | ||||
|         this(new NoFilter()); | ||||
|     } | ||||
| 
 | ||||
|     public EglViewport(@NonNull Filter filter) { | ||||
|         mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; | ||||
|         mTextureUnit = GLES20.GL_TEXTURE0; | ||||
|         mFilter = filter; | ||||
|         createProgram(); | ||||
|     } | ||||
| 
 | ||||
|     private void createProgram() { | ||||
|         mProgramHandle = GlUtils.createProgram(mFilter.getVertexShader(), | ||||
|                 mFilter.getFragmentShader()); | ||||
|         mFilter.onCreate(mProgramHandle); | ||||
|     } | ||||
| 
 | ||||
|     public void release() { | ||||
|         if (mProgramHandle != -1) { | ||||
|             mFilter.onDestroy(); | ||||
|             GLES20.glDeleteProgram(mProgramHandle); | ||||
|             mProgramHandle = -1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public int createTexture() { | ||||
|         int[] textures = new int[1]; | ||||
|         GLES20.glGenTextures(1, textures, 0); | ||||
|         GlUtils.checkError("glGenTextures"); | ||||
| 
 | ||||
|         int texId = textures[0]; | ||||
|         GLES20.glActiveTexture(mTextureUnit); | ||||
|         GLES20.glBindTexture(mTextureTarget, texId); | ||||
|         GlUtils.checkError("glBindTexture " + texId); | ||||
| 
 | ||||
|         GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, | ||||
|                 GLES20.GL_NEAREST); | ||||
|         GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, | ||||
|                 GLES20.GL_LINEAR); | ||||
|         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, | ||||
|                 GLES20.GL_CLAMP_TO_EDGE); | ||||
|         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, | ||||
|                 GLES20.GL_CLAMP_TO_EDGE); | ||||
|         GlUtils.checkError("glTexParameter"); | ||||
| 
 | ||||
|         return texId; | ||||
|     } | ||||
| 
 | ||||
|     public void setFilter(@NonNull Filter filter) { | ||||
|         // TODO see if this is needed. If setFilter is always called from the correct GL thread,
 | ||||
|         // we don't need to wait for a new draw call (which might not even happen).
 | ||||
|         mPendingFilter = filter; | ||||
|     } | ||||
| 
 | ||||
|     public void drawFrame(long timestampUs, int textureId, float[] textureMatrix) { | ||||
|         if (mPendingFilter != null) { | ||||
|             release(); | ||||
|             mFilter = mPendingFilter; | ||||
|             mPendingFilter = null; | ||||
|             createProgram(); | ||||
|         } | ||||
| 
 | ||||
|         GlUtils.checkError("draw start"); | ||||
| 
 | ||||
|         // Select the program and the active texture.
 | ||||
|         GLES20.glUseProgram(mProgramHandle); | ||||
|         GlUtils.checkError("glUseProgram"); | ||||
|         GLES20.glActiveTexture(mTextureUnit); | ||||
|         GLES20.glBindTexture(mTextureTarget, textureId); | ||||
| 
 | ||||
|         // Draw.
 | ||||
|         mFilter.draw(timestampUs, textureMatrix); | ||||
| 
 | ||||
|         // Release.
 | ||||
|         GLES20.glBindTexture(mTextureTarget, 0); | ||||
|         GLES20.glUseProgram(0); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,80 @@ | ||||
| /* | ||||
|  * Copyright 2013 Google Inc. All rights reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.otaliastudios.cameraview.internal.egl; | ||||
| 
 | ||||
| import android.graphics.SurfaceTexture; | ||||
| import android.os.Build; | ||||
| import androidx.annotation.RequiresApi; | ||||
| import android.view.Surface; | ||||
| 
 | ||||
| /** | ||||
|  * Recordable EGL window surface. | ||||
|  * <p> | ||||
|  * It's good practice to explicitly release() the surface, preferably from a "finally" block. | ||||
|  */ | ||||
| @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) | ||||
| public class EglWindowSurface extends EglBaseSurface { | ||||
|     private Surface mSurface; | ||||
|     private boolean mReleaseSurface; | ||||
| 
 | ||||
|     /** | ||||
|      * Associates an EGL surface with the native window surface. | ||||
|      * <p> | ||||
|      * Set releaseSurface to true if you want the Surface to be released when release() is | ||||
|      * called.  This is convenient, but can interfere with framework classes that expect to | ||||
|      * manage the Surface themselves (e.g. if you release a SurfaceView's Surface, the | ||||
|      * surfaceDestroyed() callback won't fire). | ||||
|      */ | ||||
|     public EglWindowSurface(EglCore eglCore, Surface surface, boolean releaseSurface) { | ||||
|         super(eglCore); | ||||
|         createWindowSurface(surface); | ||||
|         mSurface = surface; | ||||
|         mReleaseSurface = releaseSurface; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Associates an EGL surface with the SurfaceTexture. | ||||
|      */ | ||||
|     public EglWindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) { | ||||
|         super(eglCore); | ||||
|         createWindowSurface(surfaceTexture); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Associates an EGL surface with the Surface. | ||||
|      */ | ||||
|     public EglWindowSurface(EglCore eglCore, Surface surface) { | ||||
|         super(eglCore); | ||||
|         createWindowSurface(surface); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Releases any resources associated with the EGL surface (and, if configured to do so, | ||||
|      * with the Surface as well). | ||||
|      * <p> | ||||
|      * Does not require that the surface's EGL context be current. | ||||
|      */ | ||||
|     public void release() { | ||||
|         releaseEglSurface(); | ||||
|         if (mSurface != null) { | ||||
|             if (mReleaseSurface) { | ||||
|                 mSurface.release(); | ||||
|             } | ||||
|             mSurface = null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.media.CamcorderProfile; | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import android.graphics.Rect; | ||||
| 
 | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import androidx.exifinterface.media.ExifInterface; | ||||
| 
 | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| 
 | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| package com.otaliastudios.cameraview.internal.utils; | ||||
| 
 | ||||
| import android.os.Handler; | ||||
| import android.os.HandlerThread; | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue