Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 欧美一区二区在线观看视频,九草在线观看,高清国产一区二区三区

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          python學(xué)習(xí)之路(八):牛刀小試,使用OCR技術(shù)

          python學(xué)習(xí)之路(八):牛刀小試,使用OCR技術(shù)識(shí)別圖片文字

          一篇:python學(xué)習(xí)之路(七):牛刀小試,爬取網(wǎng)絡(luò)圖片下載到電腦

          使用OCR技術(shù)識(shí)別圖片里面的文字,當(dāng)然我是沒這個(gè)技術(shù)了,不過好在python有豐富的第三方模塊,我門可以使用第三方模塊來進(jìn)行文字識(shí)別。在這篇文章中我采用百度提供的文字識(shí)別模塊來寫這個(gè)腳本。

          使用百度的文字識(shí)別,需要百度提供的APP_ID 、API_KEY、SECRET_KEY 。這三個(gè)信息只需要登錄百度云創(chuàng)建一個(gè)應(yīng)用即可。

          登錄百度云:https://cloud.baidu.com/進(jìn)入控制臺(tái)

          在上面的產(chǎn)品服務(wù)下面找到文字識(shí)別,點(diǎn)進(jìn)去。

          創(chuàng)建應(yīng)用

          填好名稱和描述后點(diǎn)擊立即創(chuàng)建

          創(chuàng)建完之后就可以看到剛剛說的3個(gè)信息了

          現(xiàn)在開始安裝百度文字識(shí)別的PYTHON模塊。

          如果已安裝pip,在命令行執(zhí)行pip install baidu-aip即可。

          沒安裝的請(qǐng)閱讀:python學(xué)習(xí)之路(五):第三方模塊的安裝與調(diào)用

          模塊的使用可以查看百度提供的說明文檔:https://cloud.baidu.com/doc/OCR/OCR-Python-SDK.html

          事先準(zhǔn)備一張文字圖片

          現(xiàn)在開始寫代碼,第一步當(dāng)然是要導(dǎo)入百度文字識(shí)別模塊

          定義剛剛的三條信息

          根據(jù)百度提供的文檔,需要在加這樣一句

          百度提供的文檔里有一個(gè)函數(shù)例子,我們直接拿來用。閱讀官方提供的說明文檔是很重要的,能讓我們快速了解模塊的用法,里面也會(huì)提供很多例子,我們可以直接拿來使用。

          輸出識(shí)別結(jié)果

          對(duì)比原圖,識(shí)別一點(diǎn)都沒有錯(cuò),正確率100%。

          從輸出的結(jié)果可以知道,百度返回的識(shí)別結(jié)果是一個(gè)字典。我們需要的文字在這個(gè)字典的words_result元素下面的每一個(gè)子元素里。我們可以用for循環(huán)來提取純文字。(字典。for循環(huán)請(qǐng)閱讀:python學(xué)習(xí)之路(六):列表、元組、字典、循環(huán)語句、條件語句)

          輸出結(jié)果現(xiàn)在是純文字了

          完整代碼:

          不過對(duì)于上面那張文字圖片,太中規(guī)中矩了,識(shí)別率非常高,現(xiàn)在試一下用手機(jī)拍一張照片,看看能不能識(shí)別里面的文字。

          來看看識(shí)別結(jié)果

          其他都識(shí)別對(duì)了,最后一行的字母錯(cuò)了幾個(gè),百度的文字識(shí)別模塊正確率還是很高的。

          新智元導(dǎo)讀】在這個(gè)數(shù)據(jù)「泛濫」的時(shí)代,你的隱私數(shù)據(jù)到底被多少機(jī)構(gòu)「花式」使用了?國(guó)外的研究人員開發(fā)一款名叫Exposing.AI的工具,可以幫人們獲知自己的人臉數(shù)據(jù)被各種面部識(shí)別系統(tǒng)使用的情況,并經(jīng)常能獲得令人出乎意料的結(jié)果。


          當(dāng)科技公司開發(fā)出「蠶食個(gè)人隱私」的面部識(shí)別系統(tǒng)的時(shí)候,他們大概率已經(jīng)得到了您意想不到的幫助:你的臉——


          公司、大學(xué)和政府實(shí)驗(yàn)室使用了數(shù)百萬張從五花八門的網(wǎng)上資源中收集來的圖像,來開發(fā)這項(xiàng)技術(shù)。


          而現(xiàn)在,國(guó)外的研究人員開發(fā)了一項(xiàng)技術(shù):「Exposing.AI 」, 這項(xiàng)技術(shù)可以幫助人們?cè)谶@些圖像中搜索他們的舊照片。

          這款工具可以匹配Flickr在線照片共享服務(wù)中的圖像,為查找各種AI技術(shù)(從面部識(shí)別到聊天機(jī)器人)所使用的大量數(shù)據(jù)提供了窗口。


          「人們需要知道,他們最私密的照片很可能被利用了」,隱私和民權(quán)組織監(jiān)視技術(shù)監(jiān)督項(xiàng)目的技術(shù)總監(jiān)利茲·奧沙利文(Liz O’sullivan)如是說。


          她與在柏林的研究員、藝術(shù)家亞當(dāng)·哈維(Adam Harvey)一起合作,也參與了Exposing.AI項(xiàng)目,


          蜜月照被國(guó)家級(jí)監(jiān)控系統(tǒng)所用,這位導(dǎo)演感到了「害怕」

          人工智能系統(tǒng)不會(huì)魔法般的變得聰明——它們是通過精確定位人類生成的數(shù)據(jù)模式來自我學(xué)習(xí)的。技術(shù)是一直在進(jìn)步和發(fā)展的,然而,它們卻學(xué)到了人類對(duì)女性和少數(shù)族裔的偏見。


          大家可能并不知道,自己其實(shí)一直在默默為AI的發(fā)展做著貢獻(xiàn)


          對(duì)于一些人來說,這是一件很令人感到新奇的事,而對(duì)于另外一些人,就非常令人毛骨悚然了。


          關(guān)鍵是,在國(guó)外,這可能是違法的——


          2008年,伊利諾斯州通過了一項(xiàng)名為《生物特征信息隱私法》(Biometric Information Privacy Act)的法律,條文中要求,如果在未經(jīng)居民同意的情況下使用他們的面部掃描,將會(huì)受到經(jīng)濟(jì)處罰。


          2006年,來自不列顛哥倫比亞省維多利亞州的紀(jì)錄片導(dǎo)演布雷特?蓋勒(Brett Gaylor)將他的蜜月照片上傳到當(dāng)時(shí)很受歡迎的Flickr網(wǎng)站上:


          經(jīng)過15年后,使用哈維提供的早期版本的Exposing.AI,他發(fā)現(xiàn),數(shù)百?gòu)埶拿墼侣眯械恼掌呀?jīng)進(jìn)入到了多個(gè)數(shù)據(jù)集——這些數(shù)據(jù)集很可能被用于訓(xùn)練世界各地的面部識(shí)別系統(tǒng)


          多年來,F(xiàn)lickr被許多公司買賣,現(xiàn)在屬于照片共享服務(wù)公司SmugMug,該公司允許用戶在知識(shí)共享許可(Creative Commons license)下分享他們的照片。

          這種許可在互聯(lián)網(wǎng)網(wǎng)站上很常見,意味著其他人可以在一定的限制下使用這些照片(盡管這些限制可能被忽略了)。


          2014年,當(dāng)時(shí)擁有Flickr的雅虎(Yahoo)在一個(gè)數(shù)據(jù)集中,為了幫助計(jì)算機(jī)視覺方面的工作,使用了許多這樣的照片。


          蓋勒好奇,自己的照片究竟是如何被到處傳來傳去的。接著,他就被告知,這些照片可能被美國(guó)和其他國(guó)家的監(jiān)控系統(tǒng)所使用


          「我的好奇變成了恐懼」,他說。


          是的,一個(gè)美國(guó)人的蜜月照片,竟然被用來建立國(guó)家級(jí)別的監(jiān)視系統(tǒng),實(shí)在是令人意外。


          非商業(yè)用途MegaFace被各種公司普遍使用,下線也無法解決問題

          幾年前,頂尖大學(xué)和科技公司的人工智能研究人員,開始從各種渠道收集照片,這些渠道包括照片分享服務(wù)、社交網(wǎng)絡(luò)、OkCupid等約會(huì)網(wǎng)站,甚至還包含安裝在大學(xué)里的相機(jī)。收集之后,他們向其他組織分享了這些照片。


          這對(duì)于研究人員來說,是正常現(xiàn)象。他們都需要把數(shù)據(jù)輸入新的人工智能系統(tǒng),所以他們就分享了所有的數(shù)據(jù),但這是合法的


          MegaFace數(shù)據(jù)集就是一個(gè)例子——這是華盛頓大學(xué)的教授們?cè)?015年創(chuàng)建的一個(gè)數(shù)據(jù)集。


          他們?cè)跀?shù)據(jù)源沒有知情且同意的情況下,就把他們的照片放進(jìn)了龐大的照片庫(kù)中。


          這些教授將圖片發(fā)布到互聯(lián)網(wǎng)上,以便其他人可以下載。


          根據(jù)《紐約時(shí)報(bào)》的公開記錄請(qǐng)求,世界各地的公司和政府機(jī)構(gòu)已經(jīng)下載了超過6000次MegaFace。其中包括美國(guó)國(guó)防承包商諾斯羅普·格魯曼公司、中央情報(bào)局的投資部門In-Q-Tel,當(dāng)然還有中國(guó)的社交媒體及公司等。


          研究人員創(chuàng)建MegaFace的起初目的,是為了將其用于一項(xiàng)旨在促進(jìn)面部識(shí)別系統(tǒng)發(fā)展的學(xué)術(shù)競(jìng)賽,并不是為商業(yè)用途準(zhǔn)備的。


          然而事實(shí)是,但只有一小部分公開下載了MegaFace的用戶參加了這場(chǎng)比賽。


          「我們不適合討論第三方項(xiàng)目」,華盛頓大學(xué)發(fā)言人維克托·巴爾塔(Victor Balta)說,「MegaFace已經(jīng)“退役”,我們也不再分發(fā)MegaFace的數(shù)據(jù)了」



          今年5月,華盛頓大學(xué)(University of Washington)將MegaFace下線。然而。這些數(shù)據(jù)的副本可能出現(xiàn)在任何地方,并繼續(xù)為新的研究提供素材。


          限制之下,隱私數(shù)據(jù)濫用問題何時(shí)可解?

          奧沙利文和哈維花了數(shù)年時(shí)間,試圖開發(fā)一個(gè)可以揭露所有這些數(shù)據(jù)使用情況的工具,實(shí)際的過程比他們預(yù)料的要困難。


          他們想要使用某人的照片,來立即告訴那個(gè)人ta的臉被包含在各種數(shù)據(jù)集的次數(shù)。


          但他們擔(dān)心,這種工具可能會(huì)被其他組織用在不好的地方。


          「潛在的危害似乎很大」,奧沙利文說。


          值得一提的是,奧沙利文還是幫助企業(yè)管理AI技術(shù)使用的紐約公司Responsible.AI的副總裁。


          最后,他們被迫限制了人們搜索該工具的方式以及搜索提供的結(jié)果。結(jié)果是,這個(gè)工具并不像他們希望的那樣有效。


          Exposing.AI本身并不使用面部識(shí)別技術(shù)。只有當(dāng)你提供了可以在線指向該照片的方式——比如一個(gè)互聯(lián)網(wǎng)地址,它才能實(shí)現(xiàn)照片的精確定位


          此外,人們只能搜索發(fā)布在Flickr上的照片:他們需要Flickr的用戶名、標(biāo)簽或網(wǎng)絡(luò)地址來識(shí)別這些照片。


          研究人員表示,這一舉措是為了加強(qiáng)安全和隱私保護(hù)能力。

          雖然限制了該工具的用途,但它的效果仍然讓人大開眼界:


          Flickr上的圖片構(gòu)成了大量的面部識(shí)別數(shù)據(jù)集,這些數(shù)據(jù)集已經(jīng)在互聯(lián)網(wǎng)上廣泛流傳,其中就包括MegaFace。


          使用Exposing.AI 找到與自己有聯(lián)系的照片并不難:只需要在舊郵件中搜索Flickr鏈接,就能找到被用于MegaFace和其他面部識(shí)別數(shù)據(jù)集的照片。


          通過這個(gè)工具,蓋勒對(duì)他所發(fā)現(xiàn)的情況感到特別不安,因?yàn)樗?jīng)認(rèn)為。互聯(lián)網(wǎng)上的信息自由流動(dòng)是一件積極的事情,而他使用Flickr,是因?yàn)槠渌耸褂盟掌臋?quán)利是受到知識(shí)共享許可限制的。


          「我現(xiàn)在正經(jīng)歷著這些后果」,他說。


          他的希望——也是奧沙利文女士和哈維先生的希望——是公司和政府會(huì)制定新的規(guī)范、政策和法律,來防止個(gè)人數(shù)據(jù)被大量收集。


          此外,蓋勒正在制作一部記載他的蜜月照片漫長(zhǎng)、曲折、令人不安的「流傳歷程」的紀(jì)錄片,來揭示這個(gè)私人數(shù)據(jù)被濫用的問題。


          參考鏈接:

          https://www.nytimes.com/2021/01/31/technology/facial-recognition-photo-tool.html?referringSource=articleShare

          問題復(fù)現(xiàn)

          之前在做 html 內(nèi)容導(dǎo)出為 pdf、圖片時(shí),先是用 html2canvas 生成截屏,再進(jìn)一步轉(zhuǎn)換為 pdf 文件,感興趣的同學(xué)可以看下這篇一文搞定前端 html 內(nèi)容轉(zhuǎn)圖片、pdf 和 word 等文件,截圖得到的圖片內(nèi)容、質(zhì)量都沒有什么問題。

          不過最近有個(gè)同事反應(yīng),他導(dǎo)出的圖片有 bug,這我倒挺好奇的,因?yàn)檫@個(gè)導(dǎo)出功能已經(jīng)用了很久,并沒有人反饋過有問題(除了那個(gè) pdf 翻頁(yè)內(nèi)容被截?cái)?/span>的問題,求助 jym :前端有好的解決方法嗎?),于是我要了他的文檔,果不其然,出現(xiàn)了下面紅框所示的問題。

          檢查一下它的 DOM 結(jié)構(gòu),發(fā)現(xiàn)是下面這樣,猜測(cè)是就是這個(gè)原因?qū)е碌摹?/span>

          為了驗(yàn)證自己的猜想,淺淺調(diào)試一下 html2canvas 的源碼,看下 html2canvas 是怎樣一個(gè)流程,它是如何將 html內(nèi)轉(zhuǎn)成 canvas 的。

          調(diào)試開始

          在 html2canvas 執(zhí)行的地方打個(gè)斷點(diǎn),開始調(diào)試代碼:

          進(jìn)入 html2canvas 內(nèi)部,可以看到內(nèi)部執(zhí)行的是 renderElement 方法:

          咱們直接進(jìn)入到 renderElement 方法內(nèi)部,看下它的執(zhí)行流程:

          renderElement 執(zhí)行流程

          判斷傳入節(jié)點(diǎn)是否有效

          • Node.ownerDocument:只讀屬性,返回當(dāng)前節(jié)點(diǎn)的頂層的 document 對(duì)象;
          • document.defaultView:屬性返回當(dāng)前 document 對(duì)象所關(guān)聯(lián)的 window 對(duì)象,如果沒有,會(huì)返回 null。

          這里主要判斷節(jié)點(diǎn),快速跳過,繼續(xù)執(zhí)行 。

          合并配置項(xiàng)

          將用戶傳入的 options 與默認(rèn)的 options 合并

          構(gòu)建配置項(xiàng),將傳入的 opts 與默認(rèn)配置合并,同時(shí)初始化一個(gè) context 上下文對(duì)象(緩存、日志等):

          緩存對(duì)象 cache

          其中 cache 為緩存對(duì)象,主要是避免資源重復(fù)加載的問題。

          原理如下:

          如果遇到圖片鏈接為 blob,在加載完成后,會(huì)添加到緩存 _cache 中:

          下次使用直接通過 this._cache[src] 從緩存中獲取,不用再發(fā)送請(qǐng)求:

          同時(shí),cache控制圖片的加載和處理,包括使用 proxy 代理和使用 cors 跨域資源共享這兩種情況資源的處理。

          繼續(xù)往下執(zhí)行

          克隆原始 DOM

          使用 DocumentCloner 方法克隆原始 DOM,避免修改原始 DOM。

          使用 clonedReferenceElement 將原始 DOM 進(jìn)行克隆,并調(diào)用 toIFrame 將克隆到的 DOM 繪制到 iframe 中進(jìn)行渲染,此時(shí)在 DOM 樹中會(huì)出現(xiàn) class 為 html2canvas-container 的 iframe 節(jié)點(diǎn),通過 window.getComputedStyle 就可以拿到要克隆的目標(biāo)節(jié)點(diǎn)上所有的樣式了。

          繪制 canvas

          前面幾步很簡(jiǎn)單,主要是對(duì)傳入的 DOM 元素進(jìn)行解析,獲取目標(biāo)節(jié)點(diǎn)的樣式和內(nèi)容。重點(diǎn)是 toCanvas 即將 DOM 渲染為 canvas 的過程,html2canvas 提供了兩種繪制 canvas 的方式:

          1. 使用 foreignObject 方式繪制 canvas
          2. 使用純 canvas 方法繪制

          咱們接著執(zhí)行,當(dāng)代碼執(zhí)行到這里時(shí)判斷是否使用 foreignObject 的方式生成 canvas:

          使用 foreignObject方式繪制 canvas

          首先了解下 foreignObject 是什么?

          1. svg 中的xmlns 全稱是“XML Namespaces”,即 XML 命名空間,正是它的存在,在瀏覽器中 svg 才能正常渲染(下面這段代碼是在 iconfont 上隨便復(fù)制的一個(gè) icon svg 代碼);
          2. xml復(fù)制代碼 <svg t="1692613183565" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20058" width="200" height="200">
            <path d="M548.571429 292.571429v182.784L731.428571 475.428571v73.142858l-182.857142-0.073143V731.428571h-73.142858V548.498286L292.571429 548.571429v-73.142858l182.857142-0.073142V292.571429h73.142858z" fill="#626B7D" p-id="20059"></path>
            </svg>
          3. foreignObject 允許包含來自不同的 XML 命名空間的元素,借助 <foreignObject> 標(biāo)簽,我們可以直接將 DOM 節(jié)點(diǎn)作為 foreignObject 插入 SVG 節(jié)點(diǎn)中進(jìn)行渲染,如下:
          4. xml復(fù)制代碼<svg xmlns="http://www.w3.org/2000/svg">
            <foreignObject width="120" height="50">
            <body xmlns="http://www.w3.org/1999/xhtml">
            <div>測(cè)試測(cè)試</div>
            </body>
            </foreignObject>
            </svg>
          5. 可以看到 <foreignObject> 標(biāo)簽里面有一個(gè)設(shè)置了 xmlns="http://www.w3.org/1999/xhtml" 命名空間的 <body> 標(biāo)簽,此時(shí) <body> 標(biāo)簽及其子標(biāo)簽都會(huì)按照 XHTML 標(biāo)準(zhǔn)渲染,實(shí)現(xiàn)了 SVG 和 XHTML 的混合使用。
          6. 這樣只需要指定對(duì)應(yīng)的命名空間,就可以把它嵌套到 foreignObject 中,然后結(jié)合 SVG,直接渲染。
          7. 對(duì)于不同的命名空間,瀏覽器解析的方式也不一樣,所以在 SVG 中嵌套 HTML,解析 SVG 的時(shí)候遇到 http://www.w3.org/2000/svg 轉(zhuǎn)化 SVG 的解析方式,當(dāng)遇到了 http://www.w3.org/1999/xhtml 就使用 HTML 的解析方式。
          8. 這是為什么 SVG 中可以嵌套 HTML,并且瀏覽器能夠正常渲染。
          9. foreignObject 已經(jīng)支持除 IE 外的主流瀏覽器

          弄懂 foreignObject 后,我們嘗試將 foreignObjectRendering 設(shè)置為 true,看看它是如何生成 canvas 的:

          js復(fù)制代碼Html2canvas(warp, {
            useCORS: true,
            foreignObjectRendering: true,
          })

          在此處打個(gè)斷點(diǎn):

          進(jìn)入 ForeignObjectRenderer 類中

          這里通過 ForeignObjectRenderer 實(shí)例化一個(gè) renderer 渲染器實(shí)例,在 ForeignObjectRenderer 構(gòu)造方法中初始化 this.canvas 對(duì)象及其上下文 this.ctx

          調(diào)用 render 生成 canvas,進(jìn)入到 render 方法:

          render 方法執(zhí)行很簡(jiǎn)單,首先通過 createForeignObjectSVG 將 DOM 內(nèi)容包裝到<foreignObject>中生成 svg:

          生成的 svg 如下所示:

          接著通過。loadSerializedSVG 將上面的 SVG 序列化成 img 的 src(SVG 直接內(nèi)聯(lián)),調(diào)用this.ctx.drawImage(img, ...); 將圖片繪制到 this.canvas 上,返回生成好的 canvas 即可。

          接著點(diǎn)擊下一步,直到回到最開始的斷點(diǎn)處,將生成好的 canvas 掛在到 DOM 上,如下:

          js
          復(fù)制代碼document.body.appendChild(canvas)

          這就解決了???收工!!!

          NoNoNo,為什么使用純 canvas 繪制就有問題呢? 作為 bug 終結(jié)者,問題必須找出來,干就完了 。

          而且使用 foreignObject 渲染還有其他問題,我們后面再說。

          foreignObject 繪制 canvas 的流程

          1. 首先克隆原始 DOM,并將所有樣式都轉(zhuǎn)為行內(nèi)樣式,避免修改到頁(yè)面;
          2. 構(gòu)建 ForeignObjectRenderer 渲染器實(shí)例,調(diào)用 renderer 方法;
          3. 將 DOM 內(nèi)容通過 createForeignObjectSVG 轉(zhuǎn)為 svg 元素;
          4. 通過 loadSerializedSVG 將上面的 svg 序列化成 img 的 src;
          5. 通過 drawImage 繪制圖片到 canvas 上。

          使用純 canvas 繪制

          要想使用純 canvas 方式繪制,那么就需要將 DOM 樹轉(zhuǎn)換為 canvas 可以識(shí)別的數(shù)據(jù)類型,html2canvas 使用 parseTree 方法來實(shí)現(xiàn)轉(zhuǎn)換,我們來看下它的執(zhí)行過程。

          直接在調(diào)用 parseTree 方法處打斷點(diǎn),進(jìn)入到 parseTree 方法內(nèi):

          parseTree 的作用是將克隆 DOM 轉(zhuǎn)換為 ElementContainer 樹。

          首先將根節(jié)點(diǎn)轉(zhuǎn)換為 ElementContainer 對(duì)象,接著再調(diào)用 parseNodeTree 遍歷根節(jié)點(diǎn)下的每一個(gè)節(jié)點(diǎn),轉(zhuǎn)換為 ElementContainer 對(duì)象。

          ElementContainer 對(duì)象主要包含 DOM 元素的信息:

          ts復(fù)制代碼type TextContainer={
            // 文本內(nèi)容
            text: string;
            // 位置和大小信息
            textBounds: TextBounds[];
          }
          
          export class ElementContainer {
            // 樣式數(shù)據(jù)
            readonly styles: CSSParsedDeclaration;
            // 當(dāng)前節(jié)點(diǎn)下的文本節(jié)點(diǎn)
            readonly textNodes: TextContainer[]=[];
            //  除文本節(jié)點(diǎn)外的子元素
            readonly elements: ElementContainer[]=[];
            // 位置大小信息(寬/高、橫/縱坐標(biāo))
            bounds: Bounds;
            // 標(biāo)志位,用來決定如何渲染的標(biāo)志
            flags=0;
            ...
          }

          ElementContainer 對(duì)象是一顆樹狀結(jié)構(gòu),層層遞歸,每個(gè)節(jié)點(diǎn)都包含以上字段,形成一顆 ElementContainer 樹,如下:

          繼續(xù)下一步

          通過 CanvasRenderer 創(chuàng)建一個(gè)渲染器 renderer,創(chuàng)建 this.canvasthis.ctx上下文對(duì)象與 ForeignObjectRenderer 類似

          得到渲染器后,調(diào)用 render 方法將 parseTree 生成的 ElementContainer 樹渲染成 canvas,在這里就與 ForeignObjectRenderer 的 render 方法產(chǎn)生差別了。

          層疊上下文

          概念不懂就看 MDN:層疊上下文

          首先我們都知道 CSS 是流式布局,也就是在沒有浮動(dòng)(float)和定位(position)的影響下,是不會(huì)發(fā)生重疊的,從上到下、由外到內(nèi)按照 DOM 樹去布局。

          而浮動(dòng)和定位的元素會(huì)脫離文檔流,形成一個(gè)層疊上下文,所以如果想正常渲染,就需要得到它們的層疊信息。

          可以想象一下:在我們的視線與網(wǎng)頁(yè)之間有一條看不見的 z 軸,層疊上下文就是一塊塊薄層,而這些薄層中有很多 DOM 元素,這些薄層根據(jù)層疊信息在這個(gè) z 軸上排列,最終形成了我們看到的絢麗多彩的頁(yè)面。

          畫個(gè)圖好像更形象些:

          白色為正常元素,黃色為 float 元素,藍(lán)色為 position 元素

          更多詳細(xì)資料請(qǐng)閱讀:深入理解 CSS 中的層疊上下文和層疊順序

          canvas 在繪制節(jié)點(diǎn)時(shí)需要先計(jì)算出整個(gè)目標(biāo)節(jié)點(diǎn)里子節(jié)點(diǎn)渲染時(shí)所展現(xiàn)的不同層級(jí),因?yàn)?Canvas 繪圖需要根據(jù)樣式計(jì)算哪些元素應(yīng)該繪制在上層,哪些在下層。元素在瀏覽器中渲染時(shí),根據(jù) W3C 的標(biāo)準(zhǔn),所有的節(jié)點(diǎn)層級(jí)布局,需要遵循層疊上下文和層疊順序的標(biāo)準(zhǔn)

          調(diào)用 parseStackingContexts 方法將 parseTree 生成的 ElementContainer 樹轉(zhuǎn)為層疊上下文。

          ElementContainer 樹中的每一個(gè) ElementContainer 節(jié)點(diǎn)都會(huì)產(chǎn)生一個(gè) ElementPaint 對(duì)象,最終生成層疊上下文的 StackingContext 如下:

          數(shù)據(jù)結(jié)構(gòu)如下:

          ts復(fù)制代碼// ElementPaint 數(shù)據(jù)結(jié)構(gòu)如下
          ElementPaint: {
            // 當(dāng)前元素的container
            container: ElementContainer
            // 當(dāng)前元素的border信息
            curves: BoundCurves
          }
          
          // StackingContext 數(shù)據(jù)結(jié)構(gòu)如下
          {
            element: ElementPaint;
            // z-index為負(fù)的元素行測(cè)會(huì)給你的層疊上下文
            negativeZIndex: StackingContext[];
            // z-index為零或auto、transform或者opacity元素形成的層疊上下文
            zeroOrAutoZIndexOrTransformedOrOpacity: StackingContext[];
            // 定位或z-index大于等于1的元素形成的層疊上下文
            positiveZIndex: StackingContext[];
            // 非定位的浮動(dòng)元素形成的層疊上下文
            nonPositionedFloats: StackingContext[];
            // 內(nèi)聯(lián)的非定位元素形成的層疊上下文
            nonPositionedInlineLevel: StackingContext[];
            // 內(nèi)聯(lián)元素
            inlineLevel: ElementPaint[];
            // 非內(nèi)聯(lián)元素
            nonInlineLevel: ElementPaint[];
          }

          渲染層疊內(nèi)容時(shí)會(huì)根據(jù) StackingContext 來決定渲染的順序。

          繼續(xù)下一步,調(diào)用 renderStack 方法,renderStack 執(zhí)行 renderStackContent 方法,咱們直接進(jìn)入 renderStackContent 內(nèi):

          canvas 繪制時(shí)遵循 w3c 規(guī)定的渲染規(guī)則 painting-order,renderStackContent 方法就是對(duì)此規(guī)則的一個(gè)代碼實(shí)現(xiàn),步驟如下:

          此處的步驟 1-7 對(duì)應(yīng)上圖代碼中的 1-7:

          1. 渲染當(dāng)前層疊上下文的元素的背景和邊框;
          2. 渲染具有負(fù) z-index 級(jí)別的子層疊上下文(最負(fù)的第一個(gè));
          3. 對(duì)于流式布局、非定位的子元素調(diào)用 renderNodeContent 和 renderNode 進(jìn)行渲染:
          4. 渲染所有未定位的浮動(dòng)子元素,對(duì)于其中每一個(gè),將該元素視為創(chuàng)建了一個(gè)新的堆棧上下文;
          5. 渲染正常流式布局、內(nèi)聯(lián)元素、非定位的子元素;
          6. 渲染 z-index 為 0 或 auto,或者 transform、opacity 等屬性的子元素;
          7. 渲染由 z-index 大于或等于 1 的子元素形成的層疊上下文,按 z-index 順序(最小的在前)。

          可以看到遍歷時(shí)會(huì)對(duì)形成層疊上下文的子元素遞歸調(diào)用 renderStack,最終達(dá)到對(duì)整個(gè)層疊上下文樹進(jìn)行遞歸的目的:

          而對(duì)于未形成層疊上下文的子元素,就直接調(diào)用 renderNode 或 renderNodeContent 這兩個(gè)方法,兩者對(duì)比,renderNode 多了一層渲染節(jié)點(diǎn)的背景色和邊框的方法(renderNode 函數(shù)內(nèi)部調(diào)用 renderNodeBackgroundAndBorders 和 renderNodeContent 方法)。

          renderNodeContent 用于渲染一個(gè)元素節(jié)點(diǎn)里面的內(nèi)容,分為八種類型:純文本、圖片、canvas、svg、iframe、checkbox 和 radio、input、li 和 ol

          除了 iframe 的繪制比較特殊:重新生成渲染器實(shí)例,調(diào)用 render 方法重新繪制,其他的繪制都是調(diào)用 canvas 的一些 API 來實(shí)現(xiàn),比如繪制文字主要用 fillText 方法、繪制圖片、canvas、svg 都是調(diào)用 drawImage 方法進(jìn)行繪制。

          所有可能用到的 API

          最終繪制到 this.canvas 上返回,至此,html2canvas 的調(diào)試就結(jié)束了。

          純 canvas 繪制流程

          1. 首先克隆原始 DOM,避免修改到頁(yè)面;
          2. 使用 parseTree 遞歸遍歷 html,生成 ElementContainer 樹(與原始 DOM 層級(jí)結(jié)構(gòu)類似);
          3. 構(gòu)建 CanvasRenderer 渲染器實(shí)例,調(diào)用 renderer 方法;
          4. 遍歷上一步生成的 ElementContainer,根據(jù)層疊規(guī)則生成層疊上下文StackingContext(與原始 DOM 層級(jí)結(jié)構(gòu)區(qū)別較大);
          5. 遍歷層疊上下文,遞歸地對(duì)層疊上下文各層中的節(jié)點(diǎn)和子層疊上下文進(jìn)行解析并按順序繪制在 Canvas 上,針對(duì)要繪制的每個(gè)節(jié)點(diǎn),主要有以下兩個(gè)過程:
          6. 創(chuàng)建畫布,根據(jù)上一步生成的層疊對(duì)象遞歸渲染,最終將 html 內(nèi)容繪制到畫布 canvas 上。

          針對(duì)最初的 Bug 分析

          ok,當(dāng)調(diào)試了一遍 html2canvas 的流程之后,再回到我們的問題上,很顯然就是 canvas 渲染的時(shí)候的問題,也就是 renderNodeContent 方法,那我們直接在這里打個(gè)斷點(diǎn)進(jìn)行調(diào)試(為了方便我只輸入一行文字進(jìn)行調(diào)試),只有當(dāng)是文本節(jié)點(diǎn)時(shí)會(huì)進(jìn)入到此斷點(diǎn),等到 mark 標(biāo)簽中對(duì)應(yīng)的元素進(jìn)入斷點(diǎn)時(shí),查看:

          可以看到此時(shí) width 和 height 已經(jīng)是父節(jié)點(diǎn)的寬高,果真如此 。

          解決方案

          既然已經(jīng)知道了問題所在,那么我們開始解決問題,有以下兩種解決方案可供參考:

          foreignObjectRendering

          在 html2canvas 配置中設(shè)置 foreignObjectRenderingtrue,此問題就可以解決嗎?

          然而現(xiàn)實(shí)并沒有這么簡(jiǎn)單,這樣又會(huì)引出新的問題:導(dǎo)出的圖片內(nèi)容丟失

          這是為什么呢?

          通過 W3C 對(duì)SVG 的介紹可知:SVG 不允許連接外部的資源,比如 HTML 中圖片鏈接、CSS link 方式的資源鏈接等,在 SVG 中都會(huì)有限制。

          解決方法:需要將圖片資源轉(zhuǎn)為 base64,然后再去生成截圖,foreighnObject 這種方法更適合截取內(nèi)容為文字內(nèi)容居多的場(chǎng)景。

          對(duì)包含背景色的內(nèi)聯(lián)標(biāo)簽截?cái)嗵幚?/h1>

          在對(duì)內(nèi)聯(lián)元素進(jìn)行截?cái)嗲埃?/span>如何確定 p 標(biāo)簽中的 mark 標(biāo)簽有沒有換行? 因?yàn)槲覀儧]必要對(duì)所有內(nèi)聯(lián)標(biāo)簽做處理。

          如果 mark 標(biāo)簽的高度超過 p 標(biāo)簽的一半時(shí),就說明已經(jīng)換行了,然后將 <mark>要求一</mark> 替換為 <mark>要</mark><mark>求</mark><mark>一</mark> 即可,代碼如下:

          ts復(fù)制代碼const handleMarkTag=(ele: HTMLElement)=> {
            const markElements=ele.querySelectorAll('mark')
            for (let sel of markElements) {
              const { height }=sel.getBoundingClientRect()
              let parentElement=sel.parentElement
              while (parentElement?.tagName !=='P') {
                parentElement=parentElement?.parentElement!
              }
              const { height: parentHeight }=(
                parentElement as unknown as HTMLElement
              ).getBoundingClientRect()
              // mark的高度沒有超過p標(biāo)簽的一半時(shí) 則沒有換行
              if (height < parentHeight / 2) continue
              // 超過一半時(shí)說明換行了
              const innerText=sel.innerText
              const outHtml=sel.outerHTML
              let newHtml=''
              innerText.split('')?.forEach((text)=> {
                newHtml +=outHtml.replace(innerText, text)
              })
              sel.outerHTML=newHtml
            }
          }

          ok,再次嘗試一下,完美解決,這下可以收工了。

          總結(jié)

          通過對(duì)一個(gè)不是 bug 的 bug 的分析,嘗試調(diào)試了一遍 html2canvas 的代碼,弄懂了瀏覽器截圖的原理及 html2canvas 的核心流程,并從中學(xué)到了幾點(diǎn)新知識(shí):

          1. svg xmnls 作用以及渲染 HTML 內(nèi)容的 foreignObject 標(biāo)簽;
          2. 使用 foreignObject 實(shí)現(xiàn)快速截圖的方法;
          3. CSS 層疊上下文的概念;
          4. canvas 繪圖的一些方法。。。

          發(fā)現(xiàn) canvas 真是一個(gè)有趣的東西,什么都能畫,像我現(xiàn)在用于畫圖的工具excalidraw、圖表庫(kù)g6、g2、echarts都是用的 canvas 搞的,看來得抽時(shí)間學(xué)習(xí)一下 canvas,不要等到“書到用時(shí)方恨少“。

          以上就是本文的全部?jī)?nèi)容,希望這篇文章對(duì)你有所幫助,歡迎點(diǎn)贊和收藏 ,如果發(fā)現(xiàn)有什么錯(cuò)誤或者更好的解決方案及建議,歡迎隨時(shí)聯(lián)系。

          作者:翔子丶 鏈接:https://juejin.cn/post/7277045020423798840 來源:稀土掘金 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。


          主站蜘蛛池模板: 精品一区二区三区无码视频| 精品国产一区二区三区久| 亚洲一区二区免费视频| 亚洲国产AV无码一区二区三区 | 四虎一区二区成人免费影院网址 | 日韩毛片基地一区二区三区| 精品国产不卡一区二区三区| 国产一区二区免费在线| 日本一区频道在线视频| 日本免费一区二区久久人人澡| 午夜精品一区二区三区在线观看| 日本一区二区视频| tom影院亚洲国产一区二区| 国产高清不卡一区二区| 亚洲综合av永久无码精品一区二区 | 国产日韩高清一区二区三区 | 日本强伦姧人妻一区二区| 大香伊人久久精品一区二区| 无码aⅴ精品一区二区三区浪潮| 91国在线啪精品一区| 东京热无码一区二区三区av| 麻豆视频一区二区三区| 男人免费视频一区二区在线观看| 国产精品日韩一区二区三区| 蜜芽亚洲av无码一区二区三区| 亚欧在线精品免费观看一区| 亚洲AV无码一区二区三区人| 亚洲AV成人一区二区三区观看| 亚洲色偷精品一区二区三区| 亚洲av成人一区二区三区在线播放| 精品一区二区ww| 精品一区二区三区水蜜桃| 亚洲国产激情一区二区三区| 三上悠亚精品一区二区久久| 中文字幕精品一区二区精品| 搡老熟女老女人一区二区| 精品欧洲AV无码一区二区男男| 国内精品视频一区二区三区| 日韩毛片一区视频免费| 美女视频在线一区二区三区| 亚洲一区二区三区四区视频|