]> git.sesse.net Git - casparcg/commitdiff
#54
authorHelge Norberg <helge.norberg@svt.se>
Fri, 12 Sep 2014 17:06:12 +0000 (19:06 +0200)
committerHelge Norberg <helge.norberg@svt.se>
Fri, 12 Sep 2014 17:06:12 +0000 (19:06 +0200)
Mixer
-----

  o Added support for rotation.
  o Added support for changing the anchor point around which fill_translation,
    fill_scale and rotation will be done from.

AMCP
----

  o To support the new mixer features the following commands has been added:

    - MIXER ANCHOR -- will return or modify the anchor point for a layer
      (default is 0 0 for backwards compatibility). Example:
      MIXER 1-10 ANCHOR 0.5 0.5
      ...for changing the anchor to the middle of the layer
      (a MIXER 1-10 FILL 0.5 0.5 1 1 will be necessary to place the layer at the
      same place on screen as it was before).

    - MIXER ROTATION -- will return or modify the angle of which a layer is
      rotated by (clockwise degrees) around the point specified by ANCHOR.

CHANGES.txt
core/mixer/image/image_kernel.cpp
core/mixer/image/image_kernel.h
core/mixer/image/image_mixer.cpp
core/mixer/mixer.cpp
core/producer/frame/frame_transform.cpp
core/producer/frame/frame_transform.h
protocol/amcp/AMCPCommandsImpl.cpp

index 30ec709553bd30503554b2e6e5cfe9be3ed3766a..86c1acafc61c3dd975e21b0659fa30bb85e9067e 100644 (file)
@@ -11,10 +11,28 @@ General
     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
index dc8d933b8162c967ee3740e0b026dd0b80bdb325..354859892826e74ce89658ea0338bc78542ed353 100644 (file)
@@ -228,17 +228,46 @@ struct image_kernel::implementation : boost::noncopyable
                \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
index 975577bf8a77d7b29af59665025b369bc4b21bdd..c8fd75b2b2ef636df23ed612d3be5b74edc2bea5 100644 (file)
@@ -54,10 +54,12 @@ struct draw_params
        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
index 1ad020d26eac7ab48d7ecbb2fa75b8821fbcccaa..99bbe3182573e4a5ae12a2ee5c8e435a026b057d 100644 (file)
@@ -182,6 +182,7 @@ private:
                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
index 7def6bf3df310cb43c248bdd93f33d6aa1d53e9d..0e699c728446ee1f07c1ace79821d9607bf3d789 100644 (file)
@@ -94,9 +94,15 @@ public:
                , 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
@@ -233,6 +239,9 @@ public:
                {\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
index d540977e6c884e8a3bfa485f284cafc313ba55cc..d81186e6120a4230950a6c0a99603b462dd860b2 100644 (file)
@@ -25,6 +25,8 @@
 \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
@@ -33,10 +35,12 @@ frame_transform::frame_transform()
        , 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
@@ -50,14 +54,29 @@ frame_transform& frame_transform::operator*=(const frame_transform &other)
        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
@@ -88,6 +107,8 @@ frame_transform tween(double time, const frame_transform& source, const frame_tr
        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
@@ -96,6 +117,7 @@ frame_transform tween(double time, const frame_transform& source, const frame_tr
        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
@@ -122,4 +144,24 @@ bool operator!=(const frame_transform& lhs, const frame_transform& rhs)
        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
index a60e15eed3cfdb24ca4905332af7b207c2b19fb8..a903c8279d5b9389fc14ac1cf280346aa106297d 100644 (file)
@@ -59,10 +59,12 @@ public:
        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
@@ -79,4 +81,7 @@ bool operator<(const frame_transform& lhs, const frame_transform& rhs);
 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
index 14d83cab00648edeb88b04eb3ade84ea6e24c8d6..1981a2f5f51bc2f45bf3ad4f660d13d90c1d7f86 100644 (file)
@@ -485,6 +485,31 @@ bool MixerCommand::DoExecute()
                                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
@@ -683,6 +708,22 @@ bool MixerCommand::DoExecute()
                                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