场景: 已知四个点,画出矩形,并且计算出每个中垂线,并加箭头,标识符,点击四个顶点还能拖拽进行变动。

android 四个点绘图 四个点的图_前端


.vue

<!-- canvas -->
<template>
    <div class="content">
        <div class="name-content" @click="clickName">设备一</div>
        <!-- 背景图 -->
        <img v-if="bgImg" :src="bgImg" alt="" class="img">
        <div v-else class="empty-img">暂无抓拍图片,请重新抓拍</div>

        <!-- 画布 -->
        <canvas ref="myCanvas" class="canvas" :width="swidth" :height="sheight"></canvas>
    </div>
</template>

<script>
import {isPointInPath, calcMidLine, judgeIntersect} from './canvas.js';

export default {
  props: {
  },

  data() {
    return {
        bgImg: 'http://snapshot/2ca59c085bf8.jpg',   //抓拍的图片(假得!)
        canvas: null, //canvas对象
        position: [],  //四边形四个点的坐标
        midPosition: [],  //中垂线坐标
        imgData: null,  //缓存四边形
        hasPoint: false,  //是否已经绘制了四个顶点
        isDragging: false,  //能否拖拽
        isDrawing: false,  //是否正在绘制
        isMoving: false,   //是否可一动四边形
        selectIdx: -1,  //当前选中的顶点下标
        point: [],  //点击四边形内部当下的点坐标
        swidth: 960,
        sheight: 540
    };
  },

  watch: {
  },
  mounted() {
      this.canvas = this.$refs.myCanvas;
      this.initData();
      this.bindEvent();
  },

  methods: {
      clickName() {
          console.log("this.position",this.position);     //拖拽以后四个顶点的坐标位置
      },
      initData() {
          this.getImg();
          this.getPc6Configuration();
      },
      /**获取抓拍图片 */
      getImg() {

      },
      /**获取pc6数据 */
      getPc6Configuration() {
          const arr = [{x:0.25,y:0.25},{x:0.75,y:0.25},{x:0.75,y:0.75},{x:0.25,y:0.75}];
          if(arr.length === 4) {
              this.position = arr.map(p => [p.x * this.swidth, p.y * this.sheight]);
              this.initDraw();
          }
      },

      /**初始化四边形 */
      initDraw(type = "",position = []) {
          if(type !== "draw") {
              position = this.position;
          }
          const ctx = this.canvas.getContext('2d');
          ctx.strokeStyle = "#FF0000";  //设置线颜色
          ctx.beginPath();  //开始路径绘制
          ctx.lineWidth = 3;
          position.forEach((p,idx)=> {
              if(idx === 0) {
                  ctx.moveTo(...p);
              } else {
                  ctx.lineTo(...p);
              }
          });
          ctx.closePath();
          ctx.stroke();  //进行线的着色,这时整条线才变得可见
          if(this.position.length < 4) {
              return ;
          }
          this.drawMidLine();
          this.imgData = ctx.getImageData(0,0,this.swidth,this.sheight);  //复制画布上指定矩形的像素数据,然后通过 putImageData() 将图像数据放回画布:======图片大小 960*540
          type === "drag" && this.drawPoint();
      },

      /**绘制中垂线 */
      drawMidLine() {
          this.position.forEach((p, idx) => {
              const mid = idx === 3 ? calcMidLine(p, this.position[0],this.position) : calcMidLine(p,this.position[idx+1],this.position);
              const ctx = this.canvas.getContext("2d");

              const headlen = 6;  //自定义箭头线的长度
              const theta = 45;  //自定义箭头线与直线的夹角,(45度数刚好)
              let arrowX,arrowY;  //箭头线终点坐标
                // 计算各角度和对应的箭头终点坐标
                const angle =
                    (Math.atan2(mid[0][1] - mid[1][1], mid[0][0] - mid[1][0]) * 180) /
                    Math.PI;   //返回从原点(0,0)到(x,y)点的线段与x轴正方向之间的平面角度(弧度值)
                const angle1 = ((angle + theta) * Math.PI) / 180;
                const angle2 = ((angle - theta) * Math.PI) / 180;
                const topX = headlen * Math.cos(angle1);
                const topY = headlen * Math.sin(angle1);
                const botX = headlen * Math.cos(angle2);
                const botY = headlen * Math.sin(angle2);

                ctx.beginPath();
                ctx.lineWidth = 3;
                ctx.strokeStyle = '#ffa51c';
                //画直线
                ctx.moveTo(...mid[0]);
                ctx.lineTo(...mid[1]);

                arrowX = mid[1][0] + topX;
                arrowY = mid[1][1] + topY;

                //画上边箭头线
                ctx.moveTo(arrowX,arrowY);
                ctx.lineTo(mid[1][0],mid[1][1]);

                arrowX = mid[1][0] + botX;
                arrowY = mid[1][1] + botY;

                //画下边箭头线
                ctx.lineTo(arrowX, arrowY);
                ctx.closePath();
                ctx.fillStyle = "#ffa51c";
                ctx.fill();
                //绘制文字
                const char = String.fromCharCode(65 + idx);  //将 Unicode 编码转为一个字符
                ctx.font = "16px Georgia";
                const xm = (mid[0][0] + mid[1][0]) / 2;
                const ym = (mid[0][1] + mid[1][1]) / 2;
                ctx.fillText(char, xm, ym);
                ctx.stroke();
                this.midPosition[idx] = [...mid, [xm, ym], [char]];
        });
        // console.log("this.midPosition",this.midPosition)
      },

      /**绘制四个顶点 */
      drawPoint() {
          this.hasPoint = true;
          const ctx = this.canvas.getContext("2d");
          this.position.forEach(p => {
              ctx.beginPath();
              ctx.lineWidth = 4;
              ctx.strokeStyle = "#FF0000";
              ctx.arc(...p, 4, 0, 2*Math.PI);
              ctx.fillStyle = '#FF0000';
              ctx.fill();
              ctx.stroke();
          });
      },

      /**监听鼠标事件 */
      bindEvent() {
        this.handleMouseUp();
        this.handleMouseDown();
        this.handleMouseMove();
      },
      /**停止拖拽 */
      handleMouseUp() {
          console.log("handleMouseUp-handleMouseUp-handleMouseUp");
        let _this = this;
        this.canvas.onmouseup = function() {
            _this.isDragging = false;
            _this.isMoving = false;
        };
        this.canvas.onmouseout = function() {
            _this.isDragging = false;
            _this.isMoving = false;
        }
      },

      /**鼠标按下事件 */
      handleMouseDown() {
        let _this = this;
        this.canvas.onmousedown = function(e) {
            e = e || event;
            const x = e.offsetX;
            const y = e.offsetY;
            //绘制图像
            if(_this.isDrawing) {  //有个按钮--绘制区域(未使用到)--有个一键清空canvas的效果
                _this.position.push([x,y]);
                _this.clearCanvas();
                _this.initDraw();
                if(_this.position.length === 4) {
                    _this.isDrawing = false;
                }
            } else {
                if(_this.position.length === 0) {
                    return ;
                }

                //判断点击的是哪个点
                _this.position.forEach((p,idx) => {
                    var line = Math.abs(Math.sqrt(Math.pow(p[0] - x,2) + Math.pow(p[1] - y,2)));  //函数返回一个数的平方根
                    if(line - 8 < 0) {
                        _this.isDragging = true;
                        _this.selectIdx = idx;
                    }
                });

                //判断是否点击了四边形内部
                if(isPointInPath(x, y, _this.position)) {
                    !_this.hasPoint && _this.drawPoint();
                    if(!_this.isDragging) {
                        _this.isMoving = true;
                        _this.point = [x, y];
                    }
                } else if(_this.hasPoint && !isPointInPath(x ,y, _this.position)) {
                    _this.clearCanvas("cache");
                    _this.hasPoint = false;
                }
               
            }
        }
      },

      /**拖拽重新绘制 */
      handleMouseMove() {
          let _this = this;
          this.canvas.onmousemove = function(e) {
              e = e || event;
              const x = e.offsetX;
              const y = e.offsetY;

              //手动绘制
              if(_this.isDrawing && _this.position.length > 0) {
                  _this.clearCanvas();
                  _this.initDraw("draw",_this.position.concat([[x,y]]));
              }
              //判断是否可以拖动顶点
              if(_this.isDragging) {
                  _this.position[_this.selectIdx] = [x, y];
                  _this.clearCanvas("drag");
                  _this.initDraw("drag");
              }
              //移动整个四边形
              if(_this.isMoving) {
                  _this.position.forEach(p => {
                    p[0] += x - _this.point[0];
                    p[1] += y - _this.point[1];
                  });
                  _this.position = [x, y];
                  _this.clearCanvas();
                  _this.initDraw("drag");
              }
          }
      },

      /**清空画布 */
      clearCanvas(type) {
        const ctx = this.canvas.getContext("2d");
        ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        type === "cache" && ctx.putImageData(this.imgData, 0, 0);
        if(type === "rest") {
            this.position = [];
        }
      }
  },
};
</script>

<style lang="scss" scoped>
.content {
    position: relative;
}
.name-content {
    height: 20px;
}
.img {
    width: 960px;
    height: 540px;
}
.empty-img {
   width: 960px;
   height: 540px;
   text-align: center;
   line-height: 540px;
   float: left; 
}
.canvas {
    position: absolute;
    top: 20px;
    left: 0;
}
</style>

.config.js

// 判断点是否在四边形内
export const isPointInPath = function (testx, testy, position) {
  let i = 0,
    j = 0,
    c = false;
  const nvert = 4;
  const vertx = position.map(p => p[0]);
  const verty = position.map(p => p[1]);

  for (i = 0, j = nvert - 1; i < nvert; j = i++) {
    if (
      verty[i] > testy != verty[j] > testy &&
      testx <
      ((vertx[j] - vertx[i]) * (testy - verty[i])) / (verty[j] - verty[i]) +
      vertx[i]
    ) {
      c = !c;
    }
  }
  return c;
};

// 中垂线长度
const LINE = 30;

// 计算中垂线的两个点坐标
export const calcMidLine = function (p1, p2, position) {   //两两的点 (连成一条线),并把四个点传过来
  // console.log("calcMidLine---p1, p2, position",p1, p2, position);
  let xm = (p1[0] + p2[0]) / 4;
  if(p2[0] - 2 * xm === 0) {   
    p1 = [p1[0] -1,p1[1]-1];
    xm = (p1[0] + p2[0]) / 4;
  }

  const ym = (p1[1] + p2[1]) / 4;
  const e =
    (4 * xm * xm + 4 * ym * ym - 2 * p2[0] * xm - 2 * ym * p2[1]) /
    (p2[0] - 2 * xm);
  const d = (2 * ym - p2[1]) / (p2[0] - 2 * xm);

  const a = d * d + 1;
  const b = 2 * d * (e + 2 * xm) + 4 * ym;
  const c = LINE * LINE - 4 * ym * ym - (e + 2 * xm) * (e + 2 * xm);

  const y1 = (b + Math.sqrt(b * b + 4 * a * c)) / 2 / a;
  const x1 = y1 * d - e;
  const y2 = (b - Math.sqrt(b * b + 4 * a * c)) / 2 / a;
  const x2 = y2 * d - e;

  const x = xm + (xm + (xm + (xm + x1 / 2) / 2) / 2) / 2;
  const y = ym + (ym + (ym + (ym + y1 / 2) / 2) / 2) / 2;
  const inner = isPointInPath(x, y, position);
  const point = inner
    ? [
      [x1, y1],
      [x2, y2]
    ]
    : [
      [x2, y2],
      [x1, y1]
    ];

  return point;
};

//判断两条线段是否相交
export const judgeIntersect = function (x1, y1, x2, y2, x3, y3, x4, y4) {
  if (
    !(
      Math.min(x1, x2) <= Math.max(x3, x4) &&
      Math.min(y3, y4) <= Math.max(y1, y2) &&
      Math.min(x3, x4) <= Math.max(x1, x2) &&
      Math.min(y1, y2) <= Math.max(y3, y4)
    )
  ) {
    return false;
  }
  const u = (x3 - x1) * (y2 - y1) - (x2 - x1) * (y3 - y1);
  const v = (x4 - x1) * (y2 - y1) - (x2 - x1) * (y4 - y1);
  const w = (x1 - x3) * (y4 - y3) - (x4 - x3) * (y1 - y3);
  const z = (x2 - x3) * (y4 - y3) - (x4 - x3) * (y2 - y3);
  return u * v <= 0.00000001 && w * z <= 0.00000001;
};