canvas进阶——实现连续平滑的曲线

简介: 前言大家好,我是Fly, canvas真是个强大的东西,每天沉迷这个无法自拔, 可以做游戏,可以对图片处理,后面会给大家分享一篇,canvas实现两张图片找不同的功能, 听着是不是挺有意思的, 有点像游戏 「找你妹」,但是这都不是本篇文章想要表达的重点,读完今天这篇文章,你可以学到什么呢「Canvas」 实现一个简单的画版小工具「Canvas」 画出平滑的曲线, 这是本篇文章的重点这时候有人问我她??, 我的心里没有她的,只有你们「coder」, 下面一起学习吧,预计阅读5分钟。canvas实现一个画版小工具因为也比较简单,我大概说下思路:首先我对canvas 画布坚监听3

前言



大家好,我是Fly, canvas真是个强大的东西,每天沉迷这个无法自拔, 可以做游戏,可以对图片处理,后面会给大家分享一篇,canvas实现两张图片找不同的功能, 听着是不是挺有意思的, 有点像游戏 「找你妹」,但是这都不是本篇文章想要表达的重点,读完今天这篇文章,你可以学到什么呢


  1. 「Canvas」 实现一个简单的画版小工具


  1. 「Canvas」 画出平滑的曲线, 这是本篇文章的重点


这时候有人问我她??, 我的心里没有她的,只有你们「coder」, 下面一起学习吧,预计阅读5分钟。


canvas实现一个画版小工具



因为也比较简单,我大概说下思路:


  1. 首先我对canvas 画布坚监听3个事件, 分别是「mouseMove,mouseDown,mouseUp」 三个事件, 同时创建了isDown 这个变量, 用来标记当前画图是不是开启


  1. 当我们按下鼠标 也就是「mouseDown」 事件, 表示开始画笔,有一个初始的点, 并把「isDown」 设置为「true」, 然后紧着呢开始移动, 可以确定直线的端点, 然后再把直线的端点设置为下一条直线的起始点, 不断地重复这个过程, 「mousueUp」「isDown」 这个变量设置为「false」, 同时清空开始点和结束点


  1. 通过「mouseMove」事件不断采集鼠标经过的坐标点,当且仅当「isDown」「true」(即处于书写状态)时将当前的点通过「canvas」「LineTo」方法与前面的点进行连接、绘制;


代码如下:


class board {
        constructor() {
          this.canvas = document.getElementById('canvas')
          this.canvas.addEventListener('mousemove', this.move.bind(this))
          this.canvas.addEventListener('mousedown', this.down.bind(this))
          this.canvas.addEventListener('mouseup', this.up.bind(this))
          this.ctx = this.canvas.getContext('2d')
          this.startP = null
          this.endP = null
          this.isDown = false
          this.setLineStyle()
        }
        setLineStyle() {
          this.ctx.strokeStyle = 'red'
          this.ctx.lineWidth = 1
          this.ctx.lineJoin = 'round'
          this.ctx.lineCap = 'round'
        }
        move(e) {
          if (!this.isDown) {
            return
          }
          this.endP = this.getPot(e)
          this.drawLine()
          this.startP = this.endP
        }
        down(e) {
          this.isDown = true
          this.startP = this.getPot(e)
        }
        getPot(e) {
          return new Point2d(e.offsetX, e.offsetY)
        }
        drawLine() {
          if (!this.startP || !this.endP) {
            return
          }
          this.ctx.beginPath()
          this.ctx.moveTo(this.startP.x, this.startP.y)
          this.ctx.lineTo(this.endP.x, this.endP.y)
          this.ctx.stroke()
          this.ctx.closePath()
        }
        up(e) {
          this.startP = null
          this.endP = null
          this.isDown = false
        }
      }
      new board()


point2d是我自己写的一个2d点的一个类,不清楚的同学可以看我前几篇文章, 这里就不重复阐述了。我们看下gif:


image.png


画板


细心的同学可能发现,画的线折线感比较强,出现这个本质的原因——  「就是我们画出的线其实是一个多段线polyline, 连接两个点之间的线是直线」


如何画出平滑的曲线


想起曲线,就不得不提到贝塞尔曲线了,我之前的文章有系统的介绍过贝塞尔曲线,以及贝塞尔曲线方程的推导过程—— 传送门


canvas 肯定是支持贝塞尔曲线的quadraticCurveTo(cp1x, cp1y, x, y) , 主要是一个起始点, 一个终点,一个控制点。其实这里可以用一个巧妙的算法去解决这样的问题。


获取二阶贝塞尔曲线信息的算法



假设我们在鼠标移动的过程中有A、B、C、D、E、F、G、这6个点。如何画出平滑的曲线呢,  我们取B点和C点的中点B1 作为第一条贝塞尔曲线的终点,B点作为控制点。如图:


image.png


贝塞尔曲线


接下来呢 算出 cd 的中点 c1 以 B1 为起点, c点为控制点, c1为终点画出下面图形:


image.png

连续曲线图


然后后面按照这样的步骤不断画下去,就可以获得平滑的曲线了。理论基础我们明白了, 我们改造上面的画线的方法:


实现画出平滑的曲线



上面涉及到求两个点的中间坐标:其实两个坐标的x 和y 分别除以2:代码如下:


getMid(p1, p2) {
  const x = (p1.x + p2.x) / 2
  const y = (p1.y + p2.y) / 2
  return new Point2d(x, y)
}


我们画出二阶贝塞尔曲线至少所示需要3个点, 所以我们需要数组去存放移动过程中所有的点的信息。


我先实现画贝塞尔曲线的方法:


drawCurve(controlP, endP) {
  this.ctx.beginPath()
  this.ctx.moveTo(this.startP.x, this.startP.y)
  this.ctx.quadraticCurveTo(controlP.x, controlP.y, endP.x, endP.y)
  this.ctx.stroke()
  this.ctx.closePath()
}


然后在修改move 中的事件


move(e) {
  if (!this.isDown) {
    return
  }
  this.endP = this.getPot(e)
  this.points.push(this.endP)
  if (this.points.length >= 3) {
    const [controlP, endP] = this.points.slice(-2)
    const middle = this.getMid(controlP, endP)
    this.drawCurve(controlP, middle)
    this.startP = middle
  }
}


这里实现永远取倒数后两个点,然后画完贝塞尔曲线后再将 这个贝塞尔的终点设置为开始点方便下次画。这样是能保证画出连续的贝塞尔曲线的。


我们看下gif 图:


image.png


贝塞尔曲线

相关文章
|
23天前
|
数据可视化
绘制KOLMOGOROV-SMIRNOV KS检验图ECDF经验累积分布函数曲线
绘制KOLMOGOROV-SMIRNOV KS检验图ECDF经验累积分布函数曲线
|
5月前
|
算法 数据可视化 C#
C# | Chaikin算法 —— 计算折线对应的平滑曲线坐标点
本文将介绍一种计算折线对应的平滑曲线坐标点的算法。该算法使用Chaikin曲线平滑处理的方法,通过控制张力因子和迭代次数来调整曲线的平滑程度和精度。通过对原始点集合进行切割和插值操作,得到平滑的曲线坐标点集合。实验结果表明,该算法能够有效地平滑折线,并且具有较高的精度和可控性。
67 0
C# | Chaikin算法 —— 计算折线对应的平滑曲线坐标点
|
8月前
|
UED
线性绘制在NSDT 3D场布中的应用
线性绘制应该被视为一种工具,用于优化空间布局和视觉效果,以达到特定的设计目标。
196 0
|
11月前
|
API 图形学
【unity每日一记】—线性差值函数以及平滑阻尼的运用和实践(Lerp AND SmoothDamp)
【unity每日一记】—线性差值函数以及平滑阻尼的运用和实践(Lerp AND SmoothDamp)
182 0
|
计算机视觉
第三周作业:matlab将一张图片进行顺时针旋转 20°,做水平镜像,做错切变换,缩小图像处理,并采用双线性插值方法
简介:第三周作业:matlab将一张图片进行顺时针旋转 20°,做水平镜像,做错切变换,缩小图像处理,并采用双线性插值方法
第三周作业:matlab将一张图片进行顺时针旋转 20°,做水平镜像,做错切变换,缩小图像处理,并采用双线性插值方法
|
机器学习/深度学习 算法 计算机视觉
数字图像处理实验(一)|图像的基本操作和基本统计指标计算{图像读取imread、图像写入imwrite、图像显示imshow、图像的相关统计量|均值、方差、大小尺寸裁减旋转|}(附实验代码和实验截图)
数字图像处理实验(一)|图像的基本操作和基本统计指标计算{图像读取imread、图像写入imwrite、图像显示imshow、图像的相关统计量|均值、方差、大小尺寸裁减旋转|}(附实验代码和实验截图)
340 0
数字图像处理实验(一)|图像的基本操作和基本统计指标计算{图像读取imread、图像写入imwrite、图像显示imshow、图像的相关统计量|均值、方差、大小尺寸裁减旋转|}(附实验代码和实验截图)
|
前端开发 JavaScript
使用SVG实现动态分布的圆环发散路径动画
使用SVG实现动态分布的圆环发散路径动画
227 0
|
数据安全/隐私保护
绘制混合密度函数图以及添加分位数线
这里我主要使用 ggridges 包中的 stat_density_ridges()。这个包的介绍,小编以前做过一期内容,可见:ggridges包—峰峦图详细介绍。读者需要进一步阅读课件这篇博文[1],以及一些案例[2]。
62 0
绘制混合密度函数图以及添加分位数线
第五周作业:利用matlab将图片依次进行,平移、镜像、旋转、0.5 倍缩小,然后分别将变换后的图像进行二维傅里叶变换。
简介:第五周作业:利用matlab将图片依次进行,平移、镜像、旋转、0.5 倍缩小,然后分别将变换后的图像进行二维傅里叶变换。
第五周作业:利用matlab将图片依次进行,平移、镜像、旋转、0.5 倍缩小,然后分别将变换后的图像进行二维傅里叶变换。
【MATLAB】进阶绘图 ( 进阶绘图展示 | 对数图 | semilogx 半对刻度数图 | semilogy 半对数刻度图 | loglog 双对数刻度图 )
【MATLAB】进阶绘图 ( 进阶绘图展示 | 对数图 | semilogx 半对刻度数图 | semilogy 半对数刻度图 | loglog 双对数刻度图 )
184 0
【MATLAB】进阶绘图 ( 进阶绘图展示 | 对数图 | semilogx 半对刻度数图 | semilogy 半对数刻度图 | loglog 双对数刻度图 )