import image from '@ohos.multimedia.image'; import fs from "@ohos.file.fs" import CommonEventManager from '@ohos.commonEventManager' import uploadInstance from '../../common/util/UploadUtil'; import router from '@ohos.router'; import { DrawingInfo, DrawingPage } from '../../viewmodel/DrawingInfo'; import CommonConstants from '../../common/constants/CommonConstants'; import ProcessRequest from '../../common/util/request/ProcessRequest'; import RequestParamModel from '../../viewmodel/RequestParamModel'; import { ConfirmDialogParams } from '../../viewmodel/ConfirmDialogParam'; import { ConfirmDialog } from '../ConfirmDialog'; import promptAction from '@ohos.promptAction'; const TAG = "Process camera upload" @Component export struct MultiMediaCollect { private scrollerPhoto: Scroller = new Scroller() // @State isExpanded: boolean = false //闪光模式 0:自动 1:开启 2:关闭 @State selectedFlashMode:number = 0 //选择的照片索引 @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 // 正在上传 @State isUploading: boolean = false // 照片列表 @State photoList:DrawingInfo[]=[] // 获取本地live照片 @State commodityPixelMap: PixelMap | null = null; // 拍照动作是否完成 @State isCapturing: boolean = false; // 是否停止预览 @State isStopView: boolean = false; // 控制帧率 @State readTimer:number = 0 // 预览操作 private previewManager: PreviewManager | null = null; //创建订阅者 subscriber: CommonEventManager.CommonEventSubscriber | null = null; //订阅相机回调 subscribeInfo: CommonEventManager.CommonEventSubscribeInfo = { events: ["sonycamera_callback"] }; //提示确认弹窗 commonDialogController: CustomDialogController | null = null; private 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.uploadPhoto(); // 拍照成功 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}`); } } }); } }; //加载照片 loadPhotos=async()=>{ let res = await ProcessRequest.post('/api/v1/process/media/page', { } as RequestParamModel) as DrawingPage; this.photoList=res.records??[] } //删除照片 deletePhoto=async(photoId:string)=>{ let res = await ProcessRequest.post('/api/v1/process/media/del', { id: photoId } as RequestParamModel) as DrawingPage; this.loadPhotos() } //旋转照片 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.63 }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 ") } }) } //查询闪光模式 queryFlashMode = async () => { CommonEventManager.publish("queryflashmode", (err) => { if (err?.code) { console.info(TAG,"Publish openSession err=" + JSON.stringify(err)) } else { console.info(TAG,"Publish openSession succeed ") } }) } //开始预览 startView=()=>{ CommonEventManager.publish("startview", (err) => { if (err?.code) { console.error(TAG, JSON.stringify(err)); } else { this.isCapturing = false; this.isStopView =false; this.liveShow(); } }); } //停止预览 stopView=async()=>{ CommonEventManager.publish("stopview", (err) => { if (err?.code) { console.error(TAG, JSON.stringify(err)); } else { this.isCapturing = false; this.isStopView =true; } }); } //重连相机 reconnectCamera=async()=>{ this.showConfirmDialog({ title: '重连USB', message: `请重连USB后点击确定!`, onConfirm: async()=> { await new Promise((resolve, reject) => { CommonEventManager.publish("stopview", (err) => { if (err) return reject(err); CommonEventManager.publish("closesession", (err) => { err ? reject(err) : resolve(); }); }); }); await new Promise((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() } }); } //开始预览 50ms一张图片(20帧) liveShow = async () => { this.previewManager = new PreviewManager(getContext(this)); let index = 0; this.readTimer = setInterval(async () => { if (!this.isStopView && !this.isCapturing) { try { const success = await this.previewManager!.processFrame(index); if (success) { this.commodityPixelMap = this.previewManager!.getActiveBuffer(); } } catch (error) { console.warn(TAG, "预览更新失败:", error); } } }, 50); }; //断开相机(停止预览->关闭连接) disconnectCamera = () => { CommonEventManager.publish("stopview", (err) => { if (err?.code) { console.info(TAG,"Publish stopview err=" + JSON.stringify(err)) } else { console.info(TAG,"Publish stopview succeed ") 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)); this.isCapturing = false; } else { console.info(TAG,"Publish Publish stopview succeed"); CommonEventManager.publish("shootonly", (err) => { if (err?.code) { console.info(TAG,"Publish shootonly error=" + JSON.stringify(err)); this.isCapturing = false; } else { console.info(TAG,"Publish Publish shootonly 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 shootonly error=" + JSON.stringify(err)); } else { console.info(TAG,"Publish Publish shootonly succeed"); CommonEventManager.publish("startview", (err) => { this.isStopView = false; if (err?.code) { console.info(TAG,"Publish shootonly error=" + JSON.stringify(err)); }else{ console.info(TAG,"Publish Publish shootonly succeed"); } }) } }); } }); } //上传照片 uploadPhoto=()=>{ let imageUri: string = "/data/storage/el2/base/haps/entry/files/image_base64.txt" try { uploadInstance.startUploadBase64(imageUri, () => { this.loadPhotos() this.isUploading = false this.startView();//上传完恢复预览 }) } catch (error) { console.error(TAG,"upload failed:", error.code); } } async aboutToAppear() { this.loadPhotos(); await this.createSubscriber(); await this.connectCamera(); await this.queryFlashMode(); } aboutToDisappear(): void { this.isStopView = true; sleep(100) this.disconnectCamera() if (this.subscriber) { CommonEventManager.unsubscribe(this.subscriber); } if (this.readTimer) { clearInterval(this.readTimer); } if (this.commodityPixelMap) { this.commodityPixelMap.release(); this.commodityPixelMap = null; } if (this.previewManager) { this.previewManager.release(); } } build() { Row() { Stack(){ if (this.commodityPixelMap) { if (this.selectedPhotoIndex === -1) { Image(this.commodityPixelMap) .width('100%') .height('100%') .objectFit(ImageFit.Fill) } } if(this.isUploading){ Column() { Text('正在上传中...') .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.isUploading) .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.isUploading) .animation({ duration: 200, curve: Curve.Linear }) .onClick(() => { this.shootButtonClick = 0.9; setTimeout(() => { this.shootButtonClick = 1; }, 200); if(this.isUploading){ return } this.isUploading = true 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.20FFFFFF')) .position({ x: '88%', y: '91%' }) .borderRadius($r('app.float.virtualSize_16')) .justifyContent(FlexAlign.Center) .onClick(()=>{ this.reconnectCamera() }) } else{ Stack() { Image(CommonConstants.PICTURE_URL_PREFIX + this.photoList[this.selectedPhotoIndex].filePath) .width('100%') .height('100%') .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(() => { this.lastScale = this.scaleValue this.lastOffsetX = this.offsetX this.lastOffsetY = this.offsetY }) .onActionUpdate((event: GestureEvent) => { this.scaleValue = this.lastScale * event.scale }) .onActionEnd(() => { //缩放最小比例为1 if (this.scaleValue < 1) { //旋转90°或者270°的最小缩小比例为0.63 if(this.rotateAngle==90||this.rotateAngle==270) { if(this.scaleValue<0.63){ this.scaleValue = 0.63 } } else { this.scaleValue = 1 } } //缩放最大比例为4 if (this.scaleValue > 4) this.scaleValue = 4 }), // 单指滑动手势 PanGesture() .onActionStart(() => { if (this.scaleValue <= 1) return; this.lastOffsetX = this.offsetX; this.lastOffsetY = this.offsetY; }) .onActionUpdate((event: GestureEvent) => { 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; switch(this.rotateAngle % 360) { case 0: newOffsetX += dx * sensitivity; newOffsetY += dy * sensitivity; break; case 90: newOffsetX -= dy * sensitivity; newOffsetY += dx * sensitivity; break; case 180: newOffsetX -= dx * sensitivity; newOffsetY -= dy * sensitivity; break; case 270: newOffsetX += dy * sensitivity; newOffsetY -= dx * sensitivity; break; } this.offsetX = newOffsetX; this.offsetY = newOffsetY; }) .onActionEnd(() => { if (this.scaleValue <= 1) { // 如果缩放比例<=1,直接重置位置 this.offsetX = 0; this.offsetY = 0; return; } // 计算最大允许偏移量(基于缩放比例) const maxOffsetX = (this.scaleValue - 1) * 500; //500是容器的半宽 const maxOffsetY = (this.scaleValue - 1) * 345; //345是容器的半高 // 检查X轴边界 if (Math.abs(this.offsetX) > maxOffsetX) { this.offsetX = this.offsetX > 0 ? maxOffsetX : -maxOffsetX; } // 检查Y轴边界 if (Math.abs(this.offsetY) > maxOffsetY) { this.offsetY = this.offsetY > 0 ? maxOffsetY : -maxOffsetY; } // 添加回弹动画 animateTo({ duration: 300, curve: Curve.EaseOut }, () => { this.offsetX = this.offsetX; this.offsetY = this.offsetY; }); }) ) ) } .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: ()=> { const photosNum = this.photoList.length const photoId=this.photoList[this.selectedPhotoIndex].id //如果是图册最后一张照片,显示前一张照片,没有照片则返回拍照页面 if(this.selectedPhotoIndex===photosNum-1) { this.selectedPhotoIndex-- } this.deletePhoto(photoId) } }); }) }.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.photoList, (item: DrawingInfo, index) => { ListItem() { Column({space:4}){ Column(){ Image(CommonConstants.PICTURE_URL_PREFIX+item.filePath) .objectFit(ImageFit.Fill) .borderRadius($r('app.float.virtualSize_16')) .height('97%') .width('98%') .opacity(index === this.selectedPhotoIndex ? 0.8 : 1) // 20% 透明度 } .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.isUploading) .onClick(()=>{ this.selectedPhotoIndex = index; this.resetImageTransform() this.stopView(); }) }.height('19%') .margin({bottom:'2%'}) }) } .width('100%') .margin({top:'2%',bottom:'2%'}) .height('96%') } .width('14%') .height('100%') } .width('100%') .height('100%') //.backgroundColor($r('app.color.10FFFFFF')) .borderRadius($r('app.float.virtualSize_16')) } } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } class PreviewManager { private context: Context; private activeBuffer: PixelMap | null = null; private nextBuffer: PixelMap | null = null; private isProcessing: boolean = false; private lastFrameTime: number = 0; private frameInterval: number = 50; constructor(context: Context) { this.context = context; } async processFrame(index: number): Promise { if (this.isProcessing) return false; const now = Date.now(); if (now - this.lastFrameTime < this.frameInterval) { return false; } this.isProcessing = true; try { const filePath = `${this.context.filesDir}/live${index}.jpg`; try { fs.accessSync(filePath); } catch { this.isProcessing = false; return false; } const imageSource = image.createImageSource(filePath); if (!imageSource) { this.isProcessing = false; return false; } this.nextBuffer = await imageSource.createPixelMap(); imageSource.release(); if (this.activeBuffer) { this.activeBuffer.release(); } this.activeBuffer = this.nextBuffer; this.nextBuffer = null; this.lastFrameTime = now; return true; } catch (error) { console.warn(TAG, "处理预览帧失败:", error); return false; } finally { this.isProcessing = false; } } getActiveBuffer(): PixelMap | null { return this.activeBuffer; } release() { if (this.activeBuffer) { this.activeBuffer.release(); this.activeBuffer = null; } if (this.nextBuffer) { this.nextBuffer.release(); this.nextBuffer = null; } } } interface CameraParam { flash_mode: string; moduleName?: string; }