鸿蒙5.0开发【水印添加开发】实战案例

概述

在软件开发中,水印是一种在应用页面、图片或文档中嵌入的标记,它通常采用文字或图案的形式展现。水印通常有以下用途:

  • 标识来源:可用于标识应用、各种文件的来源或作者,确保产权的归属。
  • 版权保护:可携带版权保护信息,有效防止他人篡改、盗用、非法复制。
  • 艺术效果:可作为一种艺术效果,为图片或应用增添独特的风格。

本文通过图文与代码结合的方式,对以下几种常见的水印添加场景进行讲解,旨在让开发者理解水印添加的基本原理以及掌握开发的流程与细节。

  • [页面上添加水印]
  • [图片上添加水印]
  • [PDF文档添加水印]

页面上添加水印

场景描述

某个页面背景上添加水印文字,实现效果图如下。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现原理

关键技术

[Canvas]提供画布组件,用于自定义绘制图形。使用[CanvasRenderingContext2D]对象在[Canvas]组件上进行绘制,其中[fillText()]方法用于绘制文本,[drawImage()]方法用于图像绘制。

开发流程

  1. 创建[Canvas]画布,在画布上绘制水印。
  2. 使用[Stack]组件或[浮层overlay]属性,将画布与UI页面组件融合显示。

开发步骤

  1. 封装水印组件

  2. 创建Canvas组件,监听[Canvas.onReady]事件,该事件回调在Canvas组件初始化完成时或大小变化时执行,在回调中进行水印绘制draw()方法的执行。并通过设置Canvas组件的[hitTestBehavior]属性,使水印组件不影响其他组件的触摸测试,让页面能正常交互。

// entry/src/main/ets/component/Watermark.ets
@Component
export struct Watermark {
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  // ...
  build() {
    Canvas(this.context)
      .width('100%')
      .height('100%')
      .hitTestBehavior(HitTestMode.Transparent)
      .onReady(() => this.draw())
  }
}
  1. 实现绘制水印draw()方法。绘制的起点默认为坐标轴的原点(画布的左上角),通过坐标轴的平移及旋转,实现在画布的不同位置、不同角度绘制水印。如果水印有一定旋转角度,想保证第一个水印能完整显示,需要对绘制的起点做平移,平移距离通过旋转角度及水印宽高计算。
  • 旋转角度大于0,由下图可知,水印沿x轴方向平移距离positionX = tan(θ) * 水印宽度,即绘制起点为(positionX, 0)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 旋转角度小于0,由下图可知,水印沿y轴方向平移距离positionY = tan(θ) * 水印高度,即绘制起点为(0, positionY)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最终通过[CanvasRenderingContext2D.fillText()]方法进行水印文字的绘制。

// entry/src/main/ets/component/Watermark.ets
@Prop watermarkWidth: number = 120;
@Prop watermarkHeight: number = 120;
@Prop watermarkText: string = this.getWatermarkText();
@Prop rotationAngle: number = -30;
@Prop fillColor: string | number | CanvasGradient | CanvasPattern = '#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);
  }
}
  1. 将水印组件与UI页面组件融合显示。

方式一:使用Stack将水印组件叠加在UI组件上层。

// entry/src/main/ets/pages/WatermarkStackPage.ets
Stack({ alignContent: Alignment.Center }) {
  Column() {
    Image($r('app.media.empty'))
      .width(110)
      .height(88)
  }
  Watermark({ rotationAngle: 20 })
}

方式二:设置UI组件的overlay属性,使水印组件作为UI组件的浮层显示。

// entry/src/main/ets/pages/WatermarkOverlayPage.ets
@Builder
watermarkBuilder() {
  Column() {
    Watermark()
  }
}

build() {
  // ...
    Column() {
      Image($r('app.media.empty'))
        .width(110)
        .height(88)
    }
    .overlay(this.watermarkBuilder())
    // ...
}

说明

如果需要多个页面或应用全局添加水印,可将上述方式二中的watermarkBuilder封装到一个单独的文件,export出一个全局的watermarkBuilder。在需要添加水印页面的根节点上添加.overlay绑定watermarkBuilder即可。

图片上添加水印

场景描述

保存的图片、拍照生成的图片等场景,需要添加水印。实现效果图如下。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现原理

关键技术

[OffscreenCanvas]提供离屏画布,与[Canvas]使用场景区别在于是否需要将画布渲染在屏幕上。使用[OffscreenCanvasRenderingContext2D]在[OffscreenCanvas]上进行离屏绘制,其中[fillText()]方法用于绘制文本,[drawImage()]方法用于图像绘制。

开发流程

  1. 解析图片得到pixelMap数据。
  2. 创建与图片宽高一致的[OffscreenCanvas]离屏画布。
  3. 将图片和水印依次绘制到离屏画布上。
  4. 获取离屏画布的pixelMap数据。
  5. 将pixelMap数据写入文件中。

开发步骤

  1. 解析图片得到pixelMap数据。

  2. 使用[resourceManager.getMediaContent()]方法获取图片内容,得到ArrayBuffer数据。使用[image.createImageSource(buf: ArrayBuffer)]方法创建图片源实例。

// entry/src/main/ets/pages/SaveImagePage.ets
async getImagePixelMap(resource: Resource): Promise<ImagePixelMap> {
  const data: Uint8Array = await getContext(this).resourceManager.getMediaContent(resource);
  const arrayBuffer: ArrayBuffer = data.buffer.slice(data.byteOffset, data.byteLength + data.byteOffset);
  const imageSource: image.ImageSource = image.createImageSource(arrayBuffer);
  return await imageSource2PixelMap(imageSource);
}
  1. 使用[ImageSource.getImageInfo()]方法获取图片宽、高信息,使用[ImageSource.createPixelMap()]方法创建PixelMap对象。
// entry/src/main/ets/constants/Utils.ets
export async function imageSource2PixelMap(imageSource: image.ImageSource): Promise<ImagePixelMap> {
  const imageInfo: image.ImageInfo = await imageSource.getImageInfo();
  const height = imageInfo.size.height;
  const width = imageInfo.size.width;
  const options: image.DecodingOptions = {
    editable: true,
    desiredSize: { height, width }
  };
  const pixelMap: PixelMap = await imageSource.createPixelMap(options);
  const result: ImagePixelMap = { pixelMap, width, height };
  return result;
}
  1. 通过[OffscreenCanvas]离屏画布绘制图片及水印,得到融合水印后的pixelMap数据。

    1. 创建与图片宽高一致的[OffscreenCanvas]离屏画布,这里注意单位保持一致。
    2. 使用[OffscreenCanvasRenderingContext2D.drawImage()]将图片绘制到离屏画布上。
    3. 使用[OffscreenCanvasRenderingContext2D.fillText()]将水印绘制在离屏画布的指定位置。
    4. 使用[OffscreenCanvasRenderingContext2D.getPixelMap()]以当前离屏画布指定区域内的像素创建PixelMap对象。
// entry/src/main/ets/constants/Utils.ets
export function addWatermark(
  imagePixelMap: ImagePixelMap,
  text: string = 'watermark',
  drawWatermark?: (OffscreenContext: OffscreenCanvasRenderingContext2D) => void
): image.PixelMap {
  const height = px2vp(imagePixelMap.height);
  const width = px2vp(imagePixelMap.width);
  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 imageScale = width / px2vp(display.getDefaultDisplaySync().width);
    offScreenContext.textAlign = 'right';
    offScreenContext.fillStyle = '#A2FFFFFF';
    offScreenContext.font = 12 * imageScale + 'vp';
    const padding = 5 * imageScale;
    offScreenContext.fillText(text, width - padding, height - padding);
  }
  return offScreenContext.getPixelMap(0, 0, width, height);
}
  1. 将添加水印后得到的pixelMap数据写入文件中。
// entry/src/main/ets/constants/Utils.ets
export async function saveToFile(pixelMap: image.PixelMap, context: Context): Promise<void> {
  try {
    const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
    const filePath = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png');
    const imagePacker = image.createImagePacker();
    const imageBuffer = await imagePacker.packing(pixelMap, {
      format: 'image/png',
      quality: 100
    });
    const mode = fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE;
    fd = (await fileIo.open(filePath, mode)).fd;
    await fileIo.truncate(fd);
    await fileIo.write(fd, imageBuffer);
  } catch (err) {
    hilog.error(0x0000, TAG, 'saveToFile error:', JSON.stringify(err) ?? '');
  } finally {
    if (fd) {
      fileIo.close(fd);
    }
  }
}

PDF文档添加水印

场景描述

在PDF预览页面点击添加水印按钮,生成带水印的PDF文档,并显示在预览页面中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现原理

关键技术

[pdfService]模块为应用提供统一管理PDF页面的页眉页脚、水印、背景、批注、书签的能力。[pdfService.TextWatermarkInfo]类和[pdfService.ImageWatermarkInfo]分别提供创建文本水印和图片水印的能力。[pdfService.PdfDocument]类提供与文档相关能力,其中[addWatermark()]方法用于添加水印。

开发流程

  1. 将应用侧PDF文件写入沙箱中。
  2. 使用[pdfService]模块相关API加载指定沙箱路径的PDF并添加水印。

开发步骤

  1. 使用[getRawFileContentSync()]方法获取resource/rawfile目录下的PDF文件内容,使用[fs.writeSync()]方法写入沙箱中。
// entry/src/main/ets/pages/WatermarkPdfPage.ets
savePdfToSandbox(): string {
  const filePath = this.getPdfSandboxPath();
  fileIo.accessSync(filePath);
  const content: Uint8Array = getContext().resourceManager.getRawFileContentSync('watermark.pdf');
  const file = fileIo.openSync(filePath, fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE | fileIo.OpenMode.TRUNC);
  fileIo.writeSync(file.fd, content.buffer);
  fileIo.closeSync(file.fd);
  return filePath;
}
  1. 使用[pdfViewManager.PdfController]控制器中的[loadDocument()]方法通过沙箱路径加载文件,显示到PDF预览组件[PdfView]中
// entry/src/main/ets/pages/WatermarkPdfPage.ets
private controller: pdfViewManager.PdfController = new pdfViewManager.PdfController();
// ...
aboutToAppear(): void {
  const filePath = this.savePdfToSandbox();
  this.controller.loadDocument(filePath);
}
// ...
build() {
  // ...
      PdfView({
        controller: this.controller,
        pageFit: pdfService.PageFit.FIT_WIDTH
      })
        // ...
}
  1. 通过文本水印类[pdfService.TextWatermarkInfo]创建水印对象,设置水印内容、字体、颜色、位置等相关属性;图片水印对象通过[pdfService.ImageWatermarkInfo]创建。
// entry/src/main/ets/pages/WatermarkPdfPage.ets
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.opacity = 0.3;
  watermarkInfo.rotation = 45;
  watermarkInfo.opacity = 0.3;
  return watermarkInfo;
}
  1. 通过PDF文档类[pdfService.PdfDocument]创建文档对象,使用文档对象的[loadDocument()]方法加载文档,[addWatermark()]方法添加水印、[saveDocument()]方法将添加水印后的文档保存到沙箱中。
// entry/src/main/ets/pages/WatermarkPdfPage.ets
addWatermark() {
  const filePath = this.getPdfSandboxPath();
  let pdfDocument: pdfService.PdfDocument = new pdfService.PdfDocument();
  pdfDocument.loadDocument(filePath);
  pdfDocument.addWatermark(this.getWatermarkInfo(), 0, pdfDocument.getPageCount(), true, true);
  const watermarkFilePath = this.getAddedWatermarkPdfSandboxPath();
  pdfDocument.saveDocument(watermarkFilePath);
  this.showInPdfView(watermarkFilePath);
}
  1. 将沙箱中添加水印后的文档加载到PDF预览器中。
// entry/src/main/ets/pages/WatermarkPdfPage.ets
async showInPdfView(filePath: string) {
  this.hasWatermark = true;
  this.controller.releaseDocument();
  await this.controller.loadDocument(filePath);
  this.controller.setPageFit(pdfService.PageFit.FIT_WIDTH);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值