BizHawk/BizHawk.Client.EmuHawk/AVOut/GifWriter.cs

265 lines
5.6 KiB
C#
Raw Normal View History

using System;
using System.IO;
using System.Drawing;
using BizHawk.Client.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.EmuHawk
{
2014-10-10 18:09:00 +00:00
[VideoWriter("gif", "GIF writer", "Creates an animated .gif")]
public class GifWriter : IVideoWriter
{
public class GifToken : IDisposable
{
private int _frameskip, _framedelay;
/// <summary>
2017-04-18 17:27:44 +00:00
/// Gets how many frames to skip for each frame deposited
/// </summary>
2017-04-18 17:27:44 +00:00
public int Frameskip
{
2019-12-31 16:17:55 +00:00
get => _frameskip;
private set
{
if (value < 0)
2017-04-18 17:27:44 +00:00
{
_frameskip = 0;
2017-04-18 17:27:44 +00:00
}
else if (value > 999)
2017-04-18 17:27:44 +00:00
{
_frameskip = 999;
2017-04-18 17:27:44 +00:00
}
else
2017-04-18 17:27:44 +00:00
{
_frameskip = value;
2017-04-18 17:27:44 +00:00
}
}
}
/// <summary>
/// how long to delay between each gif frame (units of 10ms, -1 = auto)
/// </summary>
2017-04-18 17:27:44 +00:00
public int FrameDelay
{
2019-12-31 16:17:55 +00:00
get => _framedelay;
private set
{
if (value < -1)
2017-04-18 17:27:44 +00:00
{
_framedelay = -1;
2017-04-18 17:27:44 +00:00
}
else if (value > 100)
2017-04-18 17:27:44 +00:00
{
_framedelay = 100;
2017-04-18 17:27:44 +00:00
}
else
2017-04-18 17:27:44 +00:00
{
_framedelay = value;
2017-04-18 17:27:44 +00:00
}
}
}
2017-04-18 17:27:44 +00:00
public static GifToken LoadFromConfig()
{
return new GifToken(0, 0)
{
Frameskip = Global.Config.GifWriterFrameskip,
FrameDelay = Global.Config.GifWriterDelay
};
}
2017-04-18 17:27:44 +00:00
public void Dispose()
{
}
2019-12-31 16:17:55 +00:00
private GifToken(int frameskip, int frameDelay)
{
2017-04-18 17:27:44 +00:00
Frameskip = frameskip;
2019-12-31 16:17:55 +00:00
FrameDelay = frameDelay;
}
}
2017-04-18 17:27:44 +00:00
private GifToken _token;
/// <exception cref="ArgumentException"><paramref name="token"/> does not inherit <see cref="GifWriter.GifToken"/></exception>
public void SetVideoCodecToken(IDisposable token)
{
2019-12-31 16:17:55 +00:00
if (token is GifToken gifToken)
{
2019-12-31 16:17:55 +00:00
_token = gifToken;
CalcDelay();
}
else
2017-04-18 17:27:44 +00:00
{
2019-03-28 03:17:14 +00:00
throw new ArgumentException($"{nameof(GifWriter)} only takes its own tokens!");
2017-04-18 17:27:44 +00:00
}
}
public void SetDefaultVideoCodecToken()
{
2017-04-18 17:27:44 +00:00
_token = GifToken.LoadFromConfig();
CalcDelay();
}
/// <summary>
/// true if the first frame has been written to the file; false otherwise
/// </summary>
2017-04-18 17:27:44 +00:00
private bool firstdone = false;
/// <summary>
/// the underlying stream we're writing to
/// </summary>
2017-04-18 17:27:44 +00:00
private Stream f;
/// <summary>
/// a final byte we must write before closing the stream
/// </summary>
2017-04-18 17:27:44 +00:00
private byte lastbyte;
/// <summary>
/// keep track of skippable frames
/// </summary>
2017-04-18 17:27:44 +00:00
private int skipindex = 0;
2017-04-18 17:27:44 +00:00
private int fpsnum = 1, fpsden = 1;
2017-04-18 17:27:44 +00:00
public void SetFrame(int frame)
{
}
public void OpenFile(string baseName)
{
f = new FileStream(baseName, FileMode.OpenOrCreate, FileAccess.Write);
2017-04-18 17:27:44 +00:00
skipindex = _token.Frameskip;
}
public void CloseFile()
{
f.WriteByte(lastbyte);
f.Close();
}
/// <summary>
/// precooked gif header
/// </summary>
2017-04-18 17:27:44 +00:00
static byte[] GifAnimation = { 33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0 };
/// <summary>
/// little endian frame length in 10ms units
/// </summary>
byte[] Delay = {100, 0};
2017-04-18 17:27:44 +00:00
public void AddFrame(IVideoProvider source)
{
2017-04-18 17:27:44 +00:00
if (skipindex == _token.Frameskip)
{
skipindex = 0;
2017-04-18 17:27:44 +00:00
}
else
{
skipindex++;
return; // skip this frame
}
2019-12-31 16:17:55 +00:00
using var 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);
2019-12-31 16:17:55 +00:00
using var qBmp = new OctreeQuantizer(255, 8).Quantize(bmp);
MemoryStream ms = new MemoryStream();
qBmp.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);
}
2017-04-18 17:27:44 +00:00
2019-12-31 16:17:55 +00:00
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));
2019-12-31 16:17:55 +00:00
lastbyte = b[ms.Length - 1];
}
public void AddSamples(short[] samples)
{
// ignored
}
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
{
return GifWriterForm.DoTokenForm(hwnd);
}
2017-04-18 17:27:44 +00:00
private void CalcDelay()
{
2017-04-18 17:27:44 +00:00
if (_token == null)
{
return;
2017-04-18 17:27:44 +00:00
}
int delay;
2017-04-18 17:27:44 +00:00
if (_token.FrameDelay == -1)
{
delay = (100 * fpsden * (_token.Frameskip + 1) + (fpsnum / 2)) / fpsnum;
}
else
2017-04-18 17:27:44 +00:00
{
delay = _token.FrameDelay;
}
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 DesiredExtension()
{
return "gif";
}
public void Dispose()
{
if (f != null)
{
f.Dispose();
f = null;
}
}
2017-04-18 17:27:44 +00:00
public bool UsesAudio => false;
public bool UsesVideo => true;
}
}