はじめまして。
GDI+を使って、画像ファイルを開くユーザーコントロールを作っているのですが、JPGやPNG等の静止画は普通に表示できるようにはなりましたが、アニメーションGIFの再生方法が分かりません。
VB6とGDI+の機能でこれを行うことは可能でしょうか?
可能なら、どのようにすれば良いでしょうか。
アドバイス頂けたら有り難いです。
当方OSはXP、GDIをVB6上で動かすために、GDIPlus Type Library1.05を入手して使っています。
それではよろしくお願いします。
.NET の世界でいうところの「ImageAnimator クラス」の処理を
実装する必要がありそうですね。
やった事は無いですが、多分こんな感じで実装可能かと思います。
(1) タイムフレーム数は GdipImageGetFrameCount で得られます。
dimensionID は、.NET の FrameDimension.Time と同じでしょうから、
おそらくは {6aedbd6d-3fb5-418a-83a6-7f45229dc872} でしょう。
(2) フレーム間のディレイは、GdipGetPropertyItem から、
PROPID が &H5100& の PropertyItem を得ることで取得できるかと。
(3) フレームの切り替えは、GdipImageSelectActiveFrame を使用。
あとは切り替えるたびに、GdipDrawImage してやれば OK かと。
もう一つ忘れてました。
> (2) フレーム間のディレイは、GdipGetPropertyItem から、
> PROPID が &H5100& の PropertyItem を得ることで取得できるかと。
&H5101& も得た方が良いかも知れませんね。
これは、アニメーションのループ回数を意味する16bit 整数値です。
(無限ループの時は、0 が格納されているはず)
なお、&H5100& の方は 32bit整数型の配列になっていると思います。多分。
http://msdn2.microsoft.com/ja-jp/library/ms534416.aspx
魔界の仮面弁士様
ありがとうございます。早速やってみましたところ、
タイムフレーム数とフレーム切り替えは上手く行きました
Dim GdiStart As GdiPlus.GdiplusStartupInput 'パラメータ
Dim GdiToken As Long 'トークン
'ディメンジョンID
Const ID1 = &H6AEDBD6D
Const ID2 = &H3FB5
Const ID3 = &H418A
Const ID4a = &H83
Const ID4b = &HA6
Const ID4c = &H7F
Const ID4d = &H45
Const ID4e = &H22
Const ID4f = &H9D
Const ID4g = &HC8
Const ID4h = &H72
Const GdiImageDelay = &H5100&
Const GdiLoopCount = &H5101&
Dim GifAnimation As Boolean 'アニメーションフラグ
Dim DID As CLSID 'ディメンジョンID
Dim GifFraCount As Long 'フレーム数
Dim LoopCount As Integer 'ループ数
Dim Graphics As Long
Dim Img As Long
Dim GifDelays() As Long 'ディレイ
-------------------------
Private Sub Form_Load()
txtPath = "momi.gif"
GdiStart.GdiplusVersion = 1 '使用するGDIPlus.DLLのバージョンを指定する
If (GdiplusStartup(GdiToken, GdiStart) <> GdiPlus.GpStatus.Ok) Then
MsgBox "GDI+の起動に失敗しました", vbCritical
Exit Sub
End If
DID.Data1 = ID1
DID.Data2 = ID2
DID.Data3 = ID3
DID.Data4(0) = ID4a
DID.Data4(1) = ID4b
DID.Data4(2) = ID4c
DID.Data4(3) = ID4d
DID.Data4(4) = ID4e
DID.Data4(5) = ID4f
DID.Data4(6) = ID4g
DID.Data4(7) = ID4h
End Sub
-------------------------
Ret = GdipImageGetFrameCount(Img, DID, GifFraCount) 'イメージカウント取得
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "カウントの取得に失敗"
Exit Sub
End If
-------------------------
If FCount = GifFraCount Then FCount = 0
Ret = GdipImageSelectActiveFrame(Img, DID, FCount)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "フレーム選択に失敗"
Exit Sub
End If
FCount = FCount + 1
-------------------------
途中省略しましたがこんな感じでウネウネ動いています
しかし、ディレイとループ回数取得のGdipGetPropertyItem でつまづいています
'ディレイ取得
Ret = GdipGetPropertyItemSize(Img, GdiImageDelay, Psize)
Ret = GdipGetPropertyItem(Img, GdiImageDelay, Psize, RetP)
If (Ret <> GpStatus.Ok) Then
MsgBox "ディレイの取得に失敗しました"
Exit Sub
End If
これで得られるプロパティは、値がポインタで帰ってくるのですが、普段ポインタやらを使っていないためここから先がさっぱり分かりません。
ポインタアドレスから配列で値が格納されているので、.Length分だけメモリを参照しろという意味だと思うのですが…
'ディレイ配列
FrameCount = RetP.Length / 4
ReDim GifDelays(1 To FrameCount)
PHD = GetCurrentProcess 'プロセスハンドル取得(Api関数
Ret = ReadProcessMemory(PHD, RetP.ValuePtr, GifDelays(1), RetP.Length, 0) 'Api関数
こんな感じでメモリを参照してみましたが上手く行きません
(Retにも0以外が帰っているので多分間違ってる)
質問の趣旨が微妙に変わってしまい恐縮ですが
このプロパティを配列に格納するにはどうしたらいいでしょうか
申し訳ありませんがよろしくお願いします
こんな感じで。
Private Declare Sub RtlMoveMemory Lib "kernel32" _
(ByRef pDst As Any, _
ByRef pSrc As Any, _
ByVal ByteLen As Long)
Private Type PropertyItem
id As Long 'PROPID
length As Long 'ULONG
type As Integer 'WORD
Value As Long 'void*
End Type
' GpStatus WINGDIPAPI GdipGetPropertyItemSize(
' GpImage *image,
' PROPID propId,
' UINT* size)
Private Declare Function GdipGetPropertyItemSize Lib "GDIPlus" _
(ByVal Image As OLE_HANDLE, _
ByVal propId As Long, _
ByRef size As Long) As GDIPlusStatusConstants
' GpStatus WINGDIPAPI GdipGetPropertyItem(
' GpImage *image,
' PROPID propId,
' UINT propSize,
' PropertyItem* buffer)
Private Declare Function GdipGetPropertyItem Lib "GDIPlus" _
(ByVal Image As OLE_HANDLE, _
ByVal propId As Long, _
ByVal propSize As Long, _
ByRef buffer As Byte) As GDIPlusStatusConstants
Private Const PropertyTagFrameDelay As Long = &H5100&
Private Const PropertyTagLoopCount As Long = &H5101&
Private mhGDIPImage As OLE_HANDLE
Public Function GetFrameDelayList() As Currency()
Dim result As GDIPlusStatusConstants
Dim propertySize As Long
result = GdipGetPropertyItemSize( _
mhGDIPImage, _
PropertyTagFrameDelay, _
propertySize)
If result = GDIPlusStatusConstants.Ok Then
Dim buffer() As Byte
ReDim buffer(propertySize - 1)
result = GdipGetPropertyItem( _
mhGDIPImage, _
PropertyTagFrameDelay, _
propertySize, _
buffer(0))
If result = GDIPlusStatusConstants.Ok Then
Dim item As PropertyItem
RtlMoveMemory item, buffer(0), LenB(item)
If item.length > 0 Then
Dim delayList() As Long
Dim delayListUnsigned() As Currency
Dim arrayCount As Integer
arrayCount = item.length \ 4
ReDim delayList(arrayCount - 1), delayListUnsigned(arrayCount - 1)
RtlMoveMemory delayList(0), buffer(LenB(item)), item.length
Dim n As Long
For n = 0 To arrayCount - 1
If delayList(n) >= 0 Then
delayListUnsigned(n) = CCur(delayList(n))
Else
delayListUnsigned(n) = CCur(delayList(n)) + 4294967296@
End If
Next
GetFrameDelayList = delayListUnsigned
Exit Function
End If
End If
End If
Err.Raise vbObjectError + result
End Function
Public Function GetLoopCount() As Long
Dim result As GDIPlusStatusConstants
Dim propertySize As Long
result = GdipGetPropertyItemSize( _
mhGDIPImage, _
PropertyTagLoopCount, _
propertySize)
If result = GDIPlusStatusConstants.Ok Then
Dim buffer() As Byte
ReDim buffer(propertySize - 1)
result = GdipGetPropertyItem( _
mhGDIPImage, _
PropertyTagLoopCount, _
propertySize, _
buffer(0))
If result = GDIPlusStatusConstants.Ok Then
Dim item As PropertyItem
RtlMoveMemory item, buffer(0), LenB(item)
Dim loopCount As Integer
RtlMoveMemory loopCount, buffer(LenB(item)), item.length
If loopCount >= 0 Then
GetLoopCount = CLng(loopCount)
Else
GetLoopCount = CLng(loopCount) + &H10000
End If
Exit Function
End If
End If
Err.Raise vbObjectError + result
End Function
魔界の仮面弁士様
重ねてありがとうございます。
お手本を貼り付けてやってみましたところ、見事にディレイ値が得られました。
お手本のコード解析等も頑張ってみたいと思います(未だにメモリリード周りの仕組みがはっきり分かっていない)
ただ、GIFファイルによってはディレイ値を持っていないものもあるのか、全部0で帰ってくるものがありちょっと(?)でした。
こういうファイルは好きな速度で再生しろということなんでしょうかね…?
そこだけちょっと疑問です。(おいおい調べてみます)
これで一歩前進することができそうです。
本当にありがとうございました。
おっと解決にするのを忘れていました
> 未だにメモリリード周りの仕組みがはっきり分かっていない
理解度がわからないので、とりあえず、ざっくりと解説を。
RtlMoveMemory は、データをコピーする API です。
VB6 は、ポインタが指す内容を直接読み取ることができないので、この API を使って
内容を適切な変数(ProperyItemユーザー定義型や、Long配列など)にコピーしてから、
それらの変数を読み取っています。
そして GdipGetPropertyItem の方は、最後の引数に指定されたバッファに対して、
先頭 16 バイトに ProperyItem を格納し、その直後の領域に実データを格納します。
この「16 バイト」とは、ユーザ定義型のサイズである LenB(ProperyItem) を意味します。
Len では無く LenB の値である事に注意してください。
で、これらを使って得られる内容ですが:
PropertyItem.id には、PROPID そのものが入ります。
PropertyItem.length は、実データ部のバイト数です。PropertyItem 自体のサイズは含まれないため
この値は結果的に、GdipGetPropertyItemSize で得た値から 16 を引いた値となります。
PropertyItem.type は、実データ部の型を意味します。意味については、下記を参照してください。
(.NET 版の解説ですが、日本語になっているので読みやすいかと)
http://msdn2.microsoft.com/ja-jp/library/system.drawing.imaging.propertyitem.type.aspx
PropertyItem.value は、実データの格納先を示すポインタです。
実データは、PropertyItem の直後に配置されるので、結果的には
Debug.Print Hex(item.Value)
Debug.Print Hex(VarPtr(buffer(16)))
Debug.Print Hex(VarPtr(buffer(0)) + 16)
は、いずれも同じポインタ値を指すことになります。
ゆえに、RtlMoveMemory でコピーせずとも、buffer(16) 以降のバイナリを読んでいけば、
実データを読み取る事は可能だったりします。
> 全部0で帰ってくるものがありちょっと(?)でした。
う〜む。(1/100秒単位の)ディレイを行わないフレーム…?
となると、CPU の力の限り再生、という事になるのかな。
ちなみに IE の場合は、必ずしもディレイ値どおりの再生結果にはならないようです。
http://korapisi.at.infoseek.co.jp/gifspeed.htm
> おいおい調べてみます
一応、animated GIF (GIF89a) の仕様書関連。
http://www.tohoho-web.com/wwwgif.htm
http://www.martinreddy.net/gfx/2d/GIF89a.txt
http://homepage3.nifty.com/g-dragon/gif/gif_spec.html
魔界の仮面弁士 様
ここまで親切に解説してくださるとは思いませんでした
メモリ周りは私の想像通りな流れですが、なぜ私が用意した処理では読めなかったのかなーと思っていたのです
随分手順が違うのでその辺に問題があるのだろうと想像していますが…
(バイト型って普段まったく使わないのですが 目からウロコですね
GIFについての資料もありがたく読ませていただきます
ディレイ0の時は力の限り再生、ですか(w
元の画像はIEで再生速度をチェックしていたので気づきませんでした…
ディレイ0の時だけ別な処理を用意したほうがいいかもしれませんねー
ともあれ本当に勉強になりました 重ねて御礼申し上げます。
ツイート | ![]() |