Skip to content

Commit

Permalink
nodes: add Selector filter
Browse files Browse the repository at this point in the history
  • Loading branch information
ubitux committed Jan 16, 2024
1 parent 3159755 commit 351f7d7
Show file tree
Hide file tree
Showing 13 changed files with 285 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Versioning](https://semver.org/spec/v2.0.0.html) for `libnopegl`.
providing a resolution independent blurriness parameter
- `forward_transforms` parameter to the `Texture` and `RenderToTexture` nodes
to enable forwarding of the camera/model transformations
- `Selector` filter to select colors according to their lightness, chroma or hue

### Fixed
- Moving the split position in `ngl-diff`
Expand Down
1 change: 1 addition & 0 deletions libnopegl/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ shaders = {
'filter_opacity.glsl': 'filter_opacity.h',
'filter_premult.glsl': 'filter_premult.h',
'filter_saturation.glsl': 'filter_saturation.h',
'filter_selector.glsl': 'filter_selector.h',
'filter_srgb2linear.glsl': 'filter_srgb2linear.h',
'hdr_hlg2sdr.frag': 'hdr_hlg2sdr_frag.h',
'hdr_pq2sdr.frag': 'hdr_pq2sdr_frag.h',
Expand Down
93 changes: 85 additions & 8 deletions libnopegl/nodes.specs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,40 @@
"desc": "standard uniform block memory layout 430"
}
],
"selector_component": [
{
"name": "lightness",
"desc": "lightness component from OkLCH (within [0,1])"
},
{
"name": "chroma",
"desc": "chroma component from OkLCH (infinite upper boundary, but in practice within [0,0.4])"
},
{
"name": "hue",
"desc": "hue component from OkLCH (circular value in radian)"
}
],
"selector_drop": [
{
"name": "outside",
"desc": "drop if value is outside the range"
},
{
"name": "inside",
"desc": "drop if value is inside the range"
}
],
"selector_output": [
{
"name": "colorholes",
"desc": "replace the selected colors with `(0,0,0,0)`"
},
{
"name": "binary",
"desc": "same as `colorholes` but non-selected colors become `(1,1,1,1)`"
}
],
"topology": [
{
"name": "point_list",
Expand Down Expand Up @@ -1809,6 +1843,49 @@
}
]
},
"FilterSelector": {
"file": "src/node_filters.c",
"params": [
{
"name": "range",
"type": "vec2",
"default": [0.000000,1.000000],
"flags": ["live", "node"],
"desc": "values within this range are selected"
},
{
"name": "component",
"type": "select",
"default": "lightness",
"choices": "selector_component",
"flags": [],
"desc": "reference component for the selector comparison"
},
{
"name": "drop_mode",
"type": "select",
"default": "outside",
"choices": "selector_drop",
"flags": [],
"desc": "define how to interpret the `range` selector"
},
{
"name": "output_mode",
"type": "select",
"default": "colorholes",
"choices": "selector_output",
"flags": [],
"desc": "define the output color"
},
{
"name": "smoothedges",
"type": "bool",
"default": 0,
"flags": ["live", "node"],
"desc": "make edges less sharp"
}
]
},
"FilterSRGB2Linear": {
"file": "src/node_filters.c",
"params": [
Expand Down Expand Up @@ -2690,7 +2767,7 @@
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSRGB2Linear"],
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
Expand Down Expand Up @@ -2731,7 +2808,7 @@
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSRGB2Linear"],
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
Expand Down Expand Up @@ -2815,7 +2892,7 @@
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSRGB2Linear"],
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
Expand Down Expand Up @@ -2905,7 +2982,7 @@
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSRGB2Linear"],
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
Expand Down Expand Up @@ -2947,7 +3024,7 @@
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSRGB2Linear"],
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
Expand Down Expand Up @@ -3031,7 +3108,7 @@
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSRGB2Linear"],
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
Expand Down Expand Up @@ -3154,7 +3231,7 @@
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSRGB2Linear"],
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
Expand Down Expand Up @@ -3242,7 +3319,7 @@
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSRGB2Linear"],
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
Expand Down
47 changes: 47 additions & 0 deletions libnopegl/src/glsl/filter_selector.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2024 Clément Bœsch <u pkh.me>
* Copyright 2024 Nope Forge
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

vec4 filter_selector(vec4 color, vec2 coords, vec2 range, int component, int drop_mode, int output_mode, int smoothedges)
{
vec3 rgb = ngli_srgb2linear(color.rgb);
vec3 lch = ngli_linear2oklch(rgb);
float value = lch[component];

bool within; // whether we are within the range or not
if (component == 2) {
// If we selected the hue, bring both value and reference in the same [0;𝜏] range
range = mod(range, ngli_tau);
value = mod(value, ngli_tau);
if (range.x > range.y) {
// The hue is circular, so sometimes happens across boundaries
within = value >= range.x || value <= range.y;
} else {
within = value >= range.x && value <= range.y;
}
} else {
within = value >= range.x && value <= range.y;
}
float alpha = bool(drop_mode) ^^ within ? 1.0 : 0.0;
if (smoothedges != 0)
alpha = ngli_aa(alpha);
return output_mode == 0 ? color * alpha : vec4(alpha);
}
101 changes: 101 additions & 0 deletions libnopegl/src/node_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "filter_opacity.h"
#include "filter_premult.h"
#include "filter_saturation.h"
#include "filter_selector.h"
#include "filter_srgb2linear.h"

struct filteralpha_opts {
Expand Down Expand Up @@ -112,6 +113,20 @@ struct filtersaturation_priv {
struct filter filter;
};

struct filterselector_opts {
struct ngl_node *range_node;
float range[2];
int component;
int drop_mode;
int output_mode;
struct ngl_node *smoothedges_node;
int smoothedges;
};

struct filterselector_priv {
struct filter filter;
};

struct filtersrgb2linear_priv {
struct filter filter;
};
Expand All @@ -127,6 +142,7 @@ NGLI_STATIC_ASSERT(filter_on_top_of_linear2srgb_priv, offsetof(struct filterli
NGLI_STATIC_ASSERT(filter_on_top_of_opacity_priv, offsetof(struct filteropacity_priv, filter) == 0);
NGLI_STATIC_ASSERT(filter_on_top_of_premult_priv, offsetof(struct filterpremult_priv, filter) == 0);
NGLI_STATIC_ASSERT(filter_on_top_of_saturation_priv, offsetof(struct filtersaturation_priv, filter) == 0);
NGLI_STATIC_ASSERT(filter_on_top_of_selector_priv, offsetof(struct filterselector_priv, filter) == 0);
NGLI_STATIC_ASSERT(filter_on_top_of_srgb2linear_priv, offsetof(struct filtersrgb2linear_priv, filter) == 0);

#define OFFSET(x) offsetof(struct filteralpha_opts, x)
Expand Down Expand Up @@ -191,6 +207,71 @@ static const struct node_param filtersaturation_params[] = {
};
#undef OFFSET

enum { /* values are explicit to help shader readibility */
SELECTOR_COMPONENT_LIGHTNESS = 0,
SELECTOR_COMPONENT_CHROMA = 1,
SELECTOR_COMPONENT_HUE = 2,
};

static const struct param_choices selector_component_choices = {
.name = "selector_component",
.consts = {
{"lightness", SELECTOR_COMPONENT_LIGHTNESS, .desc=NGLI_DOCSTRING("lightness component from OkLCH (within [0,1])")},
{"chroma", SELECTOR_COMPONENT_CHROMA, .desc=NGLI_DOCSTRING("chroma component from OkLCH (infinite upper boundary, but in practice within [0,0.4])")},
{"hue", SELECTOR_COMPONENT_HUE, .desc=NGLI_DOCSTRING("hue component from OkLCH (circular value in radian)")},
{NULL}
}
};

enum { /* values are explicit to help shader readibility */
SELECTOR_DROP_OUTSIDE = 0,
SELECTOR_DROP_INSIDE = 1,
};

static const struct param_choices selector_drop_choices = {
.name = "selector_drop",
.consts = {
{"outside", SELECTOR_DROP_OUTSIDE, .desc=NGLI_DOCSTRING("drop if value is outside the range")},
{"inside", SELECTOR_DROP_INSIDE, .desc=NGLI_DOCSTRING("drop if value is inside the range")},
{NULL}
}
};

enum { /* values are explicit to help shader readibility */
SELECTOR_OUTPUT_COLORHOLES = 0,
SELECTOR_OUTPUT_BINARY = 1,
};

static const struct param_choices selector_output_choices = {
.name = "selector_output",
.consts = {
{"colorholes", SELECTOR_OUTPUT_COLORHOLES, .desc=NGLI_DOCSTRING("replace the selected colors with `(0,0,0,0)`")},
{"binary", SELECTOR_OUTPUT_BINARY, .desc=NGLI_DOCSTRING("same as `colorholes` but non-selected colors become `(1,1,1,1)`")},
{NULL}
}
};

#define OFFSET(x) offsetof(struct filterselector_opts, x)
static const struct node_param filterselector_params[] = {
{"range", NGLI_PARAM_TYPE_VEC2, OFFSET(range_node), {.vec={0.f, 1.f}},
.flags=NGLI_PARAM_FLAG_ALLOW_LIVE_CHANGE | NGLI_PARAM_FLAG_ALLOW_NODE,
.desc=NGLI_DOCSTRING("values within this range are selected")},
{"component", NGLI_PARAM_TYPE_SELECT, OFFSET(component), {.i32=SELECTOR_COMPONENT_LIGHTNESS},
.choices=&selector_component_choices,
.desc=NGLI_DOCSTRING("reference component for the selector comparison")},
{"drop_mode", NGLI_PARAM_TYPE_SELECT, OFFSET(drop_mode), {.i32=SELECTOR_DROP_OUTSIDE},
.choices=&selector_drop_choices,
.desc=NGLI_DOCSTRING("define how to interpret the `range` selector")},
{"output_mode", NGLI_PARAM_TYPE_SELECT, OFFSET(output_mode), {.i32=SELECTOR_OUTPUT_COLORHOLES},
.choices=&selector_output_choices,
.desc=NGLI_DOCSTRING("define the output color")},
{"smoothedges", NGLI_PARAM_TYPE_BOOL, OFFSET(smoothedges_node), {.i32=0},
.flags=NGLI_PARAM_FLAG_ALLOW_LIVE_CHANGE | NGLI_PARAM_FLAG_ALLOW_NODE,
.desc=NGLI_DOCSTRING("make edges less sharp")},
{NULL}
};
#undef OFFSET

#define filtersrgb2linear_params NULL

static int register_resource(struct darray *resources, const char *name,
Expand Down Expand Up @@ -405,6 +486,25 @@ static int filtersaturation_init(struct ngl_node *node)
return register_resource(&s->filter.resources, "saturation", o->saturation_node, &o->saturation, NGLI_TYPE_F32);
}

static int filterselector_init(struct ngl_node *node)
{
int ret = filter_init(node);
if (ret < 0)
return ret;
struct filterselector_priv *s = node->priv_data;
struct filterselector_opts *o = node->opts;
s->filter.name = "selector";
s->filter.code = filter_selector_glsl;
s->filter.helpers = NGLI_FILTER_HELPER_MISC_UTILS | NGLI_FILTER_HELPER_SRGB | NGLI_FILTER_HELPER_OKLAB;
if ((ret = register_resource(&s->filter.resources, "range", o->range_node, &o->range, NGLI_TYPE_VEC2)) < 0 ||
(ret = register_resource(&s->filter.resources, "component", NULL, &o->component, NGLI_TYPE_I32)) < 0 ||
(ret = register_resource(&s->filter.resources, "drop_mode", NULL, &o->drop_mode, NGLI_TYPE_I32)) < 0 ||
(ret = register_resource(&s->filter.resources, "output_mode", NULL, &o->output_mode, NGLI_TYPE_I32)) < 0 ||
(ret = register_resource(&s->filter.resources, "smoothedges", o->smoothedges_node, &o->smoothedges, NGLI_TYPE_I32)) < 0)
return ret;
return 0;
}

static int filtersrgb2linear_init(struct ngl_node *node)
{
int ret = filter_init(node);
Expand Down Expand Up @@ -463,4 +563,5 @@ DECLARE_FILTER(linear2srgb, NGL_NODE_FILTERLINEAR2SRGB, 0,
DECLARE_FILTER(opacity, NGL_NODE_FILTEROPACITY, sizeof(struct filteropacity_opts), "FilterOpacity")
DECLARE_FILTER(premult, NGL_NODE_FILTERPREMULT, 0, "FilterPremult")
DECLARE_FILTER(saturation, NGL_NODE_FILTERSATURATION, sizeof(struct filtersaturation_opts), "FilterSaturation")
DECLARE_FILTER(selector, NGL_NODE_FILTERSELECTOR, sizeof(struct filterselector_opts), "FilterSelector")
DECLARE_FILTER(srgb2linear, NGL_NODE_FILTERSRGB2LINEAR, 0, "FilterSRGB2Linear")
1 change: 1 addition & 0 deletions libnopegl/src/node_renderother.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
NGL_NODE_FILTEROPACITY, \
NGL_NODE_FILTERPREMULT, \
NGL_NODE_FILTERSATURATION, \
NGL_NODE_FILTERSELECTOR, \
NGL_NODE_FILTERSRGB2LINEAR, \
NGLI_NODE_NONE}

Expand Down
1 change: 1 addition & 0 deletions libnopegl/src/nodes_register.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
action(NGL_NODE_FILTEROPACITY, ngli_filteropacity_class) \
action(NGL_NODE_FILTERPREMULT, ngli_filterpremult_class) \
action(NGL_NODE_FILTERSATURATION, ngli_filtersaturation_class) \
action(NGL_NODE_FILTERSELECTOR, ngli_filterselector_class) \
action(NGL_NODE_FILTERSRGB2LINEAR, ngli_filtersrgb2linear_class) \
action(NGL_NODE_FASTGAUSSIANBLUR, ngli_fgblur_class) \
action(NGL_NODE_GAUSSIANBLUR, ngli_gblur_class) \
Expand Down
1 change: 1 addition & 0 deletions libnopegl/src/nopegl.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ NGL_API void ngl_scene_unrefp(struct ngl_scene **sp);
#define NGL_NODE_FILTEROPACITY NGLI_FOURCC('F','o','p','a')
#define NGL_NODE_FILTERPREMULT NGLI_FOURCC('F','p','r','e')
#define NGL_NODE_FILTERSATURATION NGLI_FOURCC('F','s','a','t')
#define NGL_NODE_FILTERSELECTOR NGLI_FOURCC('F','S','e','l')
#define NGL_NODE_FILTERSRGB2LINEAR NGLI_FOURCC('F','l','i','n')
#define NGL_NODE_FASTGAUSSIANBLUR NGLI_FOURCC('F','G','B','l')
#define NGL_NODE_GAUSSIANBLUR NGLI_FOURCC('G','B','l','r')
Expand Down
Loading

0 comments on commit 351f7d7

Please sign in to comment.