鸿蒙PixelMap(Bitmap)使用

958 阅读4分钟

鸿蒙PixelMap(Bitmap)使用

前言

上一篇文章在本地视频里面获取了第一帧的图片,得到的是一个PixelMap,其实就是安卓里面的Bitmap,在上篇文章里面我还将这个Bitmap保存到了文件中。

这篇文章的内容和PixelMap相关,我就直接连着发了,就是一些对PixelMap的操作。

PixelMap官方文档

PixelMap也没什么好说的,感觉比安卓的Bitmap少了好多东西,当然也可能我了解的不多,看官方文档吧:

使用PixelMap完成图像变换

使用PixelMap完成位图操作

配合Canvas使用

在安卓中,我们对Bitmap的使用经常带着Canvas,在鸿蒙中找了我好久,终于找到了同样的东西,就是OffscreenCanvas,这个也可以去看官方文档:

Canvas开发指导

其实说一千道一万,直接写个例子就很简单了:

  /**
   * 根据水印文字,生成斜着排列多层mask的遮罩图片
   *
   * @param w
   * @param h
   * @param mark
   * @param alpha
   * @returns
   */
  static createTextWatermark(w: number, h: number, mark: string, alpha: number) {
    // 通过 OffscreenCanvasRenderingContext2D 绘制水印
    const offScreenCanvas = new OffscreenCanvas(w, h);
    const offScreenContext: OffscreenCanvasRenderingContext2D = offScreenCanvas.getContext('2d');

    // 透明度
    offScreenContext.globalAlpha = alpha / 100;

    offScreenContext.textAlign = 'start';
    offScreenContext.textBaseline = 'bottom';
    offScreenContext.fillStyle = '#000000';

    // 设置字体大小
    offScreenContext.font = '60px sans-serif';

    // 添加文字阴影
    offScreenContext.shadowBlur = 20;
    offScreenContext.shadowColor = '#F3F3F3';

    // 在左边的中间位置开始添加水印
    let cols = 20;
    let rows = 10;

    // 测量宽度
    let markWidth = offScreenContext.measureText(mark).width + 100;

    // 弧度
    let v = 45 * (Math.PI / 180);

    // 旋转后宽高
    let chunkHeight = Math.sin(v) * markWidth;
    let chunkWidth = Math.cos(v) * markWidth;

    offScreenContext.save();
    offScreenContext.rotate(-v);
    for (let k = -5; k < cols; k = (k + 2)) {
      for (let i = -5; i < rows; i = (i + 2)) {
        // 绘制文本
        offScreenContext.fillText(mark, chunkWidth * k, chunkHeight * i);
      }
    }
    offScreenContext.restore();

    // 生成新位图
    let bmp = offScreenContext.getPixelMap(0, 0, offScreenCanvas.width, offScreenCanvas.height);

    return bmp;
  }

这是我写的一个绘制文字水印的例子,效果不是很好,但是凑合看吧。可以看到像save、rotate、restore方法都有,和Android的canvas还是挺像的,不过这明显就是前端的canvas,凑合用吧。

上面是文字的,下面在手写个和PixelMap相关的水印的:

  /**
   * 根据水印图片,生成斜着排列多层mask的遮罩图片
   *
   * @param w
   * @param h
   * @param mask
   * @param alpha
   * @returns
   */
  static createImgWatermark(w: number, h: number, mask: PixelMap, alpha: number): PixelMap {
    let maskInfo = mask.getImageInfoSync();

    // 旋转
    mask.rotateSync(-45);

    // 缩放
    let scale = w / 2.0 / maskInfo.size.width;
    mask.scaleSync(scale, scale);

    // 通过 OffscreenCanvasRenderingContext2D 绘制水印
    const offScreenCanvas = new OffscreenCanvas(w, h);
    const offScreenContext: OffscreenCanvasRenderingContext2D = offScreenCanvas.getContext('2d');

    // 设置透明度
    offScreenContext.globalAlpha = alpha / 100;

    let count = 0;
    while (count * w < h) {
      offScreenContext.drawImage(mask, w / 4 - maskInfo.size.width / 2, count * w);
      offScreenContext.drawImage(mask, w / 4 * 3 - maskInfo.size.width / 2, (count + 0.5) * w);
      count++;
    }

    // 生成新位图
    let bmp = offScreenContext.getPixelMap(0, 0, offScreenCanvas.width, offScreenCanvas.height);

    return bmp;
  }

其实都差不多,没啥区别。

获取PixelMap

这个获取PixelMap可能比较多,我这写了两个方法,一个从本地文件获取,一个从资源文件获取:

  /**
   * 从文件中获取位图
   *
   * @param path 本地路径
   * @returns
   */
  async getPixelMapFromFile(path: string): Promise<PixelMap> {
    return new Promise(async (resolve, reject) => {
      try {
        // 从文件创建
        let file = await fs.open(path, fs.OpenMode.READ_ONLY);
        const imageSource: image.ImageSource = image.createImageSource(file.fd);

        // 配置
        let decodingOptions: image.DecodingOptions = {
          editable: true,
          desiredPixelFormat: 3,
        }

        // 获取位图
        imageSource.createPixelMap(decodingOptions, (err: BusinessError, pixelMap: PixelMap) => {
          if (err !== undefined) {
            LogUtil.e("createPixelMap fail: " + err.message);
            reject("createPixelMap fail: " + err.message);
          } else {
            resolve(pixelMap);
            LogUtil.d("getPixelMapFromFile success!")
          }
        })
      } catch (e) {
        LogUtil.e("pixelMap2Base64 error: " + e)
        reject(e)
      }
    });
  }

  /**
   * 从rawfile中获取位图
   *
   * @param path 本地路径
   * @returns
   */
  async getPixelMapFromRawFile(context: Context, name: string): Promise<PixelMap> {
    return new Promise(async (resolve, reject) => {
      try {
        // 获取resourceManager资源管理器
        const resourceMgr = context.resourceManager;
        const fileData = await resourceMgr.getRawFileContent(name);

        // 获取图片的ArrayBuffer
        const buffer = fileData.buffer;
        const imageSource = image.createImageSource(buffer);

        // 配置
        let decodingOptions: image.DecodingOptions = {
          editable: true,
          desiredPixelFormat: 3,
        }

        // 创建pixelMap并进行简单的旋转和缩放
        // 获取位图
        imageSource.createPixelMap(decodingOptions, (err: BusinessError, pixelMap: PixelMap) => {
          if (err !== undefined) {
            LogUtil.e("createPixelMap fail: " + err.message);
            reject("createPixelMap fail: " + err.message);
          } else {
            resolve(pixelMap);
            LogUtil.d("getPixelMapFromFile success!")
          }
        })
      } catch (e) {
        LogUtil.e("pixelMap2Base64 error: " + e)
        reject(e)
      }
    });
  }

PixelMap导出

导出的功能上一篇文章也写了,这里再贴一下:

  /**
   * 保存pixelMap,返回路径
   * @param pm
   * @returns
   */
  private async savePixelMap(context: Context, pm: PixelMap): Promise<string> {
    if (pm === null) {
      LogUtil.e('传入的pm为空');
      return '';
    }

    try {
      return await this.packToFile(context, pm);
    } catch (err) {
      LogUtil.e("TAG", '保存文件失败,err=' + JSON.stringify(err));
      return '';
    }
  }

  private async packToFile(context: Context, pixelMap: PixelMap): Promise<string> {
    let fPath: string = context.cacheDir + '/image/' + this.getTimeStr() + '.jpg';
    let writeFd: fs.File = await fs.open(fPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);

    let opts : image.PackingOption = { format: "image/jpeg", quality: 100};
    const imagePacker = image.createImagePacker();
    await imagePacker.packToFile(pixelMap, writeFd.fd, opts);
    fs.closeSync(writeFd.fd);

    return fPath;
  }

  private getTimeStr() {
    const now: Date = new Date();
    const year: number = now.getFullYear();
    const month: number = now.getMonth() + 1;
    const day: number = now.getDate();
    const hours: number = now.getHours();
    const minutes: number = now.getMinutes();
    const seconds: number = now.getSeconds();
    return `${year}${month}${day}_${hours}${minutes}${seconds}`;
  }

PixelMap和Base64

有时候还要处理下base图片相关的内容,这里贴一下我用到的几个:

  async saveBase64Image(base64ImageData: string, context: Context): Promise<string> {
    try {
      let base64Result = this.dealBase64Str(base64ImageData)
      let file = this.createFile(context)
      let bufferImage = buffer.from(base64Result, 'base64')
      await fs.write(file.fd, bufferImage.buffer)
      fs.closeSync(file.fd)
      return file.path;
    } catch (e) {
      LogUtil.e("saveBase64Image error: " + JSON.stringify(e));
    }
    return ""
  }

  private dealBase64Str(base64Data: string): string {
    let imageData: string
    if (base64Data.startsWith("data")) {
      const base64Split: string[] = base64Data.split(",")
      if (base64Split.length !== 2) {
        throw new Error(`Illegal base64 data`)
      }
      imageData = base64Split[1].trim()
    } else {
      imageData = base64Data
    }
    return imageData
  }

  /**
   * 将pixelMap转成base64格式
   *
   * @param pixelMap
   * @returns
   */
  async pixelMap2Base64(pixelMap: PixelMap): Promise<string> {
    return new Promise((resolve, reject) => {
      try {
        // 转换成base64
        const imagePackerApi: image.ImagePacker = image.createImagePacker();
        let packOpts: image.PackingOption = { format: 'image/jpeg', quality: 100 };
        imagePackerApi.packing(pixelMap, packOpts).then((data: ArrayBuffer) => {
          let buf: buffer.Buffer = buffer.from(data);
          let base64 = 'data:image/jpeg;base64,' + buf.toString('base64', 0, buf.length);
          resolve(base64);
        });
      } catch (e) {
        LogUtil.e("pixelMap2Base64 error: " + e)
        reject(e)
      }
    });
  }

  private createFile(context: Context) {
    let fPath: string = context.cacheDir + '/image/' + this.getTimeStr() + '.jpg';
    let dir = context.cacheDir + '/image';
    if (!fs.accessSync(dir)) {
      fs.mkdirSync(dir)
    }
    let file = fs.openSync(fPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
    return file
  }

小结

简单地介绍了下鸿蒙里面PixelMap(Bitmap)地使用,给出了两个和安卓Canvas使用类似地例子,也给出了一下PixelMap常用到地工具方法。