最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#?WPF?內置解碼器實現?GIF?動圖控件的方法

 更新時間:2025年08月12日 08:55:58   作者:藍點lilac  
本文給大家介紹C# WPF內置解碼器實現GIF動圖控件的方法,文章對比了WPF與WinForm顯示動態(tài)GIF的差異,列舉了MediaElement、嵌入PictureBox、WpfAnimatedGif等解決方案,感興趣的朋友跟隨小編一起看看吧

相對于 WinForm PictureBox 控件原生支持動態(tài) GIF,WPF Image 控件卻不支持,讓人摸不著頭腦

常用方法

提到 WPF 播放動圖,常見的方法有三種

MediaElement

使用 MediaElement 控件,缺點是依賴 Media Player,且不支持透明

<MediaElement Source="animation.gif" LoadedBehavior="Play" Stretch="Uniform"/>

WinForm PictureBox

借助 WindowsFormsIntegration 嵌入 WinForm PictureBox,缺點是不支持透明

<WindowsFormsHost>
    <wf:PictureBox x:Name="winFormsPictureBox"/>
</WindowsFormsHost>

WpfAnimatedGif

引用 NuGet 包 WpfAnimatedGif,支持透明

<Image gif:ImageBehavior.AnimatedSource="Images/animation.gif"/>

作者還有另一個性能更好、跨平臺的 XamlAnimatedGif,用法相同

原生解碼方法

WPF 雖然原生 Image 不支持 GIF 動圖,但是提供了 GifBitmapDecoder 解碼器,可以獲取元數據,包括循環(huán)信息、邏輯尺寸、所有幀信息等

判斷是否循環(huán)和循環(huán)次數

int loop = 1;
bool isAnimated = true;
var decoder = new GifBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
var data = decoder.Metadata;
if (data.GetQuery("/appext/Application") is byte[] array1)
{
    string appName = Encoding.ASCII.GetString(array1);
    if ((appName == "NETSCAPE2.0" || appName == "ANIMEXTS1.0")
        && data.GetQuery("/appext/Data") is byte[] array2)
    {
        loop = array2[2] | array2[3] << 8;// 獲取循環(huán)次數, 0 表示無限循環(huán)
        isAnimated = array2[1] == 1;
    }
}

獲取畫布邏輯尺寸

var width = Convert.ToUInt16(data.GetQuery("/logscrdesc/Width"));
var height = Convert.ToUInt16(data.GetQuery("/logscrdesc/Height"));

獲取每一幀信息

/// <summary>當前幀播放完成后的處理方法</summary>
enum DisposalMethod
{
    /// <summary>被全尺寸不透明的下一幀覆蓋替換</summary>
    None,
    /// <summary>不丟棄, 繼續(xù)顯示下一幀未覆蓋的任何像素</summary>
    DoNotDispose,
    /// <summary>重置到背景色</summary>
    RestoreBackground,
    /// <summary>恢復到上一個未釋放的幀的狀態(tài)</summary>
    RestorePrevious,
}
sealed class FrameInfo
{
    public Image Frame { get; }
    public int DelayTime { get; }
    public DisposalMethod DisposalMethod { get; }
    public FrameInfo(BitmapFrame frame)
    {
        Frame = new Image { Source = frame };
        var data = (BitmapMetadata)frame.Metadata;
        DelayTime = Convert.ToUInt16(data.GetQuery("/grctlext/Delay"));
        DisposalMethod = (DisposalMethod)Convert.ToByte(data.GetQuery("/grctlext/Disposal"));
        ushort left = Convert.ToUInt16(data.GetQuery("/imgdesc/Left"));
        ushort top = Convert.ToUInt16(data.GetQuery("/imgdesc/Top"));
        ushort width = Convert.ToUInt16(data.GetQuery("/imgdesc/Width"));
        ushort height = Convert.ToUInt16(data.GetQuery("/imgdesc/Height"));
        Canvas.SetLeft(Frame, left);
        Canvas.SetTop(Frame, top);
        Canvas.SetRight(Frame, left + width);
        Canvas.SetBottom(Frame, top + height);
    }
}

自定義控件完整代碼

將所有幀畫面按其大小位置和順序放置在 Canvas 中,結合所有幀的播放處理方法和持續(xù)時間,使用關鍵幀動畫,即可實現無需依賴第三方的自定義控件,且性能和 XamlAnimatedGif 相差無幾

using System;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
public sealed class GifImage : ContentControl
{
    /// <summary>當前幀播放完成后的處理方法</summary>
    enum DisposalMethod
    {
        /// <summary>被全尺寸不透明的下一幀覆蓋替換</summary>
        None,
        /// <summary>不丟棄, 繼續(xù)顯示下一幀未覆蓋的任何像素</summary>
        DoNotDispose,
        /// <summary>重置到背景色</summary>
        RestoreBackground,
        /// <summary>恢復到上一個未釋放的幀的狀態(tài)</summary>
        RestorePrevious,
    }
    sealed class FrameInfo
    {
        public Image Frame { get; }
        public int DelayTime { get; }
        public DisposalMethod DisposalMethod { get; }
        public FrameInfo(BitmapFrame frame)
        {
            Frame = new Image { Source = frame };
            var data = (BitmapMetadata)frame.Metadata;
            DelayTime = Convert.ToUInt16(data.GetQuery("/grctlext/Delay"));
            DisposalMethod = (DisposalMethod)Convert.ToByte(data.GetQuery("/grctlext/Disposal"));
            ushort left = Convert.ToUInt16(data.GetQuery("/imgdesc/Left"));
            ushort top = Convert.ToUInt16(data.GetQuery("/imgdesc/Top"));
            ushort width = Convert.ToUInt16(data.GetQuery("/imgdesc/Width"));
            ushort height = Convert.ToUInt16(data.GetQuery("/imgdesc/Height"));
            Canvas.SetLeft(Frame, left);
            Canvas.SetTop(Frame, top);
            Canvas.SetRight(Frame, left + width);
            Canvas.SetBottom(Frame, top + height);
        }
    }
    public static readonly DependencyProperty UriSourceProperty =
        DependencyProperty.Register(nameof(UriSource), typeof(Uri), typeof(GifImage), new PropertyMetadata(null, OnSourceChanged));
    public static readonly DependencyProperty StreamSourceProperty =
        DependencyProperty.Register(nameof(StreamSource), typeof(Stream), typeof(GifImage), new PropertyMetadata(null, OnSourceChanged));
    public static readonly DependencyProperty FrameIndexProperty =
        DependencyProperty.Register(nameof(FrameIndex), typeof(int), typeof(GifImage), new PropertyMetadata(0, OnFrameIndexChanged));
    public static readonly DependencyProperty StretchProperty =
        DependencyProperty.Register(nameof(Stretch), typeof(Stretch), typeof(GifImage), new PropertyMetadata(Stretch.None, OnStrechChanged));
    public static readonly DependencyProperty StretchDirectionProperty =
        DependencyProperty.Register(nameof(StretchDirection), typeof(StretchDirection), typeof(GifImage), new PropertyMetadata(StretchDirection.Both, OnStrechDirectionChanged));
    public static readonly DependencyProperty IsLoadingProperty =
        DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(GifImage), new PropertyMetadata(false));
    public Uri UriSource
    {
        get => (Uri)GetValue(UriSourceProperty);
        set => SetValue(UriSourceProperty, value);
    }
    public Stream StreamSource
    {
        get => (Stream)GetValue(StreamSourceProperty);
        set => SetValue(StreamSourceProperty, value);
    }
    public int FrameIndex
    {
        get => (int)GetValue(FrameIndexProperty);
        private set => SetValue(FrameIndexProperty, value);
    }
    public Stretch Stretch
    {
        get => (Stretch)GetValue(StretchProperty);
        set => SetValue(StretchProperty, value);
    }
    public StretchDirection StretchDirection
    {
        get => (StretchDirection)GetValue(StretchDirectionProperty);
        set => SetValue(StretchDirectionProperty, value);
    }
    public bool IsLoading
    {
        get => (bool)GetValue(IsLoadingProperty);
        set => SetValue(IsLoadingProperty, value);
    }
    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((GifImage)d)?.OnSourceChanged();
    }
    private static void OnFrameIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((GifImage)d)?.OnFrameIndexChanged();
    }
    private static void OnStrechChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is GifImage image && image.Content is Viewbox viewbox)
        {
            viewbox.Stretch = image.Stretch;
        }
    }
    private static void OnStrechDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is GifImage image && image.Content is Viewbox viewbox)
        {
            viewbox.StretchDirection = image.StretchDirection;
        }
    }
    Stream stream;
    Canvas canvas;
    FrameInfo[] frameInfos;
    Int32AnimationUsingKeyFrames animation;
    public GifImage()
    {
        IsVisibleChanged += OnIsVisibleChanged;
        Unloaded += OnUnloaded;
    }
    private void OnUnloaded(object sender, RoutedEventArgs e)
    {
        Release();
    }
    private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (IsVisible)
        {
            StartAnimation();
        }
        else
        {
            StopAnimation();
        }
    }
    private void StartAnimation()
    {
        BeginAnimation(FrameIndexProperty, animation);
    }
    private void StopAnimation()
    {
        BeginAnimation(FrameIndexProperty, null);
    }
    private void Release()
    {
        StopAnimation();
        canvas?.Children.Clear();
        stream?.Dispose();
        animation = null;
        frameInfos = null;
    }
    private async void OnSourceChanged()
    {
        Release();
        IsLoading = true;
        FrameIndex = 0;
        if (UriSource != null)
        {
            stream = await ResourceHelper.GetStream(UriSource);
        }
        else
        {
            stream = StreamSource;
        }
        if (stream != null)
        {
            int loop = 1;
            bool isAnimated = true;
            var decoder = new GifBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
            var data = decoder.Metadata;
            if (data.GetQuery("/appext/Application") is byte[] array1)
            {
                string appName = Encoding.ASCII.GetString(array1);
                if ((appName == "NETSCAPE2.0" || appName == "ANIMEXTS1.0")
                    && data.GetQuery("/appext/Data") is byte[] array2)
                {
                    loop = array2[2] | array2[3] << 8;// 獲取循環(huán)次數, 0表示無限循環(huán)
                    isAnimated = array2[1] == 1;
                }
            }
            if (!(Content is Viewbox viewbox))
            {
                Content = viewbox = new Viewbox
                {
                    Stretch = Stretch,
                    StretchDirection = StretchDirection,
                };
            }
            if (canvas == null || canvas.Parent != Content)
            {
                canvas = new Canvas();
                viewbox.Child = canvas;
            }
            canvas.Width = Convert.ToUInt16(data.GetQuery("/logscrdesc/Width"));
            canvas.Height = Convert.ToUInt16(data.GetQuery("/logscrdesc/Height"));
            int count = decoder.Frames.Count;
            frameInfos = new FrameInfo[count];
            for (int i = 0; i < count; i++)
            {
                var info = new FrameInfo(decoder.Frames[i]);
                Image frame = info.Frame;
                frameInfos[i] = info;
                canvas.Children.Add(frame);
                Panel.SetZIndex(frame, i);
                canvas.Width = Math.Max(canvas.Width, Canvas.GetRight(frame));
                canvas.Height = Math.Max(canvas.Height, Canvas.GetBottom(frame));
            }
            OnFrameIndexChanged();
            if (isAnimated)
            {
                var keyFrames = new Int32KeyFrameCollection();
                var last = TimeSpan.Zero;
                for (int i = 0; i < frameInfos.Length; i++)
                {
                    last += TimeSpan.FromMilliseconds(frameInfos[i].DelayTime * 10);
                    keyFrames.Add(new DiscreteInt32KeyFrame(i, last));
                }
                animation = new Int32AnimationUsingKeyFrames
                {
                    KeyFrames = keyFrames,
                    RepeatBehavior = loop == 0 ? RepeatBehavior.Forever : new RepeatBehavior(loop)
                };
                StartAnimation();
            }
        }
        IsLoading = false;
    }
    private void OnFrameIndexChanged()
    {
        if (frameInfos != null)
        {
            int index = FrameIndex;
            frameInfos[index].Frame.Visibility = Visibility.Visible;
            if (index > 0)
            {
                var previousInfo = frameInfos[index - 1];
                switch (previousInfo.DisposalMethod)
                {
                    case DisposalMethod.RestoreBackground:
                        // 隱藏之前的所有幀
                        for (int i = 0; i < index - 1; i++)
                        {
                            frameInfos[i].Frame.Visibility = Visibility.Hidden;
                        }
                        break;
                    case DisposalMethod.RestorePrevious:
                        // 隱藏上一幀
                        previousInfo.Frame.Visibility = Visibility.Hidden;
                        break;
                }
            }
            else
            {
                // 重新循環(huán), 只顯示第一幀
                for (int i = 1; i < frameInfos.Length; i++)
                {
                    frameInfos[i].Frame.Visibility = Visibility.Hidden;
                }
            }
        }
    }
}

使用到的從 URL 獲取圖像流的方法

using System;
using System.IO;
using System.IO.Packaging;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
public static class ResourceHelper
{
    public static Task<Stream> GetStream(Uri uri)
    {
        if (!uri.IsAbsoluteUri)
        {
            throw new ArgumentException("uri must be absolute");
        }
        if (uri.Scheme == Uri.UriSchemeHttps
            || uri.Scheme == Uri.UriSchemeHttp
            || uri.Scheme == Uri.UriSchemeFtp)
        {
            return Task.Run<Stream>(() =>
            {
                using (var client = new WebClient())
                {
                    byte[] data = client.DownloadData(uri);
                    return new MemoryStream(data);
                }
            });
        }
        else if (uri.Scheme == PackUriHelper.UriSchemePack)
        {
            var info = uri.Authority == "siteoforigin:,,,"
                ? Application.GetRemoteStream(uri)
                : Application.GetResourceStream(uri);
            if (info != null)
            {
                return Task.FromResult(info.Stream);
            }
        }
        else if (uri.Scheme == Uri.UriSchemeFile)
        {
            return Task.FromResult<Stream>(File.OpenRead(uri.LocalPath));
        }
        throw new FileNotFoundException(uri.OriginalString);
    }
}

調用示例

<gif:GifImage UriSource="C:\animation.gif"/>

ImageAnimator

WinForm 中播放 GIF 用到了 ImageAnimator,利用它也可以在 WPF 中實現 GIF 動圖控件,但其是基于 GDI 的方法,更推薦性能更好、支持硬解的解碼器方法

// 將多幀圖像顯示為動畫,并觸發(fā)事件
ImageAnimator.Animate(Image, EventHandler)
// 暫停動畫
ImageAnimator.StopAnimate(Image, EventHandler)
// 判斷圖像是否支持動畫
ImageAnimator.CanAnimate(Image)
// 在圖像中前進幀,下次渲染圖像時繪制新幀
ImageAnimator.UpdateFrames(Image)

透明 GIF

GIF 本身只有 256 色,沒有 Alpha 通道,但其仍支持透明,是通過其特殊的自定義顏色表調色盤實現的

上圖是一張單幀透明 GIF,使用 Windows 自帶畫圖打開,會錯誤顯示為橙色背景

放入 WinForm PictureBox 中,Win7 和較舊的 Win10 也會錯誤顯示為橙色背景

但最新的 Win11 和 Win10 上會顯示為透明背景,猜測是近期 Win11 在截圖工具中推出了錄制 GIF 功能時順手更新了 .NET System.Drawing GIF 解析方法,Win10 也收到了這次補丁更新

不過使用 WPF 解碼器方法能過獲得正確的背景

相關資料

Table of Contents

Native Image Format Metadata Queries - Win32 apps

WICGifGraphicControlExtensionProperties (wincodec.h) - Win32 apps | Microsoft Learn

WICGifImageDescriptorProperties (wincodec.h) - Win32 apps | Microsoft Learn

[WPF疑難]在WPF中顯示動態(tài)GIF - 周銀輝 - 博客園

wpf GifBitmapDecoder 解析 gif 格式

濃縮的才是精華:淺析GIF格式圖片的存儲和壓縮 - 騰訊云開發(fā)者 - 博客園

到此這篇關于C# WPF 內置解碼器實現 GIF 動圖控件的方法的文章就介紹到這了,更多相關C# GIF 動圖控件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C# DataTable中Compute方法用法集錦(數值/字符串/運算符/表等操作)

    C# DataTable中Compute方法用法集錦(數值/字符串/運算符/表等操作)

    這篇文章主要介紹了C# DataTable中Compute方法用法,總結分析了DataTable中Compute方法常見的數值運算操作、字符串操作、運算符操作、表運算等相關技巧,需要的朋友可以參考下
    2016-06-06
  • C#?RichTextBox制作文本編輯器

    C#?RichTextBox制作文本編輯器

    這篇文章主要為大家詳細介紹了C#?RichTextBox制作文本編輯器的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • .net中常用的正則表達式

    .net中常用的正則表達式

    這篇文章介紹了.net中常用的正則表達式,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-04-04
  • C# LINQ常用語法及實際應用示例

    C# LINQ常用語法及實際應用示例

    LINQ (Language Integrated Query) 是C#中用于數據查詢的語法,可以查詢各種數據源(數組、集合、數據庫等),本文給大家介紹C# LINQ常用語法,感興趣的朋友跟隨小編一起看看吧
    2025-10-10
  • C#中Dynamic和Dictionary性能比較

    C#中Dynamic和Dictionary性能比較

    開發(fā)中需要傳遞變參,考慮使用 dynamic 還是 Dictionary,dynamic 的編碼體驗顯著優(yōu)于 Dictionary,如果性能差距不大的話,我會選擇使用dynamic。下面通過本文給大家詳細介紹下C#中Dynamic和Dictionary性能比較,一起看看吧
    2016-11-11
  • c# for循環(huán)中創(chuàng)建線程執(zhí)行問題

    c# for循環(huán)中創(chuàng)建線程執(zhí)行問題

    這篇文章主要介紹了有關c# for循環(huán)中創(chuàng)建線程執(zhí)行問題,下面文章將將以舉例的方式展開for循環(huán)中創(chuàng)建線程執(zhí)行問題的內容,需要的朋友可以參考一下,希望對你有所幫助
    2021-11-11
  • C# WinForm調用Shell_NotifyIcon的示例代碼

    C# WinForm調用Shell_NotifyIcon的示例代碼

    這篇文章主要介紹了C# WinForm調用Shell_NotifyIcon的示例代碼,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下
    2020-11-11
  • C# Socket通信的實現(同時監(jiān)聽多客戶端)

    C# Socket通信的實現(同時監(jiān)聽多客戶端)

    這篇文章主要介紹了C# Socket通信的實現(同時監(jiān)聽多客戶端),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • C#通過流寫入數據到文件的方法

    C#通過流寫入數據到文件的方法

    這篇文章主要介紹了C#通過流寫入數據到文件的方法,涉及C#通過字節(jié)流讀寫文件的相關技巧,需要的朋友可以參考下
    2015-07-07
  • 簡單聊聊C#字符串構建利器StringBuilder

    簡單聊聊C#字符串構建利器StringBuilder

    因為String類型代表不可變字符串,所以無法對當前String類型實例進行處理.所以FCL提供了System.Text.StringBuilder類型,下面這篇文章主要給大家介紹了關于C#字符串構建利器StringBuilder的相關資料,需要的朋友可以參考下
    2022-03-03

最新評論

东海县| 应城市| 宜君县| 武冈市| 南丰县| 疏附县| 邮箱| 巴林左旗| 商都县| 昌图县| 久治县| 阿克陶县| 阿克陶县| 土默特左旗| 开化县| 凌海市| 定兴县| 阿拉尔市| 阳原县| 乌拉特前旗| 浮梁县| 定襄县| 资阳市| 社旗县| 嘉定区| 三江| 玉溪市| 衢州市| 镇巴县| 南靖县| 蛟河市| 南岸区| 顺平县| 繁昌县| 江华| 浦东新区| 苍溪县| 四子王旗| 祁东县| 射阳县| 江门市|