HarmonyOS 鸿蒙Next如何实现相册图片用户自定义区域裁剪功能

发布于 1周前 作者 yibo5220 来自 鸿蒙OS

HarmonyOS 鸿蒙Next如何实现相册图片用户自定义区域裁剪功能

1、是否有API可以调用实现相册图片裁剪功能,可缩放拖动选择区域裁剪

2、是否可以提供示例代码

2 回复

1.可以使用三方库ImageKnife:https://gitee.com/openharmony-tpc/ImageKnife

2.PixelMap提供crop方法裁剪,结合canvas可实现自定义区域裁剪,参考以下demo:

import image from '[@ohos](/user/ohos).multimedia.image';
import { resourceManager } from '[@kit](/user/kit).LocalizationKit';

interface RectPosition {
 x: number;
 y: number;
 height: number;
 width: number;
}

enum ActionType {
 topLeft,
 topRight,
 bottomLeft,
 bottomRight,
 move
}

interface Position {
 x: number;
 y: number;
}


interface InitPosition {
 x: number;
 y: number;
 width: number;
 height: number;
}

[@Entry](/user/Entry)
[@Component](/user/Component)
struct Index {
 [@Provide](/user/Provide) pixelMap: image.PixelMap | undefined = undefined;
 [@Provide](/user/Provide) pixelMapBackUp: image.PixelMap | undefined = undefined;
 [@Provide](/user/Provide) imageInfo: image.ImageInfo | undefined = undefined;
 private settings: RenderingContextSettings = new RenderingContextSettings(true);
 private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
 private settings2: RenderingContextSettings = new RenderingContextSettings(true);
 private canvasContext2: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings2);
 private settings3: RenderingContextSettings = new RenderingContextSettings(true);
 private canvasContext3: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings3);
 private actionType: ActionType = ActionType.move;
 private rotateOn: boolean = false
 [@State](/user/State) imageArea: RectPosition = {
   x: 0,
   y: 0,
   width: 0,
   height: 0
 };
 private touchPosition: Position = {
   x: 0,
   y: 0,
 };
 private initPosition: InitPosition = {
   x: 0,
   y: 0,
   width: 0,
   height: 0,
 }
 [@State](/user/State) isCrop: boolean = false
 [@State](/user/State) cropImageInfo: image.ImageInfo | undefined = undefined;
 [@State](/user/State) pixelMapChange: boolean = false
 [@State](/user/State) [@Watch](/user/Watch)('drawMask') clipRect: RectPosition = {
   x: 0,
   y: 0,
   height: 0,
   width: 0
 };

 build() {
   Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
     if (this.isCrop) {
       if (this.pixelMapChange) {
         Image(this.pixelMap)
           .width(px2vp(this.cropImageInfo?.size.width))
           .height(px2vp(this.cropImageInfo?.size.height))
           .margin({ top: '10%' })
       } else {
         Image(this.pixelMap)
           .width(px2vp(this.cropImageInfo?.size.width))
           .height(px2vp(this.cropImageInfo?.size.height))
           .margin({ top: '10%' })
       }

     } else {
       Canvas(this.canvasContext)
         .width(px2vp(this.imageInfo?.size.width))
         .height(px2vp(this.imageInfo?.size.height))
         .onReady(() => {
           this.drawImage()
         })
         .onAreaChange((value: Area, newVal: Area) => {
           // 获取图片位置xy
           this.initPosition.x = Math.round(newVal.position.x as number)
           this.initPosition.y = Math.round(newVal.position.y as number)
         })
       // 蒙层
       Canvas(this.canvasContext3)
         .position({
           x: this.initPosition.x,
           y: this.initPosition.y
         })
         .width(px2vp(this.imageInfo?.size.width))
         .height(px2vp(this.imageInfo?.size.height))
       // 裁剪框
       Canvas(this.canvasContext2)
         .position({
           x: this.clipRect.x,
           y: this.clipRect.y
         })
         .width(this.clipRect.width)
         .height(this.clipRect.height)
         .onReady(() => {
           this.drawClipImage()
         })
         .onTouch(event => {
           if (event.type === TouchType.Down) {
             this.isMove(event.target.area, event.touches[0]);
             this.touchPosition = {
               x: event.touches[0].screenX,
               y: event.touches[0].screenY
             }
           } else if (event.type === TouchType.Move) {
             let moveX = event.changedTouches[0].screenX - this.touchPosition.x;
             let moveY = event.changedTouches[0].screenY - this.touchPosition.y;
             this.touchPosition = {
               x: event.changedTouches[0].screenX,
               y: event.changedTouches[0].screenY
             }
             this.moveClipCanvas(moveX, moveY);
           }
         })
     }
     Row() {
       Image($rawfile('rotate.png'))
         .width(40)
         .height(40)
         .onClick(() => {
           this.rotateImage()
         })
     }
     .margin({ top: 50 })
     .height('7%')
     .width('100%')
     .padding(30)

     Row() {
       Image($rawfile('reset.png'))
         .width(40)
         .height(40)
         .onClick(() => {
           this.cancel()
         })
       Image($rawfile('crop.png'))
         .width(40)
         .height(40)
         .onClick(() => {
           this.clipImage()
         })
     }
     .margin({ top: 10 })
     .width('100%')
     .height('7%')
     .padding(30)
     .justifyContent(FlexAlign.SpaceBetween)
   }
   .width('100%')
   .height('100%')
   .backgroundColor('#000000')
 }

 // 旋转图片
 async rotateImage() {
   if (this.rotateOn) {
     await this.pixelMap?.rotate(90)
     const info = await this.pixelMap?.getImageInfo()
     this.cropImageInfo = info
     if (this.pixelMapChange) {
       this.pixelMapChange = false
     } else {
       this.pixelMapChange = true
     }
   }
 }

 // 取消剪切
 cancel() {
   this.pixelMap = this.pixelMapBackUp
   this.isCrop = false
   this.rotateOn = false
 }

 // 判断操作类型
 isMove(area: Area, touch: TouchObject) {
   if (touch.x < 60 && touch.y < 60) { // 左上角
     this.actionType = ActionType.topLeft
   } else if (touch.x < 60 && touch.y > (Number(area.height) - 60)) { // 左下
     this.actionType = ActionType.bottomLeft
   } else if (touch.x > Number(area.width) - 60 && touch.y < 60) { // 右上
     this.actionType = ActionType.topRight
   } else if (touch.x > Number(area.width) - 60 && touch.y > (Number(area.height) - 60)) { // 右下
     this.actionType = ActionType.bottomRight
   } else {
     this.actionType = ActionType.move
   }
 }

 // 绘制背景图
 async drawImage() {
   await this.initData('test.jpg')
   if (this.imageInfo != undefined) {
     this.canvasContext.drawImage(this.pixelMap, 0, 0, px2vp(this.imageInfo.size.width),
       px2vp(this.imageInfo.size.height));
     this.canvasContext.save();
   }
 }

 // 绘制蒙层
 drawMask() {
   this.canvasContext3.clearRect(0, 0, this.imageInfo?.size.width, this.imageInfo?.size.height);
   this.canvasContext3.fillStyle = 'rgba(0,0,0,0.7)';
   this.canvasContext3.fillRect(0, 0, px2vp(this.imageInfo?.size.width), px2vp(this.imageInfo?.size.height));
   this.canvasContext3.clearRect(this.clipRect.x - this.initPosition.x, this.clipRect.y - this.initPosition.y,
     this.clipRect.width, this.clipRect.height);
 }

 // 绘制裁剪框
 drawClipImage() {
   this.canvasContext2.clearRect(0, 0, this.clipRect.width, this.clipRect.height);
   this.canvasContext2.lineWidth = 6
   this.canvasContext2.strokeStyle = '#ffffff'
   this.canvasContext2.beginPath()

   this.canvasContext2.moveTo(0, 20)
   this.canvasContext2.lineTo(0, 0);
   this.canvasContext2.lineTo(20, 0);

   this.canvasContext2.moveTo(this.clipRect.width - 20, 0);
   this.canvasContext2.lineTo(this.clipRect.width, 0);
   this.canvasContext2.lineTo(this.clipRect.width, 20);

   this.canvasContext2.moveTo(0, this.clipRect.height - 20);
   this.canvasContext2.lineTo(0, this.clipRect.height);
   this.canvasContext2.lineTo(20, this.clipRect.height);

   this.canvasContext2.moveTo(this.clipRect.width - 20, this.clipRect.height);
   this.canvasContext2.lineTo(this.clipRect.width, this.clipRect.height);
   this.canvasContext2.lineTo(this.clipRect.width, this.clipRect.height - 20);
   this.canvasContext2.stroke()

   this.canvasContext2.beginPath();
   this.canvasContext2.lineWidth = 0.5;
   let height = Math.round(this.clipRect.height / 3);
   for (let index = 0; index <= 3; index++) {
     let y = index === 3 ? this.clipRect.height : height * index;
     this.canvasContext2.moveTo(0, y);
     this.canvasContext2.lineTo(this.clipRect.width, y);
   }
   let width = Math.round(this.clipRect.width / 3);
   for (let index = 0; index <= 3; index++) {
     let x = index === 3 ? this.clipRect.width : width * index;
     this.canvasContext2.moveTo(x, 0);
     this.canvasContext2.lineTo(x, this.clipRect.height);
   }
   this.canvasContext2.stroke();
 }

 // 获取pixelMap与imageInfo
 async initData(fileName: string) {
   const context: Context = getContext(this);
   const resourceMgr: resourceManager.ResourceManager = context.resourceManager;
   const fileData = await resourceMgr.getRawFileContent(fileName);
   const buffer = fileData.buffer;
   const imageSource: image.ImageSource = image.createImageSource(buffer);
   const pixelMap: image.PixelMap = await imageSource.createPixelMap()
   this.pixelMap = pixelMap
   this.pixelMapBackUp = pixelMap
   const imageInfo = await pixelMap.getImageInfo()
   this.imageInfo = imageInfo
   // 裁剪框初始位置
   this.initPosition.width = px2vp(Math.round(this.imageInfo.size.width))
   this.initPosition.height = px2vp(Math.round(this.imageInfo.size.height))
   this.clipRect.height = px2vp(this.imageInfo.size.height)
   this.clipRect.width = px2vp(this.imageInfo.size.width)
   this.clipRect.x = this.initPosition.x
   this.clipRect.y = this.initPosition.y
 }

 // 裁剪图片
 async clipImage() {
   let x = this.clipRect.x - this.initPosition.x;
   let y = this.clipRect.y - this.initPosition.y;
   console.log('x= ' + x + ' y = ' + y + 'height = ' + this.clipRect.height + 'width = ' + this.clipRect.width)
   await this.pixelMap?.crop({
     x: vp2px(x),
     y: vp2px(y),
     size: { height: vp2px(this.clipRect.height), width: vp2px(this.clipRect.width) }
   })
   this.cropImageInfo = await this.pixelMap?.getImageInfo();
   this.isCrop = true
   this.rotateOn = true
 }

 // 裁剪框位置和大小变化 初始位置为图片的初始坐标 移动的坐标
 moveClipCanvas(moveX: number, moveY: number) {
   let clipRect: RectPosition = {
     x: this.clipRect.x,
     y: this.clipRect.y,
     width: this.clipRect.width,
     height: this.clipRect.height
   }
   switch (this.actionType) {
     case ActionType.move:
       clipRect.x += moveX;
       clipRect.y += moveY;
       break;
     case ActionType.topLeft:
       clipRect.x += moveX;
       clipRect.y += moveY;
       clipRect.width += -moveX;
       clipRect.height += -moveY;
       break;
     case ActionType.topRight:
       clipRect.y += moveY;
       clipRect.width += moveX;
       clipRect.height += -moveY;
       break;
     case ActionType.bottomLeft:
       clipRect.x += moveX;
       clipRect.width += -moveX;
       clipRect.height += moveY;
       break;
     case ActionType.bottomRight:
       clipRect.width += moveX;
       clipRect.height += moveY;
       break;
     default:
       break;
   }

   // 偏移坐标小于初始位置
   if (clipRect.x < this.initPosition.x) {
     clipRect.x = this.initPosition.x;
   }

   if (clipRect.y < this.initPosition.y) {
     clipRect.y = this.initPosition.y;
   }

   // 横坐标限制位置
   if (clipRect.width + clipRect.x > this.initPosition.width + this.initPosition.x) {
     if (this.actionType === ActionType.move) {
       clipRect.x = this.initPosition.width + this.initPosition.x - clipRect.width;
     } else {
       clipRect.width = this.initPosition.width + this.initPosition.x - clipRect.x;
     }
   }

   // 纵坐标限制
   if (clipRect.height + clipRect.y > this.initPosition.height + this.initPosition.y) {
     if (this.actionType === ActionType.move) {

       clipRect.y = this.initPosition.height + this.initPosition.y - clipRect.height;
     } else {

       clipRect.height = this.initPosition.height + this.initPosition.y - clipRect.y;
     }
   }

   this.clipRect = {
     x: Math.round(clipRect.x),
     y: Math.round(clipRect.y),
     width: Math.round(clipRect.width),
     height: Math.round(clipRect.height)
   };
 }
}

HarmonyOS 鸿蒙Next实现相册图片用户自定义区域裁剪功能,可以通过以下方式实现:

首先,使用华为开发者提供的图片处理API,特别是pixelMap对象的crop方法。该方法允许根据输入的尺寸和位置对图片进行裁剪。开发者需要获取用户选定的裁剪区域坐标和尺寸,然后调用crop方法实现裁剪。

其次,为了提供更友好的用户体验,可以集成自定义的裁剪界面。利用Canvas组件、Image组件以及手势事件,开发者可以创建一个允许用户通过手势操作选择裁剪区域的界面。这样,用户可以直观地看到裁剪效果,并进行调整。

此外,确保应用已申请并获得了读取和写入图片或视频的权限,这是进行图片裁剪操作的前提。

最后,将裁剪后的图片保存到设备或进行进一步处理,以满足业务需求。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部