\r
o Fixed bug in the contrast/saturation/brightness code where the wrong luma\r
coefficients was used.\r
+ o Rewrote the chroma key code to support variable hue, instead of fixed green\r
+ or blue. Threshold setting was removed in favour of separate hue width,\r
+ minimum saturation and minimum brightness constraints.\r
\r
AMCP\r
----\r
\r
o INFO PATHS now adds all the path elements even if they are using the default\r
values.\r
+ o MIXER CHROMA syntax deprecated (still supported) in favour of the more\r
+ advanced syntax required by the rewritten chroma key code.\r
+\r
+\r
\r
CasparCG 2.1.0 Beta 1 (w.r.t 2.0.7 Stable)\r
==========================================\r
// by F. van den Bergh & V. Lalioti
// but as a pixel shader algorithm.
//
-
- float chroma_blend_w = chroma_blend.y - chroma_blend.x;
- const vec4 grey_xfer = vec4(0.3, 0.59, 0.11, 0.0);
-
- float fma(float a, float b, float c) { return a*b + c; }
+ vec4 grey_xfer = is_hd
+ ? vec4(0.2126, 0.7152, 0.0722, 0)
+ : vec4(0.299, 0.587, 0.114, 0);
// This allows us to implement the paper's alphaMap curve in software
// rather than a largeish array
float alpha_map(float d)
{
- return 1.0-smoothstep(chroma_blend.x, chroma_blend.y, d);
+ return 1.0 - smoothstep(1.0, chroma_softness, d);
}
vec4 supress_spill(vec4 c, float d)
{
- float ds = smoothstep(chroma_spill, 1.0, d/chroma_blend.y);
+ float ds = smoothstep(chroma_spill, clamp(chroma_spill + 0.2, 0, 1), d / chroma_softness);
float gl = dot(grey_xfer, c);
- return mix(c, vec4(vec3(gl*gl), gl), ds);
+ return mix(c, vec4(vec3(pow(gl, chroma_spill_darken)), gl), ds);
+ }
+
+ // http://stackoverflow.com/questions/15095909/from-rgb-to-hsv-in-opengl-glsl
+ vec3 rgb2hsv(vec3 c)
+ {
+ vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+ vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+ vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
+
+ float d = q.x - min(q.w, q.y);
+ float e = 1.0e-10;
+ return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+ }
+
+ float AngleDiff(float angle1, float angle2)
+ {
+ return 0.5 - abs(abs(angle1 - angle2) - 0.5);
+ }
+
+ float Distance(float actual, float target)
+ {
+ return min(0.0, target - actual);
}
- // Key on green
- vec4 ChromaOnGreen(vec4 c)
+ float ColorDistance(vec3 hsv)
{
- float d = fma(2.0, c.g, -c.r - c.b)/2.0;
- c *= alpha_map(d);
- return supress_spill(c, d);
+ float hueDiff = AngleDiff(hsv.x, chroma_target_hue) * 2;
+ float saturationDiff = Distance(hsv.y, chroma_min_saturation);
+ float brightnessDiff = Distance(hsv.z, chroma_min_brightness);
+
+ float saturationBrightnessScore = max(brightnessDiff, saturationDiff);
+ float hueScore = hueDiff - chroma_hue_width;
+
+ return -hueScore * saturationBrightnessScore;
}
- //Key on blue
- vec4 ChromaOnBlue(vec4 c)
+ // Key on any color
+ vec4 ChromaOnCustomColor(vec4 c)
{
- float d = fma(2.0, c.b, -c.r - c.g)/2.0;
- c *= alpha_map(d);
- return supress_spill(c, d);
+ vec3 hsv = rgb2hsv(c.rgb);
+ float distance = ColorDistance(hsv);
+ float d = distance * -2.0 + 1.0;
+ vec4 suppressed = supress_spill(c.rgba, d).rgba;
+ float alpha = alpha_map(d);
+
+ suppressed *= alpha;
+
+ return chroma_show_mask ? vec4(suppressed.a, suppressed.a, suppressed.a, 1) : suppressed;
}
)shader";
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00};
-
+
GLubyte lower_pattern[] = {
- 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff};
struct image_kernel::impl
-{
+{
spl::shared_ptr<device> ogl_;
spl::shared_ptr<shader> shader_;
bool blend_modes_;
void draw(draw_params params)
{
- static const double epsilon = 0.001;
-
+ static const double epsilon = 0.001;
+
CASPAR_ASSERT(params.pix_desc.planes.size() == params.textures.size());
if(params.textures.empty() || !params.background)
if(params.local_key)
params.local_key->bind(static_cast<int>(texture_id::local_key));
-
+
if(params.layer_key)
params.layer_key->bind(static_cast<int>(texture_id::layer_key));
-
+
// Setup shader
-
+
shader_->use();
shader_->set("post_processing", false);
shader_->set("plane[2]", texture_id::plane2);
shader_->set("plane[3]", texture_id::plane3);
for (int n = 0; n < params.textures.size(); ++n)
- shader_->set("plane_size[" + boost::lexical_cast<std::string>(n) + "]",
- static_cast<float>(params.textures[n]->width()),
+ shader_->set("plane_size[" + boost::lexical_cast<std::string>(n) + "]",
+ static_cast<float>(params.textures[n]->width()),
static_cast<float>(params.textures[n]->height()));
shader_->set("local_key", texture_id::local_key);
shader_->set("is_hd", params.pix_desc.planes.at(0).height > 700 ? 1 : 0);
shader_->set("has_local_key", static_cast<bool>(params.local_key));
shader_->set("has_layer_key", static_cast<bool>(params.layer_key));
- shader_->set("pixel_format", params.pix_desc.format);
- shader_->set("opacity", params.transform.is_key ? 1.0 : params.transform.opacity);
+ shader_->set("pixel_format", params.pix_desc.format);
+ shader_->set("opacity", params.transform.is_key ? 1.0 : params.transform.opacity);
- if (params.transform.chroma.key != core::chroma::type::none)
+ if (params.transform.chroma.enable)
{
- shader_->set("chroma", true);
- shader_->set("chroma_mode", static_cast<int>(params.transform.chroma.key));
- shader_->set("chroma_blend", params.transform.chroma.threshold, params.transform.chroma.softness);
- shader_->set("chroma_spill", params.transform.chroma.spill);
+ shader_->set("chroma", true);
+
+ shader_->set("chroma_show_mask", params.transform.chroma.show_mask);
+ shader_->set("chroma_target_hue", params.transform.chroma.target_hue / 360.0);
+ shader_->set("chroma_hue_width", params.transform.chroma.hue_width);
+ shader_->set("chroma_min_saturation", params.transform.chroma.min_saturation);
+ shader_->set("chroma_min_brightness", params.transform.chroma.min_brightness);
+ shader_->set("chroma_softness", 1.0 + params.transform.chroma.softness);
+ shader_->set("chroma_spill", params.transform.chroma.spill);
+ shader_->set("chroma_spill_darken", params.transform.chroma.spill_darken);
}
else
shader_->set("chroma", false);
// Setup blend_func
-
+
if(params.transform.is_key)
params.blend_mode = core::blend_mode::normal;
switch(params.keyer)
{
case keyer::additive:
- GL(glBlendFunc(GL_ONE, GL_ONE));
+ GL(glBlendFunc(GL_ONE, GL_ONE));
break;
case keyer::linear:
- default:
+ default:
GL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
- }
+ }
}
// Setup image-adjustements
-
+
if (params.transform.levels.min_input > epsilon ||
params.transform.levels.max_input < 1.0-epsilon ||
params.transform.levels.min_output > epsilon ||
params.transform.levels.max_output < 1.0-epsilon ||
std::abs(params.transform.levels.gamma - 1.0) > epsilon)
{
- shader_->set("levels", true);
- shader_->set("min_input", params.transform.levels.min_input);
+ shader_->set("levels", true);
+ shader_->set("min_input", params.transform.levels.min_input);
shader_->set("max_input", params.transform.levels.max_input);
shader_->set("min_output", params.transform.levels.min_output);
shader_->set("max_output", params.transform.levels.max_output);
shader_->set("gamma", params.transform.levels.gamma);
}
else
- shader_->set("levels", false);
+ shader_->set("levels", false);
if (std::abs(params.transform.brightness - 1.0) > epsilon ||
std::abs(params.transform.saturation - 1.0) > epsilon ||
std::abs(params.transform.contrast - 1.0) > epsilon)
{
- shader_->set("csb", true);
-
- shader_->set("brt", params.transform.brightness);
+ shader_->set("csb", true);
+
+ shader_->set("brt", params.transform.brightness);
shader_->set("sat", params.transform.saturation);
shader_->set("con", params.transform.contrast);
}
else
- shader_->set("csb", false);
-
+ shader_->set("csb", false);
+
// Setup interlacing
-
- if (params.transform.field_mode != core::field_mode::progressive)
+
+ if (params.transform.field_mode != core::field_mode::progressive)
{
GL(glEnable(GL_POLYGON_STIPPLE));
}
// Setup drawing area
-
+
GL(glViewport(0, 0, params.background->width(), params.background->height()));
glDisable(GL_DEPTH_TEST);
-
+
auto m_p = params.transform.clip_translation;
auto m_s = params.transform.clip_scale;
{
double w = static_cast<double>(params.background->width());
double h = static_cast<double>(params.background->height());
-
+
GL(glEnable(GL_SCISSOR_TEST));
glScissor(static_cast<int>(m_p[0] * w), static_cast<int>(m_p[1] * h), std::max(0, static_cast<int>(m_s[0] * w)), std::max(0, static_cast<int>(m_s[1] * h)));
}
// Synchronize and set render target
-
+
if (blend_modes_)
{
// http://www.opengl.org/registry/specs/NV/texture_barrier.txt
// This allows us to use framebuffer (background) both as source and target while blending.
- glTextureBarrierNV();
+ glTextureBarrierNV();
}
params.background->attach();
default:
break;
}
-
+
// Cleanup
GL(glDisable(GL_SCISSOR_TEST));
GL(glDisable(GL_POLYGON_STIPPLE));
}
)shader";
}
-
+
std::string get_simple_blend_color_func()
{
return
return
get_chroma_glsl()
-
+
+
-
+
R"shader(
vec4 chroma_key(vec4 c)
{
- switch (chroma_mode)
- {
- case 0: return c;
- case 1: return ChromaOnGreen(c.bgra).bgra;
- case 2: return ChromaOnBlue(c.bgra).bgra;
- }
-
- return c;
+ return ChromaOnCustomColor(c.bgra).bgra;
}
)shader";
}
uniform bool straighten_alpha;
uniform bool chroma;
- uniform int chroma_mode;
- uniform vec2 chroma_blend;
+ uniform bool chroma_show_mask;
+ uniform float chroma_target_hue;
+ uniform float chroma_hue_width;
+ uniform float chroma_min_saturation;
+ uniform float chroma_min_brightness;
+ uniform float chroma_softness;
uniform float chroma_spill;
+ uniform float chroma_spill_darken;
)shader"
+
color.rgb /= color.a + 0.0000001;
return color;
- }
+ }
void main()
{
delete p;
});
};
-
+
try
- {
+ {
g_blend_modes = glTextureBarrierNV ? blend_modes_wanted : false;
g_post_processing = straight_alpha_wanted;
existing_shader.reset(new shader(get_vertex(), get_fragment(g_blend_modes, g_post_processing)), deleter);
{
CASPAR_LOG_CURRENT_EXCEPTION();
CASPAR_LOG(warning) << "Failed to compile shader. Trying to compile without blend-modes.";
-
+
g_blend_modes = false;
existing_shader.reset(new shader(get_vertex(), get_fragment(g_blend_modes, g_post_processing)), deleter);
}
image_transform& image_transform::operator*=(const image_transform &other)
{
- opacity *= other.opacity;
+ opacity *= other.opacity;
brightness *= other.brightness;
contrast *= other.contrast;
saturation *= other.saturation;
transform_corners(perspective, other.perspective);
levels.min_input = std::max(levels.min_input, other.levels.min_input);
- levels.max_input = std::min(levels.max_input, other.levels.max_input);
+ levels.max_input = std::min(levels.max_input, other.levels.max_input);
levels.min_output = std::max(levels.min_output, other.levels.min_output);
levels.max_output = std::min(levels.max_output, other.levels.max_output);
levels.gamma *= other.levels.gamma;
- chroma.key = std::max(chroma.key, other.chroma.key);
- chroma.threshold += other.chroma.threshold;
- chroma.softness += other.chroma.softness;
- chroma.spill += other.chroma.spill;
+ chroma.enable |= other.chroma.enable;
+ chroma.show_mask |= other.chroma.show_mask;
+ chroma.target_hue = std::max(other.chroma.target_hue, chroma.target_hue);
+ chroma.min_saturation = std::max(other.chroma.min_saturation, chroma.min_saturation);
+ chroma.min_brightness = std::max(other.chroma.min_brightness, chroma.min_brightness);
+ chroma.hue_width = std::max(other.chroma.hue_width, chroma.hue_width);
+ chroma.softness = std::max(other.chroma.softness, chroma.softness);
+ chroma.spill = std::min(other.chroma.spill, chroma.spill);
+ chroma.spill_darken = std::max(other.chroma.spill_darken, chroma.spill_darken);
field_mode = field_mode & other.field_mode;
is_key |= other.is_key;
is_mix |= other.is_mix;
image_transform image_transform::tween(double time, const image_transform& source, const image_transform& dest, double duration, const tweener& tween)
{
- image_transform result;
-
- result.brightness = do_tween(time, source.brightness, dest.brightness, duration, tween);
- result.contrast = do_tween(time, source.contrast, dest.contrast, duration, tween);
- result.saturation = do_tween(time, source.saturation, dest.saturation, duration, tween);
- result.opacity = do_tween(time, source.opacity, dest.opacity, duration, tween);
- result.anchor[0] = do_tween(time, source.anchor[0], dest.anchor[0], duration, tween);
- result.anchor[1] = do_tween(time, source.anchor[1], dest.anchor[1], duration, tween);
- result.fill_translation[0] = do_tween(time, source.fill_translation[0], dest.fill_translation[0], duration, tween);
- result.fill_translation[1] = do_tween(time, source.fill_translation[1], dest.fill_translation[1], duration, tween);
- result.fill_scale[0] = do_tween(time, source.fill_scale[0], dest.fill_scale[0], duration, tween);
- result.fill_scale[1] = do_tween(time, source.fill_scale[1], dest.fill_scale[1], duration, tween);
- result.clip_translation[0] = do_tween(time, source.clip_translation[0], dest.clip_translation[0], duration, tween);
- result.clip_translation[1] = do_tween(time, source.clip_translation[1], dest.clip_translation[1], duration, tween);
- result.clip_scale[0] = do_tween(time, source.clip_scale[0], dest.clip_scale[0], duration, tween);
- result.clip_scale[1] = do_tween(time, source.clip_scale[1], dest.clip_scale[1], duration, tween);
- result.angle = do_tween(time, source.angle, dest.angle, duration, tween);
- result.levels.max_input = do_tween(time, source.levels.max_input, dest.levels.max_input, duration, tween);
- result.levels.min_input = do_tween(time, source.levels.min_input, dest.levels.min_input, duration, tween);
- result.levels.max_output = do_tween(time, source.levels.max_output, dest.levels.max_output, duration, tween);
- result.levels.min_output = do_tween(time, source.levels.min_output, dest.levels.min_output, duration, tween);
- result.levels.gamma = do_tween(time, source.levels.gamma, dest.levels.gamma, duration, tween);
- result.chroma.threshold = do_tween(time, source.chroma.threshold, dest.chroma.threshold, duration, tween);
- result.chroma.softness = do_tween(time, source.chroma.softness, dest.chroma.softness, duration, tween);
- result.chroma.spill = do_tween(time, source.chroma.spill, dest.chroma.spill, duration, tween);
- result.chroma.key = dest.chroma.key;
- result.field_mode = source.field_mode & dest.field_mode;
- result.is_key = source.is_key | dest.is_key;
- result.is_mix = source.is_mix | dest.is_mix;
- result.is_still = source.is_still | dest.is_still;
- result.use_mipmap = source.use_mipmap | dest.use_mipmap;
- result.blend_mode = std::max(source.blend_mode, dest.blend_mode);
- result.layer_depth = dest.layer_depth;
+ image_transform result;
+
+ result.brightness = do_tween(time, source.brightness, dest.brightness, duration, tween);
+ result.contrast = do_tween(time, source.contrast, dest.contrast, duration, tween);
+ result.saturation = do_tween(time, source.saturation, dest.saturation, duration, tween);
+ result.opacity = do_tween(time, source.opacity, dest.opacity, duration, tween);
+ result.anchor[0] = do_tween(time, source.anchor[0], dest.anchor[0], duration, tween);
+ result.anchor[1] = do_tween(time, source.anchor[1], dest.anchor[1], duration, tween);
+ result.fill_translation[0] = do_tween(time, source.fill_translation[0], dest.fill_translation[0], duration, tween);
+ result.fill_translation[1] = do_tween(time, source.fill_translation[1], dest.fill_translation[1], duration, tween);
+ result.fill_scale[0] = do_tween(time, source.fill_scale[0], dest.fill_scale[0], duration, tween);
+ result.fill_scale[1] = do_tween(time, source.fill_scale[1], dest.fill_scale[1], duration, tween);
+ result.clip_translation[0] = do_tween(time, source.clip_translation[0], dest.clip_translation[0], duration, tween);
+ result.clip_translation[1] = do_tween(time, source.clip_translation[1], dest.clip_translation[1], duration, tween);
+ result.clip_scale[0] = do_tween(time, source.clip_scale[0], dest.clip_scale[0], duration, tween);
+ result.clip_scale[1] = do_tween(time, source.clip_scale[1], dest.clip_scale[1], duration, tween);
+ result.angle = do_tween(time, source.angle, dest.angle, duration, tween);
+ result.levels.max_input = do_tween(time, source.levels.max_input, dest.levels.max_input, duration, tween);
+ result.levels.min_input = do_tween(time, source.levels.min_input, dest.levels.min_input, duration, tween);
+ result.levels.max_output = do_tween(time, source.levels.max_output, dest.levels.max_output, duration, tween);
+ result.levels.min_output = do_tween(time, source.levels.min_output, dest.levels.min_output, duration, tween);
+ result.levels.gamma = do_tween(time, source.levels.gamma, dest.levels.gamma, duration, tween);
+ result.chroma.target_hue = do_tween(time, source.chroma.target_hue, dest.chroma.target_hue, duration, tween);
+ result.chroma.hue_width = do_tween(time, source.chroma.hue_width, dest.chroma.hue_width, duration, tween);
+ result.chroma.min_saturation = do_tween(time, source.chroma.min_saturation, dest.chroma.min_saturation, duration, tween);
+ result.chroma.min_brightness = do_tween(time, source.chroma.min_brightness, dest.chroma.min_brightness, duration, tween);
+ result.chroma.softness = do_tween(time, source.chroma.softness, dest.chroma.softness, duration, tween);
+ result.chroma.spill = do_tween(time, source.chroma.spill, dest.chroma.spill, duration, tween);
+ result.chroma.spill_darken = do_tween(time, source.chroma.spill_darken, dest.chroma.spill_darken, duration, tween);
+ result.chroma.enable = dest.chroma.enable;
+ result.chroma.show_mask = dest.chroma.show_mask;
+ result.field_mode = source.field_mode & dest.field_mode;
+ result.is_key = source.is_key | dest.is_key;
+ result.is_mix = source.is_mix | dest.is_mix;
+ result.is_still = source.is_still | dest.is_still;
+ result.use_mipmap = source.use_mipmap | dest.use_mipmap;
+ result.blend_mode = std::max(source.blend_mode, dest.blend_mode);
+ result.layer_depth = dest.layer_depth;
do_tween_rectangle(source.crop, dest.crop, result.crop, time, duration, tween);
do_tween_corners(source.perspective, dest.perspective, result.perspective, time, duration, tween);
bool operator==(const image_transform& lhs, const image_transform& rhs)
{
- return
+ return
eq(lhs.opacity, rhs.opacity) &&
eq(lhs.contrast, rhs.contrast) &&
eq(lhs.brightness, rhs.brightness) &&
lhs.use_mipmap == rhs.use_mipmap &&
lhs.blend_mode == rhs.blend_mode &&
lhs.layer_depth == rhs.layer_depth &&
+ lhs.chroma.enable == rhs.chroma.enable &&
+ lhs.chroma.show_mask == rhs.chroma.show_mask &&
+ eq(lhs.chroma.target_hue, rhs.chroma.target_hue) &&
+ eq(lhs.chroma.hue_width, rhs.chroma.hue_width) &&
+ eq(lhs.chroma.min_saturation, rhs.chroma.min_saturation) &&
+ eq(lhs.chroma.min_brightness, rhs.chroma.min_brightness) &&
+ eq(lhs.chroma.softness, rhs.chroma.softness) &&
+ eq(lhs.chroma.spill, rhs.chroma.spill) &&
+ eq(lhs.chroma.spill_darken, rhs.chroma.spill_darken) &&
lhs.crop == rhs.crop &&
lhs.perspective == rhs.perspective;
}
audio_transform& audio_transform::operator*=(const audio_transform &other)
{
- volume *= other.volume;
+ volume *= other.volume;
is_still |= other.is_still;
return *this;
}
}
audio_transform audio_transform::tween(double time, const audio_transform& source, const audio_transform& dest, double duration, const tweener& tween)
-{
+{
audio_transform result;
result.is_still = source.is_still | dest.is_still;
result.volume = do_tween(time, source.volume, dest.volume, duration, tween);
-
+
return result;
}
bool operator==(const frame_transform& lhs, const frame_transform& rhs)
{
- return lhs.image_transform == rhs.image_transform &&
+ return lhs.image_transform == rhs.image_transform &&
lhs.audio_transform == rhs.audio_transform;
}
}
-core::chroma::type get_chroma_mode(const std::wstring& str)
+boost::optional<core::chroma::legacy_type> get_chroma_mode(const std::wstring& str)
{
if (boost::iequals(str, L"none"))
- return core::chroma::type::none;
+ return core::chroma::legacy_type::none;
else if (boost::iequals(str, L"green"))
- return core::chroma::type::green;
+ return core::chroma::legacy_type::green;
else if (boost::iequals(str, L"blue"))
- return core::chroma::type::blue;
+ return core::chroma::legacy_type::blue;
else
- CASPAR_THROW_EXCEPTION(user_error() << msg_info("chroma mode has to be one of none, green or blue"));
-}
-
-std::wstring get_chroma_mode(core::chroma::type type)
-{
- switch (type)
- {
- case core::chroma::type::none:
- return L"none";
- case core::chroma::type::green:
- return L"green";
- case core::chroma::type::blue:
- return L"blue";
- default:
- CASPAR_THROW_EXCEPTION(programming_error() << msg_info("Unhandled enum constant"));
- };
+ return boost::none;
}
namespace detail {
#include <core/mixer/image/blend_modes.h>
#include <boost/array.hpp>
+#include <boost/optional.hpp>
#include <boost/property_tree/ptree.hpp>
namespace caspar { namespace core {
struct chroma
{
- enum class type
+ enum class legacy_type
{
none,
green,
blue
};
- type key = type::none;
- double threshold = 0.0;
- double softness = 0.0;
- double spill = 0.0;
+ bool enable = false;
+ bool show_mask = false;
+ double target_hue = 0.0;
+ double hue_width = 0.0;
+ double min_saturation = 0.0;
+ double min_brightness = 0.0;
+ double softness = 0.0;
+ double spill = 1.0;
+ double spill_darken = 0.0;
};
struct levels final
bool use_mipmap = false;
core::blend_mode blend_mode = core::blend_mode::normal;
int layer_depth = 0;
-
+
image_transform& operator*=(const image_transform &other);
image_transform operator*(const image_transform &other) const;
{
double volume = 1.0;
bool is_still = false;
-
+
audio_transform& operator*=(const audio_transform &other);
audio_transform operator*(const audio_transform &other) const;
bool operator==(const audio_transform& lhs, const audio_transform& rhs);
bool operator!=(const audio_transform& lhs, const audio_transform& rhs);
-//__declspec(align(16))
+//__declspec(align(16))
struct frame_transform final
{
public:
frame_transform();
-
+
core::image_transform image_transform;
core::audio_transform audio_transform;
//char padding[(sizeof(core::image_transform) + sizeof(core::audio_transform)) % 16];
-
+
frame_transform& operator*=(const frame_transform &other);
frame_transform operator*(const frame_transform &other) const;
int duration_;
int time_;
tweener tweener_;
-public:
+public:
tweened_transform()
: duration_(0)
, time_(0)
{
return dest_;
}
-
+
frame_transform fetch()
{
return time_ == duration_ ? dest_ : frame_transform::tween(static_cast<double>(time_), source_, dest_, static_cast<double>(duration_), tweener_);
}
frame_transform fetch_and_tick(int num)
- {
+ {
time_ = std::min(time_+num, duration_);
return fetch();
}
};
-chroma::type get_chroma_mode(const std::wstring& str);
-std::wstring get_chroma_mode(chroma::type type);
+boost::optional<chroma::legacy_type> get_chroma_mode(const std::wstring& str);
namespace detail {
auto speed_variable = std::make_shared<core::variable_impl<double>>(L"1.0", true, 1.0);
store_variable(L"scene_speed", speed_variable);
speed_ = speed_variable->value();
-
+
auto frame_variable = std::make_shared<core::variable_impl<double>>(L"-1", true, -1);
store_variable(L"frame", frame_variable);
frame_number_ = frame_variable->value();
angle = layer.rotation.get() * PI / 180.0;
- transform.image_transform.opacity = layer.adjustments.opacity.get();
- transform.image_transform.is_key = layer.is_key.get();
- transform.image_transform.use_mipmap = layer.use_mipmap.get();
- transform.image_transform.blend_mode = layer.blend_mode.get();
- transform.image_transform.chroma.key = layer.chroma_key.key.get();
- transform.image_transform.chroma.threshold = layer.chroma_key.threshold.get();
- transform.image_transform.chroma.softness = layer.chroma_key.softness.get();
- transform.image_transform.chroma.spill = layer.chroma_key.spill.get();
+ transform.image_transform.opacity = layer.adjustments.opacity.get();
+ transform.image_transform.is_key = layer.is_key.get();
+ transform.image_transform.use_mipmap = layer.use_mipmap.get();
+ transform.image_transform.blend_mode = layer.blend_mode.get();
+ transform.image_transform.chroma.enable = layer.chroma_key.enable.get();
+ transform.image_transform.chroma.target_hue = layer.chroma_key.target_hue.get();
+ transform.image_transform.chroma.hue_width = layer.chroma_key.hue_width.get();
+ transform.image_transform.chroma.min_saturation = layer.chroma_key.min_saturation.get();
+ transform.image_transform.chroma.min_brightness = layer.chroma_key.min_brightness.get();
+ transform.image_transform.chroma.softness = layer.chroma_key.softness.get();
+ transform.image_transform.chroma.spill = layer.chroma_key.spill.get();
+ transform.image_transform.chroma.spill_darken = layer.chroma_key.spill_darken.get();
// Mark as sublayer, so it will be composited separately by the mixer.
transform.image_transform.layer_depth = 1;
{
prec_timer timer;
timer.tick_millis(0);
-
+
auto field1 = render_progressive_frame();
-
+
timer.tick(0.5 / format_desc_.fps);
auto field2 = render_progressive_frame();
return boost::optional<interaction_target>();
}
- std::future<std::wstring> call(const std::vector<std::wstring>& params)
+ std::future<std::wstring> call(const std::vector<std::wstring>& params)
{
if (!params.empty() && boost::ends_with(params.at(0), L"()"))
return make_ready_future(handle_call(params));
{
return producer_name_;
}
-
+
boost::property_tree::wptree info() const
{
boost::property_tree::wptree info;
return impl_->info();
}
-std::future<std::wstring> scene_producer::call(const std::vector<std::wstring>& params)
+std::future<std::wstring> scene_producer::call(const std::vector<std::wstring>& params)
{
return impl_->call(params);
}
struct chroma_key
{
- binding<core::chroma::type> key;
- binding<double> threshold;
+ binding<bool> enable;
+ binding<double> target_hue;
+ binding<double> hue_width;
+ binding<double> min_saturation;
+ binding<double> min_brightness;
binding<double> softness;
binding<double> spill;
+ binding<double> spill_darken;
};
struct layer
static_cast<double>(duration)));
to_affect.set(tweened);
-
+
//CASPAR_LOG(info) << relative_frame << L" " << *start_value << L" " << duration << L" " << tweened;
};
layer.is_key = scene->create_variable<bool>(variable_prefix + L"is_key", false, elem.second.get(L"is_key", L"false"));
layer.use_mipmap = scene->create_variable<bool>(variable_prefix + L"use_mipmap", false, elem.second.get(L"use_mipmap", L"false"));
layer.blend_mode = scene->create_variable<std::wstring>(variable_prefix + L"blend_mode", false, elem.second.get(L"blend_mode", L"normal")).transformed([](const std::wstring& b) { return get_blend_mode(b); });
- layer.chroma_key.key = scene->create_variable<std::wstring>(variable_prefix + L"chroma_key.key", false, elem.second.get(L"chroma_key.key", L"none")).transformed([](const std::wstring& k) { return get_chroma_mode(k); });
- layer.chroma_key.threshold = scene->create_variable<double>(variable_prefix + L"chroma_key.threshold", false, elem.second.get(L"chroma_key.threshold", L"0.0"));
+ layer.chroma_key.enable = scene->create_variable<bool>(variable_prefix + L"chroma_key.enable", false, elem.second.get(L"chroma_key.enable", L"false"));
+ layer.chroma_key.target_hue = scene->create_variable<double>(variable_prefix + L"chroma_key.target_hue", false, elem.second.get(L"chroma_key.target_hue", L"120.0"));
+ layer.chroma_key.hue_width = scene->create_variable<double>(variable_prefix + L"chroma_key.hue_width", false, elem.second.get(L"chroma_key.hue_width", L"0.1"));
+ layer.chroma_key.min_saturation = scene->create_variable<double>(variable_prefix + L"chroma_key.min_saturation", false, elem.second.get(L"chroma_key.min_saturation", L"0.0"));
+ layer.chroma_key.min_brightness = scene->create_variable<double>(variable_prefix + L"chroma_key.min_brightness", false, elem.second.get(L"chroma_key.min_brightness", L"0.0"));
layer.chroma_key.softness = scene->create_variable<double>(variable_prefix + L"chroma_key.softness", false, elem.second.get(L"chroma_key.softness", L"0.0"));
- layer.chroma_key.spill = scene->create_variable<double>(variable_prefix + L"chroma_key.spill", false, elem.second.get(L"chroma_key.spill", L"0.0"));
+ layer.chroma_key.spill = scene->create_variable<double>(variable_prefix + L"chroma_key.spill", false, elem.second.get(L"chroma_key.spill", L"1.0"));
+ layer.chroma_key.spill_darken = scene->create_variable<double>(variable_prefix + L"chroma_key.spill_darken", false, elem.second.get(L"chroma_key.spill_darken", L"2.0"));
scene->create_variable<double>(variable_prefix + L"width", false) = layer.producer.get()->pixel_constraints().width;
scene->create_variable<double>(variable_prefix + L"height", false) = layer.producer.get()->pixel_constraints().height;
auto repo = [&scene](const std::wstring& name) -> variable&
{
- return scene->get_variable(name);
+ return scene->get_variable(name);
};
for (auto& var_name : scene->get_variables())
void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
{
sink.short_description(L"Enable chroma keying on a layer.");
- sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
+ sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[enable:0,1] {[target_hue:float] [hue_width:float] [min_saturation:float] [min_brightness:float] [softness:float] [spill:float] [spill_darken:float] [show_mask:0,1]}}" + ANIMATION_SYNTAX);
sink.para()
->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
- sink.para()->text(L"Examples:");
- sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
- sink.example(L">> MIXER 1-1 CHROMA none");
+ sink.para()->text(L"The chroma keying is done in the HSB/HSV color space.");
+ sink.para()->text(L"Parameters:");
+ sink.definitions()
+ ->item(L"enable", L"0 to disable chroma keying on layer. The rest of the parameters should not be given when disabling.")
+ ->item(L"target_hue", L"The hue in degrees between 0-360 where the center of the hue window will open up.")
+ ->item(L"hue_width", L"The width of the hue window within 0.0-1.0 where 1.0 means 100% of 360 degrees around target_hue.")
+ ->item(L"min_saturation", L"The minimum saturation within 0.0-1.0 required for a color to be within the chroma window.")
+ ->item(L"min_brightness", L"The minimum brightness within 0.0-1.0 required for a color to be within the chroma window.")
+ ->item(L"softness", L"The softness of the chroma keying window.")
+ ->item(L"spill", L"Controls the amount of spill. A value of 1.0 does not suppress any spill. A lower value gradually turns the spill into grayscale and more transparent.")
+ ->item(L"spill_darken", L"Controls the shade of gray that the spill suppression is done towards. Lower values goes towards white and higher values goes towards black.")
+ ->item(L"show_mask", L"If enabled, only shows the mask. Useful while editing the chroma key settings.")
+ ;
+ sink.example(L">> MIXER 1-1 CHROMA 1 120 0.1 0 0 0.1 1 2 0", L"for enabling chroma keying centered around a hue of 120 degrees (green) and with a 10% hue width");
+ sink.example(L">> MIXER 1-1 CHROMA 0", L"for disabling chroma keying");
sink.example(
- L">> MIXER 1-1 BLEND\n"
- L"<< 201 MIXER OK\n"
- L"<< SCREEN", L"for getting the current blend mode");
+ L">> MIXER 1-1 CHROMA 0\n"
+ L"<< 202 MIXER OK\n"
+ L"<< 1 120 0.1 0 0 0.1 1 2 0", L"for getting the current chroma key mode");
+ sink.para()->text(L"Deprecated legacy syntax:");
+ sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] [spill:float]}}" + ANIMATION_SYNTAX);
+ sink.para()->text(L"Deprecated legacy examples:");
+ sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 1.0 25 easeinsine");
+ sink.example(L">> MIXER 1-1 CHROMA none");
}
std::wstring mixer_chroma_command(command_context& ctx)
{
auto chroma = get_current_transform(ctx).image_transform.chroma;
return L"201 MIXER OK\r\n"
- + core::get_chroma_mode(chroma.key) + L" "
- + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
+ + std::wstring(chroma.enable ? L"1 " : L"0 ")
+ + boost::lexical_cast<std::wstring>(chroma.target_hue) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.hue_width) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.min_saturation) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.min_brightness) + L" "
+ boost::lexical_cast<std::wstring>(chroma.softness) + L" "
- + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
+ + boost::lexical_cast<std::wstring>(chroma.spill) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.spill_darken) + L" "
+ + std::wstring(chroma.show_mask ? L"1" : L"0") + L"\r\n";
}
transforms_applier transforms(ctx);
- int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
- std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
-
core::chroma chroma;
- chroma.key = get_chroma_mode(ctx.parameters.at(0));
- if (chroma.key != core::chroma::type::none)
+ int duration;
+ std::wstring tween;
+
+ auto legacy_mode = core::get_chroma_mode(ctx.parameters.at(0));
+
+ if (legacy_mode)
{
- chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
- chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
- chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
+
+ duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
+ tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
+
+ if (*legacy_mode == chroma::legacy_type::none)
+ {
+ chroma.enable = false;
+ }
+ else
+ {
+ chroma.enable = true;
+ chroma.hue_width = 0.5 - boost::lexical_cast<double>(ctx.parameters.at(1)) * 0.5;
+ chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(1));
+ chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(1));
+ chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2)) - boost::lexical_cast<double>(ctx.parameters.at(1));
+ chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(3));
+ chroma.spill_darken = 2;
+
+ if (*legacy_mode == chroma::legacy_type::green)
+ chroma.target_hue = 120;
+ else if (*legacy_mode == chroma::legacy_type::blue)
+ chroma.target_hue = 240;
+ }
+ }
+ else
+ {
+ duration = ctx.parameters.size() > 9 ? boost::lexical_cast<int>(ctx.parameters.at(9)) : 0;
+ tween = ctx.parameters.size() > 10 ? ctx.parameters.at(10) : L"linear";
+
+ chroma.enable = ctx.parameters.at(0) == L"1";
+
+ if (chroma.enable)
+ {
+ chroma.target_hue = boost::lexical_cast<double>(ctx.parameters.at(1));
+ chroma.hue_width = boost::lexical_cast<double>(ctx.parameters.at(2));
+ chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(3));
+ chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(4));
+ chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(5));
+ chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(6));
+ chroma.spill_darken = boost::lexical_cast<double>(ctx.parameters.at(7));
+ chroma.show_mask = boost::lexical_cast<double>(ctx.parameters.at(8));
+ }
}
+
transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
{
transform.image_transform.chroma = chroma;