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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…