]> git.sesse.net Git - casparcg/commitdiff
* Merged MIXER CROP, MIXER ANCHOR, MIXER ROTATION and MIXER PERSPECTIVE from 2.0
authorHelge Norberg <helge.norberg@svt.se>
Wed, 3 Jun 2015 21:15:46 +0000 (23:15 +0200)
committerHelge Norberg <helge.norberg@svt.se>
Wed, 3 Jun 2015 21:15:46 +0000 (23:15 +0200)
* Merged reporting of current MIXER values when no parameters are given.
* Changed so that each vertex / texture coordinate pair in frame_geometry is encapsulated in a struct, to not require producers to keep track of indexes in an a array.
* Changed so that the same code in image_kernel draws a single QUAD and a list of QUAD's
* TODO make perspective correction and cropping work with non default geometry like the text producer. Currently no cropping or perspective correction works for custom geometry.
* TODO make interaction coordinate translations work with new transforms.
* Reverted text_producer change so that the baseline of the font is the actual position.
* Fixed so that JSON encoded template data is also forwarded as is, instead of being interpreted as an .ftd file name.
* Implemented anchor, crop, perspective and rotation support in scene_producer

28 files changed:
accelerator/ogl/image/image_kernel.cpp
accelerator/ogl/image/image_kernel.h
accelerator/ogl/image/image_mixer.cpp
accelerator/ogl/image/image_shader.cpp
core/frame/frame_transform.cpp
core/frame/frame_transform.h
core/frame/geometry.cpp
core/frame/geometry.h
core/mixer/audio/audio_mixer.cpp
core/mixer/audio/audio_mixer.h
core/mixer/image/blend_modes.cpp
core/mixer/image/blend_modes.h
core/mixer/mixer.cpp
core/mixer/mixer.h
core/producer/scene/scene_producer.cpp
core/producer/scene/scene_producer.h
core/producer/scene/xml_scene_producer.cpp
core/producer/stage.cpp
core/producer/stage.h
core/producer/text/text_producer.cpp
core/producer/text/utils/color.h
core/producer/text/utils/text_info.h
core/producer/text/utils/texture_atlas.cpp
core/producer/text/utils/texture_atlas.h
core/producer/text/utils/texture_font.cpp
core/producer/text/utils/texture_font.h
protocol/amcp/AMCPCommandsImpl.cpp
protocol/amcp/AMCPCommandsImpl.h

index 57507e00738ae34b081271728fbe541428e5c31d..dd91168646dd4078176e27a479f8fb8417d9edeb 100644 (file)
 #include <core/frame/pixel_format.h>
 #include <core/frame/frame_transform.h>
 
+#include <boost/algorithm/cxx11/all_of.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+
+#include <cmath>
 
 namespace caspar { namespace accelerator { namespace ogl {
-       
+
+// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
+bool get_line_intersection(
+               double p0_x, double p0_y,
+               double p1_x, double p1_y,
+               double p2_x, double p2_y,
+               double p3_x, double p3_y,
+               double& result_x, double& result_y)
+{
+       double s1_x = p1_x - p0_x;
+       double s1_y = p1_y - p0_y;
+       double s2_x = p3_x - p2_x;
+       double s2_y = p3_y - p2_y;
+
+       double s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
+       double t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
+
+       if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
+       {
+               // Collision detected
+               result_x = p0_x + (t * s1_x);
+               result_y = p0_y + (t * s1_y);
+
+               return true;
+       }
+
+       return false; // No collision
+}
+
+double hypotenuse(double x1, double y1, double x2, double y2)
+{
+       auto x = x2 - x1;
+       auto y = y2 - y1;
+
+       return std::sqrt(x * x + y * y);
+}
+
+double calc_q(double close_diagonal, double distant_diagonal)
+{
+       return (close_diagonal + distant_diagonal) / distant_diagonal;
+}
+
+bool is_above_screen(double y)
+{
+       return y < 0.0;
+}
+
+bool is_below_screen(double y)
+{
+       return y > 1.0;
+}
+
+bool is_left_of_screen(double x)
+{
+       return x < 0.0;
+}
+
+bool is_right_of_screen(double x)
+{
+       return x > 1.0;
+}
+
+bool is_outside_screen(const std::vector<core::frame_geometry::coord>& coords)
+{
+       auto x_coords = coords | boost::adaptors::transformed([](const core::frame_geometry::coord& c) { return c.vertex_x; });
+       auto y_coords = coords | boost::adaptors::transformed([](const core::frame_geometry::coord& c) { return c.vertex_y; });
+
+       return boost::algorithm::all_of(x_coords, &is_left_of_screen)
+               || boost::algorithm::all_of(x_coords, &is_right_of_screen)
+               || boost::algorithm::all_of(y_coords, &is_above_screen)
+               || boost::algorithm::all_of(y_coords, &is_below_screen);
+}
+
 GLubyte upper_pattern[] = {
        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,
@@ -77,7 +153,82 @@ struct image_kernel::impl
 
                if(params.transform.opacity < epsilon)
                        return;
-                               
+
+               auto coords = params.geometry.data();
+
+               if (coords.empty())
+                       return;
+
+               // Calculate transforms
+               auto f_p = params.transform.fill_translation;
+               auto f_s = params.transform.fill_scale;
+
+               bool is_default_geometry = boost::equal(coords, core::frame_geometry::get_default().data());
+               auto aspect = params.aspect_ratio;
+               auto angle = params.transform.angle;
+               auto anchor = params.transform.anchor;
+               auto crop = params.transform.crop;
+               auto pers = params.transform.perspective;
+               pers.ur[0] -= 1.0;
+               pers.lr[0] -= 1.0;
+               pers.lr[1] -= 1.0;
+               pers.ll[1] -= 1.0;
+               std::vector<boost::array<double, 2>> pers_corners = { pers.ul, pers.ur, pers.lr, pers.ll };
+
+               auto do_crop = [&](core::frame_geometry::coord& coord)
+               {
+                       if (!is_default_geometry)
+                               // TODO implement support for non-default geometry.
+                               return;
+
+                       coord.vertex_x = std::max(coord.vertex_x, crop.ul[0]);
+                       coord.vertex_x = std::min(coord.vertex_x, crop.lr[0]);
+                       coord.vertex_y = std::max(coord.vertex_y, crop.ul[1]);
+                       coord.vertex_y = std::min(coord.vertex_y, crop.lr[1]);
+                       coord.texture_x = std::max(coord.texture_x, crop.ul[0]);
+                       coord.texture_x = std::min(coord.texture_x, crop.lr[0]);
+                       coord.texture_y = std::max(coord.texture_y, crop.ul[1]);
+                       coord.texture_y = std::min(coord.texture_y, crop.lr[1]);
+               };
+               auto do_perspective = [=](core::frame_geometry::coord& coord, const boost::array<double, 2>& pers_corner)
+               {
+                       if (!is_default_geometry)
+                               // TODO implement support for non-default geometry.
+                               return;
+
+                       coord.vertex_x += pers_corner[0];
+                       coord.vertex_y += pers_corner[1];
+               };
+               auto rotate = [&](core::frame_geometry::coord& coord)
+               {
+                       auto orig_x = (coord.vertex_x - anchor[0]) * f_s[0];
+                       auto orig_y = (coord.vertex_y - anchor[1]) * f_s[1] / aspect;
+                       coord.vertex_x = orig_x * std::cos(angle) - orig_y * std::sin(angle);
+                       coord.vertex_y = orig_x * std::sin(angle) + orig_y * std::cos(angle);
+                       coord.vertex_y *= aspect;
+               };
+               auto move = [&](core::frame_geometry::coord& coord)
+               {
+                       coord.vertex_x += f_p[0];
+                       coord.vertex_y += f_p[1];
+               };
+
+               int corner = 0;
+               for (auto& coord : coords)
+               {
+                       do_crop(coord);
+                       do_perspective(coord, pers_corners.at(corner));
+                       rotate(coord);
+                       move(coord);
+
+                       if (++corner == 4)
+                               corner = 0;
+               }
+
+               // Skip drawing if all the coordinates will be outside the screen.
+               if (is_outside_screen(coords))
+                       return;
+
                // Bind textures
 
                for(int n = 0; n < params.textures.size(); ++n)
@@ -141,11 +292,11 @@ struct image_kernel::impl
 
                // 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)
+               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);     
@@ -157,9 +308,9 @@ struct image_kernel::impl
                else
                        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)
+               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);  
                        
@@ -172,7 +323,7 @@ struct image_kernel::impl
                
                // 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));
 
@@ -193,21 +344,18 @@ struct image_kernel::impl
                bool scissor = m_p[0] > std::numeric_limits<double>::epsilon()                  || m_p[1] > std::numeric_limits<double>::epsilon() ||
                                           m_s[0] < (1.0 - std::numeric_limits<double>::epsilon())      || m_s[1] < (1.0 - std::numeric_limits<double>::epsilon());
 
-               if(scissor)
+               if (scissor)
                {
                        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), static_cast<int>(m_s[0]*w), static_cast<int>(m_s[1]*h));
+                       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)));
                }
 
-               auto f_p = params.transform.fill_translation;
-               auto f_s = params.transform.fill_scale;
-               
                // Synchronize and set render target
                                                                
-               if(blend_modes_)
+               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.
@@ -215,55 +363,79 @@ struct image_kernel::impl
                }
 
                params.background->attach();
-               
-               glMatrixMode(GL_MODELVIEW);
-               glPushMatrix();
-                       glTranslated(f_p[0], f_p[1], 0.0);
-                       glScaled(f_s[0], f_s[1], 1.0);
 
-                       switch(params.geometry.type())
+               // Perspective correction
+               double diagonal_intersection_x;
+               double diagonal_intersection_y;
+
+               if (get_line_intersection(
+                               pers.ul[0] + crop.ul[0], pers.ul[1] + crop.ul[1],
+                               pers.lr[0] + crop.lr[0], pers.lr[1] + crop.lr[1],
+                               pers.ur[0] + crop.lr[0], pers.ur[1] + crop.ul[1],
+                               pers.ll[0] + crop.ul[0], pers.ll[1] + crop.lr[1],
+                               diagonal_intersection_x,
+                               diagonal_intersection_y) &&
+                       is_default_geometry)
+               {
+                       // http://www.reedbeta.com/blog/2012/05/26/quadrilateral-interpolation-part-1/
+                       auto d0 = hypotenuse(pers.ll[0] + crop.ul[0], pers.ll[1] + crop.lr[1], diagonal_intersection_x, diagonal_intersection_y);
+                       auto d1 = hypotenuse(pers.lr[0] + crop.lr[0], pers.lr[1] + crop.lr[1], diagonal_intersection_x, diagonal_intersection_y);
+                       auto d2 = hypotenuse(pers.ur[0] + crop.lr[0], pers.ur[1] + crop.ul[1], diagonal_intersection_x, diagonal_intersection_y);
+                       auto d3 = hypotenuse(pers.ul[0] + crop.ul[0], pers.ul[1] + crop.ul[1], diagonal_intersection_x, diagonal_intersection_y);
+
+                       auto ulq = calc_q(d3, d1);
+                       auto urq = calc_q(d2, d0);
+                       auto lrq = calc_q(d1, d3);
+                       auto llq = calc_q(d0, d2);
+
+                       std::vector<double> q_values = { ulq, urq, lrq, llq };
+
+                       corner = 0;
+                       for (auto& coord : coords)
                        {
-                       case core::frame_geometry::geometry_type::quad:
-                               {
-                                       const std::vector<float>& data = params.geometry.data();
-                                       float v_left = data[0], v_top = data[1], t_left = data[2], t_top = data[3];
-                                       float v_right = data[4], v_bottom = data[5], t_right = data[6], t_bottom = data[7];
-
-                                       glBegin(GL_QUADS);
-                                               glMultiTexCoord2d(GL_TEXTURE0, t_left, t_top);          glVertex2d(v_left, v_top);
-                                               glMultiTexCoord2d(GL_TEXTURE0, t_right, t_top);         glVertex2d(v_right, v_top);
-                                               glMultiTexCoord2d(GL_TEXTURE0, t_right, t_bottom);      glVertex2d(v_right, v_bottom);
-                                               glMultiTexCoord2d(GL_TEXTURE0, t_left, t_bottom);       glVertex2d(v_left, v_bottom);
-                                       glEnd();
-                               }
-                               break;
+                               coord.texture_q = q_values[corner];
+                               coord.texture_x *= q_values[corner];
+                               coord.texture_y *= q_values[corner];
 
-                       case core::frame_geometry::geometry_type::quad_list:
-                               {
-                                       glClientActiveTexture(GL_TEXTURE0);
-                                       
-                                       glDisableClientState(GL_EDGE_FLAG_ARRAY);
-                                       glDisableClientState(GL_COLOR_ARRAY);
-                                       glDisableClientState(GL_INDEX_ARRAY);
-                                       glDisableClientState(GL_NORMAL_ARRAY);
-
-                                       glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-                                       glEnableClientState(GL_VERTEX_ARRAY);
-                                       
-                                       glTexCoordPointer(2, GL_FLOAT, 4*sizeof(float), &(params.geometry.data().data()[2]));
-                                       glVertexPointer(2, GL_FLOAT, 4*sizeof(float), params.geometry.data().data());
-                                       
-                                       glDrawArrays(GL_QUADS, 0, (GLsizei)params.geometry.data().size()/4);    //each vertex is four floats.
-                                       
-                                       glDisableClientState(GL_TEXTURE_COORD_ARRAY);
-                                       glDisableClientState(GL_VERTEX_ARRAY);
-                               }
-                               break;
+                               if (++corner == 4)
+                                       corner = 0;
+                       }
+               }
 
-                       default:
-                               break;
+               // Draw
+               switch(params.geometry.type())
+               {
+               case core::frame_geometry::geometry_type::quad:
+               case core::frame_geometry::geometry_type::quad_list:
+                       {
+                               glClientActiveTexture(GL_TEXTURE0);
+
+                               glDisableClientState(GL_EDGE_FLAG_ARRAY);
+                               glDisableClientState(GL_COLOR_ARRAY);
+                               glDisableClientState(GL_INDEX_ARRAY);
+                               glDisableClientState(GL_NORMAL_ARRAY);
+
+                               glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+                               glEnableClientState(GL_VERTEX_ARRAY);
+
+                               auto stride = static_cast<GLsizei>(sizeof(core::frame_geometry::coord));
+                               auto vertex_coord_member = &core::frame_geometry::coord::vertex_x;
+                               auto texture_coord_member = &core::frame_geometry::coord::texture_x;
+                               auto data_ptr = coords.data();
+                               auto vertex_coord_ptr = &(data_ptr->*vertex_coord_member);
+                               auto texture_coord_ptr = &(data_ptr->*texture_coord_member);
+
+                               glVertexPointer(2, GL_DOUBLE, stride, vertex_coord_ptr);
+                               glTexCoordPointer(4, GL_DOUBLE, stride, texture_coord_ptr);
+                               glDrawArrays(GL_QUADS, 0, static_cast<GLsizei>(coords.size())); //each vertex is four doubles.
+
+                               glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+                               glDisableClientState(GL_VERTEX_ARRAY);
                        }
-               glPopMatrix();
+                       break;
+               default:
+                       break;
+               }
                
                // Cleanup
                GL(glDisable(GL_SCISSOR_TEST));
index 2b7c1d06d4bd2e06f02502f61c35c28a8f96b2de..fd747f0b9fbdc3783a72da33fcc4988559bc4a8e 100644 (file)
@@ -39,15 +39,16 @@ enum class keyer
 
 struct draw_params final
 {
-       core::pixel_format_desc                                         pix_desc        = core::pixel_format::invalid;
+       core::pixel_format_desc                                         pix_desc                = core::pixel_format::invalid;
        std::vector<spl::shared_ptr<class texture>>     textures;
        core::image_transform                                           transform;
-       core::frame_geometry                                            geometry;
-       core::blend_mode                                                        blend_mode      = core::blend_mode::normal;
-       ogl::keyer                                                                      keyer           = ogl::keyer::linear;
+       core::frame_geometry                                            geometry                = core::frame_geometry::get_default();
+       core::blend_mode                                                        blend_mode              = core::blend_mode::normal;
+       ogl::keyer                                                                      keyer                   = ogl::keyer::linear;
        std::shared_ptr<class texture>                          background;
        std::shared_ptr<class texture>                          local_key;
        std::shared_ptr<class texture>                          layer_key;
+       double                                                                          aspect_ratio    = 1.0;
 };
 
 class image_kernel final
index 24cb8cd32c8d265c71388d2713948c7464d8e93b..9dcc90e8b7a32056d011a3fabdaf728a2f770ad1 100644 (file)
@@ -58,7 +58,7 @@ struct item
        core::pixel_format_desc         pix_desc        = core::pixel_format::invalid;
        std::vector<future_texture>     textures;
        core::image_transform           transform;
-       core::frame_geometry            geometry;
+       core::frame_geometry            geometry        = core::frame_geometry::get_default();
 };
 
 struct layer
@@ -201,7 +201,7 @@ private:
                        auto layer_texture = ogl_->create_texture(target_texture->width(), target_texture->height(), 4);
 
                        for (auto& item : layer.items)
-                               draw(layer_texture, std::move(item), layer_key_texture, local_key_texture, local_mix_texture);  
+                               draw(layer_texture, std::move(item), layer_key_texture, local_key_texture, local_mix_texture, format_desc);
                
                        draw(layer_texture, std::move(local_mix_texture), core::blend_mode::normal);                                                    
                        draw(target_texture, std::move(layer_texture), layer.blend_mode);
@@ -209,7 +209,7 @@ private:
                else // fast path
                {
                        for (auto& item : layer.items)          
-                               draw(target_texture, std::move(item), layer_key_texture, local_key_texture, local_mix_texture);         
+                               draw(target_texture, std::move(item), layer_key_texture, local_key_texture, local_mix_texture, format_desc);
                                        
                        draw(target_texture, std::move(local_mix_texture), core::blend_mode::normal);
                }                                       
@@ -217,16 +217,18 @@ private:
                layer_key_texture = std::move(local_key_texture);
        }
 
-       void draw(spl::shared_ptr<texture>&     target_texture, 
-                         item                                          item, 
-                     std::shared_ptr<texture>& layer_key_texture, 
-                         std::shared_ptr<texture>&     local_key_texture, 
-                         std::shared_ptr<texture>&     local_mix_texture)
+       void draw(spl::shared_ptr<texture>& target_texture, 
+                         item item, 
+                     std::shared_ptr<texture>& layer_key_texture, 
+                         std::shared_ptr<texture>& local_key_texture, 
+                         std::shared_ptr<texture>& local_mix_texture,
+                         const core::video_format_desc& format_desc)
        {                       
                draw_params draw_params;
-               draw_params.pix_desc    = std::move(item.pix_desc);
-               draw_params.transform   = std::move(item.transform);
-               draw_params.geometry    = item.geometry;
+               draw_params.pix_desc            = std::move(item.pix_desc);
+               draw_params.transform           = std::move(item.transform);
+               draw_params.geometry            = item.geometry;
+               draw_params.aspect_ratio        = static_cast<double>(format_desc.square_width) / static_cast<double>(format_desc.square_height);
 
                for (auto& future_texture : item.textures)
                        draw_params.textures.push_back(spl::make_shared_ptr(future_texture.get()));
@@ -235,9 +237,9 @@ private:
                {
                        local_key_texture = local_key_texture ? local_key_texture : ogl_->create_texture(target_texture->width(), target_texture->height(), 1);
 
-                       draw_params.background                  = local_key_texture;
-                       draw_params.local_key                   = nullptr;
-                       draw_params.layer_key                   = nullptr;
+                       draw_params.background  = local_key_texture;
+                       draw_params.local_key   = nullptr;
+                       draw_params.layer_key   = nullptr;
 
                        kernel_.draw(std::move(draw_params));
                }
@@ -245,11 +247,11 @@ private:
                {
                        local_mix_texture = local_mix_texture ? local_mix_texture : ogl_->create_texture(target_texture->width(), target_texture->height(), 4);
 
-                       draw_params.background                  = local_mix_texture;
-                       draw_params.local_key                   = std::move(local_key_texture);
-                       draw_params.layer_key                   = layer_key_texture;
+                       draw_params.background  = local_mix_texture;
+                       draw_params.local_key   = std::move(local_key_texture);
+                       draw_params.layer_key   = layer_key_texture;
 
-                       draw_params.keyer                               = keyer::additive;
+                       draw_params.keyer               = keyer::additive;
 
                        kernel_.draw(std::move(draw_params));
                }
@@ -257,9 +259,9 @@ private:
                {
                        draw(target_texture, std::move(local_mix_texture), core::blend_mode::normal);
                        
-                       draw_params.background                  = target_texture;
-                       draw_params.local_key                   = std::move(local_key_texture);
-                       draw_params.layer_key                   = layer_key_texture;
+                       draw_params.background  = target_texture;
+                       draw_params.local_key   = std::move(local_key_texture);
+                       draw_params.layer_key   = layer_key_texture;
 
                        kernel_.draw(std::move(draw_params));
                }       
@@ -273,13 +275,13 @@ private:
                        return;
 
                draw_params draw_params;
-               draw_params.pix_desc.format             = core::pixel_format::bgra;
-               draw_params.pix_desc.planes             = { core::pixel_format_desc::plane(source_buffer->width(), source_buffer->height(), 4) };
-               draw_params.textures                    = { spl::make_shared_ptr(source_buffer) };
-               draw_params.transform                   = core::image_transform();
-               draw_params.blend_mode                  = blend_mode;
-               draw_params.background                  = target_texture;
-               draw_params.geometry                    = core::frame_geometry::get_default();
+               draw_params.pix_desc.format     = core::pixel_format::bgra;
+               draw_params.pix_desc.planes     = { core::pixel_format_desc::plane(source_buffer->width(), source_buffer->height(), 4) };
+               draw_params.textures            = { spl::make_shared_ptr(source_buffer) };
+               draw_params.transform           = core::image_transform();
+               draw_params.blend_mode          = blend_mode;
+               draw_params.background          = target_texture;
+               draw_params.geometry            = core::frame_geometry::get_default();
 
                kernel_.draw(std::move(draw_params));
        }
index df2be866f963a87ec2f9fad4c15cd0133545b072..35d9fe41900895e4282ab091c3f723817bd520d1 100644 (file)
@@ -223,39 +223,39 @@ std::string get_fragment(bool blend_modes)
                                switch(pixel_format)
                                {
                                case 0:         //gray
-                                       return vec4(get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).rrr, 1.0);
+                                       return vec4(get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).rrr, 1.0);
                                case 1:         //bgra,
-                                       return get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).bgra;
+                                       return get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).bgra;
                                case 2:         //rgba,
-                                       return get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).rgba;
+                                       return get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).rgba;
                                case 3:         //argb,
-                                       return get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).argb;
+                                       return get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).argb;
                                case 4:         //abgr,
-                                       return get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).gbar;
+                                       return get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).gbar;
                                case 5:         //ycbcr,
                                        {
-                                               float y  = get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).r;
-                                               float cb = get_sample(plane[1], gl_TexCoord[0].st, plane_size[0]).r;
-                                               float cr = get_sample(plane[2], gl_TexCoord[0].st, plane_size[0]).r;
+                                               float y  = get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).r;
+                                               float cb = get_sample(plane[1], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).r;
+                                               float cr = get_sample(plane[2], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).r;
                                                return ycbcra_to_rgba(y, cb, cr, 1.0);
                                        }
                                case 6:         //ycbcra
                                        {
-                                               float y  = get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).r;
-                                               float cb = get_sample(plane[1], gl_TexCoord[0].st, plane_size[0]).r;
-                                               float cr = get_sample(plane[2], gl_TexCoord[0].st, plane_size[0]).r;
-                                               float a  = get_sample(plane[3], gl_TexCoord[0].st, plane_size[0]).r;
+                                               float y  = get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).r;
+                                               float cb = get_sample(plane[1], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).r;
+                                               float cr = get_sample(plane[2], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).r;
+                                               float a  = get_sample(plane[3], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).r;
                                                return ycbcra_to_rgba(y, cb, cr, a);
                                        }
                                case 7:         //luma
                                        {
-                                               vec3 y3 = get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).rrr;
+                                               vec3 y3 = get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).rrr;
                                                return vec4((y3-0.065)/0.859, 1.0);
                                        }
                                case 8:         //bgr,
-                                       return vec4(get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).bgr, 1.0);
+                                       return vec4(get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).bgr, 1.0);
                                case 9:         //rgb,
-                                       return vec4(get_sample(plane[0], gl_TexCoord[0].st, plane_size[0]).rgb, 1.0);
+                                       return vec4(get_sample(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q, plane_size[0]).rgb, 1.0);
                                }
                                return vec4(0.0, 0.0, 0.0, 0.0);
                        }
index 7db7b4df229cfffa9863aa272d7ada619b90e612..86928191cc4fda8df7cb36e3b6bc7c5c4624f9ba 100644 (file)
 #include "frame_transform.h"
 
 #include <boost/range/algorithm/equal.hpp>
+#include <boost/thread.hpp>
 
 namespace caspar { namespace core {
-               
+
 // image_transform
 
+template<typename Rect>
+void transform_rect(Rect& self, const Rect& other)
+{
+       self.ul[0] += other.ul[0];
+       self.ul[1] += other.ul[1];
+       self.lr[0] *= other.lr[0];
+       self.lr[1] *= other.lr[1];
+}
+
+void transform_corners(corners& self, const corners& other)
+{
+       transform_rect(self, other);
+
+       self.ur[0] *= other.ur[0];
+       self.ur[1] += other.ur[1];
+       self.ll[0] += other.ll[0];
+       self.ll[1] *= other.ll[1];
+
+       // TODO: figure out the math to compose perspective transforms correctly.
+}
+
 image_transform& image_transform::operator*=(const image_transform &other)
 {
        opacity                                 *= other.opacity;       
        brightness                              *= other.brightness;
        contrast                                *= other.contrast;
        saturation                              *= other.saturation;
-       fill_translation[0]             += other.fill_translation[0]*fill_scale[0];
-       fill_translation[1]             += other.fill_translation[1]*fill_scale[1];
+
+       // TODO: can this be done in any way without knowing the aspect ratio of the
+       // actual video mode? Thread local to the rescue
+       auto aspect_ratio                = detail::get_current_aspect_ratio();
+       aspect_ratio                    *= fill_scale[0] / fill_scale[1];
+
+       boost::array<double, 2> rotated;
+
+       auto orig_x                              = other.fill_translation[0];
+       auto orig_y                              = other.fill_translation[1] / aspect_ratio;
+       rotated[0]                               = orig_x * std::cos(angle) - orig_y * std::sin(angle);
+       rotated[1]                               = orig_x * std::sin(angle) + orig_y * std::cos(angle);
+       rotated[1]                              *= aspect_ratio;
+
+       anchor[0]                               += other.anchor[0] * fill_scale[0];
+       anchor[1]                               += other.anchor[1] * fill_scale[1];
+       fill_translation[0]             += rotated[0] * fill_scale[0];
+       fill_translation[1]             += rotated[1] * fill_scale[1];
        fill_scale[0]                   *= other.fill_scale[0];
        fill_scale[1]                   *= other.fill_scale[1];
-       clip_translation[0]             += other.clip_translation[0]*clip_scale[0];
-       clip_translation[1]             += other.clip_translation[1]*clip_scale[1];
+       clip_translation[0]             += other.clip_translation[0] * clip_scale[0];
+       clip_translation[1]             += other.clip_translation[1] * clip_scale[1];
        clip_scale[0]                   *= other.clip_scale[0];
        clip_scale[1]                   *= other.clip_scale[1];
+       angle                                   += other.angle;
+
+       transform_rect(crop, other.crop);
+       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.min_output                = std::max(levels.min_output, other.levels.min_output);
@@ -52,6 +95,7 @@ image_transform& image_transform::operator*=(const image_transform &other)
        is_key                                  |= other.is_key;
        is_mix                                  |= other.is_mix;
        is_still                                |= other.is_still;
+
        return *this;
 }
 
@@ -60,28 +104,51 @@ image_transform image_transform::operator*(const image_transform &other) const
        return image_transform(*this) *= other;
 }
 
+double do_tween(double time, double source, double dest, double duration, const tweener& tween)
+{
+       return tween(time, source, dest - source, duration);
+};
+
+template<typename Rect>
+void do_tween_rectangle(const Rect& source, const Rect& dest, Rect& out, double time, double duration, const tweener& tweener)
+{
+       out.ul[0] = do_tween(time, source.ul[0], dest.ul[0], duration, tweener);
+       out.ul[1] = do_tween(time, source.ul[1], dest.ul[1], duration, tweener);
+       out.lr[0] = do_tween(time, source.lr[0], dest.lr[0], duration, tweener);
+       out.lr[1] = do_tween(time, source.lr[1], dest.lr[1], duration, tweener);
+}
+
+void do_tween_corners(const corners& source, const corners& dest, corners& out, double time, double duration, const tweener& tweener)
+{
+       do_tween_rectangle(source, dest, out, time, duration, tweener);
+
+       out.ur[0] = do_tween(time, source.ur[0], dest.ur[0], duration, tweener);
+       out.ur[1] = do_tween(time, source.ur[1], dest.ur[1], duration, tweener);
+       out.ll[0] = do_tween(time, source.ll[0], dest.ll[0], duration, tweener);
+       out.ll[1] = do_tween(time, source.ll[1], dest.ll[1], duration, tweener);
+};
+
 image_transform image_transform::tween(double time, const image_transform& source, const image_transform& dest, double duration, const tweener& tween)
 {      
-       auto do_tween = [](double time, double source, double dest, double duration, const tweener& tween)
-       {
-               return tween(time, source, dest-source, duration);
-       };
-       
        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.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.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.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);
@@ -89,30 +156,53 @@ image_transform image_transform::tween(double time, const image_transform& sourc
        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;
-       
+
+       do_tween_rectangle(source.crop, dest.crop, result.crop, time, duration, tween);
+       do_tween_corners(source.perspective, dest.perspective, result.perspective, time, duration, tween);
+
        return result;
 }
 
-bool operator==(const image_transform& lhs, const image_transform& rhs)
+bool eq(double lhs, double rhs)
 {
-       auto eq = [](double lhs, double rhs)
-       {
-               return std::abs(lhs - rhs) < 5e-8;
-       };
+       return std::abs(lhs - rhs) < 5e-8;
+};
+
+bool operator==(const corners& lhs, const corners& rhs)
+{
+       return
+               boost::range::equal(lhs.ul, rhs.ul, eq) &&
+               boost::range::equal(lhs.ur, rhs.ur, eq) &&
+               boost::range::equal(lhs.lr, rhs.lr, eq) &&
+               boost::range::equal(lhs.ll, rhs.ll, eq);
+}
 
+bool operator==(const rectangle& lhs, const rectangle& rhs)
+{
+       return
+               boost::range::equal(lhs.ul, rhs.ul, eq) &&
+               boost::range::equal(lhs.lr, rhs.lr, eq);
+}
+
+bool operator==(const image_transform& lhs, const image_transform& rhs)
+{
        return 
                eq(lhs.opacity, rhs.opacity) &&
                eq(lhs.contrast, rhs.contrast) &&
                eq(lhs.brightness, rhs.brightness) &&
                eq(lhs.saturation, rhs.saturation) &&
+               boost::range::equal(lhs.anchor, rhs.anchor, eq) &&
                boost::range::equal(lhs.fill_translation, rhs.fill_translation, eq) &&
                boost::range::equal(lhs.fill_scale, rhs.fill_scale, eq) &&
                boost::range::equal(lhs.clip_translation, rhs.clip_translation, eq) &&
                boost::range::equal(lhs.clip_scale, rhs.clip_scale, eq) &&
+               eq(lhs.angle, rhs.angle) &&
                lhs.field_mode == rhs.field_mode &&
                lhs.is_key == rhs.is_key &&
                lhs.is_mix == rhs.is_mix &&
-               lhs.is_still == rhs.is_still;
+               lhs.is_still == rhs.is_still &&
+               lhs.crop == rhs.crop &&
+               lhs.perspective == rhs.perspective;
 }
 
 bool operator!=(const image_transform& lhs, const image_transform& rhs)
@@ -136,12 +226,7 @@ audio_transform audio_transform::operator*(const audio_transform &other) const
 
 audio_transform audio_transform::tween(double time, const audio_transform& source, const audio_transform& dest, double duration, const tweener& tween)
 {      
-       auto do_tween = [](double time, double source, double dest, double duration, const tweener& tween)
-       {
-               return tween(time, source, dest-source, duration);
-       };
-       
-       audio_transform result; 
+       audio_transform result;
        result.is_still                 = source.is_still | dest.is_still;
        result.volume                   = do_tween(time, source.volume,                         dest.volume,                    duration, tween);
        
@@ -150,11 +235,6 @@ audio_transform audio_transform::tween(double time, const audio_transform& sourc
 
 bool operator==(const audio_transform& lhs, const audio_transform& rhs)
 {
-       auto eq = [](double lhs, double rhs)
-       {
-               return std::abs(lhs - rhs) < 5e-8;
-       };
-
        return eq(lhs.volume, rhs.volume) && lhs.is_still == rhs.is_still;
 }
 
@@ -199,4 +279,26 @@ bool operator!=(const frame_transform& lhs, const frame_transform& rhs)
        return !(lhs == rhs);
 }
 
-}}
+namespace detail {
+
+boost::thread_specific_ptr<double>& get_thread_local_aspect_ratio()
+{
+       static boost::thread_specific_ptr<double> aspect_ratio;
+
+       if (!aspect_ratio.get())
+               aspect_ratio.reset(new double(1.0));
+
+       return aspect_ratio;
+}
+
+void set_current_aspect_ratio(double aspect_ratio)
+{
+       *get_thread_local_aspect_ratio() = aspect_ratio;
+}
+
+double get_current_aspect_ratio()
+{
+       return *get_thread_local_aspect_ratio();
+}
+
+}}}
index fdb467eec5ef0589a02c5974f10a4cc648d9f42b..7eea60ad203dadeeffae3f81140fb955798a0631 100644 (file)
@@ -37,6 +37,20 @@ struct levels final
        double max_output       = 1.0;
 };
 
+struct corners final
+{
+       boost::array<double, 2> ul = boost::array<double, 2> { { 0.0, 0.0 } };
+       boost::array<double, 2> ur = boost::array<double, 2> { { 1.0, 0.0 } };
+       boost::array<double, 2> lr = boost::array<double, 2> { { 1.0, 1.0 } };
+       boost::array<double, 2> ll = boost::array<double, 2> { { 0.0, 1.0 } };
+};
+
+struct rectangle final
+{
+       boost::array<double, 2> ul = boost::array<double, 2> { { 0.0, 0.0 } };
+       boost::array<double, 2> lr = boost::array<double, 2> { { 1.0, 1.0 } };
+};
+
 struct image_transform final
 {
        double                                  opacity                         = 1.0;
@@ -47,10 +61,14 @@ struct image_transform final
        // A bug in VS 2013 prevents us from writing:
        // boost::array<double, 2> fill_translation = { { 0.0, 0.0 } };
        // See http://blogs.msdn.com/b/vcblog/archive/2014/08/19/the-future-of-non-static-data-member-initialization.aspx
+       boost::array<double, 2> anchor                          = boost::array<double, 2> { { 0.0, 0.0 } };
        boost::array<double, 2> fill_translation        = boost::array<double, 2> { { 0.0, 0.0 } };
        boost::array<double, 2> fill_scale                      = boost::array<double, 2> { { 1.0, 1.0 } };
        boost::array<double, 2> clip_translation        = boost::array<double, 2> { { 0.0, 0.0 } };
        boost::array<double, 2> clip_scale                      = boost::array<double, 2> { { 1.0, 1.0 } };
+       double                                  angle                           = 0.0;
+       rectangle                               crop;
+       corners                                 perspective;
        core::levels                    levels;
 
        core::field_mode                field_mode                      = core::field_mode::progressive;
@@ -136,4 +154,9 @@ public:
        }
 };
 
-}}
+namespace detail {
+
+void set_current_aspect_ratio(double aspect_ratio);
+double get_current_aspect_ratio();
+
+}}}
index e25f612b6e1af7d4d3d8feb4f5797f1aa5691f84..210782890f81668f1ec01f9993fffc01f09fa0b7 100644 (file)
 
 namespace caspar { namespace core {
 
+frame_geometry::coord::coord(double vertex_x, double vertex_y, double texture_x, double texture_y)
+       : vertex_x(vertex_x)
+       , vertex_y(vertex_y)
+       , texture_x(texture_x)
+       , texture_y(texture_y)
+{
+}
+
+bool frame_geometry::coord::operator==(const frame_geometry::coord& other) const
+{
+       return vertex_x         == other.vertex_x
+               && vertex_y             == other.vertex_y
+               && texture_x    == other.texture_x
+               && texture_y    == other.texture_y
+               && texture_r    == other.texture_r
+               && texture_q    == other.texture_q;
+}
+
 struct frame_geometry::impl
 {
-       impl(frame_geometry::geometry_type t, std::vector<float> d) : type_(t), data_(std::move(d)) {}
+       impl(frame_geometry::geometry_type type, std::vector<coord> data)
+               : type_(type)
+       {
+               if (type == geometry_type::quad && data.size() != 4)
+                       CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info("The number of coordinates needs to be 4"));
+
+               if (type == geometry_type::quad_list)
+               {
+                       if (data.size() % 4 != 0)
+                               CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info("The number of coordinates needs to be a multiple of 4"));
+               }
+
+               data_ = std::move(data);
+       }
        
-       frame_geometry::geometry_type type_;
-       std::vector<float> data_;
+       frame_geometry::geometry_type   type_;
+       std::vector<coord>                              data_;
 };
 
-frame_geometry::frame_geometry() {}
-frame_geometry::frame_geometry(geometry_type t, std::vector<float> d) : impl_(new impl(t, std::move(d))) {}
+frame_geometry::frame_geometry(geometry_type type, std::vector<coord> data) : impl_(new impl(type, std::move(data))) {}
 
-frame_geometry::geometry_type frame_geometry::type() const { return impl_ ? impl_->type_ : geometry_type::none; }
-const std::vector<float>& frame_geometry::data() const
+frame_geometry::geometry_type frame_geometry::type() const { return impl_->type_; }
+const std::vector<frame_geometry::coord>& frame_geometry::data() const
 {
-       if (impl_)
-               return impl_->data_;
-       else
-               CASPAR_THROW_EXCEPTION(invalid_operation());
+       return impl_->data_;
 }
        
 const frame_geometry& frame_geometry::get_default()
 {
-       const float d[] = {0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f};
-       static frame_geometry g(frame_geometry::geometry_type::quad, std::vector<float>(std::begin(d), std::end(d)));
+       static std::vector<frame_geometry::coord> data = {
+       //    vertex    texture
+               { 0.0, 0.0, 0.0, 0.0 }, // upper left
+               { 1.0, 0.0, 1.0, 0.0 }, // upper right
+               { 1.0, 1.0, 1.0, 1.0 }, // lower right
+               { 0.0, 1.0, 0.0, 1.0 }  // lower left
+       };
+       static const frame_geometry g(frame_geometry::geometry_type::quad, data);
 
        return g;
 }
index ffa47902b9293750da1f454bf9d8c219b13ba88e..326c191491a836836b99d433ab1cded660dbaa3f 100644 (file)
@@ -21,7 +21,8 @@
 
 #pragma once
 
-#include <memory>
+#include <common/memory.h>
+
 #include <vector>
 
 namespace caspar { namespace core {
@@ -31,22 +32,35 @@ class frame_geometry
 public:
        enum class geometry_type
        {
-               none,
                quad,
                quad_list
        };
 
-       frame_geometry();
-       frame_geometry(geometry_type, std::vector<float>);
+       struct coord
+       {
+               double vertex_x         = 0.0;
+               double vertex_y         = 0.0;
+               double texture_x        = 0.0;
+               double texture_y        = 0.0;
+               double texture_r        = 0.0;
+               double texture_q        = 1.0;
+
+               coord() = default;
+               coord(double vertex_x, double vertex_y, double texture_x, double texture_y);
+
+               bool operator==(const coord& other) const;
+       };
+
+       frame_geometry(geometry_type type, std::vector<coord> data);
 
        geometry_type type() const ;
-       const std::vector<float>& data() const;
+       const std::vector<coord>& data() const;
 
        static const frame_geometry& get_default();
 
 private:
        struct impl;
-       std::shared_ptr<impl> impl_;
+       spl::shared_ptr<impl> impl_;
 };
 
 }}
\ No newline at end of file
index a1c233863bb763bc1bbbf0212af2b7e0ad9e5845..eb2f12052840eb2f8c71c6b85fe29d8182d6152f 100644 (file)
@@ -115,6 +115,11 @@ public:
                master_volume_ = volume;
        }
 
+       float get_master_volume()
+       {
+               return master_volume_;
+       }
+
        audio_buffer mix(const video_format_desc& format_desc)
        {       
                if(format_desc_ != format_desc)
@@ -218,6 +223,7 @@ void audio_mixer::push(const frame_transform& transform){impl_->push(transform);
 void audio_mixer::visit(const const_frame& frame){impl_->visit(frame);}
 void audio_mixer::pop(){impl_->pop();}
 void audio_mixer::set_master_volume(float volume) { impl_->set_master_volume(volume); }
-audio_buffer audio_mixer::operator()(const video_format_desc& format_desc){return impl_->mix(format_desc);}
+float audio_mixer::get_master_volume() { return impl_->get_master_volume(); }
+audio_buffer audio_mixer::operator()(const video_format_desc& format_desc){ return impl_->mix(format_desc); }
 
 }}
index 8ad82c5075be4e3dd525eff87aea86485374c619..4764d89cf45e9f6fdb54a5e3492c76951d07383a 100644 (file)
@@ -52,6 +52,7 @@ public:
        
        audio_buffer operator()(const struct video_format_desc& format_desc);
        void set_master_volume(float volume); 
+       float get_master_volume();
 
        // frame_visitor
 
index 5e9a774b37c7cef7f30ec4c0d9266cc6aada5089..4098d734798e1c43b2b51fd15e1b6cbf4de83ba3 100644 (file)
@@ -91,4 +91,71 @@ blend_mode get_blend_mode(const std::wstring& str)
        return blend_mode::normal;
 }
 
+std::wstring get_blend_mode(blend_mode mode)
+{
+       switch (mode)
+       {
+       case blend_mode::normal:
+               return L"normal";
+       case blend_mode::lighten:
+               return L"lighten";
+       case blend_mode::darken:
+               return L"darken";
+       case blend_mode::multiply:
+               return L"multiply";
+       case blend_mode::average:
+               return L"average";
+       case blend_mode::add:
+               return L"add";
+       case blend_mode::subtract:
+               return L"subtract";
+       case blend_mode::difference:
+               return L"difference";
+       case blend_mode::negation:
+               return L"negation";
+       case blend_mode::exclusion:
+               return L"exclusion";
+       case blend_mode::screen:
+               return L"screen";
+       case blend_mode::overlay:
+               return L"overlay";
+       case blend_mode::soft_light:
+               return L"soft_light";
+       case blend_mode::hard_light:
+               return L"hard_light";
+       case blend_mode::color_dodge:
+               return L"color_dodge";
+       case blend_mode::color_burn:
+               return L"color_burn";
+       case blend_mode::linear_dodge:
+               return L"linear_dodge";
+       case blend_mode::linear_burn:
+               return L"linear_burn";
+       case blend_mode::linear_light:
+               return L"linear_light";
+       case blend_mode::vivid_light:
+               return L"vivid_ligh";
+       case blend_mode::pin_light:
+               return L"pin_light";
+       case blend_mode::hard_mix:
+               return L"hard_mix";
+       case blend_mode::reflect:
+               return L"reflect";
+       case blend_mode::glow:
+               return L"glow";
+       case blend_mode::phoenix:
+               return L"phoenix";
+       case blend_mode::contrast:
+               return L"contrast";
+       case blend_mode::saturation:
+               return L"saturation";
+       case blend_mode::color:
+               return L"color";
+       case blend_mode::luminosity:
+               return L"luminosity";
+       default:
+               return L"normal";
+       }
+}
+
 }}
\ No newline at end of file
index 04b9ecfd4d7889eb126c5efc842fe8ca3189fcab..7ed1011a0c6469e0a8ce77461995893b570a4543 100644 (file)
@@ -59,5 +59,6 @@ enum class blend_mode
 };
 
 blend_mode get_blend_mode(const std::wstring& str);
+std::wstring get_blend_mode(blend_mode mode);
 
 }}
\ No newline at end of file
index 4f269b144b528eb54cb3b03fbc69e16025e11b5d..ee5e091640073dabf056c948560137c4c40cdb1e 100644 (file)
@@ -67,7 +67,7 @@ public:
                , image_mixer_(std::move(image_mixer))
        {                       
                graph_->set_color("mix-time", diagnostics::color(1.0f, 0.0f, 0.9f, 0.8f));
-       }       
+       }
        
        const_frame operator()(std::map<int, draw_frame> frames, const video_format_desc& format_desc)
        {               
@@ -76,7 +76,11 @@ public:
                auto frame = executor_.invoke([=]() mutable -> const_frame
                {               
                        try
-                       {       
+                       {
+                               detail::set_current_aspect_ratio(
+                                               static_cast<double>(format_desc.square_width)
+                                               / static_cast<double>(format_desc.square_height));
+
                                for (auto& frame : frames)
                                {
                                        auto blend_it = blend_modes_.find(frame.first);
@@ -119,6 +123,14 @@ public:
                }, task_priority::high_priority);
        }
 
+       blend_mode get_blend_mode(int index)
+       {
+               return executor_.invoke([=]
+               {
+                       return blend_modes_[index];
+               }, task_priority::high_priority);
+       }
+
        void clear_blend_mode(int index)
        {
                executor_.begin_invoke([=]
@@ -143,6 +155,14 @@ public:
                }, task_priority::high_priority);
        }
 
+       float get_master_volume()
+       {
+               return executor_.invoke([=]
+               {
+                       return audio_mixer_.get_master_volume();
+               }, task_priority::high_priority);
+       }
+
        std::future<boost::property_tree::wptree> info() const
        {
                return make_ready_future(boost::property_tree::wptree());
@@ -152,9 +172,11 @@ public:
 mixer::mixer(spl::shared_ptr<diagnostics::graph> graph, spl::shared_ptr<image_mixer> image_mixer) 
        : impl_(new impl(std::move(graph), std::move(image_mixer))){}
 void mixer::set_blend_mode(int index, blend_mode value){impl_->set_blend_mode(index, value);}
+blend_mode mixer::get_blend_mode(int index) { return impl_->get_blend_mode(index); }
 void mixer::clear_blend_mode(int index) { impl_->clear_blend_mode(index); }
 void mixer::clear_blend_modes() { impl_->clear_blend_modes(); }
 void mixer::set_master_volume(float volume) { impl_->set_master_volume(volume); }
+float mixer::get_master_volume() { return impl_->get_master_volume(); }
 std::future<boost::property_tree::wptree> mixer::info() const{return impl_->info();}
 const_frame mixer::operator()(std::map<int, draw_frame> frames, const struct video_format_desc& format_desc){return (*impl_)(std::move(frames), format_desc);}
 mutable_frame mixer::create_frame(const void* tag, const core::pixel_format_desc& desc) {return impl_->image_mixer_->create_frame(tag, desc);}
index 161e4d3b5fe6367c6116f0a1e8343648bf6cdcec..087c47ed9e62de99aab8c8bff3d90937786d6ca4 100644 (file)
@@ -55,12 +55,12 @@ public:
        class const_frame operator()(std::map<int, class draw_frame> frames, const struct video_format_desc& format_desc);
        
        void set_blend_mode(int index, blend_mode value);
-
+       blend_mode get_blend_mode(int index);
        void clear_blend_mode(int index);
-
        void clear_blend_modes();
 
        void set_master_volume(float volume);
+       float get_master_volume();
 
        class mutable_frame create_frame(const void* tag, const struct pixel_format_desc& desc);
 
index 6e9cbdceef6a38ca38e12a309dae20646f10f0d8..e48e8351f113857877b3ac29fc5d927fe2378877 100644 (file)
@@ -36,8 +36,12 @@ namespace caspar { namespace core { namespace scene {
 layer::layer(const std::wstring& name, const spl::shared_ptr<frame_producer>& producer)
        : name(name), producer(producer)
 {
-       clipping.width.bind(producer.get()->pixel_constraints().width);
-       clipping.height.bind(producer.get()->pixel_constraints().height);
+       crop.lower_right.x.bind(producer.get()->pixel_constraints().width);
+       crop.lower_right.y.bind(producer.get()->pixel_constraints().height);
+       perspective.upper_right.x.bind(producer.get()->pixel_constraints().width);
+       perspective.lower_right.x.bind(producer.get()->pixel_constraints().width);
+       perspective.lower_right.y.bind(producer.get()->pixel_constraints().height);
+       perspective.lower_left.y.bind(producer.get()->pixel_constraints().height);
 }
 
 adjustments::adjustments()
@@ -159,22 +163,35 @@ struct scene_producer::impl
        {
                frame_transform transform;
 
-               auto& pos = transform.image_transform.fill_translation;
-               auto& scale = transform.image_transform.fill_scale;
-               //auto& clip_pos = transform.image_transform.clip_translation;
-               //auto& clip_scale = transform.image_transform.clip_scale;
-
-               pos[0] = static_cast<double>(layer.position.x.get()) / static_cast<double>(pixel_constraints_.width.get());
-               pos[1] = static_cast<double>(layer.position.y.get()) / static_cast<double>(pixel_constraints_.height.get());
-               scale[0] = static_cast<double>(layer.producer.get()->pixel_constraints().width.get())
-                               / static_cast<double>(pixel_constraints_.width.get());
-               scale[1] = static_cast<double>(layer.producer.get()->pixel_constraints().height.get())
-                               / static_cast<double>(pixel_constraints_.height.get());
-
-               /*clip_pos[0] = static_cast<double>(layer.clipping.upper_left.x.get()) / static_cast<double>(pixel_constraints_.width.get());
-               clip_pos[1] = static_cast<double>(layer.clipping.upper_left.y.get()) / static_cast<double>(pixel_constraints_.height.get());
-               clip_scale[0] = static_cast<double>(layer.clipping.width.get()) / static_cast<double>(pixel_constraints_.width.get());
-               clip_scale[1] = static_cast<double>(layer.clipping.height.get()) / static_cast<double>(pixel_constraints_.height.get());*/
+               auto& anchor            = transform.image_transform.anchor;
+               auto& pos                       = transform.image_transform.fill_translation;
+               auto& scale                     = transform.image_transform.fill_scale;
+               auto& angle                     = transform.image_transform.angle;
+               auto& crop                      = transform.image_transform.crop;
+               auto& pers                      = transform.image_transform.perspective;
+
+               anchor[0]       = layer.anchor.x.get()                                                                          / layer.producer.get()->pixel_constraints().width.get();
+               anchor[1]       = layer.anchor.y.get()                                                                          / layer.producer.get()->pixel_constraints().height.get();
+               pos[0]          = layer.position.x.get()                                                                        / pixel_constraints_.width.get();
+               pos[1]          = layer.position.y.get()                                                                        / pixel_constraints_.height.get();
+               scale[0]        = layer.producer.get()->pixel_constraints().width.get()         / pixel_constraints_.width.get();
+               scale[1]        = layer.producer.get()->pixel_constraints().height.get()        / pixel_constraints_.height.get();
+               crop.ul[0]      = layer.crop.upper_left.x.get()                                                         / layer.producer.get()->pixel_constraints().width.get();
+               crop.ul[1]      = layer.crop.upper_left.y.get()                                                         / layer.producer.get()->pixel_constraints().height.get();
+               crop.lr[0]      = layer.crop.lower_right.x.get()                                                        / layer.producer.get()->pixel_constraints().width.get();
+               crop.lr[1]      = layer.crop.lower_right.y.get()                                                        / layer.producer.get()->pixel_constraints().height.get();
+               pers.ul[0]      = layer.perspective.upper_left.x.get()                                          / layer.producer.get()->pixel_constraints().width.get();
+               pers.ul[1]      = layer.perspective.upper_left.y.get()                                          / layer.producer.get()->pixel_constraints().height.get();
+               pers.ur[0]      = layer.perspective.upper_right.x.get()                                         / layer.producer.get()->pixel_constraints().width.get();
+               pers.ur[1]      = layer.perspective.upper_right.y.get()                                         / layer.producer.get()->pixel_constraints().height.get();
+               pers.lr[0]      = layer.perspective.lower_right.x.get()                                         / layer.producer.get()->pixel_constraints().width.get();
+               pers.lr[1]      = layer.perspective.lower_right.y.get()                                         / layer.producer.get()->pixel_constraints().height.get();
+               pers.ll[0]      = layer.perspective.lower_left.x.get()                                          / layer.producer.get()->pixel_constraints().width.get();
+               pers.ll[1]      = layer.perspective.lower_left.y.get()                                          / layer.producer.get()->pixel_constraints().height.get();
+
+               static const double PI = 3.141592653589793;
+
+               angle           = layer.rotation.get() * PI / 180.0;
 
                transform.image_transform.opacity = layer.adjustments.opacity.get();
                transform.image_transform.is_key = layer.is_key.get();
@@ -405,12 +422,6 @@ spl::shared_ptr<core::frame_producer> create_dummy_scene_producer(const spl::sha
        };
 
        auto& car_layer = scene->create_layer(create_producer(frame_factory, format_desc, create_param(L"car")), L"car");
-       car_layer.clipping.upper_left.x.set(80);
-       car_layer.clipping.upper_left.y.set(45);
-       car_layer.clipping.width.unbind();
-       car_layer.clipping.width.set(640);
-       car_layer.clipping.height.unbind();
-       car_layer.clipping.height.set(360);
        car_layer.adjustments.opacity.set(0.5);
        //car_layer.hidden = scene->frame() % 50 > 25 || !(scene->frame() < 1000);
        std::vector<std::wstring> sub_params;
index 34e47ef65d94d5221bd2a305871b63329b356725..9f9904156ab32219337abad3b35ad54b1e94a2aa 100644 (file)
@@ -42,9 +42,16 @@ struct coord
 
 struct rect
 {
-       coord                   upper_left;
-       binding<double> width;
-       binding<double> height;
+       coord upper_left;
+       coord lower_right;
+};
+
+struct corners
+{
+       coord upper_left;
+       coord upper_right;
+       coord lower_right;
+       coord lower_left;
 };
 
 struct adjustments
@@ -57,8 +64,11 @@ struct adjustments
 struct layer
 {
        binding<std::wstring>                                           name;
+       scene::coord                                                            anchor;
        scene::coord                                                            position;
-       scene::rect                                                                     clipping;
+       scene::rect                                                                     crop;
+       scene::corners                                                          perspective;
+       binding<double>                                                         rotation;
        scene::adjustments                                                      adjustments;
        binding<spl::shared_ptr<frame_producer>>        producer;
        binding<bool>                                                           hidden;
index d38382991565dd0e73508fa397f2156779b87e5e..9086861ece2bcfa6713cc49236368f53c288770b 100644 (file)
@@ -114,6 +114,21 @@ spl::shared_ptr<core::frame_producer> create_xml_scene_producer(
                layer.hidden = scene->create_variable<bool>(variable_prefix + L"hidden", false, elem.second.get(L"hidden", L"false"));
                layer.position.x = scene->create_variable<double>(variable_prefix + L"x", false, elem.second.get<std::wstring>(L"x"));
                layer.position.y = scene->create_variable<double>(variable_prefix + L"y", false, elem.second.get<std::wstring>(L"y"));
+               layer.anchor.x = scene->create_variable<double>(variable_prefix + L"anchor_x", false, elem.second.get<std::wstring>(L"anchor_x", L"0.0"));
+               layer.anchor.y = scene->create_variable<double>(variable_prefix + L"anchor_y", false, elem.second.get<std::wstring>(L"anchor_y", L"0.0"));
+               layer.rotation = scene->create_variable<double>(variable_prefix + L"rotation", false, elem.second.get<std::wstring>(L"rotation", L"0.0"));
+               layer.crop.upper_left.x = scene->create_variable<double>(variable_prefix + L"crop_upper_left_x", false, elem.second.get<std::wstring>(L"crop_upper_left_x", L"0.0"));
+               layer.crop.upper_left.y = scene->create_variable<double>(variable_prefix + L"crop_upper_left_y", false, elem.second.get<std::wstring>(L"crop_upper_left_y", L"0.0"));
+               layer.crop.lower_right.x = scene->create_variable<double>(variable_prefix + L"crop_lower_right_x", false, elem.second.get<std::wstring>(L"crop_lower_right_x", layer.producer.get()->pixel_constraints().width.as<std::wstring>().get()));
+               layer.crop.lower_right.y = scene->create_variable<double>(variable_prefix + L"crop_lower_right_y", false, elem.second.get<std::wstring>(L"crop_lower_right_y", layer.producer.get()->pixel_constraints().height.as<std::wstring>().get()));
+               layer.perspective.upper_left.x = scene->create_variable<double>(variable_prefix + L"perspective_upper_left_x", false, elem.second.get<std::wstring>(L"perspective_upper_left_x", L"0.0"));
+               layer.perspective.upper_left.y = scene->create_variable<double>(variable_prefix + L"perspective_upper_left_y", false, elem.second.get<std::wstring>(L"perspective_upper_left_y", L"0.0"));
+               layer.perspective.upper_right.x = scene->create_variable<double>(variable_prefix + L"perspective_upper_right_x", false, elem.second.get<std::wstring>(L"perspective_upper_right_x", layer.producer.get()->pixel_constraints().width.as<std::wstring>().get()));
+               layer.perspective.upper_right.y = scene->create_variable<double>(variable_prefix + L"perspective_upper_right_y", false, elem.second.get<std::wstring>(L"perspective_upper_right_y", L"0.0"));
+               layer.perspective.lower_right.x = scene->create_variable<double>(variable_prefix + L"perspective_lower_right_x", false, elem.second.get<std::wstring>(L"perspective_lower_right_x", layer.producer.get()->pixel_constraints().width.as<std::wstring>().get()));
+               layer.perspective.lower_right.y = scene->create_variable<double>(variable_prefix + L"perspective_lower_right_y", false, elem.second.get<std::wstring>(L"perspective_lower_right_y", layer.producer.get()->pixel_constraints().height.as<std::wstring>().get()));
+               layer.perspective.lower_left.x = scene->create_variable<double>(variable_prefix + L"perspective_lower_left_x", false, elem.second.get<std::wstring>(L"perspective_lower_left_x", L"0.0"));
+               layer.perspective.lower_left.y = scene->create_variable<double>(variable_prefix + L"perspective_lower_left_y", false, elem.second.get<std::wstring>(L"perspective_lower_left_y", layer.producer.get()->pixel_constraints().height.as<std::wstring>().get()));
 
                layer.adjustments.opacity = scene->create_variable<double>(variable_prefix + L"adjustment.opacity", false, elem.second.get(L"adjustments.opacity", L"1.0"));
 
index ae015afd8003990425675caf061c546d011b6ed5..224d2d1f58d06e72f094281dfa6d56aadd11e1fd 100644 (file)
@@ -178,7 +178,15 @@ public:
                        tweens_.clear();
                }, task_priority::high_priority);
        }
-               
+
+       std::future<frame_transform> get_current_transform(int index)
+       {
+               return executor_.begin_invoke([=]
+               {
+                       return tweens_[index].fetch();
+               }, task_priority::high_priority);
+       }
+
        std::future<void> load(int index, const spl::shared_ptr<frame_producer>& producer, bool preview, const boost::optional<int32_t>& auto_play_delta)
        {
                return executor_.begin_invoke([=]
@@ -378,6 +386,7 @@ std::future<void> stage::apply_transforms(const std::vector<stage::transform_tup
 std::future<void> stage::apply_transform(int index, const std::function<core::frame_transform(core::frame_transform)>& transform, unsigned int mix_duration, const tweener& tween){ return impl_->apply_transform(index, transform, mix_duration, tween); }
 std::future<void> stage::clear_transforms(int index){ return impl_->clear_transforms(index); }
 std::future<void> stage::clear_transforms(){ return impl_->clear_transforms(); }
+std::future<frame_transform> stage::get_current_transform(int index){ return impl_->get_current_transform(index); }
 std::future<void> stage::load(int index, const spl::shared_ptr<frame_producer>& producer, bool preview, const boost::optional<int32_t>& auto_play_delta){ return impl_->load(index, producer, preview, auto_play_delta); }
 std::future<void> stage::pause(int index){ return impl_->pause(index); }
 std::future<void> stage::play(int index){ return impl_->play(index); }
index 210d00a91f8b3b0afa3694219ca40b0ea898c0ea..834127917391e29e100eb1f1a6d73395c78b94a0 100644 (file)
@@ -64,20 +64,21 @@ public:
 
        std::map<int, class draw_frame> operator()(const struct video_format_desc& format_desc);
 
-       std::future<void>                       apply_transforms(const std::vector<transform_tuple_t>& transforms);
-       std::future<void>                       apply_transform(int index, const transform_func_t& transform, unsigned int mix_duration = 0, const tweener& tween = L"linear");
-       std::future<void>                       clear_transforms(int index);
-       std::future<void>                       clear_transforms();
-       std::future<void>                       load(int index, const spl::shared_ptr<class frame_producer>& producer, bool preview = false, const boost::optional<int32_t>& auto_play_delta = nullptr);
-       std::future<void>                       pause(int index);
-       std::future<void>                       play(int index);
-       std::future<void>                       stop(int index);
-       std::future<std::wstring>       call(int index, const std::vector<std::wstring>& params);
-       std::future<void>                       clear(int index);
-       std::future<void>                       clear();
-       std::future<void>                       swap_layers(stage& other);
-       std::future<void>                       swap_layer(int index, int other_index);
-       std::future<void>                       swap_layer(int index, int other_index, stage& other);
+       std::future<void>                               apply_transforms(const std::vector<transform_tuple_t>& transforms);
+       std::future<void>                               apply_transform(int index, const transform_func_t& transform, unsigned int mix_duration = 0, const tweener& tween = L"linear");
+       std::future<void>                               clear_transforms(int index);
+       std::future<void>                               clear_transforms();
+       std::future<frame_transform>    get_current_transform(int index);
+       std::future<void>                               load(int index, const spl::shared_ptr<class frame_producer>& producer, bool preview = false, const boost::optional<int32_t>& auto_play_delta = nullptr);
+       std::future<void>                               pause(int index);
+       std::future<void>                               play(int index);
+       std::future<void>                               stop(int index);
+       std::future<std::wstring>               call(int index, const std::vector<std::wstring>& params);
+       std::future<void>                               clear(int index);
+       std::future<void>                               clear();
+       std::future<void>                               swap_layers(stage& other);
+       std::future<void>                               swap_layer(int index, int other_index);
+       std::future<void>                               swap_layer(int index, int other_index, stage& other);
 
        monitor::subject& monitor_output();     
 
index 3c1a583eb66ba065d52d07efd8027c06508f2621..e43213814bacdae1528082862e368ccc74ebcd73 100644 (file)
@@ -192,7 +192,6 @@ public:
                current_bearing_y_.value().set(metrics.bearingY);
                current_protrude_under_y_.value().set(metrics.protrudeUnderY);
                frame_ = core::draw_frame(std::move(frame));
-               frame_.transform().image_transform.fill_translation[1] = static_cast<double>(metrics.bearingY) / static_cast<double>(metrics.height);
        }
 
        text::string_metrics measure_string(const std::wstring& str)
@@ -205,7 +204,10 @@ public:
        draw_frame receive_impl()
        {
                if (dirty_)
+               {
                        generate_frame();
+                       dirty_ = false;
+               }
 
                return frame_;
        }
@@ -327,12 +329,12 @@ spl::shared_ptr<frame_producer> create_text_producer(const spl::shared_ptr<frame
 
        text::text_info text_info;
        text_info.font = get_param(L"FONT", params, L"verdana");
-       text_info.size = static_cast<float>(get_param(L"SIZE", params, 30.0)); // 30.0f does not seem to work to get as float directly
+       text_info.size = get_param(L"SIZE", params, 30.0); // 30.0f does not seem to work to get as float directly
        
        std::wstring col_str = get_param(L"color", params, L"#ffffffff");
        uint32_t col_val = 0xffffffff;
        try_get_color(col_str, col_val);
-       text_info.color = core::text::color<float>(col_val);
+       text_info.color = core::text::color<double>(col_val);
 
        bool standalone = get_param(L"STANDALONE", params, false);
 
index c9c53f288edb500bdae14cd0c917d0fb137eddd6..9c41ba7c56c07ac9b9dde660c616413034f78858 100644 (file)
@@ -8,10 +8,10 @@ struct color
        color() {}
        explicit color(unsigned int value)
        {
-               b =  (value & 0x000000ff)                       / 255.0f;
-               g = ((value & 0x0000ff00) >>  8)        / 255.0f;
-               r = ((value & 0x00ff0000) >> 16)        / 255.0f;
-               a = ((value & 0xff000000) >> 24)        / 255.0f;
+               b =  (value & 0x000000ff)                       / 255.0;
+               g = ((value & 0x0000ff00) >>  8)        / 255.0;
+               r = ((value & 0x00ff0000) >> 16)        / 255.0;
+               a = ((value & 0xff000000) >> 24)        / 255.0;
        }
 
        color(T alpha, T red, T green, T blue) : r(red), g(green), b(blue), a(alpha) {}
index 02700cccf25d2c172990df9eaf086bdf1ac05518..0cb8e050f7803a58d3edd37e075fe9e8a8d6e271 100644 (file)
@@ -10,8 +10,8 @@ namespace caspar { namespace core { namespace text {
                std::wstring font;
                std::wstring font_file;
 
-               float                                   size                            = 0.0f;
-               text::color<float>              color;
+               double                                  size                            = 0.0;
+               text::color<double>             color;
                //int                                   shadow_distance;
                //int                                   shadow_size;
                //float                                 shadow_spread;
index 9d42a629c0ca8f6ccd646b33ec44e8ec941f1e3d..fde688f071f9fbe88620a16966147f880a546591 100644 (file)
@@ -138,7 +138,7 @@ public:
        }
 
        //the data parameter points to bitmap-data that is 8-bit grayscale.
-       void set_region(const size_t x, const size_t y, const size_t width, const size_t height, const unsigned char *src, const size_t stride, const color<float>& col)
+       void set_region(const size_t x, const size_t y, const size_t width, const size_t height, const unsigned char *src, const size_t stride, const color<double>& col)
        {
                //this assumes depth_ is set to 4
                for(size_t i=0; i<height; ++i)
@@ -216,7 +216,7 @@ private:
 
 texture_atlas::texture_atlas(const size_t w, const size_t h, const size_t d) : impl_(new impl(w, h, d)) {}
 rect texture_atlas::get_region(int width, int height) { return impl_->get_region(width, height); }
-void texture_atlas::set_region(const size_t x, const size_t y, const size_t width, const size_t height, const unsigned char *src, const size_t stride, const color<float>& col)
+void texture_atlas::set_region(const size_t x, const size_t y, const size_t width, const size_t height, const unsigned char *src, const size_t stride, const color<double>& col)
        { impl_->set_region(x, y, width, height, src, stride, col); }
 
 void texture_atlas::clear() { impl_->clear(); }
index ff1ffeb92653904e67355fdbe047ad5b04d0d5eb..4831158dfa9cd6c2bdf92023b0ddbad6ef51943f 100644 (file)
@@ -81,7 +81,7 @@ public:
        texture_atlas(const size_t w, const size_t h, const size_t d);
 
        rect get_region(int width, int height);
-       void set_region(const size_t x, const size_t y, const size_t width, const size_t height, const unsigned char *data, const size_t stride, const color<float>& col);
+       void set_region(const size_t x, const size_t y, const size_t width, const size_t height, const unsigned char *data, const size_t stride, const color<double>& col);
        void clear();
 
        size_t depth();
index d4f406976c7bf446a1ba49960add3f7f2a162c04..19629527e82a5980f87602b979f797c5651a8eb8 100644 (file)
@@ -30,23 +30,23 @@ struct texture_font::impl
 private:
        struct glyph_info
        {
-               glyph_info(int w, int h, float l, float t, float r, float b) : width(w), height(h), left(l), top(t), right(r), bottom(b)
+               glyph_info(int w, int h, double l, double t, double r, double b) : width(w), height(h), left(l), top(t), right(r), bottom(b)
                {}
 
-               float left, top, right, bottom;
+               double left, top, right, bottom;
                int width, height;
        };
 
        std::shared_ptr<FT_LibraryRec_> lib_;
        std::shared_ptr<FT_FaceRec_>    face_;
        texture_atlas                                   atlas_;
-       float                                                   size_;
-       float                                                   tracking_;
+       double                                                  size_;
+       double                                                  tracking_;
        bool                                                    normalize_;
        std::map<int, glyph_info>               glyphs_;
 
 public:
-       impl(texture_atlas& atlas, const text_info& info, bool normalize_coordinates) : atlas_(atlas), size_(info.size), tracking_(info.size*info.tracking/1000.0f), normalize_(normalize_coordinates)
+       impl(texture_atlas& atlas, const text_info& info, bool normalize_coordinates) : atlas_(atlas), size_(info.size), tracking_(info.size*info.tracking/1000.0), normalize_(normalize_coordinates)
        {
                FT_Library lib;
                        
@@ -68,7 +68,7 @@ public:
 
        void set_tracking(int tracking)
        {
-               tracking_ = size_ * tracking / 1000.0f;
+               tracking_ = size_ * tracking / 1000.0;
        }
 
        int count_glyphs_in_range(unicode_block block)
@@ -81,7 +81,7 @@ public:
                return range.last - range.first;
        }
 
-       void load_glyphs(unicode_block block, const color<float>& col)
+       void load_glyphs(unicode_block block, const color<double>& col)
        {
                FT_Error err;
                int flags = FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL;
@@ -108,34 +108,34 @@ public:
 
                        atlas_.set_region(region.x, region.y, bitmap.width, bitmap.rows, bitmap.buffer, bitmap.pitch, col);
                        glyphs_.insert(std::pair<int, glyph_info>(i, glyph_info(bitmap.width, bitmap.rows, 
-                                                                               region.x / (float)atlas_.width(), 
-                                                                               region.y / (float)atlas_.height(), 
-                                                                               (region.x + bitmap.width) / (float)atlas_.width(), 
-                                                                               (region.y + bitmap.rows) / (float)atlas_.height())));
+                                                                               region.x / static_cast<double>(atlas_.width()),
+                                                                               region.y / static_cast<double>(atlas_.height()),
+                                                                               (region.x + bitmap.width) / static_cast<double>(atlas_.width()),
+                                                                               (region.y + bitmap.rows) / static_cast<double>(atlas_.height()))));
                }
        }
 
-       std::vector<float> create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics)
+       std::vector<frame_geometry::coord> create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics)
        {
                //TODO: detect glyphs that aren't in the atlas and load them (and maybe that entire unicode_block on the fly
 
-               std::vector<float> result(16*str.length(), 0);
+               std::vector<frame_geometry::coord> result;
+               result.resize(4 * str.length());
 
                bool use_kerning = (face_->face_flags & FT_FACE_FLAG_KERNING) == FT_FACE_FLAG_KERNING;
                int index = 0;
                FT_UInt previous = 0;
-               float pos_x = (float)x;
-               float pos_y = (float)y;
+               double pos_x = static_cast<double>(x);
+               double pos_y = static_cast<double>(y);
 
                int maxBearingY = 0;
                int maxProtrudeUnderY = 0;
                int maxHeight = 0;
 
-               auto end = str.end();
-               for(auto it = str.begin(); it != end; ++it, ++index)
+               for (auto it = str.begin(), end = str.end(); it != end; ++it, ++index)
                {
                        auto glyph_it = glyphs_.find(*it);
-                       if(glyph_it != glyphs_.end())
+                       if (glyph_it != glyphs_.end())
                        {       
                                const glyph_info& coords = glyph_it->second;
 
@@ -146,40 +146,45 @@ public:
                                        FT_Vector delta;
                                        FT_Get_Kerning(face_.get(), previous, glyph_index, FT_KERNING_DEFAULT, &delta);
 
-                                       pos_x += delta.x / 64.0f;
+                                       pos_x += delta.x / 64.0;
                                }
 
                                FT_Load_Glyph(face_.get(), glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL);
 
-                               float left = (pos_x + face_->glyph->metrics.horiBearingX/64.0f) / parent_width ;
-                               float right = ((pos_x + face_->glyph->metrics.horiBearingX/64.0f) + coords.width) / parent_width;
-
-                               float top = (pos_y - face_->glyph->metrics.horiBearingY/64.0f) / parent_height;
-                               float bottom = ((pos_y - face_->glyph->metrics.horiBearingY/64.0f) + coords.height) / parent_height;
-
-                               //vertex 1 top left
-                               result[index*16 + 0] = left;                    //vertex.x
-                               result[index*16 + 1] = top;                             //vertex.y
-                               result[index*16 + 2] = coords.left;             //texcoord.r
-                               result[index*16 + 3] = coords.top;              //texcoord.s
-
-                               //vertex 2 top right
-                               result[index*16 + 4] = right;                   //vertex.x
-                               result[index*16 + 5] = top;                             //vertex.y
-                               result[index*16 + 6] = coords.right;    //texcoord.r
-                               result[index*16 + 7] = coords.top;              //texcoord.s
-
-                               //vertex 3 bottom right
-                               result[index*16 + 8] = right;                   //vertex.x
-                               result[index*16 + 9] = bottom;                  //vertex.y
-                               result[index*16 + 10] = coords.right;   //texcoord.r
-                               result[index*16 + 11] = coords.bottom;  //texcoord.s
-
-                               //vertex 4 bottom left
-                               result[index*16 + 12] = left;                   //vertex.x
-                               result[index*16 + 13] = bottom;                 //vertex.y
-                               result[index*16 + 14] = coords.left;    //texcoord.r
-                               result[index*16 + 15] = coords.bottom;  //texcoord.s
+                               double left = (pos_x + face_->glyph->metrics.horiBearingX / 64.0) / parent_width;
+                               double right = ((pos_x + face_->glyph->metrics.horiBearingX / 64.0) + coords.width) / parent_width;
+
+                               double top = (pos_y - face_->glyph->metrics.horiBearingY/64.0) / parent_height;
+                               double bottom = ((pos_y - face_->glyph->metrics.horiBearingY / 64.0) + coords.height) / parent_height;
+
+                               auto ul_index = index * 4;
+                               auto ur_index = ul_index + 1;
+                               auto lr_index = ul_index + 2;
+                               auto ll_index = ul_index + 3;
+
+                               //vertex 1 upper left
+                               result[ul_index].vertex_x       = left;                         //vertex.x
+                               result[ul_index].vertex_y       = top;                          //vertex.y
+                               result[ul_index].texture_x      = coords.left;          //texcoord.r
+                               result[ul_index].texture_y      = coords.top;           //texcoord.s
+
+                               //vertex 2 upper right
+                               result[ur_index].vertex_x       = right;                        //vertex.x
+                               result[ur_index].vertex_y       = top;                          //vertex.y
+                               result[ur_index].texture_x      = coords.right;         //texcoord.r
+                               result[ur_index].texture_y      = coords.top;           //texcoord.s
+
+                               //vertex 3 lower right
+                               result[lr_index].vertex_x       = right;                        //vertex.x
+                               result[lr_index].vertex_y       = bottom;                       //vertex.y
+                               result[lr_index].texture_x      = coords.right;         //texcoord.r
+                               result[lr_index].texture_y      = coords.bottom;        //texcoord.s
+
+                               //vertex 4 lower left
+                               result[ll_index].vertex_x       = left;                         //vertex.x
+                               result[ll_index].vertex_y       = bottom;                       //vertex.y
+                               result[ll_index].texture_x      = coords.left;          //texcoord.r
+                               result[ll_index].texture_y      = coords.bottom;        //texcoord.s
 
                                int bearingY = face_->glyph->metrics.horiBearingY >> 6;
 
@@ -194,7 +199,7 @@ public:
                                if (maxBearingY + maxProtrudeUnderY > maxHeight)
                                        maxHeight = maxBearingY + maxProtrudeUnderY;
 
-                               pos_x += face_->glyph->advance.x / 64.0f;
+                               pos_x += face_->glyph->advance.x / 64.0;
                                pos_x += tracking_;
                                previous = glyph_index;
                        }
@@ -206,18 +211,19 @@ public:
 
                if(normalize_)
                {
-                       float ratio_x = parent_width/(pos_x - x);
-                       float ratio_y = parent_height/(float)(maxHeight);
-                       for(index = 0; index < result.size(); index += 4)
+                       auto ratio_x = parent_width / (pos_x - x);
+                       auto ratio_y = parent_height / static_cast<double>(maxHeight);
+
+                       for (auto& coord : result)
                        {
-                               result[index + 0] *= ratio_x;
-                               result[index + 1] *= ratio_y;
+                               coord.vertex_x *= ratio_x;
+                               coord.vertex_y *= ratio_y;
                        }
                }
 
-               if(metrics != nullptr)
+               if (metrics != nullptr)
                {
-                       metrics->width = (int)(pos_x - x + 0.5f);
+                       metrics->width = (int)(pos_x - x + 0.5);
                        metrics->bearingY = maxBearingY;
                        metrics->height = maxHeight;
                        metrics->protrudeUnderY = maxProtrudeUnderY;
@@ -232,7 +238,7 @@ public:
                bool use_kerning = (face_->face_flags & FT_FACE_FLAG_KERNING) == FT_FACE_FLAG_KERNING;
                int index = 0;
                FT_UInt previous = 0;
-               float pos_x = 0;
+               double pos_x = 0;
 //             float pos_y = 0;
 
                auto end = str.end();
@@ -250,7 +256,7 @@ public:
                                        FT_Vector delta;
                                        FT_Get_Kerning(face_.get(), previous, glyph_index, FT_KERNING_DEFAULT, &delta);
 
-                                       pos_x += delta.x / 64.0f;
+                                       pos_x += delta.x / 64.0;
                                }
 
                                FT_Load_Glyph(face_.get(), glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL);
@@ -267,7 +273,7 @@ public:
                                if (result.bearingY + result.protrudeUnderY > result.height)
                                         result.height = result.bearingY + result.protrudeUnderY;
 
-                               pos_x += face_->glyph->advance.x / 64.0f;
+                               pos_x += face_->glyph->advance.x / 64.0;
                                previous = glyph_index;
                        }
                }
@@ -278,9 +284,9 @@ public:
 }; 
 
 texture_font::texture_font(texture_atlas& atlas, const text_info& info, bool normalize_coordinates) : impl_(new impl(atlas, info, normalize_coordinates)) {}
-void texture_font::load_glyphs(unicode_block range, const color<float>& col) { impl_->load_glyphs(range, col); }
+void texture_font::load_glyphs(unicode_block range, const color<double>& col) { impl_->load_glyphs(range, col); }
 void texture_font::set_tracking(int tracking) { impl_->set_tracking(tracking); }
-std::vector<float> texture_font::create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics) { return impl_->create_vertex_stream(str, x, y, parent_width, parent_height, metrics); }
+std::vector<frame_geometry::coord> texture_font::create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics) { return impl_->create_vertex_stream(str, x, y, parent_width, parent_height, metrics); }
 string_metrics texture_font::measure_string(const std::wstring& str) { return impl_->measure_string(str); }
 
 unicode_range get_range(unicode_block block)
index d3e52cebc2c585f0548cc7ce99f48767ecd5aa7b..a143eef05aa95151c6614ef22f3295adc3973a73 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "string_metrics.h"
 #include "text_info.h"
+#include "../../../frame/geometry.h"
 
 namespace caspar { namespace core { namespace text {
 
@@ -20,9 +21,9 @@ class texture_font
 
 public:
        texture_font(texture_atlas&, const text_info&, bool normalize_coordinates);
-       void load_glyphs(unicode_block block, const color<float>& col);
+       void load_glyphs(unicode_block block, const color<double>& col);
        void set_tracking(int tracking);
-       std::vector<float> create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics);
+       std::vector<frame_geometry::coord> create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics);
        string_metrics measure_string(const std::wstring& str);
 
 private:
index a4c3b469c49bebd568267f560eac8cda437572fc..abc520b5af5ffe4a830f7e901178ae0c652aff79 100644 (file)
@@ -385,6 +385,11 @@ bool CallCommand::DoExecute()
 
 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms;
 
+core::frame_transform MixerCommand::get_current_transform()
+{
+       return channel()->stage().get_current_transform(layer_index()).get();
+}
+
 bool MixerCommand::DoExecute()
 {      
        //Perform loading of the clip
@@ -398,6 +403,9 @@ bool MixerCommand::DoExecute()
 
                if(boost::iequals(parameters()[0], L"KEYER") || boost::iequals(parameters()[0], L"IS_KEY"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
+
                        bool value = boost::lexical_cast<int>(parameters().at(1));
                        transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
                        {
@@ -407,6 +415,9 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"OPACITY"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.opacity; });
+
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
 
@@ -418,8 +429,47 @@ bool MixerCommand::DoExecute()
                                return transform;                                       
                        }, duration, tween));
                }
-               else if(boost::iequals(parameters()[0], L"FILL") || boost::iequals(parameters()[0], L"FILL_RECT"))
+               else if (boost::iequals(parameters()[0], L"ANCHOR"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto transform = get_current_transform().image_transform;
+                               auto anchor = transform.anchor;
+                               SetReplyString(
+                                               L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n");
+                               return true;
+                       }
+
+                       int duration = parameters().size() > 3 ? boost::lexical_cast<int>(parameters()[3]) : 0;
+                       std::wstring tween = parameters().size() > 4 ? parameters()[4] : L"linear";
+                       double x = boost::lexical_cast<double>(parameters().at(1));
+                       double y = boost::lexical_cast<double>(parameters().at(2));
+
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) mutable -> frame_transform
+                       {
+                               transform.image_transform.anchor[0] = x;
+                               transform.image_transform.anchor[1] = y;
+                               return transform;
+                       }, duration, tween));
+               }
+               else if (boost::iequals(parameters()[0], L"FILL") || boost::iequals(parameters()[0], L"FILL_RECT"))
+               {
+                       if (parameters().size() == 1)
+                       {
+                               auto transform = get_current_transform().image_transform;
+                               auto translation = transform.fill_translation;
+                               auto scale = transform.fill_scale;
+                               SetReplyString(
+                                               L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(translation[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(translation[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(scale[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n");
+                               return true;
+                       }
+
                        int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
                        std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
                        double x        = boost::lexical_cast<double>(parameters().at(1));
@@ -438,6 +488,20 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"CLIP") || boost::iequals(parameters()[0], L"CLIP_RECT"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto transform = get_current_transform().image_transform;
+                               auto translation = transform.clip_translation;
+                               auto scale = transform.clip_scale;
+                               SetReplyString(
+                                               L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(translation[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(translation[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(scale[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n");
+                               return true;
+                       }
+
                        int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
                        std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
                        double x        = boost::lexical_cast<double>(parameters().at(1));
@@ -454,7 +518,79 @@ bool MixerCommand::DoExecute()
                                return transform;
                        }, duration, tween));
                }
-               else if(boost::iequals(parameters()[0], L"GRID"))
+               else if (boost::iequals(parameters()[0], L"CROP"))
+               {
+                       if (parameters().size() == 1)
+                       {
+                               auto crop = get_current_transform().image_transform.crop;
+                               SetReplyString(
+                                       L"201 MIXER OK\r\n"
+                                       + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
+                                       + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
+                                       + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
+                                       + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n");
+                               return true;
+                       }
+
+                       int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
+                       std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
+                       double ul_x = boost::lexical_cast<double>(parameters().at(1));
+                       double ul_y = boost::lexical_cast<double>(parameters().at(2));
+                       double lr_x = boost::lexical_cast<double>(parameters().at(3));
+                       double lr_y = boost::lexical_cast<double>(parameters().at(4));
+
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+                       {
+                               transform.image_transform.crop.ul[0] = ul_x;
+                               transform.image_transform.crop.ul[1] = ul_y;
+                               transform.image_transform.crop.lr[0] = lr_x;
+                               transform.image_transform.crop.lr[1] = lr_y;
+                               return transform;
+                       }, duration, tween));
+               }
+               else if (boost::iequals(parameters()[0], L"PERSPECTIVE"))
+               {
+                       if (parameters().size() == 1)
+                       {
+                               auto perspective = get_current_transform().image_transform.perspective;
+                               SetReplyString(
+                                               L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n");
+                               return true;
+                       }
+
+                       int duration = parameters().size() > 9 ? boost::lexical_cast<int>(parameters()[9]) : 0;
+                       std::wstring tween = parameters().size() > 10 ? parameters()[10] : L"linear";
+                       double ul_x = boost::lexical_cast<double>(parameters().at(1));
+                       double ul_y = boost::lexical_cast<double>(parameters().at(2));
+                       double ur_x = boost::lexical_cast<double>(parameters().at(3));
+                       double ur_y = boost::lexical_cast<double>(parameters().at(4));
+                       double lr_x = boost::lexical_cast<double>(parameters().at(5));
+                       double lr_y = boost::lexical_cast<double>(parameters().at(6));
+                       double ll_x = boost::lexical_cast<double>(parameters().at(7));
+                       double ll_y = boost::lexical_cast<double>(parameters().at(8));
+
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+                       {
+                               transform.image_transform.perspective.ul[0] = ul_x;
+                               transform.image_transform.perspective.ul[1] = ul_y;
+                               transform.image_transform.perspective.ur[0] = ur_x;
+                               transform.image_transform.perspective.ur[1] = ur_y;
+                               transform.image_transform.perspective.lr[0] = lr_x;
+                               transform.image_transform.perspective.lr[1] = lr_y;
+                               transform.image_transform.perspective.ll[0] = ll_x;
+                               transform.image_transform.perspective.ll[1] = ll_y;
+                               return transform;
+                       }, duration, tween));
+               }
+               else if (boost::iequals(parameters()[0], L"GRID"))
                {
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
@@ -482,17 +618,37 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"BLEND"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto blend_mode = channel()->mixer().get_blend_mode(layer_index());
+                               SetReplyString(L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(get_blend_mode(blend_mode))
+                                               + L"\r\n");
+                               return true;
+                       }
+
                        auto blend_str = parameters().at(1);                                                            
                        int layer = layer_index();
                        channel()->mixer().set_blend_mode(layer, get_blend_mode(blend_str));    
                }
                else if(boost::iequals(parameters()[0], L"MASTERVOLUME"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto volume = channel()->mixer().get_master_volume();
+                               SetReplyString(L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(volume)+L"\r\n");
+                               return true;
+                       }
+
                        float master_volume = boost::lexical_cast<float>(parameters().at(1));
                        channel()->mixer().set_master_volume(master_volume);
                }
                else if(boost::iequals(parameters()[0], L"BRIGHTNESS"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.brightness; });
+
                        auto value = boost::lexical_cast<double>(parameters().at(1));
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
@@ -504,6 +660,9 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"SATURATION"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.saturation; });
+
                        auto value = boost::lexical_cast<double>(parameters().at(1));
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
@@ -513,8 +672,11 @@ bool MixerCommand::DoExecute()
                                return transform;
                        }, duration, tween));   
                }
-               else if(parameters()[0] == L"CONTRAST")
+               else if (boost::iequals(parameters()[0], L"CONTRAST"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.contrast; });
+
                        auto value = boost::lexical_cast<double>(parameters().at(1));
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
@@ -524,8 +686,36 @@ bool MixerCommand::DoExecute()
                                return transform;
                        }, duration, tween));   
                }
-               else if(boost::iequals(parameters()[0], L"LEVELS"))
+               else if (boost::iequals(parameters()[0], L"ROTATION"))
+               {
+                       static const double PI = 3.141592653589793;
+
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; });
+
+                       auto value = boost::lexical_cast<double>(parameters().at(1)) * PI / 180.0;
+                       int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
+                       std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+                       {
+                               transform.image_transform.angle = value;
+                               return transform;
+                       }, duration, tween));
+               }
+               else if (boost::iequals(parameters()[0], L"LEVELS"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto levels = get_current_transform().image_transform.levels;
+                               SetReplyString(L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
+                                               + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
+                                               + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
+                                               + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
+                                               + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n");
+                               return true;
+                       }
+
                        levels value;
                        value.min_input  = boost::lexical_cast<double>(parameters().at(1));
                        value.max_input  = boost::lexical_cast<double>(parameters().at(2));
@@ -543,6 +733,9 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"VOLUME"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.audio_transform.volume; });
+
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
                        double value = boost::lexical_cast<double>(parameters()[1]);
@@ -1026,7 +1219,7 @@ bool CGCommand::DoExecuteAdd() {
        {       //read data
                const std::wstring& dataString = parameters()[dataIndex];
 
-               if(dataString[0] == L'<') //the data is an XML-string
+               if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
                        pDataString = dataString.c_str();
                else 
                {
@@ -1169,9 +1362,9 @@ bool CGCommand::DoExecuteUpdate()
                }
                                                
                std::wstring dataString = parameters().at(2);                           
-               if(dataString.at(0) != L'<')
+               if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
                {
-                       //The data is not an XML-string, it must be a filename
+                       //The data is not XML or Json, it must be a filename
                        std::wstring filename = env::data_folder();
                        filename.append(dataString);
                        filename.append(L".ftd");
index 30534d9202493117b38478786a146f5830919dad..f72cba10f63f58d631612a87be9d605ce3736869 100644 (file)
@@ -64,9 +64,19 @@ class MixerCommand : public AMCPChannelCommandBase<1>
 public:
        MixerCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
        {}
-
 private:
        std::wstring print() const { return L"MixerCommand";}
+       core::frame_transform get_current_transform();
+       template<typename Func>
+       bool reply_value(const Func& extractor)
+       {
+               auto value = extractor(get_current_transform());
+
+               SetReplyString(L"201 MIXER OK\r\n"
+                       + boost::lexical_cast<std::wstring>(value) + L"\r\n");
+
+               return true;
+       }
        bool DoExecute();
 };