The FrameWidthXor and FramePoolXor effects make for a cool outlining, but also can be used to detect motion, since everything that is still is darker. Each frame is saved in a block of bytes, and then those bytes are XORed with the bytes delivered in the current frame, bit-wise nulling any similarities.
The pool effect is different because it provides a moving temporal hysteresis window between the frames being compared. Overall heightened luminance caused by motion or color changes will cover more frames and be intensified with a longer frame pool size. The frame width effect only compares one frame to the one before it. The frame pool compares one to ones farther before it.
It looks like my YouTube channel art, which is from a picture of Steel Stacks (Bethlehem PA US) I took in 2016. And did what I remember as "some kind of XOR thing" but I can get it to run in real time. It's fun. If I remember right I XORed the same picture, slightly shifted.
People operate two-person machine-gun tree in outer space: Frame Pool XOR. |
Here's that code for the simple frame width XOR effect.
public sealed class FrameWidthXor : IBasicVideoEffect
{
private int byteCount;
private bool skipWrite;
private byte[] outBytes;
private byte[] backFrame;
private int pWidth, pHeight;
private CanvasDevice canvasDevice;
private IPropertySet configuration;
public bool TimeIndependent { get { return true; } }
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }
public bool IsReadOnly { get { return false; } }
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var encodingProperties = new VideoEncodingProperties();
encodingProperties.Subtype = "ARGB32";
return new List<VideoEncodingProperties>() { encodingProperties };
}
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}
public void SetProperties(IPropertySet configuration)
{
this.configuration = configuration;
object obj1;
if (configuration.TryGetValue("SkipWrite", out obj1))
{
skipWrite = (bool)obj1;
}
else
{
skipWrite = false;
}
}
public void DiscardQueuedFrames()
{
}
public void Close(MediaEffectClosedReason reason)
{
if (reason == MediaEffectClosedReason.EffectCurrentlyUnloaded)
{
outBytes = null;
backFrame = null;
pWidth = 0;
}
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
if (skipWrite)
{
using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
{
renderTarget.CopyPixelsFromBitmap(inputBitmap);
}
return;
}
using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
{
if ((pWidth != inputBitmap.SizeInPixels.Width) | (pHeight != inputBitmap.SizeInPixels.Height))
{
pWidth = (int)inputBitmap.SizeInPixels.Width;
pHeight = (int)inputBitmap.SizeInPixels.Height;
byteCount = pWidth * pHeight * 4;
outBytes = new byte[byteCount];
backFrame = inputBitmap.GetPixelBytes();
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
{
renderTarget.SetPixelBytes(backFrame);
}
return;
}
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
{
//long lo = DateTime.Now.Ticks;
byte[] bytes2 = inputBitmap.GetPixelBytes();
int ofs;
for (int int1 = 0; int1 < byteCount; int1 += 4)
{
ofs = int1;
outBytes[ofs] = (byte)(bytes2[ofs] ^ backFrame[ofs]);
ofs++;
outBytes[ofs] = (byte)(bytes2[ofs] ^ backFrame[ofs]);
ofs++;
outBytes[ofs] = (byte)(bytes2[ofs] ^ backFrame[ofs]);
outBytes[ofs + 1] = 255; //a=0 will draw in PreviewControl but
// be blank with GetPreviewFrameAsync because it's premultiplied
}
renderTarget.SetPixelBytes(outBytes);
backFrame = bytes2;
//Debug.WriteLine((DateTime.Now.Ticks - lo) / 10000);
}
}
}
}
Here is the code for the frame pool XOR effect. It runs somewhat slower because of adding required for pool indexing. Using this, there will be a lot of two copies of everything moving.
public sealed class FramePoolXor : IBasicVideoEffect
{
private int poolSize;
private int byteCount;
private bool skipWrite;
private int frameCount;
private byte[] outBytes;
private byte[] backFrames;
private int pWidth, pHeight;
private CanvasDevice canvasDevice;
private IPropertySet configuration;
public bool TimeIndependent { get { return true; } }
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }
public bool IsReadOnly { get { return false; } }
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var encodingProperties = new VideoEncodingProperties();
encodingProperties.Subtype = "ARGB32";
return new List<VideoEncodingProperties>() { encodingProperties };
}
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}
public void SetProperties(IPropertySet configuration)
{
this.configuration = configuration;
if (configuration.TryGetValue("SkipWrite", out object obj1))
{
skipWrite = (bool)obj1;
}
else
{
skipWrite = false;
}
if (configuration.TryGetValue("PoolSize", out object obj2))
{
poolSize = (int)obj2; //at least 2, important
}
else
{
poolSize = 10;
}
pWidth = 0;
}
public void DiscardQueuedFrames()
{
frameCount = 0;
}
public void Close(MediaEffectClosedReason reason)
{
if (reason == MediaEffectClosedReason.EffectCurrentlyUnloaded)
{
outBytes = null;
backFrames = null;
pWidth = 0;
}
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
//long lo = DateTime.Now.Ticks;
if (skipWrite)
{
using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
{
renderTarget.CopyPixelsFromBitmap(inputBitmap);
}
return;
}
using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
{
if ((pWidth != inputBitmap.SizeInPixels.Width) | (pHeight != inputBitmap.SizeInPixels.Height))
{
pWidth = (int)inputBitmap.SizeInPixels.Width;
pHeight = (int)inputBitmap.SizeInPixels.Height;
byteCount = pWidth * pHeight * 4;
outBytes = new byte[byteCount];
backFrames = new byte[byteCount * poolSize];
inputBitmap.GetPixelBytes().CopyTo(backFrames, 0);
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
{
renderTarget.CopyPixelsFromBitmap(inputBitmap);
}
frameCount++;
return;
}
if (frameCount < poolSize - 1)
{
int offSet = frameCount * byteCount;
inputBitmap.GetPixelBytes().CopyTo(backFrames, offSet);
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
{
renderTarget.CopyPixelsFromBitmap(inputBitmap);
}
frameCount++;
return;
}
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
{
int offSet1 = (frameCount % poolSize) * byteCount;
int offSet2 = ((frameCount + 1) % poolSize) * byteCount;
int ofs;
inputBitmap.GetPixelBytes().CopyTo(backFrames, offSet1);
for (int int1 = 0; int1 < byteCount; int1 += 4)
{
ofs = int1;
outBytes[ofs] = (byte)(backFrames[ofs + offSet1] ^ backFrames[ofs + offSet2]);
ofs++;
outBytes[ofs] = (byte)(backFrames[ofs + offSet1] ^ backFrames[ofs + offSet2]);
ofs++;
outBytes[ofs] = (byte)(backFrames[ofs + offSet1] ^ backFrames[ofs + offSet2]);
outBytes[ofs + 1] = 255;
}
frameCount++;
renderTarget.SetPixelBytes(outBytes);
}
}
//Debug.WriteLine((DateTime.Now.Ticks - lo) / 10000);
}
}
Note about code listing. When copied the right arrow (<) and left arrow (>) may not show up properly, and instead be represented by the string literals (<) and (>) respectively. Replace those four character literals with the correct arrow in that case.
Disclaimer: This is free for personal use only. Place of origin, California, US.