基于原生的水印添加能力

基于原生的水印添加能力

HarmonyOS
2024-06-11 23:17:21
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
jmzgh

添加水印是一个比较常见的功能,下面列举一些遇到的业务需求:

  •  场景一:在页面背景上添加全局水印
  • 场景二:保存图片添加水印
  • 场景三:拍照添加水印
  • 场景四:window级别的全局水印

方案描述

场景一:

在页面背景上添加全局水印

效果图

方案

页面全局水印的实现思路是使用透明背景的canvas画布绘制文字水印,在使用Stack组件叠加在page跟容器上

方式一:使用canvas进行绘制

Stack({ alignContent: Alignment.Center }) { 
  Column() { 
    Text("没有数据哦").fontColor("#495057") 
    Image($r("app.media.empty")).width(300) 
  } 
  Canvas(this.context) 
    .width("100%") 
    .height("100%") 
    .hitTestBehavior(HitTestMode.Transparent) 
    .onReady(() => { 
      this.context.fillStyle = '#10000000' 
      this.context.font = "16vp" 
      this.context.textAlign = "center" 
      this.context.textBaseline = "middle" 
      // 在这里绘制文字水印,也可以是图片水印 
      for (let i = 0; i < this.context.width / 120; i++) { 
        this.context.translate(120, 0) 
        let j = 0 
        for (; j < this.context.height / 120; j++) { 
          this.context.rotate(-Math.PI / 180 * 30) 
          // 此处水印数据是写死的,具体请替换为自己的水印 
          this.context.fillText("水印水印水印", -60, -60) 
          this.context.rotate(Math.PI / 180 * 30) 
          this.context.translate(0, 120) 
        } 
        this.context.translate(0, -120 * j) 
      } 
    }) 
}.layoutWeight(1)

方式二:使用overlay属性绘制

方式二思路与方式一实现思路一致,该方式可以讲水印组件单独拧出来,在其他使用时使用overlay属性来使用,实现效果与方式一一致

@Builder 
Watermark() { 
  Canvas(this.context) 
    .width("100%") 
    .height("100%") 
    .hitTestBehavior(HitTestMode.Transparent) 
    .onReady(() => { 
      this.context.fillStyle = '#10000000' 
      this.context.font = "16vp" 
      this.context.textAlign = "center" 
      this.context.textBaseline = "middle" 
      // 在这里绘制文字水印,也可以是图片水印 
      for (let i = 0; i < this.context.width / 120; i++) { 
        this.context.translate(120, 0) 
        let j = 0 
        for (; j < this.context.height / 120; j++) { 
          this.context.rotate(-Math.PI / 180 * 30) 
          // 此处水印数据是写死的,具体请替换为自己的水印 
          this.context.fillText("水印水印水印", -60, -60) 
          this.context.rotate(Math.PI / 180 * 30) 
          this.context.translate(0, 120) 
        } 
        this.context.translate(0, -120 * j) 
      } 
    }) 
}
Column() { 
  Text("没有数据哦").fontColor("#495057") 
  Image($r("app.media.empty")).width(300) 
} 
.layoutWeight(1) 
.overlay(this.Watermark()) 
.width("100%")

场景二:

保存图片添加水印

效果图

方案

使用OffScreenCanvas离屏画布拿到图片源数据,在离屏画布上使用OffscreenCanvasRenderingContext2D.drawImage方法讲图片数据进行绘制,使用OffscreenCanvasRenderingContext2D.fillText方法绘制文字水印,通过 OffscreenCanvasRenderingContext2D.getPixelMap方法获取添加水印后的图片pixelMap数据,再通过安全控件方式拿到图库的uri路径,在这个uri下通过image.createImagePacker方法创建图片文件,通过fs.write方法写入这个图库的文件中,具体实现代码如下所示

核心代码

第一步:使用resourceManager.getMediaContent方法获取当前的图片资源

getContext(this).resourceManager.getMediaContent($r(this.imagePath)).then((data) => { 
   // ... 
}).catch((err: BusinessError) => { 
  console.log("getPixelMap error" + err.message) 
})

第二步:使用image.createImageSource方法创建图片资源,在使用OffscreenCanvasRenderingContext2D.drawImage方法绘制图片在离屏画布上

let arrayBuffer = data.buffer.slice(data.byteOffset, data.byteLength + data.byteOffset) 
let imageSource: image.ImageSource = image.createImageSource(arrayBuffer); 
imageSource.getImageInfo((err, value) => { 
  if (err) { 
    return; 
  } 
  let opts: image.DecodingOptions = { 
    editable: true, 
    desiredSize: { 
      height: value.size.height, 
      width: value.size.width 
    } 
  }; 
  imageSource.createPixelMap(opts, async (err, pixelMap) => { 
    // 这个回调的pixelMap就是当前图片的pixelMap 
    let offScreenCanvas = new OffscreenCanvas(value.size.width, value.size.height) 
    let OffScreenContext = offScreenCanvas.getContext("2d") 
    this.imageScale = offScreenCanvas.width / display.getDefaultDisplaySync().width 
    OffScreenContext.drawImage(pixelMap, 0, 0, offScreenCanvas.width, offScreenCanvas.height) 
    // ... 
  }) 
})

第三步:使用OffscreenCanvasRenderingContext2D.fillText绘制水印

getContext(this).resourceManager.getMediaContent($r(this.imagePath)).then((data) 
  let arrayBuffer = data.buffer.slice(data.byteOffset, data.byteLength + data.byteOffset) 
  let imageSource: image.ImageSource = image.createImageSource(arrayBuffer); 
  imageSource.getImageInfo((err, value) => { 
    if (err) { 
      return; 
    } 
    let opts: image.DecodingOptions = { 
      editable: true, 
      desiredSize: { 
        height: value.size.height, 
        width: value.size.width 
      } 
    }; 
    imageSource.createPixelMap(opts, async (err, pixelMap) => { 
      // ... 
      OffScreenContext.textAlign = "right" 
      OffScreenContext.textBaseline = "bottom" 
      OffScreenContext.fillStyle = '#30000000' 
      OffScreenContext.font = 32 * this.imageScale + "vp" 
      OffScreenContext.fillText("初九会画画", offScreenCanvas.width - 20 * this.imageScale, offScreenCanvas.height - 20 * this.imageScale) 
      // ... 
    }) 
  }) 
}).catch((err: BusinessError) => { 
  console.log("getPixelMap error" + err.message) 
})

第四步:使用OffscreenCanvasRenderingContext2D.getPixelMap取出绘制后的图片数据

// 获取新的PixelMap 
this.pixelMap = OffScreenContext.getPixelMap(0, 0, offScreenCanvas.width, offScreenCanvas.height)

第五步:使用SaveButton保存到图库中

SaveButton({ icon: SaveIconStyle.FULL_FILLED }).onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => { 
  if (result == SaveButtonOnClickResult.SUCCESS) { 
    try { 
      let context = getContext(this); 
      let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); 
      let uri = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 创建媒体文件 
      this.imagePath = "app.media.img1" 
      this.addWatermark() 
      if (this.pixelMap) { 
        saveToFile(this.pixelMap, uri) 
      } 
    } catch (err) { 
      console.error('createAsset failed, message = ', err); 
    } 
  } else { 
    console.error('SaveButtonOnClickResult createAsset failed'); 
  } 
})

场景三:

拍照添加水印

方案

使用cameraPicker拉起系统相机拍照,拿到返回的picker.PickerResult.resultUri后使用fs.openSync读出图片文件转化为pixelMap,使用image组件渲染在页面上,在保存图片的时候通过drawing.TextBlob.makeFromString方法绘制水印后保存

核心代码

第一步:使用cameraPicker拉起系统相机

import picker from '@ohos.multimedia.cameraPicker'; 
import camera from '@ohos.multimedia.camera'; 
 
async openCamera() { 
  try { 
    let pickerProfile: picker.PickerProfile = { 
      cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK 
    }; 
    let pickerResult: picker.PickerResult = await picker.pick(getContext(this), 
      [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO], pickerProfile); 
    console.log("the pick pickerResult is:" + JSON.stringify(pickerResult)); 
    // ... 
  } catch (error) { 
    let err = error as BusinessError; 
    console.error(`the pick call failed. error code: ${err.message}`); 
  } 
}

第二步:将相机拍摄的照片渲染在页面中,使用fs.readSync读出文件字节流,再使用image.createImageSource创建图片资源管理器获取到图片的pixelMap

@State pixelMap: image.PixelMap | null = null 
async openCamera() { 
  try { 
    // ... 
    if (pickerResult.resultUri) { 
      this.showImage = true 
    } 
    let file = fs.openSync(pickerResult.resultUri) 
    let buffer = new ArrayBuffer(fs.statSync(file.fd).size) 
    fs.readSync(file.fd, buffer) 
    let imageSource: image.ImageSource = image.createImageSource(buffer) 
    imageSource.getImageInfo((err, value) => { 
      if (err) { 
        return; 
      } 
      let opts: image.DecodingOptions = { 
        editable: true, 
        desiredSize: { 
          height: value.size.height, 
          width: value.size.width 
        } 
      }; 
      imageSource.createPixelMap(opts, async (err, pixelMap) => { 
        this.pixelMap = pixelMap 
 
      }) 
    }) 
  } catch (error) { 
    let err = error as BusinessError; 
    console.error(`the pick call failed. error code: ${err.message}`); 
  } 
} 
 
build() { 
  // ... 
  if(this.showImage) { 
	Image(this.pixelMap).width("100%") 
    // ... 
  } 
}

第三步:使用drawing.TextBlob.makeFromString绘制水印后 使用SaveButton保存在系统相册中

addWaterMask(pixelMap: image.PixelMap) { 
  this.imageScale = this.imageWidth / display.getDefaultDisplaySync().width 
  const canvas = new drawing.Canvas(pixelMap) 
  const pen = new drawing.Pen() 
  pen.setColor({ alpha: 255, red: 0, green: 10, blue: 10 }) 
 
  const font = new drawing.Font() 
  font.setSize(32 * this.imageScale) 
  let textWidth = font.measureText("初九会画画", drawing.TextEncoding.TEXT_ENCODING_UTF8) 
 
  const brush = new drawing.Brush() 
  brush.setColor({ alpha: 255, red: 255, green: 0, blue: 0 }) 
 
  const textBlob = drawing.TextBlob.makeFromString("初九会画画", font, drawing.TextEncoding.TEXT_ENCODING_UTF8) 
  canvas.attachBrush(brush) 
  canvas.attachPen(pen) 
  canvas.drawTextBlob(textBlob, this.imageWidth - 20 * this.imageScale - textWidth, this.imageHeight - 20 * this.imageScale) 
  canvas.detachBrush(); 
  canvas.detachPen() 
  return pixelMap 
}
SaveButton() 
.onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => { 
  if (result == SaveButtonOnClickResult.SUCCESS) { 
    try { 
      let context = getContext(this); 
      let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); 
      let uri = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png'); // 创建媒体文件 
      saveToFile(this.addWaterMask(this.pixelMap!), uri); 
    } catch (err) { 
      console.error('createAsset failed, message = ', err); 
    } 
  } else { 
    console.error('SaveButtonOnClickResult createAsset failed'); 
  } 
})
分享
微博
QQ
微信
回复
2024-06-12 23:33:14
相关问题
基于原生能力跨应用跳转
266浏览 • 1回复 待解决
基于原生能力网络加载性能分析
232浏览 • 1回复 待解决
基于原生能力设备唯一ID方案
186浏览 • 1回复 待解决
如何使用canvas添加水印
397浏览 • 1回复 待解决
基于原生跨模块资源访问
260浏览 • 1回复 待解决
基于原生实现高级显示效果
135浏览 • 1回复 待解决
canvas如何实现水印效果
350浏览 • 1回复 待解决
基于webView嵌套滚动
127浏览 • 1回复 待解决
鸿蒙是基于Android吗?
9821浏览 • 2回复 已解决
基于RichEditor评论编辑
171浏览 • 1回复 待解决
PolarDB 云原生HTAP 整体架构是什么?
1533浏览 • 1回复 待解决
基于measure实现文本测量
144浏览 • 1回复 待解决
基于bindSheet半模态弹窗
161浏览 • 1回复 待解决
OpenHarmony 编译原生设置settings报错
1587浏览 • 0回复 待解决
HDF层驱动和linux原生驱动
2415浏览 • 1回复 待解决
原子布局能力对应api
7992浏览 • 1回复 待解决
基于mysql悲观锁运用场景?
1335浏览 • 1回复 待解决
是否有查看c++内存能力
249浏览 • 1回复 待解决
基于加解密算法框架规格问题
194浏览 • 1回复 待解决