import { image } from '@kit.ImageKit'; import { fileIo as fs } from '@kit.CoreFileKit'; import { common } from '@kit.AbilityKit'; import { BusinessError } from '@ohos.base'; import { util } from '@kit.ArkTS'; import { PickerUtil } from '@pura/picker_utils'; import componentSnapshot from '@ohos.arkui.componentSnapshot'; import { promptAction } from '@kit.ArkUI'; import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { subjectSegmentation } from '@kit.CoreVisionKit'; import { StickerItem } from '../viewmodel/LocalBean'; import buffer from '@ohos.buffer'; import fileIo from '@ohos.file.fs'; import { ToastUtils } from '../dialog/ToastUtils'; export class ImageUtils { private static readonly TAG: string = 'ImageUtils'; /** * 压缩图片并保存到沙箱缓存目录 * @param context 上下文 * @param uri 原始图片URI (如 datashare:// 或 file://) * @param quality 压缩质量 (0-100),默认 80 * @returns 压缩后的沙箱文件路径 */ static async compressImage(context: common.Context, uri: string, quality: number = 80): Promise { let file: fs.File | undefined; try { file = fs.openSync(uri, fs.OpenMode.READ_ONLY); const imageSource: image.ImageSource = image.createImageSource(file.fd); const packingOptions: image.PackingOption = { format: 'image/jpeg', quality: quality }; const imagePacker: image.ImagePacker = image.createImagePacker(); const arrayBuffer: ArrayBuffer = await imagePacker.packing(imageSource, packingOptions); const fileName = `compress_${Date.now()}.jpg`; const cachePath = `${context.cacheDir}/${fileName}`; const cacheFile = fs.openSync(cachePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); fs.writeSync(cacheFile.fd, arrayBuffer); fs.closeSync(cacheFile); await imagePacker.release(); await imageSource.release(); console.info(ImageUtils.TAG, `图片压缩成功: ${cachePath}`); return `file://${cachePath}`; // 返回符合上传要求的 file 协议路径 } catch (error) { const err = error as BusinessError; console.error(ImageUtils.TAG, `压缩失败: ${err.code} ${err.message}`); return uri; // 如果压缩失败,返回原路径尝试直接上传 } finally { if (file) { fs.closeSync(file); } } } /** * 将图片 URI 转换为 Base64 字符串 * @param uri 相册选择的 datashare:// 或沙箱路径 */ static async base64Image(uri: string): Promise { const context = getContext(); // 使用临时路径存储拷贝的文件 const tempFileName = `${Date.now()}.jpg`; const destPath = `${context.cacheDir}/${tempFileName}`; let srcFile: fs.File | null = null; try { srcFile = fs.openSync(uri, fs.OpenMode.READ_ONLY); fs.copyFileSync(srcFile.fd, destPath); const base64Data = await ImageUtils.fileToBase64(destPath); fs.unlinkSync(destPath); return base64Data; } catch (err) { const error = err as BusinessError; console.error(`base64Image 转换失败: ${error.message}`); return ""; } finally { if (srcFile) { fs.closeSync(srcFile); } } } static async pixelMapToBase64(pixelMap: image.PixelMap): Promise { const imagePackerApi = image.createImagePacker(); const packOptions: image.PackingOption = { format: 'image/png', quality: 100 }; const data = await imagePackerApi.packing(pixelMap, packOptions); let base64Str = buffer.from(data).toString('base64'); return `data:image/png;base64,${base64Str}`; } /** * 内部方法:读取文件转 Base64 */ private static async fileToBase64(filePath: string): Promise { const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY); const stat = fs.statSync(file.fd); const buffer = new ArrayBuffer(stat.size); fs.readSync(file.fd, buffer); fs.closeSync(file); const helper = new util.Base64Helper(); return helper.encodeToStringSync(new Uint8Array(buffer)); } /** * 保存图片到指定路径(用户自己选择) * @param pixelMap */ static async savePixelMapToGallery(pixelMap: image.PixelMap) { const imagePacker: image.ImagePacker = image.createImagePacker(); const packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 }; try { const uris = await PickerUtil.savePhoto([`Rabbit_IMG_${Date.now()}.jpg`]); const targetUri: string = Array.isArray(uris) ? uris[0] : uris; if (!targetUri) { console.warn('用户取消保存或路径获取失败'); return; } const buffer: ArrayBuffer = await imagePacker.packing(pixelMap, packOpts) as ArrayBuffer; const file = fs.openSync(targetUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); fs.writeSync(file.fd, buffer); fs.closeSync(file.fd); console.info('保存成功!路径: ' + targetUri); } catch (err) { console.error('保存过程中出错: ' + JSON.stringify(err)); } finally { imagePacker.release(); } } /** * 将图片(image.PixelMap)转成jpg、png、webp等 * 保存图片到指定路径(用户自己选择) * @param pixelMap * @param format 支持: 'image/jpeg', 'image/png', 'image/webp' */ static async saveFormatToGallery(pixelMap: image.PixelMap, format: string) { const imagePacker: image.ImagePacker = image.createImagePacker(); const packOpts: image.PackingOption = { format: format, quality: 100 }; try { const uris = await PickerUtil.savePhoto([`Rabbit_IMG_${Date.now()}.${format === 'image/jpeg' ? 'jpg' : format.substring(6)}`]); const targetUri: string = Array.isArray(uris) ? uris[0] : uris; if (!targetUri) { console.warn('用户取消保存或路径获取失败'); return; } const buffer: ArrayBuffer = await imagePacker.packing(pixelMap, packOpts) as ArrayBuffer; const file = fs.openSync(targetUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); fs.writeSync(file.fd, buffer); fs.closeSync(file.fd); ToastUtils.normalToast({ message: '图片已保存' }); } catch (err) { ToastUtils.normalToast({ message: '保存失败' }); } finally { imagePacker.release(); } } /** * 将图片(image.PixelMap)转成SVG * @param pixelMap */ static async saveSvgToGallery(pixelMap: image.PixelMap) { let imagePackerApi = image.createImagePacker(); const info = await pixelMap.getImageInfo(); const width = info.size.width; const height = info.size.height; const uris = await PickerUtil.savePhoto([`Rabbit_IMG_${Date.now()}.svg`]); const targetUri: string = Array.isArray(uris) ? uris[0] : uris; if (!targetUri) { console.warn('用户取消保存或路径获取失败'); return; } const packOptions: image.PackingOption = { format: 'image/png', quality: 100 }; const arrayBuffer = await imagePackerApi.packing(pixelMap, packOptions); // 将二进制数据转为 Base64 字符串,buffer.from().toString('base64') 默认不换行的 (等同于 NO_WRAP) const base64Data = buffer.from(arrayBuffer).toString('base64'); // 构建 SVG 内容 const svgContent = ` `; try { let file = fileIo.openSync(targetUri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE | fileIo.OpenMode.TRUNC); fileIo.writeSync(file.fd, svgContent); fileIo.closeSync(file); ToastUtils.normalToast({ message: '图片已保存' }); } catch (err) { ToastUtils.normalToast({ message: '保存失败' }); } } /** * 保存图片(通过布局ID保存图片) * 注意:id为控件的ID */ static async saveImageForID(id: string): Promise { try { let pixelMap = await componentSnapshot.get(id); if (pixelMap) { await ImageUtils.savePixelMapToGallery(pixelMap); ToastUtils.normalToast({ message: '图片已保存' }); return true; }else{ return false; } } catch (err) { console.error(`快照捕获失败: ${JSON.stringify(err)}`); return false; } } /** * 打开图库(仅可选单张图片) */ static async openGallery(): Promise { let file: fs.File | null = null; try { const photoPicker = new photoAccessHelper.PhotoViewPicker(); const options = new photoAccessHelper.PhotoSelectOptions(); options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; options.maxSelectNumber = 1; const result: photoAccessHelper.PhotoSelectResult = await photoPicker.select(options); if (result.photoUris.length === 0) return null; const uri: string = result.photoUris[0]; file = fs.openSync(uri, fs.OpenMode.READ_ONLY); const imageSource: image.ImageSource = image.createImageSource(file.fd); const pixelMap: image.PixelMap = await imageSource.createPixelMap(); let newItem = new StickerItem(pixelMap,0); return newItem; } catch (err) { ToastUtils.normalToast({ message: '获取图片失败!' }); return null; } finally { if (file) fs.closeSync(file); } } /** * 打开图库(仅可选单张图片-抠图) */ static async openCutoutGallery(): Promise { let file: fs.File | null = null; try { const photoPicker = new photoAccessHelper.PhotoViewPicker(); const options = new photoAccessHelper.PhotoSelectOptions(); options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; options.maxSelectNumber = 1; const result: photoAccessHelper.PhotoSelectResult = await photoPicker.select(options); if (result.photoUris.length === 0) return null; const uri: string = result.photoUris[0]; file = fs.openSync(uri, fs.OpenMode.READ_ONLY); const imageSource: image.ImageSource = image.createImageSource(file.fd); const pixelMap: image.PixelMap = await imageSource.createPixelMap(); ToastUtils.normalToast({ message: '抠图中...' }); await subjectSegmentation.init(); const visionInfo: subjectSegmentation.VisionInfo = { pixelMap: pixelMap }; const config: subjectSegmentation.SegmentationConfig = { enableSubjectForegroundImage: true }; const segResult: subjectSegmentation.SegmentationResult = await subjectSegmentation.doSegmentation(visionInfo, config); if (segResult && segResult.fullSubject && segResult.fullSubject.foregroundImage) { const pixelMap = segResult.fullSubject.foregroundImage; let newItem = new StickerItem(pixelMap,0); await subjectSegmentation.release(); return newItem; }else{ await subjectSegmentation.release(); return null; } } catch (err) { ToastUtils.normalToast({ message: '抠图失败,请使用规范图片' }); return null; } finally { if (file) fs.closeSync(file); } } /** * 打开图库(可选择多张图片) */ static async openMultipleGallery(): Promise { const pixelMaps: image.PixelMap[] = []; try { const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions(); photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; photoSelectOptions.maxSelectNumber = 10; const photoPicker = new photoAccessHelper.PhotoViewPicker(); const photoSelectResult = await photoPicker.select(photoSelectOptions); if (!photoSelectResult || photoSelectResult.photoUris.length === 0) { return []; } for (const uri of photoSelectResult.photoUris) { let file: fs.File | null = null; try { file = fs.openSync(uri, fs.OpenMode.READ_ONLY); const imageSource: image.ImageSource = image.createImageSource(file.fd); const decodingOptions: image.DecodingOptions = { editable: true, desiredPixelFormat: image.PixelMapFormat.RGBA_8888, }; const pm = await imageSource.createPixelMap(decodingOptions); if (pm) { pixelMaps.push(pm); } imageSource.release(); } catch (e) { console.error(`解析图片失败: ${uri}, 错误: ${JSON.stringify(e)}`); } finally { if (file) { fs.closeSync(file.fd); } } } return pixelMaps; } catch (err) { console.error('选择图片过程发生错误: ' + JSON.stringify(err)); return []; } } /** * 获取 Resource 对应的 PixelMap * @param res Resource 对象 * @returns PixelMap 对象 */ static async getPixelMapFromResource(res: Resource, context: common.UIAbilityContext): Promise { const resourceMgr = context.resourceManager; const fileData = await resourceMgr.getMediaContent(res); const imageSource = image.createImageSource(fileData.buffer as ArrayBuffer); return await imageSource.createPixelMap({ editable: true, desiredPixelFormat: image.PixelMapFormat.RGBA_8888 }); } /** * 将 PixelMap 转换为 ImageSource * @param pixelMap 输入的位图对象 * @returns 转换后的 ImageSource 对象 */ static async pixelMapToImageSource(pixelMap: image.PixelMap): Promise { const imagePacker = image.createImagePacker(); const packingOptions: image.PackingOption = { format: "image/jpeg", quality: 100 }; try { const buffer: ArrayBuffer = await imagePacker.packing(pixelMap, packingOptions); const imageSource: image.ImageSource = image.createImageSource(buffer); return imageSource; } catch (err) { console.error('转换失败: ' + JSON.stringify(err)); return undefined; } finally { imagePacker.release(); } } }