2508C++,skia動畫

gif動畫原理

先了解一下gif動畫的原理:
gif動畫由一系列靜態圖像(或叫幀)組成.這些圖像按特定的順序排列,每一幀都代表動畫中的一個瞬間,幀圖像是支持透明的.

每兩幀之間有指定的時間間隔(一般小于60毫秒),gif播放器每渲染一幀靜態圖像后,即等待此時間間隔,依此邏輯不斷循環渲染每一幀,這樣就是一個動畫了(基于人眼的視覺暫留現象)

大部分gif動畫文件是基于一個壓縮算法生成的:如果前一幀中包含的一部分像素后一幀中包含的像素相同,則后一幀不必存儲這些像素,以此減少文件體積.

也即,這類gif動畫的第一幀是一個完整的圖像,后面每一幀存儲的像素都是這一幀與前一幀不同像素數據,沒有相同像素數據.

這類gif動畫要求播放器渲染每一幀時都是在前一幀的基礎上渲染的(疊加在前一幀上面).

在窗口中播放gif動畫

在窗口中播放動畫的原理:每渲染一幀動畫重畫一次窗口.

因為gif動畫幀與幀之間等待時間一般都比較短(此例動畫幀間隔時間為50毫秒).所以得修改窗口的基礎代碼:
全局變量設置surfaceMemory,并在創建窗口成功后,即初化它指向的內存空間.

每次執行繪畫方法后,不再釋放surfaceMemory指向的內存空間,以避免每次重畫都要重新申請內存,造不必要的CPU消耗.

改變窗口大小時,再重置surfaceMemory指向的內存空間.

全局變量設置窗口句柄,HWND hwnd,這樣在渲染每一幀請求重畫窗口.
具體見全部示例代碼.來看一下播放gif動畫的示例代碼:

    //#include <thread>
SkBitmap* frameBitmap;
void animateGif()
{std::wstring imgPath = L"D:\\project\\SkiaInAction\\動畫Gif\\demo.gif";auto pathStr = wideStrToStr(imgPath);std::unique_ptr<SkFILEStream> stream = SkFILEStream::Make(pathStr.data());std::unique_ptr<SkCodec> codec = SkCodec::MakeFromStream(std::move(stream));frameBitmap = new SkBitmap();auto t = std::thread([](std::unique_ptr<SkCodec> codec) {auto imgInfo = codec->getInfo().makeColorType(kN32_SkColorType);frameBitmap->allocN32Pixels(imgInfo.width(), imgInfo.height());int frameCount = codec->getFrameCount();std::vector<SkCodec::FrameInfo> frameInfo = codec->getFrameInfo();SkCodec::Options option;option.fFrameIndex = 0;option.fPriorFrame = -1;while (true){auto start = std::chrono::system_clock::now();codec->getPixels(imgInfo, frameBitmap->getPixels(), imgInfo.minRowBytes(), &option);InvalidateRect(hwnd, nullptr, false);auto end = std::chrono::system_clock::now();auto tSpan = end - start;auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(tSpan);auto msCount = frameInfo[option.fFrameIndex].fDuration - ms.count();auto duration = std::chrono::milliseconds(msCount);std::this_thread::sleep_for(duration);if (option.fFrameIndex == frameCount - 1){option.fPriorFrame = -1;option.fFrameIndex = 0;}else{option.fPriorFrame = option.fPriorFrame + 1;option.fFrameIndex = option.fFrameIndex + 1;}}}, std::move(codec));t.detach();
}

這段代碼有以下幾點注意:
1,animateGif方法并不是在重畫窗口時執行的,而是在創建窗口成功后執行的.

2,frameBitmap是一個SkBitmap*類型的全局變量.用來存儲一幀像素數據.

3,創建了一個新的線程以解碼gif圖像中的每一幀數據,這樣做主要是為了不讓解碼工作影響應用的主線程.

4,每時每刻都在解碼(包括線程等待std::this_thread::sleep_for),如果不在一個獨立的線程放置該工作,主線程就會卡死.

5,codec解碼器的類型是std::unique_ptr<SkCodec>(不能復制),所以不能在線程的匿名函數中抓它,必須把它移動(std::move)到匿名函數內才可以.

6,通過線程對象解附方法按后臺線程設置線程,讓其自行運行(線程對象join方法會阻塞主線程),生產環境下需自行增加處理異常,釋放線程資源保護性代碼.

剛開始執行線程方法時,執行了一系列準備工作:

得到ImageInfo信息.

解碼器(codec)的getInfo方法得到的ImageInfo對象是gif圖像默認定義的,它有可能并不適合用來解碼幀數據SkBitmap對象.

因此基于它的基礎信息(長,寬等),創建了一個新的ImageInfo對象,該對象的顏色類型為:kN32_SkColorType.
初化frameBitmap,全局變量的只能存儲一幀數據內存空間.
得到gif文件中的幀數量:codec->getFrameCount()
得到幀信息:std::vector<SkCodec::FrameInfo>frameInfo=codec->getFrameInfo();
SkCodec::FrameInfo包含了很多與幀有關的信息,其中最重要的就是幀的等待時間(單位:毫秒).

初化SkCodec::Options

SkCodec::Options對象中fFrameIndex表示當前正在播放第幾幀(默認為第0幀),fPriorFrame表示上一幀是第幾幀.

準備好這些工作之后,開始正式解碼gif圖像.
循環播放``gif,所以解碼工作是在一個不會停止的循環中的.
在一些低端電腦上,解碼工作較長,所以記錄了該時間消耗.
該工作使用std::chrono::system_clock完成,得到的時間間隔單位為毫秒.

解碼器codecgetPixels方法負責把選項中指定的幀解碼到frameBitmap指向的內存空間中.
frameBitmap->getPixels()得到的是frameBitmap持有的像素數據的地址.
InvalidateRect窗口接口提供的方法,它負責向窗口發送重畫消息.

執行此方法后,窗口將收到WM_PAINT消息.
根據frameInfo里記錄的幀信息,讓線程等待一段時間再解碼下一幀.

注意這里在幀等待時間(fDuration)上減去了解碼消耗的時間,這樣做可保證,程序即使在一些低端設備上也能流暢播放.

最后更新選項里的當前幀信息和上一幀信息.
判斷是否解碼到了最后一幀,如果是,則按第0幀設置.如果不是,則按下一幀設置,接著解碼下一幀.

整個循環中,最關鍵的信息就是:在不斷的改變frameBitmap指向的內存空間的數據,而且每改變一次(解碼一幀),即請求一次重畫窗口.

重畫方法(繪畫方法)的關鍵代碼為:

SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
auto canvas = SkCanvas::MakeRasterDirect(info, surfaceMemory, 4 * w);
if (frameBitmap) {auto x = (w - frameBitmap->width()) / 2;auto y = (h - frameBitmap->height()) / 2;canvas->writePixels(*frameBitmap, x, y);
}

這段代碼很簡單,其主要意圖是在窗口正中間繪畫frameBitmap.因為每次重畫frameBitmap里的像素數據都是一幀新的圖像,所以gif就在窗口中播放起來了.
程序運行結果如下圖所示:
程序中使用的gif圖像源自:github.com/ImageOptim/...

注意

gif動畫雖然兼容很好,但效果不好.
其最多只能處理256色,不適合真彩色圖片.gif雖然支持透明效果,但其透明效果在高分屏上表現很差,圖像顆粒感很強,有鋸齒.

gif外,還有很多其他格式的文件支持動畫,比如webp,apng,svga,lottie等.

用本節示例代碼所展示的方式解碼,播放大部分非向量格式的動畫文件.

但像svga,lottie此類向量格式動畫文件,就需要寫其他代碼來渲染了.

有時并不能根據一個文件的擴展名來判斷該文件的格式.

Skia解碼器SkCodecgetEncodedFormat方法可取文件的真實格式,如下代碼所示:

    //#include "include/codec/SkEncodedImageFormat.h"
std::unique_ptr<SkFILEStream> stream = SkFILEStream::Make(pathStr.data());
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromStream(std::move(stream));
auto imgFormat = codec->getEncodedFormat();
if(imgFormat == SkEncodedImageFormat::kGIF){//......
}

在本文示例代碼中,通過一個獨立的線程來解碼gif動畫文件中的每一幀圖像(codec->getPixels),每解碼一幀圖像重畫一次窗口(InvalidateRect),重畫窗口時,會在窗口正中間渲染解碼得到的圖像,重畫完成之后,等待一段時間(frameInfo[option. fFrameIndex].fDuration)再解碼下一幀圖像(option.fFrameIndex+=1).

實際上Skia提供了一個類型:modules\skresources\src\SkAnimCodecPlayer.h來幫助播放動畫,大家也可用該類型的代碼實現來播放gif動畫.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/95751.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/95751.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/95751.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

AI + 機器人:當大語言模型賦予機械 “思考能力”,未來工廠將迎來怎樣變革?

一、引言1.1 未來工廠變革背景與趨勢在科技飛速發展的當下&#xff0c;全球制造業正站在變革的十字路口。隨著消費者需求日益多樣化、市場競爭愈發激烈&#xff0c;傳統工廠模式的弊端逐漸顯現。生產效率低下、難以適應個性化定制需求、設備維護成本高昂且缺乏前瞻性等問題&…

pinia狀態管理的作用和意義

1. 什么是狀態管理 狀態管理就是統一管理應用中的數據&#xff0c;讓數據在多個組件之間共享和同步。 // 沒有狀態管理 - 數據分散在各個組件中 // 組件A const user ref({ name: 張三, age: 25 })// 組件B const user ref({ name: 張三, age: 25 }) // 重復定義// 組件C c…

十四、STM32-----低功耗

一、電源框圖VDDA 供電區域&#xff0c;主要是 ADC 電源以及參考電壓&#xff0c;STM32 的 ADC 模塊配備獨立的供電方 式&#xff0c;使用了 VDDA 引腳作為輸入&#xff0c;使用 VSSA 引腳作為獨立地連接&#xff0c;VREF 引腳為提供給 ADC 的 參考電壓。電壓調節器是 STM32 的…

一篇文章帶你徹底搞懂 JVM 垃圾收集器

垃圾收集器是 JVM 內存管理的執行引擎&#xff0c;負責自動回收無用的對象內存。其設計核心是 權衡&#xff1a;主要是吞吐量和停頓時間之間的權衡。沒有“最好”的收集器&#xff0c;只有“最適合”特定場景的收集器。一、核心基礎&#xff1a;分代收集模型主流 HotSpot JVM 采…

服務器排故隨筆:服務器無法ssh遠程登錄

文章目錄服務器排故隨筆&#xff1a;服務器無法遠程登錄問題現象解決過程第一步&#xff1a;確認故障描述是否準確第二步&#xff1a;確認網絡是否有問題第三步&#xff1a;確認ssh服務是否有問題第四步&#xff1a;確認防火墻是否放行sshd服務第五步&#xff1a;試試萬能的“重…

Deeplizard深度學習課程(六)—— 結合Tensorboard進行結果分析

前言 Tensorboard最初是tensorflow的可視化工具&#xff0c;被用于機器學習實驗的可視化&#xff0c;后來也適配了pytorch。Tensorboard是一個前端web界面&#xff0c;&#xff0c;能夠從文件里面讀取數據并展示它&#xff08;比如損失、準確率、網絡圖&#xff09;。具體使用可…

C語言————實戰項目“掃雷游戲”(完整代碼)

無論是找工作面試&#xff0c;還是課設大作業、考研&#xff0c;都離不開實戰項目的積累&#xff0c;如果你能把一個項目搞明白&#xff0c;并且給別人熟練的講出來&#xff0c;即使你沒有過項目經歷&#xff0c;也可以說是非常加分的&#xff0c;下面來沉浸式體驗一下這款掃雷…

數據結構之加餐篇 -順序表和鏈表加餐

目錄一、鏈表分割二、隨機鏈表的復制總結一、鏈表分割 鏈表分割 題目描述的意思就如下圖&#xff1a; 也就是把1&#xff0c;2挪到前面&#xff0c;6&#xff0c;3&#xff0c;5挪到后面&#xff0c;前者的相對順序不發生改變 這里要想往后挪就要先遍歷&#xff0c;遍歷到6…

JSP與Servlet整合數據庫開發:構建Java Web應用的全棧指南

JSP與Servlet整合數據庫開發&#xff1a;構建Java Web應用的全棧指南 概述 在Java Web開發領域&#xff0c;JSP&#xff08;JavaServer Pages&#xff09;與Servlet是構建動態Web應用的核心技術組合。Servlet作為Java EE的基礎組件&#xff0c;負責處理客戶端請求、執行業務邏…

設計五種算法精確的身份證號匹配

問題定義與數據準備 我們有兩個Excel文件&#xff1a; small.xlsx: 包含約5,000條記錄。large.xlsx: 包含約140,000條記錄。 目標&#xff1a;快速、高效地從large.xlsx中找出所有其“身份證號”字段存在于small.xlsx“身份證號”字段中的記錄&#xff0c;并將這些匹配的記錄保…

Spring 框架(IoC、AOP、Spring Boot) 的必會知識點匯總

目錄&#xff1a;&#x1f9e0; 一、Spring 框架概述1. Spring 的核心功能2. Spring 模塊化結構&#x1f9e9; 二、IoC&#xff08;控制反轉&#xff09;核心知識點1. IoC 的核心思想2. Bean 的定義與管理3. IoC 容器的核心接口4. Spring Bean 的創建方式&#x1f9f1; 三、AOP…

簡單工廠模式(Simple Factory Pattern)?? 詳解

?作者簡介&#xff1a;大家好&#xff0c;我是 Meteors., 向往著更加簡潔高效的代碼寫法與編程方式&#xff0c;持續分享Java技術內容。 &#x1f34e;個人主頁&#xff1a; Meteors.的博客 &#x1f49e;當前專欄&#xff1a; 設計模式 ?特色專欄&#xff1a; 知識分享 &…

新電腦硬盤如何分區?3個必知技巧避免“空間浪費癥”!

剛到手的新電腦&#xff0c;硬盤就像一間空蕩蕩的大倉庫&#xff0c;文件扔進去沒多久就亂成一鍋粥&#xff1f;別急&#xff0c;本文會告訴你新電腦硬盤如何分區&#xff0c;這些方法不僅可以幫你給硬盤分區&#xff0c;還可以調整/合并分區大小等。所以&#xff0c;本文的分區…

【微知】git submodule的一些用法總結(不斷更新)

文章目錄綜述要點細節如何新增一個submodule&#xff1f;如何手動.gitmodules修改首次增加一個submodule&#xff1f;git submodule init&#xff0c;init子命令依據.gitmodules.gitmodules如何命令修改某個成員以及同步&#xff1f;如果submodule需要修改分支怎么辦&#xff1…

【Spring Cloud微服務】9.一站式掌握 Seata:架構設計與 AT、TCC、Saga、XA 模式選型指南

文章目錄一、Seata 框架概述二、核心功能特性三、整體架構與三大角色1. Transaction Coordinator (TC) - 事務協調器&#xff08;Seata Server&#xff09;2. Transaction Manager (TM) - 事務管理器&#xff08;集成在客戶端&#xff09;3. Resource Manager (RM) - 資源管理器…

AI賦能!Playwright帶飛UI自動化腳本維護

80%的自動化腳本因一次改版報廢&#xff1f; 開發隨意改動ID導致腳本集體崩潰&#xff1f;背景UI自動化在敏捷開發席卷行業的今天&#xff0c;UI自動化測試深陷一個尷尬困局&#xff1a;需求迭代速度&#xff08;平均2周1次&#xff09;&#xff1e; 腳本維護速度&#xff08;平…

Redis、Zookeeper 與關系型數據庫分布式鎖方案對比及性能優化實戰指南

Redis、Zookeeper 與關系型數據庫分布式鎖方案對比及性能優化實戰指南 1. 問題背景介紹 在分布式系統中&#xff0c;多節點并發訪問共享資源時&#xff0c;如果不加鎖或加鎖不當&#xff0c;會導致數據不一致、超賣超買、競態條件等問題。常見的分布式鎖方案包括基于Redis、Zoo…

網絡安全A模塊專項練習任務十一解析

任務十一&#xff1a;IP安全協議配置任務環境說明&#xff1a; (Windows 2008)系統&#xff1a;用戶名Administrator&#xff0c;密碼Pssw0rd1.指定觸發SYN洪水攻擊保護所必須超過的TCP連接請求數閾值為5&#xff1b;使用組合鍵winR&#xff0c;輸入regedit打開注冊表編輯器&am…

金蝶中間件適配HGDB

文章目錄環境文檔用途詳細信息環境 系統平臺&#xff1a;Microsoft Windows (64-bit) 10 版本&#xff1a;5.6.5 文檔用途 本文章主要介紹金蝶中間件簡單適配HGDB。 詳細信息 一、金蝶中間件Apusic安裝與配置 1.Apusic安裝與配置 Windows和Linux下安裝部署過程相同。 &…

使用a標簽跳轉之后,會刷新一次,這個a標簽添加的樣式就會消失

<ul class"header-link"><li><a href"storeActive.html">到店活動</a></li><li><a href"fuwu.html">服務</a></li><li><a href"store.html">門店</a></l…