C
Crazypennie
Guest
Who said that we cannot create an animated GIF with the framework methods ...
... well, every body
But why ??
Because the Frameworks methods are using the Windows implementation of the IWICBitmapEncoder interface (in WindowsCodecs.dll) and it dont support the the entire GIF89a spec.
But when I examine the generated binary, I realize that the only problems are that the NETSCAPE2.0 application extension block is missing(19 bytes) and the frame rate field in the frames metadata is not initialized
... easy to fix ... (Tested in several application and in several browser)
Public Class AnimatedGifEncoder
Private GifEncoder As New System.Windows.Media.Imaging.GifBitmapEncoder
<summary>
Return the GIF specification version. This always returns "GIF89a"
</summary>
Public ReadOnly Property EncoderVersion As String
Get
Return "GIF89a"
End Get
End Property
<summary>
Get or set a value that indicate if the GIF will repeat the animation after the last frame is shown. The default value is True
</summary>
Public Property Repeat As Boolean = True
<summary>
Get or set a collection of metadata string to be embedded in the GIF file. Each string has a max length of 254
characters (Any character above this limit will be truncated). The string will be encoded UTF-7.
</summary>
Public Property MetadataString As List(Of String) = New List(Of String)
<summary>
Get or set the amount of time each frame will be shown (in milliseconds). The default value is 200ms
</summary>
Public Property FrameRate As Integer = 200
<summary>
Add a frame to the encoder frame collection
</summary>
<param name="Frame The bitmap to be added</param>
Public Sub AddFrame(Frame As Bitmap)
If Frame IsNot Nothing Then
If Not (Frame.Width = 0) And Not (Frame.Height = 0) Then
Dim bmpSource = Imaging.CreateBitmapSourceFromHBitmap(Frame.GetHbitmap,
IntPtr.Zero,
Windows.Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions)
GifEncoder.Frames.Add(BitmapFrame.Create(bmpSource))
Else
Throw New ArgumentException("Argument Frame, The bitmap size cannot be zero")
End If
Else
Throw New ArgumentException("Argument Frame cannot be nothing")
End If
End Sub
<summary>
Writes the animated GIF binary to a specified IO.Stream
</summary>
<param name="Stream The stream where the binary is to be output. Can be any object type that derives from IO.Stream</param>
Public Sub Save(Stream As IO.Stream)
Dim Data() As Byte
If Not GifEncoder.Frames.Count = 0 Then
Get the raw binary
Using MStream As New IO.MemoryStream
GifEncoder.Save(MStream)
Data = MStream.ToArray
End Using
Else
Throw New Exception("Cannot encode the Gif. The frame collection is empty.")
Only documented exception is if Frames.count=0
End If
Locate the right location where to insert the metadata in the binary
This will be just before the first label &H0021F9 (Graphic Control Extension)
Dim MetadataPTR As Integer = -1
Dim flag As Integer = 0
Do
MetadataPTR += 1
If Data(MetadataPTR) = 0 Then
If Data(MetadataPTR + 1) = &H21 Then
If Data(MetadataPTR + 2) = &HF9 Then
flag = 1
End If
End If
End If
Loop While flag = 0
SET METADATA Repeat
This add an Application Extension Netscape2.0
If Repeat Then
Dim Temp(CInt(Data.Length) - 1 + 19) As Byte
label: &H21, &HFF + one byte: length(&HB) + NETSCAPE2.0 + one byte: Datalength(&H3) + {1, 0, 0} + Block terminator, 1 byte, &H00
Dim ApplicationExtension() As Byte = {&H21, &HFF, &HB, &H4E, &H45, &H54, &H53, &H43, &H41, &H50, &H45, &H32, &H2E, &H30, &H3, &H1, &H0, &H0, &H0}
Array.Copy(Data, Temp, MetadataPTR)
Array.Copy(ApplicationExtension, 0, Temp, MetadataPTR + 1, 19)
Array.Copy(Data, MetadataPTR + 1, Temp, MetadataPTR + 20, Data.Length - MetadataPTR - 1)
Data = Temp
End If
SET METADATA Comments
This add a Comment Extension for each string
If MetadataString.Count > 0 Then
For Each Comment As String In MetadataString
If Not String.IsNullOrEmpty(Comment) Then
Dim TheComment As String
If Comment.Length > 254 Then
TheComment = Comment.Substring(0, 254)
Else
TheComment = Comment
End If
Dim CommentStringBytes() As Byte = System.Text.UTF7Encoding.UTF7.GetBytes(TheComment)
Dim DataString() As Byte = New Byte() {&H21, &HFE, CByte(CommentStringBytes.Length)}
DataString = DataString.Concat(CommentStringBytes).Concat(New Byte() {&H0}).ToArray
Dim Temp(Data.Length - 1 + DataString.Length) As Byte
Array.Copy(Data, Temp, MetadataPTR)
Array.Copy(DataString, 0, Temp, MetadataPTR + 1, DataString.Length)
Array.Copy(Data, MetadataPTR + 1, Temp, MetadataPTR + DataString.Length + 1, Data.Length - MetadataPTR - 1)
Data = Temp
End If
Next
End If
SET METADATA frameRate
Sets the third and fourth byte of each Graphic Control Extension (5 bytes from each label 0x0021F9)
For x As Integer = 0 To Data.Count - 1
If Data(x) = 0 Then
If Data(x + 1) = &H21 Then
If Data(x + 2) = &HF9 Then
If Data(x + 3) = 4 Then
word, little endian, the hundredths of second to show this frame
Dim Bte() As Byte = BitConverter.GetBytes(FrameRate \ 10)
Data(x + 5) = Bte(0)
Data(x + 6) = Bte(1)
End If
End If
End If
End If
Next
Stream.Write(Data, 0, Data.Length)
End Sub
End Class
Continue reading...
... well, every body
But why ??
Because the Frameworks methods are using the Windows implementation of the IWICBitmapEncoder interface (in WindowsCodecs.dll) and it dont support the the entire GIF89a spec.
But when I examine the generated binary, I realize that the only problems are that the NETSCAPE2.0 application extension block is missing(19 bytes) and the frame rate field in the frames metadata is not initialized
... easy to fix ... (Tested in several application and in several browser)
Public Class AnimatedGifEncoder
Private GifEncoder As New System.Windows.Media.Imaging.GifBitmapEncoder
<summary>
Return the GIF specification version. This always returns "GIF89a"
</summary>
Public ReadOnly Property EncoderVersion As String
Get
Return "GIF89a"
End Get
End Property
<summary>
Get or set a value that indicate if the GIF will repeat the animation after the last frame is shown. The default value is True
</summary>
Public Property Repeat As Boolean = True
<summary>
Get or set a collection of metadata string to be embedded in the GIF file. Each string has a max length of 254
characters (Any character above this limit will be truncated). The string will be encoded UTF-7.
</summary>
Public Property MetadataString As List(Of String) = New List(Of String)
<summary>
Get or set the amount of time each frame will be shown (in milliseconds). The default value is 200ms
</summary>
Public Property FrameRate As Integer = 200
<summary>
Add a frame to the encoder frame collection
</summary>
<param name="Frame The bitmap to be added</param>
Public Sub AddFrame(Frame As Bitmap)
If Frame IsNot Nothing Then
If Not (Frame.Width = 0) And Not (Frame.Height = 0) Then
Dim bmpSource = Imaging.CreateBitmapSourceFromHBitmap(Frame.GetHbitmap,
IntPtr.Zero,
Windows.Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions)
GifEncoder.Frames.Add(BitmapFrame.Create(bmpSource))
Else
Throw New ArgumentException("Argument Frame, The bitmap size cannot be zero")
End If
Else
Throw New ArgumentException("Argument Frame cannot be nothing")
End If
End Sub
<summary>
Writes the animated GIF binary to a specified IO.Stream
</summary>
<param name="Stream The stream where the binary is to be output. Can be any object type that derives from IO.Stream</param>
Public Sub Save(Stream As IO.Stream)
Dim Data() As Byte
If Not GifEncoder.Frames.Count = 0 Then
Get the raw binary
Using MStream As New IO.MemoryStream
GifEncoder.Save(MStream)
Data = MStream.ToArray
End Using
Else
Throw New Exception("Cannot encode the Gif. The frame collection is empty.")
Only documented exception is if Frames.count=0
End If
Locate the right location where to insert the metadata in the binary
This will be just before the first label &H0021F9 (Graphic Control Extension)
Dim MetadataPTR As Integer = -1
Dim flag As Integer = 0
Do
MetadataPTR += 1
If Data(MetadataPTR) = 0 Then
If Data(MetadataPTR + 1) = &H21 Then
If Data(MetadataPTR + 2) = &HF9 Then
flag = 1
End If
End If
End If
Loop While flag = 0
SET METADATA Repeat
This add an Application Extension Netscape2.0
If Repeat Then
Dim Temp(CInt(Data.Length) - 1 + 19) As Byte
label: &H21, &HFF + one byte: length(&HB) + NETSCAPE2.0 + one byte: Datalength(&H3) + {1, 0, 0} + Block terminator, 1 byte, &H00
Dim ApplicationExtension() As Byte = {&H21, &HFF, &HB, &H4E, &H45, &H54, &H53, &H43, &H41, &H50, &H45, &H32, &H2E, &H30, &H3, &H1, &H0, &H0, &H0}
Array.Copy(Data, Temp, MetadataPTR)
Array.Copy(ApplicationExtension, 0, Temp, MetadataPTR + 1, 19)
Array.Copy(Data, MetadataPTR + 1, Temp, MetadataPTR + 20, Data.Length - MetadataPTR - 1)
Data = Temp
End If
SET METADATA Comments
This add a Comment Extension for each string
If MetadataString.Count > 0 Then
For Each Comment As String In MetadataString
If Not String.IsNullOrEmpty(Comment) Then
Dim TheComment As String
If Comment.Length > 254 Then
TheComment = Comment.Substring(0, 254)
Else
TheComment = Comment
End If
Dim CommentStringBytes() As Byte = System.Text.UTF7Encoding.UTF7.GetBytes(TheComment)
Dim DataString() As Byte = New Byte() {&H21, &HFE, CByte(CommentStringBytes.Length)}
DataString = DataString.Concat(CommentStringBytes).Concat(New Byte() {&H0}).ToArray
Dim Temp(Data.Length - 1 + DataString.Length) As Byte
Array.Copy(Data, Temp, MetadataPTR)
Array.Copy(DataString, 0, Temp, MetadataPTR + 1, DataString.Length)
Array.Copy(Data, MetadataPTR + 1, Temp, MetadataPTR + DataString.Length + 1, Data.Length - MetadataPTR - 1)
Data = Temp
End If
Next
End If
SET METADATA frameRate
Sets the third and fourth byte of each Graphic Control Extension (5 bytes from each label 0x0021F9)
For x As Integer = 0 To Data.Count - 1
If Data(x) = 0 Then
If Data(x + 1) = &H21 Then
If Data(x + 2) = &HF9 Then
If Data(x + 3) = 4 Then
word, little endian, the hundredths of second to show this frame
Dim Bte() As Byte = BitConverter.GetBytes(FrameRate \ 10)
Data(x + 5) = Bte(0)
Data(x + 6) = Bte(1)
End If
End If
End If
End If
Next
Stream.Write(Data, 0, Data.Length)
End Sub
End Class
Continue reading...