2012-11-13 8 views
6

Jak uzyskać czas trwania (animacji) animacji GIF?Edytowanie GIF w języku C#

Oto mój kod:

GifTime = PoczatkowyGIF.GetFrameCount(FrameDimension.Time); 
//GifTime is a double; PoczatkowyGIF is my gif image. 

GifTime nie jest dokładna. Moja animacja to 3,8 s, ale GifTime to 3,9 s. Kolejna animacja, którą wypróbowałem, to 0,88 s, ale GifTime to 0. Jak uzyskać animację? Ponadto, w jaki sposób mogę edytować GIF pierwszy czas ramki i dodać ramkę z określonym czasie do istniejącego GIF?

+0

Dlaczego obawiasz się różnicy 0,01 sekundy. Jak obliczyłeś długość animacji? –

+0

Użyłem do tego Easy Gif Animator. Kiedy dodam czasy każdej klatki (zrobiłem animację w Photoshopie) to równa się 3.8. Ale dobrze, pomińmy to pytanie i odpowiedzmy na drugie ... Jeśli program będzie działał poprawnie, mój kod do liczenia czasów był prawidłowy. –

+0

Nie widzę innego pytania szczerze mówiąc. –

Odpowiedz

5

W poniższym przykładzie widać, jak obliczyć całkowity czas trwania, gromadząc czas trwania każdej ramce:

for (int f = 0; f < frameCount; f++) 
{ 
    this_delay = BitConverter.ToInt32(image.GetPropertyItem(20736).Value, index) * 10; 
    // Minimum delay is 100 ms 
    delay += (this_delay < 100 ? 100 : this_delay); 
    index += 4; 
} 

pamiętać, że czas trwania ramka nie może być krótszy niż 100 ms, a które mogą stanowić dla twoich różnic obliczeniowych.

Możesz przeczytać this post o badaniu pliku .GIF, gdzie możesz znaleźć resztę kodu.

Ponadto, aby edytować ostatnia klatka z animowanym obrazem, można użyć

gifImage.SelectActiveFrame(dimension, first_frame_index); 

SelectActiveFrame pozwoli na edycję obrazu, zmieniając że określony (pierwsza w danym przypadku) ramy.

Nie jestem do końca tego pewien, ale framework nie obsługuje dodawania ramek, użyłem MagickNet do ponownego kodowania nowego GIF do tego celu. Możesz również spojrzeć na NGif jako alternatywę.

1

Poniższy kod jest klasą do tworzenia, otwierania lub edycji GIF.

Nie może odczytać czasu odtwarzania ze strumienia, ale dla drugiej części pytania może napisać animację GIF z określonym opóźnieniem w każdej klatce.

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Drawing; 
using System.Drawing.Imaging; 
using System.IO; 

/// <summary> 
/// Uses default .net GIF encoding and adds animation headers. 
/// </summary> 
public class Gif : IDisposable, IEnumerable<Image> 
{ 
    #region Header Constants 
    const byte FileTrailer = 0x3b, 
     ApplicationBlockSize = 0x0b, 
     GraphicControlExtensionBlockSize = 0x04; 

    const int ApplicationExtensionBlockIdentifier = 0xff21, 
     GraphicControlExtensionBlockIdentifier = 0xf921; 

    const long SourceGlobalColorInfoPosition = 10, 
     SourceGraphicControlExtensionPosition = 781, 
     SourceGraphicControlExtensionLength = 8, 
     SourceImageBlockPosition = 789, 
     SourceImageBlockHeaderLength = 11, 
     SourceColorBlockPosition = 13, 
     SourceColorBlockLength = 768; 

    const string ApplicationIdentification = "NETSCAPE2.0", 
     FileType = "GIF", 
     FileVersion = "89a"; 
    #endregion 

    class GifFrame 
    { 
     public GifFrame(Image image, double delay, int xOffset, int yOffset) 
     { 
      Image = image; 
      Delay = delay; 
      XOffset = xOffset; 
      YOffset = yOffset; 
     } 

     public Image Image; 
     public double Delay; 
     public int XOffset, YOffset; 
    } 

    List<GifFrame> Frames = new List<GifFrame>(); 

    public Gif() { DefaultFrameDelay = 500; } 

    public Gif(Stream InStream, int Repeat = 0, int Delay = 500) 
    { 
     using (Image Animation = Bitmap.FromStream(InStream)) 
     { 
      int Length = Animation.GetFrameCount(FrameDimension.Time); 

      DefaultFrameDelay = Delay; 
      this.Repeat = Repeat; 

      for (int i = 0; i < Length; ++i) 
      { 
       Animation.SelectActiveFrame(FrameDimension.Time, i); 

       var Frame = new Bitmap(Animation.Size.Width, Animation.Size.Height); 

       Graphics.FromImage(Frame).DrawImage(Animation, new Point(0, 0)); 

       Frames.Add(new GifFrame(Frame, Delay, 0, 0)); 
      } 
     } 
    } 

    #region Properties 
    public int DefaultWidth { get; set; } 

    public int DefaultHeight { get; set; } 

    public int Count { get { return Frames.Count; } } 

    /// <summary> 
    /// Default Delay in Milliseconds 
    /// </summary> 
    public int DefaultFrameDelay { get; set; } 

    public int Repeat { get; private set; } 
    #endregion 

    /// <summary> 
    /// Adds a frame to this animation. 
    /// </summary> 
    /// <param name="Image">The image to add</param> 
    /// <param name="XOffset">The positioning x offset this image should be displayed at.</param> 
    /// <param name="YOffset">The positioning y offset this image should be displayed at.</param> 
    public void AddFrame(Image Image, double? frameDelay = null, int XOffset = 0, int YOffset = 0) 
    { 
     Frames.Add(new GifFrame(Image, frameDelay ?? DefaultFrameDelay, XOffset, YOffset)); 
    } 

    public void AddFrame(string FilePath, double? frameDelay = null, int XOffset = 0, int YOffset = 0) 
    { 
     AddFrame(new Bitmap(FilePath), frameDelay, XOffset, YOffset); 
    } 

    public void RemoveAt(int Index) { Frames.RemoveAt(Index); } 

    public void Clear() { Frames.Clear(); } 

    public void Save(Stream OutStream) 
    { 
     using (var Writer = new BinaryWriter(OutStream)) 
     { 
      for (int i = 0; i < Count; ++i) 
      { 
       var Frame = Frames[i]; 

       using (var gifStream = new MemoryStream()) 
       { 
        Frame.Image.Save(gifStream, ImageFormat.Gif); 

        // Steal the global color table info 
        if (i == 0) InitHeader(gifStream, Writer, Frame.Image.Width, Frame.Image.Height); 

        WriteGraphicControlBlock(gifStream, Writer, Frame.Delay); 
        WriteImageBlock(gifStream, Writer, i != 0, Frame.XOffset, Frame.YOffset, Frame.Image.Width, Frame.Image.Height); 
       } 
      } 

      // Complete File 
      Writer.Write(FileTrailer); 
     } 
    } 

    #region Write 
    void InitHeader(Stream sourceGif, BinaryWriter Writer, int w, int h) 
    { 
     // File Header 
     Writer.Write(FileType.ToCharArray()); 
     Writer.Write(FileVersion.ToCharArray()); 

     Writer.Write((short)(DefaultWidth == 0 ? w : DefaultWidth)); // Initial Logical Width 
     Writer.Write((short)(DefaultHeight == 0 ? h : DefaultHeight)); // Initial Logical Height 

     sourceGif.Position = SourceGlobalColorInfoPosition; 
     Writer.Write((byte)sourceGif.ReadByte()); // Global Color Table Info 
     Writer.Write((byte)0); // Background Color Index 
     Writer.Write((byte)0); // Pixel aspect ratio 
     WriteColorTable(sourceGif, Writer); 

     // App Extension Header 
     unchecked { Writer.Write((short)ApplicationExtensionBlockIdentifier); }; 
     Writer.Write((byte)ApplicationBlockSize); 
     Writer.Write(ApplicationIdentification.ToCharArray()); 
     Writer.Write((byte)3); // Application block length 
     Writer.Write((byte)1); 
     Writer.Write((short)Repeat); // Repeat count for images. 
     Writer.Write((byte)0); // terminator 
    } 

    void WriteColorTable(Stream sourceGif, BinaryWriter Writer) 
    { 
     sourceGif.Position = SourceColorBlockPosition; // Locating the image color table 
     var colorTable = new byte[SourceColorBlockLength]; 
     sourceGif.Read(colorTable, 0, colorTable.Length); 
     Writer.Write(colorTable, 0, colorTable.Length); 
    } 

    void WriteGraphicControlBlock(Stream sourceGif, BinaryWriter Writer, double frameDelay) 
    { 
     sourceGif.Position = SourceGraphicControlExtensionPosition; // Locating the source GCE 
     var blockhead = new byte[SourceGraphicControlExtensionLength]; 
     sourceGif.Read(blockhead, 0, blockhead.Length); // Reading source GCE 

     unchecked { Writer.Write((short)GraphicControlExtensionBlockIdentifier); }; // Identifier 
     Writer.Write((byte)GraphicControlExtensionBlockSize); // Block Size 
     Writer.Write((byte)(blockhead[3] & 0xf7 | 0x08)); // Setting disposal flag 
     Writer.Write((short)(frameDelay/10)); // Setting frame delay 
     Writer.Write((byte)blockhead[6]); // Transparent color index 
     Writer.Write((byte)0); // Terminator 
    } 

    void WriteImageBlock(Stream sourceGif, BinaryWriter Writer, bool includeColorTable, int x, int y, int w, int h) 
    { 
     sourceGif.Position = SourceImageBlockPosition; // Locating the image block 
     var header = new byte[SourceImageBlockHeaderLength]; 
     sourceGif.Read(header, 0, header.Length); 
     Writer.Write((byte)header[0]); // Separator 
     Writer.Write((short)x); // Position X 
     Writer.Write((short)y); // Position Y 
     Writer.Write((short)w); // Width 
     Writer.Write((short)h); // Height 

     if (includeColorTable) // If first frame, use global color table - else use local 
     { 
      sourceGif.Position = SourceGlobalColorInfoPosition; 
      Writer.Write((byte)(sourceGif.ReadByte() & 0x3f | 0x80)); // Enabling local color table 
      WriteColorTable(sourceGif, Writer); 
     } 
     else Writer.Write((byte)(header[9] & 0x07 | 0x07)); // Disabling local color table 

     Writer.Write((byte)header[10]); // LZW Min Code Size 

     // Read/Write image data 
     sourceGif.Position = SourceImageBlockPosition + SourceImageBlockHeaderLength; 

     var dataLength = sourceGif.ReadByte(); 
     while (dataLength > 0) 
     { 
      var imgData = new byte[dataLength]; 
      sourceGif.Read(imgData, 0, dataLength); 

      Writer.Write((byte)dataLength); 
      Writer.Write(imgData, 0, dataLength); 
      dataLength = sourceGif.ReadByte(); 
     } 

     Writer.Write((byte)0); // Terminator 
    } 
    #endregion 

    public void Dispose() 
    { 
     Frames.Clear(); 
     Frames = null; 
    } 

    public Image this[int Index] { get { return Frames[Index].Image; } } 

    public IEnumerator<Image> GetEnumerator() { foreach (var Frame in Frames) yield return Frame.Image; } 

    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } 
}