X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=accelerator%2Fogl%2Fimage%2Fimage_kernel.cpp;h=0bb2643c7ec47b2a48917bbd325f172c6456a141;hb=44f608adff3740ae911192caa484ee06883913eb;hp=482d486b14c0b65433b7caf8ba9fd3e7cc728fbd;hpb=26611869a465a0187490b5322dc9e1e3bc8afc10;p=casparcg diff --git a/accelerator/ogl/image/image_kernel.cpp b/accelerator/ogl/image/image_kernel.cpp index 482d486b1..0bb2643c7 100644 --- a/accelerator/ogl/image/image_kernel.cpp +++ b/accelerator/ogl/image/image_kernel.cpp @@ -19,7 +19,7 @@ * Author: Robert Nagy, ronag89@gmail.com */ -#include "../../stdafx.h" +#include "../../StdAfx.h" #include "image_kernel.h" @@ -38,10 +38,86 @@ #include #include +#include #include +#include + +#include 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& 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, @@ -60,9 +136,9 @@ struct image_kernel::impl spl::shared_ptr shader_; bool blend_modes_; - impl(const spl::shared_ptr& ogl) + impl(const spl::shared_ptr& ogl, bool blend_modes_wanted) : ogl_(ogl) - , shader_(ogl_->invoke([&]{return get_image_shader(blend_modes_);})) + , shader_(ogl_->invoke([&]{return get_image_shader(ogl, blend_modes_, blend_modes_wanted); })) { } @@ -77,17 +153,92 @@ 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> 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& 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) params.textures[n]->bind(n); if(params.local_key) - params.local_key->bind(texture_id::local_key); + params.local_key->bind(static_cast(texture_id::local_key)); if(params.layer_key) - params.layer_key->bind(texture_id::layer_key); + params.layer_key->bind(static_cast(texture_id::layer_key)); // Setup shader @@ -97,7 +248,7 @@ struct image_kernel::impl shader_->set("plane[1]", texture_id::plane1); shader_->set("plane[2]", texture_id::plane2); shader_->set("plane[3]", texture_id::plane3); - for(int n = 0; n < params.textures.size(); ++n) + for (int n = 0; n < params.textures.size(); ++n) shader_->set("plane_size[" + boost::lexical_cast(n) + "]", static_cast(params.textures[n]->width()), static_cast(params.textures[n]->height())); @@ -105,11 +256,18 @@ struct image_kernel::impl shader_->set("local_key", texture_id::local_key); shader_->set("layer_key", texture_id::layer_key); shader_->set("is_hd", params.pix_desc.planes.at(0).height > 700 ? 1 : 0); - shader_->set("has_local_key", params.local_key); - shader_->set("has_layer_key", params.layer_key); - shader_->set("pixel_format", params.pix_desc.format.value()); + shader_->set("has_local_key", static_cast(params.local_key)); + shader_->set("has_layer_key", static_cast(params.layer_key)); + shader_->set("pixel_format", params.pix_desc.format); shader_->set("opacity", params.transform.is_key ? 1.0 : params.transform.opacity); - + + if (params.transform.chroma.key != core::chroma::type::none) + { + shader_->set("chroma", true); + shader_->set("chroma_mode", static_cast(params.transform.chroma.key)); + shader_->set("chroma_blend", params.transform.chroma.threshold, params.transform.chroma.softness); + shader_->set("chroma_spill", params.transform.chroma.spill); + } // Setup blend_func @@ -118,17 +276,17 @@ struct image_kernel::impl if(blend_modes_) { - params.background->bind(texture_id::background); + params.background->bind(static_cast(texture_id::background)); shader_->set("background", texture_id::background); - shader_->set("blend_mode", params.blend_mode.value()); - shader_->set("keyer", params.keyer.value()); + shader_->set("blend_mode", params.blend_mode); + shader_->set("keyer", params.keyer); } else { GL(glEnable(GL_BLEND)); - switch(params.keyer.value()) + switch(params.keyer) { case keyer::additive: GL(glBlendFunc(GL_ONE, GL_ONE)); @@ -141,11 +299,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 +315,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 +330,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)); @@ -185,6 +343,7 @@ struct image_kernel::impl // Setup drawing area GL(glViewport(0, 0, params.background->width(), params.background->height())); + glDisable(GL_DEPTH_TEST); auto m_p = params.transform.clip_translation; auto m_s = params.transform.clip_scale; @@ -192,21 +351,18 @@ struct image_kernel::impl bool scissor = m_p[0] > std::numeric_limits::epsilon() || m_p[1] > std::numeric_limits::epsilon() || m_s[0] < (1.0 - std::numeric_limits::epsilon()) || m_s[1] < (1.0 - std::numeric_limits::epsilon()); - if(scissor) + if (scissor) { double w = static_cast(params.background->width()); double h = static_cast(params.background->height()); GL(glEnable(GL_SCISSOR_TEST)); - glScissor(static_cast(m_p[0]*w), static_cast(m_p[1]*h), static_cast(m_s[0]*w), static_cast(m_s[1]*h)); + glScissor(static_cast(m_p[0] * w), static_cast(m_p[1] * h), std::max(0, static_cast(m_s[0] * w)), std::max(0, static_cast(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. @@ -214,32 +370,90 @@ struct image_kernel::impl } params.background->attach(); - - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glTranslated(f_p[0], f_p[1], 0); - glScaled(f_s[0], f_s[1], 1); - - // Draw - glBegin(GL_QUADS); - glMultiTexCoord2d(GL_TEXTURE0, 0.0, 0.0); glVertex2d(0, 0); - glMultiTexCoord2d(GL_TEXTURE0, 1.0, 0.0); glVertex2d(1, 0); - glMultiTexCoord2d(GL_TEXTURE0, 1.0, 1.0); glVertex2d(1, 1); - glMultiTexCoord2d(GL_TEXTURE0, 0.0, 1.0); glVertex2d(0, 1); - glEnd(); - glPopMatrix(); + + // 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 q_values = { ulq, urq, lrq, llq }; + + corner = 0; + for (auto& coord : coords) + { + coord.texture_q = q_values[corner]; + coord.texture_x *= q_values[corner]; + coord.texture_y *= q_values[corner]; + + if (++corner == 4) + corner = 0; + } + } + + // 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(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(coords.size())); //each vertex is four doubles. + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + } + break; + default: + break; + } // Cleanup - GL(glDisable(GL_SCISSOR_TEST)); GL(glDisable(GL_POLYGON_STIPPLE)); GL(glDisable(GL_BLEND)); } }; -image_kernel::image_kernel(const spl::shared_ptr& ogl) : impl_(new impl(ogl)){} +image_kernel::image_kernel(const spl::shared_ptr& ogl, bool blend_modes_wanted) : impl_(new impl(ogl, blend_modes_wanted)){} image_kernel::~image_kernel(){} void image_kernel::draw(const draw_params& params){impl_->draw(params);} bool image_kernel::has_blend_modes() const{return impl_->blend_modes_;} -}}} \ No newline at end of file +}}}