|
|
@@ -0,0 +1,970 @@
|
|
|
+/*
|
|
|
+ * 相机控制
|
|
|
+ * */
|
|
|
+import router from '@ohos.router';
|
|
|
+import image from '@ohos.multimedia.image';
|
|
|
+import fs from "@ohos.file.fs"
|
|
|
+import CommonEventManager from '@ohos.commonEventManager'
|
|
|
+import promptAction from '@ohos.promptAction';
|
|
|
+import { PreviewManager } from '../../common/util/PreviewManager';
|
|
|
+import { ConfirmDialog } from '../ConfirmDialog';
|
|
|
+import { ConfirmDialogParams } from '../../viewmodel/ConfirmDialogParam';
|
|
|
+import OperationComponent from '../../viewmodel/process/OperationComponent';
|
|
|
+
|
|
|
+const TAG = "sony_camera info"
|
|
|
+
|
|
|
+@Component
|
|
|
+export struct PhotoCollectView {
|
|
|
+ private scrollerPhoto: Scroller = new Scroller()
|
|
|
+ @Link opComponents: OperationComponent[]
|
|
|
+ @Link index:number
|
|
|
+ //
|
|
|
+ @State isExpanded: boolean = false
|
|
|
+ //闪光模式 0:自动 1:开启 2:关闭
|
|
|
+ @State selectedFlashMode:number = 0
|
|
|
+ @State uploadButtonClick:number =1
|
|
|
+ //选择的照片索引
|
|
|
+ @State selectedPhotoIndex:number = -1
|
|
|
+ //拍照按键缩放
|
|
|
+ @State shootButtonClick:number =1
|
|
|
+ // 旋转角度
|
|
|
+ @State rotateAngle: number = 0
|
|
|
+ // 照片缩放比例
|
|
|
+ @State scaleValue: number = 1
|
|
|
+ // 照片X轴偏移
|
|
|
+ @State offsetX: number = 0
|
|
|
+ // 照片Y轴偏移
|
|
|
+ @State offsetY: number = 0
|
|
|
+ // 照片上次缩放值
|
|
|
+ @State lastScale: number = 1
|
|
|
+ // 照片上次X偏移
|
|
|
+ @State lastOffsetX: number = 0
|
|
|
+ // 照片上次Y偏移
|
|
|
+ @State lastOffsetY: number = 0
|
|
|
+ //双指缩放的中心点X
|
|
|
+ @State pinchCenterX: number = 0;
|
|
|
+ //双指缩放的中心点Y
|
|
|
+ @State pinchCenterY: number = 0;
|
|
|
+ // 照片列表
|
|
|
+ //@State photoList:DrawingInfo[]=[]
|
|
|
+ // 获取本地live照片
|
|
|
+ @State commodityPixelMap: PixelMap | null = null;
|
|
|
+ // 拍照动作是否完成
|
|
|
+ @State isCapturing: boolean = false;
|
|
|
+ // 是否上传中
|
|
|
+ @State isUploading: boolean = false;
|
|
|
+ // 是否停止预览
|
|
|
+ @State isStopView: boolean = false;
|
|
|
+ // 控制帧率
|
|
|
+ @State readTimer:number = 0
|
|
|
+ // 预览操作
|
|
|
+ private previewManager: PreviewManager | null = null;
|
|
|
+ @State photoPixelMaps: photoInfo[] = []; // 存储所有照片的 PixelMap
|
|
|
+
|
|
|
+ // 加载照片并生成 PixelMap
|
|
|
+ loadPhotos = async () => {
|
|
|
+ const context = getContext(this);
|
|
|
+ console.info(TAG, '开始加载照片', context.filesDir);
|
|
|
+ try {
|
|
|
+ const localImageDir = `${context.filesDir}/`;
|
|
|
+ // 检查目录是否存在
|
|
|
+ if (!fs.accessSync(localImageDir)) {
|
|
|
+ console.warn(TAG, "目录不存在");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 获取目录下所有文件
|
|
|
+ const files = fs.listFileSync(localImageDir);
|
|
|
+ const newPixelMaps: photoInfo[] = [];
|
|
|
+ // 加载所有以image_开头的图片
|
|
|
+ for (const file of files) {
|
|
|
+ // 只处理以image_开头的文件
|
|
|
+ if (!file.startsWith('image_') || !file.endsWith('.jpg')) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const filePath = `${localImageDir}${file}`;
|
|
|
+ try {
|
|
|
+ const imageSource = image.createImageSource(filePath);
|
|
|
+ const imageInfo = await imageSource.getImageInfo();
|
|
|
+ const scaleRatio = Math.min(600 / imageInfo.size.width, 800 / imageInfo.size.height);
|
|
|
+
|
|
|
+ const thumbnailOpts: image.InitializationOptions = {
|
|
|
+ size: {
|
|
|
+ width: Math.floor(imageInfo.size.width * scaleRatio),
|
|
|
+ height: Math.floor(imageInfo.size.height * scaleRatio)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const pixelMap = await imageSource.createPixelMap(thumbnailOpts);
|
|
|
+ const timestamp = this.parseTimestampFromFilename(file);
|
|
|
+ newPixelMaps.push({
|
|
|
+ pixelMap: pixelMap,
|
|
|
+ timestamp: timestamp,
|
|
|
+ filename: file
|
|
|
+ });
|
|
|
+ imageSource.release();
|
|
|
+ } catch (err) {
|
|
|
+ console.error(TAG, `加载图片 ${file} 失败:`, err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 按时间戳降序排序
|
|
|
+ newPixelMaps.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
|
+ this.photoPixelMaps = newPixelMaps;
|
|
|
+ } catch (err) {
|
|
|
+ console.error(TAG, "加载照片失败:", err);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ private parseTimestampFromFilename(filename: string): string {
|
|
|
+ if (!filename.startsWith("image_") || !filename.endsWith(".jpg")) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ const dateTimePart = filename.substring(6, filename.length - 4);
|
|
|
+ if (dateTimePart.length !== 15) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ const datePart = dateTimePart.substring(0, 8);
|
|
|
+ const timePart = dateTimePart.substring(9);
|
|
|
+ const year = datePart.substring(0, 4);
|
|
|
+ const month = datePart.substring(4, 6);
|
|
|
+ const day = datePart.substring(6, 8);
|
|
|
+ const hour = timePart.substring(0, 2);
|
|
|
+ const minute = timePart.substring(2, 4);
|
|
|
+ const second = timePart.substring(4);
|
|
|
+ return `${year}/${month}/${day} ${hour}:${minute}:${second}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ private releaseAllPixelMaps() {
|
|
|
+ this.photoPixelMaps.forEach(item => item.pixelMap?.release());
|
|
|
+ this.photoPixelMaps = [];
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 删除照片
|
|
|
+ deletePhoto = async (index: number) => {
|
|
|
+ try {
|
|
|
+ const context = getContext(this);
|
|
|
+ const photoToDelete = this.photoPixelMaps[index];
|
|
|
+ const filePath = `${context.filesDir}/${photoToDelete.filename}`;
|
|
|
+
|
|
|
+ console.info(TAG, `要删除文件: ${filePath}`);
|
|
|
+
|
|
|
+ // 1. 删除物理文件
|
|
|
+ fs.unlinkSync(filePath);
|
|
|
+ console.info(TAG, `已删除文件: ${filePath}`);
|
|
|
+
|
|
|
+ // 2. 释放PixelMap资源
|
|
|
+ photoToDelete.pixelMap.release();
|
|
|
+
|
|
|
+ // 3. 从数组中移除
|
|
|
+ const newPhotoList = [...this.photoPixelMaps];
|
|
|
+ newPhotoList.splice(index, 1);
|
|
|
+ this.photoPixelMaps = newPhotoList;
|
|
|
+
|
|
|
+ // 4. 处理选中的照片索引
|
|
|
+ if (this.photoPixelMaps.length === 0) {
|
|
|
+ // 如果所有照片都删完了
|
|
|
+ this.selectedPhotoIndex = -1;
|
|
|
+ this.startView();
|
|
|
+ } else {
|
|
|
+ // 还有照片剩余的情况
|
|
|
+ if (this.selectedPhotoIndex === index) {
|
|
|
+ // 当前删除的是正在显示的照片
|
|
|
+ if (index > 0) {
|
|
|
+ // 如果不是第一张,显示前一张
|
|
|
+ this.selectedPhotoIndex = index - 1;
|
|
|
+ } else {
|
|
|
+ // 如果是第一张,显示下一张(现在index=0,所以下一张也是0)
|
|
|
+ this.selectedPhotoIndex = 0;
|
|
|
+ }
|
|
|
+ } else if (this.selectedPhotoIndex > index) {
|
|
|
+ // 删除的照片在当前显示照片之前,需要调整索引
|
|
|
+ this.selectedPhotoIndex -= 1;
|
|
|
+ }
|
|
|
+ // 如果删除的照片在当前显示照片之后,不需要调整索引
|
|
|
+ }
|
|
|
+
|
|
|
+ promptAction.showToast({
|
|
|
+ message: '照片删除成功',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ console.error(TAG, "删除照片失败:", err.code);
|
|
|
+ promptAction.showToast({
|
|
|
+ message: '删除照片失败',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ //创建订阅者
|
|
|
+ subscriber: CommonEventManager.CommonEventSubscriber | null = null;
|
|
|
+ //订阅相机回调
|
|
|
+ subscribeInfo: CommonEventManager.CommonEventSubscribeInfo = { events: ["sonycamera_callback"] };
|
|
|
+
|
|
|
+ //提示确认弹窗
|
|
|
+ commonDialogController: CustomDialogController | null = null;
|
|
|
+ async showConfirmDialog(params: ConfirmDialogParams) {
|
|
|
+ if (this.commonDialogController) {
|
|
|
+ this.commonDialogController.close()
|
|
|
+ }
|
|
|
+
|
|
|
+ this.commonDialogController = new CustomDialogController({
|
|
|
+ builder: ConfirmDialog({
|
|
|
+ title: params.title || '提示',
|
|
|
+ message: params.message,
|
|
|
+ onConfirm: params.onConfirm
|
|
|
+ }),
|
|
|
+ cancel: () => console.log('用户取消操作'),
|
|
|
+ customStyle: true,
|
|
|
+ autoCancel:false,
|
|
|
+ maskColor: 'rgba(0,0,0,0.6)'
|
|
|
+ });
|
|
|
+
|
|
|
+ this.commonDialogController.open();
|
|
|
+ }
|
|
|
+
|
|
|
+ //订阅回调(code=3代表连接成功,code=2代表拍照成功)
|
|
|
+ createSubscriber = async () => {
|
|
|
+ this.subscriber = await CommonEventManager.createSubscriber(this.subscribeInfo);
|
|
|
+ if (this.subscriber) {
|
|
|
+ console.info(TAG, "创建订阅回调成功");
|
|
|
+ CommonEventManager.subscribe(this.subscriber, (err, data) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.error(TAG, "SubscribeCallBack err=" + JSON.stringify(err));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ console.info(TAG, "SubscribeCallBack data=" + JSON.stringify(data));
|
|
|
+ if (data?.code !== undefined) {
|
|
|
+ switch (data.code) {
|
|
|
+ case 2:
|
|
|
+ this.loadPhotos()
|
|
|
+ this.isCapturing =false
|
|
|
+ this.startView(); // 拍照成功
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ console.info(TAG, "开始预览");
|
|
|
+ this.startView(); // 连接成功
|
|
|
+ break;
|
|
|
+ case -1:
|
|
|
+ console.info(TAG,'连接故障')
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+ console.info(TAG,'暂停预览')
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //0001:自动闪光 0002:关闭闪光 0003:开启闪光
|
|
|
+ if (data?.parameters?.flash_mode) {
|
|
|
+ const flashMode:string = data.parameters.flash_mode
|
|
|
+ console.info(TAG, `收到闪光模式: ${flashMode}`);
|
|
|
+ switch (flashMode) {
|
|
|
+ case '0001':
|
|
|
+ this.selectedFlashMode = 0
|
|
|
+ break;
|
|
|
+ case '0002':
|
|
|
+ this.selectedFlashMode = 2
|
|
|
+ break;
|
|
|
+ case '0003':
|
|
|
+ this.selectedFlashMode = 1
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ console.warn(TAG, `未知闪光模式: ${flashMode}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ //旋转照片
|
|
|
+ private rotateImage(angle: number) {
|
|
|
+ this.offsetX = 0;
|
|
|
+ this.offsetY = 0;
|
|
|
+ this.rotateAngle += angle
|
|
|
+ if (this.rotateAngle >= 360) {
|
|
|
+ this.rotateAngle -= 360
|
|
|
+ } else if (this.rotateAngle < 0) {
|
|
|
+ this.rotateAngle += 360
|
|
|
+ }
|
|
|
+ if(this.rotateAngle==90||this.rotateAngle==270)
|
|
|
+ {
|
|
|
+ this.scaleValue=0.667
|
|
|
+ }else {
|
|
|
+ this.scaleValue=1
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重置图片变换
|
|
|
+ private resetImageTransform() {
|
|
|
+ this.rotateAngle = 0
|
|
|
+ this.scaleValue = 1
|
|
|
+ this.offsetX = 0
|
|
|
+ this.offsetY = 0
|
|
|
+ this.lastScale = 1
|
|
|
+ this.lastOffsetX = 0
|
|
|
+ this.lastOffsetY = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ //连接相机
|
|
|
+ connectCamera = async () => {
|
|
|
+ CommonEventManager.publish("opensession", (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.info(TAG,"Publish openSession err=" + JSON.stringify(err))
|
|
|
+ } else {
|
|
|
+ console.info(TAG,"Publish openSession succeed ")
|
|
|
+ this.queryFlashMode();
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ //查询闪光模式
|
|
|
+ queryFlashMode = async () => {
|
|
|
+ CommonEventManager.publish("queryflashmode", (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.info(TAG,"Publish queryflashmode err=" + JSON.stringify(err))
|
|
|
+ } else {
|
|
|
+ console.info(TAG,"Publish queryflashmode succeed ")
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ //开始预览
|
|
|
+ startView=async()=>{
|
|
|
+ CommonEventManager.publish("startview", (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.error(TAG, JSON.stringify(err));
|
|
|
+ } else {
|
|
|
+ console.info(TAG, 'publish startview succeed');
|
|
|
+ this.isStopView =false;
|
|
|
+ this.liveShow();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ //停止预览
|
|
|
+ stopView=async()=>{
|
|
|
+ CommonEventManager.publish("stopview", (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.error(TAG, JSON.stringify(err));
|
|
|
+ } else {
|
|
|
+ this.isStopView =true;
|
|
|
+ console.info(TAG, 'publish stopview succeed');
|
|
|
+ if (this.readTimer) {
|
|
|
+ clearInterval(this.readTimer);
|
|
|
+ this.readTimer = 0 ;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ //重连相机
|
|
|
+ reconnectCamera=async()=>{
|
|
|
+ this.showConfirmDialog({
|
|
|
+ title: '重连USB',
|
|
|
+ message: `请重连USB后点击确定!`,
|
|
|
+ onConfirm: async()=> {
|
|
|
+ await new Promise<void>((resolve, reject) => {
|
|
|
+ CommonEventManager.publish("stopview", (err) => {
|
|
|
+ if (err) return reject(err);
|
|
|
+ CommonEventManager.publish("closesession", (err) => {
|
|
|
+ err ? reject(err) : resolve();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ await new Promise<void>((resolve, reject) => {
|
|
|
+ CommonEventManager.publish("reconnect", (err) => {
|
|
|
+ if (err) return reject(err);
|
|
|
+ resolve();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ promptAction.showToast({
|
|
|
+ message: '相机正在重连中...',
|
|
|
+ duration: 3000,
|
|
|
+ bottom: 500
|
|
|
+ });
|
|
|
+ await sleep(3000);
|
|
|
+ await this.connectCamera()
|
|
|
+ //await this.liveShow()
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ liveShow = async () => {
|
|
|
+ let index = 0;
|
|
|
+ let retryCount = 0;
|
|
|
+ const MAX_RETRIES = 1000; // 最大重试次数
|
|
|
+
|
|
|
+ const previewLoop = async () => {
|
|
|
+ if (!this.isStopView && !this.isCapturing) {
|
|
|
+ try {
|
|
|
+ const success = await this.previewManager!.processFrame(index);
|
|
|
+ if (success) {
|
|
|
+ this.commodityPixelMap = await this.previewManager!.getActiveBuffer();
|
|
|
+ retryCount = 0;
|
|
|
+ } else {
|
|
|
+ retryCount++;
|
|
|
+ console.warn(TAG, "processFrame失败");
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ retryCount++;
|
|
|
+ console.warn(TAG, "预览更新失败:", error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (retryCount === MAX_RETRIES) {
|
|
|
+ clearInterval(this.readTimer)
|
|
|
+ this.readTimer = 0;
|
|
|
+ console.info(TAG,"GG")
|
|
|
+ return;
|
|
|
+ //this.reconnectCamera();
|
|
|
+ }
|
|
|
+ if (this.readTimer !== 0) { // 检查是否应该继续循环
|
|
|
+ this.readTimer = setTimeout(previewLoop, 50);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ this.readTimer = setTimeout(previewLoop, 50);
|
|
|
+ };
|
|
|
+
|
|
|
+ //断开相机(停止预览->关闭连接)
|
|
|
+ disconnectCamera = async () => {
|
|
|
+ if(!this.isStopView)
|
|
|
+ {
|
|
|
+ await this.stopView()
|
|
|
+ }
|
|
|
+ CommonEventManager.publish("closesession", (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.info(TAG,"Publish closesession err=" + JSON.stringify(err))
|
|
|
+ } else {
|
|
|
+ console.info(TAG,"Publish closesession succeed ")
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ //拍照(停止预览->拍照->开始预览)
|
|
|
+ takePhoto = async () => {
|
|
|
+ this.isStopView = true;
|
|
|
+ this.isCapturing = true;
|
|
|
+ CommonEventManager.publish("stopview", (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.info(TAG,"Publish stopview err=" + JSON.stringify(err));
|
|
|
+ } else {
|
|
|
+ console.info(TAG,"Publish Publish stopview succeed");
|
|
|
+ CommonEventManager.publish("shootandsave", (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.info(TAG,"Publish shootandsave error=" + JSON.stringify(err));
|
|
|
+ } else {
|
|
|
+ console.info(TAG,"Publish Publish shootandsave succeed");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ //设置闪光模式
|
|
|
+ setFlashMode = (mode: 'openflash' | 'closeflash' | 'autoflash') => {
|
|
|
+ this.isStopView = true;
|
|
|
+ CommonEventManager.publish("stopview", (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.info(TAG,"Publish stopview err=" + JSON.stringify(err));
|
|
|
+ } else {
|
|
|
+ console.info(TAG,"Publish Publish stopview succeed");
|
|
|
+ CommonEventManager.publish(mode, (err) => {
|
|
|
+ if (err?.code) {
|
|
|
+ console.info(TAG,"Publish flashmode error=" + JSON.stringify(err));
|
|
|
+ } else {
|
|
|
+ console.info(TAG,"Publish Publish flashmode succeed");
|
|
|
+ CommonEventManager.publish("startview", (err) => {
|
|
|
+ this.isStopView = false;
|
|
|
+ if (err?.code) {
|
|
|
+ console.info(TAG,"Publish startview error=" + JSON.stringify(err));
|
|
|
+ }else{
|
|
|
+ console.info(TAG,"Publish Publish startview succeed");
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private adjustOffsetWithAnimation() {
|
|
|
+ const isRotated = (this.rotateAngle === 90 || this.rotateAngle === 270);
|
|
|
+ //旋转90度或者70度 宽度0.667倍,长度超过1.5倍才拖动
|
|
|
+ let moveScaleY = (this.scaleValue - (isRotated ? 0.667 : 1)) / 2;
|
|
|
+ let moveScaleX = (this.scaleValue - (isRotated ? 1.50 : 1)) / 2;
|
|
|
+ let maxOffsetX = (this.scaleValue<1.5&&isRotated) ?this.offsetX: (isRotated ? 506 : 760) * moveScaleX;
|
|
|
+ let maxOffsetY = (isRotated ? 760 : 506) * moveScaleY; //rk3588
|
|
|
+ // let maxOffsetX = (this.scaleValue<1.5&&isRotated) ?this.offsetX: (isRotated ? 1018 : 1524) * moveScaleX;
|
|
|
+ // let maxOffsetY = (isRotated ? 1524 : 1018) * moveScaleY; //rk3568
|
|
|
+ //console.info(TAG,"maxOffsetX",maxOffsetX,"scale",this.scaleValue,"offsetx",this.offsetX)
|
|
|
+ const clampedX = Math.max(-maxOffsetX, Math.min(maxOffsetX, this.offsetX));
|
|
|
+ const clampedY = Math.max(-maxOffsetY, Math.min(maxOffsetY, this.offsetY));
|
|
|
+ if (this.offsetX !== clampedX || this.offsetY !== clampedY) {
|
|
|
+ animateTo({
|
|
|
+ duration: 100,
|
|
|
+ curve: Curve.EaseOut
|
|
|
+ }, () => {
|
|
|
+ this.offsetX = clampedX;
|
|
|
+ this.offsetY = clampedY;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //上传照片
|
|
|
+ uploadPhoto=()=>{
|
|
|
+
|
|
|
+ }
|
|
|
+ async aboutToAppear() {
|
|
|
+ this.previewManager = new PreviewManager(getContext(this));
|
|
|
+ this.loadPhotos();
|
|
|
+ await this.createSubscriber();
|
|
|
+ await sleep(50)
|
|
|
+ await this.connectCamera();
|
|
|
+ }
|
|
|
+
|
|
|
+ aboutToDisappear(): void {
|
|
|
+ this.disconnectCamera()
|
|
|
+ if (this.previewManager) {
|
|
|
+ this.previewManager.release();
|
|
|
+ this.previewManager = null
|
|
|
+ }
|
|
|
+ if (this.subscriber) {
|
|
|
+ CommonEventManager.unsubscribe(this.subscriber);
|
|
|
+ }
|
|
|
+ this.releaseAllPixelMaps()
|
|
|
+ if (this.commodityPixelMap) {
|
|
|
+ this.commodityPixelMap.release();
|
|
|
+ this.commodityPixelMap = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ build() {
|
|
|
+ Row() {
|
|
|
+ Stack(){
|
|
|
+ if (this.commodityPixelMap) {
|
|
|
+ if (this.selectedPhotoIndex === -1) {
|
|
|
+ Image(this.commodityPixelMap)
|
|
|
+ .width('100%')
|
|
|
+ .height('100%')
|
|
|
+ .objectFit(ImageFit.Fill)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(this.isCapturing||this.isUploading){
|
|
|
+ Column() {
|
|
|
+ Text(this.isCapturing?'正在拍照中...':"正在上传中...")
|
|
|
+ .fontSize($r('app.float.fontSize_30'))
|
|
|
+ .margin({bottom:'10%'})
|
|
|
+ LoadingProgress()
|
|
|
+ .color(Color.Blue)
|
|
|
+ .width('25%')
|
|
|
+ .width('25%')
|
|
|
+ }
|
|
|
+ .height('30%')
|
|
|
+ .width('25%')
|
|
|
+ .backgroundColor(Color.White)
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ }
|
|
|
+ if(this.selectedPhotoIndex === -1){
|
|
|
+ Row() {
|
|
|
+ Image(
|
|
|
+ this.selectedFlashMode === 0 ? $r('app.media.process_flash_auto') :
|
|
|
+ this.selectedFlashMode === 1 ? $r('app.media.process_flash_open') :
|
|
|
+ $r('app.media.process_flash_close')
|
|
|
+ )
|
|
|
+ .width($r('app.float.virtualSize_48'))
|
|
|
+ .height($r('app.float.virtualSize_48'))
|
|
|
+ .enabled(!this.isCapturing)
|
|
|
+ .onClick(() => {
|
|
|
+ this.isExpanded = !this.isExpanded
|
|
|
+ })
|
|
|
+ .margin({right:'5%'})
|
|
|
+ if (this.isExpanded) {
|
|
|
+ Row() {
|
|
|
+ Row(){
|
|
|
+ Text('自动')
|
|
|
+ .fontSize($r('app.float.fontSize_24'))
|
|
|
+ .fontColor(this.selectedFlashMode===0?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
|
|
|
+ }
|
|
|
+ .width('33.3%')
|
|
|
+ .height('100%')
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .backgroundColor(this.selectedFlashMode===0?$r('app.color.0A84FF'):$r('app.color.60000000'))
|
|
|
+ .onClick(() => {
|
|
|
+ this.setFlashMode('autoflash')
|
|
|
+ this.isExpanded = false
|
|
|
+ this.selectedFlashMode = 0
|
|
|
+ })
|
|
|
+ Row(){
|
|
|
+ Text('开启')
|
|
|
+ .fontSize($r('app.float.fontSize_24'))
|
|
|
+ .fontColor(this.selectedFlashMode===1?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
|
|
|
+ }
|
|
|
+ .width('33.3%')
|
|
|
+ .height('100%')
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .backgroundColor(this.selectedFlashMode===1?$r('app.color.0A84FF'):$r('app.color.60000000'))
|
|
|
+ .onClick(() => {
|
|
|
+ this.setFlashMode('openflash')
|
|
|
+ this.isExpanded = false
|
|
|
+ this.selectedFlashMode = 1
|
|
|
+ })
|
|
|
+
|
|
|
+ Row(){
|
|
|
+ Text('关闭')
|
|
|
+ .fontSize($r('app.float.fontSize_24'))
|
|
|
+ .fontColor(this.selectedFlashMode===2?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
|
|
|
+ }
|
|
|
+ .width('33.3%')
|
|
|
+ .height('100%')
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .backgroundColor(this.selectedFlashMode===2?$r('app.color.0A84FF'):$r('app.color.60000000'))
|
|
|
+ .onClick(() => {
|
|
|
+ this.setFlashMode('closeflash')
|
|
|
+ this.isExpanded = false
|
|
|
+ this.selectedFlashMode = 2
|
|
|
+ })
|
|
|
+ }
|
|
|
+ .animation({ duration: 300, curve: Curve.EaseOut })
|
|
|
+ .backgroundColor($r('app.color.60000000'))
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .width('85%')
|
|
|
+ .height('100%')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .justifyContent(FlexAlign.Start)
|
|
|
+ .alignItems(VerticalAlign.Bottom)
|
|
|
+ .position({ x: '2%', y: '92%' })
|
|
|
+ .height('5%')
|
|
|
+ .width('24%')
|
|
|
+ Image(this.shootButtonClick===1?$r('app.media.process_no_shoot'):$r('app.media.process_shoot'))
|
|
|
+ .width($r('app.float.virtualSize_88'))
|
|
|
+ .height($r('app.float.virtualSize_88'))
|
|
|
+ .scale({ x: this.shootButtonClick, y: this.shootButtonClick })
|
|
|
+ .borderRadius($r('app.float.fontSize_16'))
|
|
|
+ .enabled(!this.isCapturing)
|
|
|
+ .animation({
|
|
|
+ duration: 200,
|
|
|
+ curve: Curve.Linear
|
|
|
+ })
|
|
|
+ .onClick(() => {
|
|
|
+ this.shootButtonClick = 0.9;
|
|
|
+ setTimeout(() => {
|
|
|
+ this.shootButtonClick = 1;
|
|
|
+ }, 200);
|
|
|
+ this.takePhoto();
|
|
|
+ })
|
|
|
+ .position({ x: '48%', y: '90%' })
|
|
|
+ Row(){
|
|
|
+ Text("重连相机")
|
|
|
+ .fontColor($r('app.color.FFFFFF'))
|
|
|
+ .fontSize($r('app.float.fontSize_24'))
|
|
|
+ }
|
|
|
+ .width('10%')
|
|
|
+ .height('6%')
|
|
|
+ .backgroundColor($r('app.color.60000000'))
|
|
|
+ .position({ x: '88%', y: '91%' })
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .enabled(!this.isCapturing)
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ .onClick(()=>{
|
|
|
+ this.reconnectCamera()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ else{
|
|
|
+ Stack() {
|
|
|
+ Image(this.photoPixelMaps[this.selectedPhotoIndex].pixelMap)
|
|
|
+ .width('100%')
|
|
|
+ .height('100%')
|
|
|
+ .autoResize(true)
|
|
|
+ // .onComplete((event) => {
|
|
|
+ // console.info(TAG,"width:"+event!.componentWidth +"height:"+ event!.componentHeight)
|
|
|
+ // }) // 获取Image组件的长宽(除以1.5获取vp)
|
|
|
+ .objectFit(ImageFit.Fill)
|
|
|
+ .rotate({ angle: this.rotateAngle })
|
|
|
+ .scale({ x: this.scaleValue, y: this.scaleValue })
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .translate({ x: this.offsetX, y: this.offsetY })
|
|
|
+ .gesture(
|
|
|
+ GestureGroup(GestureMode.Exclusive,
|
|
|
+ PinchGesture()
|
|
|
+ .onActionStart((event: GestureEvent) => {
|
|
|
+ this.lastScale = this.scaleValue;
|
|
|
+ this.lastOffsetX = this.offsetX;
|
|
|
+ this.lastOffsetY = this.offsetY;
|
|
|
+ // 记录双指中心点(相对于图片中心)
|
|
|
+ this.pinchCenterX = event.pinchCenterX - 380 - 760 / 2;//rk3588
|
|
|
+ this.pinchCenterY = event.pinchCenterY - 130 - 506 / 2;//rk3588
|
|
|
+ // this.pinchCenterX = event.pinchCenterX - 1016 / 2; //rk3568
|
|
|
+ // this.pinchCenterY = event.pinchCenterY - 678 / 2; //rk3568
|
|
|
+ // console.info(TAG,this.pinchCenterX,TAG,this.pinchCenterY)
|
|
|
+ })
|
|
|
+ .onActionUpdate((event: GestureEvent) => {
|
|
|
+ let newScale = this.lastScale * event.scale;
|
|
|
+ //保持双指中心点不变
|
|
|
+ const scaleRatio = newScale / this.lastScale;
|
|
|
+ const newOffsetX = this.lastOffsetX + (1 - scaleRatio) * this.pinchCenterX;
|
|
|
+ const newOffsetY = this.lastOffsetY + (1 - scaleRatio) * this.pinchCenterY;
|
|
|
+
|
|
|
+ this.offsetX = newOffsetX;
|
|
|
+ this.offsetY = newOffsetY;
|
|
|
+ this.scaleValue = newScale;
|
|
|
+ })
|
|
|
+ .onActionEnd(() => {
|
|
|
+ // 缩放最小比例为1
|
|
|
+ if (this.scaleValue < 1) {
|
|
|
+ // 旋转90°或者270°的最小缩小比例为0.667
|
|
|
+ if(this.rotateAngle==90||this.rotateAngle==270) {
|
|
|
+ if(this.scaleValue<0.667){
|
|
|
+ this.scaleValue = 0.667
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.scaleValue = 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 缩放最大比例为3
|
|
|
+ if (this.scaleValue > 3) this.scaleValue = 3
|
|
|
+ this.adjustOffsetWithAnimation()
|
|
|
+ }),
|
|
|
+
|
|
|
+ // 单指滑动手势
|
|
|
+ PanGesture()
|
|
|
+ .onActionStart(() => {
|
|
|
+ if(this.rotateAngle === 90 || this.rotateAngle === 270)
|
|
|
+ {
|
|
|
+ if (this.scaleValue <= 0.667)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ if (this.scaleValue <= 1) return;
|
|
|
+ }
|
|
|
+ this.lastOffsetX = this.offsetX;
|
|
|
+ this.lastOffsetY = this.offsetY;
|
|
|
+ })
|
|
|
+ .onActionUpdate((event: GestureEvent) => {
|
|
|
+ if(this.rotateAngle === 90 || this.rotateAngle === 270)
|
|
|
+ {
|
|
|
+ if (this.scaleValue <= 0.667)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ if (this.scaleValue <= 1) return;
|
|
|
+ }
|
|
|
+ let dx = event.offsetX;
|
|
|
+ let dy = event.offsetY;
|
|
|
+ const sensitivity = 0.5 * this.scaleValue;
|
|
|
+ // 临时计算新位置
|
|
|
+ let newOffsetX = this.lastOffsetX;
|
|
|
+ let newOffsetY = this.lastOffsetY;
|
|
|
+ newOffsetX += dx * sensitivity;
|
|
|
+ newOffsetY += dy * sensitivity;
|
|
|
+ this.offsetX = newOffsetX;
|
|
|
+ this.offsetY = newOffsetY;
|
|
|
+ //console.info(TAG,this.offsetX,TAG,this.offsetY)
|
|
|
+ })
|
|
|
+ .onActionEnd(() => {
|
|
|
+ const isRotated = (this.rotateAngle === 90 || this.rotateAngle === 270);
|
|
|
+ if(isRotated)
|
|
|
+ {
|
|
|
+ if(this.scaleValue<=1.5)
|
|
|
+ {
|
|
|
+ this.offsetX = 0
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ if (this.scaleValue <= 1) {
|
|
|
+ // 如果缩放比例<=1,直接重置位置
|
|
|
+ this.offsetX = 0;
|
|
|
+ this.offsetY = 0;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.adjustOffsetWithAnimation()
|
|
|
+ })
|
|
|
+ )
|
|
|
+ )
|
|
|
+ }
|
|
|
+ .width('100%')
|
|
|
+ .height('100%')
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .clip(true)
|
|
|
+ Row()
|
|
|
+ {
|
|
|
+ Row(){
|
|
|
+ Image($r('app.media.process_back_camera'))
|
|
|
+ .width($r('app.float.virtualSize_80'))
|
|
|
+ .height($r('app.float.virtualSize_80'))
|
|
|
+ .onClick(()=>{
|
|
|
+ this.selectedPhotoIndex=-1
|
|
|
+ this.startView()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ .width('10%')
|
|
|
+ .justifyContent(FlexAlign.Start)
|
|
|
+ Row({space:20}){
|
|
|
+ Image($r("app.media.process_photo_reset"))
|
|
|
+ .width($r('app.float.virtualSize_80'))
|
|
|
+ .height($r('app.float.virtualSize_80'))
|
|
|
+ .onClick(()=>{
|
|
|
+ this.resetImageTransform()
|
|
|
+ })
|
|
|
+ Image($r('app.media.process_photo_turn_left'))
|
|
|
+ .width($r('app.float.virtualSize_80'))
|
|
|
+ .height($r('app.float.virtualSize_80'))
|
|
|
+ .onClick(()=>{
|
|
|
+ this.rotateImage(-90)
|
|
|
+ })
|
|
|
+ Image($r('app.media.process_photo_turn_right'))
|
|
|
+ .width($r('app.float.virtualSize_80'))
|
|
|
+ .height($r('app.float.virtualSize_80'))
|
|
|
+ .onClick(()=>{
|
|
|
+ this.rotateImage(90)
|
|
|
+ })
|
|
|
+ Image($r('app.media.process_photo_delete'))
|
|
|
+ .width($r('app.float.virtualSize_80'))
|
|
|
+ .height($r('app.float.virtualSize_80'))
|
|
|
+ .onClick(()=>{
|
|
|
+ this.showConfirmDialog({
|
|
|
+ title: '删除照片',
|
|
|
+ message: `确定要删除照片吗?`,
|
|
|
+ onConfirm: ()=> {
|
|
|
+ this.deletePhoto(this.selectedPhotoIndex);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ })
|
|
|
+ }.width('88%')
|
|
|
+ .justifyContent(FlexAlign.End)
|
|
|
+ .margin({right :'2%'})
|
|
|
+ }.width('98%')
|
|
|
+ .height('10%')
|
|
|
+ .position({x:'2%',y:'90%'})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .width('86%')
|
|
|
+ .height('100%')
|
|
|
+ .backgroundColor($r('app.color.000000'))
|
|
|
+ .border({width:2})
|
|
|
+ Column(){
|
|
|
+ List({ space: 8,scroller:this.scrollerPhoto }) {
|
|
|
+ ForEach(this.photoPixelMaps, (item:photoInfo, index) => {
|
|
|
+ ListItem() {
|
|
|
+ Column({space:4}){
|
|
|
+ Column(){
|
|
|
+ Image(item.pixelMap)
|
|
|
+ .objectFit(ImageFit.Fill)
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .height('97%')
|
|
|
+ .width('98%')
|
|
|
+ .opacity(index === this.selectedPhotoIndex ? 0.8 : 1) // 20% 透明度
|
|
|
+ Text(item.timestamp)
|
|
|
+ .fontSize($r('app.float.fontSize_12'))
|
|
|
+ .fontColor($r('app.color.FFFFFF'))
|
|
|
+ .width('100%')
|
|
|
+ .textAlign(TextAlign.Start)
|
|
|
+ }
|
|
|
+ .backgroundColor(index === this.selectedPhotoIndex ? $r('app.color.30D158') : '')
|
|
|
+ .width('90%')
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ .alignItems(HorizontalAlign.Center)
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .height('85%')
|
|
|
+ // Text(`${item.updated}`)
|
|
|
+ // .fontSize($r('app.float.fontSize_12'))
|
|
|
+ // .fontColor($r('app.color.FFFFFF'))
|
|
|
+ // .width('90%')
|
|
|
+ // .textAlign(TextAlign.Start)
|
|
|
+ }
|
|
|
+ .width('100%')
|
|
|
+ .height('100%')
|
|
|
+ .justifyContent(FlexAlign.Start)
|
|
|
+ .alignItems(HorizontalAlign.Center)
|
|
|
+ .enabled(!this.isCapturing)
|
|
|
+ .onClick(()=>{
|
|
|
+ this.selectedPhotoIndex = index;
|
|
|
+ this.resetImageTransform()
|
|
|
+ this.stopView();
|
|
|
+
|
|
|
+ })
|
|
|
+ }.height('19%')
|
|
|
+ .margin({bottom:'2%'})
|
|
|
+ })
|
|
|
+ }
|
|
|
+ .width('100%')
|
|
|
+ .margin({top:'2%',bottom:'2%'})
|
|
|
+ .height('88.6%')
|
|
|
+ Row({space: 4}) {
|
|
|
+ Image($r("app.media.error_upload"))
|
|
|
+ .width($r('app.float.virtualSize_24'))
|
|
|
+ .height($r('app.float.virtualSize_24'))
|
|
|
+ Text('上传照片')
|
|
|
+ .fontColor($r('app.color.0A84FF'))
|
|
|
+ .fontSize($r('app.float.fontSize_24'))
|
|
|
+ .fontWeight(FontWeight.Medium)
|
|
|
+ }
|
|
|
+ .width('90%')
|
|
|
+ .height('7.4%')
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ .backgroundColor($r('app.color.20FFFFFF'))
|
|
|
+ .borderRadius($r('app.float.fontSize_16'))
|
|
|
+ .scale({ x: this.uploadButtonClick, y: this.uploadButtonClick })
|
|
|
+ .animation({
|
|
|
+ duration: 200,
|
|
|
+ curve: Curve.Linear
|
|
|
+ })
|
|
|
+ .onClick(async()=>{
|
|
|
+ this.uploadButtonClick = 0.9;
|
|
|
+ setTimeout(() => {
|
|
|
+ this.uploadButtonClick = 1;
|
|
|
+ }, 200);
|
|
|
+ if(this.photoPixelMaps.length===0) {
|
|
|
+ promptAction.showToast({
|
|
|
+ message: '上传列表为空',
|
|
|
+ duration: 1500,
|
|
|
+ bottom: 100
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ await this.showConfirmDialog({
|
|
|
+ title: '上传确认',
|
|
|
+ message: `是否上传所有照片!`,
|
|
|
+ onConfirm: async()=> {
|
|
|
+ this.isUploading = true
|
|
|
+ await sleep(3000);
|
|
|
+ this.isUploading = false
|
|
|
+ await this.showConfirmDialog({
|
|
|
+ title: '工步确认',
|
|
|
+ message: `图像采集收否已完成?`,
|
|
|
+ onConfirm: async()=> {
|
|
|
+ this.opComponents[this.index].isComplete=true
|
|
|
+ this.index++
|
|
|
+ this.disconnectCamera();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ //await this.liveShow()
|
|
|
+ }
|
|
|
+ });
|
|
|
+ })
|
|
|
+ }
|
|
|
+ .width('14%')
|
|
|
+ .height('100%')
|
|
|
+ }
|
|
|
+ .width('100%')
|
|
|
+ .height('100%')
|
|
|
+ .backgroundColor($r('app.color.10FFFFFF'))
|
|
|
+ .borderRadius($r('app.float.virtualSize_16'))
|
|
|
+ .borderWidth(4)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function sleep(ms: number): Promise<void> {
|
|
|
+ return new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+interface photoInfo {
|
|
|
+ pixelMap: PixelMap;
|
|
|
+ timestamp: string;
|
|
|
+ filename: string;
|
|
|
+}
|