Answered WPF и GIF. Класс анимации

  • 3 мая 2012 г. 8:23
     
      С кодом

    Здравствуйте. У меня вопрос по классу анимации. Предыдущий класс шустро забивал озу. На 1 анимацию приходилось 4-8 мб. Нашел другой, который должен быть в этом плане лучше, ковырял ковырял и ничего не получилось. Исходник ниже. При указании Source и проверки, должна пойти анимация. Но как не пытался, ничего не происходит. Помогите пожалуйста "запустить" класс.

    Imports System.Windows.Media.Animation
    
    Public Class AnimatedImage
        Inherits Image
        Public Property FrameIndex() As Integer
            Get
                Return DirectCast(GetValue(FrameIndexProperty), Integer)
            End Get
            Set(ByVal value As Integer)
                SetValue(FrameIndexProperty, value)
            End Set
        End Property
        Public Shadows Property Source() As ImageSource
            Get
                Return DirectCast(GetValue(SourceProperty), ImageSource)
            End Get
            Set(ByVal value As ImageSource)
                SetValue(SourceProperty, value)
            End Set
        End Property
        Protected Overridable Sub OnSourceChanged(ByVal aEventArgs As DependencyPropertyChangedEventArgs)
            ClearAnimation()
            Dim lBitmapImage As BitmapImage = TryCast(aEventArgs.NewValue, BitmapImage)
            If lBitmapImage IsNot Nothing Then
                Dim lImageSource As ImageSource = TryCast(aEventArgs.NewValue, ImageSource)
                MyBase.Source = lImageSource
                Return
            End If
            If Not IsAnimatedGifImage(lBitmapImage) Then
                MyBase.Source = lBitmapImage
                Return
            End If
            PrepareAnimation(lBitmapImage)
        End Sub
        Private Property Animation() As Int32Animation
            Get
            End Get
            Set(ByVal value As Int32Animation)
            End Set
        End Property
        Private Property Decoder() As GifBitmapDecoder
            Get
            End Get
            Set(ByVal value As GifBitmapDecoder)
            End Set
        End Property
        Private Property IsAnimationWorking() As Boolean
            Get
            End Get
            Set(ByVal value As Boolean)
            End Set
        End Property
        Private Sub ClearAnimation()
            If Animation IsNot Nothing Then
                BeginAnimation(FrameIndexProperty, Nothing)
            End If
            IsAnimationWorking = False
            Animation = Nothing
            Decoder = Nothing
        End Sub
        Private Sub PrepareAnimation(ByVal aBitmapImage As BitmapImage)
            Debug.Assert(aBitmapImage IsNot Nothing)
            If aBitmapImage.UriSource <> Nothing Then
                Decoder = New GifBitmapDecoder(aBitmapImage.UriSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.[Default])
            Else
                aBitmapImage.StreamSource.Position = 0
                Decoder = New GifBitmapDecoder(aBitmapImage.StreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.[Default])
            End If
            Animation = New Int32Animation(0, Decoder.Frames.Count - 1, New Duration(New TimeSpan(0, 0, 0, Decoder.Frames.Count / 10, Int(Decoder.Frames.Count / 10.0 - Decoder.Frames.Count / 10) * 1000))) With { _
             .RepeatBehavior = RepeatBehavior.Forever _
            }
            MyBase.Source = Decoder.Frames(0)
            BeginAnimation(FrameIndexProperty, Animation)
            IsAnimationWorking = True
        End Sub
        Private Function IsAnimatedGifImage(ByVal aBitmapImage As BitmapImage) As Boolean
            Debug.Assert(aBitmapImage IsNot Nothing)
            Dim lResult As Boolean = False
            If aBitmapImage.UriSource <> Nothing Then
                Dim lBitmapDecoder As BitmapDecoder = BitmapDecoder.Create(aBitmapImage.UriSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.[Default])
                lResult = TypeOf lBitmapDecoder Is GifBitmapDecoder
            ElseIf aBitmapImage.StreamSource IsNot Nothing Then
                Try
                    Dim lStreamPosition As Long = aBitmapImage.StreamSource.Position
                    aBitmapImage.StreamSource.Position = 0
                    Dim lBitmapDecoder As New GifBitmapDecoder(aBitmapImage.StreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.[Default])
                    lResult = lBitmapDecoder.Frames.Count > 1
                    aBitmapImage.StreamSource.Position = lStreamPosition
                Catch
                    lResult = False
                End Try
            End If
            Return lResult
        End Function
        Private Shared Sub ChangingFrameIndex(ByVal aObject As DependencyObject, ByVal aEventArgs As DependencyPropertyChangedEventArgs)
            Dim lAnimatedImage As AnimatedImage = TryCast(aObject, AnimatedImage)
            If lAnimatedImage Is Nothing OrElse Not lAnimatedImage.IsAnimationWorking Then
                Return
            End If
            Dim lFrameIndex As Integer = DirectCast(aEventArgs.NewValue, Integer)
            DirectCast(lAnimatedImage, Image).Source = lAnimatedImage.Decoder.Frames(lFrameIndex)
            lAnimatedImage.InvalidateVisual()
        End Sub
        Private Shared Sub OnSourceChanged(ByVal aObject As DependencyObject, ByVal aEventArgs As DependencyPropertyChangedEventArgs)
            DirectCast(aObject, AnimatedImage).OnSourceChanged(aEventArgs)
        End Sub
        Public Shared ReadOnly FrameIndexProperty As DependencyProperty =
            DependencyProperty.Register("FrameIndex", GetType(Integer), GetType(AnimatedImage), New UIPropertyMetadata(0, AddressOf ChangingFrameIndex))
        Public Shared Shadows ReadOnly SourceProperty As DependencyProperty =
            DependencyProperty.Register("Source", GetType(ImageSource), GetType(AnimatedImage),
                                        New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender Or FrameworkPropertyMetadataOptions.AffectsMeasure,
                                                                      AddressOf OnSourceChanged))
    End Class
    

Все ответы

  • 3 мая 2012 г. 12:46
    Отвечающий
     
      С кодом

    С этим страшно ужасным классом разобраться не смог но нашел другой использующий те же классы, а значит работающий с той же производительностью. Вообще тот класс что мы с вами наваяли давным давно на этом форуме работал через Drawing а значит через GDI, то есть не имел аппаратного ускорения, ел память и процессор.

    Нам же нужно показывать гиф средствами WPF и мощью DX.

    Итакк - создаете новый проект WPF. В окшке только добавляете имя к Grid, называете ее "MyGrid", потом переходите к коду и полностью заменяете содержимое на

    Imports System.Windows
    Imports System.Windows.Controls
    Imports System.Windows.Media
    Imports System.Windows.Media.Animation
    Imports System.Windows.Media.Imaging
    Class MainWindow
        Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
            Dim x As New GifImage
            MyGrid.Children.Add(x)
            ' Путь к картинке
            x.GifSource = New Uri("http://www.livegif.ru/Gallery/BEST/35M2.GIF", UriKind.Absolute)
            ' Скорость отображения (чем выше цифра, тем быстрее скорость)
            x.FramesPerSecond = 4
        End Sub
    End Class
    ''' <summary>
    ''' Класс для отображения анимированных GIF-ов.
    ''' </summary>
    Public Class GifImage
        Inherits Image
        Private _gf As GifBitmapDecoder
        Private _anim As Int32Animation
        Private _animationIsWorking As Boolean = False
    
        ''' <summary>
        ''' Индес отображаемого кадра GIF-а.
        ''' </summary>
        Public Property FrameIndex() As Integer
            Get
                Return CInt(GetValue(FrameIndexProperty))
            End Get
            Set(value As Integer)
                SetValue(FrameIndexProperty, value)
            End Set
        End Property
    
        ''' <summary>
        ''' Свойство зависимоти - индес отображаемого кадра GIF-а.
        ''' </summary>
        Public Shared ReadOnly FrameIndexProperty As DependencyProperty = DependencyProperty.Register("FrameIndex", GetType(Integer), GetType(GifImage), New UIPropertyMetadata(0, AddressOf ChangingFrameIndex))
    
        Private _gifSource As Uri
        Private _framesPerSecond As Double = 10.0
    
        Private Shared Sub ChangingFrameIndex(obj As DependencyObject, ev As DependencyPropertyChangedEventArgs)
            Dim ob = DirectCast(obj, GifImage)
            ob.Source = ob._gf.Frames(CInt(ev.NewValue))
    
            ob.InvalidateVisual()
        End Sub
    
        ''' <summary>
        ''' Адрес отображаемого GIF-а.
        ''' </summary>
        Public Property GifSource() As Uri
            Get
                Return _gifSource
            End Get
            Set(value As Uri)
                _gifSource = value
                RefreshGif()
            End Set
        End Property
    
        ''' <summary>
        ''' Скорость анимации GIF-а (кадров в секунду) 
        ''' </summary>
        Public Property FramesPerSecond() As Double
            Get
                Return _framesPerSecond
            End Get
            Set(value As Double)
                _framesPerSecond = value
                RefreshGif()
            End Set
        End Property
    
        Private Sub RefreshGif()
            _gf = New GifBitmapDecoder(_gifSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.[Default])
            If _gf.Frames IsNot Nothing Then
                _anim = New Int32Animation(0, _gf.Frames.Count - 1, New Duration(TimeSpan.FromSeconds(_gf.Frames.Count / FramesPerSecond)))
                _anim.RepeatBehavior = RepeatBehavior.Forever
                Source = _gf.Frames(0)
            End If
        End Sub
    
        Protected Overrides Sub OnRender(dc As DrawingContext)
            MyBase.OnRender(dc)
            If Not _animationIsWorking AndAlso _anim IsNot Nothing Then
                BeginAnimation(FrameIndexProperty, _anim)
                _animationIsWorking = True
            End If
        End Sub
    End Class
    
    Исходя из этого кода разбираетесь дальше...


    Влюблен в WPF Не пишу на C#

  • 4 мая 2012 г. 13:01
     
     
    А в этом классе анимация страшная :) Рябит ужасно. Все изображение как из песка и частично пропадает. А можно ли сделать анимацию со скоростью кадров заданных в самом Gif? Тут просто вся практически анимация в определенном месте быстрее, в другом наоборот останавливается.
  • 4 мая 2012 г. 14:01
     
     
    Попробуйте посмотреть проекты, в которых представлена работа с gif анимацией - WPF GIF Animation и GIF animation in WPF. Если они вас устроют - поможем переписать на vb.net.

    Для связи [mail]

  • 4 мая 2012 г. 19:49
     
     
    Первый хороший пример, работает отлично, но опять же при одном нажатии загрузки Gif выделяемая память взлетает с 13 до 60... А во втором случае видно как меняются кадры. Кадр-чисто-кадр.
  • 5 мая 2012 г. 5:19
    Отвечающий
     
     
    А в этом классе анимация страшная :) Рябит ужасно. Все изображение как из песка и частично пропадает. А можно ли сделать анимацию со скоростью кадров заданных в самом Gif? Тут просто вся практически анимация в определенном месте быстрее, в другом наоборот останавливается.

    На самом деле качество картинки зависит от размера элемента GifImage, поэтому вам показалось что картинка как из песка. Сделайте оригинальный размер, то есть размер самого gif и будет нормальное качество.

    Со сменой кадров согласен, тут с одной стороны плюс, можно управлять скоростью смены, а с другой стороны минус, так как часто нужны оригинальные настройки заданные в самом gif. Сечас помочь в решении проблемы не могу, но на досуге поковыряю и может решу эту проблему.

    UPD. Хотя если вы все же найдете идеальное решение, то многие вам будут признательны.


    Влюблен в WPF Не пишу на C#



  • 5 мая 2012 г. 9:13
     
     
    Ищу и пробую )) Насчет песка - размер оригинальный стоит. Разницы нет какой, все равно так получается. Вот gif для примера
  • 13 мая 2012 г. 10:23
    Отвечающий
     
     Отвечено С кодом

    Попробуйте вот этот вариант. Входил в библиотеку семплов VS2011.

    Imports System.Drawing
    Imports System.IO
    Imports System.Runtime.InteropServices
    Imports System.Windows
    Imports System.Windows.Interop
    Imports System.Windows.Media.Imaging
    Imports System.Windows.Threading
    
    Public Class AnimatedGIFControl
        Inherits System.Windows.Controls.Image
    
        Private _bitmap As Bitmap
        ' Local bitmap member to cache image resource
        Private _bitmapSource As BitmapSource
        Public Delegate Sub FrameUpdatedEventHandler()
    
        ''' <summary>
        ''' Delete local bitmap resource
        ''' Reference: http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx
        ''' </summary>
        <DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Private Shared Function DeleteObject(ByVal hObject As IntPtr) As Boolean
        End Function
    
        ''' <summary>
        ''' Override the OnInitialized method
        ''' </summary>
        Protected Overrides Sub OnInitialized(ByVal e As EventArgs)
            MyBase.OnInitialized(e)
        End Sub
    
        ''' <summary>
        ''' Load the embedded image for the Image.Source
        ''' </summary>
    
        Private Sub AnimatedGIFControl_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles Me.Loaded
            ' Get GIF image from Resources
    
            If (My.Resources.ProgressIndicator IsNot Nothing) Then
                _bitmap = My.Resources.ProgressIndicator
                Width = _bitmap.Width
                Height = _bitmap.Height
    
                _bitmapSource = GetBitmapSource()
                Source = _bitmapSource
            End If
        End Sub
    
        ''' <summary>
        ''' Close the FileStream to unlock the GIF file
        ''' </summary>
        Private Sub AnimatedGIFControl_Unloaded(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles Me.Unloaded
            StopAnimate()
        End Sub
    
        ''' <summary>
        ''' Start animation
        ''' </summary>
        Public Sub StartAnimate()
            ImageAnimator.Animate(_bitmap, AddressOf OnFrameChanged)
        End Sub
    
        ''' <summary>
        ''' Stop animation
        ''' </summary>
        Public Sub StopAnimate()
            ImageAnimator.StopAnimate(_bitmap, AddressOf OnFrameChanged)
        End Sub
    
        ''' <summary>
        ''' Event handler for the frame changed
        ''' </summary>
        Private Sub OnFrameChanged(ByVal sender As Object, ByVal e As EventArgs)
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, New FrameUpdatedEventHandler(AddressOf FrameUpdatedCallback))
        End Sub
    
        Private Sub FrameUpdatedCallback()
            ImageAnimator.UpdateFrames()
    
            If _bitmapSource IsNot Nothing Then
                _bitmapSource.Freeze()
            End If
    
            ' Convert the bitmap to BitmapSource that can be display in WPF Visual Tree
            _bitmapSource = GetBitmapSource()
            Source = _bitmapSource
            InvalidateVisual()
        End Sub
    
        Private Function GetBitmapSource() As BitmapSource
            Dim handle As IntPtr = IntPtr.Zero
    
            Try
                handle = _bitmap.GetHbitmap()
                _bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions())
            Finally
                If handle <> IntPtr.Zero Then
                    DeleteObject(handle)
                End If
            End Try
    
            Return _bitmapSource
        End Function
    
    
    End Class
    Что бы его размещать в xaml смотрите старую тему, там описано как это делать. Проверил на примере той анимации что вы дали выше - работает. Только нужно gif помещать в ресурсы. Можно конечно переделать, но я думаю вы и сами справитесь. Главное основа хорошая наконец то найдена.


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!


  • 13 мая 2012 г. 10:58
    Отвечающий
     
     
    И кстати не зацикливайтесь так на выделении памяти. Не сталкивались с сообщением "Out of memory" при работе наприме с фотошопом? Такое бывает когда на программу выделено меньше памяти чем ей в определенный момент понадобилось. В .Net эта проблема решается выделением памяти с запасом. Вот если каждая новая gif картинка будет прибавлять по 10 мб к памяти и после размещения 40-50 картинок програ будет есть память как добрая 3D игра, то это проблема... Если же с 40-50 картинками прога возьмет 70-80 мб, ничего страшного в этом нет.

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!