CameraView.ets 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. /*
  2. * 相机控制
  3. * */
  4. import router from '@ohos.router';
  5. import image from '@ohos.multimedia.image';
  6. import fs from "@ohos.file.fs"
  7. import CommonEventManager from '@ohos.commonEventManager'
  8. import promptAction from '@ohos.promptAction';
  9. import { ConfirmDialogParams } from '../viewmodel/ConfirmDialogParam';
  10. import { ConfirmDialog } from '../view/ConfirmDialog';
  11. import { PreviewManager } from '../common/util/PreviewManager';
  12. const TAG = "sony_camera info"
  13. @Entry
  14. @Component
  15. struct CameraControl {
  16. @StorageLink('CameraStatus') cameraStatus: boolean = false;
  17. commonDialogController: CustomDialogController | null = null;
  18. build() {
  19. Row() {
  20. Column() {
  21. Row() {
  22. }.width('100%')
  23. .height('3.4%')
  24. Row(){
  25. Row() {
  26. Image($r('app.media.general_return'))
  27. .height($r('app.float.virtualSize_56'))
  28. .width($r('app.float.virtualSize_56'))
  29. .fillColor($r('app.color.FFFFFF'))
  30. Text('图像采集')
  31. .fontColor($r('app.color.FFFFFF'))
  32. .fontSize($r('app.float.fontSize_30'))
  33. }
  34. .width('75%')
  35. .justifyContent(FlexAlign.Start)
  36. .margin({ left: '3%' })
  37. .onClick(() => {
  38. router.back()
  39. })
  40. } .height('4%').width('100%').justifyContent(FlexAlign.Start)
  41. Row(){
  42. Column(){
  43. Row(){
  44. Image($r('app.media.device_multimedia_acquisition'))
  45. .width('70%')
  46. .objectFit(ImageFit.Contain)
  47. .interpolation(ImageInterpolation.High)
  48. }
  49. .height('40%')
  50. .width('100%')
  51. //.margin({top:'5%'})
  52. .alignItems(VerticalAlign.Bottom)
  53. .justifyContent(FlexAlign.Center)
  54. Column({space:10}){
  55. InfoRow({
  56. label:"编号:",
  57. value:"DF441AS114F5555",
  58. })
  59. InfoRow({
  60. label:"状态:",
  61. value:this.cameraStatus?"在线":"离线",
  62. valueColor:$r('app.color.30D158')
  63. })
  64. InfoRow({
  65. label:"计量有效期:",
  66. value:"2024/11/11~2025/11/11",
  67. })
  68. InfoRow({
  69. label:"像素:",
  70. value:"2420万像素",
  71. })
  72. InfoRow({
  73. label:"数码变焦:",
  74. value:"8倍",
  75. })
  76. InfoRow({
  77. label:"对焦方式:",
  78. value:"自动对焦",
  79. })
  80. InfoRow({
  81. label:"闪光模式:",
  82. value:"自动,不闪光,强制闪光",
  83. })
  84. }.justifyContent(FlexAlign.Start).margin({left:'10%'})
  85. }.width('22%').justifyContent(FlexAlign.SpaceAround).height('100%')
  86. Divider()
  87. .vertical(true)
  88. .strokeWidth(1)
  89. .color($r('app.color.15FFFFFF'))
  90. .margin({top:'10%'})
  91. Column(){
  92. CameraDialog({})
  93. .height('90%')
  94. }.justifyContent(FlexAlign.SpaceAround).margin({top:'5%'})
  95. .height('100%')
  96. .width('70%')
  97. }.height('80%').width('100%').justifyContent(FlexAlign.SpaceAround)
  98. }
  99. .width('100%')
  100. .height('100%')
  101. .backgroundColor($r('app.color.000000'))
  102. }
  103. .height('100%')
  104. }
  105. }
  106. @Component
  107. struct InfoRow {
  108. label: string = ''
  109. @Prop value: string = ''
  110. valueColor?: Resource = $r('app.color.FFFFFF') // 默认白色,可覆盖
  111. build() {
  112. Row({ space: 5 }) {
  113. Circle()
  114. .width($r('app.float.virtualSize_5'))
  115. .height($r('app.float.virtualSize_5'))
  116. .fill($r('app.color.FFFFFF'))
  117. Text(this.label)
  118. .fontColor($r('app.color.FFFFFF'))
  119. .fontSize($r('app.float.fontSize_16'))
  120. Text(this.value)
  121. .fontColor(this.valueColor)
  122. .fontSize($r('app.float.fontSize_16'))
  123. }
  124. .width('100%')
  125. }
  126. }
  127. @Component
  128. export struct CameraDialog {
  129. private scrollerPhoto: Scroller = new Scroller()
  130. //
  131. @State isExpanded: boolean = false
  132. //闪光模式 0:自动 1:开启 2:关闭
  133. @State selectedFlashMode:number = 0
  134. //选择的照片索引
  135. @State selectedPhotoIndex:number = -1
  136. //拍照按键缩放
  137. @State shootButtonClick:number =1
  138. // 旋转角度
  139. @State rotateAngle: number = 0
  140. // 照片缩放比例
  141. @State scaleValue: number = 1
  142. // 照片X轴偏移
  143. @State offsetX: number = 0
  144. // 照片Y轴偏移
  145. @State offsetY: number = 0
  146. // 照片上次缩放值
  147. @State lastScale: number = 1
  148. // 照片上次X偏移
  149. @State lastOffsetX: number = 0
  150. // 照片上次Y偏移
  151. @State lastOffsetY: number = 0
  152. //双指缩放的中心点X
  153. @State pinchCenterX: number = 0;
  154. //双指缩放的中心点Y
  155. @State pinchCenterY: number = 0;
  156. // 照片列表
  157. //@State photoList:DrawingInfo[]=[]
  158. // 获取本地live照片
  159. @State commodityPixelMap: PixelMap | null = null;
  160. // 拍照动作是否完成
  161. @State isCapturing: boolean = false;
  162. // 是否停止预览
  163. @State isStopView: boolean = false;
  164. // 控制帧率
  165. @State readTimer:number = 0
  166. // 预览操作
  167. private previewManager: PreviewManager | null = null;
  168. @State photoPixelMaps: photoInfo[] = []; // 存储所有照片的 PixelMap
  169. // 加载照片并生成 PixelMap
  170. loadPhotos = async () => {
  171. const context = getContext(this);
  172. console.info(TAG, '开始加载照片', context.filesDir);
  173. try {
  174. const localImageDir = `${context.filesDir}/`;
  175. // 检查目录是否存在
  176. if (!fs.accessSync(localImageDir)) {
  177. console.warn(TAG, "目录不存在");
  178. return;
  179. }
  180. // 获取目录下所有文件
  181. const files = fs.listFileSync(localImageDir);
  182. const newPixelMaps: photoInfo[] = [];
  183. // 加载所有以image_开头的图片
  184. for (const file of files) {
  185. // 只处理以image_开头的文件
  186. if (!file.startsWith('image_') || !file.endsWith('.jpg')) {
  187. continue;
  188. }
  189. const filePath = `${localImageDir}${file}`;
  190. try {
  191. const imageSource = image.createImageSource(filePath);
  192. const imageInfo = await imageSource.getImageInfo();
  193. const scaleRatio = Math.min(600 / imageInfo.size.width, 800 / imageInfo.size.height);
  194. const thumbnailOpts: image.InitializationOptions = {
  195. size: {
  196. width: Math.floor(imageInfo.size.width * scaleRatio),
  197. height: Math.floor(imageInfo.size.height * scaleRatio)
  198. }
  199. }
  200. const pixelMap = await imageSource.createPixelMap(thumbnailOpts);
  201. const timestamp = this.parseTimestampFromFilename(file);
  202. newPixelMaps.push({
  203. pixelMap: pixelMap,
  204. timestamp: timestamp,
  205. filename: file
  206. });
  207. imageSource.release();
  208. } catch (err) {
  209. console.error(TAG, `加载图片 ${file} 失败:`, err);
  210. }
  211. }
  212. // 按时间戳降序排序
  213. newPixelMaps.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
  214. this.photoPixelMaps = newPixelMaps;
  215. } catch (err) {
  216. console.error(TAG, "加载照片失败:", err);
  217. }
  218. };
  219. private parseTimestampFromFilename(filename: string): string {
  220. if (!filename.startsWith("image_") || !filename.endsWith(".jpg")) {
  221. return "";
  222. }
  223. const dateTimePart = filename.substring(6, filename.length - 4);
  224. if (dateTimePart.length !== 15) {
  225. return "";
  226. }
  227. const datePart = dateTimePart.substring(0, 8);
  228. const timePart = dateTimePart.substring(9);
  229. const year = datePart.substring(0, 4);
  230. const month = datePart.substring(4, 6);
  231. const day = datePart.substring(6, 8);
  232. const hour = timePart.substring(0, 2);
  233. const minute = timePart.substring(2, 4);
  234. const second = timePart.substring(4);
  235. return `${year}/${month}/${day} ${hour}:${minute}:${second}`;
  236. }
  237. private releaseAllPixelMaps() {
  238. this.photoPixelMaps.forEach(item => item.pixelMap?.release());
  239. this.photoPixelMaps = [];
  240. }
  241. // 删除照片
  242. deletePhoto = async (index: number) => {
  243. try {
  244. const context = getContext(this);
  245. const photoToDelete = this.photoPixelMaps[index];
  246. const filePath = `${context.filesDir}/${photoToDelete.filename}`;
  247. console.info(TAG, `要删除文件: ${filePath}`);
  248. // 1. 删除物理文件
  249. fs.unlinkSync(filePath);
  250. console.info(TAG, `已删除文件: ${filePath}`);
  251. // 2. 释放PixelMap资源
  252. photoToDelete.pixelMap.release();
  253. // 3. 从数组中移除
  254. const newPhotoList = [...this.photoPixelMaps];
  255. newPhotoList.splice(index, 1);
  256. this.photoPixelMaps = newPhotoList;
  257. // 4. 处理选中的照片索引
  258. if (this.photoPixelMaps.length === 0) {
  259. // 如果所有照片都删完了
  260. this.selectedPhotoIndex = -1;
  261. this.startView();
  262. } else {
  263. // 还有照片剩余的情况
  264. if (this.selectedPhotoIndex === index) {
  265. // 当前删除的是正在显示的照片
  266. if (index > 0) {
  267. // 如果不是第一张,显示前一张
  268. this.selectedPhotoIndex = index - 1;
  269. } else {
  270. // 如果是第一张,显示下一张(现在index=0,所以下一张也是0)
  271. this.selectedPhotoIndex = 0;
  272. }
  273. } else if (this.selectedPhotoIndex > index) {
  274. // 删除的照片在当前显示照片之前,需要调整索引
  275. this.selectedPhotoIndex -= 1;
  276. }
  277. // 如果删除的照片在当前显示照片之后,不需要调整索引
  278. }
  279. promptAction.showToast({
  280. message: '照片删除成功',
  281. duration: 2000
  282. });
  283. } catch (err) {
  284. console.error(TAG, "删除照片失败:", err.code);
  285. promptAction.showToast({
  286. message: '删除照片失败',
  287. duration: 2000
  288. });
  289. }
  290. };
  291. //创建订阅者
  292. subscriber: CommonEventManager.CommonEventSubscriber | null = null;
  293. //订阅相机回调
  294. subscribeInfo: CommonEventManager.CommonEventSubscribeInfo = { events: ["sonycamera_callback"] };
  295. //提示确认弹窗
  296. commonDialogController: CustomDialogController | null = null;
  297. private showConfirmDialog(params: ConfirmDialogParams) {
  298. if (this.commonDialogController) {
  299. this.commonDialogController.close()
  300. }
  301. this.commonDialogController = new CustomDialogController({
  302. builder: ConfirmDialog({
  303. title: params.title || '提示',
  304. message: params.message,
  305. onConfirm: params.onConfirm
  306. }),
  307. cancel: () => console.log('用户取消操作'),
  308. customStyle: true,
  309. autoCancel:false,
  310. maskColor: 'rgba(0,0,0,0.6)'
  311. });
  312. this.commonDialogController.open();
  313. }
  314. //订阅回调(code=3代表连接成功,code=2代表拍照成功)
  315. createSubscriber = async () => {
  316. this.subscriber = await CommonEventManager.createSubscriber(this.subscribeInfo);
  317. if (this.subscriber) {
  318. console.info(TAG, "创建订阅回调成功");
  319. CommonEventManager.subscribe(this.subscriber, (err, data) => {
  320. if (err?.code) {
  321. console.error(TAG, "SubscribeCallBack err=" + JSON.stringify(err));
  322. return;
  323. }
  324. console.info(TAG, "SubscribeCallBack data=" + JSON.stringify(data));
  325. if (data?.code !== undefined) {
  326. switch (data.code) {
  327. case 2:
  328. this.loadPhotos()
  329. this.isCapturing =false
  330. this.startView(); // 拍照成功
  331. break;
  332. case 3:
  333. console.info(TAG, "开始预览");
  334. this.startView(); // 连接成功
  335. break;
  336. case -1:
  337. console.info(TAG,'连接故障')
  338. break;
  339. case 5:
  340. console.info(TAG,'暂停预览')
  341. break;
  342. }
  343. }
  344. //0001:自动闪光 0002:关闭闪光 0003:开启闪光
  345. if (data?.parameters?.flash_mode) {
  346. const flashMode:string = data.parameters.flash_mode
  347. console.info(TAG, `收到闪光模式: ${flashMode}`);
  348. switch (flashMode) {
  349. case '0001':
  350. this.selectedFlashMode = 0
  351. break;
  352. case '0002':
  353. this.selectedFlashMode = 2
  354. break;
  355. case '0003':
  356. this.selectedFlashMode = 1
  357. break;
  358. default:
  359. console.warn(TAG, `未知闪光模式: ${flashMode}`);
  360. }
  361. }
  362. });
  363. }
  364. };
  365. //旋转照片
  366. private rotateImage(angle: number) {
  367. this.offsetX = 0;
  368. this.offsetY = 0;
  369. this.rotateAngle += angle
  370. if (this.rotateAngle >= 360) {
  371. this.rotateAngle -= 360
  372. } else if (this.rotateAngle < 0) {
  373. this.rotateAngle += 360
  374. }
  375. if(this.rotateAngle==90||this.rotateAngle==270)
  376. {
  377. this.scaleValue=0.667
  378. }else {
  379. this.scaleValue=1
  380. }
  381. }
  382. // 重置图片变换
  383. private resetImageTransform() {
  384. this.rotateAngle = 0
  385. this.scaleValue = 1
  386. this.offsetX = 0
  387. this.offsetY = 0
  388. this.lastScale = 1
  389. this.lastOffsetX = 0
  390. this.lastOffsetY = 0
  391. }
  392. //连接相机
  393. connectCamera = async () => {
  394. CommonEventManager.publish("opensession", (err) => {
  395. if (err?.code) {
  396. console.info(TAG,"Publish openSession err=" + JSON.stringify(err))
  397. } else {
  398. console.info(TAG,"Publish openSession succeed ")
  399. this.queryFlashMode();
  400. }
  401. })
  402. }
  403. //查询闪光模式
  404. queryFlashMode = async () => {
  405. CommonEventManager.publish("queryflashmode", (err) => {
  406. if (err?.code) {
  407. console.info(TAG,"Publish queryflashmode err=" + JSON.stringify(err))
  408. } else {
  409. console.info(TAG,"Publish queryflashmode succeed ")
  410. }
  411. })
  412. }
  413. //开始预览
  414. startView=async()=>{
  415. CommonEventManager.publish("startview", (err) => {
  416. if (err?.code) {
  417. console.error(TAG, JSON.stringify(err));
  418. } else {
  419. console.info(TAG, 'publish startview succeed');
  420. this.isStopView =false;
  421. this.liveShow();
  422. }
  423. });
  424. }
  425. //停止预览
  426. stopView=async()=>{
  427. CommonEventManager.publish("stopview", (err) => {
  428. if (err?.code) {
  429. console.error(TAG, JSON.stringify(err));
  430. } else {
  431. this.isStopView =true;
  432. console.info(TAG, 'publish stopview succeed');
  433. if (this.readTimer) {
  434. clearInterval(this.readTimer);
  435. this.readTimer = 0 ;
  436. }
  437. }
  438. });
  439. }
  440. //重连相机
  441. reconnectCamera=async()=>{
  442. this.showConfirmDialog({
  443. title: '重连USB',
  444. message: `请重连USB后点击确定!`,
  445. onConfirm: async()=> {
  446. await new Promise<void>((resolve, reject) => {
  447. CommonEventManager.publish("stopview", (err) => {
  448. if (err) return reject(err);
  449. CommonEventManager.publish("closesession", (err) => {
  450. err ? reject(err) : resolve();
  451. });
  452. });
  453. });
  454. await new Promise<void>((resolve, reject) => {
  455. CommonEventManager.publish("reconnect", (err) => {
  456. if (err) return reject(err);
  457. resolve();
  458. });
  459. });
  460. promptAction.showToast({
  461. message: '相机正在重连中...',
  462. duration: 3000,
  463. bottom: 500
  464. });
  465. await sleep(3000);
  466. await this.connectCamera()
  467. //await this.liveShow()
  468. }
  469. });
  470. }
  471. liveShow = async () => {
  472. let index = 0;
  473. let retryCount = 0;
  474. const MAX_RETRIES = 10; // 最大重试次数
  475. const previewLoop = async () => {
  476. if (!this.isStopView && !this.isCapturing) {
  477. try {
  478. const success = await this.previewManager!.processFrame(index);
  479. if (success) {
  480. this.commodityPixelMap = await this.previewManager!.getActiveBuffer();
  481. retryCount = 0;
  482. } else {
  483. retryCount++;
  484. console.warn(TAG, "processFrame失败");
  485. }
  486. } catch (error) {
  487. retryCount++;
  488. console.warn(TAG, "预览更新失败:", error);
  489. }
  490. }
  491. if (retryCount === MAX_RETRIES) {
  492. clearInterval(this.readTimer)
  493. this.readTimer = 0;
  494. console.info(TAG,"GG")
  495. return;
  496. //this.reconnectCamera();
  497. }
  498. if (this.readTimer !== 0) { // 检查是否应该继续循环
  499. this.readTimer = setTimeout(previewLoop, 50);
  500. }
  501. };
  502. this.readTimer = setTimeout(previewLoop, 50);
  503. };
  504. //断开相机(停止预览->关闭连接)
  505. disconnectCamera = async () => {
  506. if(!this.isStopView)
  507. {
  508. await this.stopView()
  509. }
  510. CommonEventManager.publish("closesession", (err) => {
  511. if (err?.code) {
  512. console.info(TAG,"Publish closesession err=" + JSON.stringify(err))
  513. } else {
  514. console.info(TAG,"Publish closesession succeed ")
  515. }
  516. })
  517. }
  518. //拍照(停止预览->拍照->开始预览)
  519. takePhoto = async () => {
  520. this.isStopView = true;
  521. this.isCapturing = true;
  522. CommonEventManager.publish("stopview", (err) => {
  523. if (err?.code) {
  524. console.info(TAG,"Publish stopview err=" + JSON.stringify(err));
  525. } else {
  526. console.info(TAG,"Publish Publish stopview succeed");
  527. CommonEventManager.publish("shootandsave", (err) => {
  528. if (err?.code) {
  529. console.info(TAG,"Publish shootandsave error=" + JSON.stringify(err));
  530. } else {
  531. console.info(TAG,"Publish Publish shootandsave succeed");
  532. }
  533. });
  534. }
  535. });
  536. };
  537. //设置闪光模式
  538. setFlashMode = (mode: 'openflash' | 'closeflash' | 'autoflash') => {
  539. this.isStopView = true;
  540. CommonEventManager.publish("stopview", (err) => {
  541. if (err?.code) {
  542. console.info(TAG,"Publish stopview err=" + JSON.stringify(err));
  543. } else {
  544. console.info(TAG,"Publish Publish stopview succeed");
  545. CommonEventManager.publish(mode, (err) => {
  546. if (err?.code) {
  547. console.info(TAG,"Publish flashmode error=" + JSON.stringify(err));
  548. } else {
  549. console.info(TAG,"Publish Publish flashmode succeed");
  550. CommonEventManager.publish("startview", (err) => {
  551. this.isStopView = false;
  552. if (err?.code) {
  553. console.info(TAG,"Publish startview error=" + JSON.stringify(err));
  554. }else{
  555. console.info(TAG,"Publish Publish startview succeed");
  556. }
  557. })
  558. }
  559. });
  560. }
  561. });
  562. }
  563. private adjustOffsetWithAnimation() {
  564. const isRotated = (this.rotateAngle === 90 || this.rotateAngle === 270);
  565. //旋转90度或者70度 宽度0.667倍,长度超过1.5倍才拖动
  566. let moveScaleY = (this.scaleValue - (isRotated ? 0.667 : 1)) / 2;
  567. let moveScaleX = (this.scaleValue - (isRotated ? 1.50 : 1)) / 2;
  568. let maxOffsetX = (this.scaleValue<1.5&&isRotated) ?this.offsetX: (isRotated ? 506 : 760) * moveScaleX;
  569. let maxOffsetY = (isRotated ? 760 : 506) * moveScaleY; //rk3588
  570. // let maxOffsetX = (this.scaleValue<1.5&&isRotated) ?this.offsetX: (isRotated ? 1018 : 1524) * moveScaleX;
  571. // let maxOffsetY = (isRotated ? 1524 : 1018) * moveScaleY; //rk3568
  572. //console.info(TAG,"maxOffsetX",maxOffsetX,"scale",this.scaleValue,"offsetx",this.offsetX)
  573. const clampedX = Math.max(-maxOffsetX, Math.min(maxOffsetX, this.offsetX));
  574. const clampedY = Math.max(-maxOffsetY, Math.min(maxOffsetY, this.offsetY));
  575. if (this.offsetX !== clampedX || this.offsetY !== clampedY) {
  576. animateTo({
  577. duration: 100,
  578. curve: Curve.EaseOut
  579. }, () => {
  580. this.offsetX = clampedX;
  581. this.offsetY = clampedY;
  582. });
  583. }
  584. }
  585. //上传照片
  586. uploadPhoto=()=>{
  587. }
  588. async aboutToAppear() {
  589. this.previewManager = new PreviewManager(getContext(this));
  590. this.loadPhotos();
  591. await this.createSubscriber();
  592. await sleep(50)
  593. await this.connectCamera();
  594. }
  595. aboutToDisappear(): void {
  596. this.disconnectCamera()
  597. if (this.previewManager) {
  598. this.previewManager.release();
  599. this.previewManager = null
  600. }
  601. if (this.subscriber) {
  602. CommonEventManager.unsubscribe(this.subscriber);
  603. }
  604. this.releaseAllPixelMaps()
  605. if (this.commodityPixelMap) {
  606. this.commodityPixelMap.release();
  607. this.commodityPixelMap = null;
  608. }
  609. }
  610. build() {
  611. Row() {
  612. Stack(){
  613. if (this.commodityPixelMap) {
  614. if (this.selectedPhotoIndex === -1) {
  615. Image(this.commodityPixelMap)
  616. .width('100%')
  617. .height('100%')
  618. .objectFit(ImageFit.Fill)
  619. }
  620. }
  621. if(this.isCapturing){
  622. Column() {
  623. Text('正在拍照中...')
  624. .fontSize($r('app.float.fontSize_30'))
  625. .margin({bottom:'10%'})
  626. LoadingProgress()
  627. .color(Color.Blue)
  628. .width('25%')
  629. .width('25%')
  630. }
  631. .height('30%')
  632. .width('25%')
  633. .backgroundColor(Color.White)
  634. .borderRadius($r('app.float.virtualSize_16'))
  635. .justifyContent(FlexAlign.Center)
  636. }
  637. if(this.selectedPhotoIndex === -1){
  638. Row() {
  639. Image(
  640. this.selectedFlashMode === 0 ? $r('app.media.process_flash_auto') :
  641. this.selectedFlashMode === 1 ? $r('app.media.process_flash_open') :
  642. $r('app.media.process_flash_close')
  643. )
  644. .width($r('app.float.virtualSize_48'))
  645. .height($r('app.float.virtualSize_48'))
  646. .enabled(!this.isCapturing)
  647. .onClick(() => {
  648. this.isExpanded = !this.isExpanded
  649. })
  650. .margin({right:'5%'})
  651. if (this.isExpanded) {
  652. Row() {
  653. Row(){
  654. Text('自动')
  655. .fontSize($r('app.float.fontSize_24'))
  656. .fontColor(this.selectedFlashMode===0?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
  657. }
  658. .width('33.3%')
  659. .height('100%')
  660. .justifyContent(FlexAlign.Center)
  661. .borderRadius($r('app.float.virtualSize_16'))
  662. .backgroundColor(this.selectedFlashMode===0?$r('app.color.0A84FF'):$r('app.color.60000000'))
  663. .onClick(() => {
  664. this.setFlashMode('autoflash')
  665. this.isExpanded = false
  666. this.selectedFlashMode = 0
  667. })
  668. Row(){
  669. Text('开启')
  670. .fontSize($r('app.float.fontSize_24'))
  671. .fontColor(this.selectedFlashMode===1?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
  672. }
  673. .width('33.3%')
  674. .height('100%')
  675. .justifyContent(FlexAlign.Center)
  676. .borderRadius($r('app.float.virtualSize_16'))
  677. .backgroundColor(this.selectedFlashMode===1?$r('app.color.0A84FF'):$r('app.color.60000000'))
  678. .onClick(() => {
  679. this.setFlashMode('openflash')
  680. this.isExpanded = false
  681. this.selectedFlashMode = 1
  682. })
  683. Row(){
  684. Text('关闭')
  685. .fontSize($r('app.float.fontSize_24'))
  686. .fontColor(this.selectedFlashMode===2?$r('app.color.FFFFFF'):$r('app.color.60FFFFFF'))
  687. }
  688. .width('33.3%')
  689. .height('100%')
  690. .justifyContent(FlexAlign.Center)
  691. .borderRadius($r('app.float.virtualSize_16'))
  692. .backgroundColor(this.selectedFlashMode===2?$r('app.color.0A84FF'):$r('app.color.60000000'))
  693. .onClick(() => {
  694. this.setFlashMode('closeflash')
  695. this.isExpanded = false
  696. this.selectedFlashMode = 2
  697. })
  698. }
  699. .animation({ duration: 300, curve: Curve.EaseOut })
  700. .backgroundColor($r('app.color.60000000'))
  701. .borderRadius($r('app.float.virtualSize_16'))
  702. .width('85%')
  703. .height('100%')
  704. }
  705. }
  706. .justifyContent(FlexAlign.Start)
  707. .alignItems(VerticalAlign.Bottom)
  708. .position({ x: '2%', y: '92%' })
  709. .height('5%')
  710. .width('24%')
  711. Image(this.shootButtonClick===1?$r('app.media.process_no_shoot'):$r('app.media.process_shoot'))
  712. .width($r('app.float.virtualSize_88'))
  713. .height($r('app.float.virtualSize_88'))
  714. .scale({ x: this.shootButtonClick, y: this.shootButtonClick })
  715. .borderRadius($r('app.float.fontSize_16'))
  716. .enabled(!this.isCapturing)
  717. .animation({
  718. duration: 200,
  719. curve: Curve.Linear
  720. })
  721. .onClick(() => {
  722. this.shootButtonClick = 0.9;
  723. setTimeout(() => {
  724. this.shootButtonClick = 1;
  725. }, 200);
  726. this.takePhoto();
  727. })
  728. .position({ x: '48%', y: '90%' })
  729. Row(){
  730. Text("重连相机")
  731. .fontColor($r('app.color.FFFFFF'))
  732. .fontSize($r('app.float.fontSize_24'))
  733. }
  734. .width('10%')
  735. .height('6%')
  736. .backgroundColor($r('app.color.60000000'))
  737. .position({ x: '88%', y: '91%' })
  738. .borderRadius($r('app.float.virtualSize_16'))
  739. .enabled(!this.isCapturing)
  740. .justifyContent(FlexAlign.Center)
  741. .onClick(()=>{
  742. this.reconnectCamera()
  743. })
  744. }
  745. else{
  746. Stack() {
  747. Image(this.photoPixelMaps[this.selectedPhotoIndex].pixelMap)
  748. .width('100%')
  749. .height('100%')
  750. .autoResize(true)
  751. // .onComplete((event) => {
  752. // console.info(TAG,"width:"+event!.componentWidth +"height:"+ event!.componentHeight)
  753. // }) // 获取Image组件的长宽(除以1.5获取vp)
  754. .objectFit(ImageFit.Fill)
  755. .rotate({ angle: this.rotateAngle })
  756. .scale({ x: this.scaleValue, y: this.scaleValue })
  757. .borderRadius($r('app.float.virtualSize_16'))
  758. .translate({ x: this.offsetX, y: this.offsetY })
  759. .gesture(
  760. GestureGroup(GestureMode.Exclusive,
  761. PinchGesture()
  762. .onActionStart((event: GestureEvent) => {
  763. this.lastScale = this.scaleValue;
  764. this.lastOffsetX = this.offsetX;
  765. this.lastOffsetY = this.offsetY;
  766. // 记录双指中心点(相对于图片中心)
  767. this.pinchCenterX = event.pinchCenterX - 380 - 760 / 2;//rk3588
  768. this.pinchCenterY = event.pinchCenterY - 130 - 506 / 2;//rk3588
  769. // this.pinchCenterX = event.pinchCenterX - 1016 / 2; //rk3568
  770. // this.pinchCenterY = event.pinchCenterY - 678 / 2; //rk3568
  771. // console.info(TAG,this.pinchCenterX,TAG,this.pinchCenterY)
  772. })
  773. .onActionUpdate((event: GestureEvent) => {
  774. let newScale = this.lastScale * event.scale;
  775. //保持双指中心点不变
  776. const scaleRatio = newScale / this.lastScale;
  777. const newOffsetX = this.lastOffsetX + (1 - scaleRatio) * this.pinchCenterX;
  778. const newOffsetY = this.lastOffsetY + (1 - scaleRatio) * this.pinchCenterY;
  779. this.offsetX = newOffsetX;
  780. this.offsetY = newOffsetY;
  781. this.scaleValue = newScale;
  782. })
  783. .onActionEnd(() => {
  784. // 缩放最小比例为1
  785. if (this.scaleValue < 1) {
  786. // 旋转90°或者270°的最小缩小比例为0.667
  787. if(this.rotateAngle==90||this.rotateAngle==270) {
  788. if(this.scaleValue<0.667){
  789. this.scaleValue = 0.667
  790. }
  791. } else {
  792. this.scaleValue = 1
  793. }
  794. }
  795. // 缩放最大比例为3
  796. if (this.scaleValue > 3) this.scaleValue = 3
  797. this.adjustOffsetWithAnimation()
  798. }),
  799. // 单指滑动手势
  800. PanGesture()
  801. .onActionStart(() => {
  802. if(this.rotateAngle === 90 || this.rotateAngle === 270)
  803. {
  804. if (this.scaleValue <= 0.667)
  805. {
  806. return;
  807. }
  808. }else{
  809. if (this.scaleValue <= 1) return;
  810. }
  811. this.lastOffsetX = this.offsetX;
  812. this.lastOffsetY = this.offsetY;
  813. })
  814. .onActionUpdate((event: GestureEvent) => {
  815. if(this.rotateAngle === 90 || this.rotateAngle === 270)
  816. {
  817. if (this.scaleValue <= 0.667)
  818. {
  819. return;
  820. }
  821. }else{
  822. if (this.scaleValue <= 1) return;
  823. }
  824. let dx = event.offsetX;
  825. let dy = event.offsetY;
  826. const sensitivity = 0.5 * this.scaleValue;
  827. // 临时计算新位置
  828. let newOffsetX = this.lastOffsetX;
  829. let newOffsetY = this.lastOffsetY;
  830. newOffsetX += dx * sensitivity;
  831. newOffsetY += dy * sensitivity;
  832. this.offsetX = newOffsetX;
  833. this.offsetY = newOffsetY;
  834. //console.info(TAG,this.offsetX,TAG,this.offsetY)
  835. })
  836. .onActionEnd(() => {
  837. const isRotated = (this.rotateAngle === 90 || this.rotateAngle === 270);
  838. if(isRotated)
  839. {
  840. if(this.scaleValue<=1.5)
  841. {
  842. this.offsetX = 0
  843. }
  844. }else{
  845. if (this.scaleValue <= 1) {
  846. // 如果缩放比例<=1,直接重置位置
  847. this.offsetX = 0;
  848. this.offsetY = 0;
  849. return;
  850. }
  851. }
  852. this.adjustOffsetWithAnimation()
  853. })
  854. )
  855. )
  856. }
  857. .width('100%')
  858. .height('100%')
  859. .borderRadius($r('app.float.virtualSize_16'))
  860. .clip(true)
  861. Row()
  862. {
  863. Row(){
  864. Image($r('app.media.process_back_camera'))
  865. .width($r('app.float.virtualSize_80'))
  866. .height($r('app.float.virtualSize_80'))
  867. .onClick(()=>{
  868. this.selectedPhotoIndex=-1
  869. this.startView()
  870. })
  871. }
  872. .width('10%')
  873. .justifyContent(FlexAlign.Start)
  874. Row({space:20}){
  875. Image($r("app.media.process_photo_reset"))
  876. .width($r('app.float.virtualSize_80'))
  877. .height($r('app.float.virtualSize_80'))
  878. .onClick(()=>{
  879. this.resetImageTransform()
  880. })
  881. Image($r('app.media.process_photo_turn_left'))
  882. .width($r('app.float.virtualSize_80'))
  883. .height($r('app.float.virtualSize_80'))
  884. .onClick(()=>{
  885. this.rotateImage(-90)
  886. })
  887. Image($r('app.media.process_photo_turn_right'))
  888. .width($r('app.float.virtualSize_80'))
  889. .height($r('app.float.virtualSize_80'))
  890. .onClick(()=>{
  891. this.rotateImage(90)
  892. })
  893. Image($r('app.media.process_photo_delete'))
  894. .width($r('app.float.virtualSize_80'))
  895. .height($r('app.float.virtualSize_80'))
  896. .onClick(()=>{
  897. this.showConfirmDialog({
  898. title: '删除照片',
  899. message: `确定要删除照片吗?`,
  900. onConfirm: ()=> {
  901. this.deletePhoto(this.selectedPhotoIndex);
  902. }
  903. });
  904. })
  905. }.width('88%')
  906. .justifyContent(FlexAlign.End)
  907. .margin({right :'2%'})
  908. }.width('98%')
  909. .height('10%')
  910. .position({x:'2%',y:'90%'})
  911. }
  912. }
  913. .width('86%')
  914. .height('100%')
  915. .backgroundColor($r('app.color.000000'))
  916. .border({width:2})
  917. Column(){
  918. List({ space: 8,scroller:this.scrollerPhoto }) {
  919. ForEach(this.photoPixelMaps, (item:photoInfo, index) => {
  920. ListItem() {
  921. Column({space:4}){
  922. Column(){
  923. Image(item.pixelMap)
  924. .objectFit(ImageFit.Fill)
  925. .borderRadius($r('app.float.virtualSize_16'))
  926. .height('97%')
  927. .width('98%')
  928. .opacity(index === this.selectedPhotoIndex ? 0.8 : 1) // 20% 透明度
  929. Text(item.timestamp)
  930. .fontSize($r('app.float.fontSize_12'))
  931. .fontColor($r('app.color.FFFFFF'))
  932. .width('100%')
  933. .textAlign(TextAlign.Start)
  934. }
  935. .backgroundColor(index === this.selectedPhotoIndex ? $r('app.color.30D158') : '')
  936. .width('90%')
  937. .justifyContent(FlexAlign.Center)
  938. .alignItems(HorizontalAlign.Center)
  939. .borderRadius($r('app.float.virtualSize_16'))
  940. .height('85%')
  941. // Text(`${item.updated}`)
  942. // .fontSize($r('app.float.fontSize_12'))
  943. // .fontColor($r('app.color.FFFFFF'))
  944. // .width('90%')
  945. // .textAlign(TextAlign.Start)
  946. }
  947. .width('100%')
  948. .height('100%')
  949. .justifyContent(FlexAlign.Start)
  950. .alignItems(HorizontalAlign.Center)
  951. .enabled(!this.isCapturing)
  952. .onClick(()=>{
  953. this.selectedPhotoIndex = index;
  954. this.resetImageTransform()
  955. this.stopView();
  956. })
  957. }.height('19%')
  958. .margin({bottom:'2%'})
  959. })
  960. }
  961. .width('100%')
  962. .margin({top:'2%',bottom:'2%'})
  963. .height('96%')
  964. }
  965. .width('14%')
  966. .height('100%')
  967. }
  968. .width('100%')
  969. .height('100%')
  970. .backgroundColor($r('app.color.10FFFFFF'))
  971. .borderRadius($r('app.float.virtualSize_16'))
  972. .borderWidth(4)
  973. }
  974. }
  975. function sleep(ms: number): Promise<void> {
  976. return new Promise((resolve) => setTimeout(resolve, ms));
  977. }
  978. interface photoInfo {
  979. pixelMap: PixelMap;
  980. timestamp: string;
  981. filename: string;
  982. }