@Component
export struct CropImageView {
@Require
handler: CropperController;
@Require
imageUri: string;
borderWidthVp: number = 1;
focusWidthVp: number = 250;
focusHeightVp: number = 250;
focusRadiusVp: number = 250 / 2;
cropBorderColor: string | number = "#AA808080";
maskColor: string | number = "#AF000000";
@Link
isLoading: boolean;
private maxScale: number = MAX_SCALE;
private minScale: number = 1;
private currentScale: number = 1;
private preScale = this.currentScale;
private config: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.config);
private cropRectPx = new Rect();
private originImageRectVp = new Rect();
private realImageRectVp = new Rect();
private convertContainer: [number, number] = [0, 0];
// @State
// private imageOffsetInfo: OffsetModel = new OffsetModel(0, 0);
// @State
// private imageRotateInfo: RotateModel = new RotateModel();

// 图片默认大小,即,与屏幕大小最适配的显示大小
@State
private imageDefaultSize: image.Size = { width: 0, height: 0 };
// 表示当前图片是根据宽度适配还是高度适配
@State
private imageWH: number = ImageFitTypeDefault;
// 图片原始宽高比
private imageWHRatio: number = 0;
private eventOffsetX: number = 0;
private eventOffsetY: number = 0;

private offsetX = 0;
private offsetY = 0;
private startOffsetX = 0;
private startOffsetY = 0;
private pntX = 0;
private pntY = 0;
private pinchCenterX = 0;
private pinchCenterY = 0;

// 使用矩阵变换对图形进行平移、旋转和缩放等。
@State
private matrix: matrix4.Matrix4Transit = matrix4.identity()
.translate({ x: this.offsetX, y: this.offsetY })
.scale({ x: this.currentScale, y: this.currentScale });

private updateMatrix(): void {
this.matrix = matrix4.identity()
.translate({ x: this.getUIContext().vp2px(this.offsetX), y: this.getUIContext().vp2px(this.offsetY) })
.scale({ x: this.currentScale, y: this.currentScale });
}

aboutToAppear(): void {
this.handler.bindCropHandler(async (path) => {

  const node = this.getUIContext().getFrameNodeById(VIEW_ID);
  const ctx = this.getUIContext().getHostContext();
  if (node == null || ctx == null || this.cropRectPx.width <= 0 || this.cropRectPx.height <= 0) {
    return false;
  }
  let bitmap: PixelMap | null = null;
  let tmpFile: fileIo.File | null = null;
  let packer: image.ImagePacker | null = null;
  try {
    //获取目标图
    bitmap = await this.getUIContext().getComponentSnapshot()
      .getWithUniqueId(node.getUniqueId(), {
        waitUntilRenderFinished: true,
        region: {
          left: this.cropRectPx.left,
          top: this.cropRectPx.top,
          right: this.cropRectPx.right,
          bottom: this.cropRectPx.bottom
        }
      });

    //输出为500*500尺寸图
    await bitmap.scale(500 / this.cropRectPx.width, 500 / this.cropRectPx.height);

    //保存图到临时文件中
    packer = image.createImagePacker();
    const avatarTmpDir = ctx.cacheDir + "/" + TMP_FOLDER_NAME;
    if (!await fileIo.access(avatarTmpDir)) {
      await fileIo.mkdir(avatarTmpDir);
    }

    const tmpPath = avatarTmpDir + "/tmp.jpg";
    if (await fileIo.access(tmpPath)) {
      await fileIo.unlink(tmpPath);
    }
    tmpFile = await fileIo.open(tmpPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);

    await packer.packToFile(bitmap, tmpFile.fd, { format: "image/jpeg", quality: 100 })
  } catch (e) {
    Log.d(e,"CropHandler error")
    return false;
  } finally {
    bitmap?.release();
    await FileUtil.closeFile(tmpFile);
    await packer?.release();
  }
  return true;
});

}

build() {
Stack() {
Stack() {
Image(this.imageUri)
.objectFit(ImageFit.Contain)
.width("100%")
.height("100%")
.draggable(false)
.autoResize(true)
.transform(this.matrix)
.onComplete((event) => {
if (event) {
this.initImage(event);
}
})
.onError((error) => {
this.handler.error?.(error.message);
this.isLoading = false;
})
}
.width("100%")
.height("100%")
.id(VIEW_ID)

  Canvas(this.context)
    .width("100%")
    .height("100%")
    .onReady(() => {
      console.log("canvas:"+this.context.width+" "+this.context.height)
      const centX = this.context.width / 2.0;
      const centY = this.context.height / 2.0;
      const left = this.getUIContext().vp2px(centX - this.focusRadiusVp);
      const top = this.getUIContext().vp2px(centY - this.focusRadiusVp);
      const right = this.getUIContext().vp2px(centX + this.focusRadiusVp);
      const bottom = this.getUIContext().vp2px(centY + this.focusRadiusVp);
      this.cropRectPx.set(left, top, right, bottom);

      this.context.fillStyle = this.maskColor;
      this.context.beginPath();
      this.context.moveTo(0, 0);
      this.context.lineTo(this.context.width, 0);
      this.context.lineTo(this.context.width, this.context.height);
      this.context.lineTo(0, this.context.height);
      this.context.closePath();

      this.context.arc(centX, centY, this.focusRadiusVp, 0, 2 * Math.PI, true);
      this.context.fill();

      this.context.strokeStyle = this.cropBorderColor;
      this.context.lineWidth = this.borderWidthVp;
      this.context.beginPath();
      this.context.arc(centX, centY, this.focusRadiusVp, 0, 2 * Math.PI, true);
      this.context.stroke();

      //删除
      this.context.lineWidth = 1;
      this.context.strokeStyle = Color.Red;
      this.context.strokeRect(this.realImageRectVp.left, this.realImageRectVp.top, this.realImageRectVp.width,
        this.originImageRectVp.height)

      this.context.beginPath();
      const c1 = (this.realImageRectVp.left+this.realImageRectVp.right)/2;
      const c2 = (this.realImageRectVp.top+this.realImageRectVp.bottom)/2
      console.log("中心点:"+centX+" "+centY+" === c1:"+c1+" c2: "+c2)
      this.context.arc(c1,c2, 2, 0, 2 * Math.PI, true);
      this.context.fill()
    })

  if (this.isLoading) {
    LoadingProgress()
      .color($r("app.color.color_accent"))
      .width(Constants.DEFAULT_WAIT_SIZE)
      .height(Constants.DEFAULT_WAIT_SIZE)
  }
}
.width("100%")
.height("100%")
.clip(true)
.gesture(
  GestureGroup(
    GestureMode.Parallel,

    PanGesture()
      .onActionStart(() => {
        this.startOffsetX = this.offsetX;
        this.startOffsetY = this.offsetY;
      })
      .onActionUpdate((event) => {
        this.offsetX = this.startOffsetX + event.offsetX / this.currentScale;
        this.offsetY = this.startOffsetY + event.offsetY / this.currentScale;

        this.updateMatrix();

        this.testDraw();
      })
  )
)

}

private testDraw() {
this.context.reset();
const centX = this.context.width / 2.0;
const centY = this.context.height / 2.0;
const left = this.getUIContext().vp2px(centX - this.focusRadiusVp);
const top = this.getUIContext().vp2px(centY - this.focusRadiusVp);
const right = this.getUIContext().vp2px(centX + this.focusRadiusVp);
const bottom = this.getUIContext().vp2px(centY + this.focusRadiusVp);
this.cropRectPx.set(left, top, right, bottom);

this.context.fillStyle = this.maskColor;
this.context.beginPath();
this.context.moveTo(0, 0);
this.context.lineTo(this.context.width, 0);
this.context.lineTo(this.context.width, this.context.height);
this.context.lineTo(0, this.context.height);
this.context.closePath();

this.context.arc(centX, centY, this.focusRadiusVp, 0, 2 * Math.PI, true);
this.context.fill();

this.context.strokeStyle = this.cropBorderColor;
this.context.lineWidth = this.borderWidthVp;
this.context.beginPath();
this.context.arc(centX, centY, this.focusRadiusVp, 0, 2 * Math.PI, true);
this.context.stroke();

this.context.lineWidth = 1;
this.context.strokeStyle = Color.Red;
this.convertContainer[0] = this.originImageRectVp.left;
this.convertContainer[1] = this.originImageRectVp.top;
const realPoint1 = this.matrix.transformPoint(this.convertContainer);

this.convertContainer[0] = this.originImageRectVp.right;
this.convertContainer[1] = this.originImageRectVp.bottom;
const realPoint2 = this.matrix.transformPoint(this.convertContainer);

this.realImageRectVp.set(realPoint1[0],realPoint1[1],realPoint2[0],realPoint2[1])
this.context.strokeRect(this.realImageRectVp.left, this.realImageRectVp.top, this.realImageRectVp.width,
  this.realImageRectVp.height)

this.context.beginPath();
const c1 = (this.realImageRectVp.left+this.realImageRectVp.right)/2;
const c2 = (this.realImageRectVp.top+this.realImageRectVp.bottom)/2

this.context.arc(c1,c2, 20, 0, 2 * Math.PI, true);
this.context.fill()

}

private midPointX = 0;
private midPointY = 0;
private viewWidthVp = 0;
private viewHeightVp = 0;
private contentWidthVp = 0;
private contentHeightVp = 0;

private initImage(event: ImageLoadResult) {
if (event.loadingStatus != 1) {
return;
}

this.viewWidthVp = this.getUIContext().px2vp(event.componentWidth);
this.viewHeightVp = this.getUIContext().px2vp(event.componentHeight);
this.contentWidthVp = this.getUIContext().px2vp(event.contentWidth);
this.contentHeightVp = this.getUIContext().px2vp(event.contentHeight);
this.midPointX = event.componentWidth / 2;
this.midPointY = event.componentHeight / 2;

const fitFocusScale =
  this.getScale(this.contentWidthVp, this.contentHeightVp, this.focusWidthVp, this.focusHeightVp, true);
this.maxScale = fitFocusScale * MAX_SCALE;

const fitViewScale =
  this.getScale(this.contentWidthVp, this.contentHeightVp, this.viewWidthVp, this.viewHeightVp, false);
const scale = fitViewScale > fitFocusScale ? fitViewScale : fitFocusScale;
// this.currentScale = scale;

// this.matrix = matrix4.identity().scale({ x: scale, y: scale });

this.isLoading = false;

const midX = this.viewWidthVp / 2;
const midY = this.viewHeightVp / 2;

this.originImageRectVp.set(midX - this.contentWidthVp / 2, midY - this.contentHeightVp / 2, midX + this.contentWidthVp / 2,
  midY + this.contentHeightVp / 2)
this.testDraw();

}

private getScale(bitmapWidth: number, bitmapHeight: number,
minWidth: number, minHeight: number, isMinScale: boolean) {
const scaleX = minWidth / bitmapWidth;
const scaleY = minHeight / bitmapHeight;
return isMinScale ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
}
}

(Untitled)