C# winform播放webp格式动画文件

mac2026-01-06  7

这个需求源自于上一篇博客——用CefSharp做万能爬虫,批量下载抖音用户发布的作品以及点赞视频。当时做视频封面展示时,使用的是API接口返回的静态图片,但是抖音app里的视频封面都是动态的,而且winform中的PictureBox不支持这种WebP格式的图片,所以就考虑怎么把静态图片换成动态图片显示出来。

项目中用到了谷歌官方的libwebp库,网址https://developers.google.com/speed/webp/,如果上不去可以用github镜像,下载源码后使用VS开发人员命令提示符,输入命令

nmake /f Makefile.vc CFG=debug-dynamic RTLIBCFG=legacy OBJDIR=output

将其编译为动态链接库,然后在C#中调用。

下面附上关键代码,具体可以参考文末的源代码。

动画的解码

大量用到了Marshal在托管内存与非托管内存之间传送数据,这种办法很耗费内存,并且运行效率不高。如果用C风格的指针的话应该可以改善。不过指针也带来了风险,C#中不推荐使用指针。

webp格式有静态和动态之分,静态的好办,Imazen.WebP(实际上它也是libwebp的C#包装)就可以直接处理静态webp图片,本文讨论的是webp动画

using Imazen.WebP; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace WebP动画 { /// <summary> /// 支持从WebP动画文件中提取图像和扩展格式数据。 /// </summary> class SimpleAnimDecoder { private const int Version = 0x0107; public SimpleAnimDecoder() { } /// <summary> /// 从WebPData结构体解码动画 /// </summary> /// <param name="webPData">WebPData结构体</param> /// <returns>WebP动画</returns> public WebPAnimation DecodeFromWebPData(WebPData webPData) { //从非托管内存空间创建指向webPData结构体的指针 IntPtr ptrWebPData = Marshal.AllocHGlobal(Marshal.SizeOf(webPData)); //将webPData复制到非托管内存空间 Marshal.StructureToPtr(webPData, ptrWebPData, true); //解码 WebPAnimation animation = DecodeFromWebPDataPointer(ptrWebPData); //释放开辟的内存 Marshal.FreeHGlobal(ptrWebPData); //将指针置为空 ptrWebPData = IntPtr.Zero; //返回解码后的动画 return animation; } /// <summary> /// 从指向非托管内存空间的WebPData指针解码 /// </summary> /// <param name="webPDataPointer"></param> /// <returns></returns> public WebPAnimation DecodeFromWebPDataPointer(IntPtr webPDataPointer) { //解调WebP数据以提取所有帧、ICC配置文件和EXIF / XMP元数据 IntPtr demux = NativeMethods.WebPDemuxInternal(webPDataPointer, 0, IntPtr.Zero, Version); //创建迭代器,用来遍历动画的每一帧 WebPIterator iter = new WebPIterator(); //创建指向迭代器的指针 IntPtr ptrIter = Marshal.AllocHGlobal(Marshal.SizeOf(iter)); //给迭代器指针赋初值 Marshal.StructureToPtr(iter, ptrIter, true); //初始化WebP动画结构体,这是本函数要返回的结果 WebPAnimation animation = new WebPAnimation(); //遍历所有帧 if (NativeMethods.WebPDemuxGetFrame(demux, 1, ptrIter) != 0) { //如果成功获取了第一帧,就创建一个简单解码器 Imazen.WebP.SimpleDecoder simpleDecoder = new Imazen.WebP.SimpleDecoder(); do { //解引用迭代器指针,恢复出迭代器对象 iter = Marshal.PtrToStructure<WebPIterator>(ptrIter); //创建一个动画帧对象 WebPAnimationFrame frame = new WebPAnimationFrame(); //将迭代器中获得的数据存入动画帧对象中 frame.Complete = Convert.ToBoolean(iter.complete); frame.Duration = iter.duration; frame.HasAlpha = Convert.ToBoolean(iter.has_alpha); frame.Height = iter.height; frame.Width = iter.width; frame.XOffset = iter.x_offset; frame.YOffset = iter.y_offset; frame.Image = simpleDecoder.DecodeFromPointer(iter.fragment.bytes, (long)iter.fragment.size); //将动画帧添加到动画对象中 animation.Frames.Add(frame); } while (NativeMethods.WebPDemuxNextFrame(ptrIter) != 0); //释放迭代器 NativeMethods.WebPDemuxReleaseIterator(ptrIter); } //释放之前申请的非托管内存空间 Marshal.FreeHGlobal(ptrIter); //指针置为0 ptrIter = IntPtr.Zero; //返回动画对象 return animation; } public WebPAnimation DecodeFromPointer(IntPtr data, long length) { //将原始数据封装成webPData结构体 WebPData struWebPData = new WebPData(data, length); //调用其它方法来解码 WebPAnimation animation = DecodeFromWebPData(struWebPData); //释放结构体空间 struWebPData.Dispose(); return animation; } /// <summary> /// 从字节数组解码webP动画 /// </summary> /// <param name="data">原始的webp图片</param> /// <returns>webP动画</returns> public WebPAnimation DecodeFromBytes(byte[] data) { //将原始数据封装成webPData结构体 WebPData struWebPData = new WebPData(data); //调用其它方法来解码 WebPAnimation animation = DecodeFromWebPData(struWebPData); //释放结构体空间 struWebPData.Dispose(); return animation; } } }

动画的播放

思路很简单:自定义一个控件,继承picturebox。放一个定时器上去,每隔一段时间切换一下图片,这样就形成了动画。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WebP动画 { class AnimationBox : PictureBox { private Timer timer; private WebPAnimation webPAnimation; private int currentFrameIndex = 0; public WebPAnimation WebPAnimation { get { return webPAnimation; } set { this.Pause(); webPAnimation = value; timer.Interval = webPAnimation.Frames[0].Duration; this.Play(); } } public AnimationBox() { timer = new Timer(); timer.Enabled = true; timer.Tick += OnTick; } private void OnTick(object sender, EventArgs e) { this.currentFrameIndex++; if (this.currentFrameIndex>=webPAnimation.FramesCount) { this.currentFrameIndex = 0; } base.Image = webPAnimation.Frames[this.currentFrameIndex].Image; timer.Interval = webPAnimation.Frames[this.currentFrameIndex].Duration; } public void Play() { timer.Start(); } public void Pause() { timer.Stop(); } protected override void Dispose(bool disposing) { if (disposing && (webPAnimation != null)) { webPAnimation.Dispose(); } base.Dispose(disposing); } } }

源码: https://pan.baidu.com/s/1NnIyJBtIK4zH_5UiwSqFAQ 提取码: 3t75

最新回复(0)