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