Render Video with ScottPlot
Animated plots can be generated by rendering images frame-by-frame and combining them into a video file. In this example, we will use FFMpeg to generate a video file of a ScottPlot as the axis limits are panned from left to right.
-
Create a new .NET Console project
-
Modify the csproj to target
8.0-windows -
Add the
FFMpegCoreNuGet package -
Add the
ScottPlotNuGet package -
Add the following into
Program.cs
using FFMpegCore;
using FFMpegCore.Pipes;
using ScottPlot;
using SkiaSharp;
// prepare a plot with sample data
double[] values = Generate.Sin(10_000, oscillations: 100);
Plot plot = new();
plot.Add.Signal(values);
// setup the frame maker
double videoDuration = 5;
double frameRate = 30;
int frameCount = (int)(videoDuration * frameRate);
IEnumerable<IVideoFrame> frames = FrameMaker(600, 300, frameCount, frameRate, plot);
// generate the video
RawVideoPipeSource videoFramesSource = new(frames) { FrameRate = frameRate };
bool success = FFMpegArguments
.FromPipeInput(videoFramesSource)
.OutputToFile("output.webm", overwrite: true, options => options.WithVideoCodec("libvpx-vp9"))
.ProcessSynchronously();
static IEnumerable<IVideoFrame> FrameMaker(int width, int height, int frameCount, double frameRate, Plot plot)
{
for (int i = 0; i < frameCount; i++)
{
Console.WriteLine($"\rRendering frame {i + 1} of {frameCount}");
plot.Axes.SetLimitsX(i, i + 100); // pan from left to right
using SKBitmap bmp = new(width, height);
using SKCanvas canvas = new(bmp);
using SKBitmapFrame frame = new(bmp);
plot.Render(canvas, width, height);
yield return frame;
}
}
class SKBitmapFrame(SKBitmap bmp) : IVideoFrame, IDisposable
{
public int Width => Source.Width;
public int Height => Source.Height;
public string Format => "bgra";
private readonly SKBitmap Source = bmp;
public void Serialize(Stream pipe) =>
pipe.Write(Source.Bytes, 0, Source.Bytes.Length);
public Task SerializeAsync(Stream pipe, CancellationToken token) =>
pipe.WriteAsync(Source.Bytes, 0, Source.Bytes.Length, token);
public void Dispose() => Source.Dispose();
}