|
@@ -0,0 +1,311 @@
|
|
|
+package com.ethan.psbc.ui.widgets
|
|
|
+
|
|
|
+import android.Manifest
|
|
|
+import android.animation.ValueAnimator
|
|
|
+import android.annotation.SuppressLint
|
|
|
+import android.content.Context
|
|
|
+import android.content.pm.PackageManager
|
|
|
+import android.content.res.TypedArray
|
|
|
+import android.graphics.*
|
|
|
+import android.graphics.Canvas.ALL_SAVE_FLAG
|
|
|
+import android.util.AttributeSet
|
|
|
+import android.util.Log
|
|
|
+import android.view.SurfaceHolder
|
|
|
+import android.view.SurfaceView
|
|
|
+import android.view.animation.LinearInterpolator
|
|
|
+import android.widget.Toast
|
|
|
+import androidx.appcompat.app.AppCompatActivity
|
|
|
+import androidx.camera.core.*
|
|
|
+import androidx.camera.core.Camera
|
|
|
+import androidx.camera.lifecycle.ProcessCameraProvider
|
|
|
+import androidx.core.app.ActivityCompat
|
|
|
+import androidx.core.content.ContextCompat
|
|
|
+import cn.face.sdk.FaceDetTrack
|
|
|
+import cn.face.sdk.FaceInterface
|
|
|
+import cn.face.sdk.FaceParam
|
|
|
+import com.ethan.psbc.R
|
|
|
+import java.nio.ByteBuffer
|
|
|
+import java.util.concurrent.ExecutorService
|
|
|
+import java.util.concurrent.Executors
|
|
|
+import kotlin.math.cos
|
|
|
+import kotlin.math.max
|
|
|
+import kotlin.math.min
|
|
|
+import kotlin.math.sin
|
|
|
+
|
|
|
+class CameraScanView(mContext: Context, attrs:AttributeSet?, defStyleAttr:Int) : SurfaceView(mContext,attrs,defStyleAttr) ,SurfaceHolder.Callback {
|
|
|
+
|
|
|
+ constructor(context: Context) : this(context, null)
|
|
|
+ constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ private var mBackGroundPaint: Paint
|
|
|
+ private var mCirclePaint: Paint
|
|
|
+ private var mScanLinePaint: Paint
|
|
|
+
|
|
|
+ //渐变色的起始值
|
|
|
+ private var mStartColor: Int
|
|
|
+ //渐变色的终止值
|
|
|
+ private var mFinishColor: Int
|
|
|
+
|
|
|
+ //圆形半径
|
|
|
+ private var mRadius: Float
|
|
|
+ //整个view的宽度
|
|
|
+ private var mWidth: Float = 0.0f
|
|
|
+ //整个view的高度度
|
|
|
+ private var mHeight: Float = 0.0f
|
|
|
+ //圆心的横坐标
|
|
|
+ private var mCenterX: Float = 0.0f
|
|
|
+ //圆心的纵坐标
|
|
|
+ private var mCenterY: Float = 0.0f
|
|
|
+
|
|
|
+ //扫描线(梯形)右上角的点对圆心的角度
|
|
|
+ private var mDegree: Double = 0.0
|
|
|
+ //扫描线(梯形)右下角的点对圆心的角度-右上角的点对圆心的角度的差值,体现为扫描线的线宽
|
|
|
+ private var mDegreeAdd: Double = 1.0
|
|
|
+
|
|
|
+ private val mXfermode: Xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)
|
|
|
+ //遮罩的颜色
|
|
|
+ private var mBackGroundColor: Int
|
|
|
+ //扫描的动画时间
|
|
|
+ private var mDuration: Int
|
|
|
+ private var mAnimator: ValueAnimator? = null
|
|
|
+
|
|
|
+ init {
|
|
|
+ @SuppressLint("Recycle")
|
|
|
+ val typedArray: TypedArray =
|
|
|
+ context.obtainStyledAttributes(attrs, R.styleable.CameraScan)
|
|
|
+
|
|
|
+ mBackGroundPaint = Paint()
|
|
|
+ mBackGroundPaint.run {
|
|
|
+ style = Paint.Style.FILL
|
|
|
+ isAntiAlias = true // 设置抗锯齿
|
|
|
+ isDither = true // 设置抖动
|
|
|
+ }
|
|
|
+
|
|
|
+ mBackGroundColor = typedArray.getColor(R.styleable.CameraScan_backGroundColor, Color.WHITE)
|
|
|
+
|
|
|
+
|
|
|
+ mCirclePaint = Paint()
|
|
|
+ mCirclePaint.run {
|
|
|
+ style = Paint.Style.STROKE
|
|
|
+ isAntiAlias = true
|
|
|
+ isDither = true
|
|
|
+ strokeWidth = typedArray.getDimension(R.styleable.CameraScan_circleWidth, 10f)
|
|
|
+ color = typedArray.getColor(R.styleable.CameraScan_circleColor, Color.BLACK)
|
|
|
+ }
|
|
|
+ mRadius = typedArray.getDimension(R.styleable.CameraScan_circleRadius, 0.0f)
|
|
|
+
|
|
|
+
|
|
|
+ mScanLinePaint = Paint()
|
|
|
+ mScanLinePaint.run {
|
|
|
+ style = Paint.Style.FILL
|
|
|
+ strokeCap = Paint.Cap.ROUND
|
|
|
+ isAntiAlias = true
|
|
|
+ isDither = true
|
|
|
+ color = Color.GREEN
|
|
|
+ }
|
|
|
+ mDegreeAdd = typedArray.getFloat(R.styleable.CameraScan_scanLineWidth, 1.0f).toDouble()
|
|
|
+ mDuration = typedArray.getInt(R.styleable.CameraScan_scanDuration, 10000)
|
|
|
+ mStartColor = typedArray.getColor(R.styleable.CameraScan_scanLineStartColor, -1)
|
|
|
+ mFinishColor = typedArray.getColor(R.styleable.CameraScan_scanLineFinishColor, -1)
|
|
|
+
|
|
|
+ typedArray.recycle()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
|
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
|
+ mWidth = (measuredWidth - paddingLeft - paddingRight).toFloat()
|
|
|
+ mHeight = (measuredHeight - paddingTop - paddingBottom).toFloat()
|
|
|
+
|
|
|
+ mCenterX = mWidth / 2
|
|
|
+ mCenterY = mHeight / 2
|
|
|
+
|
|
|
+ val short = min(mWidth, mHeight)
|
|
|
+ if (mRadius == 0.0f || mRadius * 2 > short) {
|
|
|
+ mRadius = short / 2
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDraw(canvas: Canvas) {
|
|
|
+ super.onDraw(canvas)
|
|
|
+
|
|
|
+ //绘制黑色遮罩,先绘制黑色背景,再绘制透明圆形
|
|
|
+ mBackGroundPaint.color = Color.BLACK //随便设个颜色值,只要不是透明的就行,防止第二次绘制时,mBackGroundPaint.color 被下面赋值了Color.TRANSPARENT 导致canvas.drawColor(mBackGroundColor)不起效果
|
|
|
+ val layerID = canvas.saveLayer(0.0f, 0.0f, mWidth, mHeight, mBackGroundPaint, ALL_SAVE_FLAG) //保存图层,下面通过estoreToCount恢复
|
|
|
+ canvas.drawColor(mBackGroundColor) //遮罩的颜色
|
|
|
+ mBackGroundPaint.xfermode = mXfermode //这里选择DST_ATOP
|
|
|
+ mBackGroundPaint.color = Color.TRANSPARENT //颜色设置成透明
|
|
|
+ canvas.drawCircle(mCenterX, mCenterY, mRadius, mBackGroundPaint) //透明的圆形
|
|
|
+ mBackGroundPaint.xfermode = null
|
|
|
+ canvas.restoreToCount(layerID)
|
|
|
+
|
|
|
+ //绘制白色框,圆弧
|
|
|
+ canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint)
|
|
|
+
|
|
|
+
|
|
|
+ //角度制转弧度制
|
|
|
+ val radians = Math.toRadians(mDegree).toFloat()
|
|
|
+ val radiansAdd = Math.toRadians(mDegree + mDegreeAdd).toFloat()
|
|
|
+
|
|
|
+ //着色器,实现扫描线的渐变效果
|
|
|
+ takeIf { mStartColor != -1 && mFinishColor != -1 }?.run {
|
|
|
+ val y0 = mCenterY - mRadius * cos(radians)
|
|
|
+ val y1 = mCenterY - mRadius * cos(radiansAdd)
|
|
|
+ mScanLinePaint.shader = LinearGradient(0.0f, max(y0, y1), 0.0f, min(y0, y1), mStartColor, mFinishColor, Shader.TileMode.CLAMP)
|
|
|
+ }
|
|
|
+
|
|
|
+ //绘制扫描线,梯形
|
|
|
+ val path = Path()
|
|
|
+ path.moveTo(mCenterX + mRadius * sin(-radians), mCenterY - mRadius * cos(radians))
|
|
|
+ path.lineTo(mCenterX + mRadius * sin(radians), mCenterY - mRadius * cos(radians))
|
|
|
+ path.lineTo(mCenterX + mRadius * sin(radiansAdd), mCenterY - mRadius * cos(radiansAdd))
|
|
|
+ path.lineTo(mCenterX + mRadius * sin(-radiansAdd), mCenterY - mRadius * cos(radiansAdd))
|
|
|
+ path.close()
|
|
|
+ canvas.drawPath(path, mScanLinePaint)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //扫描动画,通过改变角度,实现扫描线上下移动
|
|
|
+ fun startScan() {
|
|
|
+ mAnimator?.run { cancel() }
|
|
|
+ mAnimator = ValueAnimator.ofFloat(0.0f, 360.0f)
|
|
|
+ mAnimator?.let {
|
|
|
+ it.addUpdateListener { animation ->
|
|
|
+ this.mDegree = (animation.animatedValue as Float).toDouble()
|
|
|
+ invalidate()
|
|
|
+ }
|
|
|
+ it.duration = mDuration.toLong()
|
|
|
+ it.interpolator = LinearInterpolator()
|
|
|
+ it.repeatCount = ValueAnimator.INFINITE
|
|
|
+ it.repeatMode = ValueAnimator.RESTART
|
|
|
+ it.start()
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ override fun surfaceCreated(holder: SurfaceHolder) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
|
|
+ TODO("Not yet implemented")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
|
|
|
+ TODO("Not yet implemented")
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ private lateinit var cameraExecutor: ExecutorService
|
|
|
+ private var cameraProvider: ProcessCameraProvider? = null
|
|
|
+ private var preview: Preview? = null
|
|
|
+ private var imageCamera: ImageCapture? = null
|
|
|
+ private lateinit var camera: Camera //相机对象
|
|
|
+ private lateinit var imageAnalysis: ImageAnalysis
|
|
|
+
|
|
|
+ private val licence:String="MDQwNTE3NWQyNTkzNGM4MmQ3ZDk2MmQyM2UzODBhMmI1YjFjNjdjd2F1dGhvcml6Zd3n5+Xk5+fk/OXm5+fg5ef15efn5+Xk4Mfl4OXm5uvliObm6+Xm5uuR5ebm6+Xm5qLr5ebm6+Xmrubg5ufr5+v95+vn4OTk59Dk5ubr5+vn1uvn5+fm5Of+5g=="
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ fun runCamera(context: Context){
|
|
|
+
|
|
|
+ if( PackageManager.PERMISSION_GRANTED!= ContextCompat.checkSelfPermission(context , Manifest.permission.CAMERA)){
|
|
|
+ ActivityCompat.requestPermissions(context as AppCompatActivity, arrayOf(Manifest.permission.CAMERA),1)
|
|
|
+ Toast.makeText(context as AppCompatActivity, "申请相机权限", Toast.LENGTH_SHORT).show()
|
|
|
+ }else{
|
|
|
+ Toast.makeText(context as AppCompatActivity, "已经拥有相机权限", Toast.LENGTH_SHORT).show()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ val cameraSelector: CameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
|
|
|
+ // mBinding.btnDemoView.implementationMode= PreviewView.ImplementationMode.COMPATIBLE
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ cameraExecutor = Executors.newSingleThreadExecutor()
|
|
|
+ val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
|
|
+ cameraProviderFuture.addListener({
|
|
|
+ cameraProvider = cameraProviderFuture.get()//获取相机信息
|
|
|
+
|
|
|
+ //预览配置
|
|
|
+ preview = Preview.Builder()
|
|
|
+ .build()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ imageCamera = ImageCapture.Builder()
|
|
|
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
|
+ .build()
|
|
|
+
|
|
|
+
|
|
|
+ imageAnalysis = ImageAnalysis.Builder()
|
|
|
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
|
+ .build()
|
|
|
+
|
|
|
+
|
|
|
+ imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
|
|
|
+ val planes: Array<ImageProxy.PlaneProxy> = imageProxy.planes
|
|
|
+ val buffer: ByteBuffer = planes[0].buffer;
|
|
|
+ val data: ByteArray = ByteArray(buffer.capacity())
|
|
|
+ buffer.rewind()
|
|
|
+ buffer.get(data)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ Log.d("demo", "正在识别图像信息")
|
|
|
+ FaceDetTrack.getInstance()
|
|
|
+ val ret: Int = FaceDetTrack.cwCreateDetHandle("", licence)
|
|
|
+ if (ret >= FaceInterface.cw_errcode_t.CW_UNKNOWN_ERR) {
|
|
|
+ Log.d("demo", "启用识别程序SDK失败")
|
|
|
+ } else {
|
|
|
+ Log.d("demo", "启用识别程序SDK成功")
|
|
|
+ val faceParam = FaceParam()
|
|
|
+ FaceDetTrack.cwGetFaceParam(ret, faceParam)
|
|
|
+ faceParam.minSize = 30
|
|
|
+ faceParam.maxSize = 400;
|
|
|
+ FaceDetTrack.cwSetFaceParam(ret, faceParam, "");
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ imageProxy.close()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ try {
|
|
|
+ cameraProvider?.unbindAll()//先解绑所有用例
|
|
|
+ camera = cameraProvider?.bindToLifecycle(
|
|
|
+ context,
|
|
|
+ cameraSelector,
|
|
|
+ preview,
|
|
|
+ imageCamera,
|
|
|
+ imageAnalysis
|
|
|
+ )!!//绑定用例
|
|
|
+ } catch (_: Exception) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ preview?.setSurfaceProvider(null)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ }, ContextCompat.getMainExecutor(context))
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|