HarmonyOS 水印添加能力
项目地址:https://gitcode.com/harmonyos_samples/watermark 技术栈:HarmonyOS / ArkTS / ArkUI
项目简介
在移动应用开发中,水印功能是一个常见且实用的需求。无论是保护版权、标识来源,还是防止信息泄露,水印都扮演着重要角色。本项目是 HarmonyOS 官方提供的水印添加能力示例,全面展示了在 HarmonyOS 生态中实现水印功能的多种技术方案。
该项目涵盖了四大核心场景:页面背景水印、图片水印、拍照水印和 PDF 水印。通过 Canvas 绘制、OffscreenCanvas 离屏渲染、PDFKit 服务等技术,为开发者提供了一套完整的水印解决方案。项目代码结构清晰,注释完善,非常适合作为 HarmonyOS 水印开发的参考实现。
功能概览
- 页面背景水印(Stack 方式):使用 Stack 层叠布局将水印组件与页面内容融合
- 页面背景水印(Overlay 方式):使用 overlay 浮层属性实现水印覆盖
- 保存图片添加水印:对本地图片进行水印处理并保存到系统图库
- 拍照图片添加水印:调用系统相机拍照后自动添加水印
- PDF 添加水印:使用 PDFKit 服务为 PDF 文档添加文字水印

上图展示了应用的首页,包含页面背景、图片、文件三大分类的水印功能入口。
工程结构
entry/src/main/ets/
├── component
│ ├── NavBar.ets // 顶部导航条组件
│ └── Watermark.ets // 页面水印核心组件
├── constants
│ ├── Utils.ets // 工具类:水印绘制、图片保存
│ └── Constants.ets // 公共常量类
├── entryability
│ └── EntryAbility.ets // 程序入口类
└── pages
├── Index.ets // 首页:功能导航
├── CameraPage.ets // 拍照添加水印
├── SaveImagePage.ets // 保存图片添加水印
├── WatermarkPdfPage.ets // PDF 文件添加水印
├── WatermarkStackPage.ets // Stack 方式页面水印
└── WatermarkOverlayPage.ets // Overlay 方式页面水印核心实现解析
模块一:Canvas 水印组件(Watermark.ets)
水印组件是整个项目的核心,它基于 ArkUI 的 Canvas 组件实现,通过 2D 绘图上下文在画布上绘制重复的文字水印。
@Component
export struct Watermark {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
// 可配置的水印属性
@Prop watermarkWidth: number = 120; // 水印单元宽度
@Prop watermarkHeight: number = 120; // 水印单元高度
@Prop watermarkText: string = '水印'; // 水印文字内容
@Prop rotationAngle: number = -30; // 旋转角度(度)
@Prop fillColor: string = '#10000000'; // 填充颜色(带透明度)
@Prop font: string = '16vp'; // 字体大小
draw() {
this.context.fillStyle = this.fillColor;
this.context.font = this.font;
// 计算需要绘制的行列数
const colCount = Math.ceil(this.context.width / this.watermarkWidth);
const rowCount = Math.ceil(this.context.height / this.watermarkHeight);
// 双重循环绘制网格状水印
for (let col = 0; col <= colCount; col++) {
let row = 0;
for (; row <= rowCount; row++) {
const angle = this.rotationAngle * Math.PI / 180;
this.context.rotate(angle);
// 根据旋转角度计算偏移位置
const positionX = this.rotationAngle > 0 ? this.watermarkHeight * Math.tan(angle) : 0;
const positionY = this.rotationAngle > 0 ? 0 : this.watermarkWidth * Math.tan(-angle);
this.context.fillText(this.watermarkText, positionX, positionY);
this.context.rotate(-angle);
this.context.translate(0, this.watermarkHeight);
}
// 换列绘制
this.context.translate(0, -this.watermarkHeight * row);
this.context.translate(this.watermarkWidth, 0);
}
}
build() {
Canvas(this.context)
.width('100%')
.height('100%')
.hitTestBehavior(HitTestMode.Transparent) // 透明穿透,不影响下层交互
.onReady(() => this.draw())
}
}技术要点解析:
- CanvasRenderingContext2D:使用 HarmonyOS 提供的 2D 绘图上下文,支持文字绘制、旋转、平移等操作
- hitTestBehavior(HitTestMode.Transparent):设置透明穿透,确保水印层不会拦截下层组件的触摸事件
- 网格绘制算法:通过双重循环计算行列位置,配合
translate变换实现满屏水印效果 - 旋转计算:将角度转换为弧度,使用三角函数计算旋转后的位置偏移
模块二:Stack 层叠布局水印(WatermarkStackPage.ets)
Stack 方式通过层叠布局将水印组件覆盖在页面内容之上,是最直观的页面水印实现方式。
@Entry
@Component
struct CanvasPage {
@State angle: number = 0;
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
NavBar()
Stack({ alignContent: Alignment.Center }) {
Column() {
Image($r('app.media.empty'))
.width(110)
.height(88)
.rotate({ angle: this.angle })
.onClick(() => {
this.angle = this.angle === 0 ? 180 : 0;
})
}
Watermark({ rotationAngle: 20 }) // 水印层覆盖在内容之上
}
.layoutWeight(1)
.width('100%')
}
.width('100%')
.height('100%')
}
}
上图展示了使用 Stack 组件添加的页面水印效果,水印以 20 度倾斜角均匀分布在页面上。
模块三:Overlay 浮层水印(WatermarkOverlayPage.ets)
Overlay 方式使用 ArkUI 的 overlay 属性,通过 @Builder 构建水印浮层,实现更灵活的水印覆盖。
@Entry
@Component
struct OverlayPage {
@State angle: number = 0;
// 使用 @Builder 构建水印浮层
@Builder
watermarkBuilder() {
Column() {
Watermark()
}
.hitTestBehavior(HitTestMode.Transparent)
}
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
NavBar()
Column() {
Image($r('app.media.empty'))
.width(110)
.height(88)
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.layoutWeight(1)
.overlay(this.watermarkBuilder()) // 通过 overlay 属性挂载水印
}
.width('100%')
.height('100%')
}
}
上图展示了使用 overlay 属性添加的页面水印效果,与 Stack 方式相比,overlay 更加轻量且易于控制层级。
Stack vs Overlay 对比:
| 特性 | Stack 方式 | Overlay 方式 |
|---|---|---|
| 实现复杂度 | 简单 | 中等 |
| 层级控制 | 通过子组件顺序 | 通过 overlay 属性 |
| 灵活性 | 适合固定水印 | 适合动态水印 |
| 性能 | 良好 | 更优(无需额外容器) |
模块四:图片水印处理(SaveImagePage.ets + Utils.ets)
图片水印是项目中技术最复杂的部分,涉及图片解码、PixelMap 操作、离屏渲染和文件保存等多个环节。
核心工具函数 addWatermark:
export function addWatermark(
imagePixelMap: ImagePixelMap,
text: string = 'watermark',
drawWatermark?: (OffscreenContext: OffscreenCanvasRenderingContext2D) => void
): image.PixelMap {
// 将像素尺寸转换为 vp 单位
const height = uiContext?.px2vp(imagePixelMap.height) as number;
const width = uiContext?.px2vp(imagePixelMap.width) as number;
// 创建离屏画布
const offScreenCanvas = new OffscreenCanvas(width, height);
const offScreenContext = offScreenCanvas.getContext('2d');
// 绘制原始图片
offScreenContext.drawImage(imagePixelMap.pixelMap, 0, 0, width, height);
// 绘制水印(支持自定义绘制逻辑)
if (drawWatermark) {
drawWatermark(offScreenContext);
} else {
// 默认水印样式:右下角半透明文字
const displayWidth = display.getDefaultDisplaySync().width;
const vpWidth = uiContext?.px2vp(displayWidth) ?? displayWidth;
const imageScale = width / vpWidth;
offScreenContext.textAlign = 'right';
offScreenContext.fillStyle = '#A2FFFFFF'; // 半透明白色
offScreenContext.font = 12 * imageScale + 'vp';
const padding = 5 * imageScale;
offScreenContext.fillText(text, width - padding, height - padding);
}
// 返回带水印的 PixelMap
return offScreenContext.getPixelMap(0, 0, width, height);
}图片保存流程:
export async function saveToFile(pixelMap: image.PixelMap, context: Context): Promise<void> {
// 1. 获取图库访问助手
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
// 2. 创建图片资源
const filePath = await phAccessHelper.createAsset(
photoAccessHelper.PhotoType.IMAGE,
'png'
);
// 3. 使用 ImagePacker 编码为 PNG
const imagePacker = image.createImagePacker();
const imageBuffer = await imagePacker.packToData(pixelMap, {
format: 'image/png',
quality: 100
});
// 4. 写入文件
const mode = fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE;
const fd = (await fileIo.open(filePath, mode)).fd;
await fileIo.truncate(fd);
await fileIo.write(fd, imageBuffer);
fileIo.close(fd);
}
上图展示了原始图片页面,底部有"添加水印"按钮。

上图展示了添加水印后的效果,右下角出现了半透明的"水印水印水印"文字。点击保存按钮可将带水印图片存入系统图库。
技术要点解析:
- OffscreenCanvas:离屏画布在后台进行绘制操作,不会阻塞主线程,适合图片处理等耗时操作
- PixelMap:HarmonyOS 的图片数据载体,支持像素级操作
- ImagePacker:图片编码器,支持 PNG、JPEG 等格式输出
- photoAccessHelper:系统图库访问助手,用于保存图片到相册
- px2vp 转换:将像素单位转换为 vp(虚拟像素),确保在不同分辨率设备上显示一致
模块五:PDF 水印(WatermarkPdfPage.ets)
PDF 水印使用 HarmonyOS 提供的 PDFKit 服务实现,支持文字水印的添加、预览和保存。
// 配置水印信息
getWatermarkInfo() {
const watermarkInfo: pdfService.TextWatermarkInfo = new pdfService.TextWatermarkInfo();
watermarkInfo.watermarkType = pdfService.WatermarkType.WATERMARK_TEXT;
watermarkInfo.content = 'This is Watermark';
watermarkInfo.textSize = 32;
watermarkInfo.textColor = 200; // 灰色
watermarkInfo.rotation = 45; // 45度旋转
watermarkInfo.opacity = 0.3; // 30% 透明度
return watermarkInfo;
}
// 添加水印到 PDF
addWatermark() {
const filePath = this.getPdfSandboxPath();
let pdfDocument: pdfService.PdfDocument = new pdfService.PdfDocument();
pdfDocument.loadDocument(filePath);
// 为所有页面添加水印
pdfDocument.addWatermark(
this.getWatermarkInfo(),
0, // 起始页
pdfDocument.getPageCount(), // 结束页
true, // 前景
true // 覆盖所有页面
);
// 保存带水印的 PDF
const watermarkFilePath = this.getAddedWatermarkPdfSandboxPath();
pdfDocument.saveDocument(watermarkFilePath);
this.showInPdfView(watermarkFilePath);
}
上图展示了 PDF 预览页面,底部有"添加水印"按钮。

上图展示了添加水印后的 PDF 效果,页面上出现了倾斜的半透明文字水印。
技术要点解析:
- PdfView 组件:HarmonyOS 提供的 PDF 预览组件,支持页面缩放、滚动等交互
- PdfController:PDF 控制器,负责文档加载、页面跳转、释放等操作
- PdfDocument:PDF 文档对象,提供添加水印、合并、拆分等操作
- TextWatermarkInfo:文字水印配置对象,支持内容、大小、颜色、旋转、透明度等属性
- 沙箱路径:PDF 文件先存入应用沙箱,处理后再通过文件选择器保存到用户指定位置
运行效果
项目提供了完整的运行效果展示,以下是各功能模块的实际截图:
| 功能 | 截图 |
|---|---|
| 首页导航 | ![]() |
| Stack 页面水印 | ![]() |
| Overlay 页面水印 | ![]() |
| 图片水印(添加前) | ![]() |
| 图片水印(添加后) | ![]() |
| PDF 水印(添加前) | ![]() |
| PDF 水印(添加后) | ![]() |
动态效果展示
项目还提供了水印效果的动态演示:

上图展示了页面水印的动态效果,可以看到水印以倾斜角度均匀分布在页面上。
关键技术总结
1. Canvas 2D 绘图
HarmonyOS 提供了完整的 Canvas 2D 绘图能力,支持:
- 文字绘制(
fillText、strokeText) - 几何变换(
rotate、translate、scale) - 样式设置(
fillStyle、font、textAlign) - 离屏渲染(
OffscreenCanvas)
2. 图片处理流程
图片水印的处理流程为:
图片资源 → ImageSource → PixelMap → OffscreenCanvas 绘制 → PixelMap → ImagePacker 编码 → 文件保存3. PDF 水印流程
PDF 水印的处理流程为:
PDF 资源 → 沙箱存储 → PdfDocument 加载 → addWatermark → 沙箱保存 → PdfView 预览 → 用户保存4. 权限说明
本项目涉及以下系统权限:
ohos.permission.WRITE_IMAGEVIDEO:保存图片到系统图库(需要动态申请)
总结
本项目作为 HarmonyOS 官方水印能力示例,全面展示了在 HarmonyOS 生态中实现水印功能的多种技术方案。无论是页面水印、图片水印还是 PDF 水印,都提供了完整的实现代码和详细的注释说明。
适用场景:
- 需要为应用添加版权保护水印的开发者
- 学习 HarmonyOS Canvas 绘图和图像处理的初学者
- 需要实现 PDF 文档水印功能的企业应用
学习建议:
- 先理解
Watermark.ets中 Canvas 绘制的核心逻辑 - 对比 Stack 和 Overlay 两种页面水印的实现差异
- 重点学习
Utils.ets中图片处理的完整流程 - 参考 PDF 水印实现,了解 PDFKit 服务的使用方法
通过本项目的学习,开发者可以快速掌握在 HarmonyOS 平台上实现各类水印功能的技术要点,并能够根据实际需求进行扩展和定制。