1

canvas 绘图技术与图片处理

 2 years ago
source link: https://gaohaoyang.github.io/2021/07/22/canvas-draw/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文将讲述一些 canvas 相关的绘图技术,其中包括:

  • 绘图 API

绘图 API

相关 api 可参考,这里不展开详讲。

下面举几个示例

代码如下:

<canvas id="mainCanvas" style="background-color: #fff;" width="800" height="400"></canvas>
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

if (canvas) {
  const context = canvas.getContext('2d')
  if (context) {
    const { offsetLeft, offsetTop } = canvas
    let x
    let y

    const mouseMoveHandler = (e: MouseEvent) => {
      x = e.pageX
      y = e.pageY
      x -= offsetLeft
      y -= offsetTop
      context.lineTo(x, y)
      context.lineCap = 'round'
      context.lineJoin = 'round'
      context.stroke()
    }

    canvas.addEventListener('mousedown', (e) => {
      context.beginPath()
      context.moveTo(e.pageX - offsetLeft, e.pageY - offsetTop)
      canvas.addEventListener('mousemove', mouseMoveHandler)
    })

    canvas.addEventListener('mouseup', () => {
      canvas.removeEventListener('mousemove', mouseMoveHandler)
    })
  }
}

demo 链接 https://gaohaoyang.github.io/canvas-practice/09-mouse-draw/

源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/09-mouse-draw/index.ts

二次贝塞尔曲线

具体 api 可参考文档 CanvasRenderingContext2D.quadraticCurveTo()

ctx.quadraticCurveTo(cpx, cpy, x, y);

它需要2个点。第一个点是控制点,第二个点是终点。起始点是当前路径最新的点,当创建二次贝赛尔曲线之前,可以使用 moveTo() 方法进行改变。

接下来实现一个 demo,用鼠标位置作为控制点,控制这个二次贝塞尔曲线。

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

if (canvas) {
  const context = canvas.getContext('2d')
  const { offsetLeft, offsetTop } = canvas

  const x0 = 300
  const y0 = 100
  const x1 = 600
  const y1 = 300

  if (context) {
    canvas.addEventListener('mousemove', (e) => {
      context.clearRect(0, 0, canvas.width, canvas.height)
      const x = e.pageX - offsetLeft
      const y = e.pageY - offsetTop

      context.beginPath()
      context.moveTo(x0, y0)
      context.quadraticCurveTo(x, y, x1, y1)
      context.stroke()
    })
  }
}

demo 链接 https://gaohaoyang.github.io/canvas-practice/10-quadratic/

源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/10-quadratic/index.ts

穿过控制点的二次贝塞尔曲线

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

if (canvas) {
  const context = canvas.getContext('2d')
  const { offsetLeft, offsetTop } = canvas

  const x0 = 300
  const y0 = 100
  const x1 = 600
  const y1 = 300

  if (context) {
    canvas.addEventListener('mousemove', (e) => {
      context.clearRect(0, 0, canvas.width, canvas.height)
      const x = e.pageX - offsetLeft
      const y = e.pageY - offsetTop

      const cpx = x * 2 - (x0 + x1) / 2
      const cpy = y * 2 - (y0 + y1) / 2

      context.beginPath()
      context.moveTo(x0, y0)
      context.quadraticCurveTo(cpx, cpy, x1, y1)
      context.stroke()
    })
  }
}
const cpx = x * 2 - (x0 + x1) / 2
const cpy = y * 2 - (y0 + y1) / 2

demo 链接 https://gaohaoyang.github.io/canvas-practice/11-quadratic-through/

源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/11-quadratic-through/index.ts

绘制平滑的曲线,用多个点控制

import Ball from '../common/Ball'

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

if (canvas) {
  const ctx = canvas.getContext('2d')

  if (ctx) {
    const points = []
    const num = 4

    for (let i = 0; i < num; i += 1) {
      const ball = new Ball(2)
      const x = Math.random() * canvas.width
      const y = Math.random() * canvas.height
      points.push({
        x,
        y,
      })
      ball.x = x
      ball.y = y
      ball.draw(ctx)
    }

    ctx.beginPath()
    ctx.moveTo(points[0].x, points[0].y)

    for (let i = 1; i < num - 2; i += 1) {
      const xAv = (points[i].x + points[i + 1].x) / 2
      const yAv = (points[i].y + points[i + 1].y) / 2
      ctx.quadraticCurveTo(points[i].x, points[i].y, xAv, yAv)
    }
    ctx.quadraticCurveTo(points[num - 2].x, points[num - 2].y, points[num - 1].x, points[num - 1].y)
    ctx.stroke()
  }
}

xAv, yAv 围边设置为循环中当前点和后续点的x,y坐标的平均值,这样就能绘制一条平滑的曲线了

demo 链接 https://gaohaoyang.github.io/canvas-practice/12-multi-quadratic/

源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/12-multi-quadratic/index.ts

闭合的多条曲线

import Ball from '../common/Ball'

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

if (canvas) {
  const ctx = canvas.getContext('2d')

  if (ctx) {
    const points = []
    const num = 4

    for (let i = 0; i < num; i += 1) {
      const ball = new Ball(2)
      const x = Math.random() * canvas.width
      const y = Math.random() * canvas.height
      points.push({
        x,
        y,
      })
      ball.x = x
      ball.y = y
      ball.draw(ctx)
    }

    const xAv1 = (points[0].x + points[num - 1].x) / 2
    const yAv1 = (points[0].y + points[num - 1].y) / 2

    ctx.beginPath()
    ctx.moveTo(xAv1, yAv1)

    for (let i = 0; i < num - 1; i += 1) {
      const xAv = (points[i].x + points[i + 1].x) / 2
      const yAv = (points[i].y + points[i + 1].y) / 2
      ctx.quadraticCurveTo(points[i].x, points[i].y, xAv, yAv)
    }
    ctx.quadraticCurveTo(points[num - 1].x, points[num - 1].y, xAv1, yAv1)
    ctx.stroke()
  }
}

demo 链接 https://gaohaoyang.github.io/canvas-practice/13-multi-quadratic-close/

源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/13-multi-quadratic-close/index.ts

图形与填充色

一般来说绘图顺序如下:

  • beginPath 开始绘制
  • moveTo 移动起点
  • lineStyle 线样式
  • fillStyle 填充样式
  • lineTo 或 quadraticCurveTo 等绘制曲线
  • closePath 闭合
  • fill 填充
  • stroke 描边

ctx.createLinearGradient(x0, y0, x1, y1)

createLinearGradient() 方法需要指定四个参数,分别表示渐变线段的开始和结束点。

ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)

arc() 是 Canvas 2D API 绘制圆弧路径的方法。 圆弧路径的圆心在 (x, y) 位置,半径为 r ,根据anticlockwise (默认为顺时针)指定的方向从 startAngle 开始绘制,到 endAngle 结束。

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

if (canvas) {
  const ctx = canvas.getContext('2d')

  if (ctx) {
    ctx.beginPath()
    const gradient = ctx.createLinearGradient(100, 100, 200, 200)
    gradient.addColorStop(0, '#ff0000')
    gradient.addColorStop(1, '#000000')
    ctx.fillStyle = gradient
    ctx.fillRect(100, 100, 100, 100)

    const gradient2 = ctx.createLinearGradient(200, 200, 300, 300)
    gradient2.addColorStop(0, '#ff0000')
    gradient2.addColorStop(0.6, '#008880')
    gradient2.addColorStop(1, '#000000')
    ctx.fillStyle = gradient2
    ctx.fillRect(200, 200, 100, 100)

    const gradient3 = ctx.createRadialGradient(500, 200, 0, 500, 200, 100)
    gradient3.addColorStop(0, '#000000')
    gradient3.addColorStop(1, '#ff0000')
    ctx.arc(500, 200, 100, 0, 2 * Math.PI)
    ctx.fillStyle = gradient3
    ctx.fill()
  }
}

效果如下:

demo 链接 https://gaohaoyang.github.io/canvas-practice/14-gradient/

源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/14-gradient/index.ts

有些场景,可能需要在 canvas 内绘制一张图片,接下来我们看看绘制图片的方式

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

if (canvas) {
  const ctx = canvas.getContext('2d')
  if (ctx) {
    const img = new Image()
    img.src = 'https://gw.alicdn.com/imgextra/i2/O1CN01gR6ymq1dfV5RmYxYk_!!6000000003763-2-tps-658-411.png'
    img.addEventListener('load', () => {
      ctx.drawImage(img, 0, 0, 658, 329, 0, 0, 800, 400)
    })
  }
}

demo 链接 https://gaohaoyang.github.io/canvas-practice/15-image/

源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/15-image/index.ts

接下来我们尝试将上述demo中的所有绿色像素移除,看看是什么效果

const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')

if (canvas) {
  const ctx = canvas.getContext('2d')
  if (ctx) {
    const img = new Image()
    img.src = '../assets/1.png'
    img.addEventListener('load', () => {
      ctx.drawImage(img, 0, 0, 658, 329, 0, 0, canvas.width, canvas.height)

      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
      const pixels = imageData.data
      for (let i = 0; i < pixels.length; i += 4) {
        pixels[i + 1] = 0
      }
      ctx.putImageData(imageData, 0, 0)
    })
  }
}

效果如下:

demo 链接 https://gaohaoyang.github.io/canvas-practice/16-image-pixel/

源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/16-image-pixel/index.ts

本文虽然没有涉及过多动画,但还是举例介绍了 canvas 2d 绘图的方式,并且介绍了如何加载图片与操作像素。这为后续的动画学习垫定了基础。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK