1
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
## 1.1.2(2025-08-17)
|
||||
* 针对微信的浏览器内核设定相关属性,以防止全屏脱离文档。
|
||||
## 1.1.1(2025-02-13)
|
||||
* web,和微信相册识别已经改版本,能识别大多数码,效率有所提升。因为采用像素解析识别码,因此web,微信它是可以通用的,并且不依赖微信官方本身,所以
|
||||
* 你现在在微信端也能独立的识别远程或者静态图片码,而不需要调用uni.scan官方自带的接口。同样微信和web图片识别都是只能单码识别
|
||||
## 1.1.0(2024-12-18)
|
||||
* 新增属性flash默认是否开启闪光灯.后续如果需要动态打开 和 关闭闪光请使用ref方法:setFlashModel(boolean)
|
||||
## 1.0.9(2024-09-12)
|
||||
* 优化和修改了核心识别,调整了安卓识别页面兼容华为部分机型.
|
||||
## 1.0.8(2024-08-31)
|
||||
* 新增ref函数chooseImage,相册选取识别
|
||||
## 1.0.7(2024-08-11)
|
||||
* 优化改善识别速率,提高安卓兼容低版本性。优化ios。
|
||||
* 现在安卓和ios允许任意尺寸嵌入页面识别了。
|
||||
## 1.0.6(2024-07-30)
|
||||
升级支持ios扫码功能,ios最低为ios12+
|
||||
升级支持H5(手机端)/WEB(带摄像头的win电脑及mac电脑)
|
||||
## 1.0.5(2024-05-4)
|
||||
* 升级后,原先的api跳转界面已经没有了,因为改成嵌入页面扫码,布局更为方便,可以自行任意装饰和布局。
|
||||
## 1.0.4(2024-01-27)
|
||||
1. 预览区域调整缩小一点,
|
||||
2. 捕捉的图像再次放小一点。
|
||||
3. 扫描速率调整到500ms
|
||||
如果你是有特定机器上有问题,请自己通过修改uts源码调整上面的这些参数,将影响识别速度,精准度,捕捉位置优化等可以自己通过源码调整。
|
||||
## 1.0.3(2024-01-26)
|
||||
修改焦距为持续视频对焦模式,并拉近焦距(相当于视野变小了,之前是宽视野可以同时识别出10几个码,但现实中没必要这样,因此根据其它的应用参考,应该拉近点焦距)。
|
||||
对焦点识别的位置进行了优化比对,识别时位置更准,但不能保证非常准确。比如手机识别慢100ms左右,你手抖了下,可能位置是前100ms的位置
|
||||
会造成识别的位置和预览图可能有点区别。但经过持续的测试,红米note11 pro ,2年前的手机,基本很少出现对焦识别失准的情况。
|
||||
我把识别速度调整为1秒识别一次,对低端机可能更好。如果想识别非常的迅速可以调低这个值,比如100ms,基本一对准就识别了,精准度非常高。
|
||||
识别的震动改为了80ms,之前是200ms,感觉过长。
|
||||
识别成功后增加了黑色遮罩。
|
||||
另外识别程序放在了子线程中执行了,防止偶尔的卡钝。
|
||||
|
||||
**特别提醒,网友测试如果你是开发模式部分手机可能会比较卡,原因不明,但打包后的程序是非常流畅无比的**
|
||||
## 1.0.2(2024-01-25)
|
||||
1. 增加了识别成功后,短震动提示
|
||||
2. 识别多点后,给提示点增加了动画缩放指示提醒用户点按识别点。
|
||||
## 1.0.1(2024-01-24)
|
||||
增加返回按钮事件
|
||||
## 1.0.0(2024-01-24)
|
||||
首次发布
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"id": "x-mlkit-scannig-u",
|
||||
"displayName": "tmui4.0 UTS原生二维码扫一扫 机器学习",
|
||||
"version": "1.1.0",
|
||||
"description": "机器学习识别码库,它是tmui4.0附属插件,可实时识别多码,速度快,效率不错,精准度也不错",
|
||||
"keywords": [
|
||||
"扫码",
|
||||
"机器学习",
|
||||
"二维码",
|
||||
"扫一扫"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.99"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-uts",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "99.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": "369986563"
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "n",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-android": {
|
||||
"minVersion": "29"
|
||||
},
|
||||
"app-ios": {
|
||||
"minVersion": "12"
|
||||
},
|
||||
"app-harmony": "n"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "n",
|
||||
"阿里": "n",
|
||||
"百度": "n",
|
||||
"字节跳动": "n",
|
||||
"QQ": "n",
|
||||
"钉钉": "n",
|
||||
"快手": "n",
|
||||
"飞书": "n",
|
||||
"京东": "n"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "n",
|
||||
"联盟": "n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
# 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 | 小程序 |
|
||||
| --- | --- | --- | --- |
|
||||
| 支持 | 支持 | 支持 | 支持 |
|
||||
|
||||
### 使用
|
||||
|
||||
如果是安卓请务必打自定义基座,如果ios:你在mac环境下配置好了环境无需打包本地编译,如果win开发ios需要打包基座。
|
||||
|
||||
```ts
|
||||
|
||||
// 添加组件
|
||||
<x-mlkit-scannig-u
|
||||
@clickQr="onclickQr"
|
||||
@scan="saningEvent"
|
||||
:autoOpenCamera="false"
|
||||
:cameraWidth="1080" :cameraeiHght="1080" style="width:750rpx;height:750rpx;background:black"
|
||||
ref="XscanU">
|
||||
</x-mlkit-scannig-u>
|
||||
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
result:[] as string[],
|
||||
apiStr:""
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
saningEvent(str:string[]){
|
||||
// #ifdef APP-ANDROID||WEB||MP
|
||||
|
||||
this.result = str as string[];
|
||||
// #endif
|
||||
// #ifdef APP-IOS
|
||||
|
||||
let resultstr = uni.getStorageSync("xMlKITScaning")
|
||||
if(resultstr!=null){
|
||||
let result = JSON.parseArray<string>(resultstr as string)
|
||||
this.result = result
|
||||
}
|
||||
|
||||
// #endif
|
||||
},
|
||||
onclickQr(str:string){
|
||||
// #ifdef APP-ANDROID||WEB||MP
|
||||
uni.showModal({
|
||||
title:"提醒",
|
||||
content:str,
|
||||
showCancel:false
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-IOS
|
||||
let resultstr = uni.getStorageSync("xMlKITScaningClick")
|
||||
uni.showModal({
|
||||
title:"提醒",
|
||||
content:resultstr,
|
||||
showCancel:false
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
openCamera(){
|
||||
let el = (this.$refs['XscanU'] as XMlkitScannigUElement)
|
||||
el.openCamera()
|
||||
},
|
||||
//通过相册选取图片识别.
|
||||
choose(){
|
||||
let el = (this.$refs['XscanU'] as XMlkitScannigUElement)
|
||||
el.chooseImage()
|
||||
},
|
||||
closeCamera(){
|
||||
let el = (this.$refs['XscanU'] as XMlkitScannigUElement)
|
||||
el.closeCamera()
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
@@ -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.xMlkitScannigU">
|
||||
<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" android:value="xMlkitScannigU" />
|
||||
<activity android:name="uts.sdk.modules.xMlkitScannigU.DemoActivity"></activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,821 @@
|
||||
import ListView from 'android.widget.ListView';
|
||||
import FrameLayout from 'android.widget.FrameLayout';
|
||||
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 Range from 'android.util.Range';
|
||||
|
||||
import Executor from 'java.util.concurrent.Executor'
|
||||
import Looper from 'android.os.Looper'
|
||||
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 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 Rect from 'android.graphics.Rect';
|
||||
import Matrix from 'android.graphics.Matrix'
|
||||
import ImageReader from 'android.media.ImageReader'
|
||||
import OnImageAvailableListener from 'android.media.ImageReader.OnImageAvailableListener'
|
||||
|
||||
|
||||
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 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 ViewGroup from 'android.view.ViewGroup';
|
||||
|
||||
|
||||
import IOException from 'java.io.IOException'
|
||||
import InputStream from 'java.io.InputStream'
|
||||
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 xView from "./libview/view.uts"
|
||||
import Task from "com.google.android.gms.tasks.Task"
|
||||
import List from "java.util.List"
|
||||
type buffoptsPlanesType = {
|
||||
data : ByteBuffer,
|
||||
getRowStride : number,
|
||||
getPixelStride : number,
|
||||
}
|
||||
type buffoptsType = {
|
||||
width : number,
|
||||
height : number,
|
||||
format : number,
|
||||
rect : Rect,
|
||||
planes : buffoptsPlanesType[],
|
||||
}
|
||||
|
||||
import { SCANNING_OPTIONS, CAMERA_PHOTO_SIZE, CallFunType, SCANNING_PHOTO_RESULT, SCANNING_PHOTO_RESULT_BOUND } from "../interface.uts"
|
||||
import { decoderUriPathToQr, decoderBuffToQr } from "./index.uts"
|
||||
|
||||
type cameraListNameType = { id : string, device : CameraCharacteristics }
|
||||
|
||||
function px2dp(n : number) : number {
|
||||
const mets = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics()
|
||||
return mets.density * n
|
||||
}
|
||||
|
||||
export class xCamera {
|
||||
parentView : FrameLayout;
|
||||
// 相机区域视图
|
||||
cameraView : SurfaceView | null = null;
|
||||
// 识别点的区域
|
||||
resultView : RelativeLayout | null = null;
|
||||
|
||||
|
||||
viewBox_width = 0
|
||||
viewBox_height = 0
|
||||
|
||||
imgPointsBycanmarea = [] as RelativeLayout[]
|
||||
|
||||
options = {
|
||||
width: 800,
|
||||
height: 800,
|
||||
cameraDevice: 'BACK',
|
||||
scanSpeedduration: 450,
|
||||
scanningQrCode: true,
|
||||
isSavePhoto: false,
|
||||
qrCodeType: null,
|
||||
vibatar: true,
|
||||
zoom: 3,
|
||||
continuousScan: false
|
||||
} as SCANNING_OPTIONS
|
||||
|
||||
/** 手动震动服务 */
|
||||
vibrator = null as null | Vibrator
|
||||
context : Context;
|
||||
// 相机旋转的角度。
|
||||
cameraRate = new Map<Number, Number>([
|
||||
[Surface.ROTATION_0, 0],
|
||||
[Surface.ROTATION_90, 0],
|
||||
[Surface.ROTATION_180, 0],
|
||||
[Surface.ROTATION_270, 0]
|
||||
])
|
||||
// 相机服务
|
||||
cameraService : CameraManager
|
||||
// 相机列表,name,id
|
||||
cameraListName = new Map<string, cameraListNameType>();
|
||||
//
|
||||
cameraPreviewSize = { width: 800, height: 800 } as CAMERA_PHOTO_SIZE
|
||||
// 当前预览的什么是设备
|
||||
cameraDevice : CameraDevice | null = null
|
||||
cameraDeviceCaptureSession : CameraCaptureSession | null = null
|
||||
sufaceHolderView : Surface | null = null
|
||||
// 当前是否在解析码制中。
|
||||
isDecoderQring = false
|
||||
// 当前相机是否正在预览中。
|
||||
isOpeningCameraing = false;
|
||||
|
||||
callFunEvent : CallFunType = (str : string[], type : 'auto' | 'click') => { }
|
||||
|
||||
// 临时存放的识别成功的结果,防止重复扫码。
|
||||
tempresultList = [] as string[];
|
||||
|
||||
isAppPause = false
|
||||
|
||||
constructor(GroupView : FrameLayout, options : SCANNING_OPTIONS | null = null) {
|
||||
this.context = UTSAndroid.getAppContext()! as Context;
|
||||
this.parentView = GroupView;
|
||||
if (options != null) {
|
||||
this.options = {
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
cameraDevice: options.cameraDevice,
|
||||
scanSpeedduration: options.scanSpeedduration == null ? this.options.scanSpeedduration : options.scanSpeedduration,
|
||||
scanningQrCode: options.scanningQrCode == null ? this.options.scanningQrCode : options.scanningQrCode,
|
||||
isSavePhoto: options.isSavePhoto == null ? this.options.isSavePhoto : options.isSavePhoto,
|
||||
qrCodeType: options.qrCodeType == null ? this.options.qrCodeType : options.qrCodeType,
|
||||
vibatar: options.vibatar == null ? this.options.vibatar : options.vibatar,
|
||||
zoom: options.zoom == null ? this.options.zoom : options.zoom,
|
||||
continuousScan: options.continuousScan == null ? this.options.continuousScan : options.continuousScan
|
||||
} as SCANNING_OPTIONS;
|
||||
}
|
||||
this.cameraService = this.context!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
this.vibrator = this.context!.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator;
|
||||
this._createCameraView()
|
||||
this._creatCameraResultView()
|
||||
}
|
||||
//震动手机
|
||||
setVibatar() {
|
||||
if (this.vibrator == null) return
|
||||
// 检查设备是否支持震动
|
||||
if (this.vibrator!.hasVibrator()) {
|
||||
// 触发短暂的震动,持续时间为200毫秒
|
||||
this.vibrator!.vibrate(80);
|
||||
}
|
||||
}
|
||||
/** 创建相机预览区域 */
|
||||
private _createCameraView() {
|
||||
let sv = new SurfaceView(this.context)
|
||||
// 让相机的高和宽与组件对齐。
|
||||
let layaout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
sv.setLayoutParams(layaout)
|
||||
this.cameraView = sv;
|
||||
|
||||
this.parentView.addView(this.cameraView!)
|
||||
}
|
||||
// 创建一个动画层容器
|
||||
private _creatCameraResultView() {
|
||||
let t = this;
|
||||
let resultView = new RelativeLayout(this.context)
|
||||
let layaout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
resultView.setLayoutParams(layaout)
|
||||
// resultView.setBackgroundColor(Color.argb(30,255,0,0))
|
||||
this.resultView = resultView;
|
||||
this.parentView.addView(this.resultView!)
|
||||
|
||||
//监听尺寸变化。
|
||||
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;
|
||||
|
||||
|
||||
// t.parentView!.removeOnLayoutChangeListener(this);
|
||||
}
|
||||
}
|
||||
this.parentView!.addOnLayoutChangeListener(new previewChangeRectadd())
|
||||
|
||||
|
||||
}
|
||||
openCamera() : Promise<boolean> {
|
||||
let _this = this;
|
||||
if (this.isOpeningCameraing) {
|
||||
this.closeCamera()
|
||||
}
|
||||
return new Promise((res, rej) => {
|
||||
_this.checkPermissions()
|
||||
.then((res : boolean) => {
|
||||
if (!res) {
|
||||
uni.showToast({ title: "未授权相机使用权", icon: "none" })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
_this.getCamreaListName()
|
||||
_this.getCamreaDeviceSizeList()
|
||||
_this.setCamreaSize()
|
||||
_this._openCamera();
|
||||
_this.setCamreaFocalLength(1)
|
||||
_this.addListenApp()
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
}
|
||||
res(true)
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: "未授权相机使用权", icon: "none" })
|
||||
rej(false)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
addListenApp() {
|
||||
let t = this;
|
||||
let listener : (() => void) | null = null;
|
||||
listener = () => {
|
||||
t.isAppPause = true;
|
||||
// console.log(UTSAndroid.getDispatcher())
|
||||
if (t.isOpeningCameraing) {
|
||||
t.pauseCamera()
|
||||
}
|
||||
if (listener != null) {
|
||||
UTSAndroid.offAppActivityPause(listener!);
|
||||
}
|
||||
}
|
||||
UTSAndroid.onAppActivityPause(listener);
|
||||
|
||||
let listener2 : (() => void) | null = null;
|
||||
listener2 = () => {
|
||||
t.isAppPause = false
|
||||
if (t.isOpeningCameraing) {
|
||||
t.openCamera()
|
||||
}
|
||||
if (listener2 != null) {
|
||||
UTSAndroid.offAppActivityResume(listener2!);
|
||||
}
|
||||
|
||||
}
|
||||
UTSAndroid.onAppActivityResume(listener2);
|
||||
|
||||
}
|
||||
pauseCamera() {
|
||||
this.sufaceHolderView?.release()
|
||||
this.cameraDeviceCaptureSession?.close()
|
||||
this.cameraDevice?.close()
|
||||
this.cameraDevice = null;
|
||||
this.cameraDeviceCaptureSession = null;
|
||||
this.sufaceHolderView = null;
|
||||
}
|
||||
closeCamera() {
|
||||
this.sufaceHolderView?.release()
|
||||
this.cameraDeviceCaptureSession?.close()
|
||||
this.cameraDevice?.close()
|
||||
this.cameraDevice = null;
|
||||
this.cameraDeviceCaptureSession = null;
|
||||
this.isOpeningCameraing = false;
|
||||
this.sufaceHolderView = null;
|
||||
for (let i = 0; i < this.imgPointsBycanmarea.length; i++) {
|
||||
this.resultView?.removeView(this.imgPointsBycanmarea[i])
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private _openCamera() {
|
||||
let _this = this;
|
||||
if (this.cameraListName.size == 0) return;
|
||||
let camreaNow = this.cameraListName.get(this.options.cameraDevice);
|
||||
if (camreaNow == null) return;
|
||||
class CamerCallTstasBack implements StateCallback {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
override onOpened(camera : CameraDevice) : void {
|
||||
console.log('打开成功')
|
||||
_this.cameraDevice = camera
|
||||
_this.createCameraPreviewSession()
|
||||
|
||||
}
|
||||
override onDisconnected(camera : CameraDevice) : void {
|
||||
console.log('连接失败')
|
||||
_this.cameraDevice?.close()
|
||||
_this.cameraDevice = null
|
||||
}
|
||||
override onError(camera : CameraDevice, error : Int) : void {
|
||||
console.log('出错')
|
||||
_this.cameraDevice?.close()
|
||||
_this.cameraDevice = null
|
||||
}
|
||||
override onClosed(camera : CameraDevice) : void {
|
||||
console.log('关闭')
|
||||
_this.cameraDevice?.close()
|
||||
_this.cameraDevice = null
|
||||
}
|
||||
|
||||
}
|
||||
this.cameraService.openCamera(camreaNow.id! as string, new CamerCallTstasBack(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查相机权限
|
||||
*/
|
||||
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[]) {
|
||||
//用户拒绝了部分权限
|
||||
// _this.cameraPermissioPass = false
|
||||
res(false)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取相机设备列表
|
||||
*/
|
||||
getCamreaListName(deviceName : 'FRONT' | 'BACK' | null = null) {
|
||||
// 前置还是后置。
|
||||
let deviceLens = CameraCharacteristics.LENS_FACING_BACK;
|
||||
let dname = this.options.cameraDevice
|
||||
if (deviceName != null) {
|
||||
dname = deviceName!
|
||||
}
|
||||
if (dname == 'FRONT') {
|
||||
deviceLens = CameraCharacteristics.LENS_FACING_FRONT;
|
||||
}
|
||||
if (dname == 'BACK') {
|
||||
deviceLens = CameraCharacteristics.LENS_FACING_BACK;
|
||||
}
|
||||
this.cameraListName.clear()
|
||||
|
||||
|
||||
for (caIds in this.cameraService.getCameraIdList()) {
|
||||
let ps = this.cameraService.getCameraCharacteristics(caIds as string);
|
||||
let facing = ps.get(CameraCharacteristics.LENS_FACING);
|
||||
if (facing == CameraCharacteristics.LENS_FACING_FRONT) {
|
||||
this.cameraListName.set('FRONT', { id: caIds as string, device: ps } as cameraListNameType)
|
||||
}
|
||||
if (facing == CameraCharacteristics.LENS_FACING_BACK) {
|
||||
this.cameraListName.set('BACK', { id: caIds as string, device: ps } as cameraListNameType)
|
||||
}
|
||||
}
|
||||
|
||||
this.setCamreaSize();
|
||||
}
|
||||
/**
|
||||
* 获取相机设备尺寸列表
|
||||
* 调用此函数的前提是要先获取设备列表,要先调用getCamreaListName函数
|
||||
*/
|
||||
getCamreaDeviceSizeList() : CAMERA_PHOTO_SIZE[] {
|
||||
let sizelist = [] as CAMERA_PHOTO_SIZE[]
|
||||
if (this.cameraListName.size == 0) {
|
||||
console.error("请先调用getCamreaListName获取设备列表")
|
||||
return sizelist;
|
||||
}
|
||||
let camrea = this.cameraListName.get(this.options.cameraDevice) as cameraListNameType;
|
||||
if (camrea != null) {
|
||||
let maps = camrea!.device!.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) as StreamConfigurationMap
|
||||
if (maps != null) {
|
||||
|
||||
let testSize = maps!.getOutputSizes(ImageFormat.JPEG)
|
||||
for (sz in testSize) {
|
||||
let w = sz.getWidth()
|
||||
let h = sz.getHeight()
|
||||
sizelist.push({ width: w, height: h } as CAMERA_PHOTO_SIZE)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return sizelist;
|
||||
}
|
||||
/**
|
||||
* 设置相机焦距
|
||||
* 0-1之间。
|
||||
* 暂未实现。
|
||||
*/
|
||||
setCamreaFocalLength(focal : number) {
|
||||
|
||||
if (this.cameraListName.size == 0) {
|
||||
console.error("请先调用getCamreaListName获取设备列表")
|
||||
return
|
||||
}
|
||||
let camrea = this.cameraListName.get(this.options.cameraDevice) as cameraListNameType;
|
||||
|
||||
if (camrea != null) {
|
||||
let zoomMax = camrea!.device!.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)
|
||||
let nowZoom = camrea!.device!.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置相机前置还是后置,设置后需要重新打开相机。
|
||||
* BACK 后置
|
||||
* FRONT 前置
|
||||
*/
|
||||
setCamreaDevice(name : 'FRONT' | 'BACK') {
|
||||
this.options.cameraDevice = name;
|
||||
}
|
||||
/**
|
||||
* 设置相机摄像尺寸,通过options中的width,height来匹配设置
|
||||
* 返回的不一定是真实的options中的宽高,只是与其相近的值。
|
||||
* 以设置后要重新打开。相机生效。
|
||||
* 返回相机生效的尺寸
|
||||
*/
|
||||
setCamreaSize(width : number | null = null, height : number | null = null) : CAMERA_PHOTO_SIZE {
|
||||
// 宽高列表
|
||||
const dimensions = this.getCamreaDeviceSizeList()
|
||||
// 目标宽高
|
||||
const targetWidth = width != null ? width! : this.options.width;
|
||||
const targetHeight = height != null ? height! : this.options.height;
|
||||
const targetRatio = targetWidth / targetHeight;
|
||||
// 找到最接近目标比例的宽高值
|
||||
const closestDimension = dimensions.reduce((prev : CAMERA_PHOTO_SIZE, curr : CAMERA_PHOTO_SIZE) : CAMERA_PHOTO_SIZE => {
|
||||
const currRatio = curr.width / curr.height;
|
||||
const prevRatio = prev.width / prev.height;
|
||||
const currDiffRatio = Math.abs(currRatio - targetRatio);
|
||||
const prevDiffRatio = Math.abs(prevRatio - targetRatio);
|
||||
|
||||
// 计算宽高值与目标值的差的平方和
|
||||
const currDiffValue = Math.pow(curr.width - targetWidth, 2) + Math.pow(curr.height - targetHeight, 2);
|
||||
const prevDiffValue = Math.pow(prev.width - targetWidth, 2) + Math.pow(prev.height - targetHeight, 2);
|
||||
|
||||
// 给比例差和值差赋予不同的权重,这里假设两者权重相同
|
||||
const currTotalDiff = currDiffRatio + currDiffValue;
|
||||
const prevTotalDiff = prevDiffRatio + prevDiffValue;
|
||||
|
||||
return currTotalDiff < prevTotalDiff ? curr : prev;
|
||||
}, dimensions[0]);
|
||||
this.cameraPreviewSize = closestDimension
|
||||
|
||||
return closestDimension;
|
||||
}
|
||||
|
||||
// 获取当前相机的旋转角度。
|
||||
getCamraRotation() : Number {
|
||||
let sensorOrientation = 0
|
||||
let tempra = 0
|
||||
if (this.cameraListName.size == 0) return sensorOrientation;
|
||||
let camreaNow = this.cameraListName.get(this.options.cameraDevice);
|
||||
if (camreaNow == null) return sensorOrientation;
|
||||
|
||||
let windowManager = UTSAndroid.getUniActivity()!.getWindowManager() as WindowManager
|
||||
|
||||
let deviceRotation = windowManager!.getDefaultDisplay().getRotation()
|
||||
let rotationCompensation = this.cameraRate.get(deviceRotation) as number
|
||||
|
||||
tempra = camreaNow.device.get(CameraCharacteristics.SENSOR_ORIENTATION)!! as number
|
||||
|
||||
if (this.options.cameraDevice == 'FRONT') {
|
||||
tempra = ((tempra + rotationCompensation) % 360)
|
||||
} else {
|
||||
tempra = ((tempra - rotationCompensation + 360) % 360)
|
||||
}
|
||||
return tempra
|
||||
}
|
||||
|
||||
// 设置识别成功的回调函数
|
||||
setCallEvent(call : CallFunType) {
|
||||
this.callFunEvent = call;
|
||||
}
|
||||
|
||||
// 创建视频预览流。
|
||||
private createCameraPreviewSession() {
|
||||
if (this.cameraView == null) return;
|
||||
|
||||
// 设置预览区域
|
||||
let previewAspectRatio = this.cameraPreviewSize.width / this.cameraPreviewSize.height;
|
||||
let layoutWidth = this.viewBox_width
|
||||
let layoutHeight = this.viewBox_height
|
||||
let layoutAspectRatio = layoutWidth / layoutHeight;
|
||||
|
||||
|
||||
let holder = this.cameraView!.getHolder()
|
||||
let suface = holder!.getSurface();
|
||||
this.sufaceHolderView = suface
|
||||
|
||||
if (this.cameraDevice == null) return;
|
||||
let t = this;
|
||||
let previewRequestBuilder = this.cameraDevice!.createCaptureRequest(CameraDevice.PREVIEW)
|
||||
previewRequestBuilder?.addTarget(suface)
|
||||
|
||||
let totalWidth = this.cameraPreviewSize.width
|
||||
let totalHeight = this.cameraPreviewSize.height
|
||||
|
||||
|
||||
// 再添加一个image来预览
|
||||
let minh = Math.min(this.options.width, this.options.height)
|
||||
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.cameraDeviceCaptureSession = session
|
||||
t.isOpeningCameraing = true;
|
||||
try {
|
||||
t.cameraDeviceCaptureSession?.setRepeatingRequest(previewRequestBuilder.build(), null, null)
|
||||
} catch (e) {
|
||||
console.log("Failed to start camera preview session.", e)
|
||||
}
|
||||
}
|
||||
override onConfigureFailed(session : CameraCaptureSession) {
|
||||
|
||||
}
|
||||
override onClosed(session : CameraCaptureSession) {
|
||||
t.closeCamera()
|
||||
}
|
||||
override onSurfacePrepared(session : CameraCaptureSession, testSuface : Surface) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
reader.setOnImageAvailableListener(this.createCameraImageRenderLisen() as OnImageAvailableListener, null)
|
||||
this.cameraDevice!.createCaptureSession(outsufaces, new sessionCallback(), null)
|
||||
|
||||
|
||||
}
|
||||
|
||||
private createCameraImageRenderLisen() : any {
|
||||
let t = this;
|
||||
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.isDecoderQring || !t.isOpeningCameraing || t.isAppPause) return;
|
||||
try {
|
||||
t.qrDecoder(curimage!)
|
||||
} catch (e) {
|
||||
t.closeCamera()
|
||||
console.error('应用异常:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.currentImage = curimage
|
||||
if (this.currentImage != null) {
|
||||
new Thread(new IntentRunable()).start()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return new imageReaderCallback()
|
||||
}
|
||||
|
||||
// 设置图片上的小圆的识别位置。
|
||||
setImageViewRusletBox(width : number, height : number, realWidth : number, realHeight : number, jieguo : SCANNING_PHOTO_RESULT) {
|
||||
let t = this;
|
||||
let wRatio = t.viewBox_width / realWidth;
|
||||
let hRatio = t.viewBox_height / realHeight;
|
||||
let btnwidth = 120;
|
||||
let zoom = 10 - this.options.zoom!
|
||||
|
||||
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 = wRatio * item.centerX
|
||||
let y = hRatio * item.centerY
|
||||
|
||||
layoutParams.topMargin = (y - 60).toInt() // 设置顶部边距
|
||||
layoutParams.leftMargin = (x - 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 {
|
||||
t.callFunEvent([rtxt], 'click')
|
||||
} 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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private YUV_420_888toNV21(opts : buffoptsType) : ByteArray {
|
||||
|
||||
let crop = opts.rect;
|
||||
let format = opts.format;
|
||||
let width = crop.width();
|
||||
let height = crop.height();
|
||||
let planes = opts.planes!;
|
||||
let data = new ByteArray((width * height * ImageFormat.getBitsPerPixel(format.toInt()) / 8).toInt());
|
||||
let rowData = new ByteArray((planes[0].getRowStride).toInt());
|
||||
let channelOffset = 0;
|
||||
let outputStride = 1;
|
||||
for (let i = 0; i < planes.length; i++) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
channelOffset = 0;
|
||||
outputStride = 1;
|
||||
break;
|
||||
case 1:
|
||||
channelOffset = width * height;
|
||||
outputStride = 1;
|
||||
break;
|
||||
case 2:
|
||||
channelOffset = width * height * 1.25;
|
||||
outputStride = 1;
|
||||
break;
|
||||
}
|
||||
let buffer = planes[i.toInt()].data!;
|
||||
let rowStride = planes[i.toInt()].getRowStride;
|
||||
let pixelStride = planes[i.toInt()].getPixelStride;
|
||||
|
||||
let shift = (i == 0) ? 0 : 1;
|
||||
let w = width >> shift;
|
||||
let h = height >> shift;
|
||||
buffer.position((rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift)).toInt());
|
||||
for (let row = 0; row < h; row++) {
|
||||
let length = 0;
|
||||
if (pixelStride == 1 && outputStride == 1) {
|
||||
length = w;
|
||||
buffer.get(data, channelOffset.toInt(), length.toInt());
|
||||
channelOffset += length;
|
||||
} else {
|
||||
length = (w - 1) * pixelStride + 1;
|
||||
buffer.get(rowData, 0, length.toInt());
|
||||
for (let col = 0; col < w; col++) {
|
||||
data[channelOffset.toInt()] = rowData[(col * pixelStride).toInt()];
|
||||
channelOffset += outputStride;
|
||||
}
|
||||
}
|
||||
if (row < h - 1) {
|
||||
buffer.position((buffer.position() + rowStride - length).toInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private qrDecoder(image : Image) {
|
||||
let t = this;
|
||||
if (!this.isDecoderQring && !this.isAppPause) {
|
||||
this.isDecoderQring = true;
|
||||
|
||||
let width = image.getWidth();
|
||||
let height = image.getHeight();
|
||||
let frameSize = width * height;
|
||||
let qFrameSize = frameSize / 4;
|
||||
// 获取各平面的数据与信息
|
||||
let planes = image.getPlanes();
|
||||
|
||||
let yBufferCopy = [] as ByteArray[]
|
||||
let yrurvrNumber = [] as number[]
|
||||
|
||||
let yr = planes[0]
|
||||
let ur = planes[1]
|
||||
let vr = planes[2]
|
||||
let oraingMax = yr.getBuffer().limit()
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let buffer = planes[i.toInt()].getBuffer();
|
||||
yBufferCopy.push(new ByteArray(buffer.remaining().toInt()))
|
||||
buffer.get(yBufferCopy[i.toInt()]);
|
||||
}
|
||||
let y = ByteBuffer.wrap(yBufferCopy[0]!)
|
||||
let u = ByteBuffer.wrap(yBufferCopy[1]!)
|
||||
let v = ByteBuffer.wrap(yBufferCopy[2]!)
|
||||
|
||||
let opts = {
|
||||
width,
|
||||
height,
|
||||
format: image.getFormat(),
|
||||
rect: image.getCropRect(),
|
||||
planes: [
|
||||
{
|
||||
data: y,
|
||||
getRowStride: yr.getRowStride(),
|
||||
getPixelStride: yr.getPixelStride(),
|
||||
},
|
||||
{
|
||||
data: u,
|
||||
getRowStride: ur.getRowStride(),
|
||||
getPixelStride: ur.getPixelStride(),
|
||||
},
|
||||
{
|
||||
data: v,
|
||||
getRowStride: vr.getRowStride(),
|
||||
getPixelStride: vr.getPixelStride(),
|
||||
}
|
||||
] as buffoptsPlanesType[]
|
||||
} as buffoptsType
|
||||
|
||||
decoderBuffToQr(this.YUV_420_888toNV21(opts), width, height, this.getCamraRotation())
|
||||
.then((res) => {
|
||||
|
||||
if (res != null && t.isOpeningCameraing && !t.isAppPause) {
|
||||
let texts = res.text as string[]
|
||||
if (texts.length > 0) {
|
||||
// texts.some((el:string):boolean=>t.tempresultList.includes(el))
|
||||
|
||||
if (t.options.vibatar == true) {
|
||||
t.setVibatar()
|
||||
}
|
||||
if (t.options.continuousScan == false) {
|
||||
t.closeCamera()
|
||||
}
|
||||
t.callFunEvent(texts, 'auto')
|
||||
t.setImageViewRusletBox(t.options.width, t.options.height, width, height, res as SCANNING_PHOTO_RESULT)
|
||||
}
|
||||
t.isDecoderQring = false
|
||||
} else {
|
||||
t.isDecoderQring = false
|
||||
}
|
||||
// image.close();
|
||||
})
|
||||
.catch(() => {
|
||||
// this.imageUrl = null
|
||||
})
|
||||
// setTimeout(function () {
|
||||
// t.isDecoderQring = false
|
||||
// }, t.options!.scanSpeedduration!);
|
||||
// image.close();
|
||||
}
|
||||
image.close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,648 @@
|
||||
import ListView from 'android.widget.ListView';
|
||||
import FrameLayout from 'android.widget.FrameLayout';
|
||||
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 Range from 'android.util.Range';
|
||||
|
||||
import Executor from 'java.util.concurrent.Executor'
|
||||
import Looper from 'android.os.Looper'
|
||||
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 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 Rect from 'android.graphics.Rect';
|
||||
import Matrix from 'android.graphics.Matrix'
|
||||
import ImageReader from 'android.media.ImageReader'
|
||||
import OnImageAvailableListener from 'android.media.ImageReader.OnImageAvailableListener'
|
||||
|
||||
|
||||
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 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 ViewGroup from 'android.view.ViewGroup';
|
||||
|
||||
|
||||
import IOException from 'java.io.IOException'
|
||||
import InputStream from 'java.io.InputStream'
|
||||
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 xView from "./libview/view.uts"
|
||||
import Task from "com.google.android.gms.tasks.Task"
|
||||
import List from "java.util.List"
|
||||
|
||||
// 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 TargetResolution from 'androidx.camera.core.ImageAnalysis.TargetResolution'
|
||||
|
||||
type buffoptsPlanesType = {
|
||||
data : ByteBuffer,
|
||||
getRowStride : number,
|
||||
getPixelStride : number,
|
||||
}
|
||||
type buffoptsType = {
|
||||
width : number,
|
||||
height : number,
|
||||
format : number,
|
||||
rect : Rect,
|
||||
planes : buffoptsPlanesType[],
|
||||
}
|
||||
|
||||
import { SCANNING_OPTIONS, CAMERA_PHOTO_SIZE, CallFunType, SCANNING_PHOTO_RESULT, SCANNING_PHOTO_RESULT_BOUND } from "../interface.uts"
|
||||
import { decoderUriPathToQr, decoderBuffToQr } from "./index.uts"
|
||||
import ContextCompat from 'androidx.core.content.ContextCompat';
|
||||
|
||||
type cameraListNameType = { id : string, device : CameraCharacteristics }
|
||||
|
||||
function px2dp(n : number) : number {
|
||||
const mets = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics()
|
||||
return mets.density * n
|
||||
}
|
||||
|
||||
export class xCamera {
|
||||
parentView : FrameLayout;
|
||||
|
||||
// 识别点的区域
|
||||
resultView : RelativeLayout | null = null;
|
||||
|
||||
|
||||
viewBox_width = 0
|
||||
viewBox_height = 0
|
||||
|
||||
imgPointsBycanmarea = [] as RelativeLayout[]
|
||||
|
||||
options = {
|
||||
width: 800,
|
||||
height: 800,
|
||||
cameraDevice: 'BACK',
|
||||
scanSpeedduration: 450,
|
||||
scanningQrCode: true,
|
||||
isSavePhoto: false,
|
||||
qrCodeType: null,
|
||||
vibatar: true,
|
||||
zoom: 3,
|
||||
continuousScan: false
|
||||
} as SCANNING_OPTIONS
|
||||
|
||||
/** 手动震动服务 */
|
||||
vibrator = null as null | Vibrator
|
||||
context : Context;
|
||||
// 相机旋转的角度。
|
||||
cameraRate = new Map<Number, Number>([
|
||||
[Surface.ROTATION_0, 0],
|
||||
[Surface.ROTATION_90, 0],
|
||||
[Surface.ROTATION_180, 0],
|
||||
[Surface.ROTATION_270, 0]
|
||||
])
|
||||
// 相机服务
|
||||
cameraService : CameraManager
|
||||
// 相机列表,name,id
|
||||
cameraListName = new Map<string, cameraListNameType>();
|
||||
//
|
||||
cameraPreviewSize = { width: 800, height: 800 } as CAMERA_PHOTO_SIZE
|
||||
// 当前预览的什么是设备
|
||||
cameraDevice : CameraDevice | null = null
|
||||
cameraDeviceCaptureSession : CameraCaptureSession | null = null
|
||||
sufaceHolderView : Surface | null = null
|
||||
|
||||
// 相机区域视图
|
||||
cameraView : PreviewView | null = null;
|
||||
cameraProvider : ProcessCameraProvider | null = null
|
||||
camera : Camera | null = null;
|
||||
cameraPreview : Preview | null = null;
|
||||
|
||||
|
||||
// 当前是否在解析码制中。
|
||||
isDecoderQring = false
|
||||
// 当前相机是否正在预览中。
|
||||
isOpeningCameraing = false;
|
||||
|
||||
callFunEvent : CallFunType = (str : string[], type : 'auto' | 'click') => { }
|
||||
|
||||
// 临时存放的识别成功的结果,防止重复扫码。
|
||||
tempresultList = [] as string[];
|
||||
|
||||
isAppPause = false
|
||||
|
||||
flashMode = false;
|
||||
|
||||
constructor(GroupView : FrameLayout, options : SCANNING_OPTIONS | null = null) {
|
||||
this.context = UTSAndroid.getAppContext()! as Context;
|
||||
this.parentView = GroupView;
|
||||
if (options != null) {
|
||||
this.options = {
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
cameraDevice: options.cameraDevice,
|
||||
scanSpeedduration: options.scanSpeedduration == null ? this.options.scanSpeedduration : options.scanSpeedduration,
|
||||
scanningQrCode: options.scanningQrCode == null ? this.options.scanningQrCode : options.scanningQrCode,
|
||||
isSavePhoto: options.isSavePhoto == null ? this.options.isSavePhoto : options.isSavePhoto,
|
||||
qrCodeType: options.qrCodeType == null ? this.options.qrCodeType : options.qrCodeType,
|
||||
vibatar: options.vibatar == null ? this.options.vibatar : options.vibatar,
|
||||
zoom: options.zoom == null ? this.options.zoom : options.zoom,
|
||||
continuousScan: options.continuousScan == null ? this.options.continuousScan : options.continuousScan
|
||||
} as SCANNING_OPTIONS;
|
||||
this.flashMode = options.flashMode
|
||||
}
|
||||
this.cameraService = this.context!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
this.vibrator = this.context!.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator;
|
||||
this._createCameraView()
|
||||
this._creatCameraResultView()
|
||||
}
|
||||
//震动手机
|
||||
setVibatar() {
|
||||
if (this.vibrator == null) return
|
||||
// 检查设备是否支持震动
|
||||
if (this.vibrator!.hasVibrator()) {
|
||||
// 触发短暂的震动,持续时间为200毫秒
|
||||
this.vibrator!.vibrate(80);
|
||||
}
|
||||
}
|
||||
/** 创建相机预览区域 */
|
||||
private _createCameraView() {
|
||||
|
||||
let sv = new PreviewView(UTSAndroid.getAppContext()!)
|
||||
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.parentView.addView(this.cameraView!)
|
||||
}
|
||||
// 创建一个动画层容器
|
||||
private _creatCameraResultView() {
|
||||
let t = this;
|
||||
let resultView = new RelativeLayout(this.context)
|
||||
let layaout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
resultView.setLayoutParams(layaout)
|
||||
// resultView.setBackgroundColor(Color.argb(30,255,0,0))
|
||||
this.resultView = resultView;
|
||||
this.parentView.addView(this.resultView!)
|
||||
|
||||
//监听尺寸变化。
|
||||
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;
|
||||
}
|
||||
}
|
||||
this.parentView!.addOnLayoutChangeListener(new previewChangeRectadd())
|
||||
|
||||
|
||||
}
|
||||
openCamera() : Promise<boolean> {
|
||||
let _this = this;
|
||||
if (this.isOpeningCameraing) {
|
||||
this.closeCamera()
|
||||
}
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
_this.checkPermissions()
|
||||
.then((res : boolean) => {
|
||||
if (!res) {
|
||||
uni.showToast({ title: "未授权相机使用权", icon: "none" })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
_this._openCamera();
|
||||
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
//TODO handle the exception
|
||||
}
|
||||
res(true)
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: "未授权相机使用权", icon: "none" })
|
||||
rej(false)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
// 设置识别成功的回调函数
|
||||
setCallEvent(call : CallFunType) {
|
||||
this.callFunEvent = call;
|
||||
}
|
||||
getCamraRotation() : Number {
|
||||
return 0
|
||||
}
|
||||
setFlashModel(flash:boolean){
|
||||
|
||||
if(this.camera!=null&&this.isOpeningCameraing){
|
||||
let cameraControl = this.camera!.cameraControl!
|
||||
cameraControl.enableTorch(flash)
|
||||
}
|
||||
}
|
||||
|
||||
closeCamera() {
|
||||
this.setFlashModel(false);
|
||||
this.cameraProvider?.unbindAll()
|
||||
this.cameraPreview?.setSurfaceProvider(null)
|
||||
this.isOpeningCameraing = false;
|
||||
|
||||
for (let i = 0; i < this.imgPointsBycanmarea.length; i++) {
|
||||
this.resultView?.removeView(this.imgPointsBycanmarea[i])
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private _openCamera() {
|
||||
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.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
|
||||
_this.setFlashModel(_this.flashMode)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cameraProviderFuture.addListener(new IntentRunable(), ContextCompat.getMainExecutor(this.context))
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查相机权限
|
||||
*/
|
||||
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[]) {
|
||||
//用户拒绝了部分权限
|
||||
// _this.cameraPermissioPass = false
|
||||
res(false)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 设置图片上的小圆的识别位置。
|
||||
setImageViewRusletBox(width : number, height : number, realWidth : number, realHeight : number, 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')
|
||||
} 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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private YUV_420_888toNV21(opts : buffoptsType) : ByteArray {
|
||||
|
||||
let crop = opts.rect;
|
||||
let format = opts.format;
|
||||
let width = crop.width();
|
||||
let height = crop.height();
|
||||
let planes = opts.planes!;
|
||||
let data = new ByteArray((width * height * ImageFormat.getBitsPerPixel(format.toInt()) / 8).toInt());
|
||||
let rowData = new ByteArray((planes[0].getRowStride).toInt());
|
||||
let channelOffset = 0;
|
||||
let outputStride = 1;
|
||||
for (let i = 0; i < planes.length; i++) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
channelOffset = 0;
|
||||
outputStride = 1;
|
||||
break;
|
||||
case 1:
|
||||
channelOffset = width * height;
|
||||
outputStride = 1;
|
||||
break;
|
||||
case 2:
|
||||
channelOffset = width * height * 1.25;
|
||||
outputStride = 1;
|
||||
break;
|
||||
}
|
||||
let buffer = planes[i.toInt()].data!;
|
||||
let rowStride = planes[i.toInt()].getRowStride;
|
||||
let pixelStride = planes[i.toInt()].getPixelStride;
|
||||
|
||||
let shift = (i == 0) ? 0 : 1;
|
||||
let w = width >> shift;
|
||||
let h = height >> shift;
|
||||
buffer.position((rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift)).toInt());
|
||||
for (let row = 0; row < h; row++) {
|
||||
let length = 0;
|
||||
if (pixelStride == 1 && outputStride == 1) {
|
||||
length = w;
|
||||
buffer.get(data, channelOffset.toInt(), length.toInt());
|
||||
channelOffset += length;
|
||||
} else {
|
||||
length = (w - 1) * pixelStride + 1;
|
||||
buffer.get(rowData, 0, length.toInt());
|
||||
for (let col = 0; col < w; col++) {
|
||||
data[channelOffset.toInt()] = rowData[(col * pixelStride).toInt()];
|
||||
channelOffset += outputStride;
|
||||
}
|
||||
}
|
||||
if (row < h - 1) {
|
||||
buffer.position((buffer.position() + rowStride - length).toInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
// 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
|
||||
}
|
||||
private qrDecoder(img : ImageProxy) {
|
||||
let t = this;
|
||||
let image = img.getImage();
|
||||
|
||||
if (!this.isDecoderQring && image != null) {
|
||||
this.isDecoderQring = true;
|
||||
|
||||
let width = image.getWidth();
|
||||
let height = image.getHeight();
|
||||
let frameSize = width * height;
|
||||
let qFrameSize = frameSize / 4;
|
||||
// 获取各平面的数据与信息
|
||||
let planes = image.getPlanes();
|
||||
|
||||
let yBufferCopy = [] as ByteArray[]
|
||||
let yrurvrNumber = [] as number[]
|
||||
|
||||
let yr = planes[0]
|
||||
let ur = planes[1]
|
||||
let vr = planes[2]
|
||||
let oraingMax = yr.getBuffer().limit()
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let buffer = planes[i.toInt()].getBuffer();
|
||||
yBufferCopy.push(new ByteArray(buffer.remaining().toInt()))
|
||||
buffer.get(yBufferCopy[i.toInt()]);
|
||||
}
|
||||
let y = ByteBuffer.wrap(yBufferCopy[0]!)
|
||||
let u = ByteBuffer.wrap(yBufferCopy[1]!)
|
||||
let v = ByteBuffer.wrap(yBufferCopy[2]!)
|
||||
|
||||
let opts = {
|
||||
width,
|
||||
height,
|
||||
format: image.getFormat(),
|
||||
rect: image.getCropRect(),
|
||||
planes: [
|
||||
{
|
||||
data: y,
|
||||
getRowStride: yr.getRowStride(),
|
||||
getPixelStride: yr.getPixelStride(),
|
||||
},
|
||||
{
|
||||
data: u,
|
||||
getRowStride: ur.getRowStride(),
|
||||
getPixelStride: ur.getPixelStride(),
|
||||
},
|
||||
{
|
||||
data: v,
|
||||
getRowStride: vr.getRowStride(),
|
||||
getPixelStride: vr.getPixelStride(),
|
||||
}
|
||||
] as buffoptsPlanesType[]
|
||||
} as buffoptsType
|
||||
|
||||
decoderBuffToQr(this.YUV_420_888toNV21(opts), width, height,0)
|
||||
.then((res) => {
|
||||
|
||||
if (res != null && t.isOpeningCameraing) {
|
||||
let matrix = t.getCorrectionMatrix(img, this.cameraView!)
|
||||
let texts = res.text as string[]
|
||||
if (texts.length > 0) {
|
||||
// texts.some((el:string):boolean=>t.tempresultList.includes(el))
|
||||
|
||||
if (t.options.vibatar == true) {
|
||||
t.setVibatar()
|
||||
}
|
||||
if (t.options.continuousScan == false) {
|
||||
t.closeCamera()
|
||||
}
|
||||
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.callFunEvent(texts, 'auto')
|
||||
t.setImageViewRusletBox(t.options.width, t.options.height, width, height, ruslt)
|
||||
}
|
||||
t.isDecoderQring = false
|
||||
} else {
|
||||
t.isDecoderQring = false
|
||||
}
|
||||
// image.close();
|
||||
})
|
||||
.catch(() => {
|
||||
// this.imageUrl = null
|
||||
})
|
||||
// setTimeout(function () {
|
||||
// t.isDecoderQring = false
|
||||
// }, t.options!.scanSpeedduration!);
|
||||
// image.close();
|
||||
}
|
||||
img.close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"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,262 @@
|
||||
/**
|
||||
* 引用 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 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'
|
||||
|
||||
/**
|
||||
* 从相册中选取图片进行解码
|
||||
* @public chooseImageDecodeQr
|
||||
*/
|
||||
export function chooseImage() : Promise<SCANNING_PHOTO_RESULT | null> {
|
||||
return new Promise((res) => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
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
|
||||
} as SCANNING_PHOTO_RESULT)
|
||||
})
|
||||
|
||||
} catch (e : IOException) {
|
||||
res(null)
|
||||
}
|
||||
}
|
||||
},
|
||||
fail() {
|
||||
res(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定图片路径Uri解析图片。
|
||||
* @public decoderUriPathToQr
|
||||
*/
|
||||
export function decoderUriPathToQr(filePath : Uri) : Promise<SCANNING_PHOTO_RESULT | null> {
|
||||
return new Promise((res) => {
|
||||
let image = InputImage.fromFilePath(UTSAndroid.getAppContext() as Context, filePath);
|
||||
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
|
||||
} as SCANNING_PHOTO_RESULT)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定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
|
||||
} as SCANNING_PHOTO_RESULT)
|
||||
})
|
||||
.addOnFailureListener((e) => {
|
||||
|
||||
res(null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 提供一个网络图片地址进行解码
|
||||
* @public urlPathDecoderQr
|
||||
*/
|
||||
export function urlPathDecoderQr(url : String) : Promise<SCANNING_PHOTO_RESULT | null> {
|
||||
return new Promise((res) => {
|
||||
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)
|
||||
}
|
||||
|
||||
try {
|
||||
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: url,
|
||||
position: listpos,
|
||||
bounds: bounedlist,
|
||||
text: rulstText
|
||||
} as SCANNING_PHOTO_RESULT)
|
||||
})
|
||||
|
||||
} catch (e : IOException) {
|
||||
res(null)
|
||||
}
|
||||
|
||||
},
|
||||
fail() {
|
||||
res(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<view class="boxView">
|
||||
|
||||
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import Context from 'android.content.Context'
|
||||
import LinearLayout from 'android.widget.LinearLayout';
|
||||
import FrameLayout from 'android.widget.FrameLayout';
|
||||
import ViewGroup from 'android.view.ViewGroup';
|
||||
import View from 'android.view.View';
|
||||
import ImageView from "android.widget.ImageView"
|
||||
import InputStream from 'java.io.InputStream'
|
||||
import Uri from 'android.net.Uri';
|
||||
import File from 'java.io.File'
|
||||
import BufferedImage from 'java.awt.image.BufferedImage'
|
||||
import IOException from 'java.io.IOException'
|
||||
import NotFoundException from 'com.google.zxing.NotFoundException'
|
||||
import FormatException from 'com.google.zxing.FormatException'
|
||||
import ChecksumException from 'com.google.zxing.ChecksumException'
|
||||
|
||||
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 Bitmap from "android.graphics.Bitmap"
|
||||
import BitmapFactory from "android.graphics.BitmapFactory"
|
||||
|
||||
import Color from "android.graphics.Color"
|
||||
import BarcodeFormat from "com.google.zxing.BarcodeFormat"
|
||||
import EncodeHintType from "com.google.zxing.EncodeHintType"
|
||||
import MultiFormatWriter from "com.google.zxing.MultiFormatWriter"
|
||||
import MultiFormatReader from "com.google.zxing.MultiFormatReader"
|
||||
import RGBLuminanceSource from "com.google.zxing.RGBLuminanceSource"
|
||||
|
||||
|
||||
import BinaryBitmap from "com.google.zxing.BinaryBitmap"
|
||||
import HybridBinarizer from "com.google.zxing.common.HybridBinarizer"
|
||||
import WriterException from "com.google.zxing.WriterException"
|
||||
import BitMatrix from "com.google.zxing.common.BitMatrix"
|
||||
import BufferedImageLuminanceSource from "com.google.zxing.client.j2se.BufferedImageLuminanceSource"
|
||||
|
||||
import ByteArrayOutputStream from 'java.io.ByteArrayOutputStream'
|
||||
import Base64 from 'java.util.Base64'
|
||||
|
||||
import Context from 'android.content.Context'
|
||||
import SurfaceView from 'android.view.SurfaceView'
|
||||
import { chooseImage } from "./index.uts"
|
||||
import { xCamera } from "./camera.uts"
|
||||
import { SCANNING_OPTIONS,CAMERA_PHOTO_SIZE,BARCODEFORMAT,CallFunType,SCANNING_PHOTO_RESULT_BOUND ,SCANNING_PHOTO_RESULT} from "../interface.uts"
|
||||
|
||||
|
||||
export default {
|
||||
name: "x-mlkit-scannig-u",
|
||||
data() {
|
||||
return {
|
||||
CBOX: null as null | FrameLayout,
|
||||
xcamera:null as null|xCamera,
|
||||
defaultscaneventFun:null as CallFunType|null
|
||||
}
|
||||
},
|
||||
emits:[
|
||||
/**
|
||||
* 多个码的时候点击触发,返回码内容
|
||||
* @param {string} str - 点的码
|
||||
*/
|
||||
'clickQr',
|
||||
/**
|
||||
* 识别成功触发
|
||||
* @param {string[]} str[] - 识别的码结果集
|
||||
*/
|
||||
'scan'
|
||||
],
|
||||
props:{
|
||||
|
||||
/**
|
||||
* 相机预览像素宽,请最好为正方形
|
||||
*/
|
||||
cameraWidth:{
|
||||
type:Number,
|
||||
default:800
|
||||
},
|
||||
/**
|
||||
* 相机预览像高,请最好为正方形
|
||||
*/
|
||||
cameraeiHght:{
|
||||
type:Number,
|
||||
default:800
|
||||
},
|
||||
/**
|
||||
* 是否自动打开相机
|
||||
*/
|
||||
autoOpenCamera:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
/**
|
||||
* 是否开启闪光灯
|
||||
*/
|
||||
flash:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
|
||||
},
|
||||
NVLoad() : FrameLayout {
|
||||
let grop = new FrameLayout(UTSAndroid!.getAppContext()!)
|
||||
this.CBOX = grop;
|
||||
return grop
|
||||
},
|
||||
NVLayouted() {
|
||||
if(this.autoOpenCamera){
|
||||
this.createXcamrera();
|
||||
}
|
||||
},
|
||||
NVBeforeUnload(){
|
||||
this.xcamera?.closeCamera()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 检查相机权限
|
||||
*/
|
||||
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[]) {
|
||||
//用户拒绝了部分权限
|
||||
// _this.cameraPermissioPass = false
|
||||
res(false)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 私有函数,外部不要调用。 */
|
||||
createXcamrera(){
|
||||
let _this = this;
|
||||
this.xcamera = new xCamera(this.CBOX!,{
|
||||
width:this.cameraWidth,height:this.cameraeiHght,cameraDevice:'BACK',
|
||||
zoom:5,
|
||||
flashMode:this.flash
|
||||
} as SCANNING_OPTIONS)
|
||||
|
||||
this.xcamera!.setCallEvent((str:string[],type:'auto'|'click')=>{
|
||||
if(type=='auto'){
|
||||
_this.$emit('scan',str)
|
||||
}else if(type=='click'){
|
||||
_this.$emit('clickQr',str[0]!)
|
||||
}
|
||||
})
|
||||
this.xcamera!.openCamera()
|
||||
|
||||
},
|
||||
/** 手动开启和关闭闪光灯 */
|
||||
setFlashModel(flash:boolean){
|
||||
if(this.xcamera != null){
|
||||
this.xcamera!.setFlashModel(flash)
|
||||
}
|
||||
},
|
||||
/** 手动打开相机预览 */
|
||||
openCamera(){
|
||||
let _this = this;
|
||||
let isPrivacyAgreeCamrea = uni.getStorageSync('isPrivacyAgreeCamreaByTmui')
|
||||
if(isPrivacyAgreeCamrea!='on'){
|
||||
uni.showModal({
|
||||
title:"提醒",
|
||||
content:"将授权你的设备相机使用权限进行拍摄识别功能,是否允许?",
|
||||
success(isPermisseions){
|
||||
if(isPermisseions.confirm){
|
||||
_this.checkPermissions()
|
||||
.then((res:boolean)=>{
|
||||
_this.createXcamrera()
|
||||
uni.setStorageSync('isPrivacyAgreeCamreaByTmui','on')
|
||||
})
|
||||
.catch(()=>{
|
||||
uni.setStorageSync('isPrivacyAgreeCamreaByTmui','off')
|
||||
})
|
||||
}
|
||||
},
|
||||
fail(){
|
||||
uni.showToast({title:"未授权相机使用权",icon:"none"})
|
||||
}
|
||||
})
|
||||
}else{
|
||||
_this.createXcamrera()
|
||||
}
|
||||
},
|
||||
/** 关闭相机 */
|
||||
closeCamera(){
|
||||
this.xcamera?.closeCamera()
|
||||
},
|
||||
chooseImage(){
|
||||
let _this = this;
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
albumMode:"system",
|
||||
sourceType:['album'],
|
||||
success(evt : ChooseImageSuccess) {
|
||||
|
||||
if (evt.tempFilePaths.length > 0) {
|
||||
|
||||
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
|
||||
// } as SCANNING_PHOTO_RESULT)
|
||||
// let resultstr = uni.getStorageSync("xMlKITScaning")
|
||||
_this.$emit('scan',rulstText.length==0?[]:rulstText)
|
||||
})
|
||||
|
||||
} catch (e : IOException) {
|
||||
_this.$emit('scan',[] as string[])
|
||||
}
|
||||
}
|
||||
},
|
||||
fail() {
|
||||
_this.$emit('scan',[] as string[])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -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,669 @@
|
||||
|
||||
/**
|
||||
* 数字转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) : UTSJSONObject {
|
||||
if (sColors == "") {
|
||||
return { r: 0, g: 0, b: 0, a: 0 }
|
||||
}
|
||||
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
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
if (p.length == 3) {
|
||||
p.push(1)
|
||||
}
|
||||
return {
|
||||
r: p[0],
|
||||
g: p[1],
|
||||
b: p[2],
|
||||
a: p[3]
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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,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/unimoduleXMlkitScannigU-Swift.h</key>
|
||||
<data>
|
||||
rrVnYx9RgfpqdbbblROMvU/4Unk=
|
||||
</data>
|
||||
<key>Headers/unimoduleXMlkitScannigU.h</key>
|
||||
<data>
|
||||
FH2SRnQAHIO8AbDZPa5/Bz/CcdA=
|
||||
</data>
|
||||
<key>Info.plist</key>
|
||||
<data>
|
||||
oggkQO8+5ChK5UkvRJRHpMoSBFI=
|
||||
</data>
|
||||
<key>Modules/module.modulemap</key>
|
||||
<data>
|
||||
wKuVddM+8I2G/8RcOmW6s0wX31M=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
|
||||
<data>
|
||||
jtqbS2Trq3D8rtb92vby7+bs5o4=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
||||
<data>
|
||||
aL7ncI5o4KD4BuCR7PRaOaVMe74=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
||||
<data>
|
||||
3tANMXD9JTgGV6RX7TRRcoYtUFA=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
|
||||
<data>
|
||||
V0pmF7rnAzF9xczsp8QRRW6oedw=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
|
||||
<data>
|
||||
3tANMXD9JTgGV6RX7TRRcoYtUFA=
|
||||
</data>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
|
||||
<data>
|
||||
HD1yZ99Moza0WmezwtWRpMx5Pnw=
|
||||
</data>
|
||||
<key>config.json</key>
|
||||
<data>
|
||||
YDYijrWiF8Rxzv8lihbkxXuOk7s=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
<dict>
|
||||
<key>Headers/unimoduleXMlkitScannigU-Swift.h</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
5EiTAikkGs6OTkMJQl4yJG93zGnE9T3Xrx0k9gPJx9k=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/unimoduleXMlkitScannigU.h</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
XAsze1m7Z4PhyI2gj+RafZVS4xyqWjgvqraTawdVPMY=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/module.modulemap</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
+9eWeZGAx3sZSdYnYgkJicwkn6jTvvAsdR2TYB6RkJ8=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
wP6hT6p1lWe3E3s+SUNjJQZ9E5PH6uILYCL8RbV/eT4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
qs8Ju2FnIp4JwyRbr3eZBTHjXnx1Zf+UZdZuRy+9kBs=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
HauXAU+7IlSB5MF/vIm1VHVr3OWKqepeLQgIUrC6wQ4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
MzXLl4ShiG1kAyuafALgeqtRIcp7ZWXecbXf1V6UnSM=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
HauXAU+7IlSB5MF/vIm1VHVr3OWKqepeLQgIUrC6wQ4=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/unimoduleXMlkitScannigU.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
4noLKKIKPbb2FMi/RHd6gtbVz/if6JDHpgyY8vWcJEw=
|
||||
</data>
|
||||
</dict>
|
||||
<key>config.json</key>
|
||||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
NoNtCzvQ0iOAfyiuCHYdlYR2DRmlZB9Toiooz3wQ0lQ=
|
||||
</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,297 @@
|
||||
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 } from 'UIKit';
|
||||
import { CGRect, CGFloat } from 'CoreFoundation';
|
||||
import { Barcode, BarcodeScannerOptions, BarcodeFormat, BarcodeScanner, CMVideoDimensions } from "MLKitBarcodeScanning"
|
||||
import { VisionImage } from "MLKit"
|
||||
import { SCANNING_PHOTO_RESULT, CALL_BACK_TYPE_PRIVATE, SCANNING_PHOTO_RESULT_BOUND, SCANNING_PHOTO_RESULT_POS ,AUTH_CALL_BACK_TYPE} from '../interface.uts';
|
||||
import { NotificationCenter , NSNotification , Notification } from 'Foundation';
|
||||
import { Selector } from 'ObjectiveC';
|
||||
// 识别结果
|
||||
let scanResult = [] as Swift.Array<Barcode>
|
||||
// 视频宽
|
||||
let videoWidth = 0
|
||||
// 视频高
|
||||
let videoHeight = 0
|
||||
// let camera:xCamera|null = null;
|
||||
let barcodeOptions = BarcodeScannerOptions(formats = BarcodeFormat.all)
|
||||
class CaptureOutSessionBuffer implements AVCaptureVideoDataOutputSampleBufferDelegate {
|
||||
onwerCamera:xCamera
|
||||
constructor(dv:xCamera) {
|
||||
this.onwerCamera = dv;
|
||||
super()
|
||||
}
|
||||
captureOutput(output : AVCaptureOutput, @argumentLabel("didOutput") sampleBuffer : CMSampleBuffer, @argumentLabel("from") connection : AVCaptureConnection) {
|
||||
let t = this;
|
||||
DispatchQueue.main.async(execute=():void => {
|
||||
|
||||
// console.log('图片捕捉正确')
|
||||
let image = new VisionImage(buffer = sampleBuffer)
|
||||
// 这里会直接影响到测量的起始点。下面是镜像,测量是图片左上为起始,但设备是竖向,因此计算时的x,y坐标其实是相对屏幕向右转下,x,y是反向的,因为是镜像
|
||||
// 所以计算位置时,x,y是反的,宽和高也是相反的。
|
||||
image.orientation = UIImage.Orientation.upMirrored
|
||||
|
||||
// options=barcodeOptions
|
||||
let barcodeScanner = new BarcodeScanner.barcodeScanner(options = barcodeOptions)
|
||||
console.log(image)
|
||||
barcodeScanner.process(image, completion = (features : Swift.Array<Barcode> | null, error) => {
|
||||
if (features == null || error != null) {
|
||||
return;
|
||||
}
|
||||
if (features!.length == 0 || scanResult.length>0) {
|
||||
return;
|
||||
}
|
||||
scanResult = features!
|
||||
let descVideo : CMFormatDescription | null = CMSampleBufferGetFormatDescription(sampleBuffer);
|
||||
if (descVideo != null) {
|
||||
let dimesionsize : CMVideoDimensions = CMVideoFormatDescriptionGetDimensions(descVideo!)
|
||||
t.onwerCamera.videoBox_width= Number(dimesionsize.width)
|
||||
t.onwerCamera.videoBox_height = Number(dimesionsize.height)
|
||||
}
|
||||
this.onwerCamera.pause()
|
||||
|
||||
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
|
||||
} as SCANNING_PHOTO_RESULT
|
||||
|
||||
t.onwerCamera.createPointView(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
|
||||
}
|
||||
// 相册选取权限
|
||||
PHPhotoLibrary.requestAuthorization((isok : PHAuthorizationStatus) => {
|
||||
// 拒绝了相册选取权限。
|
||||
if (isok != PHAuthorizationStatus.authorized) {
|
||||
realcall(false)
|
||||
return;
|
||||
}
|
||||
// 录像权限
|
||||
AVCaptureDevice.requestAccess(for = AVMediaType.video, completionHandler = (isok2 : boolean) => {
|
||||
if (!isok2) {
|
||||
realcall(false)
|
||||
return;
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class xCamera {
|
||||
// 父容器
|
||||
parentView : UIView|null = null;
|
||||
// 相机区域视图
|
||||
cameraView : AVCaptureVideoPreviewLayer|null = null;
|
||||
// 视频宽
|
||||
videoBox_width = 0
|
||||
// 视频高。
|
||||
videoBox_height = 0
|
||||
pointView = [] as UIView[]
|
||||
// 默认的相机
|
||||
captureDevice:AVCaptureDevice|null = null
|
||||
// 捕获会话
|
||||
captureSession:AVCaptureSession|null = null
|
||||
// 输入相机设备
|
||||
inputDevice : AVCaptureDeviceInput | null = null;
|
||||
// 输出会话
|
||||
captureOut = new AVCaptureVideoDataOutput();
|
||||
// 图像捕捉事件代理。
|
||||
captureOutDelegate : CaptureOutSessionBuffer|null = null ;
|
||||
constructor(){
|
||||
super();
|
||||
}
|
||||
|
||||
newCamraViewLayer(){
|
||||
// 设置预览层绑定
|
||||
// 下面的缩放会居中显示,因此左顶点可能在左上方
|
||||
this.cameraView!.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
this.cameraView!.connection!.videoOrientation = AVCaptureVideoOrientation.portrait
|
||||
this.cameraView!.frame = this.parentView!.layer.bounds;
|
||||
// 绑定渲染层。
|
||||
this.parentView!.layer.addSublayer(this.cameraView!)
|
||||
}
|
||||
clearPointView(){
|
||||
for (let i = 0; i < this.pointView.length; i++) {
|
||||
let item = this.pointView[i];
|
||||
item.removeFromSuperview();
|
||||
}
|
||||
this.pointView = [] as UIView[]
|
||||
}
|
||||
// 创建点。
|
||||
createPointView (result : SCANNING_PHOTO_RESULT){
|
||||
if (this.cameraView == null || this.parentView == null) return;
|
||||
let realCgSize = this.cameraView!.frame as CGRect;
|
||||
let realWidth = Number(realCgSize.width);
|
||||
let realHeight = Number(realCgSize.height);
|
||||
this.clearPointView();
|
||||
// 计算视频和容器的宽高比
|
||||
let videoAspectRatio = this.videoBox_width / this.videoBox_height
|
||||
let containerAspectRatio = realWidth / realHeight
|
||||
let ratio_real_width = 0
|
||||
let ratio_real_height = 0
|
||||
if (videoAspectRatio > containerAspectRatio) {
|
||||
ratio_real_width = realWidth
|
||||
// 视频的宽高比大于预览层的宽高比,宽为预览宽,但高会大于预览高从而被裁剪。
|
||||
ratio_real_height = realHeight * videoAspectRatio
|
||||
} else {
|
||||
ratio_real_width = realWidth / videoAspectRatio
|
||||
ratio_real_height = realHeight
|
||||
}
|
||||
|
||||
let pointSize = 30
|
||||
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 centerx = (pos[2][0] - pos[0][0]) / 2 + pos[0][0]
|
||||
let centery = (pos[2][1] - pos[0][1]) / 2 + pos[0][1]
|
||||
let diffTop = (realHeight - ratio_real_height) / 2
|
||||
let diffLeft = (realWidth - ratio_real_width) / 2
|
||||
let x = (ratio_real_width - ratio_real_width * (bounds.centerY / this.videoBox_height) - diffLeft - 15).toInt()
|
||||
|
||||
let y = (ratio_real_height * (bounds.centerX / this.videoBox_width) + diffTop - 15).toInt()
|
||||
// console.log(bounds.centerX,bounds.centerY,x,y,videoWidth,videoHeight,realWidth,realHeight,ratio_real_width,ratio_real_height)
|
||||
|
||||
let rect = new CGRect(x = x, y = y, 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);
|
||||
pointViewSelf.isUserInteractionEnabled = true;
|
||||
// let onlick = new PointClickEvent((atext:string) => {
|
||||
// console.log("ok....",result.text)
|
||||
// component!.__$$emit('clickQr',new Map<string,string>([["result",atext]]))
|
||||
// },text);
|
||||
// pointClickEvent.push(onlick)
|
||||
// let tapGestureCancel = new UITapGestureRecognizer()
|
||||
// tapGestureCancel.addTarget(pointClickEvent[pointClickEvent.length-1], action = new Selector("onclick"))
|
||||
// pointViewSelf.addGestureRecognizer(tapGestureCancel)
|
||||
|
||||
this.parentView!.addSubview(pointViewSelf)
|
||||
|
||||
this.pointView.push(pointViewSelf)
|
||||
|
||||
}
|
||||
}
|
||||
setView(view:UIView){
|
||||
this.parentView = view;
|
||||
}
|
||||
oninit(){
|
||||
this.captureDevice = AVCaptureDevice.default(for = AVMediaType.video)
|
||||
this.captureSession = new AVCaptureSession();
|
||||
this.captureSession!.sessionPreset = AVCaptureSession.Preset.low
|
||||
|
||||
this.captureOutDelegate = new CaptureOutSessionBuffer(this)
|
||||
|
||||
// 注册相机启动时的通知事件。
|
||||
NotificationCenter.default.addObserver(this,selector = Selector("camreaOpeingStatus"),name=NSNotification.Name("didStartRunningNotification"),object = null)
|
||||
// 新建设备的输入设备
|
||||
this.inputDevice = UTSiOS.try(new AVCaptureDeviceInput(device = this.captureDevice!), "?")
|
||||
if(this.inputDevice!=null){
|
||||
// 添加输入到会话中。
|
||||
this.captureSession!.addInput(this.inputDevice!)
|
||||
}
|
||||
|
||||
this.captureOut.alwaysDiscardsLateVideoFrames = true;
|
||||
// 设置输出会话
|
||||
this.captureOut.setSampleBufferDelegate(this.captureOutDelegate!, queue = DispatchQueue.global())
|
||||
if (this.captureSession!.canAddOutput(this.captureOut)) {
|
||||
this.captureSession!.addOutput(this.captureOut)
|
||||
console.log("绑定输出层正确!")
|
||||
}
|
||||
let t = this;
|
||||
DispatchQueue.main.async(execute=():void => {
|
||||
t.cameraView = new AVCaptureVideoPreviewLayer(session = t.captureSession!)
|
||||
// 设置预览层绑定
|
||||
// 下面的缩放会居中显示,因此左顶点可能在左上方
|
||||
t.cameraView!.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
t.cameraView!.connection!.videoOrientation = AVCaptureVideoOrientation.portrait
|
||||
t.cameraView!.frame = t.parentView!.layer.bounds;
|
||||
// 绑定渲染层。
|
||||
t.parentView!.layer.addSublayer(t.cameraView!)
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
@objc static camreaOpeingStatus(notification:Notification){
|
||||
|
||||
console.log(notification,'启动了。')
|
||||
}
|
||||
open(){
|
||||
this.clearPointView()
|
||||
this.pause();
|
||||
this.captureSession!.startRunning()
|
||||
this.oninit();
|
||||
}
|
||||
stop(){
|
||||
// 暂停预览。
|
||||
this.pause()
|
||||
// 移除通知。
|
||||
NotificationCenter.default.removeObserver(this, name=NSNotification.Name("didStartRunningNotification"),object = null)
|
||||
// 移除视频预览层。
|
||||
this.cameraView!.removeFromSuperlayer()
|
||||
// 移除点。
|
||||
this.clearPointView()
|
||||
if(this.inputDevice!=null){
|
||||
this.captureSession!.removeInput(this.inputDevice!)
|
||||
}
|
||||
this.captureSession!.removeOutput(this.captureOut)
|
||||
|
||||
}
|
||||
pause(){
|
||||
if(this.captureSession!.isRunning){
|
||||
this.captureSession!.stopRunning()
|
||||
|
||||
}
|
||||
this.cameraView!.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"deploymentTarget": "12",
|
||||
"dependencies-pods": [
|
||||
{
|
||||
"name": "GoogleMLKit/BarcodeScanning",
|
||||
"version": "5.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
<template>
|
||||
<view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
// import { UIView } from 'UIKit';
|
||||
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 } from 'UIKit';
|
||||
import { CGRect, CGFloat, CGPoint, CGAffineTransform } from 'CoreFoundation';
|
||||
import { Barcode, BarcodeScannerOptions, BarcodeFormat, BarcodeScanner, CMVideoDimensions } from "MLKitBarcodeScanning"
|
||||
import { VisionImage } from "MLKit"
|
||||
import { SCANNING_PHOTO_RESULT, CALL_BACK_TYPE_PRIVATE, SCANNING_PHOTO_RESULT_BOUND, SCANNING_PHOTO_RESULT_POS, AUTH_CALL_BACK_TYPE } from '../interface.uts';
|
||||
import { NotificationCenter, NSNotification, Notification } from 'Foundation';
|
||||
import { Selector } from 'ObjectiveC';
|
||||
|
||||
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 inputDevice : AVCaptureDeviceInput | null = null;
|
||||
// 识别结果
|
||||
let scanResult = [] as Swift.Array<Barcode>
|
||||
let captureSession : null | AVCaptureSession = null;
|
||||
// 视频宽
|
||||
let videoWidth = 0
|
||||
// 视频高
|
||||
let videoHeight = 0
|
||||
// 图像捕捉事件代理。
|
||||
let captureOutDelegate : CaptureOutSessionBuffer | null = null;
|
||||
let pointsEvents = [] as PointClickEvent[]
|
||||
let isCamreaing = false;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
previewLayer: null as null | UIView,
|
||||
pointView: [] as UIView[],
|
||||
avcapDev:null as null|AVCaptureDevice
|
||||
}
|
||||
},
|
||||
name: "x-mlkit-scannig-u",
|
||||
emits: ["scan", "clickQr"],
|
||||
props:{
|
||||
/**
|
||||
* 是否开启闪光灯
|
||||
*/
|
||||
flash:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
// 创建组件对应的原生 View,返回值类型需要修改为实际 View 类型
|
||||
NVLoad() : UIView {
|
||||
let view = new UIView();
|
||||
this.previewLayer = view
|
||||
|
||||
return view
|
||||
},
|
||||
NVLayouted() {
|
||||
|
||||
|
||||
},
|
||||
NVBeforeUnload() {
|
||||
// 注销前关闭摄像头。
|
||||
this.closeCamera();
|
||||
},
|
||||
methods: {
|
||||
|
||||
isCheckPermissions(callFun : null | AUTH_CALL_BACK_TYPE) {
|
||||
let realcall : AUTH_CALL_BACK_TYPE = (_ : boolean) => { };
|
||||
if (callFun != null) {
|
||||
let cllfunFun = callFun!;
|
||||
realcall = cllfunFun
|
||||
}
|
||||
|
||||
AVCaptureDevice.requestAccess(for = AVMediaType.video, completionHandler = (isok2 : boolean) => {
|
||||
if (!isok2) {
|
||||
realcall(false)
|
||||
return;
|
||||
}
|
||||
realcall(true)
|
||||
})
|
||||
},
|
||||
/** 手动开启和关闭闪光灯 */
|
||||
setFlashModel(flash:boolean){
|
||||
if(this.avcapDev!=null){
|
||||
let islock = UTSiOS.try(this.avcapDev!.lockForConfiguration(),"?")
|
||||
if(islock!=null){
|
||||
this.avcapDev!.torchMode = flash? AVCaptureDevice.TorchMode.on : AVCaptureDevice.TorchMode.off
|
||||
this.avcapDev!.unlockForConfiguration()
|
||||
}
|
||||
}
|
||||
},
|
||||
openCamera() {
|
||||
let t = this;
|
||||
this.isCheckPermissions((auth : boolean) => {
|
||||
if (auth) {
|
||||
t.createVideo()
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
chooseImage() {
|
||||
let t = this;
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sourceType:['album'],
|
||||
success(evt : ChooseImageSuccess) {
|
||||
|
||||
if (evt.tempFilePaths.length > 0) {
|
||||
let imgs = evt.tempFilePaths[0] as string;
|
||||
let realImgPath = imgs as string
|
||||
if(imgs.indexOf('file://')>-1){
|
||||
realImgPath = imgs.substring(7) as string;
|
||||
}
|
||||
let image = new UIImage(contentsOfFile = realImgPath);
|
||||
|
||||
if (image != null) {
|
||||
let realimageds = image! as UIImage;
|
||||
|
||||
// 图片识别码.
|
||||
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) {
|
||||
uni.setStorageSync("xMlKITScaning", JSON.stringify([] as string[]))
|
||||
t.$emit("scan")
|
||||
return;
|
||||
}
|
||||
if (features!.length == 0) {
|
||||
uni.setStorageSync("xMlKITScaning", JSON.stringify([] as string[]))
|
||||
t.$emit("scan")
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
} as SCANNING_PHOTO_RESULT
|
||||
|
||||
|
||||
uni.setStorageSync("xMlKITScaning", JSON.stringify(rulstText))
|
||||
t.$emit("scan")
|
||||
})
|
||||
} else {
|
||||
|
||||
uni.setStorageSync("xMlKITScaning", JSON.stringify([] as string[]))
|
||||
t.$emit("scan")
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
uni.setStorageSync("xMlKITScaning", JSON.stringify([] as string[]))
|
||||
t.$emit("scan")
|
||||
}
|
||||
},
|
||||
fail() {
|
||||
uni.setStorageSync("xMlKITScaning", JSON.stringify([] as string[]))
|
||||
t.$emit("scan")
|
||||
}
|
||||
})
|
||||
},
|
||||
closeCamera() {
|
||||
// 移除点。
|
||||
this.clearPointView()
|
||||
this.setFlashModel(false)
|
||||
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[]
|
||||
this.avcapDev = null;
|
||||
|
||||
},
|
||||
createPointView(result : SCANNING_PHOTO_RESULT) {
|
||||
|
||||
let realCgSize = this.previewLayer!.frame as CGRect;
|
||||
let realWidth = Number(realCgSize.width);
|
||||
let realHeight = Number(realCgSize.height);
|
||||
this.clearPointView();
|
||||
// 计算视频和容器的宽高比
|
||||
let videoAspectRatio = videoWidth / videoHeight
|
||||
let containerAspectRatio = realWidth / realHeight
|
||||
let ratio_real_width = 0
|
||||
let ratio_real_height = 0
|
||||
if (videoAspectRatio > containerAspectRatio) {
|
||||
ratio_real_width = realWidth
|
||||
// 视频的宽高比大于预览层的宽高比,宽为预览宽,但高会大于预览高从而被裁剪。
|
||||
ratio_real_height = realHeight * videoAspectRatio
|
||||
} else {
|
||||
ratio_real_width = realWidth / videoAspectRatio
|
||||
ratio_real_height = realHeight
|
||||
}
|
||||
let t = this;
|
||||
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 centerx = (pos[2][0] - pos[0][0]) / 2 + pos[0][0]
|
||||
let centery = (pos[2][1] - pos[0][1]) / 2 + pos[0][1]
|
||||
let diffTop = (realHeight - ratio_real_height) / 2
|
||||
let diffLeft = (realWidth - ratio_real_width) / 2
|
||||
let x = (ratio_real_width - ratio_real_width * (bounds.centerY / videoHeight) - diffLeft - 15).toInt()
|
||||
|
||||
let y = (ratio_real_height * (bounds.centerX / videoWidth) + diffTop - 15).toInt()
|
||||
// console.log(bounds.centerX,bounds.centerY,x,y,videoWidth,videoHeight,realWidth,realHeight,ratio_real_width,ratio_real_height)
|
||||
let result = calcPoint(realWidth, realHeight, videoWidth, videoHeight, bounds.centerX, bounds.centerY)
|
||||
let rect = new CGRect(x = (result.x - 15).toInt(), y = (result.y - 15).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) => {
|
||||
console.log("ok....", atext)
|
||||
uni.setStorageSync("xMlKITScaningClick", atext)
|
||||
t.$emit('clickQr')
|
||||
}, text);
|
||||
pointsEvents.push(onlick)
|
||||
let tapGestureCancel = new UITapGestureRecognizer()
|
||||
tapGestureCancel.addTarget(onlick, action = new Selector("onclick"))
|
||||
pointViewSelf.addGestureRecognizer(tapGestureCancel)
|
||||
|
||||
|
||||
this.previewLayer!.addSubview(pointViewSelf)
|
||||
this.pointView.push(pointViewSelf)
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
clearPointView() {
|
||||
DispatchQueue.main.async(execute = () : void => {
|
||||
for (let i = 0; i < this.pointView.length; i++) {
|
||||
let item = this.pointView[i];
|
||||
item.removeFromSuperview();
|
||||
}
|
||||
this.pointView = [] as UIView[]
|
||||
})
|
||||
},
|
||||
|
||||
createVideo() {
|
||||
let t = this;
|
||||
isCamreaing = false;
|
||||
this.closeCamera();
|
||||
captureSession = new AVCaptureSession();
|
||||
let captureDevice = AVCaptureDevice.default(for = AVMediaType.video)
|
||||
let captureOut = new AVCaptureVideoDataOutput();
|
||||
|
||||
captureSession!.sessionPreset = AVCaptureSession.Preset.photo
|
||||
let parentView = this.previewLayer!;
|
||||
|
||||
captureOutDelegate = new CaptureOutSessionBuffer((result : SCANNING_PHOTO_RESULT) => {
|
||||
t.createPointView(result)
|
||||
uni.setStorageSync("xMlKITScaning", JSON.stringify(result.text))
|
||||
t.$emit("scan")
|
||||
})
|
||||
|
||||
// 新建设备的输入设备
|
||||
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("绑定输出层正确!")
|
||||
}
|
||||
|
||||
|
||||
DispatchQueue.main.async(execute = () : void => {
|
||||
let cameraView = new AVCaptureVideoPreviewLayer(session = captureSession!)
|
||||
// 设置预览层绑定
|
||||
// 下面的缩放会居中显示,因此左顶点可能在左上方
|
||||
cameraView.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
//后摄像头,设备是横向向的,显示到竖向,是顺时旋转90度.
|
||||
cameraView.connection!.videoOrientation = AVCaptureVideoOrientation.portrait
|
||||
cameraView.frame = parentView.layer.bounds;
|
||||
// 绑定渲染层。
|
||||
parentView.layer.addSublayer(cameraView)
|
||||
captureSession!.startRunning()
|
||||
t.avcapDev = captureDevice!
|
||||
t.setFlashModel(t.flash)
|
||||
// ios识别太快了,图像还没定焦就识别出来了,稍微延迟下,不要太快了。与安卓对齐 下。
|
||||
setTimeout(function () {
|
||||
isCamreaing = true;
|
||||
}, 300);
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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.upMirrored
|
||||
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
|
||||
} as SCANNING_PHOTO_RESULT
|
||||
|
||||
this.callback(res)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//映射坐标.
|
||||
function getCorrectionMatrix(c_w : number, c_h : number, image_w : number, image_h : number, image_point_x : number, image_point_y : number) : CGPoint {
|
||||
let sourcePoints = [
|
||||
0,
|
||||
0,
|
||||
image_w.toInt(),
|
||||
0,
|
||||
image_w.toInt(),
|
||||
image_h.toInt(),
|
||||
0,
|
||||
image_h.toInt()
|
||||
]
|
||||
let destinationPoints = [
|
||||
0,
|
||||
0,
|
||||
c_w.toInt(),
|
||||
0,
|
||||
c_w.toInt(),
|
||||
c_h.toInt(),
|
||||
0,
|
||||
c_h.toInt()
|
||||
]
|
||||
|
||||
let vertexSize = 2
|
||||
let rotationDegrees = 90 // 默认后置摄像头就是90度的
|
||||
let shiftOffset = rotationDegrees / 90 * vertexSize;
|
||||
let tempArray = destinationPoints.slice(0)
|
||||
for (let i = 0; i < sourcePoints.length; i++) {
|
||||
let fromIndex = (i + shiftOffset) % sourcePoints.length
|
||||
destinationPoints[i] = tempArray[fromIndex.toInt()]
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
let result = mapContentCoordinatesToContainer(image_point_y, image_point_x, rc)
|
||||
|
||||
let cp = new CGPoint(x = (c_w - result.x).toInt(), y = result.y.toInt());
|
||||
return cp;
|
||||
}
|
||||
function calcPoint(c_w : number, c_h : number, image_w : number, image_h : number, image_point_x : number, image_point_y : number) {
|
||||
// 图片被顺时旋转了90度
|
||||
let newconverxy = getCorrectionMatrix(c_w, c_h, image_w, image_h, image_point_x, image_point_y)
|
||||
return {
|
||||
x: newconverxy.x, y: newconverxy.y, minx: 0, maxx: 0, miny: 0, maxy: 0
|
||||
} as ConverXy
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,69 @@
|
||||
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[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type CAMERA_PHOTO_SIZE = {
|
||||
width:number,
|
||||
height:number
|
||||
}
|
||||
export type CallFunType = (str:string[],type:'auto'|'click') => void;
|
||||
export type CALL_BACK_TYPE_PRIVATE = (str:string[])=>void
|
||||
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,
|
||||
flashMode:boolean
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<view :id="uid2">
|
||||
<camera v-if="isOpenScan" style="width100%;height:100%" mode="scanCode" device-positio="back"
|
||||
flash="auto" @scancode="scancodeEvt" @error="scanError" ></camera>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import Zxing from "./zxing.js"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uid: "xcanvas" + Date.now(),
|
||||
uid2: "xcanvas2" + Date.now(),
|
||||
resultQr: "",
|
||||
flashMode: "off",
|
||||
orientationMode: "back",
|
||||
isOpenScan: false
|
||||
}
|
||||
},
|
||||
name: "x-mlkit-scannig-u",
|
||||
emits: ["scan", "clickQr"],
|
||||
props: {
|
||||
orientation:{
|
||||
type:String,
|
||||
default:"back"
|
||||
},
|
||||
/**
|
||||
* 是否打开闪光灯,对于orientation为back有效.
|
||||
*/
|
||||
flash: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
NVLoad() : any {
|
||||
return null;
|
||||
},
|
||||
NVLayouted() {
|
||||
this.flashMode = this.flash ? 'on' : 'off'
|
||||
|
||||
},
|
||||
NVBeforeUnload() {
|
||||
},
|
||||
watch:{
|
||||
flash(newvalue){
|
||||
this.flashMode = newvalue ? 'on' : 'off'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
scanError(e){
|
||||
console.error(e)
|
||||
},
|
||||
scancodeEvt(evt) {
|
||||
const qrdata = evt.detail.result;
|
||||
if(this.resultQr==""){
|
||||
this.resultQr = qrdata
|
||||
this.$emit("scan",[qrdata])
|
||||
}
|
||||
},
|
||||
openCamera() {
|
||||
this.isOpenScan = true;
|
||||
},
|
||||
chooseImage() {
|
||||
let t = this;
|
||||
|
||||
uni.chooseImage({
|
||||
sourceType:["album"],
|
||||
async success(res) {
|
||||
if(res.tempFilePaths.length == 0) return
|
||||
wx.compressImage({
|
||||
src:res.tempFilePaths[0],
|
||||
compressedWidth:600,
|
||||
async success(res2) {
|
||||
let imgpath = res2.tempFilePath
|
||||
|
||||
const canvas = wx.createOffscreenCanvas({
|
||||
type: "2d",
|
||||
width: 400,
|
||||
height: 400
|
||||
}, this)
|
||||
|
||||
// 创建一个图片
|
||||
const image = canvas.createImage()
|
||||
// 等待图片加载
|
||||
await new Promise(resolve => {
|
||||
image.onload = resolve
|
||||
image.src = imgpath
|
||||
})
|
||||
let width = 400
|
||||
let height = width*image.width / image.height
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
// 把图片画到离屏 canvas 上
|
||||
context.clearRect(0, 0, width, height)
|
||||
context.drawImage(image, 0, 0, width, height)
|
||||
// 获取画完后的数据
|
||||
const imageData = context.getImageData(0, 0, 400, 400)
|
||||
// https://www.npmjs.com/package/jsqr-es6
|
||||
let code = new Zxing(imageData.data,400,400,{inversionAttempts:true})
|
||||
if(code&&code?.data){
|
||||
t.$emit("scan", [code.data])
|
||||
}else{
|
||||
t.$emit("scan", [])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
},
|
||||
closeCamera() {
|
||||
this.isOpenScan = false;
|
||||
this.resultQr = ""
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<view :id="id">
|
||||
|
||||
</view>
|
||||
</template>
|
||||
<script lang="uts">
|
||||
import Zxing from "./zxing.js"
|
||||
|
||||
var resultQr = ""
|
||||
let uuid = Date.now().toString()
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
id: uuid,
|
||||
video: null as null | HTMLElement,
|
||||
qre: null,
|
||||
cid : null
|
||||
}
|
||||
},
|
||||
name: "x-mlkit-scannig-u",
|
||||
emits: ["scan", "clickQr"],
|
||||
// 创建组件对应的原生 View,返回值类型需要修改为实际 View 类型
|
||||
NVLoad() : HTMLElement {
|
||||
return document.createElement("div") as HTMLElement
|
||||
},
|
||||
NVLayouted() {
|
||||
},
|
||||
NVBeforeUnload() {
|
||||
cancelAnimationFrame(this.cid)
|
||||
},
|
||||
methods: {
|
||||
openCamera() {
|
||||
let t = this;
|
||||
t.qre = null;
|
||||
resultQr = ""
|
||||
|
||||
function scanning() {
|
||||
t.closeCamera()
|
||||
let parentView = document.getElementById(uuid)
|
||||
let video : HTMLVideoElement = document.createElement("video")
|
||||
video.style.width = "100%"
|
||||
video.style.height = "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')
|
||||
|
||||
|
||||
t.video = video;
|
||||
|
||||
parentView!.appendChild(video)
|
||||
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;
|
||||
t.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
|
||||
t.$emit("scan", [code.data])
|
||||
cancelAnimationFrame(t.cid)
|
||||
}
|
||||
|
||||
t.cid = requestAnimationFrame(readFrame);
|
||||
}
|
||||
// 开始读取帧
|
||||
readFrame();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error accessing media devices:', error);
|
||||
});
|
||||
|
||||
}
|
||||
scanning()
|
||||
},
|
||||
chooseImage() {
|
||||
let t = this;
|
||||
function scanning() {
|
||||
uni.chooseImage({
|
||||
sourceType:["album"],
|
||||
success(res) {
|
||||
if (res.tempFilePaths.length > 0) {
|
||||
let img = new Image()
|
||||
img.src = res.tempFilePaths[0];
|
||||
img.onload = function () {
|
||||
close();
|
||||
var newWidth = 600;
|
||||
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 = Zxing(imageData.data,600,600,{inversionAttempts:true})
|
||||
if(code&&code?.data){
|
||||
t.$emit("scan", [code.data])
|
||||
}else{
|
||||
t.$emit("scan", [])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
scanning()
|
||||
},
|
||||
closeCamera() {
|
||||
let t = this;
|
||||
let parentView = document.getElementById(uuid)
|
||||
parentView!.innerHTML = ""
|
||||
if (t.qre) {
|
||||
t.video?.pause();
|
||||
// 停止所有轨道
|
||||
this.qre.getTracks().forEach(function (track) {
|
||||
track.stop();
|
||||
});
|
||||
}
|
||||
resultQr = ""
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user