鸿蒙5.0开发进阶:图片编辑实现马赛克效果

往期鸿蒙全套实战文章必看:


介绍

本示例将原图手指划过的区域分割成若干个大小一致的小方格,然后获取每个小方格中的像素点的平均色彩数值,使用获取到的平均色彩数值替换该方格中所有的像素点。最后使用createPixelMapSync接口将新的像素点数据写入图片,即可实现原始图片的局部马赛克处理。

效果图预览

使用说明

  1. 进入页面,手指划过图片的某一个区域即可将该区域马赛克处理。点击底部的“恢复原图”按钮,将恢复为原图。

实现思路

  1. 获取原始图片信息,将原始图片设置为可编辑状态。

    /**
    * 获取图片内容
    */
    @Concurrent
    async function getImageContent(imgPath: string, context: Context): Promise<Uint8Array | undefined> {
      // 获取resourceManager资源管理
      const resourceMgr: resourceManager.ResourceManager = context.resourceManager;
      // 获取rawfile中的图片资源
      const fileData: Uint8Array = await resourceMgr.getRawFileContent(imgPath);
      return fileData;
    }
    /**
    * 获取原始图片信息
    */
    async getSrcImageInfo(): Promise<void> {
     // TODO: 性能知识点:使用new taskpool.Task()创建任务项,传入获取图片内容函数和所需参数
     const task: taskpool.Task = new taskpool.Task(getImageContent, MosaicConstants.RAWFILE_PICPATH, getContext(this));
     try {
       const fileData: Uint8Array = await taskpool.execute(task) as Uint8Array;
       // 获取图片的ArrayBuffer
       const buffer = fileData.buffer.slice(fileData.byteOffset, fileData.byteLength + fileData.byteOffset);
       // 获取原图imageSource
       this.imageSource = image.createImageSource(buffer);
       // TODO 知识点: 将图片设置为可编辑
       const decodingOptions: image.DecodingOptions = {
         editable: true,
         desiredPixelFormat: image.PixelMapFormat.RGBA_8888,
       }
       // 创建PixelMap
       this.pixelMapSrc = await this.imageSource.createPixelMap(decodingOptions);
     } catch (err) {
       console.error("getSrcImageInfo: execute fail, err:" + (err as BusinessError).toString());
     }
    }
  2. 保存图片的原始尺寸及在屏幕的显示区域。

     // 读取图片信息
     const imageInfo: image.ImageInfo = await this.pixelMapSrc!.getImageInfo();
     // 获取图片的宽度和高度
     this.imageWidth = imageInfo.size.width;
     this.imageHeight = imageInfo.size.height;
     // 获取屏幕尺寸
     const displayData: display.Display = display.getDefaultDisplaySync();
     // 计算图片的显示尺寸
     this.displayWidth = px2vp(displayData.width);
     this.displayHeight = this.displayWidth * this.imageHeight / this.imageWidth;
  3. 获取手指按下和移动时的坐标,手指移动时执行马赛克任务。

    PanGesture()
      .onActionStart((event: GestureEvent) => {
        const finger: FingerInfo = event.fingerList[0];
        if (finger == undefined) {
          return;
        }
        this.startX = finger.localX;
        this.startY = finger.localY;
        })
      .onActionUpdate((event: GestureEvent) => {
        const finger: FingerInfo = event.fingerList[0];
          if (finger == undefined) {
            return;
          }
          this.endX = finger.localX;
          this.endY = finger.localY;
          // 执行马赛克任务
          await this.doMosaicTask(this.startX, this.startY, this.endX, this.endY);
          this.startX = this.endX;
          this.startY = this.endY;
         })
  4. 在马赛克任务中处理坐标转换问题后执行马赛克处理函数applyMosaic。

async doMosaicTask(offMinX: number, offMinY: number, offMaxX: number, offMaxY: number): Promise<void> {
  // TODO 知识点:将手势移动的起始坐标转换为原始图片中的坐标
  offMinX = Math.round(offMinX * this.imageWidth / this.displayWidth);
  offMinY = Math.round(offMinY * this.imageHeight / this.displayHeight);
  offMaxX = Math.round(offMaxX * this.imageWidth / this.displayWidth);
  offMaxY = Math.round(offMaxY * this.imageHeight / this.displayHeight);
  // 处理起始坐标大于终点坐标的情况
  if (offMinX > offMaxX) {
    const temp = offMinX;
    offMinX = offMaxX;
    offMaxX = temp;
  }
  if (offMinY > offMaxY) {
    const temp = offMinY;
    offMinY = offMaxY;
    offMaxY = temp;
  }
  // 获取像素数据的字节数
  const bufferData = new ArrayBuffer(this.pixelMapSrc!.getPixelBytesNumber());
  await this.pixelMapSrc!.readPixelsToBuffer(bufferData);
  // 将像素数据转换为 Uint8Array 便于像素处理
  let dataArray = new Uint8Array(bufferData);
  // TODO: 性能知识点:使用new taskpool.Task()创建任务项,传入任务执行函数和所需参数
  const task: taskpool.Task =
    new taskpool.Task(applyMosaic, dataArray, this.imageWidth, this.imageHeight, MosaicConstants.BLOCK_SIZE,
      offMinX, offMinY, offMaxX, offMaxY);
  try {
    taskpool.execute(task, taskpool.Priority.HIGH).then(async (res: Object) => {
      this.pixelMapSrc = image.createPixelMapSync((res as Uint8Array).buffer, this.opts);
      this.isMosaic = true;
    })
  } catch (err) {
    console.error("doMosaicTask: execute fail, " + (err as BusinessError).toString());
  }
}
  1. 实现图像局部马赛克处理函数

    async applyMosaic(dataArray: Uint8Array, imageWidth: number, imageHeight: number, blockSize: number,
        offMinX: number, offMinY: number, offMaxX: number, offMaxY: number): Promise<Uint8Array | undefined> {
     try {
     // 计算横排和纵排的块数
     let xBlocks = Math.floor((Math.abs(offMaxX - offMinX)) / blockSize);
     let yBlocks = Math.floor((Math.abs(offMaxY - offMinY)) / blockSize);
     logger.info(MosaicConstants.TAG, 'xBlocks: ' + xBlocks.toString() + ' ,yBlocks:' + yBlocks.toString());
     // 不足一块的,按一块计算
     if (xBlocks < 1) {
       xBlocks = 1;
       offMaxX = offMinX + blockSize;
     }
     if (yBlocks < 1) {
       yBlocks = 1;
       offMaxY = offMinY + blockSize;
     }
    
     // 遍历每个块
     for (let y = 0; y < yBlocks; y++) {
       for (let x = 0; x < xBlocks; x++) {
         const startX = x * blockSize + offMinX;
         const startY = y * blockSize + offMinY;
    
         // 计算块内的平均颜色
         let totalR = 0;
         let totalG = 0;
         let totalB = 0;
         let pixelCount = 0;
         for (let iy = startY; iy < startY + blockSize && iy < imageHeight && iy < offMaxY; iy++) {
           for (let ix = startX; ix < startX + blockSize && ix < imageWidth && ix < offMaxX; ix++) {
             // TODO 知识点:像素点数据包括RGB通道的分量值及图片透明度
             const index = (iy * imageWidth + ix) * 4; // 4 像素点数据包括RGB通道的分量值及图片透明度
             totalR += dataArray[index];
             totalG += dataArray[index + 1];
             totalB += dataArray[index + 2];
             pixelCount++;
           }
         }
         const averageR = Math.floor(totalR / pixelCount);
         const averageG = Math.floor(totalG / pixelCount);
         const averageB = Math.floor(totalB / pixelCount);
         // TODO 知识点: 将块内平均颜色应用到块内的每个像素
         for (let iy = startY; iy < startY + blockSize && iy < imageHeight && iy < offMaxY; iy++) {
           for (let ix = startX; ix < startX + blockSize && ix < imageWidth && ix < offMaxX; ix++) {
             const index = (iy * imageWidth + ix) * 4; // 4 像素点数据包括RGB通道的分量值及图片透明度
             dataArray[index] = averageR;
             dataArray[index + 1] = averageG;
             dataArray[index + 2] = averageB;
           }
         }
       }
     }
     return dataArray;
     } catch (error) {
       logger.error(MosaicConstants.TAG, 'applyMosaic fail,err:' + error);
       return undefined;
     }
    }

高性能知识点

本示例使用了taskpool执行耗时操作以达到性能优化。

工程结构&模块类型

imagemosaic                               // har类型  
|---view  
|   |---ImageMosaicView.ets               // 视图层-图片马赛克场景  
|---constants  
|   |---MosaicConstants.ets               // 常量  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值