Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 53 additions & 56 deletions src/Extensions/CameraPreviewVisualizer.bonsai
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
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:p1="clr-namespace:;assembly=Extensions"
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">
<Workflow>
<Nodes>
Expand All @@ -30,7 +30,7 @@
<Property Name="Name" DisplayName="FrameSubject" />
</Expression>
<Expression xsi:type="SubscribeSubject">
<Name>Camera</Name>
<Name>Frame</Name>
</Expression>
<Expression xsi:type="imgui:TableBuilder">
<imgui:Visible>true</imgui:Visible>
Expand Down Expand Up @@ -68,7 +68,7 @@
</Expression>
<Expression xsi:type="Equal">
<Operand xsi:type="StringProperty">
<Value>SideCamera</Value>
<Value>Camera1</Value>
</Operand>
</Expression>
<Expression xsi:type="WorkflowOutput" />
Expand Down Expand Up @@ -105,27 +105,6 @@
<Expression xsi:type="MemberSelector">
<Selector>Value.Image</Selector>
</Expression>
<Expression xsi:type="SubscribeSubject">
<Name>OverlayAlpha</Name>
</Expression>
<Expression xsi:type="PropertyMapping">
<PropertyMappings>
<Property Name="Alpha" />
</PropertyMappings>
</Expression>
<Expression xsi:type="ExternalizedMapping">
<Property Name="MinSaturation" />
</Expression>
<Expression xsi:type="ExternalizedMapping">
<Property Name="MaxSaturation" />
</Expression>
<Expression xsi:type="Combinator">
<Combinator xsi:type="p1:SaturationThreshold">
<p1:MinSaturation>53</p1:MinSaturation>
<p1:MaxSaturation>215</p1:MaxSaturation>
<p1:Alpha>0.5</p1:Alpha>
</Combinator>
</Expression>
<Expression xsi:type="ExternalizedMapping">
<Property Name="Value" DisplayName="FlipState" />
</Expression>
Expand Down Expand Up @@ -223,8 +202,8 @@
</Expression>
<Expression xsi:type="Combinator">
<Combinator xsi:type="cv:CreatePoint2f">
<cv:X>320</cv:X>
<cv:Y>240</cv:Y>
<cv:X>720</cv:X>
<cv:Y>540</cv:Y>
</Combinator>
</Expression>
<Expression xsi:type="PropertyMapping">
Expand All @@ -238,8 +217,8 @@
<Expression xsi:type="Combinator">
<Combinator xsi:type="cv:AffineTransform">
<cv:Pivot>
<cv:X>320</cv:X>
<cv:Y>240</cv:Y>
<cv:X>720</cv:X>
<cv:Y>540</cv:Y>
</cv:Pivot>
<cv:Translation>
<cv:X>0</cv:X>
Expand Down Expand Up @@ -281,6 +260,25 @@
<imviz:MagFilter>Linear</imviz:MagFilter>
</Combinator>
</Expression>
<Expression xsi:type="SubscribeSubject">
<Name>OverlayAlpha</Name>
</Expression>
<Expression xsi:type="PropertyMapping">
<PropertyMappings>
<Property Name="Alpha" />
</PropertyMappings>
</Expression>
<Expression xsi:type="ExternalizedMapping">
<Property Name="MinSaturation" />
<Property Name="MaxSaturation" />
</Expression>
<Expression xsi:type="Combinator">
<Combinator xsi:type="p1:SaturationShaderPass">
<p1:Alpha>0.5</p1:Alpha>
<p1:MinSaturation>0.1</p1:MinSaturation>
<p1:MaxSaturation>0.3</p1:MaxSaturation>
</Combinator>
</Expression>
<Expression xsi:type="PropertyMapping">
<PropertyMappings>
<Property Name="Image" />
Expand Down Expand Up @@ -368,54 +366,53 @@
<Edge From="7" To="8" Label="Source1" />
<Edge From="8" To="9" Label="Source1" />
<Edge From="10" To="11" Label="Source1" />
<Edge From="11" To="43" Label="Source1" />
<Edge From="11" To="42" Label="Source1" />
<Edge From="12" To="14" Label="Source1" />
<Edge From="13" To="14" Label="Source2" />
<Edge From="14" To="15" Label="Source1" />
<Edge From="15" To="20" Label="Source1" />
<Edge From="15" To="18" Label="Source1" />
<Edge From="15" To="27" Label="Source1" />
<Edge From="16" To="17" Label="Source1" />
<Edge From="17" To="20" Label="Source2" />
<Edge From="18" To="20" Label="Source3" />
<Edge From="19" To="20" Label="Source4" />
<Edge From="17" To="18" Label="Source2" />
<Edge From="18" To="19" Label="Source1" />
<Edge From="18" To="24" Label="Source1" />
<Edge From="19" To="20" Label="Source1" />
<Edge From="19" To="21" Label="Source1" />
<Edge From="20" To="23" Label="Source1" />
<Edge From="20" To="32" Label="Source1" />
<Edge From="21" To="22" Label="Source1" />
<Edge From="22" To="23" Label="Source2" />
<Edge From="23" To="24" Label="Source1" />
<Edge From="23" To="29" Label="Source1" />
<Edge From="23" To="26" Label="Source1" />
<Edge From="24" To="25" Label="Source1" />
<Edge From="24" To="26" Label="Source1" />
<Edge From="25" To="28" Label="Source1" />
<Edge From="26" To="27" Label="Source1" />
<Edge From="27" To="28" Label="Source2" />
<Edge From="28" To="31" Label="Source1" />
<Edge From="25" To="26" Label="Source2" />
<Edge From="26" To="34" Label="Source1" />
<Edge From="27" To="28" Label="Source1" />
<Edge From="28" To="29" Label="Source1" />
<Edge From="29" To="30" Label="Source1" />
<Edge From="30" To="31" Label="Source2" />
<Edge From="31" To="39" Label="Source1" />
<Edge From="30" To="32" Label="Source1" />
<Edge From="31" To="32" Label="Source2" />
<Edge From="32" To="33" Label="Source1" />
<Edge From="33" To="34" Label="Source1" />
<Edge From="33" To="34" Label="Source2" />
<Edge From="34" To="35" Label="Source1" />
<Edge From="35" To="37" Label="Source1" />
<Edge From="36" To="37" Label="Source2" />
<Edge From="35" To="36" Label="Source1" />
<Edge From="36" To="40" Label="Source1" />
<Edge From="37" To="38" Label="Source1" />
<Edge From="38" To="39" Label="Source2" />
<Edge From="39" To="40" Label="Source1" />
<Edge From="38" To="40" Label="Source2" />
<Edge From="39" To="40" Label="Source3" />
<Edge From="40" To="41" Label="Source1" />
<Edge From="41" To="42" Label="Source1" />
<Edge From="42" To="43" Label="Source2" />
<Edge From="43" To="55" Label="Source1" />
<Edge From="41" To="42" Label="Source2" />
<Edge From="42" To="54" Label="Source1" />
<Edge From="43" To="44" Label="Source1" />
<Edge From="44" To="45" Label="Source1" />
<Edge From="45" To="46" Label="Source1" />
<Edge From="46" To="47" Label="Source1" />
<Edge From="47" To="48" Label="Source1" />
<Edge From="47" To="50" Label="Source1" />
<Edge From="48" To="49" Label="Source1" />
<Edge From="48" To="51" Label="Source1" />
<Edge From="49" To="50" Label="Source1" />
<Edge From="50" To="54" Label="Source1" />
<Edge From="49" To="53" Label="Source1" />
<Edge From="50" To="51" Label="Source1" />
<Edge From="51" To="52" Label="Source1" />
<Edge From="52" To="53" Label="Source1" />
<Edge From="52" To="53" Label="Source2" />
<Edge From="53" To="54" Label="Source2" />
<Edge From="54" To="55" Label="Source2" />
</Edges>
</Workflow>
</Expression>
Expand Down
207 changes: 207 additions & 0 deletions src/Extensions/SaturationShaderPass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
using Bonsai;
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Hexa.NET.ImGui;
using OpenTK.Graphics.OpenGL4;
using System.Runtime.Remoting.Contexts;


public class SaturationShaderPass : Combinator<ImTextureRef, ImTextureRef>
{
public float Alpha { get; set; }
public float MinSaturation {get; set;}
public float MaxSaturation {get; set;}

const string VertexShaderSource = @"
#version 330 core
layout(location = 0) in vec2 vertexPosition;
layout(location = 1) in vec2 vertexTexCoords;

out vec2 texCoord;

void main()
{
texCoord = vec2(.5, .5) * (vertexPosition.xy + 1);
gl_Position = vec4(vertexPosition.xy, 0.0, 1.0);
}
";

const string SaturationFragmentShader = @"
#version 330 core
uniform sampler2D tex0;
uniform vec2 iResolution;
uniform float minSaturation = 0.1;
uniform float maxSaturation = 0.9;
uniform float alpha = 0.5;
in vec2 texCoord;
out vec4 fragColor;

void main()
{
vec4 texColor = texture(tex0, texCoord);
vec4 overlay = vec4(0,0,0,0);

if (texColor.r >= maxSaturation) overlay = vec4(1,0,0,alpha);
else if (texColor.r <= minSaturation) overlay = vec4(0,0,1,alpha);

fragColor = mix(texColor, overlay, overlay.a);
}
";

static readonly float[] QuadVertices = new float[] { -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f };

public override IObservable<ImTextureRef> Process(IObservable<ImTextureRef> source)
{
return Observable.Defer(() =>
{
int shaderProgram = 0;
int vertexArray = 0;
int framebuffer = 0;
int targetTexture = 0;
int targetWidth = 0;
int targetHeight = 0;
var targetRef = default(ImTextureRef);
int alphaLocation = 0;
int minSaturation = 0;
int maxSaturation = 0;
return source.Select(texture =>
{
var currentContext = ImGui.GetCurrentContext();
var sourceTexture = (int)(ulong)texture.GetTexID();
if (sourceTexture == 0) return texture;

if (shaderProgram == 0)
{
shaderProgram = CreateProgram(VertexShaderSource, SaturationFragmentShader);
vertexArray = CreateQuad();
framebuffer = GL.GenFramebuffer();
alphaLocation = GL.GetUniformLocation(shaderProgram, "alpha");
maxSaturation = GL.GetUniformLocation(shaderProgram, "maxSaturation");
minSaturation = GL.GetUniformLocation(shaderProgram, "minSaturation");
}

// The pass renders into a texture of the same size as the incoming one.
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)
{
targetTexture = CreateTarget(targetTexture, width, height);
targetRef = CreateTextureRef(targetTexture);
targetWidth = width;
targetHeight = height;
GL.BindFramebuffer(FramebufferTarget.Framebuffer, framebuffer);
GL.FramebufferTexture2D(
FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0,
TextureTarget.Texture2D, targetTexture, 0);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
}

var viewport = new int[4];
GL.GetInteger(GetPName.Viewport, viewport);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, framebuffer);
GL.Viewport(0, 0, width, height);
GL.UseProgram(shaderProgram);

GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, sourceTexture);
GL.Uniform1(alphaLocation, Alpha);
GL.Uniform1(maxSaturation, MaxSaturation);
GL.Uniform1(minSaturation, MinSaturation);

GL.BindVertexArray(vertexArray);
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);

GL.BindVertexArray(0);
GL.UseProgram(0);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
GL.Viewport(viewport[0], viewport[1], viewport[2], viewport[3]);

return targetRef;
});
});
}

static int CreateQuad()
{
var vertexArray = GL.GenVertexArray();
GL.BindVertexArray(vertexArray);
GL.BindBuffer(BufferTarget.ArrayBuffer, GL.GenBuffer());
GL.BufferData(
BufferTarget.ArrayBuffer, QuadVertices.Length * sizeof(float),
QuadVertices, BufferUsageHint.StaticDraw);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0);
GL.BindVertexArray(0);
return vertexArray;
}

static int CreateTarget(int texture, int width, int height)
{
if (texture == 0) texture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, texture);
GL.TexImage2D(
TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
return texture;
}

static ImTextureRef CreateTextureRef(int texture)
{
var textureRef = default(ImTextureRef);
textureRef.TexID = new ImTextureID((IntPtr)texture);
return textureRef;
}

static int CreateProgram(string vertexCode, string fragmentCode)
{
var vertexShader = CompileShader(ShaderType.VertexShader, vertexCode);
var fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentCode);
int status;

var program = GL.CreateProgram();
GL.AttachShader(program, vertexShader);
GL.AttachShader(program, fragmentShader);
GL.LinkProgram(program);
GL.DetachShader(program, vertexShader);
GL.DetachShader(program, fragmentShader);
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out status);

if (status == 0)
{
var infoLog = GL.GetProgramInfoLog(program);
GL.DeleteProgram(program);
throw new InvalidOperationException(string.Format("Failed to link the shader program: {0}", infoLog));
}

GL.DeleteShader(vertexShader);
GL.DeleteShader(fragmentShader);

return program;
}

static int CompileShader(ShaderType type, string source)
{
int status;

var shader = GL.CreateShader(type);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
GL.GetShader(shader, ShaderParameter.CompileStatus, out status);

if (status == 0)
{
var infoLog = GL.GetShaderInfoLog(shader);
GL.DeleteShader(shader);
throw new InvalidOperationException(string.Format("Failed to compile the {0}.", infoLog));
}

return shader;
}
}
Loading