Commit 2e070f771351333af5cc3022df3cce36d23b26fa
1 parent
88aea392
身份证采集页面UI开发
Showing
76 changed files
with
5151 additions
and
168 deletions
.idea/misc.xml
| @@ -39,7 +39,7 @@ | @@ -39,7 +39,7 @@ | ||
| 39 | </value> | 39 | </value> |
| 40 | </option> | 40 | </option> |
| 41 | </component> | 41 | </component> |
| 42 | - <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | 42 | + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK"> |
| 43 | <output url="file://$PROJECT_DIR$/build/classes" /> | 43 | <output url="file://$PROJECT_DIR$/build/classes" /> |
| 44 | </component> | 44 | </component> |
| 45 | <component name="ProjectType"> | 45 | <component name="ProjectType"> |
app/strike/build.gradle
| @@ -187,5 +187,6 @@ dependencies { | @@ -187,5 +187,6 @@ dependencies { | ||
| 187 | // implementation project(path: ':core:encipher') | 187 | // implementation project(path: ':core:encipher') |
| 188 | // implementation project(path: ':cloud:user') | 188 | // implementation project(path: ':cloud:user') |
| 189 | implementation project(path: ':cloud:wjj_lib') | 189 | implementation project(path: ':cloud:wjj_lib') |
| 190 | + implementation project(path: ':core:dentity_verfication') | ||
| 190 | 191 | ||
| 191 | } | 192 | } |
app/strike/src/main/AndroidManifest.xml
| @@ -17,7 +17,8 @@ | @@ -17,7 +17,8 @@ | ||
| 17 | android:roundIcon="@mipmap/ic_launcher_round" | 17 | android:roundIcon="@mipmap/ic_launcher_round" |
| 18 | android:supportsRtl="true" | 18 | android:supportsRtl="true" |
| 19 | android:theme="@style/AppTheme"> | 19 | android:theme="@style/AppTheme"> |
| 20 | - <activity android:name="com.cnlive.strike.imgutil.SimplePrevActivity"></activity> | 20 | + <activity android:name="com.cnlive.strike.imgutil.UgcActivity"></activity> |
| 21 | + <activity android:name="com.cnlive.strike.imgutil.SimplePrevActivity" /> | ||
| 21 | <activity android:name="com.cnlive.strike.demo.activity.HappyChinaActivity" /> | 22 | <activity android:name="com.cnlive.strike.demo.activity.HappyChinaActivity" /> |
| 22 | 23 | ||
| 23 | <provider | 24 | <provider |
| @@ -54,7 +55,6 @@ | @@ -54,7 +55,6 @@ | ||
| 54 | </intent-filter> | 55 | </intent-filter> |
| 55 | </activity> | 56 | </activity> |
| 56 | <activity android:name="com.cnlive.strike.demo.activity.DemoMvpActivity" /> | 57 | <activity android:name="com.cnlive.strike.demo.activity.DemoMvpActivity" /> |
| 57 | - | ||
| 58 | <activity android:name="com.cnlive.strike.demo.activity.DemoBottomSheetActivity" /> | 58 | <activity android:name="com.cnlive.strike.demo.activity.DemoBottomSheetActivity" /> |
| 59 | 59 | ||
| 60 | <meta-data | 60 | <meta-data |
app/strike/src/main/java/com/cnlive/strike/MainActivity.java
| @@ -10,6 +10,7 @@ import android.view.View; | @@ -10,6 +10,7 @@ import android.view.View; | ||
| 10 | import com.cnlive.cloud.R; | 10 | import com.cnlive.cloud.R; |
| 11 | import com.cnlive.strike.demo.activity.DemoMvpActivity; | 11 | import com.cnlive.strike.demo.activity.DemoMvpActivity; |
| 12 | import com.cnlive.strike.imgutil.ImgActivity; | 12 | import com.cnlive.strike.imgutil.ImgActivity; |
| 13 | +import com.cnlive.strike.imgutil.UgcActivity; | ||
| 13 | 14 | ||
| 14 | import androidx.annotation.NonNull; | 15 | import androidx.annotation.NonNull; |
| 15 | import androidx.annotation.Nullable; | 16 | import androidx.annotation.Nullable; |
| @@ -30,6 +31,7 @@ public class MainActivity extends AppCompatActivity { | @@ -30,6 +31,7 @@ public class MainActivity extends AppCompatActivity { | ||
| 30 | } | 31 | } |
| 31 | }); | 32 | }); |
| 32 | findViewById(R.id.to_img).setOnClickListener(view -> startActivity(new Intent(this, ImgActivity.class))); | 33 | findViewById(R.id.to_img).setOnClickListener(view -> startActivity(new Intent(this, ImgActivity.class))); |
| 34 | + findViewById(R.id.ugc_btn).setOnClickListener(view -> startActivity(new Intent(this, UgcActivity.class))); | ||
| 33 | } | 35 | } |
| 34 | 36 | ||
| 35 | @Nullable | 37 | @Nullable |
app/strike/src/main/java/com/cnlive/strike/imgutil/UgcActivity.java
0 → 100644
| 1 | +package com.cnlive.strike.imgutil; | ||
| 2 | + | ||
| 3 | +import android.content.Intent; | ||
| 4 | +import android.os.Bundle; | ||
| 5 | + | ||
| 6 | +import com.cnlive.cloud.R; | ||
| 7 | +import com.cnlive.core.people_verfication.activity.TakeIdentityPictureActivity; | ||
| 8 | + | ||
| 9 | +import androidx.appcompat.app.AppCompatActivity; | ||
| 10 | + | ||
| 11 | +public class UgcActivity extends AppCompatActivity { | ||
| 12 | + | ||
| 13 | + @Override | ||
| 14 | + protected void onCreate(Bundle savedInstanceState) { | ||
| 15 | + super.onCreate(savedInstanceState); | ||
| 16 | + setContentView(R.layout.activity_ugc); | ||
| 17 | + findViewById(R.id.people_denty).setOnClickListener(view -> startActivity(new Intent(this, TakeIdentityPictureActivity.class))); | ||
| 18 | + } | ||
| 19 | +} |
app/strike/src/main/res/layout/activity_main.xml
| @@ -20,5 +20,12 @@ | @@ -20,5 +20,12 @@ | ||
| 20 | android:layout_width="wrap_content" | 20 | android:layout_width="wrap_content" |
| 21 | android:layout_height="wrap_content" | 21 | android:layout_height="wrap_content" |
| 22 | android:text="图片加载测试"/> | 22 | android:text="图片加载测试"/> |
| 23 | + | ||
| 24 | + <Button | ||
| 25 | + android:id="@+id/ugc_btn" | ||
| 26 | + android:layout_width="wrap_content" | ||
| 27 | + android:layout_height="wrap_content" | ||
| 28 | + android:text="ugc直播认证" | ||
| 29 | + /> | ||
| 23 | </LinearLayout> | 30 | </LinearLayout> |
| 24 | </layout> | 31 | </layout> |
| 25 | \ No newline at end of file | 32 | \ No newline at end of file |
app/strike/src/main/res/layout/activity_ugc.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | + xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | + xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | + android:layout_width="match_parent" | ||
| 6 | + android:layout_height="match_parent" | ||
| 7 | + tools:context="com.cnlive.strike.imgutil.UgcActivity"> | ||
| 8 | + | ||
| 9 | + <Button | ||
| 10 | + android:id="@+id/people_denty" | ||
| 11 | + android:layout_width="wrap_content" | ||
| 12 | + android:layout_height="wrap_content" | ||
| 13 | + android:text="个人认证" | ||
| 14 | + /> | ||
| 15 | +</LinearLayout> |
core/base/src/main/res/drawable-hdpi/top_back_black.webp
0 → 100644
No preview for this file type
core/base/src/main/res/drawable-xhdpi/top_back_black.webp
0 → 100644
No preview for this file type
core/base/src/main/res/drawable-xxhdpi/top_back_black.webp
0 → 100644
No preview for this file type
core/base/src/main/res/values/ids.xml
| @@ -7,4 +7,5 @@ | @@ -7,4 +7,5 @@ | ||
| 7 | <item name="isexpand" type="id"></item> | 7 | <item name="isexpand" type="id"></item> |
| 8 | <!-- --> | 8 | <!-- --> |
| 9 | <item name="rightBtn" type="id" /> | 9 | <item name="rightBtn" type="id" /> |
| 10 | + <item name="fragment_back" type="id" /> | ||
| 10 | </resources> | 11 | </resources> |
| 11 | \ No newline at end of file | 12 | \ No newline at end of file |
core/dentity_verfication/build.gradle
| @@ -5,11 +5,12 @@ android { | @@ -5,11 +5,12 @@ android { | ||
| 5 | } | 5 | } |
| 6 | 6 | ||
| 7 | dependencies { | 7 | dependencies { |
| 8 | - implementation fileTree(dir: 'libs', include: ['*.jar']) | ||
| 9 | - | 8 | + implementation fileTree(include: ['*.jar'], dir: 'libs') |
| 10 | implementation 'androidx.appcompat:appcompat:1.0.2' | 9 | implementation 'androidx.appcompat:appcompat:1.0.2' |
| 11 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' | 10 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' |
| 12 | testImplementation 'junit:junit:4.12' | 11 | testImplementation 'junit:junit:4.12' |
| 13 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' | 12 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' |
| 14 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' | 13 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' |
| 14 | + implementation "com.cnlive.libs:mediapicker:$rootProject.libMediaPicker" | ||
| 15 | + implementation files('libs/license.jar') | ||
| 15 | } | 16 | } |
core/dentity_verfication/libs/license.jar
0 → 100644
No preview for this file type
core/dentity_verfication/libs/ocr-sdk.jar
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/AndroidManifest.xml
| @@ -2,8 +2,23 @@ | @@ -2,8 +2,23 @@ | ||
| 2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | package="com.cnlive.core.people_verfication"> | 3 | package="com.cnlive.core.people_verfication"> |
| 4 | 4 | ||
| 5 | + <uses-permission android:name="android.permission.CAMERA" /> | ||
| 6 | + <uses-permission android:name="android.permission.INTERNET" /> | ||
| 7 | + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | ||
| 8 | + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||
| 9 | + | ||
| 10 | + <uses-feature android:name="android.hardware.camera2.full" /> | ||
| 11 | + <uses-feature android:name="android.hardware.camera" /> | ||
| 12 | + <uses-feature android:name="android.hardware.camera.autofocus" /> | ||
| 13 | + | ||
| 5 | <application> | 14 | <application> |
| 6 | - <activity android:name=".TakePictureActivity"></activity> | 15 | + <activity android:name=".activity.TakeIdentityPictureActivity"></activity> |
| 16 | + <activity | ||
| 17 | + android:name="com.cnlive.core.ocr.ui.camera.CameraActivity" | ||
| 18 | + android:configChanges="screenSize|orientation" | ||
| 19 | + android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" | ||
| 20 | + android:launchMode="singleTop" | ||
| 21 | + android:screenOrientation="portrait"/> | ||
| 7 | </application> | 22 | </application> |
| 8 | 23 | ||
| 9 | </manifest> | 24 | </manifest> |
| 10 | \ No newline at end of file | 25 | \ No newline at end of file |
core/dentity_verfication/src/main/assets/models/integrity_model_secret.bin
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/assets/models/quality_model_secret.bin
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/RecognizeService.java
0 → 100644
| 1 | +package com.cnlive.core.ocr; | ||
| 2 | + | ||
| 3 | +import android.content.Context; | ||
| 4 | +import android.util.Log; | ||
| 5 | + | ||
| 6 | +import com.baidu.ocr.sdk.OCR; | ||
| 7 | +import com.baidu.ocr.sdk.OnResultListener; | ||
| 8 | +import com.baidu.ocr.sdk.exception.OCRError; | ||
| 9 | +import com.baidu.ocr.sdk.model.AccessToken; | ||
| 10 | +import com.baidu.ocr.sdk.model.BankCardParams; | ||
| 11 | +import com.baidu.ocr.sdk.model.BankCardResult; | ||
| 12 | +import com.baidu.ocr.sdk.model.IDCardParams; | ||
| 13 | +import com.baidu.ocr.sdk.model.IDCardResult; | ||
| 14 | + | ||
| 15 | +import java.io.File; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * @author Lynn | ||
| 19 | + * @date 2018/1/4 | ||
| 20 | + */ | ||
| 21 | + | ||
| 22 | +public class RecognizeService { | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * 识别银行卡 | ||
| 26 | + * | ||
| 27 | + * @param filePath 文件路径 | ||
| 28 | + * @param listener 回调 | ||
| 29 | + */ | ||
| 30 | + public static void recBankCard(String filePath, OnResultListener<BankCardResult> listener) { | ||
| 31 | + BankCardParams params = new BankCardParams(); | ||
| 32 | + params.setImageFile(new File(filePath)); | ||
| 33 | + OCR.getInstance().recognizeBankCard(params, listener); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 识别身份证 | ||
| 38 | + * | ||
| 39 | + * @param idCardSide 身份证正反面 | ||
| 40 | + * @param filePath 文件路径 | ||
| 41 | + * @param listener 回调 | ||
| 42 | + */ | ||
| 43 | + public static void recIDCard(String idCardSide, String filePath, OnResultListener<IDCardResult> listener) { | ||
| 44 | + IDCardParams param = new IDCardParams(); | ||
| 45 | + param.setImageFile(new File(filePath)); | ||
| 46 | + // 设置身份证正反面 | ||
| 47 | + param.setIdCardSide(idCardSide); | ||
| 48 | + // 设置方向检测 | ||
| 49 | + param.setDetectDirection(true); | ||
| 50 | + // 设置图像参数压缩质量0-100, 越大图像质量越好但是请求时间越长。 不设置则默认值为20 | ||
| 51 | + param.setImageQuality(20); | ||
| 52 | + OCR.getInstance().recognizeIDCard(param, listener); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * 初始化 with AK & SK | ||
| 57 | + * | ||
| 58 | + * @param context context | ||
| 59 | + */ | ||
| 60 | + public static void initAccessTokenWithAkSk(final Context context) { | ||
| 61 | + OCR.getInstance().initAccessTokenWithAkSk(new OnResultListener<AccessToken>() { | ||
| 62 | + @Override | ||
| 63 | + public void onResult(AccessToken result) { | ||
| 64 | + String token = result.getAccessToken(); | ||
| 65 | + Log.e("LynnTest", result.toString()); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + @Override | ||
| 69 | + public void onError(OCRError error) { | ||
| 70 | + error.printStackTrace(); | ||
| 71 | + Log.e("LynnTest", error.getMessage()); | ||
| 72 | + } | ||
| 73 | + }, context, "YIGsVzdh35y3GCaNUDsxM3cC", "neAe1orXgeTaiHkAbygrgCevKe1fPrG1"); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + /** | ||
| 77 | + * 初始化 授权文件(安全模式) | ||
| 78 | + * | ||
| 79 | + * @param context | ||
| 80 | + */ | ||
| 81 | + public static void initAccessToken(Context context) { | ||
| 82 | + OCR.getInstance().initAccessToken(new OnResultListener<AccessToken>() { | ||
| 83 | + @Override | ||
| 84 | + public void onResult(AccessToken accessToken) { | ||
| 85 | + String token = accessToken.getAccessToken(); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + @Override | ||
| 89 | + public void onError(OCRError ocrError) { | ||
| 90 | + | ||
| 91 | + } | ||
| 92 | + }, context); | ||
| 93 | + } | ||
| 94 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/idcardquality/IDcardQualityProcess.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.idcardquality; | ||
| 5 | + | ||
| 6 | +import android.content.res.AssetManager; | ||
| 7 | +import android.graphics.Bitmap; | ||
| 8 | + | ||
| 9 | +import com.baidu.idl.authority.AlgorithmOnMainThreadException; | ||
| 10 | +import com.baidu.idl.authority.IDLAuthorityException; | ||
| 11 | +import com.baidu.idl.license.License; | ||
| 12 | +import com.baidu.idl.util.UIThread; | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +public class IDcardQualityProcess { | ||
| 16 | + | ||
| 17 | + private static IDcardQualityProcess mInstance; | ||
| 18 | + private static String tokenString; | ||
| 19 | + private static int mAuthorityStatus; | ||
| 20 | + private static Throwable loadNativeException = null; | ||
| 21 | + private static volatile boolean hasReleased; | ||
| 22 | + | ||
| 23 | + public IDcardQualityProcess() { | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + public static synchronized IDcardQualityProcess getInstance() { | ||
| 27 | + if (null == mInstance) { | ||
| 28 | + mInstance = new IDcardQualityProcess(); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + return mInstance; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + public native byte[] convertRGBImage(int[] colors, int width, int height); | ||
| 35 | + | ||
| 36 | + public native int idcardQualityModelInit(AssetManager var1, String var2); | ||
| 37 | + | ||
| 38 | + public native int idcardQualityCaptchaRelease(); | ||
| 39 | + | ||
| 40 | + public native int idcardQualityProcess(byte[] var1, int var2, int var3, boolean var4, int var5); | ||
| 41 | + | ||
| 42 | + public static synchronized int init(String token) throws AlgorithmOnMainThreadException, IDLAuthorityException { | ||
| 43 | + if (UIThread.isUITread()) { | ||
| 44 | + throw new AlgorithmOnMainThreadException(); | ||
| 45 | + } else { | ||
| 46 | + tokenString = token; | ||
| 47 | + | ||
| 48 | + try { | ||
| 49 | + mAuthorityStatus = License.getInstance().init(tokenString); | ||
| 50 | + } catch (Exception var2) { | ||
| 51 | + if (var2!=null) | ||
| 52 | + var2.printStackTrace(); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + return mAuthorityStatus; | ||
| 56 | + } | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + public int idcardQualityInit(AssetManager assetManager, String modelPath) { | ||
| 60 | + if (mAuthorityStatus == 0) { | ||
| 61 | + hasReleased = false; | ||
| 62 | + return this.idcardQualityModelInit(assetManager, modelPath); | ||
| 63 | + } else { | ||
| 64 | + return mAuthorityStatus; | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + public int idcardQualityRelease() { | ||
| 69 | + if (mAuthorityStatus == 0) { | ||
| 70 | + hasReleased = true; | ||
| 71 | + this.idcardQualityCaptchaRelease(); | ||
| 72 | + return 0; | ||
| 73 | + } else { | ||
| 74 | + return mAuthorityStatus; | ||
| 75 | + } | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + public int idcardQualityDetectionImg(Bitmap img, boolean isfont) { | ||
| 79 | + if (mAuthorityStatus == 0) { | ||
| 80 | + if (hasReleased) { | ||
| 81 | + return -1; | ||
| 82 | + } | ||
| 83 | + int imgHeight = img.getHeight(); | ||
| 84 | + int imgWidth = img.getWidth(); | ||
| 85 | + byte[] imageData = this.getRGBImageData(img); | ||
| 86 | + return this.idcardQualityProcess(imageData, imgHeight, imgWidth, isfont, 3); | ||
| 87 | + } else { | ||
| 88 | + return mAuthorityStatus; | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + public static Throwable getLoadSoException() { | ||
| 93 | + return loadNativeException; | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + public byte[] getRGBImageData(Bitmap img) { | ||
| 97 | + int imgWidth = img.getWidth(); | ||
| 98 | + int imgHeight = img.getHeight(); | ||
| 99 | + int[] pixels = new int[imgWidth * imgHeight]; | ||
| 100 | + img.getPixels(pixels, 0, imgWidth, 0, 0, imgWidth, imgHeight); | ||
| 101 | + byte[] imageData = convertRGBImage(pixels, imgWidth, imgHeight); | ||
| 102 | + return imageData; | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + public void releaseModel() { | ||
| 106 | + this.idcardQualityRelease(); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + static { | ||
| 110 | + try { | ||
| 111 | + System.loadLibrary("idl_license"); | ||
| 112 | + System.loadLibrary("idcard_quality.1.1.1"); | ||
| 113 | + } catch (Throwable e) { | ||
| 114 | + loadNativeException = e; | ||
| 115 | + } | ||
| 116 | + mInstance = null; | ||
| 117 | + mAuthorityStatus = 256; | ||
| 118 | + } | ||
| 119 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/Camera1Control.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.camera; | ||
| 5 | + | ||
| 6 | +import android.Manifest; | ||
| 7 | +import android.content.Context; | ||
| 8 | +import android.content.pm.FeatureInfo; | ||
| 9 | +import android.content.pm.PackageManager; | ||
| 10 | +import android.graphics.ImageFormat; | ||
| 11 | +import android.graphics.Rect; | ||
| 12 | +import android.graphics.SurfaceTexture; | ||
| 13 | +import android.graphics.YuvImage; | ||
| 14 | +import android.hardware.Camera; | ||
| 15 | +import android.os.Build; | ||
| 16 | +import android.util.Log; | ||
| 17 | +import android.view.TextureView; | ||
| 18 | +import android.view.View; | ||
| 19 | +import android.widget.FrameLayout; | ||
| 20 | + | ||
| 21 | +import com.cnlive.core.libs.base.util.AlertUtil; | ||
| 22 | + | ||
| 23 | +import java.io.ByteArrayOutputStream; | ||
| 24 | +import java.io.IOException; | ||
| 25 | +import java.util.ArrayList; | ||
| 26 | +import java.util.Collections; | ||
| 27 | +import java.util.Comparator; | ||
| 28 | +import java.util.List; | ||
| 29 | +import java.util.concurrent.atomic.AtomicBoolean; | ||
| 30 | + | ||
| 31 | +import androidx.core.app.ActivityCompat; | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + * 5.0以下相机API的封装。 | ||
| 35 | + */ | ||
| 36 | +@SuppressWarnings("deprecation") | ||
| 37 | +public class Camera1Control implements ICameraControl { | ||
| 38 | + | ||
| 39 | + private int displayOrientation = 0; | ||
| 40 | + private int cameraId = 0; | ||
| 41 | + private int flashMode; | ||
| 42 | + private AtomicBoolean takingPicture = new AtomicBoolean(false); | ||
| 43 | + private AtomicBoolean abortingScan = new AtomicBoolean(false); | ||
| 44 | + private Context context; | ||
| 45 | + private Camera camera; | ||
| 46 | + | ||
| 47 | + private Camera.Parameters parameters; | ||
| 48 | + private PermissionCallback permissionCallback; | ||
| 49 | + private Rect previewFrame = new Rect(); | ||
| 50 | + | ||
| 51 | + private PreviewView previewView; | ||
| 52 | + private View displayView; | ||
| 53 | + private int rotation = 0; | ||
| 54 | + private OnDetectPictureCallback detectCallback; | ||
| 55 | + private int previewFrameCount = 0; | ||
| 56 | + private Camera.Size optSize; | ||
| 57 | + | ||
| 58 | + /* | ||
| 59 | + * 非扫描模式 | ||
| 60 | + */ | ||
| 61 | + private final int MODEL_NOSCAN = 0; | ||
| 62 | + | ||
| 63 | + /* | ||
| 64 | + * 本地质量控制扫描模式 | ||
| 65 | + */ | ||
| 66 | + private final int MODEL_SCAN = 1; | ||
| 67 | + | ||
| 68 | + private int detectType = MODEL_NOSCAN; | ||
| 69 | + | ||
| 70 | + public int getCameraRotation() { | ||
| 71 | + return rotation; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + public AtomicBoolean getAbortingScan() { | ||
| 75 | + return abortingScan; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + @Override | ||
| 79 | + public void setDetectCallback(OnDetectPictureCallback callback) { | ||
| 80 | + detectType = MODEL_SCAN; | ||
| 81 | + detectCallback = callback; | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + private void onRequestDetect(byte[] data) { | ||
| 85 | + // 相机已经关闭 | ||
| 86 | + if (camera == null || data == null) { | ||
| 87 | + return; | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + YuvImage img = new YuvImage(data, ImageFormat.NV21, optSize.width, optSize.height, null); | ||
| 91 | + ByteArrayOutputStream os = null; | ||
| 92 | + try { | ||
| 93 | + os = new ByteArrayOutputStream(data.length); | ||
| 94 | + img.compressToJpeg(new Rect(0, 0, optSize.width, optSize.height), 80, os); | ||
| 95 | + byte[] jpeg = os.toByteArray(); | ||
| 96 | + int status = detectCallback.onDetect(jpeg, getCameraRotation()); | ||
| 97 | + | ||
| 98 | + if (status == 0) { | ||
| 99 | + clearPreviewCallback(); | ||
| 100 | + } | ||
| 101 | + } catch (OutOfMemoryError e) { | ||
| 102 | + // 内存溢出则取消当次操作 | ||
| 103 | + } finally { | ||
| 104 | + try { | ||
| 105 | + os.close(); | ||
| 106 | + } catch (Throwable e) { | ||
| 107 | + e.printStackTrace(); | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + @Override | ||
| 114 | + public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) { | ||
| 115 | + this.displayOrientation = displayOrientation; | ||
| 116 | + switch (displayOrientation) { | ||
| 117 | + case CameraView.ORIENTATION_PORTRAIT: | ||
| 118 | + rotation = 90; | ||
| 119 | + break; | ||
| 120 | + case CameraView.ORIENTATION_HORIZONTAL: | ||
| 121 | + rotation = 0; | ||
| 122 | + break; | ||
| 123 | + case CameraView.ORIENTATION_INVERT: | ||
| 124 | + rotation = 180; | ||
| 125 | + break; | ||
| 126 | + default: | ||
| 127 | + rotation = 0; | ||
| 128 | + } | ||
| 129 | + previewView.requestLayout(); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + /** | ||
| 133 | + * {@inheritDoc} | ||
| 134 | + */ | ||
| 135 | + @Override | ||
| 136 | + public void refreshPermission() { | ||
| 137 | + startPreview(true); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + /** | ||
| 141 | + * {@inheritDoc} | ||
| 142 | + */ | ||
| 143 | + @Override | ||
| 144 | + public void setFlashMode(@FlashMode int flashMode) { | ||
| 145 | + if (this.flashMode == flashMode) { | ||
| 146 | + return; | ||
| 147 | + } | ||
| 148 | + this.flashMode = flashMode; | ||
| 149 | + updateFlashMode(flashMode); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + @Override | ||
| 153 | + public int getFlashMode() { | ||
| 154 | + return flashMode; | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + @Override | ||
| 158 | + public void start() { | ||
| 159 | + | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + @Override | ||
| 163 | + public void stop() { | ||
| 164 | + if (camera != null) { | ||
| 165 | + camera.setPreviewCallback(null); | ||
| 166 | + stopPreview(); | ||
| 167 | + // 避免同步代码,为了先设置null后release | ||
| 168 | + Camera tempC = camera; | ||
| 169 | + camera = null; | ||
| 170 | + tempC.release(); | ||
| 171 | + camera = null; | ||
| 172 | + buffer = null; | ||
| 173 | + } | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + private void stopPreview() { | ||
| 177 | + if (camera != null) { | ||
| 178 | + camera.stopPreview(); | ||
| 179 | + } | ||
| 180 | + } | ||
| 181 | + | ||
| 182 | + @Override | ||
| 183 | + public void pause() { | ||
| 184 | + if (camera != null) { | ||
| 185 | + stopPreview(); | ||
| 186 | + } | ||
| 187 | + setFlashMode(FLASH_MODE_OFF); | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + @Override | ||
| 191 | + public void resume() { | ||
| 192 | + takingPicture.set(false); | ||
| 193 | + if (camera == null) { | ||
| 194 | + openCamera(); | ||
| 195 | + } else { | ||
| 196 | + previewView.textureView.setSurfaceTextureListener(surfaceTextureListener); | ||
| 197 | + if (previewView.textureView.isAvailable()) { | ||
| 198 | + startPreview(false); | ||
| 199 | + } | ||
| 200 | + } | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + @Override | ||
| 204 | + public View getDisplayView() { | ||
| 205 | + return displayView; | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + @Override | ||
| 209 | + public void takePicture(final OnTakePictureCallback onTakePictureCallback) { | ||
| 210 | + if (takingPicture.get()) { | ||
| 211 | + return; | ||
| 212 | + } | ||
| 213 | + try { | ||
| 214 | + switch (displayOrientation) { | ||
| 215 | + case CameraView.ORIENTATION_PORTRAIT: | ||
| 216 | + parameters.setRotation(90); | ||
| 217 | + break; | ||
| 218 | + case CameraView.ORIENTATION_HORIZONTAL: | ||
| 219 | + parameters.setRotation(0); | ||
| 220 | + break; | ||
| 221 | + case CameraView.ORIENTATION_INVERT: | ||
| 222 | + parameters.setRotation(180); | ||
| 223 | + break; | ||
| 224 | + } | ||
| 225 | + Camera.Size picSize = getOptimalSize(camera.getParameters().getSupportedPictureSizes()); | ||
| 226 | + parameters.setPictureSize(picSize.width, picSize.height); | ||
| 227 | + camera.setParameters(parameters); | ||
| 228 | + takingPicture.set(true); | ||
| 229 | + | ||
| 230 | + camera.takePicture(null, null, new Camera.PictureCallback() { | ||
| 231 | + @Override | ||
| 232 | + public void onPictureTaken(byte[] data, Camera camera) { | ||
| 233 | + startPreview(false); | ||
| 234 | + takingPicture.set(false); | ||
| 235 | + if (onTakePictureCallback != null) { | ||
| 236 | + onTakePictureCallback.onPictureTaken(data); | ||
| 237 | + } | ||
| 238 | + } | ||
| 239 | + }); | ||
| 240 | + } catch (RuntimeException e) { | ||
| 241 | + e.printStackTrace(); | ||
| 242 | + startPreview(false); | ||
| 243 | + ; | ||
| 244 | + takingPicture.set(false); | ||
| 245 | + } | ||
| 246 | + } | ||
| 247 | + | ||
| 248 | + @Override | ||
| 249 | + public void setPermissionCallback(PermissionCallback callback) { | ||
| 250 | + this.permissionCallback = callback; | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + public Camera1Control(Context context) { | ||
| 254 | + this.context = context; | ||
| 255 | + previewView = new PreviewView(context); | ||
| 256 | + openCamera(); | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + private void openCamera() { | ||
| 260 | + setupDisplayView(); | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + private void setupDisplayView() { | ||
| 264 | + final TextureView textureView = new TextureView(context); | ||
| 265 | + previewView.textureView = textureView; | ||
| 266 | + previewView.setTextureView(textureView); | ||
| 267 | + displayView = previewView; | ||
| 268 | + textureView.setSurfaceTextureListener(surfaceTextureListener); | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + private SurfaceTexture surfaceCache; | ||
| 272 | + | ||
| 273 | + private byte[] buffer = null; | ||
| 274 | + | ||
| 275 | + private void setPreviewCallbackImpl() { | ||
| 276 | + if (buffer == null) { | ||
| 277 | + buffer = new byte[displayView.getWidth() | ||
| 278 | + * displayView.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8]; | ||
| 279 | + } | ||
| 280 | + if (camera != null && detectType == MODEL_SCAN) { | ||
| 281 | + camera.addCallbackBuffer(buffer); | ||
| 282 | + camera.setPreviewCallback(previewCallback); | ||
| 283 | + } | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + private void clearPreviewCallback() { | ||
| 287 | + if (camera != null && detectType == MODEL_SCAN) { | ||
| 288 | + camera.setPreviewCallback(null); | ||
| 289 | + stopPreview(); | ||
| 290 | + } | ||
| 291 | + } | ||
| 292 | + | ||
| 293 | + Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() { | ||
| 294 | + @Override | ||
| 295 | + public void onPreviewFrame(final byte[] data, Camera camera) { | ||
| 296 | + // 扫描成功阻止打开新线程处理 | ||
| 297 | + if (abortingScan.get()) { | ||
| 298 | + return; | ||
| 299 | + } | ||
| 300 | + // 节流 | ||
| 301 | + if (previewFrameCount++ % 5 != 0) { | ||
| 302 | + return; | ||
| 303 | + } | ||
| 304 | + | ||
| 305 | + // 在某些机型和某项项目中,某些帧的data的数据不符合nv21的格式,需要过滤,否则后续处理会导致crash | ||
| 306 | + if (data.length != parameters.getPreviewSize().width * parameters.getPreviewSize().height * 1.5) { | ||
| 307 | + return; | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + camera.addCallbackBuffer(buffer); | ||
| 311 | + | ||
| 312 | + CameraThreadPool.execute(new Runnable() { | ||
| 313 | + @Override | ||
| 314 | + public void run() { | ||
| 315 | + Camera1Control.this.onRequestDetect(data); | ||
| 316 | + } | ||
| 317 | + }); | ||
| 318 | + } | ||
| 319 | + }; | ||
| 320 | + | ||
| 321 | + | ||
| 322 | + private void initCamera() { | ||
| 323 | + try { | ||
| 324 | + if (camera == null) { | ||
| 325 | + Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); | ||
| 326 | + for (int i = 0; i < Camera.getNumberOfCameras(); i++) { | ||
| 327 | + Camera.getCameraInfo(i, cameraInfo); | ||
| 328 | + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { | ||
| 329 | + cameraId = i; | ||
| 330 | + } | ||
| 331 | + } | ||
| 332 | + try { | ||
| 333 | + camera = Camera.open(cameraId); | ||
| 334 | + } catch (Throwable e) { | ||
| 335 | + e.printStackTrace(); | ||
| 336 | + startPreview(true); | ||
| 337 | + return; | ||
| 338 | + } | ||
| 339 | + | ||
| 340 | + } | ||
| 341 | + if (parameters == null) { | ||
| 342 | + parameters = camera.getParameters(); | ||
| 343 | + parameters.setPreviewFormat(ImageFormat.NV21); | ||
| 344 | + } | ||
| 345 | + opPreviewSize(previewView.getWidth(), previewView.getHeight()); | ||
| 346 | + camera.setPreviewTexture(surfaceCache); | ||
| 347 | + setPreviewCallbackImpl(); | ||
| 348 | + startPreview(false); | ||
| 349 | + } catch (IOException e) { | ||
| 350 | + e.printStackTrace(); | ||
| 351 | + } | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { | ||
| 355 | + @Override | ||
| 356 | + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { | ||
| 357 | + surfaceCache = surface; | ||
| 358 | + initCamera(); | ||
| 359 | + } | ||
| 360 | + | ||
| 361 | + @Override | ||
| 362 | + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { | ||
| 363 | + opPreviewSize(previewView.getWidth(), previewView.getHeight()); | ||
| 364 | + setPreviewCallbackImpl(); | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + @Override | ||
| 368 | + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { | ||
| 369 | + return false; | ||
| 370 | + } | ||
| 371 | + | ||
| 372 | + @Override | ||
| 373 | + public void onSurfaceTextureUpdated(SurfaceTexture surface) { | ||
| 374 | + setPreviewCallbackImpl(); | ||
| 375 | + } | ||
| 376 | + }; | ||
| 377 | + | ||
| 378 | + // 开启预览 | ||
| 379 | + private void startPreview(boolean checkPermission) { | ||
| 380 | + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) | ||
| 381 | + != PackageManager.PERMISSION_GRANTED) { | ||
| 382 | + if (checkPermission && permissionCallback != null) { | ||
| 383 | + permissionCallback.onRequestPermission(); | ||
| 384 | + } | ||
| 385 | + return; | ||
| 386 | + } | ||
| 387 | + if (camera == null) { | ||
| 388 | + initCamera(); | ||
| 389 | + } else { | ||
| 390 | + try { | ||
| 391 | + camera.startPreview(); | ||
| 392 | + CameraThreadPool.createAutoFocusTimerTask(new Runnable() { | ||
| 393 | + @Override | ||
| 394 | + public void run() { | ||
| 395 | + synchronized (Camera1Control.this) { | ||
| 396 | + if (camera != null && !takingPicture.get()) { | ||
| 397 | + try { | ||
| 398 | + camera.autoFocus(new Camera.AutoFocusCallback() { | ||
| 399 | + @Override | ||
| 400 | + public void onAutoFocus(boolean success, Camera camera) { | ||
| 401 | + } | ||
| 402 | + }); | ||
| 403 | + } catch (Throwable e) { | ||
| 404 | + // startPreview是异步实现,可能在某些机器上前几次调用会autofocus failß | ||
| 405 | + } | ||
| 406 | + } | ||
| 407 | + } | ||
| 408 | + } | ||
| 409 | + }); | ||
| 410 | + } catch (Exception e) { | ||
| 411 | + Log.e("ocr_ui startPreview", "startPreview: " + (e == null ? "" : e.getMessage())); | ||
| 412 | + } | ||
| 413 | + } | ||
| 414 | + } | ||
| 415 | + | ||
| 416 | + private void opPreviewSize(int width, @SuppressWarnings("unused") int height) { | ||
| 417 | + | ||
| 418 | + if (parameters != null && camera != null && width > 0) { | ||
| 419 | + try { | ||
| 420 | + optSize = getOptimalSize(camera.getParameters().getSupportedPreviewSizes()); | ||
| 421 | + }catch (Exception e){ | ||
| 422 | + e.printStackTrace(); | ||
| 423 | + } | ||
| 424 | + optSize = getOptimalSize(camera.getParameters().getSupportedPreviewSizes()); | ||
| 425 | + parameters.setPreviewSize(optSize.width, optSize.height); | ||
| 426 | + previewView.setRatio(1.0f * optSize.width / optSize.height); | ||
| 427 | + | ||
| 428 | + camera.setDisplayOrientation(getSurfaceOrientation()); | ||
| 429 | + stopPreview(); | ||
| 430 | + try { | ||
| 431 | + camera.setParameters(parameters); | ||
| 432 | + } catch (RuntimeException e) { | ||
| 433 | + e.printStackTrace(); | ||
| 434 | + | ||
| 435 | + } | ||
| 436 | + startPreview(false); | ||
| 437 | + } | ||
| 438 | + } | ||
| 439 | + | ||
| 440 | + private Camera.Size getOptimalSize(List<Camera.Size> sizes) { | ||
| 441 | + int width = previewView.textureView.getWidth(); | ||
| 442 | + int height = previewView.textureView.getHeight(); | ||
| 443 | + Camera.Size pictureSize = sizes.get(0); | ||
| 444 | + | ||
| 445 | + List<Camera.Size> candidates = new ArrayList<>(); | ||
| 446 | + | ||
| 447 | + for (Camera.Size size : sizes) { | ||
| 448 | + if (size.width >= width && size.height >= height && size.width * height == size.height * width) { | ||
| 449 | + // 比例相同 | ||
| 450 | + candidates.add(size); | ||
| 451 | + } else if (size.height >= width && size.width >= height && size.width * width == size.height * height) { | ||
| 452 | + // 反比例 | ||
| 453 | + candidates.add(size); | ||
| 454 | + } | ||
| 455 | + } | ||
| 456 | + if (!candidates.isEmpty()) { | ||
| 457 | + return Collections.min(candidates, sizeComparator); | ||
| 458 | + } | ||
| 459 | + | ||
| 460 | + for (Camera.Size size : sizes) { | ||
| 461 | + if (size.width > width && size.height > height) { | ||
| 462 | + return size; | ||
| 463 | + } | ||
| 464 | + } | ||
| 465 | + | ||
| 466 | + return pictureSize; | ||
| 467 | + } | ||
| 468 | + | ||
| 469 | + private Comparator<Camera.Size> sizeComparator = new Comparator<Camera.Size>() { | ||
| 470 | + @Override | ||
| 471 | + public int compare(Camera.Size lhs, Camera.Size rhs) { | ||
| 472 | + return Long.signum((long) lhs.width * lhs.height - (long) rhs.width * rhs.height); | ||
| 473 | + } | ||
| 474 | + }; | ||
| 475 | + | ||
| 476 | + private void updateFlashMode(int flashMode) { | ||
| 477 | + switch (flashMode) { | ||
| 478 | + case FLASH_MODE_TORCH: | ||
| 479 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||
| 480 | + try { | ||
| 481 | + parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); | ||
| 482 | + } catch (Exception e) { | ||
| 483 | + e.printStackTrace(); | ||
| 484 | + } | ||
| 485 | + }else { | ||
| 486 | + boolean isUse = false; | ||
| 487 | + PackageManager packageManager = context.getPackageManager(); | ||
| 488 | + FeatureInfo[] features = packageManager.getSystemAvailableFeatures(); | ||
| 489 | + for (FeatureInfo featureInfo : features) { | ||
| 490 | + if (PackageManager.FEATURE_CAMERA_FLASH.equals(featureInfo.name)) { // 断设备是否支持闪光灯 | ||
| 491 | + try { | ||
| 492 | + parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); | ||
| 493 | + isUse = true; | ||
| 494 | + } catch (Exception e) { | ||
| 495 | + e.printStackTrace(); | ||
| 496 | + } | ||
| 497 | + } | ||
| 498 | + } | ||
| 499 | + if(!isUse) AlertUtil.showDeftToast(context,"该设备不支持闪光灯"); | ||
| 500 | + } | ||
| 501 | + break; | ||
| 502 | + case FLASH_MODE_OFF: | ||
| 503 | + parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); | ||
| 504 | + break; | ||
| 505 | + case ICameraControl.FLASH_MODE_AUTO: | ||
| 506 | + parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO); | ||
| 507 | + break; | ||
| 508 | + default: | ||
| 509 | + parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO); | ||
| 510 | + break; | ||
| 511 | + } | ||
| 512 | + if (parameters == null || camera == null) | ||
| 513 | + return; | ||
| 514 | + camera.setParameters(parameters); | ||
| 515 | + } | ||
| 516 | + | ||
| 517 | + private int getSurfaceOrientation() { | ||
| 518 | + @CameraView.Orientation | ||
| 519 | + int orientation = displayOrientation; | ||
| 520 | + switch (orientation) { | ||
| 521 | + case CameraView.ORIENTATION_PORTRAIT: | ||
| 522 | + return 90; | ||
| 523 | + case CameraView.ORIENTATION_HORIZONTAL: | ||
| 524 | + return 0; | ||
| 525 | + case CameraView.ORIENTATION_INVERT: | ||
| 526 | + return 180; | ||
| 527 | + default: | ||
| 528 | + return 90; | ||
| 529 | + } | ||
| 530 | + } | ||
| 531 | + | ||
| 532 | + /** | ||
| 533 | + * 有些相机匹配不到完美的比例。比如。我们的layout是4:3的。预览只有16:9 | ||
| 534 | + * 的,如果直接显示图片会拉伸,变形。缩放的话,又有黑边。所以我们采取的策略 | ||
| 535 | + * 是,等比例放大。这样预览的有一部分会超出屏幕。拍照后再进行裁剪处理。 | ||
| 536 | + */ | ||
| 537 | + private class PreviewView extends FrameLayout { | ||
| 538 | + | ||
| 539 | + private TextureView textureView; | ||
| 540 | + | ||
| 541 | + private float ratio = 0.75f; | ||
| 542 | + | ||
| 543 | + void setTextureView(TextureView textureView) { | ||
| 544 | + this.textureView = textureView; | ||
| 545 | + removeAllViews(); | ||
| 546 | + addView(textureView); | ||
| 547 | + } | ||
| 548 | + | ||
| 549 | + void setRatio(float ratio) { | ||
| 550 | + this.ratio = ratio; | ||
| 551 | + requestLayout(); | ||
| 552 | + relayout(getWidth(), getHeight()); | ||
| 553 | + } | ||
| 554 | + | ||
| 555 | + public PreviewView(Context context) { | ||
| 556 | + super(context); | ||
| 557 | + } | ||
| 558 | + | ||
| 559 | + @Override | ||
| 560 | + protected void onSizeChanged(int w, int h, int oldw, int oldh) { | ||
| 561 | + super.onSizeChanged(w, h, oldw, oldh); | ||
| 562 | + relayout(w, h); | ||
| 563 | + } | ||
| 564 | + | ||
| 565 | + private void relayout(int w, int h) { | ||
| 566 | + int width = w; | ||
| 567 | + int height = h; | ||
| 568 | + if (w < h) { | ||
| 569 | + // 垂直模式,高度固定。 | ||
| 570 | + height = (int) (width * ratio); | ||
| 571 | + } else { | ||
| 572 | + // 水平模式,宽度固定。 | ||
| 573 | + width = (int) (height * ratio); | ||
| 574 | + } | ||
| 575 | + | ||
| 576 | + int l = (getWidth() - width) / 2; | ||
| 577 | + int t = (getHeight() - height) / 2; | ||
| 578 | + | ||
| 579 | + previewFrame.left = l; | ||
| 580 | + previewFrame.top = t; | ||
| 581 | + previewFrame.right = l + width; | ||
| 582 | + previewFrame.bottom = t + height; | ||
| 583 | + } | ||
| 584 | + | ||
| 585 | + @Override | ||
| 586 | + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | ||
| 587 | + super.onLayout(changed, left, top, right, bottom); | ||
| 588 | + textureView.layout(previewFrame.left, previewFrame.top, previewFrame.right, previewFrame.bottom); | ||
| 589 | + } | ||
| 590 | + } | ||
| 591 | + | ||
| 592 | + @Override | ||
| 593 | + public Rect getPreviewFrame() { | ||
| 594 | + return previewFrame; | ||
| 595 | + } | ||
| 596 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/Camera2Control.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | + | ||
| 5 | +package com.cnlive.core.ocr.ui.camera; | ||
| 6 | + | ||
| 7 | +import android.Manifest; | ||
| 8 | +import android.annotation.SuppressLint; | ||
| 9 | +import android.annotation.TargetApi; | ||
| 10 | +import android.content.Context; | ||
| 11 | +import android.content.pm.PackageManager; | ||
| 12 | +import android.graphics.ImageFormat; | ||
| 13 | +import android.graphics.Matrix; | ||
| 14 | +import android.graphics.Point; | ||
| 15 | +import android.graphics.Rect; | ||
| 16 | +import android.graphics.RectF; | ||
| 17 | +import android.graphics.SurfaceTexture; | ||
| 18 | +import android.hardware.camera2.CameraAccessException; | ||
| 19 | +import android.hardware.camera2.CameraCaptureSession; | ||
| 20 | +import android.hardware.camera2.CameraCharacteristics; | ||
| 21 | +import android.hardware.camera2.CameraDevice; | ||
| 22 | +import android.hardware.camera2.CameraManager; | ||
| 23 | +import android.hardware.camera2.CameraMetadata; | ||
| 24 | +import android.hardware.camera2.CaptureFailure; | ||
| 25 | +import android.hardware.camera2.CaptureRequest; | ||
| 26 | +import android.hardware.camera2.CaptureResult; | ||
| 27 | +import android.hardware.camera2.TotalCaptureResult; | ||
| 28 | +import android.hardware.camera2.params.StreamConfigurationMap; | ||
| 29 | +import android.media.Image; | ||
| 30 | +import android.media.ImageReader; | ||
| 31 | +import android.os.Build; | ||
| 32 | +import android.os.Handler; | ||
| 33 | +import android.os.HandlerThread; | ||
| 34 | +import android.util.Size; | ||
| 35 | +import android.util.SparseIntArray; | ||
| 36 | +import android.view.Surface; | ||
| 37 | +import android.view.TextureView; | ||
| 38 | +import android.view.View; | ||
| 39 | +import android.view.WindowManager; | ||
| 40 | + | ||
| 41 | +import java.nio.ByteBuffer; | ||
| 42 | +import java.util.ArrayList; | ||
| 43 | +import java.util.Arrays; | ||
| 44 | +import java.util.Collections; | ||
| 45 | +import java.util.Comparator; | ||
| 46 | +import java.util.List; | ||
| 47 | +import java.util.concurrent.Semaphore; | ||
| 48 | +import java.util.concurrent.TimeUnit; | ||
| 49 | +import java.util.concurrent.atomic.AtomicBoolean; | ||
| 50 | + | ||
| 51 | +import androidx.annotation.NonNull; | ||
| 52 | +import androidx.core.content.ContextCompat; | ||
| 53 | + | ||
| 54 | +@TargetApi(Build.VERSION_CODES.LOLLIPOP) | ||
| 55 | +public class Camera2Control implements ICameraControl { | ||
| 56 | + @Override | ||
| 57 | + public void setDetectCallback(OnDetectPictureCallback callback) { | ||
| 58 | + // TODO 暂时只用camera | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @Override | ||
| 62 | + public AtomicBoolean getAbortingScan() { | ||
| 63 | + // TODO 暂时只用camera | ||
| 64 | + return null; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * Conversion from screen rotation to JPEG orientation. | ||
| 69 | + */ | ||
| 70 | + private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); | ||
| 71 | + private static final int MAX_PREVIEW_SIZE = 2048; | ||
| 72 | + | ||
| 73 | + static { | ||
| 74 | + ORIENTATIONS.append(Surface.ROTATION_0, 90); | ||
| 75 | + ORIENTATIONS.append(Surface.ROTATION_90, 0); | ||
| 76 | + ORIENTATIONS.append(Surface.ROTATION_180, 270); | ||
| 77 | + ORIENTATIONS.append(Surface.ROTATION_270, 180); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + private static final int STATE_PREVIEW = 0; | ||
| 81 | + private static final int STATE_WAITING_FOR_LOCK = 1; | ||
| 82 | + private static final int STATE_WAITING_FOR_CAPTURE = 2; | ||
| 83 | + private static final int STATE_CAPTURING = 3; | ||
| 84 | + private static final int STATE_PICTURE_TAKEN = 4; | ||
| 85 | + | ||
| 86 | + private static final int MAX_PREVIEW_WIDTH = 1920; | ||
| 87 | + private static final int MAX_PREVIEW_HEIGHT = 1080; | ||
| 88 | + | ||
| 89 | + private int flashMode; | ||
| 90 | + private int orientation = 0; | ||
| 91 | + private int state = STATE_PREVIEW; | ||
| 92 | + | ||
| 93 | + private Context context; | ||
| 94 | + private OnTakePictureCallback onTakePictureCallback; | ||
| 95 | + private PermissionCallback permissionCallback; | ||
| 96 | + | ||
| 97 | + private String cameraId; | ||
| 98 | + private TextureView textureView; | ||
| 99 | + private Size previewSize; | ||
| 100 | + private Rect previewFrame = new Rect(); | ||
| 101 | + | ||
| 102 | + private HandlerThread backgroundThread; | ||
| 103 | + private Handler backgroundHandler; | ||
| 104 | + private ImageReader imageReader; | ||
| 105 | + private CameraCaptureSession captureSession; | ||
| 106 | + private CameraDevice cameraDevice; | ||
| 107 | + | ||
| 108 | + private CaptureRequest.Builder previewRequestBuilder; | ||
| 109 | + private CaptureRequest previewRequest; | ||
| 110 | + | ||
| 111 | + private Semaphore cameraLock = new Semaphore(1); | ||
| 112 | + private int sensorOrientation; | ||
| 113 | + | ||
| 114 | + @Override | ||
| 115 | + public void start() { | ||
| 116 | + startBackgroundThread(); | ||
| 117 | + if (textureView.isAvailable()) { | ||
| 118 | + openCamera(textureView.getWidth(), textureView.getHeight()); | ||
| 119 | + textureView.setSurfaceTextureListener(surfaceTextureListener); | ||
| 120 | + } else { | ||
| 121 | + textureView.setSurfaceTextureListener(surfaceTextureListener); | ||
| 122 | + } | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + @Override | ||
| 126 | + public void stop() { | ||
| 127 | + textureView.setSurfaceTextureListener(null); | ||
| 128 | + closeCamera(); | ||
| 129 | + stopBackgroundThread(); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + @Override | ||
| 133 | + public void pause() { | ||
| 134 | + setFlashMode(FLASH_MODE_OFF); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + @Override | ||
| 138 | + public void resume() { | ||
| 139 | + state = STATE_PREVIEW; | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + @Override | ||
| 143 | + public View getDisplayView() { | ||
| 144 | + return textureView; | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + @Override | ||
| 148 | + public Rect getPreviewFrame() { | ||
| 149 | + return previewFrame; | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + @Override | ||
| 153 | + public void takePicture(OnTakePictureCallback callback) { | ||
| 154 | + this.onTakePictureCallback = callback; | ||
| 155 | + // 拍照第一步,对焦 | ||
| 156 | + lockFocus(); | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + @Override | ||
| 160 | + public void setPermissionCallback(PermissionCallback callback) { | ||
| 161 | + this.permissionCallback = callback; | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + @Override | ||
| 165 | + public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) { | ||
| 166 | + this.orientation = displayOrientation / 90; | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + @Override | ||
| 170 | + public void refreshPermission() { | ||
| 171 | + openCamera(textureView.getWidth(), textureView.getHeight()); | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + @Override | ||
| 175 | + public void setFlashMode(@FlashMode int flashMode) { | ||
| 176 | + if (this.flashMode == flashMode) { | ||
| 177 | + return; | ||
| 178 | + } | ||
| 179 | + this.flashMode = flashMode; | ||
| 180 | + try { | ||
| 181 | + previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, | ||
| 182 | + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); | ||
| 183 | + updateFlashMode(flashMode, previewRequestBuilder); | ||
| 184 | + previewRequest = previewRequestBuilder.build(); | ||
| 185 | + captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler); | ||
| 186 | + } catch (Exception e) { | ||
| 187 | + if (e!=null) | ||
| 188 | + e.printStackTrace(); | ||
| 189 | + } | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + @Override | ||
| 193 | + public int getFlashMode() { | ||
| 194 | + return flashMode; | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + public Camera2Control(Context activity) { | ||
| 198 | + this.context = activity; | ||
| 199 | + textureView = new TextureView(activity); | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + private final TextureView.SurfaceTextureListener surfaceTextureListener = | ||
| 203 | + new TextureView.SurfaceTextureListener() { | ||
| 204 | + @Override | ||
| 205 | + public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { | ||
| 206 | + openCamera(width, height); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + @Override | ||
| 210 | + public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { | ||
| 211 | + configureTransform(width, height); | ||
| 212 | + previewFrame.left = 0; | ||
| 213 | + previewFrame.top = 0; | ||
| 214 | + previewFrame.right = width; | ||
| 215 | + previewFrame.bottom = height; | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + @Override | ||
| 219 | + public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { | ||
| 220 | + stop(); | ||
| 221 | + return true; | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + @Override | ||
| 225 | + public void onSurfaceTextureUpdated(SurfaceTexture texture) { | ||
| 226 | + } | ||
| 227 | + }; | ||
| 228 | + | ||
| 229 | + @SuppressLint("MissingPermission") | ||
| 230 | + private void openCamera(int width, int height) { | ||
| 231 | + // 6.0+的系统需要检查系统权限 。 | ||
| 232 | + if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) | ||
| 233 | + != PackageManager.PERMISSION_GRANTED) { | ||
| 234 | + requestCameraPermission(); | ||
| 235 | + return; | ||
| 236 | + } | ||
| 237 | + setUpCameraOutputs(width, height); | ||
| 238 | + configureTransform(width, height); | ||
| 239 | + CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); | ||
| 240 | + try { | ||
| 241 | + if (!cameraLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { | ||
| 242 | + throw new RuntimeException("Time out waiting to lock camera opening."); | ||
| 243 | + } | ||
| 244 | + manager.openCamera(cameraId, deviceStateCallback, backgroundHandler); | ||
| 245 | + } catch (CameraAccessException e) { | ||
| 246 | + if (e!=null) | ||
| 247 | + e.printStackTrace(); | ||
| 248 | + } catch (InterruptedException e) { | ||
| 249 | + if (e!=null) | ||
| 250 | + throw new RuntimeException("Interrupted while trying to lock camera opening.", e); | ||
| 251 | + } | ||
| 252 | + } | ||
| 253 | + | ||
| 254 | + private final CameraDevice.StateCallback deviceStateCallback = new CameraDevice.StateCallback() { | ||
| 255 | + @Override | ||
| 256 | + public void onOpened(@NonNull CameraDevice cameraDevice) { | ||
| 257 | + cameraLock.release(); | ||
| 258 | + Camera2Control.this.cameraDevice = cameraDevice; | ||
| 259 | + createCameraPreviewSession(); | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + @Override | ||
| 263 | + public void onDisconnected(@NonNull CameraDevice cameraDevice) { | ||
| 264 | + cameraLock.release(); | ||
| 265 | + cameraDevice.close(); | ||
| 266 | + Camera2Control.this.cameraDevice = null; | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + @Override | ||
| 270 | + public void onError(@NonNull CameraDevice cameraDevice, int error) { | ||
| 271 | + cameraLock.release(); | ||
| 272 | + cameraDevice.close(); | ||
| 273 | + Camera2Control.this.cameraDevice = null; | ||
| 274 | + } | ||
| 275 | + }; | ||
| 276 | + | ||
| 277 | + private void createCameraPreviewSession() { | ||
| 278 | + try { | ||
| 279 | + SurfaceTexture texture = textureView.getSurfaceTexture(); | ||
| 280 | + assert texture != null; | ||
| 281 | + texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); | ||
| 282 | + Surface surface = new Surface(texture); | ||
| 283 | + previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | ||
| 284 | + previewRequestBuilder.addTarget(surface); | ||
| 285 | + updateFlashMode(this.flashMode, previewRequestBuilder); | ||
| 286 | + cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), | ||
| 287 | + new CameraCaptureSession.StateCallback() { | ||
| 288 | + | ||
| 289 | + @Override | ||
| 290 | + public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { | ||
| 291 | + // The camera is already closed | ||
| 292 | + if (null == cameraDevice) { | ||
| 293 | + return; | ||
| 294 | + } | ||
| 295 | + captureSession = cameraCaptureSession; | ||
| 296 | + try { | ||
| 297 | + previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, | ||
| 298 | + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); | ||
| 299 | + previewRequest = previewRequestBuilder.build(); | ||
| 300 | + captureSession.setRepeatingRequest(previewRequest, | ||
| 301 | + captureCallback, backgroundHandler); | ||
| 302 | + } catch (CameraAccessException e) { | ||
| 303 | + if (e!=null) | ||
| 304 | + e.printStackTrace(); | ||
| 305 | + } | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + @Override | ||
| 309 | + public void onConfigureFailed( | ||
| 310 | + @NonNull CameraCaptureSession cameraCaptureSession) { | ||
| 311 | + // TODO | ||
| 312 | + } | ||
| 313 | + }, null | ||
| 314 | + ); | ||
| 315 | + } catch (CameraAccessException e) { | ||
| 316 | + if (e!=null) | ||
| 317 | + e.printStackTrace(); | ||
| 318 | + } | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + private final ImageReader.OnImageAvailableListener onImageAvailableListener = | ||
| 322 | + new ImageReader.OnImageAvailableListener() { | ||
| 323 | + @Override | ||
| 324 | + public void onImageAvailable(ImageReader reader) { | ||
| 325 | + if (onTakePictureCallback != null) { | ||
| 326 | + Image image = reader.acquireNextImage(); | ||
| 327 | + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); | ||
| 328 | + byte[] bytes = new byte[buffer.remaining()]; | ||
| 329 | + buffer.get(bytes); | ||
| 330 | + | ||
| 331 | + image.close(); | ||
| 332 | + onTakePictureCallback.onPictureTaken(bytes); | ||
| 333 | + } | ||
| 334 | + } | ||
| 335 | + }; | ||
| 336 | + | ||
| 337 | + private CameraCaptureSession.CaptureCallback captureCallback = | ||
| 338 | + new CameraCaptureSession.CaptureCallback() { | ||
| 339 | + private void process(CaptureResult result) { | ||
| 340 | + switch (state) { | ||
| 341 | + case STATE_PREVIEW: { | ||
| 342 | + break; | ||
| 343 | + } | ||
| 344 | + case STATE_WAITING_FOR_LOCK: { | ||
| 345 | + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); | ||
| 346 | + if (afState == null) { | ||
| 347 | + captureStillPicture(); | ||
| 348 | + return; | ||
| 349 | + } | ||
| 350 | + switch (afState) { | ||
| 351 | + case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED: | ||
| 352 | + case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: | ||
| 353 | + case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED: { | ||
| 354 | + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); | ||
| 355 | + if (aeState == null | ||
| 356 | + || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { | ||
| 357 | + captureStillPicture(); | ||
| 358 | + } else { | ||
| 359 | + runPreCaptureSequence(); | ||
| 360 | + } | ||
| 361 | + break; | ||
| 362 | + } | ||
| 363 | + default: | ||
| 364 | + captureStillPicture(); | ||
| 365 | + } | ||
| 366 | + break; | ||
| 367 | + } | ||
| 368 | + case STATE_WAITING_FOR_CAPTURE: { | ||
| 369 | + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); | ||
| 370 | + if (aeState == null | ||
| 371 | + || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE | ||
| 372 | + || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { | ||
| 373 | + state = STATE_CAPTURING; | ||
| 374 | + } else { | ||
| 375 | + if (aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { | ||
| 376 | + captureStillPicture(); | ||
| 377 | + } | ||
| 378 | + } | ||
| 379 | + break; | ||
| 380 | + } | ||
| 381 | + case STATE_CAPTURING: { | ||
| 382 | + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); | ||
| 383 | + if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { | ||
| 384 | + captureStillPicture(); | ||
| 385 | + } | ||
| 386 | + break; | ||
| 387 | + } | ||
| 388 | + default: | ||
| 389 | + break; | ||
| 390 | + } | ||
| 391 | + } | ||
| 392 | + | ||
| 393 | + @Override | ||
| 394 | + public void onCaptureProgressed(@NonNull CameraCaptureSession session, | ||
| 395 | + @NonNull CaptureRequest request, | ||
| 396 | + @NonNull CaptureResult partialResult) { | ||
| 397 | + process(partialResult); | ||
| 398 | + } | ||
| 399 | + | ||
| 400 | + @Override | ||
| 401 | + public void onCaptureCompleted(@NonNull CameraCaptureSession session, | ||
| 402 | + @NonNull CaptureRequest request, | ||
| 403 | + @NonNull TotalCaptureResult result) { | ||
| 404 | + process(result); | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + }; | ||
| 408 | + | ||
| 409 | + private Size getOptimalSize(Size[] choices, int textureViewWidth, | ||
| 410 | + int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { | ||
| 411 | + List<Size> bigEnough = new ArrayList<>(); | ||
| 412 | + List<Size> notBigEnough = new ArrayList<>(); | ||
| 413 | + int w = aspectRatio.getWidth(); | ||
| 414 | + int h = aspectRatio.getHeight(); | ||
| 415 | + for (Size option : choices) { | ||
| 416 | + if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight | ||
| 417 | + && option.getHeight() == option.getWidth() * h / w) { | ||
| 418 | + if (option.getWidth() >= textureViewWidth | ||
| 419 | + && option.getHeight() >= textureViewHeight) { | ||
| 420 | + bigEnough.add(option); | ||
| 421 | + } else { | ||
| 422 | + notBigEnough.add(option); | ||
| 423 | + } | ||
| 424 | + } | ||
| 425 | + } | ||
| 426 | + | ||
| 427 | + // Pick the smallest of those big enough. If there is no one big enough, pick the | ||
| 428 | + // largest of those not big enough. | ||
| 429 | + if (bigEnough.size() > 0) { | ||
| 430 | + return Collections.min(bigEnough, sizeComparator); | ||
| 431 | + } | ||
| 432 | + | ||
| 433 | + for (Size option : choices) { | ||
| 434 | + if (option.getWidth() >= maxWidth && option.getHeight() >= maxHeight) { | ||
| 435 | + return option; | ||
| 436 | + } | ||
| 437 | + } | ||
| 438 | + | ||
| 439 | + if (notBigEnough.size() > 0) { | ||
| 440 | + return Collections.max(notBigEnough, sizeComparator); | ||
| 441 | + } | ||
| 442 | + | ||
| 443 | + return choices[0]; | ||
| 444 | + } | ||
| 445 | + | ||
| 446 | + private Comparator<Size> sizeComparator = new Comparator<Size>() { | ||
| 447 | + @Override | ||
| 448 | + public int compare(Size lhs, Size rhs) { | ||
| 449 | + return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); | ||
| 450 | + } | ||
| 451 | + }; | ||
| 452 | + | ||
| 453 | + private void requestCameraPermission() { | ||
| 454 | + if (permissionCallback != null) { | ||
| 455 | + permissionCallback.onRequestPermission(); | ||
| 456 | + } | ||
| 457 | + } | ||
| 458 | + | ||
| 459 | + @SuppressWarnings("SuspiciousNameCombination") | ||
| 460 | + private void setUpCameraOutputs(int width, int height) { | ||
| 461 | + CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); | ||
| 462 | + try { | ||
| 463 | + for (String cameraId : manager.getCameraIdList()) { | ||
| 464 | + CameraCharacteristics characteristics = | ||
| 465 | + manager.getCameraCharacteristics(cameraId); | ||
| 466 | + | ||
| 467 | + Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); | ||
| 468 | + if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { | ||
| 469 | + continue; | ||
| 470 | + } | ||
| 471 | + | ||
| 472 | + StreamConfigurationMap map = characteristics.get( | ||
| 473 | + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); | ||
| 474 | + if (map == null) { | ||
| 475 | + continue; | ||
| 476 | + } | ||
| 477 | + | ||
| 478 | + // int preferredPictureSize = (int) (Math.max(textureView.getWidth(), textureView | ||
| 479 | + // .getHeight()) * 1.5); | ||
| 480 | + // preferredPictureSize = Math.min(preferredPictureSize, 2560); | ||
| 481 | + | ||
| 482 | + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); | ||
| 483 | + Point screenSize = new Point(); | ||
| 484 | + windowManager.getDefaultDisplay().getSize(screenSize); | ||
| 485 | + int maxImageSize = Math.max(MAX_PREVIEW_SIZE, screenSize.y * 3 / 2); | ||
| 486 | + | ||
| 487 | + Size size = getOptimalSize(map.getOutputSizes(ImageFormat.JPEG), textureView.getWidth(), | ||
| 488 | + textureView.getHeight(), maxImageSize, maxImageSize, new Size(4, 3)); | ||
| 489 | + | ||
| 490 | + imageReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), | ||
| 491 | + ImageFormat.JPEG, 1); | ||
| 492 | + imageReader.setOnImageAvailableListener( | ||
| 493 | + onImageAvailableListener, backgroundHandler); | ||
| 494 | + | ||
| 495 | + int displayRotation = orientation; | ||
| 496 | + // noinspection ConstantConditions | ||
| 497 | + sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); | ||
| 498 | + boolean swappedDimensions = false; | ||
| 499 | + switch (displayRotation) { | ||
| 500 | + case Surface.ROTATION_0: | ||
| 501 | + case Surface.ROTATION_180: | ||
| 502 | + if (sensorOrientation == 90 || sensorOrientation == 270) { | ||
| 503 | + swappedDimensions = true; | ||
| 504 | + } | ||
| 505 | + break; | ||
| 506 | + case Surface.ROTATION_90: | ||
| 507 | + case Surface.ROTATION_270: | ||
| 508 | + if (sensorOrientation == 0 || sensorOrientation == 180) { | ||
| 509 | + swappedDimensions = true; | ||
| 510 | + } | ||
| 511 | + break; | ||
| 512 | + default: | ||
| 513 | + } | ||
| 514 | + | ||
| 515 | + int rotatedPreviewWidth = width; | ||
| 516 | + int rotatedPreviewHeight = height; | ||
| 517 | + int maxPreviewWidth = screenSize.x; | ||
| 518 | + int maxPreviewHeight = screenSize.y; | ||
| 519 | + | ||
| 520 | + if (swappedDimensions) { | ||
| 521 | + rotatedPreviewWidth = height; | ||
| 522 | + rotatedPreviewHeight = width; | ||
| 523 | + maxPreviewWidth = screenSize.y; | ||
| 524 | + maxPreviewHeight = screenSize.x; | ||
| 525 | + } | ||
| 526 | + | ||
| 527 | + maxPreviewWidth = Math.min(maxPreviewWidth, MAX_PREVIEW_WIDTH); | ||
| 528 | + maxPreviewHeight = Math.min(maxPreviewHeight, MAX_PREVIEW_HEIGHT); | ||
| 529 | + | ||
| 530 | + previewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), | ||
| 531 | + rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, | ||
| 532 | + maxPreviewHeight, size); | ||
| 533 | + this.cameraId = cameraId; | ||
| 534 | + return; | ||
| 535 | + } | ||
| 536 | + } catch (CameraAccessException | NullPointerException e) { | ||
| 537 | + if (e!=null) | ||
| 538 | + e.printStackTrace(); | ||
| 539 | + } | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + private void closeCamera() { | ||
| 543 | + try { | ||
| 544 | + cameraLock.acquire(); | ||
| 545 | + if (null != captureSession) { | ||
| 546 | + captureSession.close(); | ||
| 547 | + captureSession = null; | ||
| 548 | + } | ||
| 549 | + if (null != cameraDevice) { | ||
| 550 | + cameraDevice.close(); | ||
| 551 | + cameraDevice = null; | ||
| 552 | + } | ||
| 553 | + if (null != imageReader) { | ||
| 554 | + imageReader.close(); | ||
| 555 | + imageReader = null; | ||
| 556 | + } | ||
| 557 | + } catch (InterruptedException e) { | ||
| 558 | + throw new RuntimeException("Interrupted while trying to lock camera closing.", e); | ||
| 559 | + } finally { | ||
| 560 | + cameraLock.release(); | ||
| 561 | + } | ||
| 562 | + } | ||
| 563 | + | ||
| 564 | + private void startBackgroundThread() { | ||
| 565 | + backgroundThread = new HandlerThread("ocr_camera"); | ||
| 566 | + backgroundThread.start(); | ||
| 567 | + backgroundHandler = new Handler(backgroundThread.getLooper()); | ||
| 568 | + } | ||
| 569 | + | ||
| 570 | + private void stopBackgroundThread() { | ||
| 571 | + if (backgroundThread != null) { | ||
| 572 | + backgroundThread.quitSafely(); | ||
| 573 | + backgroundThread = null; | ||
| 574 | + backgroundHandler = null; | ||
| 575 | + } | ||
| 576 | + } | ||
| 577 | + | ||
| 578 | + private void configureTransform(int viewWidth, int viewHeight) { | ||
| 579 | + if (null == textureView || null == previewSize || null == context) { | ||
| 580 | + return; | ||
| 581 | + } | ||
| 582 | + int rotation = orientation; | ||
| 583 | + Matrix matrix = new Matrix(); | ||
| 584 | + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); | ||
| 585 | + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); | ||
| 586 | + float centerX = viewRect.centerX(); | ||
| 587 | + float centerY = viewRect.centerY(); | ||
| 588 | + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { | ||
| 589 | + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); | ||
| 590 | + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); | ||
| 591 | + float scale = Math.max( | ||
| 592 | + (float) viewHeight / previewSize.getHeight(), | ||
| 593 | + (float) viewWidth / previewSize.getWidth()); | ||
| 594 | + matrix.postScale(scale, scale, centerX, centerY); | ||
| 595 | + matrix.postRotate(90 * (rotation - 2), centerX, centerY); | ||
| 596 | + } else if (Surface.ROTATION_180 == rotation) { | ||
| 597 | + matrix.postRotate(180, centerX, centerY); | ||
| 598 | + } | ||
| 599 | + textureView.setTransform(matrix); | ||
| 600 | + } | ||
| 601 | + | ||
| 602 | + // 拍照前,先对焦 | ||
| 603 | + private void lockFocus() { | ||
| 604 | + if (captureSession != null && state == STATE_PREVIEW) { | ||
| 605 | + try { | ||
| 606 | + previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, | ||
| 607 | + CameraMetadata.CONTROL_AF_TRIGGER_START); | ||
| 608 | + state = STATE_WAITING_FOR_LOCK; | ||
| 609 | + captureSession.capture(previewRequestBuilder.build(), captureCallback, | ||
| 610 | + backgroundHandler); | ||
| 611 | + } catch (CameraAccessException e) { | ||
| 612 | + if (e!=null) | ||
| 613 | + e.printStackTrace(); | ||
| 614 | + } | ||
| 615 | + } | ||
| 616 | + } | ||
| 617 | + | ||
| 618 | + private void runPreCaptureSequence() { | ||
| 619 | + try { | ||
| 620 | + previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, | ||
| 621 | + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); | ||
| 622 | + state = STATE_WAITING_FOR_CAPTURE; | ||
| 623 | + captureSession.capture(previewRequestBuilder.build(), captureCallback, | ||
| 624 | + backgroundHandler); | ||
| 625 | + } catch (CameraAccessException e) { | ||
| 626 | + if (e!=null) | ||
| 627 | + e.printStackTrace(); | ||
| 628 | + } | ||
| 629 | + } | ||
| 630 | + | ||
| 631 | + // 拍照session | ||
| 632 | + private void captureStillPicture() { | ||
| 633 | + try { | ||
| 634 | + if (null == context || null == cameraDevice) { | ||
| 635 | + return; | ||
| 636 | + } | ||
| 637 | + final CaptureRequest.Builder captureBuilder = | ||
| 638 | + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); | ||
| 639 | + captureBuilder.addTarget(imageReader.getSurface()); | ||
| 640 | + captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, | ||
| 641 | + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); | ||
| 642 | + | ||
| 643 | + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(orientation)); | ||
| 644 | + updateFlashMode(this.flashMode, captureBuilder); | ||
| 645 | + CameraCaptureSession.CaptureCallback captureCallback = | ||
| 646 | + new CameraCaptureSession.CaptureCallback() { | ||
| 647 | + @Override | ||
| 648 | + public void onCaptureCompleted(@NonNull CameraCaptureSession session, | ||
| 649 | + @NonNull CaptureRequest request, | ||
| 650 | + @NonNull TotalCaptureResult result) { | ||
| 651 | + unlockFocus(); | ||
| 652 | + } | ||
| 653 | + | ||
| 654 | + @Override | ||
| 655 | + public void onCaptureFailed(@NonNull CameraCaptureSession session, | ||
| 656 | + @NonNull CaptureRequest request, | ||
| 657 | + @NonNull CaptureFailure failure) { | ||
| 658 | + super.onCaptureFailed(session, request, failure); | ||
| 659 | + } | ||
| 660 | + }; | ||
| 661 | + | ||
| 662 | + // 停止预览 | ||
| 663 | + captureSession.stopRepeating(); | ||
| 664 | + captureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler); | ||
| 665 | + state = STATE_PICTURE_TAKEN; | ||
| 666 | + // unlockFocus(); | ||
| 667 | + } catch (CameraAccessException e) { | ||
| 668 | + if (e!=null) | ||
| 669 | + e.printStackTrace(); | ||
| 670 | + } | ||
| 671 | + } | ||
| 672 | + | ||
| 673 | + private int getOrientation(int rotation) { | ||
| 674 | + return (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360; | ||
| 675 | + } | ||
| 676 | + | ||
| 677 | + // 停止对焦 | ||
| 678 | + private void unlockFocus() { | ||
| 679 | + try { | ||
| 680 | + previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, | ||
| 681 | + CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); | ||
| 682 | + captureSession.capture(previewRequestBuilder.build(), captureCallback, | ||
| 683 | + backgroundHandler); | ||
| 684 | + state = STATE_PREVIEW; | ||
| 685 | + // 预览 | ||
| 686 | + captureSession.setRepeatingRequest(previewRequest, captureCallback, | ||
| 687 | + backgroundHandler); | ||
| 688 | + textureView.setSurfaceTextureListener(surfaceTextureListener); | ||
| 689 | + } catch (CameraAccessException e) { | ||
| 690 | + if (e!=null) | ||
| 691 | + e.printStackTrace(); | ||
| 692 | + } | ||
| 693 | + } | ||
| 694 | + | ||
| 695 | + private void updateFlashMode(@FlashMode int flashMode, CaptureRequest.Builder builder) { | ||
| 696 | + switch (flashMode) { | ||
| 697 | + case FLASH_MODE_TORCH: | ||
| 698 | + builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH); | ||
| 699 | + break; | ||
| 700 | + case FLASH_MODE_OFF: | ||
| 701 | + builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF); | ||
| 702 | + break; | ||
| 703 | + case ICameraControl.FLASH_MODE_AUTO: | ||
| 704 | + default: | ||
| 705 | + builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE); | ||
| 706 | + break; | ||
| 707 | + } | ||
| 708 | + } | ||
| 709 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/CameraActivity.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.camera; | ||
| 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.graphics.Bitmap; | ||
| 12 | +import android.graphics.Rect; | ||
| 13 | +import android.graphics.drawable.BitmapDrawable; | ||
| 14 | +import android.os.Build; | ||
| 15 | +import android.os.Bundle; | ||
| 16 | +import android.os.Environment; | ||
| 17 | +import android.os.Handler; | ||
| 18 | +import android.view.Surface; | ||
| 19 | +import android.view.View; | ||
| 20 | +import android.widget.ImageView; | ||
| 21 | +import android.widget.Toast; | ||
| 22 | + | ||
| 23 | +import com.cnlive.core.libs.base.util.AlertUtil; | ||
| 24 | +import com.cnlive.core.ocr.idcardquality.IDcardQualityProcess; | ||
| 25 | +import com.cnlive.core.ocr.ui.crop.CropView; | ||
| 26 | +import com.cnlive.core.ocr.ui.crop.FrameOverlayView; | ||
| 27 | +import com.cnlive.core.people_verfication.R; | ||
| 28 | +import com.cnlive.media.picker.ImagePicker; | ||
| 29 | +import com.cnlive.media.picker.PickerConfig; | ||
| 30 | +import com.cnlive.media.picker.entity.Media; | ||
| 31 | + | ||
| 32 | +import java.io.File; | ||
| 33 | +import java.io.FileOutputStream; | ||
| 34 | +import java.io.IOException; | ||
| 35 | +import java.util.ArrayList; | ||
| 36 | + | ||
| 37 | +import androidx.annotation.NonNull; | ||
| 38 | +import androidx.core.app.ActivityCompat; | ||
| 39 | + | ||
| 40 | +public class CameraActivity extends Activity { | ||
| 41 | + | ||
| 42 | + public static final String KEY_OUTPUT_FILE_PATH = "outputFilePath"; | ||
| 43 | + public static final String KEY_CONTENT_TYPE = "contentType"; | ||
| 44 | + public static final String KEY_NATIVE_TOKEN = "nativeToken"; | ||
| 45 | + public static final String KEY_NATIVE_ENABLE = "nativeEnable"; | ||
| 46 | + | ||
| 47 | + public static final String CONTENT_TYPE_GENERAL = "general"; | ||
| 48 | + public static final String CONTENT_TYPE_ID_CARD_FRONT = "IDCardFront"; | ||
| 49 | + public static final String CONTENT_TYPE_ID_CARD_BACK = "IDCardBack"; | ||
| 50 | + public static final String CONTENT_TYPE_BANK_CARD = "bankCard"; | ||
| 51 | + | ||
| 52 | + private static final int REQUEST_CODE_PICK_IMAGE = 100; | ||
| 53 | + private static final int PERMISSIONS_REQUEST_CAMERA = 800; | ||
| 54 | + private static final int PERMISSIONS_EXTERNAL_STORAGE = 801; | ||
| 55 | + | ||
| 56 | + private File outputFile; | ||
| 57 | + private String contentType; | ||
| 58 | + private Handler handler = new Handler(); | ||
| 59 | + | ||
| 60 | + private OCRCameraLayout takePictureContainer; | ||
| 61 | + private OCRCameraLayout cropContainer; | ||
| 62 | + private OCRCameraLayout confirmResultContainer; | ||
| 63 | + private ImageView lightButton; | ||
| 64 | + private CameraView cameraView; | ||
| 65 | + private ImageView displayImageView; | ||
| 66 | + private CropView cropView; | ||
| 67 | + private FrameOverlayView overlayView; | ||
| 68 | + private MaskView cropMaskView; | ||
| 69 | + private ImageView takePhotoBtn; | ||
| 70 | + private ImageView back; | ||
| 71 | + //上一个被点击过的btn的点击时间 | ||
| 72 | + private long preViewClickTime = 0; | ||
| 73 | + private PermissionCallback permissionCallback = new PermissionCallback() { | ||
| 74 | + @Override | ||
| 75 | + public boolean onRequestPermission() { | ||
| 76 | + ActivityCompat.requestPermissions(CameraActivity.this, | ||
| 77 | + new String[]{Manifest.permission.CAMERA, | ||
| 78 | + Manifest.permission.WRITE_EXTERNAL_STORAGE, | ||
| 79 | + Manifest.permission.READ_EXTERNAL_STORAGE}, | ||
| 80 | + PERMISSIONS_REQUEST_CAMERA); | ||
| 81 | + return false; | ||
| 82 | + } | ||
| 83 | + }; | ||
| 84 | + | ||
| 85 | + @Override | ||
| 86 | + protected void onCreate(Bundle savedInstanceState) { | ||
| 87 | + | ||
| 88 | + super.onCreate(savedInstanceState); | ||
| 89 | + setContentView(R.layout.bd_ocr_activity_camera); | ||
| 90 | + | ||
| 91 | + takePictureContainer = (OCRCameraLayout) findViewById(R.id.take_picture_container); | ||
| 92 | + confirmResultContainer = (OCRCameraLayout) findViewById(R.id.confirm_result_container); | ||
| 93 | + | ||
| 94 | + cameraView = (CameraView) findViewById(R.id.camera_view); | ||
| 95 | + cameraView.getCameraControl().setPermissionCallback(permissionCallback); | ||
| 96 | + lightButton = (ImageView) findViewById(R.id.light_button); | ||
| 97 | + lightButton.setOnClickListener(lightButtonOnClickListener); | ||
| 98 | + takePhotoBtn = (ImageView) findViewById(R.id.take_photo_button); | ||
| 99 | + findViewById(R.id.album_button).setOnClickListener(albumButtonOnClickListener); | ||
| 100 | + takePhotoBtn.setOnClickListener(takeButtonOnClickListener); | ||
| 101 | + | ||
| 102 | + // confirm result; | ||
| 103 | + displayImageView = (ImageView) findViewById(R.id.display_image_view); | ||
| 104 | + confirmResultContainer.findViewById(R.id.confirm_button).setOnClickListener(confirmButtonOnClickListener); | ||
| 105 | + confirmResultContainer.findViewById(R.id.cancel_button).setOnClickListener(confirmCancelButtonOnClickListener); | ||
| 106 | + findViewById(R.id.rotate_button).setOnClickListener(rotateButtonOnClickListener); | ||
| 107 | + | ||
| 108 | + cropView = (CropView) findViewById(R.id.crop_view); | ||
| 109 | + cropContainer = (OCRCameraLayout) findViewById(R.id.crop_container); | ||
| 110 | + overlayView = (FrameOverlayView) findViewById(R.id.overlay_view); | ||
| 111 | + cropContainer.findViewById(R.id.confirm_button).setOnClickListener(cropConfirmButtonListener); | ||
| 112 | + cropMaskView = (MaskView) cropContainer.findViewById(R.id.crop_mask_view); | ||
| 113 | + cropContainer.findViewById(R.id.cancel_button).setOnClickListener(cropCancelButtonListener); | ||
| 114 | + back = findViewById(R.id.back); | ||
| 115 | + back.setOnClickListener(finishListener); | ||
| 116 | + setOrientation(getResources().getConfiguration()); | ||
| 117 | + initParams(); | ||
| 118 | + | ||
| 119 | + cameraView.setAutoPictureCallback(autoTakePictureCallback); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + @Override | ||
| 123 | + protected void onStart() { | ||
| 124 | + super.onStart(); | ||
| 125 | + cameraView.start(); | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + @Override | ||
| 129 | + protected void onStop() { | ||
| 130 | + super.onStop(); | ||
| 131 | + cameraView.stop(); | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + private void initParams() { | ||
| 135 | + String outputPath = getIntent().getStringExtra(KEY_OUTPUT_FILE_PATH); | ||
| 136 | + final String token = getIntent().getStringExtra(KEY_NATIVE_TOKEN); | ||
| 137 | + boolean isNativeEnable = getIntent().getBooleanExtra(KEY_NATIVE_ENABLE, true); | ||
| 138 | + if (token == null) { | ||
| 139 | + isNativeEnable = false; | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + if (outputPath != null) { | ||
| 143 | + outputFile = new File(outputPath); | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + contentType = getIntent().getStringExtra(KEY_CONTENT_TYPE); | ||
| 147 | + if (contentType == null) { | ||
| 148 | + contentType = CONTENT_TYPE_GENERAL; | ||
| 149 | + } | ||
| 150 | + int maskType; | ||
| 151 | + switch (contentType) { | ||
| 152 | + case CONTENT_TYPE_ID_CARD_FRONT: | ||
| 153 | + maskType = MaskView.MASK_TYPE_ID_CARD_FRONT; | ||
| 154 | + overlayView.setVisibility(View.INVISIBLE); | ||
| 155 | + if (isNativeEnable) { | ||
| 156 | + takePhotoBtn.setVisibility(View.INVISIBLE); | ||
| 157 | + } | ||
| 158 | + break; | ||
| 159 | + case CONTENT_TYPE_ID_CARD_BACK: | ||
| 160 | + maskType = MaskView.MASK_TYPE_ID_CARD_BACK; | ||
| 161 | + overlayView.setVisibility(View.INVISIBLE); | ||
| 162 | + if (isNativeEnable) { | ||
| 163 | + takePhotoBtn.setVisibility(View.INVISIBLE); | ||
| 164 | + } | ||
| 165 | + break; | ||
| 166 | + case CONTENT_TYPE_BANK_CARD: | ||
| 167 | + maskType = MaskView.MASK_TYPE_BANK_CARD; | ||
| 168 | + overlayView.setVisibility(View.INVISIBLE); | ||
| 169 | + break; | ||
| 170 | + case CONTENT_TYPE_GENERAL: | ||
| 171 | + default: | ||
| 172 | + maskType = MaskView.MASK_TYPE_NONE; | ||
| 173 | + cropMaskView.setVisibility(View.INVISIBLE); | ||
| 174 | + break; | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + // 身份证本地能力初始化 | ||
| 178 | + if (maskType == MaskView.MASK_TYPE_ID_CARD_FRONT || maskType == MaskView.MASK_TYPE_ID_CARD_BACK) { | ||
| 179 | + if (isNativeEnable) { | ||
| 180 | + initNative(token); | ||
| 181 | + } | ||
| 182 | + } | ||
| 183 | + cameraView.setEnableScan(isNativeEnable); | ||
| 184 | + cameraView.setMaskType(maskType, this); | ||
| 185 | + cropMaskView.setMaskType(maskType); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + private void initNative(final String token) { | ||
| 189 | + CameraThreadPool.execute(new Runnable() { | ||
| 190 | + @Override | ||
| 191 | + public void run() { | ||
| 192 | + // 加载本地so失败, 异常返回getloadSoException | ||
| 193 | + if (IDcardQualityProcess.getLoadSoException() != null) { | ||
| 194 | + cameraView.setInitNativeStatus(CameraView.NATIVE_SOLOAD_FAIL); | ||
| 195 | + return; | ||
| 196 | + } | ||
| 197 | + // 授权状态 | ||
| 198 | + int authStatus = IDcardQualityProcess.init(token); | ||
| 199 | + // 加载模型状态 | ||
| 200 | + int initModelStatus = IDcardQualityProcess.getInstance() | ||
| 201 | + .idcardQualityInit(CameraActivity.this.getAssets(), | ||
| 202 | + "models"); | ||
| 203 | + if (authStatus != 0) { | ||
| 204 | + cameraView.setInitNativeStatus(CameraView.NATIVE_AUTH_FAIL); | ||
| 205 | + return; | ||
| 206 | + } | ||
| 207 | + if (initModelStatus != 0) { | ||
| 208 | + cameraView.setInitNativeStatus(CameraView.NATIVE_INIT_FAIL); | ||
| 209 | + return; | ||
| 210 | + } | ||
| 211 | + } | ||
| 212 | + }); | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + private void showTakePicture() { | ||
| 216 | + cameraView.getCameraControl().resume(); | ||
| 217 | + updateFlashMode(); | ||
| 218 | + takePictureContainer.setVisibility(View.VISIBLE); | ||
| 219 | + back.setVisibility(View.VISIBLE); | ||
| 220 | + confirmResultContainer.setVisibility(View.INVISIBLE); | ||
| 221 | + cropContainer.setVisibility(View.INVISIBLE); | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + private void showCrop() { | ||
| 225 | + cameraView.getCameraControl().pause(); | ||
| 226 | + updateFlashMode(); | ||
| 227 | + takePictureContainer.setVisibility(View.INVISIBLE); | ||
| 228 | + back.setVisibility(View.INVISIBLE); | ||
| 229 | + confirmResultContainer.setVisibility(View.INVISIBLE); | ||
| 230 | + cropContainer.setVisibility(View.VISIBLE); | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + private void showResultConfirm() { | ||
| 234 | + cameraView.getCameraControl().pause(); | ||
| 235 | + updateFlashMode(); | ||
| 236 | + takePictureContainer.setVisibility(View.INVISIBLE); | ||
| 237 | + back.setVisibility(View.INVISIBLE); | ||
| 238 | + confirmResultContainer.setVisibility(View.VISIBLE); | ||
| 239 | + cropContainer.setVisibility(View.INVISIBLE); | ||
| 240 | + } | ||
| 241 | + | ||
| 242 | + // take photo; | ||
| 243 | + private void updateFlashMode() { | ||
| 244 | + int flashMode = cameraView.getCameraControl().getFlashMode(); | ||
| 245 | + if (flashMode == ICameraControl.FLASH_MODE_TORCH) { | ||
| 246 | + lightButton.setImageResource(R.drawable.bd_ocr_light_on); | ||
| 247 | + } else { | ||
| 248 | + lightButton.setImageResource(R.drawable.bd_ocr_light_off); | ||
| 249 | + } | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + private View.OnClickListener albumButtonOnClickListener = new View.OnClickListener() { | ||
| 253 | + @Override | ||
| 254 | + public void onClick(View v) { | ||
| 255 | + long curClickTime = System.currentTimeMillis(); | ||
| 256 | + if (preViewClickTime > 0 && curClickTime - preViewClickTime <= 2000 ) { | ||
| 257 | + return; | ||
| 258 | + } | ||
| 259 | + preViewClickTime = curClickTime; | ||
| 260 | + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) | ||
| 261 | + != PackageManager.PERMISSION_GRANTED) { | ||
| 262 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | ||
| 263 | + ActivityCompat.requestPermissions(CameraActivity.this, | ||
| 264 | + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, | ||
| 265 | + PERMISSIONS_EXTERNAL_STORAGE); | ||
| 266 | + return; | ||
| 267 | + } | ||
| 268 | + } | ||
| 269 | + new ImagePicker | ||
| 270 | + .Builder() | ||
| 271 | + .setSelectGif(false) | ||
| 272 | + .maxImageSize(20 * 1024 * 1024) | ||
| 273 | + //最大选择图片数目,默认40张 | ||
| 274 | + .maxNum(1) | ||
| 275 | + //选择模式(默认PICKER_IMAGE_VIDEO) PICKER_IMAGE-->图片 PICKER_VIDEO-->视频 PICKER_IMAGE_VIDEO-->图片&视频 | ||
| 276 | + .selectMode(PickerConfig.PICKER_IMAGE) | ||
| 277 | + //拍照或录像存储位置 默认Environment.getExternalStorageDirectory().getPath() + File.separator + "JCamera" | ||
| 278 | + .cachePath(Environment.getExternalStorageDirectory() + "/strike/file/") | ||
| 279 | + //是否为发朋友圈模式(默认false) true-->图片与视频不能同时选择 | ||
| 280 | + .isFriendCircle(true) | ||
| 281 | + //裁剪 只有在单选模式下才可以使用 | ||
| 282 | +// .doCrop(1, 1, 300, 300) | ||
| 283 | + //是否开启单选模式(默认false) | ||
| 284 | + .isSinglePick(true) | ||
| 285 | + .builder() | ||
| 286 | + .start(CameraActivity.this, PickerConfig.PICKER_IMAGE, REQUEST_CODE_PICK_IMAGE); | ||
| 287 | + } | ||
| 288 | + }; | ||
| 289 | + | ||
| 290 | + private View.OnClickListener lightButtonOnClickListener = new View.OnClickListener() { | ||
| 291 | + @Override | ||
| 292 | + public void onClick(View v) { | ||
| 293 | + if (cameraView.getCameraControl().getFlashMode() == ICameraControl.FLASH_MODE_OFF) { | ||
| 294 | + cameraView.getCameraControl().setFlashMode(ICameraControl.FLASH_MODE_TORCH); | ||
| 295 | + } else { | ||
| 296 | + cameraView.getCameraControl().setFlashMode(ICameraControl.FLASH_MODE_OFF); | ||
| 297 | + } | ||
| 298 | + updateFlashMode(); | ||
| 299 | + } | ||
| 300 | + }; | ||
| 301 | + | ||
| 302 | + private View.OnClickListener takeButtonOnClickListener = new View.OnClickListener() { | ||
| 303 | + @Override | ||
| 304 | + public void onClick(View v) { | ||
| 305 | + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CAMERA) | ||
| 306 | + != PackageManager.PERMISSION_GRANTED) { | ||
| 307 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | ||
| 308 | + ActivityCompat.requestPermissions(CameraActivity.this, | ||
| 309 | + new String[]{Manifest.permission.CAMERA, | ||
| 310 | + Manifest.permission.WRITE_EXTERNAL_STORAGE, | ||
| 311 | + Manifest.permission.READ_EXTERNAL_STORAGE}, | ||
| 312 | + PERMISSIONS_REQUEST_CAMERA); | ||
| 313 | + return; | ||
| 314 | + } | ||
| 315 | + } | ||
| 316 | + cameraView.takePicture(outputFile, takePictureCallback); | ||
| 317 | + } | ||
| 318 | + }; | ||
| 319 | + | ||
| 320 | + private CameraView.OnTakePictureCallback autoTakePictureCallback = new CameraView.OnTakePictureCallback() { | ||
| 321 | + @Override | ||
| 322 | + public void onPictureTaken(final Bitmap bitmap) { | ||
| 323 | + CameraThreadPool.execute(new Runnable() { | ||
| 324 | + @Override | ||
| 325 | + public void run() { | ||
| 326 | + try { | ||
| 327 | + FileOutputStream fileOutputStream = new FileOutputStream(outputFile); | ||
| 328 | + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream); | ||
| 329 | + bitmap.recycle(); | ||
| 330 | + fileOutputStream.close(); | ||
| 331 | + } catch (IOException e) { | ||
| 332 | + e.printStackTrace(); | ||
| 333 | + } | ||
| 334 | + Intent intent = new Intent(); | ||
| 335 | + intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, contentType); | ||
| 336 | + setResult(Activity.RESULT_OK, intent); | ||
| 337 | + finish(); | ||
| 338 | + } | ||
| 339 | + }); | ||
| 340 | + } | ||
| 341 | + }; | ||
| 342 | + | ||
| 343 | + private CameraView.OnTakePictureCallback takePictureCallback = new CameraView.OnTakePictureCallback() { | ||
| 344 | + @Override | ||
| 345 | + public void onPictureTaken(final Bitmap bitmap) { | ||
| 346 | + handler.post(new Runnable() { | ||
| 347 | + @Override | ||
| 348 | + public void run() { | ||
| 349 | + takePictureContainer.setVisibility(View.INVISIBLE); | ||
| 350 | + back.setVisibility(View.INVISIBLE); | ||
| 351 | + if (cropMaskView.getMaskType() == MaskView.MASK_TYPE_NONE) { | ||
| 352 | + cropView.setFilePath(outputFile.getAbsolutePath()); | ||
| 353 | + showCrop(); | ||
| 354 | + } else if (cropMaskView.getMaskType() == MaskView.MASK_TYPE_BANK_CARD) { | ||
| 355 | + cropView.setFilePath(outputFile.getAbsolutePath()); | ||
| 356 | + cropMaskView.setVisibility(View.INVISIBLE); | ||
| 357 | + overlayView.setVisibility(View.VISIBLE); | ||
| 358 | + overlayView.setTypeWide(); | ||
| 359 | + showCrop(); | ||
| 360 | + } else { | ||
| 361 | + displayImageView.setImageBitmap(bitmap); | ||
| 362 | + showResultConfirm(); | ||
| 363 | + } | ||
| 364 | + } | ||
| 365 | + }); | ||
| 366 | + } | ||
| 367 | + }; | ||
| 368 | + | ||
| 369 | + private View.OnClickListener cropCancelButtonListener = new View.OnClickListener() { | ||
| 370 | + @Override | ||
| 371 | + public void onClick(View v) { | ||
| 372 | + // 释放 cropView中的bitmap; | ||
| 373 | + cropView.setFilePath(null); | ||
| 374 | + showTakePicture(); | ||
| 375 | + } | ||
| 376 | + }; | ||
| 377 | + | ||
| 378 | + | ||
| 379 | + private View.OnClickListener finishListener = new View.OnClickListener() { | ||
| 380 | + @Override | ||
| 381 | + public void onClick(View v) { | ||
| 382 | + finish(); | ||
| 383 | + } | ||
| 384 | + }; | ||
| 385 | + | ||
| 386 | + private View.OnClickListener cropConfirmButtonListener = new View.OnClickListener() { | ||
| 387 | + @Override | ||
| 388 | + public void onClick(View v) { | ||
| 389 | + int maskType = cropMaskView.getMaskType(); | ||
| 390 | + Rect rect; | ||
| 391 | + switch (maskType) { | ||
| 392 | + case MaskView.MASK_TYPE_BANK_CARD: | ||
| 393 | + case MaskView.MASK_TYPE_ID_CARD_BACK: | ||
| 394 | + case MaskView.MASK_TYPE_ID_CARD_FRONT: | ||
| 395 | + rect = cropMaskView.getFrameRect(); | ||
| 396 | + break; | ||
| 397 | + case MaskView.MASK_TYPE_NONE: | ||
| 398 | + default: | ||
| 399 | + rect = overlayView.getFrameRect(); | ||
| 400 | + break; | ||
| 401 | + } | ||
| 402 | + Bitmap cropped = cropView.crop(rect); | ||
| 403 | + if(cropped == null){ | ||
| 404 | + return; | ||
| 405 | + } | ||
| 406 | + displayImageView.setImageBitmap(cropped); | ||
| 407 | + cropAndConfirm(); | ||
| 408 | + } | ||
| 409 | + }; | ||
| 410 | + | ||
| 411 | + private void cropAndConfirm() { | ||
| 412 | + cameraView.getCameraControl().pause(); | ||
| 413 | + updateFlashMode(); | ||
| 414 | + doConfirmResult(); | ||
| 415 | + } | ||
| 416 | + | ||
| 417 | + private void doConfirmResult() { | ||
| 418 | + CameraThreadPool.execute(new Runnable() { | ||
| 419 | + @Override | ||
| 420 | + public void run() { | ||
| 421 | + try { | ||
| 422 | + FileOutputStream fileOutputStream = new FileOutputStream(outputFile); | ||
| 423 | + Bitmap bitmap = ((BitmapDrawable) displayImageView.getDrawable()).getBitmap(); | ||
| 424 | + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream); | ||
| 425 | + fileOutputStream.close(); | ||
| 426 | + } catch (IOException e) { | ||
| 427 | + if (e!=null) | ||
| 428 | + e.printStackTrace(); | ||
| 429 | + } | ||
| 430 | + Intent intent = new Intent(); | ||
| 431 | + intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, contentType); | ||
| 432 | + setResult(Activity.RESULT_OK, intent); | ||
| 433 | + finish(); | ||
| 434 | + } | ||
| 435 | + }); | ||
| 436 | + } | ||
| 437 | + | ||
| 438 | + private View.OnClickListener confirmButtonOnClickListener = new View.OnClickListener() { | ||
| 439 | + @Override | ||
| 440 | + public void onClick(View v) { | ||
| 441 | + doConfirmResult(); | ||
| 442 | + } | ||
| 443 | + }; | ||
| 444 | + | ||
| 445 | + private View.OnClickListener confirmCancelButtonOnClickListener = new View.OnClickListener() { | ||
| 446 | + @Override | ||
| 447 | + public void onClick(View v) { | ||
| 448 | + displayImageView.setImageBitmap(null); | ||
| 449 | + showTakePicture(); | ||
| 450 | + } | ||
| 451 | + }; | ||
| 452 | + | ||
| 453 | + private View.OnClickListener rotateButtonOnClickListener = new View.OnClickListener() { | ||
| 454 | + @Override | ||
| 455 | + public void onClick(View v) { | ||
| 456 | + cropView.rotate(90); | ||
| 457 | + } | ||
| 458 | + }; | ||
| 459 | + | ||
| 460 | + @Override | ||
| 461 | + public void onConfigurationChanged(Configuration newConfig) { | ||
| 462 | + super.onConfigurationChanged(newConfig); | ||
| 463 | + setOrientation(newConfig); | ||
| 464 | + } | ||
| 465 | + | ||
| 466 | + private void setOrientation(Configuration newConfig) { | ||
| 467 | + int rotation = getWindowManager().getDefaultDisplay().getRotation(); | ||
| 468 | + int orientation; | ||
| 469 | + int cameraViewOrientation = CameraView.ORIENTATION_PORTRAIT; | ||
| 470 | + switch (newConfig.orientation) { | ||
| 471 | + case Configuration.ORIENTATION_PORTRAIT: | ||
| 472 | + cameraViewOrientation = CameraView.ORIENTATION_PORTRAIT; | ||
| 473 | + orientation = OCRCameraLayout.ORIENTATION_PORTRAIT; | ||
| 474 | + break; | ||
| 475 | + case Configuration.ORIENTATION_LANDSCAPE: | ||
| 476 | + orientation = OCRCameraLayout.ORIENTATION_HORIZONTAL; | ||
| 477 | + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) { | ||
| 478 | + cameraViewOrientation = CameraView.ORIENTATION_HORIZONTAL; | ||
| 479 | + } else { | ||
| 480 | + cameraViewOrientation = CameraView.ORIENTATION_INVERT; | ||
| 481 | + } | ||
| 482 | + break; | ||
| 483 | + default: | ||
| 484 | + orientation = OCRCameraLayout.ORIENTATION_PORTRAIT; | ||
| 485 | + cameraView.setOrientation(CameraView.ORIENTATION_PORTRAIT); | ||
| 486 | + break; | ||
| 487 | + } | ||
| 488 | + takePictureContainer.setOrientation(orientation); | ||
| 489 | + cameraView.setOrientation(cameraViewOrientation); | ||
| 490 | + cropContainer.setOrientation(orientation); | ||
| 491 | + confirmResultContainer.setOrientation(orientation); | ||
| 492 | + } | ||
| 493 | + | ||
| 494 | + @Override | ||
| 495 | + protected void onActivityResult(int requestCode, int resultCode, Intent data) { | ||
| 496 | + super.onActivityResult(requestCode, resultCode, data); | ||
| 497 | + if (requestCode == REQUEST_CODE_PICK_IMAGE && data != null) { | ||
| 498 | + ArrayList<Media> select = data.getParcelableArrayListExtra(PickerConfig.EXTRA_RESULT); | ||
| 499 | + String path = select.get(0).path; | ||
| 500 | + cropView.setFilePath(path); | ||
| 501 | + showCrop(); | ||
| 502 | + }else { | ||
| 503 | + cameraView.getCameraControl().resume(); | ||
| 504 | + } | ||
| 505 | + } | ||
| 506 | + | ||
| 507 | + | ||
| 508 | + | ||
| 509 | + @Override | ||
| 510 | + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, | ||
| 511 | + @NonNull int[] grantResults) { | ||
| 512 | + super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||
| 513 | + switch (requestCode) { | ||
| 514 | + case PERMISSIONS_REQUEST_CAMERA: { | ||
| 515 | + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||
| 516 | + cameraView.getCameraControl().refreshPermission(); | ||
| 517 | + } else { | ||
| 518 | + AlertUtil.showLongToast(getApplicationContext(), getString(R.string.camera_permission_required), Toast.LENGTH_LONG); | ||
| 519 | + } | ||
| 520 | + break; | ||
| 521 | + } | ||
| 522 | + case PERMISSIONS_EXTERNAL_STORAGE: | ||
| 523 | + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||
| 524 | + | ||
| 525 | + } else { | ||
| 526 | + AlertUtil.showLongToast(getApplicationContext(), getString(R.string.permission_required), Toast.LENGTH_LONG); | ||
| 527 | + } | ||
| 528 | + default: | ||
| 529 | + break; | ||
| 530 | + } | ||
| 531 | + } | ||
| 532 | + | ||
| 533 | + /** | ||
| 534 | + * 做一些收尾工作,把这些工作从onDestory中移到正确的位置 | ||
| 535 | + * 可以以防止新的activity启动后被调用 | ||
| 536 | + */ | ||
| 537 | + private void doClear() { | ||
| 538 | + CameraThreadPool.cancelAutoFocusTimer(); | ||
| 539 | + IDcardQualityProcess.getInstance().releaseModel(); | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + | ||
| 543 | + @Override | ||
| 544 | + protected void onDestroy() { | ||
| 545 | + super.onDestroy(); | ||
| 546 | + this.doClear(); | ||
| 547 | + } | ||
| 548 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/CameraThreadPool.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.camera; | ||
| 5 | + | ||
| 6 | +import java.util.Timer; | ||
| 7 | +import java.util.TimerTask; | ||
| 8 | +import java.util.concurrent.ExecutorService; | ||
| 9 | +import java.util.concurrent.Executors; | ||
| 10 | + | ||
| 11 | +public class CameraThreadPool { | ||
| 12 | + | ||
| 13 | + static Timer timerFocus = null; | ||
| 14 | + | ||
| 15 | + /* | ||
| 16 | + * 对焦频率 | ||
| 17 | + */ | ||
| 18 | + static final long cameraScanInterval = 2000; | ||
| 19 | + | ||
| 20 | + /* | ||
| 21 | + * 线程池大小 | ||
| 22 | + */ | ||
| 23 | + private static int poolCount = Runtime.getRuntime().availableProcessors(); | ||
| 24 | + | ||
| 25 | + private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(poolCount); | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 给线程池添加任务 | ||
| 29 | + * | ||
| 30 | + * @param runnable 任务 | ||
| 31 | + */ | ||
| 32 | + public static void execute(Runnable runnable) { | ||
| 33 | + fixedThreadPool.execute(runnable); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 创建一个定时对焦的timer任务 | ||
| 38 | + * | ||
| 39 | + * @param runnable 对焦代码 | ||
| 40 | + * @return Timer Timer对象,用来终止自动对焦 | ||
| 41 | + */ | ||
| 42 | + public static Timer createAutoFocusTimerTask(final Runnable runnable) { | ||
| 43 | + if (timerFocus != null) { | ||
| 44 | + return timerFocus; | ||
| 45 | + } | ||
| 46 | + timerFocus = new Timer(); | ||
| 47 | + TimerTask task = new TimerTask() { | ||
| 48 | + @Override | ||
| 49 | + public void run() { | ||
| 50 | + runnable.run(); | ||
| 51 | + } | ||
| 52 | + }; | ||
| 53 | + timerFocus.scheduleAtFixedRate(task, 0, cameraScanInterval); | ||
| 54 | + return timerFocus; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * 终止自动对焦任务,实际调用了cancel方法并且清空对象 | ||
| 59 | + * 但是无法终止执行中的任务,需额外处理 | ||
| 60 | + */ | ||
| 61 | + public static void cancelAutoFocusTimer() { | ||
| 62 | + if (timerFocus != null) { | ||
| 63 | + timerFocus.cancel(); | ||
| 64 | + timerFocus = null; | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/CameraView.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.camera; | ||
| 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.Color; | ||
| 11 | +import android.graphics.Matrix; | ||
| 12 | +import android.graphics.Rect; | ||
| 13 | +import android.os.Handler; | ||
| 14 | +import android.os.Looper; | ||
| 15 | +import android.util.AttributeSet; | ||
| 16 | +import android.util.TypedValue; | ||
| 17 | +import android.view.Gravity; | ||
| 18 | +import android.view.View; | ||
| 19 | +import android.widget.FrameLayout; | ||
| 20 | +import android.widget.ImageView; | ||
| 21 | +import android.widget.LinearLayout; | ||
| 22 | +import android.widget.TextView; | ||
| 23 | + | ||
| 24 | +import com.cnlive.core.ocr.idcardquality.IDcardQualityProcess; | ||
| 25 | +import com.cnlive.core.ocr.ui.util.ImageUtil; | ||
| 26 | +import com.cnlive.core.ocr.ui.util.PxUtil; | ||
| 27 | +import com.cnlive.core.people_verfication.R; | ||
| 28 | + | ||
| 29 | +import java.io.File; | ||
| 30 | +import java.io.FileOutputStream; | ||
| 31 | +import java.io.IOException; | ||
| 32 | + | ||
| 33 | +import androidx.annotation.IntDef; | ||
| 34 | + | ||
| 35 | +/** | ||
| 36 | + * 负责,相机的管理。同时提供,裁剪遮罩功能。 | ||
| 37 | + */ | ||
| 38 | +public class CameraView extends FrameLayout { | ||
| 39 | + | ||
| 40 | + private int maskType; | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 照相回调 | ||
| 44 | + */ | ||
| 45 | + interface OnTakePictureCallback { | ||
| 46 | + void onPictureTaken(Bitmap bitmap); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + /** | ||
| 50 | + * 垂直方向 {@link #setOrientation(int)} | ||
| 51 | + */ | ||
| 52 | + public static final int ORIENTATION_PORTRAIT = 0; | ||
| 53 | + /** | ||
| 54 | + * 水平方向 {@link #setOrientation(int)} | ||
| 55 | + */ | ||
| 56 | + public static final int ORIENTATION_HORIZONTAL = 90; | ||
| 57 | + /** | ||
| 58 | + * 水平翻转方向 {@link #setOrientation(int)} | ||
| 59 | + */ | ||
| 60 | + public static final int ORIENTATION_INVERT = 270; | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * 本地模型授权,加载成功 | ||
| 64 | + */ | ||
| 65 | + public static final int NATIVE_AUTH_INIT_SUCCESS = 0; | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * 本地模型授权,加载成功 | ||
| 69 | + */ | ||
| 70 | + public static final int NATIVE_SOLOAD_FAIL = 10; | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * 本地模型授权,加载成功 | ||
| 74 | + */ | ||
| 75 | + public static final int NATIVE_AUTH_FAIL = 11; | ||
| 76 | + | ||
| 77 | + /** | ||
| 78 | + * 本地模型授权,加载成功 | ||
| 79 | + */ | ||
| 80 | + public static final int NATIVE_INIT_FAIL = 12; | ||
| 81 | + | ||
| 82 | + | ||
| 83 | + /** | ||
| 84 | + * 是否已经通过本地质量控制扫描 | ||
| 85 | + */ | ||
| 86 | + private final int SCAN_SUCCESS = 0; | ||
| 87 | + | ||
| 88 | + public void setInitNativeStatus(int initNativeStatus) { | ||
| 89 | + this.initNativeStatus = initNativeStatus; | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + /** | ||
| 93 | + * 本地检测初始化,模型加载标识 | ||
| 94 | + */ | ||
| 95 | + private int initNativeStatus = NATIVE_AUTH_INIT_SUCCESS; | ||
| 96 | + | ||
| 97 | + @IntDef({ORIENTATION_PORTRAIT, ORIENTATION_HORIZONTAL, ORIENTATION_INVERT}) | ||
| 98 | + public @interface Orientation { | ||
| 99 | + | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + private CameraViewTakePictureCallback cameraViewTakePictureCallback = new CameraViewTakePictureCallback(); | ||
| 103 | + | ||
| 104 | + private ICameraControl cameraControl; | ||
| 105 | + | ||
| 106 | + /** | ||
| 107 | + * 相机预览View | ||
| 108 | + */ | ||
| 109 | + private View displayView; | ||
| 110 | + /** | ||
| 111 | + * 身份证,银行卡,等裁剪用的遮罩 | ||
| 112 | + */ | ||
| 113 | + private MaskView maskView; | ||
| 114 | + | ||
| 115 | + /** | ||
| 116 | + * 用于显示提示证 "请对齐身份证正面" 之类的背景 | ||
| 117 | + */ | ||
| 118 | + private ImageView hintView; | ||
| 119 | + | ||
| 120 | + /** | ||
| 121 | + * 用于显示提示证 "请对齐身份证正面" 之类的文字 | ||
| 122 | + */ | ||
| 123 | + private TextView hintViewText; | ||
| 124 | + | ||
| 125 | + /** | ||
| 126 | + * 提示文案容器 | ||
| 127 | + */ | ||
| 128 | + private LinearLayout hintViewTextWrapper; | ||
| 129 | + | ||
| 130 | + /** | ||
| 131 | + * 是否是本地质量控制扫描 | ||
| 132 | + */ | ||
| 133 | + private boolean isEnableScan; | ||
| 134 | + | ||
| 135 | + public void setEnableScan(boolean enableScan) { | ||
| 136 | + isEnableScan = enableScan; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + /** | ||
| 140 | + * UI线程的handler | ||
| 141 | + */ | ||
| 142 | + Handler uiHandler = new Handler(Looper.getMainLooper()); | ||
| 143 | + | ||
| 144 | + public ICameraControl getCameraControl() { | ||
| 145 | + return cameraControl; | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + public void setOrientation(@Orientation int orientation) { | ||
| 149 | + cameraControl.setDisplayOrientation(orientation); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + public CameraView(Context context) { | ||
| 153 | + super(context); | ||
| 154 | + init(); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + public CameraView(Context context, AttributeSet attrs) { | ||
| 158 | + super(context, attrs); | ||
| 159 | + init(); | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { | ||
| 163 | + super(context, attrs, defStyleAttr); | ||
| 164 | + init(); | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + public void start() { | ||
| 168 | + cameraControl.start(); | ||
| 169 | + setKeepScreenOn(true); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + public void stop() { | ||
| 173 | + cameraControl.stop(); | ||
| 174 | + setKeepScreenOn(false); | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + public void takePicture(final File file, final OnTakePictureCallback callback) { | ||
| 178 | + cameraViewTakePictureCallback.file = file; | ||
| 179 | + cameraViewTakePictureCallback.callback = callback; | ||
| 180 | + cameraControl.takePicture(cameraViewTakePictureCallback); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + private OnTakePictureCallback autoPictureCallback; | ||
| 184 | + | ||
| 185 | + public void setAutoPictureCallback(OnTakePictureCallback callback) { | ||
| 186 | + autoPictureCallback = callback; | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + public void setMaskType(@MaskView.MaskType int maskType, final Context ctx) { | ||
| 190 | + maskView.setMaskType(maskType); | ||
| 191 | + | ||
| 192 | + maskView.setVisibility(VISIBLE); | ||
| 193 | + hintView.setVisibility(VISIBLE); | ||
| 194 | + | ||
| 195 | + int hintResourceId = R.drawable.bd_ocr_hint_align_id_card; | ||
| 196 | + this.maskType = maskType; | ||
| 197 | + boolean isNeedSetImage = true; | ||
| 198 | + switch (maskType) { | ||
| 199 | + case MaskView.MASK_TYPE_ID_CARD_FRONT: | ||
| 200 | + hintResourceId = R.drawable.bd_ocr_round_corner; | ||
| 201 | + isNeedSetImage = false; | ||
| 202 | + break; | ||
| 203 | + case MaskView.MASK_TYPE_ID_CARD_BACK: | ||
| 204 | + isNeedSetImage = false; | ||
| 205 | + hintResourceId = R.drawable.bd_ocr_round_corner; | ||
| 206 | + break; | ||
| 207 | + case MaskView.MASK_TYPE_BANK_CARD: | ||
| 208 | + hintResourceId = R.drawable.bd_ocr_hint_align_bank_card; | ||
| 209 | + break; | ||
| 210 | + case MaskView.MASK_TYPE_NONE: | ||
| 211 | + default: | ||
| 212 | + maskView.setVisibility(INVISIBLE); | ||
| 213 | + hintView.setVisibility(INVISIBLE); | ||
| 214 | + break; | ||
| 215 | + } | ||
| 216 | + | ||
| 217 | + if (isNeedSetImage) { | ||
| 218 | + hintView.setImageResource(hintResourceId); | ||
| 219 | + hintViewTextWrapper.setVisibility(INVISIBLE); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + if (maskType == MaskView.MASK_TYPE_ID_CARD_FRONT && isEnableScan) { | ||
| 223 | + cameraControl.setDetectCallback(new ICameraControl.OnDetectPictureCallback() { | ||
| 224 | + @Override | ||
| 225 | + public int onDetect(byte[] data, int rotation) { | ||
| 226 | + return detect(data, rotation); | ||
| 227 | + } | ||
| 228 | + }); | ||
| 229 | + } | ||
| 230 | + | ||
| 231 | + if (maskType == MaskView.MASK_TYPE_ID_CARD_BACK && isEnableScan) { | ||
| 232 | + cameraControl.setDetectCallback(new ICameraControl.OnDetectPictureCallback() { | ||
| 233 | + @Override | ||
| 234 | + public int onDetect(byte[] data, int rotation) { | ||
| 235 | + return detect(data, rotation); | ||
| 236 | + } | ||
| 237 | + }); | ||
| 238 | + } | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + private int detect(byte[] data, final int rotation) { | ||
| 242 | + if (initNativeStatus != NATIVE_AUTH_INIT_SUCCESS) { | ||
| 243 | + showTipMessage(initNativeStatus); | ||
| 244 | + return 1; | ||
| 245 | + } | ||
| 246 | + // 扫描成功阻止多余的操作 | ||
| 247 | + if (cameraControl.getAbortingScan().get()) { | ||
| 248 | + return 0; | ||
| 249 | + } | ||
| 250 | + // BitmapRegionDecoder不会将整个图片加载到内存。 | ||
| 251 | + BitmapRegionDecoder decoder = null; | ||
| 252 | + try { | ||
| 253 | + decoder = BitmapRegionDecoder.newInstance(data, 0, data.length, true); | ||
| 254 | + } catch (IOException e) { | ||
| 255 | + if (e!=null) | ||
| 256 | + e.printStackTrace(); | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + Rect previewFrame = cameraControl.getPreviewFrame(); | ||
| 260 | + | ||
| 261 | + int width = rotation % 180 == 0 ? decoder.getWidth() : decoder.getHeight(); | ||
| 262 | + int height = rotation % 180 == 0 ? decoder.getHeight() : decoder.getWidth(); | ||
| 263 | + | ||
| 264 | + Rect frameRect = maskView.getFrameRectExtend(); | ||
| 265 | + | ||
| 266 | + int left = width * frameRect.left / maskView.getWidth(); | ||
| 267 | + int top = height * frameRect.top / maskView.getHeight(); | ||
| 268 | + int right = width * frameRect.right / maskView.getWidth(); | ||
| 269 | + int bottom = height * frameRect.bottom / maskView.getHeight(); | ||
| 270 | + | ||
| 271 | + // 高度大于图片 | ||
| 272 | + if (previewFrame.top < 0) { | ||
| 273 | + // 宽度对齐。 | ||
| 274 | + int adjustedPreviewHeight = previewFrame.height() * getWidth() / previewFrame.width(); | ||
| 275 | + int topInFrame = ((adjustedPreviewHeight - frameRect.height()) / 2) | ||
| 276 | + * getWidth() / previewFrame.width(); | ||
| 277 | + int bottomInFrame = ((adjustedPreviewHeight + frameRect.height()) / 2) * getWidth() | ||
| 278 | + / previewFrame.width(); | ||
| 279 | + | ||
| 280 | + // 等比例投射到照片当中。 | ||
| 281 | + top = topInFrame * height / previewFrame.height(); | ||
| 282 | + bottom = bottomInFrame * height / previewFrame.height(); | ||
| 283 | + } else { | ||
| 284 | + // 宽度大于图片 | ||
| 285 | + if (previewFrame.left < 0) { | ||
| 286 | + // 高度对齐 | ||
| 287 | + int adjustedPreviewWidth = previewFrame.width() * getHeight() / previewFrame.height(); | ||
| 288 | + int leftInFrame = ((adjustedPreviewWidth - maskView.getFrameRect().width()) / 2) * getHeight() | ||
| 289 | + / previewFrame.height(); | ||
| 290 | + int rightInFrame = ((adjustedPreviewWidth + maskView.getFrameRect().width()) / 2) * getHeight() | ||
| 291 | + / previewFrame.height(); | ||
| 292 | + | ||
| 293 | + // 等比例投射到照片当中。 | ||
| 294 | + left = leftInFrame * width / previewFrame.width(); | ||
| 295 | + right = rightInFrame * width / previewFrame.width(); | ||
| 296 | + } | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + Rect region = new Rect(); | ||
| 300 | + region.left = left; | ||
| 301 | + region.top = top; | ||
| 302 | + region.right = right; | ||
| 303 | + region.bottom = bottom; | ||
| 304 | + | ||
| 305 | + // 90度或者270度旋转 | ||
| 306 | + if (rotation % 180 == 90) { | ||
| 307 | + int x = decoder.getWidth() / 2; | ||
| 308 | + int y = decoder.getHeight() / 2; | ||
| 309 | + | ||
| 310 | + int rotatedWidth = region.height(); | ||
| 311 | + int rotated = region.width(); | ||
| 312 | + | ||
| 313 | + // 计算,裁剪框旋转后的坐标 | ||
| 314 | + region.left = x - rotatedWidth / 2; | ||
| 315 | + region.top = y - rotated / 2; | ||
| 316 | + region.right = x + rotatedWidth / 2; | ||
| 317 | + region.bottom = y + rotated / 2; | ||
| 318 | + region.sort(); | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + BitmapFactory.Options options = new BitmapFactory.Options(); | ||
| 322 | + | ||
| 323 | + // 最大图片大小。 | ||
| 324 | + int maxPreviewImageSize = 2560; | ||
| 325 | + int size = Math.min(decoder.getWidth(), decoder.getHeight()); | ||
| 326 | + size = Math.min(size, maxPreviewImageSize); | ||
| 327 | + | ||
| 328 | + options.inSampleSize = ImageUtil.calculateInSampleSize(options, size, size); | ||
| 329 | + options.inScaled = true; | ||
| 330 | + options.inDensity = Math.max(options.outWidth, options.outHeight); | ||
| 331 | + options.inTargetDensity = size; | ||
| 332 | + options.inPreferredConfig = Bitmap.Config.RGB_565; | ||
| 333 | + | ||
| 334 | + Bitmap bitmap = decoder.decodeRegion(region, options); | ||
| 335 | + | ||
| 336 | + if (rotation != 0) { | ||
| 337 | + // 只能是裁剪完之后再旋转了。有没有别的更好的方案呢? | ||
| 338 | + Matrix matrix = new Matrix(); | ||
| 339 | + matrix.postRotate(rotation); | ||
| 340 | + Bitmap rotatedBitmap = Bitmap.createBitmap( | ||
| 341 | + bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); | ||
| 342 | + if (bitmap != rotatedBitmap) { | ||
| 343 | + // 有时候 createBitmap会复用对象 | ||
| 344 | + bitmap.recycle(); | ||
| 345 | + } | ||
| 346 | + bitmap = rotatedBitmap; | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + final int status; | ||
| 350 | + | ||
| 351 | + // 调用本地质量控制请求 | ||
| 352 | + switch (maskType) { | ||
| 353 | + case MaskView.MASK_TYPE_ID_CARD_FRONT: | ||
| 354 | + status = IDcardQualityProcess.getInstance().idcardQualityDetectionImg(bitmap, true); | ||
| 355 | + break; | ||
| 356 | + case MaskView.MASK_TYPE_ID_CARD_BACK: | ||
| 357 | + status = IDcardQualityProcess.getInstance().idcardQualityDetectionImg(bitmap, false); | ||
| 358 | + break; | ||
| 359 | + default: | ||
| 360 | + status = 1; | ||
| 361 | + } | ||
| 362 | + | ||
| 363 | + // 当有某个扫描处理线程调用成功后,阻止其他线程继续调用本地控制代码 | ||
| 364 | + if (status == SCAN_SUCCESS) { | ||
| 365 | + // 扫描成功阻止多线程同时回调 | ||
| 366 | + if (!cameraControl.getAbortingScan().compareAndSet(false, true)) { | ||
| 367 | + bitmap.recycle(); | ||
| 368 | + return 0; | ||
| 369 | + } | ||
| 370 | + autoPictureCallback.onPictureTaken(bitmap); | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + showTipMessage(status); | ||
| 374 | + | ||
| 375 | + return status; | ||
| 376 | + } | ||
| 377 | + | ||
| 378 | + private void showTipMessage(final int status) { | ||
| 379 | + // 提示tip文字变化 | ||
| 380 | + uiHandler.post(new Runnable() { | ||
| 381 | + @Override | ||
| 382 | + public void run() { | ||
| 383 | + if (status == 0) { | ||
| 384 | + hintViewText.setVisibility(View.INVISIBLE); | ||
| 385 | + } else if (!cameraControl.getAbortingScan().get()) { | ||
| 386 | + hintViewText.setVisibility(View.VISIBLE); | ||
| 387 | + hintViewText.setText(getScanMessage(status)); | ||
| 388 | + } | ||
| 389 | + } | ||
| 390 | + }); | ||
| 391 | + } | ||
| 392 | + | ||
| 393 | + private String getScanMessage(int status) { | ||
| 394 | + String message; | ||
| 395 | + switch (status) { | ||
| 396 | + case 0: | ||
| 397 | + message = ""; | ||
| 398 | + break; | ||
| 399 | + case 2: | ||
| 400 | + message = "身份证模糊,请重新尝试"; | ||
| 401 | + break; | ||
| 402 | + case 3: | ||
| 403 | + message = "身份证反光,请重新尝试"; | ||
| 404 | + break; | ||
| 405 | + case 4: | ||
| 406 | + message = "请将身份证前后反转再进行识别"; | ||
| 407 | + break; | ||
| 408 | + case 5: | ||
| 409 | + message = "请拿稳镜头和身份证"; | ||
| 410 | + break; | ||
| 411 | + case 6: | ||
| 412 | + message = "请将镜头靠近身份证"; | ||
| 413 | + break; | ||
| 414 | + case 7: | ||
| 415 | + message = "请将身份证完整置于取景框内"; | ||
| 416 | + break; | ||
| 417 | + case NATIVE_AUTH_FAIL: | ||
| 418 | + message = "本地质量控制授权失败"; | ||
| 419 | + break; | ||
| 420 | + case NATIVE_INIT_FAIL: | ||
| 421 | + message = "本地模型加载失败"; | ||
| 422 | + break; | ||
| 423 | + case NATIVE_SOLOAD_FAIL: | ||
| 424 | + message = "本地SO库加载失败"; | ||
| 425 | + break; | ||
| 426 | + case 1: | ||
| 427 | + default: | ||
| 428 | + message = "请将身份证置于取景框内"; | ||
| 429 | + } | ||
| 430 | + | ||
| 431 | + | ||
| 432 | + return message; | ||
| 433 | + } | ||
| 434 | + | ||
| 435 | + private void init() { | ||
| 436 | +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||
| 437 | +// cameraControl = new Camera2Control(getContext()); | ||
| 438 | +// } else { | ||
| 439 | +// | ||
| 440 | +// } | ||
| 441 | + cameraControl = new Camera1Control(getContext()); | ||
| 442 | + | ||
| 443 | + displayView = cameraControl.getDisplayView(); | ||
| 444 | + addView(displayView); | ||
| 445 | + | ||
| 446 | + maskView = new MaskView(getContext()); | ||
| 447 | + addView(maskView); | ||
| 448 | + | ||
| 449 | + hintView = new ImageView(getContext()); | ||
| 450 | + addView(hintView); | ||
| 451 | + | ||
| 452 | + hintViewTextWrapper = new LinearLayout(getContext()); | ||
| 453 | + hintViewTextWrapper.setOrientation(LinearLayout.VERTICAL); | ||
| 454 | + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, | ||
| 455 | + PxUtil.dp2px(25)); | ||
| 456 | + | ||
| 457 | + lp.gravity = Gravity.CENTER; | ||
| 458 | + hintViewText = new TextView(getContext()); | ||
| 459 | + hintViewText.setBackgroundResource(R.drawable.bd_ocr_round_corner); | ||
| 460 | + hintViewText.setAlpha(0.5f); | ||
| 461 | + hintViewText.setPadding(PxUtil.dp2px(10), 0, PxUtil.dp2px(10), 0); | ||
| 462 | + hintViewTextWrapper.addView(hintViewText, lp); | ||
| 463 | + | ||
| 464 | + | ||
| 465 | + hintViewText.setGravity(Gravity.CENTER); | ||
| 466 | + hintViewText.setTextColor(Color.WHITE); | ||
| 467 | + hintViewText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); | ||
| 468 | + hintViewText.setText(getScanMessage(-1)); | ||
| 469 | + | ||
| 470 | + | ||
| 471 | + addView(hintViewTextWrapper, lp); | ||
| 472 | + } | ||
| 473 | + | ||
| 474 | + @Override | ||
| 475 | + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | ||
| 476 | + displayView.layout(left, 0, right, bottom - top); | ||
| 477 | + maskView.layout(left, 0, right, bottom - top); | ||
| 478 | + | ||
| 479 | + int hintViewWidth = PxUtil.dp2px(250); | ||
| 480 | + int hintViewHeight = PxUtil.dp2px(25); | ||
| 481 | + | ||
| 482 | + int hintViewLeft = (getWidth() - hintViewWidth) / 2; | ||
| 483 | + int hintViewTop = maskView.getFrameRect().bottom + PxUtil.dp2px(16); | ||
| 484 | + | ||
| 485 | + hintViewTextWrapper.layout(hintViewLeft, hintViewTop, | ||
| 486 | + hintViewLeft + hintViewWidth, hintViewTop + hintViewHeight); | ||
| 487 | + | ||
| 488 | + hintView.layout(hintViewLeft, hintViewTop, | ||
| 489 | + hintViewLeft + hintViewWidth, hintViewTop + hintViewHeight); | ||
| 490 | + } | ||
| 491 | + | ||
| 492 | + /** | ||
| 493 | + * 拍摄后的照片。需要进行裁剪。有些手机(比如三星)不会对照片数据进行旋转,而是将旋转角度写入EXIF信息当中, | ||
| 494 | + * 所以需要做旋转处理。 | ||
| 495 | + * | ||
| 496 | + * @param outputFile 写入照片的文件。 | ||
| 497 | + * @param data 原始照片数据。 | ||
| 498 | + * @param rotation 照片exif中的旋转角度。 | ||
| 499 | + * @return 裁剪好的bitmap。 | ||
| 500 | + */ | ||
| 501 | + @SuppressWarnings("ResultOfMethodCallIgnored") | ||
| 502 | + private Bitmap crop(File outputFile, byte[] data, int rotation) { | ||
| 503 | + try { | ||
| 504 | + // BitmapRegionDecoder不会将整个图片加载到内存。 | ||
| 505 | + BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(data, 0, data.length, true); | ||
| 506 | + | ||
| 507 | + Rect previewFrame = cameraControl.getPreviewFrame(); | ||
| 508 | + | ||
| 509 | + int width = rotation % 180 == 0 ? decoder.getWidth() : decoder.getHeight(); | ||
| 510 | + int height = rotation % 180 == 0 ? decoder.getHeight() : decoder.getWidth(); | ||
| 511 | + | ||
| 512 | + Rect frameRect = maskView.getFrameRect(); | ||
| 513 | + | ||
| 514 | + int left = width * frameRect.left / maskView.getWidth(); | ||
| 515 | + int top = height * frameRect.top / maskView.getHeight(); | ||
| 516 | + int right = width * frameRect.right / maskView.getWidth(); | ||
| 517 | + int bottom = height * frameRect.bottom / maskView.getHeight(); | ||
| 518 | + | ||
| 519 | + // 高度大于图片 | ||
| 520 | + if (previewFrame.top < 0) { | ||
| 521 | + // 宽度对齐。 | ||
| 522 | + int adjustedPreviewHeight = previewFrame.height() * getWidth() / previewFrame.width(); | ||
| 523 | + int topInFrame = ((adjustedPreviewHeight - frameRect.height()) / 2) | ||
| 524 | + * getWidth() / previewFrame.width(); | ||
| 525 | + int bottomInFrame = ((adjustedPreviewHeight + frameRect.height()) / 2) * getWidth() | ||
| 526 | + / previewFrame.width(); | ||
| 527 | + | ||
| 528 | + // 等比例投射到照片当中。 | ||
| 529 | + top = topInFrame * height / previewFrame.height(); | ||
| 530 | + bottom = bottomInFrame * height / previewFrame.height(); | ||
| 531 | + } else { | ||
| 532 | + // 宽度大于图片 | ||
| 533 | + if (previewFrame.left < 0) { | ||
| 534 | + // 高度对齐 | ||
| 535 | + int adjustedPreviewWidth = previewFrame.width() * getHeight() / previewFrame.height(); | ||
| 536 | + int leftInFrame = ((adjustedPreviewWidth - maskView.getFrameRect().width()) / 2) * getHeight() | ||
| 537 | + / previewFrame.height(); | ||
| 538 | + int rightInFrame = ((adjustedPreviewWidth + maskView.getFrameRect().width()) / 2) * getHeight() | ||
| 539 | + / previewFrame.height(); | ||
| 540 | + | ||
| 541 | + // 等比例投射到照片当中。 | ||
| 542 | + left = leftInFrame * width / previewFrame.width(); | ||
| 543 | + right = rightInFrame * width / previewFrame.width(); | ||
| 544 | + } | ||
| 545 | + } | ||
| 546 | + | ||
| 547 | + Rect region = new Rect(); | ||
| 548 | + region.left = left; | ||
| 549 | + region.top = top; | ||
| 550 | + region.right = right; | ||
| 551 | + region.bottom = bottom; | ||
| 552 | + | ||
| 553 | + // 90度或者270度旋转 | ||
| 554 | + if (rotation % 180 == 90) { | ||
| 555 | + int x = decoder.getWidth() / 2; | ||
| 556 | + int y = decoder.getHeight() / 2; | ||
| 557 | + | ||
| 558 | + int rotatedWidth = region.height(); | ||
| 559 | + int rotated = region.width(); | ||
| 560 | + | ||
| 561 | + // 计算,裁剪框旋转后的坐标 | ||
| 562 | + region.left = x - rotatedWidth / 2; | ||
| 563 | + region.top = y - rotated / 2; | ||
| 564 | + region.right = x + rotatedWidth / 2; | ||
| 565 | + region.bottom = y + rotated / 2; | ||
| 566 | + region.sort(); | ||
| 567 | + } | ||
| 568 | + | ||
| 569 | + BitmapFactory.Options options = new BitmapFactory.Options(); | ||
| 570 | + | ||
| 571 | + // 最大图片大小。 | ||
| 572 | + int maxPreviewImageSize = 2560; | ||
| 573 | + int size = Math.min(decoder.getWidth(), decoder.getHeight()); | ||
| 574 | + size = Math.min(size, maxPreviewImageSize); | ||
| 575 | + | ||
| 576 | + options.inSampleSize = ImageUtil.calculateInSampleSize(options, size, size); | ||
| 577 | + options.inScaled = true; | ||
| 578 | + options.inDensity = Math.max(options.outWidth, options.outHeight); | ||
| 579 | + options.inTargetDensity = size; | ||
| 580 | + options.inPreferredConfig = Bitmap.Config.RGB_565; | ||
| 581 | + Bitmap bitmap = decoder.decodeRegion(region, options); | ||
| 582 | + | ||
| 583 | + if (rotation != 0) { | ||
| 584 | + // 只能是裁剪完之后再旋转了。有没有别的更好的方案呢? | ||
| 585 | + Matrix matrix = new Matrix(); | ||
| 586 | + matrix.postRotate(rotation); | ||
| 587 | + Bitmap rotatedBitmap = Bitmap.createBitmap( | ||
| 588 | + bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); | ||
| 589 | + if (bitmap != rotatedBitmap) { | ||
| 590 | + // 有时候 createBitmap会复用对象 | ||
| 591 | + bitmap.recycle(); | ||
| 592 | + } | ||
| 593 | + bitmap = rotatedBitmap; | ||
| 594 | + } | ||
| 595 | + | ||
| 596 | + try { | ||
| 597 | + if (!outputFile.exists()) { | ||
| 598 | + outputFile.createNewFile(); | ||
| 599 | + } | ||
| 600 | + FileOutputStream fileOutputStream = new FileOutputStream(outputFile); | ||
| 601 | + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream); | ||
| 602 | + fileOutputStream.flush(); | ||
| 603 | + fileOutputStream.close(); | ||
| 604 | + return bitmap; | ||
| 605 | + } catch (IOException e) { | ||
| 606 | + if (e!=null) | ||
| 607 | + e.printStackTrace(); | ||
| 608 | + } | ||
| 609 | + } catch (IOException e) { | ||
| 610 | + if (e!=null) | ||
| 611 | + e.printStackTrace(); | ||
| 612 | + } | ||
| 613 | + return null; | ||
| 614 | + } | ||
| 615 | + | ||
| 616 | + public void release() { | ||
| 617 | + IDcardQualityProcess.getInstance().releaseModel(); | ||
| 618 | + } | ||
| 619 | + | ||
| 620 | + private class CameraViewTakePictureCallback implements ICameraControl.OnTakePictureCallback { | ||
| 621 | + | ||
| 622 | + private File file; | ||
| 623 | + private OnTakePictureCallback callback; | ||
| 624 | + | ||
| 625 | + @Override | ||
| 626 | + public void onPictureTaken(final byte[] data) { | ||
| 627 | + CameraThreadPool.execute(new Runnable() { | ||
| 628 | + @Override | ||
| 629 | + public void run() { | ||
| 630 | + final int rotation = ImageUtil.getOrientation(data); | ||
| 631 | + Bitmap bitmap = crop(file, data, rotation); | ||
| 632 | + callback.onPictureTaken(bitmap); | ||
| 633 | + } | ||
| 634 | + }); | ||
| 635 | + } | ||
| 636 | + } | ||
| 637 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/ICameraControl.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.camera; | ||
| 5 | + | ||
| 6 | +import android.graphics.Rect; | ||
| 7 | +import android.view.View; | ||
| 8 | + | ||
| 9 | +import java.util.concurrent.atomic.AtomicBoolean; | ||
| 10 | + | ||
| 11 | +import androidx.annotation.IntDef; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * Android 5.0 相机的API发生很大的变化。些类屏蔽掉了 api的变化。相机的操作和功能,抽象剥离出来。 | ||
| 15 | + */ | ||
| 16 | +public interface ICameraControl { | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 闪光灯关 {@link #setFlashMode(int)} | ||
| 20 | + */ | ||
| 21 | + int FLASH_MODE_OFF = 0; | ||
| 22 | + /** | ||
| 23 | + * 闪光灯开 {@link #setFlashMode(int)} | ||
| 24 | + */ | ||
| 25 | + int FLASH_MODE_TORCH = 1; | ||
| 26 | + /** | ||
| 27 | + * 闪光灯自动 {@link #setFlashMode(int)} | ||
| 28 | + */ | ||
| 29 | + int FLASH_MODE_AUTO = 2; | ||
| 30 | + | ||
| 31 | + @IntDef({FLASH_MODE_TORCH, FLASH_MODE_OFF, FLASH_MODE_AUTO}) | ||
| 32 | + @interface FlashMode { | ||
| 33 | + | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 照相回调。 | ||
| 38 | + */ | ||
| 39 | + interface OnTakePictureCallback { | ||
| 40 | + void onPictureTaken(byte[] data); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * 设置本地质量控制回调,如果不设置则视为不扫描调用本地质量控制代码。 | ||
| 45 | + */ | ||
| 46 | + void setDetectCallback(OnDetectPictureCallback callback); | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 预览回调 | ||
| 50 | + */ | ||
| 51 | + interface OnDetectPictureCallback { | ||
| 52 | + int onDetect(byte[] data, int rotation); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * 打开相机。 | ||
| 57 | + */ | ||
| 58 | + void start(); | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * 关闭相机 | ||
| 62 | + */ | ||
| 63 | + void stop(); | ||
| 64 | + | ||
| 65 | + void pause(); | ||
| 66 | + | ||
| 67 | + void resume(); | ||
| 68 | + | ||
| 69 | + /** | ||
| 70 | + * 相机对应的预览视图。 | ||
| 71 | + * @return 预览视图 | ||
| 72 | + */ | ||
| 73 | + View getDisplayView(); | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * 看到的预览可能不是照片的全部。返回预览视图的全貌。 | ||
| 77 | + * @return 预览视图frame; | ||
| 78 | + */ | ||
| 79 | + Rect getPreviewFrame(); | ||
| 80 | + | ||
| 81 | + /** | ||
| 82 | + * 拍照。结果在回调中获取。 | ||
| 83 | + * @param callback 拍照结果回调 | ||
| 84 | + */ | ||
| 85 | + void takePicture(OnTakePictureCallback callback); | ||
| 86 | + | ||
| 87 | + /** | ||
| 88 | + * 设置权限回调,当手机没有拍照权限时,可在回调中获取。 | ||
| 89 | + * @param callback 权限回调 | ||
| 90 | + */ | ||
| 91 | + void setPermissionCallback(PermissionCallback callback); | ||
| 92 | + | ||
| 93 | + /** | ||
| 94 | + * 设置水平方向 | ||
| 95 | + * @param displayOrientation 参数值见 {@link com.cnlive.ocr.ui.camera.CameraView.Orientation} | ||
| 96 | + */ | ||
| 97 | + void setDisplayOrientation(@CameraView.Orientation int displayOrientation); | ||
| 98 | + | ||
| 99 | + /** | ||
| 100 | + * 获取到拍照权限时,调用些函数以继续。 | ||
| 101 | + */ | ||
| 102 | + void refreshPermission(); | ||
| 103 | + | ||
| 104 | + /** | ||
| 105 | + * 获取已经扫描成功,处理中 | ||
| 106 | + */ | ||
| 107 | + AtomicBoolean getAbortingScan(); | ||
| 108 | + | ||
| 109 | + /** | ||
| 110 | + * 设置闪光灯状态。 | ||
| 111 | + * @param flashMode {@link #FLASH_MODE_TORCH,#FLASH_MODE_OFF,#FLASH_MODE_AUTO} | ||
| 112 | + */ | ||
| 113 | + void setFlashMode(@FlashMode int flashMode); | ||
| 114 | + | ||
| 115 | + /** | ||
| 116 | + * 获取当前闪光灯状态 | ||
| 117 | + * @return 当前闪光灯状态 参见 {@link #setFlashMode(int)} | ||
| 118 | + */ | ||
| 119 | + @FlashMode | ||
| 120 | + int getFlashMode(); | ||
| 121 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/MaskView.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.camera; | ||
| 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.util.AttributeSet; | ||
| 17 | +import android.view.View; | ||
| 18 | + | ||
| 19 | +import com.cnlive.core.people_verfication.R; | ||
| 20 | + | ||
| 21 | +import java.io.File; | ||
| 22 | + | ||
| 23 | +import androidx.annotation.IntDef; | ||
| 24 | +import androidx.annotation.RequiresApi; | ||
| 25 | +import androidx.core.content.res.ResourcesCompat; | ||
| 26 | + | ||
| 27 | +@SuppressWarnings("unused") | ||
| 28 | +public class MaskView extends View { | ||
| 29 | + | ||
| 30 | + public static final int MASK_TYPE_NONE = 0; | ||
| 31 | + public static final int MASK_TYPE_ID_CARD_FRONT = 1; | ||
| 32 | + public static final int MASK_TYPE_ID_CARD_BACK = 2; | ||
| 33 | + public static final int MASK_TYPE_BANK_CARD = 11; | ||
| 34 | + public static final int MASK_TYPE_AVATAR = 12; | ||
| 35 | + | ||
| 36 | + @IntDef({MASK_TYPE_NONE, MASK_TYPE_ID_CARD_FRONT, MASK_TYPE_ID_CARD_BACK, MASK_TYPE_BANK_CARD, MASK_TYPE_AVATAR}) | ||
| 37 | + @interface MaskType { | ||
| 38 | + | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public void setLineColor(int lineColor) { | ||
| 42 | + this.lineColor = lineColor; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public void setMaskColor(int maskColor) { | ||
| 46 | + this.maskColor = maskColor; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + private int lineColor = Color.WHITE; | ||
| 50 | + | ||
| 51 | + private int maskType = MASK_TYPE_ID_CARD_FRONT; | ||
| 52 | + | ||
| 53 | + private int maskColor = Color.argb(100, 0, 0, 0); | ||
| 54 | + | ||
| 55 | + private Paint eraser = new Paint(Paint.ANTI_ALIAS_FLAG); | ||
| 56 | + private Paint pen = new Paint(Paint.ANTI_ALIAS_FLAG); | ||
| 57 | + | ||
| 58 | + private Rect frame = new Rect(); | ||
| 59 | + | ||
| 60 | + private Drawable locatorDrawable; | ||
| 61 | + | ||
| 62 | + public Rect getFrameRect() { | ||
| 63 | + if (maskType == MASK_TYPE_NONE) { | ||
| 64 | + return new Rect(0, 0, getWidth(), getHeight()); | ||
| 65 | + } else { | ||
| 66 | + return new Rect(frame); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + public Rect getFrameRectExtend() { | ||
| 72 | + Rect rc = new Rect(frame); | ||
| 73 | + int widthExtend = (int) ((frame.right - frame.left) * 0.02f); | ||
| 74 | + int heightExtend = (int) ((frame.bottom - frame.top) * 0.02f); | ||
| 75 | + rc.left -= widthExtend; | ||
| 76 | + rc.right += widthExtend; | ||
| 77 | + rc.top -= heightExtend; | ||
| 78 | + rc.bottom += heightExtend; | ||
| 79 | + return rc; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + public void setMaskType(@MaskType int maskType) { | ||
| 83 | + this.maskType = maskType; | ||
| 84 | + switch (maskType) { | ||
| 85 | + case MASK_TYPE_ID_CARD_FRONT: | ||
| 86 | + locatorDrawable = ResourcesCompat.getDrawable(getResources(), | ||
| 87 | + R.drawable.bd_ocr_id_card_locator_front, null); | ||
| 88 | + break; | ||
| 89 | + case MASK_TYPE_ID_CARD_BACK: | ||
| 90 | + locatorDrawable = ResourcesCompat.getDrawable(getResources(), | ||
| 91 | + R.drawable.bd_ocr_id_card_locator_back, null); | ||
| 92 | + break; | ||
| 93 | + case MASK_TYPE_BANK_CARD: | ||
| 94 | + break; | ||
| 95 | + case MASK_TYPE_AVATAR: | ||
| 96 | + break; | ||
| 97 | + case MASK_TYPE_NONE: | ||
| 98 | + default: | ||
| 99 | + break; | ||
| 100 | + } | ||
| 101 | + invalidate(); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + public int getMaskType() { | ||
| 105 | + return maskType; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + public void setOrientation(@CameraView.Orientation int orientation) { | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + public MaskView(Context context) { | ||
| 112 | + super(context); | ||
| 113 | + init(); | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + public MaskView(Context context, AttributeSet attrs) { | ||
| 117 | + super(context, attrs); | ||
| 118 | + init(); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + public MaskView(Context context, AttributeSet attrs, int defStyleAttr) { | ||
| 122 | + super(context, attrs, defStyleAttr); | ||
| 123 | + init(); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + private void init() { | ||
| 127 | + locatorDrawable = ResourcesCompat.getDrawable(getResources(), R.drawable.bd_ocr_id_card_locator_front, null); | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + @Override | ||
| 131 | + protected void onSizeChanged(int w, int h, int oldw, int oldh) { | ||
| 132 | + super.onSizeChanged(w, h, oldw, oldh); | ||
| 133 | + if (w > 0 && h > 0) { | ||
| 134 | + float ratio = h > w ? 0.9f : 0.72f; | ||
| 135 | + | ||
| 136 | + int width = (int) (w * ratio); | ||
| 137 | + int height; | ||
| 138 | + if (maskType == MASK_TYPE_AVATAR) { | ||
| 139 | + height = width; | ||
| 140 | + } else { | ||
| 141 | + height = width * 400 / 620; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + int left = (w - width) / 2; | ||
| 145 | + int top = (h - height) / 2; | ||
| 146 | + int right = width + left; | ||
| 147 | + int bottom = height + top; | ||
| 148 | + | ||
| 149 | + frame.left = left; | ||
| 150 | + frame.top = top; | ||
| 151 | + frame.right = right; | ||
| 152 | + frame.bottom = bottom; | ||
| 153 | + | ||
| 154 | + } | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) | ||
| 158 | + @Override | ||
| 159 | + protected void onDraw(Canvas canvas) { | ||
| 160 | + super.onDraw(canvas); | ||
| 161 | + | ||
| 162 | + int width = frame.width(); | ||
| 163 | + int height = frame.height(); | ||
| 164 | + | ||
| 165 | + int left = frame.left; | ||
| 166 | + int top = frame.top; | ||
| 167 | + int right = frame.right; | ||
| 168 | + int bottom = frame.bottom; | ||
| 169 | + | ||
| 170 | + canvas.drawColor(maskColor); | ||
| 171 | + fillRectRound(left, top, right, bottom, 30, 30, false); | ||
| 172 | + canvas.drawPath(path, pen); | ||
| 173 | + canvas.drawPath(path, eraser); | ||
| 174 | + | ||
| 175 | + if (maskType == MASK_TYPE_ID_CARD_FRONT) { | ||
| 176 | + locatorDrawable.setBounds( | ||
| 177 | + (int) (left + 601f / 1006 * width), | ||
| 178 | + (int) (top + (110f / 632) * height), | ||
| 179 | + (int) (left + (963f / 1006) * width), | ||
| 180 | + (int) (top + (476f / 632) * height)); | ||
| 181 | + } else if (maskType == MASK_TYPE_ID_CARD_BACK) { | ||
| 182 | + locatorDrawable.setBounds( | ||
| 183 | + (int) (left + 51f / 1006 * width), | ||
| 184 | + (int) (top + (48f / 632) * height), | ||
| 185 | + (int) (left + (250f / 1006) * width), | ||
| 186 | + (int) (top + (262f / 632) * height)); | ||
| 187 | + } | ||
| 188 | + if (locatorDrawable != null) { | ||
| 189 | + locatorDrawable.draw(canvas); | ||
| 190 | + } | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + private Path path = new Path(); | ||
| 194 | + | ||
| 195 | + private Path fillRectRound(float left, float top, float right, float bottom, float rx, float ry, boolean | ||
| 196 | + conformToOriginalPost) { | ||
| 197 | + | ||
| 198 | + path.reset(); | ||
| 199 | + if (rx < 0) { | ||
| 200 | + rx = 0; | ||
| 201 | + } | ||
| 202 | + if (ry < 0) { | ||
| 203 | + ry = 0; | ||
| 204 | + } | ||
| 205 | + float width = right - left; | ||
| 206 | + float height = bottom - top; | ||
| 207 | + if (rx > width / 2) { | ||
| 208 | + rx = width / 2; | ||
| 209 | + } | ||
| 210 | + if (ry > height / 2) { | ||
| 211 | + ry = height / 2; | ||
| 212 | + } | ||
| 213 | + float widthMinusCorners = (width - (2 * rx)); | ||
| 214 | + float heightMinusCorners = (height - (2 * ry)); | ||
| 215 | + | ||
| 216 | + path.moveTo(right, top + ry); | ||
| 217 | + path.rQuadTo(0, -ry, -rx, -ry); | ||
| 218 | + path.rLineTo(-widthMinusCorners, 0); | ||
| 219 | + path.rQuadTo(-rx, 0, -rx, ry); | ||
| 220 | + path.rLineTo(0, heightMinusCorners); | ||
| 221 | + | ||
| 222 | + if (conformToOriginalPost) { | ||
| 223 | + path.rLineTo(0, ry); | ||
| 224 | + path.rLineTo(width, 0); | ||
| 225 | + path.rLineTo(0, -ry); | ||
| 226 | + } else { | ||
| 227 | + path.rQuadTo(0, ry, rx, ry); | ||
| 228 | + path.rLineTo(widthMinusCorners, 0); | ||
| 229 | + path.rQuadTo(rx, 0, rx, -ry); | ||
| 230 | + } | ||
| 231 | + | ||
| 232 | + path.rLineTo(0, -heightMinusCorners); | ||
| 233 | + path.close(); | ||
| 234 | + return path; | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + { | ||
| 238 | + // 硬件加速不支持,图层混合。 | ||
| 239 | + setLayerType(View.LAYER_TYPE_SOFTWARE, null); | ||
| 240 | + | ||
| 241 | + pen.setColor(Color.WHITE); | ||
| 242 | + pen.setStyle(Paint.Style.STROKE); | ||
| 243 | + pen.setStrokeWidth(6); | ||
| 244 | + | ||
| 245 | + eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); | ||
| 246 | + } | ||
| 247 | + | ||
| 248 | + private void capture(File file) { | ||
| 249 | + | ||
| 250 | + } | ||
| 251 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/OCRCameraLayout.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.camera; | ||
| 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 | +import com.cnlive.core.people_verfication.R; | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +public class OCRCameraLayout extends FrameLayout { | ||
| 20 | + | ||
| 21 | + public static int ORIENTATION_PORTRAIT = 0; | ||
| 22 | + public static int ORIENTATION_HORIZONTAL = 1; | ||
| 23 | + | ||
| 24 | + private int orientation = ORIENTATION_PORTRAIT; | ||
| 25 | + private View contentView; | ||
| 26 | + private View centerView; | ||
| 27 | + private View leftDownView; | ||
| 28 | + private View rightUpView; | ||
| 29 | + | ||
| 30 | + private int contentViewId; | ||
| 31 | + private int centerViewId; | ||
| 32 | + private int leftDownViewId; | ||
| 33 | + private int rightUpViewId; | ||
| 34 | + | ||
| 35 | + public void setOrientation(int orientation) { | ||
| 36 | + if (this.orientation == orientation) { | ||
| 37 | + return; | ||
| 38 | + } | ||
| 39 | + this.orientation = orientation; | ||
| 40 | + requestLayout(); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + public OCRCameraLayout(Context context) { | ||
| 44 | + super(context); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + public OCRCameraLayout(Context context, AttributeSet attrs) { | ||
| 48 | + super(context, attrs); | ||
| 49 | + parseAttrs(attrs); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public OCRCameraLayout(Context context, AttributeSet attrs, int defStyleAttr) { | ||
| 53 | + super(context, attrs, defStyleAttr); | ||
| 54 | + parseAttrs(attrs); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + { | ||
| 58 | + setWillNotDraw(false); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + private void parseAttrs(AttributeSet attrs) { | ||
| 62 | + TypedArray a = getContext().getTheme().obtainStyledAttributes( | ||
| 63 | + attrs, | ||
| 64 | + R.styleable.OCRCameraLayout, | ||
| 65 | + 0, 0); | ||
| 66 | + try { | ||
| 67 | + contentViewId = a.getResourceId(R.styleable.OCRCameraLayout_contentView, -1); | ||
| 68 | + centerViewId = a.getResourceId(R.styleable.OCRCameraLayout_centerView, -1); | ||
| 69 | + leftDownViewId = a.getResourceId(R.styleable.OCRCameraLayout_leftDownView, -1); | ||
| 70 | + rightUpViewId = a.getResourceId(R.styleable.OCRCameraLayout_rightUpView, -1); | ||
| 71 | + } finally { | ||
| 72 | + a.recycle(); | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + @Override | ||
| 77 | + protected void onAttachedToWindow() { | ||
| 78 | + super.onAttachedToWindow(); | ||
| 79 | + contentView = findViewById(contentViewId); | ||
| 80 | + if (centerViewId != -1) { | ||
| 81 | + centerView = findViewById(centerViewId); | ||
| 82 | + } | ||
| 83 | + leftDownView = findViewById(leftDownViewId); | ||
| 84 | + rightUpView = findViewById(rightUpViewId); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + private Rect backgroundRect = new Rect(); | ||
| 88 | + private Paint paint = new Paint(); | ||
| 89 | + | ||
| 90 | + { | ||
| 91 | + paint.setStyle(Paint.Style.FILL); | ||
| 92 | + paint.setColor(Color.argb(83, 0, 0, 0)); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + @Override | ||
| 96 | + protected void onLayout(boolean changed, int l, int t, int r, int b) { | ||
| 97 | + int width = getWidth(); | ||
| 98 | + int height = getHeight(); | ||
| 99 | + int left; | ||
| 100 | + int top; | ||
| 101 | + | ||
| 102 | + MarginLayoutParams leftDownViewLayoutParams = (MarginLayoutParams) leftDownView.getLayoutParams(); | ||
| 103 | + MarginLayoutParams rightUpViewLayoutParams = (MarginLayoutParams) rightUpView.getLayoutParams(); | ||
| 104 | + if (r < b) { | ||
| 105 | + int contentHeight = width * 4 / 3; | ||
| 106 | + int heightLeft = height - contentHeight; | ||
| 107 | + contentView.layout(l, t, r, contentHeight); | ||
| 108 | + | ||
| 109 | + backgroundRect.left = 0; | ||
| 110 | + backgroundRect.top = contentHeight; | ||
| 111 | + backgroundRect.right = width; | ||
| 112 | + backgroundRect.bottom = height; | ||
| 113 | + | ||
| 114 | + // layout centerView; | ||
| 115 | + if (centerView != null) { | ||
| 116 | + left = (width - centerView.getMeasuredWidth()) / 2; | ||
| 117 | + top = contentHeight + (heightLeft - centerView.getMeasuredHeight()) / 2; | ||
| 118 | + centerView | ||
| 119 | + .layout(left, top, left + centerView.getMeasuredWidth(), top + centerView.getMeasuredHeight()); | ||
| 120 | + } | ||
| 121 | + // layout leftDownView | ||
| 122 | + | ||
| 123 | + left = leftDownViewLayoutParams.leftMargin; | ||
| 124 | + top = contentHeight + (heightLeft - leftDownView.getMeasuredHeight()) / 2; | ||
| 125 | + leftDownView | ||
| 126 | + .layout(left, top, left + leftDownView.getMeasuredWidth(), top + leftDownView.getMeasuredHeight()); | ||
| 127 | + // layout rightUpView | ||
| 128 | + left = width - rightUpView.getMeasuredWidth() - rightUpViewLayoutParams.rightMargin; | ||
| 129 | + top = contentHeight + (heightLeft - rightUpView.getMeasuredHeight()) / 2; | ||
| 130 | + rightUpView.layout(left, top, left + rightUpView.getMeasuredWidth(), top + rightUpView.getMeasuredHeight()); | ||
| 131 | + } else { | ||
| 132 | + int contentWidth = height * 4 / 3; | ||
| 133 | + int widthLeft = width - contentWidth; | ||
| 134 | + contentView.layout(l, t, contentWidth, height); | ||
| 135 | + | ||
| 136 | + backgroundRect.left = contentWidth; | ||
| 137 | + backgroundRect.top = 0; | ||
| 138 | + backgroundRect.right = width; | ||
| 139 | + backgroundRect.bottom = height; | ||
| 140 | + | ||
| 141 | + // layout centerView | ||
| 142 | + if (centerView != null) { | ||
| 143 | + left = contentWidth + (widthLeft - centerView.getMeasuredWidth()) / 2; | ||
| 144 | + top = (height - centerView.getMeasuredHeight()) / 2; | ||
| 145 | + centerView | ||
| 146 | + .layout(left, top, left + centerView.getMeasuredWidth(), top + centerView.getMeasuredHeight()); | ||
| 147 | + } | ||
| 148 | + // layout leftDownView | ||
| 149 | + left = contentWidth + (widthLeft - leftDownView.getMeasuredWidth()) / 2; | ||
| 150 | + top = height - leftDownView.getMeasuredHeight() - leftDownViewLayoutParams.bottomMargin; | ||
| 151 | + leftDownView | ||
| 152 | + .layout(left, top, left + leftDownView.getMeasuredWidth(), top + leftDownView.getMeasuredHeight()); | ||
| 153 | + // layout rightUpView | ||
| 154 | + left = contentWidth + (widthLeft - rightUpView.getMeasuredWidth()) / 2; | ||
| 155 | + | ||
| 156 | + top = rightUpViewLayoutParams.topMargin; | ||
| 157 | + rightUpView.layout(left, top, left + rightUpView.getMeasuredWidth(), top + rightUpView.getMeasuredHeight()); | ||
| 158 | + } | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + @Override | ||
| 162 | + protected void onDraw(Canvas canvas) { | ||
| 163 | + super.onDraw(canvas); | ||
| 164 | + canvas.drawRect(backgroundRect, paint); | ||
| 165 | + } | ||
| 166 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/OCRFrameLayout.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.camera; | ||
| 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 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/camera/PermissionCallback.java
0 → 100644
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/crop/CropView.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.crop; | ||
| 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 | +import com.cnlive.core.libs.base.util.AlertUtil; | ||
| 22 | +import com.cnlive.core.ocr.ui.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 | + options.inPreferredConfig = Bitmap.Config.RGB_565; | ||
| 82 | + | ||
| 83 | + options.inJustDecodeBounds = false; | ||
| 84 | + this.bitmap = BitmapFactory.decodeFile(path, options); | ||
| 85 | + } catch (IOException e) { | ||
| 86 | + e.printStackTrace(); | ||
| 87 | + this.bitmap = original; | ||
| 88 | + } catch (NullPointerException e) { | ||
| 89 | + e.printStackTrace(); | ||
| 90 | + } | ||
| 91 | + setBitmap(this.bitmap); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + private void setBitmap(Bitmap bitmap) { | ||
| 95 | + this.bitmap = bitmap; | ||
| 96 | + matrix.reset(); | ||
| 97 | + centerImage(getWidth(), getHeight()); | ||
| 98 | + rotation = 0; | ||
| 99 | + invalidate(); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + @Override | ||
| 103 | + protected void onSizeChanged(int w, int h, int oldw, int oldh) { | ||
| 104 | + super.onSizeChanged(w, h, oldw, oldh); | ||
| 105 | + centerImage(w, h); | ||
| 106 | + invalidate(); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + public Bitmap crop(Rect frame) { | ||
| 110 | + float scale = getScale(); | ||
| 111 | + | ||
| 112 | + float[] src = new float[] {frame.left, frame.top}; | ||
| 113 | + float[] desc = new float[] {0, 0}; | ||
| 114 | + | ||
| 115 | + Matrix invertedMatrix = new Matrix(); | ||
| 116 | + this.matrix.invert(invertedMatrix); | ||
| 117 | + invertedMatrix.mapPoints(desc, src); | ||
| 118 | + | ||
| 119 | + Matrix matrix = new Matrix(); | ||
| 120 | + | ||
| 121 | + int width = (int) (frame.width() / scale); | ||
| 122 | + int height = (int) (frame.height() / scale); | ||
| 123 | + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); | ||
| 124 | + Canvas canvas = new Canvas(bitmap); | ||
| 125 | + | ||
| 126 | + Bitmap originalBitmap = this.bitmap; | ||
| 127 | + if(originalBitmap == null){ | ||
| 128 | + AlertUtil.showDeftToast(getContext(),"该照片已失效,请重新选择"); | ||
| 129 | + return null; | ||
| 130 | + } | ||
| 131 | + matrix.postTranslate(-desc[0], -desc[1]); | ||
| 132 | + canvas.drawBitmap(originalBitmap, matrix, null); | ||
| 133 | + return bitmap; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + public void setMinimumScale(float setMinimumScale) { | ||
| 137 | + this.setMinimumScale = setMinimumScale; | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + public void setMaximumScale(float maximumScale) { | ||
| 141 | + this.maximumScale = maximumScale; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + private float setMinimumScale = 0.2f; | ||
| 145 | + private float maximumScale = 4.0f; | ||
| 146 | + | ||
| 147 | + private float[] matrixArray = new float[9]; | ||
| 148 | + private Matrix matrix = new Matrix(); | ||
| 149 | + private Bitmap bitmap; | ||
| 150 | + | ||
| 151 | + private GestureDetector gestureDetector; | ||
| 152 | + | ||
| 153 | + private ScaleGestureDetector scaleGestureDetector; | ||
| 154 | + private ScaleGestureDetector.OnScaleGestureListener onScaleGestureListener = | ||
| 155 | + new ScaleGestureDetector.OnScaleGestureListener() { | ||
| 156 | + @Override | ||
| 157 | + public boolean onScale(ScaleGestureDetector detector) { | ||
| 158 | + scale(detector); | ||
| 159 | + return true; | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + @Override | ||
| 163 | + public boolean onScaleBegin(ScaleGestureDetector detector) { | ||
| 164 | + return true; | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + @Override | ||
| 168 | + public void onScaleEnd(ScaleGestureDetector detector) { | ||
| 169 | + float scale = detector.getScaleFactor(); | ||
| 170 | + matrix.postScale(scale, scale); | ||
| 171 | + invalidate(); | ||
| 172 | + } | ||
| 173 | + }; | ||
| 174 | + | ||
| 175 | + private void init() { | ||
| 176 | + scaleGestureDetector = new ScaleGestureDetector(getContext(), onScaleGestureListener); | ||
| 177 | + gestureDetector = new GestureDetector(getContext(), new GestureDetector.OnGestureListener() { | ||
| 178 | + @Override | ||
| 179 | + public boolean onDown(MotionEvent e) { | ||
| 180 | + return true; | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + @Override | ||
| 184 | + public void onShowPress(MotionEvent e) { | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + @Override | ||
| 188 | + public boolean onSingleTapUp(MotionEvent e) { | ||
| 189 | + return false; | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + @Override | ||
| 193 | + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { | ||
| 194 | + translate(distanceX, distanceY); | ||
| 195 | + return true; | ||
| 196 | + } | ||
| 197 | + | ||
| 198 | + @Override | ||
| 199 | + public void onLongPress(MotionEvent e) { | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + @Override | ||
| 203 | + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { | ||
| 204 | + return false; | ||
| 205 | + } | ||
| 206 | + }); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + int rotation = 0; | ||
| 210 | + | ||
| 211 | + public void rotate(int degrees) { | ||
| 212 | + Matrix matrix = new Matrix(); | ||
| 213 | + | ||
| 214 | + if(bitmap == null) return; | ||
| 215 | + int dx = this.bitmap.getWidth() / 2; | ||
| 216 | + int dy = this.bitmap.getHeight() / 2; | ||
| 217 | + | ||
| 218 | + matrix.postTranslate(-dx, -dy); | ||
| 219 | + matrix.postRotate(degrees); | ||
| 220 | + matrix.postTranslate(dy, dx); | ||
| 221 | + Bitmap scaledBitmap = this.bitmap; | ||
| 222 | + Bitmap rotatedBitmap = Bitmap.createBitmap(scaledBitmap.getHeight(), scaledBitmap.getWidth(), | ||
| 223 | + Bitmap.Config.RGB_565); | ||
| 224 | + Canvas canvas = new Canvas(rotatedBitmap); | ||
| 225 | + canvas.drawBitmap(this.bitmap, matrix, null); | ||
| 226 | + this.bitmap.recycle(); | ||
| 227 | + this.bitmap = rotatedBitmap; | ||
| 228 | + centerImage(getWidth(), getHeight()); | ||
| 229 | + invalidate(); | ||
| 230 | + } | ||
| 231 | + | ||
| 232 | + private void translate(float distanceX, float distanceY) { | ||
| 233 | + matrix.getValues(matrixArray); | ||
| 234 | + float left = matrixArray[Matrix.MTRANS_X]; | ||
| 235 | + float top = matrixArray[Matrix.MTRANS_Y]; | ||
| 236 | + | ||
| 237 | + Rect bound = getRestrictedBound(); | ||
| 238 | + if (bound != null) { | ||
| 239 | + float scale = getScale(); | ||
| 240 | + float right = left + (int) (bitmap.getWidth() / scale); | ||
| 241 | + float bottom = top + (int) (bitmap.getHeight() / scale); | ||
| 242 | + | ||
| 243 | + if (left - distanceX > bound.left) { | ||
| 244 | + distanceX = left - bound.left; | ||
| 245 | + } | ||
| 246 | + if (top - distanceY > bound.top) { | ||
| 247 | + distanceY = top - bound.top; | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + if (distanceX > 0) { | ||
| 251 | + if (right - distanceX < bound.right) { | ||
| 252 | + distanceX = right - bound.right; | ||
| 253 | + } | ||
| 254 | + } | ||
| 255 | + if (distanceY > 0) { | ||
| 256 | + if (bottom - distanceY < bound.bottom) { | ||
| 257 | + distanceY = bottom - bound.bottom; | ||
| 258 | + } | ||
| 259 | + } | ||
| 260 | + } | ||
| 261 | + matrix.postTranslate(-distanceX, -distanceY); | ||
| 262 | + invalidate(); | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + private void scale(ScaleGestureDetector detector) { | ||
| 266 | + float scale = detector.getScaleFactor(); | ||
| 267 | + float currentScale = getScale(); | ||
| 268 | + if (currentScale * scale < setMinimumScale) { | ||
| 269 | + scale = setMinimumScale / currentScale; | ||
| 270 | + } | ||
| 271 | + if (currentScale * scale > maximumScale) { | ||
| 272 | + scale = maximumScale / currentScale; | ||
| 273 | + } | ||
| 274 | + matrix.postScale(scale, scale, detector.getFocusX(), detector.getFocusY()); | ||
| 275 | + invalidate(); | ||
| 276 | + } | ||
| 277 | + | ||
| 278 | + private void centerImage(int width, int height) { | ||
| 279 | + if (width <= 0 || height <= 0 || bitmap == null) { | ||
| 280 | + return; | ||
| 281 | + } | ||
| 282 | + float widthRatio = 1.0f * height / this.bitmap.getHeight(); | ||
| 283 | + float heightRatio = 1.0f * width / this.bitmap.getWidth(); | ||
| 284 | + | ||
| 285 | + float ratio = Math.min(widthRatio, heightRatio); | ||
| 286 | + | ||
| 287 | + float dx = (width - this.bitmap.getWidth()) / 2; | ||
| 288 | + float dy = (height - this.bitmap.getHeight()) / 2; | ||
| 289 | + matrix.setTranslate(0, 0); | ||
| 290 | + matrix.setScale(ratio, ratio, bitmap.getWidth() / 2, bitmap.getHeight() / 2); | ||
| 291 | + matrix.postTranslate(dx, dy); | ||
| 292 | + invalidate(); | ||
| 293 | + } | ||
| 294 | + | ||
| 295 | + private float getScale() { | ||
| 296 | + matrix.getValues(matrixArray); | ||
| 297 | + float scale = matrixArray[Matrix.MSCALE_X]; | ||
| 298 | + if (Math.abs(scale) <= 0.1) { | ||
| 299 | + scale = matrixArray[Matrix.MSKEW_X]; | ||
| 300 | + } | ||
| 301 | + return Math.abs(scale); | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + @Override | ||
| 305 | + protected void onDraw(Canvas canvas) { | ||
| 306 | + super.onDraw(canvas); | ||
| 307 | + if (bitmap != null) { | ||
| 308 | + canvas.drawBitmap(bitmap, matrix, null); | ||
| 309 | + } | ||
| 310 | + } | ||
| 311 | + | ||
| 312 | + @Override | ||
| 313 | + public boolean onTouchEvent(MotionEvent event) { | ||
| 314 | + boolean result = scaleGestureDetector.onTouchEvent(event); | ||
| 315 | + result = gestureDetector.onTouchEvent(event) || result; | ||
| 316 | + return result || super.onTouchEvent(event); | ||
| 317 | + } | ||
| 318 | + | ||
| 319 | + private Rect restrictBound; | ||
| 320 | + | ||
| 321 | + private Rect getRestrictedBound() { | ||
| 322 | + return restrictBound; | ||
| 323 | + } | ||
| 324 | + | ||
| 325 | + public void setRestrictBound(Rect rect) { | ||
| 326 | + this.restrictBound = rect; | ||
| 327 | + } | ||
| 328 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/crop/FrameOverlayView.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.crop; | ||
| 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.cnlive.core.ocr.ui.util.PxUtil; | ||
| 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 = PxUtil.dp2px(18); | ||
| 98 | + cornerLineWidth = PxUtil.dp2px(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(PxUtil.dp2px(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 | + | ||
| 277 | + private void notifyFrameChange() { | ||
| 278 | + if (onFrameChangeListener != null) { | ||
| 279 | + onFrameChangeListener.onFrameChange(frameRect); | ||
| 280 | + } | ||
| 281 | + } | ||
| 282 | + | ||
| 283 | + private float getMinimumFrameWidth() { | ||
| 284 | + return 2.4f * cornerLength; | ||
| 285 | + } | ||
| 286 | + | ||
| 287 | + private float getMinimumFrameHeight() { | ||
| 288 | + return 2.4f * cornerLength; | ||
| 289 | + } | ||
| 290 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/util/ImageUtil.java
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + */ | ||
| 4 | +package com.cnlive.core.ocr.ui.util; | ||
| 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.Paint; | ||
| 11 | +import android.graphics.Path; | ||
| 12 | +import android.graphics.Rect; | ||
| 13 | +import android.media.ExifInterface; | ||
| 14 | +import android.util.Log; | ||
| 15 | + | ||
| 16 | +public class ImageUtil { | ||
| 17 | + private static final String TAG = "CameraExif"; | ||
| 18 | + | ||
| 19 | + public static int exifToDegrees(int exifOrientation) { | ||
| 20 | + if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { | ||
| 21 | + return 90; | ||
| 22 | + } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) { | ||
| 23 | + return 180; | ||
| 24 | + } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) { | ||
| 25 | + return 270; | ||
| 26 | + } | ||
| 27 | + return 0; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + // Returns the degrees in clockwise. Values are 0, 90, 180, or 270. | ||
| 31 | + public static int getOrientation(byte[] jpeg) { | ||
| 32 | + if (jpeg == null) { | ||
| 33 | + return 0; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + int offset = 0; | ||
| 37 | + int length = 0; | ||
| 38 | + | ||
| 39 | + // ISO/IEC 10918-1:1993(E) | ||
| 40 | + while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) { | ||
| 41 | + int marker = jpeg[offset] & 0xFF; | ||
| 42 | + | ||
| 43 | + // Check if the marker is a padding. | ||
| 44 | + if (marker == 0xFF) { | ||
| 45 | + continue; | ||
| 46 | + } | ||
| 47 | + offset++; | ||
| 48 | + | ||
| 49 | + // Check if the marker is SOI or TEM. | ||
| 50 | + if (marker == 0xD8 || marker == 0x01) { | ||
| 51 | + continue; | ||
| 52 | + } | ||
| 53 | + // Check if the marker is EOI or SOS. | ||
| 54 | + if (marker == 0xD9 || marker == 0xDA) { | ||
| 55 | + break; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + // Get the length and check if it is reasonable. | ||
| 59 | + length = pack(jpeg, offset, 2, false); | ||
| 60 | + if (length < 2 || offset + length > jpeg.length) { | ||
| 61 | + Log.e(TAG, "Invalid length"); | ||
| 62 | + return 0; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + // Break if the marker is EXIF in APP1. | ||
| 66 | + if (marker == 0xE1 && length >= 8 | ||
| 67 | + && pack(jpeg, offset + 2, 4, false) == 0x45786966 | ||
| 68 | + && pack(jpeg, offset + 6, 2, false) == 0) { | ||
| 69 | + offset += 8; | ||
| 70 | + length -= 8; | ||
| 71 | + break; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + // Skip other markers. | ||
| 75 | + offset += length; | ||
| 76 | + length = 0; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + // JEITA CP-3451 Exif Version 2.2 | ||
| 80 | + if (length > 8) { | ||
| 81 | + // Identify the byte order. | ||
| 82 | + int tag = pack(jpeg, offset, 4, false); | ||
| 83 | + if (tag != 0x49492A00 && tag != 0x4D4D002A) { | ||
| 84 | + Log.e(TAG, "Invalid byte order"); | ||
| 85 | + return 0; | ||
| 86 | + } | ||
| 87 | + boolean littleEndian = (tag == 0x49492A00); | ||
| 88 | + | ||
| 89 | + // Get the offset and check if it is reasonable. | ||
| 90 | + int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; | ||
| 91 | + if (count < 10 || count > length) { | ||
| 92 | + Log.e(TAG, "Invalid offset"); | ||
| 93 | + return 0; | ||
| 94 | + } | ||
| 95 | + offset += count; | ||
| 96 | + length -= count; | ||
| 97 | + | ||
| 98 | + // Get the count and go through all the elements. | ||
| 99 | + count = pack(jpeg, offset - 2, 2, littleEndian); | ||
| 100 | + while (count-- > 0 && length >= 12) { | ||
| 101 | + // Get the tag and check if it is orientation. | ||
| 102 | + tag = pack(jpeg, offset, 2, littleEndian); | ||
| 103 | + if (tag == 0x0112) { | ||
| 104 | + // We do not really care about type and count, do we? | ||
| 105 | + int orientation = pack(jpeg, offset + 8, 2, littleEndian); | ||
| 106 | + switch (orientation) { | ||
| 107 | + case 1: | ||
| 108 | + return 0; | ||
| 109 | + case 3: | ||
| 110 | + return 180; | ||
| 111 | + case 6: | ||
| 112 | + return 90; | ||
| 113 | + case 8: | ||
| 114 | + return 270; | ||
| 115 | + default: | ||
| 116 | + return 0; | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + offset += 12; | ||
| 120 | + length -= 12; | ||
| 121 | + } | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + Log.i(TAG, "Orientation not found"); | ||
| 125 | + return 0; | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + private static int pack(byte[] bytes, int offset, int length, | ||
| 129 | + boolean littleEndian) { | ||
| 130 | + int step = 1; | ||
| 131 | + if (littleEndian) { | ||
| 132 | + offset += length - 1; | ||
| 133 | + step = -1; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + int value = 0; | ||
| 137 | + while (length-- > 0) { | ||
| 138 | + value = (value << 8) | (bytes[offset] & 0xFF); | ||
| 139 | + offset += step; | ||
| 140 | + } | ||
| 141 | + return value; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + | ||
| 145 | + public static int calculateInSampleSize( | ||
| 146 | + BitmapFactory.Options options, int reqWidth, int reqHeight) { | ||
| 147 | + // Raw height and width of image | ||
| 148 | + final int height = options.outHeight; | ||
| 149 | + final int width = options.outWidth; | ||
| 150 | + int inSampleSize = 1; | ||
| 151 | + | ||
| 152 | + if (height > reqHeight || width > reqWidth) { | ||
| 153 | + | ||
| 154 | + final int halfHeight = height / 2; | ||
| 155 | + final int halfWidth = width / 2; | ||
| 156 | + // Calculate the largest inSampleSize value that is a power of 2 and keeps both | ||
| 157 | + // height and width larger than the requested height and width. | ||
| 158 | + while ((halfHeight / inSampleSize) >= reqHeight | ||
| 159 | + && (halfWidth / inSampleSize) >= reqWidth) { | ||
| 160 | + inSampleSize *= 2; | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + return inSampleSize; | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + /** | ||
| 168 | + * 绘制文字到右下角 | ||
| 169 | + * | ||
| 170 | + * @param context | ||
| 171 | + * @param bitmap | ||
| 172 | + * @param text | ||
| 173 | + * @param size | ||
| 174 | + * @param color | ||
| 175 | + * @param paddingRight | ||
| 176 | + * @param paddingBottom | ||
| 177 | + * @return | ||
| 178 | + */ | ||
| 179 | + public static Bitmap drawTextToRightBottom(Context context, Bitmap bitmap, String text, | ||
| 180 | + int size, int color, int paddingRight, int paddingBottom) { | ||
| 181 | + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); | ||
| 182 | + paint.setColor(color); | ||
| 183 | + paint.setTextSize(dp2px(context, size)); | ||
| 184 | + Rect bounds = new Rect(); | ||
| 185 | + paint.getTextBounds(text, 0, text.length(), bounds); | ||
| 186 | + return drawTextToBitmap(context, bitmap, text, paint, bounds, | ||
| 187 | + bitmap.getWidth() - bounds.width() - dp2px(context, paddingRight), | ||
| 188 | + bitmap.getHeight() - dp2px(context, paddingBottom)); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + //图片上绘制文字 | ||
| 192 | + private static Bitmap drawTextToBitmap(Context context, Bitmap bitmap, String text, | ||
| 193 | + Paint paint, Rect bounds, int paddingLeft, int paddingTop) { | ||
| 194 | + Bitmap.Config bitmapConfig = bitmap.getConfig(); | ||
| 195 | + | ||
| 196 | + paint.setDither(true); // 获取跟清晰的图像采样 | ||
| 197 | + paint.setFilterBitmap(true);// 过滤一些 | ||
| 198 | + if (bitmapConfig == null) { | ||
| 199 | + bitmapConfig = Bitmap.Config.ARGB_8888; | ||
| 200 | + } | ||
| 201 | + bitmap = bitmap.copy(bitmapConfig, true); | ||
| 202 | + Canvas canvas = new Canvas(bitmap); | ||
| 203 | +// canvas.rotate(-45); | ||
| 204 | +// canvas.drawText(text, paddingLeft, paddingTop, paint); | ||
| 205 | + | ||
| 206 | + Path path = new Path(); | ||
| 207 | + path.moveTo(bitmap.getWidth() / 2, bitmap.getHeight()); | ||
| 208 | + path.lineTo(bitmap.getWidth(), 50); | ||
| 209 | + | ||
| 210 | + canvas.drawTextOnPath(text, path, 20, 20, paint); | ||
| 211 | + | ||
| 212 | + return bitmap; | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + /** | ||
| 216 | + * dip转pix | ||
| 217 | + * | ||
| 218 | + * @param context | ||
| 219 | + * @param dp | ||
| 220 | + * @return | ||
| 221 | + */ | ||
| 222 | + public static int dp2px(Context context, float dp) { | ||
| 223 | + final float scale = context.getResources().getDisplayMetrics().density; | ||
| 224 | + return (int) (dp * scale + 0.5f); | ||
| 225 | + } | ||
| 226 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/ocr/ui/util/PxUtil.java
0 → 100644
core/dentity_verfication/src/main/java/com/cnlive/core/people_verfication/TakePictureActivity.java deleted
100644 → 0
| 1 | -package com.cnlive.core.people_verfication; | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -import com.cnlive.core.libs.base.frame.activity.BaseActivity; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * 拍摄照片 身份证件采集 | ||
| 8 | - */ | ||
| 9 | -public class TakePictureActivity extends BaseActivity { | ||
| 10 | - | ||
| 11 | - @Override | ||
| 12 | - protected int getViewLayoutId() { | ||
| 13 | - return R.layout.activity_take_picture; | ||
| 14 | - } | ||
| 15 | - | ||
| 16 | - @Override | ||
| 17 | - protected void initView() { | ||
| 18 | - | ||
| 19 | - } | ||
| 20 | -} |
core/dentity_verfication/src/main/java/com/cnlive/core/people_verfication/activity/TakeIdentityPictureActivity.java
0 → 100644
| 1 | +package com.cnlive.core.people_verfication.activity; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +import android.Manifest; | ||
| 5 | +import android.app.Activity; | ||
| 6 | +import android.content.Intent; | ||
| 7 | +import android.text.TextUtils; | ||
| 8 | + | ||
| 9 | +import com.baidu.ocr.sdk.model.IDCardParams; | ||
| 10 | +import com.cnlive.core.libs.base.frame.activity.BaseActivity; | ||
| 11 | +import com.cnlive.core.libs.base.util.FileUtils; | ||
| 12 | +import com.cnlive.core.ocr.ui.camera.CameraActivity; | ||
| 13 | +import com.cnlive.core.people_verfication.R; | ||
| 14 | +import com.cnlive.core.people_verfication.databinding.ActivityTakePictureBinding; | ||
| 15 | +import com.cnlive.core.people_verfication.presenter.TakeIdentityPicturePresenter; | ||
| 16 | +import com.cnlive.core.people_verfication.view.ICertificatePhohosView; | ||
| 17 | +import com.cnlive.core.people_verfication.view.TakeIdentityPictureView; | ||
| 18 | +import com.cnlive.media.picker.utils.PermissionChecker; | ||
| 19 | + | ||
| 20 | +import androidx.annotation.NonNull; | ||
| 21 | +import androidx.databinding.ViewDataBinding; | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * 拍摄照片 身份证件采集 | ||
| 25 | + */ | ||
| 26 | +public class TakeIdentityPictureActivity extends BaseActivity<TakeIdentityPictureView, TakeIdentityPicturePresenter> { | ||
| 27 | + | ||
| 28 | + public ActivityTakePictureBinding binding; | ||
| 29 | + //上一个被点击过的btn的点击时间 | ||
| 30 | + private long preViewClickTime = 0; | ||
| 31 | + //上一个被点击过的btn的id | ||
| 32 | + private int preViewClickId = 0; | ||
| 33 | + private static int CAMERA_WRITE_FR_PERMISSION = 1001; | ||
| 34 | + private static int CAMERA_WRITE_BA_PERMISSION = 1002; | ||
| 35 | + | ||
| 36 | + @Override | ||
| 37 | + protected int getBindLayoutId() { | ||
| 38 | + return R.layout.activity_take_picture; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + @Override | ||
| 42 | + protected void initBindingLayoutData(ViewDataBinding baseBinding) { | ||
| 43 | + binding = (ActivityTakePictureBinding) baseBinding; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + @Override | ||
| 47 | + protected void initView() { | ||
| 48 | + binding.topbar.setTitle("拍摄照片"); | ||
| 49 | + binding.topbar.setLeft(R.drawable.back); | ||
| 50 | + binding.topbar.addLeftImageButton(R.drawable.top_back_black, R.id.fragment_back).setOnClickListener(v -> { | ||
| 51 | + finish(); | ||
| 52 | + }); | ||
| 53 | + binding.setClick(v -> onClick(v.getId())); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + private void onClick(int id) { | ||
| 57 | + long curClickTime = System.currentTimeMillis(); | ||
| 58 | + if (preViewClickId > 0 && preViewClickTime > 0) { | ||
| 59 | + if (preViewClickId == id && curClickTime - preViewClickTime <= 2000) { | ||
| 60 | + return; | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | + preViewClickTime = curClickTime; | ||
| 64 | + preViewClickId = id; | ||
| 65 | + if (id == R.id.certificate_front) { | ||
| 66 | + if(hasCampermission(CAMERA_WRITE_FR_PERMISSION)){//权限判断 | ||
| 67 | + getMvpView().takeIdCardPictures(this, ICertificatePhohosView.ID_CARD_SIDE_FRONT); | ||
| 68 | + } | ||
| 69 | + } else if (id == R.id.certificate_back) { | ||
| 70 | + if(hasCampermission(CAMERA_WRITE_BA_PERMISSION)){//权限判断 | ||
| 71 | + getMvpView().takeIdCardPictures(this, ICertificatePhohosView.ID_CARD_SIDE_BACK); | ||
| 72 | + } | ||
| 73 | + } else if (id == R.id.take_picture) { | ||
| 74 | + | ||
| 75 | + } | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + private boolean hasCampermission(int typeNum){ | ||
| 79 | + boolean hasPermission = PermissionChecker.checkPermissions(this | ||
| 80 | + , new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE} | ||
| 81 | + , typeNum, R.string.dialog_imagepicker_permission_camera_nerver_ask_message); | ||
| 82 | + return hasPermission; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @Override | ||
| 86 | + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | ||
| 87 | + super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||
| 88 | + if (requestCode == CAMERA_WRITE_FR_PERMISSION) { | ||
| 89 | + if (PermissionChecker.onRequestPermissionsResult(this,requestCode, permissions, grantResults, false, R.string.dialog_imagepicker_permission_sdcard_message)[0]) { | ||
| 90 | + getMvpView().takeIdCardPictures(this, ICertificatePhohosView.ID_CARD_SIDE_FRONT); | ||
| 91 | + } | ||
| 92 | + }else if(requestCode == CAMERA_WRITE_BA_PERMISSION){ | ||
| 93 | + if (PermissionChecker.onRequestPermissionsResult(this,requestCode, permissions, grantResults, false, R.string.dialog_imagepicker_permission_sdcard_message)[0]) { | ||
| 94 | + getMvpView().takeIdCardPictures(this, ICertificatePhohosView.ID_CARD_SIDE_BACK); | ||
| 95 | + } | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + @Override | ||
| 100 | + protected void onActivityResult(int requestCode, int resultCode, Intent data) { | ||
| 101 | + super.onActivityResult(requestCode, resultCode, data); | ||
| 102 | + if (requestCode == ICertificatePhohosView.REQUEST_CODE_CAMERA && resultCode == Activity.RESULT_OK) { | ||
| 103 | + if (data != null) { | ||
| 104 | + String contentType = data.getStringExtra(CameraActivity.KEY_CONTENT_TYPE); | ||
| 105 | + if (!TextUtils.isEmpty(contentType)) { | ||
| 106 | + if (CameraActivity.CONTENT_TYPE_ID_CARD_FRONT.equals(contentType)) { | ||
| 107 | + String frontFilePath = FileUtils.getSaveFile(getApplicationContext(), ICertificatePhohosView.PIC_FRONT).getAbsolutePath(); | ||
| 108 | + if (getMvpView() == null) return; | ||
| 109 | + getMvpView().recIDCard(IDCardParams.ID_CARD_SIDE_FRONT, frontFilePath); | ||
| 110 | + | ||
| 111 | + } else if (CameraActivity.CONTENT_TYPE_ID_CARD_BACK.equals(contentType)) { | ||
| 112 | + String backFilePath = FileUtils.getSaveFile(getApplicationContext(), ICertificatePhohosView.PIC_BACK).getAbsolutePath(); | ||
| 113 | + if (getMvpView() == null) return; | ||
| 114 | + getMvpView().recIDCard(IDCardParams.ID_CARD_SIDE_BACK, backFilePath); | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + @Override | ||
| 122 | + protected void onDestroy() { | ||
| 123 | + super.onDestroy(); | ||
| 124 | +// if (getPresenter() != null) getPresenter().cancel(); | ||
| 125 | + } | ||
| 126 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/people_verfication/presenter/TakeIdentityPicturePresenter.java
0 → 100644
| 1 | +package com.cnlive.core.people_verfication.presenter; | ||
| 2 | + | ||
| 3 | +import com.cnlive.core.libs.base.frame.presenter.BasePresenter; | ||
| 4 | +import com.cnlive.core.people_verfication.view.TakeIdentityPictureView; | ||
| 5 | + | ||
| 6 | +public class TakeIdentityPicturePresenter extends BasePresenter<TakeIdentityPictureView> { | ||
| 7 | + | ||
| 8 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/people_verfication/view/ICertificatePhohosView.java
0 → 100644
| 1 | +package com.cnlive.core.people_verfication.view; | ||
| 2 | + | ||
| 3 | +import com.hannesdorfmann.mosby3.mvp.MvpView; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * Created by cnlive20080010 on 2018/10/9. | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | +public interface ICertificatePhohosView extends MvpView { | ||
| 10 | + int REQUEST_CODE_CAMERA = 102; | ||
| 11 | + int ID_CARD_SIDE_FRONT = 1; | ||
| 12 | + int ID_CARD_SIDE_BACK = 2; | ||
| 13 | + String PIC_FRONT = "pic_front"; | ||
| 14 | + String PIC_BACK = "pic_back"; | ||
| 15 | +} |
core/dentity_verfication/src/main/java/com/cnlive/core/people_verfication/view/TakeIdentityPictureView.java
0 → 100644
| 1 | +package com.cnlive.core.people_verfication.view; | ||
| 2 | + | ||
| 3 | +import android.app.Activity; | ||
| 4 | +import android.content.Context; | ||
| 5 | +import android.content.Intent; | ||
| 6 | +import android.graphics.Bitmap; | ||
| 7 | +import android.graphics.BitmapFactory; | ||
| 8 | +import android.hardware.Camera; | ||
| 9 | +import android.os.Environment; | ||
| 10 | +import android.os.Handler; | ||
| 11 | +import android.os.Looper; | ||
| 12 | +import android.text.TextUtils; | ||
| 13 | +import android.widget.ImageView; | ||
| 14 | + | ||
| 15 | +import com.baidu.ocr.sdk.OCR; | ||
| 16 | +import com.baidu.ocr.sdk.OnResultListener; | ||
| 17 | +import com.baidu.ocr.sdk.exception.OCRError; | ||
| 18 | +import com.baidu.ocr.sdk.model.AccessToken; | ||
| 19 | +import com.baidu.ocr.sdk.model.IDCardParams; | ||
| 20 | +import com.baidu.ocr.sdk.model.IDCardResult; | ||
| 21 | +import com.bumptech.glide.load.engine.DiskCacheStrategy; | ||
| 22 | +import com.bumptech.glide.request.RequestOptions; | ||
| 23 | +import com.cnlive.core.libs.base.application.AppConfig; | ||
| 24 | +import com.cnlive.core.libs.base.frame.view.BaseView; | ||
| 25 | +import com.cnlive.core.libs.base.util.AlertUtil; | ||
| 26 | +import com.cnlive.core.libs.base.util.FileUtils; | ||
| 27 | +import com.cnlive.core.libs.base.util.GlideUtil; | ||
| 28 | +import com.cnlive.core.ocr.RecognizeService; | ||
| 29 | +import com.cnlive.core.ocr.ui.camera.CameraActivity; | ||
| 30 | +import com.cnlive.core.ocr.ui.util.ImageUtil; | ||
| 31 | +import com.cnlive.core.people_verfication.R; | ||
| 32 | +import com.cnlive.core.people_verfication.activity.TakeIdentityPictureActivity; | ||
| 33 | + | ||
| 34 | +import java.io.ByteArrayOutputStream; | ||
| 35 | +import java.io.File; | ||
| 36 | +import java.io.FileNotFoundException; | ||
| 37 | +import java.io.FileOutputStream; | ||
| 38 | +import java.io.IOException; | ||
| 39 | +import java.text.SimpleDateFormat; | ||
| 40 | +import java.util.Date; | ||
| 41 | + | ||
| 42 | +import static com.cnlive.core.people_verfication.view.ICertificatePhohosView.PIC_BACK; | ||
| 43 | +import static com.cnlive.core.people_verfication.view.ICertificatePhohosView.PIC_FRONT; | ||
| 44 | + | ||
| 45 | +public class TakeIdentityPictureView extends BaseView { | ||
| 46 | + | ||
| 47 | + public TakeIdentityPictureActivity activity; | ||
| 48 | + //上一个被点击过的btn的点击时间 | ||
| 49 | + private long preViewClickTime = 0; | ||
| 50 | + private boolean isFrontEffective = false; | ||
| 51 | + private boolean isBackEffective = false; | ||
| 52 | + private String idCardNo; | ||
| 53 | + private String realName; | ||
| 54 | + | ||
| 55 | + public TakeIdentityPictureView(TakeIdentityPictureActivity activity) { | ||
| 56 | + this.activity = activity; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + private Activity getContext() { | ||
| 60 | + return activity == null ? null : activity; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + public void finish() { | ||
| 64 | + activity.finish(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * 拍摄 | ||
| 69 | + * | ||
| 70 | + * @param idCardSide | ||
| 71 | + */ | ||
| 72 | + public void takeIdCardPictures(Context context, final int idCardSide) { | ||
| 73 | + long curClickTime = System.currentTimeMillis(); | ||
| 74 | + if (preViewClickTime > 0 && curClickTime - preViewClickTime <= 2000 ) { | ||
| 75 | + return; | ||
| 76 | + } | ||
| 77 | + preViewClickTime = curClickTime; | ||
| 78 | + OCR.getInstance().initAccessTokenWithAkSk(new OnResultListener<AccessToken>() { | ||
| 79 | + @Override | ||
| 80 | + public void onResult(AccessToken result) { | ||
| 81 | + String token = result.getAccessToken(); | ||
| 82 | + if (idCardSide == ICertificatePhohosView.ID_CARD_SIDE_FRONT) { | ||
| 83 | + takePhoto(PIC_FRONT, CameraActivity.CONTENT_TYPE_ID_CARD_FRONT); | ||
| 84 | + } else if (idCardSide == ICertificatePhohosView.ID_CARD_SIDE_BACK) { | ||
| 85 | + takePhoto(PIC_BACK, CameraActivity.CONTENT_TYPE_ID_CARD_BACK); | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + @Override | ||
| 90 | + public void onError(OCRError error) { | ||
| 91 | + error.printStackTrace(); | ||
| 92 | + new Thread() { | ||
| 93 | + @Override | ||
| 94 | + public void run() { | ||
| 95 | + new Handler(Looper.getMainLooper()).post(new Runnable() { | ||
| 96 | + @Override | ||
| 97 | + public void run() { | ||
| 98 | + AlertUtil.showDeftToast(getContext(), getContext().getString(R.string.init_baidu_faile)); | ||
| 99 | + } | ||
| 100 | + }); | ||
| 101 | + } | ||
| 102 | + }.start(); | ||
| 103 | + } | ||
| 104 | + }, context, AppConfig.getBaiduAppKey(), AppConfig.getBaiduAppSecret()); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + /** | ||
| 108 | + * 拍摄身份证 | ||
| 109 | + * | ||
| 110 | + * @param fileName | ||
| 111 | + * @param contentType | ||
| 112 | + */ | ||
| 113 | + public void takePhoto(String fileName, String contentType) { | ||
| 114 | + Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); | ||
| 115 | + Camera camera = null; | ||
| 116 | + int cameraId = 0; | ||
| 117 | + for (int i = 0; i < Camera.getNumberOfCameras(); i++) { | ||
| 118 | + Camera.getCameraInfo(i, cameraInfo); | ||
| 119 | + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { | ||
| 120 | + cameraId = i; | ||
| 121 | + } | ||
| 122 | + } | ||
| 123 | + try { | ||
| 124 | + camera = Camera.open(cameraId); | ||
| 125 | + Intent intent = new Intent(getContext(), CameraActivity.class); | ||
| 126 | + intent.putExtra(CameraActivity.KEY_OUTPUT_FILE_PATH, | ||
| 127 | + FileUtils.getSaveFile(getContext().getApplication(), fileName).getAbsolutePath()); | ||
| 128 | + intent.putExtra(CameraActivity.KEY_CONTENT_TYPE, contentType); | ||
| 129 | + getContext().startActivityForResult(intent, ICertificatePhohosView.REQUEST_CODE_CAMERA); | ||
| 130 | + camera.release(); | ||
| 131 | + } catch (Throwable e) { | ||
| 132 | + e.printStackTrace(); | ||
| 133 | + AlertUtil.showDeftToast(getContext(),"没有相机权限,相机开启失败"); | ||
| 134 | + return; | ||
| 135 | + } | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + /** | ||
| 139 | + * 展示拍摄的身份证,并获取身份证信息 | ||
| 140 | + * | ||
| 141 | + * @param idCardSide | ||
| 142 | + * @param bitmap | ||
| 143 | + * @param imageView | ||
| 144 | + */ | ||
| 145 | + public void showIdCard(String idCardSide, Bitmap bitmap, ImageView imageView) { | ||
| 146 | + RequestOptions options = new RequestOptions(); | ||
| 147 | + options.skipMemoryCache(true); | ||
| 148 | + options.diskCacheStrategy(DiskCacheStrategy.NONE); | ||
| 149 | + GlideUtil.into(imageView, bitmap, options); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + /** | ||
| 153 | + * 身份证人像面信息是否完整 | ||
| 154 | + * | ||
| 155 | + * @param idCardResult | ||
| 156 | + * @return | ||
| 157 | + */ | ||
| 158 | + private boolean isEffectiveFront(IDCardResult idCardResult) { | ||
| 159 | + return idCardResult.getName() != null && idCardResult.getAddress() != null && idCardResult.getIdNumber() != null && idCardResult.getBirthday() != null && idCardResult.getGender() != null && idCardResult.getEthnic() != null; | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + /** | ||
| 163 | + * 身份证国徽面信息是否完整 | ||
| 164 | + * | ||
| 165 | + * @param idCardResult | ||
| 166 | + * @return | ||
| 167 | + */ | ||
| 168 | + private boolean isEffectiveBack(IDCardResult idCardResult) { | ||
| 169 | + return idCardResult.getSignDate() != null && idCardResult.getExpiryDate() != null && idCardResult.getIssueAuthority() != null; | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + private Bitmap frontBitmap; | ||
| 173 | + private Bitmap backBitmap; | ||
| 174 | + | ||
| 175 | + /** | ||
| 176 | + * 验证身份证信息 | ||
| 177 | + * | ||
| 178 | + * @param idCardSide | ||
| 179 | + * @param filePath | ||
| 180 | + */ | ||
| 181 | + public void recIDCard(final String idCardSide, final String filePath) { | ||
| 182 | + RecognizeService.recIDCard(idCardSide, filePath, new OnResultListener<IDCardResult>() { | ||
| 183 | + @Override | ||
| 184 | + public void onResult(IDCardResult idCardResult) { | ||
| 185 | + if (idCardResult != null) { | ||
| 186 | + if (isEffectiveFront(idCardResult) || isEffectiveBack(idCardResult)) { | ||
| 187 | + Bitmap bitmap = ImageUtil.drawTextToRightBottom(getContext(), BitmapFactory.decodeFile(filePath), getContext().getString(R.string.only_for_verified), 20, getContext().getResources().getColor(R.color.grayText), 10, 10); | ||
| 188 | + | ||
| 189 | + if (idCardSide.equals(IDCardParams.ID_CARD_SIDE_FRONT)) { | ||
| 190 | + isFrontEffective = true; | ||
| 191 | + idCardNo = idCardResult.getIdNumber().toString(); | ||
| 192 | + realName = idCardResult.getName().toString(); | ||
| 193 | + if (TextUtils.isEmpty(idCardNo) || TextUtils.isEmpty(realName)) { | ||
| 194 | +// activity.binding.submit.setClickable(false); | ||
| 195 | + AlertUtil.showDeftToast(getContext(), "身份证信息读取失败,请重新拍摄"); | ||
| 196 | + return; | ||
| 197 | + } | ||
| 198 | + frontBitmap = bitmap; | ||
| 199 | + showIdCard(idCardSide, frontBitmap, activity.binding.certificateFront); | ||
| 200 | + activity.binding.certificateFront.setImageBitmap(bitmap); | ||
| 201 | + if(isBackEffective){ | ||
| 202 | +// activity.binding.submit.setClickable(true); | ||
| 203 | +// activity.binding.submit.setTextColor(activity.getResources().getColor(R.color.white)); | ||
| 204 | +// activity.binding.submit.setBackground(getContext().getResources().getDrawable(R.drawable.bg_apply_btn)); | ||
| 205 | + } | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + if (idCardSide.equals(IDCardParams.ID_CARD_SIDE_BACK)) { | ||
| 209 | + backBitmap = bitmap; | ||
| 210 | + isBackEffective = true; | ||
| 211 | + if(TextUtils.isEmpty(idCardResult.getIssueAuthority().toString().trim()) || | ||
| 212 | + TextUtils.isEmpty(idCardResult.getSignDate().toString().trim())){ | ||
| 213 | +// activity.binding.submit.setClickable(false); | ||
| 214 | + AlertUtil.showDeftToast(getContext(), "身份证信息读取失败,请重新拍摄"); | ||
| 215 | + return; | ||
| 216 | + } | ||
| 217 | + try{ | ||
| 218 | + Integer.parseInt(idCardResult.getExpiryDate().toString().trim()); | ||
| 219 | + if(idCardResult.getExpiryDate() != null && Integer.parseInt(idCardResult.getExpiryDate().toString().trim()) < getCurrentTime()){ | ||
| 220 | +// activity.binding.submit.setClickable(false); | ||
| 221 | + AlertUtil.showDeftToast(getContext(), "身份证有效期失效"); | ||
| 222 | + return; | ||
| 223 | + } | ||
| 224 | + }catch (Exception e){ | ||
| 225 | + e.printStackTrace(); | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + | ||
| 229 | + showIdCard(idCardSide, backBitmap, activity.binding.certificateBack); | ||
| 230 | + activity.binding.certificateBack.setImageBitmap(bitmap); | ||
| 231 | + if (!TextUtils.isEmpty(idCardNo) && !TextUtils.isEmpty(realName)) { | ||
| 232 | +// activity.binding.submit.setClickable(true); | ||
| 233 | +// activity.binding.submit.setTextColor(activity.getResources().getColor(R.color.white)); | ||
| 234 | +// activity.binding.submit.setBackground(getContext().getResources().getDrawable(R.drawable.bg_apply_btn)); | ||
| 235 | + } | ||
| 236 | + } | ||
| 237 | + }else { | ||
| 238 | +// activity.binding.submit.setClickable(false); | ||
| 239 | + AlertUtil.showDeftToast(getContext(), "身份证信息读取失败,请重新拍摄"); | ||
| 240 | + } | ||
| 241 | + } | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + @Override | ||
| 245 | + public void onError(OCRError ocrError) { | ||
| 246 | + if (ocrError.getErrorCode() == 283504) { | ||
| 247 | + AlertUtil.showDeftToast(getContext(), "网络请求失败,请稍后再试"); | ||
| 248 | + } else { | ||
| 249 | + AlertUtil.showDeftToast(getContext(), "识别失败,请重拍"); | ||
| 250 | + } | ||
| 251 | + } | ||
| 252 | + }); | ||
| 253 | + | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + private Integer getCurrentTime() { | ||
| 257 | + SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMdd"); | ||
| 258 | + String date = sDateFormat.format(new Date()); | ||
| 259 | + return Integer.parseInt(date); | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + public File compressImage(Bitmap bitmap, String filename) { | ||
| 263 | + ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
| 264 | + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 | ||
| 265 | + int options = 100; | ||
| 266 | + while (baos.toByteArray().length / 1024 > 500) { //循环判断如果压缩后图片是否大于500kb,大于继续压缩 | ||
| 267 | + baos.reset();//重置baos即清空baos | ||
| 268 | + options -= 10;//每次都减少10 | ||
| 269 | + bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中 | ||
| 270 | + long length = baos.toByteArray().length; | ||
| 271 | + } | ||
| 272 | + File file = new File(Environment.getExternalStorageDirectory() + "/aegis/file"); | ||
| 273 | + if (!file.exists()) { | ||
| 274 | + file.mkdirs(); | ||
| 275 | + } | ||
| 276 | + file = new File(Environment.getExternalStorageDirectory() + "/aegis/file/", filename + ".png"); | ||
| 277 | + try { | ||
| 278 | + FileOutputStream fos = new FileOutputStream(file); | ||
| 279 | + try { | ||
| 280 | + fos.write(baos.toByteArray()); | ||
| 281 | + fos.flush(); | ||
| 282 | + fos.close(); | ||
| 283 | + } catch (IOException e) { | ||
| 284 | + e.printStackTrace(); | ||
| 285 | + } | ||
| 286 | + } catch (FileNotFoundException e) { | ||
| 287 | + e.printStackTrace(); | ||
| 288 | + } | ||
| 289 | +// recycleBitmap(bitmap); | ||
| 290 | + return file; | ||
| 291 | + } | ||
| 292 | +} |
core/dentity_verfication/src/main/jniLibs/arm64-v8a/libidcard_quality.1.1.1.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/arm64-v8a/libidl_license.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/arm64-v8a/libocr-sdk.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/armeabi-v7a/libidcard_quality.1.1.1.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/armeabi-v7a/libidl_license.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/armeabi-v7a/libocr-sdk.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/armeabi/libidcard_quality.1.1.1.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/armeabi/libidl_license.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/armeabi/libocr-sdk.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/x86/libidcard_quality.1.1.1.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/x86/libidl_license.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/jniLibs/x86/libocr-sdk.so
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_cancel.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_close.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_confirm.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_gallery.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_hint_align_bank_card.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_hint_align_id_card.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_hint_align_id_card_back.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_id_card_locator_back.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_id_card_locator_front.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_light_off.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_light_on.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_reset.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_rotate.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_round_corner.xml
0 → 100644
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_take_photo_highlight.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_take_photo_normal.webp
0 → 100644
No preview for this file type
core/dentity_verfication/src/main/res/drawable-xhdpi/bd_ocr_take_photo_selector.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | + <!-- 按压时 --> | ||
| 4 | + <item android:drawable="@drawable/bd_ocr_take_photo_highlight" android:state_pressed="true" /> | ||
| 5 | + <!-- 被选中时 --> | ||
| 6 | + <item android:drawable="@drawable/bd_ocr_take_photo_normal" android:state_selected="true" /> | ||
| 7 | + <!-- 被激活时 --> | ||
| 8 | + <item android:drawable="@drawable/bd_ocr_take_photo_normal" android:state_activated="true" /> | ||
| 9 | + <!-- 默认时 --> | ||
| 10 | + <item android:drawable="@drawable/bd_ocr_take_photo_normal" /> | ||
| 11 | +</selector> | ||
| 0 | \ No newline at end of file | 12 | \ No newline at end of file |
core/dentity_verfication/src/main/res/layout/activity_take_picture.xml
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | - xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 4 | - xmlns:tools="http://schemas.android.com/tools" | ||
| 5 | - android:layout_width="match_parent" | ||
| 6 | - android:layout_height="match_parent" | ||
| 7 | - android:orientation="vertical" | ||
| 8 | - tools:context=".TakePictureActivity"> | ||
| 9 | - | ||
| 10 | - <com.qmuiteam.qmui.widget.QMUITopBar | ||
| 11 | - android:id="@+id/topbar" | ||
| 12 | - android:layout_width="match_parent" | ||
| 13 | - android:layout_height="?attr/qmui_topbar_height" /> | 2 | +<layout> |
| 3 | + | ||
| 4 | + <data> | ||
| 5 | + <variable | ||
| 6 | + name="click" | ||
| 7 | + type="android.view.View.OnClickListener" /> | ||
| 8 | + </data> | ||
| 14 | 9 | ||
| 15 | - <ScrollView | 10 | + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 11 | + xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 12 | + xmlns:tools="http://schemas.android.com/tools" | ||
| 16 | android:layout_width="match_parent" | 13 | android:layout_width="match_parent" |
| 17 | - android:layout_height="match_parent"> | 14 | + android:layout_height="match_parent" |
| 15 | + android:orientation="vertical" | ||
| 16 | + tools:context=".activity.TakeIdentityPictureActivity"> | ||
| 17 | + | ||
| 18 | + <com.qmuiteam.qmui.widget.QMUITopBar | ||
| 19 | + android:id="@+id/topbar" | ||
| 20 | + android:layout_width="match_parent" | ||
| 21 | + android:layout_height="?attr/qmui_topbar_height" /> | ||
| 18 | 22 | ||
| 19 | - <LinearLayout | 23 | + <ScrollView |
| 20 | android:layout_width="match_parent" | 24 | android:layout_width="match_parent" |
| 21 | - android:layout_height="match_parent" | ||
| 22 | - android:orientation="vertical"> | 25 | + android:layout_height="match_parent"> |
| 23 | 26 | ||
| 24 | <LinearLayout | 27 | <LinearLayout |
| 25 | - android:layout_width="wrap_content" | ||
| 26 | - android:layout_height="wrap_content" | ||
| 27 | - android:layout_gravity="center_horizontal" | 28 | + android:layout_width="match_parent" |
| 29 | + android:layout_height="match_parent" | ||
| 28 | android:orientation="vertical"> | 30 | android:orientation="vertical"> |
| 29 | 31 | ||
| 30 | - <TextView | ||
| 31 | - android:layout_width="wrap_content" | ||
| 32 | - android:layout_height="wrap_content" | ||
| 33 | - android:layout_marginTop="11dp" | ||
| 34 | - android:text="@string/step_one" | ||
| 35 | - android:textColor="#ff1a1a1a" | ||
| 36 | - android:textSize="16sp" /> | ||
| 37 | - | ||
| 38 | <LinearLayout | 32 | <LinearLayout |
| 39 | - android:layout_width="match_parent" | 33 | + android:layout_width="wrap_content" |
| 40 | android:layout_height="wrap_content" | 34 | android:layout_height="wrap_content" |
| 41 | - android:layout_marginTop="14dp" | ||
| 42 | - android:orientation="horizontal"> | 35 | + android:layout_gravity="center_horizontal" |
| 36 | + android:orientation="vertical"> | ||
| 43 | 37 | ||
| 44 | <TextView | 38 | <TextView |
| 45 | android:layout_width="wrap_content" | 39 | android:layout_width="wrap_content" |
| 46 | android:layout_height="wrap_content" | 40 | android:layout_height="wrap_content" |
| 47 | - android:text="@string/please_get_yourself_card" | ||
| 48 | - android:textColor="@color/color_9c9c9c" | ||
| 49 | - android:textSize="12sp" /> | 41 | + android:layout_marginTop="11dp" |
| 42 | + android:text="@string/step_one" | ||
| 43 | + android:textColor="#ff1a1a1a" | ||
| 44 | + android:textSize="16sp" /> | ||
| 50 | 45 | ||
| 51 | - <TextView | ||
| 52 | - android:layout_width="wrap_content" | 46 | + <LinearLayout |
| 47 | + android:layout_width="match_parent" | ||
| 53 | android:layout_height="wrap_content" | 48 | android:layout_height="wrap_content" |
| 54 | - android:text="@string/font" | ||
| 55 | - android:textColor="@color/color_ff3b30" | ||
| 56 | - android:textSize="12sp" /> | ||
| 57 | - | ||
| 58 | - <TextView | 49 | + android:layout_marginTop="14dp" |
| 50 | + android:orientation="horizontal"> | ||
| 51 | + | ||
| 52 | + <TextView | ||
| 53 | + android:layout_width="wrap_content" | ||
| 54 | + android:layout_height="wrap_content" | ||
| 55 | + android:text="@string/please_get_yourself_card" | ||
| 56 | + android:textColor="@color/color_9c9c9c" | ||
| 57 | + android:textSize="12sp" /> | ||
| 58 | + | ||
| 59 | + <TextView | ||
| 60 | + android:layout_width="wrap_content" | ||
| 61 | + android:layout_height="wrap_content" | ||
| 62 | + android:text="@string/font" | ||
| 63 | + android:textColor="@color/color_ff3b30" | ||
| 64 | + android:textSize="12sp" /> | ||
| 65 | + | ||
| 66 | + <TextView | ||
| 67 | + android:layout_width="wrap_content" | ||
| 68 | + android:layout_height="wrap_content" | ||
| 69 | + android:text="@string/card_notice" | ||
| 70 | + android:textColor="@color/color_9c9c9c" | ||
| 71 | + android:textSize="12sp" /> | ||
| 72 | + | ||
| 73 | + </LinearLayout> | ||
| 74 | + | ||
| 75 | + <ImageView | ||
| 76 | + android:id="@+id/certificate_front" | ||
| 59 | android:layout_width="wrap_content" | 77 | android:layout_width="wrap_content" |
| 60 | android:layout_height="wrap_content" | 78 | android:layout_height="wrap_content" |
| 61 | - android:text="@string/card_notice" | ||
| 62 | - android:textColor="@color/color_9c9c9c" | ||
| 63 | - android:textSize="12sp" /> | 79 | + android:layout_gravity="center_horizontal" |
| 80 | + android:layout_marginTop="12dp" | ||
| 81 | + android:layout_marginBottom="22dp" | ||
| 82 | + android:onClick="@{click}" | ||
| 83 | + android:src="@drawable/observer_idcard_front" /> | ||
| 64 | 84 | ||
| 65 | </LinearLayout> | 85 | </LinearLayout> |
| 66 | 86 | ||
| 67 | - <ImageView | ||
| 68 | - android:id="@+id/certificate_front" | 87 | + <View |
| 88 | + android:layout_width="match_parent" | ||
| 89 | + android:layout_height="10dp" | ||
| 90 | + android:background="#f2f2f2" /> | ||
| 91 | + <LinearLayout | ||
| 69 | android:layout_width="wrap_content" | 92 | android:layout_width="wrap_content" |
| 70 | android:layout_height="wrap_content" | 93 | android:layout_height="wrap_content" |
| 71 | android:layout_gravity="center_horizontal" | 94 | android:layout_gravity="center_horizontal" |
| 72 | - android:layout_marginTop="12dp" | ||
| 73 | - android:layout_marginBottom="22dp" | ||
| 74 | - android:onClick="@{click}" | ||
| 75 | - android:src="@drawable/observer_idcard_front" /> | ||
| 76 | - | ||
| 77 | - </LinearLayout> | ||
| 78 | - | ||
| 79 | - <View | ||
| 80 | - android:layout_width="match_parent" | ||
| 81 | - android:layout_height="10dp" | ||
| 82 | - android:background="#f2f2f2" /> | ||
| 83 | - <LinearLayout | ||
| 84 | - android:layout_width="wrap_content" | ||
| 85 | - android:layout_height="wrap_content" | ||
| 86 | - android:layout_gravity="center_horizontal" | ||
| 87 | - android:orientation="vertical"> | ||
| 88 | - <TextView | ||
| 89 | - android:layout_width="wrap_content" | ||
| 90 | - android:layout_height="wrap_content" | ||
| 91 | - android:layout_marginTop="11dp" | ||
| 92 | - android:text="@string/step_tow" | ||
| 93 | - android:textColor="#ff1a1a1a" | ||
| 94 | - android:textSize="16sp" /> | 95 | + android:orientation="vertical"> |
| 96 | + <TextView | ||
| 97 | + android:layout_width="wrap_content" | ||
| 98 | + android:layout_height="wrap_content" | ||
| 99 | + android:layout_marginTop="11dp" | ||
| 100 | + android:text="@string/step_tow" | ||
| 101 | + android:textColor="#ff1a1a1a" | ||
| 102 | + android:textSize="16sp" /> | ||
| 95 | 103 | ||
| 96 | - <LinearLayout | ||
| 97 | - android:layout_width="match_parent" | ||
| 98 | - android:layout_height="wrap_content" | ||
| 99 | - android:layout_marginTop="14dp" | ||
| 100 | - android:orientation="horizontal"> | 104 | + <LinearLayout |
| 105 | + android:layout_width="match_parent" | ||
| 106 | + android:layout_height="wrap_content" | ||
| 107 | + android:layout_marginTop="14dp" | ||
| 108 | + android:orientation="horizontal"> | ||
| 109 | + | ||
| 110 | + <TextView | ||
| 111 | + android:layout_width="wrap_content" | ||
| 112 | + android:layout_height="wrap_content" | ||
| 113 | + android:text="@string/please_get_yourself_card" | ||
| 114 | + android:textColor="@color/color_9c9c9c" | ||
| 115 | + android:textSize="12sp" /> | ||
| 116 | + | ||
| 117 | + <TextView | ||
| 118 | + android:layout_width="wrap_content" | ||
| 119 | + android:layout_height="wrap_content" | ||
| 120 | + android:text="@string/back" | ||
| 121 | + android:textColor="@color/color_ff3b30" | ||
| 122 | + android:textSize="12sp" /> | ||
| 123 | + | ||
| 124 | + <TextView | ||
| 125 | + android:layout_width="wrap_content" | ||
| 126 | + android:layout_height="wrap_content" | ||
| 127 | + android:text="@string/card_notice" | ||
| 128 | + android:textColor="@color/color_9c9c9c" | ||
| 129 | + android:textSize="12sp" /> | ||
| 130 | + | ||
| 131 | + </LinearLayout> | ||
| 132 | + | ||
| 133 | + <ImageView | ||
| 134 | + android:id="@+id/certificate_back" | ||
| 135 | + android:layout_width="wrap_content" | ||
| 136 | + android:layout_height="wrap_content" | ||
| 137 | + android:layout_gravity="center_horizontal" | ||
| 138 | + android:layout_marginTop="12dp" | ||
| 139 | + android:layout_marginBottom="22dp" | ||
| 140 | + android:onClick="@{click}" | ||
| 141 | + android:src="@drawable/observer_idcard_back" /> | ||
| 142 | + </LinearLayout> | ||
| 101 | 143 | ||
| 102 | - <TextView | ||
| 103 | - android:layout_width="wrap_content" | ||
| 104 | - android:layout_height="wrap_content" | ||
| 105 | - android:text="@string/please_get_yourself_card" | ||
| 106 | - android:textColor="@color/color_9c9c9c" | ||
| 107 | - android:textSize="12sp" /> | ||
| 108 | 144 | ||
| 109 | - <TextView | ||
| 110 | - android:layout_width="wrap_content" | ||
| 111 | - android:layout_height="wrap_content" | ||
| 112 | - android:text="@string/back" | ||
| 113 | - android:textColor="@color/color_ff3b30" | ||
| 114 | - android:textSize="12sp" /> | 145 | + <View |
| 146 | + android:layout_width="match_parent" | ||
| 147 | + android:layout_height="10dp" | ||
| 148 | + android:background="#f2f2f2" /> | ||
| 115 | 149 | ||
| 116 | - <TextView | 150 | + <LinearLayout |
| 117 | android:layout_width="wrap_content" | 151 | android:layout_width="wrap_content" |
| 118 | android:layout_height="wrap_content" | 152 | android:layout_height="wrap_content" |
| 119 | - android:text="@string/card_notice" | ||
| 120 | - android:textColor="@color/color_9c9c9c" | ||
| 121 | - android:textSize="12sp" /> | ||
| 122 | - | ||
| 123 | - </LinearLayout> | ||
| 124 | - | ||
| 125 | - <ImageView | ||
| 126 | - android:id="@+id/certificate_back" | ||
| 127 | - android:layout_width="wrap_content" | ||
| 128 | - android:layout_height="wrap_content" | ||
| 129 | - android:layout_gravity="center_horizontal" | ||
| 130 | - android:layout_marginTop="12dp" | ||
| 131 | - android:layout_marginBottom="22dp" | ||
| 132 | - android:onClick="@{click}" | ||
| 133 | - android:src="@drawable/observer_idcard_back" /> | ||
| 134 | - </LinearLayout> | ||
| 135 | - | 153 | + android:layout_gravity="center_horizontal" |
| 154 | + android:orientation="vertical"> | ||
| 136 | 155 | ||
| 137 | - <View | ||
| 138 | - android:layout_width="match_parent" | ||
| 139 | - android:layout_height="10dp" | ||
| 140 | - android:background="#f2f2f2" /> | 156 | + <TextView |
| 157 | + android:layout_width="wrap_content" | ||
| 158 | + android:layout_height="wrap_content" | ||
| 159 | + android:layout_marginTop="11dp" | ||
| 160 | + android:text="@string/step_third" | ||
| 161 | + android:textColor="#ff1a1a1a" | ||
| 162 | + android:textSize="16sp" /> | ||
| 141 | 163 | ||
| 142 | - <LinearLayout | ||
| 143 | - android:layout_width="wrap_content" | ||
| 144 | - android:layout_height="wrap_content" | ||
| 145 | - android:layout_gravity="center_horizontal" | ||
| 146 | - android:orientation="vertical"> | 164 | + <LinearLayout |
| 165 | + android:layout_width="match_parent" | ||
| 166 | + android:layout_height="wrap_content" | ||
| 167 | + android:layout_marginTop="14dp" | ||
| 168 | + android:orientation="horizontal"> | ||
| 147 | 169 | ||
| 148 | - <TextView | ||
| 149 | - android:layout_width="wrap_content" | ||
| 150 | - android:layout_height="wrap_content" | ||
| 151 | - android:layout_marginTop="11dp" | ||
| 152 | - android:text="@string/step_third" | ||
| 153 | - android:textColor="#ff1a1a1a" | ||
| 154 | - android:textSize="16sp" /> | 170 | + <TextView |
| 171 | + android:layout_width="wrap_content" | ||
| 172 | + android:layout_height="wrap_content" | ||
| 173 | + android:text="@string/take_photo_yourself" | ||
| 174 | + android:textColor="@color/color_9c9c9c" | ||
| 175 | + android:textSize="12sp" /> | ||
| 155 | 176 | ||
| 156 | - <LinearLayout | ||
| 157 | - android:layout_width="match_parent" | ||
| 158 | - android:layout_height="wrap_content" | ||
| 159 | - android:layout_marginTop="14dp" | ||
| 160 | - android:orientation="horizontal"> | 177 | + </LinearLayout> |
| 161 | 178 | ||
| 162 | - <TextView | 179 | + <ImageView |
| 180 | + android:id="@+id/take_picture" | ||
| 163 | android:layout_width="wrap_content" | 181 | android:layout_width="wrap_content" |
| 164 | android:layout_height="wrap_content" | 182 | android:layout_height="wrap_content" |
| 165 | - android:text="@string/take_photo_yourself" | ||
| 166 | - android:textColor="@color/color_9c9c9c" | ||
| 167 | - android:textSize="12sp" /> | ||
| 168 | - | 183 | + android:layout_marginTop="12dp" |
| 184 | + android:layout_marginBottom="22dp" | ||
| 185 | + android:onClick="@{click}" | ||
| 186 | + android:src="@drawable/take_photo" /> | ||
| 169 | </LinearLayout> | 187 | </LinearLayout> |
| 170 | 188 | ||
| 171 | - <ImageView | ||
| 172 | - android:id="@+id/take_picture" | ||
| 173 | - android:layout_width="wrap_content" | ||
| 174 | - android:layout_height="wrap_content" | ||
| 175 | - android:layout_marginTop="12dp" | ||
| 176 | - android:layout_marginBottom="22dp" | ||
| 177 | - android:onClick="@{click}" | ||
| 178 | - android:src="@drawable/take_photo" /> | ||
| 179 | </LinearLayout> | 189 | </LinearLayout> |
| 180 | - | ||
| 181 | - </LinearLayout> | ||
| 182 | - </ScrollView> | ||
| 183 | -</LinearLayout> | ||
| 184 | \ No newline at end of file | 190 | \ No newline at end of file |
| 191 | + </ScrollView> | ||
| 192 | + </LinearLayout> | ||
| 193 | +</layout> |
core/dentity_verfication/src/main/res/layout/bd_ocr_activity_camera.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?><!-- | ||
| 2 | + ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 3 | + --> | ||
| 4 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 5 | + android:layout_width="match_parent" | ||
| 6 | + android:layout_height="match_parent"> | ||
| 7 | + | ||
| 8 | + <include | ||
| 9 | + android:id="@+id/take_picture_container" | ||
| 10 | + layout="@layout/bd_ocr_take_picture" | ||
| 11 | + android:layout_width="match_parent" | ||
| 12 | + android:layout_height="match_parent" /> | ||
| 13 | + | ||
| 14 | + <include | ||
| 15 | + android:id="@+id/crop_container" | ||
| 16 | + layout="@layout/bd_ocr_crop" | ||
| 17 | + android:layout_width="match_parent" | ||
| 18 | + android:layout_height="match_parent" | ||
| 19 | + android:visibility="gone" /> | ||
| 20 | + | ||
| 21 | + <include | ||
| 22 | + android:id="@+id/confirm_result_container" | ||
| 23 | + layout="@layout/bd_ocr_confirm_result" | ||
| 24 | + android:layout_width="match_parent" | ||
| 25 | + android:layout_height="match_parent" | ||
| 26 | + android:visibility="gone" /> | ||
| 27 | + | ||
| 28 | + <ImageView | ||
| 29 | + android:id="@+id/back" | ||
| 30 | + android:layout_width="45dp" | ||
| 31 | + android:layout_height="45dp" | ||
| 32 | + android:padding="15dp" | ||
| 33 | + android:src="@drawable/bd_ocr_cancel" /> | ||
| 34 | +</RelativeLayout> |
core/dentity_verfication/src/main/res/layout/bd_ocr_confirm_result.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<!-- | ||
| 3 | + ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 4 | + --> | ||
| 5 | +<com.cnlive.core.ocr.ui.camera.OCRCameraLayout | ||
| 6 | + xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 7 | + xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 8 | + android:layout_width="match_parent" | ||
| 9 | + android:layout_height="match_parent" | ||
| 10 | + app:contentView="@+id/display_image_view" | ||
| 11 | + app:leftDownView="@+id/cancel_button" | ||
| 12 | + app:rightUpView="@+id/confirm_button"> | ||
| 13 | + | ||
| 14 | + <ImageView | ||
| 15 | + android:id="@+id/display_image_view" | ||
| 16 | + android:layout_width="match_parent" | ||
| 17 | + android:layout_height="wrap_content" | ||
| 18 | + android:layout_gravity="center" | ||
| 19 | + app:srcCompat="@drawable/bd_ocr_id_card_locator_front"/> | ||
| 20 | + | ||
| 21 | + <ImageView | ||
| 22 | + android:id="@+id/cancel_button" | ||
| 23 | + android:layout_width="60dp" | ||
| 24 | + android:layout_height="40dp" | ||
| 25 | + android:padding="12dp" | ||
| 26 | + android:src="@drawable/bd_ocr_cancel" | ||
| 27 | + android:layout_marginLeft="@dimen/camera_left_margin" | ||
| 28 | + android:layout_marginStart="@dimen/camera_left_margin" | ||
| 29 | + android:layout_marginBottom="@dimen/camera_bottom_margin" | ||
| 30 | + /> | ||
| 31 | + | ||
| 32 | + <ImageView | ||
| 33 | + android:id="@+id/confirm_button" | ||
| 34 | + android:layout_width="60dp" | ||
| 35 | + android:layout_height="40dp" | ||
| 36 | + android:padding="12dp" | ||
| 37 | + android:src="@drawable/bd_ocr_confirm" | ||
| 38 | + android:layout_marginRight="@dimen/camera_right_margin" | ||
| 39 | + android:layout_marginEnd="@dimen/camera_right_margin" | ||
| 40 | + android:layout_marginTop="@dimen/camera_top_margin" | ||
| 41 | + /> | ||
| 42 | + | ||
| 43 | +</com.cnlive.core.ocr.ui.camera.OCRCameraLayout> |
core/dentity_verfication/src/main/res/layout/bd_ocr_crop.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<!-- | ||
| 3 | + ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 4 | + --> | ||
| 5 | +<com.cnlive.core.ocr.ui.camera.OCRCameraLayout | ||
| 6 | + xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 7 | + xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 8 | + android:layout_width="match_parent" | ||
| 9 | + android:layout_height="match_parent" | ||
| 10 | + app:contentView="@+id/crop_view_container" | ||
| 11 | + app:leftDownView="@+id/cancel_button" | ||
| 12 | + app:centerView="@+id/rotate_button" | ||
| 13 | + app:rightUpView="@+id/confirm_button"> | ||
| 14 | + | ||
| 15 | + <com.cnlive.core.ocr.ui.camera.OCRFrameLayout | ||
| 16 | + android:id="@+id/crop_view_container" | ||
| 17 | + android:layout_width="match_parent" | ||
| 18 | + android:layout_height="wrap_content"> | ||
| 19 | + | ||
| 20 | + <com.cnlive.core.ocr.ui.crop.CropView | ||
| 21 | + android:id="@+id/crop_view" | ||
| 22 | + android:layout_width="match_parent" | ||
| 23 | + android:layout_height="match_parent"/> | ||
| 24 | + | ||
| 25 | + <com.cnlive.core.ocr.ui.crop.FrameOverlayView | ||
| 26 | + android:id="@+id/overlay_view" | ||
| 27 | + android:layout_width="match_parent" | ||
| 28 | + android:layout_height="match_parent"/> | ||
| 29 | + | ||
| 30 | + <com.cnlive.core.ocr.ui.camera.MaskView | ||
| 31 | + android:id="@+id/crop_mask_view" | ||
| 32 | + android:layout_width="match_parent" | ||
| 33 | + android:layout_height="match_parent"/> | ||
| 34 | + | ||
| 35 | + </com.cnlive.core.ocr.ui.camera.OCRFrameLayout> | ||
| 36 | + | ||
| 37 | + <ImageView | ||
| 38 | + android:id="@+id/rotate_button" | ||
| 39 | + android:layout_width="60dp" | ||
| 40 | + android:layout_height="58dp" | ||
| 41 | + android:padding="18dp" | ||
| 42 | + android:src="@drawable/bd_ocr_rotate"/> | ||
| 43 | + | ||
| 44 | + <ImageView | ||
| 45 | + android:id="@+id/confirm_button" | ||
| 46 | + android:layout_width="60dp" | ||
| 47 | + android:layout_height="40dp" | ||
| 48 | + android:padding="12dp" | ||
| 49 | + android:src="@drawable/bd_ocr_confirm" | ||
| 50 | + android:layout_marginRight="@dimen/camera_right_margin" | ||
| 51 | + android:layout_marginEnd="@dimen/camera_right_margin" | ||
| 52 | + android:layout_marginTop="@dimen/camera_top_margin"/> | ||
| 53 | + | ||
| 54 | + <ImageView | ||
| 55 | + android:id="@+id/cancel_button" | ||
| 56 | + android:layout_width="60dp" | ||
| 57 | + android:layout_height="40dp" | ||
| 58 | + android:padding="12dp" | ||
| 59 | + android:src="@drawable/bd_ocr_cancel" | ||
| 60 | + android:layout_marginLeft="@dimen/camera_left_margin" | ||
| 61 | + android:layout_marginStart="@dimen/camera_left_margin" | ||
| 62 | + android:layout_marginBottom="@dimen/camera_bottom_margin"/> | ||
| 63 | + | ||
| 64 | +</com.cnlive.core.ocr.ui.camera.OCRCameraLayout> |
core/dentity_verfication/src/main/res/layout/bd_ocr_take_picture.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<!-- | ||
| 3 | + ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 4 | + --> | ||
| 5 | +<com.cnlive.core.ocr.ui.camera.OCRCameraLayout | ||
| 6 | + xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 7 | + xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 8 | + android:layout_width="match_parent" | ||
| 9 | + android:layout_height="match_parent" | ||
| 10 | + app:contentView="@+id/camera_view" | ||
| 11 | + app:leftDownView="@+id/album_button" | ||
| 12 | + app:centerView="@+id/take_photo_button" | ||
| 13 | + app:rightUpView="@+id/light_button"> | ||
| 14 | + | ||
| 15 | + <com.cnlive.core.ocr.ui.camera.CameraView | ||
| 16 | + android:id="@+id/camera_view" | ||
| 17 | + android:layout_width="match_parent" | ||
| 18 | + android:layout_height="match_parent"> | ||
| 19 | + </com.cnlive.core.ocr.ui.camera.CameraView> | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + <ImageView | ||
| 23 | + android:id="@+id/album_button" | ||
| 24 | + android:layout_width="60dp" | ||
| 25 | + android:layout_height="54dp" | ||
| 26 | + android:padding="18dp" | ||
| 27 | + android:layout_marginLeft="@dimen/camera_left_margin" | ||
| 28 | + android:layout_marginStart="@dimen/camera_left_margin" | ||
| 29 | + android:layout_marginBottom="@dimen/camera_bottom_margin" | ||
| 30 | + android:src="@drawable/bd_ocr_gallery"/> | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + <ImageView | ||
| 34 | + android:id="@+id/take_photo_button" | ||
| 35 | + android:layout_width="58dp" | ||
| 36 | + android:layout_height="58dp" | ||
| 37 | + android:clickable="true" | ||
| 38 | + android:background="@drawable/bd_ocr_take_photo_selector"/> | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + <ImageView | ||
| 42 | + android:id="@+id/light_button" | ||
| 43 | + android:layout_width="60dp" | ||
| 44 | + android:layout_height="60dp" | ||
| 45 | + android:padding="15dp" | ||
| 46 | + android:clickable="true" | ||
| 47 | + android:layout_marginRight="@dimen/camera_right_margin" | ||
| 48 | + android:layout_marginEnd="@dimen/camera_right_margin" | ||
| 49 | + android:layout_marginTop="@dimen/camera_top_margin" | ||
| 50 | + android:paddingBottom="3dp" | ||
| 51 | + android:src="@drawable/bd_ocr_light_off" | ||
| 52 | + /> | ||
| 53 | + | ||
| 54 | +</com.cnlive.core.ocr.ui.camera.OCRCameraLayout> |
core/dentity_verfication/src/main/res/values/bd_ocr_dimensions.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<!-- | ||
| 3 | + ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 4 | + --> | ||
| 5 | +<resources> | ||
| 6 | + <dimen name="camera_left_margin">18dp</dimen> | ||
| 7 | + <dimen name="camera_right_margin">18dp</dimen> | ||
| 8 | + <dimen name="camera_bottom_margin">16dp</dimen> | ||
| 9 | + <dimen name="camera_top_margin">16dp</dimen> | ||
| 10 | +</resources> |
core/dentity_verfication/src/main/res/values/bd_ocr_widgets.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<!-- | ||
| 3 | + ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
| 4 | + --> | ||
| 5 | +<resources> | ||
| 6 | + <declare-styleable name="OCRCameraLayout"> | ||
| 7 | + <attr name="contentView" format="reference"/> | ||
| 8 | + <attr name="centerView" format="reference"/> | ||
| 9 | + <attr name="leftDownView" format="reference"/> | ||
| 10 | + <attr name="rightUpView" format="reference"/> | ||
| 11 | + </declare-styleable> | ||
| 12 | +</resources> |
core/dentity_verfication/src/main/res/values/color.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<resources> | ||
| 3 | + <color name="white">#ffffff</color> | ||
| 4 | + <color name="color_3bd8c4">#3bd8c4</color> | ||
| 5 | + <color name="color_9c9c9c">#9c9c9c</color> | ||
| 6 | + <color name="color_28c1c6">#28c1c6</color> | ||
| 7 | + <color name="color_2bcdc9">#2bcdc9</color> | ||
| 8 | + <color name="color_28b4a7">#28b4a7</color> | ||
| 9 | + <color name="color_2cd1c9">#2cd1c9</color> | ||
| 10 | + <color name="color_282828">#282828</color> | ||
| 11 | + <color name="color_989898">#989898</color> | ||
| 12 | + <color name="color_ffa300">#1AAD19</color> | ||
| 13 | + <color name="color_f2f2f2">#f2f2f2</color> | ||
| 14 | + <color name="color_656565">#656565</color> | ||
| 15 | + <color name="color_999999">#999999</color> | ||
| 16 | + <color name="color_99f2f2f2">#99f2f2f2</color> | ||
| 17 | + <color name="color_898989">#898989</color> | ||
| 18 | + <color name="blacktext">#1c1c1c</color> | ||
| 19 | + <color name="color_39c2c3">#39c2c3</color> | ||
| 20 | + <color name="color_666666">#666666</color> | ||
| 21 | + <color name="grayText">#aaaaaa</color> | ||
| 22 | + <color name="ff3b30">#FF3B30</color> | ||
| 23 | + <color name="color_23d41e">#23D41E</color> | ||
| 24 | + <color name="color_d8d8d8">#D8D8D8</color> | ||
| 25 | + <color name="monkey_agreement">#BC491C</color> | ||
| 26 | + <color name="witness_agreement">#0DC204</color> | ||
| 27 | +</resources> | ||
| 0 | \ No newline at end of file | 28 | \ No newline at end of file |
core/dentity_verfication/src/main/res/values/strings.xml
| 1 | <resources> | 1 | <resources> |
| 2 | + <string name="camera_permission_required">本功能需要相机权限!</string> | ||
| 3 | + <string name="permission_required">本功能需要存储权限!</string> | ||
| 2 | <string name="step_one">第一步</string> | 4 | <string name="step_one">第一步</string> |
| 3 | <string name="please_get_yourself_card">请拍摄本人身份证</string> | 5 | <string name="please_get_yourself_card">请拍摄本人身份证</string> |
| 4 | <string name="font">正面</string> | 6 | <string name="font">正面</string> |
| @@ -7,4 +9,8 @@ | @@ -7,4 +9,8 @@ | ||
| 7 | <string name="back">反面</string> | 9 | <string name="back">反面</string> |
| 8 | <string name="step_third">第三步</string> | 10 | <string name="step_third">第三步</string> |
| 9 | <string name="take_photo_yourself">请您拍摄本人照片,确保肩部至头部清晰可见</string> | 11 | <string name="take_photo_yourself">请您拍摄本人照片,确保肩部至头部清晰可见</string> |
| 12 | + | ||
| 13 | + | ||
| 14 | + <string name="init_baidu_faile">初始化失败</string> | ||
| 15 | + <string name="only_for_verified">仅用于实名认证</string> | ||
| 10 | </resources> | 16 | </resources> |