Additional shaders
Some checks failed
CI / Native Windows Build And Tests (push) Has been cancelled
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-06 00:23:20 +10:00
parent cf31c91831
commit 437199f3f0
9 changed files with 349 additions and 39 deletions

View File

@@ -234,12 +234,11 @@ If your Windows runner stores the Blackmagic SDK outside the repo, configure `GP
Audio Audio
improve text rendering improve text rendering
genlock genlock
find a better UI libary
Logs Logs
anamorphic desqueeze anamorphic desqueeze
solid color layer
refactor, cleanup of source files refactor, cleanup of source files
display URL (Maybe clicakable) for control in the windows app (Not on the output) display URL (Maybe clicakable) for control in the windows app (Not on the output)
Sound shader as seperate .slang in shader package? Sound shader as seperate .slang in shader package?
runtime date time UTC and offset from PCs internal clock runtime date time UTC and offset from PCs internal clock
Add a value control to the color wheels
![alt text](image.png) ![alt text](image.png)

View File

@@ -0,0 +1,46 @@
{
"id": "anamorphic-desqueeze",
"name": "Anamorphic Desqueeze",
"description": "Desqueezes anamorphic footage by 1.3x, 1.33x, 1.5x, or 2x with fit or fill framing.",
"category": "Transform",
"entryPoint": "shadeVideo",
"parameters": [
{
"id": "desqueezeFactor",
"label": "Desqueeze",
"type": "enum",
"default": "x1_33",
"options": [
{ "value": "x1_3", "label": "1.3x" },
{ "value": "x1_33", "label": "1.33x" },
{ "value": "x1_5", "label": "1.5x" },
{ "value": "x2_0", "label": "2x" }
]
},
{
"id": "framing",
"label": "Framing",
"type": "enum",
"default": "fit",
"options": [
{ "value": "fit", "label": "Fit" },
{ "value": "fill", "label": "Fill" }
]
},
{
"id": "pan",
"label": "Pan",
"type": "vec2",
"default": [0.0, 0.0],
"min": [-1.0, -1.0],
"max": [1.0, 1.0],
"step": [0.001, 0.001]
},
{
"id": "outsideColor",
"label": "Outside Color",
"type": "color",
"default": [0.0, 0.0, 0.0, 1.0]
}
]
}

View File

@@ -0,0 +1,32 @@
float selectedDesqueezeFactor()
{
if (desqueezeFactor == 0)
return 1.3;
if (desqueezeFactor == 1)
return 1.3333333;
if (desqueezeFactor == 2)
return 1.5;
return 2.0;
}
float4 shadeVideo(ShaderContext context)
{
float factor = selectedDesqueezeFactor();
float2 centered = context.uv - 0.5;
if (framing == 0)
{
centered.y *= factor;
}
else
{
centered.x /= factor;
}
float2 sourceUv = centered + 0.5 - pan;
bool inside = sourceUv.x >= 0.0 && sourceUv.x <= 1.0 && sourceUv.y >= 0.0 && sourceUv.y <= 1.0;
if (!inside)
return outsideColor;
return sampleVideo(sourceUv);
}

View File

@@ -0,0 +1,8 @@
{
"id": "smpte-color-bars",
"name": "SMPTE Color Bars",
"description": "Generates a procedural SMPTE RP 219-style 16:9 color bar test pattern matching the common Wikimedia 1920x1080 reference layout.",
"category": "Calibration",
"entryPoint": "shadeVideo",
"parameters": []
}

View File

@@ -0,0 +1,90 @@
float3 hexColor(float r, float g, float b)
{
return float3(r, g, b) / 255.0;
}
float3 smpteTop(float x)
{
if (x < 240.0)
return hexColor(102.0, 102.0, 102.0);
if (x < 445.0)
return hexColor(191.0, 191.0, 191.0);
if (x < 651.0)
return hexColor(191.0, 191.0, 0.0);
if (x < 857.0)
return hexColor(0.0, 191.0, 191.0);
if (x < 1063.0)
return hexColor(0.0, 191.0, 0.0);
if (x < 1269.0)
return hexColor(191.0, 0.0, 191.0);
if (x < 1475.0)
return hexColor(191.0, 0.0, 0.0);
if (x < 1680.0)
return hexColor(0.0, 0.0, 191.0);
return hexColor(102.0, 102.0, 102.0);
}
float3 smpteMiddleA(float x)
{
if (x < 240.0)
return hexColor(0.0, 255.0, 255.0);
if (x < 445.0)
return hexColor(0.0, 63.0, 105.0);
if (x < 1680.0)
return hexColor(191.0, 191.0, 191.0);
return hexColor(0.0, 0.0, 255.0);
}
float3 smpteMiddleB(float x)
{
if (x < 240.0)
return hexColor(255.0, 255.0, 0.0);
if (x < 445.0)
return hexColor(65.0, 0.0, 119.0);
if (x < 1475.0)
{
float ramp = saturate((x - 445.0) / (1475.0 - 445.0));
return float3(ramp, ramp, ramp);
}
if (x < 1680.0)
return float3(1.0, 1.0, 1.0);
return hexColor(255.0, 0.0, 0.0);
}
float3 smpteBottom(float x)
{
if (x < 240.0)
return hexColor(38.0, 38.0, 38.0);
if (x < 549.0)
return float3(0.0, 0.0, 0.0);
if (x < 960.0)
return float3(1.0, 1.0, 1.0);
if (x < 1268.0)
return float3(0.0, 0.0, 0.0);
if (x < 1337.0)
return hexColor(5.0, 5.0, 5.0);
if (x < 1405.0)
return float3(0.0, 0.0, 0.0);
if (x < 1474.0)
return hexColor(10.0, 10.0, 10.0);
if (x < 1680.0)
return float3(0.0, 0.0, 0.0);
return hexColor(38.0, 38.0, 38.0);
}
float4 shadeVideo(ShaderContext context)
{
float2 uv = saturate(context.uv);
float2 pixel = float2(uv.x, 1.0 - uv.y) * float2(1920.0, 1080.0);
if (pixel.y < 630.0)
return float4(smpteTop(pixel.x), 1.0);
if (pixel.y < 720.0)
return float4(smpteMiddleA(pixel.x), 1.0);
if (pixel.y < 810.0)
return float4(smpteMiddleB(pixel.x), 1.0);
return float4(smpteBottom(pixel.x), 1.0);
}

View File

@@ -0,0 +1,15 @@
{
"id": "solid-color",
"name": "Solid Color",
"description": "Fills the frame with a single user-selected color.",
"category": "Color",
"entryPoint": "shadeVideo",
"parameters": [
{
"id": "fillColor",
"label": "Fill",
"type": "color",
"default": [1.0, 1.0, 1.0, 1.0]
}
]
}

View File

@@ -0,0 +1,4 @@
float4 shadeVideo(ShaderContext context)
{
return saturate(fillColor);
}

View File

@@ -190,11 +190,15 @@ export function ParameterField({ layer, parameter, onParameterChange }) {
while (values.length < 4) { while (values.length < 4) {
values.push(values.length === 3 ? 1 : 0); values.push(values.length === 3 ? 1 : 0);
} }
const hsva = colorValueToHsva(values);
const wheelHsva = { ...hsva, v: 100 };
const sendHsva = (nextHsva) => scheduleSendValue(hsvaToColorValue(nextHsva, values[3]));
return ( return (
<section className="parameter"> <section className="parameter">
{header} {header}
<div className="parameter__wheel-row"> <div className="parameter__wheel-row">
<div className="parameter__color-stack">
<div <div
className="parameter__wheel" className="parameter__wheel"
onPointerDown={beginInteraction} onPointerDown={beginInteraction}
@@ -203,12 +207,34 @@ export function ParameterField({ layer, parameter, onParameterChange }) {
onBlur={endInteraction} onBlur={endInteraction}
> >
<Wheel <Wheel
color={colorValueToHsva(values)} color={wheelHsva}
width={132} width={196}
height={132} height={196}
onChange={(color) => scheduleSendValue(hsvaToColorValue(color.hsva, values[3]))} onChange={(color) => sendHsva({ ...color.hsva, v: hsva.v })}
/> />
</div> </div>
<label className="parameter__value-slider">
<input
type="range"
min={0}
max={100}
step={1}
value={Math.round(hsva.v)}
aria-label={`${parameter.label} value`}
onMouseDown={beginInteraction}
onPointerDown={beginInteraction}
onTouchStart={beginInteraction}
onChange={(event) => sendHsva({ ...hsva, v: Number(event.target.value) })}
onMouseUp={endInteraction}
onTouchEnd={endInteraction}
onPointerUp={endInteraction}
onKeyDown={beginInteraction}
onKeyUp={endInteraction}
onBlur={endInteraction}
/>
</label>
</div>
<div className="parameter__color-bottom">
<label className="parameter__alpha"> <label className="parameter__alpha">
<span>Alpha</span> <span>Alpha</span>
<input <input
@@ -228,6 +254,7 @@ export function ParameterField({ layer, parameter, onParameterChange }) {
</label> </label>
<div className="parameter__swatch" style={{ background: colorValueToHex(values) }} aria-hidden="true" /> <div className="parameter__swatch" style={{ background: colorValueToHex(values) }} aria-hidden="true" />
</div> </div>
</div>
<ParameterValueDisplay parameterType={parameter.type} value={appliedValue} pending={isPending} /> <ParameterValueDisplay parameterType={parameter.type} value={appliedValue} pending={isPending} />
</section> </section>
); );

View File

@@ -858,14 +858,23 @@ pre {
.parameter__wheel-row { .parameter__wheel-row {
display: grid; display: grid;
grid-template-columns: auto minmax(5.25rem, 1fr); grid-template-columns: minmax(0, 196px);
gap: 0.5rem; gap: 0.625rem;
align-items: start; align-items: start;
justify-content: center;
}
.parameter__color-stack {
display: grid;
gap: 0.625rem;
width: 196px;
} }
.parameter__wheel { .parameter__wheel {
width: 132px; width: 196px;
height: 132px; height: 196px;
overflow: hidden;
border-radius: 50%;
} }
.parameter__wheel [class*="react-colorful"], .parameter__wheel [class*="react-colorful"],
@@ -873,14 +882,87 @@ pre {
max-width: 100%; max-width: 100%;
} }
.parameter__wheel svg,
.parameter__wheel canvas {
display: block;
}
.parameter__color-bottom {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(4.5rem, 0.85fr);
gap: 0.625rem;
align-items: end;
width: 196px;
}
.parameter__swatch { .parameter__swatch {
grid-column: 2;
width: 100%; width: 100%;
min-height: 28px; min-height: 38px;
border: 1px solid var(--app-border); border: 1px solid var(--app-border);
border-radius: var(--app-radius-sm); border-radius: var(--app-radius-sm);
} }
.parameter__value-slider {
display: block;
width: 196px;
}
.parameter__value-slider input[type="range"] {
--value-thumb-size: 18px;
display: block;
width: 196px;
height: 22px;
margin: 0;
accent-color: #f2f6fb;
background: linear-gradient(90deg, #000 0%, #fff 100%);
border-radius: 999px;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
}
.parameter__value-slider input[type="range"]::-webkit-slider-runnable-track {
height: 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.28);
border-radius: 999px;
background: linear-gradient(90deg, #000 0%, #fff 100%);
}
.parameter__value-slider input[type="range"]::-webkit-slider-thumb {
width: var(--value-thumb-size);
height: var(--value-thumb-size);
margin-top: calc((0.75rem - var(--value-thumb-size)) / 2);
border: 2px solid #f7fbff;
border-radius: 999px;
background: #f7fbff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.65), 0 1px 4px rgba(0, 0, 0, 0.45);
-webkit-appearance: none;
}
.parameter__value-slider input[type="range"]::-moz-range-track {
height: 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.28);
border-radius: 999px;
background: linear-gradient(90deg, #000 0%, #fff 100%);
}
.parameter__value-slider input[type="range"]::-moz-range-thumb {
width: var(--value-thumb-size);
height: var(--value-thumb-size);
border: 2px solid #f7fbff;
border-radius: 999px;
background: #f7fbff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.65), 0 1px 4px rgba(0, 0, 0, 0.45);
}
.parameter__value-slider strong {
text-align: right;
min-height: 1rem;
color: var(--app-text);
font-size: 0.74rem;
line-height: 1;
}
.parameter__alpha { .parameter__alpha {
display: grid; display: grid;
gap: 0.25rem; gap: 0.25rem;
@@ -975,6 +1057,13 @@ pre {
grid-column: auto; grid-column: auto;
} }
.parameter__color-stack,
.parameter__color-bottom,
.parameter__value-slider,
.parameter__value-slider input[type="range"] {
width: 100%;
}
.kv-row { .kv-row {
grid-template-columns: minmax(6.25rem, 0.6fr) minmax(0, 1fr); grid-template-columns: minmax(6.25rem, 0.6fr) minmax(0, 1fr);
} }