MultiMediaCollect.ets 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. import image from '@ohos.multimedia.image';
  2. import fs from "@ohos.file.fs"
  3. import CommonEventManager from '@ohos.commonEventManager'
  4. import uploadInstance from '../../common/util/UploadUtil';
  5. import router from '@ohos.router';
  6. import { DrawingInfo, DrawingPage } from '../../viewmodel/DrawingInfo';
  7. import CommonConstants from '../../common/constants/CommonConstants';
  8. import ProcessRequest from '../../common/util/request/ProcessRequest';
  9. import RequestParamModel from '../../viewmodel/RequestParamModel';
  10. import { ConfirmDialogParams } from '../../viewmodel/ConfirmDialogParam';
  11. import { ConfirmDialog } from '../ConfirmDialog';
  12. import promptAction from '@ohos.promptAction';
  13. const TAG = "Process camera upload"
  14. @Component
  15. export struct MultiMediaCollect {
  16. private scrollerPhoto: Scroller = new Scroller()
  17. //
  18. @State isExpanded: boolean = false
  19. //闪光模式 0:自动 1:开启 2:关闭
  20. @State selectedFlashMode:number = 0
  21. //选择的照片索引
  22. @State selectedPhotoIndex:number = -1
  23. //拍照按键缩放
  24. @State shootButtonClick:number =1
  25. // 旋转角度
  26. @State rotateAngle: number = 0
  27. // 照片缩放比例
  28. @State scaleValue: number = 1
  29. // 照片X轴偏移
  30. @State offsetX: number = 0
  31. // 照片Y轴偏移
  32. @State offsetY: number = 0
  33. // 照片上次缩放值
  34. @State lastScale: number = 1
  35. // 照片上次X偏移
  36. @State lastOffsetX: number = 0
  37. // 照片上次Y偏移
  38. @State lastOffsetY: number = 0
  39. // 正在上传
  40. @State isUploading: boolean = false
  41. // 照片列表
  42. @State photoList:DrawingInfo[]=[]
  43. // 获取本地live照片
  44. @State commodityPixelMap: PixelMap | null = null;
  45. // 拍照动作是否完成
  46. @State isCapturing: boolean = false;
  47. // 是否停止预览
  48. @State isStopView: boolean = false;
  49. // 控制帧率
  50. @State readTimer:number = 0
  51. // 预览操作
  52. private previewManager: PreviewManager | null = null;
  53. //创建订阅者
  54. subscriber: CommonEventManager.CommonEventSubscriber | null = null;
  55. //订阅相机回调
  56. subscribeInfo: CommonEventManager.CommonEventSubscribeInfo = { events: ["sonycamera_callback"] };
  57. //提示确认弹窗
  58. commonDialogController: CustomDialogController | null = null;
  59. private showConfirmDialog(params: ConfirmDialogParams) {
  60. if (this.commonDialogController) {
  61. this.commonDialogController.close()
  62. }
  63. this.commonDialogController = new CustomDialogController({
  64. builder: ConfirmDialog({
  65. title: params.title || '提示',
  66. message: params.message,
  67. onConfirm: params.onConfirm
  68. }),
  69. cancel: () => console.log('用户取消操作'),
  70. customStyle: true,
  71. autoCancel:false,
  72. maskColor: 'rgba(0,0,0,0.6)'
  73. });
  74. this.commonDialogController.open();
  75. }
  76. //订阅回调(code=3代表连接成功,code=2代表拍照成功)
  77. createSubscriber = async () => {
  78. this.subscriber = await CommonEventManager.createSubscriber(this.subscribeInfo);
  79. if (this.subscriber) {
  80. console.info(TAG, "创建订阅回调成功");
  81. CommonEventManager.subscribe(this.subscriber, (err, data) => {
  82. if (err?.code) {
  83. console.error(TAG, "SubscribeCallBack err=" + JSON.stringify(err));
  84. return;
  85. }
  86. console.info(TAG, "SubscribeCallBack data=" + JSON.stringify(data));
  87. if (data?.code !== undefined) {
  88. switch (data.code) {
  89. case 2:
  90. this.uploadPhoto(); // 拍照成功
  91. break;
  92. case 3:
  93. console.info(TAG, "开始预览");
  94. this.startView(); // 连接成功
  95. break;
  96. case -1:
  97. console.info(TAG,'连接故障')
  98. break;
  99. case 5:
  100. console.info(TAG,'暂停预览')
  101. break;
  102. }
  103. }
  104. //0001:自动闪光 0002:关闭闪光 0003:开启闪光
  105. if (data?.parameters?.flash_mode) {
  106. const flashMode:string = data.parameters.flash_mode
  107. console.info(TAG, `收到闪光模式: ${flashMode}`);
  108. switch (flashMode) {
  109. case '0001':
  110. this.selectedFlashMode = 0
  111. break;
  112. case '0002':
  113. this.selectedFlashMode = 2
  114. break;
  115. case '0003':
  116. this.selectedFlashMode = 1
  117. break;
  118. default:
  119. console.warn(TAG, `未知闪光模式: ${flashMode}`);
  120. }
  121. }
  122. });
  123. }
  124. };
  125. //加载照片
  126. loadPhotos=async()=>{
  127. let res = await ProcessRequest.post('/api/v1/process/media/page', {
  128. } as RequestParamModel) as DrawingPage;
  129. this.photoList=res.records??[]
  130. }
  131. //删除照片
  132. deletePhoto=async(photoId:string)=>{
  133. let res = await ProcessRequest.post('/api/v1/process/media/del', {
  134. id: photoId
  135. } as RequestParamModel) as DrawingPage;
  136. this.loadPhotos()
  137. }
  138. //旋转照片
  139. private rotateImage(angle: number) {
  140. this.offsetX = 0;
  141. this.offsetY = 0;
  142. this.rotateAngle += angle
  143. if (this.rotateAngle >= 360) {
  144. this.rotateAngle -= 360
  145. } else if (this.rotateAngle < 0) {
  146. this.rotateAngle += 360
  147. }
  148. if(this.rotateAngle==90||this.rotateAngle==270)
  149. {
  150. this.scaleValue=0.63
  151. }else {
  152. this.scaleValue=1
  153. }
  154. }
  155. // 重置图片变换
  156. private resetImageTransform() {
  157. this.rotateAngle = 0
  158. this.scaleValue = 1
  159. this.offsetX = 0
  160. this.offsetY = 0
  161. this.lastScale = 1
  162. this.lastOffsetX = 0
  163. this.lastOffsetY = 0
  164. }
  165. //连接相机
  166. connectCamera = async () => {
  167. CommonEventManager.publish("opensession", (err) => {
  168. if (err?.code) {
  169. console.info(TAG,"Publish openSession err=" + JSON.stringify(err))
  170. } else {
  171. console.info(TAG,"Publish openSession succeed ")
  172. }
  173. })
  174. }
  175. //查询闪光模式
  176. queryFlashMode = async () => {
  177. CommonEventManager.publish("queryflashmode", (err) => {
  178. if (err?.code) {
  179. console.info(TAG,"Publish openSession err=" + JSON.stringify(err))
  180. } else {
  181. console.info(TAG,"Publish openSession succeed ")
  182. }
  183. })
  184. }
  185. //开始预览
  186. startView=()=>{
  187. CommonEventManager.publish("startview", (err) => {
  188. if (err?.code) {
  189. console.error(TAG, JSON.stringify(err));
  190. } else {
  191. this.isCapturing = false;
  192. this.isStopView =false;
  193. this.liveShow();
  194. }
  195. });
  196. }
  197. //停止预览
  198. stopView=async()=>{
  199. CommonEventManager.publish("stopview", (err) => {
  200. if (err?.code) {
  201. console.error(TAG, JSON.stringify(err));
  202. } else {
  203. this.isCapturing = false;
  204. this.isStopView =true;
  205. }
  206. });
  207. }
  208. //重连相机
  209. reconnectCamera=async()=>{
  210. this.showConfirmDialog({
  211. title: '重连USB',
  212. message: `请重连USB后点击确定!`,
  213. onConfirm: async()=> {
  214. await new Promise<void>((resolve, reject) => {
  215. CommonEventManager.publish("stopview", (err) => {
  216. if (err) return reject(err);
  217. CommonEventManager.publish("closesession", (err) => {
  218. err ? reject(err) : resolve();
  219. });
  220. });
  221. });
  222. await new Promise<void>((resolve, reject) => {
  223. CommonEventManager.publish("reconnect", (err) => {
  224. if (err) return reject(err);
  225. resolve();
  226. });
  227. });
  228. promptAction.showToast({
  229. message: '相机正在重连中...',
  230. duration: 3000,
  231. bottom: 500
  232. });
  233. await sleep(3000);
  234. await this.connectCamera()
  235. //await this.liveShow()
  236. }
  237. });
  238. }
  239. //开始预览 50ms一张图片(20帧)
  240. liveShow = async () => {
  241. this.previewManager = new PreviewManager(getContext(this));
  242. let index = 0;
  243. this.readTimer = setInterval(async () => {
  244. if (!this.isStopView && !this.isCapturing) {
  245. try {
  246. const success = await this.previewManager!.processFrame(index);
  247. if (success) {
  248. this.commodityPixelMap = this.previewManager!.getActiveBuffer();
  249. }
  250. } catch (error) {
  251. console.warn(TAG, "预览更新失败:", error);
  252. }
  253. }
  254. }, 50);
  255. };
  256. //断开相机(停止预览->关闭连接)
  257. disconnectCamera = () => {
  258. CommonEventManager.publish("stopview", (err) => {
  259. if (err?.code) {
  260. console.info(TAG,"Publish stopview err=" + JSON.stringify(err))
  261. } else {
  262. console.info(TAG,"Publish stopview succeed ")
  263. CommonEventManager.publish("closesession", (err) => {
  264. if (err?.code) {
  265. console.info(TAG,"Publish closesession err=" + JSON.stringify(err))
  266. } else {
  267. console.info(TAG,"Publish closesession succeed ")
  268. }
  269. })
  270. }
  271. })
  272. }
  273. //拍照(停止预览->拍照->开始预览)
  274. takePhoto = async () => {
  275. this.isStopView = true;
  276. this.isCapturing = true;
  277. CommonEventManager.publish("stopview", (err) => {
  278. if (err?.code) {
  279. console.info(TAG,"Publish stopview err=" + JSON.stringify(err));
  280. this.isCapturing = false;
  281. } else {
  282. console.info(TAG,"Publish Publish stopview succeed");
  283. CommonEventManager.publish("shootonly", (err) => {
  284. if (err?.code) {
  285. console.info(TAG,"Publish shootonly error=" + JSON.stringify(err));
  286. this.isCapturing = false;
  287. } else {
  288. console.info(TAG,"Publish Publish shootonly succeed");
  289. }
  290. });
  291. }
  292. });
  293. };
  294. //设置闪光模式
  295. setFlashMode = (mode: 'openflash' | 'closeflash' | 'autoflash') => {
  296. this.isStopView = true;
  297. CommonEventManager.publish("stopview", (err) => {
  298. if (err?.code) {
  299. console.info(TAG,"Publish stopview err=" + JSON.stringify(err));
  300. } else {
  301. console.info(TAG,"Publish Publish stopview succeed");
  302. CommonEventManager.publish(mode, (err) => {
  303. if (err?.code) {
  304. console.info(TAG,"Publish shootonly error=" + JSON.stringify(err));
  305. } else {
  306. console.info(TAG,"Publish Publish shootonly succeed");
  307. CommonEventManager.publish("startview", (err) => {
  308. this.isStopView = false;
  309. if (err?.code) {
  310. console.info(TAG,"Publish shootonly error=" + JSON.stringify(err));
  311. }else{
  312. console.info(TAG,"Publish Publish shootonly succeed");
  313. }
  314. })
  315. }
  316. });
  317. }
  318. });
  319. }
  320. //上传照片
  321. uploadPhoto=()=>{
  322. let imageUri: string = "/data/storage/el2/base/haps/entry/files/image_base64.txt"
  323. try {
  324. uploadInstance.startUploadBase64(imageUri, () => {
  325. this.loadPhotos()
  326. this.isUploading = false
  327. this.startView();//上传完恢复预览
  328. })
  329. } catch (error) {
  330. console.error(TAG,"upload failed:", error.code);
  331. }
  332. }
  333. async aboutToAppear() {
  334. this.loadPhotos();
  335. await this.createSubscriber();
  336. await this.connectCamera();
  337. await this.queryFlashMode();
  338. }
  339. aboutToDisappear(): void {
  340. this.isStopView = true;
  341. sleep(100)
  342. this.disconnectCamera()
  343. if (this.subscriber) {
  344. CommonEventManager.unsubscribe(this.subscriber);
  345. }
  346. if (this.readTimer) {
  347. clearInterval(this.readTimer);
  348. }
  349. if (this.commodityPixelMap) {
  350. this.commodityPixelMap.release();
  351. this.commodityPixelMap = null;
  352. }
  353. if (this.previewManager) {
  354. this.previewManager.release();
  355. }
  356. }
  357. build() {
  358. Row() {
  359. Stack(){
  360. if (this.commodityPixelMap) {
  361. if (this.selectedPhotoIndex === -1) {
  362. Image(this.commodityPixelMap)
  363. .width('100%')
  364. .height('100%')
  365. .objectFit(ImageFit.Fill)
  366. }
  367. }
  368. if(this.isUploading){
  369. Column() {
  370. Text('正在上传中...')
  371. .fontSize($r('app.float.fontSize_30'))
  372. .margin({bottom:'10%'})
  373. LoadingProgress()
  374. .color(Color.Blue)
  375. .width('25%')
  376. .width('25%')
  377. }
  378. .height('30%')
  379. .width('25%')
  380. .backgroundColor(Color.White)
  381. .borderRadius($r('app.float.virtualSize_16'))
  382. .justifyContent(FlexAlign.Center)
  383. }
  384. if(this.selectedPhotoIndex === -1){
  385. Row() {
  386. Image(
  387. this.selectedFlashMode === 0 ? $r('app.media.process_flash_auto') :
  388. this.selectedFlashMode === 1 ? $r('app.media.process_flash_open') :
  389. $r('app.media.process_flash_close')
  390. )
  391. .width($r('app.float.virtualSize_48'))
  392. .height($r('app.float.virtualSize_48'))
  393. .enabled(!this.isUploading)
  394. .onClick(() => {
  395. this.isExpanded = !this.isExpanded
  396. })
  397. .margin({right:'5%'})
  398. if (this.isExpanded) {
  399. Row() {
  400. Row(){
  401. Text('自动')
  402. .fontSize($r('app.float.fontSize_24'))
  403. .fontColor(this.selectedFlashMode===0?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
  404. }
  405. .width('33.3%')
  406. .height('100%')
  407. .justifyContent(FlexAlign.Center)
  408. .borderRadius($r('app.float.virtualSize_16'))
  409. .backgroundColor(this.selectedFlashMode===0?$r('app.color.0A84FF'):$r('app.color.60000000'))
  410. .onClick(() => {
  411. this.setFlashMode('autoflash')
  412. this.isExpanded = false
  413. this.selectedFlashMode = 0
  414. })
  415. Row(){
  416. Text('开启')
  417. .fontSize($r('app.float.fontSize_24'))
  418. .fontColor(this.selectedFlashMode===1?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
  419. }
  420. .width('33.3%')
  421. .height('100%')
  422. .justifyContent(FlexAlign.Center)
  423. .borderRadius($r('app.float.virtualSize_16'))
  424. .backgroundColor(this.selectedFlashMode===1?$r('app.color.0A84FF'):$r('app.color.60000000'))
  425. .onClick(() => {
  426. this.setFlashMode('openflash')
  427. this.isExpanded = false
  428. this.selectedFlashMode = 1
  429. })
  430. Row(){
  431. Text('关闭')
  432. .fontSize($r('app.float.fontSize_24'))
  433. .fontColor(this.selectedFlashMode===2?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
  434. }
  435. .width('33.3%')
  436. .height('100%')
  437. .justifyContent(FlexAlign.Center)
  438. .borderRadius($r('app.float.virtualSize_16'))
  439. .backgroundColor(this.selectedFlashMode===2?$r('app.color.0A84FF'):$r('app.color.60000000'))
  440. .onClick(() => {
  441. this.setFlashMode('closeflash')
  442. this.isExpanded = false
  443. this.selectedFlashMode = 2
  444. })
  445. }
  446. .animation({ duration: 300, curve: Curve.EaseOut }) // 展开动画
  447. .backgroundColor($r('app.color.60000000'))
  448. .borderRadius($r('app.float.virtualSize_16'))
  449. .width('85%')
  450. .height('100%')
  451. }
  452. }
  453. .justifyContent(FlexAlign.Start)
  454. .alignItems(VerticalAlign.Bottom)
  455. .position({ x: '2%', y: '92%' })
  456. .height('5%')
  457. .width('24%')
  458. Image(this.shootButtonClick===1?$r('app.media.process_no_shoot'):$r('app.media.process_shoot'))
  459. .width($r('app.float.virtualSize_88'))
  460. .height($r('app.float.virtualSize_88'))
  461. .scale({ x: this.shootButtonClick, y: this.shootButtonClick })
  462. .borderRadius($r('app.float.fontSize_16'))
  463. .enabled(!this.isUploading)
  464. .animation({
  465. duration: 200,
  466. curve: Curve.Linear
  467. })
  468. .onClick(() => {
  469. this.shootButtonClick = 0.9;
  470. setTimeout(() => {
  471. this.shootButtonClick = 1;
  472. }, 200);
  473. if(this.isUploading){
  474. return
  475. }
  476. this.isUploading = true
  477. this.takePhoto();
  478. })
  479. .position({ x: '48%', y: '90%' })
  480. Row(){
  481. Text("重连相机")
  482. .fontColor($r('app.color.FFFFFF'))
  483. .fontSize($r('app.float.fontSize_24'))
  484. }
  485. .width('10%')
  486. .height('6%')
  487. .backgroundColor($r('app.color.20FFFFFF'))
  488. .position({ x: '88%', y: '91%' })
  489. .borderRadius($r('app.float.virtualSize_16'))
  490. .justifyContent(FlexAlign.Center)
  491. .onClick(()=>{
  492. this.reconnectCamera()
  493. })
  494. }
  495. else{
  496. Stack() {
  497. Image(CommonConstants.PICTURE_URL_PREFIX + this.photoList[this.selectedPhotoIndex].filePath)
  498. .width('100%')
  499. .height('100%')
  500. .objectFit(ImageFit.Fill)
  501. .rotate({ angle: this.rotateAngle })
  502. .scale({ x: this.scaleValue, y: this.scaleValue })
  503. .borderRadius($r('app.float.virtualSize_16'))
  504. .translate({ x: this.offsetX, y: this.offsetY })
  505. .gesture(
  506. GestureGroup(GestureMode.Exclusive,
  507. // 双指缩放手势
  508. PinchGesture()
  509. .onActionStart(() => {
  510. this.lastScale = this.scaleValue
  511. this.lastOffsetX = this.offsetX
  512. this.lastOffsetY = this.offsetY
  513. })
  514. .onActionUpdate((event: GestureEvent) => {
  515. this.scaleValue = this.lastScale * event.scale
  516. })
  517. .onActionEnd(() => {
  518. //缩放最小比例为1
  519. if (this.scaleValue < 1) {
  520. //旋转90°或者270°的最小缩小比例为0.63
  521. if(this.rotateAngle==90||this.rotateAngle==270) {
  522. if(this.scaleValue<0.63){
  523. this.scaleValue = 0.63
  524. }
  525. } else {
  526. this.scaleValue = 1
  527. }
  528. }
  529. //缩放最大比例为4
  530. if (this.scaleValue > 4) this.scaleValue = 4
  531. }),
  532. // 单指滑动手势
  533. PanGesture()
  534. .onActionStart(() => {
  535. if (this.scaleValue <= 1) return;
  536. this.lastOffsetX = this.offsetX;
  537. this.lastOffsetY = this.offsetY;
  538. })
  539. .onActionUpdate((event: GestureEvent) => {
  540. if (this.scaleValue <= 1) return;
  541. let dx = event.offsetX;
  542. let dy = event.offsetY;
  543. const sensitivity = 0.5 * this.scaleValue;
  544. // 临时计算新位置
  545. let newOffsetX = this.lastOffsetX;
  546. let newOffsetY = this.lastOffsetY;
  547. switch(this.rotateAngle % 360) {
  548. case 0:
  549. newOffsetX += dx * sensitivity;
  550. newOffsetY += dy * sensitivity;
  551. break;
  552. case 90:
  553. newOffsetX -= dy * sensitivity;
  554. newOffsetY += dx * sensitivity;
  555. break;
  556. case 180:
  557. newOffsetX -= dx * sensitivity;
  558. newOffsetY -= dy * sensitivity;
  559. break;
  560. case 270:
  561. newOffsetX += dy * sensitivity;
  562. newOffsetY -= dx * sensitivity;
  563. break;
  564. }
  565. this.offsetX = newOffsetX;
  566. this.offsetY = newOffsetY;
  567. })
  568. .onActionEnd(() => {
  569. if (this.scaleValue <= 1) {
  570. // 如果缩放比例<=1,直接重置位置
  571. this.offsetX = 0;
  572. this.offsetY = 0;
  573. return;
  574. }
  575. // 计算最大允许偏移量(基于缩放比例)
  576. const maxOffsetX = (this.scaleValue - 1) * 500; //500是容器的半宽
  577. const maxOffsetY = (this.scaleValue - 1) * 345; //345是容器的半高
  578. // 检查X轴边界
  579. if (Math.abs(this.offsetX) > maxOffsetX) {
  580. this.offsetX = this.offsetX > 0 ? maxOffsetX : -maxOffsetX;
  581. }
  582. // 检查Y轴边界
  583. if (Math.abs(this.offsetY) > maxOffsetY) {
  584. this.offsetY = this.offsetY > 0 ? maxOffsetY : -maxOffsetY;
  585. }
  586. // 添加回弹动画
  587. animateTo({
  588. duration: 300,
  589. curve: Curve.EaseOut
  590. }, () => {
  591. this.offsetX = this.offsetX;
  592. this.offsetY = this.offsetY;
  593. });
  594. })
  595. )
  596. )
  597. }
  598. .width('100%')
  599. .height('100%')
  600. .borderRadius($r('app.float.virtualSize_16'))
  601. .clip(true)
  602. Row()
  603. {
  604. Row(){
  605. Image($r('app.media.process_back_camera'))
  606. .width($r('app.float.virtualSize_80'))
  607. .height($r('app.float.virtualSize_80'))
  608. .onClick(()=>{
  609. this.selectedPhotoIndex=-1
  610. this.startView()
  611. })
  612. }
  613. .width('10%')
  614. .justifyContent(FlexAlign.Start)
  615. Row({space:20}){
  616. Image($r("app.media.process_photo_reset"))
  617. .width($r('app.float.virtualSize_80'))
  618. .height($r('app.float.virtualSize_80'))
  619. .onClick(()=>{
  620. this.resetImageTransform()
  621. })
  622. Image($r('app.media.process_photo_turn_left'))
  623. .width($r('app.float.virtualSize_80'))
  624. .height($r('app.float.virtualSize_80'))
  625. .onClick(()=>{
  626. this.rotateImage(-90)
  627. })
  628. Image($r('app.media.process_photo_turn_right'))
  629. .width($r('app.float.virtualSize_80'))
  630. .height($r('app.float.virtualSize_80'))
  631. .onClick(()=>{
  632. this.rotateImage(90)
  633. })
  634. Image($r('app.media.process_photo_delete'))
  635. .width($r('app.float.virtualSize_80'))
  636. .height($r('app.float.virtualSize_80'))
  637. .onClick(()=>{
  638. this.showConfirmDialog({
  639. title: '删除照片',
  640. message: `确定要删除照片吗?`,
  641. onConfirm: ()=> {
  642. const photosNum = this.photoList.length
  643. const photoId=this.photoList[this.selectedPhotoIndex].id
  644. //如果是图册最后一张照片,显示前一张照片,没有照片则返回拍照页面
  645. if(this.selectedPhotoIndex===photosNum-1)
  646. {
  647. this.selectedPhotoIndex--
  648. }
  649. this.deletePhoto(photoId)
  650. }
  651. });
  652. })
  653. }.width('88%')
  654. .justifyContent(FlexAlign.End)
  655. .margin({right :'2%'})
  656. }.width('98%')
  657. .height('10%')
  658. .position({x:'2%',y:'90%'})
  659. }
  660. }
  661. .width('86%')
  662. .height('100%')
  663. .backgroundColor($r('app.color.000000'))
  664. .border({width:2})
  665. Column(){
  666. List({ space: 8,scroller:this.scrollerPhoto }) {
  667. ForEach(this.photoList, (item: DrawingInfo, index) => {
  668. ListItem() {
  669. Column({space:4}){
  670. Column(){
  671. Image(CommonConstants.PICTURE_URL_PREFIX+item.filePath)
  672. .objectFit(ImageFit.Fill)
  673. .borderRadius($r('app.float.virtualSize_16'))
  674. .height('97%')
  675. .width('98%')
  676. .opacity(index === this.selectedPhotoIndex ? 0.8 : 1) // 20% 透明度
  677. }
  678. .backgroundColor(index === this.selectedPhotoIndex ? $r('app.color.30D158') : '')
  679. .width('90%')
  680. .justifyContent(FlexAlign.Center)
  681. .alignItems(HorizontalAlign.Center)
  682. .borderRadius($r('app.float.virtualSize_16'))
  683. .height('85%')
  684. Text(`${item.updated}`)
  685. .fontSize($r('app.float.fontSize_12'))
  686. .fontColor($r('app.color.FFFFFF'))
  687. .width('90%')
  688. .textAlign(TextAlign.Start)
  689. }
  690. .width('100%')
  691. .height('100%')
  692. .justifyContent(FlexAlign.Start)
  693. .alignItems(HorizontalAlign.Center)
  694. .enabled(!this.isUploading)
  695. .onClick(()=>{
  696. this.selectedPhotoIndex = index;
  697. this.resetImageTransform()
  698. this.stopView();
  699. })
  700. }.height('19%')
  701. .margin({bottom:'2%'})
  702. })
  703. }
  704. .width('100%')
  705. .margin({top:'2%',bottom:'2%'})
  706. .height('96%')
  707. }
  708. .width('14%')
  709. .height('100%')
  710. }
  711. .width('100%')
  712. .height('100%')
  713. //.backgroundColor($r('app.color.10FFFFFF'))
  714. .borderRadius($r('app.float.virtualSize_16'))
  715. }
  716. }
  717. function sleep(ms: number): Promise<void> {
  718. return new Promise((resolve) => setTimeout(resolve, ms));
  719. }
  720. class PreviewManager {
  721. private context: Context;
  722. private activeBuffer: PixelMap | null = null;
  723. private nextBuffer: PixelMap | null = null;
  724. private isProcessing: boolean = false;
  725. private lastFrameTime: number = 0;
  726. private frameInterval: number = 50;
  727. constructor(context: Context) {
  728. this.context = context;
  729. }
  730. async processFrame(index: number): Promise<boolean> {
  731. if (this.isProcessing) return false;
  732. const now = Date.now();
  733. if (now - this.lastFrameTime < this.frameInterval) {
  734. return false;
  735. }
  736. this.isProcessing = true;
  737. try {
  738. const filePath = `${this.context.filesDir}/live${index}.jpg`;
  739. try {
  740. fs.accessSync(filePath);
  741. } catch {
  742. this.isProcessing = false;
  743. return false;
  744. }
  745. const imageSource = image.createImageSource(filePath);
  746. if (!imageSource) {
  747. this.isProcessing = false;
  748. return false;
  749. }
  750. this.nextBuffer = await imageSource.createPixelMap();
  751. imageSource.release();
  752. if (this.activeBuffer) {
  753. this.activeBuffer.release();
  754. }
  755. this.activeBuffer = this.nextBuffer;
  756. this.nextBuffer = null;
  757. this.lastFrameTime = now;
  758. return true;
  759. } catch (error) {
  760. console.warn(TAG, "处理预览帧失败:", error);
  761. return false;
  762. } finally {
  763. this.isProcessing = false;
  764. }
  765. }
  766. getActiveBuffer(): PixelMap | null {
  767. return this.activeBuffer;
  768. }
  769. release() {
  770. if (this.activeBuffer) {
  771. this.activeBuffer.release();
  772. this.activeBuffer = null;
  773. }
  774. if (this.nextBuffer) {
  775. this.nextBuffer.release();
  776. this.nextBuffer = null;
  777. }
  778. }
  779. }
  780. interface CameraParam {
  781. flash_mode: string;
  782. moduleName?: string;
  783. }