js使用canvas实现画roi功能,并实现交集并集差集操作,附源码

效果概览

支持圆形,矩形,旋转矩形绘制,鼠标像素拾取,图片缩放,图片拖拽,像素测量,roi交集并集补集输出
TODO:实现自由路径绘制,与后台交互数据
gif

实现原理

交集并集差集使用像素做运算,使用0代表没有像素,1代表有像素,然后再做运算

    // 计算交集
    calculateIntersection(shape1, shape2) {
        return shape1.map((pixel, index) => pixel && shape2[index] ? 1 : 0);
    }

    // 计算并集
    calculateUnion(shape1, shape2) {
        return shape1.map((pixel, index) => pixel || shape2[index] ? 1 : 0);
    }

    // 计算差集
    calculateDifference(shape1, shape2) {
        return shape1.map((pixel, index) => pixel && !shape2[index] ? 1 : 0);
    }

canvas事件实现

使用两个canvas,使用隐藏的OffscreenCanvas来判断鼠标命中的是哪个元素,需要把shape同时画到两个canvas中,获取隐藏canvas中的像素值就知道命中哪一个

事件分发器

class EventSimulator {
    constructor() {
      // 初始化事件监听器映射对象
      this.listenersMap = {};
      // 初始化最后的鼠标按下和移动的 ID
      this.lastDownId = null;
      this.lastMoveId = null;
    }
  
    // 添加事件动作
    addAction(action, evt) {
      const { type, id } = action;
  
      // 如果是鼠标移动事件
      if (type === ActionType.Move) {
        // 触发 mousemove 事件
        this.fire(id, EventNames.mousemove, evt);
        
        // 如果存在最后的移动 ID 且与当前 ID 不同
        if (this.lastMoveId && this.lastMoveId !== id) {
          // 触发 mouseleave 事件
          this.fire(this.lastMoveId, EventNames.mouseleave, evt);
          // 触发 mouseenter 事件
          this.fire(id, EventNames.mouseenter, evt);
        }
      }
  
      // 如果是鼠标按下事件
      if (type === ActionType.Down) {
        // 触发 mousedown 事件
        this.fire(id, EventNames.mousedown, evt);
      }
  
      // 如果是鼠标释放事件
      if (type === ActionType.Up) {
        // 触发 mouseup 事件
        this.fire(id, EventNames.mouseup, evt);
  
        // 如果最后的按下 ID 等于当前 ID,则触发 click 事件
        if (this.lastDownId === id) {
          this.fire(id, EventNames.click, evt);
        }
      }
  
      // 更新最后的移动和按下 ID
      if (type === ActionType.Move) {
        this.lastMoveId = action.id;
      } else if (type === ActionType.Down) {
        this.lastDownId = action.id;
      }
    }
  
    // 添加事件监听器
    addListeners(id, listeners) {
      this.listenersMap[id] = listeners;
    }
  
    // 触发事件
    fire(id, eventName, evt) {
      // 检查是否有对应 ID 和事件名称的监听器,如果有则依次执行监听器函数
      if (this.listenersMap[id] && this.listenersMap[id][eventName]) {
        this.listenersMap[id][eventName].forEach((listener) => listener(evt));
      }
    }
}

shape控制点实现

每个shape都有对应的控制点,控制点也绘制在OffscreenCanvas中,通过添加事件来控制shape

旋转矩形的控制点实现

handleCtrlPointMove(x,y,evts){
        //判断拖动的哪个点
        //顺时针方向
        if(evts.activateId==this.ctrlDotId[0]||evts.activateId==this.ctrlDotId[1]||evts.activateId==this.ctrlDotId[2]||evts.activateId==this.ctrlDotId[3]){//左上
            let p2 = getNextPoint(this.x,this.y,20,this.phi);
            let p1 = {x:this.x,y:this.y};
            let p3 = {x:x,y:y};
            let dist1=distanceToLine(p3,p1,p2);

            let phi2=radianToVerticalAngle(this.phi);
            let p22 = getNextPoint(this.x,this.y,20,phi2);
            let dist2=distanceToLine(p3,p1,p22);

            this.w=dist1*2;
            this.h=dist2*2;  
        }else if(evts.activateId==this.ctrlDotId[4]){//旋转
            var dx = x-this.x;
            var dy = y-this.y;
            this.phi=-Math.atan2(dy,dx)
        }

        evts.redraw();
        
    }

画布拖拽实现

使用ctx的setTransform实现

画布缩放实现

使用ctx的setTransform实现

this.ctx.setTransform(CanvasStatus.scale,0,0,CanvasStatus.scale,CanvasStatus.offset.x,CanvasStatus.offset.y);

shape移动实现

通过改变shape的内部属性重绘shape实现

class Circle extends BaseShape{
    constructor(opts = {}) {
        super();
        this.type=ShapeType.circle;
        Object.assign(this, {
            x: 0,
            y: 0,
            radius: 0,
            strokeWidth: 1,
            strokeColor: '#e6a23c',
            fillColor: '#fff'
        }, opts);
    }

    handleCtrlPointMove(x,y,evts){
        //最小10个像素
        let diffX=x-this.x;
        this.radius=Math.max(diffX,10);
        if(diffX>4){
            evts.redraw();
        }
    }

    drawMask(ctx){
        const x = this.x;
        const y = this.y;
        const radius = this.radius;
        let strokeColor = this.makStrokeColor;
        let fillColor = this.maskFillColor;
        const strokeWidth = this.strokeWidth;
        //ctx.save();
        ctx.beginPath();
        ctx.fillStyle = fillColor;
        ctx.strokeStyle = strokeColor;
        ctx.lineWidth = strokeWidth;
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();
        ctx.stroke();
        //ctx.restore();
    }

    toShape(shapesOperator){
        return shapesOperator.createShapeCircle(this.x,this.y,this.radius);
    }
    }

详细实现

TODO

源码下载

https://download.csdn.net/download/isyoungboy/89072580?spm=1001.2014.3001.5503

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用 React 和 Canvas 实现方框的例子: ```javascript import React, { useState, useRef, useEffect } from 'react'; function BoxCanvas() { const canvasRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); const [startX, setStartX] = useState(null); const [startY, setStartY] = useState(null); const [endX, setEndX] = useState(null); const [endY, setEndY] = useState(null); useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); function drawBox() { if (isDrawing) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.beginPath(); ctx.rect(startX, startY, endX - startX, endY - startY); ctx.stroke(); } } drawBox(); }, [isDrawing, startX, startY, endX, endY]); function handleMouseDown(e) { setIsDrawing(true); setStartX(e.nativeEvent.offsetX); setStartY(e.nativeEvent.offsetY); setEndX(e.nativeEvent.offsetX); setEndY(e.nativeEvent.offsetY); } function handleMouseMove(e) { if (isDrawing) { setEndX(e.nativeEvent.offsetX); setEndY(e.nativeEvent.offsetY); } } function handleMouseUp() { setIsDrawing(false); } return ( <canvas ref={canvasRef} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} style={{ border: '1px solid black' }} /> ); } export default BoxCanvas; ``` 这个例子中,我们使用了 `useRef` 和 `useEffect` 来获取和绘制 Canvas 元素,并使用了 `useState` 来跟踪鼠标事件和绘制状态。当用户按下鼠标左键时,我们会记录起点坐标 `startX` 和 `startY`,当用户拖动鼠标时,我们会更新终点坐标 `endX` 和 `endY`。在每次更新 `startX`、`startY`、`endX` 或 `endY` 时,我们都会重新绘制 Canvas 元素,以更新方框的位置和大小。 注意,这个例子中我们只实现空心的方框,如果需要实心的方框,可以使用 `ctx.fillRect()` 方法替换 `ctx.rect()` 方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值