当前位置: 首页 > news >正文

【第2章 绘制】2.7 路径、描边与填充

文章目录

  • 前言
  • 示例-图形的描边与填充效果
  • 路径与子路径
  • 非零环绕规则
  • 圆形剪纸效果
  • 其他剪纸图形


前言

大多数绘制系统,都是基于路径的。你需要先定义一个路径,然后再对其进行描边或填充,也可以在描边的同时进行填充。


示例-图形的描边与填充效果

下面应用程序演示了这三种绘制方式,对左边一列的路径进行了描边操作,对中间一列的路径进行了填充,并对右边一列的路径同时进行描边与填充。

第一行的矩形路径与最后一行的圆弧路径都是封闭路径(closed Path),而中间一行的弧形路径则是开放路径(open path)。请注意,不论一个路径是开放或是封闭,你都可以对其进行填充。当填充某个开放路径时,浏览器会把它当成封闭路径来填充。

在这里插入图片描述

示例代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>2-9-文本、矩形与圆弧的描边及填充</title><style>html,body {margin: 0;padding: 0;width: 100%;height: 100%;}#canvas {display: block;}</style></head><body><canvas id="canvas"> canvas not supported </canvas><script>const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')// 设置canvas大小canvas.width = window.innerWidthcanvas.height = window.innerHeight// Functions ......const drawGrid = (context, color, stepX, stepY) => {context.strokeStyle = colorcontext.lineWidth = 0.5// 绘制垂直线for (let i = stepX + 0.5; i < canvas.width; i += stepX) {context.beginPath()context.moveTo(i, 0)context.lineTo(i, canvas.height)context.stroke()}// 绘制水平线for (let i = stepY + 0.5; i < canvas.height; i += stepY) {context.beginPath()context.moveTo(0, i)context.lineTo(canvas.width, i)context.stroke()}}drawGrid(context, 'lightgray', 10, 10)// 绘图环境属性context.font = '48pt Helvetica'context.strokeStyle = 'blue'context.fillStyle = 'red'context.lineWidth = 2// 绘制文本context.strokeText('Stroke', 60, 110)context.fillText('Fill', 440, 110)context.strokeText('Stroke & Fill', 650, 110)context.fillText('Stroke & Fill', 650, 110)// 绘制矩形context.lineWidth = 5context.beginPath()context.rect(80, 150, 150, 100)context.stroke()context.beginPath()context.rect(400, 150, 150, 100)context.fill()context.beginPath()context.rect(750, 150, 150, 100)context.stroke()context.fill()// 绘制开放的圆弧context.beginPath()context.arc(150, 370, 60, 0, (Math.PI * 3) / 2)context.stroke()context.beginPath()context.arc(475, 370, 60, 0, (Math.PI * 3) / 2)context.fill()context.beginPath()context.arc(820, 370, 60, 0, (Math.PI * 3) / 2)context.stroke()context.fill()// 绘制闭合的圆弧context.beginPath()context.arc(150, 550, 60, 0, (Math.PI * 3) / 2)context.closePath()context.stroke()context.beginPath()context.arc(475, 550, 60, 0, (Math.PI * 3) / 2)context.closePath()context.fill()context.beginPath()context.arc(820, 550, 60, 0, (Math.PI * 3) / 2)context.closePath()context.stroke()context.fill()</script></body>
</html>

CanvasRenderingContext2D之中与路径有关的方法

方法描述
fill()填充当前绘图(路径)
stroke()绘制已定义的路径
beginPath()起始一条路径,或重置当前路径
moveTo()把路径移动到画布中的指定点,不创建线条
closePath()创建从当前点回到起始点的路径
lineTo()添加一个新点,然后在画布中创建从该点到最后指定点的线条
clip()从原始画布剪切任意形状和尺寸的区域
quadraticCurveTo()创建二次贝塞尔曲线
bezierCurveTo()创建三次方贝塞尔曲线
arc()创建弧/曲线(用于创建圆形或部分圆)
arcTo()创建两切线之间的弧/曲线
ellipse()创建椭圆路径
rect()创建矩形路径
roundRect()创建圆角矩形路径
isPointInPath()如果指定的点位于当前路径中,则返回 true,否则返回 false
isPointInStroke()用于检测某点是否在路径的描边所在的区域内

提示:路径与隐形墨水
有一个很恰当的比喻,可以用来说明“创建路径随后对其进行描边或填充”这个操作。我们可以将该操作比作“使用隐形墨水来绘图”。
你用隐形墨水所绘制的内容并不会立刻显示出来,必须进行一些后续操作,像是加热、涂抹化学药品等,才可以将你所画的内容显示出来。
使用rect()与arc()这样的方法来创建路径,就好比使用隐形墨水来进行绘制一样。这些方法会创建一条不可见的路径,稍后可以调用stroke()或fill()令其可见。

路径与子路径

在某一时刻,canvas 之中只能有一条路径存在,Canvas 规范将其称为“当前路径”(current path)。然而,这条路径却可以包含许多子路径(subpath)。而子路径,又是由两个或更多的点组成的。比方说,可以像这样绘制出两个矩形来:

context.beginPath();context.rect(10,10,100,100);
context.stroke();context.beginPath();context.rect(30,30,100,100);
context.stroke();

以上这段代码通过调用 beginPath() 来开启一段新的路径,该方法会将当前路径中所有子路径都清除掉,然后调用 rect() 方法,该方法向当前路径中添加了一个含有4个点的子路径,最后调用 stroke() 方法描边显形。

接下来再次调用了 beginPath() 方法,清除了上次调用 rect() 方法所创建的子路径(指那个矩形路径),然后再添加了另一个矩形子路径,最后进行描边显形。

如果我们第二次不调用beginPath()方法,那么此时路径便有两个矩形,那么第二次stroke()时便又会重新绘制一遍第一个矩形。

context.beginPath();context.rect(10,10,100,100);
context.stroke();context.rect(30,30,100,100);
context.stroke();

非零环绕规则

如果当路径是循环的,或者包含多个相交的子路径,那fill()就会按照“非零环绕规则(nonzero winding rule)”来判断填充哪些区域。

在这里插入图片描述

非零环绕规则的判断过程:

  • 对于路径中的任意给定区域,在区域内画一条足够长的线段,使线段终点完全落于路径范围之外。(图中三个箭头表示此过程)
  • 计数器初始化为0,对于每一条线段来说,每当线段与路径顺时针部分相交时+1,与路径逆时针部分相交时-1。
  • 如果计数器最终值不是0,则证明在内部,如果是0,则证明在外部。仅填充在内部的区域。

以上图为例

  • 上箭头与逆时针相交,计数器结果-1,填充;
  • 右箭头与两个逆时针相交,结果-2,填充;
  • 左箭头同时与一个顺时针和逆时针路径相交,结果为0,不填充。

圆形剪纸效果

我们运用所学到的路径、阴影与非零环绕规则等知识,实现如图圆环剪纸(cutout)效果。

左边的圆环,一个圆在另一个内部,通过设定arc()最后一个参数值,以顺时针方向绘制内部的圆形,并且以逆时针方向绘制了外围的圆形。

右边的圆环,都是以顺时针方向绘制。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>2-9-文本、矩形与圆弧的描边及填充</title><style>html,body {margin: 0;padding: 0;width: 100%;height: 100%;}#canvas {display: block;}</style></head><body><canvas id="canvas"> canvas not supported </canvas><script>const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')// 设置canvas大小canvas.width = window.innerWidthcanvas.height = window.innerHeight// Functions ......const drawGrid = (context, color, stepX, stepY) => {context.strokeStyle = colorcontext.lineWidth = 0.5// 绘制垂直线for (let i = stepX + 0.5; i < canvas.width; i += stepX) {context.beginPath()context.moveTo(i, 0)context.lineTo(i, canvas.height)context.stroke()}// 绘制水平线for (let i = stepY + 0.5; i < canvas.height; i += stepY) {context.beginPath()context.moveTo(0, i)context.lineTo(canvas.width, i)context.stroke()}}const drawTwoArcs = () => {context.beginPath()// 非零环绕规则 会让两个同心圆的填充效果不同// 外圆context.arc(300, 190, 150, 0, Math.PI * 2, false)// 内圆context.arc(300, 190, 100, 0, Math.PI * 2, true)context.fill()context.stroke()context.beginPath()// 外圆context.arc(700, 190, 150, 0, Math.PI * 2)// 内圆context.arc(700, 190, 100, 0, Math.PI * 2)context.fill()context.stroke()}const draw = () => {context.clearRect(0, 0, canvas.width, canvas.height)drawGrid(context, 'lightgray', 10, 10)context.save()context.shadowColor = 'rgba(0, 0, 0, 0.8)'context.shadowOffsetX = 12context.shadowOffsetY = 12context.shadowBlur = 15drawTwoArcs()context.restore()}// Initialization......context.fillStyle = 'rgba(100,140,230,0.5)'context.strokeStyle = 'rgba(100,140,230,0.5)'draw()</script></body>
</html>

其他剪纸图形

我们可以看到,矩形内绘制了三个图形,一个矩形,一个三角形,一个圆形,其实这也是利用非零环绕规则做出的效果。

因为rect()总是按照顺时针方向绘制,这里重写了rect()的方法,并增加了一个可以控制方向的参数。

只有外框是顺时针绘制,里面的三个图形都是按照逆时针路径绘制的,所以填充之后展现为镂空形状。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>2-9-文本、矩形与圆弧的描边及填充</title><style>html,body {margin: 0;padding: 0;width: 100%;height: 100%;}#canvas {display: block;}</style></head><body><canvas id="canvas"> canvas not supported </canvas><script>const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')// 设置canvas大小canvas.width = window.innerWidthcanvas.height = window.innerHeight// Functions ......const drawGrid = (context, color, stepX, stepY) => {context.strokeStyle = colorcontext.lineWidth = 0.5// 绘制垂直线for (let i = stepX + 0.5; i < canvas.width; i += stepX) {context.beginPath()context.moveTo(i, 0)context.lineTo(i, canvas.height)context.stroke()}// 绘制水平线for (let i = stepY + 0.5; i < canvas.height; i += stepY) {context.beginPath()context.moveTo(0, i)context.lineTo(canvas.width, i)context.stroke()}}// 定义了自己的rect方法,绘制矩形能控制方向const rect = (x, y, w, h, direction) => {// 默认false 顺时针if (direction) {// 逆时针context.moveTo(x, y)context.lineTo(x, y + h)context.lineTo(x + w, y + h)context.lineTo(x + w, y)context.closePath()} else {// 顺时针context.moveTo(x, y)context.lineTo(x + w, y)context.lineTo(x + w, y + h)context.lineTo(x, y + h)context.closePath()}}// 绘制外围矩形路径const addOuterRectanglePath = () => {// context.rect方法绘制矩形的方向是顺时针方向context.rect(110, 25, 370, 335)}// 绘制内部圆形路径const addCirclePath = () => {context.arc(300, 300, 40, 0, Math.PI * 2, true)}// 绘制内部矩形路径const addRectanglePath = () => {rect(310, 55, 70, 35, true)}// 绘制内部三角形路径const addTrianglePath = () => {context.moveTo(400, 200)context.lineTo(250, 115)context.lineTo(200, 200)context.closePath()}const drawCutouts = () => {context.beginPath()addOuterRectanglePath()addCirclePath()addRectanglePath()addTrianglePath()context.fill()}const draw = () => {// 清除画布context.clearRect(0, 0, canvas.width, canvas.height)// 绘制网格drawGrid(context, 'lightgray', 10, 10)context.save()context.shadowColor = 'rgba(200, 200, 0, 0.5)'context.shadowOffsetX = 12context.shadowOffsetY = 12context.shadowBlur = 15drawCutouts()context.restore()}// Initialization......context.fillStyle = 'goldenrod'draw()</script></body>
</html>

相关文章:

  • 企业信息管理系统的设计与实现(代码+数据库+LW)
  • 开源架构在移动端开发中的卓越应用与深度解析
  • 基于c++11重构的muduo核心库项目梳理
  • node_modules\node-sass: Command failed.报错了
  • Java设计模式之命令模式详解
  • YARN架构解析:大数据资源管理核心
  • Browser-use快速了解
  • WifiEspNow库函数详解
  • 树莓派搭配 Tailscale 搭建个人云网盘
  • SpringBoot3.4.5 开启虚拟线程(JDK21)
  • Spring测试框架全面解析
  • 【JavaSE】异常处理学习笔记
  • GRIT:让AI“指着图说话“的新思路
  • 【AGI】Qwen3模型高效微调
  • 234. Palindrome Linked List
  • ISOLAR软件生成报错处理(四)
  • 常见路由协议解析:从原理到应用场景
  • react-native的token认证流程
  • 运营商地址和ip属地一样吗?怎么样更改ip属地地址
  • 输配电行业国产PLM转型方案:南通禛华电气的云PLM研发转型
  • 网络架构分为几层/seo提供服务
  • wordpress远程 媒体库/seo公司赚钱吗
  • 做外贸女装有哪些网站有哪些/seo优化排名技术百度教程
  • 北京自己怎么做网站/网站开发详细流程
  • 有后台的网站/中国最权威的网站排名
  • wordpress 首页打开慢/seo搜索引擎优化工具