亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > HTML > 正文

探究 canvas 繪圖中撤銷(undo)功能的實現方式詳解

2024-08-26 00:20:42
字體:
來源:轉載
供稿:網友

最近在做網頁版圖片處理相關的項目,也算是初入了 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,undo,撤銷

奇怪,好像和我們預期的結果不太一致。我們想要的結果是 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 說明:

/* * @param { Number } sx 將要被提取的圖像數據矩形區域的左上角 x 坐標 * @param { Number } sy 將要被提取的圖像數據矩形區域的左上角 y 坐標 * @param { Number } sw 將要被提取的圖像數據矩形區域的寬度 * @param { Number } sh 將要被提取的圖像數據矩形區域的高度 * @return { Object } ImageData 包含 canvas 給定的矩形圖像數據 */ ImageData ctx.getImageData(sx, sy, sw, sh);  /* * @param { Object } imagedata 包含像素值的對象 * @param { Number } dx 源圖像數據在目標畫布中的位置偏移量(x 軸方向的偏移量) * @param { Number } dy 源圖像數據在目標畫布中的位置偏移量(y 軸方向的偏移量) */ 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,undo,撤銷

因此,我們認為此優化方案是可行的。

改進后的應用方式大致如下:

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,如有錯誤與不足,歡迎指出。以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到HTML教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品久久久久久五月尺| 国外日韩电影在线观看| 久久久久亚洲精品国产| 国产91在线播放九色快色| 精品国产一区二区三区四区在线观看| 欧美三级欧美成人高清www| 亚洲人午夜精品免费| 久久久久这里只有精品| 久久视频这里只有精品| 成人情趣片在线观看免费| 国产精品海角社区在线观看| 日韩欧美极品在线观看| 久久精品美女视频网站| 91久久精品在线| 国产精品视频公开费视频| 国产精品91在线观看| 亚洲激情 国产| 亚洲春色另类小说| 日韩男女性生活视频| 欧美日韩视频免费播放| 国产精品免费在线免费| 国产美女精彩久久| 欧美在线视频一区| 国产一区二区三区在线免费观看| 亚洲精品v欧美精品v日韩精品| 国产精品久久久久久久电影| 欧美一区二三区| 亚洲自拍欧美色图| 欧美日韩亚洲一区二区| 国产成人自拍视频在线观看| 成人av番号网| 国产做受高潮69| 日韩在线观看精品| 国产成人福利夜色影视| 欧美三级免费观看| 亚洲第一精品夜夜躁人人躁| 久久久999精品免费| 欧美重口另类videos人妖| 51午夜精品视频| 欧美午夜xxx| 亚洲精品视频二区| 亚洲国产精品国自产拍av秋霞| 国产精品极品尤物在线观看| 在线视频亚洲欧美| 国产精品旅馆在线| 亚洲r级在线观看| 日韩精品视频免费| 91视频免费网站| 日韩精品视频中文在线观看| 超薄丝袜一区二区| 91在线免费网站| 欧美孕妇与黑人孕交| 欧美成人激情视频免费观看| 欧美自拍视频在线观看| 亚洲天堂男人天堂女人天堂| 国产欧美日韩91| 日本不卡视频在线播放| 欧美日韩一区二区在线| 91色中文字幕| 国产精品最新在线观看| 日韩免费观看网站| 2018日韩中文字幕| 亚洲欧美国产视频| 亚洲国产精品久久精品怡红院| 亚洲人av在线影院| 乱亲女秽乱长久久久| 久久久av免费| 亚洲欧美在线磁力| 在线观看欧美成人| 久久影院资源网| 欧美高清在线观看| 中日韩美女免费视频网站在线观看| 成人精品在线观看| 国产精品视频精品视频| 人人澡人人澡人人看欧美| 欧美亚州一区二区三区| 亚洲欧美制服第一页| 91精品国产自产在线老师啪| 日本亚洲精品在线观看| 一区二区亚洲欧洲国产日韩| 亚洲第一区中文字幕| 国产91精品青草社区| 久久成人人人人精品欧| 亚洲一品av免费观看| 狠狠色狠色综合曰曰| 亚洲电影天堂av| 日韩成人中文字幕在线观看| 久久国产精品久久久| 欧洲精品毛片网站| 亚洲欧美激情在线视频| 在线观看成人黄色| 色婷婷综合久久久久| 亚洲成人激情小说| 91亚洲一区精品| 91在线观看免费高清完整版在线观看| 久久亚洲春色中文字幕| 久久免费少妇高潮久久精品99| 欧美激情a∨在线视频播放| 精品视频在线播放色网色视频| 色妞在线综合亚洲欧美| 成人黄色中文字幕| 91在线视频一区| 成人欧美一区二区三区在线湿哒哒| 日韩av电影国产| 亚洲免费精彩视频| 久久精品成人动漫| 久久久在线观看| 2023亚洲男人天堂| 中文字幕一区二区三区电影| 91网站免费观看| 欧美在线激情网| 欧美华人在线视频| 成人精品视频久久久久| 亚洲午夜女主播在线直播| 午夜精品一区二区三区在线视频| 亚洲xxxxx性| 亚洲天堂第二页| 成人xxxxx| 欧美情侣性视频| 九九热99久久久国产盗摄| 国产精品高清在线观看| 亚洲天堂免费观看| 精品久久久久久中文字幕一区奶水| 久久精品国产一区二区电影| 久久五月情影视| www高清在线视频日韩欧美| 欧美激情久久久| 88国产精品欧美一区二区三区| 日韩精品视频免费专区在线播放| 国产精品福利久久久| 亚洲v日韩v综合v精品v| 亚洲精品国产suv| 69久久夜色精品国产69| 久久久久久午夜| 日韩免费av在线| 精品福利樱桃av导航| 国产精品久久久久久av| 91久久精品一区| 色天天综合狠狠色| 欧美性资源免费| 丝袜情趣国产精品| 成人性生交大片免费看小说| 国产91对白在线播放| 国产精品嫩草影院一区二区| 国模精品一区二区三区色天香| 黄色精品在线看| 日韩欧美第一页| 伦伦影院午夜日韩欧美限制| 日本欧美黄网站| 日韩高清电影免费观看完整| 午夜免费日韩视频| 日本免费久久高清视频| 成人情趣片在线观看免费| 亚洲欧美综合精品久久成人| 欧美日韩国产成人高清视频| 久久久久久尹人网香蕉| 欧美xxxx做受欧美| 18性欧美xxxⅹ性满足| 欧美另类在线观看| 亚洲激情视频在线播放| 欧美激情在线有限公司| 国产精品久久久久不卡| 欧美国产高跟鞋裸体秀xxxhd| 欧美日韩国产丝袜美女|