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

这个需求源自于上一篇博客——用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动画
{
    /// 
    /// 支持从WebP动画文件中提取图像和扩展格式数据。
    /// 
    class SimpleAnimDecoder
    {
        private const int Version = 0x0107;

        public SimpleAnimDecoder()
        {
           
        }
        /// 
        /// 从WebPData结构体解码动画
        /// 
        /// WebPData结构体
        /// WebP动画
        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;
        }
        /// 
        /// 从指向非托管内存空间的WebPData指针解码
        /// 
        /// 
        /// 
        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(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;
        }
        /// 
        /// 从字节数组解码webP动画
        /// 
        /// 原始的webp图片
        /// webP动画
        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

你可能感兴趣的:(.Net/C#)