none
Gif animation slows down

    Question

  • Hello I have a question about gif animation.
    If I read the gif in to the memory is all ok.
    If I read selective one frame at time directly from the disk,
    the animation it slows down in the last frames.
    You can see this effect at NumericUpDown1 control.
    What I have made wrong.
    Why it slows down like replay.

    Put all to the form editor.
    I post the animation also you can use to see the effect.
    That can happen in any gif with lot of frames.

    Public Class Form1
        Private WithEvents GifTimer As Timer
        Private GifCounter As Integer
        Private frameCount As Integer
        Private LoadImage As String
        Private FrameInterval As Integer = 1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            DoubleBuffered = True
            SetStyle((ControlStyles.UserPaint Or (ControlStyles.AllPaintingInWmPaint Or ControlStyles.DoubleBuffer)), True)
            UpdateStyles()
            GifTimer = New Timer
            GifTimer.Enabled = True
            GifTimer.Interval = 1
            LoadImage = "YOUR GIF FILE"
          End Sub
    
        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            Me.Invalidate()
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
            NumericUpDown1.Value = GifCounter
        End Sub
    
        Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
    
            Dim Bm As Bitmap = GetGifFremeFromDisk(LoadImage, GifCounter).Bitmap
            e.Graphics.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            Bm.Dispose()
            GC.Collect()
    
        End Sub
    
        Private Function GetGifFremeFromDisk(ByVal File As String, ByVal Index As Integer) As Frame
            FrameInterval = 1
            If System.IO.File.Exists(File) = False Then
                Return Nothing
            End If
    
            Dim gif As Image = Image.FromFile(File)
            Dim fd As New Imaging.FrameDimension(gif.FrameDimensionsList()(0))
            Dim frameCounter As Integer = gif.GetFrameCount(fd)
            frameCount = frameCounter
            Dim OneFrame As Frame
            If Index >= frameCounter Then Index = frameCounter
            If frameCounter > 1 Then
                Dim times() As Byte = gif.GetPropertyItem(&H5100).Value
                ' For Index As Integer = 0 To frameCount - 1
                gif.SelectActiveFrame(fd, Index)
                Dim length As Integer = BitConverter.ToInt32(times, 4 * Index) * 10
                OneFrame = New Frame(length, New Bitmap(gif))
                'Next
                FrameInterval = length
            Else
                OneFrame = New Frame(FrameInterval, New Bitmap(gif))
            End If
            Return OneFrame
    
        End Function
    
    End Class
    
    Public Class Frame
        Public Property MilliSecondDuration As Integer
        Public Property Bitmap As Image
        Public Sub New(ByVal duration As Integer, ByVal img As Bitmap)
            Me.MilliSecondDuration = duration
            Me.Bitmap = img
        End Sub
        Public Sub Dispose()
            Bitmap = Nothing
            MilliSecondDuration = Nothing
            GC.Collect()
        End Sub
    End Class
    

    Thanks

    Wednesday, May 3, 2017 3:35 PM

Answers

  • There can be more, but this is really heavy stuff in your code. 

     Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
    
            Dim Bm As Bitmap = GetGifFremeFromDisk(LoadImage, GifCounter).Bitmap
            e.Graphics.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            Bm.Dispose()
            GC.Collect()
    
        End Sub

    The Paint event is called by every change on the screen, and then you start even the GC Collect to do an extra job. 

    I would draw the image on another place. 

    A sample on our website.

    http://www.vb-tips.com/CreateImageAndCopyFromScreen.ASPX

    Be aware that calling GC collect is something which should not be needed and is seen as bad usage.


    Success
    Cor

    Wednesday, May 3, 2017 3:56 PM
  • Thanks for respond

    I have make this but the same effeckt. :(

        Dim Gr As Graphics
    
        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            'Me.Invalidate()
            Draw()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
        End Sub
    
        Private Sub Draw()
            Dim Bm As Bitmap = GetGifFremeFromDisk(LoadImage, GifCounter).Bitmap
            Gr.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            '    Bm.Dispose()
            '   GC.Collect()
    
        End Sub

    Hi,

    in the timer tick event stop the timer as first statement. Restart it as last statement.

    (Else the timer runs multiple times on the same image, since the complete code in the tick event takes more than the timer's interval)

    [Also consider using a more precise timer, since the Windows.Forms.Timer's precision is "less" (= more) than 12 ms. EG. CreateTimerQueueTimer

      https://msdn.microsoft.com/en-us/library/windows/desktop/ms682485(v=vs.85).aspx

    see IronRazerz code here:

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/0eb6106f-8f13-4039-b8eb-fc3f29c6063f/visual-basic-and-the-timerqueuetimer?forum=vbgeneral

    ]

        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifTimer.Stop()
    
            //... your code here like
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            'Me.Invalidate()
            Draw()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
    
            GifTimer.Start()
        End Sub

    Regards,

      Thorsten


    Wednesday, May 3, 2017 8:01 PM
  • Hi

    The interval is the same in all frames.

    You can use your gif file for test.

    I think this is Iron Razers code 

    Option Strict On
    Imports System.Drawing.Imaging
    Public Class Form1
        WithEvents MasterButton As New Button With {.Text = "Start", .Size = New Size(75, 25)}
        WithEvents NUD As New NumericUpDown With {.Minimum = 1, .Maximum = 500, .Value = 5, .Size = New Size(75, 25)}
        WithEvents Timer As New Timer With {.Interval = 5, .Enabled = False}
        Dim PB As New PictureBox With {.SizeMode = PictureBoxSizeMode.AutoSize, .Location = New Point(0, 0)}
        Dim animatedImage As Image
        Dim Images As New List(Of Bitmap) ' tried (Of Image) also
        Dim ImageCount As Integer
        Dim ImageWidth As Integer
        Dim ImageHeight As Integer
        Dim SkipHowMany As Integer = 1
        Dim AnimatedGifPath As String
        Dim Index As Integer
        Private Function DisassembleAniGif(GifFile As Bitmap) As Integer
            Dim oDimension As New FrameDimension(GifFile.FrameDimensionsList(0))
            Dim FrameCount As Integer = animatedImage.GetFrameCount(oDimension)
            If FrameCount = 0 Then
                Return FrameCount
            End If
            ImageCount = FrameCount
    
            GifFile.SelectActiveFrame(oDimension, 0)
            Using AniGifFrame As New Bitmap(GifFile) ' one time just to get dimensions
                ImageWidth = AniGifFrame.Width
                ImageHeight = AniGifFrame.Height
            End Using
    
            For i As Integer = 0 To FrameCount - 1 ' process all if needed to get images saved
                GifFile.SelectActiveFrame(oDimension, i)
                Using AniGifFrame As New Bitmap(animatedImage)
                    Images.Add(CType(AniGifFrame.Clone, Bitmap))
                End Using
            Next
            Index = 0
            Return FrameCount
        End Function
    
        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            AnimatedGifPath = "d:\movingball.gif"
            If Not IO.File.Exists(AnimatedGifPath) Then
                MessageBox.Show(Me, "Cannot Find the Animated GIF" & vbNewLine, "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Error)
                End
            End If
            animatedImage = Image.FromFile(AnimatedGifPath)
            If DisassembleAniGif(CType(animatedImage, Bitmap)) = 0 Then
                MessageBox.Show(Me, "Found the Animated GIF, but it has no frames", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                End
            End If
            MasterButton.Left = ImageWidth + 20
            MasterButton.Top = 10
            Me.Controls.Add(MasterButton)
            NUD.Left = ImageWidth + 20
            NUD.Top = MasterButton.Bottom + 10
            Me.Controls.Add(NUD)
            MasterButton.Text = "Start"
            Me.DoubleBuffered = True
            Me.Width = ImageWidth + NUD.Width + 100
            Me.Height = ImageHeight + 100
            Me.Controls.Add(PB)
        End Sub
    
        Private Sub MasterButton_Click(sender As System.Object, e As System.EventArgs) Handles MasterButton.Click
            If MasterButton.Text = "Stop" Then
                Timer.Enabled = False
                MasterButton.Text = "Start"
            Else
                Timer.Enabled = True
                MasterButton.Text = "Stop"
            End If
        End Sub
    
        Private Sub NUD_ValueChanged(sender As Object, e As EventArgs) Handles NUD.ValueChanged
            Timer.Interval = CInt(NUD.Value)
        End Sub
    
        Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
            PB.Image = Images(Index)
            Index += 1
            Me.Text = Index.ToString("0000")
            If Index >= Images.Count Then
                Index = 0
            End If
        End Sub
    End Class
    

    The file "d:\movingball.gif" is your image saved to disk. 

    AnimatedGifPath = "d:\movingball.gif"

    Wednesday, May 3, 2017 11:28 PM
  • Toni,

    Ok I see the same performance issues in your example.

    Well I started cleaning up your last example but I think you are misunderstanding how the system memory your are looking at works. There are some other issues with your code I don't have the energy to explain.

    I would not worry about the memory unless you start getting out of memory errors. Then I would make sure I am disposing the bmps I make.

    Here is an example that works using proper methods unless I forgot something. It shows your memory thing, whatever that is. And the frame count and playback speed in fps.

    This example also uses the Razerz approved code. Reads the file from disc once in form load. Set the frame delay for each frame from the original gif.

    Play with it lets us know what you think.

    Option Strict On
    Imports System.Drawing.Imaging
    Public Class Form8
        Private WithEvents timer1 As New Timer With {.Interval = 10}
        Private Sw As New Stopwatch
        Private GifFrames As New List(Of GifFrame)
        Private PlayIndex As Integer = 0
    
        Private Sub Form8_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ClearGifFrames()
            StripGifFrames("C:\bitmaps\animated gifs\star wars ships.gif")
    
        End Sub
    
        Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
            Sw.Stop()
            Using bmp As Bitmap = CType(GifFrames(PlayIndex).Frame.Clone, Bitmap)
    
                'fit to picturebox
                Dim r As Double = PictureBox1.ClientSize.Width / bmp.Width
                e.Graphics.DrawImage(bmp, New Rectangle(0, 0, CInt(bmp.Width * r), CInt(bmp.Height * r)))
    
                Dim x As Process = Process.GetCurrentProcess()
                Dim inf As String
                inf = "Mem Usage: " & x.WorkingSet / 1024 & " K" & vbCrLf _
                        & "Paged Memory: " & x.PagedMemorySize / 1024 & " K"
    
                e.Graphics.DrawString(inf, New Font("arial", 12, FontStyle.Bold), Brushes.Yellow, 0, 0)
    
                Dim t As String = "Frame: " & PlayIndex & "   " & (1000 / Sw.ElapsedMilliseconds).ToString("f1") & " fps"
                e.Graphics.DrawString(t, New Font("arial", 12, FontStyle.Bold), Brushes.Yellow, 0, 40)
    
                Sw.Reset()
                Sw.Start()
            End Using
        End Sub
    
        Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles timer1.Tick
            If timer1.Interval = 10 Then
                timer1.Interval = GifFrames(PlayIndex).Delay
            End If
    
            PlayIndex += 1
            If PlayIndex > GifFrames.Count - 1 Then
                PlayIndex = 0
            End If
            PictureBox1.Refresh()
        End Sub
    
        Private Sub StripGifFrames(ByVal fName As String)
            Const PIID_FRAMEDELAY As Integer = &H5100
    
            Using bm As New Bitmap(fName)
                Dim fd As New FrameDimension(bm.FrameDimensionsList(0))
                Dim framecount As Integer = bm.GetFrameCount(fd)
    
                If framecount > 1 Then 'if the gif image only has 1 frame then it is not an animated gif
    
                    'gets a byte array of the frame delays. Each frame delay is a 4 byte integer
                    Dim fdv() As Byte = bm.GetPropertyItem(PIID_FRAMEDELAY).Value
    
                    For i As Integer = 0 To framecount - 1
    
                        'converts 4 bytes to an integer value that is in 100ths of a second so, it needs to be multiplied by 10 to get the delay in seconds
                        Dim framedelay As Integer = (BitConverter.ToInt32(fdv, 4 * i)) * 10
    
                        'Some gif formats will return 0 for the delays so, you have to set it to a default if it does.
                        'I found that most that return 0 have the same delay for each frame so, it seems to be pretty safe setting them to the same default delay.
                        If framedelay < 10 Then framedelay = 30 'you may want to adjust the default 90ms a little
    
                        Try
                            bm.SelectActiveFrame(fd, i)
                            GifFrames.Add(New GifFrame(bm, framedelay)) 'add the new GifFrame to the GifFrames List
    
                        Catch ex As Exception
                            MessageBox.Show("An Unexpected Error Occurred Stripping Frame (" & i.ToString & "}", "Error...", MessageBoxButtons.OK, MessageBoxIcon.Error)
                            ClearGifFrames()
                            Exit For
                        End Try
                    Next
                End If
    
                fd = Nothing
            End Using
        End Sub
    
        Private Sub ClearGifFrames()
            timer1.Stop()
            PictureBox1.Image = Nothing
            For i As Integer = GifFrames.Count - 1 To 0 Step -1
                GifFrames(i).Frame.Dispose()
            Next
            GifFrames.Clear()
            PlayIndex = 0
            timer1.Interval = 10
        End Sub
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            If timer1.Enabled Then
                Button1.Text = "Start"
                timer1.Stop()
            Else
                Button1.Text = "Stop"
                timer1.Start()
            End If
        End Sub
    
        Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
            ClearGifFrames()
        End Sub
    End Class
    
    Public Class GifFrame
            Public Property Frame As Bitmap
            Public Property Delay As Integer
    
            Public Sub New(ByVal frm As Bitmap, ByVal dly As Integer)
                Me.Frame = New Bitmap(frm)
                Me.Delay = dly
            End Sub
        End Class
    


    • Edited by tommytwotrain Thursday, May 4, 2017 2:03 PM timer interval = 10 to start
    • Marked as answer by The better Toni Thursday, May 4, 2017 4:04 PM
    Thursday, May 4, 2017 1:58 PM
  • Here is the update from what i have say before.

    This time you need 3 Buttons and 1 Picturebox
    1 Button is for Memory usage.
    2 Button is for to load the gif into memory.
    3 Button is for (the old method) read directly from disk.

    If you have any suggestion please send it.

    Imports System.Drawing.Imaging
    
    Public Class Form1
        Private WithEvents GifTimer As Timer
        Private WithEvents GifTimer2 As Timer
        Private GifCounter As Integer
        Private frameCount As Integer
        Private LoadImage As String
        Private FrameInterval As Integer = 1
        Dim GifFrames() As Frame
        Dim gif As Image
        Dim fd As FrameDimension
    
        Dim Gr As Graphics
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            DoubleBuffered = True
            SetStyle((ControlStyles.UserPaint Or (ControlStyles.AllPaintingInWmPaint Or ControlStyles.DoubleBuffer)), True)
            UpdateStyles()
    
            GifTimer = New Timer
            GifTimer.Enabled = True
            GifTimer.Interval = 1
    
            LoadImage = "YOUR GIF FILE"
            LoadImage = "D:\Diafora Pictures\Gif Files\rDGrP.gif"
    
            Gr = PictureBox1.CreateGraphics
            gif = Image.FromFile(LoadImage)
            fd = New FrameDimension(gif.FrameDimensionsList()(0))
            Dim frameCounter As Integer = gif.GetFrameCount(fd)
            frameCount = frameCounter
    
        End Sub
    
        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            'Me.Invalidate()
            Draw()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim x As Process = Process.GetCurrentProcess()
            Dim c As Process = Process.GetCurrentProcess()
            Dim inf As String
            inf = "Mem Usage: " & x.WorkingSet / 1024 & " K" & vbCrLf _
                & "Paged Memory: " & x.PagedMemorySize / 1024 & " K"
    
            MessageBox.Show(inf, "Memory Usage")
            MessageBox.Show("Mem Usage (Working Set): " & c.WorkingSet / 1024 & " K" & vbCrLf _
    & "VM Size (Private Bytes): " & c.PagedMemorySize / 1024 & " K" & vbCrLf _
    & "GC TotalMemory: " & GC.GetTotalMemory(True) & " bytes", "Current Memory Usage")
    
        End Sub
    
        Private Sub Draw()
            Dim Bm As Bitmap = GetGifFremeFromDisk(LoadImage, GifCounter).Bitmap
            Gr.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            'Bm.Dispose()
            'GC.Collect()
    
        End Sub
        Private Sub Draw1()
            Dim Bm As Bitmap = GifFrames(GifCounter).Bitmap
            GifTimer.Interval = GifFrames(GifCounter).MilliSecondDuration
    
            Gr.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            'Bm.Dispose()
            'GC.Collect()
    
        End Sub
        Private Function GetGifFremeFromDisk(ByVal File As String, ByVal Index As Integer) As Frame
            FrameInterval = 1
            If System.IO.File.Exists(File) = False Then
                Return Nothing
            End If
    
            Dim gif As Image = Image.FromFile(File)
            Dim fd As New Imaging.FrameDimension(gif.FrameDimensionsList()(0))
            Dim frameCounter As Integer = gif.GetFrameCount(fd)
            frameCount = frameCounter
            Dim OneFrame As Frame
            If Index >= frameCount Then Index = frameCount
            If frameCount > 1 Then
                Dim times() As Byte = gif.GetPropertyItem(&H5100).Value
                ' For Index As Integer = 0 To frameCount - 1
                gif.SelectActiveFrame(fd, Index)
                Dim length As Integer = BitConverter.ToInt32(times, 4 * Index) * 10
                OneFrame = New Frame(length, New Bitmap(gif))
                'Next
                FrameInterval = length
            Else
                OneFrame = New Frame(FrameInterval, New Bitmap(gif))
            End If
            gif.Dispose()
            GC.Collect()
            Return OneFrame
    
        End Function
    
        Public Function GetPage(ByVal File As String, ByVal Index As Integer) As Frame
            Dim bm As Image
            bm = Bitmap.FromFile(File)
            frameCount = 300
    
            bm.SelectActiveFrame(FrameDimension.Page, (Index))
            Dim times() As Byte = bm.GetPropertyItem(&H5100).Value
            Dim length As Integer = BitConverter.ToInt32(times, 4 * Index) * 10
            Dim ret As Frame = New Frame(length, New Bitmap(bm))
            FrameInterval = length
            bm.Dispose()
            Return ret
        End Function
    
        Private Function GetAllinMemory(ByVal GifImg As Image) As Frame()
            'EDO EPISTREFEI OLES TIS IMAGES KAI TO DURATION TOUS SE ENA DIM VARIABLE
            Dim gif As Image = GifImg ' Image.FromFile("MyGif.gif")
            Dim fd As New Imaging.FrameDimension(gif.FrameDimensionsList()(0))
            Dim frameCount As Integer = gif.GetFrameCount(fd)
            Dim frames(frameCount) As Frame
            If frameCount > 1 Then
                Dim times() As Byte = gif.GetPropertyItem(&H5100).Value
                For i As Integer = 0 To frameCount - 1
                    gif.SelectActiveFrame(fd, i)
                    Dim length As Integer = BitConverter.ToInt32(times, 4 * i) * 10
                    frames(i) = New Frame(length, New Bitmap(gif))
                Next
            Else
                frames(0) = New Frame(0, New Bitmap(gif))
            End If
            Return frames
        End Function
    
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            On Error Resume Next
    
            GifTimer.Enabled = False
            Dim bm As Image = Image.FromFile(LoadImage)
    
            GifFrames = GetAllinMemory(bm)
            bm.Dispose()
    
            GifTimer2 = New Timer
            GifTimer2.Enabled = True
            GifTimer2.Interval = 1
    
        End Sub
    
        Private Sub GifTimer2_Tick(sender As Object, e As EventArgs) Handles GifTimer2.Tick
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            'Me.Invalidate()
            Draw1()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
        End Sub
    
        Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
            On Error Resume Next
            GifTimer2.Enabled = False
    
            GifTimer.Enabled = True
            GifTimer.Interval = 1
            Gr = PictureBox1.CreateGraphics
    
        End Sub
    End Class
    
    Public Class Frame
        Public Property MilliSecondDuration As Integer
        Public Property Bitmap As Image
        Public Sub New(ByVal duration As Integer, ByVal img As Bitmap)
            Me.MilliSecondDuration = duration
            Me.Bitmap = img
        End Sub
        Public Sub Dispose()
            Bitmap = Nothing
            MilliSecondDuration = Nothing
            GC.Collect()
        End Sub
    End Class
    

    Thursday, May 4, 2017 8:39 AM

All replies

  • There can be more, but this is really heavy stuff in your code. 

     Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
    
            Dim Bm As Bitmap = GetGifFremeFromDisk(LoadImage, GifCounter).Bitmap
            e.Graphics.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            Bm.Dispose()
            GC.Collect()
    
        End Sub

    The Paint event is called by every change on the screen, and then you start even the GC Collect to do an extra job. 

    I would draw the image on another place. 

    A sample on our website.

    http://www.vb-tips.com/CreateImageAndCopyFromScreen.ASPX

    Be aware that calling GC collect is something which should not be needed and is seen as bad usage.


    Success
    Cor

    Wednesday, May 3, 2017 3:56 PM
  • Thanks for respond

    I have make this but the same effeckt. :(

        Dim Gr As Graphics
    
        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            'Me.Invalidate()
            Draw()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
        End Sub
    
        Private Sub Draw()
            Dim Bm As Bitmap = GetGifFremeFromDisk(LoadImage, GifCounter).Bitmap
            Gr.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            '    Bm.Dispose()
            '   GC.Collect()
    
        End Sub

    Wednesday, May 3, 2017 4:12 PM
  • Hi

    I tried your code, and can see the issue you mention. I can't offer any help, but I wonder if the slow down is related to the time overhead in fetching further and further into the Gif image on each iteration? There has to be a time overhead in the indexing - isn't there?


    Regards Les, Livingston, Scotland

    Wednesday, May 3, 2017 4:18 PM
  • Hi

    The interval is the same in all frames.

    You can use your gif file for test.

    Wednesday, May 3, 2017 4:22 PM
  • Hi Toni,

    Despite not the worst is drawing for sure not my specialty here. 

    As I  looking at your code I'm therefore in doubt. 

    You know that if you show a gif image in a Picturebox it shows animated?


    Success
    Cor

    Wednesday, May 3, 2017 7:42 PM
  • Thanks for respond

    I have make this but the same effeckt. :(

        Dim Gr As Graphics
    
        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            'Me.Invalidate()
            Draw()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
        End Sub
    
        Private Sub Draw()
            Dim Bm As Bitmap = GetGifFremeFromDisk(LoadImage, GifCounter).Bitmap
            Gr.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            '    Bm.Dispose()
            '   GC.Collect()
    
        End Sub

    Hi,

    in the timer tick event stop the timer as first statement. Restart it as last statement.

    (Else the timer runs multiple times on the same image, since the complete code in the tick event takes more than the timer's interval)

    [Also consider using a more precise timer, since the Windows.Forms.Timer's precision is "less" (= more) than 12 ms. EG. CreateTimerQueueTimer

      https://msdn.microsoft.com/en-us/library/windows/desktop/ms682485(v=vs.85).aspx

    see IronRazerz code here:

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/0eb6106f-8f13-4039-b8eb-fc3f29c6063f/visual-basic-and-the-timerqueuetimer?forum=vbgeneral

    ]

        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifTimer.Stop()
    
            //... your code here like
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            'Me.Invalidate()
            Draw()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
    
            GifTimer.Start()
        End Sub

    Regards,

      Thorsten


    Wednesday, May 3, 2017 8:01 PM
  • Hi

    The interval is the same in all frames.

    You can use your gif file for test.

    I think this is Iron Razers code 

    Option Strict On
    Imports System.Drawing.Imaging
    Public Class Form1
        WithEvents MasterButton As New Button With {.Text = "Start", .Size = New Size(75, 25)}
        WithEvents NUD As New NumericUpDown With {.Minimum = 1, .Maximum = 500, .Value = 5, .Size = New Size(75, 25)}
        WithEvents Timer As New Timer With {.Interval = 5, .Enabled = False}
        Dim PB As New PictureBox With {.SizeMode = PictureBoxSizeMode.AutoSize, .Location = New Point(0, 0)}
        Dim animatedImage As Image
        Dim Images As New List(Of Bitmap) ' tried (Of Image) also
        Dim ImageCount As Integer
        Dim ImageWidth As Integer
        Dim ImageHeight As Integer
        Dim SkipHowMany As Integer = 1
        Dim AnimatedGifPath As String
        Dim Index As Integer
        Private Function DisassembleAniGif(GifFile As Bitmap) As Integer
            Dim oDimension As New FrameDimension(GifFile.FrameDimensionsList(0))
            Dim FrameCount As Integer = animatedImage.GetFrameCount(oDimension)
            If FrameCount = 0 Then
                Return FrameCount
            End If
            ImageCount = FrameCount
    
            GifFile.SelectActiveFrame(oDimension, 0)
            Using AniGifFrame As New Bitmap(GifFile) ' one time just to get dimensions
                ImageWidth = AniGifFrame.Width
                ImageHeight = AniGifFrame.Height
            End Using
    
            For i As Integer = 0 To FrameCount - 1 ' process all if needed to get images saved
                GifFile.SelectActiveFrame(oDimension, i)
                Using AniGifFrame As New Bitmap(animatedImage)
                    Images.Add(CType(AniGifFrame.Clone, Bitmap))
                End Using
            Next
            Index = 0
            Return FrameCount
        End Function
    
        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            AnimatedGifPath = "d:\movingball.gif"
            If Not IO.File.Exists(AnimatedGifPath) Then
                MessageBox.Show(Me, "Cannot Find the Animated GIF" & vbNewLine, "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Error)
                End
            End If
            animatedImage = Image.FromFile(AnimatedGifPath)
            If DisassembleAniGif(CType(animatedImage, Bitmap)) = 0 Then
                MessageBox.Show(Me, "Found the Animated GIF, but it has no frames", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                End
            End If
            MasterButton.Left = ImageWidth + 20
            MasterButton.Top = 10
            Me.Controls.Add(MasterButton)
            NUD.Left = ImageWidth + 20
            NUD.Top = MasterButton.Bottom + 10
            Me.Controls.Add(NUD)
            MasterButton.Text = "Start"
            Me.DoubleBuffered = True
            Me.Width = ImageWidth + NUD.Width + 100
            Me.Height = ImageHeight + 100
            Me.Controls.Add(PB)
        End Sub
    
        Private Sub MasterButton_Click(sender As System.Object, e As System.EventArgs) Handles MasterButton.Click
            If MasterButton.Text = "Stop" Then
                Timer.Enabled = False
                MasterButton.Text = "Start"
            Else
                Timer.Enabled = True
                MasterButton.Text = "Stop"
            End If
        End Sub
    
        Private Sub NUD_ValueChanged(sender As Object, e As EventArgs) Handles NUD.ValueChanged
            Timer.Interval = CInt(NUD.Value)
        End Sub
    
        Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
            PB.Image = Images(Index)
            Index += 1
            Me.Text = Index.ToString("0000")
            If Index >= Images.Count Then
                Index = 0
            End If
        End Sub
    End Class
    

    The file "d:\movingball.gif" is your image saved to disk. 

    AnimatedGifPath = "d:\movingball.gif"

    Wednesday, May 3, 2017 11:28 PM
  • Good Morning guys :)
    Thanks All of you for the response.

    I have test it in the picturebox and makes the same effect.
    I have add all images in to memory like Devon_Nullman and plays as i say at the beginning of the quest without any problem but, if i read every picture directly from the disk i have this slowdown.

    I suppose the System.Drawing.Imaging doesn't read only one image from the gif,
    but from the beginning of the gif until the indexed frame.
    That's the way it slows down every one frame a bit more.
    Is any other way to read the indexed image?
    Because if i play 4-5 different gifs in the form the memory is finish.
    With the indexed image i try to save memory.




    Thursday, May 4, 2017 12:46 AM
  • Good Morning guys :)
    Thanks All of you for the response.

    I have test it in the picturebox and makes the same effect.
    I have add all images in to memory like Devon_Nullman and plays as i say at the beginning of the quest without any problem but, if i read every picture directly from the disk i have this slowdown.

    I suppose the System.Drawing.Imaging doesn't read only one image from the gif,
    but from the beginning of the gif until the indexed frame.
    That's the way it slows down every one frame a bit more.
    Is any other way to read the indexed image?
    Because if i play 4-5 different gifs in the form the memory is finish.
    With the indexed image i try to save memory.





    Its just not a good idea to read a file in the paint event like this. I would not do it.

    Are all the gifs like this one? Its size is less than a mb?

    I dont see why you cant do 4 or 5 in memory? Did you try it? Show the code you used. Or I can show an example.

    How many animations do you show at once? If you must read from disc just read one into memory frames at a time and when you show the next animation read the next image from disc into its frames in memory.

    I think Razerz will know what the FrameDimension is doing exactly as far as that speed goes but I think its the method is limited due to reading from disc.

    Thursday, May 4, 2017 4:30 AM
  • Here is the update from what i have say before.

    This time you need 3 Buttons and 1 Picturebox
    1 Button is for Memory usage.
    2 Button is for to load the gif into memory.
    3 Button is for (the old method) read directly from disk.

    If you have any suggestion please send it.

    Imports System.Drawing.Imaging
    
    Public Class Form1
        Private WithEvents GifTimer As Timer
        Private WithEvents GifTimer2 As Timer
        Private GifCounter As Integer
        Private frameCount As Integer
        Private LoadImage As String
        Private FrameInterval As Integer = 1
        Dim GifFrames() As Frame
        Dim gif As Image
        Dim fd As FrameDimension
    
        Dim Gr As Graphics
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            DoubleBuffered = True
            SetStyle((ControlStyles.UserPaint Or (ControlStyles.AllPaintingInWmPaint Or ControlStyles.DoubleBuffer)), True)
            UpdateStyles()
    
            GifTimer = New Timer
            GifTimer.Enabled = True
            GifTimer.Interval = 1
    
            LoadImage = "YOUR GIF FILE"
            LoadImage = "D:\Diafora Pictures\Gif Files\rDGrP.gif"
    
            Gr = PictureBox1.CreateGraphics
            gif = Image.FromFile(LoadImage)
            fd = New FrameDimension(gif.FrameDimensionsList()(0))
            Dim frameCounter As Integer = gif.GetFrameCount(fd)
            frameCount = frameCounter
    
        End Sub
    
        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            'Me.Invalidate()
            Draw()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim x As Process = Process.GetCurrentProcess()
            Dim c As Process = Process.GetCurrentProcess()
            Dim inf As String
            inf = "Mem Usage: " & x.WorkingSet / 1024 & " K" & vbCrLf _
                & "Paged Memory: " & x.PagedMemorySize / 1024 & " K"
    
            MessageBox.Show(inf, "Memory Usage")
            MessageBox.Show("Mem Usage (Working Set): " & c.WorkingSet / 1024 & " K" & vbCrLf _
    & "VM Size (Private Bytes): " & c.PagedMemorySize / 1024 & " K" & vbCrLf _
    & "GC TotalMemory: " & GC.GetTotalMemory(True) & " bytes", "Current Memory Usage")
    
        End Sub
    
        Private Sub Draw()
            Dim Bm As Bitmap = GetGifFremeFromDisk(LoadImage, GifCounter).Bitmap
            Gr.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            'Bm.Dispose()
            'GC.Collect()
    
        End Sub
        Private Sub Draw1()
            Dim Bm As Bitmap = GifFrames(GifCounter).Bitmap
            GifTimer.Interval = GifFrames(GifCounter).MilliSecondDuration
    
            Gr.DrawImage(Bm, New Rectangle(Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Me.ClientSize.Width / 2 - Bm.Width / 2,
                                                           Bm.Width,
                                                           Bm.Height))
            'Bm.Dispose()
            'GC.Collect()
    
        End Sub
        Private Function GetGifFremeFromDisk(ByVal File As String, ByVal Index As Integer) As Frame
            FrameInterval = 1
            If System.IO.File.Exists(File) = False Then
                Return Nothing
            End If
    
            Dim gif As Image = Image.FromFile(File)
            Dim fd As New Imaging.FrameDimension(gif.FrameDimensionsList()(0))
            Dim frameCounter As Integer = gif.GetFrameCount(fd)
            frameCount = frameCounter
            Dim OneFrame As Frame
            If Index >= frameCount Then Index = frameCount
            If frameCount > 1 Then
                Dim times() As Byte = gif.GetPropertyItem(&H5100).Value
                ' For Index As Integer = 0 To frameCount - 1
                gif.SelectActiveFrame(fd, Index)
                Dim length As Integer = BitConverter.ToInt32(times, 4 * Index) * 10
                OneFrame = New Frame(length, New Bitmap(gif))
                'Next
                FrameInterval = length
            Else
                OneFrame = New Frame(FrameInterval, New Bitmap(gif))
            End If
            gif.Dispose()
            GC.Collect()
            Return OneFrame
    
        End Function
    
        Public Function GetPage(ByVal File As String, ByVal Index As Integer) As Frame
            Dim bm As Image
            bm = Bitmap.FromFile(File)
            frameCount = 300
    
            bm.SelectActiveFrame(FrameDimension.Page, (Index))
            Dim times() As Byte = bm.GetPropertyItem(&H5100).Value
            Dim length As Integer = BitConverter.ToInt32(times, 4 * Index) * 10
            Dim ret As Frame = New Frame(length, New Bitmap(bm))
            FrameInterval = length
            bm.Dispose()
            Return ret
        End Function
    
        Private Function GetAllinMemory(ByVal GifImg As Image) As Frame()
            'EDO EPISTREFEI OLES TIS IMAGES KAI TO DURATION TOUS SE ENA DIM VARIABLE
            Dim gif As Image = GifImg ' Image.FromFile("MyGif.gif")
            Dim fd As New Imaging.FrameDimension(gif.FrameDimensionsList()(0))
            Dim frameCount As Integer = gif.GetFrameCount(fd)
            Dim frames(frameCount) As Frame
            If frameCount > 1 Then
                Dim times() As Byte = gif.GetPropertyItem(&H5100).Value
                For i As Integer = 0 To frameCount - 1
                    gif.SelectActiveFrame(fd, i)
                    Dim length As Integer = BitConverter.ToInt32(times, 4 * i) * 10
                    frames(i) = New Frame(length, New Bitmap(gif))
                Next
            Else
                frames(0) = New Frame(0, New Bitmap(gif))
            End If
            Return frames
        End Function
    
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            On Error Resume Next
    
            GifTimer.Enabled = False
            Dim bm As Image = Image.FromFile(LoadImage)
    
            GifFrames = GetAllinMemory(bm)
            bm.Dispose()
    
            GifTimer2 = New Timer
            GifTimer2.Enabled = True
            GifTimer2.Interval = 1
    
        End Sub
    
        Private Sub GifTimer2_Tick(sender As Object, e As EventArgs) Handles GifTimer2.Tick
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
            'Me.Invalidate()
            Draw1()
            NumericUpDown1.Value = GifCounter
            Debug.Print(" Counter :" & GifCounter & " Interval" & FrameInterval)
    
        End Sub
    
        Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
            On Error Resume Next
            GifTimer2.Enabled = False
    
            GifTimer.Enabled = True
            GifTimer.Interval = 1
            Gr = PictureBox1.CreateGraphics
    
        End Sub
    End Class
    
    Public Class Frame
        Public Property MilliSecondDuration As Integer
        Public Property Bitmap As Image
        Public Sub New(ByVal duration As Integer, ByVal img As Bitmap)
            Me.MilliSecondDuration = duration
            Me.Bitmap = img
        End Sub
        Public Sub Dispose()
            Bitmap = Nothing
            MilliSecondDuration = Nothing
            GC.Collect()
        End Sub
    End Class
    

    Thursday, May 4, 2017 8:39 AM
  • Toni,

    Ok I see the same performance issues in your example.

    Well I started cleaning up your last example but I think you are misunderstanding how the system memory your are looking at works. There are some other issues with your code I don't have the energy to explain.

    I would not worry about the memory unless you start getting out of memory errors. Then I would make sure I am disposing the bmps I make.

    Here is an example that works using proper methods unless I forgot something. It shows your memory thing, whatever that is. And the frame count and playback speed in fps.

    This example also uses the Razerz approved code. Reads the file from disc once in form load. Set the frame delay for each frame from the original gif.

    Play with it lets us know what you think.

    Option Strict On
    Imports System.Drawing.Imaging
    Public Class Form8
        Private WithEvents timer1 As New Timer With {.Interval = 10}
        Private Sw As New Stopwatch
        Private GifFrames As New List(Of GifFrame)
        Private PlayIndex As Integer = 0
    
        Private Sub Form8_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ClearGifFrames()
            StripGifFrames("C:\bitmaps\animated gifs\star wars ships.gif")
    
        End Sub
    
        Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
            Sw.Stop()
            Using bmp As Bitmap = CType(GifFrames(PlayIndex).Frame.Clone, Bitmap)
    
                'fit to picturebox
                Dim r As Double = PictureBox1.ClientSize.Width / bmp.Width
                e.Graphics.DrawImage(bmp, New Rectangle(0, 0, CInt(bmp.Width * r), CInt(bmp.Height * r)))
    
                Dim x As Process = Process.GetCurrentProcess()
                Dim inf As String
                inf = "Mem Usage: " & x.WorkingSet / 1024 & " K" & vbCrLf _
                        & "Paged Memory: " & x.PagedMemorySize / 1024 & " K"
    
                e.Graphics.DrawString(inf, New Font("arial", 12, FontStyle.Bold), Brushes.Yellow, 0, 0)
    
                Dim t As String = "Frame: " & PlayIndex & "   " & (1000 / Sw.ElapsedMilliseconds).ToString("f1") & " fps"
                e.Graphics.DrawString(t, New Font("arial", 12, FontStyle.Bold), Brushes.Yellow, 0, 40)
    
                Sw.Reset()
                Sw.Start()
            End Using
        End Sub
    
        Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles timer1.Tick
            If timer1.Interval = 10 Then
                timer1.Interval = GifFrames(PlayIndex).Delay
            End If
    
            PlayIndex += 1
            If PlayIndex > GifFrames.Count - 1 Then
                PlayIndex = 0
            End If
            PictureBox1.Refresh()
        End Sub
    
        Private Sub StripGifFrames(ByVal fName As String)
            Const PIID_FRAMEDELAY As Integer = &H5100
    
            Using bm As New Bitmap(fName)
                Dim fd As New FrameDimension(bm.FrameDimensionsList(0))
                Dim framecount As Integer = bm.GetFrameCount(fd)
    
                If framecount > 1 Then 'if the gif image only has 1 frame then it is not an animated gif
    
                    'gets a byte array of the frame delays. Each frame delay is a 4 byte integer
                    Dim fdv() As Byte = bm.GetPropertyItem(PIID_FRAMEDELAY).Value
    
                    For i As Integer = 0 To framecount - 1
    
                        'converts 4 bytes to an integer value that is in 100ths of a second so, it needs to be multiplied by 10 to get the delay in seconds
                        Dim framedelay As Integer = (BitConverter.ToInt32(fdv, 4 * i)) * 10
    
                        'Some gif formats will return 0 for the delays so, you have to set it to a default if it does.
                        'I found that most that return 0 have the same delay for each frame so, it seems to be pretty safe setting them to the same default delay.
                        If framedelay < 10 Then framedelay = 30 'you may want to adjust the default 90ms a little
    
                        Try
                            bm.SelectActiveFrame(fd, i)
                            GifFrames.Add(New GifFrame(bm, framedelay)) 'add the new GifFrame to the GifFrames List
    
                        Catch ex As Exception
                            MessageBox.Show("An Unexpected Error Occurred Stripping Frame (" & i.ToString & "}", "Error...", MessageBoxButtons.OK, MessageBoxIcon.Error)
                            ClearGifFrames()
                            Exit For
                        End Try
                    Next
                End If
    
                fd = Nothing
            End Using
        End Sub
    
        Private Sub ClearGifFrames()
            timer1.Stop()
            PictureBox1.Image = Nothing
            For i As Integer = GifFrames.Count - 1 To 0 Step -1
                GifFrames(i).Frame.Dispose()
            Next
            GifFrames.Clear()
            PlayIndex = 0
            timer1.Interval = 10
        End Sub
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            If timer1.Enabled Then
                Button1.Text = "Start"
                timer1.Stop()
            Else
                Button1.Text = "Stop"
                timer1.Start()
            End If
        End Sub
    
        Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
            ClearGifFrames()
        End Sub
    End Class
    
    Public Class GifFrame
            Public Property Frame As Bitmap
            Public Property Delay As Integer
    
            Public Sub New(ByVal frm As Bitmap, ByVal dly As Integer)
                Me.Frame = New Bitmap(frm)
                Me.Delay = dly
            End Sub
        End Class
    


    • Edited by tommytwotrain Thursday, May 4, 2017 2:03 PM timer interval = 10 to start
    • Marked as answer by The better Toni Thursday, May 4, 2017 4:04 PM
    Thursday, May 4, 2017 1:58 PM
  • Hi tommytwotrain

    Maybe you don't understand me. This method that you sent is the same with my function.
    Private Function GetAllinMemory(ByVal GifImg As Image) As Frame()

    I don't want to load all frames in to a variable (Memory), but every tick from timer, just only one frame, to save memory.
    In both methods you can see a huge difference of memory usage.
    If some one can send a method how to get in every timer tick only the indexed frame will be VERY VERY Appreciated.
    Thursday, May 4, 2017 2:29 PM
  • then from where do you get the frames. 

    Be aware if that is from disk, you are completely relying on the IO system which you cannot influence. 

    By the way. You told you use a picturebox. 

    How. All the code you need one time is then

    ThePictureBox1.Image = image.fromFile("TheImageFilePath")



    Success
    Cor

    Thursday, May 4, 2017 3:34 PM
  • Hi tommytwotrain

    Maybe you don't understand me. This method that you sent is the same with my function.
    Private Function GetAllinMemory(ByVal GifImg As Image) As Frame()

    I don't want to load all frames in to a variable (Memory), but every tick from timer, just only one frame, to save memory.
    In both methods you can see a huge difference of memory usage.
    If some one can send a method how to get in every timer tick only the indexed frame will be VERY VERY Appreciated.

    Toni,

    Thats like saying "I only want to breath every 10 minutes to save air. Can someone please show me how"?

    There is plenty of air but there is no way to breath every 10 minutes.

    The problem is you are reading from disc each tick. You are just measuring disc performance and your graphics is limited by how fast you can read from disc.

    So you are breathing every tick but you are breathing through a bar straw and cant get enough air so you take longer with each breath.

    Then you dont dispose of the bitmaps you make each tick so your memory numbers keep going up. However the garbage collection will take care of that unless you are loading over 400 new 1000 kb images every 30 secs or so.

    Thursday, May 4, 2017 3:47 PM
  • Hi all

    I thing we have to close this question.
    THANKS ALL OF YOU.
    I will mark as answer some good samples of you.
    Thanks again.

    Thursday, May 4, 2017 4:03 PM
  • Hi all

    I thing we have to close this question.
    THANKS ALL OF YOU.
    I will mark as answer some good samples of you.
    Thanks again.

    Toni,

    If you can show us where you run out of memory using my example then we can work on that. But until then, from past experience, I just dont agree with your conclusions. I think the problem IS READING FROM DISC ONLY GOES SO FAST and we can animate about 10x faster. Seems simple to me.

    But, maybe I am wrong?

    Then you should show me how. We tried to answer your questions. You should do the same for us.

    So my first question is why cant you just show the gif in a picturebox as Cor is saying?

    We need to understand more of exactly what you are doing and exactly what the problem is. We need to be able to recreate the problem.

    I see your example runs slow. I don't see why you read each frame from disc. So you will have to convince us that's what is required before we will agree.

    As I asked before how many gifs are we talking about running at once and how big are they? What are you doing to get the problem? Just the one gif using your example?

    If you show a working example that runs out of memory or what ever problem then there is probably a way to deal with it. Like reading the entire image from disc async and making the memory frames only when you have a new image to show. Then disposing of the last images. Reading the next image while the last is playing.

    But as I say, I am not convinced you have to or should do that. In the past I have run many animated gifs at once with no problem. I have also worked on this read from disc problem but its not a problem unless you have many big gifs like full screen full color gifs.

    Finally animated gifs have limits. Thats why there is mpeg and streaming video and such.

    Just trying to help, and learn. Maybe others have the same question?

    :)

    Thursday, May 4, 2017 4:36 PM
  • PS Here I am running 15 agifs totaling 12 mb file size using similar to my example. I can run full screen without problem but does make my fan come on. This is a good system but not super fast.

    Thats why I dont understand the problem you describe.

    Thursday, May 4, 2017 5:17 PM
  • Hi,

    why not simply use the ImageAnimator?

    https://msdn.microsoft.com/de-de/library/system.drawing.imageanimator(v=vs.110).aspx

    Regards,

      Thorsten

    Thursday, May 4, 2017 5:31 PM
  • ... an example is in here:

    https://msdn.microsoft.com/en-us/library/system.drawing.imageanimator.animate(v=vs.110).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-2

    Regards,

      Thorsten

    Thursday, May 4, 2017 5:35 PM
  • Hi,

    with some modifications your code runs well IMHO:

    Remember, that you cannot load a frame from disk, when using Image.FromFile (as you did in your getgifframefromdisk method. FromFile always reads in the complete image-file. So what you could do maybe is to use Image.FromStream instead, but I'm not sure, if not the memory usage will accumulate during multiple read operations on the stream up to the footprint of the complete image -> it shouldnt, else you wouldnt be able to watch a movie, but Im not sure for gif-images... )

    Imports System.Drawing.Imaging
    
    Public Class Form1
        Private animatedImage As New Bitmap("C:\Users\...\Downloads\1057922.gif") 'put your path here
    
        Private WithEvents GifTimer As Timer
        Private GifCounter As Integer
        Private FrameInterval As Integer = 1
        Private frameCounter As Integer
        Private gif As Image
        Private fd As FrameDimension
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            DoubleBuffered = True
    
            gif = animatedImage
            fd = New Imaging.FrameDimension(gif.FrameDimensionsList()(0))
            frameCounter = gif.GetFrameCount(fd)
    
            GifTimer = New Timer
            GifTimer.Interval = 1
            GifTimer.Enabled = True
        End Sub
    
        Private Sub GifTimer_Tick(sender As Object, e As EventArgs) Handles GifTimer.Tick
            GifTimer.Stop()
            GifCounter += 1
            If GifCounter >= frameCounter Then
                GifCounter = 0
            End If
            GifTimer.Interval = FrameInterval
            GetGifFrame(GifCounter)
            Me.Invalidate()
            GifTimer.Start()
        End Sub
    
        Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
            e.Graphics.DrawImage(animatedImage, New Rectangle(Me.ClientSize.Width / 2 - animatedImage.Width / 2,
                                                   Me.ClientSize.Width / 2 - animatedImage.Width / 2,
                                                   animatedImage.Width,
                                                   animatedImage.Height))
        End Sub
    
        Private Sub GetGifFrame(ByVal Index As Integer)
            FrameInterval = 1
    
            If Index >= frameCounter Then Index = frameCounter
            If frameCounter > 1 Then
                Dim times() As Byte = gif.GetPropertyItem(&H5100).Value
                ' For Index As Integer = 0 To frameCount - 1
                gif.SelectActiveFrame(fd, Index)
                Dim length As Integer = BitConverter.ToInt32(times, 4 * Index) * 10
                'OneFrame = New Frame(length, gif)
                'Next
                FrameInterval = length
            End If
        End Sub
    
        Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
            If Not animatedImage Is Nothing Then
                animatedImage.Dispose()
            End If
        End Sub
    End Class

    Regards,

      Thorsten


    Friday, May 5, 2017 6:56 AM
  • Hello Everyone.

    Thorsten Gudera

    and

    tommytwotrain

    Thank you for your insistence for helping me, I LIKE IT :).

    You know i am working o n project like windows explorer and in any folder can be found everything.
    I want to show every gif and his animation to the user while he exploring.
    If it is possible to show one frame at every timer tick without to put all frames in to memory, just only one frame in every timer tick.
    That makes at the end a huge difference of memory usage.


    Friday, May 5, 2017 8:05 AM

  • If it is possible to show one frame at every timer tick without to put all frames in to memory, just only one frame in every timer tick.


    Hi,

    well, I dont know, if there's an easy way. As I mentioned you could use Image.FromStream with the constructor that takes 3 parameters. Set the third parameter "validateimagedata" to false. Then instanciating an object from that stream will only read a couple of bytes from the stream, just the header. I simply dont know, if the fw methods to select the next frame needs all frames to be decoded (the complete image data), or just reads the bytes for the next frame and decodes that. Do some tests with that and check the stream position after each selecting a frame.

    https://msdn.microsoft.com/en-us/library/21zw9ah6(v=vs.110).aspx

    The other option you - always - have, is to do everything yourself. So get familiar with the gif filetype, so that you can:

    - read from the stream just the needed bytes for the next frame

    - LZW-decode that frame and

    - copy these bytes to the Scan0 address of a BitmapDataObject with appropriate width, height and PixelFormat.

    - then display that image and dispose the previous one.

    Regards,

      Thorsten

    Friday, May 5, 2017 10:30 AM

  • The other option you - always - have, is to do everything yourself. So get familiar with the gif filetype, so that you can:


    I will my friend, thanks for your helpful support.
    Friday, May 5, 2017 11:20 AM
  • Hello Everyone.

    Thorsten Gudera

    and

    tommytwotrain

    Thank you for your insistence for helping me, I LIKE IT :).

    You know i am working o n project like windows explorer and in any folder can be found everything.
    I want to show every gif and his animation to the user while he exploring.
    If it is possible to show one frame at every timer tick without to put all frames in to memory, just only one frame in every timer tick.
    That makes at the end a huge difference of memory usage.


    Ok, well then, thats what I mean. Nice to know why. We are all interested that's why we answer questions on the forum.

    Well I think Razerz may know the most about it. He will be around one of these days. However it seems even if you double your read speed you still have some limit of how fast you can read a file vs how many you want to read. So what is your goal? 10 files/ms or what? Where do you say that's fast enough?

    One thing that hit me is you dont have to show the agifs at their normal frame rate. You can also skip frames. If you just loop your routine as fast as it will go, and show like every other frame then you can show twice as fast.

    Also we can show you how to draw faster so your drawing time is a bit less so more disc time is available with each loop. Also if you read the files async you may be able to read them while they are displaying.

    Also you dont have to read them all at once. You can read say 3, start animating those, read the next 5, animating 8, read 5 more now animating 13...

    So I think that is what you are up against. Maybe you can cut your read time of the single frame if Razerz or someone knows how. But you are still up against the shear number of files.

    I mean I see on my 1900x1100 display at large icons there are 50 thumbnails so you have 50 file reads each timer tick. And small icons.... ?

    Seems you will be unable to do it after a certain point even if they are all in memory.

    You could test it by saving all the frames in an agif as single image files. Read each file normally (its not a frame inside the gif just a single frame image for testing. Then you can try to read say 100 images and see how long it takes and you will see how close you are to running at animating speeds ie like at least 6 fps or so. Just seems there will be a limit there.

    So what speed can you live with ie 50 files per second? 500? 5000? Seems the read single frame from a gif issue fades. I mean say you double your read time, do you go from 50 to 100 files ps? But you need 5000?

    Have you tried it? What is the max you can do using the code you have now and how much faster do you want to go?


    Friday, May 5, 2017 1:00 PM
  • PS I did some testing here are the results and example.

    This example reads a single normal image from disc 9 ms per frame. Extract frame from gif gets the slow down ranging from 50 ms per frame to 300.

    BTW I improved your frame extraction routine code and dispose of the images you were making.

    So extracting from the agif gives a performance hit of x10 or more so as is.

    Looks like you can maybe do say 100 ms per frame at best (using current read code)??? And that gives you 10 fps which is ok I would say, for one image. So now if you have 50 images you can do 10 frames in a second or 50 frames every 5 seconds.

    So theres the problem. How many do you want to animate at a time before you say its enough.


    In the example you can change the "speed" which is just skipping frames ie speed 1 is every frame speed 2 is every other frame.

    You still see the slow down as it plays to the end of the frames.

    Anyway. You can play with this to see if you can do what you want even not extracting the gif frames just reading a single image. I hope I have all the images disposed etc.

    Option Strict On
    Imports System.Drawing.Imaging
    Public Class Form9
        'play gif animation by paint loop 9-17-16 v1
        Private BitMaps As New List(Of Bitmap)
        Private Speed As Integer = 1
        Private GoMsg As String = "LMB Click to Start. RMB +Speed"
        Private Running As Boolean = False
        Private CurrentImg As Image
        Private GifCounter As Integer
        Private frameCount As Integer
        Private LoadImage As String
        Private FrameInterval As Integer = 1
    
    
        Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            DoubleBuffered = True
            ClientSize = New Size(400, 400)
            CurrentImg = Image.FromFile("c:\test2\0.png")
    
        End Sub
    
        Private Sub Form6_MouseDown(sender As Object, e As MouseEventArgs) Handles Me.MouseDown
            If e.Button = MouseButtons.Right Then
                'rmb increase speed
                Speed += 1
                If Speed > 6 Then Speed = 1
            Else
                If Running Then
                    Running = False
                    GoMsg = "LMB Click to Start. RMB +Speed"
                Else
                    'Begin the animation.
                    Running = True
                    GoMsg = "LMB Click to Stop. RMB +Speed"
                End If
            End If
            Invalidate()
        End Sub
    
        Private Sub Form6_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
            Static paintcount As Integer = 0
            Static lasttime As Date = Now
            Static temptime As String = ""
            Static ImageCount As Integer = 0
            Dim thistime As Date = Now
            Dim dt As TimeSpan = thistime - lasttime
            paintcount += 1
    
            If dt.TotalMilliseconds > 171 Then
                lasttime = thistime
                temptime = "Time: " &
                        (dt.Milliseconds / paintcount).ToString("f1") & " ms    " &
                        (paintcount / (dt.Milliseconds / 1000)).ToString("f1") & " fps"
                paintcount = 0
            End If
    
            'draw the current frame
            'read single normal frames
            'Dim thisFile As String = "c:\test2\" & ImageCount.ToString & ".png"
    
            'extract frame from gif
            Dim thisFile As String = "C:\bitmaps\animated gifs\star wars ships.gif"
    
            'copy current to temp and dispose
            Dim tempImage As New Bitmap(1, 1)
            If CurrentImg IsNot Nothing Then tempImage = CType(CurrentImg, Bitmap)
    
            'read single normal frames
            'CurrentImg = Image.FromFile(thisFile)
    
            'extract frame from gif
            'CurrentImg = GetGifFremeFromDisk(thisFile, GifCounter).Bitmap
    
            FrameInterval = 1
            GifCounter += 1
            If GifCounter >= frameCount Then
                GifCounter = 0
            End If
    
            Using gif As Image = Image.FromFile(thisFile)
                Dim OneFrame As Frame
                Dim fd As New Imaging.FrameDimension(gif.FrameDimensionsList()(0))
                Dim frameCounter As Integer = gif.GetFrameCount(fd)
                frameCount = frameCounter
    
                If GifCounter >= frameCounter Then GifCounter = frameCounter
                If frameCounter > 1 Then
                    Dim times() As Byte = gif.GetPropertyItem(&H5100).Value
                    gif.SelectActiveFrame(fd, GifCounter)
                    Dim length As Integer = BitConverter.ToInt32(times, 4 * GifCounter) * 10
                    OneFrame = New Frame(length, New Bitmap(gif))
                    FrameInterval = length
                Else
                    OneFrame = New Frame(FrameInterval, New Bitmap(gif))
                End If
                CurrentImg = CType(OneFrame.Bitmap.Clone, Image)
    
                OneFrame.Dispose()
            End Using
    
    
    
    
            tempImage.Dispose()
    
            e.Graphics.DrawImage(CurrentImg, 0, 0, ClientSize.Width, ClientSize.Height)
            e.Graphics.DrawString(temptime & "  Speed: " & Speed.ToString, New Font("arial", 12, FontStyle.Bold), Brushes.Yellow, 2, 2)
            e.Graphics.DrawString(GoMsg, New Font("arial", 12, FontStyle.Bold), Brushes.Yellow, 2, ClientSize.Height - 30)
    
            ImageCount += Speed
            If ImageCount >= 50 Then ImageCount = 0
            If Running Then Invalidate()
        End Sub
    
        Private Sub Form6_Resize(sender As Object, e As EventArgs) Handles Me.Resize
            Invalidate()
        End Sub
    End Class


    PS note this example has no timer. It is an infinite loop running as fast as you can read the files by using invalidate in the paint event to cause the repeat loop.

    Friday, May 5, 2017 2:13 PM
  • Toni,

    PSS what I wanted to do just yesterday was just show one animation from windows file explorer without double clicking the thumbnail and loading into internet explorer or whatever to view the animation. I thought, I would like to put my mouse pointer over the image and a pop up preview would play over the file explorer icon. Sort of like google does in the image search listing.

    Maybe that will work for you as a feature better than file explorer?

    Just show the one agif the mouse pointer is over in a little popup animation window ?????

    Friday, May 5, 2017 4:53 PM
  • I thought, I would like to put my mouse pointer over the image and a pop up preview would play over the file explorer icon.

    Good Morning.
    This is a brilliant idea. In this way i can save lot of memory.
    I will be focus on it.

    Also the sample before is also good without using any timer.

    Thanks


    Saturday, May 6, 2017 5:57 AM
  • Hi

    Look your Idea.

    Thanks

    Saturday, May 6, 2017 8:37 AM
  • Toni,

    Very Good.

    You can send the royalties to the address on my web site.

    :)

    PS One could also draw the file info text below the animation. Like file size, frames, frame delay, all that good info the frame extractor will give.

    PSS. Why stop with gifs? You could pop up detailed info for any file type. Name, file size, type, anything else. How about a pdf preview, video, etc.

    Saturday, May 6, 2017 12:17 PM
  • Toni,

    Very Good.

    You can send the royalties to the address on my web site.

    :)


    HA HA HA.

    For the rest like video ect. same they are already there. For next i will insert the video preview.
    I am working on it.

    Saturday, May 6, 2017 1:11 PM