canvas绘制图像轮廓效果
在2d图形可视化开发中,经常要绘制对象的选中效果。 一般来说,表达对象选中可以使用边框,轮廓或者发光的效果。 发光的效果,可以使用canvas的阴影功能,比较容易实现,此处不在赘述。
绘制边框
绘制边框是最容易实现的效果,比如下面的图片
要绘制边框,只需要使用strokeRect的方式即可。效果如下图所示:
这个代码也很简单,如下所示:
ctx1.strokeStyle = "red";ctx1.lineWidth = 2;ctx1.drawImage(img, 1, 1,img.width ,img.height)ctx1.strokeRect(1,1,img.width,img.height);
绘制轮廓
问题是,简单粗暴的加一个边框,并不能满足需求。很多时候,人们需要的是轮廓的效果,也就是图片的有像素和无像素的边缘处。如下图的效果所示:
要实现上述效果,最容易想到的思路就是通过像素的计算来判断边缘,并对边缘进行特定颜色的像素填充。但是像素的计算算法并不容易,简单的算法又很难达到预期的效果,而且由于逐像素操作,效率不高。
考虑到在三维webgl中,计算轮廓的算法思路是这样的:
- 先绘制三维模型自身,并在绘制的时候启动模板测试,把三维图像保存到模板缓冲中。
- 把模型适当放大,用纯属绘制模型,并在绘制的时候启用模板测试,和之前的模板缓冲区中的像素进行比较,如果对应的坐标处在之前模板缓冲区中有像素,就不绘制纯色。
依据上述的原理,就可以绘制处三维对象的轮廓了。下面是一个示例效果,(参考https://stemkoski.github.io/Three.js/Outline.html)
在2d canvas里面有类似的原理可以实现轮廓效果,就是使用globalCompositeOperation了。 大体思路是这样的:
- 首先绘制放大一些的图片。
- 然后开启globalCompositeOperation = ‘source-in’, 并用纯色填充整个canvas区域,由于source-in的效果,纯色会填充放大图片有像素的区域。
- 使用默认的globalCompositeOperation(source-over),用原始尺寸绘制图片。
绘制放大一些的图片
通过drawImage的参数可以控制绘制图片的大小,如下所示,drawImage有几个形式:
1 void ctx.drawImage(image, dx, dy);
2 void ctx.drawImage(image, dx, dy, dWidth, dHeight);
3 void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
其中dx,dy 代表绘制的起始位置,一般绘制的时候使用第一个方法,代表绘制的大小就是原本图片的大小。而使用第二个方法,我们可以指定绘制的尺寸,我们可以使用第二个方法绘制放大的图片,代码如所示:
ctx.drawImage(img, p - s, p - s, w + 2 * s, h+ 2 * s);
其中p代表图片本身的绘制位置,s代表向左,向上的偏移量,同时图片的宽和高都增加 2 * s
用纯色填充放大图片的区域
在上一步绘制的基础上,开启globalCompositeOperation = ‘source-in’, 并用纯色填充整个canvas区域。 代码如下所示:
// fill with colorctx.globalCompositeOperation = "source-in";ctx.fillStyle = "#FF0000";ctx.fillRect(0, 0, cw, ch);
最终的效果如下图所示:
为什么会出现这种效果是因为使用了globalCompositeOperation = ‘source-in’,具体原理可以参考本人的其他文章。
绘制原始图片
最后一步就是绘制原始图片,代码如下所示:
ctx.globalCompositeOperation = "source-over";ctx.drawImage(img, p, p, w, h);
首先恢复globalCompositeOperation为默认值 “source-over”,然后按照原本的大小绘制图片。
经过以上步骤,最终的效果如下图所示:
可以看出最终获得了我们要的效果。
只显示轮廓
如果我们只想得到图片的轮廓,则可以在最后绘制的时候,globalCompositeOperation 设置为“destination-out”,代码如下:
ctx.globalCompositeOperation = "destination-out";ctx.drawImage(img, p, p, w, h);
效果图如下:
轮廓粗细不一致的问题
上面的算法实现,是在图片的有像素值区域中心和图片本身的几何中心基本一直,如果图片的有像素值的中心和图片本身的几何中心相差比较大,则会出现轮廓粗细不一致的情况,比如下面这张图:
上半部分是透明的,下半部分是非透明的,像素的中心在3/4出,而几何中心在1/2处。使用上面的算法,该图片的轮廓如下:
可以发现上边缘的轮廓宽度变成了0。
在比如下图,
绘制后上边缘的轮廓比其他边缘的细。
怎么处理这种情况呢?可以在绘制放大图片的时候,不直接使用缩放,而是在上下左右,上左,上右,下左,下右几个方向进行偏移绘制,多次绘制,代码如下:
var dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1], // offset array// draw images at offsets from the array scaled by sfor (var i = 0; i < dArr.length; i += 2) {ctx.drawImage(img, p + dArr[i] * s, p + dArr[i + 1] * s, w, h);}
再看上面图片的轮廓效果,如下所示:
半透明的情况
我在其他文章中说过,globalCompositeOperation为"source-in"的时候,source图形的透明度,会影响到目标绘制图形的透明度。所以会导致轮廓的像素值会乘以透明度。比如,我们在绘制放大图的时候,设置globalAlpha = 0.5进行模拟。
最后的绘制效果如下:
可以看到轮廓的颜色变浅了,解决办法就是多绘制几次放大图。比如:
ctx.globalAlpha = 0.5;
ctx.drawImage(img, p - s, p - s, w + 2 * s, h+ 2 * s);
ctx.drawImage(img, p - s, p - s, w + 2 * s, h+ 2 * s);
而上面通过偏移的方式绘制的时候,本身都绘制了好多遍,所以不存在这个问题。如下:
ctx.globalAlpha = 0.5;for (var i = 0; i < dArr.length; i += 2) {ctx.drawImage(img, p + dArr[i] * s, p + dArr[i + 1] * s, w, h);}
如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rgWBajGC-1617175276389)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cfe45a37a17f4ca6916d361ae767bfc9~tplv-k3u1fbpfcp-zoom-1.image)]
当然,在透明度很低的情况下,使用绘制很多遍的方式,不是很好的解决方案。
# 使用算法(marching-squares-algorithm)
上面的方法对于有些图片效果就很不好,比如这张图片:
由于其有很多中空的效果,所以其最终效果如下图所示:
但是想要的只是外部的轮廓,而不需要中空部分也绘制上轮廓效果。此时需要使用其他的算法。 直接使用marching squares algorithm 可以获取图片的边缘。这一块的算法具体实现本文不再讲解,后续有机会单独一篇文章进行讲解。 此处直接使用开源的实现。比如可以使用 https://github.com/sakri/MarchingSquaresJS,代码如下:
function drawOuttline2(){var canvas = document.createElement('canvas');var ctx = canvas.getContext('2d');var w = img.width;var h = img.height;canvas.width = w;canvas.height = h;ctx.drawImage(img, 0, 0, w, h);var pathPoints = MarchingSquares.getBlobOutlinePoints(canvas);var points = [];for(var i = 0;i < pathPoints.length;i += 2){points.push({x:pathPoints[i],y:pathPoints[i + 1],})}// ctx.clearRect(0, 0, w, h);ctx.beginPath();ctx.lineWidth = 2;ctx.strokeStyle = '#00CCFF';ctx.moveTo(points[0].x, points[0].y);for (var i = 1; i < points.length; i += 1) {var point = points[i];ctx.lineTo(point.x,point.y);}ctx.closePath();ctx.stroke();ctx1.drawImage(canvas,0,0);}
首先使用调用MarchingSquaresJS的方法获取img图像的轮廓点的集合,然后把所有的点连接起来。形成轮廓图,最终效果如下:
不过可以看出,MarchingSquares 算法获得的轮廓效果锯齿相对较多的。有光这块算法的优化,本文不讲解。
总结
对于没有中空效果的图片,我们一般不采用MarchingSquares算法,而采用前面的一种方式来实现,效率高,而且效果相对更好。 而对于有中空,就会使用MarchingSquares算法,效果相对差,效率也相对低一些,实际应用中,可以通过缓存来降低性能的损耗。
本文的起源来资源一个2.5D项目,上一张项目图吧:
参考文档
https://www.emanueleferonato.com/2013/03/01/using-marching-squares-algorithm-to-trace-the-contour-of-an-image/
https://github.com/sakri/MarchingSquaresJS
https://github.com/OSUblake/msqr
http://users.polytech.unice.fr/~lingrand/MarchingCubes/algo.html#squar
如果对可视化感兴趣, 关注公号“ITMan彪叔” 可以及时收到更多有价值的文章。也可以加微信541002349进行交流。
canvas绘制图像轮廓效果相关推荐
- opencv 绘制图像轮廓
图像轮廓概念 轮廓是一系列相连的点组成的曲线,代表了物体的基本外形. 谈起轮廓不免想到边缘,它们确实很像.简单的说,轮廓是连续的,边缘并不全都连续(下图).其实边缘主要是作为图像的特征使用,比如可以用 ...
- Python机器视觉--OpenCV进阶(核心)--图像轮廓查找识别,绘制图像轮廓与图像轮廓的面积周长计算
1.图像轮廓查找识别与绘制图像轮廓 1.1 什么是图像轮廓 图像轮廓是具有相同颜色或灰度的连续点的曲线. 轮廓在形状分析和物体的检测和识别中很有用. 轮廓的作用: 用于图形分析 物体的识别和检测 注意 ...
- canvas绘制图像image
canvas绘制图像image 1.image的三个script的基本语法 准备工作:1.定义画布长度,获取2D绘图环境 2.建立对面对象,设置图片路径 3.载入图片,开始绘制 a.简单的画布 ...
- 放射性渐变色html,html5 canvas绘制放射性渐变色效果
效果图展示: canvas有些地方还是有点坑的,比如fillRect是方法不是属性,如果写成fillRect=这样是没效果的,而且还不报错.... 这里用到了createRadialGradient这 ...
- js html 卡通 学生,JavaScript+html5 canvas绘制的小人效果
本文实例讲述了JavaScript+html5 canvas绘制的小人效果.分享给大家供大家参考,具体如下: 运行效果截图如下: index.html代码如下: canvas中的缩放 #canvas ...
- html小人动画效果代码,JavaScript+html5 canvas绘制的小人效果
本文实例讲述了JavaScript+html5 canvas绘制的小人效果.分享给大家供大家参考,具体如下: 运行效果截图如下: index.html代码如下: canvas中的缩放 #canvas ...
- canvas绘制图像-drawImage
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...
- php打印玫瑰花,HTML_HTML5 canvas绘制的玫瑰花效果,有人用html就写了一朵漂亮的玫 - phpStudy...
HTML5 canvas绘制的玫瑰花效果 有人用html就写了一朵漂亮的玫瑰,是不是有点太骚气了.纯javascipt就弄出了玫瑰花,再次显示了HTML5的威力 警告:传说IE6内核是无法看到的,建议 ...
- Canvas 渐变 图像组合效果 颜色翻转
// canvas 简单例子 var canvas = document.getElementById('canvas'); if (canvas.getContext) {var context = ...
最新文章
- SAP PP MD04结果里没考虑受限使用库存?
- 网络***技术开篇——SQL Injection
- 从单体到混乱的微服务,阿里云托管式服务网格是如何诞生的?
- 高并发02_同步锁的一些题目
- switch off c语言,逆向工程 | C 语言之 switch-case 分支
- 简明 MongoDB 入门教程 1
- 日报系统1 创建框架 Django3.0
- ROS机器人操作系统——什么是ROS(一)
- dw网页制作的基本步骤_网页制作一般使用哪些工具?DW使用方法教学?
- 桌面的快捷方式图标异常
- 为什么不能睁一只眼闭一只眼_“睁一只眼,闭一只眼”才是人生的最高境界
- scss exceeded maximum budget. Budget 4.00 kB was not met by 130 bytes with a total of 4.13 kB.
- Spring源码解析二之创建Bean(实例化)
- vue3中的watch和watchEffect
- 【论文阅读】【3d目标检测】Sparse Fuse Dense: Towards High Quality 3D Detection with Depth Completion
- 大漠多线程循环任务自动切换模板判断账号登陆成功
- scrollView无限滚动,横屏和竖屏
- 如何测试硬盘软件,如何进行硬盘测试?磁盘健康检测方法介绍
- java web应用开发技术_第一章 Java Web应用开发技术
- 判断一个字符串中是否包含另一个字符串
热门文章
- Linux 的磁盘挂载 | 详解
- 干货 | 这些年,我用的绘图工具大合集
- 《惢客创业日记》2021.08.06-09(周五)惢客与征信的区别(下)
- linux egrep命令使用示例
- 在html循环字母,怎么样能遍历一个字符串
- windows10服务器运行失败,win10遇到服务器启动失败 80端口被占用如何解决
- 基于stm32f103xb处理器的Daplink固件编译
- 【Python_PyQtGraph 学习笔记(五)】基于PyQtGraph和GraphicsLayoutWidget动态绘图并实现窗口模式,且保留全部绘图信息
- 2021-09-29 每天几个LCEDA小知识——如何修改元器件封装
- multisim怎么设置晶体管rbe_multisim中添加大功率三极管的办法 multisim 三极管设置方法...