uni-app 图片压缩 API uni.compressImage 全解析:参数、平台差异与源码实现原理
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
uni.compressImage 是 uni-app(uni-app x)内置的图片压缩 API,用于在 App(Android / iOS / HarmonyOS)与微信小程序端对本地图片执行质量压缩、尺寸缩放与旋转处理,常用于上传前瘦身、缩略图生成等场景。本文以官方文档为骨架,结合本仓库src/uni_modules/uni-media的 UTS 源码与自动化测试,完整讲解该 API 的入参、回调、错误码、平台行为差异以及各端底层实现原理,帮助开发者写出兼容多端、表现可预期的压缩代码。
API 概述与平台兼容性
uni.compressImage(options)是一个全局同步发起、异步回调的 API,传入一个CompressImageOptions对象,压缩完成后通过success回调返回压缩图片的临时文件路径。
从官方文档的兼容性表格看,该 API 在不同平台的支持情况如下:
| Web | 微信小程序 | Android | iOS | HarmonyOS | | :- | :- | :- | :- | :- | | 不支持(x) | 4.41 | 4.18 | 4.25 | 4.61 |
要点:
- Web 端完全不支持该 API(文档中所有参数在 Web 平台均标记为 x),因此文档示例明确提示“该 API 不支持 Web,请运行 hello uni-app x 到 App 平台体验”。
- 微信小程序端支持
quality、compressedWidth、compressedHeight等主要参数,但不支持rotate旋转参数。 - App 三端(Android / iOS / HarmonyOS)均支持全部核心参数,其中 Android 与 iOS 从 4.18 / 4.25 开始提供,HarmonyOS 从 4.61 开始提供。
- 上表中列出的兼容性版本号对应的是 uni-app x 运行时(unixVer)版本;从 uni-media 模块的 interface 定义中的
@uniPlatform注释可以看到,HarmonyOS 端标注为uniVer: 4.31 / unixVer: 4.61,Android 与 iOS 为uniVer: √ / unixVer: 4.18 / 4.25。
参数详解:CompressImageOptions
调用时只需传入一个options对象,其完整属性定义如下(对应 interface.uts 中的 CompressImageOptions):
| 名称 | 类型 | 必备 | 描述 | | :- | :- | :- | :- | | src | string.ImageURIString | 是 | 图片路径,可以是相对路径、临时文件路径、存储文件路径 | | quality | number | 否 | 压缩质量,范围 0~100,数值越小质量越低、压缩率越高(仅对 jpg 有效) | | rotate | number | 否 | 旋转度数,范围 0~360(微信小程序不支持) | | compressedHeight | number | 否 | 压缩后图片的高度,单位 px;不填则以 compressedWidth 为准等比缩放 | | compressedWidth | number | 否 | 压缩后图片的宽度,单位 px;不填则以 compressedHeight 为准等比缩放 | | success | (res: CompressImageSuccess) => void | 否 | 接口调用成功的回调函数 | | fail | (err: CompressImageFail) => void | 否 | 接口调用失败的回调函数 | | complete | (res: any) => void | 否 | 接口调用结束的回调函数(成功、失败都会执行) | |width| string | 否 | 缩放图片的宽度(已废弃) | |height| string | 否 | 缩放图片的高度(已废弃) |
需要特别说明的几点:
- src 是唯一必填参数。它可以是相对路径、chooseImage 返回的临时文件路径,或存储文件路径。在 protocol.uts 的 CompressImageApiProtocol 中,只有
src被标记为required: true。 - quality 有默认值 80。虽然在文档表格中 quality 标为选填,但查看 protocol.uts 的 formatArgs 处理:当
quality为空时会被赋值为80。这与示例代码中const quality = ref(80)的默认输入框取值一致。 - src 传入后会自动经过
getRealPath归一化,将相对路径、协议路径转换为各端可识别的真实路径(见 protocol.uts),因此调用方无需手动拼接路径。 - 废弃参数 width / height:旧版本中的
width、height(字符串类型)已废弃,请改用compressedWidth/compressedHeight(number 类型)。在 Android 实现中仍保留了向后兼容逻辑——当compressedWidth为空时回退读取废弃的width字段,取不到则使用"auto"(见 CompressUtils.uts)。 - 宽高等比规则:只传
compressedWidth时,高度按原图宽高比自动计算;只传compressedHeight时反之;两者都传则按指定尺寸缩放;都不传则仅按 quality 做质量压缩。
回调对象与错误码
CompressImageSuccess
成功回调返回的res对象只有一个核心字段(对应 interface.uts 的 CompressImageSuccess):
| 名称 | 类型 | 描述 | | :- | :- | :- | | tempFilePath | string | 压缩后图片的临时文件路径 |
拿到tempFilePath后可直接用于<image>展示、uni.uploadFile上传或uni.saveImageToPhotosAlbum保存。
CompressImageFail
失败回调返回的err对象(对应 interface.uts 的 IMediaError,它继承自统一错误类型 UniError):
| 名称 | 类型 | 描述 | | :- | :- | :- | | errCode | number | 错误码 | | errSubject | string | 统一错误主题(模块)名称 | | data | any | 错误信息中包含的数据 | | cause | Error | 源错误信息,可包含多个错误(见 SourceError) | | errMsg | string | 错误信息 |
errCode 合法值
错误码在 interface.uts 的 MediaErrorCode 类型中定义,与文档完全一致:
| 合法值 | 描述 | | :- | :- | | 1101001 | 用户取消 | | 1101002 | urls 至少包含一张图片地址 | | 1101003 | 文件不存在 | | 1101004 | 图片加载失败 | | 1101005 | 未获取权限 | | 1101006 | 图片或视频保存失败 | | 1101007 | 图片裁剪失败 | | 1101008 | 拍照或录像失败 | | 1101009 | 图片压缩失败 | | 1101010 | 其他错误 |
其中与图片压缩最相关的是1101009(图片压缩失败)与1101003(文件不存在):从 Android 源码 CompressUtils.uts 可以看到,src 为空或文件不存在时抛出 1101003,Bitmap 解码失败或写入失败时抛出 1101009。
完整示例:压缩前/后信息对比页面
官方文档给出的示例对应仓库中的 compress-image.uvue 页面,完整流程为:从相册选择图片 → 展示压缩前宽高与大小 → 输入质量、宽高、旋转参数 → 调用 compressImage → 展示压缩后信息。以下为核心代码:
<template> <scroll-view style="flex:1"> <view> <page-head :title="title"></page-head> <view class="uni-padding-wrap"> <view class="image-container"> <image class="image" :src="beforeCompressPath" mode="aspectFit"></image> <image class="image" :src="afterCompressPath" mode="aspectFit"></image> </view> <view class="uni-title"> <text class="uni-subtitle-text">压缩前图片信息</text> </view> <text>{{beforeCompressImageInfo}}</text> <view class="uni-title"> <text class="uni-subtitle-text">压缩后图片信息</text> </view> <text>{{afterCompressImageInfo}}</text> <view class="uni-btn-v"> <button type="primary" @click="chooseImage">从相册中选取待压缩的图片</button> </view> <view class="uni-btn-v"> <button type="primary" @click="compressImage">压缩图片</button> </view> </view> <input-data defaultValue="80" title="压缩质量,范围0~100,数值越小,质量越低,压缩率越高(仅对jpg有效)" type="number" @confirm="onQualityConfirm"></input-data> <input-data defaultValue="" title="压缩后图片的宽度,单位px" type="string" @confirm="onCompressedWidthConfirm"></input-data> <input-data defaultValue="" title="压缩后图片的高度,单位px" type="string" @confirm="onCompressedHeightConfirm"></input-data> <input-data defaultValue="0" title="旋转度数,范围0~360" type="number" @confirm="onRotateConfirm"></input-data> </view> </scroll-view> </template> <script setup lang="uts"> type DataType = { imageInfoForTest: UTSJSONObject | null, imageSrcForTest: string, compressedWidth: number | null, compressedHeight: number | null } const title = ref("compressImage") const beforeCompressImageInfo = ref("") const afterCompressImageInfo = ref("") const beforeCompressPath = ref("") const afterCompressPath = ref("") const quality = ref(80) const rotate = ref(0) const compressImage = () => { if (beforeCompressPath.value == "") { uni.showToast({ title: "请先选择图片", icon: "error" }); return; } uni.showLoading({ title: "图片压缩中" }); uni.compressImage({ src: beforeCompressPath.value, quality: quality.value, compressedWidth: data.compressedWidth, compressedHeight: data.compressedHeight, rotate: rotate.value, success: (res) => { console.log("compressImage success", JSON.stringify(res)); afterCompressPath.value = res.tempFilePath; uni.showToast({ title: "压缩成功", icon: null }); uni.getImageInfo({ src: res.tempFilePath, success: (_res) => { afterCompressImageInfo.value = `图片宽度: ${_res.width}\n图片高度: ${_res.height}\n`; // #ifdef APP-HARMONY || APP-ANDROID const fsm = uni.getFileSystemManager() fsm.getFileInfo({ filePath: res.tempFilePath, digestAlgorithm: null, success: (res) => { afterCompressImageInfo.value = afterCompressImageInfo.value.concat(`图片大小: ${res.size}KB`); } }) // #endif } }); }, fail: (err) => { uni.showModal({ title: "压缩图片失败", content: JSON.stringify(err), showCancel: false }); }, complete: (_) => { uni.hideLoading(); } }); } const chooseImage = () => { uni.chooseImage({ count: 1, sizeType: ["original"], // #ifdef APP-ANDROID albumMode: "system", // #endif sourceType: ["album"], success: (res) => { beforeCompressPath.value = res.tempFilePaths[0]; uni.getImageInfo({ src: res.tempFilePaths[0], success: (_res) => { beforeCompressImageInfo.value = `图片宽度: ${_res.width}\n图片高度: ${_res.height}\n`; // #ifdef APP-HARMONY || APP-ANDROID const fsm = uni.getFileSystemManager() fsm.getFileInfo({ filePath: res.tempFilePaths[0], digestAlgorithm: null, success: (res) => { beforeCompressImageInfo.value = beforeCompressImageInfo.value.concat(`图片大小: ${res.size}KB`); }, fail: (err) => { console.log(err); } }) // #endif } }); } }); } </script>示例中的关键实践值得学习:
- 选图使用
sizeType: ["original"],确保压缩前拿到的是原图而非系统已压缩图,便于对比压缩效果;Android 端额外传入albumMode: "system"使用系统相册选择器。 - 压缩前先
showLoading,complete中hideLoading,保证 loading 提示一定会关闭。 - 失败时用
showModal展示JSON.stringify(err),便于在真机上定位错误码。 - 压缩成功后用
uni.getImageInfo读取宽高、用uni.getFileSystemManager().getFileInfo读取文件大小(App 端),形成“压缩前后对比”的验证闭环。 input-data是 hello uni-app x 示例工程中的通用输入组件,质量默认 80、旋转默认 0,与 API 默认行为一致。
平台差异与使用限制(Tips)
这是使用 compressImage 时最需要关注的部分,官方文档以 Tips 形式给出了明确的平台行为差异:
- quality 属性的生效范围:
- Android、iOS 平台:仅对 JPG 格式图片生效,非 JPG 格式图片的 quality 属性始终视为 100(即不进行有损压缩);
- HarmonyOS 平台:对 JPG、HEIF 格式生效,其他格式视为 100。
- 支持的压缩格式:
- Android 平台仅支持对 JPG 格式图片进行压缩,其他格式会被转为 JPG 输出。如果对带透明度的 PNG 压缩成 JPG,会丢失透明度变成黑色;
- iOS 平台支持对 JPG 和 PNG 两种格式进行压缩;
- HarmonyOS 平台支持 JPG(透明色将变为黑色)、HEIF、PNG(转为 JPG,透明色将变为黑色)。
- 因此,对于需要保留透明通道的场景(如 logo、贴纸类图片),在 Android / HarmonyOS 端使用本 API 会得到黑底图片,应改用其它处理方案或在设计上规避透明底图。
源码级原理:三个 App 端如何实现压缩
uni.compressImage 在 uni-app x 中由内置插件 uni-media 模块承载,各端实现方式差异明显,理解底层有助于预判压缩结果。
统一入口与参数协议
所有平台共享 interface.uts 中声明的compressImage(options: CompressImageOptions)类型签名,以及 protocol.uts 中定义的参数协议与默认值处理。参数协议负责两件事:
src经getRealPath转换为真实路径;quality缺省时补默认值 80。
Android:Bitmap + Matrix + JPEG 编码
Android 端入口在 app-android/index.uts,直接委托给 CompressUtils.uts 的 transcodeImage 执行,其关键流程为:
- 校验 src 非空且文件存在(支持
content://与/android_asset/前缀),否则返回错误码 1101003; - 通过
BitmapFactory.decodeFile / decodeStream解码;当图片大小超过 1.5MB 时设置inSampleSize = 2进行 2 倍采样降载,避免大图 OOM; - 若指定了压缩尺寸,用
Matrix.setScale按目标宽高缩放(见 CompressUtils.uts); - 若
rotate > 0,用Matrix.postRotate旋转; - 统一以
Bitmap.CompressFormat.JPEG编码写入uni-media/缓存目录下的.jpg文件,并保存 EXIF 信息;成功后返回file://前缀的临时路径(见 CompressUtils.uts)。
这从源码层面印证了文档 Tips:Android 端输出恒为 JPG,故 PNG 透明度必然丢失。
HarmonyOS:ImageKit ImagePacker
HarmonyOS 端实现在 app-harmony/media/compressImage.uts,基于@kit.ImageKit的ImagePacker:
- 先根据扩展名判断是否支持压缩,支持列表为
jpg / jpe / jpeg / png / webp / heif,不支持的格式直接原样返回,不压缩(见 compressImage.uts); - 支持格式则创建
ImageSource,通过DecodingOptions设置rotate与目标尺寸desiredSize; - 用
ImagePacker.packToFile按指定格式(jpeg / webp / heif)与quality(默认 80)打包输出到缓存目录uni-media/; - 一个细节是:若压缩后文件反而大于原图(如 PNG 转 JPG 后的边界情况),会触发
compressedImage兜底逻辑再次处理,确保输出体积不劣化(见 compressImage.uts)。
iOS:原生框架 DCloudMediaImageCompress
iOS 端通过 app-ios/index.uts 调用原生框架DCloudMediaPicker.xcframework中的DCloudMediaImageCompress能力完成压缩,其头文件定义在 DCloudMediaImageCompress.h。iOS 原生图像框架天然支持 JPG 与 PNG 两种格式输出,与文档 Tips 描述一致。
自动化测试:压缩结果的可验证性
仓库为 compressImage 提供了端到端自动化测试 compress-image.test.js:
describe('API-compressImage', () => { // Web / iOS / 小程序平台直接跳过 it('test compressImage', async () => { const page = await program.reLaunch('/pages/API/compress-image/compress-image'); await page.waitFor('view'); await page.setData({ data: { compressedWidth: 100 } }) await page.callMethod('testCompressImage'); await page.waitFor(1000); expect(await page.data('data.imageInfoForTest')).toEqual({ width: 100, height: 100, isSizeReduce: true }); }); });测试逻辑与页面中的testCompressImage方法对应(见 compress-image.uvue 中的testCompressImage):使用工程内置的 logo.png(192x192)作为测试素材,将压缩宽高固定为 100x100,断言压缩结果宽高均为 100、且文件体积确实缩小(isSizeReduce: true)。这同时验证了三件事:压缩尺寸参数生效、等比缩放逻辑正确、输出文件确实小于原图。
常见问题与最佳实践
基于文档与源码,总结以下实战建议:
- 上传前压缩的正确姿势:
uni.chooseImage拿到tempFilePaths后直接作为src传入,无需额外转换路径;质量建议从 80 起步调试,在“肉眼无差别”与“体积最小”之间取平衡。 - 不要对透明 PNG 走 Android / HarmonyOS 压缩:会变黑底。可先判断图片格式(
uni.getImageInfo拿到 path 后缀),只有 JPG 才走压缩流程,或接受转码结果。 - rotate 参数仅 App 端可用,微信小程序端如需旋转应结合
uni.getImageInfo的 orientation 信息自行处理。 - 利用 complete 统一收尾:无论 success 还是 fail,
complete都会触发,适合放置 loading 关闭等收尾逻辑。 - 结合文件系统 API 验证效果:压缩后可用
uni.getFileSystemManager().getFileInfo读取实际体积,判断是否达到预期压缩率;HarmonyOS 端在压缩后体积不降时会自动回退为原图,因此不要假设输出一定小于输入。 - 压缩结果路径是临时文件:
tempFilePath位于各端缓存目录(如 Android 的appCachePath/uni-media/),如需长期保存应复制到存储目录,避免被系统清理。
总结
uni.compressImage 是一个参数简洁但平台差异显著的多端图片压缩 API:统一入参CompressImageOptions由 protocol.uts 收敛默认值与路径归一化,Android 端基于 Bitmap/JPEG 编码、HarmonyOS 端基于 ImageKit ImagePacker、iOS 端基于原生 DCloudMediaImageCompress 各自落地。开发时重点记住三件事:quality 只对 JPG 类格式生效、Android/HarmonyOS 输出为 JPG 会丢失 PNG 透明度、Web 端不支持需做条件编译降级。参考 compress-image.uvue 的完整示例与 compress-image.test.js 的验证思路,即可在自己的项目中稳定落地“选图 → 压缩 → 上传”的完整链路。
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考