ScreenToGif
ScreenToGif
允许您记录屏幕的选定区域、网络摄像头的实时信息或素描板上的实时绘图。之后,您可以编辑动画并将其保存为 gif、apng、视频、psd 或 png 图像。
ScreenToGif
在github下的地址在这里。
功能介绍:
-
1.记录你的屏幕,并直接保存到一个GIF动画的环形。
-
2.暂停和继续记录。
-
3.移动窗口来记录你想要的。
-
4.您可以添加文字,字幕和标题框。
-
5.编辑框,添加过滤器,还原,做优悠风格,斯洛伐克运动,加边框。
-
6.导出帧。
-
7.作物和调整。
-
8.你可以工作,即使在节目录制。
-
9.删除你不想要的帧。
-
10.选择一个文件夹,自动保存文件或选择一个enconding之前。
ScreenToGif
也比较简单,主要的有两个功能,一个是录取屏幕一个是录取摄像头的视频。
还有一个白板的功能,也是挺好玩的:
主要代码分析
获取摄像头的数据:
protected void CreateGraph()
{
//Skip if already created
if ((int)ActualGraphState < (int)GraphState.Created)
{
//Make a new filter graph.
GraphBuilder = (ExtendStreaming.IGraphBuilder)Activator.CreateInstance(Type.GetTypeFromCLSID(Uuid.Clsid.FilterGraph, true));
//Get the Capture Graph Builder.
var clsid = Uuid.Clsid.CaptureGraphBuilder2;
var riid = typeof(ExtendStreaming.ICaptureGraphBuilder2).GUID;
CaptureGraphBuilder = (ExtendStreaming.ICaptureGraphBuilder2)Activator.CreateInstance(Type.GetTypeFromCLSID(clsid, true));
//Link the CaptureGraphBuilder to the filter graph
var hr = CaptureGraphBuilder.SetFiltergraph(GraphBuilder);
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
var comType = Type.GetTypeFromCLSID(Uuid.Clsid.SampleGrabber);
if (comType == null)
throw new Exception("DirectShow SampleGrabber not installed/registered!");
var comObj = Activator.CreateInstance(comType);
SampGrabber = (EditStreaming.ISampleGrabber)comObj; comObj = null;
_baseGrabFlt = (CoreStreaming.IBaseFilter) SampGrabber;
var media = new CoreStreaming.AmMediaType();
//Get the video device and add it to the filter graph
if (VideoDevice != null)
{
VideoDeviceFilter = (CoreStreaming.IBaseFilter)Marshal.BindToMoniker(VideoDevice.MonikerString);
hr = GraphBuilder.AddFilter(VideoDeviceFilter, "Video Capture Device");
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
media.majorType = Uuid.MediaType.Video;
media.subType = Uuid.MediaSubType.RGB32;//RGB24;
media.formatType = Uuid.FormatType.VideoInfo;
media.temporalCompression = true; //New
hr = SampGrabber.SetMediaType(media);
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
hr = GraphBuilder.AddFilter(_baseGrabFlt, "Grabber");
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
}
// Retrieve the stream control interface for the video device
// FindInterface will also add any required filters
// (WDM devices in particular may need additional
// upstream filters to function).
// Try looking for an interleaved media type
object o;
var cat = Uuid.PinCategory.Capture;
var med = Uuid.MediaType.Interleaved;
var iid = typeof(ExtendStreaming.IAMStreamConfig).GUID;
hr = CaptureGraphBuilder.FindInterface(cat, med, VideoDeviceFilter, iid, out o);
if (hr != 0)
{
// If not found, try looking for a video media type
med = Uuid.MediaType.Video;
hr = CaptureGraphBuilder.FindInterface(cat, med, VideoDeviceFilter, iid, out o);
if (hr != 0)
o = null;
}
VideoStreamConfig = o as ExtendStreaming.IAMStreamConfig;
// Retrieve the media control interface (for starting/stopping graph)
MediaControl = (ControlStreaming.IMediaControl)GraphBuilder;
// Reload any video crossbars
//if (videoSources != null) videoSources.Dispose(); videoSources = null;
_videoInfoHeader = (EditStreaming.VideoInfoHeader)Marshal.PtrToStructure(media.formatPtr, typeof(EditStreaming.VideoInfoHeader));
Marshal.FreeCoTaskMem(media.formatPtr); media.formatPtr = IntPtr.Zero;
hr = SampGrabber.SetBufferSamples(false);
if (hr == 0)
hr = SampGrabber.SetOneShot(false);
if (hr == 0)
hr = SampGrabber.SetCallback(null, 0);
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
}
//Update the state now that we are done.
ActualGraphState = GraphState.Created;
}
每一帧的数据:
public Bitmap GetFrame()
{
//TODO: Verify any possible leaks.
//Asks for the buffer size.
var bufferSize = 0;
SampGrabber.GetCurrentBuffer(ref bufferSize, IntPtr.Zero);
//Allocs the byte array.
var handleObj = GCHandle.Alloc(_savedArray, GCHandleType.Pinned);
//Gets the address of the pinned object.
var address = handleObj.AddrOfPinnedObject();
//Puts the buffer inside the byte array.
SampGrabber.GetCurrentBuffer(ref bufferSize, address);
//Image size.
var width = _videoInfoHeader.BmiHeader.Width;
var height = _videoInfoHeader.BmiHeader.Height;
var stride = width * 3;
//address += (height - 1) * stride;
address += height * stride;
var bitmap = new Bitmap(width, height, -stride, System.Drawing.Imaging.PixelFormat.Format24bppRgb, address);
handleObj.Free();
return bitmap;
}
截取屏幕:
public override int Capture(FrameInfo frame)
{
var res = new Result(-1);
var wasCaptured = false;
try
{
//Try to get the duplicated output frame within given time.
res = DuplicatedOutput.TryAcquireNextFrame(0, out var info, out var resource);
if (res.Failure || resource == null || info.TotalMetadataBufferSize == 0)
{
//Somehow, it was not possible to retrieve the resource, frame or metadata.
resource?.Dispose();
return FrameCount;
}
#region Process changes
//Copy resource into memory that can be accessed by the CPU.
using (var screenTexture = resource.QueryInterface<Texture2D>())
{
#region Moved rectangles
var movedRectangles = new OutputDuplicateMoveRectangle[info.TotalMetadataBufferSize];
DuplicatedOutput.GetFrameMoveRects(movedRectangles.Length, movedRectangles, out var movedRegionsLength);
for (var movedIndex = 0; movedIndex < movedRegionsLength / Marshal.SizeOf(typeof(OutputDuplicateMoveRectangle)); movedIndex++)
{
//Crop the destination rectangle to the scree area rectangle.
var left = Math.Max(movedRectangles[movedIndex].DestinationRect.Left, Left);
var right = Math.Min(movedRectangles[movedIndex].DestinationRect.Right, Left + Width);
var top = Math.Max(movedRectangles[movedIndex].DestinationRect.Top, Top);
var bottom = Math.Min(movedRectangles[movedIndex].DestinationRect.Bottom, Top + Height);
//Copies from the screen texture only the area which the user wants to capture.
if (right > left && bottom > top)
{
//Limit the source rectangle to the available size within the destination rectangle.
var sourceWidth = movedRectangles[movedIndex].SourcePoint.X + (right - left);
var sourceHeight = movedRectangles[movedIndex].SourcePoint.Y + (bottom - top);
Device.ImmediateContext.CopySubresourceRegion(screenTexture, 0, new ResourceRegion(movedRectangles[movedIndex].SourcePoint.X, movedRectangles[movedIndex].SourcePoint.Y, 0, sourceWidth, sourceHeight, 1),
StagingTexture, 0, left - Left, top - Top);
wasCaptured = true;
}
}
#endregion
#region Dirty rectangles
var dirtyRectangles = new RawRectangle[info.TotalMetadataBufferSize];
DuplicatedOutput.GetFrameDirtyRects(dirtyRectangles.Length, dirtyRectangles, out var dirtyRegionsLength);
for (var dirtyIndex = 0; dirtyIndex < dirtyRegionsLength / Marshal.SizeOf(typeof(RawRectangle)); dirtyIndex++)
{
//Crop screen positions and size to frame sizes.
var left = Math.Max(dirtyRectangles[dirtyIndex].Left, Left);
var right = Math.Min(dirtyRectangles[dirtyIndex].Right, Left + Width);
var top = Math.Max(dirtyRectangles[dirtyIndex].Top, Top);
var bottom = Math.Min(dirtyRectangles[dirtyIndex].Bottom, Top + Height);
//Copies from the screen texture only the area which the user wants to capture.
if (right > left && bottom > top)
{
Device.ImmediateContext.CopySubresourceRegion(screenTexture, 0, new ResourceRegion(left, top, 0, right, bottom, 1), StagingTexture, 0, left - Left, top - Top);
wasCaptured = true;
}
}
#endregion
if (!wasCaptured)
{
//Nothing was changed within the capture region, so ignore this frame.
resource.Dispose();
return FrameCount;
}
}
#endregion
#region Gets the image data
//Gets the staging texture as a stream.
var data = Device.ImmediateContext.MapSubresource(StagingTexture, 0, MapMode.Read, MapFlags.None);
if (data.IsEmpty)
{
Device.ImmediateContext.UnmapSubresource(StagingTexture, 0);
resource?.Dispose();
return FrameCount;
}
var bitmap = new System.Drawing.Bitmap(Width, Height, PixelFormat.Format32bppArgb);
var boundsRect = new System.Drawing.Rectangle(0, 0, Width, Height);
//Copy pixels from screen capture Texture to the GDI bitmap.
var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
var sourcePtr = data.DataPointer;
var destPtr = mapDest.Scan0;
for (var y = 0; y < Height; y++)
{
//Copy a single line.
Utilities.CopyMemory(destPtr, sourcePtr, Width * 4);
//Advance pointers.
sourcePtr = IntPtr.Add(sourcePtr, data.RowPitch);
destPtr = IntPtr.Add(destPtr, mapDest.Stride);
}
//Release source and dest locks.
bitmap.UnlockBits(mapDest);
//Set frame details.
FrameCount++;
frame.Path = $"{Project.FullPath}{FrameCount}.png";
frame.Delay = FrameRate.GetMilliseconds();
frame.Image = bitmap;
BlockingCollection.Add(frame);
#endregion
Device.ImmediateContext.UnmapSubresource(StagingTexture, 0);
resource?.Dispose();
return FrameCount;
}
catch (SharpDXException se) when (se.ResultCode.Code == SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
{
return FrameCount;
}
catch (SharpDXException se) when (se.ResultCode.Code == SharpDX.DXGI.ResultCode.DeviceRemoved.Result.Code || se.ResultCode.Code == SharpDX.DXGI.ResultCode.DeviceReset.Result.Code)
{
//When the device gets lost or reset, the resources should be instantiated again.
DisposeInternal();
Initialize();
return FrameCount;
}
catch (Exception ex)
{
LogWriter.Log(ex, "It was not possible to finish capturing the frame with DirectX.");
OnError.Invoke(ex);
return FrameCount;
}
finally
{
try
{
//Only release the frame if there was a success in capturing it.
if (res.Success)
DuplicatedOutput.ReleaseFrame();
}
catch (Exception e)
{
LogWriter.Log(e, "It was not possible to release the frame.");
}
}
}
白板数据:
定时获取白板内容:
private void Normal_Elapsed(object sender, EventArgs e)
{
var fileName = $"{Project.FullPath}{FrameCount}.png";
//TODO: GetRender fails to create useful image when the control has decimals values as size.
var render = MainBorder.GetRender(_dpi); //TODO: Too heavy! Maybe just save the strokes? like layers?
Project.Frames.Add(new FrameInfo(fileName, FrameRate.GetMilliseconds()));
ThreadPool.QueueUserWorkItem(delegate { AddFrames(fileName, render); });
FrameCount++;
}
数据加入每一帧中:
private void AddFrames(string fileName, BitmapSource bitmap)
{
//var mutexLock = new Mutex(false, bitmap.GetHashCode().ToString());
//mutexLock.WaitOne();
using (var stream = new FileStream(fileName, FileMode.Create))
{
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(stream);
stream.Flush();
stream.Close();
}
//GC.Collect(1);
//mutexLock.ReleaseMutex();
}