【学习笔记】Windows GDI绘图(十三)动画播放ImageAnimator(可调速)

文章目录

  • 前言
  • 定义
  • 方法
    • CanAnimate 是否可动画显示
    • Animate 动画显示多帧图像
    • UpdateFrames
    • StopAnimate终止动画
    • Image.GetFrameCount 获取动画总帧数
    • Image.GetPropertyItem(0x5100) 获取帧延迟
  • 自定义GIF播放(可调速)

前言

在前一篇文章中用到ImageAnimator获取了GIF动画的一些属性,但没有真正使用ImageAnimator来进行GIF动画播放(还可能误导),所有这篇就深入了解ImageAnimator的使用。
本文使用的动画为另一篇文章 心动(GDI+) 使用 GDI+纯手工绘制生成,有兴趣的可以查看原文,并配原码。

定义

public sealed class ImageAnimator

作用:对具有基于时间帧的图像进行动画显示。

方法

CanAnimate 是否可动画显示

原型:

public static bool CanAnimate (System.Drawing.Image image);

作用:判断指定图像是否包含基于时间帧。

Animate 动画显示多帧图像

原型:

public static void Animate (System.Drawing.Image image, EventHandler onFrameChangedHandler);

作用:将多帧图像显示为动画。

UpdateFrames

原型:

public static void UpdateFrames ();
public static void UpdateFrames (System.Drawing.Image image);

作用:使多帧动画前推进,下次渲染时,使用新的图像。

1、加载一个动画Image.FromFile
2、判断该图像是基于时间帧的图像 CanAnimate
3、指定帧切换事件 Animate
4、推进多帧动画
5、绘制最新图像

private Image gifImg = null;
/// <summary>
/// 加载多帧图像
/// </summary>
private void LoadGif()
{
    gifImg = Image.FromFile("Heartbeat.gif");
    if (ImageAnimator.CanAnimate(gifImg))
    {
        ImageAnimator.Animate(gifImg, OnFrameChanged);
    }
}
//切换帧事件
private void OnFrameChanged(object o, EventArgs e)
{
    //强制显示
    this.paintCtrl_Main.Panel_Main.Invalidate();
}

[System.ComponentModel.Description("ImageAnimator的Animate方法")]
public void Demo13_01(PaintEventArgs e)
{
    if (gifImg == null)
    {
        LoadGif();
    }
    ImageAnimator.UpdateFrames(gifImg);
    e.Graphics.DrawImage(gifImg, 0, 0, this.paintCtrl_Main.Panel_Main.Width, 
                                       this.paintCtrl_Main.Panel_Main.Height);
}

由以上代码,使用ImageAnimator加GDI+绘制,可以很容易实现GIF动画的播放。
Gif动画播放

StopAnimate终止动画

原型:

public static void StopAnimate (System.Drawing.Image image, EventHandler onFrameChangedHandler);

作用:停止动画显示。(实际上没有真正停止,还需要其他条件配合)

private Image gifImg = null;
EventHandler OnFrameChangedEventHandler;
/// <summary>
/// 加载多帧图像
/// </summary>
private void LoadGif()
{
    gifImg = Image.FromFile("Heartbeat.gif");
    if (ImageAnimator.CanAnimate(gifImg))
    {
        OnFrameChangedEventHandler = new EventHandler(OnFrameChanged);
        isStop = false;
        ImageAnimator.Animate(gifImg, OnFrameChangedEventHandler);
        this.paintCtrl_Main.Panel_Main.Click += PaintCtrl_Main_Click; ;
    }
}

private void PaintCtrl_Main_Click(object sender, EventArgs e)
{
    if (isStop)
    {
        ImageAnimator.Animate(gifImg, OnFrameChangedEventHandler);
    }
    else
    {
        ImageAnimator.StopAnimate(gifImage, OnFrameChangedEventHandler);
    }
    isStop = !isStop;
}

private bool isStop = false;
private void OnFrameChangedStop(object o, EventArgs e)
{
    isStop = true;
}

//切换帧事件
private void OnFrameChanged(object o, EventArgs e)
{
    //强制显示
    this.paintCtrl_Main.Panel_Main.Invalidate();
}

[System.ComponentModel.Description("ImageAnimator的Animate方法")]
public void Demo13_01(PaintEventArgs e)
{
    if (gifImg == null)
    {
        LoadGif();
    }
    if (!isStop)
    {
        ImageAnimator.UpdateFrames(gifImg);
    }
    e.Graphics.DrawImage(gifImg, 0, 0, this.paintCtrl_Main.Panel_Main.Width,
                               this.paintCtrl_Main.Panel_Main.Height);
}

通过鼠标点击,控制Gif动画开始,还是停止。

Image.GetFrameCount 获取动画总帧数

原型:

public int GetFrameCount (System.Drawing.Imaging.FrameDimension dimension);

作用:返回指定维度的帧数。

Image.GetPropertyItem(0x5100) 获取帧延迟

作用:返回每帧的延迟数

/// <summary>
/// 获取帧总数、帧延迟
/// </summary>
private void GetFrameDelays()
{
    //总帧数
    var frameCount = gifImg.GetFrameCount(FrameDimension.Time);
    var frameDelay = gifImg.GetPropertyItem(0x5100);//FrameDelay 帧延迟
    //var loopCount= gifImg.GetPropertyItem(0x5110);//LoopCount GIF循环计数

    byte[] values = frameDelay.Value;

    // 每个延迟时间占 4 个字节
    var frameDelays = new int[frameCount];
    for (int i = 0; i < frameCount; i++)
    {
        frameDelays[i] = BitConverter.ToInt32(values, i * 4) * 10; // 单位是 1/100 秒,转换为毫秒
    }
}

自定义GIF播放(可调速)

使用ImageAnimator的Animate的速度由Gif内置的每帧时间延迟控制,具体可见GetFrameDelays函数示例。如果需要实现自定义速度播放,可使用Timer来控制播放速度。
1、使用Image.FromFile加载Gif动画
2、用ImageAnimator.CanAnimate判断是否为多帧动画
3、获取Gif动画的FrameDimension和总帧数
4、定义并启用一个Timer,设置Interval控制播放速度
5、设置Timer.Tick函数,切换当前帧数和强制控件刷新
6、设置控件MouseClick事件,左键timer.Interval减小,右键Interval加大
7、在Paint事件中SelectActiveFrame激活指定帧,然后绘制

[System.ComponentModel.Description("ImageAnimator的GIF动画播放可调速")]
public void Demo13_02(PaintEventArgs e)
{
    if (gifImg == null)
    {
        LoadGif2();
    }
    if (FrameCount > 0)
    {
        gifImg.SelectActiveFrame(dimension, currentFrame);
        e.Graphics.DrawImage(gifImg, 0, 0, gifImg.Width, gifImg.Height);
        e.Graphics.DrawString($"左键加速/右键减速 {1000 / GifTimer.Interval}帧/秒,当前帧:{currentFrame}", 
            Font, Brushes.Red, new PointF(20, 20));
    }
}

FrameDimension dimension;
/// <summary>
/// 加载多帧图像
/// </summary>
private void LoadGif2()
{
    gifImg = Image.FromFile("Heartbeat.gif");
    if (ImageAnimator.CanAnimate(gifImg))
    {
        dimension = new FrameDimension(gifImg.FrameDimensionsList[0]);
        FrameCount = gifImg.GetFrameCount(dimension);
        GifTimer = new Timer();
        GifTimer.Interval = 40;
        GifTimer.Tick += OnTick;
        GifTimer.Start();
        this.paintCtrl_Main.Panel_Main.MouseClick += Panel_Main_MouseClick;
    }
}
private int currentFrame = 0;
/// <summary>
/// 切换当前帧数
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnTick(object sender, EventArgs e)
{
    currentFrame = (currentFrame + 1) % FrameCount;
    this.paintCtrl_Main.Panel_Main.Invalidate();
}

private Timer GifTimer;
//加/减速
private void Panel_Main_MouseClick(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Right)
    {
        GifTimer.Interval *= 2;
    }
    else
    {
        var interval = GifTimer.Interval / 2;
        if (interval < 1)
        {
            interval = 1;
        }
        GifTimer.Interval = interval;
    }
}
int FrameCount = 0;

使用Timer控制Gif播放

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/688858.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第十五天| 110.平衡二叉树、 257. 二叉树的所有路径、404.左叶子之和

110.平衡二叉树 题目链接&#xff1a;110.平衡二叉树 文档讲讲&#xff1a;代码随想录 状态&#xff1a;还可以 思路&#xff1a;计算左右子树的深度差&#xff0c;递归判断左右子树是否符合平衡条件 题解&#xff1a; public boolean isBalanced(TreeNode root) {if (root n…

Turnitin揭露AI写作痕迹,是否会影响论述是重复率?

Turnitin&#xff08;www.checktoo.com&#xff09;为学术界提供了便捷的服务&#xff0c;以确保论文的原创性和学术诚信。然而&#xff0c;许多学生和研究人员在使用Turnitin时&#xff0c;常常会想Turnitin查论文AI率和重复率之间的关系。那么&#xff0c;使用Turnitin查重论…

Kafka消费者api编写教程

1.基本属性配置 输入new Properties().var 回车 //创建属性Properties properties new Properties();//连接集群properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"node1:9092,node2:9092");//反序列化properties.put(ConsumerConfig.KEY_DESERIALIZER_CL…

Windows mstsc

windows mstsc 局域网远程计算机192.168.0.113为例&#xff0c;远程控制命令mstsc

odoo10 权限控制用户只允许看到自己的字段

假设一个小区管理员用户&#xff0c;只想看到自己小区的信息。 首先添加一个用户信息选项卡界面&#xff0c;如下图的 用户 > 隶属信息&#xff1a; 我们在自己创建的user模块中&#xff0c;views文件夹下添加base_user.xml <?xml version"1.0" encoding&q…

【Microelectronic Systems】期末速通

PART1 嵌入式系统概述与玩转mbed 1 嵌入式系统&#xff0c;微控制器&#xff0c;与ARM 1.1什么是嵌入式系统&#xff1f; 微处理器不仅仅存在于通用计算机中&#xff0c;也可以安置在一些不需要计算的设备内部&#xff0c;比如洗衣机&#xff0c;摄像机。微处理器常常可以控制…

前端面试题(二)答案版

面试形式&#xff1a;线上面试&#xff08;不露脸&#xff09;&#xff1a;时长40分钟 面试评价&#xff1a;由易到难&#xff0c;由细到全&#xff0c;比较不错 面试官&#xff1a;项目经理 面试官提问&#xff08;面试题&#xff09;&#xff1a; 1、聊聊最近写的这个项目…

VLAN与三层交换机

目录 一、VLAN的概念及优势 1.1 分割广播域 1.2 VLAN的优势 二、VLAN的种类 2.1 静态VLAN (重点&#xff0c;大部分用的是静态&#xff09; 2.2 动态VLAN 2.3 查看VLAN表&#xff08;命令&#xff09; 三、静态VLAN的配置 3.1 VLAN的范围 3.2 配置静态VLAN的步骤 3.…

Go源码--sync库(2)

简介 这边文章主要讲解 Sync.Cond和Sync.Rwmutex Sync.Cond 简介 sync.Cond 经常用来处理 多个协程等待 一个协程通知 这种场景&#xff0c; 主要 是阻塞在某一协程中 等待被另一个协程唤醒 继续执行 这个协程后续的功能。cond经常被用来协调协程对某一资源的访问 ants协程池…

拥抱生态农业,享受绿色生活

随着人们对健康生活的追求日益增强&#xff0c;生态农业逐渐成为人们关注的焦点。我们深知生态农业对于保护生态环境、提高农产品品质的重要性&#xff0c;因此&#xff0c;我们积极推广生态农业理念&#xff0c;让更多的人了解并参与到生态农业的实践中来。 生态农业的蓝总说&…

[消息队列 Kafka] Kafka 架构组件及其特性(二)Producer原理

这边整理下Kafka三大主要组件Producer原理。 目录 一、Producer发送消息源码流程 二、ACK应答机制和ISR机制 1&#xff09;ACK应答机制 2&#xff09;ISR机制 三、消息的幂等性 四、Kafka生产者事务 一、Producer发送消息源码流程 Producer发送消息流程如上图。主要是用…

flask招聘数据分析及展示平台-计算机毕业设计源码39292

目 录 摘要 1 绪论 1.1研究意义 1.2国内外研究进展 1.3flask框架介绍 2 1.4论文结构与章节安排 3 2 招聘数据分析及展示平台分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1数据增加流程 5 2.3.2数据修改流程 5 2.3.3数据删除流程 5 2.3 系统功能分析 5 2.3.1 功能性分…

Nginx 配置防护 缓慢的 HTTP拒绝服务攻击+点击劫持:X-Frame-Options未配置

一 安全团队检测网站 1 检测到目标主机可能存在缓慢的HTTP拒绝服务攻击 缓慢的HTTP拒绝服务攻击是一种专门针对于Web的应用层拒绝服务攻击&#xff0c;攻击者操纵网络,对目标Web服务器进行海量HTTP请求攻击&#xff0c;直到服务器带宽被打满&#xff0c;造成了拒绝服务。 慢…

覆盖路径规划经典算法 The Boustrophedon Cellular Decomposition 详解

2000年一篇论文 Coverage of Known Spaces: The Boustrophedon Cellular Decomposition 横空出世&#xff0c;解决了很多计算机和机器人领域的覆盖路径问题&#xff0c;今天我来详细解读这个算法。 The Boustrophedon Cellular Decomposition 算法详解 这篇论文标题为"C…

使用智谱 GLM-4-9B 和 SiliconCloud 云服务快速构建一个编码类智能体应用

本篇文章我将介绍使用智谱 AI 最新开源的 GLM-4-9B 模型和 GenAI 云服务 SiliconCloud 快速构建一个 RAG 应用&#xff0c;首先我会详细介绍下 GLM-4-9B 模型的能力情况和开源限制&#xff0c;以及 SiliconCloud 的使用介绍&#xff0c;最后构建一个编码类智能体应用作为测试。…

5 分钟内构建一个简单的基于 Python 的 GAN

文章目录 一、说明二、代码三、训练四、后记 一、说明 生成对抗网络&#xff08;GAN&#xff09;因其能力而在学术界引起轩然大波。机器能够创作出新颖、富有灵感的作品&#xff0c;这让每个人都感到敬畏和恐惧。因此&#xff0c;人们开始好奇&#xff0c;如何构建一个这样的网…

混合关键性系统技术【同构异构】【SMP、AMP、BMP】【嵌入式虚拟化】

混合关键性系统技术【同构异构】【SMP、AMP、BMP】【嵌入式虚拟化】 1 介绍1.1 概述openEuler Embedded 的运行模式openEuler Embedded 混合关键性系统技术架构UniProton 1.2 同构异构区别 【硬件侧】1.3 系统架构【SMP、AMP、BMP】多核处理器平台的系统架构 【软件侧】【SMP、…

inflight 守恒和带宽资源守恒的有效性

接着昨天的问题&#xff0c;inflight 守恒的模型一定存在稳定点吗&#xff1f;并不是。如果相互抑制强度大于自我抑制强度&#xff0c;系统也会跑飞&#xff1a; 模拟结果如下&#xff1a; 所以一定要记得 a < b。 比对前两个图和后两个图的 a&#xff0c;b 参数关系&am…

Docker镜像加载原理(Union文件系统)

联合文件系统 Union文件系统&#xff0c;是一种轻量级的分层高性能服务系统&#xff0c;支持对文件系统的修改来进行一层一层的叠加&#xff0c;同时将不同目录挂载到同一个虚拟文件系统中&#xff0c;Union文件系统是Docker镜像的基础&#xff0c;通过分层来进行集成&am…

代驾公司在市场竞争中如何保持优势?

在竞争激烈的市场中&#xff0c;代驾公司可以通过多种策略保持其竞争优势&#xff0c;包括利用市场潜力、创新服务模式、提高服务效率以及加强品牌建设等。以下是具体的策略&#xff1a; 利用市场潜力 汽车产业空间巨大&#xff1a;随着汽车保有量的增加&#xff0c;代驾行业…