HarmonyOS 鸿蒙Next图片编辑功能自定义裁剪画笔马赛克三合一

HarmonyOS 鸿蒙Next图片编辑功能自定义裁剪画笔马赛克三合一 找了很多三方库,基本都不能满足这三个功能,都是单独的使用方式,有没有大神做过这些,提供一点demo

3 回复

参考下下面的demo,裁剪是使用PixelMap进行图片编辑,马赛克和画笔是使用Canvas实现的,保存图片是通过componentSnapshot截图的组件。

马赛克模块:img: ImageBitmap = new ImageBitmap(“Images/canvas.png”)是用于覆盖的马赛克图片,图片放在src/main/ets/Images/canvas.png

const TAG: string = 'imageEdit_Encode';
const context = getContext(this);

@Entry
@Component
export struct DrawView {
  @State pixelMap?: image.PixelMap = undefined;
  private regionItem: RegionItem = new RegionItem(0,0);
  viewModel: DrawViewModel = new DrawViewModel();
  @State imgHeight: number = 300;
  @State imgWidth: number = 300;
  @State isPixelMapChange: boolean = false;
  build() {
    Column({ space: 8 }) {
      Button('选择图片').onClick(() => {
        try {
          let uris: Array<string> = [];
          let PhotoSelectOptions = new picker.PhotoSelectOptions();
          PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
          PhotoSelectOptions.maxSelectNumber = 1;
          let photoPicker = new picker.PhotoViewPicker();
          photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
            uris = PhotoSelectResult.photoUris;
            this.testMethod(uris[0])
            let file = fileIo.openSync(uris[0], fileIo.OpenMode.READ_ONLY);
            console.info('file fd: ' + file.fd);
            let buffer = new ArrayBuffer(4096);
            let readLen = fileIo.readSync(file.fd, buffer);
            console.info('readSync data to file succeed and buffer size is:' + readLen);
            const imageSource: image.ImageSource = image.createImageSource(file.fd);
            let decodingOptions: image.DecodingOptions = {
              editable: true,
              desiredPixelFormat: 3,
            }
            imageSource.createPixelMap(decodingOptions, (err: BusinessError, pixelMap: image.PixelMap) => {
              if (err !== undefined) {
                console.error(`Failed to create pixelMap.code is ${err.code},message is ${err.message}`);
              } else {
                this.pixelMap = pixelMap
                console.info('Succeeded in creating pixelMap object.');
              }
            })
          }).catch((err: BusinessError) => {
            console.error(`Invoke photoPicker.select failed, code is ${err.code}, message is ${err.message}`);
          })
        } catch (error) {
          let err: BusinessError = error as BusinessError;
          console.error('photoPicker failed with err: ' + JSON.stringify(err));
        }
      })
      Text('请选择画笔类型')
        .fontColor(Color.Red)
        .textAlign(TextAlign.Center)
        .fontSize(30)
        .width('80%')
        .margin('10%')
      Column(){
        Stack(){
          if(this.isPixelMapChange){
            Image(this.pixelMap)
              .objectFit(ImageFit.Auto)
              .width('100%')
              .height('100%')
          } else {
            Image(this.pixelMap)
              .objectFit(ImageFit.Auto)
              .width('100%')
              .height('100%')
          }
          Canvas(this.viewModel.context)
            .width('100%')
            .height('100%')
            .onAreaChange((oldValue: Area, newValue: Area) => {
              this.viewModel.canvasAreaChange(newValue)
            })
            .gesture(
              GestureGroup(GestureMode.Exclusive,
                PanGesture()
                  .onActionStart((event: GestureEvent) => {
                    let finger: FingerInfo = event.fingerList[0];
                    if (finger == undefined) { return }
                    let x = finger.localX
                    let y = finger.localY
                    this.viewModel.moveStart(x,y)
                  })
                  .onActionUpdate((event: GestureEvent) => {
                    let finger: FingerInfo = event.fingerList[0];
                    if (finger == undefined) { return }
                    let x = finger.localX
                    let y = finger.localY
                    this.viewModel.moveUpdate(x,y)
                  })
                  .onActionEnd((event: GestureEvent) => {
                    let finger: FingerInfo = event.fingerList[0];
                    if (finger == undefined) { return }
                    this.viewModel.moveEnd()
                  })
              )
            )
        }.id('canvas')
      }
      .width('100%')
      .height('60%')
      .alignItems(HorizontalAlign.Center)
      .justifyContent(FlexAlign.Center)
      Flex({direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround }) {
        if(this.pixelMap){
          Button('裁剪').onClick(() => {
            banner(this.pixelMap!, this.regionItem.x, this.regionItem.y).then(() => {
              this.isPixelMapChange = !this.isPixelMapChange;
            });
          })
        }
        Button('普通画笔')
          .onClick(() => {
            this.viewModel.drawModel.pathType = DrawPathType.pen
          })
        Button('马赛克画笔')
          .onClick(() => {
            this.viewModel.drawModel.pathType = DrawPathType.pattern
          })
        Button('清除内容')
          .onClick(() => {
            this.viewModel.clearPath()
          })
      }
      Button("保存图片").onClick(async () => {
        AlertDialog.show({
          title: '保存图片',
          message: '确定要保存图片吗',
          alignment: DialogAlignment.Center,
          primaryButton: {
            value: '保存',
            action: () => {
              if (this.pixelMap) {
                componentSnapshot.get('canvas', async (error: Error, pixmap: image.PixelMap) => {
                  let packOpts: image.PackingOption = { format: "image/jpeg", quality: 100 };
                  const imagePackerApi = image.createImagePacker();
                  imagePackerApi.packing(pixmap, packOpts).then(async (buffer: ArrayBuffer) => {
                    try {
                      let helper = photoAccessHelper.getPhotoAccessHelper(context)
                      let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg')
                      let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
                      await fs.write(file.fd, buffer);
                      // 关闭文件
                      await fs.close(file.fd);
                    } catch (error) {
                      console.error("保存失败:" + JSON.stringify(error))
                    }
                  }).catch((error: BusinessError) => {
                    console.error('保存失败,失败原因: ' + error);
                  })
                })
              }
            }
          },
          secondaryButton: {
            value: '取消',
            action: () => {
              console.info(TAG, `cancel`);
            }
          }
        })
      })
    }
  }
  async testMethod(uri: string) {
    let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
    predicates.equalTo(photoAccessHelper.PhotoKeys.URI, uri);
    let fetchOptions: photoAccessHelper.FetchOptions = {
      fetchColumns: [
        "title",
        photoAccessHelper.PhotoKeys.WIDTH,
        photoAccessHelper.PhotoKeys.HEIGHT,
        photoAccessHelper.PhotoKeys.DURATION,
        photoAccessHelper.PhotoKeys.SIZE,
        photoAccessHelper.PhotoKeys.DISPLAY_NAME,
        photoAccessHelper.PhotoKeys.ORIENTATION,//旋转
        photoAccessHelper.PhotoKeys.PHOTO_SUBTYPE],
      predicates: predicates
    };
    try {
      let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext(this));
      let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> =
        await phAccessHelper.getAssets(fetchOptions);
      let photoAsset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();
      let title :photoAccessHelper.PhotoKeys = photoAccessHelper.PhotoKeys.TITLE;
      let paTitle = photoAsset.get(title.toString())
      let orientation = photoAsset.get("orientation") as number
      this.regionItem = new RegionItem(photoAsset.get("width") as number,photoAsset.get("height") as number);
      console.debug("ccTestShow","title: "+paTitle.toString()+"-width:"+photoAsset.get("height")+"-height:"+photoAsset.get("width")+"--"+"ORIENTATION: "+photoAsset.get("orientation"))
      fetchResult.close();
      return photoAsset
    } catch (err) {
      console.error('getAssets failed with err: ' + err);
    }
    return
  }
}

class RegionItem {
  /**
   * width coordinate.
   */
  x: number;

  /**
   * height coordinate.
   */
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

export async function cropCommon(pixelMap: PixelMap, cropWidth: number, cropHeight: number, cropPosition: RegionItem) {
  pixelMap.crop({
    size: {
      width: cropWidth,
      height: cropHeight
    },
    x: cropPosition.x,
    y: cropPosition.y
  });
}

// 传入image.PixelMap、图片width、图片height三个参数,获取到裁剪后的图片宽度和高度后将参数传入cropCommon方法
export async function banner(pixelMap: PixelMap, width: number, height: number) {
  if (width <= height) {
    const cropWidth = width;
    const cropHeight = Math.floor(width * 0.75);
    const cropPosition = new RegionItem(0, Math.floor((height - cropHeight) / 2));
    cropCommon(pixelMap, cropWidth, cropHeight, cropPosition);
    return;
  }
  if (width * 0.75 >= height) {
    const cropWidth = Math.floor(height / 0.75);
    const cropHeight = height;
    const cropPosition = new RegionItem(Math.floor((width - cropWidth) / 2), 0);
    cropCommon(pixelMap, cropWidth, cropHeight, cropPosition);
    return;
  }
  const cropWidth = width;
  const cropHeight = Math.floor(width * 0.75);
  const cropPosition = new RegionItem(0, Math.floor((height - cropHeight) / 2));
  cropCommon(pixelMap, cropWidth, cropHeight, cropPosition);
}

更多关于HarmonyOS 鸿蒙Next图片编辑功能自定义裁剪画笔马赛克三合一的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


开源网站上收录了UI、系统接口、Web、创新特性等场景化鸿蒙示例DEMO,开发中可以参考:
https://gitee.com/scenario-samples/demo-index

HarmonyOS 鸿蒙Next的图片编辑功能集成了自定义裁剪、画笔和马赛克三合一功能。用户可以在图库或文件管理应用中直接编辑图片,支持调整图片的亮度、对比度、饱和度等基本参数。裁剪功能允许用户自由选择裁剪区域或使用预设比例进行裁剪。画笔工具提供了多种颜色和粗细选择,支持手绘涂鸦和标注。马赛克功能则提供了不同粗细的马赛克笔刷,用于遮盖图片中的敏感信息。这些功能均通过鸿蒙系统的分布式能力和高效的图形处理技术实现,确保了操作的流畅性和响应速度。

回到顶部