图片标注编辑平台搭建系列教程(4)——fabric几何定制渲染

背景

标注的几何,有时需要一些定制化的渲染样式,例如,线中间展示箭头,表示方向。本期教程教大家如何实现fabric几何定制化渲染。

带箭头的线

fabric提供了一些原生的几何,例如Point、Polyline、Polygon。同时提供了一些抽象的实体,如Object、Path。

如果使用原生的几何,可配置的样式是有限的。

比如Point的配置是:

{
    radius: 5,
    stroke: 'rgba(0,0,0,1)',
    strokeWidth: 2,
    fill: 'rgba(255,0,0,1)'
}

Polyline的配置是:

{
    stroke: 'rgba(0,0,0,1)',
    strokeWidth: 5
}

Polygon的配置是:

{
    fill: 'rgba(255,0,0,0,3)',
    strokeWidth: 1
}

可见这些线宽、线颜色、填充色等并不能实现箭头样式,需要对这些类进行扩展。

fabric提供了方法,可以扩展原生几何,代码如下:

export const NewPolyline = fabric.util.createClass(fabric.Polyline, {
  type: 'NewPolyline',

  initialize: function (points: any = [], options: any = {}) {
    this.callSuper('initialize', points, { ...options }); // 调用Polyline的初始化方法
  },
  
  _render: function (ctx: any) {
    // 自定义渲染,每次canvas.renderAll()都会触发该方法。
    this.callSuper('_render', ctx);
  }
}

此时我们得到了一个新的fabric几何类型:NewPolyline。其初始化参数和Polyline一致,points是点序,options里设置其样式。

而箭头样式,需要在_render方法里实现。_render方法可以拿到ctx,即canvas的实例,我们可以利用ctx进行各种绘制操作。

注意:在_render这个步骤,每个几何都有自己的坐标系,其坐标系原点是几何的外接矩形的中心点。

因此,我们每个坐标都需要减去当前几何的width/2和height/2,进行原点平移。

举个例子,比如我们有一条线,其参数如下:

{
    left: 10,
    top: 10,
    points: [{x: 0, y: 0}, {x: 5, y: 0}, {x: 5, y: 5}],
}

在坐标系中,如图,

left和top将要素的坐标系从O移动到了O',在此基础上,绘制折线[[0,0],[5,0],[5,5]]。

在渲染时,fabric又将坐标原点O'平移到外接矩形的中心点O''。

知道坐标系后,我们先来求线段的中点:

const points = this.get('points');
const width = this.get('width');
const height = this.get('height');

for (let i = 0; i < points.length; i++) {
  const midX = (points[i].x + points[i + 1].x) / 2 - width / 2;
  const midY = (points[i].y + points[i + 1].y) / 2 - height / 2;
  console.log(midX, midY);
}

// 结果:
// -2.5, -2.5
// 2.5, -2.5
// 2.5, 2.5

看懂上面的代码,你就可以以线段中心点为中心,画沿着线段的三角形了,代码如下:

for (let i = 0; i < points.length - 1; i++) {
      const midX = (points[i].x + points[i + 1].x) / 2 - width / 2;
      const midY = (points[i].y + points[i + 1].y) / 2 - height / 2;
      const rotate = Math.atan2(points[i + 1].y - points[i].y, points[i + 1].x - points[i].x);
      ctx.moveTo(midX, midY);
      const firstX = midX - (arrowWidth / 2) * Math.sin(rotate);
      const firstY = midY + (arrowWidth / 2) * Math.cos(rotate);
      ctx.lineTo(firstX, firstY);
      const secondX = midX + (arrowWidth / 2) * Math.sqrt(3) * Math.cos(rotate);
      const secondY = midY + (arrowWidth / 2) * Math.sqrt(3) * Math.sin(rotate);
      ctx.lineTo(secondX, secondY);
      const thirdX = midX + (arrowWidth / 2) * Math.sin(rotate);
      const thirdY = midY - (arrowWidth / 2) * Math.cos(rotate);
      ctx.lineTo(thirdX, thirdY);
      ctx.closePath();
      ctx.fill();
 }

效果图如下:

了解这个原理,你就可以利用canvas的绘制操作实现任何的自定义样式。

缩放控制线宽等宽

上一章我们讲到了,画布是可以拖拽和缩放的,本质上是修改canvas的transform。

在每次缩放后,canvas会调用renderAll方法,从而调用每个几何的_render方法。在_render内,我们需要重新计算strokeWidth:

const strokeWidth = 5;
const zoom = canvas.getZoom();
this.set({
    strokeWidth: strokeWidth / zoom
});

这样可以保证每次缩放后,线宽依然维持一个固定值。如果我们不修改线宽,则会被同样得缩放。

预告

下一章,我们详细聊一个极其隐蔽的问题:线居中渲染。

最近更新

  1. TCP协议是安全的吗?

    2024-03-30 16:34:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-30 16:34:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-30 16:34:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-30 16:34:02       18 阅读

热门阅读

  1. SpringBoot -- 整合SpringMVC

    2024-03-30 16:34:02       19 阅读
  2. 用静态工厂方法代替构造器

    2024-03-30 16:34:02       18 阅读
  3. AI每周更新:追踪人工智能研究进展

    2024-03-30 16:34:02       18 阅读
  4. 代码随想录算法训练营总结

    2024-03-30 16:34:02       18 阅读
  5. 【LeetCode】合并两个有序数组

    2024-03-30 16:34:02       15 阅读
  6. Python搜索算法——二分搜索

    2024-03-30 16:34:02       17 阅读
  7. 代码随想录算法训练营第三十二天|leetcode738题

    2024-03-30 16:34:02       18 阅读