C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[2]: VB.NET GIF再生での不具合


(過去ログ 71 を表示中)

[トピック内 3 記事 (1 - 3 表示)]  << 0 >>

■41710 / inTopicNo.1)  VB.NET GIF再生での不具合
  
□投稿者/ A.K (1回)-(2009/09/29(Tue) 02:47:24)

分類:[VB.NET/VB2005 以降] 

環境:XP / VB2005

こんにちは。
VB.NETで、GIFが再生できる簡単なイメージビューワを作ろうと思い、ImageAnimatorクラスを使用してユーザーコントロールを作成しました。
ユーザーコントロールは、ピクチャボックスが一つ乗っているだけの単純なものです。
一通り組み終えてGIFを再生したところ、いくつか不満な部分があり、色々と調べてみましたが解決できなかったので、ここで質問させていただきました。
問題点は、
1 遅い
2 一部のGIFを再生すると、GDI+がエラーを起こす
3 ImageAnimatorクラスのStopAnimateが返ってこなくなることがある

です。

1:
GIFファイルによりますが、明らかに描画が遅いです。
WebBrowserを使用すると再生速度は問題なくなりますが、ビューワとしての機能(拡大とか)を考えるとあまり使いたくありません…

2:
サンプルのGIFファイル(他サイトの直リンクなので 問題があったら消します)
http://www30.atwiki.jp/niconicomugen/?plugin=ref&serial=3689
再生テスト用に適当に探したものなのですが、これを再生すると、ImageAnimator.UpdateFrames()で
「GDI+ で汎用エラーが発生しました。」というエラーが出ます。
また、このファイルを再生していると、キャッチできないタイミングでGDI+がエラーを出すことがあるようです。
せめてキャッチさせてくれないとアプリケーションが落ちるのでなんとか回避したいのですが、方法はあるでしょうか?

3:
GIFイメージ再生時は、次のファイルの再生を行う直前に、StopAnimateを行っているのですが、
UCOpenFileメソッドを短い時間の間に連続で呼び出す等した場合、StopAnimateが返ってこなくなることがあります。
どうやらImageAnimatorクラスが応答しなくなるだけで、VBがフリーズしているわけではないようですが、回避方法がわかりません。


ソースコードは以下のような感じです。
(実際のものはもう少し処理が多いですが、削ってあります)

''' <summary>
''' ビューワ本体
''' </summary>
''' <remarks></remarks>
Public Class UserControl1

#Region "定数・変数"

    '=====GIFアニメ用ハンドラ・デリゲート
    Private FrameChange As New EventHandler(AddressOf OnFrameChanged)
    Delegate Sub AnimateCallback(ByVal sender As Object, ByVal e As EventArgs)
    Dim AnimateDelegate As New AnimateCallback(AddressOf OnFrameChanged)
    Dim DummyArray() As Object = {Nothing, Nothing}

    '=====プロパティ値
    Private _ImagePath As String           'イメージパス
    Private _GifAnimationPlayMode As Boolean          'GIFアニメ再生モード

    Private FLImage1 As System.Drawing.Bitmap             'イメージ
    Private _AnimePlayingFlg As Boolean    'アニメ再生フラグ 


#End Region


#Region "イベント"
    ''' <summary>
    ''' ロード時処理
    ''' 各設定値の初期化を行う
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub IMG_Viewer_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        'ダブルバッファ
        Me.DoubleBuffered = True
    End Sub

    ''' <summary>
    ''' 再描画
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub PictureBox1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
        If Not IsNothing(FLImage1) Then
            Try

                If _AnimePlayingFlg Then
                    ImageAnimator.UpdateFrames()     '問題点2
                End If
                e.Graphics.DrawImage(FLImage1, 0, 0, FLImage1.Width, FLImage1.Height)

            Catch ex As Exception
                Debug.Print(ex.Message)
            End Try
        End If
    End Sub

    ''' <summary>
    ''' GIFアニメのフレーム切り替え時
    ''' Invokeを使用し、Animatorからの呼び出しを自スレッドに振り返る
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub OnFrameChanged(ByVal sender As Object, ByVal e As EventArgs)
        Try
            If Me.PictureBox1.InvokeRequired Then
                Me.Invoke(AnimateDelegate, DummyArray)

            Else
                ' ここは必ずメインのスレッドで実行される
                'ImageAnimator.UpdateFrames()
                Me.Invalidate(True)
            End If

        Catch ex As Exception
            Debug.Print(ex.Message)
        End Try

    End Sub

#End Region

#Region "上位メソッド"

    ''' <summary>
    ''' ファイルオープン
    ''' </summary>
    ''' <param name="FilePath"></param>
    ''' <remarks></remarks>
    Private Overloads Sub UCOpenFile(ByVal FilePath As String)

        Dim FLStream As System.IO.FileStream
        Dim GraphicsTemp As Graphics
        Try

            If _AnimePlayingFlg Then
                '再生の停止
                ImageAnimator.StopAnimate(FLImage1, FrameChange)  '問題点3
                _AnimePlayingFlg = False
                System.Threading.Thread.Sleep(75)
            End If

            'ストリーム取得
            FLStream = New IO.FileStream(FilePath, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Delete Or IO.FileShare.ReadWrite)

            'イメージ取得
            If Not FLImage1 Is Nothing Then FLImage1.Dispose()
            FLImage1 = New Drawing.Bitmap(FLStream)

            'グラフィックオブジェクト取得
            GraphicsTemp = PictureBox1.CreateGraphics()

            'GIFアニメチェック
            If _GifAnimationPlayMode AndAlso Drawing.ImageAnimator.CanAnimate(FLImage1) Then

                '再生の開始
                GraphicsTemp.InterpolationMode = Drawing2D.InterpolationMode.Default
                _AnimePlayingFlg = True
                ImageAnimator.Animate(FLImage1, FrameChange)

                Exit Sub
            Else
                GraphicsTemp.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
            End If

            'イメージ描画
            GraphicsTemp.DrawImage(FLImage1, 0, 0, FLImage1.Width, FLImage1.Height)

            '終了処理
            FLStream.Dispose()

        Catch ex As Exception
            If _AnimePlayingFlg Then
                '再生の停止
                ImageAnimator.StopAnimate(FLImage1, FrameChange)
                _AnimePlayingFlg = False
            End If
            Throw ex
        End Try

    End Sub

#End Region

#Region "プロパティ"

    ''' <summary>
    ''' GIFアニメの再生
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property GifAnimationPlayMode() As Boolean
        Get
            Return _GifAnimationPlayMode
        End Get
        Set(ByVal value As Boolean)
            _GifAnimationPlayMode = value
        End Set
    End Property

    ''' <summary>
    ''' 現在のGIFアニメの再生状態
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property GifAnimationPlaying() As Boolean
        Get
            Return _AnimePlayingFlg
        End Get
    End Property

    ''' <summary>
    ''' 表示ファイルパス
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ImagePath() As String
        Get
            Return _ImagePath
        End Get

        Set(ByVal value As String)
            If value Is Nothing OrElse value = "" Then

            Else
                Call UCOpenFile(value)
            End If

        End Set
    End Property

#End Region
End Class


もし分かる方がいらっしゃいましたら、返事を頂ければ有り難いです。
よろしくお願いします。

引用返信 編集キー/
■41724 / inTopicNo.2)  Re[1]: VB.NET GIF再生での不具合
□投稿者/ Hongliang (472回)-(2009/09/29(Tue) 11:02:37)
ImageAnimator を使わず、タイマを使って自前でフレームを管理する方法をお勧めします。

アニメーションのタイミング及びループ回数は、PropertyItem オブジェクトとして Image に格納されています。
次のフレームへ遷移する時間の PropertyItem は Id:0x5100、ループ回数の方は Id:0x5101 であり、これらの Id に対応する PropertyItem は Image オブジェクトの GetPropertyItem メソッドなどを使って取得できます。

SelectActiveFrame メソッドにより、アクティブなフレームを変更できます。このメソッドの第一引数である FrameDimension には、通常、FrameDimensionsList で取得できる配列の 0 番目の GUID を元に作成します。
引用返信 編集キー/
■41762 / inTopicNo.3)  Re[2]: VB.NET GIF再生での不具合
□投稿者/ A.K (2回)-(2009/09/30(Wed) 00:35:13)
Hongliang さん

ありがとうございます。
VBの限界とかではなくて、単にImageAnimatorがよろしくなかったのですね…
仰る通り、自前でフレーム管理を行うことにします。
こちらの方が、逆再生や再生速度の変化などもいじれますし、何より早かったです。助かりました。
(ImageAnimatorは、GIFアニメのための一般的なクラスだと思っていましたが、どうもそうでもないようですね…落ちるし)

ただ、質問に挙げた、
2:一部のGIFを再生すると、GDI+がエラーを起こす
については、やはり同じ部分でエラーが出るようです。
しかしこちらは必ずキャッチが可能なようですので、大きな問題にはならなさそうです。
(GIFのフォーマットを詳細に調べれば、なんとかなるのでしょうけど… 今後の課題とします)


蛇足ですが、以下に私が試したソースを載せておきます。

Public Class Form3
    Private ReadOnly FrameDelay As Integer = Convert.ToInt32("0x5100", System.Globalization.NumberStyles.AllowParentheses)
    Private ReadOnly Frames As Integer = Convert.ToInt32("0x5101", System.Globalization.NumberStyles.AllowParentheses)

    ''' <summary>
    ''' GIFイメージのディメンジョンデータ
    ''' </summary>
    ''' <remarks></remarks>
    Dim Dimension As Drawing.Imaging.FrameDimension

    ''' <summary>
    ''' ディレイ値のプロパティアイテム
    ''' </summary>
    ''' <remarks></remarks>
    Dim PItemDelay As Drawing.Imaging.PropertyItem

    ''' <summary>
    ''' ディレイ値のバイト数
    ''' </summary>
    ''' <remarks></remarks>
    Dim PItemLenDelay As Integer

    ''' <summary>
    ''' ディレイ値を収めた配列。1/100秒単位
    ''' </summary>
    ''' <remarks></remarks>
    Dim FrameDelays() As Integer

    ''' <summary>
    ''' ループカウントのプロパティアイテム
    ''' </summary>
    ''' <remarks></remarks>
    Dim PItemLoopCnt As Drawing.Imaging.PropertyItem

    ''' <summary>
    ''' ループカウントのバイト数
    ''' </summary>
    ''' <remarks></remarks>
    Dim PItemLenLoop As Integer

    ''' <summary>
    ''' ループカウントを収めたShort型
    ''' </summary>
    ''' <remarks></remarks>
    Dim MaxLoopCnt As Short

    ''' <summary>
    ''' 全フレーム数のカウント
    ''' </summary>
    ''' <remarks></remarks>
    Dim MaxFrameCount As Integer


    Dim LoopCnt As Short = 0
    Dim FrameCount As Integer = 0

    Private AnimationFlg As Boolean
    Private GifImage As Bitmap

    Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        TextBox1.Text = "C:\Documents and Settings\Administrator.JES-TOKYO\デスクトップ\12men_w_gif\niconicomugen.gif"
        Me.DoubleBuffered = True

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click


        Dim I As Integer

        Timer1.Enabled = False

        If Not GifImage Is Nothing Then
            GifImage.Dispose()
            LoopCnt = 0
            FrameCount = 0
            AnimationFlg = False
            Dimension = Nothing
        End If

        GifImage = New Bitmap(TextBox1.Text)

        'ディメンジョン取得
        Dimension = New Drawing.Imaging.FrameDimension(GifImage.FrameDimensionsList(0))

        'フレーム数
        MaxFrameCount = GifImage.GetFrameCount(Dimension)
        ReDim FrameDelays(MaxFrameCount - 1)

        'プロパティアイテム取得
        PItemDelay = GifImage.GetPropertyItem(FrameDelay)
        PItemLenDelay = PItemDelay.Len
        PItemLoopCnt = GifImage.GetPropertyItem(Frames)
        PItemLenLoop = PItemLoopCnt.Len

        Debug.Print(String.Format("FrameCount:{0}", MaxFrameCount))
        Debug.Print(String.Format("PItemLenDelay:{0}", PItemLenDelay))
        Debug.Print(String.Format("PItemLenLoop:{0}", PItemLenLoop))

        'ディレイデータ取得
        For I = 0 To MaxFrameCount - 1
            FrameDelays(I) = BitConverter.ToInt32(PItemDelay.Value, I * 4)
            Debug.Print(String.Format("FrameDelays:{0}/{1}", I, FrameDelays(I).ToString))
        Next

        'ループカウント取得
        MaxLoopCnt = BitConverter.ToInt16(PItemLoopCnt.Value, 0)

        Debug.Print(String.Format("LoopCnt:{0}", MaxLoopCnt))

        '描画
        GifImage.SelectActiveFrame(Dimension, 0)

        Me.PictureBox1.CreateGraphics.DrawImage(GifImage, 0, 0)

        'タイマー開始
        FrameCount = 1
        Me.Timer1.Interval = FrameDelays(0) * 10
        Me.Timer1.Enabled = True

        AnimationFlg = True
    End Sub

    Private Sub IncreaseFrame()

        If MaxLoopCnt <> 0 AndAlso LoopCnt = MaxLoopCnt AndAlso FrameCount = MaxFrameCount Then
            '再生終了
            Me.Timer1.Enabled = False
            AnimationFlg = False
            Return
        End If

        If FrameCount = MaxFrameCount Then
            LoopCnt += 1
            FrameCount = 1
        Else
            FrameCount += 1
        End If
        Try
            GifImage.SelectActiveFrame(Dimension, FrameCount - 1)
        Catch ex As Exception
            Debug.Print(String.Format("{0}/{1}", Now, ex.Message))
        End Try

        Me.Timer1.Interval = FrameDelays(FrameCount - 1) * 10

        Me.Invalidate(True)

    End Sub

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        If AnimationFlg Then IncreaseFrame()
    End Sub

    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
        If Not GifImage Is Nothing Then
            e.Graphics.DrawImage(GifImage, 0, 0)
        End If
    End Sub
End Class

解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -