rabbit-harmony/common/src/main/ets/utils/ImageUtils.ets

411 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string> {
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<string> {
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<string> {
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<string> {
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 = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="${width}" height="${height}"
xlink:href="data:image/png;base64,${base64Data}"
href="data:image/png;base64,${base64Data}" />
</svg>`;
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<boolean> {
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<StickerItem|null> {
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<StickerItem|null> {
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<image.PixelMap[]> {
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<image.PixelMap> {
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<image.ImageSource|undefined> {
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();
}
}
}