* Implemented support for chroma in scene_producer as well.
return glsl;
}
+
+static std::string get_chroma_glsl()
+{
+ static std::string glsl = R"shader(
+ // Chroma keying
+ // Author: Tim Eves <timseves@googlemail.com>
+ //
+ // This implements the Chroma key algorithm described in the paper:
+ // 'Software Chroma Keying in an Imersive Virtual Environment'
+ // 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; }
+
+ // 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);
+ }
+
+ vec4 supress_spill(vec4 c, float d)
+ {
+ float ds = smoothstep(chroma_spill, 1.0, d/chroma_blend.y);
+ float gl = dot(grey_xfer, c);
+ return mix(c, vec4(vec3(gl*gl), gl), ds);
+ }
+
+ // Key on green
+ vec4 ChromaOnGreen(vec4 c)
+ {
+ float d = fma(2.0, c.g, -c.r - c.b)/2.0;
+ c *= alpha_map(d);
+ return supress_spill(c, d);
+ }
+
+ //Key on blue
+ vec4 ChromaOnBlue(vec4 c)
+ {
+ float d = fma(2.0, c.b, -c.r - c.g)/2.0;
+ c *= alpha_map(d);
+ return supress_spill(c, d);
+ }
+ )shader";
+
+ return glsl;
+}
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("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);
// Setup blend_func
)shader";
}
+std::string get_chroma_func()
+{
+ 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;
+ }
+ )shader";
+}
+
std::string get_vertex()
{
return R"shader(
uniform float sat;
uniform float con;
+ uniform int chroma_mode;
+ uniform vec2 chroma_blend;
+ uniform float chroma_spill;
)shader"
+
+
+ get_chroma_func()
+
+ +
+
R"shader(
vec4 ycbcra_to_rgba_sd(float Y, float Cb, float Cr, float A)
void main()
{
vec4 color = get_rgba_color();
- if(levels)
+ color = chroma_key(color);
+ if(levels)
color.rgb = LevelsControl(color.rgb, min_input, gamma, max_input, min_output, max_output);
if(csb)
color.rgb = ContrastSaturationBrightness(color.rgb, brt, sat, con);
GL(glUniform1f(get_location(name.c_str()), value));
}
- void set(const std::string& name, float value0, float value1)
+ void set(const std::string& name, double value0, double value1)
{
- GL(glUniform2f(get_location(name.c_str()), value0, value1));
+ GL(glUniform2f(get_location(name.c_str()), static_cast<float>(value0), static_cast<float>(value1)));
}
void set(const std::string& name, double value)
void shader::set(const std::string& name, bool value){impl_->set(name, value);}
void shader::set(const std::string& name, int value){impl_->set(name, value);}
void shader::set(const std::string& name, float value){impl_->set(name, value);}
-void shader::set(const std::string& name, float value0, float value1){impl_->set(name, value0, value1);}
+void shader::set(const std::string& name, double value0, double value1){impl_->set(name, value0, value1);}
void shader::set(const std::string& name, double value){impl_->set(name, value);}
int shader::id() const{return impl_->program_;}
void shader::use()const{impl_->use();}
void set(const std::string& name, bool value);
void set(const std::string& name, int value);
void set(const std::string& name, float value);
- void set(const std::string& name, float value0, float value1);
+ void set(const std::string& name, double value0, double value1);
void set(const std::string& name, double value);
template<typename E>
struct invalid_argument : virtual caspar_exception {};
struct null_argument : virtual invalid_argument {};
struct out_of_range : virtual invalid_argument {};
+struct programming_error : virtual caspar_exception {};
struct bad_alloc : virtual caspar_exception {};
struct invalid_operation : virtual caspar_exception {};
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;
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.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;
return !(lhs == rhs);
}
+
+core::chroma::type get_chroma_mode(const std::wstring& str)
+{
+ if (boost::iequals(str, L"none"))
+ return core::chroma::type::none;
+ else if (boost::iequals(str, L"green"))
+ return core::chroma::type::green;
+ else if (boost::iequals(str, L"blue"))
+ return core::chroma::type::blue;
+ else
+ CASPAR_THROW_EXCEPTION(invalid_argument() << 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"));
+ };
+}
+
namespace detail {
boost::thread_specific_ptr<double>& get_thread_local_aspect_ratio()
#include <boost/property_tree/ptree.hpp>
namespace caspar { namespace core {
-
+
+struct chroma
+{
+ enum class type
+ {
+ none,
+ green,
+ blue
+ };
+
+ type key = type::none;
+ double threshold = 0.0;
+ double softness = 0.0;
+ double spill = 0.0;
+};
+
struct levels final
{
double min_input = 0.0;
rectangle crop;
corners perspective;
core::levels levels;
+ core::chroma chroma;
core::field_mode field_mode = core::field_mode::progressive;
bool is_key = false;
}
};
+chroma::type get_chroma_mode(const std::wstring& str);
+std::wstring get_chroma_mode(chroma::type type);
+
namespace detail {
void set_current_aspect_ratio(double aspect_ratio);
{
}
- virtual void evaluate() = 0;
+ virtual void evaluate() const = 0;
void depend_on(const std::shared_ptr<impl_base>& dependency)
{
struct impl : public detail::impl_base
{
- T value_;
+ mutable T value_;
std::function<T ()> expression_;
+ mutable bool evaluated_ = false;
impl()
: value_()
T get() const
{
+ if (!evaluated_)
+ evaluate();
+
return value_;
}
on_change();
}
- void evaluate() override
+ void evaluate() const override
{
if (expression_)
{
on_change();
}
}
+
+ evaluated_ = true;
}
using impl_base::on_change;
- void on_change()
+ void on_change() const
{
auto copy = on_change_;
unbind();
depend_on(other);
expression_ = [other]{ return other->get(); };
- evaluate();
+ //evaluate();
}
void unbind()
explicit binding(const Expr& expression)
: impl_(new impl(expression))
{
- impl_->evaluate();
+ //impl_->evaluate();
}
// Expr -> T ()
: impl_(new impl(expression))
{
depend_on(dep);
- impl_->evaluate();
+ //impl_->evaluate();
}
// Expr -> T ()
{
depend_on(dep1);
depend_on(dep2);
- impl_->evaluate();
+ //impl_->evaluate();
}
void* identity() const
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.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();
// Mark as sublayer, so it will be composited separately by the mixer.
transform.image_transform.layer_depth = 1;
adjustments();
};
+struct chroma_key
+{
+ binding<core::chroma::type> key;
+ binding<double> threshold;
+ binding<double> softness;
+ binding<double> spill;
+};
+
struct layer
{
binding<std::wstring> name;
binding<bool> is_key;
binding<bool> use_mipmap;
binding<core::blend_mode> blend_mode;
+ scene::chroma_key chroma_key;
explicit layer(const std::wstring& name, const spl::shared_ptr<frame_producer>& producer);
};
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.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"));
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;
return transform;
}, 0, L"linear"));
}
- else if(boost::iequals(parameters()[0], L"MASTERVOLUME"))
+ else if (boost::iequals(parameters()[0], L"CHROMA"))
+ {
+ if (parameters().size() == 1)
+ {
+ auto chroma = get_current_transform().image_transform.chroma;
+ SetReplyString(
+ L"201 MIXER OK\r\n"
+ + core::get_chroma_mode(chroma.key) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
+ + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n");
+ return true;
+ }
+
+ int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters().at(5)) : 0;
+ std::wstring tween = parameters().size() > 6 ? parameters().at(6) : L"linear";
+
+ core::chroma chroma;
+ chroma.key = get_chroma_mode(parameters().at(1));
+ chroma.threshold = boost::lexical_cast<double>(parameters().at(2));
+ chroma.softness = boost::lexical_cast<double>(parameters().at(3));
+ chroma.spill = parameters().size() > 4 ? boost::lexical_cast<double>(parameters().at(4)) : 0.0;
+ transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+ {
+ transform.image_transform.chroma = chroma;
+ return transform;
+ }, duration, tween));
+ }
+ else if (boost::iequals(parameters()[0], L"MASTERVOLUME"))
{
if (parameters().size() == 1)
{