返回顶部
首页 > 资讯 > 精选 >canvas绘图中如何实现撤销功能
  • 618
分享到

canvas绘图中如何实现撤销功能

2023-06-09 21:06:41 618人浏览 泡泡鱼
摘要

这篇文章给大家分享的是有关canvas绘图中如何实现撤销功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。最近在做网页版图片处理相关的项目,也算是初入了 canvas 的坑。项目需求中有一个给图片添加水印的功能。

这篇文章给大家分享的是有关canvas绘图中如何实现撤销功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

最近在做网页版图片处理相关的项目,也算是初入了 canvas 的坑。项目需求中有一个给图片添加水印的功能。我们知道,在浏览器端实现图片添加水印功能,通常的做法就是使用 canvasdrawImage 方法。对于普通的合成(比如一张底图和一张 PNG 水印图片合成)来说,其大致实现原理如下:

var canvas = document.getElementById("canvas");var ctx = canvas.getContext('2d');// img: 底图// watermarkImg: 水印图片// x, y 是画布上放置 img 的坐标ctx.drawImage(img, x, y);ctx.drawImage(watermarkImg, x, y);

直接连续使用 drawImage() 把对应的图片绘制到 canvas 画布上就行。

以上就是背景介绍。但是略麻烦的是添加水印的需求中还有一个需要实现的功能是用户能够切换水印的位置。我们自然会想到能否实现 canvasundo 功能,当用户切换水印位置时,先撤销上一步 drawImage 操作,然后再重新绘制水印图片位置。

restore / save ?

效率最高也是最方便的肯定是查阅 canvas 2D 原生 api 是否有此功能。经过一番搜索, restore / save 这一对 API 进入视线。我们先看一下这两个 API 的描述:

CanvasRenderinGContext2D.restore() 是 Canvas 2D API 通过在绘图状态栈中弹出顶端的状态,将 canvas 恢复到最近的保存状态的方法。 如果没有保存状态,此方法不做任何改变。

CanvasRenderingContext2D.save() 是 Canvas 2D API 通过将当前状态放入栈中,保存 canvas 全部状态的方法。

乍看起来可以满足需求。我们看一下官方示例代码:

var canvas = document.getElementById("canvas");var ctx = canvas.getContext("2d");ctx.save(); // 保存默认的状态ctx.fillStyle = "green";ctx.fillRect(10, 10, 100, 100);ctx.restore(); // 还原到上次保存的默认状态ctx.fillRect(150, 75, 100, 100);

结果如下图所示:

canvas绘图中如何实现撤销功能

奇怪,好像和我们预期的结果不太一致。我们想要的结果是 save 方法调用后能够保存当前画布的快照, resolve 方法调用后能够完全回到上一个保存的快照处的状态。

再仔细研究一下 API。原来我们遗漏一个重要概念: drawing state ,也就是绘制状态。保存到栈中的绘制状态包含以下几个部分:

  1. 当前的变换矩阵

  2. 当前的剪切区域

  3. 当前的虚线列表

以下属性当前的值:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled.

好吧, drawImage 操作后对画布的改变根本不存在于绘制状态中。所以,使用 resolve / save 无法实现我们需要的 undo 功能。

模拟栈实现

既然原生的 API 保存绘制状态的栈无法满足需求,那么自然我们会想到自己模拟一个保存操作的栈。随之而来的问题就是:每次绘制操作之后,应该保存什么数据进栈?前面说过,我们想要的是每步绘制操作之后能够保存当前画布的 快照 ,如果能拿到快照数据,同时能利用快照数据恢复画布的话,问题也就迎刃而解了。

幸运的是 canvas 2D 原生提供了获取快照和通过快照恢复画布的 API —— getImageData / putImageData 。以下是 API 说明:

 ImageData ctx.getImageData(sx, sy, sw, sh);   void ctx.putImageData(imagedata, dx, dy);

我们来看一个简单的应用方式:

class WrappedCanvas {    constructor (canvas) {        this.ctx = canvas.getContext('2d');        this.width = this.ctx.canvas.width;        this.height = this.ctx.canvas.height;        this.imgStack = [];    }    drawImage (...params) {        const imgData = this.ctx.getImageData(0, 0, this.width, this.height);        this.imgStack.push(imgData);this.ctx.drawImage(...params);    }    undo () {        if (this.imgStack.length > 0) {            const imgData = this.imgStack.pop();            this.ctx.putImageData(imgData, 0, 0);        }    }}

我们封装了一下 canvasdrawImage 方法,每次调用该方法之前都会保存上一个状态的快照到模拟的栈中。在执行 undo 操作时,从栈中取出最新保存的快照,然后重新绘制画布,即可实现撤销操作。实际测试也符合预期。

性能优化

上一节中我们很粗犷地实现了 canvas 的撤销功能。为什么说粗犷呢?一个很显而易见的原因就是此方案性能不好。我们的方案相当于每次都是重新绘制整个画布。假设操作步骤很多,我们在模拟栈也就是内存中就会保存很多预存的图片数据。此外,在绘制图片过于复杂时, getImageDataputImageData 这两个方法会产生比较严重的性能问题。stackoverflow 上有详细的讨论: Why is putImageData so slow? 。我们还可以从 jsperf 上这个测试用例的数据来验证这一点。淘宝 FED 在Canvas 最佳实践中也提到了尽量“不在动画中使用 putImageData 方法”。另外,文章里还提到一点,“尽可能调用那些渲染开销较低的 API”。我们可以从这里入手思考如何进行优化

之前说过,我们通过对整个画布保存快照的方式来记录每个操作,换个角度思考,如果我们把每次绘制的动作保存到一个数组中,在每次执行撤销操作时,首先清空画布,然后重绘这个绘图动作数组,也可以实现撤销操作的功能。可行性方面,首先这样可以减少保存到内存的数据量,其次还避免了使用渲染开销较高的 putImageData 。以 drawImage 为比较对象,看 jsperf 上这个测试用例,二者的性能存在数量级的差距。

canvas绘图中如何实现撤销功能

因此,我们认为此优化方案是可行的。

改进后的应用方式大致如下:

class WrappedCanvas {    constructor (canvas) {        this.ctx = canvas.getContext('2d');        this.width = this.ctx.canvas.width;        this.height = this.ctx.canvas.height;        this.executionArray = [];    }    drawImage (...params) {        this.executionArray.push({            method: 'drawImage',            params: params        });this.ctx.drawImage(...params);    }    clearCanvas () {        this.ctx.clearRect(0, 0, this.width, this.height);    }    undo () {        if (this.executionArray.length > 0) {            // 清空画布            this.clearCanvas();            // 删除当前操作            this.executionArray.pop();            // 逐个执行绘图动作进行重绘            for (let exe of this.executionArray) {                this[exe.method](...exe.params)            }        }    }}

感谢各位的阅读!关于“canvas绘图中如何实现撤销功能”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

--结束END--

本文标题: canvas绘图中如何实现撤销功能

本文链接: https://lsjlt.com/news/257901.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • canvas绘图中如何实现撤销功能
    这篇文章给大家分享的是有关canvas绘图中如何实现撤销功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。最近在做网页版图片处理相关的项目,也算是初入了 canvas 的坑。项目需求中有一个给图片添加水印的功能。...
    99+
    2023-06-09
  • mfc撤销功能如何实现
    MFC(Microsoft Foundation Classes)是一个用于开发Windows应用程序的C++类库。MFC提供了撤销...
    99+
    2023-09-16
    mfc
  • mfc如何实现撤销功能
    MFC(Microsoft Foundation Classes)是一个用于开发Windows应用程序的框架,可以通过以下步骤实现撤...
    99+
    2023-10-23
    mfc
  • Android如何实现iPhone晃动撤销输入功能
    小编给大家分享一下Android如何实现iPhone晃动撤销输入功能,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!很多程序中我们可能会输入长文本内容,比如短信,写...
    99+
    2023-05-30
    android iphone
  • php如何实现绘图功能
    本文小编为大家详细介绍“php如何实现绘图功能”,内容详细,步骤清晰,细节处理妥当,希望这篇“php如何实现绘图功能”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。PHP绘图可以使用GD扩展或ImageMagick...
    99+
    2023-07-05
  • 怎么用Immutable.js实现撤销重做功能
    这篇文章主要介绍了怎么用Immutable.js实现撤销重做功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么用Immutable.js实现撤销重做功能文章都会有所收获,下...
    99+
    2024-04-02
  • OpenCv实现绘图功能
    本文实例为大家分享了OpenCv实现绘图功能的具体代码,供大家参考,具体内容如下 绘制一个图像在上面画线: import numpy as np import matplotli...
    99+
    2024-04-02
  • canvas与html5如何实现视频截图功能
    这篇文章主要介绍了canvas与html5如何实现视频截图功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。制作方法:1.在页面中加载视频在...
    99+
    2024-04-02
  • 如何利用canvas实现图片压缩功能
    小编给大家分享一下如何利用canvas实现图片压缩功能,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!项目中做身份证识别时,需要传送图片的 base64 格式编码,...
    99+
    2023-06-09
  • vue项目中canvas实现截图功能
    本文实例为大家分享了vue项目中canvas实现截图功能的具体代码,供大家参考,具体内容如下 实现效果: 整理一下最近在vue项目中做的一个截图功能(只能够截取图片),即用鼠标在画...
    99+
    2024-04-02
  • Vue Echarts如何实现多功能图表绘制
    这篇文章主要介绍“Vue Echarts如何实现多功能图表绘制”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue Echarts如何实现多功能图表绘制”文章能帮助大家解决问题。...
    99+
    2023-07-05
  • Vue+canvas实现视频截图功能
    开发过程中遇到一个实际问题,上传的视频需要提供视频封面(视频封面必填)。封面可以自己制作并上传,但是这样需要脱离网站,用其他方式制作封面,使用体验并不友好,因此第一个想到的方案是:上...
    99+
    2024-04-02
  • C++实现自定义撤销重做功能的示例代码
    目录前言一、完整代码二、使用示例1、基本用法2、gdi画线撤销总结前言 在使用c++做界面开发的时候,需要涉及到到撤销重做操作,尤其是实现白板功能时需要自己实现一套撤销重做功能,如果...
    99+
    2022-12-15
    C++自定义撤销重做功能 C++撤销重做功能 C++撤销重做
  • unity如何实现绘画功能
    这篇文章将为大家详细讲解有关unity如何实现绘画功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。本文实例为大家分享了unity实现绘画功能的具体代码,具体内容如下直接先上效果:gif里面有些颜色不一样...
    99+
    2023-06-14
  • html2中canvas如何生成清晰的图片实现打印功能
    小编给大家分享一下html2中canvas如何生成清晰的图片实现打印功能,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!基本用法window.html2canvas(dom, {   ...
    99+
    2023-06-09
  • 小程序中如何实现canvas拖动功能
    这篇文章给大家分享的是有关小程序中如何实现canvas拖动功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。创建画布<canvas type="2d" id=&quo...
    99+
    2023-06-21
  • html5如何利用canvas实现颜色容差抠图功能
    这篇文章主要介绍了html5如何利用canvas实现颜色容差抠图功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。利用canvas的getImageData,我们可以获取到一...
    99+
    2023-06-09
  • canvas如何实现轨迹回放功能
    这篇文章将为大家详细讲解有关canvas如何实现轨迹回放功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。json结构[   {  &nbs...
    99+
    2024-04-02
  • html5中如何实现Canvas路径绘图、坐标变换应用
    这篇文章主要介绍了html5中如何实现Canvas路径绘图、坐标变换应用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。 代码如下:...
    99+
    2024-04-02
  • html5中canvas如何实现画图
    这篇文章主要为大家展示了“html5中canvas如何实现画图”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“html5中canvas如何实现画图”这篇文章吧。 ...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作