├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── gamerole │ │ └── orcamera │ │ └── MainActivity.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── orcameralib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── gamerole │ │ └── orcameralib │ │ ├── Camera1Control.java │ │ ├── Camera2Control.java │ │ ├── CameraActivity.java │ │ ├── CameraView.java │ │ ├── CropView.java │ │ ├── FrameOverlayView.java │ │ ├── ICameraControl.java │ │ ├── MaskView.java │ │ ├── OCRCameraLayout.java │ │ ├── OCRFrameLayout.java │ │ ├── PermissionCallback.java │ │ └── util │ │ ├── DimensionUtil.java │ │ └── ImageUtil.java │ └── res │ ├── anim │ └── image_rotate_once.xml │ ├── drawable │ ├── bd_ocr_cancel.png │ ├── bd_ocr_close.png │ ├── bd_ocr_confirm.png │ ├── bd_ocr_gallery.png │ ├── bd_ocr_hint_align_bank_card.png │ ├── bd_ocr_hint_align_id_card.png │ ├── bd_ocr_hint_align_id_card_back.png │ ├── bd_ocr_id_card_locator_back.png │ ├── bd_ocr_id_card_locator_front.png │ ├── bd_ocr_light_off.png │ ├── bd_ocr_light_on.png │ ├── bd_ocr_rotate.png │ ├── bd_ocr_take_photo_highlight.png │ ├── bd_ocr_take_photo_normal.png │ └── bd_ocr_take_photo_selector.xml │ ├── layout │ ├── bd_ocr_activity_camera.xml │ ├── bd_ocr_confirm_result.xml │ ├── bd_ocr_crop.xml │ └── bd_ocr_take_picture.xml │ └── values │ ├── bd_ocr_dimensions.xml │ ├── bd_ocr_widgets.xml │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ORCamera 2 | #### Android 好用的身份证拍照界面封装 3 |
4 | 5 | 6 |
7 | 8 | 9 | `手持身份证`调用 10 | ```java 11 | Intent intent = new Intent(this, CameraActivity.class); 12 | intent.putExtra(CameraActivity.KEY_OUTPUT_FILE_PATH,filePath); 13 | intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, CameraActivity.CONTENT_TYPE_GENERAL); 14 | startActivityForResult(intent, REQUEST_CODE); 15 | ``` 16 | `身份证正面`调用 17 | ```java 18 | Intent intent = new Intent(this, CameraActivity.class); 19 | intent.putExtra(CameraActivity.KEY_OUTPUT_FILE_PATH,filePath); 20 | intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, CameraActivity.CONTENT_TYPE_ID_CARD_FRONT); 21 | startActivityForResult(intent, REQUEST_CODE); 22 | ``` 23 | `身份证反面`调用 24 | ```java 25 | Intent intent = new Intent(this, CameraActivity.class); 26 | intent.putExtra(CameraActivity.KEY_OUTPUT_FILE_PATH,filePath); 27 | intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, CameraActivity.CONTENT_TYPE_ID_CARD_BACK); 28 | startActivityForResult(intent, REQUEST_CODE); 29 | ``` 30 | `CameraActivity.KEY_OUTPUT_FILE_PATH`:拍照后照片要保存的文件 31 | `CameraActivity.KEY_CONTENT_TYPE`:拍照类型 32 | + CameraActivity.CONTENT_TYPE_GENERAL:手持身份证 33 | + CameraActivity.CONTENT_TYPE_ID_CARD_FRONT:身份证正面 34 | + CameraActivity.CONTENT_TYPE_ID_CARD_BACK:身份证反面 35 | 36 | 37 | 38 | `手持身份证`调用 39 | ```java 40 | Intent intent = new Intent(this, CameraActivity.class); 41 | intent.putExtra(CameraActivity.KEY_OUTPUT_FILE_PATH,filePath); 42 | intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, CameraActivity.CONTENT_TYPE_GENERAL); 43 | startActivityForResult(intent, REQUEST_CODE); 44 | ``` 45 | `身份证正面`调用 46 | ```java 47 | Intent intent = new Intent(this, CameraActivity.class); 48 | intent.putExtra(CameraActivity.KEY_OUTPUT_FILE_PATH,filePath); 49 | intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, CameraActivity.CONTENT_TYPE_ID_CARD_FRONT); 50 | startActivityForResult(intent, REQUEST_CODE); 51 | ``` 52 | `身份证反面`调用 53 | ```java 54 | Intent intent = new Intent(this, CameraActivity.class); 55 | intent.putExtra(CameraActivity.KEY_OUTPUT_FILE_PATH,filePath); 56 | intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, CameraActivity.CONTENT_TYPE_ID_CARD_BACK); 57 | startActivityForResult(intent, REQUEST_CODE); 58 | ``` 59 | `CameraActivity.KEY_OUTPUT_FILE_PATH`:拍照后照片要保存的文件 60 | `CameraActivity.KEY_CONTENT_TYPE`:拍照类型 61 | + CameraActivity.CONTENT_TYPE_GENERAL:手持身份证 62 | + CameraActivity.CONTENT_TYPE_ID_CARD_FRONT:身份证正面 63 | + CameraActivity.CONTENT_TYPE_ID_CARD_BACK:身份证反面 64 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 27 9 | defaultConfig { 10 | applicationId "com.gamerole.orcamera" 11 | minSdkVersion 19 12 | targetSdkVersion 27 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | ext.anko_version='0.10.5' 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation 'com.android.support:appcompat-v7:27.1.1' 29 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 30 | implementation "org.jetbrains.anko:anko:$anko_version" 31 | implementation 'io.reactivex.rxjava2:rxjava:2.1.12' 32 | implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar' 33 | 34 | implementation project(':orcameralib') 35 | } 36 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/gamerole/orcamera/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gamerole.orcamera 2 | 3 | import android.Manifest 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import com.gamerole.orcameralib.CameraActivity 7 | import com.tbruyelle.rxpermissions2.RxPermissions 8 | import kotlinx.android.synthetic.main.activity_main.* 9 | import org.jetbrains.anko.startActivityForResult 10 | import org.jetbrains.anko.toast 11 | import java.io.File 12 | 13 | class MainActivity : AppCompatActivity() { 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | setContentView(R.layout.activity_main) 18 | // RxPermissions(this).request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) 19 | // .subscribe { 20 | // if (it) { 21 | tvShouchi.setOnClickListener { 22 | startActivityForResult(1000, 23 | CameraActivity.KEY_OUTPUT_FILE_PATH to File(filesDir, "1.jpg"), 24 | CameraActivity.KEY_CONTENT_TYPE to CameraActivity.CONTENT_TYPE_GENERAL) 25 | } 26 | tvFront.setOnClickListener { 27 | startActivityForResult(1001, 28 | CameraActivity.KEY_OUTPUT_FILE_PATH to File(filesDir, "2.jpg"), 29 | CameraActivity.KEY_CONTENT_TYPE to CameraActivity.CONTENT_TYPE_ID_CARD_FRONT) 30 | } 31 | tvBack.setOnClickListener { 32 | startActivityForResult(1002, 33 | CameraActivity.KEY_OUTPUT_FILE_PATH to File(filesDir, "3.jpg"), 34 | CameraActivity.KEY_CONTENT_TYPE to CameraActivity.CONTENT_TYPE_ID_CARD_BACK) 35 | } 36 | // } else { 37 | // toast("需要开启照相权限") 38 | // } 39 | // } 40 | 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ORCamera 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.2.51' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.1.3' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Aug 07 13:45:00 CST 2018 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-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 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" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 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 85 | -------------------------------------------------------------------------------- /orcameralib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /orcameralib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 17 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'com.android.support:appcompat-v7:27.1.1' 31 | } 32 | -------------------------------------------------------------------------------- /orcameralib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /orcameralib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/Camera1Control.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | import android.Manifest; 7 | import android.content.Context; 8 | import android.content.pm.PackageManager; 9 | import android.graphics.Rect; 10 | import android.graphics.SurfaceTexture; 11 | import android.hardware.Camera; 12 | import android.support.v4.app.ActivityCompat; 13 | import android.view.TextureView; 14 | import android.view.View; 15 | import android.widget.FrameLayout; 16 | 17 | import java.io.IOException; 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.Comparator; 21 | import java.util.List; 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | 24 | /** 25 | * 5.0以下相机API的封装。 26 | */ 27 | @SuppressWarnings("deprecation") 28 | public class Camera1Control implements ICameraControl { 29 | 30 | private int displayOrientation = 0; 31 | private int cameraId = 0; 32 | private int flashMode; 33 | private AtomicBoolean takingPicture = new AtomicBoolean(false); 34 | 35 | private Context context; 36 | private Camera camera; 37 | 38 | private Camera.Parameters parameters; 39 | private PermissionCallback permissionCallback; 40 | private Rect previewFrame = new Rect(); 41 | 42 | private PreviewView previewView; 43 | private View displayView; 44 | 45 | @Override 46 | public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) { 47 | this.displayOrientation = displayOrientation; 48 | previewView.requestLayout(); 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | */ 54 | @Override 55 | public void refreshPermission() { 56 | startPreview(true); 57 | } 58 | 59 | /** 60 | * {@inheritDoc} 61 | */ 62 | @Override 63 | public void setFlashMode(@FlashMode int flashMode) { 64 | if (this.flashMode == flashMode) { 65 | return; 66 | } 67 | this.flashMode = flashMode; 68 | updateFlashMode(flashMode); 69 | } 70 | 71 | @Override 72 | public int getFlashMode() { 73 | return flashMode; 74 | } 75 | 76 | @Override 77 | public void start() { 78 | 79 | } 80 | 81 | @Override 82 | public void stop() { 83 | if (camera != null) { 84 | camera.stopPreview(); 85 | camera.release(); 86 | camera = null; 87 | } 88 | } 89 | 90 | @Override 91 | public void pause() { 92 | if (camera != null) { 93 | camera.stopPreview(); 94 | } 95 | setFlashMode(FLASH_MODE_OFF); 96 | } 97 | 98 | @Override 99 | public void resume() { 100 | takingPicture.set(false); 101 | if (camera == null) { 102 | openCamera(); 103 | } else { 104 | previewView.textureView.setSurfaceTextureListener(surfaceTextureListener); 105 | if (previewView.textureView.isAvailable()) { 106 | camera.startPreview(); 107 | } 108 | } 109 | } 110 | 111 | @Override 112 | public View getDisplayView() { 113 | return displayView; 114 | } 115 | 116 | @Override 117 | public void takePicture(final OnTakePictureCallback onTakePictureCallback) { 118 | if (takingPicture.get()) { 119 | return; 120 | } 121 | 122 | switch (displayOrientation) { 123 | case CameraView.ORIENTATION_PORTRAIT: 124 | parameters.setRotation(90); 125 | break; 126 | case CameraView.ORIENTATION_HORIZONTAL: 127 | parameters.setRotation(0); 128 | break; 129 | case CameraView.ORIENTATION_INVERT: 130 | parameters.setRotation(180); 131 | break; 132 | } 133 | Camera.Size picSize = getOptimalSize(camera.getParameters().getSupportedPictureSizes()); 134 | parameters.setPictureSize(picSize.width, picSize.height); 135 | camera.setParameters(parameters); 136 | takingPicture.set(true); 137 | camera.autoFocus(new Camera.AutoFocusCallback() { 138 | @Override 139 | public void onAutoFocus(boolean success, Camera camera) { 140 | camera.cancelAutoFocus(); 141 | try { 142 | camera.takePicture(null, null, new Camera.PictureCallback() { 143 | @Override 144 | public void onPictureTaken(byte[] data, Camera camera) { 145 | camera.startPreview(); 146 | takingPicture.set(false); 147 | if (onTakePictureCallback != null) { 148 | onTakePictureCallback.onPictureTaken(data); 149 | } 150 | } 151 | }); 152 | } catch (RuntimeException e) { 153 | e.printStackTrace(); 154 | camera.startPreview(); 155 | takingPicture.set(false); 156 | } 157 | } 158 | }); 159 | } 160 | 161 | @Override 162 | public void setPermissionCallback(PermissionCallback callback) { 163 | this.permissionCallback = callback; 164 | } 165 | 166 | public Camera1Control(Context context) { 167 | this.context = context; 168 | previewView = new PreviewView(context); 169 | openCamera(); 170 | } 171 | 172 | private void openCamera() { 173 | setupDisplayView(); 174 | } 175 | 176 | private void setupDisplayView() { 177 | final TextureView textureView = new TextureView(context); 178 | previewView.textureView = textureView; 179 | previewView.setTextureView(textureView); 180 | displayView = previewView; 181 | textureView.setSurfaceTextureListener(surfaceTextureListener); 182 | } 183 | 184 | private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { 185 | @Override 186 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 187 | try { 188 | if (camera == null) { 189 | Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); 190 | for (int i = 0; i < Camera.getNumberOfCameras(); i++) { 191 | Camera.getCameraInfo(i, cameraInfo); 192 | if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { 193 | cameraId = i; 194 | } 195 | } 196 | camera = Camera.open(cameraId); 197 | } 198 | if (parameters == null) { 199 | parameters = camera.getParameters(); 200 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 201 | } 202 | opPreviewSize(previewView.getWidth(), previewView.getHeight()); 203 | camera.setPreviewTexture(surface); 204 | startPreview(false); 205 | } catch (IOException e) { 206 | e.printStackTrace(); 207 | } 208 | } 209 | 210 | @Override 211 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 212 | opPreviewSize(previewView.getWidth(), previewView.getHeight()); 213 | } 214 | 215 | @Override 216 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 217 | return false; 218 | } 219 | 220 | @Override 221 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 222 | } 223 | }; 224 | 225 | // 开启预览 226 | private void startPreview(boolean checkPermission) { 227 | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) 228 | != PackageManager.PERMISSION_GRANTED) { 229 | if (checkPermission && permissionCallback != null) { 230 | permissionCallback.onRequestPermission(); 231 | } 232 | return; 233 | } 234 | camera.startPreview(); 235 | } 236 | 237 | private void opPreviewSize(int width, @SuppressWarnings("unused") int height) { 238 | Camera.Size optSize; 239 | if (parameters != null && camera != null && width > 0) { 240 | optSize = getOptimalSize(camera.getParameters().getSupportedPreviewSizes()); 241 | 242 | parameters.setPreviewSize(optSize.width, optSize.height); 243 | previewView.setRatio(1.0f * optSize.width / optSize.height); 244 | 245 | camera.setDisplayOrientation(getSurfaceOrientation()); 246 | camera.stopPreview(); 247 | try { 248 | camera.setParameters(parameters); 249 | } catch (RuntimeException e) { 250 | e.printStackTrace(); 251 | 252 | } 253 | camera.startPreview(); 254 | } 255 | } 256 | 257 | private Camera.Size getOptimalSize(List sizes) { 258 | int width = previewView.textureView.getWidth(); 259 | int height = previewView.textureView.getHeight(); 260 | 261 | Camera.Size pictureSize = sizes.get(0); 262 | 263 | List candidates = new ArrayList<>(); 264 | 265 | for (Camera.Size size : sizes) { 266 | if (size.width >= width && size.height >= height && size.width * height == size.height * width) { 267 | // 比例相同 268 | candidates.add(size); 269 | } else if (size.height >= width && size.width >= height && size.width * width == size.height * height) { 270 | // 反比例 271 | candidates.add(size); 272 | } 273 | } 274 | if (!candidates.isEmpty()) { 275 | return Collections.min(candidates, sizeComparator); 276 | } 277 | 278 | for (Camera.Size size : sizes) { 279 | if (size.width > width && size.height > height) { 280 | return size; 281 | } 282 | } 283 | 284 | return pictureSize; 285 | } 286 | 287 | private Comparator sizeComparator = new Comparator() { 288 | @Override 289 | public int compare(Camera.Size lhs, Camera.Size rhs) { 290 | return Long.signum((long) lhs.width * lhs.height - (long) rhs.width * rhs.height); 291 | } 292 | }; 293 | 294 | private void updateFlashMode(int flashMode) { 295 | switch (flashMode) { 296 | case FLASH_MODE_TORCH: 297 | parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); 298 | break; 299 | case FLASH_MODE_OFF: 300 | parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); 301 | break; 302 | case ICameraControl.FLASH_MODE_AUTO: 303 | parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO); 304 | break; 305 | default: 306 | parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO); 307 | break; 308 | } 309 | camera.setParameters(parameters); 310 | } 311 | 312 | private int getSurfaceOrientation() { 313 | @CameraView.Orientation 314 | int orientation = displayOrientation; 315 | switch (orientation) { 316 | case CameraView.ORIENTATION_PORTRAIT: 317 | return 90; 318 | case CameraView.ORIENTATION_HORIZONTAL: 319 | return 0; 320 | case CameraView.ORIENTATION_INVERT: 321 | return 180; 322 | default: 323 | return 90; 324 | } 325 | } 326 | 327 | /** 328 | * 有些相机匹配不到完美的比例。比如。我们的layout是4:3的。预览只有16:9 329 | * 的,如果直接显示图片会拉伸,变形。缩放的话,又有黑边。所以我们采取的策略 330 | * 是,等比例放大。这样预览的有一部分会超出屏幕。拍照后再进行裁剪处理。 331 | */ 332 | private class PreviewView extends FrameLayout { 333 | 334 | private TextureView textureView; 335 | 336 | private float ratio = 0.75f; 337 | 338 | void setTextureView(TextureView textureView) { 339 | this.textureView = textureView; 340 | removeAllViews(); 341 | addView(textureView); 342 | } 343 | 344 | void setRatio(float ratio) { 345 | this.ratio = ratio; 346 | requestLayout(); 347 | relayout(getWidth(), getHeight()); 348 | } 349 | 350 | public PreviewView(Context context) { 351 | super(context); 352 | } 353 | 354 | @Override 355 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 356 | super.onSizeChanged(w, h, oldw, oldh); 357 | relayout(w, h); 358 | } 359 | 360 | private void relayout(int w, int h) { 361 | int width = w; 362 | int height = h; 363 | if (w < h) { 364 | // 垂直模式,高度固定。 365 | height = (int) (width * ratio); 366 | } else { 367 | // 水平模式,宽度固定。 368 | width = (int) (height * ratio); 369 | } 370 | 371 | int l = (getWidth() - width) / 2; 372 | int t = (getHeight() - height) / 2; 373 | 374 | previewFrame.left = l; 375 | previewFrame.top = t; 376 | previewFrame.right = l + width; 377 | previewFrame.bottom = t + height; 378 | } 379 | 380 | @Override 381 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 382 | super.onLayout(changed, left, top, right, bottom); 383 | textureView.layout(previewFrame.left, previewFrame.top, previewFrame.right, previewFrame.bottom); 384 | } 385 | } 386 | 387 | @Override 388 | public Rect getPreviewFrame() { 389 | return previewFrame; 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/Camera2Control.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | 5 | package com.gamerole.orcameralib; 6 | 7 | import android.Manifest; 8 | import android.annotation.TargetApi; 9 | import android.content.Context; 10 | import android.content.pm.PackageManager; 11 | import android.graphics.ImageFormat; 12 | import android.graphics.Matrix; 13 | import android.graphics.Point; 14 | import android.graphics.Rect; 15 | import android.graphics.RectF; 16 | import android.graphics.SurfaceTexture; 17 | import android.hardware.camera2.CameraAccessException; 18 | import android.hardware.camera2.CameraCaptureSession; 19 | import android.hardware.camera2.CameraCharacteristics; 20 | import android.hardware.camera2.CameraDevice; 21 | import android.hardware.camera2.CameraManager; 22 | import android.hardware.camera2.CameraMetadata; 23 | import android.hardware.camera2.CaptureFailure; 24 | import android.hardware.camera2.CaptureRequest; 25 | import android.hardware.camera2.CaptureResult; 26 | import android.hardware.camera2.TotalCaptureResult; 27 | import android.hardware.camera2.params.StreamConfigurationMap; 28 | import android.media.Image; 29 | import android.media.ImageReader; 30 | import android.os.Build; 31 | import android.os.Handler; 32 | import android.os.HandlerThread; 33 | import android.support.annotation.NonNull; 34 | import android.support.v4.content.ContextCompat; 35 | import android.util.Size; 36 | import android.util.SparseIntArray; 37 | import android.view.Surface; 38 | import android.view.TextureView; 39 | import android.view.View; 40 | import android.view.WindowManager; 41 | 42 | import java.nio.ByteBuffer; 43 | import java.util.ArrayList; 44 | import java.util.Arrays; 45 | import java.util.Collections; 46 | import java.util.Comparator; 47 | import java.util.List; 48 | import java.util.concurrent.Semaphore; 49 | import java.util.concurrent.TimeUnit; 50 | 51 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 52 | public class Camera2Control implements ICameraControl { 53 | 54 | /** 55 | * Conversion from screen rotation to JPEG orientation. 56 | */ 57 | private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); 58 | private static final int MAX_PREVIEW_SIZE = 2048; 59 | 60 | static { 61 | ORIENTATIONS.append(Surface.ROTATION_0, 90); 62 | ORIENTATIONS.append(Surface.ROTATION_90, 0); 63 | ORIENTATIONS.append(Surface.ROTATION_180, 270); 64 | ORIENTATIONS.append(Surface.ROTATION_270, 180); 65 | } 66 | 67 | private static final int STATE_PREVIEW = 0; 68 | private static final int STATE_WAITING_FOR_LOCK = 1; 69 | private static final int STATE_WAITING_FOR_CAPTURE = 2; 70 | private static final int STATE_CAPTURING = 3; 71 | private static final int STATE_PICTURE_TAKEN = 4; 72 | 73 | private static final int MAX_PREVIEW_WIDTH = 1920; 74 | private static final int MAX_PREVIEW_HEIGHT = 1080; 75 | 76 | private int flashMode; 77 | private int orientation = 0; 78 | private int state = STATE_PREVIEW; 79 | 80 | private Context context; 81 | private OnTakePictureCallback onTakePictureCallback; 82 | private PermissionCallback permissionCallback; 83 | 84 | private String cameraId; 85 | private TextureView textureView; 86 | private Size previewSize; 87 | private Rect previewFrame = new Rect(); 88 | 89 | private HandlerThread backgroundThread; 90 | private Handler backgroundHandler; 91 | private ImageReader imageReader; 92 | private CameraCaptureSession captureSession; 93 | private CameraDevice cameraDevice; 94 | 95 | private CaptureRequest.Builder previewRequestBuilder; 96 | private CaptureRequest previewRequest; 97 | 98 | private Semaphore cameraLock = new Semaphore(1); 99 | private int sensorOrientation; 100 | 101 | @Override 102 | public void start() { 103 | startBackgroundThread(); 104 | if (textureView.isAvailable()) { 105 | openCamera(textureView.getWidth(), textureView.getHeight()); 106 | textureView.setSurfaceTextureListener(surfaceTextureListener); 107 | } else { 108 | textureView.setSurfaceTextureListener(surfaceTextureListener); 109 | } 110 | } 111 | 112 | @Override 113 | public void stop() { 114 | textureView.setSurfaceTextureListener(null); 115 | closeCamera(); 116 | stopBackgroundThread(); 117 | } 118 | 119 | @Override 120 | public void pause() { 121 | setFlashMode(FLASH_MODE_OFF); 122 | } 123 | 124 | @Override 125 | public void resume() { 126 | state = STATE_PREVIEW; 127 | } 128 | 129 | @Override 130 | public View getDisplayView() { 131 | return textureView; 132 | } 133 | 134 | @Override 135 | public Rect getPreviewFrame() { 136 | return previewFrame; 137 | } 138 | 139 | @Override 140 | public void takePicture(OnTakePictureCallback callback) { 141 | this.onTakePictureCallback = callback; 142 | // 拍照第一步,对焦 143 | lockFocus(); 144 | } 145 | 146 | @Override 147 | public void setPermissionCallback(PermissionCallback callback) { 148 | this.permissionCallback = callback; 149 | } 150 | 151 | @Override 152 | public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) { 153 | this.orientation = displayOrientation / 90; 154 | } 155 | 156 | @Override 157 | public void refreshPermission() { 158 | openCamera(textureView.getWidth(), textureView.getHeight()); 159 | } 160 | 161 | @Override 162 | public void setFlashMode(@FlashMode int flashMode) { 163 | if (this.flashMode == flashMode) { 164 | return; 165 | } 166 | this.flashMode = flashMode; 167 | try { 168 | previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, 169 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 170 | updateFlashMode(flashMode, previewRequestBuilder); 171 | previewRequest = previewRequestBuilder.build(); 172 | captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler); 173 | } catch (Exception e) { 174 | e.printStackTrace(); 175 | } 176 | } 177 | 178 | @Override 179 | public int getFlashMode() { 180 | return flashMode; 181 | } 182 | 183 | public Camera2Control(Context activity) { 184 | this.context = activity; 185 | textureView = new TextureView(activity); 186 | } 187 | 188 | private final TextureView.SurfaceTextureListener surfaceTextureListener = 189 | new TextureView.SurfaceTextureListener() { 190 | @Override 191 | public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { 192 | openCamera(width, height); 193 | } 194 | 195 | @Override 196 | public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { 197 | configureTransform(width, height); 198 | previewFrame.left = 0; 199 | previewFrame.top = 0; 200 | previewFrame.right = width; 201 | previewFrame.bottom = height; 202 | } 203 | 204 | @Override 205 | public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { 206 | stop(); 207 | return true; 208 | } 209 | 210 | @Override 211 | public void onSurfaceTextureUpdated(SurfaceTexture texture) { 212 | } 213 | }; 214 | 215 | private void openCamera(int width, int height) { 216 | // 6.0+的系统需要检查系统权限 。 217 | if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) 218 | != PackageManager.PERMISSION_GRANTED) { 219 | requestCameraPermission(); 220 | return; 221 | } 222 | setUpCameraOutputs(width, height); 223 | configureTransform(width, height); 224 | CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); 225 | try { 226 | if (!cameraLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 227 | throw new RuntimeException("Time out waiting to lock camera opening."); 228 | } 229 | manager.openCamera(cameraId, deviceStateCallback, backgroundHandler); 230 | } catch (CameraAccessException e) { 231 | e.printStackTrace(); 232 | } catch (InterruptedException e) { 233 | throw new RuntimeException("Interrupted while trying to lock camera opening.", e); 234 | } 235 | } 236 | 237 | private final CameraDevice.StateCallback deviceStateCallback = new CameraDevice.StateCallback() { 238 | @Override 239 | public void onOpened(@NonNull CameraDevice cameraDevice) { 240 | cameraLock.release(); 241 | Camera2Control.this.cameraDevice = cameraDevice; 242 | createCameraPreviewSession(); 243 | } 244 | 245 | @Override 246 | public void onDisconnected(@NonNull CameraDevice cameraDevice) { 247 | cameraLock.release(); 248 | cameraDevice.close(); 249 | Camera2Control.this.cameraDevice = null; 250 | } 251 | 252 | @Override 253 | public void onError(@NonNull CameraDevice cameraDevice, int error) { 254 | cameraLock.release(); 255 | cameraDevice.close(); 256 | Camera2Control.this.cameraDevice = null; 257 | } 258 | }; 259 | 260 | private void createCameraPreviewSession() { 261 | try { 262 | SurfaceTexture texture = textureView.getSurfaceTexture(); 263 | assert texture != null; 264 | texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); 265 | Surface surface = new Surface(texture); 266 | previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 267 | previewRequestBuilder.addTarget(surface); 268 | updateFlashMode(this.flashMode, previewRequestBuilder); 269 | cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), 270 | new CameraCaptureSession.StateCallback() { 271 | 272 | @Override 273 | public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { 274 | // The camera is already closed 275 | if (null == cameraDevice) { 276 | return; 277 | } 278 | captureSession = cameraCaptureSession; 279 | try { 280 | previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, 281 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 282 | previewRequest = previewRequestBuilder.build(); 283 | captureSession.setRepeatingRequest(previewRequest, 284 | captureCallback, backgroundHandler); 285 | } catch (CameraAccessException e) { 286 | e.printStackTrace(); 287 | } 288 | } 289 | 290 | @Override 291 | public void onConfigureFailed( 292 | @NonNull CameraCaptureSession cameraCaptureSession) { 293 | // TODO 294 | } 295 | }, null 296 | ); 297 | } catch (CameraAccessException e) { 298 | e.printStackTrace(); 299 | } 300 | } 301 | 302 | private final ImageReader.OnImageAvailableListener onImageAvailableListener = 303 | new ImageReader.OnImageAvailableListener() { 304 | @Override 305 | public void onImageAvailable(ImageReader reader) { 306 | if (onTakePictureCallback != null) { 307 | Image image = reader.acquireNextImage(); 308 | ByteBuffer buffer = image.getPlanes()[0].getBuffer(); 309 | byte[] bytes = new byte[buffer.remaining()]; 310 | buffer.get(bytes); 311 | 312 | image.close(); 313 | onTakePictureCallback.onPictureTaken(bytes); 314 | } 315 | } 316 | }; 317 | 318 | private CameraCaptureSession.CaptureCallback captureCallback = 319 | new CameraCaptureSession.CaptureCallback() { 320 | private void process(CaptureResult result) { 321 | switch (state) { 322 | case STATE_PREVIEW: { 323 | break; 324 | } 325 | case STATE_WAITING_FOR_LOCK: { 326 | Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); 327 | if (afState == null) { 328 | captureStillPicture(); 329 | return; 330 | } 331 | switch (afState) { 332 | case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED: 333 | case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: 334 | case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: { 335 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 336 | if (aeState == null 337 | || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 338 | captureStillPicture(); 339 | } else { 340 | runPreCaptureSequence(); 341 | } 342 | break; 343 | } 344 | default: 345 | captureStillPicture(); 346 | } 347 | break; 348 | } 349 | case STATE_WAITING_FOR_CAPTURE: { 350 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 351 | if (aeState == null 352 | || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE 353 | || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { 354 | state = STATE_CAPTURING; 355 | } else { 356 | if (aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 357 | captureStillPicture(); 358 | } 359 | } 360 | break; 361 | } 362 | case STATE_CAPTURING: { 363 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 364 | if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { 365 | captureStillPicture(); 366 | } 367 | break; 368 | } 369 | default: 370 | break; 371 | } 372 | } 373 | 374 | @Override 375 | public void onCaptureProgressed(@NonNull CameraCaptureSession session, 376 | @NonNull CaptureRequest request, 377 | @NonNull CaptureResult partialResult) { 378 | process(partialResult); 379 | } 380 | 381 | @Override 382 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 383 | @NonNull CaptureRequest request, 384 | @NonNull TotalCaptureResult result) { 385 | process(result); 386 | } 387 | 388 | }; 389 | 390 | private Size getOptimalSize(Size[] choices, int textureViewWidth, 391 | int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { 392 | List bigEnough = new ArrayList<>(); 393 | List notBigEnough = new ArrayList<>(); 394 | int w = aspectRatio.getWidth(); 395 | int h = aspectRatio.getHeight(); 396 | for (Size option : choices) { 397 | if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight 398 | && option.getHeight() == option.getWidth() * h / w) { 399 | if (option.getWidth() >= textureViewWidth 400 | && option.getHeight() >= textureViewHeight) { 401 | bigEnough.add(option); 402 | } else { 403 | notBigEnough.add(option); 404 | } 405 | } 406 | } 407 | 408 | // Pick the smallest of those big enough. If there is no one big enough, pick the 409 | // largest of those not big enough. 410 | if (bigEnough.size() > 0) { 411 | return Collections.min(bigEnough, sizeComparator); 412 | } 413 | 414 | for (Size option : choices) { 415 | if (option.getWidth() >= maxWidth && option.getHeight() >= maxHeight) { 416 | return option; 417 | } 418 | } 419 | 420 | if (notBigEnough.size() > 0) { 421 | return Collections.max(notBigEnough, sizeComparator); 422 | } 423 | 424 | return choices[0]; 425 | } 426 | 427 | private Comparator sizeComparator = new Comparator() { 428 | @Override 429 | public int compare(Size lhs, Size rhs) { 430 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); 431 | } 432 | }; 433 | 434 | private void requestCameraPermission() { 435 | if (permissionCallback != null) { 436 | permissionCallback.onRequestPermission(); 437 | } 438 | } 439 | 440 | @SuppressWarnings("SuspiciousNameCombination") 441 | private void setUpCameraOutputs(int width, int height) { 442 | CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); 443 | try { 444 | for (String cameraId : manager.getCameraIdList()) { 445 | CameraCharacteristics characteristics = 446 | manager.getCameraCharacteristics(cameraId); 447 | 448 | Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); 449 | if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { 450 | continue; 451 | } 452 | 453 | StreamConfigurationMap map = characteristics.get( 454 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 455 | if (map == null) { 456 | continue; 457 | } 458 | 459 | // int preferredPictureSize = (int) (Math.max(textureView.getWidth(), textureView 460 | // .getHeight()) * 1.5); 461 | // preferredPictureSize = Math.min(preferredPictureSize, 2560); 462 | 463 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 464 | Point screenSize = new Point(); 465 | windowManager.getDefaultDisplay().getSize(screenSize); 466 | int maxImageSize = Math.max(MAX_PREVIEW_SIZE, screenSize.y * 3 / 2); 467 | 468 | Size size = getOptimalSize(map.getOutputSizes(ImageFormat.JPEG), textureView.getWidth(), 469 | textureView.getHeight(), maxImageSize, maxImageSize, new Size(4, 3)); 470 | 471 | imageReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), 472 | ImageFormat.JPEG, 1); 473 | imageReader.setOnImageAvailableListener( 474 | onImageAvailableListener, backgroundHandler); 475 | 476 | int displayRotation = orientation; 477 | // noinspection ConstantConditions 478 | sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); 479 | boolean swappedDimensions = false; 480 | switch (displayRotation) { 481 | case Surface.ROTATION_0: 482 | case Surface.ROTATION_180: 483 | if (sensorOrientation == 90 || sensorOrientation == 270) { 484 | swappedDimensions = true; 485 | } 486 | break; 487 | case Surface.ROTATION_90: 488 | case Surface.ROTATION_270: 489 | if (sensorOrientation == 0 || sensorOrientation == 180) { 490 | swappedDimensions = true; 491 | } 492 | break; 493 | default: 494 | } 495 | 496 | int rotatedPreviewWidth = width; 497 | int rotatedPreviewHeight = height; 498 | int maxPreviewWidth = screenSize.x; 499 | int maxPreviewHeight = screenSize.y; 500 | 501 | if (swappedDimensions) { 502 | rotatedPreviewWidth = height; 503 | rotatedPreviewHeight = width; 504 | maxPreviewWidth = screenSize.y; 505 | maxPreviewHeight = screenSize.x; 506 | } 507 | 508 | maxPreviewWidth = Math.min(maxPreviewWidth, MAX_PREVIEW_WIDTH); 509 | maxPreviewHeight = Math.min(maxPreviewHeight, MAX_PREVIEW_HEIGHT); 510 | 511 | previewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), 512 | rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, 513 | maxPreviewHeight, size); 514 | this.cameraId = cameraId; 515 | return; 516 | } 517 | } catch (CameraAccessException | NullPointerException e) { 518 | e.printStackTrace(); 519 | } 520 | } 521 | 522 | private void closeCamera() { 523 | try { 524 | cameraLock.acquire(); 525 | if (null != captureSession) { 526 | captureSession.close(); 527 | captureSession = null; 528 | } 529 | if (null != cameraDevice) { 530 | cameraDevice.close(); 531 | cameraDevice = null; 532 | } 533 | if (null != imageReader) { 534 | imageReader.close(); 535 | imageReader = null; 536 | } 537 | } catch (InterruptedException e) { 538 | throw new RuntimeException("Interrupted while trying to lock camera closing.", e); 539 | } finally { 540 | cameraLock.release(); 541 | } 542 | } 543 | 544 | private void startBackgroundThread() { 545 | backgroundThread = new HandlerThread("ocr_camera"); 546 | backgroundThread.start(); 547 | backgroundHandler = new Handler(backgroundThread.getLooper()); 548 | } 549 | 550 | private void stopBackgroundThread() { 551 | if (backgroundThread != null) { 552 | backgroundThread.quitSafely(); 553 | backgroundThread = null; 554 | backgroundHandler = null; 555 | } 556 | } 557 | 558 | private void configureTransform(int viewWidth, int viewHeight) { 559 | if (null == textureView || null == previewSize || null == context) { 560 | return; 561 | } 562 | int rotation = orientation; 563 | Matrix matrix = new Matrix(); 564 | RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 565 | RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); 566 | float centerX = viewRect.centerX(); 567 | float centerY = viewRect.centerY(); 568 | if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { 569 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 570 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 571 | float scale = Math.max( 572 | (float) viewHeight / previewSize.getHeight(), 573 | (float) viewWidth / previewSize.getWidth()); 574 | matrix.postScale(scale, scale, centerX, centerY); 575 | matrix.postRotate(90 * (rotation - 2), centerX, centerY); 576 | } else if (Surface.ROTATION_180 == rotation) { 577 | matrix.postRotate(180, centerX, centerY); 578 | } 579 | textureView.setTransform(matrix); 580 | } 581 | 582 | // 拍照前,先对焦 583 | private void lockFocus() { 584 | if (captureSession != null && state == STATE_PREVIEW) { 585 | try { 586 | previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 587 | CameraMetadata.CONTROL_AF_TRIGGER_START); 588 | state = STATE_WAITING_FOR_LOCK; 589 | captureSession.capture(previewRequestBuilder.build(), captureCallback, 590 | backgroundHandler); 591 | } catch (CameraAccessException e) { 592 | e.printStackTrace(); 593 | } 594 | } 595 | } 596 | 597 | private void runPreCaptureSequence() { 598 | try { 599 | previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 600 | CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); 601 | state = STATE_WAITING_FOR_CAPTURE; 602 | captureSession.capture(previewRequestBuilder.build(), captureCallback, 603 | backgroundHandler); 604 | } catch (CameraAccessException e) { 605 | e.printStackTrace(); 606 | } 607 | } 608 | 609 | // 拍照session 610 | private void captureStillPicture() { 611 | try { 612 | if (null == context || null == cameraDevice) { 613 | return; 614 | } 615 | final CaptureRequest.Builder captureBuilder = 616 | cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 617 | captureBuilder.addTarget(imageReader.getSurface()); 618 | captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, 619 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 620 | 621 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(orientation)); 622 | updateFlashMode(this.flashMode, captureBuilder); 623 | CameraCaptureSession.CaptureCallback captureCallback = 624 | new CameraCaptureSession.CaptureCallback() { 625 | @Override 626 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 627 | @NonNull CaptureRequest request, 628 | @NonNull TotalCaptureResult result) { 629 | unlockFocus(); 630 | } 631 | 632 | @Override 633 | public void onCaptureFailed(@NonNull CameraCaptureSession session, 634 | @NonNull CaptureRequest request, 635 | @NonNull CaptureFailure failure) { 636 | super.onCaptureFailed(session, request, failure); 637 | } 638 | }; 639 | 640 | // 停止预览 641 | captureSession.stopRepeating(); 642 | captureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler); 643 | state = STATE_PICTURE_TAKEN; 644 | // unlockFocus(); 645 | } catch (CameraAccessException e) { 646 | e.printStackTrace(); 647 | } 648 | } 649 | 650 | private int getOrientation(int rotation) { 651 | return (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360; 652 | } 653 | 654 | // 停止对焦 655 | private void unlockFocus() { 656 | try { 657 | previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 658 | CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 659 | captureSession.capture(previewRequestBuilder.build(), captureCallback, 660 | backgroundHandler); 661 | state = STATE_PREVIEW; 662 | // 预览 663 | captureSession.setRepeatingRequest(previewRequest, captureCallback, 664 | backgroundHandler); 665 | textureView.setSurfaceTextureListener(surfaceTextureListener); 666 | } catch (CameraAccessException e) { 667 | e.printStackTrace(); 668 | } 669 | } 670 | 671 | private void updateFlashMode(@FlashMode int flashMode, CaptureRequest.Builder builder) { 672 | switch (flashMode) { 673 | case FLASH_MODE_TORCH: 674 | builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH); 675 | break; 676 | case FLASH_MODE_OFF: 677 | builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF); 678 | break; 679 | case ICameraControl.FLASH_MODE_AUTO: 680 | default: 681 | builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE); 682 | break; 683 | } 684 | } 685 | } 686 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/CameraActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | import android.Manifest; 7 | import android.app.Activity; 8 | import android.content.Intent; 9 | import android.content.pm.PackageManager; 10 | import android.content.res.Configuration; 11 | import android.database.Cursor; 12 | import android.graphics.Bitmap; 13 | import android.graphics.Rect; 14 | import android.graphics.drawable.BitmapDrawable; 15 | import android.net.Uri; 16 | import android.os.Build; 17 | import android.os.Bundle; 18 | import android.os.Handler; 19 | import android.provider.MediaStore; 20 | import android.support.annotation.NonNull; 21 | import android.support.v4.app.ActivityCompat; 22 | import android.support.v7.app.AppCompatActivity; 23 | import android.view.Surface; 24 | import android.view.View; 25 | import android.widget.ImageView; 26 | import android.widget.Toast; 27 | 28 | import java.io.File; 29 | import java.io.FileOutputStream; 30 | import java.io.IOException; 31 | 32 | 33 | public class CameraActivity extends AppCompatActivity { 34 | 35 | public static final String KEY_OUTPUT_FILE_PATH = "outputFilePath"; 36 | public static final String KEY_CONTENT_TYPE = "contentType"; 37 | 38 | public static final String CONTENT_TYPE_GENERAL = "general"; 39 | public static final String CONTENT_TYPE_ID_CARD_FRONT = "IDCardFront"; 40 | public static final String CONTENT_TYPE_ID_CARD_BACK = "IDCardBack"; 41 | public static final String CONTENT_TYPE_BANK_CARD = "bankCard"; 42 | 43 | private static final int REQUEST_CODE_PICK_IMAGE = 100; 44 | private static final int PERMISSIONS_REQUEST_CAMERA = 800; 45 | private static final int PERMISSIONS_EXTERNAL_STORAGE = 801; 46 | 47 | private File outputFile; 48 | private String contentType; 49 | private Handler handler = new Handler(); 50 | 51 | private OCRCameraLayout takePictureContainer; 52 | private OCRCameraLayout cropContainer; 53 | private OCRCameraLayout confirmResultContainer; 54 | private ImageView lightButton; 55 | private CameraView cameraView; 56 | private ImageView displayImageView; 57 | private CropView cropView; 58 | private FrameOverlayView overlayView; 59 | private MaskView cropMaskView; 60 | private PermissionCallback permissionCallback = new PermissionCallback() { 61 | @Override 62 | public boolean onRequestPermission() { 63 | ActivityCompat.requestPermissions(CameraActivity.this, 64 | new String[] {Manifest.permission.CAMERA}, 65 | PERMISSIONS_REQUEST_CAMERA); 66 | return false; 67 | } 68 | }; 69 | 70 | @Override 71 | protected void onCreate(Bundle savedInstanceState) { 72 | super.onCreate(savedInstanceState); 73 | setContentView(R.layout.bd_ocr_activity_camera); 74 | 75 | takePictureContainer = (OCRCameraLayout) findViewById(R.id.take_picture_container); 76 | confirmResultContainer = (OCRCameraLayout) findViewById(R.id.confirm_result_container); 77 | 78 | cameraView = (CameraView) findViewById(R.id.camera_view); 79 | cameraView.getCameraControl().setPermissionCallback(permissionCallback); 80 | lightButton = (ImageView) findViewById(R.id.light_button); 81 | lightButton.setOnClickListener(lightButtonOnClickListener); 82 | findViewById(R.id.album_button).setOnClickListener(albumButtonOnClickListener); 83 | findViewById(R.id.take_photo_button).setOnClickListener(takeButtonOnClickListener); 84 | findViewById(R.id.close_button).setOnClickListener(closeButtonOnClickListener); 85 | 86 | // confirm result; 87 | displayImageView = (ImageView) findViewById(R.id.display_image_view); 88 | confirmResultContainer.findViewById(R.id.confirm_button).setOnClickListener(confirmButtonOnClickListener); 89 | confirmResultContainer.findViewById(R.id.cancel_button).setOnClickListener(confirmCancelButtonOnClickListener); 90 | findViewById(R.id.rotate_button).setOnClickListener(rotateButtonOnClickListener); 91 | 92 | cropView = (CropView) findViewById(R.id.crop_view); 93 | cropContainer = (OCRCameraLayout) findViewById(R.id.crop_container); 94 | overlayView = (FrameOverlayView) findViewById(R.id.overlay_view); 95 | cropContainer.findViewById(R.id.confirm_button).setOnClickListener(cropConfirmButtonListener); 96 | cropMaskView = (MaskView) cropContainer.findViewById(R.id.crop_mask_view); 97 | cropContainer.findViewById(R.id.cancel_button).setOnClickListener(cropCancelButtonListener); 98 | 99 | setOrientation(getResources().getConfiguration()); 100 | initParams(); 101 | } 102 | 103 | @Override 104 | protected void onStart() { 105 | super.onStart(); 106 | cameraView.start(); 107 | } 108 | 109 | @Override 110 | protected void onStop() { 111 | super.onStop(); 112 | cameraView.stop(); 113 | } 114 | 115 | private void initParams() { 116 | String outputPath = getIntent().getStringExtra(KEY_OUTPUT_FILE_PATH); 117 | if (outputPath != null) { 118 | outputFile = new File(outputPath); 119 | } 120 | 121 | contentType = getIntent().getStringExtra(KEY_CONTENT_TYPE); 122 | if (contentType == null) { 123 | contentType = CONTENT_TYPE_GENERAL; 124 | } 125 | int maskType; 126 | switch (contentType) { 127 | case CONTENT_TYPE_ID_CARD_FRONT: 128 | maskType = MaskView.MASK_TYPE_ID_CARD_FRONT; 129 | overlayView.setVisibility(View.INVISIBLE); 130 | break; 131 | case CONTENT_TYPE_ID_CARD_BACK: 132 | maskType = MaskView.MASK_TYPE_ID_CARD_BACK; 133 | overlayView.setVisibility(View.INVISIBLE); 134 | break; 135 | case CONTENT_TYPE_BANK_CARD: 136 | maskType = MaskView.MASK_TYPE_BANK_CARD; 137 | overlayView.setVisibility(View.INVISIBLE); 138 | break; 139 | case CONTENT_TYPE_GENERAL: 140 | default: 141 | maskType = MaskView.MASK_TYPE_NONE; 142 | cropMaskView.setVisibility(View.INVISIBLE); 143 | break; 144 | } 145 | cameraView.setMaskType(maskType); 146 | cropMaskView.setMaskType(maskType); 147 | } 148 | 149 | private void showTakePicture() { 150 | cameraView.getCameraControl().resume(); 151 | updateFlashMode(); 152 | takePictureContainer.setVisibility(View.VISIBLE); 153 | confirmResultContainer.setVisibility(View.INVISIBLE); 154 | cropContainer.setVisibility(View.INVISIBLE); 155 | } 156 | 157 | private void showCrop() { 158 | cameraView.getCameraControl().pause(); 159 | updateFlashMode(); 160 | takePictureContainer.setVisibility(View.INVISIBLE); 161 | confirmResultContainer.setVisibility(View.INVISIBLE); 162 | cropContainer.setVisibility(View.VISIBLE); 163 | } 164 | 165 | private void showResultConfirm() { 166 | cameraView.getCameraControl().pause(); 167 | updateFlashMode(); 168 | takePictureContainer.setVisibility(View.INVISIBLE); 169 | confirmResultContainer.setVisibility(View.VISIBLE); 170 | cropContainer.setVisibility(View.INVISIBLE); 171 | } 172 | 173 | // take photo; 174 | private void updateFlashMode() { 175 | int flashMode = cameraView.getCameraControl().getFlashMode(); 176 | if (flashMode == ICameraControl.FLASH_MODE_TORCH) { 177 | lightButton.setImageResource(R.drawable.bd_ocr_light_on); 178 | } else { 179 | lightButton.setImageResource(R.drawable.bd_ocr_light_off); 180 | } 181 | } 182 | 183 | private View.OnClickListener albumButtonOnClickListener = new View.OnClickListener() { 184 | @Override 185 | public void onClick(View v) { 186 | 187 | if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) 188 | != PackageManager.PERMISSION_GRANTED) { 189 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 190 | ActivityCompat.requestPermissions(CameraActivity.this, 191 | new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, 192 | PERMISSIONS_EXTERNAL_STORAGE); 193 | return; 194 | } 195 | } 196 | 197 | Intent intent = new Intent(Intent.ACTION_PICK); 198 | intent.setType("image/*"); 199 | startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE); 200 | } 201 | }; 202 | 203 | private View.OnClickListener lightButtonOnClickListener = new View.OnClickListener() { 204 | @Override 205 | public void onClick(View v) { 206 | if (cameraView.getCameraControl().getFlashMode() == ICameraControl.FLASH_MODE_OFF) { 207 | cameraView.getCameraControl().setFlashMode(ICameraControl.FLASH_MODE_TORCH); 208 | } else { 209 | cameraView.getCameraControl().setFlashMode(ICameraControl.FLASH_MODE_OFF); 210 | } 211 | updateFlashMode(); 212 | } 213 | }; 214 | 215 | private View.OnClickListener takeButtonOnClickListener = new View.OnClickListener() { 216 | @Override 217 | public void onClick(View v) { 218 | cameraView.takePicture(outputFile, takePictureCallback); 219 | } 220 | }; 221 | 222 | private CameraView.OnTakePictureCallback takePictureCallback = new CameraView.OnTakePictureCallback() { 223 | @Override 224 | public void onPictureTaken(final Bitmap bitmap) { 225 | handler.post(new Runnable() { 226 | @Override 227 | public void run() { 228 | takePictureContainer.setVisibility(View.INVISIBLE); 229 | if (cropMaskView.getMaskType() == MaskView.MASK_TYPE_NONE) { 230 | cropView.setFilePath(outputFile.getAbsolutePath()); 231 | showCrop(); 232 | } else if (cropMaskView.getMaskType() == MaskView.MASK_TYPE_BANK_CARD) { 233 | cropView.setFilePath(outputFile.getAbsolutePath()); 234 | cropMaskView.setVisibility(View.INVISIBLE); 235 | overlayView.setVisibility(View.VISIBLE); 236 | overlayView.setTypeWide(); 237 | showCrop(); 238 | } else { 239 | displayImageView.setImageBitmap(bitmap); 240 | showResultConfirm(); 241 | } 242 | } 243 | }); 244 | } 245 | }; 246 | 247 | private View.OnClickListener cropCancelButtonListener = new View.OnClickListener() { 248 | @Override 249 | public void onClick(View v) { 250 | // 释放 cropView中的bitmap; 251 | cropView.setFilePath(null); 252 | showTakePicture(); 253 | } 254 | }; 255 | 256 | private View.OnClickListener cropConfirmButtonListener = new View.OnClickListener() { 257 | @Override 258 | public void onClick(View v) { 259 | int maskType = cropMaskView.getMaskType(); 260 | Rect rect; 261 | switch (maskType) { 262 | case MaskView.MASK_TYPE_BANK_CARD: 263 | case MaskView.MASK_TYPE_ID_CARD_BACK: 264 | case MaskView.MASK_TYPE_ID_CARD_FRONT: 265 | rect = cropMaskView.getFrameRect(); 266 | break; 267 | case MaskView.MASK_TYPE_NONE: 268 | default: 269 | rect = overlayView.getFrameRect(); 270 | break; 271 | } 272 | Bitmap cropped = cropView.crop(rect); 273 | displayImageView.setImageBitmap(cropped); 274 | cropAndConfirm(); 275 | } 276 | }; 277 | 278 | private void cropAndConfirm() { 279 | cameraView.getCameraControl().pause(); 280 | updateFlashMode(); 281 | doConfirmResult(); 282 | } 283 | 284 | // confirm result; 285 | private View.OnClickListener closeButtonOnClickListener = new View.OnClickListener() { 286 | @Override 287 | public void onClick(View v) { 288 | setResult(Activity.RESULT_CANCELED); 289 | finish(); 290 | } 291 | }; 292 | 293 | private void doConfirmResult() { 294 | new Thread() { 295 | @Override 296 | public void run() { 297 | super.run(); 298 | try { 299 | FileOutputStream fileOutputStream = new FileOutputStream(outputFile); 300 | Bitmap bitmap = ((BitmapDrawable) displayImageView.getDrawable()).getBitmap(); 301 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream); 302 | fileOutputStream.close(); 303 | } catch (IOException e) { 304 | e.printStackTrace(); 305 | } 306 | 307 | Intent intent = new Intent(); 308 | intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, contentType); 309 | setResult(Activity.RESULT_OK, intent); 310 | finish(); 311 | } 312 | }.start(); 313 | } 314 | 315 | private View.OnClickListener confirmButtonOnClickListener = new View.OnClickListener() { 316 | @Override 317 | public void onClick(View v) { 318 | doConfirmResult(); 319 | } 320 | }; 321 | 322 | private View.OnClickListener confirmCancelButtonOnClickListener = new View.OnClickListener() { 323 | @Override 324 | public void onClick(View v) { 325 | displayImageView.setImageBitmap(null); 326 | showTakePicture(); 327 | } 328 | }; 329 | 330 | private View.OnClickListener rotateButtonOnClickListener = new View.OnClickListener() { 331 | @Override 332 | public void onClick(View v) { 333 | cropView.rotate(90); 334 | } 335 | }; 336 | 337 | private String getRealPathFromURI(Uri contentURI) { 338 | String result; 339 | Cursor cursor = getContentResolver().query(contentURI, null, null, null, null); 340 | if (cursor == null) { 341 | result = contentURI.getPath(); 342 | } else { 343 | cursor.moveToFirst(); 344 | int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 345 | result = cursor.getString(idx); 346 | cursor.close(); 347 | } 348 | return result; 349 | } 350 | 351 | @Override 352 | public void onConfigurationChanged(Configuration newConfig) { 353 | super.onConfigurationChanged(newConfig); 354 | setOrientation(newConfig); 355 | } 356 | 357 | private void setOrientation(Configuration newConfig) { 358 | int rotation = getWindowManager().getDefaultDisplay().getRotation(); 359 | int orientation; 360 | int cameraViewOrientation = CameraView.ORIENTATION_PORTRAIT; 361 | switch (newConfig.orientation) { 362 | case Configuration.ORIENTATION_PORTRAIT: 363 | cameraViewOrientation = CameraView.ORIENTATION_PORTRAIT; 364 | orientation = OCRCameraLayout.ORIENTATION_PORTRAIT; 365 | break; 366 | case Configuration.ORIENTATION_LANDSCAPE: 367 | orientation = OCRCameraLayout.ORIENTATION_HORIZONTAL; 368 | if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) { 369 | cameraViewOrientation = CameraView.ORIENTATION_HORIZONTAL; 370 | } else { 371 | cameraViewOrientation = CameraView.ORIENTATION_INVERT; 372 | } 373 | break; 374 | default: 375 | orientation = OCRCameraLayout.ORIENTATION_PORTRAIT; 376 | cameraView.setOrientation(CameraView.ORIENTATION_PORTRAIT); 377 | break; 378 | } 379 | takePictureContainer.setOrientation(orientation); 380 | cameraView.setOrientation(cameraViewOrientation); 381 | cropContainer.setOrientation(orientation); 382 | confirmResultContainer.setOrientation(orientation); 383 | } 384 | 385 | @Override 386 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 387 | super.onActivityResult(requestCode, resultCode, data); 388 | if (requestCode == REQUEST_CODE_PICK_IMAGE && resultCode == Activity.RESULT_OK) { 389 | Uri uri = data.getData(); 390 | cropView.setFilePath(getRealPathFromURI(uri)); 391 | showCrop(); 392 | } 393 | } 394 | 395 | @Override 396 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 397 | @NonNull int[] grantResults) { 398 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 399 | switch (requestCode) { 400 | case PERMISSIONS_REQUEST_CAMERA: { 401 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 402 | cameraView.getCameraControl().refreshPermission(); 403 | } else { 404 | Toast.makeText(getApplicationContext(), R.string.camera_permission_required, Toast.LENGTH_LONG) 405 | .show(); 406 | } 407 | break; 408 | } 409 | case PERMISSIONS_EXTERNAL_STORAGE: 410 | default: 411 | break; 412 | } 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/CameraView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | import android.content.Context; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.BitmapRegionDecoder; 10 | import android.graphics.Matrix; 11 | import android.graphics.Rect; 12 | import android.os.Build; 13 | import android.os.Handler; 14 | import android.os.HandlerThread; 15 | import android.support.annotation.IntDef; 16 | import android.util.AttributeSet; 17 | import android.view.View; 18 | import android.widget.FrameLayout; 19 | import android.widget.ImageView; 20 | 21 | 22 | import com.gamerole.orcameralib.util.DimensionUtil; 23 | import com.gamerole.orcameralib.util.ImageUtil; 24 | 25 | import java.io.File; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | 29 | /** 30 | * 负责,相机的管理。同时提供,裁剪遮罩功能。 31 | */ 32 | public class CameraView extends FrameLayout { 33 | 34 | /** 35 | * 照相回调 36 | */ 37 | public interface OnTakePictureCallback { 38 | void onPictureTaken(Bitmap bitmap); 39 | } 40 | 41 | /** 42 | * 垂直方向 {@link #setOrientation(int)} 43 | */ 44 | public static final int ORIENTATION_PORTRAIT = 0; 45 | /** 46 | * 水平方向 {@link #setOrientation(int)} 47 | */ 48 | public static final int ORIENTATION_HORIZONTAL = 90; 49 | /** 50 | * 水平翻转方向 {@link #setOrientation(int)} 51 | */ 52 | public static final int ORIENTATION_INVERT = 270; 53 | 54 | @IntDef({ORIENTATION_PORTRAIT, ORIENTATION_HORIZONTAL, ORIENTATION_INVERT}) 55 | public @interface Orientation { 56 | 57 | } 58 | 59 | private CameraViewTakePictureCallback cameraViewTakePictureCallback = new CameraViewTakePictureCallback(); 60 | 61 | private ICameraControl cameraControl; 62 | 63 | /** 64 | * 相机预览View 65 | */ 66 | private View displayView; 67 | /** 68 | * 身份证,银行卡,等裁剪用的遮罩 69 | */ 70 | private MaskView maskView; 71 | 72 | /** 73 | * 用于显示提示证 "请对齐身份证正面" 之类的 74 | */ 75 | private ImageView hintView; 76 | 77 | public ICameraControl getCameraControl() { 78 | return cameraControl; 79 | } 80 | 81 | public void setOrientation(@Orientation int orientation) { 82 | cameraControl.setDisplayOrientation(orientation); 83 | } 84 | 85 | public CameraView(Context context) { 86 | super(context); 87 | init(); 88 | } 89 | 90 | public CameraView(Context context, AttributeSet attrs) { 91 | super(context, attrs); 92 | init(); 93 | } 94 | 95 | public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { 96 | super(context, attrs, defStyleAttr); 97 | init(); 98 | } 99 | 100 | public void start() { 101 | cameraControl.start(); 102 | setKeepScreenOn(true); 103 | } 104 | 105 | public void stop() { 106 | cameraControl.stop(); 107 | setKeepScreenOn(false); 108 | } 109 | 110 | public void takePicture(final File file, final OnTakePictureCallback callback) { 111 | cameraViewTakePictureCallback.file = file; 112 | cameraViewTakePictureCallback.callback = callback; 113 | cameraControl.takePicture(cameraViewTakePictureCallback); 114 | } 115 | 116 | public void setMaskType(@MaskView.MaskType int maskType) { 117 | maskView.setMaskType(maskType); 118 | 119 | maskView.setVisibility(VISIBLE); 120 | hintView.setVisibility(VISIBLE); 121 | 122 | int hintResourceId = R.drawable.bd_ocr_hint_align_id_card; 123 | switch (maskType) { 124 | case MaskView.MASK_TYPE_ID_CARD_FRONT: 125 | hintResourceId = R.drawable.bd_ocr_hint_align_id_card; 126 | break; 127 | case MaskView.MASK_TYPE_ID_CARD_BACK: 128 | hintResourceId = R.drawable.bd_ocr_hint_align_id_card_back; 129 | break; 130 | case MaskView.MASK_TYPE_BANK_CARD: 131 | hintResourceId = R.drawable.bd_ocr_hint_align_bank_card; 132 | break; 133 | case MaskView.MASK_TYPE_NONE: 134 | default: 135 | maskView.setVisibility(INVISIBLE); 136 | hintView.setVisibility(INVISIBLE); 137 | break; 138 | } 139 | 140 | hintView.setImageResource(hintResourceId); 141 | } 142 | 143 | private void init() { 144 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 145 | cameraControl = new Camera2Control(getContext()); 146 | } else { 147 | cameraControl = new Camera1Control(getContext()); 148 | } 149 | displayView = cameraControl.getDisplayView(); 150 | addView(displayView); 151 | 152 | maskView = new MaskView(getContext()); 153 | addView(maskView); 154 | 155 | hintView = new ImageView(getContext()); 156 | addView(hintView); 157 | } 158 | 159 | @Override 160 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 161 | displayView.layout(left, 0, right, bottom - top); 162 | maskView.layout(left, 0, right, bottom - top); 163 | 164 | int hintViewWidth = DimensionUtil.dpToPx(150); 165 | int hintViewHeight = DimensionUtil.dpToPx(25); 166 | 167 | int hintViewLeft = (getWidth() - hintViewWidth) / 2; 168 | int hintViewTop = maskView.getFrameRect().bottom + DimensionUtil.dpToPx(16); 169 | 170 | hintView.layout(hintViewLeft, hintViewTop, hintViewLeft + hintViewWidth, hintViewTop + hintViewHeight); 171 | } 172 | 173 | /** 174 | * 拍摄后的照片。需要进行裁剪。有些手机(比如三星)不会对照片数据进行旋转,而是将旋转角度写入EXIF信息当中, 175 | * 所以需要做旋转处理。 176 | * 177 | * @param outputFile 写入照片的文件。 178 | * @param imageFile 原始照片。 179 | * @param rotation 照片exif中的旋转角度。 180 | * 181 | * @return 裁剪好的bitmap。 182 | */ 183 | @SuppressWarnings("ResultOfMethodCallIgnored") 184 | private Bitmap crop(File outputFile, File imageFile, int rotation) { 185 | try { 186 | // BitmapRegionDecoder不会将整个图片加载到内存。 187 | BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(imageFile.getAbsolutePath(), true); 188 | 189 | Rect previewFrame = cameraControl.getPreviewFrame(); 190 | 191 | int width = rotation % 180 == 0 ? decoder.getWidth() : decoder.getHeight(); 192 | int height = rotation % 180 == 0 ? decoder.getHeight() : decoder.getWidth(); 193 | 194 | Rect frameRect = maskView.getFrameRect(); 195 | 196 | int left = width * frameRect.left / maskView.getWidth(); 197 | int top = height * frameRect.top / maskView.getHeight(); 198 | int right = width * frameRect.right / maskView.getWidth(); 199 | int bottom = height * frameRect.bottom / maskView.getHeight(); 200 | 201 | // 高度大于图片 202 | if (previewFrame.top < 0) { 203 | // 宽度对齐。 204 | int adjustedPreviewHeight = previewFrame.height() * getWidth() / previewFrame.width(); 205 | int topInFrame = ((adjustedPreviewHeight - frameRect.height()) / 2) 206 | * getWidth() / previewFrame.width(); 207 | int bottomInFrame = ((adjustedPreviewHeight + frameRect.height()) / 2) * getWidth() 208 | / previewFrame.width(); 209 | 210 | // 等比例投射到照片当中。 211 | top = topInFrame * height / previewFrame.height(); 212 | bottom = bottomInFrame * height / previewFrame.height(); 213 | } else { 214 | // 宽度大于图片 215 | if (previewFrame.left < 0) { 216 | // 高度对齐 217 | int adjustedPreviewWidth = previewFrame.width() * getHeight() / previewFrame.height(); 218 | int leftInFrame = ((adjustedPreviewWidth - maskView.getFrameRect().width()) / 2) * getHeight() 219 | / previewFrame.height(); 220 | int rightInFrame = ((adjustedPreviewWidth + maskView.getFrameRect().width()) / 2) * getHeight() 221 | / previewFrame.height(); 222 | 223 | // 等比例投射到照片当中。 224 | left = leftInFrame * width / previewFrame.width(); 225 | right = rightInFrame * width / previewFrame.width(); 226 | } 227 | } 228 | 229 | Rect region = new Rect(); 230 | region.left = left; 231 | region.top = top; 232 | region.right = right; 233 | region.bottom = bottom; 234 | 235 | // 90度或者270度旋转 236 | if (rotation % 180 == 90) { 237 | int x = decoder.getWidth() / 2; 238 | int y = decoder.getHeight() / 2; 239 | 240 | int rotatedWidth = region.height(); 241 | int rotated = region.width(); 242 | 243 | // 计算,裁剪框旋转后的坐标 244 | region.left = x - rotatedWidth / 2; 245 | region.top = y - rotated / 2; 246 | region.right = x + rotatedWidth / 2; 247 | region.bottom = y + rotated / 2; 248 | region.sort(); 249 | } 250 | 251 | BitmapFactory.Options options = new BitmapFactory.Options(); 252 | // options.inPreferredConfig = Bitmap.Config.RGB_565; 253 | 254 | // 最大图片大小。 255 | int maxPreviewImageSize = 2560; 256 | int size = Math.min(decoder.getWidth(), decoder.getHeight()); 257 | size = Math.min(size, maxPreviewImageSize); 258 | 259 | options.inSampleSize = ImageUtil.calculateInSampleSize(options, size, size); 260 | options.inScaled = true; 261 | options.inDensity = Math.max(options.outWidth, options.outHeight); 262 | options.inTargetDensity = size; 263 | 264 | Bitmap bitmap = decoder.decodeRegion(region, options); 265 | 266 | if (rotation != 0) { 267 | // 只能是裁剪完之后再旋转了。有没有别的更好的方案呢? 268 | Matrix matrix = new Matrix(); 269 | matrix.postRotate(rotation); 270 | Bitmap rotatedBitmap = Bitmap.createBitmap( 271 | bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); 272 | if (bitmap != rotatedBitmap) { 273 | // 有时候 createBitmap会复用对象 274 | bitmap.recycle(); 275 | } 276 | bitmap = rotatedBitmap; 277 | } 278 | 279 | try { 280 | if (!outputFile.exists()) { 281 | outputFile.createNewFile(); 282 | } 283 | FileOutputStream fileOutputStream = new FileOutputStream(outputFile); 284 | bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream); 285 | fileOutputStream.flush(); 286 | fileOutputStream.close(); 287 | return bitmap; 288 | } catch (IOException e) { 289 | e.printStackTrace(); 290 | } 291 | } catch (IOException e) { 292 | e.printStackTrace(); 293 | } 294 | return null; 295 | } 296 | 297 | @Override 298 | protected void onDetachedFromWindow() { 299 | super.onDetachedFromWindow(); 300 | if (cameraViewTakePictureCallback.thread != null) { 301 | cameraViewTakePictureCallback.thread.quit(); 302 | } 303 | } 304 | 305 | private class CameraViewTakePictureCallback implements ICameraControl.OnTakePictureCallback { 306 | 307 | private File file; 308 | private OnTakePictureCallback callback; 309 | 310 | HandlerThread thread = new HandlerThread("cropThread"); 311 | Handler handler; 312 | 313 | { 314 | thread.start(); 315 | handler = new Handler(thread.getLooper()); 316 | } 317 | 318 | @Override 319 | public void onPictureTaken(final byte[] data) { 320 | handler.post(new Runnable() { 321 | @Override 322 | public void run() { 323 | try { 324 | final int rotation = ImageUtil.getOrientation(data); 325 | final File tempFile = File.createTempFile(String.valueOf(System.currentTimeMillis()), "jpg"); 326 | FileOutputStream fileOutputStream = new FileOutputStream(tempFile); 327 | fileOutputStream.write(data); 328 | fileOutputStream.flush(); 329 | fileOutputStream.close(); 330 | handler.post(new Runnable() { 331 | @Override 332 | public void run() { 333 | Bitmap bitmap = crop(file, tempFile, rotation); 334 | callback.onPictureTaken(bitmap); 335 | boolean deleted = tempFile.delete(); 336 | if (!deleted) { 337 | tempFile.deleteOnExit(); 338 | } 339 | } 340 | }); 341 | } catch (IOException e) { 342 | e.printStackTrace(); 343 | } 344 | } 345 | }); 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/CropView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | import android.content.Context; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Canvas; 10 | import android.graphics.Matrix; 11 | import android.graphics.Point; 12 | import android.graphics.Rect; 13 | import android.media.ExifInterface; 14 | import android.util.AttributeSet; 15 | import android.view.GestureDetector; 16 | import android.view.MotionEvent; 17 | import android.view.ScaleGestureDetector; 18 | import android.view.View; 19 | import android.view.WindowManager; 20 | 21 | 22 | import com.gamerole.orcameralib.util.ImageUtil; 23 | 24 | import java.io.IOException; 25 | 26 | public class CropView extends View { 27 | 28 | public CropView(Context context) { 29 | super(context); 30 | init(); 31 | } 32 | 33 | public CropView(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | init(); 36 | } 37 | 38 | public CropView(Context context, AttributeSet attrs, int defStyleAttr) { 39 | super(context, attrs, defStyleAttr); 40 | init(); 41 | } 42 | 43 | public void setFilePath(String path) { 44 | 45 | if (this.bitmap != null && !this.bitmap.isRecycled()) { 46 | this.bitmap.recycle(); 47 | } 48 | 49 | if (path == null) { 50 | return; 51 | } 52 | 53 | BitmapFactory.Options options = new BitmapFactory.Options(); 54 | options.inJustDecodeBounds = true; 55 | Bitmap original = BitmapFactory.decodeFile(path, options); 56 | 57 | try { 58 | ExifInterface exif = new ExifInterface(path); 59 | int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); 60 | Matrix matrix = new Matrix(); 61 | int rotationInDegrees = ImageUtil.exifToDegrees(rotation); 62 | if (rotation != 0f) { 63 | matrix.preRotate(rotationInDegrees); 64 | } 65 | 66 | // 图片太大会导致内存泄露,所以在显示前对图片进行裁剪。 67 | int maxPreviewImageSize = 2560; 68 | 69 | int min = Math.min(options.outWidth, options.outHeight); 70 | min = Math.min(min, maxPreviewImageSize); 71 | 72 | WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 73 | Point screenSize = new Point(); 74 | windowManager.getDefaultDisplay().getSize(screenSize); 75 | min = Math.min(min, screenSize.x * 2 / 3); 76 | 77 | options.inSampleSize = ImageUtil.calculateInSampleSize(options, min, min); 78 | options.inScaled = true; 79 | options.inDensity = options.outWidth; 80 | options.inTargetDensity = min * options.inSampleSize; 81 | 82 | options.inJustDecodeBounds = false; 83 | this.bitmap = BitmapFactory.decodeFile(path, options); 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | this.bitmap = original; 87 | } catch (NullPointerException e) { 88 | e.printStackTrace(); 89 | } 90 | setBitmap(this.bitmap); 91 | } 92 | 93 | private void setBitmap(Bitmap bitmap) { 94 | this.bitmap = bitmap; 95 | matrix.reset(); 96 | centerImage(getWidth(), getHeight()); 97 | rotation = 0; 98 | invalidate(); 99 | } 100 | 101 | @Override 102 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 103 | super.onSizeChanged(w, h, oldw, oldh); 104 | centerImage(w, h); 105 | invalidate(); 106 | } 107 | 108 | public Bitmap crop(Rect frame) { 109 | float scale = getScale(); 110 | 111 | float[] src = new float[] {frame.left, frame.top}; 112 | float[] desc = new float[] {0, 0}; 113 | 114 | Matrix invertedMatrix = new Matrix(); 115 | this.matrix.invert(invertedMatrix); 116 | invertedMatrix.mapPoints(desc, src); 117 | 118 | Matrix matrix = new Matrix(); 119 | 120 | int width = (int) (frame.width() / scale); 121 | int height = (int) (frame.height() / scale); 122 | Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); 123 | Canvas canvas = new Canvas(bitmap); 124 | 125 | Bitmap originalBitmap = this.bitmap; 126 | matrix.postTranslate(-desc[0], -desc[1]); 127 | canvas.drawBitmap(originalBitmap, matrix, null); 128 | return bitmap; 129 | } 130 | 131 | public void setMinimumScale(float setMinimumScale) { 132 | this.setMinimumScale = setMinimumScale; 133 | } 134 | 135 | public void setMaximumScale(float maximumScale) { 136 | this.maximumScale = maximumScale; 137 | } 138 | 139 | private float setMinimumScale = 0.2f; 140 | private float maximumScale = 4.0f; 141 | 142 | private float[] matrixArray = new float[9]; 143 | private Matrix matrix = new Matrix(); 144 | private Bitmap bitmap; 145 | 146 | private GestureDetector gestureDetector; 147 | 148 | private ScaleGestureDetector scaleGestureDetector; 149 | private ScaleGestureDetector.OnScaleGestureListener onScaleGestureListener = 150 | new ScaleGestureDetector.OnScaleGestureListener() { 151 | @Override 152 | public boolean onScale(ScaleGestureDetector detector) { 153 | scale(detector); 154 | return true; 155 | } 156 | 157 | @Override 158 | public boolean onScaleBegin(ScaleGestureDetector detector) { 159 | return true; 160 | } 161 | 162 | @Override 163 | public void onScaleEnd(ScaleGestureDetector detector) { 164 | float scale = detector.getScaleFactor(); 165 | matrix.postScale(scale, scale); 166 | invalidate(); 167 | } 168 | }; 169 | 170 | private void init() { 171 | scaleGestureDetector = new ScaleGestureDetector(getContext(), onScaleGestureListener); 172 | gestureDetector = new GestureDetector(getContext(), new GestureDetector.OnGestureListener() { 173 | @Override 174 | public boolean onDown(MotionEvent e) { 175 | return true; 176 | } 177 | 178 | @Override 179 | public void onShowPress(MotionEvent e) { 180 | } 181 | 182 | @Override 183 | public boolean onSingleTapUp(MotionEvent e) { 184 | return false; 185 | } 186 | 187 | @Override 188 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 189 | translate(distanceX, distanceY); 190 | return true; 191 | } 192 | 193 | @Override 194 | public void onLongPress(MotionEvent e) { 195 | } 196 | 197 | @Override 198 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 199 | return false; 200 | } 201 | }); 202 | } 203 | 204 | int rotation = 0; 205 | 206 | public void rotate(int degrees) { 207 | Matrix matrix = new Matrix(); 208 | 209 | int dx = this.bitmap.getWidth() / 2; 210 | int dy = this.bitmap.getHeight() / 2; 211 | 212 | matrix.postTranslate(-dx, -dy); 213 | matrix.postRotate(degrees); 214 | matrix.postTranslate(dy, dx); 215 | Bitmap scaledBitmap = this.bitmap; 216 | Bitmap rotatedBitmap = Bitmap.createBitmap(scaledBitmap.getHeight(), scaledBitmap.getWidth(), 217 | Bitmap.Config.RGB_565); 218 | Canvas canvas = new Canvas(rotatedBitmap); 219 | canvas.drawBitmap(this.bitmap, matrix, null); 220 | this.bitmap.recycle(); 221 | this.bitmap = rotatedBitmap; 222 | centerImage(getWidth(), getHeight()); 223 | invalidate(); 224 | } 225 | 226 | private void translate(float distanceX, float distanceY) { 227 | matrix.getValues(matrixArray); 228 | float left = matrixArray[Matrix.MTRANS_X]; 229 | float top = matrixArray[Matrix.MTRANS_Y]; 230 | 231 | Rect bound = getRestrictedBound(); 232 | if (bound != null) { 233 | float scale = getScale(); 234 | float right = left + (int) (bitmap.getWidth() / scale); 235 | float bottom = top + (int) (bitmap.getHeight() / scale); 236 | 237 | if (left - distanceX > bound.left) { 238 | distanceX = left - bound.left; 239 | } 240 | if (top - distanceY > bound.top) { 241 | distanceY = top - bound.top; 242 | } 243 | 244 | if (distanceX > 0) { 245 | if (right - distanceX < bound.right) { 246 | distanceX = right - bound.right; 247 | } 248 | } 249 | if (distanceY > 0) { 250 | if (bottom - distanceY < bound.bottom) { 251 | distanceY = bottom - bound.bottom; 252 | } 253 | } 254 | } 255 | matrix.postTranslate(-distanceX, -distanceY); 256 | invalidate(); 257 | } 258 | 259 | private void scale(ScaleGestureDetector detector) { 260 | float scale = detector.getScaleFactor(); 261 | float currentScale = getScale(); 262 | if (currentScale * scale < setMinimumScale) { 263 | scale = setMinimumScale / currentScale; 264 | } 265 | if (currentScale * scale > maximumScale) { 266 | scale = maximumScale / currentScale; 267 | } 268 | matrix.postScale(scale, scale, detector.getFocusX(), detector.getFocusY()); 269 | invalidate(); 270 | } 271 | 272 | private void centerImage(int width, int height) { 273 | if (width <= 0 || height <= 0 || bitmap == null) { 274 | return; 275 | } 276 | float widthRatio = 1.0f * height / this.bitmap.getHeight(); 277 | float heightRatio = 1.0f * width / this.bitmap.getWidth(); 278 | 279 | float ratio = Math.min(widthRatio, heightRatio); 280 | 281 | float dx = (width - this.bitmap.getWidth()) / 2; 282 | float dy = (height - this.bitmap.getHeight()) / 2; 283 | matrix.setTranslate(0, 0); 284 | matrix.setScale(ratio, ratio, bitmap.getWidth() / 2, bitmap.getHeight() / 2); 285 | matrix.postTranslate(dx, dy); 286 | invalidate(); 287 | } 288 | 289 | private float getScale() { 290 | matrix.getValues(matrixArray); 291 | float scale = matrixArray[Matrix.MSCALE_X]; 292 | if (Math.abs(scale) <= 0.1) { 293 | scale = matrixArray[Matrix.MSKEW_X]; 294 | } 295 | return Math.abs(scale); 296 | } 297 | 298 | @Override 299 | protected void onDraw(Canvas canvas) { 300 | super.onDraw(canvas); 301 | if (bitmap != null) { 302 | canvas.drawBitmap(bitmap, matrix, null); 303 | } 304 | } 305 | 306 | @Override 307 | public boolean onTouchEvent(MotionEvent event) { 308 | boolean result = scaleGestureDetector.onTouchEvent(event); 309 | result = gestureDetector.onTouchEvent(event) || result; 310 | return result || super.onTouchEvent(event); 311 | } 312 | 313 | private Rect restrictBound; 314 | 315 | private Rect getRestrictedBound() { 316 | return restrictBound; 317 | } 318 | 319 | public void setRestrictBound(Rect rect) { 320 | this.restrictBound = rect; 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/FrameOverlayView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | 7 | import android.content.Context; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | import android.graphics.PorterDuff; 12 | import android.graphics.PorterDuffXfermode; 13 | import android.graphics.Rect; 14 | import android.graphics.RectF; 15 | import android.util.AttributeSet; 16 | import android.view.GestureDetector; 17 | import android.view.MotionEvent; 18 | import android.view.View; 19 | 20 | import com.gamerole.orcameralib.util.DimensionUtil; 21 | 22 | 23 | public class FrameOverlayView extends View { 24 | 25 | interface OnFrameChangeListener { 26 | void onFrameChange(RectF newFrame); 27 | } 28 | 29 | public Rect getFrameRect() { 30 | Rect rect = new Rect(); 31 | rect.left = (int) frameRect.left; 32 | rect.top = (int) frameRect.top; 33 | rect.right = (int) frameRect.right; 34 | rect.bottom = (int) frameRect.bottom; 35 | return rect; 36 | } 37 | 38 | public FrameOverlayView(Context context) { 39 | super(context); 40 | init(); 41 | } 42 | 43 | public FrameOverlayView(Context context, AttributeSet attrs) { 44 | super(context, attrs); 45 | init(); 46 | } 47 | 48 | public FrameOverlayView(Context context, AttributeSet attrs, int defStyleAttr) { 49 | super(context, attrs, defStyleAttr); 50 | init(); 51 | } 52 | 53 | private GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener() { 54 | @Override 55 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 56 | translate(distanceX, distanceY); 57 | return true; 58 | } 59 | 60 | }; 61 | 62 | private static final int CORNER_LEFT_TOP = 1; 63 | private static final int CORNER_RIGHT_TOP = 2; 64 | private static final int CORNER_RIGHT_BOTTOM = 3; 65 | private static final int CORNER_LEFT_BOTTOM = 4; 66 | 67 | private int currentCorner = -1; 68 | int margin = 20; 69 | int cornerLength = 100; 70 | int cornerLineWidth = 6; 71 | 72 | private int maskColor = Color.argb(180, 0, 0, 0); 73 | 74 | private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 75 | private Paint eraser = new Paint(Paint.ANTI_ALIAS_FLAG); 76 | private GestureDetector gestureDetector; 77 | private RectF touchRect = new RectF(); 78 | private RectF frameRect = new RectF(); 79 | 80 | private OnFrameChangeListener onFrameChangeListener; 81 | 82 | { 83 | setLayerType(View.LAYER_TYPE_SOFTWARE, null); 84 | paint.setColor(Color.WHITE); 85 | paint.setStyle(Paint.Style.STROKE); 86 | paint.setStrokeWidth(6); 87 | 88 | eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 89 | } 90 | 91 | public void setOnFrameChangeListener(OnFrameChangeListener onFrameChangeListener) { 92 | this.onFrameChangeListener = onFrameChangeListener; 93 | } 94 | 95 | private void init() { 96 | gestureDetector = new GestureDetector(getContext(), onGestureListener); 97 | cornerLength = DimensionUtil.dpToPx(18); 98 | cornerLineWidth = DimensionUtil.dpToPx(3); 99 | } 100 | 101 | @Override 102 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 103 | super.onSizeChanged(w, h, oldw, oldh); 104 | resetFrameRect(w, h); 105 | } 106 | 107 | private void resetFrameRect(int w, int h) { 108 | if (shapeType == 1) { 109 | frameRect.left = (int) (w * 0.05); 110 | frameRect.top = (int) (h * 0.25); 111 | } else { 112 | frameRect.left = (int) (w * 0.2); 113 | frameRect.top = (int) (h * 0.2); 114 | } 115 | frameRect.right = w - frameRect.left; 116 | frameRect.bottom = h - frameRect.top; 117 | } 118 | 119 | private int shapeType = 0; 120 | 121 | public void setTypeWide() { 122 | shapeType = 1; 123 | } 124 | 125 | private void translate(float x, float y) { 126 | if (x > 0) { 127 | // moving left; 128 | if (frameRect.left - x < margin) { 129 | x = frameRect.left - margin; 130 | } 131 | } else { 132 | if (frameRect.right - x > getWidth() - margin) { 133 | x = frameRect.right - getWidth() + margin; 134 | } 135 | } 136 | 137 | if (y > 0) { 138 | if (frameRect.top - y < margin) { 139 | y = frameRect.top - margin; 140 | } 141 | } else { 142 | if (frameRect.bottom - y > getHeight() - margin) { 143 | y = frameRect.bottom - getHeight() + margin; 144 | } 145 | } 146 | frameRect.offset(-x, -y); 147 | invalidate(); 148 | } 149 | 150 | @Override 151 | protected void onDraw(Canvas canvas) { 152 | super.onDraw(canvas); 153 | 154 | canvas.drawColor(maskColor); 155 | 156 | paint.setStrokeWidth(DimensionUtil.dpToPx(1)); 157 | canvas.drawRect(frameRect, paint); 158 | canvas.drawRect(frameRect, eraser); 159 | drawCorners(canvas); 160 | } 161 | 162 | private void drawCorners(Canvas canvas) { 163 | paint.setStrokeWidth(cornerLineWidth); 164 | // left top 165 | drawLine(canvas, frameRect.left - cornerLineWidth / 2, frameRect.top, cornerLength, 0); 166 | drawLine(canvas, frameRect.left, frameRect.top, 0, cornerLength); 167 | 168 | // right top 169 | drawLine(canvas, frameRect.right + cornerLineWidth / 2, frameRect.top, -cornerLength, 0); 170 | drawLine(canvas, frameRect.right, frameRect.top, 0, cornerLength); 171 | 172 | // right bottom 173 | drawLine(canvas, frameRect.right, frameRect.bottom, 0, -cornerLength); 174 | drawLine(canvas, frameRect.right + cornerLineWidth / 2, frameRect.bottom, -cornerLength, 0); 175 | 176 | // left bottom 177 | drawLine(canvas, frameRect.left - cornerLineWidth / 2, frameRect.bottom, cornerLength, 0); 178 | drawLine(canvas, frameRect.left, frameRect.bottom, 0, -cornerLength); 179 | } 180 | 181 | private void drawLine(Canvas canvas, float x, float y, int dx, int dy) { 182 | canvas.drawLine(x, y, x + dx, y + dy, paint); 183 | } 184 | 185 | @Override 186 | public boolean onTouchEvent(MotionEvent event) { 187 | boolean result = handleDown(event); 188 | float ex = 60; 189 | RectF rectExtend = new RectF(frameRect.left - ex, frameRect.top - ex, 190 | frameRect.right + ex, frameRect.bottom + ex); 191 | if (!result) { 192 | if (rectExtend.contains(event.getX(), event.getY())) { 193 | gestureDetector.onTouchEvent(event); 194 | return true; 195 | } 196 | } 197 | return result; 198 | } 199 | 200 | private boolean handleDown(MotionEvent event) { 201 | switch (event.getAction()) { 202 | case MotionEvent.ACTION_CANCEL: 203 | case MotionEvent.ACTION_UP: 204 | currentCorner = -1; 205 | break; 206 | case MotionEvent.ACTION_DOWN: { 207 | float radius = cornerLength; 208 | touchRect.set(event.getX() - radius, event.getY() - radius, event.getX() + radius, 209 | event.getY() + radius); 210 | if (touchRect.contains(frameRect.left, frameRect.top)) { 211 | currentCorner = CORNER_LEFT_TOP; 212 | return true; 213 | } 214 | 215 | if (touchRect.contains(frameRect.right, frameRect.top)) { 216 | currentCorner = CORNER_RIGHT_TOP; 217 | return true; 218 | } 219 | 220 | if (touchRect.contains(frameRect.right, frameRect.bottom)) { 221 | currentCorner = CORNER_RIGHT_BOTTOM; 222 | return true; 223 | } 224 | 225 | if (touchRect.contains(frameRect.left, frameRect.bottom)) { 226 | currentCorner = CORNER_LEFT_BOTTOM; 227 | return true; 228 | } 229 | return false; 230 | } 231 | case MotionEvent.ACTION_MOVE: 232 | return handleScale(event); 233 | default: 234 | 235 | } 236 | return false; 237 | } 238 | 239 | private boolean handleScale(MotionEvent event) { 240 | switch (currentCorner) { 241 | case CORNER_LEFT_TOP: 242 | scaleTo(event.getX(), event.getY(), frameRect.right, frameRect.bottom); 243 | return true; 244 | case CORNER_RIGHT_TOP: 245 | scaleTo(frameRect.left, event.getY(), event.getX(), frameRect.bottom); 246 | return true; 247 | case CORNER_RIGHT_BOTTOM: 248 | scaleTo(frameRect.left, frameRect.top, event.getX(), event.getY()); 249 | return true; 250 | case CORNER_LEFT_BOTTOM: 251 | scaleTo(event.getX(), frameRect.top, frameRect.right, event.getY()); 252 | return true; 253 | default: 254 | return false; 255 | } 256 | } 257 | 258 | private void scaleTo(float left, float top, float right, float bottom) { 259 | if (bottom - top < getMinimumFrameHeight()) { 260 | top = frameRect.top; 261 | bottom = frameRect.bottom; 262 | } 263 | if (right - left < getMinimumFrameWidth()) { 264 | left = frameRect.left; 265 | right = frameRect.right; 266 | } 267 | left = Math.max(margin, left); 268 | top = Math.max(margin, top); 269 | right = Math.min(getWidth() - margin, right); 270 | bottom = Math.min(getHeight() - margin, bottom); 271 | 272 | frameRect.set(left, top, right, bottom); 273 | invalidate(); 274 | } 275 | 276 | private void notifyFrameChange() { 277 | if (onFrameChangeListener != null) { 278 | onFrameChangeListener.onFrameChange(frameRect); 279 | } 280 | } 281 | 282 | private float getMinimumFrameWidth() { 283 | return 2.4f * cornerLength; 284 | } 285 | 286 | private float getMinimumFrameHeight() { 287 | return 2.4f * cornerLength; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/ICameraControl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | import android.graphics.Rect; 7 | import android.support.annotation.IntDef; 8 | import android.view.View; 9 | 10 | /** 11 | * Android 5.0 相机的API发生很大的变化。些类屏蔽掉了 api的变化。相机的操作和功能,抽象剥离出来。 12 | */ 13 | public interface ICameraControl { 14 | 15 | /** 16 | * 闪光灯关 {@link #setFlashMode(int)} 17 | */ 18 | int FLASH_MODE_OFF = 0; 19 | /** 20 | * 闪光灯开 {@link #setFlashMode(int)} 21 | */ 22 | int FLASH_MODE_TORCH = 1; 23 | /** 24 | * 闪光灯自动 {@link #setFlashMode(int)} 25 | */ 26 | int FLASH_MODE_AUTO = 2; 27 | 28 | @IntDef({FLASH_MODE_TORCH, FLASH_MODE_OFF, FLASH_MODE_AUTO}) 29 | @interface FlashMode { 30 | 31 | } 32 | 33 | /** 34 | * 照相回调。 35 | */ 36 | interface OnTakePictureCallback { 37 | void onPictureTaken(byte[] data); 38 | } 39 | 40 | /** 41 | * 打开相机。 42 | */ 43 | void start(); 44 | 45 | /** 46 | * 关闭相机 47 | */ 48 | void stop(); 49 | 50 | void pause(); 51 | 52 | void resume(); 53 | 54 | /** 55 | * 相机对应的预览视图。 56 | * @return 预览视图 57 | */ 58 | View getDisplayView(); 59 | 60 | /** 61 | * 看到的预览可能不是照片的全部。返回预览视图的全貌。 62 | * @return 预览视图frame; 63 | */ 64 | Rect getPreviewFrame(); 65 | 66 | /** 67 | * 拍照。结果在回调中获取。 68 | * @param callback 拍照结果回调 69 | */ 70 | void takePicture(OnTakePictureCallback callback); 71 | 72 | /** 73 | * 设置权限回调,当手机没有拍照权限时,可在回调中获取。 74 | * @param callback 权限回调 75 | */ 76 | void setPermissionCallback(PermissionCallback callback); 77 | 78 | /** 79 | * 设置水平方向 80 | * @param displayOrientation 参数值见 {@link CameraView.Orientation} 81 | */ 82 | void setDisplayOrientation(@CameraView.Orientation int displayOrientation); 83 | 84 | /** 85 | * 获取到拍照权限时,调用些函数以继续。 86 | */ 87 | void refreshPermission(); 88 | 89 | /** 90 | * 设置闪光灯状态。 91 | * @param flashMode {@link #FLASH_MODE_TORCH,#FLASH_MODE_OFF,#FLASH_MODE_AUTO} 92 | */ 93 | void setFlashMode(@FlashMode int flashMode); 94 | 95 | /** 96 | * 获取当前闪光灯状态 97 | * @return 当前闪光灯状态 参见 {@link #setFlashMode(int)} 98 | */ 99 | @FlashMode 100 | int getFlashMode(); 101 | } 102 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/MaskView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Path; 11 | import android.graphics.PorterDuff; 12 | import android.graphics.PorterDuffXfermode; 13 | import android.graphics.Rect; 14 | import android.graphics.drawable.Drawable; 15 | import android.os.Build; 16 | import android.support.annotation.IntDef; 17 | import android.support.annotation.RequiresApi; 18 | import android.support.v4.content.res.ResourcesCompat; 19 | import android.util.AttributeSet; 20 | import android.view.View; 21 | 22 | 23 | import java.io.File; 24 | 25 | @SuppressWarnings("unused") 26 | public class MaskView extends View { 27 | 28 | public static final int MASK_TYPE_NONE = 0; 29 | public static final int MASK_TYPE_ID_CARD_FRONT = 1; 30 | public static final int MASK_TYPE_ID_CARD_BACK = 2; 31 | public static final int MASK_TYPE_BANK_CARD = 11; 32 | 33 | @IntDef({MASK_TYPE_NONE, MASK_TYPE_ID_CARD_FRONT, MASK_TYPE_ID_CARD_BACK, MASK_TYPE_BANK_CARD}) 34 | @interface MaskType { 35 | 36 | } 37 | 38 | public void setLineColor(int lineColor) { 39 | this.lineColor = lineColor; 40 | } 41 | 42 | public void setMaskColor(int maskColor) { 43 | this.maskColor = maskColor; 44 | } 45 | 46 | private int lineColor = Color.WHITE; 47 | 48 | private int maskType = MASK_TYPE_ID_CARD_FRONT; 49 | 50 | private int maskColor = Color.argb(100, 0, 0, 0); 51 | 52 | private Paint eraser = new Paint(Paint.ANTI_ALIAS_FLAG); 53 | private Paint pen = new Paint(Paint.ANTI_ALIAS_FLAG); 54 | 55 | private Rect frame = new Rect(); 56 | 57 | private Drawable locatorDrawable; 58 | 59 | public Rect getFrameRect() { 60 | if (maskType == MASK_TYPE_NONE) { 61 | return new Rect(0, 0, getWidth(), getHeight()); 62 | } else { 63 | return new Rect(frame); 64 | } 65 | 66 | } 67 | 68 | public void setMaskType(@MaskType int maskType) { 69 | this.maskType = maskType; 70 | switch (maskType) { 71 | case MASK_TYPE_ID_CARD_FRONT: 72 | locatorDrawable = ResourcesCompat.getDrawable(getResources(), 73 | R.drawable.bd_ocr_id_card_locator_front, null); 74 | break; 75 | case MASK_TYPE_ID_CARD_BACK: 76 | locatorDrawable = ResourcesCompat.getDrawable(getResources(), 77 | R.drawable.bd_ocr_id_card_locator_back, null); 78 | break; 79 | case MASK_TYPE_BANK_CARD: 80 | break; 81 | case MASK_TYPE_NONE: 82 | default: 83 | break; 84 | } 85 | invalidate(); 86 | } 87 | 88 | public int getMaskType() { 89 | return maskType; 90 | } 91 | 92 | public void setOrientation(@CameraView.Orientation int orientation) { 93 | } 94 | 95 | public MaskView(Context context) { 96 | super(context); 97 | init(); 98 | } 99 | 100 | public MaskView(Context context, AttributeSet attrs) { 101 | super(context, attrs); 102 | init(); 103 | } 104 | 105 | public MaskView(Context context, AttributeSet attrs, int defStyleAttr) { 106 | super(context, attrs, defStyleAttr); 107 | init(); 108 | } 109 | 110 | private void init() { 111 | locatorDrawable = ResourcesCompat.getDrawable(getResources(), R.drawable.bd_ocr_id_card_locator_front, null); 112 | } 113 | 114 | @Override 115 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 116 | super.onSizeChanged(w, h, oldw, oldh); 117 | if (w > 0 && h > 0) { 118 | float ratio = h > w ? 0.9f : 0.72f; 119 | 120 | int width = (int) (w * ratio); 121 | int height = width * 400 / 620; 122 | 123 | int left = (w - width) / 2; 124 | int top = (h - height) / 2; 125 | int right = width + left; 126 | int bottom = height + top; 127 | 128 | frame.left = left; 129 | frame.top = top; 130 | frame.right = right; 131 | frame.bottom = bottom; 132 | 133 | } 134 | } 135 | 136 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 137 | @Override 138 | protected void onDraw(Canvas canvas) { 139 | super.onDraw(canvas); 140 | 141 | int width = frame.width(); 142 | int height = frame.height(); 143 | 144 | int left = frame.left; 145 | int top = frame.top; 146 | int right = frame.right; 147 | int bottom = frame.bottom; 148 | 149 | canvas.drawColor(maskColor); 150 | fillRectRound(left, top, right, bottom, 30, 30, false); 151 | canvas.drawPath(path, pen); 152 | canvas.drawPath(path, eraser); 153 | 154 | if (maskType == MASK_TYPE_ID_CARD_FRONT) { 155 | locatorDrawable.setBounds( 156 | (int) (left + 601f / 1006 * width), 157 | (int) (top + (110f / 632) * height), 158 | (int) (left + (963f / 1006) * width), 159 | (int) (top + (476f / 632) * height)); 160 | } else if (maskType == MASK_TYPE_ID_CARD_BACK) { 161 | locatorDrawable.setBounds( 162 | (int) (left + 51f / 1006 * width), 163 | (int) (top + (48f / 632) * height), 164 | (int) (left + (250f / 1006) * width), 165 | (int) (top + (262f / 632) * height)); 166 | } 167 | if (locatorDrawable != null) { 168 | locatorDrawable.draw(canvas); 169 | } 170 | } 171 | 172 | private Path path = new Path(); 173 | 174 | private Path fillRectRound(float left, float top, float right, float bottom, float rx, float ry, boolean 175 | conformToOriginalPost) { 176 | 177 | path.reset(); 178 | if (rx < 0) { 179 | rx = 0; 180 | } 181 | if (ry < 0) { 182 | ry = 0; 183 | } 184 | float width = right - left; 185 | float height = bottom - top; 186 | if (rx > width / 2) { 187 | rx = width / 2; 188 | } 189 | if (ry > height / 2) { 190 | ry = height / 2; 191 | } 192 | float widthMinusCorners = (width - (2 * rx)); 193 | float heightMinusCorners = (height - (2 * ry)); 194 | 195 | path.moveTo(right, top + ry); 196 | path.rQuadTo(0, -ry, -rx, -ry); 197 | path.rLineTo(-widthMinusCorners, 0); 198 | path.rQuadTo(-rx, 0, -rx, ry); 199 | path.rLineTo(0, heightMinusCorners); 200 | 201 | if (conformToOriginalPost) { 202 | path.rLineTo(0, ry); 203 | path.rLineTo(width, 0); 204 | path.rLineTo(0, -ry); 205 | } else { 206 | path.rQuadTo(0, ry, rx, ry); 207 | path.rLineTo(widthMinusCorners, 0); 208 | path.rQuadTo(rx, 0, rx, -ry); 209 | } 210 | 211 | path.rLineTo(0, -heightMinusCorners); 212 | path.close(); 213 | return path; 214 | } 215 | 216 | { 217 | // 硬件加速不支持,图层混合。 218 | setLayerType(View.LAYER_TYPE_SOFTWARE, null); 219 | 220 | pen.setColor(Color.WHITE); 221 | pen.setStyle(Paint.Style.STROKE); 222 | pen.setStrokeWidth(6); 223 | 224 | eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 225 | } 226 | 227 | private void capture(File file) { 228 | 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/OCRCameraLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | import android.graphics.Rect; 12 | import android.util.AttributeSet; 13 | import android.view.View; 14 | import android.widget.FrameLayout; 15 | 16 | 17 | 18 | public class OCRCameraLayout extends FrameLayout { 19 | 20 | public static int ORIENTATION_PORTRAIT = 0; 21 | public static int ORIENTATION_HORIZONTAL = 1; 22 | 23 | private int orientation = ORIENTATION_PORTRAIT; 24 | private View contentView; 25 | private View centerView; 26 | private View leftDownView; 27 | private View rightUpView; 28 | 29 | private int contentViewId; 30 | private int centerViewId; 31 | private int leftDownViewId; 32 | private int rightUpViewId; 33 | 34 | public void setOrientation(int orientation) { 35 | if (this.orientation == orientation) { 36 | return; 37 | } 38 | this.orientation = orientation; 39 | requestLayout(); 40 | } 41 | 42 | public OCRCameraLayout(Context context) { 43 | super(context); 44 | } 45 | 46 | public OCRCameraLayout(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | parseAttrs(attrs); 49 | } 50 | 51 | public OCRCameraLayout(Context context, AttributeSet attrs, int defStyleAttr) { 52 | super(context, attrs, defStyleAttr); 53 | parseAttrs(attrs); 54 | } 55 | 56 | { 57 | setWillNotDraw(false); 58 | } 59 | 60 | private void parseAttrs(AttributeSet attrs) { 61 | TypedArray a = getContext().getTheme().obtainStyledAttributes( 62 | attrs, 63 | R.styleable.OCRCameraLayout, 64 | 0, 0); 65 | try { 66 | contentViewId = a.getResourceId(R.styleable.OCRCameraLayout_contentView, -1); 67 | centerViewId = a.getResourceId(R.styleable.OCRCameraLayout_centerView, -1); 68 | leftDownViewId = a.getResourceId(R.styleable.OCRCameraLayout_leftDownView, -1); 69 | rightUpViewId = a.getResourceId(R.styleable.OCRCameraLayout_rightUpView, -1); 70 | } finally { 71 | a.recycle(); 72 | } 73 | } 74 | 75 | @Override 76 | protected void onAttachedToWindow() { 77 | super.onAttachedToWindow(); 78 | contentView = findViewById(contentViewId); 79 | if (centerViewId != -1) { 80 | centerView = findViewById(centerViewId); 81 | } 82 | leftDownView = findViewById(leftDownViewId); 83 | rightUpView = findViewById(rightUpViewId); 84 | } 85 | 86 | private Rect backgroundRect = new Rect(); 87 | private Paint paint = new Paint(); 88 | 89 | { 90 | paint.setStyle(Paint.Style.FILL); 91 | paint.setColor(Color.argb(83, 0, 0, 0)); 92 | } 93 | 94 | @Override 95 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 96 | int width = getWidth(); 97 | int height = getHeight(); 98 | int left; 99 | int top; 100 | 101 | MarginLayoutParams leftDownViewLayoutParams = (MarginLayoutParams) leftDownView.getLayoutParams(); 102 | MarginLayoutParams rightUpViewLayoutParams = (MarginLayoutParams) rightUpView.getLayoutParams(); 103 | if (r < b) { 104 | int contentHeight = width * 4 / 3; 105 | int heightLeft = height - contentHeight; 106 | contentView.layout(l, t, r, contentHeight); 107 | 108 | backgroundRect.left = 0; 109 | backgroundRect.top = contentHeight; 110 | backgroundRect.right = width; 111 | backgroundRect.bottom = height; 112 | 113 | // layout centerView; 114 | if (centerView != null) { 115 | left = (width - centerView.getMeasuredWidth()) / 2; 116 | top = contentHeight + (heightLeft - centerView.getMeasuredHeight()) / 2; 117 | centerView 118 | .layout(left, top, left + centerView.getMeasuredWidth(), top + centerView.getMeasuredHeight()); 119 | } 120 | // layout leftDownView 121 | 122 | left = leftDownViewLayoutParams.leftMargin; 123 | top = contentHeight + (heightLeft - leftDownView.getMeasuredHeight()) / 2; 124 | leftDownView 125 | .layout(left, top, left + leftDownView.getMeasuredWidth(), top + leftDownView.getMeasuredHeight()); 126 | // layout rightUpView 127 | left = width - rightUpView.getMeasuredWidth() - rightUpViewLayoutParams.rightMargin; 128 | top = contentHeight + (heightLeft - rightUpView.getMeasuredHeight()) / 2; 129 | rightUpView.layout(left, top, left + rightUpView.getMeasuredWidth(), top + rightUpView.getMeasuredHeight()); 130 | } else { 131 | int contentWidth = height * 4 / 3; 132 | int widthLeft = width - contentWidth; 133 | contentView.layout(l, t, contentWidth, height); 134 | 135 | backgroundRect.left = contentWidth; 136 | backgroundRect.top = 0; 137 | backgroundRect.right = width; 138 | backgroundRect.bottom = height; 139 | 140 | // layout centerView 141 | if (centerView != null) { 142 | left = contentWidth + (widthLeft - centerView.getMeasuredWidth()) / 2; 143 | top = (height - centerView.getMeasuredHeight()) / 2; 144 | centerView 145 | .layout(left, top, left + centerView.getMeasuredWidth(), top + centerView.getMeasuredHeight()); 146 | } 147 | // layout leftDownView 148 | left = contentWidth + (widthLeft - leftDownView.getMeasuredWidth()) / 2; 149 | top = height - leftDownView.getMeasuredHeight() - leftDownViewLayoutParams.bottomMargin; 150 | leftDownView 151 | .layout(left, top, left + leftDownView.getMeasuredWidth(), top + leftDownView.getMeasuredHeight()); 152 | // layout rightUpView 153 | left = contentWidth + (widthLeft - rightUpView.getMeasuredWidth()) / 2; 154 | 155 | top = rightUpViewLayoutParams.topMargin; 156 | rightUpView.layout(left, top, left + rightUpView.getMeasuredWidth(), top + rightUpView.getMeasuredHeight()); 157 | } 158 | } 159 | 160 | @Override 161 | protected void onDraw(Canvas canvas) { 162 | super.onDraw(canvas); 163 | canvas.drawRect(backgroundRect, paint); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/OCRFrameLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | 7 | import android.content.Context; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | public class OCRFrameLayout extends ViewGroup { 13 | 14 | public OCRFrameLayout(Context context) { 15 | super(context); 16 | } 17 | 18 | public OCRFrameLayout(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | parseAttrs(attrs); 21 | } 22 | 23 | public OCRFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | parseAttrs(attrs); 26 | } 27 | 28 | private void parseAttrs(AttributeSet attrs) { 29 | } 30 | 31 | @Override 32 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 33 | int childCount = getChildCount(); 34 | for (int i = 0; i < childCount; i++) { 35 | View view = getChildAt(i); 36 | view.layout(l, t, r, b); 37 | } 38 | } 39 | 40 | @Override 41 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 42 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 43 | int width; 44 | int height; 45 | int childCount = getChildCount(); 46 | 47 | width = MeasureSpec.getSize(widthMeasureSpec); 48 | height = MeasureSpec.getSize(heightMeasureSpec); 49 | for (int i = 0; i < childCount; i++) { 50 | View view = getChildAt(i); 51 | measureChild(view, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec 52 | (height, MeasureSpec.EXACTLY)); 53 | } 54 | setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/PermissionCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib; 5 | 6 | public interface PermissionCallback { 7 | boolean onRequestPermission(); 8 | } 9 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/util/DimensionUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib.util; 5 | 6 | import android.content.res.Resources; 7 | 8 | public class DimensionUtil { 9 | 10 | public static int dpToPx(int dp) { 11 | return (int) (dp * Resources.getSystem().getDisplayMetrics().density); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /orcameralib/src/main/java/com/gamerole/orcameralib/util/ImageUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. 3 | */ 4 | package com.gamerole.orcameralib.util; 5 | 6 | import android.content.Context; 7 | import android.graphics.BitmapFactory; 8 | import android.media.ExifInterface; 9 | import android.util.Log; 10 | import android.view.animation.Animation; 11 | import android.view.animation.AnimationUtils; 12 | import android.view.animation.LinearInterpolator; 13 | import android.widget.ImageView; 14 | 15 | import com.gamerole.orcameralib.R; 16 | 17 | 18 | public class ImageUtil { 19 | private static final String TAG = "CameraExif"; 20 | 21 | public static int exifToDegrees(int exifOrientation) { 22 | if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { 23 | return 90; 24 | } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) { 25 | return 180; 26 | } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) { 27 | return 270; 28 | } 29 | return 0; 30 | } 31 | 32 | // Returns the degrees in clockwise. Values are 0, 90, 180, or 270. 33 | public static int getOrientation(byte[] jpeg) { 34 | if (jpeg == null) { 35 | return 0; 36 | } 37 | 38 | int offset = 0; 39 | int length = 0; 40 | 41 | // ISO/IEC 10918-1:1993(E) 42 | while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) { 43 | int marker = jpeg[offset] & 0xFF; 44 | 45 | // Check if the marker is a padding. 46 | if (marker == 0xFF) { 47 | continue; 48 | } 49 | offset++; 50 | 51 | // Check if the marker is SOI or TEM. 52 | if (marker == 0xD8 || marker == 0x01) { 53 | continue; 54 | } 55 | // Check if the marker is EOI or SOS. 56 | if (marker == 0xD9 || marker == 0xDA) { 57 | break; 58 | } 59 | 60 | // Get the length and check if it is reasonable. 61 | length = pack(jpeg, offset, 2, false); 62 | if (length < 2 || offset + length > jpeg.length) { 63 | Log.e(TAG, "Invalid length"); 64 | return 0; 65 | } 66 | 67 | // Break if the marker is EXIF in APP1. 68 | if (marker == 0xE1 && length >= 8 69 | && pack(jpeg, offset + 2, 4, false) == 0x45786966 70 | && pack(jpeg, offset + 6, 2, false) == 0) { 71 | offset += 8; 72 | length -= 8; 73 | break; 74 | } 75 | 76 | // Skip other markers. 77 | offset += length; 78 | length = 0; 79 | } 80 | 81 | // JEITA CP-3451 Exif Version 2.2 82 | if (length > 8) { 83 | // Identify the byte order. 84 | int tag = pack(jpeg, offset, 4, false); 85 | if (tag != 0x49492A00 && tag != 0x4D4D002A) { 86 | Log.e(TAG, "Invalid byte order"); 87 | return 0; 88 | } 89 | boolean littleEndian = (tag == 0x49492A00); 90 | 91 | // Get the offset and check if it is reasonable. 92 | int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; 93 | if (count < 10 || count > length) { 94 | Log.e(TAG, "Invalid offset"); 95 | return 0; 96 | } 97 | offset += count; 98 | length -= count; 99 | 100 | // Get the count and go through all the elements. 101 | count = pack(jpeg, offset - 2, 2, littleEndian); 102 | while (count-- > 0 && length >= 12) { 103 | // Get the tag and check if it is orientation. 104 | tag = pack(jpeg, offset, 2, littleEndian); 105 | if (tag == 0x0112) { 106 | // We do not really care about type and count, do we? 107 | int orientation = pack(jpeg, offset + 8, 2, littleEndian); 108 | switch (orientation) { 109 | case 1: 110 | return 0; 111 | case 3: 112 | return 180; 113 | case 6: 114 | return 90; 115 | case 8: 116 | return 270; 117 | default: 118 | return 0; 119 | } 120 | } 121 | offset += 12; 122 | length -= 12; 123 | } 124 | } 125 | 126 | Log.i(TAG, "Orientation not found"); 127 | return 0; 128 | } 129 | 130 | private static int pack(byte[] bytes, int offset, int length, 131 | boolean littleEndian) { 132 | int step = 1; 133 | if (littleEndian) { 134 | offset += length - 1; 135 | step = -1; 136 | } 137 | 138 | int value = 0; 139 | while (length-- > 0) { 140 | value = (value << 8) | (bytes[offset] & 0xFF); 141 | offset += step; 142 | } 143 | return value; 144 | } 145 | 146 | public static int calculateInSampleSize( 147 | BitmapFactory.Options options, int reqWidth, int reqHeight) { 148 | // Raw height and width of image 149 | final int height = options.outHeight; 150 | final int width = options.outWidth; 151 | int inSampleSize = 1; 152 | 153 | if (height > reqHeight || width > reqWidth) { 154 | 155 | final int halfHeight = height / 2; 156 | final int halfWidth = width / 2; 157 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 158 | // height and width larger than the requested height and width. 159 | while ((halfHeight / inSampleSize) >= reqHeight 160 | && (halfWidth / inSampleSize) >= reqWidth) { 161 | inSampleSize *= 2; 162 | } 163 | } 164 | 165 | return inSampleSize; 166 | } 167 | /** 168 | * 将图片旋转一圈 169 | * @param context 170 | * @param ivSwap 171 | */ 172 | public static void rotateImageOnce(Context context, ImageView ivSwap) { 173 | Animation operatingAnim = AnimationUtils.loadAnimation(context, 174 | R.anim.image_rotate_once); 175 | LinearInterpolator lin = new LinearInterpolator(); 176 | operatingAnim.setInterpolator(lin); 177 | if (operatingAnim != null) { 178 | ivSwap.startAnimation(operatingAnim); 179 | } 180 | } 181 | 182 | 183 | 184 | } 185 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/anim/image_rotate_once.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_cancel.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_close.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_confirm.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_gallery.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_hint_align_bank_card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_hint_align_bank_card.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_hint_align_id_card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_hint_align_id_card.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_hint_align_id_card_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_hint_align_id_card_back.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_id_card_locator_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_id_card_locator_back.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_id_card_locator_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_id_card_locator_front.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_light_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_light_off.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_light_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_light_on.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_rotate.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_take_photo_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_take_photo_highlight.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_take_photo_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvzhihao100/ORCamera/e3fa42c80b21fd2b2c66b1ac1439fa5ba44c599b/orcameralib/src/main/res/drawable/bd_ocr_take_photo_normal.png -------------------------------------------------------------------------------- /orcameralib/src/main/res/drawable/bd_ocr_take_photo_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/layout/bd_ocr_activity_camera.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 13 | 14 | 20 | 21 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/layout/bd_ocr_confirm_result.xml: -------------------------------------------------------------------------------- 1 | 4 | 11 | 12 | 18 | 19 | 28 | 29 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/layout/bd_ocr_crop.xml: -------------------------------------------------------------------------------- 1 | 4 | 12 | 13 | 17 | 18 | 22 | 23 | 27 | 28 | 32 | 33 | 34 | 35 | 41 | 42 | 51 | 52 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/layout/bd_ocr_take_picture.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 14 | 15 | 19 | 20 | 28 | 29 | 30 | 39 | 40 | 41 | 47 | 48 | 49 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/values/bd_ocr_dimensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 18dp 7 | 18dp 8 | 16dp 9 | 16dp 10 | 11 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/values/bd_ocr_widgets.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /orcameralib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 本功能需要相机权限! 4 | 5 | 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':orcameralib' 2 | --------------------------------------------------------------------------------