first command line argument to casparcg.exe.\r
o Fixed various bugs.\r
\r
+Mixer\r
+-----\r
+\r
+ o Added support for rotation.\r
+ o Added support for changing the anchor point around which fill_translation,\r
+ fill_scale and rotation will be done from.\r
+\r
AMCP\r
----\r
\r
o Added RESUME command to complement PAUSE. (Peter Keuter)\r
+ o To support the new mixer features the following commands has been added:\r
+\r
+ - MIXER ANCHOR -- will return or modify the anchor point for a layer\r
+ (default is 0 0 for backwards compatibility). Example:\r
+ MIXER 1-10 ANCHOR 0.5 0.5\r
+ ...for changing the anchor to the middle of the layer\r
+ (a MIXER 1-10 FILL 0.5 0.5 1 1 will be necessary to place the layer at the\r
+ same place on screen as it was before).\r
+\r
+ - MIXER ROTATION -- will return or modify the angle of which a layer is\r
+ rotated by (clockwise degrees) around the point specified by ANCHOR.\r
\r
Consumers\r
---------\r
\r
ogl_->attach(*params.background);\r
\r
+ // Calculate rotation\r
+ auto aspect = params.aspect_ratio;\r
+ auto angle = params.transform.angle;\r
+\r
+ auto rotate = [angle, aspect](double orig_x, double orig_y) -> boost::array<double, 2>\r
+ {\r
+ boost::array<double, 2> result;\r
+ result[0] = orig_x * std::cos(angle) - orig_y * std::sin(angle);\r
+ result[1] = orig_x * std::sin(angle) + orig_y * std::cos(angle);\r
+ result[1] *= aspect;\r
+\r
+ return result;\r
+ };\r
+\r
+ auto anchor = params.transform.anchor;\r
+\r
+ auto ul = rotate( -anchor[0] * f_s[0], -anchor[1] *f_s[1] / aspect);\r
+ auto ur = rotate((-anchor[0] + 1) * f_s[0], -anchor[1] *f_s[1] / aspect);\r
+ auto lr = rotate((-anchor[0] + 1) * f_s[0], (-anchor[1] + 1) *f_s[1] / aspect);\r
+ auto ll = rotate( -anchor[0] * f_s[0], (-anchor[1] + 1) *f_s[1] / aspect);\r
+\r
+ auto upper_left_x = f_p[0] + ul[0];\r
+ auto upper_left_y = f_p[1] + ul[1];\r
+ auto upper_right_x = f_p[0] + ur[0];\r
+ auto upper_right_y = f_p[1] + ur[1];\r
+ auto lower_right_x = f_p[0] + lr[0];\r
+ auto lower_right_y = f_p[1] + lr[1];\r
+ auto lower_left_x = f_p[0] + ll[0];\r
+ auto lower_left_y = f_p[1] + ll[1];\r
+\r
// Draw\r
- \r
/*\r
GL_TEXTURE0 are texture coordinates to the source material, what will be rendered with this call. These are always set to the whole thing.\r
GL_TEXTURE1 are texture coordinates to background- / key-material, that which will have to be taken in consideration when blending. These are set to the rectangle over which the source will be rendered\r
*/\r
glBegin(GL_QUADS);\r
- glMultiTexCoord2d(GL_TEXTURE0, 0.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1, f_p[0] , f_p[1] ); glVertex2d( f_p[0] *2.0-1.0, f_p[1] *2.0-1.0);\r
- glMultiTexCoord2d(GL_TEXTURE0, 1.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1, (f_p[0]+f_s[0]), f_p[1] ); glVertex2d((f_p[0]+f_s[0])*2.0-1.0, f_p[1] *2.0-1.0);\r
- glMultiTexCoord2d(GL_TEXTURE0, 1.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, (f_p[0]+f_s[0]), (f_p[1]+f_s[1])); glVertex2d((f_p[0]+f_s[0])*2.0-1.0, (f_p[1]+f_s[1])*2.0-1.0);\r
- glMultiTexCoord2d(GL_TEXTURE0, 0.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, f_p[0] , (f_p[1]+f_s[1])); glVertex2d( f_p[0] *2.0-1.0, (f_p[1]+f_s[1])*2.0-1.0);\r
+ glMultiTexCoord2d(GL_TEXTURE0, 0.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1, upper_left_x, upper_left_y ); glVertex2d(upper_left_x * 2.0 - 1.0, upper_left_y * 2.0 - 1.0);\r
+ glMultiTexCoord2d(GL_TEXTURE0, 1.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1, upper_right_x, upper_right_y); glVertex2d(upper_right_x * 2.0 - 1.0, upper_right_y * 2.0 - 1.0);\r
+ glMultiTexCoord2d(GL_TEXTURE0, 1.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, lower_right_x, lower_right_y); glVertex2d(lower_right_x * 2.0 - 1.0, lower_right_y * 2.0 - 1.0);\r
+ glMultiTexCoord2d(GL_TEXTURE0, 0.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, lower_left_x, lower_left_y ); glVertex2d(lower_left_x * 2.0 - 1.0, lower_left_y * 2.0 - 1.0);\r
glEnd();\r
\r
// Cleanup\r
std::shared_ptr<device_buffer> background;\r
std::shared_ptr<device_buffer> local_key;\r
std::shared_ptr<device_buffer> layer_key;\r
+ double aspect_ratio;\r
\r
draw_params() \r
: blend_mode(blend_mode::normal)\r
, keyer(keyer::linear)\r
+ , aspect_ratio(1.0)\r
{\r
}\r
};\r
draw_params.pix_desc = std::move(item.pix_desc);\r
draw_params.textures = std::move(item.textures);\r
draw_params.transform = std::move(item.transform);\r
+ draw_params.aspect_ratio = static_cast<double>(format_desc.square_width) / static_cast<double>(format_desc.square_height);\r
\r
if(item.transform.is_key)\r
{\r
, image_mixer_(ogl)\r
, executor_(L"mixer")\r
, monitor_subject_(make_safe<monitor::subject>("/mixer"))\r
- { \r
+ {\r
graph_->set_color("mix-time", diagnostics::color(1.0f, 0.0f, 0.9f, 0.8));\r
current_mix_time_ = 0;\r
+ executor_.invoke([&]\r
+ {\r
+ set_current_aspect_ratio(\r
+ static_cast<double>(format_desc.square_width)\r
+ / static_cast<double>(format_desc.square_height));\r
+ });\r
\r
audio_mixer_.monitor_output().attach_parent(monitor_subject_);\r
}\r
{\r
tbb::spin_mutex::scoped_lock lock(format_desc_mutex_);\r
format_desc_ = format_desc;\r
+ set_current_aspect_ratio(\r
+ static_cast<double>(format_desc.square_width)\r
+ / static_cast<double>(format_desc.square_height));\r
});\r
}\r
\r
\r
#include <common/utility/assert.h>\r
\r
+#include <boost/thread.hpp>\r
+\r
namespace caspar { namespace core {\r
\r
frame_transform::frame_transform() \r
, brightness(1.0)\r
, contrast(1.0)\r
, saturation(1.0)\r
+ , angle(0.0)\r
, field_mode(field_mode::progressive)\r
, is_key(false)\r
, is_mix(false)\r
{\r
+ std::fill(anchor.begin(), anchor.end(), 0.0);\r
std::fill(fill_translation.begin(), fill_translation.end(), 0.0);\r
std::fill(fill_scale.begin(), fill_scale.end(), 1.0);\r
std::fill(clip_translation.begin(), clip_translation.end(), 0.0);\r
brightness *= other.brightness;\r
contrast *= other.contrast;\r
saturation *= other.saturation;\r
- fill_translation[0] += other.fill_translation[0]*fill_scale[0];\r
- fill_translation[1] += other.fill_translation[1]*fill_scale[1];\r
+\r
+ // TODO: can this be done in any way without knowing the aspect ratio of the\r
+ // actual video mode? Thread local to the rescue\r
+ auto aspect_ratio = get_current_aspect_ratio();\r
+ boost::array<double, 2> rotated;\r
+ auto orig_x = other.fill_translation[0];\r
+ auto orig_y = other.fill_translation[1] / aspect_ratio;\r
+ rotated[0] = orig_x * std::cos(angle) - orig_y * std::sin(angle);\r
+ rotated[1] = orig_x * std::sin(angle) + orig_y * std::cos(angle);\r
+ rotated[1] *= aspect_ratio;\r
+\r
+ anchor[0] += other.anchor[0]*fill_scale[0];\r
+ anchor[1] += other.anchor[1]*fill_scale[1];\r
+\r
+ fill_translation[0] += rotated[0] * fill_scale[0];\r
+ fill_translation[1] += rotated[1] * fill_scale[1];\r
fill_scale[0] *= other.fill_scale[0];\r
fill_scale[1] *= other.fill_scale[1];\r
clip_translation[0] += other.clip_translation[0]*clip_scale[0];\r
clip_translation[1] += other.clip_translation[1]*clip_scale[1];\r
clip_scale[0] *= other.clip_scale[0];\r
clip_scale[1] *= other.clip_scale[1];\r
+ angle += other.angle;\r
levels.min_input = std::max(levels.min_input, other.levels.min_input);\r
levels.max_input = std::min(levels.max_input, other.levels.max_input); \r
levels.min_output = std::max(levels.min_output, other.levels.min_output);\r
result.contrast = do_tween(time, source.contrast, dest.contrast, duration, tweener);\r
result.saturation = do_tween(time, source.saturation, dest.saturation, duration, tweener);\r
result.opacity = do_tween(time, source.opacity, dest.opacity, duration, tweener); \r
+ result.anchor[0] = do_tween(time, source.anchor[0], dest.anchor[0], duration, tweener), \r
+ result.anchor[1] = do_tween(time, source.anchor[1], dest.anchor[1], duration, tweener); \r
result.fill_translation[0] = do_tween(time, source.fill_translation[0], dest.fill_translation[0], duration, tweener), \r
result.fill_translation[1] = do_tween(time, source.fill_translation[1], dest.fill_translation[1], duration, tweener); \r
result.fill_scale[0] = do_tween(time, source.fill_scale[0], dest.fill_scale[0], duration, tweener), \r
result.clip_translation[1] = do_tween(time, source.clip_translation[1], dest.clip_translation[1], duration, tweener); \r
result.clip_scale[0] = do_tween(time, source.clip_scale[0], dest.clip_scale[0], duration, tweener), \r
result.clip_scale[1] = do_tween(time, source.clip_scale[1], dest.clip_scale[1], duration, tweener);\r
+ result.angle = do_tween(time, source.angle, dest.angle, duration, tweener); \r
result.levels.max_input = do_tween(time, source.levels.max_input, dest.levels.max_input, duration, tweener);\r
result.levels.min_input = do_tween(time, source.levels.min_input, dest.levels.min_input, duration, tweener); \r
result.levels.max_output = do_tween(time, source.levels.max_output, dest.levels.max_output, duration, tweener);\r
return !(lhs == rhs);\r
}\r
\r
-}}
\ No newline at end of file
+boost::thread_specific_ptr<double>& get_thread_local_aspect_ratio()\r
+{\r
+ static boost::thread_specific_ptr<double> aspect_ratio;\r
+\r
+ if (!aspect_ratio.get())\r
+ aspect_ratio.reset(new double(1.0));\r
+\r
+ return aspect_ratio;\r
+}\r
+\r
+void set_current_aspect_ratio(double aspect_ratio)\r
+{\r
+ *get_thread_local_aspect_ratio() = aspect_ratio;\r
+}\r
+\r
+double get_current_aspect_ratio()\r
+{\r
+ return *get_thread_local_aspect_ratio();\r
+}\r
+\r
+}}\r
double contrast;\r
double brightness;\r
double saturation;\r
- boost::array<double, 2> fill_translation; \r
- boost::array<double, 2> fill_scale; \r
- boost::array<double, 2> clip_translation; \r
- boost::array<double, 2> clip_scale; \r
+ boost::array<double, 2> anchor;\r
+ boost::array<double, 2> fill_translation;\r
+ boost::array<double, 2> fill_scale;\r
+ boost::array<double, 2> clip_translation;\r
+ boost::array<double, 2> clip_scale;\r
+ double angle;\r
levels levels;\r
\r
field_mode::type field_mode;\r
bool operator==(const frame_transform& lhs, const frame_transform& rhs);\r
bool operator!=(const frame_transform& lhs, const frame_transform& rhs);\r
\r
+void set_current_aspect_ratio(double aspect_ratio);\r
+double get_current_aspect_ratio();\r
+\r
}}\r
return transform; \r
}, duration, tween));\r
}\r
+ else if(_parameters[0] == L"ANCHOR")\r
+ {\r
+ if (_parameters.size() == 1)\r
+ {\r
+ auto transform = get_current_transform();\r
+ auto anchor = transform.anchor;\r
+ SetReplyString(\r
+ L"201 MIXER OK\r\n" \r
+ + lexical_cast<std::wstring>(anchor[0]) + L" "\r
+ + lexical_cast<std::wstring>(anchor[1]) + L"\r\n");\r
+ return true;\r
+ }\r
+\r
+ int duration = _parameters.size() > 3 ? boost::lexical_cast<int>(_parameters[3]) : 0;\r
+ std::wstring tween = _parameters.size() > 4 ? _parameters[4] : L"linear";\r
+ double x = boost::lexical_cast<double>(_parameters.at(1));\r
+ double y = boost::lexical_cast<double>(_parameters.at(2));\r
+\r
+ transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) mutable -> frame_transform\r
+ {\r
+ transform.anchor[0] = x;\r
+ transform.anchor[1] = y;\r
+ return transform;\r
+ }, duration, tween));\r
+ }\r
else if(_parameters[0] == L"FILL" || _parameters[0] == L"FILL_RECT")\r
{\r
if (_parameters.size() == 1)\r
return transform;\r
}, duration, tween)); \r
}\r
+ else if(_parameters[0] == L"ROTATION")\r
+ {\r
+ static const double PI = 3.141592653589793;\r
+\r
+ if (_parameters.size() == 1)\r
+ return reply_value([](const frame_transform& t) { return t.angle / PI * 180.0; });\r
+\r
+ auto value = boost::lexical_cast<double>(_parameters.at(1)) * PI / 180.0;\r
+ int duration = _parameters.size() > 2 ? boost::lexical_cast<int>(_parameters[2]) : 0;\r
+ std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear";\r
+ transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
+ {\r
+ transform.angle = value;\r
+ return transform;\r
+ }, duration, tween)); \r
+ }\r
else if(_parameters[0] == L"LEVELS")\r
{\r
if (_parameters.size() == 1)\r