婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av

主頁 > 知識庫 > 探究 canvas 繪圖中撤銷(undo)功能的實現方式詳解

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

熱門標簽:ai電銷機器人連接網關 鶴壁手機自動外呼系統怎么安裝 農村住宅地圖標注 威海營銷外呼系統招商 中紳電銷智能機器人 漳州人工外呼系統排名 跟電銷機器人做同事 濟南辦理400電話 鄭州電銷外呼系統違法嗎

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

結果如下圖所示:

奇怪,好像和我們預期的結果不太一致。我們想要的結果是 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 上這個測試用例,二者的性能存在數量級的差距。

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

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

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

標簽:萍鄉 甘南 惠州 營口 紅河 文山 咸陽 蘇州

巨人網絡通訊聲明:本文標題《探究 canvas 繪圖中撤銷(undo)功能的實現方式詳解》,本文關鍵詞  探究,canvas,繪,圖中,撤銷,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《探究 canvas 繪圖中撤銷(undo)功能的實現方式詳解》相關的同類信息!
  • 本頁收集關于探究 canvas 繪圖中撤銷(undo)功能的實現方式詳解的相關信息資訊供網民參考!
  • 推薦文章
    婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av
    色成年激情久久综合| 九九在线精品视频| 久久久久久久久久电影| 亚洲天堂2014| 精品粉嫩超白一线天av| 亚洲午夜精品网| 欧美日韩在线电影| 久久精品国产精品亚洲综合| 制服丝袜中文字幕亚洲| 婷婷国产v国产偷v亚洲高清| 欧美一级理论片| 国产精品私人自拍| 婷婷开心激情综合| 色美美综合视频| 国产精品久久久久久久久搜平片| 日韩精品电影一区亚洲| 日韩av不卡一区二区| 亚洲国产aⅴ成人精品无吗| 欧美日韩在线亚洲一区蜜芽| 亚洲欧洲综合另类| 久久精品国产久精国产| 日韩精品一区二区三区视频播放| 九九精品视频在线看| 丁香婷婷综合激情五月色| 日韩欧美在线不卡| 国产精品乱人伦中文| 天堂精品中文字幕在线| 激情国产一区二区| 3d动漫精品啪啪1区2区免费| 亚洲视频在线一区| 国产999精品久久久久久绿帽| 久久久久国产精品人| 国产成人av影院| 日本高清不卡aⅴ免费网站| 亚洲综合小说图片| 精品国产91乱码一区二区三区| 一本久久a久久精品亚洲| 日韩成人av影视| 成人午夜私人影院| 丰满亚洲少妇av| 国产91综合一区在线观看| 亚洲成人免费观看| 国产亚洲精久久久久久| 99re成人精品视频| 2023国产一二三区日本精品2022| 欧洲亚洲精品在线| 色综合婷婷久久| 欧美日韩在线精品一区二区三区激情 | 久久精品一区二区三区四区| 国产成a人亚洲| 福利一区二区在线观看| 成人高清av在线| 欧美一区二区视频免费观看| 色噜噜偷拍精品综合在线| 成人黄色av电影| 欧美无砖专区一中文字| 国产69精品久久99不卡| 国产91精品免费| 91免费版pro下载短视频| 国产盗摄精品一区二区三区在线| 图片区日韩欧美亚洲| 天天亚洲美女在线视频| 麻豆成人综合网| 国产一区二区0| www.在线欧美| 成人福利在线看| 色先锋资源久久综合| 国产午夜精品久久久久久免费视| 国产精品99久久久久久久女警 | 一区二区三区在线免费| 欧美日韩国产高清一区| 一本大道久久精品懂色aⅴ| 国产精选一区二区三区| 一本色道久久综合亚洲aⅴ蜜桃 | 91九色02白丝porn| 国产欧美精品一区二区色综合| 国产拍揄自揄精品视频麻豆| 亚洲精品福利视频网站| 五月天亚洲婷婷| 国产三级一区二区三区| 亚洲国产成人av好男人在线观看| 国产在线视视频有精品| 99re亚洲国产精品| 日日骚欧美日韩| 国产传媒久久文化传媒| 中文字幕av一区 二区| 95精品视频在线| 国产精品123区| 欧美三级乱人伦电影| 久久久亚洲高清| 精品一区二区三区免费| 国产精品不卡视频| 69堂国产成人免费视频| 成人高清在线视频| 国产亚洲欧美日韩日本| 国精产品一区一区三区mba视频| 欧美在线免费观看视频| 成人免费电影视频| 在线观看视频91| 亚洲黄色片在线观看| 99综合影院在线| 色婷婷精品久久二区二区蜜臂av| 久久久久久久久99精品| 欧美videos中文字幕| 蜜臂av日日欢夜夜爽一区| 色综合久久久久综合体桃花网| 色呦呦网站一区| 亚洲视频资源在线| 色视频欧美一区二区三区| 成人app在线观看| 性欧美疯狂xxxxbbbb| k8久久久一区二区三区| 国产在线精品一区二区| 三级不卡在线观看| 一区二区三区四区在线播放| 中文字幕av一区 二区| 日韩一区二区三区电影在线观看| 久久99久久精品| 日韩一级片在线播放| 99久久久无码国产精品| 不卡一二三区首页| 在线免费观看成人短视频| 国产一区二区0| 国产精品一区二区无线| 午夜久久电影网| 不卡的av电影| 99精品欧美一区二区蜜桃免费 | jlzzjlzz亚洲日本少妇| 国内外成人在线| 亚洲人成亚洲人成在线观看图片| 久久99精品国产麻豆婷婷洗澡| 91精品在线免费| 国产精品久久久久精k8| 男人操女人的视频在线观看欧美| 日韩精品一区二区三区中文不卡 | 中文字幕成人网| 色婷婷久久久亚洲一区二区三区| 日韩免费福利电影在线观看| 久久久高清一区二区三区| 555夜色666亚洲国产免| 欧美一区二区三区思思人| 精品日韩欧美在线| 亚洲精品乱码久久久久久| 日韩免费观看高清完整版在线观看| 国产欧美一区二区三区在线看蜜臀| 午夜精品久久久久久久 | 色偷偷久久人人79超碰人人澡| 久久久久一区二区三区四区| 国产精品第13页| 国产三级欧美三级| 国产电影精品久久禁18| 国产午夜精品美女毛片视频| 成人精品免费视频| 国产原创一区二区三区| 亚洲国产成人av网| 日韩欧美国产1| 精品亚洲免费视频| 国产欧美一区二区精品久导航| 亚洲最大成人网4388xx| 91麻豆精品国产91久久久更新时间 | 欧美日韩精品系列| 精品少妇一区二区三区在线播放| 99久久久久久| 激情综合网激情| 一道本成人在线| 国精产品一区一区三区mba视频| 国产精品久久久久久久久动漫| 国产美女娇喘av呻吟久久| 久久精品一区二区三区av| 日韩美一区二区三区| 欧美精品一区男女天堂| 日韩西西人体444www| 欧美激情一区三区| 国产精品福利影院| 亚洲欧洲成人精品av97| 日韩精品欧美精品| 日本女优在线视频一区二区| 久久国内精品自在自线400部| 欧洲人成人精品| 亚洲国产欧美日韩另类综合| 波多野结衣中文一区| 国产午夜精品一区二区| 国产成人鲁色资源国产91色综| k8久久久一区二区三区 | 亚洲精品第一国产综合野| 日韩国产欧美视频| 777午夜精品免费视频| 免费美女久久99| 日韩精品一区二区三区视频播放| 视频一区在线播放| 国产91精品精华液一区二区三区 | 久久国产尿小便嘘嘘| www.成人在线| 日韩中文字幕亚洲一区二区va在线| 国产99久久久国产精品免费看| 中文字幕不卡在线观看| 成人av综合一区| 久久久国产午夜精品| 日本韩国一区二区| 亚洲精品欧美综合四区|