轻松上手-图片压缩秘籍

news/2024/10/18 14:16:18/文章来源:https://www.cnblogs.com/army16/p/18474161
作者:狼哥
团队:坚果派
团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原生应用,三方库60+,欢迎交流。

介绍

​ 图片压缩功能在现代数字生活中扮演着至关重要的角色,其好处多不胜数。在填写报名表时,常遇到对上传图片大小的严格限制,通过图片压缩,可以迅速减小文件体积,确保顺利提交,避免因文件过大而延误申请。同样,在发表文章或分享至社交媒体时,图片压缩能显著提升加载速度,减少用户等待时间,提升阅读体验。此外,压缩后的图片更便于存储与传输,节省宝贵的存储空间和网络带宽。总之,图片压缩功能不仅解决了大小限制的问题,还优化了网络体验,是现代信息交流的得力助手。

效果预览

工程目录

├──entry/src/main/ets                         // 代码区
│  ├──dialog
│  │  └──ImagePicker.ets                      // 图片选择
│  ├──entryability
│  │  └──EntryAbility.ets 
│  ├──model
│  │  ├──ImageModel.ets                       // 图片操作
│  └──pages
│     └──Index.ets                            // 首页
└──entry/src/main/resources                   // 应用资源目录

具体实现

1. 权限添加

配置文件module.json5里添加读取图片及视频权限和修改图片或视频权限。

"requestPermissions": [{"name": "ohos.permission.WRITE_MEDIA","usedScene": {"abilities": ["EntryAbility"],"when": "inuse"},"reason": "$string:WRITE_MEDIA"},{"name": "ohos.permission.MEDIA_LOCATION","usedScene": {"abilities": ["EntryAbility"],"when": "inuse"},"reason": "$string:MEDIA_LOCATION"},{"name": "ohos.permission.READ_IMAGEVIDEO","usedScene": {"abilities": ["EntryAbility"],"when": "inuse"},"reason": "$string:READ_IMAGEVIDEO"},{"name": "ohos.permission.WRITE_IMAGEVIDEO","usedScene": {"abilities": ["EntryAbility"],"when": "inuse"},"reason": "$string:WRITE_IMAGEVIDEO"}]
2. 图片选择对话

获取本地图片:首先使用getPhotoAccessHelper获取相册管理模块实例,然后使用getAssets方法获取文件资源,最后使用getAllObjects获取检索结果中的所有文件资产方便展示;

	let photoList: Array<photoAccessHelper.PhotoAsset> = [];let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();let fetchOptions: photoAccessHelper.FetchOptions = {fetchColumns: [],predicates: predicates}let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> = await this.phAccessHelper.getAssets(fetchOptions);if (fetchResult != undefined) {let photoAsset: Array<photoAccessHelper.PhotoAsset> = await fetchResult.getAllObjects();if (photoAsset != undefined && photoAsset.length > 0) {for (let i = 0; i < photoAsset.length; i++) {if (photoAsset[i].photoType === 1) {photoList.push(photoAsset[i]);}}}}

自定义对话框显示获取到的本地图片

import { photoAccessHelper } from '@kit.MediaLibraryKit';@CustomDialog
export struct ImagePicker {@Link index: number;private imagesData: Array<photoAccessHelper.PhotoAsset> = [];public controller: CustomDialogController;@State selected: number = 0;build() {Column() {List({ space: 5 }) {ForEach(this.imagesData, (item: photoAccessHelper.PhotoAsset, index) => {ListItem() {Stack({ alignContent: Alignment.TopEnd }) {...}}, (item: photoAccessHelper.PhotoAsset) => JSON.stringify(item))}.width('95%').height(160).listDirection(Axis.Horizontal)Row() {...}.margin({ bottom: 10, top: 10 })}.width('100%').padding({ top: 16, left: 16, right: 16 })}
}
3. 图片压缩

​ 3.1 点击“图片压缩”按钮查看压缩后的图片。效果图中是按照固定目标大小为500kb,实际压缩大小小于等于500kb,不一定准确为500kb,此案例是固定了压缩大小为500kb,可以提供一个文本输入框或下拉框提供一些选择要压缩大小。

​ 3.2. 先判断设置图片质量参数quality为0时,packing能压缩到的图片最小字节大小是否满足指定的图片压缩大小。如果满足,则使用packing方式二分查找最接近指定图片压缩目标大小的quality来压缩图片。如果不满足,则使用scale对图片先进行缩放,采用while循环每次递减0.4倍缩放图片,再用packing(图片质量参数quality设置0)获取压缩图片大小,最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。

async compressedImage(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number): Promise<PixelMap> {// 创建图像编码ImagePacker对象const imagePackerApi = image.createImagePacker();// 定义图片质量参数const IMAGE_QUALITY = 0;// 设置编码输出流和编码参数。图片质量参数quality范围0-100。const packOpts: image.PackingOption = { format: "image/jpeg", quality: IMAGE_QUALITY };// 通过PixelMap进行编码。compressedImageData为打包获取到的图片文件流。let compressedImageData: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);// 压缩目标图像字节长度const maxCompressedImageByte = maxCompressedImageSize * 1024;// TODO 知识点:图片压缩。先判断设置图片质量参数quality为0时,packing能压缩到的图片最小字节大小是否满足指定的图片压缩大小。如果满足,则使用packing方式二分查找最接近指定图片压缩目标大小的quality来压缩图片。如果不满足,则使用scale对图片先进行缩放,采用while循环每次递减0.4倍缩放图片,再用packing(图片质量参数quality设置0)获取压缩图片大小,最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。if (maxCompressedImageByte > compressedImageData.byteLength) {// 使用packing二分压缩获取图片文件流compressedImageData =await this.packingImage(compressedImageData, sourcePixelMap, IMAGE_QUALITY, maxCompressedImageByte);} else {// 使用scale对图片先进行缩放,采用while循环每次递减0.4倍缩放图片,再用packing(图片质量参数quality设置0)获取压缩图片大小,最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据let imageScale = 1; // 定义图片宽高的缩放倍数,1表示原比例。const REDUCE_SCALE = 0.4; // 图片缩小倍数// 判断压缩后的图片大小是否大于指定图片的压缩目标大小,如果大于,继续降低缩放倍数压缩。while (compressedImageData.byteLength > maxCompressedImageByte) {if (imageScale > 0) {// 性能知识点: 由于scale会直接修改图片PixelMap数据,所以不适用二分查找scale缩放倍数。这里采用循环递减0.4倍缩放图片,来查找确定最适// 合的缩放倍数。如果对图片压缩质量要求不高,建议调高每次递减的缩放倍数reduceScale,减少循环,提升scale压缩性能。imageScale = imageScale - REDUCE_SCALE; // 每次缩放倍数减0.4// 使用scale对图片进行缩放await sourcePixelMap.scale(imageScale, imageScale);// packing压缩compressedImageData = await this.packing(sourcePixelMap, IMAGE_QUALITY);} else {// imageScale缩放小于等于0时,没有意义,结束压缩。这里不考虑图片缩放倍数小于reduceScale的情况。break;}}}let pixelMap = await image.createImageSource(compressedImageData).createPixelMap();return pixelMap;}

​ 3.3 packing压缩

  async packing(sourcePixelMap: image.PixelMap, imageQuality: number): Promise<ArrayBuffer> {const imagePackerApi = image.createImagePacker();const packOpts: image.PackingOption = { format: "image/jpeg", quality: imageQuality };const data: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);return data;}

​ 3.4 packing 二分方式循环压缩

async packingImage(compressedImageData: ArrayBuffer, sourcePixelMap: image.PixelMap, imageQuality: number,maxCompressedImageByte: number): Promise<ArrayBuffer> {// 图片质量参数范围为0-100,这里以10为最小二分单位创建用于packing二分图片质量参数的数组。const packingArray: number[] = [];const DICHOTOMY_ACCURACY = 10;// 性能知识点: 如果对图片压缩质量要求不高,建议调高最小二分单位dichotomyAccuracy,减少循环,提升packing压缩性能。for (let i = 0; i <= 100; i += DICHOTOMY_ACCURACY) {packingArray.push(i);}let left = 0; // 定义二分搜索范围的左边界let right = packingArray.length - 1; // 定义二分搜索范围的右边界// 二分压缩图片while (left <= right) {const mid = Math.floor((left + right) / 2); // 定义二分搜索范围的中间位置imageQuality = packingArray[mid]; // 获取二分中间位置的图片质量值// 根据传入的图片质量参数进行packing压缩,返回压缩后的图片文件流数据。compressedImageData = await this.packing(sourcePixelMap, imageQuality);// 判断查找一个尽可能接近但不超过压缩目标的压缩大小if (compressedImageData.byteLength <= maxCompressedImageByte) {// 二分目标值在右半边,继续在更高的图片质量参数(即mid + 1)中搜索left = mid + 1;// 判断mid是否已经二分到最后,如果二分完了,退出if (mid === packingArray.length - 1) {break;}// 获取下一次二分的图片质量参数(mid+1)压缩的图片文件流数据compressedImageData = await this.packing(sourcePixelMap, packingArray[mid + 1]);// 判断用下一次图片质量参数(mid+1)压缩的图片大小是否大于指定图片的压缩目标大小。如果大于,说明当前图片质量参数(mid)压缩出来的// 图片大小最接近指定图片的压缩目标大小。传入当前图片质量参数mid,得到最终目标图片压缩数据。if (compressedImageData.byteLength > maxCompressedImageByte) {compressedImageData = await this.packing(sourcePixelMap, packingArray[mid]);break;}} else {// 目标值不在当前范围的右半部分,将搜索范围的右边界向左移动,以缩小搜索范围并继续在下一次迭代中查找左半部分。right = mid - 1;}}return compressedImageData;}
4.保存图片

​ 点击“保存图片”按钮把压缩后的图片保存到图库里。

  async savePixelMap(pm: PixelMap) {if (this.phAccessHelper === null) {return;}const imagePackerApi: image.ImagePacker = image.createImagePacker();const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 30 };try {const buffer: ArrayBuffer = await imagePackerApi.packing(pm, packOpts);let options: photoAccessHelper.CreateOptions = {title: new Date().getTime().toString()};let photoUri: string = await this.phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg', options);let file: fileIo.File = fileIo.openSync(photoUri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);await fileIo.write(file.fd, buffer);fileIo.closeSync(file);promptAction.showToast({message: '保存成功!'})} catch (err) {promptAction.showToast({message: '保存失败!'})console.error('xxx', err)}}
5. 界面布局

​ 使用垂直布局显示,上面图片初始化为图库第一张图片,点击图片可以弹窗显示图库图片提供切换选择要压缩图片,中间是图片压缩和保存图片两个按钮,下面是压缩后图片预览图。

  // 图库上图片@State imgData: Array<photoAccessHelper.PhotoAsset> = [];// 选择图库图片的下标@StorageLink('index') @Watch('onImageChange') index: number = 0;private imageModel: ImageModel = new ImageModel();@State compressedImg: PixelMap | null = null;// 这里500是希望压缩到500K大小private targetSize: number = 500;
Column() {Image(this.imgData[this.index]?.uri).objectFit(ImageFit.Contain).width('100%').aspectRatio(1).margin(20).onClick(async () => {this.imgData = await this.imageModel.getAllImg();setTimeout(() => {this.dialogController.open();}, 200);})Stack() {Divider().width('100%').color(Color.Orange)Row({ space: 10 }) {Button('图片压缩').onClick(async () => {this.compressedImg = null;// 以只读方式打开指定下标图片await fileIo.open(this.imgData[this.index].uri, fileIo.OpenMode.READ_ONLY).then(async (file: fileIo.File) => {let fd: number = file.fd;// 获取图片源let imageSource: PixelMap = await image.createImageSource(fd).createPixelMap();// 这里500是希望压缩到500K大小this.compressedImg = await this.imageModel.compressedImage(imageSource, this.targetSize);});})Button('保存图片').onClick(async () => {if (this.compressedImg) {await this.imageModel.savePixelMap(this.compressedImg)}})}}.width('100%').height(30)Scroll() {Column() {Image(this.compressedImg).objectFit(ImageFit.Contain)}.width('90%').padding(10)}.edgeEffect(EdgeEffect.Spring)}.height('100%').width('100%')
6. 权限申请

在页面生命周期aboutToAppear函数时,调用权限申请,并获取图库数据。

const PERMISSIONS: Array<Permissions> = ['ohos.permission.READ_MEDIA','ohos.permission.WRITE_MEDIA','ohos.permission.MEDIA_LOCATION','ohos.permission.MANAGE_MISSIONS','ohos.permission.WRITE_IMAGEVIDEO'
];
async aboutToAppear() {await abilityAccessCtrl.createAtManager().requestPermissionsFromUser(getContext(this), PERMISSIONS);this.imgData = await this.imageModel.getAllImg();}

总结

​ 本案例参考 图片压缩方案 在packing方式压缩图片时,使用二分查找最接近指定图片压缩目标大小的图片质量quality来压缩图片,提升查找性能。

相关权限

读取图片及视频权限:ohos.permission.READ_IMAGEVIDEO

修改图片或视频权限:ohos.permission.WRITE_IMAGEVIDEO

约束与限制

1.本示例仅支持标准系统上运行,支持设备:华为手机。

2.HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上。

3.DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上。

4.HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/816856.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记上报信息格式异常问题的解决

本文记述遇到字符串中包含无效字符时的格式化异常问题的全过程。本文记述解决上报信息格式异常问题的全过程。 问题描述 生产环境监控上报无法解析终端信息,通过日志发现是PCN字段前面缺失#号,导致解析程序解析失败。正常情况下,应该展示如下内容:HD1234#PCN1234发生错误情…

云存储图片生成缩略图开发

作者:狼哥 团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁…

PHP简介与开发环境搭建

PHP简介与开发环境搭建 一、PHP简介 PHP,全称PHP: Hypertext Preprocessor(超文本预处理器),是一种广泛使用的开源服务器端脚本语言,尤其适合Web开发。PHP由Rasmus Lerdorf在1994年创建,最初是为了维护个人网页而制作的简单程序,后来逐渐发展成为功能强大的脚本语言。PH…

2153: 【例8.3】计算球的体积 球的体积公式

include <bits/stdc++.h> using namespace std; double r, pi=3.14; int main( ) { cin >> r; cout << fixed << setprecision(2)<< 4.0/3.0pirrr; return 0; } 球体是一个半圆绕直径所在直线旋转一周所成的空间几何体,简称球。球体是有且只有一…

深入理解浮点数的运算

浮点数的运算步骤 浮点数的加减运算一般由以下五个步骤完成:对阶、尾数运算、规格化、舍入处理、溢出判断 所谓对阶是指将两个进行运算的浮点数的阶码对齐的操作。对阶的目的是为使两个浮点数的尾数能够进行加减运算。因为,当进行 $ M_{x} \times 2^{E_{x}}$与 $ M_{y} \time…

轻松上手-识图文字朗读

作者:狼哥 团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁…

mysql语法-DMLDQL

1.DML操作数据——添加、修改、删除 (1)添加数据:实例(2)修改数据实例注意:修改时如果update语句不加where条件,则会把表中所有数据都修改了! (3)删除数据:实例2.DQL查询 查询语法(1)基础查询:实例(2)条件查询:

免费使用AI写作助手,为你轻松打造爆款文章

在当今内容为王的时代,一篇高质量的文章能够迅速抓住读者的眼球,提升个人或品牌的曝光度。但对于许多创作者而言,灵感枯竭和写作效率低下是常见的挑战。此时,免费AI写作助手的出现,为解决这些问题提供了新的可能性。以下是这款AI写作助手的独特魅力和使用指南。一、AI写作…

从组合优化问题建模到贪心法求解以简单调度为例

此为课题组所指导本科生和低年级硕士生学习组合优化问题汇报 所用教材:北京大学屈婉玲教授《算法设计与分析》 课程资料:https://www.icourse163.org/course/PKU-1002525003 承诺不用于任何商业用途,仅用于学术交流和分享更多内容请关注课题组官方中文主页:https://JaywayX…

python: invalid value encountered in divide以及invalid value encountered in double_scalars报错

运行命令python eqtl_prepare_expression.py data.tpm.gct data.reads_count.gct --tpm_threshold 0.1 --count_threshold 2 --sample_frac_threshold 0.2 --normalization_method tmm --output data.txt时出现了报错“invalid value encountered in divide”以及“invalid val…

java报错大合集

​D:\代码\Mybatis-84\src\test\java\com\lu\TestNews.java:100:39 java: 找不到符号符号: 方法 of(int,int)位置: 接口 java.util.List解决idea中的jdk变成1..8了而List.of()是9出的所有报错,改回17 在“class java.lang.String”中没有名为“name”的属性的 getter纯属粗心…

DataDream:调一调更好,基于LoRA微调SD的训练集合成新方案 | ECCV24

尽管文本到图像的扩散模型已被证明在图像合成方面达到了最先进的结果,但它们尚未证明在下游应用中的有效性。先前的研究提出了在有限的真实数据访问下为图像分类器训练生成数据的方法。然而,这些方法在生成内部分布图像或描绘细粒度特征方面存在困难,从而阻碍了在合成数据集…