首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >canvas绘制图像轮廓效果绘制边框绘制轮廓 使用算法(marching-squares-algorithm)总结参考文档

canvas绘制图像轮廓效果绘制边框绘制轮廓 使用算法(marching-squares-algorithm)总结参考文档

作者头像
用户3158888
发布于 2021-04-02 01:55:46
发布于 2021-04-02 01:55:46
2.9K00
代码可运行
举报
运行总次数:0
代码可运行

绘制边框

绘制边框是最容易实现的效果,比如下面的图片

要绘制边框,只需要使用strokeRect的方式即可。效果如下图所示:

这个代码也很简单,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
     ctx1.strokeStyle = "red";
     ctx1.lineWidth = 2;
     ctx1.drawImage(img, 1, 1,img.width ,img.height)
     ctx1.strokeRect(1,1,img.width,img.height);

绘制轮廓

问题是,简单粗暴的加一个边框,并不能满足需求。很多时候,人们需要的是轮廓的效果,也就是图片的有像素和无像素的边缘处。如下图的效果所示:

要实现上述效果,最容易想到的思路就是通过像素的计算来判断边缘,并对边缘进行特定颜色的像素填充。但是像素的计算算法并不容易,简单的算法又很难达到预期的效果,而且由于逐像素操作,效率不高。

考虑到在三维webgl中,计算轮廓的算法思路是这样的:

  1. 先绘制三维模型自身,并在绘制的时候启动模板测试,把三维图像保存到模板缓冲中。
  2. 把模型适当放大,用纯属绘制模型,并在绘制的时候启用模板测试,和之前的模板缓冲区中的像素进行比较,如果对应的坐标处在之前模板缓冲区中有像素,就不绘制纯色。

依据上述的原理,就可以绘制处三维对象的轮廓了。下面是一个示例效果,(参考https://stemkoski.github.io/Three.js/Outline.html

在2d canvas里面有类似的原理可以实现轮廓效果,就是使用globalCompositeOperation了。 大体思路是这样的:

  1. 首先绘制放大一些的图片。
  2. 然后开启globalCompositeOperation = 'source-in', 并用纯色填充整个canvas区域,由于source-in的效果,纯色会填充放大图片有像素的区域。
  3. 使用默认的globalCompositeOperation(source-over),用原始尺寸绘制图片。

绘制放大一些的图片

通过drawImage的参数可以控制绘制图片的大小,如下所示,drawImage有几个形式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 代表绘制的起始位置,一般绘制的时候使用第一个方法,代表绘制的大小就是原本图片的大小。而使用第二个方法,我们可以指定绘制的尺寸,我们可以使用第二个方法绘制放大的图片,代码如所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ctx.drawImage(img, p - s, p  - s, w + 2 * s, h+ 2 * s);

其中p代表图片本身的绘制位置,s代表向左,向上的偏移量,同时图片的宽和高都增加 2 * s

用纯色填充放大图片的区域

在上一步绘制的基础上,开启globalCompositeOperation = 'source-in', 并用纯色填充整个canvas区域。 代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 // fill with color
        ctx.globalCompositeOperation = "source-in";
        ctx.fillStyle = "#FF0000";
        ctx.fillRect(0, 0, cw, ch);

最终的效果如下图所示:

为什么会出现这种效果是因为使用了globalCompositeOperation = 'source-in',具体原理可以参考本人的其他文章。

绘制原始图片

最后一步就是绘制原始图片,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  ctx.globalCompositeOperation = "source-over";
  ctx.drawImage(img, p, p, w, h);

首先恢复globalCompositeOperation为默认值 "source-over",然后按照原本的大小绘制图片。

经过以上步骤,最终的效果如下图所示:

可以看出最终获得了我们要的效果。

只显示轮廓

如果我们只想得到图片的轮廓,则可以在最后绘制的时候,globalCompositeOperation 设置为“destination-out”,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        ctx.globalCompositeOperation = "destination-out";
        ctx.drawImage(img, p, p, w, h);

效果图如下:

轮廓粗细不一致的问题

上面的算法实现,是在图片的有像素值区域中心和图片本身的几何中心基本一直,如果图片的有像素值的中心和图片本身的几何中心相差比较大,则会出现轮廓粗细不一致的情况,比如下面这张图:

上半部分是透明的,下半部分是非透明的,像素的中心在3/4出,而几何中心在1/2处。使用上面的算法,该图片的轮廓如下:

可以发现上边缘的轮廓宽度变成了0。

在比如下图,

绘制后上边缘的轮廓比其他边缘的细。

怎么处理这种情况呢?可以在绘制放大图片的时候,不直接使用缩放,而是在上下左右,上左,上右,下左,下右几个方向进行偏移绘制,多次绘制,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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 s
 for (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进行模拟。 最后的绘制效果如下:

可以看到轮廓的颜色变浅了,解决办法就是多绘制几次放大图。比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);

而上面通过偏移的方式绘制的时候,本身都绘制了好多遍,所以不存在这个问题。如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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);
  }

如下图所示:

当然,在透明度很低的情况下,使用绘制很多遍的方式,不是很好的解决方案。

 使用算法(marching-squares-algorithm)

上面的方法对于有些图片效果就很不好,比如这张图片:

由于其有很多中空的效果,所以其最终效果如下图所示:

但是想要的只是外部的轮廓,而不需要中空部分也绘制上轮廓效果。此时需要使用其他的算法。 直接使用marching squares algorithm 可以获取图片的边缘。这一块的算法具体实现本文不再讲解,后续有机会单独一篇文章进行讲解。 此处直接使用开源的实现。比如可以使用 https://github.com/sakri/MarchingSquaresJS,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
canvas详细教程! ( 近1万字吐血总结)
今天为大家带来的是我已经写了很久了的canvas详细教程,按照文章的案例一个一个敲下来保证你可以学会绘制很多图案和动画,对入门canvas很有帮助!
用户9999906
2022/12/22
4.5K0
Canvas 烟花合集 -- 将粉丝头像做成烟花在天空绽放✨
这一步的目的是获取到图片每个像素的颜色,这样我们就可以通过这些像素点合成一张图片,也可以排除掉一些像素点,筛出想要的图形
小丞同学
2021/08/16
1.5K0
canvas学习笔记(八)—- 基本动画
1.用window.setInterVal()、window.setTimeOut()和window.requestAnimationFrame()来定期执行一个指定函数
Java架构师必看
2021/08/19
7820
【Web技术】774- 基于canvas完成图片裁剪工具
本文是基于canvas去实现图片裁剪工具。因为canvas代码还是比较长的,尽量写思路,完整代码已放在github上。
pingan8787
2020/11/19
1.5K1
【Web技术】774- 基于canvas完成图片裁剪工具
MarsCode 助力:Canvas 上的素描变色魔法✨
😎嘿!首先,先来看超酷的效果哦😃!🎨素描图在用户鼠标按下后,就像被施了魔法一样🧙‍♂️,将鼠标范围内的素描像素神奇地转换成有色像素🌈。
一杯茶Ja
2024/09/17
2960
MarsCode 助力:Canvas 上的素描变色魔法✨
第156天:canvas(三)
​ translate方法接受两个参数。x 是左右偏移量,y 是上下偏移量,如右图所示。
半指温柔乐
2018/09/11
5710
第156天:canvas(三)
Canvas
注意canvas 的 width 和 height 不要用 css 来设定,如果用 css 样式来设置,会变形和失真
小丞同学
2021/08/16
1.4K0
利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果
近日公司接到一个轨道系统的需求,需要将地铁线路及列车实时位置展示在大屏上。既然是大屏项目,那视觉效果当然是第一重点,咱们可以先来看看项目完成后的效果图。
用户3158888
2020/12/05
6220
鸿蒙Next画布Canvas基础使用演示
本文将Canvas基础方法和属性罗列出来,通过不同按钮实现不同的绘制,可直观的看到每个功能的绘制结果。感兴趣的同学,可以复制源码,运行起来点点。
用户4773577
2025/06/28
1450
Canvas如何实现滤镜效果
用过photoshop或者美颜相机,我们都知道滤镜可以帮助我们把图片修缮的更加完美。
terrence386
2022/07/15
1.5K0
Canvas如何实现滤镜效果
第155天:canvas(二)
​ 这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。
半指温柔乐
2018/09/11
5470
第155天:canvas(二)
制作高大上的Canvas粒子动画
制作粒子动画效果要解决两个问题:一个是粒子动画轨迹,另外一个是粒子执行动画的时机。 首先来看下我们准备要做的粒子动画效果是怎么样的~ 是这样(粒子漂浮): 或者这样(粒子轨迹动画): 甚至是这样
练小习
2017/12/29
2.8K0
H5和微信小游戏 Canvas API 整理前言
这段时间闲下来,系统学习了微信小程序和微信小游戏,发现还是挺有意思的。现在微信小游戏的开发都离不开游戏引擎,用原生小游戏开发工具开发的很少很少。但是毕竟我不是专业游戏开发,所有游戏引擎就不搞了,我们就单纯来看原生微信小游戏开发。
大公爵
2018/10/10
3.1K0
H5和微信小游戏 Canvas API 整理前言
学习 canvas 的 globalCompositeOperation 做出的神奇效果
最早知道 canvas 的 globalCompositeOperation 属性,是在需要实现一个刮刮卡效果的时候,当时也就是网上找到刮刮卡的效果赶紧完成任务就完了,这次又学习一次,希望能加深理解吧。
FEWY
2019/05/26
1.7K0
Canvas 进阶(四)实现一个“刮刮乐”游戏
我们创建一个 ScrapAward 类,通过传入 option 和调用其 restart() 方法实现重新开始。
小皮咖
2019/11/05
1.1K0
Day 3 学习Canvas这一篇文章就够了
一、canvas简介 ​ <canvas> 是 HTML5 新增的,一个可以使用脚本(通常为JavaScript)在其中绘制图像的 HTML 元素。它可以用来制作照片集或者制作简单(也不是那么简单)的动画,甚至可以进行实时视频处理和渲染。 ​ 它最初由苹果内部使用自己MacOS X WebKit推出,供应用程序使用像仪表盘的构件和 Safari 浏览器使用。 后来,有人通过Gecko内核的浏览器 (尤其是Mozilla和Firefox),Opera和Chrome和超文本网络应用技术工作组建议为下一代的网络技术使用该元素。 ​ Canvas是由HTML代码配合高度和宽度属性而定义出的可绘制区域。JavaScript代码可以访问该区域,类似于其他通用的二维API,通过一套完整的绘图函数来动态生成图形。 ​ Mozilla 程序从 Gecko 1.8 (Firefox 1.5)开始支持 <canvas>, Internet Explorer 从IE9开始<canvas> 。Chrome和Opera 9+ 也支持 <canvas>。 二、Canvas基本使用 2.1 <canvas>元素
IT人一直在路上
2019/09/16
1.2K0
Day 3 学习Canvas这一篇文章就够了
Canvas之使用图片 原
canvas有比较强的图片操作能力。可以用于动态的图像合成或者作为图形的背景。浏览器支持任意格式如PNG、GIF、或者JPEG,你甚至可以将同一个页面中的其他canvas元素生成的图片作为图片源(toDataURL("image/png"),canvas.toDataURL('image/jpeg', quality))
tianyawhl
2019/04/04
1.2K0
Canvas之使用图片
                                                                            原
Canvas初实现拍照小游戏
由于实现的比较简单,且在部分机型上会出现点小问题,此处仅作为js代码的记录,暂不打算写相关教程。
WindCoder
2018/09/20
1.1K0
全民刷军装背后的AI技术及简单实现
昨天有Design-AI-Lab用户后台留言,问为什么换军装的h5这么火,但没见到有技术文章分析如何实现。 我回复说,大概是比较简单吧,主要工作是图像合成。 后来,我亲自体验了下,反应速度比较慢,大概是因为火了吧,访问者太多; 关键的技术是人脸识别; 前端的话,canvas实现图像合成; 整个h5设计不算惊艳,只能算一般; 运营亮点是抓住热点事件,设计了激发用户分享的产品。 再细想一想,决定还是自己动手实现一个,试试整个技术的难度。 于是,通过开发者工具,阅读了 http://www.h5case
mixlab
2018/04/17
1.5K0
全民刷军装背后的AI技术及简单实现
从Storyboard到DIY实现一个漫画生成器-01
用户只需拍摄一段视频并将其加载到 Storyboard 中即可将视频转换为单页漫画的布局。该应用会自动选择有趣的帧,并将其应用于6种视觉样式中的一种。生成的漫画大约1.6万亿种不同的可能性!
mixlab
2018/07/25
9750
从Storyboard到DIY实现一个漫画生成器-01
相关推荐
canvas详细教程! ( 近1万字吐血总结)
更多 >
交个朋友
加入前端学习入门群
前端基础系统教学 经验分享避坑指南
加入腾讯云技术交流站
前端技术前沿探索 云开发实战案例分享
加入云开发企业交流群
企业云开发实战交流 探讨技术架构优化
换一批
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档