This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

@@ -0,0 +1,41 @@
## 1.1.22025-08-17
* 针对微信的浏览器内核设定相关属性,以防止全屏脱离文档。
## 1.1.12025-02-13
* web,和微信相册识别已经改版本,能识别大多数码,效率有所提升。因为采用像素解析识别码,因此web,微信它是可以通用的,并且不依赖微信官方本身,所以
* 你现在在微信端也能独立的识别远程或者静态图片码,而不需要调用uni.scan官方自带的接口。同样微信和web图片识别都是只能单码识别
## 1.0.92024-11-18
* 界面上添加了闪光灯控制,用于打开和关闭手电筒
## 1.0.82024-09-12
* 优化和修改了核心识别,调整了安卓识别页面兼容华为部分机型.
## 1.0.72024-08-31
* openCameraApi接口调用增加了第二个onlyCamre是否仅使用相机识别,不通过相册识别.
## 1.0.62024-08-11
* 升级安卓扫码为全屏扫码,码识别速度提升,并支持5.0安卓以上.
* 升级ios支持全屏识别,改善识别速率.
* ios,安卓,web增加显示动画缩放动画小到大.
* 安卓,ios填写相册识别并让小绿点精准率提到为100%,实时的小绿点位置在多点时可能会有偏差.
## 1.0.52024-07-30
升级支持ios扫码功能,ios最低为ios12+
升级支持H5(手机端)/WEB(带摄像头的win电脑及mac电脑)
## 1.0.42024-01-27
1. 预览区域调整缩小一点,
2. 捕捉的图像再次放小一点。
3. 扫描速率调整到500ms
如果你是有特定机器上有问题,请自己通过修改uts源码调整上面的这些参数,将影响识别速度,精准度,捕捉位置优化等可以自己通过源码调整。
## 1.0.32024-01-26
修改焦距为持续视频对焦模式,并拉近焦距(相当于视野变小了,之前是宽视野可以同时识别出10几个码,但现实中没必要这样,因此根据其它的应用参考,应该拉近点焦距)。
对焦点识别的位置进行了优化比对,识别时位置更准,但不能保证非常准确。比如手机识别慢100ms左右,你手抖了下,可能位置是前100ms的位置
会造成识别的位置和预览图可能有点区别。但经过持续的测试,红米note11 pro ,2年前的手机,基本很少出现对焦识别失准的情况。
我把识别速度调整为1秒识别一次,对低端机可能更好。如果想识别非常的迅速可以调低这个值,比如100ms,基本一对准就识别了,精准度非常高。
识别的震动改为了80ms,之前是200ms,感觉过长。
识别成功后增加了黑色遮罩。
另外识别程序放在了子线程中执行了,防止偶尔的卡钝。
**特别提醒,网友测试如果你是开发模式部分手机可能会比较卡,原因不明,但打包后的程序是非常流畅无比的**
## 1.0.22024-01-25
1. 增加了识别成功后,短震动提示
2. 识别多点后,给提示点增加了动画缩放指示提醒用户点按识别点。
## 1.0.12024-01-24
增加返回按钮事件
## 1.0.02024-01-24
首次发布
@@ -0,0 +1,86 @@
{
"id": "x-mlkit-scannig-s",
"displayName": "tmui4.0 UTS原生二维码扫一扫 机器学习",
"version": "1.0.7",
"description": "机器学习识别码库,它是tmui4.0附属插件,可实时识别多码,速度快,效率不错,精准度也不错",
"keywords": [
"扫码",
"机器学习",
"二维码",
"扫一扫"
],
"repository": "",
"engines": {
"HBuilderX": "^3.99"
},
"dcloudext": {
"type": "component-uts",
"sale": {
"regular": {
"price": "99.00"
},
"sourcecode": {
"price": "199.00"
}
},
"contact": {
"qq": "369986563"
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "n",
"vue3": "y"
},
"App": {
"app-android": {
"minVersion": "21"
},
"app-ios": "n"
},
"H5-mobile": {
"Safari": "n",
"Android Browser": "n",
"微信浏览器(Android)": "n",
"QQ浏览器(Android)": "n"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "n",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "n",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n"
},
"快应用": {
"华为": "n",
"联盟": "n"
}
}
}
}
}
+45
View File
@@ -0,0 +1,45 @@
# x-mlkit-scannig-u
### 开发文档
[TMUI4.0文档](https://xui.tmui.design/)
[TMUI4.0组件库](https://ext.dcloud.net.cn/plugin?id=16369)
### 功能特色
1. ios,安卓是Ai模型识别速度非常快,大概ios:50-100ms,安卓:200-350ms
2. web/h5带摄像头,实时识别时,效率比较低,识别精度不高,要多对几次
3. web/微信图片识别时,精度中上。(不依赖微信官方,也不依赖Uniapp官方纯像素插件识别),不要图片了好多码,识别不准,如果码是黑色,多码能识别一个
如果多码是非黑色,可能识别不出来
4. 嵌入式页面识别,方便自己布局和装饰扫码界面。
5. 微信,web只能是单码识别,多码一起识别不精准
**嵌入式相机识别在微信端:微信官方的本身这个组件就有很多问题,如果遇到没有扫码结果,请自行向微信官方提问,不需要问我,我的xs max就没有任何响应,但安卓有**
**最主要的更新是兼容了微信,并支持微信自定图片识别,这样可以不用打开微信自带的界面再选择图片识别,给了大家商用非常好的便利**
**web,微信图片识别时,请注意图片的干净,尽量不要多码放一起,一图一码,识别精准度还是好的**
### 兼容性
| IOS | Andriod | WEB | 小程序 |
| --- | --- | --- | --- |
| 支持 | 支持 | 支持 | 支持 |
### 说明
这是tmui4.0|XUI的原生插件附赠插件。种子用户可免于购买。
非种子用户:
普通授权:99元
源码授权:199元
### 使用
需要自定义基座运行,且安卓5.0+(含5.0)以上支持
```ts
import {openCameraApi,closeCamera} from "@/uni_modules/x-mlkit-scannig-s"
const apiOpen = ()=>{
openCameraApi((str:string)=>{
console.log(str)
},false)
}
```
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
package="io.dcloud.uni_modules.xMlkitScannigS">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<application>
<meta-data android:name="UTS_MLKIT_SCANNIG_xMlkitScannigS" android:value="xMlkitScannigS" />
<activity android:name="uts.sdk.modules.xMlkitScannigS.DemoActivity"></activity>
</application>
</manifest>
@@ -0,0 +1,951 @@
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 xView from "./libview/view.uts"
import Gravity from 'android.view.Gravity';
// x
import ProcessCameraProvider from "androidx.camera.lifecycle.ProcessCameraProvider"
import PreviewView from "androidx.camera.view.PreviewView"
import CameraController from "androidx.camera.view.CameraController"
import CameraSelector from "androidx.camera.core.CameraSelector"
import Preview from 'androidx.camera.core.Preview'
import Camera from 'androidx.camera.core.Camera'
import LifecycleObserver from "androidx.lifecycle.LifecycleObserver"
import LifecycleOwner from "androidx.lifecycle.LifecycleOwner"
import OnLifecycleEvent from "androidx.lifecycle.OnLifecycleEvent"
import ImageAnalysis from 'androidx.camera.core.ImageAnalysis'
import ImageProxy from 'androidx.camera.core.ImageProxy'
import NonNull from 'androidx.annotation.NonNull'
import ExecutorService from 'java.util.concurrent.ExecutorService'
import Executors from 'java.util.concurrent.Executors'
import ContextCompat from 'androidx.core.content.ContextCompat';
import LifecycleRegistry from "androidx.lifecycle.LifecycleRegistry"
import Lifecycle from "androidx.lifecycle.Lifecycle"
import FrameLayout from 'android.widget.FrameLayout';
import ViewPropertyAnimator from 'android.view.ViewPropertyAnimator';
import { SCANNING_OPTIONS, CAMERA_PHOTO_SIZE, CALL_RESULT_FUN, Call_BACK, Call_BACK_TYPE, SCANNING_PHOTO_RESULT, SCANNING_PHOTO_RESULT_BOUND } from "../interface.uts"
type buffoptsPlanesType = {
data : ByteBuffer,
getRowStride : number,
getPixelStride : number,
}
type buffoptsType = {
width : number,
height : number,
format : number,
rect : Rect,
planes : buffoptsPlanesType[],
}
type ConverXy = {
x : number,
y : number,
minx : number,
maxx : number,
miny : number,
maxy : number,
}
type contaiaSize = {
width : number,
height : number
}
type contaiaCalseSize = {
scale : number,
offsetX : number,
offsetY : number
}
type contaiPointXy = {
x : number,
y : number
}
let parentView : RelativeLayout | null = null;
let maskDomId = 8586
function px2dp(n : number) : number {
const mets = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics()
return mets.density * n
}
export class xCamera {
/** 手动震动服务 */
vibrator = null as null | Vibrator
context : Context;
// 识别点的区域
resultView : RelativeLayout | null = null;
// 存放小绿点的数组
imgPointsBycanmarea = [] as RelativeLayout[]
//相机容器
camreaLayouView:RelativeLayout|null = null;
// 图片容器层.
imageLayouView:RelativeLayout|null = null;
viewBox_width = 0
viewBox_height = 0
imageView = null as null | ImageView
imageUrl = null as null | Uri
isSelectedImging = false;
// 相机区域视图
cameraView : PreviewView | null = null;
cameraProvider : ProcessCameraProvider | null = null
camera : Camera | null = null;
cameraPreview : Preview | null = null;
// 当前是否在解析码制中。
isDecoderQring = false
// 当前相机是否正在预览中。
isOpeningCameraing = false;
callFunEvent : CALL_RESULT_FUN = (str : string) => { }
onlyCamera:boolean = false;
// 临时存放的识别成功的结果,防止重复扫码。
tempresultList = [] as string[];
activityresult = (requestCode : Int, resultCode : Int, data ?: Intent)=>{
let t = this;
if (resultCode == Activity.RESULT_OK && data != null && data?.data != null) {
let selectedImageUri = data.data!
this.camreaLayouView!.setVisibility(View.GONE)
this.imageUrl = selectedImageUri
this.isSelectedImging = false
decoderUriPathToQrByUri(this.imageUrl!,(res:SCANNING_PHOTO_RESULT)=>{
t.isOpeningCameraing = false
t.clearImageView();
t.imageView!.setImageURI(t.imageUrl!);
let matrix = t.getCorrectionMatrixByimgUri(t.imageView!, t.cameraView!,90)
let ruslt = res as SCANNING_PHOTO_RESULT;
if(ruslt.text.length>0){
t.setVibatar()
}
for (let i = 0; i < ruslt.bounds.length; i++) {
let item = ruslt.bounds[i]
let result = t.calcPoint(t.imageView!, t.cameraView!,item.centerX,item.centerY,0)
// let ponit = floatArrayOf(item.centerX.toFloat(), item.centerY.toFloat())
// matrix.mapPoints(ponit)
// item.centerX = ponit[0]
// item.centerY = ponit[1]
item.centerX = result.x
item.centerY = result.y
}
t.setImageViewRusletBox(ruslt)
if (ruslt.text.length == 1) {
t.callFunEvent(ruslt.text[0])
t.close()
}
})
} else {
this.isSelectedImging = false
}
}
constructor() {
this.context = UTSAndroid.getAppContext()! as Context
this.vibrator = this.context!.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator;
}
clearImageView() {
this.clearImagePonit();
if (!this.isOpeningCameraing) {
this.imageView!.setImageDrawable(null)
}
}
setVibatar() {
if (this.vibrator == null) return
// 检查设备是否支持震动
if (this.vibrator!.hasVibrator()) {
// 触发短暂的震动,持续时间为200毫秒
this.vibrator!.vibrate(80);
}
}
openCamera(call:CALL_RESULT_FUN,onlyCameras:boolean) {
this.onlyCamera = onlyCameras;
let t = this;
this.callFunEvent = call
if (parentView == null) {
this.createView();
t._openCamera()
} else {
this._openCamera()
}
}
close() {
UTSAndroid.offAppActivityResult(this.activityresult)
let decorView = UTSAndroid.getUniActivity()!.window.decorView as ViewGroup
this.closeCamera();
this.clearImagePonit();
if (parentView != null) {
decorView.removeView(parentView)
}
parentView = null;
}
private _openCamera() {
let _this = this;
this.closeCamera();
this.clearImagePonit();
let cameraProviderFuture = ProcessCameraProvider.getInstance(this.context)
this.camreaLayouView!.setVisibility(View.VISIBLE)
class IntentRunable extends Runnable {
override run() {
let cameraProvider : ProcessCameraProvider = cameraProviderFuture.get()
let previewView = _this.cameraView! as PreviewView
let cameraSelector : CameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
let preview : Preview = new Preview.Builder().build();
preview.setSurfaceProvider(previewView.getSurfaceProvider())
let imageAnalysis = new ImageAnalysis.Builder()
.setTargetResolution(new Size(800, 800))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
class MyImageAnalyzer implements ImageAnalysis.Analyzer {
constructor() {
super()
}
override analyze(image : ImageProxy) {
_this.qrDecoder(image)
}
override getDefaultTargetResolution() : Size {
return new Size(_this.viewBox_width.toInt(), _this.viewBox_height.toInt());
}
}
let cameraExecutor = Executors.newSingleThreadExecutor();
imageAnalysis.setAnalyzer(cameraExecutor, new MyImageAnalyzer());
let camera : Camera = cameraProvider.bindToLifecycle(
UTSAndroid.getUniActivity()! as LifecycleOwner, cameraSelector, preview, imageAnalysis);
_this.camera = camera
_this.cameraPreview = preview
_this.cameraProvider = cameraProvider
_this.isDecoderQring = false
_this.isOpeningCameraing = true
}
}
cameraProviderFuture.addListener(new IntentRunable(), ContextCompat.getMainExecutor(this.context))
}
closeCamera() {
this.cameraProvider?.unbindAll()
this.cameraPreview?.setSurfaceProvider(null)
this.isOpeningCameraing = false;
}
clearImagePonit(){
for (let i = 0; i < this.imgPointsBycanmarea.length; i++) {
this.resultView?.removeView(this.imgPointsBycanmarea[i])
}
}
private createView() {
let t = this;
let decorView = UTSAndroid.getUniActivity()!.window.decorView as ViewGroup
// 创建父容器。
parentView = new RelativeLayout(this.context);
parentView!.setId((maskDomId).toInt())
// parentView!.setOrientation(LinearLayout.VERTICAL)
parentView!.setBackgroundColor(Color.BLACK)
parentView!.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
))
class MaskerDomClickListsner extends View.OnTouchListener {
constructor() {
super();
}
//阻止事件向下穿透。
override onTouch(view : View, event : MotionEvent) : Boolean {
return true;
}
}
parentView!.setOnTouchListener(new MaskerDomClickListsner())
let continaer = new LinearLayout(this.context);
continaer.setOrientation(LinearLayout.VERTICAL)
continaer.setBackgroundColor(Color.TRANSPARENT)
let continaerLayparams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
continaer.setLayoutParams(continaerLayparams)
// 创建一个中间容器
let boxWrapParent = new LinearLayout(this.context);
boxWrapParent.setOrientation(LinearLayout.VERTICAL)
let boxLayparams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
boxLayparams.weight = (1).toFloat();
boxLayparams.height = 0;
boxWrapParent.setLayoutParams(boxLayparams)
// 创建一个定位容器
let boxWrap = new RelativeLayout(this.context);
boxWrap.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
))
boxWrapParent.addView(boxWrap)
//监听尺寸变化。
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.viewBox_width = right - left;
t.viewBox_height = bottom - top;
}
}
boxWrap.addOnLayoutChangeListener(new previewChangeRectadd())
let rongqitop = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
rongqitop.topMargin = 0
rongqitop.leftMargin = 0
// 创建容器内小绿点容器。
this.resultView = new RelativeLayout(this.context);
this.resultView!.setLayoutParams(rongqitop)
// 图片容器
let imgPview = new RelativeLayout(this.context);
let rongqitop2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
rongqitop2.topMargin = 0
rongqitop2.leftMargin = 0
imgPview.setLayoutParams(rongqitop2)
// 创建一张图片
this.imageView = new ImageView(this.context);
this.imageView!.setScaleType(ImageView.ScaleType.CENTER_INSIDE)
this.imageView!.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
imgPview.addView(this.imageView!)
this.imageView!.setImageDrawable(null)
// 相机容器。
let cview = new RelativeLayout(this.context);
let rongqitop3 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
// rongqitop3.width = 1000
// rongqitop3.height = 1000
cview.setLayoutParams(rongqitop3)
// 相机预览层。
let sv = new PreviewView(this.context)
// https://developer.android.google.cn/reference/androidx/camera/view/PreviewView.ScaleType?hl=en
sv.setScaleType(PreviewView.ScaleType.FILL_CENTER)
// 让相机的高和宽与组件对齐。
let layaout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
sv.setLayoutParams(layaout)
this.cameraView = sv;
cview.addView(this.cameraView!)
// this.imageView!.setVisibility(View.INVISIBLE)
//相机容器
this.camreaLayouView = cview;
// 图片容器层.
this.imageLayouView= imgPview;
boxWrap.addView(imgPview)
boxWrap.addView(cview)
boxWrap.addView(this.resultView!)
// 创建底
let footerWrap = new LinearLayout(this.context);
footerWrap.setOrientation(LinearLayout.HORIZONTAL)
let footerWrapLayparams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
footerWrapLayparams.height = px2dp(100).toInt();
footerWrap.setLayoutParams(footerWrapLayparams)
// 创建选择相册
let photoView = new LinearLayout(this.context);
photoView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
))
let xiangcheText = new TextView(this.context);
xiangcheText.setTextColor(Color.WHITE)
xiangcheText.setPadding(32, 52, 0, 0)
xiangcheText.setTextSize((16).toFloat())
xiangcheText.setGravity(Gravity.LEFT)
xiangcheText.setText(!this.onlyCamera?"相册选择":" ")
xiangcheText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
class XiangcheClickListsner extends View.OnTouchListener {
constructor() {
super();
}
override onTouch(view : View, event : MotionEvent) : Boolean {
UTSAndroid.offAppActivityResult(t.activityresult)
if(event.getAction()==MotionEvent.ACTION_UP){
t.closeCamera()
t.isSelectedImging = true;
let intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
UTSAndroid.onAppActivityResult(t.activityresult);
UTSAndroid.getUniActivity()!.startActivityForResult(intent, 1)
}
return true;
}
}
if(!this.onlyCamera){
xiangcheText.setOnTouchListener(new XiangcheClickListsner())
}
photoView.addView(xiangcheText)
// 中间占位
let cneterView = new LinearLayout(this.context);
let cneterViewParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
cneterViewParams.weight = (1).toFloat();
cneterView.setLayoutParams(cneterViewParams)
// 扫一扫view
let scanView = new LinearLayout(this.context);
scanView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
))
scanView.setGravity(Gravity.RIGHT)
let shaoyishaoText = new TextView(this.context);
shaoyishaoText.setTextColor(Color.WHITE)
shaoyishaoText.setPadding(0, 52, 32, 0)
shaoyishaoText.setTextSize((16).toFloat())
shaoyishaoText.setGravity(Gravity.RIGHT)
shaoyishaoText.setText("扫一扫")
shaoyishaoText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
class ShaoyishaoTextClickListsner extends View.OnTouchListener {
constructor() {
super();
}
override onTouch(view : View, event : MotionEvent) : Boolean {
if(event.getAction()==MotionEvent.ACTION_UP){
t._openCamera()
}
return true;
}
}
shaoyishaoText.setOnTouchListener(new ShaoyishaoTextClickListsner())
scanView.addView(shaoyishaoText)
footerWrap.addView(photoView)
footerWrap.addView(cneterView)
footerWrap.addView(scanView)
// 创建一个顶部
let titleView = new RelativeLayout(this.context);
// titleView!.setOrientation(LinearLayout.HORIZONTAL)
// titleView!.setBackgroundColor(Color.RED)
let tplays = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
tplays.topMargin = px2dp(60).toInt()
tplays.leftMargin = px2dp(16).toInt()
titleView!.setLayoutParams(tplays)
// 创建一个返回按钮
let backBtn = new TextView(this.context);
backBtn.setTextColor(Color.WHITE)
backBtn.setTextSize((16).toFloat())
backBtn.setGravity(Gravity.BOTTOM)
backBtn.setText("返回")
backBtn.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
class BackDomClickListsner extends View.OnTouchListener {
constructor() {
super();
}
override onTouch(view : View, event : MotionEvent) : Boolean {
t.close();
return true;
}
}
backBtn.setOnTouchListener(new BackDomClickListsner())
titleView!.addView(backBtn)
continaer.addView(boxWrapParent)
continaer.addView(footerWrap)
parentView!.addView(continaer)
parentView!.addView(titleView)
decorView.addView(parentView!)
// 增加动画
try {
let s0 = (0).toFloat()
let s1 = (1).toFloat()
parentView!.setAlpha(s0)
parentView!.setScaleX(s0)
parentView!.setScaleY(s0)
let ani = parentView!.animate() as ViewPropertyAnimator;
ani.alpha(s1)
.scaleX(s1)
.scaleY(s1)
.setDuration(250)
.start()
} catch (e) {
//TODO handle the exception
}
}
// 设置图片上的小圆的识别位置。
setImageViewRusletBox(jieguo : SCANNING_PHOTO_RESULT) {
let t = this;
let btnwidth = 100;
let btnwidth2 = 50;
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
)
let x = item.centerX
let y = item.centerY
layoutParams.topMargin = (y - btnwidth2).toInt() // 设置顶部边距
layoutParams.leftMargin = (x - btnwidth2).toInt() // 设置左边距
let relativeLayout = new RelativeLayout(t.context)
relativeLayout.layoutParams = layoutParams
const rtxt = jieguo.text[i]
let v = new xView(t.context)
v.setSize(btnwidth, btnwidth)
.setRadius(100)
.setBackgroundColor('#21d429')
.setBorder(12, "#ffffff", false)
.setClick((event : MotionEvent) => {
try {
// t.callFunEvent([rtxt], 'click')
t.callFunEvent(rtxt)
t.close()
} 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(ov)
this.imgPointsBycanmarea.push(relativeLayout)
this.resultView!.addView(relativeLayout)
}
}
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
}
getCorrectionMatrixByimgUri(imgView : ImageView, previewView : PreviewView,rotationDegrees:number) : Matrix {
let matrix = new Matrix()
let imgWidt = imgView.drawable!.intrinsicWidth as number
let imgHeight = imgView.drawable!.intrinsicHeight as number
let d0 = (0).toFloat()
let source = floatArrayOf(
d0,
d0,
imgWidt.toFloat(),
d0,
imgWidt.toFloat(),
imgHeight.toFloat(),
d0,
imgHeight.toFloat()
)
// A float array of the destination vertices in clockwise order.
let destination = floatArrayOf(
d0,
d0,
previewView.width.toFloat(),
d0,
previewView.width.toFloat(),
previewView.height.toFloat(),
d0,
previewView.height.toFloat()
)
let vertexSize = 2
let shiftOffset = rotationDegrees / 90 * vertexSize;
let tempArray = destination.clone()
for (toIndex in source.indices) {
let fromIndex = (toIndex + shiftOffset) % source.size
destination[toIndex] = tempArray[fromIndex.toInt()]
}
let success = matrix.setPolyToPoly(source, 0, destination, 0, 4)
return matrix
}
getCorrectionMatrix(imageProxy : ImageProxy, previewView : PreviewView) : Matrix {
let cropRect = imageProxy.cropRect
let rotationDegrees = imageProxy.imageInfo.rotationDegrees
let matrix = new Matrix()
// A float array of the source vertices (crop rect) in clockwise order.
let source = floatArrayOf(
cropRect.left.toFloat(),
cropRect.top.toFloat(),
cropRect.right.toFloat(),
cropRect.top.toFloat(),
cropRect.right.toFloat(),
cropRect.bottom.toFloat(),
cropRect.left.toFloat(),
cropRect.bottom.toFloat()
)
let d0 = (0).toFloat()
// A float array of the destination vertices in clockwise order.
let destination = floatArrayOf(
d0,
d0,
previewView.width.toFloat(),
d0,
previewView.width.toFloat(),
previewView.height.toFloat(),
d0,
previewView.height.toFloat()
)
let vertexSize = 2
let shiftOffset = rotationDegrees / 90 * vertexSize;
let tempArray = destination.clone()
for (toIndex in source.indices) {
let fromIndex = (toIndex + shiftOffset) % source.size
destination[toIndex] = tempArray[fromIndex.toInt()]
}
let success = matrix.setPolyToPoly(source, 0, destination, 0, 4)
return matrix
}
calcPoint(imgView : ImageView, previewView : PreviewView, image_point_x : number, image_point_y : number,degration:number) : ConverXy {
const image_w = imgView.drawable!.intrinsicWidth as number
const image_h = imgView.drawable!.intrinsicHeight as number
const c_w = previewView.width;
const c_h = previewView.height;
function scaleAndCenterContent(
container : contaiaSize,
content : contaiaSize
) : contaiaCalseSize {
// 计算缩放比例
const scaleWidth = container.width / content.width;
const scaleHeight = container.height / content.height;
const scale = Math.min(scaleWidth, scaleHeight);
// 计算居中偏移量
const offsetX = (container.width - content.width * scale) / 2;
const offsetY = (container.height - content.height * scale) / 2;
return { scale, offsetX, offsetY } as contaiaCalseSize;
}
function mapContentCoordinatesToContainer(
contentX : number,
contentY : number,
calcsize : contaiaCalseSize
) : contaiPointXy {
// 映射内容层坐标到容器层坐标
const containerX = contentX * calcsize.scale + calcsize.offsetX;
const containerY = contentY * calcsize.scale + calcsize.offsetY;
return { x: containerX, y: containerY } as contaiPointXy;
}
let rc = scaleAndCenterContent(
{ width: c_w, height: c_h } as contaiaSize,
{ width: image_h, height: image_w } as contaiaSize
)
if(degration==0){
rc = scaleAndCenterContent(
{ width: c_w, height: c_h } as contaiaSize,
{ width: image_w, height: image_h } as contaiaSize
)
}
let result = mapContentCoordinatesToContainer(image_point_y, image_point_x, rc)
if(degration==0){
result = mapContentCoordinatesToContainer(image_point_x,image_point_y,rc)
}
let nexyxcov = {
x: c_w - result.x, y: result.y, minx: 0, maxx: 0, miny: 0, maxy: 0
} as ConverXy
if(degration==0){
nexyxcov = {
x: result.x, y: result.y, minx: 0, maxx: 0, miny: 0, maxy: 0
} as ConverXy
}
return nexyxcov
}
qrDecoder(img : ImageProxy) {
let t = this;
let image = img.getImage();
if (!this.isDecoderQring&&image!=null&&t.isOpeningCameraing) {
this.isDecoderQring = 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)
let rotationDegrees = img.imageInfo.rotationDegrees
decoderBuffToQr(av, width, height, 0)
.then((res) => {
if (res != null ) {
let matrix = t.getCorrectionMatrix(img, t.cameraView!)
if (res.text.length > 0) {
let ruslt = res as SCANNING_PHOTO_RESULT;
for (let i = 0; i < ruslt.bounds.length; i++) {
let item = ruslt.bounds[i]
let ponit = floatArrayOf(item.centerX.toFloat(), item.centerY.toFloat())
// let xy = t.covertXy(img, t.cameraView!,item.centerX,item.centerY);
matrix.mapPoints(ponit)
item.centerX = ponit[0]
item.centerY = ponit[1]
// item.centerX = xy[0]
// item.centerY = xy[1]
// console.log(item.centerX ,item.centerY)
}
if(ruslt.text.length>0){
t.setVibatar()
}
t.setImageViewRusletBox(ruslt)
t.closeCamera()
if (ruslt.text.length == 1) {
t.callFunEvent(ruslt.text[0])
t.close()
}
}else{
t.isDecoderQring = false
}
}
// image.close();
})
.catch(() => {
t.isDecoderQring = false
})
}
img.close();
}
}
@@ -0,0 +1,951 @@
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 xView from "./libview/view.uts"
import Gravity from 'android.view.Gravity';
// x
import ProcessCameraProvider from "androidx.camera.lifecycle.ProcessCameraProvider"
import PreviewView from "androidx.camera.view.PreviewView"
import CameraController from "androidx.camera.view.CameraController"
import CameraSelector from "androidx.camera.core.CameraSelector"
import Preview from 'androidx.camera.core.Preview'
import Camera from 'androidx.camera.core.Camera'
import LifecycleObserver from "androidx.lifecycle.LifecycleObserver"
import LifecycleOwner from "androidx.lifecycle.LifecycleOwner"
import OnLifecycleEvent from "androidx.lifecycle.OnLifecycleEvent"
import ImageAnalysis from 'androidx.camera.core.ImageAnalysis'
import ImageProxy from 'androidx.camera.core.ImageProxy'
import NonNull from 'androidx.annotation.NonNull'
import ExecutorService from 'java.util.concurrent.ExecutorService'
import Executors from 'java.util.concurrent.Executors'
import ContextCompat from 'androidx.core.content.ContextCompat';
import LifecycleRegistry from "androidx.lifecycle.LifecycleRegistry"
import Lifecycle from "androidx.lifecycle.Lifecycle"
import FrameLayout from 'android.widget.FrameLayout';
import ViewPropertyAnimator from 'android.view.ViewPropertyAnimator';
import { SCANNING_OPTIONS, CAMERA_PHOTO_SIZE, CALL_RESULT_FUN, Call_BACK, Call_BACK_TYPE, SCANNING_PHOTO_RESULT, SCANNING_PHOTO_RESULT_BOUND } from "../interface.uts"
type buffoptsPlanesType = {
data : ByteBuffer,
getRowStride : number,
getPixelStride : number,
}
type buffoptsType = {
width : number,
height : number,
format : number,
rect : Rect,
planes : buffoptsPlanesType[],
}
type ConverXy = {
x : number,
y : number,
minx : number,
maxx : number,
miny : number,
maxy : number,
}
type contaiaSize = {
width : number,
height : number
}
type contaiaCalseSize = {
scale : number,
offsetX : number,
offsetY : number
}
type contaiPointXy = {
x : number,
y : number
}
let parentView : RelativeLayout | null = null;
let maskDomId = 8586
function px2dp(n : number) : number {
const mets = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics()
return mets.density * n
}
export class xCamera {
/** 手动震动服务 */
vibrator = null as null | Vibrator
context : Context;
// 识别点的区域
resultView : RelativeLayout | null = null;
// 存放小绿点的数组
imgPointsBycanmarea = [] as RelativeLayout[]
//相机容器
camreaLayouView:RelativeLayout|null = null;
// 图片容器层.
imageLayouView:RelativeLayout|null = null;
viewBox_width = 0
viewBox_height = 0
imageView = null as null | ImageView
imageUrl = null as null | Uri
isSelectedImging = false;
// 相机区域视图
cameraView : PreviewView | null = null;
cameraProvider : ProcessCameraProvider | null = null
camera : Camera | null = null;
cameraPreview : Preview | null = null;
// 当前是否在解析码制中。
isDecoderQring = false
// 当前相机是否正在预览中。
isOpeningCameraing = false;
callFunEvent : CALL_RESULT_FUN = (str : string) => { }
onlyCamera:boolean = false;
// 临时存放的识别成功的结果,防止重复扫码。
tempresultList = [] as string[];
activityresult = (requestCode : Int, resultCode : Int, data ?: Intent)=>{
let t = this;
if (resultCode == Activity.RESULT_OK && data != null && data?.data != null) {
let selectedImageUri = data.data!
this.camreaLayouView!.setVisibility(View.GONE)
this.imageUrl = selectedImageUri
this.isSelectedImging = false
decoderUriPathToQrByUri(this.imageUrl!,(res:SCANNING_PHOTO_RESULT)=>{
t.isOpeningCameraing = false
t.clearImageView();
t.imageView!.setImageURI(t.imageUrl!);
let matrix = t.getCorrectionMatrixByimgUri(t.imageView!, t.cameraView!,90)
let ruslt = res as SCANNING_PHOTO_RESULT;
if(ruslt.text.length>0){
t.setVibatar()
}
for (let i = 0; i < ruslt.bounds.length; i++) {
let item = ruslt.bounds[i]
let result = t.calcPoint(t.imageView!, t.cameraView!,item.centerX,item.centerY,0)
// let ponit = floatArrayOf(item.centerX.toFloat(), item.centerY.toFloat())
// matrix.mapPoints(ponit)
// item.centerX = ponit[0]
// item.centerY = ponit[1]
item.centerX = result.x
item.centerY = result.y
}
t.setImageViewRusletBox(ruslt)
if (ruslt.text.length == 1) {
t.callFunEvent(ruslt.text[0])
t.close()
}
})
} else {
this.isSelectedImging = false
}
}
constructor() {
this.context = UTSAndroid.getAppContext()! as Context
this.vibrator = this.context!.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator;
}
clearImageView() {
this.clearImagePonit();
if (!this.isOpeningCameraing) {
this.imageView!.setImageDrawable(null)
}
}
setVibatar() {
if (this.vibrator == null) return
// 检查设备是否支持震动
if (this.vibrator!.hasVibrator()) {
// 触发短暂的震动,持续时间为200毫秒
this.vibrator!.vibrate(80);
}
}
openCamera(call:CALL_RESULT_FUN,onlyCameras:boolean) {
this.onlyCamera = onlyCameras;
let t = this;
this.callFunEvent = call
if (parentView == null) {
this.createView();
t._openCamera()
} else {
this._openCamera()
}
}
close() {
UTSAndroid.offAppActivityResult(this.activityresult)
let decorView = UTSAndroid.getUniActivity()!.window.decorView as ViewGroup
this.closeCamera();
this.clearImagePonit();
if (parentView != null) {
decorView.removeView(parentView)
}
parentView = null;
}
private _openCamera() {
let _this = this;
this.closeCamera();
this.clearImagePonit();
let cameraProviderFuture = ProcessCameraProvider.getInstance(this.context)
this.camreaLayouView!.setVisibility(View.VISIBLE)
class IntentRunable extends Runnable {
override run() {
let cameraProvider : ProcessCameraProvider = cameraProviderFuture.get()
let previewView = _this.cameraView! as PreviewView
let cameraSelector : CameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
let preview : Preview = new Preview.Builder().build();
preview.setSurfaceProvider(previewView.getSurfaceProvider())
let imageAnalysis = new ImageAnalysis.Builder()
.setTargetResolution(new Size(800, 800))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
class MyImageAnalyzer implements ImageAnalysis.Analyzer {
constructor() {
super()
}
override analyze(image : ImageProxy) {
_this.qrDecoder(image)
}
override getDefaultTargetResolution() : Size {
return new Size(_this.viewBox_width.toInt(), _this.viewBox_height.toInt());
}
}
let cameraExecutor = Executors.newSingleThreadExecutor();
imageAnalysis.setAnalyzer(cameraExecutor, new MyImageAnalyzer());
let camera : Camera = cameraProvider.bindToLifecycle(
UTSAndroid.getUniActivity()! as LifecycleOwner, cameraSelector, preview, imageAnalysis);
_this.camera = camera
_this.cameraPreview = preview
_this.cameraProvider = cameraProvider
_this.isDecoderQring = false
_this.isOpeningCameraing = true
}
}
cameraProviderFuture.addListener(new IntentRunable(), ContextCompat.getMainExecutor(this.context))
}
closeCamera() {
this.cameraProvider?.unbindAll()
this.cameraPreview?.setSurfaceProvider(null)
this.isOpeningCameraing = false;
}
clearImagePonit(){
for (let i = 0; i < this.imgPointsBycanmarea.length; i++) {
this.resultView?.removeView(this.imgPointsBycanmarea[i])
}
}
private createView() {
let t = this;
let decorView = UTSAndroid.getUniActivity()!.window.decorView as ViewGroup
// 创建父容器。
parentView = new RelativeLayout(this.context);
parentView!.setId((maskDomId).toInt())
// parentView!.setOrientation(LinearLayout.VERTICAL)
parentView!.setBackgroundColor(Color.BLACK)
parentView!.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
))
class MaskerDomClickListsner extends View.OnTouchListener {
constructor() {
super();
}
//阻止事件向下穿透。
override onTouch(view : View, event : MotionEvent) : Boolean {
return true;
}
}
parentView!.setOnTouchListener(new MaskerDomClickListsner())
let continaer = new LinearLayout(this.context);
continaer.setOrientation(LinearLayout.VERTICAL)
continaer.setBackgroundColor(Color.TRANSPARENT)
let continaerLayparams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
continaer.setLayoutParams(continaerLayparams)
// 创建一个中间容器
let boxWrapParent = new LinearLayout(this.context);
boxWrapParent.setOrientation(LinearLayout.VERTICAL)
let boxLayparams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
boxLayparams.weight = (1).toFloat();
boxLayparams.height = 0;
boxWrapParent.setLayoutParams(boxLayparams)
// 创建一个定位容器
let boxWrap = new RelativeLayout(this.context);
boxWrap.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
))
boxWrapParent.addView(boxWrap)
//监听尺寸变化。
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.viewBox_width = right - left;
t.viewBox_height = bottom - top;
}
}
boxWrap.addOnLayoutChangeListener(new previewChangeRectadd())
let rongqitop = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
rongqitop.topMargin = 0
rongqitop.leftMargin = 0
// 创建容器内小绿点容器。
this.resultView = new RelativeLayout(this.context);
this.resultView!.setLayoutParams(rongqitop)
// 图片容器
let imgPview = new RelativeLayout(this.context);
let rongqitop2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
rongqitop2.topMargin = 0
rongqitop2.leftMargin = 0
imgPview.setLayoutParams(rongqitop2)
// 创建一张图片
this.imageView = new ImageView(this.context);
this.imageView!.setScaleType(ImageView.ScaleType.CENTER_INSIDE)
this.imageView!.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
imgPview.addView(this.imageView!)
this.imageView!.setImageDrawable(null)
// 相机容器。
let cview = new RelativeLayout(this.context);
let rongqitop3 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
// rongqitop3.width = 1000
// rongqitop3.height = 1000
cview.setLayoutParams(rongqitop3)
// 相机预览层。
let sv = new PreviewView(this.context)
// https://developer.android.google.cn/reference/androidx/camera/view/PreviewView.ScaleType?hl=en
sv.setScaleType(PreviewView.ScaleType.FILL_CENTER)
// 让相机的高和宽与组件对齐。
let layaout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
sv.setLayoutParams(layaout)
this.cameraView = sv;
cview.addView(this.cameraView!)
// this.imageView!.setVisibility(View.INVISIBLE)
//相机容器
this.camreaLayouView = cview;
// 图片容器层.
this.imageLayouView= imgPview;
boxWrap.addView(imgPview)
boxWrap.addView(cview)
boxWrap.addView(this.resultView!)
// 创建底
let footerWrap = new LinearLayout(this.context);
footerWrap.setOrientation(LinearLayout.HORIZONTAL)
let footerWrapLayparams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
footerWrapLayparams.height = px2dp(100).toInt();
footerWrap.setLayoutParams(footerWrapLayparams)
// 创建选择相册
let photoView = new LinearLayout(this.context);
photoView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
))
let xiangcheText = new TextView(this.context);
xiangcheText.setTextColor(Color.WHITE)
xiangcheText.setPadding(32, 52, 0, 0)
xiangcheText.setTextSize((16).toFloat())
xiangcheText.setGravity(Gravity.LEFT)
xiangcheText.setText(!this.onlyCamera?"相册选择":" ")
xiangcheText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
class XiangcheClickListsner extends View.OnTouchListener {
constructor() {
super();
}
override onTouch(view : View, event : MotionEvent) : Boolean {
UTSAndroid.offAppActivityResult(t.activityresult)
if(event.getAction()==MotionEvent.ACTION_UP){
t.closeCamera()
t.isSelectedImging = true;
let intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
UTSAndroid.onAppActivityResult(t.activityresult);
UTSAndroid.getUniActivity()!.startActivityForResult(intent, 1)
}
return true;
}
}
if(!this.onlyCamera){
xiangcheText.setOnTouchListener(new XiangcheClickListsner())
}
photoView.addView(xiangcheText)
// 中间占位
let cneterView = new LinearLayout(this.context);
let cneterViewParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
cneterViewParams.weight = (1).toFloat();
cneterView.setLayoutParams(cneterViewParams)
// 扫一扫view
let scanView = new LinearLayout(this.context);
scanView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
))
scanView.setGravity(Gravity.RIGHT)
let shaoyishaoText = new TextView(this.context);
shaoyishaoText.setTextColor(Color.WHITE)
shaoyishaoText.setPadding(0, 52, 32, 0)
shaoyishaoText.setTextSize((16).toFloat())
shaoyishaoText.setGravity(Gravity.RIGHT)
shaoyishaoText.setText("扫一扫")
shaoyishaoText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
class ShaoyishaoTextClickListsner extends View.OnTouchListener {
constructor() {
super();
}
override onTouch(view : View, event : MotionEvent) : Boolean {
if(event.getAction()==MotionEvent.ACTION_UP){
t._openCamera()
}
return true;
}
}
shaoyishaoText.setOnTouchListener(new ShaoyishaoTextClickListsner())
scanView.addView(shaoyishaoText)
footerWrap.addView(photoView)
footerWrap.addView(cneterView)
footerWrap.addView(scanView)
// 创建一个顶部
let titleView = new RelativeLayout(this.context);
// titleView!.setOrientation(LinearLayout.HORIZONTAL)
// titleView!.setBackgroundColor(Color.RED)
let tplays = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
tplays.topMargin = px2dp(60).toInt()
tplays.leftMargin = px2dp(16).toInt()
titleView!.setLayoutParams(tplays)
// 创建一个返回按钮
let backBtn = new TextView(this.context);
backBtn.setTextColor(Color.WHITE)
backBtn.setTextSize((16).toFloat())
backBtn.setGravity(Gravity.BOTTOM)
backBtn.setText("返回")
backBtn.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
class BackDomClickListsner extends View.OnTouchListener {
constructor() {
super();
}
override onTouch(view : View, event : MotionEvent) : Boolean {
t.close();
return true;
}
}
backBtn.setOnTouchListener(new BackDomClickListsner())
titleView!.addView(backBtn)
continaer.addView(boxWrapParent)
continaer.addView(footerWrap)
parentView!.addView(continaer)
parentView!.addView(titleView)
decorView.addView(parentView!)
// 增加动画
try {
let s0 = (0).toFloat()
let s1 = (1).toFloat()
parentView!.setAlpha(s0)
parentView!.setScaleX(s0)
parentView!.setScaleY(s0)
let ani = parentView!.animate() as ViewPropertyAnimator;
ani.alpha(s1)
.scaleX(s1)
.scaleY(s1)
.setDuration(250)
.start()
} catch (e) {
//TODO handle the exception
}
}
// 设置图片上的小圆的识别位置。
setImageViewRusletBox(jieguo : SCANNING_PHOTO_RESULT) {
let t = this;
let btnwidth = 100;
let btnwidth2 = 50;
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
)
let x = item.centerX
let y = item.centerY
layoutParams.topMargin = (y - btnwidth2).toInt() // 设置顶部边距
layoutParams.leftMargin = (x - btnwidth2).toInt() // 设置左边距
let relativeLayout = new RelativeLayout(t.context)
relativeLayout.layoutParams = layoutParams
const rtxt = jieguo.text[i]
let v = new xView(t.context)
v.setSize(btnwidth, btnwidth)
.setRadius(100)
.setBackgroundColor('#21d429')
.setBorder(12, "#ffffff", false)
.setClick((event : MotionEvent) => {
try {
// t.callFunEvent([rtxt], 'click')
t.callFunEvent(rtxt)
t.close()
} 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(ov)
this.imgPointsBycanmarea.push(relativeLayout)
this.resultView!.addView(relativeLayout)
}
}
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
}
getCorrectionMatrixByimgUri(imgView : ImageView, previewView : PreviewView,rotationDegrees:number) : Matrix {
let matrix = new Matrix()
let imgWidt = imgView.drawable!.intrinsicWidth as number
let imgHeight = imgView.drawable!.intrinsicHeight as number
let d0 = (0).toFloat()
let source = floatArrayOf(
d0,
d0,
imgWidt.toFloat(),
d0,
imgWidt.toFloat(),
imgHeight.toFloat(),
d0,
imgHeight.toFloat()
)
// A float array of the destination vertices in clockwise order.
let destination = floatArrayOf(
d0,
d0,
previewView.width.toFloat(),
d0,
previewView.width.toFloat(),
previewView.height.toFloat(),
d0,
previewView.height.toFloat()
)
let vertexSize = 2
let shiftOffset = rotationDegrees / 90 * vertexSize;
let tempArray = destination.clone()
for (toIndex in source.indices) {
let fromIndex = (toIndex + shiftOffset) % source.size
destination[toIndex] = tempArray[fromIndex.toInt()]
}
let success = matrix.setPolyToPoly(source, 0, destination, 0, 4)
return matrix
}
getCorrectionMatrix(imageProxy : ImageProxy, previewView : PreviewView) : Matrix {
let cropRect = imageProxy.cropRect
let rotationDegrees = imageProxy.imageInfo.rotationDegrees
let matrix = new Matrix()
// A float array of the source vertices (crop rect) in clockwise order.
let source = floatArrayOf(
cropRect.left.toFloat(),
cropRect.top.toFloat(),
cropRect.right.toFloat(),
cropRect.top.toFloat(),
cropRect.right.toFloat(),
cropRect.bottom.toFloat(),
cropRect.left.toFloat(),
cropRect.bottom.toFloat()
)
let d0 = (0).toFloat()
// A float array of the destination vertices in clockwise order.
let destination = floatArrayOf(
d0,
d0,
previewView.width.toFloat(),
d0,
previewView.width.toFloat(),
previewView.height.toFloat(),
d0,
previewView.height.toFloat()
)
let vertexSize = 2
let shiftOffset = rotationDegrees / 90 * vertexSize;
let tempArray = destination.clone()
for (toIndex in source.indices) {
let fromIndex = (toIndex + shiftOffset) % source.size
destination[toIndex] = tempArray[fromIndex.toInt()]
}
let success = matrix.setPolyToPoly(source, 0, destination, 0, 4)
return matrix
}
calcPoint(imgView : ImageView, previewView : PreviewView, image_point_x : number, image_point_y : number,degration:number) : ConverXy {
const image_w = imgView.drawable!.intrinsicWidth as number
const image_h = imgView.drawable!.intrinsicHeight as number
const c_w = previewView.width;
const c_h = previewView.height;
function scaleAndCenterContent(
container : contaiaSize,
content : contaiaSize
) : contaiaCalseSize {
// 计算缩放比例
const scaleWidth = container.width / content.width;
const scaleHeight = container.height / content.height;
const scale = Math.min(scaleWidth, scaleHeight);
// 计算居中偏移量
const offsetX = (container.width - content.width * scale) / 2;
const offsetY = (container.height - content.height * scale) / 2;
return { scale, offsetX, offsetY } as contaiaCalseSize;
}
function mapContentCoordinatesToContainer(
contentX : number,
contentY : number,
calcsize : contaiaCalseSize
) : contaiPointXy {
// 映射内容层坐标到容器层坐标
const containerX = contentX * calcsize.scale + calcsize.offsetX;
const containerY = contentY * calcsize.scale + calcsize.offsetY;
return { x: containerX, y: containerY } as contaiPointXy;
}
let rc = scaleAndCenterContent(
{ width: c_w, height: c_h } as contaiaSize,
{ width: image_h, height: image_w } as contaiaSize
)
if(degration==0){
rc = scaleAndCenterContent(
{ width: c_w, height: c_h } as contaiaSize,
{ width: image_w, height: image_h } as contaiaSize
)
}
let result = mapContentCoordinatesToContainer(image_point_y, image_point_x, rc)
if(degration==0){
result = mapContentCoordinatesToContainer(image_point_x,image_point_y,rc)
}
let nexyxcov = {
x: c_w - result.x, y: result.y, minx: 0, maxx: 0, miny: 0, maxy: 0
} as ConverXy
if(degration==0){
nexyxcov = {
x: result.x, y: result.y, minx: 0, maxx: 0, miny: 0, maxy: 0
} as ConverXy
}
return nexyxcov
}
qrDecoder(img : ImageProxy) {
let t = this;
let image = img.getImage();
if (!this.isDecoderQring&&image!=null&&t.isOpeningCameraing) {
this.isDecoderQring = 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)
let rotationDegrees = img.imageInfo.rotationDegrees
decoderBuffToQr(av, width, height, 0)
.then((res) => {
if (res != null ) {
let matrix = t.getCorrectionMatrix(img, t.cameraView!)
if (res.text.length > 0) {
let ruslt = res as SCANNING_PHOTO_RESULT;
for (let i = 0; i < ruslt.bounds.length; i++) {
let item = ruslt.bounds[i]
let ponit = floatArrayOf(item.centerX.toFloat(), item.centerY.toFloat())
// let xy = t.covertXy(img, t.cameraView!,item.centerX,item.centerY);
matrix.mapPoints(ponit)
item.centerX = ponit[0]
item.centerY = ponit[1]
// item.centerX = xy[0]
// item.centerY = xy[1]
// console.log(item.centerX ,item.centerY)
}
if(ruslt.text.length>0){
t.setVibatar()
}
t.setImageViewRusletBox(ruslt)
t.closeCamera()
if (ruslt.text.length == 1) {
t.callFunEvent(ruslt.text[0])
t.close()
}
}else{
t.isDecoderQring = false
}
}
// image.close();
})
.catch(() => {
t.isDecoderQring = false
})
}
img.close();
}
}
@@ -0,0 +1,936 @@
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"
import Gravity from 'android.view.Gravity';
// x
import ProcessCameraProvider from "androidx.camera.lifecycle.ProcessCameraProvider"
import PreviewView from "androidx.camera.view.PreviewView"
import CameraController from "androidx.camera.view.CameraController"
import CameraSelector from "androidx.camera.core.CameraSelector"
import Preview from 'androidx.camera.core.Preview'
import Camera from 'androidx.camera.core.Camera'
import LifecycleObserver from "androidx.lifecycle.LifecycleObserver"
import LifecycleOwner from "androidx.lifecycle.LifecycleOwner"
import OnLifecycleEvent from "androidx.lifecycle.OnLifecycleEvent"
import ImageAnalysis from 'androidx.camera.core.ImageAnalysis'
import ImageProxy from 'androidx.camera.core.ImageProxy'
import NonNull from 'androidx.annotation.NonNull'
import ExecutorService from 'java.util.concurrent.ExecutorService'
import Executors from 'java.util.concurrent.Executors'
import ContextCompat from 'androidx.core.content.ContextCompat';
import LifecycleRegistry from "androidx.lifecycle.LifecycleRegistry"
import Lifecycle from "androidx.lifecycle.Lifecycle"
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 implements LifecycleOwner {
scanSpeed = 500 //识别速度单位ms
scanPrevwSize = 800 //预期识别的像素大小,这是预期,会根据这个值找到相机支持的相近的相机尺寸。
lifecycleRegistry:LifecycleRegistry|null = null
context : Context;
camera_service : CameraManager;
cameraPermissioPass = false;
camerManIds = null as string | null
cameraDevice = null as null | CameraDevice
preview = null as null | LinearLayout;
captureSession = null as null | CameraCaptureSession
// 相机区域视图
cameraView : PreviewView | null = null;
cameraProvider : ProcessCameraProvider | null = null
camera : Camera | null = null;
cameraPreview : Preview | null = null;
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.lifecycleRegistry = new LifecycleRegistry(this);
this.lifecycleRegistry!.markState(Lifecycle.State.CREATED);
this.hideTitleBar()
this.setContentView(R.layout.demo_activity);
this.overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
this.preview = this.findViewById<LinearLayout>(R.id.surface_camera_box);
this.preview!.removeView(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
)
this._createCameraView()
//监听尺寸变化。
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();
}
/** 创建相机预览区域 */
private _createCameraView() {
let sv = new PreviewView(this.context)
sv.scaleType = PreviewView.ScaleType.FILL_CENTER
// 让相机的高和宽与组件对齐。
let layaout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
sv.setLayoutParams(layaout)
this.cameraView = sv;
this.preview!.addView(this.cameraView!)
}
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);
}
}
// 在需要隐藏标题栏的地方调用
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)
})
}
})
}
// 设置相机对焦模式
// 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 {
return 0
}
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
}
getCorrectionMatrix(imageProxy : ImageProxy, previewView : PreviewView) : Matrix {
let cropRect = imageProxy.cropRect
let rotationDegrees = imageProxy.imageInfo.rotationDegrees
let matrix = new Matrix()
// A float array of the source vertices (crop rect) in clockwise order.
new FloatArray(8)
let source = floatArrayOf(cropRect.left.toFloat(),
cropRect.top.toFloat(),
cropRect.right.toFloat(),
cropRect.top.toFloat(),
cropRect.right.toFloat(),
cropRect.bottom.toFloat(),
cropRect.left.toFloat(),
cropRect.bottom.toFloat())
let d0 = (0).toFloat()
// A float array of the destination vertices in clockwise order.
let destination = floatArrayOf(d0,
d0,
previewView.width.toFloat(),
d0,
previewView.width.toFloat(),
previewView.height.toFloat(),
d0,
previewView.height.toFloat())
// The destination vertexes need to be shifted based on rotation degrees. The
// rotation degree represents the clockwise rotation needed to correct the image.
// Each vertex is represented by 2 float numbers in the vertices array.
let vertexSize = 2
// The destination needs to be shifted 1 vertex for every 90° rotation.
let shiftOffset = rotationDegrees / 90 * vertexSize;
let tempArray = destination.clone()
for (toIndex in source.indices) {
let fromIndex = (toIndex + shiftOffset) % source.size
destination[toIndex] = tempArray[fromIndex.toInt()]
}
let success = matrix.setPolyToPoly(source, 0, destination, 0, 4)
return matrix
}
qrDecoder(img : ImageProxy) {
let t = this;
let image = img.getImage();
if (!this.isDecodreIng&&image!=null) {
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 ) {
let matrix = t.getCorrectionMatrix(img, t.cameraView!)
if (res.text.length > 0) {
t.vibatarLong()
let ruslt = res as SCANNING_PHOTO_RESULT;
for (let i = 0; i < ruslt.bounds.length; i++) {
let item = ruslt.bounds[i]
let ponit = floatArrayOf(item.centerX.toFloat(), item.centerY.toFloat())
matrix.mapPoints(ponit)
item.centerX = ponit[0]
item.centerY = ponit[1]
}
t.setImageViewRusletBox(t.previewWidth, t.previewWidth, width, height, ruslt)
uni.setStorageSync('xMlkitScannigUResult_time_start', 'end:'+(new Date().getTime() - starttime).toString()+'ms')
t.closeCamra()
if (ruslt.text.length == 1) {
uni.setStorageSync('xMlkitScannigUResult', ruslt.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);
}
img.close();
}
closeCamra() {
this.cameraProvider?.unbindAll()
this.cameraPreview?.setSurfaceProvider(null)
this.clearImageView()
}
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.closeCamra();
t.openCamrae();
})
}
}
this.preview!.addOnLayoutChangeListener(new previewChangeRectadd())
} else {
t.isScannig = true
t.closeCamra();
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;
let btnwidth = 100;
let btnwidth2 = 50;
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 = (item.centerY- btnwidth2).toInt() // 设置顶部边距
layoutParams.leftMargin = (item.centerX- btnwidth2).toInt() // 设置左边距
} else {
layoutParams.topMargin = (hRatio * item.centerY-btnwidth2).toInt() // 设置顶部边距
layoutParams.leftMargin = (wRatio * item.centerX-btnwidth2).toInt() // 设置左边距
}
let relativeLayout = new RelativeLayout(t.context)
relativeLayout.layoutParams = layoutParams
const rtxt = jieguo.text[i]
let v = new xView(t.context)
v.setSize(btnwidth , btnwidth)
.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 onStart() {
super.onStart();
lifecycleRegistry!.markState(Lifecycle.State.STARTED);
}
override onDestroy() : void {
super.onDestroy();
this.closeCamra()
this.showTitleBar();
}
override onStop() {
lifecycleRegistry!.markState(Lifecycle.State.CREATED);
super.onStop();
}
override onPause() : void {
super.onPause();
lifecycleRegistry!.markState(Lifecycle.State.CREATED);
console.log("暂停扫码")
this.closeCamra()
}
override onResume() : void {
super.onResume();
lifecycleRegistry!.markState(Lifecycle.State.RESUMED);
let t = this;
if (this.captureSession != null || t.imageUrl != null || t.isSelectedImging) {
return;
}
console.log("恢复扫码")
this.closeCamra()
this.openCamra();
}
override getLifecycle(): Lifecycle {
return this.lifecycleRegistry!
}
openCamrae() {
let _this = this;
let cameraProviderFuture = ProcessCameraProvider.getInstance(this.context)
class IntentRunable extends Runnable {
override run() {
let cameraProvider : ProcessCameraProvider = cameraProviderFuture.get()
let previewView = _this.cameraView! as PreviewView
let cameraSelector : CameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
let preview : Preview = new Preview.Builder().build();
preview.setSurfaceProvider(previewView.getSurfaceProvider())
let imageAnalysis = new ImageAnalysis.Builder()
// .setTargetResolution(new Size(_this.viewBox_width.toInt(), _this.viewBox_height.toInt()))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
class MyImageAnalyzer implements ImageAnalysis.Analyzer {
constructor() {
super()
}
override analyze(image : ImageProxy) {
_this.qrDecoder(image)
}
override getDefaultTargetResolution() : Size {
return new Size(_this.previewWidth.toInt(), _this.previewHeight.toInt());
}
}
let cameraExecutor = Executors.newSingleThreadExecutor();
imageAnalysis.setAnalyzer(cameraExecutor, new MyImageAnalyzer());
let camera : Camera = cameraProvider.bindToLifecycle(
_this as LifecycleOwner, cameraSelector, preview, imageAnalysis);
_this.camera = camera
_this.cameraPreview = preview
_this.cameraProvider = cameraProvider
_this.isDecodreIng = false
_this.isScannig = true
}
}
cameraProviderFuture.addListener(new IntentRunable(), ContextCompat.getMainExecutor(this.context))
}
}
@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;
}
@@ -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;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
{
"minSdkVersion": "21",
"dependencies":[
"com.google.mlkit:barcode-scanning:17.2.0",
"androidx.camera:camera-core:1.3.4",
"androidx.camera:camera-camera2:1.3.4",
"androidx.camera:camera-lifecycle:1.3.4",
"androidx.camera:camera-video:1.3.4",
"androidx.camera:camera-view:1.3.4",
"androidx.camera:camera-extensions:1.3.4"
]
}
@@ -0,0 +1,382 @@
/**
* 引用 Android 系统库,示例如下:
* import { Context } from "android.content.Context";
* [可选实现,按需引入]
*/
/* 引入 interface.uts 文件中定义的变量 */
import { SCANNING_PHOTO_RESULT, SCANNING_PHOTO_RESULT_BOUND, SCANNING_PHOTO_RESULT_POS } from '../interface.uts';
/* 引入 unierror.uts 文件中定义的变量 */
import Intent from 'android.content.Intent';
import Context from 'android.content.Context'
import IOException from 'java.io.IOException'
import InputStream from 'java.io.InputStream'
import Uri from 'android.net.Uri';
import File from 'java.io.File'
import Bitmap from "android.graphics.Bitmap"
import BarcodeScanner from "com.google.mlkit.vision.barcode.BarcodeScanner"
import BarcodeScannerOptions from "com.google.mlkit.vision.barcode.BarcodeScannerOptions"
import BarcodeScanning from "com.google.mlkit.vision.barcode.BarcodeScanning"
import ZoomSuggestionOptions from "com.google.mlkit.vision.barcode.ZoomSuggestionOptions"
import ZoomCallback from "com.google.mlkit.vision.barcode.ZoomSuggestionOptions.ZoomCallback"
import Barcode from "com.google.mlkit.vision.barcode.common.Barcode"
import InputImage from "com.google.mlkit.vision.common.InputImage"
import Task from "com.google.android.gms.tasks.Task"
import List from "java.util.List"
import ByteBuffer from 'java.nio.ByteBuffer'
import ImageFormat from 'android.graphics.ImageFormat'
import {xCamera} from "./camera.uts"
import {CALL_RESULT_FUN} from "../interface.uts"
import {gotoDemoActivity} from "./cameraByx.uts"
const camera = new xCamera()
/**
* 从相册中选取图片进行解码
* @public chooseImageDecodeQr
*/
export function chooseImage() : Promise<SCANNING_PHOTO_RESULT | null> {
return new Promise((res) => {
uni.chooseImage({
count: 1,
sourceType:['album'],
albumMode:"system",
success(evt : ChooseImageSuccess) {
console.log('**')
if (evt.tempFilePaths.length > 0) {
console.log(evt, "----")
const imgs = evt.tempFilePaths[0] as string;
try {
const decodedPath = Uri.decode(imgs).substring(7)
const file = File(decodedPath)
let image = InputImage.fromFilePath(UTSAndroid.getAppContext() as Context, Uri.fromFile(file));
let scanner = BarcodeScanning.getClient();
scanner.process(image)
.addOnSuccessListener((barcodes) => {
let listpos = [] as number[][][]
let bounedlist = [] as SCANNING_PHOTO_RESULT_BOUND[]
let rulstText = [] as string[]
for (barcode in barcodes) {
// 位置信息
let bounds = barcode.boundingBox
// 坐标
let corners = barcode.cornerPoints
// 解析的内容
let rawValue = barcode.rawValue
// 码的格式
let valueType = barcode.valueType
if (rawValue != null && bounds != null && corners != null) {
let iminfo = { width: bounds!.width(), height: bounds!.height(), centerX: bounds!.centerX(), centerY: bounds!.centerY() } as SCANNING_PHOTO_RESULT_BOUND
let posinfo = [] as number[][]
for (pos in corners) {
posinfo.push([pos!.x as number, pos!.y as number] as number[])
}
listpos.push(posinfo)
bounedlist.push(iminfo)
rulstText.push(rawValue)
}
}
res({
url: imgs,
position: listpos,
bounds: bounedlist,
text: rulstText,
imgWidth:image.getWidth(),
imgHeight:image.getHeight()
} as SCANNING_PHOTO_RESULT)
})
} catch (e : IOException) {
res(null)
}
}
},
fail() {
res(null)
}
})
})
}
/**
* 给定图片路径解析图片。
* @public decoderUriPathToQr
*/
export function decoderUriPathToQr(filePath : string,callback:(rs:SCANNING_PHOTO_RESULT)=>void) {
try{
let uri = UTSAndroid.getFileProviderUri(new File(filePath))!
let image = InputImage.fromFilePath(UTSAndroid.getAppContext() as Context, uri);
let scanner = BarcodeScanning.getClient();
scanner.process(image)
.addOnSuccessListener((barcodes) => {
let listpos = [] as number[][][]
let bounedlist = [] as SCANNING_PHOTO_RESULT_BOUND[]
let rulstText = [] as string[]
for (barcode in barcodes) {
// 位置信息
let bounds = barcode.boundingBox
// 坐标
let corners = barcode.cornerPoints
// 解析的内容
let rawValue = barcode.rawValue
// 码的格式
let valueType = barcode.valueType
if (rawValue != null && bounds != null && corners != null) {
let iminfo = { width: bounds!.width(), height: bounds!.height(), centerX: bounds!.centerX(), centerY: bounds!.centerY() } as SCANNING_PHOTO_RESULT_BOUND
let posinfo = [] as number[][]
for (pos in corners) {
posinfo.push([pos!.x as number, pos!.y as number] as number[])
}
listpos.push(posinfo)
bounedlist.push(iminfo)
rulstText.push(rawValue)
}
}
callback({
url: "",
position: listpos,
bounds: bounedlist,
text: rulstText,
imgWidth:image.getWidth(),
imgHeight:image.getHeight()
} as SCANNING_PHOTO_RESULT)
})
}catch(e){
console.error(e)
}
}
/**
* 给定图片路径Uri解析图片。
* @public decoderUriPathToQr
*/
export function decoderUriPathToQrByUri(filePath : Uri,callback:(rs:SCANNING_PHOTO_RESULT)=>void) {
try{
let image = InputImage.fromFilePath(UTSAndroid.getAppContext() as Context, filePath);
let options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_QR_CODE,
Barcode.FORMAT_EAN_13,
Barcode.FORMAT_CODE_128,
Barcode.FORMAT_CODABAR
)
.build()
let scanner = BarcodeScanning.getClient(options);
scanner.process(image)
.addOnSuccessListener((barcodes) => {
let listpos = [] as number[][][]
let bounedlist = [] as SCANNING_PHOTO_RESULT_BOUND[]
let rulstText = [] as string[]
for (barcode in barcodes) {
// 位置信息
let bounds = barcode.boundingBox
// 坐标
let corners = barcode.cornerPoints
// 解析的内容
let rawValue = barcode.rawValue
// 码的格式
let valueType = barcode.valueType
if (rawValue != null && bounds != null && corners != null) {
let iminfo = { width: bounds!.width(), height: bounds!.height(), centerX: bounds!.centerX(), centerY: bounds!.centerY() } as SCANNING_PHOTO_RESULT_BOUND
let posinfo = [] as number[][]
for (pos in corners) {
posinfo.push([pos!.x as number, pos!.y as number] as number[])
}
listpos.push(posinfo)
bounedlist.push(iminfo)
rulstText.push(rawValue)
}
}
callback({
url: "",
position: listpos,
bounds: bounedlist,
text: rulstText,
imgWidth:image.getWidth(),
imgHeight:image.getHeight()
} as SCANNING_PHOTO_RESULT)
})
}catch(e){
console.error(e)
}
}
/**
* 给定ImageBuffer
* @public decoderBuffToQr
*/
export function decoderBuffToQr(bitmap : ByteArray, width : Number, height : Number, ration : Number) : Promise<SCANNING_PHOTO_RESULT | null> {
return new Promise((res) => {
let options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_QR_CODE,
Barcode.FORMAT_EAN_13,
Barcode.FORMAT_CODE_128,
Barcode.FORMAT_CODABAR
)
.build()
let image = InputImage.fromByteArray(bitmap, width.toInt(), height.toInt(), ration.toInt(), InputImage.IMAGE_FORMAT_NV21)
// 如果只扫qr开启这行,速度快一点。
let scanner = BarcodeScanning.getClient(options);
// let scanner = BarcodeScanning.getClient();
scanner.process(image)
.addOnSuccessListener((barcodes) => {
let listpos = [] as number[][][]
let bounedlist = [] as SCANNING_PHOTO_RESULT_BOUND[]
let rulstText = [] as string[]
for (barcode in barcodes) {
// 位置信息
let bounds = barcode.boundingBox
// 坐标
let corners = barcode.cornerPoints
// 解析的内容
let rawValue = barcode.rawValue
// 码的格式
let valueType = barcode.valueType
if (rawValue != null && bounds != null && corners != null) {
let iminfo = { width: bounds!.width(), height: bounds!.height(), centerX: bounds!.centerX(), centerY: bounds!.centerY() } as SCANNING_PHOTO_RESULT_BOUND
let posinfo = [] as number[][]
for (pos in corners) {
posinfo.push([pos!.x as number, pos!.y as number] as number[])
}
listpos.push(posinfo)
bounedlist.push(iminfo)
rulstText.push(rawValue)
}
}
res({
url: "",
position: listpos,
bounds: bounedlist,
text: rulstText,
imgWidth:image.getWidth(),
imgHeight:image.getHeight()
} as SCANNING_PHOTO_RESULT)
})
.addOnFailureListener((e) => {
res(null)
})
})
}
/**
* 提供一个网络图片地址进行解码
* @public urlPathDecoderQr
*/
export function urlPathDecoderQr(url : String,callback:(rs:SCANNING_PHOTO_RESULT)=>void) {
uni.downloadFile({
url,
success(res) {
const contentResolver = UTSAndroid.getAppContext()!.contentResolver
let decodedPath = res.tempFilePath
if (res.tempFilePath.indexOf('file://') > -1) {
decodedPath = Uri.decode(res.tempFilePath).substring(7)
}
decoderUriPathToQr(decodedPath,callback)
},
fail() {
console.error('下载图片错误')
}
})
}
/**
* 检查相机权限
*/
export function checkPermissions() : Promise<boolean> {
let permissionCheck = ["android.permission.CAMERA"]
let _this = this;
// 请求拍照权限
return new Promise((res) => {
if (UTSAndroid.checkSystemPermissionGranted(UTSAndroid.getUniActivity()!, permissionCheck)) {
// _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[]) {
//用户拒绝了部分权限
res(false)
})
}
})
}
let callCustomFun = null as null|CALL_RESULT_FUN
const xMlkitScanningActivityResult = (requestCode : Int, resultCode : Int, data ?: Intent)=>{
let _this = this;
let jgCode = 1;
let datastr = uni.getStorageSync('xMlkitScannigUResult');
if(requestCode==jgCode.toInt()&&datastr!=null&&callCustomFun!=null){
let str = ""
let call = callCustomFun!;
if(typeof datastr == 'bigint'||typeof datastr == 'number' || typeof datastr == 'int'){
call(datastr.toString())
}else{
str = datastr as string;
call(str)
}
uni.removeStorageSync('xMlkitScannigUResult')
}
}
export function openCameraApi(call:CALL_RESULT_FUN,onlyCamera:boolean){
// UTSAndroid.offAppActivityResult(xMlkitScanningActivityResult)
// callCustomFun = call
let isPrivacyAgreeCamrea = uni.getStorageSync('isPrivacyAgreeCamreaByTmui')
if(isPrivacyAgreeCamrea!='on'){
uni.showModal({
title:"提醒",
content:"将授权你的设备相机使用权限进行拍摄识别功能,是否允许?",
success(isPermisseions){
if(isPermisseions.confirm){
checkPermissions()
.then((res:boolean)=>{
gotoDemoActivity(call,onlyCamera)
uni.setStorageSync('isPrivacyAgreeCamreaByTmui','on')
})
.catch(()=>{
uni.setStorageSync('isPrivacyAgreeCamreaByTmui','off')
})
}
},
fail(){
uni.showToast({title:"未授权相机使用权",icon:"none"})
}
})
}else{
// camera.openCamera(call,onlyCamera)
gotoDemoActivity(call,onlyCamera)
}
}
export function closeCamera(){
}
@@ -0,0 +1,468 @@
import Context from 'android.content.Context'
import LinearLayout from 'android.widget.LinearLayout';
import ViewGroup from 'android.view.ViewGroup';
import View from 'android.view.View';
import TextView from 'android.widget.TextView';
import ImageView from 'android.widget.ImageView';
import Gravity from 'android.view.Gravity';
import Color from 'android.graphics.Color';
import Activity from 'android.app.Activity';
// import CardView from 'androidx.cardview.widget.CardView';
import { ref, } from "vue"
import TextUtils from 'android.text.TextUtils';
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
import MotionEvent from 'android.view.MotionEvent';
import ColorStateList from 'android.content.res.ColorStateList';
import attr from 'android.R.attr'
import {hexToRgb,getDefaultColor} from "./xCoreColorUtil.uts"
/**
* 随机一个uid
* @param rdix 随机因子
* @param length 取的长度
* @param isAddStr 是否限制随机结果中的长度,不允许输出长度
* @returns String
*/
function getUid(rdix = 1, length = 12) : string {
let ix = "";
// #ifndef APP
ix = Math.floor(Math.random() * rdix * Math.floor(Math.random() * Date.now())).toString().substring(0, length);
// #endif
// #ifdef APP
ix = Math.floor(Math.random() * rdix * Math.floor(Math.random() * Date.now())).toString().substring(0, length as Int);
// #endif
return ix;
}
class MyClickListener implements View.OnClickListener {
callbackFun:()=>void;
constructor(cal?:()=>void){
this.callbackFun = cal as ()=>void;
}
override onClick(v?:View):void {
if(typeof this.callbackFun !='underfinde'){
this.callbackFun();
}
}
}
class MyTouchListener implements View.OnTouchListener {
//down,up,move,cancel,longClick
callbackFun:(types:string,event : MotionEvent,detail:UTSJSONObject)=>void;
dubleTime = 0;
tid = 56;
_x = 0;
_y = 0;
// 判断方向的差值
swipe_mindiff = 40
// 滑动时,一定时间内只能触发一次事件。不能连续触发。
tid_siper = 100;
swipeDirection = ""
constructor(call:(types:string,event : MotionEvent,detail?:UTSJSONObject)=>void) {
this.callbackFun = call;
}
override onTouch(view : View, event : MotionEvent) : Boolean {
if(event.action == MotionEvent.ACTION_DOWN ){
this._x = event.getX();
this._y = event.getY();
this.swipeDirection = ""
this.callbackFun('down',event,{});
let difftime = new Date().getTime() - this.dubleTime;
if(difftime >0 && difftime<=300){
this.callbackFun('doubleClick',event ,{});
}
this.dubleTime = new Date().getTime();
clearTimeout(this.tid)
tid = setTimeout(function() {
this.callbackFun('longPress',event,{});
}, 800);
}
if(event.action == MotionEvent.ACTION_UP ){
clearTimeout(this.tid)
this.callbackFun('up',event,{});
if(new Date().getTime() - this.dubleTime > 50){
this.callbackFun('click',event,{});
}
}
if(event.action == MotionEvent.ACTION_CANCEL || event.action== MotionEvent.ACTION_OUTSIDE ){
this.callbackFun('cancel',event,{});
clearTimeout(this.tid)
}
if(event.action == MotionEvent.ACTION_MOVE ){
let x = event.getX();
let y = event.getY();
let deltaX = Math.abs(x - this._x);
let deltaY = Math.abs(y - this._y);
if(deltaX > deltaY && deltaX > this.swipe_mindiff){
this.swipeDirection = (this._x > x) ? "left" : "right";
}else if(deltaY > deltaX && deltaY > this.swipe_mindiff){
this.swipeDirection = (this._y < y) ? "down" : "up";
}
if(this.swipeDirection !=""){
this.callbackFun('swiper',event,{x,y,diffX:deltaX,diffY:deltaY,direction:this.swipeDirection});
}
// this._x = x;
// this._y = y;
this.callbackFun('move',event,{});
clearTimeout(this.tid)
}
return true;
}
}
class xView {
public view : any;
public tagId:string = 'x_view_id_'+getUid();
public bgView:GradientDrawable
private _onclickFun= (_event : MotionEvent):void=>{};
private _onTouchStart= (_event : MotionEvent):void=>{};
private _onTouchEnd= (_event : MotionEvent):void=>{};
private _onTouchCancel= (_event : MotionEvent):void=>{};
private _onTouchMove= (_event : MotionEvent):void=>{};
private _onTouchLongPress= (_event : MotionEvent):void=>{};
private _onTouchDubleClick= (_event : MotionEvent):void=>{};
private _onTouchSwiper= (_event : MotionEvent,detail?:UTSJSONObject):void=>{};
// 设置禁用,不会触发上面的事件。
private _disabled = false;
constructor(context : Context) {
let view = new View(context);
view.setTag(this.tagId)
let layoutParams_crd = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
view.setLayoutParams(layoutParams_crd);
this.bgView = new GradientDrawable();
// let statuesInts= [[attr.state_enabled].toKotlinList().toIntArray(),[attr.state_pressed].toKotlinList().toIntArray(),[-attr.state_enabled].toKotlinList().toIntArray()]
// let statuesColors= [Color.WHITE,Color.RED,Color.RED]
// let states = new ColorStateList(statuesInts.toKotlinList().toTypedArray(),statuesColors.toKotlinList().toIntArray())
this.bgView.setShape(GradientDrawable.RECTANGLE)
this.bgView.setColor(Color.WHITE)
this.bgView.mutate()
view.setBackground(this.bgView)
this.view = view;
// view.setOnClickListener(new MyClickListener(():void=>{
// this._onclickFun()
// }));
view.setOnTouchListener(new MyTouchListener((types:string,event : MotionEvent,detail?:UTSJSONObject):void=>{
if(this._disabled == false){
if(types=='down'){
this._onTouchStart(event)
}
if(types=='up'){
this._onTouchEnd(event);
}
if(types=='click'){
this._onclickFun(event);
}
if(types=='move'){
this._onTouchMove(event)
}
if(types=='cancel'){
this._onTouchCancel(event)
}
if(types=='longPress'){
this._onTouchLongPress(event)
}
if(types=='doubleClick'){
this._onTouchDubleClick(event)
}
if(types=='swiper'){
this._onTouchSwiper(event,detail)
}
}
}));
}
getView():View{
return this.view as View;
}
setDisabled(dis:boolean) : xView{
this._disabled = dis;
return this;
}
/**
* 触发距离是大于50时触发并判断方向
* x,y当前坐标,diffX滑动时距离开始时按下的横向x距离,diffY表示竖向。Direction为方向:left,right,up,dowon
*detail:
*{x,y,diffX,diffY,direction}
*/
setSwiper(fun?:(event: MotionEvent,detail?:UTSJSONObject)=>void) : xView{
if(typeof fun !== 'undefined'){
this._onTouchSwiper = fun;
}
return this;
}
setDoubleClick(fun?:(event: MotionEvent)=>void) : xView{
if(typeof fun !== 'undefined'){
this._onTouchDubleClick = fun;
}
return this;
}
setClick(fun?:(event: MotionEvent)=>void) : xView{
if(typeof fun !== 'undefined'){
this._onclickFun = fun;
}
return this;
}
setTouchStart(fun?:(event: MotionEvent)=>void) : xView{
if(typeof fun !== 'undefined'){
this._onTouchStart = fun;
}
return this;
}
setTouchMove(fun?:(event: MotionEvent)=>void) : xView{
if(typeof fun !== 'undefined'){
this._onTouchMove = fun;
}
return this;
}
setTouchEnd(fun?:(event: MotionEvent)=>void) : xView{
if(typeof fun !== 'undefined'){
this._onTouchEnd = fun;
}
return this;
}
setTouchCancel(fun?:(event: MotionEvent)=>void) : xView{
if(typeof fun !== 'undefined'){
this._onTouchCancel = fun;
}
return this;
}
setTouchLongPress(fun?:(event: MotionEvent)=>void) : xView{
if(typeof fun !== 'undefined'){
this._onTouchLongPress = fun;
}
return this;
}
setBackgroundColor(colorStr : string) : xView {
this.bgView.mutate()
if(colorStr == 'transparent'){
this.bgView.setColor(Color.TRANSPARENT)
}else{
this.bgView.setColor(Color.parseColor(getDefaultColor(colorStr)))
}
return this;
}
setBorder(w?:number,colorStr?:string,dashed?:boolean) : xView {
this.bgView.mutate()
let tw:number = 1;
let tcolor = 'transparent';
let tdashed = false;
if(typeof tw !== 'undefined'){
tw = w as number;
}
if(typeof tw !== 'undefined'){
tcolor = colorStr as string;
}
if(typeof dashed !== 'undefined'){
tdashed = dashed as boolean;
}
let colorNum = Color.TRANSPARENT;
if(tcolor != 'transparent'){
colorNum = Color.parseColor(getDefaultColor(tcolor));
}
if(dashed == true){
let dashedWidth = 8
let dashedGap = 5
this.bgView.setStroke(tw.toInt(),colorNum,dashedWidth.toFloat(),dashedGap.toFloat())
}else{
this.bgView.setStroke(tw.toInt(),colorNum)
}
return this;
}
setMargin(n:any):xView{
let view = this.view as View;
let selfPrams = view.getLayoutParams()
let params = new LinearLayout.LayoutParams(
selfPrams.width,
selfPrams.height
)
if(Array.isArray(n)){
let rd = n as number[];
if(rd.length==0){
let rds = 0;
let rdf = (rds).toInt();
params.setMargins(rdf,rdf,rdf,rdf)
}else if(rd.length==1){
let rdf = (rd[0]).toInt();
params.setMargins(rdf,rdf,rdf,rdf)
}else if(rd.length==2){
let rds = 0;
params.setMargins((rd[0]).toInt(),(rd[1]).toInt(),(rd[0]).toInt(),(rd[1]).toInt())
}else if(rd.length==3){
let rds = 0;
let rdf = rds.toInt();
params.setMargins((rd[0]).toInt(),(rd[1]).toInt(),(rd[2]).toInt(),rdf)
}else if(rd.length==4){
params.setMargins((rd[0]).toInt(),(rd[1]).toInt(),(rd[2]).toInt(),(rd[3]).toInt())
}
}else{
let rd = n as number;
let rdf = (rd).toInt();
params.setMargins(rdf,rdf,rdf,rdf)
}
view.setLayoutParams(params);
return this;
}
setPadding(n:any):xView{
let view = this.view as View;
if(Array.isArray(n)){
let rd = n as number[];
if(rd.length==0){
let rds = 0;
let rdf = (rds).toInt();
view.setPadding(rdf,rdf,rdf,rdf)
}else if(rd.length==1){
let rdf = (rd[0]).toInt();
view.setPadding(rdf,rdf,rdf,rdf)
}else if(rd.length==2){
view.setPadding((rd[0]).toInt(),(rd[1]).toInt(),(rd[0]).toInt(),(rd[1]).toInt())
}else if(rd.length==3){
let rds = 0;
let rdf = rds.toInt();
view.setPadding((rd[0]).toInt(),(rd[1]).toInt(),(rd[2]).toInt(),rdf)
}else if(rd.length==4){
view.setPadding((rd[0]).toInt(),(rd[1]).toInt(),(rd[2]).toInt(),(rd[3]).toInt())
}
}else{
let rd = n as number;
let rdf = (rd).toInt();
view.setPadding(rdf,rdf,rdf,rdf)
}
return this;
}
setRadius(radius:any) : xView{
let x =0
let y =0
this.bgView.mutate()
if(Array.isArray(radius)){
let rd = radius as number[];
if(rd.length==0){
let rdfs = 0;
this.bgView.setCornerRadius((rdfs).toFloat());
// view.setBackground(this.bgView)
}else if(rd.length==1){
let rdfs = rd[0];
this.bgView.setCornerRadius((rdfs).toFloat());
// view.setBackground(this.bgView)
}else if(rd.length==2){
let tl = [
(rd[0]).toFloat(),(rd[0]).toFloat(),
(rd[1]).toFloat(),(rd[1]).toFloat(),
x.toFloat(),x.toFloat(),
x.toFloat(),x.toFloat()
]
let tlint = tl.toKotlinList().toFloatArray()
this.bgView.setCornerRadii(tlint);
}else if(rd.length==3){
let tl = [
(rd[0]).toFloat(),(rd[0]).toFloat(),
(rd[1]).toFloat(),(rd[1]).toFloat(),
(rd[2]).toFloat(),(rd[2]).toFloat(),
x.toFloat(),x.toFloat()
]
let tlint = tl.toKotlinList().toFloatArray()
this.bgView.setCornerRadii(tlint);
}else if(rd.length==4){
let tl = [
(rd[0]).toFloat(),(rd[0]).toFloat(),
(rd[1]).toFloat(),(rd[1]).toFloat(),
(rd[2]).toFloat(),(rd[2]).toFloat(),
(rd[3]).toFloat(),(rd[3]).toFloat(),
]
let tlint = tl.toKotlinList().toFloatArray()
this.bgView.setCornerRadii(tlint);
}
}else{
let rd = radius as number;
this.bgView.setCornerRadius((rd).toFloat());
}
return this;
}
/** 设置当前宽和高为父级的相对宽和高
*w,h有效值为:auto|100%
*/
setSizeBy(w:string="100%",h:string = "auto"){
let view = this.view as View;
let ww = ViewGroup.LayoutParams.MATCH_PARENT;
let hh = ViewGroup.LayoutParams.MATCH_PARENT;
if(w=="auto"){
ww = ViewGroup.LayoutParams.WRAP_CONTENT
}
if(h=="auto"){
hh = ViewGroup.LayoutParams.WRAP_CONTENT
}
let layoutParams_crd = new LinearLayout.LayoutParams(ww,hh)
view.setLayoutParams(layoutParams_crd);
}
setSize(width : number, height : number) : xView {
let view = this.view as View;
let layoutParams_crd = new LinearLayout.LayoutParams(
(width).toInt(),
(height).toInt()
)
view.setLayoutParams(layoutParams_crd);
return this;
}
setWidth(width : number) : xView {
let view = this.view as View;
let lpar = view.getLayoutParams();
let layoutParams_crd = new LinearLayout.LayoutParams(
(width).toInt(),
(lpar.height).toInt(),
)
view.setLayoutParams(layoutParams_crd);
return this;
}
setHeight(height : number) : xView {
let view = this.view as View;
let lpar = view.getLayoutParams();
let layoutParams_crd = new LinearLayout.LayoutParams(
(lpar.width).toInt(),
(height).toInt(),
)
view.setLayoutParams(layoutParams_crd);
return this;
}
}
export default xView;
@@ -0,0 +1,676 @@
/**
* 数字转16进制
*/
function toHex(numbers : number) : string {
let i = 0 as number
if (numbers === i) {
return '00';
}
let hex = '';
const hexChars = '0123456789abcdef';
while (numbers > 0) {
const remainder = numbers % 16;
hex = hexChars[remainder] + hex;
numbers = Math.floor(numbers / 16);
}
return hex;
}
/**
* 颜色值列表
*/
export const colors = new Map<string, string>([
['primary', '#0579FF'],
['success', '#09CC54'],
['danger', '#FE6D1B'],
['warn', '#F0A22C'],
['error', '#E2001B'],
['info', '#F2F2F2'],
['kleinblue', '#002FA7'], // 克莱因蓝
['chinesered', '#FF0000'], // 中国红
['internationalorange', '#FF4F00'], // 国际橙
['egyptianvlue', '#1034A6'], // 埃及艳蓝
['parisviolet', '#6C3082'], // 巴黎紫
['moroccanblue', '#1256A7'], // 摩洛哥蓝
['brazilgreen', '#009B3A'], // 巴西绿
['britishracinggreen', '#004225'], // 英国赛车绿
['indianyellow', '#E3A857'], // 印度黄
['australiangold', '#FFDF00'], // 澳大利亚金
['venetianred', '#C80815'], // 威尼斯红
['majorelleblue', '#6050DC'], // 马若雷蓝
['tuscanred', '#7C3030'], // 托斯卡红
['naplesyellow', '#FADA5E'], // 那不勒斯黄
['capumortuum', '#592720'], // 死者之首
['mayablue', '#73C2FB'], // 玛雅蓝
['persianrose', '#FE28A2'], // 波斯玫瑰
['tyrianpurple', '#66023C'], // 泰尔紫
['saharasand', '#F1E788'], // 撒哈拉沙
['burmeseruby', '#B00A0A'], // 缅甸红宝石
['transparent', 'rgba(0,0,0,0)'],
['aliceblue', '#F0F8FF'],
['antiquewhite', '#FAEBD7'],
['aqua', '#00FFFF'],
['aquamarine', '#7FFFD4'],
['azure', '#F0FFFF'],
['beige', '#F5F5DC'],
['bisque', '#FFE4C4'],
['black', '#000000'],
['blanchedalmond', '#FFEBCD'],
['blue', '#0000FF'],
['blueviolet', '#8A2BE2'],
['brown', '#A52A2A'],
['burlywood', '#DEB887'],
['cadetblue', '#5F9EA0'],
['chartreuse', '#7FFF00'],
['chocolate', '#D2691E'],
['coral', '#FF7F50'],
['cornflowerblue', '#6495ED'],
['cornsilk', '#FFF8DC'],
['crimson', '#DC143C'],
['cyan', '#00FFFF'],
['darkblue', '#00008B'],
['darkcyan', '#008B8B'],
['darkgoldenrod', '#B8860B'],
['darkgray', '#A9A9A9'],
['darkgreen', '#006400'],
['darkkhaki', '#BDB76B'],
['darkmagenta', '#8B008B'],
['darkolivegreen', '#556B2F'],
['darkorange', '#FF8C00'],
['darkorchid', '#9932CC'],
['darkred', '#8B0000'],
['darksalmon', '#E9967A'],
['darkseagreen', '#8FBC8F'],
['darkslateblue', '#483D8B'],
['darkslategray', '#2F4F4F'],
['darkturquoise', '#00CED1'],
['darkviolet', '#9400D3'],
['deeppink', '#FF1493'],
['deepskyblue', '#00BFFF'],
['dimgray', '#696969'],
['dodgerblue', '#1E90FF'],
['firebrick', '#B22222'],
['floralwhite', '#FFFAF0'],
['forestgreen', '#228B22'],
['fuchsia', '#FF00FF'],
['gainsboro', '#DCDCDC'],
['ghostwhite', '#F8F8FF'],
['gold', '#FFD700'],
['goldenrod', '#DAA520'],
['gray', '#808080'],
['green', '#008000'],
['greenyellow', '#ADFF2F'],
['honeydew', '#F0FFF0'],
['hotpink', '#FF69B4'],
['indianred', '#CD5C5C'],
['indigo', '#4B0082'],
['ivory', '#FFFFF0'],
['khaki', '#F0E68C'],
['lavender', '#E6E6FA'],
['lavenderblush', '#FFF0F5'],
['lawngreen', '#7CFC00'],
['lemonchiffon', '#FFFACD'],
['lightblue', '#ADD8E6'],
['lightcoral', '#F08080'],
['lightcyan', '#E0FFFF'],
['lightgoldenrodyellow', '#FAFAD2'],
['lightgray', '#D3D3D3'],
['lightgreen', '#90EE90'],
['lightpink', '#FFB6C1'],
['lightsalmon', '#FFA07A'],
['lightseagreen', '#20B2AA'],
['lightskyblue', '#87CEFA'],
['lightslategray', '#778899'],
['lightsteelblue', '#B0C4DE'],
['lightyellow', '#FFFFE0'],
['lime', '#00FF00'],
['limegreen', '#32CD32'],
['linen', '#FAF0E6'],
['magenta', '#FF00FF'],
['maroon', '#800000'],
['mediumaquamarine', '#66CDAA'],
['mediumblue', '#0000CD'],
['mediumorchid', '#BA55D3'],
['mediumpurple', '#9370DB'],
['mediumseagreen', '#3CB371'],
['mediumslateblue', '#7B68EE'],
['mediumspringgreen', '#00FA9A'],
['mediumturquoise', '#48D1CC'],
['mediumvioletred', '#C71585'],
['midnightblue', '#191970'],
['mintcream', '#F5FFFA'],
['mistyrose', '#FFE4E1'],
['moccasin', '#FFE4B5'],
['navajowhite', '#FFDEAD'],
['navy', '#000080'],
['oldlace', '#FDF5E6'],
['olive', '#808000'],
['olivedrab', '#6B8E23'],
['orange', '#FFA500'],
['orangered', '#FF4500'],
['orchid', '#DA70D6'],
['palegoldenrod', '#EEE8AA'],
['palegreen', '#98FB98'],
['paleturquoise', '#AFEEEE'],
['palevioletred', '#DB7093'],
['papayawhip', '#FFEFD5'],
['peachpuff', '#FFDAB9'],
['peru', '#CD853F'],
['pink', '#FFC0CB'],
['plum', '#DDA0DD'],
['powderblue', '#B0E0E6'],
['purple', '#800080'],
['rebeccapurple', '#663399'],
['red', '#FF0000'],
['rosybrown', '#BC8F8F'],
['royalblue', '#4169E1'],
['saddlebrown', '#8B4513'],
['salmon', '#FA8072'],
['sandybrown', '#F4A460'],
['seagreen', '#2E8B57'],
['seashell', '#FFF5EE'],
['sienna', '#A0522D'],
['silver', '#C0C0C0'],
['skyblue', '#87CEEB'],
['slateblue', '#6A5ACD'],
['slategray', '#708090'],
['snow', '#FFFAFA'],
['springgreen', '#00FF7F'],
['steelblue', '#4682B4'],
['tan', '#D2B48C'],
['teal', '#008080'],
['thistle', '#D8BFD8'],
['tomato', '#FF6347'],
['turquoise', '#40E0D0'],
['violet', '#EE82EE'],
['wheat', '#F5DEB3'],
['white', '#FFFFFF'],
['whitesmoke', '#F5F5F5'],
['yellow', '#FFFF00'],
['yellowgreen', '#9ACD32']
])
export function isValidColor(color : string) : boolean {
// return chroma.valid(color);
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8})$/;
const rgbRegex = /^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/;
const rgbaRegex = /^rgba\((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s*,\s*(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s*,\s*(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s*,\s*((1(\.0{1,2})?)|(0(\.\d{1,2})?))\)$/;
const hslRegex = /^hsl\((\d{1,3}), (\d{1,3})%, (\d{1,3})%\)$/;
const hslaRegex = /^hsla\(\s*((\d{1,2}|[1-2]\d{2}|3[0-5]\d)(\.\d+)?|\d+(\.\d+)?)\s*,\s*((\d{1,2}|\d{0,1}\d{1}\d{1}|[1-2]\d{2}|3[0-5]\d)(\.\d+)?|\d+(\.\d+)?)%\s*,\s*((\d{1,2}|\d{0,1}\d{1}\d{1}|[1-2]\d{2}|3[0-5]\d)(\.\d+)?|\d+(\.\d+)?)%\s*,\s*((1|0(\.\d{1,2})?|(\.\d{1,2})))\)$/i;
if (color == '') {
return false; // 颜色值为空
} else if (color === 'inherit' || color === 'transparent') {
return false; // 特殊颜色值
} else if (color === 'currentColor') {
return false; // currentColor 不是有效的颜色值
} else if (hexRegex.test(color) || rgbRegex.test(color) || rgbaRegex.test(color) || hslRegex.test(color) || hslaRegex.test(color)) {
return true; // 符合颜色值的格式
}
return false;
}
/**
* 获取默认的颜色值
* @param [string] 颜色名称或者16进制颜色值
*/
export function getDefaultColor(sColor : string) : string {
if(sColor=="") return ""
let sc = sColor.toLocaleLowerCase().trim().replace(" ","");
if (isValidColor(sc)) {
// 如果符合所有颜色值,进行转换为16进制。css方便统一解析。
return sc;
}
// 检测是否是颜色名称。
let sco = colors.get(sc);
if (typeof sco == 'string') return sco as string;
return colors.get("primary")!;
}
/**
* 16进制转rgb
*/
export function hexToRgb(sColors : string) : RGBA {
if (sColors == "") {
return {
r: 0,
g: 0,
b: 0,
a: 1
} as RGBA
}
let reg = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8})$/;
let sColor : string = sColors.toLowerCase();
if (sColor != '' && reg.test(sColor)) {
if (sColor.length == 4) {
let sColorNew = "#";
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
}
sColor = sColorNew;
}
//处理六位的颜色值
let sColorChange : number[] = [];
sColorChange.push(parseInt(sColor.substring(1, 3), 16));
sColorChange.push(parseInt(sColor.substring(3, 5), 16));
sColorChange.push(parseInt(sColor.substring(5, 7), 16));
if (sColor.length == 9) {
sColorChange.push(parseInt(sColor.substring(7, 9), 16) / 255);
}
return {
r: sColorChange[0],
g: sColorChange[1],
b: sColorChange[2],
a: sColorChange.length == 4 ? sColorChange[3] : 1
} as RGBA
} else if (/^(rgb|RGB|rgba|RGBA)/.test(sColor)) {
let arr : string[] = sColor.replace(/(?:\(|\)|rgba|rgb|RGB|RGBA)*/g, "").split(",")
let p : number[] = arr.map((val : string) : number => parseInt(val));
if (p.length < 3) {
return {
r: 0,
g: 0,
b: 0,
a: 1
} as RGBA
}
if (p.length == 3) {
p.add(1)
}
return {
r: p[0],
g: p[1],
b: p[2],
a: arr.length==3?p[3]:parseFloat(arr[3])
} as RGBA
}
return {
r: 0,
g: 0,
b: 0,
a: 1
} as RGBA;
}
/**
* rgb转hsl
*/
export function rgbToHsl(rgb : UTSJSONObject) : UTSJSONObject {
let r = rgb.getNumber("r")
r = r as number / 255
let g = rgb.getNumber("g")
g = g as number / 255
let b = rgb.getNumber("b")
b = b as number / 255
let a = rgb.getNumber("a")
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
let h=60 * (4 + (r - g) / (max - min));
let s=(max - min) / (2 - max - min);
let l= (max + min) / 2;
if (max === r) {
h = (60 * (g - b)) / (max - min);
} else if (max === g) {
h = 60 * (2 + (b - r) / (max - min));
}
if (h < 0) {
h += 360;
}
if (max === min) {
s = 0;
} else if (l < 0.5) {
s = (max - min) / (max + min);
}
return { h: h, s: s * 100, l: l * 100, a: a };
}
/**
* hsl转rgb
*/
export function hslToRgb(hsl : UTSJSONObject) : UTSJSONObject {
let h = hsl.getNumber("h")!
let s = hsl.getNumber("s")!
let l = hsl.getNumber("l")!
let a = hsl.getNumber("a")!
h = h / 360;
s = s / 100;
l = l / 100;
let r = l;
let g = l;
let b = l;
function hue2rgb(pxx : number, q : number, txs : number) : number {
let t = txs;
let p = pxx;
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
if (s > 0) {
let q = l * (1 + s);
if (l >= 0.5) {
q = l + s - l * s;
}
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
return { r, g, b, a };
}
/**
* rgb转16进制
*/
export function rgbToHex(rgb : UTSJSONObject) : string {
let r = rgb.getNumber("r")!
let g = rgb.getNumber("g")!
let b = rgb.getNumber("b")!
let a = rgb.getNumber("a")!
return "#" + toHex(r) + toHex(g) + toHex(b) + toHex(a*255)
}
export function hslaToCss(hsl : UTSJSONObject) : string {
let rgb = hslToRgb(hsl)
return rgbToHex(rgb)
}
export function hslaToRgbCss(hsl : UTSJSONObject) : string {
let rgb = hslToRgb(hsl)
let r = rgb.getNumber("r")!
let g = rgb.getNumber("g")!
let b = rgb.getNumber("b")!
let a = rgb.getNumber("a")!
return `rgba(${r},${g},${b},${a})`
}
// 根据 HSLA 颜色计算亮度
export function getLuminance(color : UTSJSONObject, type = "rgba") : number {
let colordefault : UTSJSONObject = type == 'rgba' ? color : hslToRgb(color);
const r= colordefault.getNumber("r")!
const g= colordefault.getNumber("g")!
const b= colordefault.getNumber("b")!
const rs = r / 255
const gs = g / 255
const bs = b / 255
const rl = Math.pow(rs <= 0.03928 ? rs / 12.92 : ((rs + 0.055) / 1.055), 2.4);
const gl = Math.pow(gs <= 0.03928 ? gs / 12.92 : ((gs + 0.055) / 1.055), 2.4);
const bl = Math.pow(bs <= 0.03928 ? bs / 12.92 : ((bs + 0.055) / 1.055), 2.4);
return 0.2126 * rl + 0.7152 * gl + 0.0722 * bl;
}
/**
* 对颜色加深
*/
export function colorAddDeepen(sColor : string) : string {
let rgb = hexToRgb(getDefaultColor(sColor));
let hsl = rgbToHsl(rgb);
let l = hsl.getNumber("l")! - 5;
l = Math.max(0, Math.min(l, 100))
return hslaToRgbCss({ h: hsl.getNumber("h"), s: hsl.getNumber("s"), l, a: hsl.getNumber("a")! })
}
/**
* 对颜色提亮
*/
export function colorAddBrighten(sColor : string) : string {
let rgb = hexToRgb(getDefaultColor(sColor));
let hsl = rgbToHsl(rgb);
let l = hsl.getNumber("l")! + 10;
l = Math.max(0, Math.min(l, 100))
return rgbToHex(hslToRgb({ h: hsl.getNumber("h"), s:hsl.getNumber("s"), l, a: hsl.getNumber("a")! }))
}
/**
* 获取浅色系主题,根据颜色值计算。返回正常值和hover值。
*/
export function colorGetThint(sColor : string) {
let rgb = hexToRgb(getDefaultColor(sColor));
let hsl = rgbToHsl(rgb);
let s = hsl.getNumber("s")!;
// let l = hsl.getNumber("l");
if (Math.ceil(s) > 0) {
s = 45
}
return {
default: rgbToHex(hslToRgb({ h: hsl.getNumber("h"), s, l: 93, a: hsl.getNumber("a")! })),
hover: rgbToHex(hslToRgb({ h: hsl.getNumber("h"), s, l: 85, a: hsl.getNumber("a")! }))
}
}
/**
* 获取正常主题,根据颜色值计算。返回正常值和hover值。
*/
export function colorGetHover(sColor : string) {
return {
default: sColor,
hover: colorAddDeepen(sColor)
}
}
/**
* 处理相主题色。
* @param color 需要处理的颜色值
* @param hoverColor 对应按下去的颜色默认与color相等。
* @returns UTSJSONObject
*/
export function getDefaultColorObj(color : string, hoverColor : string) : UTSJSONObject {
let hsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hoverHsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(hoverColor)));
let shadow = hslaToRgbCss({ h: hsla.getNumber("h")!, s: (hsla.getNumber("s")!)== 0 ? 0 : 60, l: 40, a: 0.4 });
let shadowHOver = hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: (hoverHsla.getNumber("s")!)== 0 ? 0 : 60, l: 40, a: 0.4 });
if((hsla.getNumber("h")!)==0&&(hsla.getNumber("s")!)==0){
shadow = "transparent"
}
if((hoverHsla.getNumber("h")!)==0&&(hoverHsla.getNumber("s")!)==0){
shadowHOver = "transparent"
}
let o = {
default: {
background: hslaToRgbCss(hsla),
borderColor: hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l: Math.min(Math.max(0, hsla.getNumber("l")! - 5), hsla.getNumber("l")!) }),
fontColor: getLuminance(hsla, 'hsla') < 0.46 && hsla.getNumber("a")! > 0.1 ? '#ffffff' : '#000000',
shadow: shadow
},
active: {
background: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: Math.min(Math.max(0, hoverHsla.getNumber("l")! - 10), hoverHsla.getNumber("l")!) }),
borderColor: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: Math.max(Math.max(0, hoverHsla.getNumber("l")! - 10), hoverHsla.getNumber("l")!) }),
fontColor: getLuminance(hoverHsla, 'hsla') < 0.46 && hoverHsla.getNumber("a")! > 0.1 ? '#ffffff' : '#000000',
shadow: shadowHOver
}
} as UTSJSONObject
return o;
}
/**
* 处理相主题色。
* @param color 需要处理的颜色值
* @param hoverColor 对应按下去的颜色默认与color相等。
* @returns UTSJSONObject
*/
export function getTextColorObj(color : string, hoverColor : string) : UTSJSONObject {
let hsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hoverHsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(hoverColor)));
// tmxColor.hslaToCss({ ...hsla}) : tmxColor.hslaToCss({ ...hsla,l:30})
let o = {
default: {
background: "transparent",
borderColor: "transparent",
fontColor: getLuminance(hsla, 'hsla') < 0.3 && hsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hsla) : hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
},
active: {
background: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: 95 }),
borderColor:"transparent",
fontColor: getLuminance(hoverHsla, 'hsla') < 0.3 && hoverHsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hoverHsla) : hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
}
} as UTSJSONObject
return o;
}
/**
* 处理相主题色。
* @param color 需要处理的颜色值
* @param hoverColor 对应按下去的颜色默认与color相等。
* @returns UTSJSONObject
*/
export function getThinColorObj(color : string, hoverColor : string,isCoverDark?:boolean) : UTSJSONObject {
let dark = isCoverDark==null?false:isCoverDark
// #ifdef WEB
dark = isCoverDark==undefined?false:dark
// #endif
let hsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hoverHsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(hoverColor)));
// tmxColor.hslaToCss({ ...hsla}) : tmxColor.hslaToCss({ ...hsla,l:30})
let fontcolor = getLuminance(hsla, 'hsla') < 0.3 && hsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hsla) : hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l:30 })
let bordercolor = hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: dark==true?21:92 })
let bgcolor = hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: dark==true?20:95 })
if(dark){
let p = hsla;
p.set('l',90)
fontcolor = hslaToRgbCss(p)
p.set('l',20)
p.set('s',p.getNumber('s')!==0?0:20)
bgcolor = hslaToRgbCss(p)
p.set('l',22)
bordercolor = hslaToRgbCss(p)
}
let o = {
default: {
background: bgcolor,
borderColor:bordercolor,
fontColor: fontcolor,
shadow: "transparent",
},
active: {
background: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: dark==true?18:88 }),
borderColor: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a:0, l: dark==true?18:80 }),
fontColor: getLuminance(hoverHsla, 'hsla') < 0.3 && hoverHsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hoverHsla) : hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
}
} as UTSJSONObject
return o;
}
/**
* 处理相主题色。
* @param color 需要处理的颜色值
* @param hoverColor 对应按下去的颜色默认与color相等。
* @returns UTSJSONObject
*/
export function getOutlineColorObj(color : string, hoverColor : string) : UTSJSONObject {
let hsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(color)));
let hoverHsla : UTSJSONObject = rgbToHsl(hexToRgb(getDefaultColor(hoverColor)));
// tmxColor.hslaToCss({ ...hsla}) : tmxColor.hslaToCss({ ...hsla,l:30})
let o = {
default: {
background: 'transparent',
borderColor:hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: 1, l: 75 }),
fontColor: getLuminance(hsla, 'hsla') < 0.3 && hsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hsla) : hslaToRgbCss({ h: hsla.getNumber("h")!, s: hsla.getNumber("s")!, a: hsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
},
active: {
background: 'transparent',
borderColor: hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a:1, l: 70 }),
fontColor: getLuminance(hoverHsla, 'hsla') < 0.3 && hoverHsla.getNumber("a")! > 0.1 ? hslaToRgbCss(hoverHsla) : hslaToRgbCss({ h: hoverHsla.getNumber("h")!, s: hoverHsla.getNumber("s")!, a: hoverHsla.getNumber("a")!, l: 30 }),
shadow: "transparent",
}
} as UTSJSONObject
return o;
}
/**
* 设置颜色的亮度值。29
* isCoverDark:是根据亮度值,是否要设置n的值,如果n超过了
* 指定的30就不操作。
*/
export function setTextColorLightByDark(color:string):string{
let realColor = getDefaultColor(color);
let hsla = rgbToHsl(hexToRgb(realColor))
let l = hsla.getNumber('l')!
let s = hsla.getNumber('s')!
if(l<30){
if(s>0){
hsla.set('l',50)
}else{
hsla.set('l',100)
}
}
return hslaToRgbCss(hsla)
}
/**
* 变动的颜色是否需要变深。如果不需要返回原始颜色
* 如果果需要自动加深。
*/
export function setBgColorLightByDark(color:string):string{
let realColor = getDefaultColor(color);
let hsla = rgbToHsl(hexToRgb(realColor))
let l = hsla.getNumber('l')!
let s = hsla.getNumber('s')!
if(s==0&&l>50){
hsla.set('l',100-l)
}
if(s>0){
if(l>50){
hsla.set('l',100-l)
}
}
return hslaToRgbCss(hsla)
}
export function isBlackAndWhite(color:string):boolean{
let realColor = getDefaultColor(color);
let hsla = rgbToHsl(hexToRgb(realColor))
let s = hsla.getNumber('s')!
return s==0
}
@@ -0,0 +1,3 @@
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="100%" android:toYDelta="0%"
android:duration="300"/>
@@ -0,0 +1,3 @@
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="0%" android:toYDelta="100%"
android:duration="300"/>
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
android:layout_width="match_parent" android:background="#000000" android:layout_height="match_parent">
<LinearLayout android:layout_width="match_parent" android:id="@+id/surface_camera_box" android:layout_height="match_parent" android:layout_gravity="center_vertical">
<SurfaceView android:id="@+id/surface_camera" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_gravity="center_vertical">
</SurfaceView>
</LinearLayout>
<RelativeLayout android:orientation="vertical" android:layout_marginBottom="100dp" android:id="@+id/scan_box" android:layout_width="match_parent"
android:layout_height="match_parent" >
</RelativeLayout>
<RelativeLayout android:orientation="vertical"
android:layout_gravity="center_vertical"
android:id="@+id/camre_center_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</RelativeLayout>
<RelativeLayout android:orientation="vertical" android:id="@+id/relative_layout" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_gravity="top|bottom" android:orientation="vertical">
<LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"
android:gravity="top" android:orientation="horizontal">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="100dp" android:paddingLeft="32dp"
android:paddingTop="88px" android:orientation="vertical" android:gravity="center_horizontal|left"
android:id="@+id/header_top">
<!-- 这里的内容将会水平居中并底部对齐 -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textColor="#ffffff" android:textSize="16dp" android:id="@+id/header_back"
android:text="返回" />
</LinearLayout>
</LinearLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"
android:gravity="bottom" android:orientation="horizontal">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="100dp"
android:orientation="horizontal" android:background="#000000" android:gravity="center_horizontal|top"
android:id="@+id/header_bottom">
<TextView android:paddingTop="30px" android:id="@+id/footer_asml" android:height="50dp" android:layout_width="0dp" android:textAlignment="center" android:layout_weight="1" android:layout_height="wrap_content"
android:textColor="#fff" android:text="相册选取" android:textSize="16dp" />
<!-- 这里的内容将会水平居中并底部对齐 -->
<TextView android:paddingTop="30px" android:height="50dp" android:id="@+id/footer_camre" android:textAlignment="center" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content"
android:textColor="#fff" android:text="" android:textSize="16dp" />
<TextView android:paddingTop="30px" android:id="@+id/footer_scanning" android:height="50dp" android:textAlignment="center" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content"
android:textColor="#fff" android:text="扫一扫" android:textSize="16dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</FrameLayout>
@@ -0,0 +1,7 @@
import {CALL_RESULT_FUN} from "../interface.uts"
export function openCameraApi(callback : CALL_RESULT_FUN, onlyCameras : boolean) {
}
export function closeCamera() {
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>
@@ -0,0 +1,212 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Headers/unimoduleXMlkitScannigS-Swift.h</key>
<data>
HFgFRh52QVFRripRAm++m8vPUac=
</data>
<key>Headers/unimoduleXMlkitScannigS.h</key>
<data>
FH2SRnQAHIO8AbDZPa5/Bz/CcdA=
</data>
<key>Info.plist</key>
<data>
G4k0SeG744Lkgqw1b+k6ksST6dA=
</data>
<key>Modules/module.modulemap</key>
<data>
NFo72bjg30tp1kal2iM5fGM3zq8=
</data>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
<data>
pOY7RAEwyRn2iytrDpxN4AHPkRk=
</data>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
<data>
K/bnDRDf5y9AgNJoEBNi0VTphF4=
</data>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
<data>
mVavA9fh+FYY94dQ5NaeIyOJVuA=
</data>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
<data>
QEufnDNcE56gLXZxvRbTRO6726k=
</data>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
<data>
mVavA9fh+FYY94dQ5NaeIyOJVuA=
</data>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
<data>
TikwGU5EgjS4+ZdpC0krx1khEDA=
</data>
<key>config.json</key>
<data>
88FIH2zCpVE0VhuGIIM7Imjpgcc=
</data>
</dict>
<key>files2</key>
<dict>
<key>Headers/unimoduleXMlkitScannigS-Swift.h</key>
<dict>
<key>hash2</key>
<data>
E4Dgwf5RamV3mYHjKR7FONZ/4+4DSHTMNJiHRDFDXDg=
</data>
</dict>
<key>Headers/unimoduleXMlkitScannigS.h</key>
<dict>
<key>hash2</key>
<data>
XAsze1m7Z4PhyI2gj+RafZVS4xyqWjgvqraTawdVPMY=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
fUhLEoFeXfVrTN631TcXpE6uFd0Axl0ViKW+dgm9asA=
</data>
</dict>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
<dict>
<key>hash2</key>
<data>
KPKUL9/AgF4vrs7f0PSiUYH7vvw6J/2C/Kr/vuy3xQc=
</data>
</dict>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
<dict>
<key>hash2</key>
<data>
Y9riNBbHfsbD+9QW/87fL8r+aXQhImNKSiL6J9cFX3Q=
</data>
</dict>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
<dict>
<key>hash2</key>
<data>
1pT7hTGOzumJGeoX2uCvP8YdB2INlF8T9zDlXWZcDz4=
</data>
</dict>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
<dict>
<key>hash2</key>
<data>
JSEDYvJXFaJ8lmZPib1fHYRGfbS+OjRe6rsh0MJGPKM=
</data>
</dict>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
<dict>
<key>hash2</key>
<data>
1pT7hTGOzumJGeoX2uCvP8YdB2INlF8T9zDlXWZcDz4=
</data>
</dict>
<key>Modules/unimoduleXMlkitScannigS.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
<dict>
<key>hash2</key>
<data>
TfRDPS+zHSBY/geHv1Gqp4N3jBAqPIrencgsGg5vDzI=
</data>
</dict>
<key>config.json</key>
<dict>
<key>hash2</key>
<data>
w41XwHPkqBxEtGJ4WF63+7VPz1ej3515wAWG/Aou0XY=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,9 @@
{
"deploymentTarget": "12",
"dependencies-pods": [
{
"name": "GoogleMLKit/BarcodeScanning",
"version": "5.0.0"
}
]
}
@@ -0,0 +1,743 @@
import { AVCaptureSession, AVCaptureDevice, AVMediaType, AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureDeviceInput, AVCaptureInput, AVCaptureVideoDataOutput, AVCaptureConnection, AVCaptureVideoPreviewLayer, AVLayerVideoGravity, AVCaptureVideoOrientation } from 'AVFoundation';
import { PHPhotoLibrary, PHAuthorizationStatus } from 'Photos';
import { CMSampleBuffer, CMSampleBufferGetFormatDescription, CMFormatDescription, CMVideoFormatDescriptionGetDimensions } from 'CoreMedia';
import { DispatchQueue } from "Dispatch"
import { UIView, UIImage, UIApplication, UIImageView, UILabel, UIViewController, UIFontDescriptor, UIFont, NSTextAlignment, UITapGestureRecognizer, UIImagePickerController, UIImagePickerControllerDelegate, UINavigationControllerDelegate } from 'UIKit';
import { CGRect, CGFloat, CGPoint } from 'CoreFoundation';
import { Barcode, BarcodeScannerOptions, BarcodeFormat, BarcodeScanner, CMVideoDimensions } from "MLKitBarcodeScanning"
import { VisionImage } from "MLKit"
import { SCANNING_PHOTO_RESULT, SCANNING_PHOTO_RESULT_BOUND, CALL_RESULT_FUN, AUTH_CALL_BACK_TYPE } from '../interface.uts';
import { NotificationCenter, NSNotification, Notification } from 'Foundation';
import { Selector } from 'ObjectiveC';
import { Alignment } from 'ARKit';
import { CABasicAnimation } from 'QuartzCore';
type ConverXy = {
x : number,
y : number,
minx : number,
maxx : number,
miny : number,
maxy : number,
}
type contaiaSize = {
width : number,
height : number
}
type contaiaCalseSize = {
scale : number,
offsetX : number,
offsetY : number
}
type contaiPointXy = {
x : number,
y : number
}
// 资料参考:
// https://github.com/JimmyPun610/BarcodeScanner.Mobile/blob/master/BarcodeScanner.Mobile.Maui/Platforms/iOS/CaptureVideoDelegate.cs
// 识别结果
let scanResult = [] as Swift.Array<Barcode>
let captureSession : null | AVCaptureSession = null;
let captureDeviceAllGlobal : null | AVCaptureDevice = null;
let FlashButton:null|UILabel = null;
let flashModel = false;
// 视频宽
let videoWidth = 0
// 视频高
let videoHeight = 0
// 图像捕捉事件代理。
let captureOutDelegate : CaptureOutSessionBuffer | null = null;
let pointsEvents = [] as PointClickEvent[]
let isCamreaing = false;
let pointView = [] as UIView[]
let onlyCamera : boolean = false;
let barcodeOptions = BarcodeScannerOptions(formats = BarcodeFormat.all)
let parentView : UIView | null = null
let camreaViewLayer : UIView | null = null
let pointsLayerView : UIView | null = null
let imageUiView : UIImageView | null = null
let cameraView : AVCaptureVideoPreviewLayer | null = null
let backevents : BackEvents | null = null;
let photosevents : PhotoSelectedEvents | null = null;
let scanevents : ScanEvents | null = null;
let flashBtnevents:FlashBtnEvents|null = null;
let rsultCallBack : CALL_RESULT_FUN = (str : string) => { }
let delegate : null | useImagePickerDelegate = null;
let openAnimation = true;
/** 加上这个标签可以持续响应事件,回调执行时必要的标签. */
@MainActor
class useImagePickerDelegate implements UIImagePickerControllerDelegate, UINavigationControllerDelegate {
call : (image : any | null) => void;
constructor(call : (image : any | null) => void) {
this.call = call;
}
imagePickerController(controller : UIImagePickerController, @argumentLabel("didFinishPickingMediaWithInfo") info : Map<UIImagePickerController.InfoKey, any>) {
let image = info.get(UIImagePickerController.InfoKey.originalImage)
this.call(image)
controller.dismiss(animated = true, completion = null)
}
imagePickerControllerDidCancel(controller : UIImagePickerController) {
this.call(null)
controller.dismiss(animated = true, completion = null)
}
}
class PointClickEvent {
private callback : (txt : string) => void
private text : string;
constructor(callback : (txt : string) => void, atext : string) {
this.callback = callback;
this.text = atext;
super.init()
}
@objc onclick() {
this.callback(this.text)
}
}
class BackEvents {
private callback : () => void
constructor(callback : () => void) {
this.callback = callback
super.init()
}
@objc onclick() {
this.callback()
}
}
class PhotoSelectedEvents {
private callback : () => void
constructor(callback : () => void) {
this.callback = callback
super.init()
}
@objc onclick() {
this.callback()
}
}
class ScanEvents {
private callback : () => void
constructor(callback : () => void) {
this.callback = callback
super.init()
}
@objc onclick() {
this.callback()
}
}
class FlashBtnEvents {
private callback : () => void
constructor(callback : () => void) {
this.callback = callback
super.init()
}
@objc onclick() {
this.callback()
}
}
class CaptureOutSessionBuffer implements AVCaptureVideoDataOutputSampleBufferDelegate {
callback : (str : SCANNING_PHOTO_RESULT) => void = (str : SCANNING_PHOTO_RESULT) => { }
constructor(dv : (str : SCANNING_PHOTO_RESULT) => void) {
this.callback = dv;
super()
}
captureOutput(output : AVCaptureOutput, @argumentLabel("didOutput") sampleBuffer : CMSampleBuffer, @argumentLabel("from") connection : AVCaptureConnection) {
if (!isCamreaing) return;
// console.log('图片捕捉正确')
let image = new VisionImage(buffer = sampleBuffer)
// 这里会直接影响到测量的起始点。下面是镜像,测量是图片左上为起始,但设备是竖向,因此计算时的x,y坐标其实是相对屏幕向右转下,x,y是反向的,因为是镜像
// 所以计算位置时,x,y是反的,宽和高也是相反的。
image.orientation = UIImage.Orientation.right
let barcodeOptions = BarcodeScannerOptions(formats = BarcodeFormat.all)
// options=barcodeOptions
let barcodeScanner = new BarcodeScanner.barcodeScanner(options = barcodeOptions)
barcodeScanner.process(image, completion = (features : Swift.Array<Barcode> | null, error) => {
if (features == null || error != null) {
return;
}
if (features!.length == 0 || scanResult.length > 0) {
return;
}
// 暂停识别。
captureSession!.stopRunning()
scanResult = features!
let descVideo : CMFormatDescription | null = CMSampleBufferGetFormatDescription(sampleBuffer);
if (descVideo != null) {
let dimesionsize : CMVideoDimensions = CMVideoFormatDescriptionGetDimensions(descVideo!)
videoWidth = Number(dimesionsize.width)
videoHeight = Number(dimesionsize.height)
}
let listpos = [] as number[][][]
let bounedlist = [] as SCANNING_PHOTO_RESULT_BOUND[]
let rulstText = [] as string[]
for (let i = 0; i < features!.length; i++) {
let barcode = features![i]
// 位置信息
let bounds = barcode.frame
// 坐标
let corners = barcode.cornerPoints
// 解析的内容
let rawValue = barcode.rawValue
let iminfo = {
width: Number(bounds.width),
height: Number(bounds.height),
centerX: Number(bounds.midX),
centerY: Number(bounds.midY)
} as SCANNING_PHOTO_RESULT_BOUND
let posinfo = [] as number[][]
for (pos in corners!) {
// pos是pointValue类型并返回CGPoint,其中有x,y
let ponit = pos.cgPointValue
posinfo.push([Number(ponit.x), Number(ponit.y)] as number[])
}
listpos.push(posinfo)
bounedlist.push(iminfo)
rulstText.push(rawValue == null ? '' : (rawValue!))
}
let res = {
url: '',
position: listpos,
bounds: bounedlist,
text: rulstText,
imgWidth:0,
imgHeight:0
} as SCANNING_PHOTO_RESULT
this.callback(res)
})
}
}
// 检查权限
export function isCheckPermissions(callFun : null | AUTH_CALL_BACK_TYPE) {
let realcall : AUTH_CALL_BACK_TYPE = (_ : boolean) => { };
if (callFun != null) {
let cllfunFun = callFun!;
realcall = cllfunFun
}
if (!onlyCamera) {
PHPhotoLibrary.requestAuthorization((isok : PHAuthorizationStatus) => {
// 拒绝了相册选取权限。
if (isok != PHAuthorizationStatus.authorized) {
realcall(false)
return;
}
AVCaptureDevice.requestAccess(for = AVMediaType.video, completionHandler = (isok2 : boolean) => {
if (!isok2) {
realcall(false)
return;
}
realcall(true)
})
})
} else {
AVCaptureDevice.requestAccess(for = AVMediaType.video, completionHandler = (isok2 : boolean) => {
if (!isok2) {
realcall(false)
return;
}
realcall(true)
})
}
}
function xCamreaInit() {
let t = this;
let sys = uni.getSystemInfoSync();
if (parentView != null) {
parentView!.removeFromSuperview()
}
parentView = new UIView();
let scaleAni = new CABasicAnimation(keyPath = "transform.scale")
scaleAni.fromValue = 0
scaleAni.toValue = 1
scaleAni.duration = 0.25
parentView!.frame = new CGRect(x = 0, y = 0, width = (sys.windowWidth).toInt(), height = (sys.screenHeight).toInt())
parentView!.backgroundColor = UTSiOS.colorWithString("#000000")
let parent = UTSiOS.getCurrentViewController() as UIViewController;
let windUiview = parent.view as UIView
windUiview.addSubview(parentView!)
let safeTop = sys.statusBarHeight + 50
let safeHeight = sys.screenHeight - 100 - sys.statusBarHeight - 50
// 绘制界面
// 创建一张图片预览层。
imageUiView = new UIImageView();
imageUiView!.frame = new CGRect(x = 0, y = safeTop.toInt(), width = (sys.windowWidth).toInt(), height = (safeHeight).toInt())
imageUiView!.isHidden = true;
parentView!.addSubview(imageUiView!);
// 创建小绿点层.
pointsLayerView = new UIView()
pointsLayerView!.frame = new CGRect(x = 0, y = safeTop.toInt(), width = (sys.windowWidth).toInt(), height = (safeHeight).toInt())
// 创建视频预览层。
camreaViewLayer = new UIView()
camreaViewLayer!.frame = new CGRect(x = 0, y = safeTop.toInt(), width = (sys.windowWidth).toInt(), height = (safeHeight).toInt())
parentView!.addSubview(camreaViewLayer!);
cameraView = new AVCaptureVideoPreviewLayer(session = captureSession!)
// 设置预览层绑定
// 下面的缩放会居中显示,因此左顶点可能在左上方
cameraView!.videoGravity = AVLayerVideoGravity.resizeAspectFill
cameraView!.connection!.videoOrientation = AVCaptureVideoOrientation.portrait
cameraView!.frame = new CGRect(x = 0, y = 0, width = (sys.windowWidth).toInt(), height = (safeHeight).toInt())
DispatchQueue.main.async(execute = () : void => {
// 绑定渲染层。
camreaViewLayer!.layer.addSublayer(cameraView!)
captureSession!.startRunning()
// 创建一个上遮盖板。
// let toplayer = new UIView()
// toplayer.frame = new CGRect(x = 0, y = 0, width = (sys.windowWidth).toInt(), height = 100)
// toplayer.backgroundColor = UTSiOS.colorWithString("rgba(255,0,0,0)")
// parentView!.addSubview(toplayer);
// 创建一个下遮盖板。
let bottomlayer = new UIView()
bottomlayer.frame = new CGRect(x = 0, y = 0, width = (sys.windowWidth).toInt(), height = 100)
bottomlayer.backgroundColor = UTSiOS.colorWithString("#000000")
parentView!.addSubview(bottomlayer);
// 创建一个返回按钮文本。
let backButton = new UILabel()
backButton.frame = new CGRect(x = 16, y = 58, width = (sys.windowWidth - 16).toInt(), height = 32)
backButton.text = String("返回")
backButton.font = UIFont.systemFont(ofSize = new CGFloat(16))
backButton.textColor = UTSiOS.colorWithString("#FFFFFF")
parentView!.addSubview(backButton);
// 创建一个相册选取按钮
let photoBtn = new UILabel()
photoBtn.frame = new CGRect(x = 16, y = (sys.screenHeight - 88).toInt(), width = 80, height = 32)
photoBtn.text = String(onlyCamera ? "" : "相册选取")
photoBtn.font = UIFont.systemFont(ofSize = new CGFloat(16))
photoBtn.textColor = UTSiOS.colorWithString("#FFFFFF")
parentView!.addSubview(photoBtn);
// 创建个闪光灯打开和关闭的文本层.
let flashBtn = new UILabel()
flashBtn.frame = new CGRect(x = ((sys.windowWidth - 80)/2).toInt(), y = (sys.screenHeight - 88).toInt(), width = 80, height = 32)
flashBtn.text = String("开灯")
flashBtn.font = UIFont.systemFont(ofSize = new CGFloat(16))
flashBtn.textAlignment = NSTextAlignment.center
flashBtn.textColor = UTSiOS.colorWithString("#FFFFFF")
parentView!.addSubview(flashBtn);
FlashButton = flashBtn;
// 创建一个继续扫一扫的按钮
let scanBtn = new UILabel()
scanBtn.frame = new CGRect(x = (sys.windowWidth - 96).toInt(), y = (sys.screenHeight - 88).toInt(), width = 80, height = 32)
scanBtn.text = String("扫一扫")
scanBtn.font = UIFont.systemFont(ofSize = new CGFloat(16))
scanBtn.textAlignment = NSTextAlignment.right
scanBtn.textColor = UTSiOS.colorWithString("#FFFFFF")
parentView!.addSubview(scanBtn);
// 最高层级.
parentView!.addSubview(pointsLayerView!);
// 添加事件。
// 返回,关闭弹层。
backevents = new BackEvents(() => {
closeScan();
quitView();
})
// 点击了相册选取。
function setToXiangChe() {
if (onlyCamera) {
return;
}
let pic = new UIImagePickerController()
DispatchQueue.main.async(execute = () : void => {
delegate = new useImagePickerDelegate((image : any | null) => {
if (image != null) {
let realimageds = image! as UIImage;
closeScan();
imageUiView!.image = realimageds
imageUiView!.contentMode = UIView.ContentMode.scaleAspectFit
camreaViewLayer!.isHidden = true;
imageUiView!.isHidden = false;
// 图片识别码.
let visionImage = new VisionImage(image = realimageds)
visionImage.orientation = realimageds.imageOrientation
let barcodeOptions = BarcodeScannerOptions(formats = BarcodeFormat.all)
let barcodeScanner = new BarcodeScanner.barcodeScanner(options = barcodeOptions)
barcodeScanner.process(visionImage, completion = (features : Swift.Array<Barcode> | null, error) => {
if (features == null || error != null) {
return;
}
if (features!.length == 0) {
return;
}
scanResult = features!
videoWidth = Number(realimageds.size.width);
videoHeight = Number(realimageds.size.height);
let listpos = [] as number[][][]
let bounedlist = [] as SCANNING_PHOTO_RESULT_BOUND[]
let rulstText = [] as string[]
for (let i = 0; i < features!.length; i++) {
let barcode = features![i]
// 位置信息
let bounds = barcode.frame
// 坐标
let corners = barcode.cornerPoints
// 解析的内容
let rawValue = barcode.rawValue
let iminfo = {
width: Number(bounds.width),
height: Number(bounds.height),
centerX: Number(bounds.midX),
centerY: Number(bounds.midY)
} as SCANNING_PHOTO_RESULT_BOUND
let posinfo = [] as number[][]
for (pos in corners!) {
// pos是pointValue类型并返回CGPoint,其中有x,y
let ponit = pos.cgPointValue
posinfo.push([Number(ponit.x), Number(ponit.y)] as number[])
}
listpos.push(posinfo)
bounedlist.push(iminfo)
rulstText.push(rawValue == null ? '' : (rawValue!))
}
let result = {
url: '',
position: listpos,
bounds: bounedlist,
text: rulstText,
imgWidth:0,
imgHeight:0
} as SCANNING_PHOTO_RESULT
let deg = 0;
if (realimageds.imageOrientation == UIImage.Orientation.right) {
deg = 90
} else if (realimageds.imageOrientation == UIImage.Orientation.up) {
deg = 0
} else if (realimageds.imageOrientation == UIImage.Orientation.left) {
deg = 270
} else if (realimageds.imageOrientation == UIImage.Orientation.down) {
deg = 180
}
console.log(deg)
createPointView(result, deg)
uni.setStorageSync("xMlKITScaning", JSON.stringify(result.text))
// t.$emit("scan")
if (result.text.length == 1) {
rsultCallBack(result.text[0])
closeScan()
quitView()
}
})
}
});
pic.sourceType = UIImagePickerController.SourceType.photoLibrary
pic.mediaTypes = ["public.image"]
pic.delegate = delegate!
UTSiOS.getCurrentViewController().present(pic, animated = true, completion = nil)
})
}
photosevents = new PhotoSelectedEvents(() => {
setToXiangChe();
})
scanevents = new ScanEvents(() => {
// 继续扫一扫
openAnimation = false
startCamera();
})
flashBtnevents = new FlashBtnEvents(() => {
// 开头灯光
flashModel = !flashModel
setFlashModel(flashModel)
flashBtn.text = flashModel?"关灯":String("开灯")
})
// 返回的事件。
backButton.isUserInteractionEnabled = true
let backevents_rz = new UITapGestureRecognizer()
backevents_rz.addTarget(backevents, action = new Selector("onclick"))
backButton.addGestureRecognizer(backevents_rz)
// 相册 按钮事件。。
let photosevents_rz = new UITapGestureRecognizer()
photosevents_rz.addTarget(photosevents, action = new Selector("onclick"))
photoBtn.addGestureRecognizer(photosevents_rz)
photoBtn.isUserInteractionEnabled = true
// 扫一扫 按钮事件。。
scanBtn.isUserInteractionEnabled = true
let scanevents_rz = new UITapGestureRecognizer()
scanevents_rz.addTarget(scanevents, action = new Selector("onclick"))
scanBtn.addGestureRecognizer(scanevents_rz)
//打开灯光事件.setFlashModel
flashBtn.isUserInteractionEnabled = true
let flashBtnevents_rz = new UITapGestureRecognizer()
flashBtnevents_rz.addTarget(flashBtnevents, action = new Selector("onclick"))
flashBtn.addGestureRecognizer(flashBtnevents_rz)
if (openAnimation) {
// 增加显示动画.
parentView!.layer.add(scaleAni, forKey = "scaleAnimation")
}
// ios识别太快了,图像还没定焦就识别出来了,稍微延迟下,不要太快了。与安卓对齐 下。
setTimeout(function () {
isCamreaing = true;
}, 300);
})
}
function calcPoint(c_w : number, c_h : number, image_w : number, image_h : number, image_point_x : number, image_point_y : number, degration : number) : ConverXy {
function scaleAndCenterContent(
container : contaiaSize,
content : contaiaSize
) : contaiaCalseSize {
// 计算缩放比例
const scaleWidth = container.width / content.width;
const scaleHeight = container.height / content.height;
const scale = Math.min(scaleWidth, scaleHeight);
// 计算居中偏移量
const offsetX = (container.width - content.width * scale) / 2;
const offsetY = (container.height - content.height * scale) / 2;
return { scale, offsetX, offsetY } as contaiaCalseSize;
}
function mapContentCoordinatesToContainer(
contentX : number,
contentY : number,
calcsize : contaiaCalseSize
) : contaiPointXy {
// 映射内容层坐标到容器层坐标
const containerX = contentX * calcsize.scale + calcsize.offsetX;
const containerY = contentY * calcsize.scale + calcsize.offsetY;
return { x: containerX, y: containerY } as contaiPointXy;
}
let rc = scaleAndCenterContent(
{ width: c_w, height: c_h } as contaiaSize,
{ width: image_h, height: image_w } as contaiaSize
)
if (degration == 0) {
rc = scaleAndCenterContent(
{ width: c_w, height: c_h } as contaiaSize,
{ width: image_w, height: image_h } as contaiaSize
)
}
let result = mapContentCoordinatesToContainer(image_point_y, image_point_x, rc)
if (degration == 0) {
result = mapContentCoordinatesToContainer(image_point_x, image_point_y, rc)
}
let nexyxcov = {
x: c_w - result.x, y: result.y, minx: 0, maxx: 0, miny: 0, maxy: 0
} as ConverXy
if (degration == 0) {
nexyxcov = {
x: result.x, y: result.y, minx: 0, maxx: 0, miny: 0, maxy: 0
} as ConverXy
}
return nexyxcov
}
function createPointView(result : SCANNING_PHOTO_RESULT, degration : number) {
let realCgSize = camreaViewLayer!.frame as CGRect;
let realWidth = Number(realCgSize.width);
let realHeight = Number(realCgSize.height);
clearPointView();
let pointSize = 30
DispatchQueue.main.async(execute = () : void => {
for (let i = 0; i < result.bounds.length; i++) {
let text = result.text[i]
let bounds = result.bounds[i]
let pos = result.position[i];
let resutxy = calcPoint(realWidth, realHeight, videoWidth, videoHeight, bounds.centerX, bounds.centerY, degration)
let x = resutxy.x - 15
let y = resutxy.y - 15
let rect = new CGRect(x = x.toInt(), y = y.toInt(), width = pointSize.toInt(), height = pointSize.toInt())
let pointViewSelf = new UIView();
pointViewSelf.layer.frame = rect;
pointViewSelf.backgroundColor = UTSiOS.colorWithString('#21d429');
pointViewSelf.layer.borderWidth = 2
pointViewSelf.layer.borderColor = UTSiOS.colorWithString('#FFFFFF').cgColor;
pointViewSelf.layer.cornerRadius = new CGFloat(pointSize / 2);
let onlick = new PointClickEvent((atext : string) => {
rsultCallBack(atext)
closeScan()
quitView()
}, text);
pointsEvents.push(onlick)
let tapGestureCancel = new UITapGestureRecognizer()
tapGestureCancel.addTarget(onlick, action = new Selector("onclick"))
pointViewSelf.addGestureRecognizer(tapGestureCancel)
pointsLayerView!.addSubview(pointViewSelf)
pointView.push(pointViewSelf)
}
})
}
/** 手动开启和关闭闪光灯 */
function setFlashModel(flash : boolean) {
if (captureDeviceAllGlobal != null) {
let islock = UTSiOS.try(captureDeviceAllGlobal!.lockForConfiguration(), "?")
if (islock != null) {
captureDeviceAllGlobal!.torchMode = flash ? AVCaptureDevice.TorchMode.on : AVCaptureDevice.TorchMode.off
captureDeviceAllGlobal!.unlockForConfiguration()
}
}
}
function clearPointView() {
DispatchQueue.main.async(execute = () : void => {
for (let i = 0; i < pointView.length; i++) {
let item = pointView[i];
item.removeFromSuperview();
}
pointView = [] as UIView[]
})
}
function closeScan() {
// 移除点。
clearPointView()
if (captureSession != null) {
captureSession!.stopRunning()
for (input in captureSession!.inputs) {
captureSession!.removeInput(input)
}
for (output in captureSession!.outputs) {
captureSession!.removeOutput(output)
}
captureSession = null;
}
scanResult = [] as Swift.Array<Barcode>
pointsEvents = [] as PointClickEvent[]
captureDeviceAllGlobal = nil;
FlashButton = nil;
}
function startCamera() {
isCamreaing = false;
closeScan();
captureSession = new AVCaptureSession();
let captureDevice = AVCaptureDevice.default(for = AVMediaType.video)
let captureOut = new AVCaptureVideoDataOutput();
captureSession!.sessionPreset = AVCaptureSession.Preset.photo
captureOutDelegate = new CaptureOutSessionBuffer((result : SCANNING_PHOTO_RESULT) => {
createPointView(result, 90)
uni.setStorageSync("xMlKITScaning", JSON.stringify(result.text))
// t.$emit("scan")
if (result.text.length == 1) {
rsultCallBack(result.text[0])
setTimeout(function () {
closeScan()
quitView()
}, 150);
}
})
// 新建设备的输入设备
let inputDevice = UTSiOS.try(new AVCaptureDeviceInput(device = captureDevice!), "?")
if (inputDevice != null) {
// 添加输入到会话中。
captureSession!.addInput(inputDevice!)
}
captureOut.alwaysDiscardsLateVideoFrames = true;
// 设置输出会话
captureOut.setSampleBufferDelegate(captureOutDelegate!, queue = DispatchQueue.global())
if (captureSession!.canAddOutput(captureOut)) {
captureSession!.addOutput(captureOut)
// console.log("绑定输出层正确!")
}
xCamreaInit();
captureDeviceAllGlobal = captureDevice
}
function quitView() {
if (parentView != null) {
parentView!.removeFromSuperview()
}
}
export function openCameraApi(callback : CALL_RESULT_FUN, onlyCameras : boolean) {
rsultCallBack = callback
openAnimation = true;
onlyCamera = onlyCameras
startCamera();
}
export function closeCamera() {
closeScan();
quitView();
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>我们需要您的相机权限来拍照或录像。</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要使用你的相册进行选择视频及图片</string>
</dict>
</plist>
@@ -0,0 +1,71 @@
export type SCANNING_PHOTO_RESULT_BOUND = {
width:number,
height:number,
centerX:number,
centerY:number
}
export type SCANNING_PHOTO_RESULT = {
url:string,//图片地址
position:number[][][],
bounds:SCANNING_PHOTO_RESULT_BOUND[],
text:string[],
imgWidth:number,
imgHeight:number
}
export type CAMERA_PHOTO_SIZE = {
width:number,
height:number
}
export type CALL_RESULT_FUN= (str:string) => void;
export type Call_BACK = (str:string,type : string) => void;
export type Call_BACK_TYPE ='auto' | 'click'
export type AUTH_CALL_BACK_TYPE = (auth:boolean)=>void;
export type BARCODEFORMAT = 'AZTEC'
| 'CODABAR'
| 'CODE_39'
| 'CODE_93'
| 'CODE_128'
| 'DATA_MATRIX'
| 'EAN_8'
| 'EAN_13'
| 'ITF'
| 'MAXICODE'
| 'PDF_417'
| 'QR_CODE'
| 'RSS_14'
| 'RSS_EXPANDED'
| 'UPC_A'
| 'UPC_E'
| 'UPC_EAN_EXTENSION';
export type SCANNING_OPTIONS = {
width:number,
height:number,
//前置还是后置摄像头
cameraDevice:'FRONT'|'BACK',
/** 放大倍数1-10效果要看你的设备,并一定有用。越小视频越宽,越大视野越小。 */
zoom?:number,
// 是否连续扫码识别,相对的间隔如果是自动的话根据设备性能一般是50-300ms之间
// 默认为false即单次识别就停止需要手动继续识别
continuousScan?:boolean,
//识别成功后是否震动设备
vibatar?:boolean,
//单位毫秒,扫描识别间隔/及抓拍间隔
scanSpeedduration?:450,
//是否打开识别码的功能,暂不支持设置,默认就是识别功能,等我完善
scanningQrCode?:boolean,
//扫码成功后是否保存图片,暂未实现,等我完善
isSavePhoto?:boolean,
/**
* 开识别条码的类型
* 如果需要指定识别类型的条码时有用。比如快递单上有多个码制
* 如果指定为条码,就可以避免识别二维码了。
* ,暂不支持设置,等我完善
*/
qrCodeType?:BARCODEFORMAT|null
}
@@ -0,0 +1,20 @@
import { SCANNING_PHOTO_RESULT, SCANNING_PHOTO_RESULT_BOUND, Call_BACK, AUTH_CALL_BACK_TYPE } from '../interface.uts';
var qre;
var resultQr = ""
// https://www.npmjs.com/package/qr-scanner/v/1.4.2?activeTab=readme
export function closeCamera() {
}
export function openCameraApi(callback : Call_BACK,onlyCameras:boolean) {
wx.scanCode({
onlyFromCamera:onlyCameras,
success(res){
if(res?.result){
callback(res?.result??"","auto")
}
},
fail(er){
console.error(er)
}
})
}
@@ -0,0 +1,241 @@
export { SCANNING_PHOTO_RESULT, SCANNING_PHOTO_RESULT_BOUND, Call_BACK, AUTH_CALL_BACK_TYPE } from '../interface.uts';
import Zxing from "./zxing.js"
var qre;
var resultQr = ""
var cid
// https://www.npmjs.com/package/qr-scanner/v/1.4.2?activeTab=readme
export function closeCamera() {
let parentView = document.getElementById("xScannigDiv")
if (parentView) {
parentView.remove()
}
if (qre) {
qre.getTracks().forEach(function (track) {
track.stop();
});
}
resultQr = ""
}
// @ts-ignore
export function openCameraApi(callback : Call_BACK,onlyCameras:boolean) {
qre = null;
resultQr = ""
let onleyCamrae = onlyCameras
scanning()
function scanning() {
closeCamera();
let parenView : HTMLElement = document.createElement("div")
parenView.id = 'xScannigDiv';
parenView.style.position = "fixed"
parenView.style.left = "0px"
parenView.style.top = "0px"
parenView.style.width = "100%"
parenView.style.height = "100%"
parenView.style.zIndex = "888"
parenView.style.backgroundColor = "#000"
parenView.style.display = "flex"
parenView.style.flexDirection = "column"
parenView.style.transitionDuration = '0.25s'
parenView.style.transitionProperty = 'transform'
parenView.style.transitionTimingFunction = 'linear'
parenView.style.transform = 'scale(0)'
let backText = document.createElement("div")
backText.innerText = "返回"
backText.style.fontSize = "16px"
backText.style.height = "50px"
backText.style.lineHeight = "50px"
backText.style.padding = "0px 20px"
backText.style.color = "white"
backText.onclick = function () {
closeCamera()
}
let video : HTMLVideoElement = document.createElement("video")
video.style.flex = "1"
video.style.backgroundColor = '#111'
video.style.width = '100%'
video.style.objectFit = 'cover'
video.playsInline = true;
video.muted = true;
video.autoplay = true;
video.controls = false;
// 针对微信的x5内核
video.setAttribute('playsinline',true)
video.setAttribute('webkit-playsinline',true)
video.setAttribute('x5-playsinline',true)
video.setAttribute('x5-video-player-type','h5')
video.setAttribute('x5-video-player-fullscreen','false')
let bottomView = document.createElement("div")
bottomView.style.height = "100px"
bottomView.style.display = "flex"
bottomView.style.flexDirection = "row"
bottomView.style.justifyContent = "space-between"
let photo = document.createElement("div")
photo.innerText = onleyCamrae?"":"相册选取"
photo.style.fontSize = "16px"
photo.style.height = "50px"
photo.style.lineHeight = "50px"
photo.style.padding = "0px 20px"
photo.style.color = "white"
let scan = document.createElement("div")
scan.innerText = "扫一扫"
scan.style.fontSize = "16px"
scan.style.height = "50px"
scan.style.lineHeight = "50px"
scan.style.padding = "0px 20px"
scan.style.color = "white"
photo.onclick = function () {
if(onleyCamrae) return;
uni.chooseImage({
sourceType:["album"],
success(res) {
if (res.tempFilePaths.length > 0) {
let img = new Image()
img.src = res.tempFilePaths[0];
img.onload = function () {
closeCamera();
var newWidth = 600;
// @ts-ignore
var newHeight = newWidth*img.width / img.height;
var canvas = document.createElement('canvas');
canvas.width = 600;
canvas.height = 600;
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, newWidth, newHeight)
ctx.drawImage(img, 0, 0, newWidth, newHeight)
var imageData = ctx.getImageData(0, 0, 600, 600);
let code = new Zxing(imageData.data,600,600,{inversionAttempts:true})
if(code&&code?.data){
callback(code.data)
resultQr = ""
}
}
}
}
})
}
bottomView.appendChild(photo)
bottomView.appendChild(scan)
parenView.appendChild(backText)
parenView.appendChild(video)
parenView.appendChild(bottomView)
document.body.appendChild(parenView)
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
var constraints = {
audio: false,
video: {
width: 800,
height: 800,
facingMode:"environment"
}
};
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
video.srcObject = stream;
qre = stream
video.play();
var newWidth = 800;
var newHeight = newWidth*video.offsetWidth / video.offsetHeight;
canvas.width = newWidth
canvas.height = newHeight
function readFrame() {
ctx.clearRect(0,0,newWidth,newWidth)
ctx.drawImage(video, 0, 0, newWidth, newHeight);
const frameData = ctx.getImageData(0, 0, newWidth, newWidth);
let code = Zxing(frameData.data,newWidth,newWidth,{inversionAttempts:true})
if(code&&code?.data&&resultQr==''){
resultQr = code.data
callback(code.data)
cancelAnimationFrame(cid)
closeCamera()
}
cid = requestAnimationFrame(readFrame);
}
// 开始读取帧
readFrame();
})
.catch(error => {
console.error('Error accessing media devices:', error);
});
// navigator.mediaDevices.getUserMedia({ video: true })
// .then(stream => {
// video.srcObject = stream;
// t.qre = stream
// video.play();
// // 设置 canvas 用于读取视频帧
// const canvas = document.createElement('canvas');
// canvas.style.width = video.offsetWidth + 'px'
// canvas.style.height = video.offsetHeight + 'px'
// canvas.width = video.offsetWidth
// canvas.height = video.offsetHeight
// const ctx = canvas.getContext('2d');
// function readFrame() {
// ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// const frameData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// let p = new Zxing()
// let qrd = new p()
// qrd.callback = function (err, value) {
// if (resultQr == '' && value?.result) {
// resultQr = value.result
// t.$emit("scan", [value.result])
// cancelAnimationFrame(t.cid)
// }
// }
// qrd.decode(frameData);
// t.cid = requestAnimationFrame(readFrame);
// }
// // 开始读取帧
// readFrame();
// })
// .catch(error => {
// console.error('Error accessing media devices:', error);
// });
// qre = new QrScanner(
// video,
// function (result) {
// console.log(result)
// qre.stop()
// video.pause();
// resultQr = result;
// callback(result)
// closeCamera();
// qre = null;
// }
// );
// qre.start()
setTimeout(function() {
parenView.style.transform = 'scale(1)'
}, 50);
}
}
File diff suppressed because it is too large Load Diff