* 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
#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,
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)
// 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);
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);
// 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));
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.
}
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));
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
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
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);
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);
}
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()));
{
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));
}
{
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));
}
{
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));
}
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));
}
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);
}
#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);
is_key |= other.is_key;
is_mix |= other.is_mix;
is_still |= other.is_still;
+
return *this;
}
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);
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)
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);
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;
}
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();
+}
+
+}}}
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;
// 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;
}
};
-}}
+namespace detail {
+
+void set_current_aspect_ratio(double aspect_ratio);
+double get_current_aspect_ratio();
+
+}}}
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;
}
#pragma once
-#include <memory>
+#include <common/memory.h>
+
#include <vector>
namespace caspar { namespace core {
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
master_volume_ = volume;
}
+ float get_master_volume()
+ {
+ return master_volume_;
+ }
+
audio_buffer mix(const video_format_desc& format_desc)
{
if(format_desc_ != format_desc)
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); }
}}
audio_buffer operator()(const struct video_format_desc& format_desc);
void set_master_volume(float volume);
+ float get_master_volume();
// frame_visitor
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
};
blend_mode get_blend_mode(const std::wstring& str);
+std::wstring get_blend_mode(blend_mode mode);
}}
\ No newline at end of file
, 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)
{
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);
}, 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([=]
}, 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());
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);}
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);
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()
{
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();
};
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;
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
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;
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"));
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([=]
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); }
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();
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)
draw_frame receive_impl()
{
if (dirty_)
+ {
generate_frame();
+ dirty_ = false;
+ }
return 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);
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) {}
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;
}
//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)
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(); }
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();
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;
void set_tracking(int tracking)
{
- tracking_ = size_ * tracking / 1000.0f;
+ tracking_ = size_ * tracking / 1000.0;
}
int count_glyphs_in_range(unicode_block block)
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;
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;
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;
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;
}
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;
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();
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);
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;
}
}
};
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)
#include "string_metrics.h"
#include "text_info.h"
+#include "../../../frame/geometry.h"
namespace caspar { namespace core { namespace text {
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:
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
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
{
}
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";
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));
}
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));
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";
}
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";
}
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";
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";
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));
}
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]);
{ //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
{
}
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");
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();
};