diff --git a/BizHawk.MultiClient/AVOut/GifWriter.cs b/BizHawk.MultiClient/AVOut/GifWriter.cs new file mode 100644 index 0000000000..bc0a507a51 --- /dev/null +++ b/BizHawk.MultiClient/AVOut/GifWriter.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Drawing; + +namespace BizHawk.MultiClient.AVOut +{ + public class GifWriter : IVideoWriter + { + public class GifToken : IDisposable + { + /// + /// how many frames to skip for each frame deposited + /// + public int frameskip { get; private set; } + public void Dispose() { } + + public GifToken(int frameskip) + { + this.frameskip = frameskip; + } + + public static GifToken LoadFromConfig() + { + GifToken ret = new GifToken(0); + ret.frameskip = Global.Config.GifWriterFrameskip; + return ret; + } + } + GifToken token; + + public void SetVideoCodecToken(IDisposable token) + { + if (token is GifToken) + { + this.token = (GifToken)token; + CalcDelay(); + } + else + throw new ArgumentException("GifWriter only takes its own tokens!"); + } + + public void SetDefaultVideoCodecToken() + { + token = GifToken.LoadFromConfig(); + CalcDelay(); + } + + /// + /// true if the first frame has been written to the file; false otherwise + /// + bool firstdone = false; + + /// + /// the underlying stream we're writing to + /// + Stream f; + + /// + /// a final byte we must write before closing the stream + /// + byte lastbyte; + + /// + /// keep track of skippable frames + /// + int skipindex = 0; + + int fpsnum = 1, fpsden = 1; + + + public void OpenFile(string baseName) + { + f = new FileStream(baseName, FileMode.OpenOrCreate, FileAccess.Write); + skipindex = token.frameskip; + } + + public void CloseFile() + { + f.WriteByte(lastbyte); + f.Close(); + } + + /// + /// precooked gif header + /// + static byte[] GifAnimation = {33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0}; + /// + /// little endian frame length in 10ms units + /// + byte[] Delay = {100, 0}; + public void AddFrame(IVideoProvider source) + { + if (skipindex == token.frameskip) + skipindex = 0; + else + { + skipindex++; + return; // skip this frame + } + + + Bitmap bmp = new Bitmap(source.BufferWidth, source.BufferHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + { + var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + System.Runtime.InteropServices.Marshal.Copy(source.GetVideoBuffer(), 0, data.Scan0, bmp.Width * bmp.Height); + bmp.UnlockBits(data); + } + + MemoryStream ms = new MemoryStream(); + bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); + byte[] b = ms.GetBuffer(); + if (!firstdone) + { + firstdone = true; + b[10] = (byte)(b[10] & 0x78); // no global color table + f.Write(b, 0, 13); + f.Write(GifAnimation, 0, GifAnimation.Length); + } + b[785] = Delay[0]; + b[786] = Delay[1]; + b[798] = (byte)(b[798] | 0x87); + f.Write(b, 781, 18); + f.Write(b, 13, 768); + f.Write(b, 799, (int)(ms.Length - 800)); + + lastbyte = b[ms.Length - 1]; + } + + public void AddSamples(short[] samples) + { + // ignored + } + + public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd) + { + return GifWriterForm.DoTokenForm(hwnd); + } + + void CalcDelay() + { + if (token == null) + return; + int delay = (100 * fpsden * (token.frameskip + 1) + (fpsnum / 2)) / fpsnum; + Delay[0] = (byte)(delay & 0xff); + Delay[1] = (byte)(delay >> 8 & 0xff); + } + + public void SetMovieParameters(int fpsnum, int fpsden) + { + this.fpsnum = fpsnum; + this.fpsden = fpsden; + CalcDelay(); + } + + public void SetVideoParameters(int width, int height) + { + // we read them directly from each individual frame, ignore the rest + } + + public void SetAudioParameters(int sampleRate, int channels, int bits) + { + // ignored + } + + public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords) + { + // gif can't support this + } + + public string WriterDescription() + { + return "Creates an animated .gif"; + } + + public string DesiredExtension() + { + return "gif"; + } + + public string ShortName() + { + return "gif"; + } + + public void Dispose() + { + if (f != null) + { + f.Dispose(); + f = null; + } + } + + public override string ToString() + { + return "gif writer"; + } + } +} diff --git a/BizHawk.MultiClient/AVOut/GifWriterForm.Designer.cs b/BizHawk.MultiClient/AVOut/GifWriterForm.Designer.cs new file mode 100644 index 0000000000..454fc66fc4 --- /dev/null +++ b/BizHawk.MultiClient/AVOut/GifWriterForm.Designer.cs @@ -0,0 +1,105 @@ +namespace BizHawk.MultiClient.AVOut +{ + partial class GifWriterForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); + this.label1 = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); + this.SuspendLayout(); + // + // button1 + // + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Location = new System.Drawing.Point(12, 51); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 0; + this.button1.Text = "OK"; + this.button1.UseVisualStyleBackColor = true; + // + // button2 + // + this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button2.Location = new System.Drawing.Point(93, 51); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(75, 23); + this.button2.TabIndex = 1; + this.button2.Text = "Cancel"; + this.button2.UseVisualStyleBackColor = true; + // + // numericUpDown1 + // + this.numericUpDown1.Location = new System.Drawing.Point(12, 25); + this.numericUpDown1.Maximum = new decimal(new int[] { + 999, + 0, + 0, + 0}); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(120, 20); + this.numericUpDown1.TabIndex = 2; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(232, 13); + this.label1.TabIndex = 3; + this.label1.Text = "Number of frames to skip for each frame written:"; + // + // GifWriterForm + // + this.AcceptButton = this.button1; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button2; + this.ClientSize = new System.Drawing.Size(269, 83); + this.Controls.Add(this.label1); + this.Controls.Add(this.numericUpDown1); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Name = "GifWriterForm"; + this.Text = "GifWriter Options"; + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.NumericUpDown numericUpDown1; + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/BizHawk.MultiClient/AVOut/GifWriterForm.cs b/BizHawk.MultiClient/AVOut/GifWriterForm.cs new file mode 100644 index 0000000000..777fc10727 --- /dev/null +++ b/BizHawk.MultiClient/AVOut/GifWriterForm.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace BizHawk.MultiClient.AVOut +{ + public partial class GifWriterForm : Form + { + public GifWriterForm() + { + InitializeComponent(); + } + + public static GifWriter.GifToken DoTokenForm(IWin32Window parent) + { + using (var dlg = new GifWriterForm()) + { + dlg.numericUpDown1.Value = Global.Config.GifWriterFrameskip; + + var result = dlg.ShowDialog(parent); + if (result == DialogResult.OK) + { + Global.Config.GifWriterFrameskip = (int)dlg.numericUpDown1.Value; + return GifWriter.GifToken.LoadFromConfig(); + } + else + return null; + } + } + } +} diff --git a/BizHawk.MultiClient/AVOut/GifWriterForm.resx b/BizHawk.MultiClient/AVOut/GifWriterForm.resx new file mode 100644 index 0000000000..29dcb1b3a3 --- /dev/null +++ b/BizHawk.MultiClient/AVOut/GifWriterForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BizHawk.MultiClient/AVOut/IVideoWriter.cs b/BizHawk.MultiClient/AVOut/IVideoWriter.cs index fe1f4fc3cf..d539dad857 100644 --- a/BizHawk.MultiClient/AVOut/IVideoWriter.cs +++ b/BizHawk.MultiClient/AVOut/IVideoWriter.cs @@ -109,7 +109,8 @@ namespace BizHawk new JMDWriter(), new WavWriterV(), new FFmpegWriter(), - new NutWriter() + new NutWriter(), + new BizHawk.MultiClient.AVOut.GifWriter() }; return ret; } diff --git a/BizHawk.MultiClient/BizHawk.MultiClient.csproj b/BizHawk.MultiClient/BizHawk.MultiClient.csproj index 3653210010..f8b4d7d499 100644 --- a/BizHawk.MultiClient/BizHawk.MultiClient.csproj +++ b/BizHawk.MultiClient/BizHawk.MultiClient.csproj @@ -122,6 +122,13 @@ FFmpegWriterForm.cs + + + Form + + + GifWriterForm.cs + Form @@ -392,6 +399,9 @@ FFmpegWriterForm.cs + + GifWriterForm.cs + JMDForm.cs diff --git a/BizHawk.MultiClient/Config.cs b/BizHawk.MultiClient/Config.cs index fae9f8c5a2..216bbc47b7 100644 --- a/BizHawk.MultiClient/Config.cs +++ b/BizHawk.MultiClient/Config.cs @@ -326,6 +326,7 @@ namespace BizHawk.MultiClient public string FFmpegFormat = ""; public string FFmpegCustomCommand = "-c:a foo -c:v bar -f baz"; public string AVICodecToken = ""; + public int GifWriterFrameskip = 3; // NESPPU Settings public bool AutoLoadNESPPU = false;