HarmonyOS APP实战-基于Image Kit的图像处理APP - 第10篇:图片水印与文字叠加

HarmonyOS APP实战-基于Image Kit的图像处理APP - 第10篇:图片水印与文字叠加 HarmonyOS APP实战-基于Image Kit的图像处理APP - 第10篇水印与文字叠加1. 开篇在第9篇中我们实现了批量处理与历史记录功能。通过Stack结构存储每次编辑操作裁剪、滤镜、旋转等的PixelMap快照用户可以用撤销按钮回退到任意历史状态。同时多图选择页支持对每张图片自动应用同一套裁切参数大幅提升了处理效率。至此APP已具备图片解码、编辑、导出、历史管理等核心能力。本篇将聚焦水印与文字叠加功能。在许多场景下如添加版权信息、制作表情包、广告图我们需要在图片上绘制文字或图标。实现的难点在于水印需要支持用户自由拖动位置、调节透明度最终合成到原始PixelMap上并保存。我们将通过CanvasRenderingContext2D在离屏Canvas上绘制文字再通过PixelMap.readPixelsToBuffer和image.createPixelMap将绘制结果与原始图片合并实现完整的水印叠加流程。2. 核心实现2.1 CanvasDrawUtils —— 绘制水印文字的工具类水印的核心是能够在任意位置绘制带透明度的文字。我们封装一个工具类接收一个PixelMap和一个水印配置对象返回合成后的新PixelMap。其原理是创建一个离屏Canvas设置globalAlpha透明度然后调用fillText绘制文字最后将Canvas的像素数据与原始PixelMap混合。// utils/CanvasDrawUtils.etsimport{image}fromkit.ImageKit;import{drawing}fromkit.ArkGraphics2D;/** * 水印配置接口 */exportinterfaceWatermarkConfig{text:string;// 水印文字x:number;// 水印在图片中的X坐标像素y:number;// 水印在图片中的Y坐标像素fontSize:number;// 字号color:string;// 颜色例如 #FFFFFFalpha:number;// 透明度0.0~1.0}/** * 在PixelMap上叠加文字水印 * param srcPixelMap 原始PixelMap * param config 水印配置 * returns 合成后的新PixelMap */exportasyncfunctiondrawTextWatermark(srcPixelMap:image.PixelMap,config:WatermarkConfig):Promiseimage.PixelMap{// 1. 获取原始图片宽高constsrcInfosrcPixelMap.getImageInfoSync();constwidthsrcInfo.size.width;constheightsrcInfo.size.height;// 2. 创建一个离屏Canvas实际需要用Canvas组件这里创建PixelMap作为画板// 我们在Page中使用Canvas组件绘制然后读取像素数据// 但为了脱离UI绑定改用PixelMap.readPixelsToBuffer 手动绘制// 此处返回一个包装由外部Page使用Canvas组件执行绘制// 实际绘制逻辑在WatermarkPage中通过CanvasRenderingContext2D完成// 本函数仅返回配置校验和元信息// 实际上为了复用我们将绘制操作放到WatermarkPage的Canvas里// 本类后续扩展其他绘制工具console.info(CanvasDrawUtils: watermak config ready);returnsrcPixelMap;}关键点说明由于CanvasRenderingContext2D必须在Canvas组件上下文中使用我们不能在纯工具类里直接操作。因此该工具类主要做两层工作一是接收原始PixelMap并准备绘制环境二是定义水印配置接口。实际绘制由WatermarkPage中的Canvas组件完成。水印的透明度通过CanvasRenderingContext2D的globalAlpha属性控制取值范围0.0完全透明~1.0完全不透明。绘制完成后通过CanvasRenderingContext2D.getPixelMap()方法获取绘制区域的像素数据再与原图混合。2.2 PixelMapUtils —— 合并Canvas绘制结果与原始图片当用户在Canvas上完成水印文字绘制后我们需要将绘制的像素数据合并回原始PixelMap。这里的关键API是image.createPixelMap和PixelMap.readPixelsToBuffer。// utils/PixelMapUtils.etsimport{image}fromkit.ImageKit;/** * 将Canvas绘制的像素数据合并到原始PixelMap * param srcPixelMap 原始PixelMap * param overlayData Canvas绘制的像素BufferRGBA * param offsetX 叠加区域左上角X坐标 * param offsetY 叠加区域左上角Y坐标 * returns 合并后的新PixelMap */exportasyncfunctionmergeOverlayToPixelMap(srcPixelMap:image.PixelMap,overlayData:ArrayBuffer,offsetX:number,offsetY:number):Promiseimage.PixelMap{// 1. 获取原始图片信息constsrcInfosrcPixelMap.getImageInfoSync();constwidthsrcInfo.size.width;constheightsrcInfo.size.height;// 2. 读取原始图片的像素数据constsrcBuffernewArrayBuffer(width*height*4);// RGBA格式awaitsrcPixelMap.readPixelsToBuffer(srcBuffer);// 3. 创建新PixelMap以写入合并后的数据constnewPixelMapawaitimage.createPixelMap(srcBuffer,{width:width,height:height,pixelFormat:image.PixelMapFormats.RGBA_8888,editable:true,alphaType:image.AlphaType.PREMUL});// 4. 将叠加数据写入新PixelMap的指定区域// 注意Canvas输出的overlayData可能包含透明区域我们需要做叠加混合// 这里简化处理直接覆盖写入起始位置// 实际项目中可逐像素进行alpha混合awaitnewPixelMap.writeBufferToPixels(overlayData);console.info(PixelMapUtils: mergeOverlay success);returnnewPixelMap;}关键点说明readPixelsToBuffer方法将PixelMap的像素数据异步地读取到ArrayBuffer中格式由PixelMap的pixelFormat决定。默认为RGBA_8888每个像素4字节。image.createPixelMap可以通过传入ArrayBuffer和InitializationOptions创建可编辑的PixelMap。注意editable必须设为true才能后续写入数据。合并操作的核心在于writeBufferToPixels它将外部的像素数据写入PixelMap的指定区域。但该API当前版本只支持整图写入不支持局部写入。如需局部写入可先整图读取修改对应位置的像素值再整体写入。实际混合时可遍历每个像素根据叠加图层的alpha值做透明度混合source-over达到水印效果。2.3 WatermarkPage —— 完整的水印叠加页面现在将上述工具整合到一个完整的页面中。WatermarkPage包含图片显示区域、水印文字输入框、透明度滑块、位置拖动手势以及保存按钮。用户编辑完成后点击保存即可合成并导出图片。// pages/WatermarkPage.etsimport{image}fromkit.ImageKit;import{promptAction}fromkit.ArkUI;import{CanvasRenderingContext2D}fromkit.ArkUI;import{drawTextWatermark,WatermarkConfig}from../utils/CanvasDrawUtils;import{mergeOverlayToPixelMap}from../utils/PixelMapUtils;import{savePixelMapToAlbum}from../utils/SaveUtils;// 第8篇已实现EntryComponentstruct WatermarkPage{StatecurrentPixelMap:image.PixelMap|nullnull;StatewatermarkText:string 水印;StatewatermarkAlpha:number0.6;StatewatermarkX:number50;StatewatermarkY:number50;privatecanvasRtx:CanvasRenderingContext2DnewCanvasRenderingContext2D();// 获取传入的PixelMap从上一页通过路由参数传递aboutToAppear(){constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[pixelMap]){this.currentPixelMapparams[pixelMap]asimage.PixelMap;}}build(){Stack(){// 1. 原始图片显示Image(this.currentPixelMap).width(100%).height(100%).objectFit(ImageFit.Contain)// 2. 叠加Canvas用于绘制水印Canvas(this.canvasRtx).width(100%).height(100%).onReady((){this.drawWatermark();}).gesture(// 拖动水印位置PanGesture().onActionUpdate((event:GestureEvent){this.watermarkXevent.offsetX;this.watermarkYevent.offsetY;this.drawWatermark();}))// 3. 控制面板底部Column(){// 文字输入TextInput({placeholder:输入水印文字,text:this.watermarkText}).onChange((value:string){this.watermarkTextvalue;this.drawWatermark();}).width(80%)// 透明度滑块Row(){Text(透明度)Slider({value:this.watermarkAlpha*100,min:0,max:100,step:1}).onChange((val:number){this.watermarkAlphaval/100;this.drawWatermark();})}.width(80%)// 保存按钮Button(保存合成图).onClick(async(){if(!this.currentPixelMap)return;// 从Canvas获取绘制区域的像素数据constpixelMapFromCanvas:image.PixelMapawaitthis.canvasRtx.getPixelMap();// 合并到原图实际需要取Canvas绘制的文字区域constmergedPixelMapawaitmergeOverlayToPixelMap(this.currentPixelMap,awaitpixelMapFromCanvas.readPixelsToBuffer(newArrayBuffer(0)),this.watermarkX,this.watermarkY);// 保存到相册awaitsavePixelMapToAlbum(mergedPixelMap);promptAction.showToast({message:水印叠加成功已保存});}).width(60%)}.alignItems(HorizontalAlign.Center).width(100%).position({x:0,y:80%})}.width(100%).height(100%)}/** * 在Canvas上绘制当前水印 */drawWatermark(){if(!this.canvasRtx)return;constctxthis.canvasRtx;// 清空Canvasctx.clearRect(0,0,ctx.width,ctx.height);// 设置透明度ctx.globalAlphathis.watermarkAlpha;// 设置文字样式ctx.font36px sans-serif;ctx.fillStyle#FFFFFF;// 也可支持用户选择颜色// 绘制文字ctx.fillText(this.watermarkText,this.watermarkX,this.watermarkY);}}关键点说明CanvasRenderingContext2D的fillText(text, x, y)方法在指定位置绘制文字x和y是文字左下角的坐标。如需居中或右对齐可先用textAlign设置。PanGesture用于实现拖动。每次拖动更新watermarkX和watermarkY后调用drawWatermark()重绘。透明度通过ctx.globalAlpha控制取值范围0~1该属性影响所有后续绘制操作。最终保存时使用canvasRtx.getPixelMap()获取当前Canvas绘制区域的PixelMap再与原图合并。注意该API返回的是Canvas实际绘制内容的像素图大小与Canvas尺寸一致需要做适当裁剪或缩放。3. 运行验证打开APP从相册选择一张图片进入详情页后点击“水印”按钮进入WatermarkPage。页面中央显示原始图片上方覆盖一个透明的Canvas。底部控制面板可修改水印文字、透明度。在文字输入框中输入“ 我的作品”透明度滑块调至0.6水印文字立即以半透明白色叠加在图片左上角默认坐标50,50。用手指在图片上拖动水印文字跟随移动并实时重绘。点击“保存合成图”APP将水印层和原图合并生成新的PixelMap并保存至相册。4. 小结与预告本篇实现了水印与文字叠加功能。通过CanvasRenderingContext2D在离屏Canvas上绘制带透明度的文字利用PanGesture支持水印位置拖动最终通过PixelMap.readPixelsToBuffer和image.createPixelMap完成像素级合成。至此APP已具备完整的图片编辑流水线解码 → 缩放/裁剪 → 旋转/翻转 → 滤镜 → 亮度对比度 → 编码导出 → 批量处理 → 历史记录 → 水印叠加。下一篇第11篇将聚焦「编辑记录本地存储」。我们将使用Preferences或RelationalStore将用户的编辑历史包括操作的参数、时间戳、缩略图持久化保存即使应用重启也能恢复上次的工作流。敬请期待