基于canvas的平移缩放进行面的绘制

做项目中有基于高德地图进行点、线、面的绘制,使用的是高德地图封装的SDK,实现难度不大。其实点、线、面的绘制原理都是在canvas上绘制的,于是想尝试不基于高德地图,直接在canvas上进行面的绘制,并支持适应canvas画布的放大缩小与拖动。所绘制的面的类型有多边形、矩形、圆形。

# 示例demo
http://121.4.85.237:7781

1.定义canvas元素

首先是先创建一个canvas元素,让canvas元素的宽高与父容器的宽高一样,并支持响应式。还有给canvas元素添加一些监听事件。

// HTML部分
<div class="canvas-container w-full h-full relative">
  <canvas
    id="canvas"
    :style="{ width: cssWidth + 'px', height: cssHeight + 'px' }"
  ></canvas>
</div>

// JS部分
mounted() {
  this.canvas = this.firstResize();
  this.canvas.addEventListener("mousedown", this.addPoint);
  this.canvas.addEventListener("mouseup", this.upPoint);
  this.canvas.addEventListener("mousemove", this.mousePoint);
  this.canvas.addEventListener("contextmenu", this.endPoint);
  this.canvas.addEventListener("mousewheel", this.wheelCanvas);
  if (this.canvas.getContext) {
    this.ctx = this.canvas.getContext("2d");
  }
  window.addEventListener("resize", () => {
    this.commomResize();
  });
},
    
firstResize() {
  const canvas = document.getElementById("canvas");
  const container = document.getElementsByClassName("canvas-container")[0];
  canvas.width = container.offsetWidth;
  canvas.height = container.offsetHeight;
  this.cssWidth = container.offsetWidth;
  this.cssHeight = container.offsetHeight;
  return canvas;
},
// canvas大小响应式
commomResize() {
  this.firstResize();
  this.$nextTick(() => {
    // ...处理其他逻辑
  });
},

2.canvas画布的平移

这个canvas元素画布是支持画布平移和缩放的,所以需要先处理画布平移与缩放逻辑,再进行面的绘制。

在点击画布并拖拽鼠标后,此时如果没有开启面的绘制,就是要进行画布的平移操作。

在画布平移操作过程中,需要不断重绘画布上所有的面。

canvasDraw: {
  isDrawCanvas: false,
  point: [],
  origin: { x: 0, y: 0 }, // canvas的原点,默认为(0, 0)
  offset: { x: 0, y: 0 }, // canvas画布的偏移量
  originScale: 1, // canvas画布当前缩放比,默认为1
  preScale: 1,    // canvas画布上一次的缩放比
},

核心代码如下所示:

windowToCanvas(x, y) {
  const box = this.canvas.getBoundingClientRect();
  return {
    x: x - box.left,
    y: y - box.top,
  };
},
// canvas的mousedown事件
addPoint(e) {
  if (this.isDraw) {
    // 面的绘制
  } else {
    // 画布的平移
    const res = this.windowToCanvas(e.clientX, e.clientY);
    this.canvasDraw.isDrawCanvas = true;
    this.canvasDraw.point = res;
  }
},
// canvas的mousemove事件
mousePoint(e) {
  if (this.isDraw && this.point.length) {
    // ...
  } else if (this.canvasDraw.isDrawCanvas) {
    // 开启canvas画布的平移
    this.translateCnvas(e);
  }
},
// 移动canvas画布
translateCnvas(e) {
  const res = this.windowToCanvas(e.clientX, e.clientY);
  this.canvasDraw.offset.x =
    this.canvasDraw.origin.x + res.x - this.canvasDraw.point.x;
  this.canvasDraw.offset.y =
    this.canvasDraw.origin.y + res.y - this.canvasDraw.point.y;
  this.drawReset(); // 画布上所有面的重绘
},

在canvas画布有平移和缩放操作时,清除画布不能再使用clearRect()方法,此方法无法控制所需清除画布的大小。

此情况下可以重设canvas尺寸,就会清空并重置canvas内置的translate、scale等。

canvas属性重置后需要重新赋值。

// 重绘
drawReset() {
  this.clearCanvas(); // 清空画布
  this.ctx.translate(this.canvasDraw.offset.x, this.canvasDraw.offset.y);
  this.ctx.scale(this.canvasDraw.originScale, this.canvasDraw.originScale);
  this.drawOtherArea(); // 画布上绘制所有面
},
// 重设canvas尺寸会清空并重置canvas内置的scale等
clearCanvas() {
  this.canvas.width = this.cssWidth;
},

3.canvas画布的缩放

画布缩放思路同画布平移类似,这里自己设置的每次放大都是放大1.25倍,每次缩小都是缩小0.8倍。

// canvas的mousewheel事件
wheelCanvas(e) {
  e.preventDefault();
  const res = this.windowToCanvas(e.clientX, e.clientY);
  this.canvasDraw.preScale = this.canvasDraw.originScale;
  if (e.wheelDelta > 0) {
    // 放大
    this.canvasDraw.originScale = this.canvasDraw.originScale * 1.25;
  } else {
    // 缩小
    this.canvasDraw.originScale = this.canvasDraw.originScale * 0.8;
  }
  this.canvasDraw.offset.x =
    res.x -
    ((res.x - this.canvasDraw.offset.x) * this.canvasDraw.originScale) /
      this.canvasDraw.preScale;
  this.canvasDraw.offset.y =
    res.y -
    ((res.y - this.canvasDraw.offset.y) * this.canvasDraw.originScale) /
      this.canvasDraw.preScale;
  this.drawReset();
  this.canvasDraw.origin.x = this.canvasDraw.offset.x;
  this.canvasDraw.origin.y = this.canvasDraw.offset.y;
},

4.绘制面

绘制面时要考虑此时的canvas画布是否已经平移缩放过。多边形面的类型是polygon,矩形面的类型是rectangle,圆形面的类型是circle

当canvas画布已经平移或缩放过再进行面的绘制时,需要考虑到此时画布的偏移量与缩放大小。

windowToTransformCanvas(x, y) {
  const box = this.canvas.getBoundingClientRect();
  return {
    x:
      (x - box.left - this.canvasDraw.origin.x) /
      this.canvasDraw.originScale,
    y:
      (y - box.top - this.canvasDraw.origin.y) /
      this.canvasDraw.originScale,
  };
},
// canvas的mousedown事件
addPoint(e) {
  if (this.isDraw) {
    const res = this.windowToTransformCanvas(e.clientX, e.clientY);
    this.point.push({
      x: res.x,
      y: res.y,
    });
  }
},
// canvas的mousemove事件
mousePoint(e) {
  if (this.isDraw && this.point.length) {
    const res = this.windowToTransformCanvas(e.clientX, e.clientY);
    if (["rectangle"].includes(this.drawType)) {
      const w = Math.abs(this.point[0].x - res.x);
      const h = Math.abs(this.point[0].y - res.y);
      const left = this.point[0].x > res.x ? res.x : this.point[0].x;
      const top = this.point[0].y > res.y ? res.y : this.point[0].y;
      this.drawRectangle(left, top, w, h);
    } else if (["circle"].includes(this.drawType)) {
      const w = Math.abs(this.point[0].x - res.x);
      const h = Math.abs(this.point[0].y - res.y);
      const r = Math.sqrt(w * w + h * h);
      this.drawCircle(this.point[0], r);
    } else {
      this.drawPolygon(
        this.point.concat({
          x: res.x,
          y: res.y,
        })
      );
    }
  }
},

绘制矩形

// 绘制矩形
drawRectangle(left, top, w, h) {
  this.drawOtherArea();
  this.ctx.beginPath();
  this.ctx.strokeStyle = "red";
  this.ctx.fillStyle = "rgba(161, 195, 255, 0.5)";
  this.ctx.fillRect(left, top, w, h);
  this.ctx.strokeRect(left, top, w, h);
},

绘制圆形

// 绘制圆形
drawCircle(point, r) {
  this.drawOtherArea();
  this.ctx.beginPath();
  this.ctx.strokeStyle = "red";
  this.ctx.fillStyle = "rgba(161, 195, 255, 0.5)";
  this.ctx.arc(point.x, point.y, r, 0, 2 * Math.PI);
  this.ctx.fill();
  this.ctx.stroke();
},

绘制多边形

// 绘制多边形
drawPolygon(list) {
  this.drawOtherArea();
  this.ctx.beginPath();
  this.ctx.strokeStyle = "red";
  this.ctx.fillStyle = "rgba(161, 195, 255, 0.5)";
  this.ctx.moveTo(list[0].x, list[0].y);
  list.forEach((v) => {
    this.ctx.lineTo(v.x, v.y);
  });
  this.ctx.fill();
  this.ctx.closePath();
  this.ctx.stroke();
},

绘制之前绘制好的所有区域

drawOtherArea(params) {
  this.arrList.forEach((item, index) => {
    this.ctx.beginPath();
    this.ctx.strokeStyle = "red";
    this.ctx.fillStyle = "rgba(161, 195, 255, 0.5)";
    if (["circle"].includes(item.type)) {
      this.ctx.arc(item.data[0].x, item.data[0].y, item.r, 0, 2 * Math.PI);
      this.ctx.fill();
      this.ctx.stroke();
    } else if (["rectangle"].includes(item.type)) {
      this.ctx.fillRect(item.left, item.top, item.w, item.h);
      this.ctx.strokeRect(item.left, item.top, item.w, item.h);
    } else {
      this.ctx.moveTo(item.data[0].x, item.data[0].y);
      item.data.forEach((v) => {
        this.ctx.lineTo(v.x, v.y);
      });
      this.ctx.fill();
      this.ctx.closePath();
      this.ctx.stroke();
    }
  });
},

5.面的重心计算

有时需要在面的重心处添加一些文字提示,如面的名称、面的大小等。

矩形和圆形的重心点很容易计算,下面主要描述一下多边形的重心点计算。

# 多边形重心计算步骤
1. 求每个剖分出来的三角形的重心。重心计算公式:x = (x1+x2+x3)/3 ; y = (y1+y2+y3)/3
2. 求每个剖分出来的三角形的面积。三角形面积:area =(p0.x * p1.y +p1.x * p2.y +p2.x * p0.y -p0.x * p2.y -p1.x * p0.y -p2.x*p1.y)/2
3. 求多边形的面积。
4. 多边形重心横坐标 = 多边形剖分的每一个三角形重心的横坐标 * 该三角形的面积之和 / 多边形总面积
   多边形重心纵坐标 = 多边形剖分的每一个三角形重心的纵坐标 * 该三角形的面积之和 / 多边形总面积

// 多边形的重心点(几何中心点)
getPolygonAreaCenter(points) {
  let sum_x = 0;
  let sum_y = 0;
  let sum_area = 0;
  let p1 = points[1];
  for (let i = 2; i < points.length; i++) {
    const p2 = points[i];
    const area = this.Area(points[0], p1, p2);
    sum_area += area;
    sum_x += (points[0].x + p1.x + p2.x) * area;
    sum_y += (points[0].y + p1.y + p2.y) * area;
    p1 = p2;
  }
  return {
    x: sum_x / sum_area / 3,
    y: sum_y / sum_area / 3,
  };
},
// 计算面积,这里是拆成一个个三角形进行的计算
Area(p0, p1, p2) {
  let area =
    (p0.x * p1.y +
      p1.x * p2.y +
      p2.x * p0.y -
      p0.x * p2.y -
      p1.x * p0.y -
      p2.x * p1.y) /
    2;
  return area;
},

6.总结

实现起来有几点需要注意:

1.画布的平移缩放逻辑处理,这里要精准计算出偏移量,逻辑是有一点复杂的。

2.画布的清除,这里使用clearRect()方法是清除不干净画布的。

3.在画布平移缩放后的面的绘制,这里要注意数据的处理逻辑,处理不对所绘制的面会与鼠标点击的位置产生偏移。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实现SurfaceView的缩放平移,可以通过以下步骤进行操作: 1. 创建一个SurfaceView并设置好SurfaceHolder.Callback回调函数。 2. 在SurfaceHolder.Callback的surfaceCreated()方法中,获取SurfaceView的宽度和高度,并创建一个Bitmap对象来存储SurfaceView的内容。 3. 在SurfaceHolder.Callback的surfaceChanged()方法中,根据SurfaceView的宽度和高度,将Bitmap对象的大小设置为SurfaceView的大小。 4. 在SurfaceHolder.Callback的surfaceDestroyed()方法中,释放Bitmap对象的内存空间。 5. 在SurfaceView的onTouchEvent()方法中,实现手势缩放平移,并调用draw()方法重新绘制SurfaceView。 6. 在SurfaceView的draw()方法中,将Bitmap对象绘制在SurfaceView上。 下是一个简单的示例代码: ``` public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private Bitmap mBitmap; private Canvas mCanvas; private float mScale = 1.0f; private float mTranslateX = 0.0f; private float mTranslateY = 0.0f; private float mLastX; private float mLastY; public MySurfaceView(Context context) { super(context); mHolder = getHolder(); mHolder.addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); } @Override public void surfaceDestroyed(SurfaceHolder holder) { mBitmap.recycle(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = event.getX(); mLastY = event.getY(); break; case MotionEvent.ACTION_MOVE: float deltaX = event.getX() - mLastX; float deltaY = event.getY() - mLastY; mTranslateX += deltaX; mTranslateY += deltaY; mLastX = event.getX(); mLastY = event.getY(); break; case MotionEvent.ACTION_POINTER_DOWN: if (event.getPointerCount() == 2) { float distance = distance(event); mScale = distance / distance(event.getX(0), event.getY(0), event.getX(1), event.getY(1)); } break; case MotionEvent.ACTION_POINTER_UP: if (event.getPointerCount() == 2) { mScale = 1.0f; } break; } draw(); return true; } private void draw() { Canvas canvas = mHolder.lockCanvas(); if (canvas != null) { canvas.drawColor(Color.WHITE); canvas.drawBitmap(mBitmap, mTranslateX, mTranslateY, null); mHolder.unlockCanvasAndPost(canvas); } } private float distance(MotionEvent event) { return distance(event.getX(0), event.getY(0), event.getX(1), event.getY(1)); } private float distance(float x1, float y1, float x2, float y2) { float deltaX = x2 - x1; float deltaY = y2 - y1; return (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY); } } ``` 在上的代码中,我们实现了手势缩放平移功能,并在draw()方法中将Bitmap对象绘制在SurfaceView上。在onTouchEvent()方法中,我们检测到手势事件后,更新缩放平移的参数,并调用draw()方法重新绘制SurfaceView。注意,我们使用lockCanvas()方法获取Canvas对象来绘制SurfaceView的内容,使用unlockCanvasAndPost()方法将Canvas对象提交到SurfaceView上显示。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值