1
This commit is contained in:
@@ -0,0 +1,979 @@
|
||||
import CameraManager from 'android.hardware.camera2.CameraManager'
|
||||
import CameraDevice from 'android.hardware.camera2.CameraDevice'
|
||||
import StateCallback from 'android.hardware.camera2.CameraDevice.StateCallback'
|
||||
import CameraCaptureSession from 'android.hardware.camera2.CameraCaptureSession'
|
||||
|
||||
import CaptureRequest from 'android.hardware.camera2.CaptureRequest'
|
||||
import TotalCaptureResult from 'android.hardware.camera2.TotalCaptureResult'
|
||||
import StreamConfigurationMap from 'android.hardware.camera2.params.StreamConfigurationMap'
|
||||
import MeteringRectangle from 'android.hardware.camera2.params.MeteringRectangle'
|
||||
import OutputConfiguration from 'android.hardware.camera2.params.OutputConfiguration'
|
||||
import SessionConfiguration from 'android.hardware.camera2.params.SessionConfiguration'
|
||||
import CameraCharacteristics from 'android.hardware.camera2.CameraCharacteristics'
|
||||
import CameraMetadata from 'android.hardware.camera2.CameraMetadata'
|
||||
|
||||
|
||||
import ImageFormat from 'android.graphics.ImageFormat'
|
||||
import Size from 'android.util.Size'
|
||||
import SurfaceTexture from 'android.graphics.SurfaceTexture'
|
||||
import Handler from 'android.os.Handler'
|
||||
import Surface from 'android.view.Surface'
|
||||
import Image from 'android.media.Image'
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import Intent from 'android.content.Intent';
|
||||
import Integer from 'java.lang.Integer';
|
||||
import ByteOrder from 'java.nio.ByteOrder';
|
||||
|
||||
import SurfaceHolder from 'android.view.SurfaceHolder'
|
||||
import SurfaceView from 'android.view.SurfaceView'
|
||||
import TextureView from 'android.view.TextureView'
|
||||
import View from 'android.view.View'
|
||||
import ViewGroup from 'android.view.ViewGroup'
|
||||
import Context from 'android.content.Context'
|
||||
import Manifest from "android.Manifest";
|
||||
import Uri from 'android.net.Uri';
|
||||
import File from 'java.io.File'
|
||||
import System from 'java.lang.System'
|
||||
import Activity from 'android.app.Activity';
|
||||
import Bundle from 'android.os.Bundle';
|
||||
import Intent from 'android.content.Intent';
|
||||
import IntentFilter from 'android.content.IntentFilter';
|
||||
import R from 'io.dcloud.uni_modules.xMlkitScannigS.R';
|
||||
import Rect from 'android.graphics.Rect';
|
||||
import Matrix from 'android.graphics.Matrix'
|
||||
import ImageReader from 'android.media.ImageReader'
|
||||
import ByteBuffer from 'java.nio.ByteBuffer'
|
||||
import MediaStore from 'android.provider.MediaStore'
|
||||
import Plane from 'android.media.Image.Plane'
|
||||
import Bitmap from "android.graphics.Bitmap"
|
||||
import BitmapFactory from "android.graphics.BitmapFactory"
|
||||
import Vibrator from "android.os.Vibrator"
|
||||
import Animation from "android.view.animation.Animation"
|
||||
import ScaleAnimation from "android.view.animation.ScaleAnimation"
|
||||
import AnimationUtils from "android.view.animation.AnimationUtils"
|
||||
|
||||
|
||||
import Color from "android.graphics.Color"
|
||||
import Window from "android.view.Window"
|
||||
import WindowManager from "android.view.WindowManager"
|
||||
import TextView from "android.widget.TextView"
|
||||
import RelativeLayout from "android.widget.RelativeLayout"
|
||||
import ImageView from "android.widget.ImageView"
|
||||
import Bundle from 'android.os.Bundle'
|
||||
import Handler from 'android.os.Handler'
|
||||
import HandlerThread from 'android.os.HandlerThread'
|
||||
import YuvImage from 'android.graphics.YuvImage'
|
||||
import ByteArrayOutputStream from 'java.io.ByteArrayOutputStream'
|
||||
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
|
||||
import MotionEvent from 'android.view.MotionEvent';
|
||||
import { decoderUriPathToQr, decoderBuffToQr,decoderUriPathToQrByUri } from "./index.uts"
|
||||
import { SCANNING_PHOTO_RESULT } from "../interface.uts"
|
||||
import xView from "./libview/view.uts"
|
||||
|
||||
type Dimension = number[];
|
||||
let textStr = null as null | string
|
||||
function findClosestRatio(dimensions : Dimension[], targetWidth : number, targetHeight : number) : Dimension | null {
|
||||
let minDiff = Integer.MAX_VALUE;
|
||||
let closestPair : Dimension | null = null;
|
||||
|
||||
for (let i = 0; i < dimensions.length; i++) {
|
||||
const width = dimensions[i][0]!
|
||||
const height = dimensions[i][1]!
|
||||
const currentRatio = width / height;
|
||||
const targetRatio = targetWidth / targetHeight;
|
||||
|
||||
// const diff = Math.abs(currentRatio - targetRatio);
|
||||
const diff = Math.abs(width - targetWidth) + Math.abs(height - targetHeight);
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff.toInt();
|
||||
closestPair = [width, height];
|
||||
}
|
||||
}
|
||||
|
||||
return closestPair;
|
||||
}
|
||||
|
||||
|
||||
|
||||
class DemoActivity extends Activity {
|
||||
scanSpeed = 500 //识别速度单位ms
|
||||
scanPrevwSize = 800 //预期识别的像素大小,这是预期,会根据这个值找到相机支持的相近的相机尺寸。
|
||||
|
||||
context : Context;
|
||||
camera_service : CameraManager;
|
||||
cameraPermissioPass = false;
|
||||
camerManIds = null as string | null
|
||||
cameraDevice = null as null | CameraDevice
|
||||
preview = null as null | SurfaceView;
|
||||
captureSession = null as null | CameraCaptureSession
|
||||
|
||||
previewWidth = 0 as Int;
|
||||
previewHeight = 0 as Int;
|
||||
prviewTop = 0
|
||||
prviewLeft = 0
|
||||
okScal = [0, 0] as Dimension
|
||||
imageHandler = null as null | Handler;
|
||||
|
||||
isDecodreIng = false
|
||||
|
||||
scanBoxView = null as null | View
|
||||
scanBoxViewDrawble = null as null | GradientDrawable
|
||||
//选择相册按钮
|
||||
xiangCeBtn = null as TextView | null
|
||||
paiZhaoBtn = null as TextView | null
|
||||
shaoyishaoBtn = null as TextView | null
|
||||
|
||||
//相册的标识点容器
|
||||
xiangjiBox = null as RelativeLayout | null
|
||||
|
||||
//相册图片预览父viewout
|
||||
imageYuranBox = null as RelativeLayout | null
|
||||
imageYuranBox_width = 0 as Int;
|
||||
imageYuranBox_height = 0 as Int;
|
||||
imageView = null as null | ImageView
|
||||
imageUrl = null as null | Uri
|
||||
isSelectedImging = false;
|
||||
|
||||
// 结果集的标注点。
|
||||
imgPoints = [] as RelativeLayout[]
|
||||
imgPointsBycanmarea = [] as RelativeLayout[]
|
||||
|
||||
ORIENTATIONS = new Map<Number, Number>()
|
||||
// 当前是否正在扫码中
|
||||
isScannig = true
|
||||
|
||||
tid = 0;
|
||||
|
||||
vibrator = null as null|Vibrator
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.context = UTSAndroid.getAppContext() as Context;
|
||||
this.camera_service = this.context!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
this.ORIENTATIONS.set(Surface.ROTATION_0, 0)
|
||||
this.ORIENTATIONS.set(Surface.ROTATION_90, 90)
|
||||
this.ORIENTATIONS.set(Surface.ROTATION_180, 180)
|
||||
this.ORIENTATIONS.set(Surface.ROTATION_270, 270)
|
||||
|
||||
// 在你的Activity或者Fragment中
|
||||
this.vibrator = this.context!.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
override onCreate(savedInstanceState ?: Bundle) : void {
|
||||
super.onCreate(savedInstanceState)
|
||||
this.hideTitleBar()
|
||||
this.setContentView(R.layout.demo_activity);
|
||||
|
||||
this.overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
|
||||
|
||||
this.preview = this.findViewById<SurfaceView>(R.id.surface_camera);
|
||||
this.xiangCeBtn = this.findViewById<TextView>(R.id.footer_asml);
|
||||
this.paiZhaoBtn = this.findViewById<TextView>(R.id.footer_camre);
|
||||
this.shaoyishaoBtn = this.findViewById<TextView>(R.id.footer_scanning);
|
||||
//相机的图片预览
|
||||
this.imageYuranBox = this.findViewById<RelativeLayout>(R.id.scan_box);
|
||||
//相机的遮罩标记点的容器
|
||||
this.xiangjiBox = this.findViewById<RelativeLayout>(R.id.camre_center_preview);
|
||||
|
||||
let t = this;
|
||||
|
||||
this.steBtnLisents()
|
||||
|
||||
|
||||
// 创建图片预览的组件。
|
||||
this.imageView = new ImageView(this.context);
|
||||
this.imageView!.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
|
||||
this.imageYuranBox!.addView(this.imageView)
|
||||
|
||||
|
||||
let layoutParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
//监听尺寸变化。
|
||||
class previewChangeRectadd extends View.OnLayoutChangeListener {
|
||||
override onLayoutChange(v : View, left : Int, top : Int, right : Int, bottom : Int,
|
||||
oldLeft : Int, oldTop : Int, oldRight : Int, oldBottom : Int
|
||||
) {
|
||||
t.imageYuranBox_width = right - left;
|
||||
t.imageYuranBox_height = bottom - top;
|
||||
v.removeOnLayoutChangeListener(this);
|
||||
}
|
||||
}
|
||||
this.imageYuranBox!.addOnLayoutChangeListener(new previewChangeRectadd())
|
||||
|
||||
|
||||
// this.scanBoxView = this.findViewById<View>(R.id.scan_view_canvas);
|
||||
// this.scanBoxViewDrawble = new GradientDrawable();
|
||||
// this.scanBoxView.setOnDragListener
|
||||
// this.customStyleSacnBox();
|
||||
|
||||
}
|
||||
steBtnLisents() {
|
||||
let t = this;
|
||||
class xiangCheCallClick extends View.OnClickListener {
|
||||
override onClick(v ?: View) : void {
|
||||
t.closeCamra()
|
||||
t.isSelectedImging = true;
|
||||
let intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||
t.startActivityForResult(intent, 1)
|
||||
}
|
||||
}
|
||||
// 相同选择时。
|
||||
this.xiangCeBtn!.setOnClickListener(new xiangCheCallClick())
|
||||
|
||||
|
||||
class shaoyishaoBtnCallClick extends View.OnClickListener {
|
||||
override onClick(v ?: View) : void {
|
||||
t.isSelectedImging = false;
|
||||
t.clearImageView()
|
||||
t.openCamra();
|
||||
}
|
||||
}
|
||||
//实时扫一扫时
|
||||
this.shaoyishaoBtn!.setOnClickListener(new shaoyishaoBtnCallClick())
|
||||
|
||||
let backBtn = this.findViewById<TextView>(R.id.header_back);
|
||||
|
||||
class backCallClick extends View.OnClickListener {
|
||||
override onClick(v ?: View) : void {
|
||||
t.finish()
|
||||
t.overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
|
||||
}
|
||||
}
|
||||
//实时扫一扫时
|
||||
backBtn!.setOnClickListener(new backCallClick())
|
||||
|
||||
}
|
||||
// 设置扫码框的样式。
|
||||
customStyleSacnBox() {
|
||||
if (this.scanBoxView == null) return;
|
||||
// this.scanBoxViewDrawble!.setShape(GradientDrawable.RECTANGLE)
|
||||
// this.scanBoxViewDrawble!.setStroke((4).toInt(),Color.GREEN)
|
||||
// this.scanBoxViewDrawble!.mutate()
|
||||
// this.scanBoxView!.setBackground(this!.scanBoxViewDrawble)
|
||||
}
|
||||
//震动手机
|
||||
vibatarLong(){
|
||||
if(this.vibrator == null) return
|
||||
// 检查设备是否支持震动
|
||||
if (this.vibrator!.hasVibrator()) {
|
||||
// 触发短暂的震动,持续时间为200毫秒
|
||||
this.vibrator!.vibrate(80);
|
||||
}
|
||||
}
|
||||
|
||||
override onDestroy() : void {
|
||||
super.onDestroy();
|
||||
if (this.captureSession != null) {
|
||||
this.captureSession!.close();
|
||||
this.cameraDevice?.close()
|
||||
this.captureSession = null;
|
||||
this.cameraDevice = null;
|
||||
console.log("关闭相机");
|
||||
|
||||
}
|
||||
this.showTitleBar();
|
||||
}
|
||||
|
||||
// 在需要隐藏标题栏的地方调用
|
||||
hideTitleBar() : void {
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
// 在需要显示标题栏的地方调用
|
||||
showTitleBar() : void {
|
||||
this.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查相机权限
|
||||
*/
|
||||
isCheckPermissions() : Promise<boolean> {
|
||||
let permissionCheck = ["android.permission.CAMERA"]
|
||||
// 请求拍照权限
|
||||
return new Promise(res => {
|
||||
if (UTSAndroid.checkSystemPermissionGranted(UTSAndroid.getUniActivity()!, permissionCheck)) {
|
||||
console.log("当前已具备指定权限")
|
||||
this.cameraPermissioPass = true
|
||||
res(true)
|
||||
} else {
|
||||
console.log("当前不具备指定权限")
|
||||
// 请求拍照权限
|
||||
UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionCheck, function (allRight : boolean, _ : string[]) {
|
||||
this.cameraPermissioPass = allRight
|
||||
res(true)
|
||||
}, function (_ : boolean, _ : string[]) {
|
||||
//用户拒绝了部分权限
|
||||
this.cameraPermissioPass = false
|
||||
res(false)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
getCameraById() {
|
||||
// const cameraIds = this.camera_service.cameraIdList
|
||||
let caIds = null as null | string;
|
||||
|
||||
for (id in this.camera_service.getCameraIdList()) {
|
||||
let ps = this.camera_service.getCameraCharacteristics(id);
|
||||
let facing = ps.get(CameraCharacteristics.LENS_FACING);
|
||||
// 后置摄像头
|
||||
if (facing == CameraCharacteristics.LENS_FACING_BACK) {
|
||||
caIds = id;
|
||||
let rulest = null as Dimension | null
|
||||
// 获取尺寸
|
||||
let maps = ps.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
|
||||
if (maps != null) {
|
||||
let testSize = maps!.getOutputSizes(ImageFormat.JPEG)
|
||||
let psdd = [] as Array<Dimension>
|
||||
for (sz in testSize) {
|
||||
let w = sz.getWidth()
|
||||
let h = sz.getHeight()
|
||||
psdd.push([w, h] as Dimension)
|
||||
}
|
||||
// console.log(psdd)
|
||||
// 这里使用一个800*800视野区域图片大概64万像素的图片进行识别,这样可以提高性能减少手机识别的负担。
|
||||
let pd = findClosestRatio(psdd, this.scanPrevwSize, this.scanPrevwSize)
|
||||
// 如果想要高清识别或者加缩小焦距(就景深远,视野大,就开启下面的)
|
||||
// let pd = findClosestRatio(psdd, this.previewWidth, this.previewHeight)
|
||||
if (pd != null) {
|
||||
this.okScal = pd as Dimension;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.camerManIds = caIds
|
||||
// characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
|
||||
// console.log(this.camera_service.cameraIdList)
|
||||
// this.camerListIds = this.camera_service.getCameraIdList();
|
||||
// for(let i=0;i<this.camerListIds.length;i++){
|
||||
// console.log(this.camerListIds[i])
|
||||
// }
|
||||
}
|
||||
//裁剪区域的缩放比。
|
||||
calculateCropRegion(cameraOutputWidth : Number, cameraOutputHeight : Number, previewWidth : Number, previewHeight : Number) : Rect {
|
||||
let targetAspectRatio = previewWidth / previewHeight
|
||||
let cropWidth = 0
|
||||
let cropHeight = 0
|
||||
|
||||
// 根据目标宽高比确定裁剪宽度或高度,并确保不超过相机输出尺寸
|
||||
if (cameraOutputWidth * previewHeight > cameraOutputHeight * previewWidth) {
|
||||
// 裁剪高度为相机输出高度,根据宽高比计算裁剪宽度
|
||||
cropHeight = cameraOutputHeight
|
||||
cropWidth = (cropHeight * targetAspectRatio)
|
||||
cropWidth = Math.min(cropWidth, cameraOutputWidth)
|
||||
} else {
|
||||
// 裁剪宽度为相机输出宽度,根据宽高比计算裁剪高度
|
||||
cropWidth = cameraOutputWidth
|
||||
cropHeight = (cropWidth / targetAspectRatio)
|
||||
cropHeight = Math.min(cropHeight, cameraOutputHeight)
|
||||
}
|
||||
|
||||
// 计算居中对齐的裁剪区域
|
||||
let centerX = (cameraOutputWidth - cropWidth) / 2
|
||||
let centerY = (cameraOutputHeight - cropHeight) / 2
|
||||
|
||||
return Rect(
|
||||
centerX.toInt(), // 左边界
|
||||
centerY.toInt(), // 上边界
|
||||
(centerX + cropWidth).toInt(), // 右边界
|
||||
(centerY + cropHeight).toInt() // 下边界
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
createCameraPreviewSession() {
|
||||
if (this.preview == null) return;
|
||||
let holder = this.preview!.getHolder()
|
||||
let suface = holder!.getSurface();
|
||||
if (this.cameraDevice == null) return;
|
||||
let t = this;
|
||||
let previewRequestBuilder = this.cameraDevice!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
|
||||
previewRequestBuilder?.addTarget(suface)
|
||||
let totalWidth = this.okScal[0].toInt()
|
||||
let totalHeight = this.okScal[1].toInt()
|
||||
|
||||
console.log(totalWidth,totalHeight)
|
||||
previewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, new Rect(0, 0, totalWidth*3, totalHeight*3));
|
||||
// previewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, this.calculateCropRegion(totalWidth, totalHeight, this.previewWidth, this.previewWidth));
|
||||
// 再添加一个image来预览
|
||||
let minh = Math.min(this.previewWidth, this.previewHeight)
|
||||
let reader = ImageReader.newInstance(minh.toInt(), minh.toInt(), ImageFormat.YUV_420_888, 1)
|
||||
previewRequestBuilder?.addTarget(reader.getSurface())
|
||||
let outsufaces = [suface, reader.getSurface()] as Surface[]
|
||||
|
||||
|
||||
class sessionCallback extends CameraCaptureSession.StateCallback {
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
override onConfigured(session : CameraCaptureSession) {
|
||||
t.captureSession = session
|
||||
try {
|
||||
t.captureSession?.setRepeatingRequest(previewRequestBuilder.build(), null, null)
|
||||
} catch (e) {
|
||||
console.log("Failed to start camera preview session.", e)
|
||||
}
|
||||
}
|
||||
override onConfigureFailed(session : CameraCaptureSession) {
|
||||
|
||||
}
|
||||
override onClosed(session : CameraCaptureSession) {
|
||||
|
||||
}
|
||||
override onSurfacePrepared(session : CameraCaptureSession, testSuface : Surface) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class imageReaderCallback extends ImageReader.OnImageAvailableListener {
|
||||
private currentImage = null as Image | null;
|
||||
override onImageAvailable(irader : ImageReader) {
|
||||
if (this.currentImage != null) {
|
||||
this.currentImage!.close(); // 先关闭上一帧的Image
|
||||
}
|
||||
let curimage = irader.acquireNextImage();
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class IntentRunable extends Runnable{
|
||||
override run(){
|
||||
if(!t.isScannig ) return;
|
||||
t.qrDecoder(curimage!)
|
||||
}
|
||||
}
|
||||
// curimage.close()
|
||||
// 跳转到一个新的界面
|
||||
// UTSAndroid.getUniActivity()!.runOnUiThread(new IntentRunable())
|
||||
|
||||
this.currentImage = curimage
|
||||
if (this.currentImage != null) {
|
||||
new Thread(new IntentRunable()).start()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
reader.setOnImageAvailableListener(new imageReaderCallback(), null)
|
||||
this.cameraDevice!.createCaptureSession(outsufaces, new sessionCallback(), null)
|
||||
|
||||
// previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO);
|
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
||||
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
|
||||
|
||||
|
||||
|
||||
}
|
||||
// 设置相机对焦模式
|
||||
// setCamraFoucsAuto(){
|
||||
// let cameraManager = this.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
// let characteristics = cameraManager.getCameraCharacteristics(this.camerManIds!)
|
||||
// // console.log(CaptureRequest.CONTROL_AF_MODE)
|
||||
// captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO);
|
||||
// }
|
||||
// 获取当前相机的旋转角度。
|
||||
getCamraRotation() : Int {
|
||||
let cameraId = this.camerManIds!;
|
||||
let isFrontFacing = false;
|
||||
let deviceRotation = this.windowManager.defaultDisplay.rotation
|
||||
let rotationCompensation = ORIENTATIONS.get(deviceRotation) as number
|
||||
|
||||
// Get the device's sensor orientation.
|
||||
let cameraManager = this.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
let sensorOrientation = cameraManager
|
||||
.getCameraCharacteristics(cameraId)
|
||||
.get(CameraCharacteristics.SENSOR_ORIENTATION)!! as number
|
||||
|
||||
if (isFrontFacing) {
|
||||
rotationCompensation = ((sensorOrientation + rotationCompensation) % 360).toInt()
|
||||
} else { // back-facing
|
||||
rotationCompensation = ((sensorOrientation - rotationCompensation + 360) % 360).toInt()
|
||||
}
|
||||
return rotationCompensation
|
||||
}
|
||||
|
||||
YUV42088ToNV21(image : Image) : ByteArray {
|
||||
// 获取图像的宽度、高度以及各平面的行间距
|
||||
let width = image.getWidth();
|
||||
let height = image.getHeight();
|
||||
// 获取各平面的数据与信息
|
||||
let planes = image.getPlanes();
|
||||
let yBuffer = planes[0].getBuffer().duplicate();
|
||||
let uBuffer = planes[1].getBuffer().duplicate();
|
||||
let vBuffer = planes[2].getBuffer().duplicate();
|
||||
|
||||
yBuffer.rewind();
|
||||
uBuffer.rewind();
|
||||
vBuffer.rewind();
|
||||
|
||||
|
||||
let yRowStride = planes[0].getRowStride();
|
||||
let uvRowStride = planes[1].getRowStride();
|
||||
let uvPixelStride = planes[1].getPixelStride(); // U 和 V 的像素间距通常是一样的
|
||||
|
||||
// 创建一个足够大的NV21输出数组
|
||||
let nv21Data = new ByteArray(width * height * 3 / 2);
|
||||
|
||||
// 指针用于遍历输出数组
|
||||
let yIndex = 0;
|
||||
let uvIndex = width * height;
|
||||
let bytesPerPixel = 1
|
||||
try {
|
||||
// 遍历Y平面
|
||||
for (let row = 0; row < height; row++) {
|
||||
for (let col = 0; col < width; col++) {
|
||||
nv21Data[(yIndex++).toInt()] = yBuffer.get();
|
||||
}
|
||||
// 跳过未使用的行数据(如果有)
|
||||
let rowRemainder = yRowStride - width * bytesPerPixel;
|
||||
|
||||
if (rowRemainder > 0) {
|
||||
yBuffer.position((yBuffer.position() + rowRemainder).toInt());
|
||||
} else {
|
||||
yBuffer.position((yBuffer.position() + width * bytesPerPixel).toInt()); // 防止由于计算错误导致的负数
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
}
|
||||
// // 处理UV平面
|
||||
try {
|
||||
for (let row = 0; row < height / 2; row++) {
|
||||
for (let col = 0; col < width / 2; col++) {
|
||||
// 先读取U分量
|
||||
nv21Data[uvIndex++] = uBuffer.get();
|
||||
// 如果UV像素不是交错存储,则跳过一个位置
|
||||
if (uvPixelStride == 2) {
|
||||
uBuffer.position(uBuffer.position() + 1);
|
||||
}
|
||||
|
||||
// 再读取V分量
|
||||
nv21Data[uvIndex++] = vBuffer.get();
|
||||
// 如果UV像素是交错存储,这里也需要相应调整位置
|
||||
if (uvPixelStride == 2) {
|
||||
vBuffer.position(vBuffer.position() + 1);
|
||||
}
|
||||
}
|
||||
// 跳过未使用的行数据(如果有)
|
||||
let uvRowSkip = uvRowStride - (width / 2) * uvPixelStride;
|
||||
if (uvRowSkip > 0) {
|
||||
uBuffer.position(uBuffer.position() + uvRowSkip);
|
||||
vBuffer.position(vBuffer.position() + uvRowSkip);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
}
|
||||
return nv21Data;
|
||||
}
|
||||
|
||||
YUV42088ToNV21Copy(a:ByteBuffer,b:ByteBuffer,c:ByteBuffer,widths:number,heights:number,yr:number,ur:number,vr:number) : ByteArray {
|
||||
// 获取图像的宽度、高度以及各平面的行间距
|
||||
let width = widths
|
||||
let height = heights;
|
||||
// 获取各平面的数据与信息
|
||||
|
||||
let yBuffer = a;
|
||||
let uBuffer = b;
|
||||
let vBuffer = c;
|
||||
|
||||
yBuffer.rewind();
|
||||
uBuffer.rewind();
|
||||
vBuffer.rewind();
|
||||
|
||||
let yRowStride = yr;
|
||||
let uvRowStride = ur;
|
||||
let uvPixelStride = vr; // U 和 V 的像素间距通常是一样的
|
||||
|
||||
// 创建一个足够大的NV21输出数组
|
||||
let nv21Data = new ByteArray((width * height * 3 / 2).toInt());
|
||||
|
||||
// 指针用于遍历输出数组
|
||||
let yIndex = 0;
|
||||
let uvIndex = width * height;
|
||||
let bytesPerPixel = 1
|
||||
try {
|
||||
// 遍历Y平面
|
||||
for (let row = 0; row < height; row++) {
|
||||
for (let col = 0; col < width; col++) {
|
||||
nv21Data[(yIndex++).toInt()] = yBuffer.get();
|
||||
}
|
||||
// 跳过未使用的行数据(如果有)
|
||||
let rowRemainder = yRowStride - width * bytesPerPixel;
|
||||
|
||||
if (rowRemainder > 0) {
|
||||
yBuffer.position((yBuffer.position() + rowRemainder).toInt());
|
||||
} else {
|
||||
yBuffer.position((yBuffer.position() + width * bytesPerPixel).toInt()); // 防止由于计算错误导致的负数
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
}
|
||||
// // 处理UV平面
|
||||
try {
|
||||
for (let row = 0; row < height / 2; row++) {
|
||||
for (let col = 0; col < width / 2; col++) {
|
||||
// 先读取U分量
|
||||
nv21Data[(uvIndex++).toInt()] = uBuffer.get();
|
||||
// 如果UV像素不是交错存储,则跳过一个位置
|
||||
if (uvPixelStride == 2) {
|
||||
uBuffer.position((uBuffer.position() + 1).toInt());
|
||||
}
|
||||
|
||||
// 再读取V分量
|
||||
nv21Data[(uvIndex++).toInt()] = vBuffer.get();
|
||||
// 如果UV像素是交错存储,这里也需要相应调整位置
|
||||
if (uvPixelStride == 2) {
|
||||
vBuffer.position((vBuffer.position() + 1).toInt());
|
||||
}
|
||||
}
|
||||
// 跳过未使用的行数据(如果有)
|
||||
let uvRowSkip = uvRowStride - (width / 2) * uvPixelStride;
|
||||
if (uvRowSkip > 0) {
|
||||
uBuffer.position((uBuffer.position() + uvRowSkip).toInt());
|
||||
vBuffer.position((vBuffer.position() + uvRowSkip).toInt());
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
}
|
||||
return nv21Data;
|
||||
}
|
||||
|
||||
copyImagePlan(plane:Plane):ByteBuffer{
|
||||
// 创建一个新的ByteBuffer来存储复制的数据
|
||||
let copiedBuffer = ByteBuffer.allocateDirect(plane.getBuffer().capacity());
|
||||
copiedBuffer.order(ByteOrder.nativeOrder()); // 确保字节顺序与原Buffer一致
|
||||
|
||||
// 将原始Plane的ByteBuffer内容复制到新的ByteBuffer中
|
||||
plane.getBuffer().rewind(); // 移动缓冲区指针到开始位置(可选,如果已经知道在开始处则无需此步)
|
||||
copiedBuffer.put(plane.getBuffer());
|
||||
|
||||
// 复制完成后,可以移动新ByteBuffer的指针回到起始位置以便读取
|
||||
copiedBuffer.rewind();
|
||||
|
||||
return copiedBuffer
|
||||
}
|
||||
qrDecoder(image : Image) {
|
||||
let t = this;
|
||||
if (!this.isDecodreIng) {
|
||||
this.isDecodreIng = true;
|
||||
let width = image.getWidth();
|
||||
let height = image.getHeight();
|
||||
|
||||
// 获取各平面的数据与信息
|
||||
let planes = image.getPlanes();
|
||||
|
||||
let a = this.copyImagePlan(planes[0]);
|
||||
let b = this.copyImagePlan(planes[1]);
|
||||
let c = this.copyImagePlan(planes[2]);
|
||||
let yr = planes[0].getRowStride()
|
||||
let ur = planes[1].getRowStride()
|
||||
let vr = planes[1].getPixelStride()
|
||||
|
||||
let starttime = new Date().getTime()
|
||||
|
||||
let av = this.YUV42088ToNV21Copy(a,b,c,width,height,yr,ur,vr)
|
||||
decoderBuffToQr(av, width, height, this.getCamraRotation())
|
||||
.then((res) => {
|
||||
if (res != null&&t.isScannig ) {
|
||||
if (res.text.length > 0) {
|
||||
t.vibatarLong()
|
||||
t.clearImageView();
|
||||
t.setImageViewRusletBox(t.previewWidth, t.previewWidth, width, height, res as SCANNING_PHOTO_RESULT)
|
||||
uni.setStorageSync('xMlkitScannigUResult_time_start', 'end:'+(new Date().getTime() - starttime).toString()+'ms')
|
||||
t.closeCamra()
|
||||
if (res.text.length == 1) {
|
||||
uni.setStorageSync('xMlkitScannigUResult', res.text[0])
|
||||
t.finish();
|
||||
t.overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// image.close();
|
||||
})
|
||||
.catch(() => {
|
||||
// this.imageUrl = null
|
||||
})
|
||||
setTimeout(function () {
|
||||
this.isDecodreIng = false
|
||||
}, this.scanSpeed);
|
||||
image.close();
|
||||
}
|
||||
|
||||
}
|
||||
closeCamra() {
|
||||
this.isScannig = false
|
||||
if (this.captureSession != null) {
|
||||
this.captureSession!.close();
|
||||
this.captureSession = null;
|
||||
this.cameraDevice?.close()
|
||||
this.cameraDevice = null;
|
||||
console.log("手动关闭相机");
|
||||
}
|
||||
}
|
||||
|
||||
openCamra() {
|
||||
let t = this;
|
||||
if (this.captureSession != null) {
|
||||
return;
|
||||
}
|
||||
if (t.previewWidth == 0) {
|
||||
//监听尺寸变化。
|
||||
class previewChangeRectadd extends View.OnLayoutChangeListener {
|
||||
override onLayoutChange(v : View, left : Int, top : Int, right : Int, bottom : Int,
|
||||
oldLeft : Int, oldTop : Int, oldRight : Int, oldBottom : Int
|
||||
) {
|
||||
t.previewWidth = right - left;
|
||||
t.previewHeight = bottom - top;
|
||||
t.prviewTop = top
|
||||
t.prviewLeft = left
|
||||
v.removeOnLayoutChangeListener(this);
|
||||
t.isCheckPermissions()
|
||||
.then(() => {
|
||||
let layoutParams = t.preview!.getLayoutParams() as ViewGroup.LayoutParams
|
||||
layoutParams.height = t.previewWidth
|
||||
t.preview!.layoutParams = layoutParams;
|
||||
|
||||
|
||||
|
||||
let layoutParamsbx = t.xiangjiBox!.getLayoutParams() as ViewGroup.LayoutParams
|
||||
layoutParamsbx.height = t.previewWidth
|
||||
t.xiangjiBox!.layoutParams = layoutParamsbx;
|
||||
|
||||
|
||||
t.isScannig = true
|
||||
t.clearImageView()
|
||||
t.getCameraById();
|
||||
t.openCamrae();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.preview!.addOnLayoutChangeListener(new previewChangeRectadd())
|
||||
} else {
|
||||
t.isScannig = true
|
||||
t.clearImageView()
|
||||
t.getCameraById();
|
||||
t.openCamrae();
|
||||
}
|
||||
}
|
||||
|
||||
// 设置图片上的小圆的识别位置。
|
||||
setImageViewRusletBox(width : number, height : number, realWidth : number, realHeight : number, jieguo : SCANNING_PHOTO_RESULT) {
|
||||
let t = this;
|
||||
let wRatio = width / realWidth;
|
||||
let hRatio = height / realHeight;
|
||||
this.imageYuranBox!.setBackgroundColor(Color.argb(0,0,0,0))
|
||||
this.xiangjiBox!.setBackgroundColor(Color.argb(0,0,0,0))
|
||||
for (let i = 0; i < jieguo.bounds.length; i++) {
|
||||
let item = jieguo.bounds[i];
|
||||
let layoutParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
|
||||
if (this.isScannig) {
|
||||
layoutParams.topMargin = (hRatio * (item.centerY-60)).toInt() // 设置顶部边距
|
||||
layoutParams.leftMargin = (wRatio * (item.centerX-60)).toInt() // 设置左边距
|
||||
} else {
|
||||
layoutParams.topMargin = (hRatio * item.centerY-60).toInt() // 设置顶部边距
|
||||
layoutParams.leftMargin = (wRatio * item.centerX-60).toInt() // 设置左边距
|
||||
}
|
||||
let relativeLayout = new RelativeLayout(t.context)
|
||||
relativeLayout.layoutParams = layoutParams
|
||||
|
||||
const rtxt = jieguo.text[i]
|
||||
|
||||
let v = new xView(t.context)
|
||||
v.setSize(120, 120)
|
||||
.setRadius(100)
|
||||
.setBackgroundColor('#21d429')
|
||||
.setBorder(12, "#ffffff", false)
|
||||
.setClick((event : MotionEvent) => {
|
||||
try {
|
||||
uni.setStorageSync('xMlkitScannigUResult', rtxt)
|
||||
t.finish()
|
||||
t.overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
}
|
||||
})
|
||||
let ov = v.getView();
|
||||
|
||||
|
||||
// 创建缩放动画
|
||||
let scaleAnimation = new ScaleAnimation(1.0.toFloat(), 0.7.toFloat(), 1.0.toFloat(), 0.7.toFloat(), Animation.RELATIVE_TO_SELF, 0.7.toFloat(), Animation.RELATIVE_TO_SELF, 0.7.toFloat());
|
||||
scaleAnimation.setDuration(600); // 设置动画持续时间
|
||||
scaleAnimation.setRepeatMode(Animation.REVERSE); // 设置重复模式为反向
|
||||
scaleAnimation.setRepeatCount(Animation.INFINITE); // 设置重复次数为无限
|
||||
// 启动动画
|
||||
ov.startAnimation(scaleAnimation);
|
||||
|
||||
|
||||
relativeLayout.addView(v.getView())
|
||||
if (this.isScannig) {
|
||||
this.xiangjiBox!.addView(relativeLayout)
|
||||
this.imgPointsBycanmarea.push(relativeLayout)
|
||||
this.xiangjiBox!.setBackgroundColor(Color.argb(100,0,0,0))
|
||||
} else {
|
||||
this.imageYuranBox!.addView(relativeLayout)
|
||||
this.imgPoints.push(relativeLayout)
|
||||
this.imageYuranBox!.setBackgroundColor(Color.argb(100,0,0,0))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
clearImageView() {
|
||||
for (let i = 0; i < this.imgPointsBycanmarea.length; i++) {
|
||||
this.xiangjiBox!.removeView(this.imgPointsBycanmarea[i])
|
||||
}
|
||||
for (let i = 0; i < this.imgPoints.length; i++) {
|
||||
this.imageYuranBox!.removeView(this.imgPoints[i])
|
||||
}
|
||||
if (!this.isScannig) {
|
||||
this.imageView!.setImageDrawable(null)
|
||||
}
|
||||
|
||||
}
|
||||
override onActivityResult(requestCode : Int, resultCode : Int, data ?: Intent) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
let t = this;
|
||||
if (resultCode == Activity.RESULT_OK && data != null && data?.data != null) {
|
||||
let selectedImageUri = data.data!
|
||||
this.imageUrl = selectedImageUri
|
||||
this.isSelectedImging = false
|
||||
decoderUriPathToQrByUri(this.imageUrl!,(res:SCANNING_PHOTO_RESULT)=>{
|
||||
t.isScannig = false
|
||||
t.clearImageView();
|
||||
t.imageView!.setImageURI(t.imageUrl!);
|
||||
let reaLwidth = t.imageView!.drawable?.intrinsicWidth;
|
||||
let reaLheight = t.imageView!.drawable?.intrinsicHeight;
|
||||
|
||||
if (reaLwidth != null && reaLheight != null) {
|
||||
t.vibatarLong()
|
||||
t.setImageViewRusletBox(t.imageView!.width, t.imageView!.height, reaLwidth!, reaLheight!, res!)
|
||||
if (res.text.length == 1) {
|
||||
uni.setStorageSync('xMlkitScannigUResult', res.text[0])
|
||||
t.finish()
|
||||
t.overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
} else {
|
||||
this.isSelectedImging = false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override onPause() : void {
|
||||
super.onPause();
|
||||
console.log("暂停扫码")
|
||||
|
||||
this.isScannig = false
|
||||
if (this.captureSession != null) {
|
||||
this.captureSession!.close();
|
||||
this.captureSession = null;
|
||||
this.cameraDevice?.close()
|
||||
this.cameraDevice = null;
|
||||
console.log("关闭相机");
|
||||
}
|
||||
|
||||
// 停止图像处理线程
|
||||
if (this.imageHandler != null) {
|
||||
this.imageHandler!.getLooper()!.quitSafely();
|
||||
}
|
||||
}
|
||||
|
||||
override onResume() : void {
|
||||
super.onResume();
|
||||
|
||||
let t = this;
|
||||
if (this.captureSession != null || t.imageUrl != null || t.isSelectedImging) {
|
||||
return;
|
||||
}
|
||||
console.log("恢复扫码")
|
||||
|
||||
|
||||
this.openCamra();
|
||||
|
||||
|
||||
}
|
||||
|
||||
openCamrae() {
|
||||
if (this.camerManIds == null) return;
|
||||
// let t = this;
|
||||
class CamerCallTstasBack implements StateCallback {
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
override onOpened(camera : CameraDevice) : void {
|
||||
console.log('打开成功')
|
||||
cameraDevice = camera
|
||||
createCameraPreviewSession()
|
||||
|
||||
}
|
||||
override onDisconnected(camera : CameraDevice) : void {
|
||||
console.log('连接失败')
|
||||
cameraDevice?.close()
|
||||
cameraDevice = null
|
||||
}
|
||||
override onError(camera : CameraDevice, error : Int) : void {
|
||||
console.log('出错')
|
||||
cameraDevice?.close()
|
||||
cameraDevice = null
|
||||
}
|
||||
override onClosed(camera : CameraDevice) : void {
|
||||
console.log('关闭')
|
||||
cameraDevice?.close()
|
||||
cameraDevice = null
|
||||
}
|
||||
|
||||
}
|
||||
this.camera_service.openCamera(this!.camerManIds as string, new CamerCallTstasBack(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class IntentRunable extends Runnable {
|
||||
override run() {
|
||||
let intent = new Intent(UTSAndroid.getUniActivity(), DemoActivity().javaClass);
|
||||
UTSAndroid.getUniActivity()!.startActivityForResult(intent,1);
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
export function gotoDemoActivity() : boolean {
|
||||
// 跳转到一个新的界面
|
||||
UTSAndroid.getUniActivity()!.runOnUiThread(new IntentRunable())
|
||||
return true;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user