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

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

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

熱門標(biāo)簽:ai電銷機(jī)器人連接網(wǎng)關(guān) 鶴壁手機(jī)自動(dòng)外呼系統(tǒng)怎么安裝 農(nóng)村住宅地圖標(biāo)注 威海營銷外呼系統(tǒng)招商 中紳電銷智能機(jī)器人 漳州人工外呼系統(tǒng)排名 跟電銷機(jī)器人做同事 濟(jì)南辦理400電話 鄭州電銷外呼系統(tǒng)違法嗎

最近在做網(wǎng)頁版圖片處理相關(guān)的項(xiàng)目,也算是初入了 canvas 的坑。項(xiàng)目需求中有一個(gè)給圖片添加水印的功能。我們知道,在瀏覽器端實(shí)現(xiàn)圖片添加水印功能,通常的做法就是使用 canvasdrawImage 方法。對于普通的合成(比如一張底圖和一張 PNG 水印圖片合成)來說,其大致實(shí)現(xiàn)原理如下:

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');

// img: 底圖
// watermarkImg: 水印圖片
// x, y 是畫布上放置 img 的坐標(biāo)
ctx.drawImage(img, x, y);
ctx.drawImage(watermarkImg, x, y);

直接連續(xù)使用 drawImage() 把對應(yīng)的圖片繪制到 canvas 畫布上就行。

以上就是背景介紹。但是略麻煩的是添加水印的需求中還有一個(gè)需要實(shí)現(xiàn)的功能是用戶能夠切換水印的位置。我們自然會(huì)想到能否實(shí)現(xiàn) canvasundo 功能,當(dāng)用戶切換水印位置時(shí),先撤銷上一步 drawImage 操作,然后再重新繪制水印圖片位置。

restore / save ?

效率最高也是最方便的肯定是查閱 canvas 2D 原生 API 是否有此功能。經(jīng)過一番搜索, restore / save 這一對 API 進(jìn)入視線。我們先看一下這兩個(gè) API 的描述:

CanvasRenderingContext2D.restore() 是 Canvas 2D API 通過在繪圖狀態(tài)棧中彈出頂端的狀態(tài),將 canvas 恢復(fù)到最近的保存狀態(tài)的方法。 如果沒有保存狀態(tài),此方法不做任何改變。

CanvasRenderingContext2D.save() 是 Canvas 2D API 通過將當(dāng)前狀態(tài)放入棧中,保存 canvas 全部狀態(tài)的方法。

乍看起來可以滿足需求。我們看一下官方示例代碼:

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

ctx.save(); // 保存默認(rèn)的狀態(tài)
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 100, 100);

ctx.restore(); // 還原到上次保存的默認(rèn)狀態(tài)
ctx.fillRect(150, 75, 100, 100);

結(jié)果如下圖所示:

奇怪,好像和我們預(yù)期的結(jié)果不太一致。我們想要的結(jié)果是 save 方法調(diào)用后能夠保存當(dāng)前畫布的快照, resolve 方法調(diào)用后能夠完全回到上一個(gè)保存的快照處的狀態(tài)。

再仔細(xì)研究一下 API。原來我們遺漏一個(gè)重要概念: drawing state ,也就是繪制狀態(tài)。保存到棧中的繪制狀態(tài)包含以下幾個(gè)部分:

  1. 當(dāng)前的變換矩陣
  2. 當(dāng)前的剪切區(qū)域
  3. 當(dāng)前的虛線列表

以下屬性當(dāng)前的值:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled.

好吧, drawImage 操作后對畫布的改變根本不存在于繪制狀態(tài)中。所以,使用 resolve / save 無法實(shí)現(xiàn)我們需要的 undo 功能。

模擬棧實(shí)現(xiàn)

既然原生的 API 保存繪制狀態(tài)的棧無法滿足需求,那么自然我們會(huì)想到自己模擬一個(gè)保存操作的棧。隨之而來的問題就是:每次繪制操作之后,應(yīng)該保存什么數(shù)據(jù)進(jìn)棧?前面說過,我們想要的是每步繪制操作之后能夠保存當(dāng)前畫布的 快照 ,如果能拿到快照數(shù)據(jù),同時(shí)能利用快照數(shù)據(jù)恢復(fù)畫布的話,問題也就迎刃而解了。

幸運(yùn)的是 canvas 2D 原生提供了獲取快照和通過快照恢復(fù)畫布的 API —— getImageData / putImageData 。以下是 API 說明:

/*
 * @param { Number } sx 將要被提取的圖像數(shù)據(jù)矩形區(qū)域的左上角 x 坐標(biāo)
 * @param { Number } sy 將要被提取的圖像數(shù)據(jù)矩形區(qū)域的左上角 y 坐標(biāo)
 * @param { Number } sw 將要被提取的圖像數(shù)據(jù)矩形區(qū)域的寬度
 * @param { Number } sh 將要被提取的圖像數(shù)據(jù)矩形區(qū)域的高度
 * @return { Object } ImageData 包含 canvas 給定的矩形圖像數(shù)據(jù)
 */
 ImageData ctx.getImageData(sx, sy, sw, sh);
 
 /*
 * @param { Object } imagedata 包含像素值的對象
 * @param { Number } dx 源圖像數(shù)據(jù)在目標(biāo)畫布中的位置偏移量(x 軸方向的偏移量)
 * @param { Number } dy 源圖像數(shù)據(jù)在目標(biāo)畫布中的位置偏移量(y 軸方向的偏移量)
 */
 void ctx.putImageData(imagedata, dx, dy);

我們來看一個(gè)簡單的應(yīng)用方式:

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 方法,每次調(diào)用該方法之前都會(huì)保存上一個(gè)狀態(tài)的快照到模擬的棧中。在執(zhí)行 undo 操作時(shí),從棧中取出最新保存的快照,然后重新繪制畫布,即可實(shí)現(xiàn)撤銷操作。實(shí)際測試也符合預(yù)期。

性能優(yōu)化

上一節(jié)中我們很粗獷地實(shí)現(xiàn)了 canvas 的撤銷功能。為什么說粗獷呢?一個(gè)很顯而易見的原因就是此方案性能不好。我們的方案相當(dāng)于每次都是重新繪制整個(gè)畫布。假設(shè)操作步驟很多,我們在模擬棧也就是內(nèi)存中就會(huì)保存很多預(yù)存的圖片數(shù)據(jù)。此外,在繪制圖片過于復(fù)雜時(shí), getImageDataputImageData 這兩個(gè)方法會(huì)產(chǎn)生比較嚴(yán)重的性能問題。stackoverflow 上有詳細(xì)的討論: Why is putImageData so slow? 。我們還可以從 jsperf 上這個(gè)測試用例的數(shù)據(jù)來驗(yàn)證這一點(diǎn)。淘寶 FED 在Canvas 最佳實(shí)踐中也提到了盡量“不在動(dòng)畫中使用 putImageData 方法”。另外,文章里還提到一點(diǎn),“盡可能調(diào)用那些渲染開銷較低的 API”。我們可以從這里入手思考如何進(jìn)行優(yōu)化。

之前說過,我們通過對整個(gè)畫布保存快照的方式來記錄每個(gè)操作,換個(gè)角度思考,如果我們把每次繪制的動(dòng)作保存到一個(gè)數(shù)組中,在每次執(zhí)行撤銷操作時(shí),首先清空畫布,然后重繪這個(gè)繪圖動(dòng)作數(shù)組,也可以實(shí)現(xiàn)撤銷操作的功能。可行性方面,首先這樣可以減少保存到內(nèi)存的數(shù)據(jù)量,其次還避免了使用渲染開銷較高的 putImageData 。以 drawImage 為比較對象,看 jsperf 上這個(gè)測試用例,二者的性能存在數(shù)量級的差距。

因此,我們認(rèn)為此優(yōu)化方案是可行的。

改進(jìn)后的應(yīng)用方式大致如下:

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();
            // 刪除當(dāng)前操作
            this.executionArray.pop();
            // 逐個(gè)執(zhí)行繪圖動(dòng)作進(jìn)行重繪
            for (let exe of this.executionArray) {
                this[exe.method](...exe.params)
            }
        }
    }
}

新人入坑 canvas,如有錯(cuò)誤與不足,歡迎指出。以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

標(biāo)簽:萍鄉(xiāng) 甘南 惠州 營口 紅河 文山 咸陽 蘇州

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《探究 canvas 繪圖中撤銷(undo)功能的實(shí)現(xiàn)方式詳解》,本文關(guān)鍵詞  探究,canvas,繪,圖中,撤銷,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《探究 canvas 繪圖中撤銷(undo)功能的實(shí)現(xiàn)方式詳解》相關(guān)的同類信息!
  • 本頁收集關(guān)于探究 canvas 繪圖中撤銷(undo)功能的實(shí)現(xiàn)方式詳解的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av
    久久先锋影音av鲁色资源网| 色综合天天天天做夜夜夜夜做| 亚洲精品欧美激情| 国产欧美一二三区| 国产亚洲1区2区3区| 亚洲精品在线免费观看视频| 精品欧美乱码久久久久久| 欧美不卡一区二区三区四区| 日韩欧美在线网站| 久久久国际精品| 亚洲丝袜自拍清纯另类| 亚洲久草在线视频| 日韩成人一级大片| 国产精品一区在线观看乱码 | 亚洲成人av一区二区三区| 亚洲国产欧美在线人成| 日av在线不卡| 成人理论电影网| 在线观看一区不卡| 欧美一级一区二区| 日本一区二区成人在线| 一区二区三区免费观看| 日本少妇一区二区| 国产成人小视频| 欧美色欧美亚洲另类二区| 欧美精品一区在线观看| 国产精品国产三级国产普通话99 | 欧美久久一区二区| 26uuu精品一区二区| 亚洲丝袜美腿综合| 麻豆国产91在线播放| 成人综合婷婷国产精品久久 | 波多野结衣亚洲一区| 欧美中文字幕一二三区视频| 欧美成人福利视频| 亚洲精品国产视频| 国产综合久久久久影院| 色菇凉天天综合网| 久久伊人中文字幕| 性久久久久久久久久久久| 国产一区二区免费看| 欧美日韩一级片在线观看| 国产精品乱人伦中文| 免费观看在线综合色| 在线视频国内自拍亚洲视频| 国产三级精品三级在线专区| 亚洲一区二区三区国产| 成人免费黄色在线| 2欧美一区二区三区在线观看视频| 亚洲午夜一区二区| 91视频国产资源| 国产日本一区二区| 精品一区二区三区在线观看| 欧美日韩视频不卡| 亚洲欧美欧美一区二区三区| 国产成人免费网站| 精品国产不卡一区二区三区| 日韩成人一级片| 欧美性色黄大片| 亚洲男女毛片无遮挡| av在线不卡电影| 国产精品日韩精品欧美在线| 久久99国产精品免费| 91精品国产综合久久久久久漫画| 亚洲精品久久久蜜桃| 91视频免费播放| 最新不卡av在线| 99久久99久久综合| 亚洲色大成网站www久久九九| 成人激情黄色小说| 国产精品成人免费在线| 99免费精品视频| 中文字幕综合网| 在线免费视频一区二区| 亚洲动漫第一页| 欧美日韩国产综合一区二区 | 不卡免费追剧大全电视剧网站| 久久久一区二区三区捆绑**| 国产自产高清不卡| 国产日韩av一区| 成人av中文字幕| 亚洲精品第一国产综合野| 色综合久久88色综合天天 | 国产揄拍国内精品对白| 久久这里只有精品6| 成人妖精视频yjsp地址| 亚洲欧美一区二区不卡| 在线观看一区二区视频| 亚洲成av人片观看| 欧美精品一区二区久久久 | 亚洲国产一区二区视频| 777xxx欧美| 国产专区欧美精品| 国产精品久久久久久久裸模| 欧洲一区在线观看| 蜜臀久久99精品久久久画质超高清| 精品久久人人做人人爰| 国产成人亚洲综合a∨婷婷| 亚洲同性gay激情无套| 欧美精品精品一区| 激情综合网天天干| 亚洲欧美日韩系列| 精品少妇一区二区| 91在线观看污| 免费看精品久久片| 亚洲精品成人a在线观看| 日韩精品中文字幕在线一区| 国产suv精品一区二区6| 亚洲午夜精品网| 久久久精品黄色| 欧美日韩一区二区三区视频| 国产一区视频网站| 亚洲国产精品久久不卡毛片| 精品99999| 欧美精品自拍偷拍动漫精品| 高清beeg欧美| 蜜臀av性久久久久蜜臀aⅴ | 综合中文字幕亚洲| 精品国产99国产精品| 91官网在线观看| 成人亚洲精品久久久久软件| 天涯成人国产亚洲精品一区av| 国产精品视频在线看| 日韩免费视频一区二区| 在线视频欧美区| jizzjizzjizz欧美| 成人在线视频一区二区| 免费在线观看成人| 一区二区三区在线观看网站| 国产欧美一区二区精品忘忧草| 欧美一区二区三区免费大片| 99国产麻豆精品| 成人免费av在线| 精品一区二区三区在线播放| 亚洲电影第三页| 亚洲乱码日产精品bd| 中文字幕亚洲一区二区va在线| 精品日韩一区二区| 日韩三级视频在线观看| 欧美一二三区在线| 51精品视频一区二区三区| 欧洲国内综合视频| 在线亚洲+欧美+日本专区| 国产69精品久久99不卡| 盗摄精品av一区二区三区| 国产中文字幕一区| 国产大陆亚洲精品国产| 国产成人精品免费在线| 国产乱一区二区| 国产一区二区三区久久久| 国产伦精品一区二区三区免费迷| 国产酒店精品激情| 国产传媒欧美日韩成人| 国产sm精品调教视频网站| 不卡的看片网站| 一本到不卡精品视频在线观看| 91精品办公室少妇高潮对白| 在线一区二区三区四区五区| 欧美伊人精品成人久久综合97| 欧美性色aⅴ视频一区日韩精品| 欧美人牲a欧美精品| 91精品国产品国语在线不卡| 精品少妇一区二区三区 | 日本韩国精品在线| 在线观看国产一区二区| 制服丝袜中文字幕一区| 久久久国产午夜精品| 国产精品久久久久久久久动漫| 亚洲码国产岛国毛片在线| 无吗不卡中文字幕| 国产成人精品一区二| 97se亚洲国产综合自在线不卡| 欧美视频在线观看一区二区| 日韩久久精品一区| 亚洲视频中文字幕| 蜜桃av噜噜一区二区三区小说| 国产麻豆精品一区二区| 91丝袜国产在线播放| 日韩一区二区三| 国产精品久久久久久久久免费樱桃 | 免费看欧美美女黄的网站| 福利一区福利二区| 欧美亚洲国产一区二区三区| 亚洲精品在线免费观看视频| 国产精品久久久久7777按摩| 午夜电影一区二区三区| 国产成人综合亚洲网站| 欧美在线观看一二区| 久久久久久久性| 亚洲国产中文字幕| 成人动漫精品一区二区| 91精品国产综合久久精品麻豆| 国产欧美一区视频| 久99久精品视频免费观看| 一本大道久久精品懂色aⅴ| 欧美精品一区二区三区很污很色的 | 一区二区三区四区高清精品免费观看| 麻豆国产精品777777在线| 欧美色综合影院| 国产精品久久久久永久免费观看 |