YTPhotoHelper.ets 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import axios, { AxiosProgressEvent } from '@ohos/axios';
  2. import { fileIo, fileUri } from '@kit.CoreFileKit';
  3. import { camera, cameraPicker as picker } from '@kit.CameraKit';
  4. import { util } from '@kit.ArkTS';
  5. import { photoAccessHelper } from '@kit.MediaLibraryKit';
  6. import { image } from '@kit.ImageKit';
  7. import { BusinessError } from '@kit.BasicServicesKit';
  8. import { ContextHelper, ResultCallBack, YTLog } from '../../../../Index';
  9. import { componentSnapshot } from '@kit.ArkUI';
  10. import fs from '@ohos.file.fs';
  11. /**
  12. * @method cashPhotos 传入需要下载得url数组并下载文件,通过回调函数返回对应cashPaths数组
  13. * @method takePicture 拍照获取图片
  14. * @method selectImage 从相册选择图片
  15. * @method saveByShowAssetsCreationDialog 通过ShowAssetsCreationDialog保存文件(弹窗)
  16. * @method getPhotoFileBuffer 将缓存的图片转化为流
  17. * @method saveImgToAssets 直接保存图片至相册 需要权限
  18. * @method saveScreenshotToAlbum 截屏保存图片
  19. * @method manualCompression压缩图片
  20. */
  21. export class YTPhotoHelper {
  22. private cashPaths: string[] = []
  23. private currentIndex: number = 0
  24. //传入需要下载得url数组并下载文件,通过回调函数返回对应cashPaths数组
  25. async cashPhotos(urls: string[], finishDownLoad: (cashPaths: string[]) => void, cashPhotoType: string = '.jpg') {
  26. let filePath = ContextHelper.context.cacheDir + '/' + util.generateRandomUUID() + cashPhotoType
  27. // Download the file. If the file already exists, delete the existing one first.
  28. try {
  29. fileIo.accessSync(filePath);
  30. fileIo.unlinkSync(filePath);
  31. } catch (err) {
  32. YTLog.error(err)
  33. }
  34. await axios({
  35. url: urls[this.currentIndex],
  36. method: 'get',
  37. context: ContextHelper.context,
  38. filePath: filePath,
  39. onDownloadProgress: (progressEvent: AxiosProgressEvent): void => {
  40. YTLog.info("progress: " + progressEvent && progressEvent.loaded && progressEvent.total ?
  41. Math.ceil(progressEvent.loaded / progressEvent.total * 100) : 0)
  42. }
  43. })
  44. this.currentIndex++
  45. this.cashPaths.unshift(filePath)
  46. if (this.currentIndex < urls.length) {
  47. this.cashPhotos(urls, finishDownLoad, cashPhotoType)
  48. } else {
  49. finishDownLoad(this.cashPaths)
  50. }
  51. }
  52. //拍照获取图片
  53. async takePicture() {
  54. let pathDir = ContextHelper.context.filesDir;
  55. let fileName = `${new Date().getTime()}`
  56. let filePath = pathDir + `/${fileName}.png`
  57. fileIo.createRandomAccessFileSync(filePath, fileIo.OpenMode.CREATE);
  58. let uri = fileUri.getUriFromPath(filePath);
  59. let pickerProfile: picker.PickerProfile = {
  60. cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
  61. saveUri: uri
  62. };
  63. let result: picker.PickerResult =
  64. await picker.pick(ContextHelper.context, [picker.PickerMediaType.PHOTO],
  65. //(如果需要录像可以添加) picker.PickerMediaType.VIDEO
  66. pickerProfile);
  67. if (!result.resultUri) {
  68. return Promise.reject('用户未拍照')
  69. }
  70. return filePath
  71. }
  72. //从相册选择图片
  73. selectImage(success: (fullPath: string) => void, maxNumber: number = 1) {
  74. const option = new photoAccessHelper.PhotoSelectOptions()
  75. option.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
  76. option.maxSelectNumber = maxNumber
  77. const picker = new photoAccessHelper.PhotoViewPicker()
  78. picker.select(option)
  79. .then(res => {
  80. const photoName = util.generateRandomUUID()
  81. const photoType = '.' + res.photoUris[0].split('.').pop()
  82. const fullPath = ContextHelper.context.cacheDir + '/' + photoName + photoType
  83. const photo = fileIo.openSync(res.photoUris[0])
  84. try {
  85. fileIo.copyFile(photo.fd, fullPath)
  86. .then(() => {
  87. YTLog.info(fullPath)
  88. success(fullPath)
  89. })
  90. } catch (err) {
  91. YTLog.error(err)
  92. }
  93. })
  94. }
  95. // 保存截图到相册的方法
  96. async saveScreenshotToAlbum(activityId: string, result?: ResultCallBack<undefined>, clearCashPaths: boolean = false) {
  97. try {
  98. // 获取组件截图
  99. const pixelMap = componentSnapshot.getSync(activityId)
  100. if (!pixelMap) {
  101. console.error('Failed to get screenshot')
  102. return
  103. }
  104. // 创建临时文件路径
  105. const tempDir = ContextHelper.context.tempDir
  106. const filePath = tempDir + '/screenshot_' + util.generateRandomUUID() + '.jpg'
  107. // 将PixelMap打包为JPEG格式
  108. const imagePacker = image.createImagePacker()
  109. const packOptions: image.PackingOption = {
  110. format: 'image/jpeg',
  111. quality: 90
  112. }
  113. const data = await imagePacker.packing(pixelMap, packOptions)
  114. // 写入文件
  115. const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
  116. fs.writeSync(file.fd, data)
  117. fs.closeSync(file)
  118. this.saveByShowAssetsCreationDialog([filePath], result, clearCashPaths)
  119. } catch (error) {
  120. console.error('Error saving screenshot: ' + JSON.stringify(error))
  121. }
  122. }
  123. //传入需要保存得cashPaths数组,并通过ShowAssetsCreationDialog保存文件
  124. async saveByShowAssetsCreationDialog(
  125. cashPaths: string[],
  126. result?: ResultCallBack<undefined>,
  127. clearCashPaths: boolean = false) {
  128. const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(ContextHelper.context);
  129. const uris = cashPaths.map(item => fileUri.getUriFromPath(item))
  130. try {
  131. // 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选。
  132. const photoCreationConfigs = uris.map(() => {
  133. const photoCreationConfig: photoAccessHelper.PhotoCreationConfig = {
  134. fileNameExtension: 'jpg',
  135. photoType: photoAccessHelper.PhotoType.IMAGE,
  136. }
  137. return photoCreationConfig
  138. })
  139. // 基于弹窗授权的方式获取媒体库的目标uri。
  140. let desFileUris: Array<string> =
  141. await phAccessHelper.showAssetsCreationDialog(uris, photoCreationConfigs);
  142. let index = 0
  143. while (index < uris.length) {
  144. //通过该uri打开图片
  145. let file = await fileIo.open(desFileUris[index], fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE)
  146. //获取流
  147. const buffer = await this.getPhotoFileBuffer(cashPaths[index])
  148. //通过流写入相册
  149. await fileIo.write(file.fd, buffer)
  150. //关流
  151. await fileIo.close(file)
  152. index++
  153. }
  154. result?.()
  155. if (clearCashPaths) {
  156. cashPaths.forEach(cashPath => fileIo.unlink(cashPath))
  157. }
  158. } catch (err) {
  159. YTLog.error(err)
  160. result?.(undefined, err)
  161. }
  162. }
  163. //传入图片地址,转化为流
  164. async getPhotoFileBuffer(filePath: string): Promise<ArrayBuffer> {
  165. try {
  166. const imageSource = image.createImageSource(filePath)
  167. let imagePackerApi = image.createImagePacker();
  168. let buffer = await imagePackerApi.packing(imageSource, {
  169. quality: 100,
  170. format: 'image/jpeg'
  171. })
  172. return buffer
  173. } catch (err) {
  174. let error: BusinessError = err as BusinessError;
  175. console.error(`read file failed, errCode:${error.code}, errMessage:${error.message}`);
  176. return Promise.reject(err)
  177. }
  178. }
  179. //须先申请权限ohos.permission.WRITE_IMAGEVIDEO 保存图片至相册
  180. async saveImgToAssets(cashPaths: string[]) {
  181. try {
  182. let index = 0;
  183. while (index < cashPaths.length) {
  184. //获取当前上下文
  185. //通过accessHelper模块获取accessHelper对象
  186. let accessHelper = photoAccessHelper.getPhotoAccessHelper(ContextHelper.context)
  187. //指定待创建的文件类型、后缀和创建选项,创建图片或视频资源 返回创建的图片和视频的uri
  188. let uri = await accessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg')
  189. //通过该uri打开图片
  190. let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE)
  191. //获取流
  192. const buffer = await this.getPhotoFileBuffer(cashPaths[index])
  193. //通过流写入相册
  194. await fileIo.write(file.fd, buffer)
  195. //关流
  196. await fileIo.close(file)
  197. index++
  198. }
  199. } catch (e) {
  200. YTLog.error(e)
  201. }
  202. }
  203. /**
  204. * 手动压缩图片
  205. * @param filePath 图片路径
  206. * @param result 回调
  207. * @param quality 压缩质量
  208. */
  209. async manualCompression(filePath: string, result: (imagePath: string) => void, quality: number = 20) {
  210. const imageSourceApi: image.ImageSource = image.createImageSource(filePath);
  211. const packOpts: image.PackingOption = { format: "image/jpeg", quality };
  212. const imagePackerApi = image.createImagePacker()
  213. const buffer = await imagePackerApi.packing(imageSourceApi, packOpts)
  214. const imagePath = ContextHelper.context.cacheDir + '/' + util.generateRandomUUID() + '.jpeg'
  215. const file = fileIo.openSync(imagePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)
  216. fileIo.writeSync(file.fd, buffer)
  217. fileIo.closeSync(file)
  218. fileIo.unlinkSync(filePath)
  219. result(imagePath)
  220. return imagePath
  221. }
  222. }