diff --git a/src/Extensions/CameraPreviewVisualizer.bonsai b/src/Extensions/CameraPreviewVisualizer.bonsai
index c8d3fc5..fb53817 100644
--- a/src/Extensions/CameraPreviewVisualizer.bonsai
+++ b/src/Extensions/CameraPreviewVisualizer.bonsai
@@ -3,8 +3,6 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core"
xmlns:imgui="clr-namespace:Bonsai.ImGui;assembly=Bonsai.ImGui"
- xmlns:cv="clr-namespace:Bonsai.Vision;assembly=Bonsai.Vision"
- xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions"
xmlns:imviz="clr-namespace:Bonsai.ImGui.Visualizers;assembly=Bonsai.ImGui.Visualizers"
xmlns:p1="clr-namespace:;assembly=Extensions"
xmlns="https://bonsai-rx.org/2018/workflow">
@@ -18,9 +16,10 @@
-
+
+
CameraPreview
@@ -68,7 +67,7 @@
- BodyCamera
+ Camera1
@@ -105,149 +104,6 @@
Value.Image
-
-
-
-
-
- 2
-
-
-
-
-
-
- Flipped
-
-
-
- Source1
-
-
- Item2
-
-
-
- 2
-
-
-
-
-
-
-
-
-
-
-
-
- Item1
-
-
- Item2
-
-
-
-
-
-
-
-
- Vertical
-
-
-
- NotFlipped
-
-
-
- Source1
-
-
- Item2
-
-
-
- 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Item1
-
-
-
-
-
- new (
- it.Size.Width / 2 as X,
- it.Size.Height / 2 as Y
-)
-
-
-
-
-
-
-
-
-
- 320
- 240
-
-
-
-
-
-
-
-
-
-
-
-
-
- 320
- 240
-
-
- 0
- 0
-
- 0
-
- 1
- 1
-
-
-
-
-
-
-
-
-
-
- 1,0,0;0,1,0
- Linear
-
- 0
- 0
- 0
- 0
-
-
-
Table
@@ -271,12 +127,18 @@
+
+
+
0.5
0.1
- 0.9
+ 0.3
+ 0.1
+ false
+ false
@@ -366,53 +228,31 @@
-
+
-
-
+
-
+
-
-
-
-
+
+
-
+
-
-
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/Extensions/SaturationShaderPass.cs b/src/Extensions/SaturationShaderPass.cs
index 770896c..b9899f2 100644
--- a/src/Extensions/SaturationShaderPass.cs
+++ b/src/Extensions/SaturationShaderPass.cs
@@ -15,6 +15,15 @@ public class SaturationShaderPass : Combinator
public float MinSaturation {get; set;}
public float MaxSaturation {get; set;}
+ [Description("Clockwise rotation applied to the image, in radians.")]
+ public float Rotation { get; set; }
+
+ [Description("Mirrors the image left to right, about its vertical axis.")]
+ public bool FlipHorizontal { get; set; }
+
+ [Description("Mirrors the image top to bottom, about its horizontal axis.")]
+ public bool FlipVertical { get; set; }
+
const string VertexShaderSource = @"
#version 330 core
layout(location = 0) in vec2 vertexPosition;
@@ -36,12 +45,43 @@ void main()
uniform float minSaturation = 0.1;
uniform float maxSaturation = 0.9;
uniform float alpha = 0.5;
+ uniform float rotation = 0.0;
+ uniform vec2 sourceSize;
+ uniform vec2 outputSize;
+ uniform vec2 flip = vec2(0.0, 0.0);
in vec2 texCoord;
out vec4 fragColor;
+ // Maps a coordinate in the rotated output back into the source texture by
+ // applying the inverse transform about the centre of the image. The rotation
+ // offsets are in pixels rather than normalised units, so a non-square image is
+ // rotated rigidly instead of being skewed by its aspect ratio. The forward
+ // transform mirrors the image before rotating it, so the inverse undoes the
+ // rotation first and the mirroring second. Each component of `flip` is 1 to
+ // mirror that axis and 0 to leave it alone; mirroring maps the unit square onto
+ // itself, so it does not disturb the coverage test in main().
+ vec2 sourceCoord(vec2 uv)
+ {
+ vec2 offset = (uv - 0.5) * outputSize;
+ float c = cos(rotation);
+ float s = sin(rotation);
+ vec2 rotated = vec2(c * offset.x + s * offset.y, c * offset.y - s * offset.x);
+ vec2 sourceUv = rotated / sourceSize + 0.5;
+ return mix(sourceUv, 1.0 - sourceUv, flip);
+ }
+
void main()
{
- vec4 texColor = texture(tex0, texCoord);
+ vec2 sourceUv = sourceCoord(texCoord);
+
+ // The rotated image does not cover the whole output; leave the rest clear.
+ if (any(lessThan(sourceUv, vec2(0.0))) || any(greaterThan(sourceUv, vec2(1.0))))
+ {
+ fragColor = vec4(0,0,0,0);
+ return;
+ }
+
+ vec4 texColor = texture(tex0, sourceUv);
vec4 overlay = vec4(0,0,0,0);
if (texColor.r >= maxSaturation) overlay = vec4(1,0,0,alpha);
@@ -67,6 +107,10 @@ public override IObservable Process(IObservable sour
int alphaLocation = 0;
int minSaturation = 0;
int maxSaturation = 0;
+ int rotationLocation = 0;
+ int sourceSizeLocation = 0;
+ int outputSizeLocation = 0;
+ int flipLocation = 0;
return source.Select(texture =>
{
var currentContext = ImGui.GetCurrentContext();
@@ -81,19 +125,30 @@ public override IObservable Process(IObservable sour
alphaLocation = GL.GetUniformLocation(shaderProgram, "alpha");
maxSaturation = GL.GetUniformLocation(shaderProgram, "maxSaturation");
minSaturation = GL.GetUniformLocation(shaderProgram, "minSaturation");
+ rotationLocation = GL.GetUniformLocation(shaderProgram, "rotation");
+ sourceSizeLocation = GL.GetUniformLocation(shaderProgram, "sourceSize");
+ outputSizeLocation = GL.GetUniformLocation(shaderProgram, "outputSize");
+ flipLocation = GL.GetUniformLocation(shaderProgram, "flip");
}
- // The pass renders into a texture of the same size as the incoming one.
+ // The pass renders into a texture large enough to hold the bounding box
+ // of the rotated image, so no part of the source is cropped away.
int width, height;
GL.BindTexture(TextureTarget.Texture2D, sourceTexture);
GL.GetTexLevelParameter(TextureTarget.Texture2D, 0, GetTextureParameter.TextureWidth, out width);
GL.GetTexLevelParameter(TextureTarget.Texture2D, 0, GetTextureParameter.TextureHeight, out height);
- if (width != targetWidth || height != targetHeight)
+
+ var rotation = Rotation;
+ var cos = Math.Abs(Math.Cos(rotation));
+ var sin = Math.Abs(Math.Sin(rotation));
+ var outputWidth = Math.Max(1, (int)Math.Round(width * cos + height * sin));
+ var outputHeight = Math.Max(1, (int)Math.Round(width * sin + height * cos));
+ if (outputWidth != targetWidth || outputHeight != targetHeight)
{
- targetTexture = CreateTarget(targetTexture, width, height);
+ targetTexture = CreateTarget(targetTexture, outputWidth, outputHeight);
targetRef = CreateTextureRef(targetTexture);
- targetWidth = width;
- targetHeight = height;
+ targetWidth = outputWidth;
+ targetHeight = outputHeight;
GL.BindFramebuffer(FramebufferTarget.Framebuffer, framebuffer);
GL.FramebufferTexture2D(
FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0,
@@ -103,8 +158,10 @@ public override IObservable Process(IObservable sour
var viewport = new int[4];
GL.GetInteger(GetPName.Viewport, viewport);
+ var blendEnabled = GL.IsEnabled(EnableCap.Blend);
+ GL.Disable(EnableCap.Blend);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, framebuffer);
- GL.Viewport(0, 0, width, height);
+ GL.Viewport(0, 0, outputWidth, outputHeight);
GL.UseProgram(shaderProgram);
GL.ActiveTexture(TextureUnit.Texture0);
@@ -112,6 +169,10 @@ public override IObservable Process(IObservable sour
GL.Uniform1(alphaLocation, Alpha);
GL.Uniform1(maxSaturation, MaxSaturation);
GL.Uniform1(minSaturation, MinSaturation);
+ GL.Uniform1(rotationLocation, rotation);
+ GL.Uniform2(sourceSizeLocation, (float)width, (float)height);
+ GL.Uniform2(outputSizeLocation, (float)outputWidth, (float)outputHeight);
+ GL.Uniform2(flipLocation, FlipHorizontal ? 1f : 0f, FlipVertical ? 1f : 0f);
GL.BindVertexArray(vertexArray);
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
@@ -120,6 +181,7 @@ public override IObservable Process(IObservable sour
GL.UseProgram(0);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
GL.Viewport(viewport[0], viewport[1], viewport[2], viewport[3]);
+ if (blendEnabled) GL.Enable(EnableCap.Blend);
return targetRef;
});
diff --git a/src/test_visualizer.bonsai b/src/test_visualizer.bonsai
index 80d9447..41f6d21 100644
--- a/src/test_visualizer.bonsai
+++ b/src/test_visualizer.bonsai
@@ -370,7 +370,7 @@
- PT1.756S
+ PT1.537S
@@ -422,7 +422,7 @@
- PT1.826S
+ PT0.079S
@@ -493,7 +493,7 @@
- PT0.735S
+ PT2.62S
@@ -1013,7 +1013,7 @@ it.Index as TrialIndex
true
- false
+ true
@@ -1039,7 +1039,7 @@ it.Index as TrialIndex
- PT0.377S
+ PT0.974S
@@ -1731,9 +1731,9 @@ it.Index as TrialIndex
true
- 0.22
- 196
- 0.21708835946260363
+ -0.04
+ 3
+ -0.037661941925837626
0
-10
@@ -1997,76 +1997,6 @@ it.Index as TrialIndex
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Break
200
@@ -2125,9 +2055,10 @@ it.Index as TrialIndex
-
+
+
CameraPreview
@@ -2212,149 +2143,6 @@ it.Index as TrialIndex
Value
-
-
-
-
-
- 2
-
-
-
-
-
-
- Flipped
-
-
-
- Source1
-
-
- Item2
-
-
-
- 2
-
-
-
-
-
-
-
-
-
-
-
-
- Item1
-
-
- Item2
-
-
-
-
-
-
-
-
- Vertical
-
-
-
- NotFlipped
-
-
-
- Source1
-
-
- Item2
-
-
-
- 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Item1
-
-
-
-
-
- new (
- it.Size.Width / 2 as X,
- it.Size.Height / 2 as Y
-)
-
-
-
-
-
-
-
-
-
- 320
- 240
-
-
-
-
-
-
-
-
-
-
-
-
-
- 320
- 240
-
-
- 0
- 0
-
- 0
-
- 1
- 1
-
-
-
-
-
-
-
-
-
-
- 1,0,0;0,1,0
- Linear
-
- 0
- 0
- 0
- 0
-
-
-
Table
@@ -2378,12 +2166,18 @@ it.Index as TrialIndex
+
+
+
- 0.371
+ 0.5
0.1
0.3
+ 0.1
+ false
+ false
@@ -2473,53 +2267,31 @@ it.Index as TrialIndex
-
+
-
-
+
-
+
-
-
-
-
+
+
-
+
-
-
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -2739,9 +2511,6 @@ it.Index as TrialIndex
-
-
-
@@ -2799,12 +2568,16 @@ it.Index as TrialIndex
+
0.5
0.1
0.3
+ 0
+ false
+ false
@@ -2894,7 +2667,7 @@ it.Index as TrialIndex
-
+
@@ -2912,35 +2685,34 @@ it.Index as TrialIndex
-
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
-
+
-