第41篇|补光与水印:效果选项如何参与最终照片记录

上一篇讲美颜预设,这一篇继续看补光和水印。补光解决的是拍摄时的即时视觉反馈,水印解决的是照片落盘后的信息保留。两者看起来都在效果面板里,但进入工程链路的位置不同。

补光主要影响预览层,用户选择暖光、冷光或玫瑰光后,页面用叠层反馈氛围。水印则要在拍摄前固化成 pending 状态,并在图片写入后真正调用服务层把文字写进文件。

这一篇继续围绕 21 天「智能相机开发实战」训练营展开。内容只使用当前项目里的 ArkTS、服务层代码和真实页面截图来讲,不把封面图放进正文。阅读时可以先看截图理解用户侧效果,再顺着函数名回到工程定位实现。

本篇目标

  • 理解补光和水印在工程链路中的不同位置。
  • 掌握补光颜色、透明度和预览叠层的推导方式。
  • 读懂水印文本如何在拍摄前固定,避免回调后状态漂移。
  • 知道照片文件写入后如何调用服务层应用水印。

对应源码位置

  • entry/src/main/ets/pages/Index.ets
  • entry/src/main/ets/services/DualPhotoComposerService.ets

一、补光是预览反馈,水印是照片元信息

补光选项的核心价值是让用户在拍摄前看到氛围变化。它不一定要求底层相机输出发生变化,至少在训练营项目中,先通过 UI 叠层建立反馈闭环。

水印则不同。水印需要进入最终照片和相册记录,否则用户回看时无法知道这张图使用的是时间、地点还是双镜标记。它必须在拍摄前锁定文本,在图片写入后执行文件处理。

图1 补光与水印在真实相机页中的入口和处理流向

图1 补光与水印在真实相机页中的入口和处理流向

二、补光颜色和透明度从 selectedFillLightColor 推导

项目把补光色彩和透明度封装成函数。off 时透明度为 0,其余颜色返回固定透明度。UI 渲染时只关心“当前需要什么颜色、显示多强”,不关心用户刚才点的是第几个按钮。

这类推导函数很适合单独测试。你可以给不同 key 输入,检查返回颜色和透明度是否符合预期,而不需要启动完整相机页面。

图2 补光颜色和透明度由 selectedFillLightColor 推导

图2 补光颜色和透明度由 selectedFillLightColor 推导

    return '关';
  }

  private getFillLightOverlayColor(): string {
    if (this.selectedFillLightColor === 'warm') {
      return '#33FFE3A3';
    }
    if (this.selectedFillLightColor === 'cool') {
      return '#2FDDF6FF';
    }
    if (this.selectedFillLightColor === 'rose') {
      return '#2DFFD3DA';
    }
    return '#00000000';
  }

  private getFillLightOverlayOpacity(): number {
    return this.selectedFillLightColor === 'off' ? 0 : 0.58;
  }

补光不是简单给页面加一层颜色,它还需要和相机控件、拍摄按钮、预览画面共存。透明度固定在函数里,能减少 UI 层误改造成遮挡。

三、水印文本必须在拍摄前固定

拍照回调是异步的,用户可能在回调期间切换水印样式或地点。为了保证照片记录和文件内容一致,项目在触发拍摄前调用 armPendingWatermark,把样式和文本存到 pending 字段。

这个动作看似很小,但对相机工程非常重要。所有与“这一张照片”绑定的信息,都应该在触发 capture 前后形成稳定上下文,而不是回调时再读取可变页面状态。

图3 拍摄前固定水印文本,避免回调后状态漂移

图3 拍摄前固定水印文本,避免回调后状态漂移

  private buildCapturedWatermarkText(
    style: WatermarkStyleKey,
    timestamp: number,
    place: string,
    captureMode: CaptureMode
  ): string {
    if (style === 'none') {
      return '';
    }
    const timeLabel = this.getTimeLabelForTimestamp(timestamp);
    if (style === 'time') {
      return timeLabel;
    }
    if (style === 'dual') {
      return captureMode === 'dual' ? '双镜' : '单拍';
    }
    return place;
  }

  private armPendingWatermark(timestamp: number, place: string, captureMode: CaptureMode): void {
    this.pendingWatermarkStyle = this.selectedWatermarkStyle;
    this.pendingWatermarkText = this.buildCapturedWatermarkText(
      this.pendingWatermarkStyle,
      timestamp,
      place,
      captureMode
    );
  }

如果以后水印包含天气、海拔或小艺推荐语,也应该采用同样思路:先生成 pending 文本,再让文件处理服务消费。

四、照片写入后再真正应用水印

图片文件写入完成后,项目调用 applyCaptureWatermark。它会收集后摄和前摄目标路径,并逐个调用 DualPhotoComposerService.applyTextWatermark。如果某一路写入失败,错误会被捕获并记录,不会把整次拍摄结果直接抹掉。

水印处理放在服务层,页面层只传路径和文本。这样后续替换为更复杂的水印布局、字体、阴影或二维码时,页面拍摄流程不需要大改。

图4 applyCaptureWatermark 对后摄和前摄路径逐个写入水印

图4 applyCaptureWatermark 对后摄和前摄路径逐个写入水印

  private async applyPendingWatermarkToCapturePaths(paths: CaptureOutputPaths): Promise<void> {
    if (this.pendingWatermarkStyle === 'none') {
      return;
    }
    const watermarkText = this.pendingWatermarkText.trim();
    if (watermarkText.length === 0) {
      return;
    }
    const targetPaths = [paths.backPath, paths.frontPath].filter((path: string, index: number, array: Array<string>) => {
      return path.length > 0 && array.indexOf(path) === index;
    });
    for (let index = 0; index < targetPaths.length; index++) {
      try {
        await DualPhotoComposerService.applyTextWatermark(targetPaths[index], watermarkText);
      } catch (error) {
        const message = error instanceof Error ? error.message : JSON.stringify(error);
        console.error(`Failed to apply capture watermark: ${message}`);
      }
    }

文件处理失败不能等同于拍摄失败。对用户来说,保住照片本身优先级更高,水印失败可以提示或记录日志。

工程检查清单

  • 补光逻辑先保证预览反馈清晰,不遮挡核心取景画面。
  • 水印文本在触发 capture 前固定到 pending 字段。
  • 图片写入后再应用水印,避免对不存在的文件操作。
  • 服务层负责具体写图,页面层只控制时机和路径。
  • 水印失败时保留原始照片,不让用户作品丢失。

今日练习

  1. 把水印样式切到 time,阅读 buildCapturedWatermarkText 返回内容。
  2. 尝试在拍摄过程中快速切换地点,思考 pendingWatermarkText 如何保证结果一致。
  3. 设计一个“拍摄参数水印”,把美颜、补光和模式摘要加入文本。

训练营后面的内容会继续按“真实页面效果 → 源码定位 → 状态闭环 → 可验证结果”的节奏推进。每一篇都尽量让你能拿着代码直接回到项目里复现,而不是只停留在概念说明。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐