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 - + - - - - + + + + - - - - - - - - + + + + + + + + + - - - + + - + -