979 lines
31 KiB
Plaintext
979 lines
31 KiB
Plaintext
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;
|
|
|
|
} |