最可靠方式是用 uni.createCanvasContext 绘制水印后导出,需处理图片加载、尺寸适配和跨端路径转换;drawImage 必须用九参数控制源图裁剪缩放;网络图须转本地临时路径;保存应使用用户文件目录而非临时路径;水印字体推荐 'bold 14px Arial' 并动态测量。

直接用 uni.createCanvasContext 绘制水印再导出图片是最可靠的方式,但必须处理好图片加载、尺寸适配、跨端临时路径转换这三步,否则在小程序或 App 端会导出空白或裁剪异常。
canvas.drawImage 的宽高参数不能照搬原始图片尺寸
很多同学直接用 getImageInfo 返回的 width/height 填进 drawImage(src, 0, 0, w, h),结果水印被拉伸、偏移或只显示左上角一小块。这是因为:
- 部分安卓机型(如红米9)拍摄的图片方向信息未被 canvas 自动识别,
width/height实际是旋转后的逻辑尺寸 - 微信小程序 canvas 对宽高超过 2048px 的图片会自动缩放,导致绘制失真
- H5 端 canvas 默认分辨率是设备像素比(dpr)下的物理尺寸,不缩放会模糊
正确做法是:先按目标显示区域确定 canvas 宽高(比如固定为 750rpx × 1000rpx),再用 drawImage 的九参数重载控制源图裁剪和缩放:
context.drawImage( src, 0, 0, originalWidth, originalHeight, // 源图区域 0, 0, canvasWidth, canvasHeight // 目标画布区域 )
这样能确保源图完整铺满 canvas,水印坐标计算也基于这个统一画布空间。
网络图片必须转成本地临时路径才能 drawImage
直接传 HTTP 地址给 drawImage 在微信/支付宝小程序里会静默失败,H5 和 App 端也不稳定。错误现象是 canvas 内容为空,canvasToTempFilePath 导出黑图或白图。
必须用 uni.getImageInfo 预加载并获取 path 字段:
-
path是平台生成的本地临时路径,仅本次会话有效 - 如果图片来自 OSS 或 CDN,建议加缓存参数(如
?t=1725376680)避免 304 缓存干扰 - 视频封面需额外处理:阿里云 OSS 路径要拼
x-oss-process=video/snapshot,腾讯云用?ci-process=snapshot
示例中 preloadMediaImages 就是干这个的,它返回的是已校验可用的本地路径数组,不是原始 URL。
保存时别用临时路径,优先选用户文件目录
canvasToTempFilePath 返回的仍是临时路径,App 或小程序退出后可能被系统回收。想长期保留水印图,得立刻用 uni.saveFile 或 uni.getFileSystemManager().writeFile 存到用户目录。
- 小程序推荐
uni.saveFile:生成带时间戳的文件名,自动存入“我的文件”可见目录,用户可手动管理 - App 端(Android/iOS)用
uni.getFileSystemManager().writeFile更可控,可指定子目录如myapp/watermark/ - 千万别把
tempFilePath直接传给后端上传——它在小程序里 24 小时失效,在 App 里可能几小时就 404
保存成功后,返回的 savedFilePath 才是真正可分享、可上传、可预览的持久化路径。
最易忽略的一点:水印文字的 font 设置在不同平台渲染效果差异极大。iOS 上 '12px sans-serif' 可能极细看不清,安卓某些 ROM 会默认替换字体导致宽度计算偏差。建议固定用 'bold 14px Arial' 并配合 ctx.measureText 动态算间距,别依赖 CSS 字体继承。











