楔子

最近一个项目,需要绘制双线的效果,双线效果表示的是轨道(类似铁轨之类的),如下图所示:

负责这块功能开发的小伙,姑且称之为L吧,最开始是通过数学计算的方式来实现这种双线,也就是在原来的路径的基础上,计算出两条路径。但是这个过程的计算算挺复杂,而是最终实现的效果很耗性能,性能损耗估计主要在于路径的计算上。

优化技巧

后来他找到我来看这个问题,我在分析了项目背景的情况下,给予了一个简单的绘制技巧,就是先用较粗的线条绘制路径,然后再用较细的线条绘制路径,较细线条的颜色正好是背景颜色。
之所以能够使用这个技巧,是因为该项目的绘制背景是纯色的,而不是渐变色或者图片。
示例代码如下:

               ctx.beginPath();
ctx.fillStyle = 'blue';
ctx.rect(10,10,1000,1000);
ctx.fill(); ctx.save();
ctx.strokeStyle = 'red';
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(200,100); //起始点
ctx.lineTo(400,100);
ctx.quadraticCurveTo(500,100,500,200);
ctx.lineTo(500,400);
ctx.quadraticCurveTo(500,500,400,500);
ctx.lineTo(200,500);
ctx.quadraticCurveTo(100,500,100,400);
ctx.lineTo(100,200);
ctx.quadraticCurveTo(100,100,200,100);
ctx.stroke(); ctx.strokeStyle= 'blue'
ctx.lineWidth = 4;
ctx.stroke();
ctx.restore();

代码的思路是,首先使用纯色blue绘制了一个背景,然后使用线条颜色red绘制一条线,然后使用较小的线宽,并把线条颜色改成背景颜色blue,绘制另外一个条线段。最终的绘制效果如下:

double_line

到此,项目的这个技术难点问题,算是被解决了。这种解决方法,不仅算法简单,不用构思数学方法来构造双线,而且轻量,不会有性能负担。

背景不是纯色的情况

前面说到:之所以能够使用这个技巧,是因为该项目的绘制背景是纯色的,而不是渐变色或者图片。
那如果背景是图片或者渐变颜色的情况下,用这种技巧,肯定就是失效的了。

之所以会思考这个问题,是得益于公司的技术分享会。我会要求员工定期组织分享会,分享一些经验。在此打个小广告,可以看出我们公司的技术氛围是很好的,所以有兴趣的小伙伴可以抓紧时间投简历。怎么投简历呢,关注微信号ITman彪叔。
过程中,当时小伙伴L也分享了前面提到这种思路。在分享的过程中,我提出了进一步的问题,如果背景不是纯色,而是渐变色或者图片怎么办?并且灵感乍现,想到了一个解决方法,就是使用ctx.globalCompositeOperation。

有关globalCompositeOperation的说明,可以参考如下链接的说明:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
http://www.w3school.com.cn/tags/canvas_globalcompositeoperation.asp

globalCompositeOperation的定义和用法

globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。其中:

  • 源图像 = 您打算放置到画布上的绘图。
  • 目标图像 = 您已经放置在画布上的绘图
    下图显示了globalCompositeOperation的不同的值的解释:

    globalCompositeOperation的不同的值的解释

要实现双线的绘制,就要求用同样的路径,不同的线宽绘制两条线路
(我们称之为目标线路和源线路)。并要达到一条线路抠出另外一条线路的效果。
结合上图,我们可以看出destination-out,source-out,xor可以达到效果。下面以destination-out举例说明。

destination-out绘制原理说明

比如首先通过 css 设置背景图,并去掉绘制背景颜色,代码如下:

 <body onload="init()" style="background: url(../test/images/diffuse.png);">

然后绘制代码如下:

  ctx.save();
ctx.strokeStyle = 'red';
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(200,100); //起始点
ctx.lineTo(400,100);
ctx.quadraticCurveTo(500,100,500,200);
ctx.lineTo(500,400);
ctx.quadraticCurveTo(500,500,400,500);
ctx.lineTo(200,500);
ctx.quadraticCurveTo(100,500,100,400);
ctx.lineTo(100,200);
ctx.quadraticCurveTo(100,100,200,100);
ctx.stroke(); ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 4;
ctx.stroke();
ctx.restore();

首先设置路径,然后设置线宽为10,调用stroke方法绘制一条线宽为10的路线A。
之后设置globalCompositeOperation为 'destination-out',调整线宽为4,调用stroke方法绘制一条线宽为4的路线B。
看下destination-out的解释:

在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。

绘制了线路A的canvas图像是目标图像,线路B是源图像。根据上面解释,只有源图像之外的目标图像能够被显示。最终绘制的效果如下:

destination-out.png

xor 和 source-out

把上面的代码的globalCompositeOperation修改成xor,发现效果也是可以的,xor的解释如下:

使用异或操作对源图像与目标图像进行组合。 英文解释如下:
Shapes are made transparent where both overlap and drawn normal everywhere else.

意思源和目标的像素重叠(overlap)的部分会被变成透明像素,其他部分正常绘制。 所以上面示例中,线条A和线条B重叠的部分会被变成透明。绘制的效果也是线条A的被挖空。

对于source-out,其效果正好和destination-out的效果相反:

在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。

应此只需要取反操作即可,先用宽度4绘制线条A,然后用宽度10绘制线条B,其结果也是一样的。

背景不是纯色的情况2

前面的背景是通过css的方式设置上去的,如果是通过canvas的drawImage直接绘制上去,效果就不一样了。还是以destination-out为例说明,首先绘制了image,然后绘制线路A,此时的目标图像不在是线路A组成的图形,而是image和线路A组合成的图形,此时用destination-out的方式绘制线路B,不仅会挖空线路A,背景也会被挖空,如下图所示:

背景不是纯色的情况2

应此要想达到真正的双线效果,要么背景只能是用css设置,要么用两个canvas叠加,一个绘制背景图片,一个绘制路径。
当然还有一种方式,就是绘制双线总是在一个临时的canvas上面进行,然后把这个临时的canvas绘制结果再次绘制到工作canvas上面,相关实践留给读者自己进行。

后记

在网络上面搜索canvas double line,搜索到stackoverflow上的一条结果如下:
https://stackoverflow.com/questions/13441610/double-line-stroke-in-html5-canvas
其中的答案也是采用了globalCompositeOperation设置为destination-out的方式。

欢迎关注公众号“ITman彪叔”。彪叔,拥有10多年开发经验,现任公司系统架构师、技术总监、技术培训师、职业规划师。熟悉Java、JavaScript、Python语言,熟悉数据库。熟悉java、nodejs应用系统架构,大数据高并发、高可用、分布式架构。在计算机图形学、WebGL、前端可视化方面有深入研究。对程序员思维能力训练和培训、程序员职业规划有浓厚兴趣。

ITman彪叔公众号

canvas 绘制双线技巧的更多相关文章

  1. 利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

    利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果 前言 近日公司接到一个轨道系统的需求,需要将地铁线路及列车实时位置展示在大屏上.既然是大屏项目,那视觉效果当然是第一重点,咱们可以先来看看项 ...

  2. Win10 UWP开发中的重复性静态UI绘制小技巧 1

    介绍 在Windows 10 UWP界面实现的过程中,有时会遇到一些重复性的.静态的界面设计.比如:画许多等距的线条,画一圈时钟型的刻度线,同特别的策略排布元素,等等. 读者可能觉得这些需求十分简单, ...

  3. 【朝花夕拾】Android自定义View篇之(三)Canvas绘制文字

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10968358.html],谢谢! 前面的文章中在介绍Canvas的时候,提到过后续单独讲Can ...

  4. HTML5学习总结——canvas绘制象棋(canvas绘图)

    一.HTML5学习总结——canvas绘制象棋 1.第一次:canvas绘制象棋(笨方法)示例代码: <!DOCTYPE html> <html> <head> & ...

  5. 用canvas绘制折线图

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. 封装 用canvas绘制直线的函数--面向对象

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. 学习笔记:HTML5 Canvas绘制简单图形

    HTML5 Canvas绘制简单图形 1.添加Canvas标签,添加id供js操作. <canvas id="mycanvas" height="700" ...

  8. canvas绘制经典折线图(一)

    最终效果图如下: 实现步骤如下:注-引用了jQuery HTML代码 <!doctype html> <html lang="en"> <head&g ...

  9. Canvas绘制图形

    1.Canvas绘制一个蓝色的矩形 <!DOCTYPE html> <html> <head lang="en"> <meta chars ...

随机推荐

  1. 一起来学习android自定义控件—边缘凹凸的View

    1前言 最近做项目的时候遇到一个卡劵的效果,由于自己觉得用图片来做的话可以会出现适配效果不好,再加上自己自定义view方面的知识比较薄弱,所以想试试用自定义View来实现.但是由于自己知识点薄弱,一开 ...

  2. 一起玩转CoordinatorLayout

    作为Material Design风格的重要组件,CoordinatorLayout协调多种组件的联动,实现各种复杂的效果,在实际项目中扮演着越来越重要的角色.本篇博客将由浅到深,带你一起玩转Coor ...

  3. NLS_LANG引起的SQLPLUS乱码和length长度不正确.

    创建一个实验表语句如下 SQL> create table test(id number,name varchar2(10)); 当我们在SQLPLUS里面敲入下面的语句并回车执行的时候,SQL ...

  4. LeetCode 之二叉树中序遍历(使用栈实现)

    1.题目描述 2.使用栈实现难度大于使用递归实现 3.代码 vector<int> inorderTraversal(TreeNode* root) { // 非递归实现,借助栈 vect ...

  5. Oracle EBS 报表日期格式问题

    1.确保参数日期值集选择:FND_STANDARD_DATE 2.将程序的入口参数选择为 varchar2类型 3.注意输出和游标时参数的截断  to_date(substr(p_DATE_from, ...

  6. leetcode 刷题

    176:第二高的薪水 offset ) as secondhighestsalary; ---去掉第一个,再从第一个开始 177:第N高的薪水 ------相关子查询:子查询中引用了外层查询所引用表的 ...

  7. 教你使用Paw解析http请求

    教你使用Paw解析http请求 软件下载地址: 链接: http://pan.baidu.com/s/1gdzmjq7 密码: 3mpb 这款应用的图片像极了百度,哈哈. 支持的请求方式: 完整的显示 ...

  8. spring4声明式事务—02 xml配置方式

    1.配置普通的 controller,service ,dao 的bean. <!-- 配置 dao ,service --> <bean id="bookShopDao& ...

  9. LinkedHashSet 元素唯一,存储取出有序

      package cn.itcast_04; import java.util.LinkedHashSet; /* * LinkedHashSet:底层数据结构由哈希表和链表组成. * 哈希表保证元 ...

  10. 解决标题过长的CSS

    不知道为什么大家用截取字符串的人很多呢.. <html> <head> <style type="text/css"> .divout { di ...