Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nodes: add DrawMask node #252

Merged
merged 1 commit into from
Apr 11, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Versioning](https://semver.org/spec/v2.0.0.html) for `libnopegl`.
scene
- `TextEffect.anchor` and `TextEffect.anchor_ref` to control the character
relative anchor for scale and rotate transforms
- `DrawMask` node to facilitate alpha masking with textures

### Changed
- `Text.font_files` text-based parameter is replaced with `Text.font_faces` node
Expand Down
2 changes: 2 additions & 0 deletions libnopegl/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,8 @@ shaders = {
'source_gradient4.vert': 'source_gradient4_vert.h',
'source_histogram.frag': 'source_histogram_frag.h',
'source_histogram.vert': 'source_histogram_vert.h',
'source_mask.frag': 'source_mask_frag.h',
'source_mask.vert': 'source_mask_vert.h',
'source_noise.frag': 'source_noise_frag.h',
'source_noise.vert': 'source_noise_vert.h',
'source_texture.frag': 'source_texture_frag.h',
Expand Down
48 changes: 48 additions & 0 deletions libnopegl/nodes.specs
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +2179,54 @@
}
]
},
"DrawMask": {
"file": "src/node_drawother.c",
"params": [
{
"name": "content",
"type": "node",
"node_types": ["Rotate", "RotateQuat", "Transform", "Translate", "Scale", "Skew", "Texture2D"],
"flags": ["nonull"],
"desc": "content texture being masked"
},
{
"name": "mask",
"type": "node",
"node_types": ["Rotate", "RotateQuat", "Transform", "Translate", "Scale", "Skew", "Texture2D"],
"flags": ["nonull"],
"desc": "texture serving as mask (only the red channel is used)"
},
{
"name": "inverted",
"type": "bool",
"default": 0,
"flags": [],
"desc": "whether to dig into or keep"
},
{
"name": "blending",
"type": "select",
"default": "default",
"choices": "blend_preset",
"flags": [],
"desc": "define how this node and the current frame buffer are blending together"
},
{
"name": "geometry",
"type": "node",
"node_types": ["Circle", "Geometry", "Quad", "Triangle"],
"flags": [],
"desc": "geometry to be rasterized"
},
{
"name": "filters",
"type": "node_list",
"node_types": ["FilterAlpha", "FilterColorMap", "FilterContrast", "FilterExposure", "FilterInverseAlpha", "FilterLinear2sRGB", "FilterOpacity", "FilterPremult", "FilterSaturation", "FilterSelector", "FilterSRGB2Linear"],
"flags": [],
"desc": "filter chain to apply on top of this source"
}
]
},
"DrawNoise": {
"file": "src/node_drawother.c",
"params": [
Expand Down
28 changes: 28 additions & 0 deletions libnopegl/src/glsl/source_mask.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 source_mask()
{
float mval = ngl_texvideo(mask, mask_coord).r;
float alpha = mix(mval, 1.0 - mval, float(inverted));
return ngl_texvideo(content, content_coord) * alpha;
}
29 changes: 29 additions & 0 deletions libnopegl/src/glsl/source_mask.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.
*/

void main()
{
ngl_out_pos = projection_matrix * modelview_matrix * vec4(position, 1.0);
uv = uvcoord;
content_coord = (content_coord_matrix * vec4(uvcoord, 0.0, 1.0)).xy;
mask_coord = (mask_coord_matrix * vec4(uvcoord, 0.0, 1.0)).xy;
}
135 changes: 134 additions & 1 deletion libnopegl/src/node_drawother.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Copyright 2023 Nope Forge
* Copyright 2021-2022 GoPro Inc.
* Copyright 2021-2022 Clément Bœsch <u pkh.me>
* Copyright 2021-2024 Clément Bœsch <u pkh.me>
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
Expand Down Expand Up @@ -50,6 +50,8 @@
#include "source_gradient4_vert.h"
#include "source_histogram_frag.h"
#include "source_histogram_vert.h"
#include "source_mask_frag.h"
#include "source_mask_vert.h"
#include "source_noise_frag.h"
#include "source_noise_vert.h"
#include "source_texture_frag.h"
Expand Down Expand Up @@ -212,6 +214,17 @@ struct drawhistogram_priv {
struct render_common common;
};

struct drawmask_opts {
struct ngl_node *content;
struct ngl_node *mask;
int inverted;
struct render_common_opts common;
};

struct drawmask_priv {
struct render_common common;
};

struct drawtexture_opts {
struct ngl_node *texture_node;
struct render_common_opts common;
Expand Down Expand Up @@ -399,6 +412,23 @@ static const struct node_param drawhistogram_params[] = {
};
#undef OFFSET

#define OFFSET(x) offsetof(struct drawmask_opts, x)
static const struct node_param drawmask_params[] = {
{"content", NGLI_PARAM_TYPE_NODE, OFFSET(content),
.flags=NGLI_PARAM_FLAG_NON_NULL,
.node_types=(const uint32_t[]){TRANSFORM_TYPES_ARGS, NGL_NODE_TEXTURE2D, NGLI_NODE_NONE},
.desc=NGLI_DOCSTRING("content texture being masked")},
{"mask", NGLI_PARAM_TYPE_NODE, OFFSET(mask),
.flags=NGLI_PARAM_FLAG_NON_NULL,
.node_types=(const uint32_t[]){TRANSFORM_TYPES_ARGS, NGL_NODE_TEXTURE2D, NGLI_NODE_NONE},
.desc=NGLI_DOCSTRING("texture serving as mask (only the red channel is used)")},
{"inverted", NGLI_PARAM_TYPE_BOOL, OFFSET(inverted),
.desc=NGLI_DOCSTRING("whether to dig into or keep")},
COMMON_PARAMS
{NULL}
};
#undef OFFSET

#define NOISE_TYPE_BLOCKY 0
#define NOISE_TYPE_PERLIN 1

Expand Down Expand Up @@ -671,6 +701,31 @@ static int drawhistogram_init(struct ngl_node *node)
return init(node, &s->common, &o->common, "source_histogram", source_histogram_frag);
}

static int drawmask_init(struct ngl_node *node)
{
struct drawmask_priv *s = node->priv_data;
struct drawmask_opts *o = node->opts;

const struct ngl_node *content = ngli_transform_get_leaf_node(o->content);
if (!content) {
LOG(ERROR, "no content texture found at the end of the transform chain");
return NGL_ERROR_INVALID_USAGE;
}

const struct ngl_node *mask = ngli_transform_get_leaf_node(o->mask);
if (!mask) {
LOG(ERROR, "no mask texture found at the end of the transform chain");
return NGL_ERROR_INVALID_USAGE;
}

ngli_darray_init(&s->common.draw_resources, sizeof(struct ngl_node *), 0);
if (!ngli_darray_push(&s->common.draw_resources, &content) ||
!ngli_darray_push(&s->common.draw_resources, &mask))
return NGL_ERROR_MEMORY;

return init(node, &s->common, &o->common, "source_mask", source_mask_frag);
}

static int drawnoise_init(struct ngl_node *node)
{
struct drawnoise_priv *s = node->priv_data;
Expand Down Expand Up @@ -1110,6 +1165,83 @@ static int drawhistogram_prepare(struct ngl_node *node)
return 0;
}

static int drawmask_prepare(struct ngl_node *node)
{
struct ngl_ctx *ctx = node->ctx;
struct drawmask_priv *s = node->priv_data;
struct drawmask_opts *o = node->opts;

const struct pgcraft_uniform uniforms[] = {
{.name="inverted", .type=NGLI_TYPE_BOOL, .stage=NGLI_PROGRAM_SHADER_FRAG, .data=&o->inverted},
};

struct render_common *c = &s->common;
int ret = init_desc(node, c, uniforms, NGLI_ARRAY_NB(uniforms));
if (ret < 0)
return ret;

struct texture_priv *content_priv = o->content->priv_data;
const struct texture_opts *content_opts = o->content->opts;
struct texture_priv *mask_priv = o->mask->priv_data;
const struct texture_opts *mask_opts = o->mask->opts;
struct pgcraft_texture textures[] = {
{
.name = "content",
.stage = NGLI_PROGRAM_SHADER_FRAG,
.image = &content_priv->image,
.format = content_priv->params.format,
.clamp_video = content_opts->clamp_video,
}, {
.name = "mask",
.stage = NGLI_PROGRAM_SHADER_FRAG,
.image = &mask_priv->image,
.format = mask_priv->params.format,
.clamp_video = mask_opts->clamp_video,
},
};

if (content_opts->data_src && content_opts->data_src->cls->id == NGL_NODE_MEDIA)
textures[0].type = NGLI_PGCRAFT_SHADER_TEX_TYPE_VIDEO;
else
textures[0].type = NGLI_PGCRAFT_SHADER_TEX_TYPE_2D;

if (mask_opts->data_src && mask_opts->data_src->cls->id == NGL_NODE_MEDIA)
textures[1].type = NGLI_PGCRAFT_SHADER_TEX_TYPE_VIDEO;
else
textures[1].type = NGLI_PGCRAFT_SHADER_TEX_TYPE_2D;

static const struct pgcraft_iovar vert_out_vars[] = {
{.name = "uv", .type = NGLI_TYPE_VEC2},
{.name = "content_coord", .type = NGLI_TYPE_VEC2},
{.name = "mask_coord", .type = NGLI_TYPE_VEC2},
};

struct pipeline_desc *descs = ngli_darray_data(&c->pipeline_descs);
struct pipeline_desc *desc = &descs[ctx->rnode_pos->id];
const struct pgcraft_attribute attributes[] = {c->position_attr, c->uvcoord_attr};
const struct pgcraft_params crafter_params = {
.program_label = "nopegl/drawmask",
.vert_base = source_mask_vert,
.frag_base = c->combined_fragment,
.uniforms = ngli_darray_data(&desc->uniforms),
.nb_uniforms = ngli_darray_count(&desc->uniforms),
.textures = textures,
.nb_textures = NGLI_ARRAY_NB(textures),
.attributes = attributes,
.nb_attributes = NGLI_ARRAY_NB(attributes),
.vert_out_vars = vert_out_vars,
.nb_vert_out_vars = NGLI_ARRAY_NB(vert_out_vars),
};

ngli_darray_init(&desc->reframing_nodes, sizeof(struct ngl_node *), 0);
if (!ngli_darray_push(&desc->reframing_nodes, &o->content) ||
!ngli_darray_push(&desc->reframing_nodes, &o->mask))
return NGL_ERROR_MEMORY;

const struct render_common_opts *co = &o->common;
return finalize_pipeline(node, c, co, &crafter_params);
}

static int drawnoise_prepare(struct ngl_node *node)
{
struct ngl_ctx *ctx = node->ctx;
Expand Down Expand Up @@ -1374,6 +1506,7 @@ DECLARE_DRAWOTHER(drawdisplace, NGL_NODE_DRAWDISPLACE, "DrawDisplace")
DECLARE_DRAWOTHER(drawgradient, NGL_NODE_DRAWGRADIENT, "DrawGradient")
DECLARE_DRAWOTHER(drawgradient4, NGL_NODE_DRAWGRADIENT4, "DrawGradient4")
DECLARE_DRAWOTHER(drawhistogram, NGL_NODE_DRAWHISTOGRAM, "DrawHistogram")
DECLARE_DRAWOTHER(drawmask, NGL_NODE_DRAWMASK, "DrawMask")
DECLARE_DRAWOTHER(drawnoise, NGL_NODE_DRAWNOISE, "DrawNoise")
DECLARE_DRAWOTHER(drawtexture, NGL_NODE_DRAWTEXTURE, "DrawTexture")
DECLARE_DRAWOTHER(drawwaveform, NGL_NODE_DRAWWAVEFORM, "DrawWaveform")
1 change: 1 addition & 0 deletions libnopegl/src/nodes_register.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
action(NGL_NODE_DRAWGRADIENT, ngli_drawgradient_class) \
action(NGL_NODE_DRAWGRADIENT4, ngli_drawgradient4_class) \
action(NGL_NODE_DRAWHISTOGRAM, ngli_drawhistogram_class) \
action(NGL_NODE_DRAWMASK, ngli_drawmask_class) \
action(NGL_NODE_DRAWNOISE, ngli_drawnoise_class) \
action(NGL_NODE_DRAWPATH, ngli_drawpath_class) \
action(NGL_NODE_DRAWTEXTURE, ngli_drawtexture_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 @@ -324,6 +324,7 @@ NGL_API void ngl_scene_unrefp(struct ngl_scene **sp);
#define NGL_NODE_DRAWGRADIENT NGLI_FOURCC('D','g','r','d')
#define NGL_NODE_DRAWGRADIENT4 NGLI_FOURCC('D','g','d','4')
#define NGL_NODE_DRAWHISTOGRAM NGLI_FOURCC('D','h','s','t')
#define NGL_NODE_DRAWMASK NGLI_FOURCC('D','m','s','k')
#define NGL_NODE_DRAWNOISE NGLI_FOURCC('D','N','o','i')
#define NGL_NODE_DRAWPATH NGLI_FOURCC('D','P','t','h')
#define NGL_NODE_DRAWTEXTURE NGLI_FOURCC('D','t','e','x')
Expand Down
1 change: 1 addition & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ foreach backend : backends
'cubemap',
'cubemap_mipmap',
'reframing',
'masking',
]

if max_color_attachments >= 4
Expand Down
5 changes: 5 additions & 0 deletions tests/refs/texture_masking.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
00000554100113010C54475401000000 AAAAAAAAAAAA822A8A7E8F3AAAAAAAAA 5555577D7CC3D37DDE7DD77D77555D55 00000000000000000000000000000000
000005141041471C0D54054100000000 AAAAAAAAAAAA8F1E8F9EA8A2AAAAAAAA 5555575D70E3DF1DDDDD55F7575D5DD5 00000000000000000000000000000000
1555400048801DCD30F014D505050050 AAAEAAAA888A1DCF3CFAA0DFA28EAAAA 555FF331498A5DDF75FB5DDFDF5F7575 00000000000000000000000000000000
A0A0F3710338861CD7F407D4A704F0C1 A0A0F371833A823C83FA87FEA982F882 F4E5F371D77DD63DD7FD87FDA71DF77D 00000000000000000000000000000000
B555A000A00017F396549775B755A000 BFF5AAA0AAA0177796548775A305AAA0 B775A220A0A0177396549775B755B0F0 00000000000000000000000000000000
33 changes: 33 additions & 0 deletions tests/texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,3 +664,36 @@ def texture_reframing(cfg: ngl.SceneCfg):
tex = ngl.Translate(tex, vector=ngl.AnimatedVec3(anim_pos_kf))
geometry = ngl.Quad(corner=(-0.8, -0.8, 0), width=(1.6, 0, 0), height=(0, 1.6, 0))
return ngl.DrawTexture(texture=tex, geometry=geometry)


@test_fingerprint(width=640, height=360, keyframes=5, tolerance=1)
@ngl.scene()
def texture_masking(cfg: ngl.SceneCfg):
media = load_media("cat")

cfg.aspect_ratio = (media.width, media.height)
cfg.duration = d = media.duration

animkf = [
ngl.AnimKeyFrameVec3(0, (0.5, 0.5, 1)),
ngl.AnimKeyFrameVec3(d, (20, 20, 1), "exp_in"),
]

return ngl.Group(
children=[
ngl.DrawGradient4(),
ngl.DrawMask(
content=ngl.Texture2D(
data_src=ngl.Media(filename=media.filename),
),
mask=ngl.Texture2D(
data_src=ngl.Scale(
child=ngl.Text("CAT"),
factors=ngl.AnimatedVec3(animkf),
anchor=(0, -0.09, 0),
),
),
blending="src_over",
),
]
)
Loading