diff --git a/CrtTextBox/CrtTextBox.csproj b/CrtTextBox/CrtTextBox.csproj
index 4acdcef..7fa669f 100644
--- a/CrtTextBox/CrtTextBox.csproj
+++ b/CrtTextBox/CrtTextBox.csproj
@@ -20,6 +20,10 @@
+
+
+
+
diff --git a/CrtTextBox/ShaderControl.cs b/CrtTextBox/ShaderControl.cs
index c188c47..06d7d9a 100644
--- a/CrtTextBox/ShaderControl.cs
+++ b/CrtTextBox/ShaderControl.cs
@@ -20,7 +20,10 @@
using Avalonia.Rendering.Composition;
using Avalonia.Skia;
using Avalonia.Threading;
+using OpenCvSharp;
using SkiaSharp;
+using Rect = Avalonia.Rect;
+using Size = Avalonia.Size;
namespace RenderTest;
@@ -46,12 +49,15 @@ public class ShaderControl : UserControl
///
public static readonly StyledProperty FpsProperty = AvaloniaProperty.Register(nameof(Fps), 30);
+ private VideoCapture m_videoCapture;
+ private Mat m_webcamFrame;
+
static ShaderControl()
{
AffectsRender(ShaderUriProperty);
AffectsMeasure(ShaderUriProperty);
}
-
+
///
/// Gets or sets the frames per second (FPS) at which the source control is sampled.
///
@@ -69,7 +75,7 @@ public Uri ShaderUri
get => GetValue(ShaderUriProperty);
set => SetValue(ShaderUriProperty, value);
}
-
+
///
/// Gets or sets the control source to which the shader effect will be applied.
///
@@ -105,7 +111,7 @@ public Control ControlSource
///
public void AddUniform(string name, bool value) =>
AddUniform(name, value ? 1.0f : 0.0f);
-
+
///
/// Call to set a constant uniform float/float2/float3/float4 value, to be passed to the shader code each frame.
///
@@ -146,14 +152,88 @@ private void RenderSourceAsImage()
using var rtb = new RenderTargetBitmap(new PixelSize(m_sourceControlBitmap.Width, m_sourceControlBitmap.Height));
rtb.Render(ControlSource);
+ // Copy the rendered pixels to the bitmap.
rtb.CopyPixels(
new PixelRect(0, 0, m_sourceControlBitmap.Width, m_sourceControlBitmap.Height),
m_sourceControlBitmap.GetPixels(),
m_sourceControlBitmap.ByteCount,
m_sourceControlBitmap.RowBytes);
+ ApplyWebcamOverlay();
+
m_visualHandler.SourceBitmap = m_sourceControlBitmap;
}
+ private unsafe void ApplyWebcamOverlay()
+ {
+ if (Design.IsDesignMode)
+ return;
+
+ if (m_videoCapture == null)
+ {
+ m_videoCapture = new VideoCapture(0);
+
+ // Set the desired resolution (e.g., 640x480)
+ m_videoCapture.Set(VideoCaptureProperties.FrameWidth, 640);
+ m_videoCapture.Set(VideoCaptureProperties.FrameHeight, 480);
+ m_webcamFrame ??= new Mat();
+ }
+
+ m_videoCapture.Read(m_webcamFrame);
+ var resizedFrame = m_webcamFrame;
+ var resized = false;
+
+ // Check if resizing is necessary
+ if (m_webcamFrame.Width != m_sourceControlBitmap.Width || m_webcamFrame.Height != m_sourceControlBitmap.Height)
+ {
+ resizedFrame = m_webcamFrame.Resize(new OpenCvSharp.Size(m_sourceControlBitmap.Width, m_sourceControlBitmap.Height));
+ resized = true;
+ }
+
+ var pixels = m_sourceControlBitmap.GetPixels(); // Get pixel data pointer
+ var pixelSpan = new Span((void*)pixels, m_sourceControlBitmap.ByteCount);
+ for (var y = 0; y < m_sourceControlBitmap.Height; y++)
+ {
+ for (var x = 0; x < m_sourceControlBitmap.Width; x++)
+ {
+ // Ranges from 0.0 (no desaturation) to 1.0 (full desaturation).
+ var desaturationAmount = 0.8;
+
+ // Get the pixel index
+ var pixelIndex = (y * m_sourceControlBitmap.Width + x) * 4;
+
+ // Read the original color
+ var origR = pixelSpan[pixelIndex];
+ var origG = pixelSpan[pixelIndex + 1];
+ var origB = pixelSpan[pixelIndex + 2];
+
+ // Read the webcam pixel data (RGB)
+ var camColor = resizedFrame.At(y, m_sourceControlBitmap.Width - x - 1);
+
+ // Convert webcam color to grayscale (luminance)
+ var camLuminance = 0.21 * camColor.Item2 + 0.72 * camColor.Item1 + 0.07 * camColor.Item0;
+
+ // Desaturate webcam color by blending the grayscale value with the original webcam color
+ var camR = (camColor.Item2 * (1.0 - desaturationAmount)) + (camLuminance * desaturationAmount);
+ var camG = (camColor.Item1 * (1.0 - desaturationAmount)) + (camLuminance * desaturationAmount);
+ var camB = (camColor.Item0 * (1.0 - desaturationAmount)) + (camLuminance * desaturationAmount);
+
+ // Blend the desaturated webcam color with the original image color
+ var reflectionIntensity = 0.025; // Adjust this value to control reflection strength (0.0 to 1.0)
+ var r = (byte)Math.Min(255, origR * (1 - reflectionIntensity) + camR * reflectionIntensity);
+ var g = (byte)Math.Min(255, origG * (1 - reflectionIntensity) + camG * reflectionIntensity);
+ var b = (byte)Math.Min(255, origB * (1 - reflectionIntensity) + camB * reflectionIntensity);
+
+ // Set new color in the span
+ pixelSpan[pixelIndex] = r;
+ pixelSpan[pixelIndex + 1] = g;
+ pixelSpan[pixelIndex + 2] = b;
+ }
+ }
+
+ // Dispose of the resized frame if it was created.
+ if (resized)
+ resizedFrame.Dispose();
+ }
private Size GetShaderSize() => m_controlSource?.Bounds.Size ?? new Size(512, 512);
@@ -162,8 +242,7 @@ protected override Size MeasureOverride(Size availableSize) =>
protected override Size ArrangeOverride(Size finalSize)
{
- var source = ShaderUri;
- if (source == null)
+ if (ShaderUri == null)
return new Size();
var sourceSize = GetShaderSize();
@@ -237,8 +316,12 @@ private void Start()
private void Stop() =>
m_customVisual?.SendHandlerMessage(new ShaderVisualHandler.DrawPayload(ShaderVisualHandler.Command.Stop));
- private void DisposeImpl() =>
+ private void DisposeImpl()
+ {
m_customVisual?.SendHandlerMessage(new ShaderVisualHandler.DrawPayload(ShaderVisualHandler.Command.Dispose));
+ m_webcamFrame?.Dispose();
+ m_videoCapture?.Dispose();
+ }
///
/// Apply new shader code.
@@ -269,7 +352,10 @@ private class ShaderVisualHandler : CompositionCustomVisualHandler
public ShaderVisualHandler(Dictionary> uniforms)
{
m_customUniforms = uniforms;
- m_customUniforms["iTime"] = () => new[] { (float)CompositionNow.TotalSeconds };
+ m_customUniforms["iTime"] = () => new[]
+ {
+ (float)CompositionNow.TotalSeconds
+ };
}
///