diff options
57 files changed, 2185 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24bddc8 --- /dev/null +++ b/.gitignore | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | # Built application files | ||
| 2 | app/release | ||
| 3 | *.apk | ||
| 4 | *.ap_ | ||
| 5 | |||
| 6 | # Files for the ART/Dalvik VM | ||
| 7 | *.dex | ||
| 8 | |||
| 9 | # Java class files | ||
| 10 | *.class | ||
| 11 | |||
| 12 | # Generated files | ||
| 13 | bin/ | ||
| 14 | gen/ | ||
| 15 | out/ | ||
| 16 | |||
| 17 | # Gradle files | ||
| 18 | .gradle/ | ||
| 19 | build/ | ||
| 20 | |||
| 21 | # Local configuration file (sdk path, etc) | ||
| 22 | local.properties | ||
| 23 | |||
| 24 | # Proguard folder generated by Eclipse | ||
| 25 | proguard/ | ||
| 26 | |||
| 27 | # Log Files | ||
| 28 | *.log | ||
| 29 | |||
| 30 | # Android Studio Navigation editor temp files | ||
| 31 | .navigation/ | ||
| 32 | |||
| 33 | # Android Studio captures folder | ||
| 34 | captures/ | ||
| 35 | |||
| 36 | # IntelliJ | ||
| 37 | *.iml | ||
| 38 | .idea/* | ||
| 39 | !.idea/runConfigurations | ||
| 40 | |||
| 41 | # Keystore files | ||
| 42 | *.jks | ||
| 43 | |||
| 44 | # External native build folder generated in Android Studio 2.2 and later | ||
| 45 | .externalNativeBuild | ||
| 46 | |||
| 47 | # Google Services (e.g. APIs or Firebase) | ||
| 48 | google-services.json | ||
| 49 | |||
| 50 | # Freeline | ||
| 51 | freeline.py | ||
| 52 | freeline/ | ||
| 53 | freeline_project_description.json | ||
| 54 | |||
| 55 | # fastlane | ||
| 56 | fastlane/report.xml | ||
| 57 | fastlane/Preview.html | ||
| 58 | fastlane/screenshots | ||
| 59 | fastlane/test_output | ||
| 60 | fastlane/readme.md | ||
diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e90f33 --- /dev/null +++ b/README.md | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | # CCCB Display Camera Control Tool | ||
| 2 | |||
| 3 | |||
| 4 | Main logic is in | ||
| 5 | |||
| 6 | `CCCB_Camera_Andoroid/tflite/src/main/java/com/example/android/camerax/tflite` | ||
| 7 | |||
| 8 | the rest is boilerplate from | ||
| 9 | |||
| 10 | https://github.com/android/camera-samples | ||
diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..30ce363 --- /dev/null +++ b/build.gradle | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 Google LLC | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | buildscript { | ||
| 18 | |||
| 19 | ext { | ||
| 20 | // Top-level variables used for versioning | ||
| 21 | ext.kotlin_version = '1.5.21' | ||
| 22 | ext.java_version = JavaVersion.VERSION_1_8 | ||
| 23 | } | ||
| 24 | |||
| 25 | repositories { | ||
| 26 | google() | ||
| 27 | mavenCentral() | ||
| 28 | } | ||
| 29 | |||
| 30 | dependencies { | ||
| 31 | classpath 'com.android.tools.build:gradle:7.4.2' | ||
| 32 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||
| 33 | classpath 'com.diffplug.spotless:spotless-plugin-gradle:5.11.1' | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | allprojects { | ||
| 38 | repositories { | ||
| 39 | google() | ||
| 40 | mavenCentral() | ||
| 41 | maven { // repo for TFLite snapshot | ||
| 42 | name 'ossrh-snapshot' | ||
| 43 | url 'https://s01.oss.sonatype.org/content/repositories/snapshots' | ||
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | subprojects { | ||
| 49 | apply plugin: 'com.diffplug.spotless' | ||
| 50 | spotless { | ||
| 51 | java { | ||
| 52 | target "**/*.java" | ||
| 53 | trimTrailingWhitespace() | ||
| 54 | removeUnusedImports() | ||
| 55 | googleJavaFormat() | ||
| 56 | endWithNewline() | ||
| 57 | } | ||
| 58 | kotlin { | ||
| 59 | target "**/*.kt" | ||
| 60 | trimTrailingWhitespace() | ||
| 61 | endWithNewline() | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..23339e0 --- /dev/null +++ b/gradle.properties | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | # Project-wide Gradle settings. | ||
| 2 | # IDE (e.g. Android Studio) users: | ||
| 3 | # Gradle settings configured through the IDE *will override* | ||
| 4 | # any settings specified in this file. | ||
| 5 | # For more details on how to configure your build environment visit | ||
| 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html | ||
| 7 | # Specifies the JVM arguments used for the daemon process. | ||
| 8 | # The setting is particularly useful for tweaking memory settings. | ||
| 9 | org.gradle.jvmargs=-Xmx1536m | ||
| 10 | # When configured, Gradle will run in incubating parallel mode. | ||
| 11 | # This option should only be used with decoupled projects. More details, visit | ||
| 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||
| 13 | # org.gradle.parallel=true | ||
| 14 | # AndroidX package structure to make it clearer which packages are bundled with the | ||
| 15 | # Android operating system, and which are packaged with your app's APK | ||
| 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn | ||
| 17 | android.useAndroidX=true | ||
| 18 | # Automatically convert third-party libraries to use AndroidX | ||
| 19 | android.enableJetifier=true | ||
| 20 | # Kotlin code style for this project: "official" or "obsolete": | ||
| 21 | kotlin.code.style=official | ||
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar | |||
| Binary files differ | |||
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..38c6ed2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #Wed Dec 16 12:00:46 WET 2020 | ||
| 2 | distributionBase=GRADLE_USER_HOME | ||
| 3 | distributionPath=wrapper/dists | ||
| 4 | zipStoreBase=GRADLE_USER_HOME | ||
| 5 | zipStorePath=wrapper/dists | ||
| 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip | ||
| @@ -0,0 +1,172 @@ | |||
| 1 | #!/usr/bin/env sh | ||
| 2 | |||
| 3 | ############################################################################## | ||
| 4 | ## | ||
| 5 | ## Gradle start up script for UN*X | ||
| 6 | ## | ||
| 7 | ############################################################################## | ||
| 8 | |||
| 9 | # Attempt to set APP_HOME | ||
| 10 | # Resolve links: $0 may be a link | ||
| 11 | PRG="$0" | ||
| 12 | # Need this for relative symlinks. | ||
| 13 | while [ -h "$PRG" ] ; do | ||
| 14 | ls=`ls -ld "$PRG"` | ||
| 15 | link=`expr "$ls" : '.*-> \(.*\)$'` | ||
| 16 | if expr "$link" : '/.*' > /dev/null; then | ||
| 17 | PRG="$link" | ||
| 18 | else | ||
| 19 | PRG=`dirname "$PRG"`"/$link" | ||
| 20 | fi | ||
| 21 | done | ||
| 22 | SAVED="`pwd`" | ||
| 23 | cd "`dirname \"$PRG\"`/" >/dev/null | ||
| 24 | APP_HOME="`pwd -P`" | ||
| 25 | cd "$SAVED" >/dev/null | ||
| 26 | |||
| 27 | APP_NAME="Gradle" | ||
| 28 | APP_BASE_NAME=`basename "$0"` | ||
| 29 | |||
| 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 31 | DEFAULT_JVM_OPTS="" | ||
| 32 | |||
| 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. | ||
| 34 | MAX_FD="maximum" | ||
| 35 | |||
| 36 | warn () { | ||
| 37 | echo "$*" | ||
| 38 | } | ||
| 39 | |||
| 40 | die () { | ||
| 41 | echo | ||
| 42 | echo "$*" | ||
| 43 | echo | ||
| 44 | exit 1 | ||
| 45 | } | ||
| 46 | |||
| 47 | # OS specific support (must be 'true' or 'false'). | ||
| 48 | cygwin=false | ||
| 49 | msys=false | ||
| 50 | darwin=false | ||
| 51 | nonstop=false | ||
| 52 | case "`uname`" in | ||
| 53 | CYGWIN* ) | ||
| 54 | cygwin=true | ||
| 55 | ;; | ||
| 56 | Darwin* ) | ||
| 57 | darwin=true | ||
| 58 | ;; | ||
| 59 | MINGW* ) | ||
| 60 | msys=true | ||
| 61 | ;; | ||
| 62 | NONSTOP* ) | ||
| 63 | nonstop=true | ||
| 64 | ;; | ||
| 65 | esac | ||
| 66 | |||
| 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||
| 68 | |||
| 69 | # Determine the Java command to use to start the JVM. | ||
| 70 | if [ -n "$JAVA_HOME" ] ; then | ||
| 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||
| 72 | # IBM's JDK on AIX uses strange locations for the executables | ||
| 73 | JAVACMD="$JAVA_HOME/jre/sh/java" | ||
| 74 | else | ||
| 75 | JAVACMD="$JAVA_HOME/bin/java" | ||
| 76 | fi | ||
| 77 | if [ ! -x "$JAVACMD" ] ; then | ||
| 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||
| 79 | |||
| 80 | Please set the JAVA_HOME variable in your environment to match the | ||
| 81 | location of your Java installation." | ||
| 82 | fi | ||
| 83 | else | ||
| 84 | JAVACMD="java" | ||
| 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
| 86 | |||
| 87 | Please set the JAVA_HOME variable in your environment to match the | ||
| 88 | location of your Java installation." | ||
| 89 | fi | ||
| 90 | |||
| 91 | # Increase the maximum file descriptors if we can. | ||
| 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | ||
| 93 | MAX_FD_LIMIT=`ulimit -H -n` | ||
| 94 | if [ $? -eq 0 ] ; then | ||
| 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | ||
| 96 | MAX_FD="$MAX_FD_LIMIT" | ||
| 97 | fi | ||
| 98 | ulimit -n $MAX_FD | ||
| 99 | if [ $? -ne 0 ] ; then | ||
| 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" | ||
| 101 | fi | ||
| 102 | else | ||
| 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | ||
| 104 | fi | ||
| 105 | fi | ||
| 106 | |||
| 107 | # For Darwin, add options to specify how the application appears in the dock | ||
| 108 | if $darwin; then | ||
| 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | ||
| 110 | fi | ||
| 111 | |||
| 112 | # For Cygwin, switch paths to Windows format before running java | ||
| 113 | if $cygwin ; then | ||
| 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` | ||
| 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | ||
| 116 | JAVACMD=`cygpath --unix "$JAVACMD"` | ||
| 117 | |||
| 118 | # We build the pattern for arguments to be converted via cygpath | ||
| 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | ||
| 120 | SEP="" | ||
| 121 | for dir in $ROOTDIRSRAW ; do | ||
| 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" | ||
| 123 | SEP="|" | ||
| 124 | done | ||
| 125 | OURCYGPATTERN="(^($ROOTDIRS))" | ||
| 126 | # Add a user-defined pattern to the cygpath arguments | ||
| 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then | ||
| 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | ||
| 129 | fi | ||
| 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||
| 131 | i=0 | ||
| 132 | for arg in "$@" ; do | ||
| 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | ||
| 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | ||
| 135 | |||
| 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | ||
| 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | ||
| 138 | else | ||
| 139 | eval `echo args$i`="\"$arg\"" | ||
| 140 | fi | ||
| 141 | i=$((i+1)) | ||
| 142 | done | ||
| 143 | case $i in | ||
| 144 | (0) set -- ;; | ||
| 145 | (1) set -- "$args0" ;; | ||
| 146 | (2) set -- "$args0" "$args1" ;; | ||
| 147 | (3) set -- "$args0" "$args1" "$args2" ;; | ||
| 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; | ||
| 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | ||
| 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | ||
| 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | ||
| 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | ||
| 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | ||
| 154 | esac | ||
| 155 | fi | ||
| 156 | |||
| 157 | # Escape application args | ||
| 158 | save () { | ||
| 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | ||
| 160 | echo " " | ||
| 161 | } | ||
| 162 | APP_ARGS=$(save "$@") | ||
| 163 | |||
| 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules | ||
| 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | ||
| 166 | |||
| 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong | ||
| 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then | ||
| 169 | cd "$(dirname "$0")" | ||
| 170 | fi | ||
| 171 | |||
| 172 | exec "$JAVACMD" "$@" | ||
diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | @if "%DEBUG%" == "" @echo off | ||
| 2 | @rem ########################################################################## | ||
| 3 | @rem | ||
| 4 | @rem Gradle startup script for Windows | ||
| 5 | @rem | ||
| 6 | @rem ########################################################################## | ||
| 7 | |||
| 8 | @rem Set local scope for the variables with windows NT shell | ||
| 9 | if "%OS%"=="Windows_NT" setlocal | ||
| 10 | |||
| 11 | set DIRNAME=%~dp0 | ||
| 12 | if "%DIRNAME%" == "" set DIRNAME=. | ||
| 13 | set APP_BASE_NAME=%~n0 | ||
| 14 | set APP_HOME=%DIRNAME% | ||
| 15 | |||
| 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 17 | set DEFAULT_JVM_OPTS= | ||
| 18 | |||
| 19 | @rem Find java.exe | ||
| 20 | if defined JAVA_HOME goto findJavaFromJavaHome | ||
| 21 | |||
| 22 | set JAVA_EXE=java.exe | ||
| 23 | %JAVA_EXE% -version >NUL 2>&1 | ||
| 24 | if "%ERRORLEVEL%" == "0" goto init | ||
| 25 | |||
| 26 | echo. | ||
| 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
| 28 | echo. | ||
| 29 | echo Please set the JAVA_HOME variable in your environment to match the | ||
| 30 | echo location of your Java installation. | ||
| 31 | |||
| 32 | goto fail | ||
| 33 | |||
| 34 | :findJavaFromJavaHome | ||
| 35 | set JAVA_HOME=%JAVA_HOME:"=% | ||
| 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||
| 37 | |||
| 38 | if exist "%JAVA_EXE%" goto init | ||
| 39 | |||
| 40 | echo. | ||
| 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | ||
| 42 | echo. | ||
| 43 | echo Please set the JAVA_HOME variable in your environment to match the | ||
| 44 | echo location of your Java installation. | ||
| 45 | |||
| 46 | goto fail | ||
| 47 | |||
| 48 | :init | ||
| 49 | @rem Get command-line arguments, handling Windows variants | ||
| 50 | |||
| 51 | if not "%OS%" == "Windows_NT" goto win9xME_args | ||
| 52 | |||
| 53 | :win9xME_args | ||
| 54 | @rem Slurp the command line arguments. | ||
| 55 | set CMD_LINE_ARGS= | ||
| 56 | set _SKIP=2 | ||
| 57 | |||
| 58 | :win9xME_args_slurp | ||
| 59 | if "x%~1" == "x" goto execute | ||
| 60 | |||
| 61 | set CMD_LINE_ARGS=%* | ||
| 62 | |||
| 63 | :execute | ||
| 64 | @rem Setup the command line | ||
| 65 | |||
| 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||
| 67 | |||
| 68 | @rem Execute Gradle | ||
| 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | ||
| 70 | |||
| 71 | :end | ||
| 72 | @rem End local scope for the variables with windows NT shell | ||
| 73 | if "%ERRORLEVEL%"=="0" goto mainEnd | ||
| 74 | |||
| 75 | :fail | ||
| 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||
| 77 | rem the _cmd.exe /c_ return code! | ||
| 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | ||
| 79 | exit /b 1 | ||
| 80 | |||
| 81 | :mainEnd | ||
| 82 | if "%OS%"=="Windows_NT" endlocal | ||
| 83 | |||
| 84 | :omega | ||
diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..844db50 --- /dev/null +++ b/settings.gradle | |||
| @@ -0,0 +1 @@ | |||
| include 'tflite' | |||
diff --git a/tflite/.gitignore b/tflite/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/tflite/.gitignore | |||
| @@ -0,0 +1 @@ | |||
| /build | |||
diff --git a/tflite/build.gradle b/tflite/build.gradle new file mode 100644 index 0000000..8e9fcf4 --- /dev/null +++ b/tflite/build.gradle | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 Google LLC | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | apply plugin: "com.android.application" | ||
| 18 | apply plugin: "kotlin-android" | ||
| 19 | |||
| 20 | android { | ||
| 21 | compileSdkVersion 31 | ||
| 22 | ndkVersion "21.3.6528147" | ||
| 23 | |||
| 24 | defaultConfig { | ||
| 25 | applicationId 'com.android.example.camerax.tflite' | ||
| 26 | minSdkVersion 21 | ||
| 27 | targetSdkVersion 30 | ||
| 28 | versionCode 1 | ||
| 29 | versionName "0.0.1" | ||
| 30 | } | ||
| 31 | |||
| 32 | compileOptions { | ||
| 33 | sourceCompatibility rootProject.ext.java_version | ||
| 34 | targetCompatibility rootProject.ext.java_version | ||
| 35 | } | ||
| 36 | |||
| 37 | kotlinOptions { | ||
| 38 | jvmTarget = rootProject.ext.java_version | ||
| 39 | } | ||
| 40 | |||
| 41 | |||
| 42 | buildFeatures { | ||
| 43 | viewBinding true | ||
| 44 | } | ||
| 45 | androidResources { | ||
| 46 | noCompress 'lite' | ||
| 47 | } | ||
| 48 | namespace 'com.android.example.camerax.tflite' | ||
| 49 | } | ||
| 50 | |||
| 51 | dependencies { | ||
| 52 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' | ||
| 53 | |||
| 54 | // App compat and UI things | ||
| 55 | implementation 'androidx.appcompat:appcompat:1.3.1' | ||
| 56 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' | ||
| 57 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' | ||
| 58 | |||
| 59 | // CameraX | ||
| 60 | def camerax_version = "1.1.0-beta01" | ||
| 61 | implementation "androidx.camera:camera-core:${camerax_version}" | ||
| 62 | implementation "androidx.camera:camera-camera2:${camerax_version}" | ||
| 63 | implementation "androidx.camera:camera-lifecycle:${camerax_version}" | ||
| 64 | implementation "androidx.camera:camera-view:${camerax_version}" | ||
| 65 | implementation "androidx.camera:camera-video:${camerax_version}" | ||
| 66 | |||
| 67 | |||
| 68 | // Tensorflow lite dependencies | ||
| 69 | implementation 'org.tensorflow:tensorflow-lite:2.9.0' | ||
| 70 | implementation 'org.tensorflow:tensorflow-lite-gpu:2.9.0' | ||
| 71 | implementation 'org.tensorflow:tensorflow-lite-support:0.4.2' | ||
| 72 | |||
| 73 | testImplementation 'org.robolectric:robolectric:4.4' | ||
| 74 | testImplementation 'junit:junit:4.13.2' | ||
| 75 | } | ||
diff --git a/tflite/src/main/AndroidManifest.xml b/tflite/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d38c505 --- /dev/null +++ b/tflite/src/main/AndroidManifest.xml | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | xmlns:tools="http://schemas.android.com/tools"> | ||
| 19 | |||
| 20 | <!-- Declare features --> | ||
| 21 | <uses-feature android:name="android.hardware.camera" /> | ||
| 22 | |||
| 23 | <!-- Declare permissions --> | ||
| 24 | <uses-permission android:name="android.permission.CAMERA" /> | ||
| 25 | <uses-permission android:name="android.permission.INTERNET" /> | ||
| 26 | |||
| 27 | <application | ||
| 28 | android:allowBackup="true" | ||
| 29 | android:icon="@mipmap/ic_launcher" | ||
| 30 | android:label="@string/app_name" | ||
| 31 | android:roundIcon="@mipmap/ic_launcher_round" | ||
| 32 | android:supportsRtl="true" | ||
| 33 | android:theme="@style/AppTheme" | ||
| 34 | android:screenOrientation="landscape" | ||
| 35 | |||
| 36 | tools:ignore="AllowBackup,GoogleAppIndexingWarning"> | ||
| 37 | |||
| 38 | <activity | ||
| 39 | android:name="com.example.android.camerax.tflite.CameraActivity" | ||
| 40 | android:rotationAnimation="seamless" | ||
| 41 | android:screenOrientation="landscape" | ||
| 42 | tools:targetApi="O"> | ||
| 43 | <intent-filter> | ||
| 44 | <action android:name="android.intent.action.MAIN"/> | ||
| 45 | <category android:name="android.intent.category.LAUNCHER"/> | ||
| 46 | </intent-filter> | ||
| 47 | </activity> | ||
| 48 | |||
| 49 | </application> | ||
| 50 | |||
| 51 | </manifest> | ||
diff --git a/tflite/src/main/ic_launcher-playstore.png b/tflite/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..dd70d80 --- /dev/null +++ b/tflite/src/main/ic_launcher-playstore.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/java/com/example/android/camerax/tflite/CameraActivity.kt b/tflite/src/main/java/com/example/android/camerax/tflite/CameraActivity.kt new file mode 100644 index 0000000..cde9778 --- /dev/null +++ b/tflite/src/main/java/com/example/android/camerax/tflite/CameraActivity.kt | |||
| @@ -0,0 +1,281 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 Google LLC | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package com.example.android.camerax.tflite | ||
| 18 | |||
| 19 | import android.Manifest | ||
| 20 | import android.annotation.SuppressLint | ||
| 21 | import android.content.Context | ||
| 22 | import android.content.pm.PackageManager | ||
| 23 | import android.graphics.Bitmap | ||
| 24 | import android.graphics.Color | ||
| 25 | import android.graphics.Matrix | ||
| 26 | import android.graphics.RectF | ||
| 27 | import android.os.Bundle | ||
| 28 | import android.util.Log | ||
| 29 | import android.util.Size | ||
| 30 | import android.view.View | ||
| 31 | import android.view.ViewGroup | ||
| 32 | import androidx.appcompat.app.AppCompatActivity | ||
| 33 | import androidx.camera.core.AspectRatio | ||
| 34 | import androidx.camera.core.CameraSelector | ||
| 35 | import androidx.camera.core.ImageAnalysis | ||
| 36 | import androidx.camera.core.Preview | ||
| 37 | import androidx.camera.lifecycle.ProcessCameraProvider | ||
| 38 | import androidx.core.app.ActivityCompat | ||
| 39 | import androidx.core.content.ContextCompat | ||
| 40 | import androidx.lifecycle.LifecycleOwner | ||
| 41 | import com.android.example.camerax.tflite.databinding.ActivityCameraBinding | ||
| 42 | import java.net.DatagramPacket | ||
| 43 | import java.net.DatagramSocket | ||
| 44 | import java.net.InetSocketAddress | ||
| 45 | import java.util.concurrent.Executors | ||
| 46 | import java.util.concurrent.TimeUnit | ||
| 47 | import kotlin.random.Random | ||
| 48 | |||
| 49 | |||
| 50 | /** Activity that displays the camera and performs object detection on the incoming frames */ | ||
| 51 | class CameraActivity : AppCompatActivity() { | ||
| 52 | |||
| 53 | private lateinit var activityCameraBinding: ActivityCameraBinding | ||
| 54 | |||
| 55 | private lateinit var bitmapBuffer: Bitmap | ||
| 56 | |||
| 57 | private val executor = Executors.newSingleThreadExecutor() | ||
| 58 | private val permissions = listOf(Manifest.permission.CAMERA) | ||
| 59 | private val permissionsRequestCode = Random.nextInt(0, 10000) | ||
| 60 | |||
| 61 | private var lensFacing: Int = CameraSelector.LENS_FACING_BACK | ||
| 62 | private val isFrontFacing get() = lensFacing == CameraSelector.LENS_FACING_FRONT | ||
| 63 | |||
| 64 | private var pauseAnalysis = false | ||
| 65 | private var imageRotationDegrees: Int = 0 | ||
| 66 | private var socket: DatagramSocket = DatagramSocket() | ||
| 67 | |||
| 68 | override fun onCreate(savedInstanceState: Bundle?) { | ||
| 69 | super.onCreate(savedInstanceState) | ||
| 70 | activityCameraBinding = ActivityCameraBinding.inflate(layoutInflater) | ||
| 71 | setContentView(activityCameraBinding.root) | ||
| 72 | } | ||
| 73 | |||
| 74 | override fun onDestroy() { | ||
| 75 | |||
| 76 | // Terminate all outstanding analyzing jobs (if there is any). | ||
| 77 | executor.apply { | ||
| 78 | shutdown() | ||
| 79 | awaitTermination(1000, TimeUnit.MILLISECONDS) | ||
| 80 | } | ||
| 81 | super.onDestroy() | ||
| 82 | } | ||
| 83 | |||
| 84 | /** Declare and bind preview and analysis use cases */ | ||
| 85 | @SuppressLint("UnsafeExperimentalUsageError") | ||
| 86 | private fun bindCameraUseCases() = activityCameraBinding.viewFinder.post { | ||
| 87 | |||
| 88 | val cameraProviderFuture = ProcessCameraProvider.getInstance(this) | ||
| 89 | cameraProviderFuture.addListener ({ | ||
| 90 | |||
| 91 | // Camera provider is now guaranteed to be available | ||
| 92 | val cameraProvider = cameraProviderFuture.get() | ||
| 93 | |||
| 94 | // Set up the view finder use case to display camera preview | ||
| 95 | val preview = Preview.Builder() | ||
| 96 | .setTargetAspectRatio(AspectRatio.RATIO_4_3) | ||
| 97 | .setTargetRotation(activityCameraBinding.viewFinder.display.rotation) | ||
| 98 | .build() | ||
| 99 | |||
| 100 | // Set up the image analysis use case which will process frames in real time | ||
| 101 | val imageAnalysis = ImageAnalysis.Builder() | ||
| 102 | .setTargetResolution(Size(448, 236)) | ||
| 103 | .setTargetRotation(activityCameraBinding.viewFinder.display.rotation) | ||
| 104 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) | ||
| 105 | .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888) | ||
| 106 | .build() | ||
| 107 | |||
| 108 | var frameCounter = 0 | ||
| 109 | var lastFpsTimestamp = System.currentTimeMillis() | ||
| 110 | |||
| 111 | imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { image -> | ||
| 112 | if (!::bitmapBuffer.isInitialized) { | ||
| 113 | // The image rotation and RGB image buffer are initialized only once | ||
| 114 | // the analyzer has started running | ||
| 115 | imageRotationDegrees = image.imageInfo.rotationDegrees | ||
| 116 | bitmapBuffer = Bitmap.createBitmap( | ||
| 117 | image.width, image.height, Bitmap.Config.ARGB_8888) | ||
| 118 | } | ||
| 119 | |||
| 120 | // Early exit: image analysis is in paused state | ||
| 121 | if (pauseAnalysis) { | ||
| 122 | image.close() | ||
| 123 | return@Analyzer | ||
| 124 | } | ||
| 125 | |||
| 126 | // Copy out RGB bits to our shared buffer | ||
| 127 | image.use { bitmapBuffer.copyPixelsFromBuffer(image.planes[0].buffer) } | ||
| 128 | |||
| 129 | // val CAM_WIDTH = 858 | ||
| 130 | // val CAM_HEIGHT = 480 | ||
| 131 | val CAM_WIDTH = 480 | ||
| 132 | val CAM_HEIGHT = 360 | ||
| 133 | |||
| 134 | val DISPLAY_WIDTH = 448 | ||
| 135 | val DISPLAY_HEIGHT = 160 | ||
| 136 | val DISPLAY_VHEIGHT = 236 | ||
| 137 | |||
| 138 | val scaledBitmap = Bitmap.createScaledBitmap(bitmapBuffer, DISPLAY_WIDTH, DISPLAY_VHEIGHT, true) | ||
| 139 | |||
| 140 | val scratch = IntArray((2 + DISPLAY_VHEIGHT) * DISPLAY_WIDTH) | ||
| 141 | val output = UByteArray(10 + DISPLAY_HEIGHT * DISPLAY_WIDTH / 8) | ||
| 142 | output[1] = 0x12u | ||
| 143 | output[4] = 0x23u | ||
| 144 | var offset = 10 | ||
| 145 | /* | ||
| 146 | for ( row in 0..DISPLAY_VHEIGHT - 1) { | ||
| 147 | val zeile = (row * bitmapBuffer.height ) / DISPLAY_VHEIGHT | ||
| 148 | for (column in 0..DISPLAY_WIDTH - 1) { | ||
| 149 | val spalte = (column * bitmapBuffer.width ) / DISPLAY_WIDTH | ||
| 150 | val pixel = bitmapBuffer.getPixel(spalte, zeile) | ||
| 151 | scratch[row * DISPLAY_WIDTH + column] = Color.red(pixel) * 19535 + Color.green(pixel) * 38470 + Color.blue(pixel) * 7448 | ||
| 152 | } | ||
| 153 | } | ||
| 154 | */ | ||
| 155 | scaledBitmap.getPixels(scratch, 0, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, DISPLAY_VHEIGHT) | ||
| 156 | for (off in 0..DISPLAY_VHEIGHT * DISPLAY_WIDTH) | ||
| 157 | scratch[off] = Color.red(scratch[off]) * 19535 + Color.green(scratch[off]) * 38470 + Color.blue(scratch[off]) * 7448 | ||
| 158 | |||
| 159 | var acc = 0 | ||
| 160 | var accv = 0 | ||
| 161 | |||
| 162 | for ( row in 0.. DISPLAY_VHEIGHT - 1) | ||
| 163 | for ( column in 0..DISPLAY_WIDTH - 1) { | ||
| 164 | val pixel = scratch[row * DISPLAY_WIDTH + column] | ||
| 165 | val bwpixel = if (pixel < 0x810000) 0 else 0xFFFFFF | ||
| 166 | |||
| 167 | if (row % 12 < 8) { | ||
| 168 | acc = (acc shl 1) + (bwpixel shr 23) | ||
| 169 | if (++accv == 8) { | ||
| 170 | output[offset++] = acc.toUByte() | ||
| 171 | acc = 0 | ||
| 172 | accv = 0 | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | val err = (pixel - bwpixel) / 42 | ||
| 177 | fun AddSatShift(xoff : Int, yoff: Int, shift : Int) { | ||
| 178 | val pixelold = scratch[(row + yoff) * DISPLAY_WIDTH + column + xoff ] | ||
| 179 | var r = pixelold + (err shl (16 - shift)) | ||
| 180 | if ( r < 0 ) r = 0 | ||
| 181 | if ( r > 0xFFFFFF) r = 0xFFFFFF | ||
| 182 | scratch[(row + yoff) * DISPLAY_WIDTH + column + xoff ] = r | ||
| 183 | } | ||
| 184 | |||
| 185 | AddSatShift(0, 1, 13) | ||
| 186 | AddSatShift(0, 2, 14) | ||
| 187 | if (column > 0) { | ||
| 188 | AddSatShift(-1, 1, 14) | ||
| 189 | AddSatShift(-1, 2, 15) | ||
| 190 | } | ||
| 191 | |||
| 192 | if (column > 1) { | ||
| 193 | AddSatShift(-2, 1, 15) | ||
| 194 | AddSatShift(-2, 2, 16) | ||
| 195 | } | ||
| 196 | |||
| 197 | if (column < DISPLAY_WIDTH - 1) { | ||
| 198 | AddSatShift( 1, 0, 13) | ||
| 199 | AddSatShift( 1, 1, 14) | ||
| 200 | AddSatShift( 1, 2, 15) | ||
| 201 | } | ||
| 202 | |||
| 203 | if (column < DISPLAY_WIDTH - 2) { | ||
| 204 | AddSatShift( 2, 0, 14) | ||
| 205 | AddSatShift( 2, 1, 15) | ||
| 206 | AddSatShift( 2, 2, 16) | ||
| 207 | } | ||
| 208 | } | ||
| 209 | val address = InetSocketAddress("172.23.42.29", 2342 ) | ||
| 210 | // val address = InetSocketAddress("192.168.178.69", 2342 ) | ||
| 211 | try { | ||
| 212 | socket.send(DatagramPacket(output.toByteArray(), offset, address)) | ||
| 213 | } catch (e: Exception) { | ||
| 214 | // Ignore network exceptions | ||
| 215 | } | ||
| 216 | |||
| 217 | // Compute the FPS of the entire pipeline | ||
| 218 | val frameCount = 10 | ||
| 219 | if (++frameCounter % frameCount == 0) { | ||
| 220 | frameCounter = 0 | ||
| 221 | val now = System.currentTimeMillis() | ||
| 222 | val delta = now - lastFpsTimestamp | ||
| 223 | val fps = 1000 * frameCount.toFloat() / delta | ||
| 224 | // Log.d(TAG, "FPS: ${"%.02f".format(fps)} " + bitmapBuffer.width + " x " + bitmapBuffer.height) | ||
| 225 | |||
| 226 | activityCameraBinding.viewFinder.post { | ||
| 227 | activityCameraBinding.textPrediction.text = "FPS: ${"%.02f".format(fps)}" | ||
| 228 | } | ||
| 229 | |||
| 230 | lastFpsTimestamp = now | ||
| 231 | } | ||
| 232 | }) | ||
| 233 | |||
| 234 | // Create a new camera selector each time, enforcing lens facing | ||
| 235 | val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build() | ||
| 236 | |||
| 237 | // Apply declared configs to CameraX using the same lifecycle owner | ||
| 238 | cameraProvider.unbindAll() | ||
| 239 | cameraProvider.bindToLifecycle( | ||
| 240 | this as LifecycleOwner, cameraSelector, preview, imageAnalysis) | ||
| 241 | |||
| 242 | // Use the camera object to link our preview use case with the view | ||
| 243 | preview.setSurfaceProvider(activityCameraBinding.viewFinder.surfaceProvider) | ||
| 244 | |||
| 245 | }, ContextCompat.getMainExecutor(this)) | ||
| 246 | } | ||
| 247 | |||
| 248 | override fun onResume() { | ||
| 249 | super.onResume() | ||
| 250 | |||
| 251 | // Request permissions each time the app resumes, since they can be revoked at any time | ||
| 252 | if (!hasPermissions(this)) { | ||
| 253 | ActivityCompat.requestPermissions( | ||
| 254 | this, permissions.toTypedArray(), permissionsRequestCode) | ||
| 255 | } else { | ||
| 256 | bindCameraUseCases() | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | override fun onRequestPermissionsResult( | ||
| 261 | requestCode: Int, | ||
| 262 | permissions: Array<out String>, | ||
| 263 | grantResults: IntArray | ||
| 264 | ) { | ||
| 265 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) | ||
| 266 | if (requestCode == permissionsRequestCode && hasPermissions(this)) { | ||
| 267 | bindCameraUseCases() | ||
| 268 | } else { | ||
| 269 | finish() // If we don't have the required permissions, we can't run | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | /** Convenience method used to check if all permissions required by this app are granted */ | ||
| 274 | private fun hasPermissions(context: Context) = permissions.all { | ||
| 275 | ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED | ||
| 276 | } | ||
| 277 | |||
| 278 | companion object { | ||
| 279 | private val TAG = CameraActivity::class.java.simpleName | ||
| 280 | } | ||
| 281 | } | ||
diff --git a/tflite/src/main/res/drawable/ic_launcher_background.xml b/tflite/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..268681b --- /dev/null +++ b/tflite/src/main/res/drawable/ic_launcher_background.xml | |||
| @@ -0,0 +1,89 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <vector | ||
| 18 | xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 19 | android:height="108dp" | ||
| 20 | android:width="108dp" | ||
| 21 | android:viewportHeight="108" | ||
| 22 | android:viewportWidth="108"> | ||
| 23 | <path android:fillColor="#008577" | ||
| 24 | android:pathData="M0,0h108v108h-108z"/> | ||
| 25 | <path android:fillColor="#00000000" android:pathData="M9,0L9,108" | ||
| 26 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 27 | <path android:fillColor="#00000000" android:pathData="M19,0L19,108" | ||
| 28 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 29 | <path android:fillColor="#00000000" android:pathData="M29,0L29,108" | ||
| 30 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 31 | <path android:fillColor="#00000000" android:pathData="M39,0L39,108" | ||
| 32 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 33 | <path android:fillColor="#00000000" android:pathData="M49,0L49,108" | ||
| 34 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 35 | <path android:fillColor="#00000000" android:pathData="M59,0L59,108" | ||
| 36 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 37 | <path android:fillColor="#00000000" android:pathData="M69,0L69,108" | ||
| 38 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 39 | <path android:fillColor="#00000000" android:pathData="M79,0L79,108" | ||
| 40 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 41 | <path android:fillColor="#00000000" android:pathData="M89,0L89,108" | ||
| 42 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 43 | <path android:fillColor="#00000000" android:pathData="M99,0L99,108" | ||
| 44 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 45 | <path android:fillColor="#00000000" android:pathData="M0,9L108,9" | ||
| 46 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 47 | <path android:fillColor="#00000000" android:pathData="M0,19L108,19" | ||
| 48 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 49 | <path android:fillColor="#00000000" android:pathData="M0,29L108,29" | ||
| 50 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 51 | <path android:fillColor="#00000000" android:pathData="M0,39L108,39" | ||
| 52 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 53 | <path android:fillColor="#00000000" android:pathData="M0,49L108,49" | ||
| 54 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 55 | <path android:fillColor="#00000000" android:pathData="M0,59L108,59" | ||
| 56 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 57 | <path android:fillColor="#00000000" android:pathData="M0,69L108,69" | ||
| 58 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 59 | <path android:fillColor="#00000000" android:pathData="M0,79L108,79" | ||
| 60 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 61 | <path android:fillColor="#00000000" android:pathData="M0,89L108,89" | ||
| 62 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 63 | <path android:fillColor="#00000000" android:pathData="M0,99L108,99" | ||
| 64 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 65 | <path android:fillColor="#00000000" android:pathData="M19,29L89,29" | ||
| 66 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 67 | <path android:fillColor="#00000000" android:pathData="M19,39L89,39" | ||
| 68 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 69 | <path android:fillColor="#00000000" android:pathData="M19,49L89,49" | ||
| 70 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 71 | <path android:fillColor="#00000000" android:pathData="M19,59L89,59" | ||
| 72 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 73 | <path android:fillColor="#00000000" android:pathData="M19,69L89,69" | ||
| 74 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 75 | <path android:fillColor="#00000000" android:pathData="M19,79L89,79" | ||
| 76 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 77 | <path android:fillColor="#00000000" android:pathData="M29,19L29,89" | ||
| 78 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 79 | <path android:fillColor="#00000000" android:pathData="M39,19L39,89" | ||
| 80 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 81 | <path android:fillColor="#00000000" android:pathData="M49,19L49,89" | ||
| 82 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 83 | <path android:fillColor="#00000000" android:pathData="M59,19L59,89" | ||
| 84 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 85 | <path android:fillColor="#00000000" android:pathData="M69,19L69,89" | ||
| 86 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 87 | <path android:fillColor="#00000000" android:pathData="M79,19L79,89" | ||
| 88 | android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> | ||
| 89 | </vector> | ||
diff --git a/tflite/src/main/res/drawable/ic_shutter.xml b/tflite/src/main/res/drawable/ic_shutter.xml new file mode 100644 index 0000000..ab352ba --- /dev/null +++ b/tflite/src/main/res/drawable/ic_shutter.xml | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2019 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 18 | <item android:state_pressed="true" android:drawable="@drawable/ic_shutter_pressed" /> | ||
| 19 | <item android:state_focused="true" android:drawable="@drawable/ic_shutter_focused" /> | ||
| 20 | <item android:drawable="@drawable/ic_shutter_normal" /> | ||
| 21 | </selector> \ No newline at end of file | ||
diff --git a/tflite/src/main/res/drawable/ic_shutter_focused.xml b/tflite/src/main/res/drawable/ic_shutter_focused.xml new file mode 100644 index 0000000..fa852ac --- /dev/null +++ b/tflite/src/main/res/drawable/ic_shutter_focused.xml | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2019 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | android:width="24dp" | ||
| 19 | android:height="24dp" | ||
| 20 | android:viewportWidth="74" | ||
| 21 | android:viewportHeight="74"> | ||
| 22 | <path android:fillColor="#FFFFFF" android:fillType="evenOdd" | ||
| 23 | android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37" | ||
| 24 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 25 | <path android:fillColor="#58A0C4" android:fillType="evenOdd" | ||
| 26 | android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37" | ||
| 27 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 28 | </vector> | ||
diff --git a/tflite/src/main/res/drawable/ic_shutter_normal.xml b/tflite/src/main/res/drawable/ic_shutter_normal.xml new file mode 100644 index 0000000..25a10e1 --- /dev/null +++ b/tflite/src/main/res/drawable/ic_shutter_normal.xml | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2019 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | android:width="24dp" | ||
| 19 | android:height="24dp" | ||
| 20 | android:viewportWidth="74" | ||
| 21 | android:viewportHeight="74"> | ||
| 22 | <path android:fillColor="#FFFFFF" android:fillType="evenOdd" | ||
| 23 | android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37" | ||
| 24 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 25 | <path android:fillColor="#CFD7DB" android:fillType="evenOdd" | ||
| 26 | android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37" | ||
| 27 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 28 | </vector> | ||
diff --git a/tflite/src/main/res/drawable/ic_shutter_pressed.xml b/tflite/src/main/res/drawable/ic_shutter_pressed.xml new file mode 100644 index 0000000..fa852ac --- /dev/null +++ b/tflite/src/main/res/drawable/ic_shutter_pressed.xml | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2019 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | android:width="24dp" | ||
| 19 | android:height="24dp" | ||
| 20 | android:viewportWidth="74" | ||
| 21 | android:viewportHeight="74"> | ||
| 22 | <path android:fillColor="#FFFFFF" android:fillType="evenOdd" | ||
| 23 | android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37" | ||
| 24 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 25 | <path android:fillColor="#58A0C4" android:fillType="evenOdd" | ||
| 26 | android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37" | ||
| 27 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 28 | </vector> | ||
diff --git a/tflite/src/main/res/drawable/shape_rectangle.xml b/tflite/src/main/res/drawable/shape_rectangle.xml new file mode 100644 index 0000000..5365e4c --- /dev/null +++ b/tflite/src/main/res/drawable/shape_rectangle.xml | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <shape xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | android:shape="rectangle"> | ||
| 19 | <corners | ||
| 20 | android:radius="4dp" | ||
| 21 | android:topRightRadius="0dp" | ||
| 22 | android:bottomRightRadius="0dp" | ||
| 23 | android:bottomLeftRadius="0dp" /> | ||
| 24 | <stroke | ||
| 25 | android:width="4dp" | ||
| 26 | android:color="@android:color/white" /> | ||
| 27 | <solid android:color="@android:color/transparent"/> | ||
| 28 | </shape> \ No newline at end of file | ||
diff --git a/tflite/src/main/res/layout-land/activity_camera.xml b/tflite/src/main/res/layout-land/activity_camera.xml new file mode 100644 index 0000000..1cc66f1 --- /dev/null +++ b/tflite/src/main/res/layout-land/activity_camera.xml | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <androidx.constraintlayout.widget.ConstraintLayout | ||
| 18 | xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 19 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 20 | xmlns:tools="http://schemas.android.com/tools" | ||
| 21 | android:id="@+id/camera_container" | ||
| 22 | android:background="@android:color/black" | ||
| 23 | android:layout_width="match_parent" | ||
| 24 | android:layout_height="match_parent"> | ||
| 25 | |||
| 26 | <androidx.camera.view.PreviewView | ||
| 27 | android:id="@+id/view_finder" | ||
| 28 | android:layout_width="match_parent" | ||
| 29 | android:layout_height="match_parent"/> | ||
| 30 | |||
| 31 | <ImageView | ||
| 32 | android:id="@+id/image_predicted" | ||
| 33 | android:layout_width="match_parent" | ||
| 34 | android:layout_height="match_parent" | ||
| 35 | android:scaleType="centerCrop" | ||
| 36 | android:visibility="gone" /> | ||
| 37 | |||
| 38 | <TextView | ||
| 39 | android:id="@+id/text_prediction" | ||
| 40 | android:layout_width="wrap_content" | ||
| 41 | android:layout_height="wrap_content" | ||
| 42 | android:layout_marginTop="@dimen/margin_xsmall" | ||
| 43 | app:layout_constraintTop_toTopOf="parent" | ||
| 44 | app:layout_constraintStart_toStartOf="parent" | ||
| 45 | app:layout_constraintEnd_toEndOf="parent" | ||
| 46 | android:textAllCaps="true" | ||
| 47 | android:textAppearance="@style/TextAppearance.AppCompat.Display1" | ||
| 48 | android:text="@string/unknown" /> | ||
| 49 | |||
| 50 | <View | ||
| 51 | android:id="@+id/box_prediction" | ||
| 52 | android:layout_width="0dp" | ||
| 53 | android:layout_height="0dp" | ||
| 54 | android:background="@drawable/shape_rectangle" | ||
| 55 | app:layout_constraintTop_toTopOf="parent" | ||
| 56 | app:layout_constraintStart_toStartOf="parent" /> | ||
| 57 | |||
| 58 | <!-- Camera control buttons --> | ||
| 59 | |||
| 60 | <ImageButton | ||
| 61 | android:id="@+id/camera_capture_button" | ||
| 62 | android:layout_width="@dimen/round_button_large" | ||
| 63 | android:layout_height="@dimen/round_button_large" | ||
| 64 | android:layout_marginEnd="@dimen/shutter_button_margin" | ||
| 65 | android:scaleType="fitCenter" | ||
| 66 | android:background="@drawable/ic_shutter" | ||
| 67 | app:layout_constraintEnd_toEndOf="parent" | ||
| 68 | app:layout_constraintTop_toTopOf="parent" | ||
| 69 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 70 | android:contentDescription="@string/capture_button_alt" /> | ||
| 71 | |||
| 72 | </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file | ||
diff --git a/tflite/src/main/res/layout/activity_camera.xml b/tflite/src/main/res/layout/activity_camera.xml new file mode 100644 index 0000000..c094f94 --- /dev/null +++ b/tflite/src/main/res/layout/activity_camera.xml | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <androidx.constraintlayout.widget.ConstraintLayout | ||
| 18 | xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 19 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 20 | xmlns:tools="http://schemas.android.com/tools" | ||
| 21 | android:id="@+id/camera_container" | ||
| 22 | android:background="@android:color/black" | ||
| 23 | android:layout_width="match_parent" | ||
| 24 | android:layout_height="match_parent"> | ||
| 25 | |||
| 26 | <androidx.camera.view.PreviewView | ||
| 27 | android:id="@+id/view_finder" | ||
| 28 | android:layout_width="match_parent" | ||
| 29 | android:layout_height="match_parent"/> | ||
| 30 | |||
| 31 | <ImageView | ||
| 32 | android:id="@+id/image_predicted" | ||
| 33 | android:layout_width="match_parent" | ||
| 34 | android:layout_height="match_parent" | ||
| 35 | android:scaleType="centerCrop" | ||
| 36 | android:visibility="gone" /> | ||
| 37 | |||
| 38 | <TextView | ||
| 39 | android:id="@+id/text_prediction" | ||
| 40 | android:layout_width="wrap_content" | ||
| 41 | android:layout_height="wrap_content" | ||
| 42 | android:layout_marginTop="@dimen/margin_xsmall" | ||
| 43 | app:layout_constraintTop_toTopOf="parent" | ||
| 44 | app:layout_constraintStart_toStartOf="parent" | ||
| 45 | app:layout_constraintEnd_toEndOf="parent" | ||
| 46 | android:textAllCaps="true" | ||
| 47 | android:textAppearance="@style/TextAppearance.AppCompat.Display1" | ||
| 48 | android:text="@string/unknown" /> | ||
| 49 | |||
| 50 | <View | ||
| 51 | android:id="@+id/box_prediction" | ||
| 52 | android:layout_width="0dp" | ||
| 53 | android:layout_height="0dp" | ||
| 54 | android:background="@drawable/shape_rectangle" | ||
| 55 | app:layout_constraintTop_toTopOf="parent" | ||
| 56 | app:layout_constraintStart_toStartOf="parent" /> | ||
| 57 | |||
| 58 | <!-- Camera control buttons --> | ||
| 59 | |||
| 60 | <ImageButton | ||
| 61 | android:id="@+id/camera_capture_button" | ||
| 62 | android:layout_width="@dimen/round_button_large" | ||
| 63 | android:layout_height="@dimen/round_button_large" | ||
| 64 | android:layout_marginBottom="@dimen/shutter_button_margin" | ||
| 65 | android:scaleType="fitCenter" | ||
| 66 | android:background="@drawable/ic_shutter" | ||
| 67 | app:layout_constraintLeft_toLeftOf="parent" | ||
| 68 | app:layout_constraintRight_toRightOf="parent" | ||
| 69 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 70 | android:contentDescription="@string/capture_button_alt" /> | ||
| 71 | |||
| 72 | </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file | ||
diff --git a/tflite/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/tflite/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..fe90d56 --- /dev/null +++ b/tflite/src/main/res/mipmap-anydpi-v26/ic_launcher.xml | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2019 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 18 | <background android:drawable="@drawable/ic_launcher_background"/> | ||
| 19 | <foreground android:drawable="@mipmap/ic_launcher_foreground"/> | ||
| 20 | </adaptive-icon> \ No newline at end of file | ||
diff --git a/tflite/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/tflite/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..fe90d56 --- /dev/null +++ b/tflite/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2019 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 18 | <background android:drawable="@drawable/ic_launcher_background"/> | ||
| 19 | <foreground android:drawable="@mipmap/ic_launcher_foreground"/> | ||
| 20 | </adaptive-icon> \ No newline at end of file | ||
diff --git a/tflite/src/main/res/mipmap-hdpi/ic_launcher.png b/tflite/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..edd7b36 --- /dev/null +++ b/tflite/src/main/res/mipmap-hdpi/ic_launcher.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/tflite/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a050bf2 --- /dev/null +++ b/tflite/src/main/res/mipmap-hdpi/ic_launcher_foreground.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-hdpi/ic_launcher_round.png b/tflite/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..ec13a43 --- /dev/null +++ b/tflite/src/main/res/mipmap-hdpi/ic_launcher_round.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-mdpi/ic_launcher.png b/tflite/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..6fd4f91 --- /dev/null +++ b/tflite/src/main/res/mipmap-mdpi/ic_launcher.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/tflite/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..df81982 --- /dev/null +++ b/tflite/src/main/res/mipmap-mdpi/ic_launcher_foreground.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-mdpi/ic_launcher_round.png b/tflite/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..2616048 --- /dev/null +++ b/tflite/src/main/res/mipmap-mdpi/ic_launcher_round.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xhdpi/ic_launcher.png b/tflite/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..fdae11d --- /dev/null +++ b/tflite/src/main/res/mipmap-xhdpi/ic_launcher.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/tflite/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..667d566 --- /dev/null +++ b/tflite/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/tflite/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..cb11f72 --- /dev/null +++ b/tflite/src/main/res/mipmap-xhdpi/ic_launcher_round.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tflite/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..5d4fa36 --- /dev/null +++ b/tflite/src/main/res/mipmap-xxhdpi/ic_launcher.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/tflite/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..33dc1c6 --- /dev/null +++ b/tflite/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/tflite/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..07d1393 --- /dev/null +++ b/tflite/src/main/res/mipmap-xxhdpi/ic_launcher_round.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..675e313 --- /dev/null +++ b/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..9f9808a --- /dev/null +++ b/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..54e07e0 --- /dev/null +++ b/tflite/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png | |||
| Binary files differ | |||
diff --git a/tflite/src/main/res/values/dimens.xml b/tflite/src/main/res/values/dimens.xml new file mode 100644 index 0000000..53948b6 --- /dev/null +++ b/tflite/src/main/res/values/dimens.xml | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <resources> | ||
| 18 | <dimen name="margin_xsmall">16dp</dimen> | ||
| 19 | <dimen name="margin_small">32dp</dimen> | ||
| 20 | <dimen name="margin_medium">48dp</dimen> | ||
| 21 | <dimen name="margin_large">64dp</dimen> | ||
| 22 | <dimen name="margin_xlarge">92dp</dimen> | ||
| 23 | |||
| 24 | <dimen name="spacing_small">4dp</dimen> | ||
| 25 | <dimen name="spacing_medium">8dp</dimen> | ||
| 26 | <dimen name="spacing_large">16dp</dimen> | ||
| 27 | |||
| 28 | <dimen name="round_button_small">32dp</dimen> | ||
| 29 | <dimen name="round_button_medium">64dp</dimen> | ||
| 30 | <dimen name="round_button_large">92dp</dimen> | ||
| 31 | |||
| 32 | <dimen name="shutter_button_margin">80dp</dimen> | ||
| 33 | </resources> \ No newline at end of file | ||
diff --git a/tflite/src/main/res/values/strings.xml b/tflite/src/main/res/values/strings.xml new file mode 100644 index 0000000..dbfb4c0 --- /dev/null +++ b/tflite/src/main/res/values/strings.xml | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <resources> | ||
| 18 | <string name="app_name">Camera Object Detector</string> | ||
| 19 | <string name="capture_button_alt">Capture</string> | ||
| 20 | <string name="unknown">UNKNOWN</string> | ||
| 21 | </resources> | ||
diff --git a/tflite/src/main/res/values/styles.xml b/tflite/src/main/res/values/styles.xml new file mode 100644 index 0000000..8bfac62 --- /dev/null +++ b/tflite/src/main/res/values/styles.xml | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 Google LLC | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <resources> | ||
| 18 | |||
| 19 | <!-- Base application theme. --> | ||
| 20 | <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar"> | ||
| 21 | <item name="android:immersive">true</item> | ||
| 22 | <item name="android:windowFullscreen">true</item> | ||
| 23 | <item name="android:windowTranslucentStatus">true</item> | ||
| 24 | <item name="android:windowTranslucentNavigation">true</item> | ||
| 25 | </style> | ||
| 26 | |||
| 27 | </resources> | ||
diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000..38c5875 --- /dev/null +++ b/utils/.gitignore | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | /build | ||
| 2 | .idea \ No newline at end of file | ||
diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 0000000..7a10722 --- /dev/null +++ b/utils/README.md | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | Do not modify code under this folder outside of `CameraUtils`, it is copied | ||
| 2 | automatically by `.github/scripts/copy_utils.sh`. \ No newline at end of file | ||
diff --git a/utils/build.gradle b/utils/build.gradle new file mode 100644 index 0000000..1ef65fa --- /dev/null +++ b/utils/build.gradle | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 The Android Open Source Project | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | apply plugin: 'com.android.library' | ||
| 18 | apply plugin: 'kotlin-android' | ||
| 19 | |||
| 20 | android { | ||
| 21 | compileSdkVersion 29 | ||
| 22 | |||
| 23 | defaultConfig { | ||
| 24 | minSdkVersion 21 | ||
| 25 | targetSdkVersion 29 | ||
| 26 | versionCode 1 | ||
| 27 | versionName "1.0" | ||
| 28 | |||
| 29 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||
| 30 | consumerProguardFiles 'consumer-rules.pro' | ||
| 31 | } | ||
| 32 | |||
| 33 | compileOptions { | ||
| 34 | sourceCompatibility rootProject.ext.java_version | ||
| 35 | targetCompatibility rootProject.ext.java_version | ||
| 36 | } | ||
| 37 | |||
| 38 | kotlinOptions { | ||
| 39 | jvmTarget = "$rootProject.ext.java_version" | ||
| 40 | } | ||
| 41 | |||
| 42 | buildTypes { | ||
| 43 | release { | ||
| 44 | minifyEnabled false | ||
| 45 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | dependencies { | ||
| 51 | |||
| 52 | // Kotlin lang | ||
| 53 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" | ||
| 54 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4' | ||
| 55 | |||
| 56 | // App compat and UI things | ||
| 57 | implementation 'androidx.appcompat:appcompat:1.1.0' | ||
| 58 | implementation 'androidx.recyclerview:recyclerview:1.1.0' | ||
| 59 | |||
| 60 | // EXIF Interface | ||
| 61 | implementation 'androidx.exifinterface:exifinterface:1.2.0' | ||
| 62 | |||
| 63 | // Unit testing | ||
| 64 | testImplementation 'androidx.test.ext:junit:1.1.1' | ||
| 65 | testImplementation 'androidx.test:rules:1.2.0' | ||
| 66 | testImplementation 'androidx.test:runner:1.2.0' | ||
| 67 | testImplementation 'androidx.test.espresso:espresso-core:3.2.0' | ||
| 68 | testImplementation 'org.robolectric:robolectric:4.3.1' | ||
| 69 | |||
| 70 | // Instrumented testing | ||
| 71 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' | ||
| 72 | androidTestImplementation 'androidx.test:rules:1.2.0' | ||
| 73 | androidTestImplementation 'androidx.test:runner:1.2.0' | ||
| 74 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' | ||
| 75 | } | ||
diff --git a/utils/src/main/AndroidManifest.xml b/utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2e13c37 --- /dev/null +++ b/utils/src/main/AndroidManifest.xml | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 The Android Open Source Project | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <manifest package="com.example.android.camera.utils" /> | ||
diff --git a/utils/src/main/java/com/example/android/camera/utils/AutoFitSurfaceView.kt b/utils/src/main/java/com/example/android/camera/utils/AutoFitSurfaceView.kt new file mode 100644 index 0000000..3d900d1 --- /dev/null +++ b/utils/src/main/java/com/example/android/camera/utils/AutoFitSurfaceView.kt | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 The Android Open Source Project | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package com.example.android.camera.utils | ||
| 18 | |||
| 19 | import android.content.Context | ||
| 20 | import android.util.AttributeSet | ||
| 21 | import android.util.Log | ||
| 22 | import android.view.SurfaceView | ||
| 23 | import kotlin.math.roundToInt | ||
| 24 | |||
| 25 | /** | ||
| 26 | * A [SurfaceView] that can be adjusted to a specified aspect ratio and | ||
| 27 | * performs center-crop transformation of input frames. | ||
| 28 | */ | ||
| 29 | class AutoFitSurfaceView @JvmOverloads constructor( | ||
| 30 | context: Context, | ||
| 31 | attrs: AttributeSet? = null, | ||
| 32 | defStyle: Int = 0 | ||
| 33 | ) : SurfaceView(context, attrs, defStyle) { | ||
| 34 | |||
| 35 | private var aspectRatio = 0f | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Sets the aspect ratio for this view. The size of the view will be | ||
| 39 | * measured based on the ratio calculated from the parameters. | ||
| 40 | * | ||
| 41 | * @param width Camera resolution horizontal size | ||
| 42 | * @param height Camera resolution vertical size | ||
| 43 | */ | ||
| 44 | fun setAspectRatio(width: Int, height: Int) { | ||
| 45 | require(width > 0 && height > 0) { "Size cannot be negative" } | ||
| 46 | aspectRatio = width.toFloat() / height.toFloat() | ||
| 47 | holder.setFixedSize(width, height) | ||
| 48 | requestLayout() | ||
| 49 | } | ||
| 50 | |||
| 51 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | ||
| 52 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) | ||
| 53 | val width = MeasureSpec.getSize(widthMeasureSpec) | ||
| 54 | val height = MeasureSpec.getSize(heightMeasureSpec) | ||
| 55 | if (aspectRatio == 0f) { | ||
| 56 | setMeasuredDimension(width, height) | ||
| 57 | } else { | ||
| 58 | |||
| 59 | // Performs center-crop transformation of the camera frames | ||
| 60 | val newWidth: Int | ||
| 61 | val newHeight: Int | ||
| 62 | val actualRatio = if (width > height) aspectRatio else 1f / aspectRatio | ||
| 63 | if (width < height * actualRatio) { | ||
| 64 | newHeight = height | ||
| 65 | newWidth = (height * actualRatio).roundToInt() | ||
| 66 | } else { | ||
| 67 | newWidth = width | ||
| 68 | newHeight = (width / actualRatio).roundToInt() | ||
| 69 | } | ||
| 70 | |||
| 71 | Log.d(TAG, "Measured dimensions set: $newWidth x $newHeight") | ||
| 72 | setMeasuredDimension(newWidth, newHeight) | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | companion object { | ||
| 77 | private val TAG = AutoFitSurfaceView::class.java.simpleName | ||
| 78 | } | ||
| 79 | } | ||
diff --git a/utils/src/main/java/com/example/android/camera/utils/CameraSizes.kt b/utils/src/main/java/com/example/android/camera/utils/CameraSizes.kt new file mode 100644 index 0000000..6db01d3 --- /dev/null +++ b/utils/src/main/java/com/example/android/camera/utils/CameraSizes.kt | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 The Android Open Source Project | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package com.example.android.camera.utils | ||
| 18 | |||
| 19 | import android.graphics.Point | ||
| 20 | import android.hardware.camera2.CameraCharacteristics | ||
| 21 | import android.hardware.camera2.params.StreamConfigurationMap | ||
| 22 | import android.util.Size | ||
| 23 | import android.view.Display | ||
| 24 | import kotlin.math.max | ||
| 25 | import kotlin.math.min | ||
| 26 | |||
| 27 | /** Helper class used to pre-compute shortest and longest sides of a [Size] */ | ||
| 28 | class SmartSize(width: Int, height: Int) { | ||
| 29 | var size = Size(width, height) | ||
| 30 | var long = max(size.width, size.height) | ||
| 31 | var short = min(size.width, size.height) | ||
| 32 | override fun toString() = "SmartSize(${long}x${short})" | ||
| 33 | } | ||
| 34 | |||
| 35 | /** Standard High Definition size for pictures and video */ | ||
| 36 | val SIZE_1080P: SmartSize = SmartSize(1920, 1080) | ||
| 37 | |||
| 38 | /** Returns a [SmartSize] object for the given [Display] */ | ||
| 39 | fun getDisplaySmartSize(display: Display): SmartSize { | ||
| 40 | val outPoint = Point() | ||
| 41 | display.getRealSize(outPoint) | ||
| 42 | return SmartSize(outPoint.x, outPoint.y) | ||
| 43 | } | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Returns the largest available PREVIEW size. For more information, see: | ||
| 47 | * https://d.android.com/reference/android/hardware/camera2/CameraDevice and | ||
| 48 | * https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap | ||
| 49 | */ | ||
| 50 | fun <T>getPreviewOutputSize( | ||
| 51 | display: Display, | ||
| 52 | characteristics: CameraCharacteristics, | ||
| 53 | targetClass: Class<T>, | ||
| 54 | format: Int? = null | ||
| 55 | ): Size { | ||
| 56 | |||
| 57 | // Find which is smaller: screen or 1080p | ||
| 58 | val screenSize = getDisplaySmartSize(display) | ||
| 59 | val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short | ||
| 60 | val maxSize = if (hdScreen) SIZE_1080P else screenSize | ||
| 61 | |||
| 62 | // If image format is provided, use it to determine supported sizes; else use target class | ||
| 63 | val config = characteristics.get( | ||
| 64 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! | ||
| 65 | if (format == null) | ||
| 66 | assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)) | ||
| 67 | else | ||
| 68 | assert(config.isOutputSupportedFor(format)) | ||
| 69 | val allSizes = if (format == null) | ||
| 70 | config.getOutputSizes(targetClass) else config.getOutputSizes(format) | ||
| 71 | |||
| 72 | // Get available sizes and sort them by area from largest to smallest | ||
| 73 | val validSizes = allSizes | ||
| 74 | .sortedWith(compareBy { it.height * it.width }) | ||
| 75 | .map { SmartSize(it.width, it.height) }.reversed() | ||
| 76 | |||
| 77 | // Then, get the largest output size that is smaller or equal than our max size | ||
| 78 | return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size | ||
| 79 | } \ No newline at end of file | ||
diff --git a/utils/src/main/java/com/example/android/camera/utils/ExifUtils.kt b/utils/src/main/java/com/example/android/camera/utils/ExifUtils.kt new file mode 100644 index 0000000..561c14b --- /dev/null +++ b/utils/src/main/java/com/example/android/camera/utils/ExifUtils.kt | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 The Android Open Source Project | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package com.example.android.camera.utils | ||
| 18 | |||
| 19 | import android.graphics.Bitmap | ||
| 20 | import android.graphics.Matrix | ||
| 21 | import android.util.Log | ||
| 22 | import androidx.exifinterface.media.ExifInterface | ||
| 23 | |||
| 24 | private const val TAG: String = "ExifUtils" | ||
| 25 | |||
| 26 | /** Transforms rotation and mirroring information into one of the [ExifInterface] constants */ | ||
| 27 | fun computeExifOrientation(rotationDegrees: Int, mirrored: Boolean) = when { | ||
| 28 | rotationDegrees == 0 && !mirrored -> ExifInterface.ORIENTATION_NORMAL | ||
| 29 | rotationDegrees == 0 && mirrored -> ExifInterface.ORIENTATION_FLIP_HORIZONTAL | ||
| 30 | rotationDegrees == 180 && !mirrored -> ExifInterface.ORIENTATION_ROTATE_180 | ||
| 31 | rotationDegrees == 180 && mirrored -> ExifInterface.ORIENTATION_FLIP_VERTICAL | ||
| 32 | rotationDegrees == 270 && mirrored -> ExifInterface.ORIENTATION_TRANSVERSE | ||
| 33 | rotationDegrees == 90 && !mirrored -> ExifInterface.ORIENTATION_ROTATE_90 | ||
| 34 | rotationDegrees == 90 && mirrored -> ExifInterface.ORIENTATION_TRANSPOSE | ||
| 35 | rotationDegrees == 270 && mirrored -> ExifInterface.ORIENTATION_ROTATE_270 | ||
| 36 | rotationDegrees == 270 && !mirrored -> ExifInterface.ORIENTATION_TRANSVERSE | ||
| 37 | else -> ExifInterface.ORIENTATION_UNDEFINED | ||
| 38 | } | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Helper function used to convert an EXIF orientation enum into a transformation matrix | ||
| 42 | * that can be applied to a bitmap. | ||
| 43 | * | ||
| 44 | * @return matrix - Transformation required to properly display [Bitmap] | ||
| 45 | */ | ||
| 46 | fun decodeExifOrientation(exifOrientation: Int): Matrix { | ||
| 47 | val matrix = Matrix() | ||
| 48 | |||
| 49 | // Apply transformation corresponding to declared EXIF orientation | ||
| 50 | when (exifOrientation) { | ||
| 51 | ExifInterface.ORIENTATION_NORMAL -> Unit | ||
| 52 | ExifInterface.ORIENTATION_UNDEFINED -> Unit | ||
| 53 | ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F) | ||
| 54 | ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F) | ||
| 55 | ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F) | ||
| 56 | ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1F, 1F) | ||
| 57 | ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1F, -1F) | ||
| 58 | ExifInterface.ORIENTATION_TRANSPOSE -> { | ||
| 59 | matrix.postScale(-1F, 1F) | ||
| 60 | matrix.postRotate(270F) | ||
| 61 | } | ||
| 62 | ExifInterface.ORIENTATION_TRANSVERSE -> { | ||
| 63 | matrix.postScale(-1F, 1F) | ||
| 64 | matrix.postRotate(90F) | ||
| 65 | } | ||
| 66 | |||
| 67 | // Error out if the EXIF orientation is invalid | ||
| 68 | else -> Log.e(TAG, "Invalid orientation: $exifOrientation") | ||
| 69 | } | ||
| 70 | |||
| 71 | // Return the resulting matrix | ||
| 72 | return matrix | ||
| 73 | } | ||
diff --git a/utils/src/main/java/com/example/android/camera/utils/GenericListAdapter.kt b/utils/src/main/java/com/example/android/camera/utils/GenericListAdapter.kt new file mode 100644 index 0000000..a55af27 --- /dev/null +++ b/utils/src/main/java/com/example/android/camera/utils/GenericListAdapter.kt | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 The Android Open Source Project | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package com.example.android.camera.utils | ||
| 18 | |||
| 19 | import android.view.LayoutInflater | ||
| 20 | import android.view.View | ||
| 21 | import android.view.ViewGroup | ||
| 22 | import androidx.recyclerview.widget.RecyclerView | ||
| 23 | |||
| 24 | /** Type helper used for the callback triggered once our view has been bound */ | ||
| 25 | typealias BindCallback<T> = (view: View, data: T, position: Int) -> Unit | ||
| 26 | |||
| 27 | /** List adapter for generic types, intended used for small-medium lists of data */ | ||
| 28 | class GenericListAdapter<T>( | ||
| 29 | private val dataset: List<T>, | ||
| 30 | private val itemLayoutId: Int? = null, | ||
| 31 | private val itemViewFactory: (() -> View)? = null, | ||
| 32 | private val onBind: BindCallback<T> | ||
| 33 | ) : RecyclerView.Adapter<GenericListAdapter.GenericListViewHolder>() { | ||
| 34 | |||
| 35 | class GenericListViewHolder(val view: View) : RecyclerView.ViewHolder(view) | ||
| 36 | |||
| 37 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = GenericListViewHolder(when { | ||
| 38 | itemViewFactory != null -> itemViewFactory.invoke() | ||
| 39 | itemLayoutId != null -> { | ||
| 40 | LayoutInflater.from(parent.context) | ||
| 41 | .inflate(itemLayoutId, parent, false) | ||
| 42 | } | ||
| 43 | else -> { | ||
| 44 | throw IllegalStateException( | ||
| 45 | "Either the layout ID or the view factory need to be non-null") | ||
| 46 | } | ||
| 47 | }) | ||
| 48 | |||
| 49 | override fun onBindViewHolder(holder: GenericListViewHolder, position: Int) { | ||
| 50 | if (position < 0 || position > dataset.size) return | ||
| 51 | onBind(holder.view, dataset[position], position) | ||
| 52 | } | ||
| 53 | |||
| 54 | override fun getItemCount() = dataset.size | ||
| 55 | } \ No newline at end of file | ||
diff --git a/utils/src/main/java/com/example/android/camera/utils/OrientationLiveData.kt b/utils/src/main/java/com/example/android/camera/utils/OrientationLiveData.kt new file mode 100644 index 0000000..f9d9a47 --- /dev/null +++ b/utils/src/main/java/com/example/android/camera/utils/OrientationLiveData.kt | |||
| @@ -0,0 +1,95 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 The Android Open Source Project | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package com.example.android.camera.utils | ||
| 18 | |||
| 19 | import android.content.Context | ||
| 20 | import android.hardware.camera2.CameraCharacteristics | ||
| 21 | import android.view.OrientationEventListener | ||
| 22 | import android.view.Surface | ||
| 23 | import androidx.lifecycle.LiveData | ||
| 24 | |||
| 25 | |||
| 26 | /** | ||
| 27 | * Calculates closest 90-degree orientation to compensate for the device | ||
| 28 | * rotation relative to sensor orientation, i.e., allows user to see camera | ||
| 29 | * frames with the expected orientation. | ||
| 30 | */ | ||
| 31 | class OrientationLiveData( | ||
| 32 | context: Context, | ||
| 33 | characteristics: CameraCharacteristics | ||
| 34 | ): LiveData<Int>() { | ||
| 35 | |||
| 36 | private val listener = object : OrientationEventListener(context.applicationContext) { | ||
| 37 | override fun onOrientationChanged(orientation: Int) { | ||
| 38 | val rotation = when { | ||
| 39 | orientation <= 45 -> Surface.ROTATION_0 | ||
| 40 | orientation <= 135 -> Surface.ROTATION_90 | ||
| 41 | orientation <= 225 -> Surface.ROTATION_180 | ||
| 42 | orientation <= 315 -> Surface.ROTATION_270 | ||
| 43 | else -> Surface.ROTATION_0 | ||
| 44 | } | ||
| 45 | val relative = computeRelativeRotation(characteristics, rotation) | ||
| 46 | if (relative != value) postValue(relative) | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | override fun onActive() { | ||
| 51 | super.onActive() | ||
| 52 | listener.enable() | ||
| 53 | } | ||
| 54 | |||
| 55 | override fun onInactive() { | ||
| 56 | super.onInactive() | ||
| 57 | listener.disable() | ||
| 58 | } | ||
| 59 | |||
| 60 | companion object { | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Computes rotation required to transform from the camera sensor orientation to the | ||
| 64 | * device's current orientation in degrees. | ||
| 65 | * | ||
| 66 | * @param characteristics the [CameraCharacteristics] to query for the sensor orientation. | ||
| 67 | * @param surfaceRotation the current device orientation as a Surface constant | ||
| 68 | * @return the relative rotation from the camera sensor to the current device orientation. | ||
| 69 | */ | ||
| 70 | @JvmStatic | ||
| 71 | private fun computeRelativeRotation( | ||
| 72 | characteristics: CameraCharacteristics, | ||
| 73 | surfaceRotation: Int | ||
| 74 | ): Int { | ||
| 75 | val sensorOrientationDegrees = | ||
| 76 | characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! | ||
| 77 | |||
| 78 | val deviceOrientationDegrees = when (surfaceRotation) { | ||
| 79 | Surface.ROTATION_0 -> 0 | ||
| 80 | Surface.ROTATION_90 -> 90 | ||
| 81 | Surface.ROTATION_180 -> 180 | ||
| 82 | Surface.ROTATION_270 -> 270 | ||
| 83 | else -> 0 | ||
| 84 | } | ||
| 85 | |||
| 86 | // Reverse device orientation for front-facing cameras | ||
| 87 | val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) == | ||
| 88 | CameraCharacteristics.LENS_FACING_FRONT) 1 else -1 | ||
| 89 | |||
| 90 | // Calculate desired JPEG orientation relative to camera orientation to make | ||
| 91 | // the image upright relative to the device orientation | ||
| 92 | return (sensorOrientationDegrees - (deviceOrientationDegrees * sign) + 360) % 360 | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
diff --git a/utils/src/main/java/com/example/android/camera/utils/Yuv.kt b/utils/src/main/java/com/example/android/camera/utils/Yuv.kt new file mode 100644 index 0000000..c476ad0 --- /dev/null +++ b/utils/src/main/java/com/example/android/camera/utils/Yuv.kt | |||
| @@ -0,0 +1,191 @@ | |||
| 1 | package com.example.android.camera.utils | ||
| 2 | |||
| 3 | import android.graphics.ImageFormat | ||
| 4 | import android.media.Image | ||
| 5 | import androidx.annotation.IntDef | ||
| 6 | import java.nio.ByteBuffer | ||
| 7 | |||
| 8 | /* | ||
| 9 | This file is converted from part of https://github.com/gordinmitya/yuv2buf. | ||
| 10 | Follow the link to find demo app, performance benchmarks and unit tests. | ||
| 11 | |||
| 12 | Intro to YUV image formats: | ||
| 13 | YUV_420_888 - is a generic format that can be represented as I420, YV12, NV21, and NV12. | ||
| 14 | 420 means that for each 4 luminosity pixels we have 2 chroma pixels: U and V. | ||
| 15 | |||
| 16 | * I420 format represents an image as Y plane followed by U then followed by V plane | ||
| 17 | without chroma channels interleaving. | ||
| 18 | For example: | ||
| 19 | Y Y Y Y | ||
| 20 | Y Y Y Y | ||
| 21 | U U V V | ||
| 22 | |||
| 23 | * NV21 format represents an image as Y plane followed by V and U interleaved. First V then U. | ||
| 24 | For example: | ||
| 25 | Y Y Y Y | ||
| 26 | Y Y Y Y | ||
| 27 | V U V U | ||
| 28 | |||
| 29 | * YV12 and NV12 are the same as previous formats but with swapped order of V and U. (U then V) | ||
| 30 | |||
| 31 | Visualization of these 4 formats: | ||
| 32 | https://user-images.githubusercontent.com/9286092/89119601-4f6f8100-d4b8-11ea-9a51-2765f7e513c2.jpg | ||
| 33 | |||
| 34 | It's guaranteed that image.getPlanes() always returns planes in order Y U V for YUV_420_888. | ||
| 35 | https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888 | ||
| 36 | |||
| 37 | Because I420 and NV21 are more widely supported (RenderScript, OpenCV, MNN) | ||
| 38 | the conversion is done into these formats. | ||
| 39 | |||
| 40 | More about each format: https://www.fourcc.org/yuv.php | ||
| 41 | */ | ||
| 42 | |||
| 43 | @kotlin.annotation.Retention(AnnotationRetention.SOURCE) | ||
| 44 | @IntDef(ImageFormat.NV21, ImageFormat.YUV_420_888) | ||
| 45 | annotation class YuvType | ||
| 46 | |||
| 47 | class YuvByteBuffer(image: Image, dstBuffer: ByteBuffer? = null) { | ||
| 48 | @YuvType | ||
| 49 | val type: Int | ||
| 50 | val buffer: ByteBuffer | ||
| 51 | |||
| 52 | init { | ||
| 53 | val wrappedImage = ImageWrapper(image) | ||
| 54 | |||
| 55 | type = if (wrappedImage.u.pixelStride == 1) { | ||
| 56 | ImageFormat.YUV_420_888 | ||
| 57 | } else { | ||
| 58 | ImageFormat.NV21 | ||
| 59 | } | ||
| 60 | val size = image.width * image.height * 3 / 2 | ||
| 61 | buffer = if ( | ||
| 62 | dstBuffer == null || dstBuffer.capacity() < size || | ||
| 63 | dstBuffer.isReadOnly || !dstBuffer.isDirect | ||
| 64 | ) { | ||
| 65 | ByteBuffer.allocateDirect(size) } | ||
| 66 | else { | ||
| 67 | dstBuffer | ||
| 68 | } | ||
| 69 | buffer.rewind() | ||
| 70 | |||
| 71 | removePadding(wrappedImage) | ||
| 72 | } | ||
| 73 | |||
| 74 | // Input buffers are always direct as described in | ||
| 75 | // https://developer.android.com/reference/android/media/Image.Plane#getBuffer() | ||
| 76 | private fun removePadding(image: ImageWrapper) { | ||
| 77 | val sizeLuma = image.y.width * image.y.height | ||
| 78 | val sizeChroma = image.u.width * image.u.height | ||
| 79 | if (image.y.rowStride > image.y.width) { | ||
| 80 | removePaddingCompact(image.y, buffer, 0) | ||
| 81 | } else { | ||
| 82 | buffer.position(0) | ||
| 83 | buffer.put(image.y.buffer) | ||
| 84 | } | ||
| 85 | if (type == ImageFormat.YUV_420_888) { | ||
| 86 | if (image.u.rowStride > image.u.width) { | ||
| 87 | removePaddingCompact(image.u, buffer, sizeLuma) | ||
| 88 | removePaddingCompact(image.v, buffer, sizeLuma + sizeChroma) | ||
| 89 | } else { | ||
| 90 | buffer.position(sizeLuma) | ||
| 91 | buffer.put(image.u.buffer) | ||
| 92 | buffer.position(sizeLuma + sizeChroma) | ||
| 93 | buffer.put(image.v.buffer) | ||
| 94 | } | ||
| 95 | } else { | ||
| 96 | if (image.u.rowStride > image.u.width * 2) { | ||
| 97 | removePaddingNotCompact(image, buffer, sizeLuma) | ||
| 98 | } else { | ||
| 99 | buffer.position(sizeLuma) | ||
| 100 | var uv = image.v.buffer | ||
| 101 | val properUVSize = image.v.height * image.v.rowStride - 1 | ||
| 102 | if (uv.capacity() > properUVSize) { | ||
| 103 | uv = clipBuffer(image.v.buffer, 0, properUVSize) | ||
| 104 | } | ||
| 105 | buffer.put(uv) | ||
| 106 | val lastOne = image.u.buffer[image.u.buffer.capacity() - 1] | ||
| 107 | buffer.put(buffer.capacity() - 1, lastOne) | ||
| 108 | } | ||
| 109 | } | ||
| 110 | buffer.rewind() | ||
| 111 | } | ||
| 112 | |||
| 113 | private fun removePaddingCompact( | ||
| 114 | plane: PlaneWrapper, | ||
| 115 | dst: ByteBuffer, | ||
| 116 | offset: Int | ||
| 117 | ) { | ||
| 118 | require(plane.pixelStride == 1) { | ||
| 119 | "use removePaddingCompact with pixelStride == 1" | ||
| 120 | } | ||
| 121 | |||
| 122 | val src = plane.buffer | ||
| 123 | val rowStride = plane.rowStride | ||
| 124 | var row: ByteBuffer | ||
| 125 | dst.position(offset) | ||
| 126 | for (i in 0 until plane.height) { | ||
| 127 | row = clipBuffer(src, i * rowStride, plane.width) | ||
| 128 | dst.put(row) | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | private fun removePaddingNotCompact( | ||
| 133 | image: ImageWrapper, | ||
| 134 | dst: ByteBuffer, | ||
| 135 | offset: Int | ||
| 136 | ) { | ||
| 137 | require(image.u.pixelStride == 2) { | ||
| 138 | "use removePaddingNotCompact pixelStride == 2" | ||
| 139 | } | ||
| 140 | val width = image.u.width | ||
| 141 | val height = image.u.height | ||
| 142 | val rowStride = image.u.rowStride | ||
| 143 | var row: ByteBuffer | ||
| 144 | dst.position(offset) | ||
| 145 | for (i in 0 until height - 1) { | ||
| 146 | row = clipBuffer(image.v.buffer, i * rowStride, width * 2) | ||
| 147 | dst.put(row) | ||
| 148 | } | ||
| 149 | row = clipBuffer(image.u.buffer, (height - 1) * rowStride - 1, width * 2) | ||
| 150 | dst.put(row) | ||
| 151 | } | ||
| 152 | |||
| 153 | private fun clipBuffer(buffer: ByteBuffer, start: Int, size: Int): ByteBuffer { | ||
| 154 | val duplicate = buffer.duplicate() | ||
| 155 | duplicate.position(start) | ||
| 156 | duplicate.limit(start + size) | ||
| 157 | return duplicate.slice() | ||
| 158 | } | ||
| 159 | |||
| 160 | private class ImageWrapper(image:Image) { | ||
| 161 | val width= image.width | ||
| 162 | val height = image.height | ||
| 163 | val y = PlaneWrapper(width, height, image.planes[0]) | ||
| 164 | val u = PlaneWrapper(width / 2, height / 2, image.planes[1]) | ||
| 165 | val v = PlaneWrapper(width / 2, height / 2, image.planes[2]) | ||
| 166 | |||
| 167 | // Check this is a supported image format | ||
| 168 | // https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888 | ||
| 169 | init { | ||
| 170 | require(y.pixelStride == 1) { | ||
| 171 | "Pixel stride for Y plane must be 1 but got ${y.pixelStride} instead." | ||
| 172 | } | ||
| 173 | require(u.pixelStride == v.pixelStride && u.rowStride == v.rowStride) { | ||
| 174 | "U and V planes must have the same pixel and row strides " + | ||
| 175 | "but got pixel=${u.pixelStride} row=${u.rowStride} for U " + | ||
| 176 | "and pixel=${v.pixelStride} and row=${v.rowStride} for V" | ||
| 177 | } | ||
| 178 | require(u.pixelStride == 1 || u.pixelStride == 2) { | ||
| 179 | "Supported" + " pixel strides for U and V planes are 1 and 2" | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | private class PlaneWrapper(width: Int, height: Int, plane: Image.Plane) { | ||
| 185 | val width = width | ||
| 186 | val height = height | ||
| 187 | val buffer: ByteBuffer = plane.buffer | ||
| 188 | val rowStride = plane.rowStride | ||
| 189 | val pixelStride = plane.pixelStride | ||
| 190 | } | ||
| 191 | } \ No newline at end of file | ||
diff --git a/utils/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt b/utils/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt new file mode 100644 index 0000000..8dcd559 --- /dev/null +++ b/utils/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2020 The Android Open Source Project | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package com.example.android.camera.utils | ||
| 18 | |||
| 19 | import android.content.Context | ||
| 20 | import android.graphics.Bitmap | ||
| 21 | import android.graphics.ImageFormat | ||
| 22 | import android.media.Image | ||
| 23 | import android.renderscript.Allocation | ||
| 24 | import android.renderscript.Element | ||
| 25 | import android.renderscript.RenderScript | ||
| 26 | import android.renderscript.ScriptIntrinsicYuvToRGB | ||
| 27 | import android.renderscript.Type | ||
| 28 | import java.nio.ByteBuffer | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Helper class used to convert a [Image] object from | ||
| 32 | * [ImageFormat.YUV_420_888] format to an RGB [Bitmap] object, it has equivalent | ||
| 33 | * functionality to https://github | ||
| 34 | * .com/androidx/androidx/blob/androidx-main/camera/camera-core/src/main/java/androidx/camera/core/ImageYuvToRgbConverter.java | ||
| 35 | * | ||
| 36 | * NOTE: This has been tested in a limited number of devices and is not | ||
| 37 | * considered production-ready code. It was created for illustration purposes, | ||
| 38 | * since this is not an efficient camera pipeline due to the multiple copies | ||
| 39 | * required to convert each frame. For example, this | ||
| 40 | * implementation | ||
| 41 | * (https://stackoverflow.com/questions/52726002/camera2-captured-picture-conversion-from-yuv-420-888-to-nv21/52740776#52740776) | ||
| 42 | * might have better performance. | ||
| 43 | */ | ||
| 44 | class YuvToRgbConverter(context: Context) { | ||
| 45 | private val rs = RenderScript.create(context) | ||
| 46 | private val scriptYuvToRgb = | ||
| 47 | ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)) | ||
| 48 | |||
| 49 | // Do not add getters/setters functions to these private variables | ||
| 50 | // because yuvToRgb() assume they won't be modified elsewhere | ||
| 51 | private var yuvBits: ByteBuffer? = null | ||
| 52 | private var bytes: ByteArray = ByteArray(0) | ||
| 53 | private var inputAllocation: Allocation? = null | ||
| 54 | private var outputAllocation: Allocation? = null | ||
| 55 | |||
| 56 | @Synchronized | ||
| 57 | fun yuvToRgb(image: Image, output: Bitmap) { | ||
| 58 | val yuvBuffer = YuvByteBuffer(image, yuvBits) | ||
| 59 | yuvBits = yuvBuffer.buffer | ||
| 60 | |||
| 61 | if (needCreateAllocations(image, yuvBuffer)) { | ||
| 62 | val yuvType = Type.Builder(rs, Element.U8(rs)) | ||
| 63 | .setX(image.width) | ||
| 64 | .setY(image.height) | ||
| 65 | .setYuvFormat(yuvBuffer.type) | ||
| 66 | inputAllocation = Allocation.createTyped( | ||
| 67 | rs, | ||
| 68 | yuvType.create(), | ||
| 69 | Allocation.USAGE_SCRIPT | ||
| 70 | ) | ||
| 71 | bytes = ByteArray(yuvBuffer.buffer.capacity()) | ||
| 72 | val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)) | ||
| 73 | .setX(image.width) | ||
| 74 | .setY(image.height) | ||
| 75 | outputAllocation = Allocation.createTyped( | ||
| 76 | rs, | ||
| 77 | rgbaType.create(), | ||
| 78 | Allocation.USAGE_SCRIPT | ||
| 79 | ) | ||
| 80 | } | ||
| 81 | |||
| 82 | yuvBuffer.buffer.get(bytes) | ||
| 83 | inputAllocation!!.copyFrom(bytes) | ||
| 84 | |||
| 85 | // Convert NV21 or YUV_420_888 format to RGB | ||
| 86 | inputAllocation!!.copyFrom(bytes) | ||
| 87 | scriptYuvToRgb.setInput(inputAllocation) | ||
| 88 | scriptYuvToRgb.forEach(outputAllocation) | ||
| 89 | outputAllocation!!.copyTo(output) | ||
| 90 | } | ||
| 91 | |||
| 92 | private fun needCreateAllocations(image: Image, yuvBuffer: YuvByteBuffer): Boolean { | ||
| 93 | return (inputAllocation == null || // the very 1st call | ||
| 94 | inputAllocation!!.type.x != image.width || // image size changed | ||
| 95 | inputAllocation!!.type.y != image.height || | ||
| 96 | inputAllocation!!.type.yuv != yuvBuffer.type || // image format changed | ||
| 97 | bytes.size == yuvBuffer.buffer.capacity()) | ||
| 98 | } | ||
| 99 | } | ||
diff --git a/utils/src/main/res/drawable/ic_shutter.xml b/utils/src/main/res/drawable/ic_shutter.xml new file mode 100644 index 0000000..9bb91ab --- /dev/null +++ b/utils/src/main/res/drawable/ic_shutter.xml | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 The Android Open Source Project | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 18 | <item android:state_pressed="true" android:drawable="@drawable/ic_shutter_pressed" /> | ||
| 19 | <item android:state_focused="true" android:drawable="@drawable/ic_shutter_focused" /> | ||
| 20 | <item android:drawable="@drawable/ic_shutter_normal" /> | ||
| 21 | </selector> \ No newline at end of file | ||
diff --git a/utils/src/main/res/drawable/ic_shutter_focused.xml b/utils/src/main/res/drawable/ic_shutter_focused.xml new file mode 100644 index 0000000..9bf521d --- /dev/null +++ b/utils/src/main/res/drawable/ic_shutter_focused.xml | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 The Android Open Source Project | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | android:width="24dp" | ||
| 19 | android:height="24dp" | ||
| 20 | android:viewportWidth="74" | ||
| 21 | android:viewportHeight="74"> | ||
| 22 | <path android:fillColor="#FFFFFF" android:fillType="evenOdd" | ||
| 23 | android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37" | ||
| 24 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 25 | <path android:fillColor="#58A0C4" android:fillType="evenOdd" | ||
| 26 | android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37" | ||
| 27 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 28 | </vector> | ||
diff --git a/utils/src/main/res/drawable/ic_shutter_normal.xml b/utils/src/main/res/drawable/ic_shutter_normal.xml new file mode 100644 index 0000000..cb50026 --- /dev/null +++ b/utils/src/main/res/drawable/ic_shutter_normal.xml | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 The Android Open Source Project | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | android:width="24dp" | ||
| 19 | android:height="24dp" | ||
| 20 | android:viewportWidth="74" | ||
| 21 | android:viewportHeight="74"> | ||
| 22 | <path android:fillColor="#FFFFFF" android:fillType="evenOdd" | ||
| 23 | android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37" | ||
| 24 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 25 | <path android:fillColor="#CFD7DB" android:fillType="evenOdd" | ||
| 26 | android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37" | ||
| 27 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 28 | </vector> | ||
diff --git a/utils/src/main/res/drawable/ic_shutter_pressed.xml b/utils/src/main/res/drawable/ic_shutter_pressed.xml new file mode 100644 index 0000000..9bf521d --- /dev/null +++ b/utils/src/main/res/drawable/ic_shutter_pressed.xml | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- | ||
| 3 | ~ Copyright 2020 The Android Open Source Project | ||
| 4 | ~ | ||
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | ~ you may not use this file except in compliance with the License. | ||
| 7 | ~ You may obtain a copy of the License at | ||
| 8 | ~ | ||
| 9 | ~ https://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | ~ | ||
| 11 | ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | ~ See the License for the specific language governing permissions and | ||
| 15 | ~ limitations under the License. | ||
| 16 | --> | ||
| 17 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 18 | android:width="24dp" | ||
| 19 | android:height="24dp" | ||
| 20 | android:viewportWidth="74" | ||
| 21 | android:viewportHeight="74"> | ||
| 22 | <path android:fillColor="#FFFFFF" android:fillType="evenOdd" | ||
| 23 | android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37" | ||
| 24 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 25 | <path android:fillColor="#58A0C4" android:fillType="evenOdd" | ||
| 26 | android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37" | ||
| 27 | android:strokeColor="#00000000" android:strokeWidth="1"/> | ||
| 28 | </vector> | ||
