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

【第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>
http://www.xdnf.cn/news/9628.html

相关文章:

  • 【C++进阶篇】哈希表的模拟实现(赋源码)
  • WSL中ubuntu通过Windows带代理访问github
  • 【razor】采集的同时支持预览和传输的讨论和改造方案探讨
  • DAY38
  • 整合Jdk17+Spring Boot3.2+Elasticsearch9.0+mybatis3.5.12的简单用法
  • 电化学震荡- N 型负微分电阻
  • Android LiveData 详解
  • QT使用cmake添加资源文件闪退,创建了qrc文件不能添加的问题解决
  • 深圳SMT贴片打样全流程优化方案
  • 在监视器(Monitor)内部,是如何做线程同步的?
  • 半桥栅极驱动芯片D2104M使用手册
  • 虚拟机配置网络
  • mac10.15.7 安装erlang23.3 源码安装(未完待续)
  • Compass Arena大模型竞技场
  • Linux中的Shell脚本基础
  • 易学探索助手-项目记录(十一)
  • Polar编译码(SCL译码)和LDPC编译码(BP译码)的matlab性能仿真,并对比香浓限
  • 96. 不同的二叉搜索树
  • uniapp调用java接口 跨域问题
  • 数据分析学习笔记——A/B测试
  • 题目 3314: 蓝桥杯2025年第十六届省赛真题-魔法科考试
  • Fastmcp本地搭建 ,查询本地mysql,接入agent-cursor--详细流程
  • Odoo 条码功能全面深度解析(VIP15万字版)
  • 仿真科普|弥合市场需求断层,高性能仿真,“性能”与“安全”如何兼得?
  • Tesseract 字库介绍与训练指南
  • 深兰科技董事长陈海波率队考察南京,加速AI大模型区域落地应用
  • 设计模式26——解释器模式
  • 软件测试环境搭建及测试过程
  • 在Shopify性能调优过程中,如何考虑用户体验的完整性?
  • C语言进阶--数据的存储