From: Niklas P Andersson Date: Mon, 14 Dec 2015 11:44:05 +0000 (+0100) Subject: * support for cornerpinning in psd-producer via vector-masks X-Git-Tag: 2.1.0_Beta1~141^2~1 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=935aecd22040939ff884ec0595c596d5508106db;p=casparcg * support for cornerpinning in psd-producer via vector-masks * support for rotation and non-uniform scaling of dynamic text-layers in psd-producer * support for per layer blend modes in psd-producer --- diff --git a/core/producer/text/text_producer.cpp b/core/producer/text/text_producer.cpp index 35c2a0456..e00d828d6 100644 --- a/core/producer/text/text_producer.cpp +++ b/core/producer/text/text_producer.cpp @@ -136,6 +136,9 @@ struct text_producer::impl variable_impl text_; std::shared_ptr text_subscription_; variable_impl tracking_; + variable_impl scale_x_; + variable_impl scale_y_; + variable_impl shear_; std::shared_ptr tracking_subscription_; variable_impl current_bearing_y_; variable_impl current_protrude_under_y_; @@ -158,6 +161,9 @@ public: font_.load_glyphs(text::unicode_block::Latin_Extended_A, text_info.color); tracking_.value().set(text_info.tracking); + scale_x_.value().set(text_info.scale_x); + scale_y_.value().set(text_info.scale_y); + shear_.value().set(text_info.shear); text_subscription_ = text_.value().on_change([this]() { dirty_ = true; @@ -186,13 +192,13 @@ public: text::string_metrics metrics; font_.set_tracking(static_cast(tracking_.value().get())); - auto vertex_stream = font_.create_vertex_stream(text_.value().get(), x_, y_, parent_width_, parent_height_, &metrics); + auto vertex_stream = font_.create_vertex_stream(text_.value().get(), x_, y_, parent_width_, parent_height_, &metrics, shear_.value().get()); auto frame = frame_factory_->create_frame(vertex_stream.data(), pfd, core::audio_channel_layout::invalid()); memcpy(frame.image_data().data(), atlas_.data(), frame.image_data().size()); frame.set_geometry(frame_geometry(frame_geometry::geometry_type::quad_list, std::move(vertex_stream))); - this->constraints_.width.set(metrics.width); - this->constraints_.height.set(metrics.height); + this->constraints_.width.set(metrics.width * this->scale_x_.value().get()); + this->constraints_.height.set(metrics.height * this->scale_y_.value().get()); current_bearing_y_.value().set(metrics.bearingY); current_protrude_under_y_.value().set(metrics.protrudeUnderY); frame_ = core::draw_frame(std::move(frame)); diff --git a/core/producer/text/text_producer.h b/core/producer/text/text_producer.h index 93c0474d1..05069cec5 100644 --- a/core/producer/text/text_producer.h +++ b/core/producer/text/text_producer.h @@ -45,7 +45,8 @@ namespace caspar { namespace core { class text_producer : public frame_producer_base { public: - text_producer(const spl::shared_ptr& frame_factory, int x, int y, const std::wstring& str, text::text_info& text_info, long parent_width, long parent_height, bool standalone); + text_producer(const spl::shared_ptr& frame_factory, int x, int y, const std::wstring& str, + text::text_info& text_info, long parent_width, long parent_height, bool standalone); static spl::shared_ptr create(const spl::shared_ptr& frame_factory, int x, int y, const std::wstring& str, text::text_info& text_info, long parent_width, long parent_height, bool standalone = false); draw_frame receive_impl() override; diff --git a/core/producer/text/utils/text_info.h b/core/producer/text/utils/text_info.h index 0cb8e050f..8075eee12 100644 --- a/core/producer/text/utils/text_info.h +++ b/core/producer/text/utils/text_info.h @@ -18,6 +18,9 @@ namespace caspar { namespace core { namespace text { //text::color shadow_color; int baseline_shift = 0; int tracking = 0; + double scale_x = 1.0; + double scale_y = 1.0; + double shear = 0; }; }}} diff --git a/core/producer/text/utils/texture_font.cpp b/core/producer/text/utils/texture_font.cpp index 879f3bfda..c0e371175 100644 --- a/core/producer/text/utils/texture_font.cpp +++ b/core/producer/text/utils/texture_font.cpp @@ -105,7 +105,7 @@ public: } } - std::vector create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics) + std::vector create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics, double shear) { //TODO: detect glyphs that aren't in the atlas and load them (and maybe that entire unicode_block on the fly @@ -153,28 +153,28 @@ public: auto ll_index = ul_index + 3; //vertex 1 upper left - result[ul_index].vertex_x = left; //vertex.x + result[ul_index].vertex_x = left + top; //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_x = right + top; //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 + result[lr_index].vertex_x = right + bottom; //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 + result[ll_index].vertex_x = left + bottom; //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; @@ -286,7 +286,7 @@ public: texture_font::texture_font(texture_atlas& atlas, const text_info& info, bool normalize_coordinates) : impl_(new impl(atlas, info, normalize_coordinates)) {} void texture_font::load_glyphs(unicode_block range, const color& col) { impl_->load_glyphs(range, col); } void texture_font::set_tracking(int tracking) { impl_->set_tracking(tracking); } -std::vector 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 texture_font::create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics, double shear) { return impl_->create_vertex_stream(str, x, y, parent_width, parent_height, metrics, shear); } string_metrics texture_font::measure_string(const std::wstring& str) { return impl_->measure_string(str); } std::wstring texture_font::get_name() const { return impl_->get_name(); } double texture_font::get_size() const { return impl_->get_size(); } diff --git a/core/producer/text/utils/texture_font.h b/core/producer/text/utils/texture_font.h index 8adb06c11..89662c134 100644 --- a/core/producer/text/utils/texture_font.h +++ b/core/producer/text/utils/texture_font.h @@ -23,7 +23,7 @@ public: texture_font(texture_atlas&, const text_info&, bool normalize_coordinates); void load_glyphs(unicode_block block, const color& col); void set_tracking(int tracking); - std::vector create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics); + std::vector create_vertex_stream(const std::wstring& str, int x, int y, int parent_width, int parent_height, string_metrics* metrics, double shear = 0.0); string_metrics measure_string(const std::wstring& str); std::wstring get_name() const; double get_size() const; diff --git a/modules/psd/layer.cpp b/modules/psd/layer.cpp index d35c8583c..b74350210 100644 --- a/modules/psd/layer.cpp +++ b/modules/psd/layer.cpp @@ -46,7 +46,7 @@ void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream) break; case 20: - case 36: //discard total user mask data + case 36: rect_.location.y = stream.read_long(); rect_.location.x = stream.read_long(); rect_.size.height = stream.read_long() - rect_.location.y; @@ -78,8 +78,8 @@ void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream) bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream& stream, int doc_width, int doc_height) { - typedef std::pair path_point; - bool bFail = false; + std::vector> knots; + bool smooth_curve = false; stream.read_long(); // version this->flags_ = static_cast(stream.read_long()); // flags @@ -87,8 +87,6 @@ bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream& auto position = stream.current_position(); - std::vector knots; - const int SELECTOR_SIZE = 2; const int PATH_POINT_SIZE = 4 + 4; const int PATH_POINT_RECORD_SIZE = SELECTOR_SIZE + (3 * PATH_POINT_SIZE); @@ -100,21 +98,22 @@ bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream& { auto p_y = stream.read_long(); auto p_x = stream.read_long(); - path_point cp_prev(p_x, p_y); + point prev{ static_cast(p_x), static_cast(p_y) }; auto a_y = stream.read_long(); auto a_x = stream.read_long(); - path_point anchor(a_x, a_y); + point anchor{ static_cast(a_x), static_cast(a_y) }; auto n_y = stream.read_long(); auto n_x = stream.read_long(); - path_point cp_next(n_x, n_y); + point next{ static_cast(n_x), static_cast(n_y) }; - if (anchor == cp_prev && anchor == cp_next) + if (anchor == prev && anchor == next) knots.push_back(anchor); - else - { //we can't handle smooth curves yet - bFail = true; + else + { + //note that we've got a smooth curve, but continue to iterate through the data + smooth_curve = true; } } @@ -122,37 +121,46 @@ bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream& stream.set_position(position + offset); } - if ((knots.size() != 4) || (!(knots[0].first == knots[3].first && knots[1].first == knots[2].first && knots[0].second == knots[1].second && knots[2].second == knots[3].second))) - bFail = true; - - if (bFail) { + if (smooth_curve || knots.size() != 4) //we can't handle smooth-curves yet and we only support quad-gons + { rect_.clear(); - flags_ = static_cast(flags::unsupported); + flags_ = static_cast(flags::unsupported | flags::disabled); + return false; + } + + //the path_points are given in fixed-point 8.24 as a ratio with regards to the width/height of the document. we need to divide by 16777215.0f to get the real ratio. + float x_ratio = doc_width / 16777215.0f; + float y_ratio = doc_height / 16777215.0f; + rect_.clear(); + knots_.clear(); + + //is it an orthogonal rectangle + if (knots[0].x == knots[3].x && knots[1].x == knots[2].x && knots[0].y == knots[1].y && knots[2].y == knots[3].y) + { + rect_.location.x = static_cast(knots[0].x * x_ratio + 0.5f); //add .5 to get propper rounding when converting to integer + rect_.location.y = static_cast(knots[0].y * y_ratio + 0.5f); //add .5 to get propper rounding when converting to integer + rect_.size.width = static_cast(knots[1].x * x_ratio + 0.5f) - rect_.location.x; //add .5 to get propper rounding when converting to integer + rect_.size.height = static_cast(knots[2].y * y_ratio + 0.5f) - rect_.location.y; //add .5 to get propper rounding when converting to integer } - else + else //it's could be any kind of quad-gon { - //the path_points are given in fixed-point 8.24 as a ratio with regards to the width/height of the document. we need to divide by 16777215.0f to get the real ratio. - float x_ratio = doc_width / 16777215.0f; - float y_ratio = doc_height / 16777215.0f; - rect_.location.x = static_cast(knots[0].first * x_ratio + 0.5f); //add .5 to get propper rounding when converting to integer - rect_.location.y = static_cast(knots[0].second * y_ratio + 0.5f); //add .5 to get propper rounding when converting to integer - rect_.size.width = static_cast(knots[1].first * x_ratio + 0.5f) - rect_.location.x; //add .5 to get propper rounding when converting to integer - rect_.size.height = static_cast(knots[2].second * y_ratio + 0.5f) - rect_.location.y; //add .5 to get propper rounding when converting to integer + for (auto& k : knots) + knots_.push_back(psd::point{static_cast(k.x * x_ratio + 0.5f), static_cast(k.y * y_ratio + 0.5f)}); } - return !bFail; + return true; } struct layer::impl { friend class layer; - impl() : blend_mode_(blend_mode::InvalidBlendMode), layer_type_(layer_type::content), link_group_id_(0), opacity_(255), sheet_color_(0), baseClipping_(false), flags_(0), protection_flags_(0), masks_count_(0), text_scale_(1.0f), tags_(layer_tag::none) + impl() : blend_mode_(caspar::core::blend_mode::normal), layer_type_(layer_type::content), link_group_id_(0), opacity_(255), sheet_color_(0), baseClipping_(false), flags_(0), protection_flags_(0), masks_count_(0), scale_{ 1.0, 1.0 }, angle_(0), shear_(0), tags_(layer_tag::none) {} private: std::vector channels_; - blend_mode blend_mode_; + caspar::core::blend_mode blend_mode_; layer_type layer_type_; int link_group_id_; int opacity_; @@ -162,7 +170,10 @@ private: std::uint32_t protection_flags_; std::wstring name_; int masks_count_; - double text_scale_; + psd::point text_pos_; + psd::point scale_; + double angle_; + double shear_; layer::mask_info mask_; @@ -285,6 +296,7 @@ public: case 'lnk3': //linked layer break; + case 'vsms': case 'vmsk': mask_.read_vector_mask_data(length, stream, doc.width(), doc.height()); break; @@ -388,12 +400,11 @@ public: auto xy = stream.read_double(); auto yx = stream.read_double(); auto yy = stream.read_double(); - stream.read_double(); // tx - stream.read_double(); // ty - if(xx != yy || (xy != 0 && yx != 0)) - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Rotation and non-uniform scaling of dynamic textfields is not supported yet")); + auto tx = stream.read_double(); // tx + auto ty = stream.read_double(); // ty - text_scale_ = xx; + text_pos_.x = tx; + text_pos_.y = ty; if(stream.read_short() != 50) //"text version" should be 50 CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid text version")); @@ -420,10 +431,45 @@ public: descriptor warp_descriptor(L"warp"); warp_descriptor.populate(stream); - stream.read_double(); // w_top - stream.read_double(); // w_left + stream.read_double(); // w_left + stream.read_double(); // w_top stream.read_double(); // w_right stream.read_double(); // w_bottom + + + //extract scale, angle and shear factor from transformation matrix + const double PI = 3.141592653589793; + auto angle = atan2(xy, xx); + + auto c = cos(angle); + auto s = sin(angle); + auto scale_x = (abs(c) > 0.1) ? xx / c : xy / s; + + if (xx / scale_x < 0) { //fel kvadrant + angle += PI; + c = cos(angle); + s = sin(angle); + scale_x = (abs(c) > 0.1) ? xx / c : xy / s; + } + + auto shear_factor = (yx*c + yy*s) / (yy*c - yx * s); + auto scale_y = 1.0; + if (abs(shear_factor) < 0.0001 || isnan(shear_factor)) { + shear_factor = 0; + scale_y = (abs(c) > 0.1) ? yy / c : yx / -s; + } + else { + scale_y = yx / (c*shear_factor - s); + } + + scale_.x = scale_x; + scale_.y = scale_y; + angle_ = angle * 180 / PI; + shear_ = shear_factor; + + + + } //TODO: implement @@ -591,14 +637,19 @@ void layer::read_channel_data(bigendian_file_input_stream& stream) { impl_->read const std::wstring& layer::name() const { return impl_->name_; } int layer::opacity() const { return impl_->opacity_; } +caspar::core::blend_mode layer::blend_mode() const { return impl_->blend_mode_; } int layer::sheet_color() const { return impl_->sheet_color_; } bool layer::is_visible() { return (impl_->flags_ & 2) == 0; } //the (PSD file-format) documentation is is saying the opposite but what the heck bool layer::is_position_protected() { return (impl_->protection_flags_& 4) == 4; } const layer::mask_info& layer::mask() const { return impl_->mask_; } + +const psd::point& layer::text_pos() const { return impl_->text_pos_; } +const psd::point& layer::scale() const { return impl_->scale_; } +const double layer::angle() const { return impl_->angle_; } +const double layer::shear() const { return impl_->shear_; } -double layer::text_scale() const { return impl_->text_scale_; } bool layer::is_text() const { return !impl_->text_layer_info_.empty(); } const boost::property_tree::wptree& layer::text_data() const { return impl_->text_layer_info_; } @@ -621,6 +672,8 @@ bool layer::is_static() const { return (impl_->tags_ & layer_tag::rasterized) == bool layer::is_movable() const { return (impl_->tags_ & layer_tag::moveable) == layer_tag::moveable; } bool layer::is_resizable() const { return (impl_->tags_ & layer_tag::resizable) == layer_tag::resizable; } bool layer::is_placeholder() const { return (impl_->tags_ & layer_tag::placeholder) == layer_tag::placeholder; } +bool layer::is_cornerpin() const { return (impl_->tags_ & layer_tag::cornerpin) == layer_tag::cornerpin; } + layer_tag layer::tags() const { return impl_->tags_; } } //namespace psd diff --git a/modules/psd/layer.h b/modules/psd/layer.h index 7f2ca628c..2919ad682 100644 --- a/modules/psd/layer.h +++ b/modules/psd/layer.h @@ -48,20 +48,22 @@ public: class vector_mask_info { + std::uint8_t flags_; + psd::rect rect_; + std::vector> knots_; + + friend class layer::mask_info; + bool populate(int length, bigendian_file_input_stream& stream, int doc_width, int doc_height); + + public: enum class flags { + none = 0, inverted = 1, unlinked = 2, disabled = 4, unsupported = 128 }; - std::uint8_t flags_; - psd::rect rect_; - - friend class layer::mask_info; - bool populate(int length, bigendian_file_input_stream& stream, int doc_width, int doc_height); - - public: vector_mask_info() : flags_(0) {} @@ -70,9 +72,10 @@ public: bool inverted() const { return (flags_ & static_cast(flags::inverted)) == static_cast(flags::inverted); } bool unsupported() const { return (flags_ & static_cast(flags::unsupported)) == static_cast(flags::unsupported); } - bool empty() { return rect_.empty(); } + bool empty() { return rect_.empty() && knots_.empty(); } const psd::rect& rect() const { return rect_; } + const std::vector>& knots() const { return knots_; } }; class mask_info @@ -90,7 +93,7 @@ public: image8bit_ptr bitmap_; std::uint8_t default_value_; std::uint8_t flags_; - psd::rect rect_; + psd::rect rect_; std::unique_ptr vector_mask_; std::unique_ptr total_mask_; @@ -124,13 +127,18 @@ public: const std::wstring& name() const; int opacity() const; + caspar::core::blend_mode blend_mode() const; int sheet_color() const; bool is_visible(); bool is_position_protected(); const mask_info& mask() const; - double text_scale() const; + const psd::point& text_pos() const; + const psd::point& scale() const; + const double angle() const; + const double shear() const; + bool is_text() const; const boost::property_tree::wptree& text_data() const; @@ -154,8 +162,11 @@ public: bool is_movable() const; bool is_resizable() const; bool is_placeholder() const; + bool is_cornerpin() const; layer_tag tags() const; }; +ENUM_ENABLE_BITWISE(layer::vector_mask_info::flags); + } //namespace psd } //namespace caspar diff --git a/modules/psd/misc.cpp b/modules/psd/misc.cpp index 829a9ee52..ed098a599 100644 --- a/modules/psd/misc.cpp +++ b/modules/psd/misc.cpp @@ -49,58 +49,47 @@ std::wstring layer_type_to_string(layer_type b) } -blend_mode int_to_blend_mode(std::uint32_t x) +caspar::core::blend_mode int_to_blend_mode(std::uint32_t x) { - blend_mode mode = static_cast(x); - - switch (mode) + switch (x) { - case blend_mode::Normal: - case blend_mode::Darken: - case blend_mode::Lighten: - case blend_mode::Hue: - case blend_mode::Saturation: - case blend_mode::Color: - case blend_mode::Luminosity: - case blend_mode::Multiply: - case blend_mode::Screen: - case blend_mode::Dissolve: - case blend_mode::Overlay: - case blend_mode::HardLight: - case blend_mode::SoftLight: - case blend_mode::Difference: - case blend_mode::Exclusion: - case blend_mode::ColorDodge: - case blend_mode::ColorBurn: - return mode; - default: - return blend_mode::InvalidBlendMode; + case 'norm': + return core::blend_mode::normal; + case 'dark': + return core::blend_mode::darken; + case 'lite': + return core::blend_mode::lighten; + case'hue ': + return core::blend_mode::contrast; + case 'sat ': + return core::blend_mode::saturation; + case 'colr': + return core::blend_mode::color; + case 'lum ': + return core::blend_mode::luminosity; + case 'mul ': + return core::blend_mode::multiply; + case 'scrn': + return core::blend_mode::screen; + case 'diss': //no support for the 'dissove' blend mode atm + return core::blend_mode::normal; + case 'over': + return core::blend_mode::overlay; + case 'hLit': + return core::blend_mode::hard_light; + case 'sLit': + return core::blend_mode::soft_light; + case 'diff': + return core::blend_mode::difference; + case 'smud': + return core::blend_mode::exclusion; + case 'div ': + return core::blend_mode::color_dodge; + case 'idiv': + return core::blend_mode::color_burn; } -} -std::wstring blend_mode_to_string(blend_mode b) -{ - switch(b) - { - case blend_mode::Normal: return L"Normal"; - case blend_mode::Darken: return L"Darken"; - case blend_mode::Lighten: return L"Lighten"; - case blend_mode::Hue: return L"Hue"; - case blend_mode::Saturation: return L"Saturation"; - case blend_mode::Color: return L"Color"; - case blend_mode::Luminosity: return L"Luminosity"; - case blend_mode::Multiply: return L"Multiply"; - case blend_mode::Screen: return L"Screen"; - case blend_mode::Dissolve: return L"Dissolve"; - case blend_mode::Overlay: return L"Overlay"; - case blend_mode::HardLight: return L"HardLight"; - case blend_mode::SoftLight: return L"SoftLight"; - case blend_mode::Difference: return L"Difference"; - case blend_mode::Exclusion: return L"Exclusion"; - case blend_mode::ColorDodge: return L"ColorDodge"; - case blend_mode::ColorBurn: return L"ColorBurn"; - default: return L"Invalid"; - } + return core::blend_mode::normal; } color_mode int_to_color_mode(std::uint16_t x) diff --git a/modules/psd/misc.h b/modules/psd/misc.h index 1f5680875..840b365b9 100644 --- a/modules/psd/misc.h +++ b/modules/psd/misc.h @@ -23,9 +23,11 @@ #include #include +#include #include #include +#include namespace caspar { namespace psd { @@ -38,6 +40,10 @@ struct point T y; void clear() { x = 0; y = 0; } + + bool operator==(const point& rhs) { + return x == rhs.x && y == rhs.y; + } }; template @@ -97,30 +103,7 @@ enum class layer_type layer_type int_to_layer_type(std::uint32_t x, std::uint32_t y); std::wstring layer_type_to_string(layer_type b); -enum class blend_mode -{ - InvalidBlendMode = -1, - Normal = 'norm', - Darken = 'dark', - Lighten = 'lite', - Hue = 'hue ', - Saturation = 'sat ', - Color = 'colr', - Luminosity = 'lum ', - Multiply = 'mul ', - Screen = 'scrn', - Dissolve = 'diss', - Overlay = 'over', - HardLight = 'hLit', - SoftLight = 'sLit', - Difference = 'diff', - Exclusion = 'smud', - ColorDodge = 'div ', - ColorBurn = 'idiv' -}; - -blend_mode int_to_blend_mode(std::uint32_t x); -std::wstring blend_mode_to_string(blend_mode b); +caspar::core::blend_mode int_to_blend_mode(std::uint32_t x); enum class color_mode { diff --git a/modules/psd/psd_scene_producer.cpp b/modules/psd/psd_scene_producer.cpp index 727ca17f9..f372419b5 100644 --- a/modules/psd/psd_scene_producer.cpp +++ b/modules/psd/psd_scene_producer.cpp @@ -413,6 +413,12 @@ spl::shared_ptr create_psd_scene_producer(const core::fram if (psd_layer->is_resizable()) //TODO: we could add support for resizable groups with vector masks CASPAR_LOG(warning) << "Groups doesn't support the \"resizable\"-tag."; + if (psd_layer->is_placeholder()) + CASPAR_LOG(warning) << "Groups doesn't support the \"producer\"-tag."; + + if (psd_layer->is_cornerpin()) + CASPAR_LOG(warning) << "Groups doesn't support the \"cornerpin\"-tag."; + if (psd_layer->is_explicit_dynamic()) CASPAR_LOG(warning) << "Groups doesn't support the \"dynamic\"-tag."; @@ -427,98 +433,152 @@ spl::shared_ptr create_psd_scene_producer(const core::fram continue; } - std::shared_ptr layer_producer; - int adjustment_x = 0, - adjustment_y = 0; - - if(psd_layer->is_visible()) + if (psd_layer->is_visible()) { + caspar::core::scene::layer* scene_layer = nullptr; + std::shared_ptr layer_producer; auto layer_name = psd_layer->name(); + int adjustment_x = 0, + adjustment_y = 0; if(psd_layer->is_text() && !psd_layer->is_static()) { std::wstring str = psd_layer->text_data().get(L"EngineDict.Editor.Text", L""); core::text::text_info text_info(std::move(get_text_info(psd_layer->text_data()))); - text_info.size *= psd_layer->text_scale(); + auto max_scale = std::max(abs(psd_layer->scale().x), abs(psd_layer->scale().y)); + text_info.size *= max_scale; + text_info.scale_x = psd_layer->scale().x / max_scale; + text_info.scale_y = psd_layer->scale().y / max_scale; + text_info.shear = 0; auto text_producer = core::text_producer::create(dependencies.frame_factory, 0, 0, str, text_info, doc.width(), doc.height()); - text_producer->pixel_constraints().width.set(psd_layer->size().width); - text_producer->pixel_constraints().height.set(psd_layer->size().height); + //text_producer->pixel_constraints().width.set(psd_layer->size().width); + //text_producer->pixel_constraints().height.set(psd_layer->size().height); core::text::string_metrics metrics = text_producer->measure_string(str); - adjustment_x = -2; //the 2 offset is just a hack for now. don't know why our text is rendered 2 px to the right of that in photoshop - adjustment_y = metrics.bearingY; + //adjustment_x = -2; //the 2 offset is just a hack for now. don't know why our text is rendered 2 px to the right of that in photoshop + //adjustment_y = metrics.bearingY; layer_producer = text_producer; - + scene_layer = ¤t.scene()->create_layer(spl::make_shared_ptr(layer_producer), static_cast(psd_layer->text_pos().x) + adjustment_x, static_cast(psd_layer->text_pos().y) + adjustment_y, layer_name); text_producers_by_layer_name.push_back(std::make_pair(layer_name, text_producer)); } else { - if(psd_layer->is_solid()) + if (psd_layer->is_placeholder()) + { + auto hotswap = std::make_shared(psd_layer->bitmap()->width(), psd_layer->bitmap()->height()); + + if (psd_layer->is_explicit_dynamic()) + { + /*auto& var = root->create_variable(boost::to_lower_copy(layer_name), true, L""); + var.on_change([=]() { + hotswap->producer().set(dependencies.producer_registry->create_producer(dependencies, root->get_variable(layer_name).as().get())); + });*/ + + } + else + hotswap->producer().set(dependencies.producer_registry->create_producer(dependencies, layer_name)); + + layer_producer = hotswap; + } + else if(psd_layer->is_solid()) { layer_producer = core::create_const_producer(core::create_color_frame(it->get(), dependencies.frame_factory, psd_layer->solid_color().to_uint32()), psd_layer->bitmap()->width(), psd_layer->bitmap()->height()); } else if(psd_layer->bitmap()) { - if (psd_layer->is_placeholder()) - { - auto hotswap = std::make_shared(psd_layer->bitmap()->width(), psd_layer->bitmap()->height()); - hotswap->producer().set(dependencies.producer_registry->create_producer(dependencies, layer_name)); - layer_producer = hotswap; - } - else - { - core::pixel_format_desc pfd(core::pixel_format::bgra); - pfd.planes.push_back(core::pixel_format_desc::plane(psd_layer->bitmap()->width(), psd_layer->bitmap()->height(), 4)); + core::pixel_format_desc pfd(core::pixel_format::bgra); + pfd.planes.push_back(core::pixel_format_desc::plane(psd_layer->bitmap()->width(), psd_layer->bitmap()->height(), 4)); - auto frame = dependencies.frame_factory->create_frame(it->get(), pfd, core::audio_channel_layout::invalid()); - auto destination = frame.image_data().data(); - auto source = psd_layer->bitmap()->data(); - memcpy(destination, source, frame.image_data().size()); + auto frame = dependencies.frame_factory->create_frame(it->get(), pfd, core::audio_channel_layout::invalid()); + auto destination = frame.image_data().data(); + auto source = psd_layer->bitmap()->data(); + memcpy(destination, source, frame.image_data().size()); - layer_producer = core::create_const_producer(core::draw_frame(std::move(frame)), psd_layer->bitmap()->width(), psd_layer->bitmap()->height()); - } + layer_producer = core::create_const_producer(core::draw_frame(std::move(frame)), psd_layer->bitmap()->width(), psd_layer->bitmap()->height()); } + else { + CASPAR_LOG(warning) << "Ignoring layer \"" << layer_name << "\", layer type not supported"; + } + + if(layer_producer) + scene_layer = ¤t.scene()->create_layer(spl::make_shared_ptr(layer_producer), psd_layer->location().x + adjustment_x, psd_layer->location().y + adjustment_y, layer_name); } - if (layer_producer) + if (layer_producer && scene_layer) { - auto& scene_layer = current.scene()->create_layer(spl::make_shared_ptr(layer_producer), psd_layer->location().x + adjustment_x, psd_layer->location().y + adjustment_y, layer_name); - scene_layer.adjustments.opacity.set(psd_layer->opacity() / 255.0); - scene_layer.hidden.set(!psd_layer->is_visible()); //this will always evaluate to true + scene_layer->adjustments.opacity.set(psd_layer->opacity() / 255.0); + scene_layer->hidden.set(!psd_layer->is_visible()); //this will always evaluate to true + scene_layer->blend_mode.set(psd_layer->blend_mode()); + scene_layer->rotation.set(psd_layer->angle()); if (psd_layer->mask().has_vector()) { - - //this rectangle is in document-coordinates - auto mask = psd_layer->mask().vector()->rect(); - - //remap to layer-coordinates - auto left = static_cast(mask.location.x) - scene_layer.position.x.get(); - auto right = left + static_cast(mask.size.width); - auto top = static_cast(mask.location.y) - scene_layer.position.y.get(); - auto bottom = top + static_cast(mask.size.height); - - scene_layer.crop.upper_left.x.unbind(); - scene_layer.crop.upper_left.x.set(left); - scene_layer.crop.upper_left.y.unbind(); - scene_layer.crop.upper_left.y.set(top); - - scene_layer.crop.lower_right.x.unbind(); - scene_layer.crop.lower_right.x.set(right); - - scene_layer.crop.lower_right.y.unbind(); - scene_layer.crop.lower_right.y.set(bottom); + + if (psd_layer->is_placeholder() && psd_layer->is_cornerpin()) { + + psd::point layer_pos{ static_cast(scene_layer->position.x.get()), static_cast(scene_layer->position.y.get()) }; + auto unbind_and_set = [&layer_pos](caspar::core::scene::coord& c, const caspar::psd::point& pt) { + c.x.unbind(); + c.x.set(pt.x - layer_pos.x); //remap to layer-coordinates + c.y.unbind(); + c.y.set(pt.y - layer_pos.y); //remap to layer-coordinates + }; + + if (!psd_layer->mask().vector()->rect().empty()) { + //this rectangle is in document-coordinates + auto& mask = psd_layer->mask().vector()->rect(); + + unbind_and_set(scene_layer->perspective.upper_left, mask.location); + unbind_and_set(scene_layer->perspective.upper_right, point{mask.location.x + mask.size.width, mask.location.y}); + unbind_and_set(scene_layer->perspective.lower_right, point{mask.location.x + mask.size.width, mask.location.y + mask.size.width}); + unbind_and_set(scene_layer->perspective.lower_left, point{mask.location.x, mask.location.y + mask.size.width}); + } + else if (!psd_layer->mask().vector()->knots().empty()) { + auto& knots = psd_layer->mask().vector()->knots(); + unbind_and_set(scene_layer->perspective.upper_left, knots[0]); + unbind_and_set(scene_layer->perspective.upper_right, knots[1]); + unbind_and_set(scene_layer->perspective.lower_right, knots[2]); + unbind_and_set(scene_layer->perspective.lower_left, knots[3]); + } + } + else if(!psd_layer->mask().vector()->rect().empty()) + { + //this rectangle is in document-coordinates + auto mask = psd_layer->mask().vector()->rect(); + + //remap to layer-coordinates + auto left = static_cast(mask.location.x) - scene_layer->position.x.get(); + auto right = left + static_cast(mask.size.width); + auto top = static_cast(mask.location.y) - scene_layer->position.y.get(); + auto bottom = top + static_cast(mask.size.height); + + scene_layer->crop.upper_left.x.unbind(); + scene_layer->crop.upper_left.x.set(left); + + scene_layer->crop.upper_left.y.unbind(); + scene_layer->crop.upper_left.y.set(top); + + scene_layer->crop.lower_right.x.unbind(); + scene_layer->crop.lower_right.x.set(right); + + scene_layer->crop.lower_right.y.unbind(); + scene_layer->crop.lower_right.y.set(bottom); + } } if (psd_layer->has_timeline()) - create_timelines(root, dependencies.format_desc, scene_layer, psd_layer, adjustment_x, adjustment_y); + create_timelines(root, dependencies.format_desc, *scene_layer, psd_layer, adjustment_x, adjustment_y); if (psd_layer->is_movable() || psd_layer->is_resizable() || (psd_layer->is_text() && !psd_layer->is_static())) - current.add(&scene_layer, psd_layer->tags(), -adjustment_x, -adjustment_y, psd_layer->mask().has_vector()); + current.add(scene_layer, psd_layer->tags(), -adjustment_x, -adjustment_y, psd_layer->mask().has_vector()); + + if (psd_layer->is_placeholder()) + scene_layer->use_mipmap.set(true); } } } + if (scene_stack.size() != 1) { } @@ -531,7 +591,8 @@ spl::shared_ptr create_psd_scene_producer(const core::fram // Reset all dynamic text fields to empty strings and expose them as a scene parameter. for (auto& text_layer : text_producers_by_layer_name) { text_layer.second->text().set(L""); - text_layer.second->text().bind(root->create_variable(boost::to_lower_copy(text_layer.first), true, L"")); + auto var = root->create_variable(boost::to_lower_copy(text_layer.first), true, L""); + text_layer.second->text().bind(var); } auto params2 = params; @@ -542,8 +603,16 @@ spl::shared_ptr create_psd_scene_producer(const core::fram return root; } +void describe_psd_scene_producer(core::help_sink& sink, const core::help_repository& repo) +{ + sink.short_description(L"A producer for dynamic graphics using Photoshops .psd files."); + sink.syntax(L"[.psd_filename:string] {[param1:string] [value1:string]} {[param2:string] [value2:string]} ..."); + sink.para()->text(L"A producer that looks in the ")->code(L"templates")->text(L" folder for .psd files."); +} + void init(core::module_dependencies dependencies) { + dependencies.producer_registry->register_producer_factory(L"PSD Scene Producer", create_psd_scene_producer, describe_psd_scene_producer); dependencies.cg_registry->register_cg_producer( L"psd", { L".psd" }, diff --git a/shell/casparcg.config b/shell/casparcg.config index 231954018..03fc51de7 100644 --- a/shell/casparcg.config +++ b/shell/casparcg.config @@ -1,17 +1,21 @@ - media/ - log/ - data/ - template/ - thumbnail/ - font/ + c:/caspar/content/_media/ + c:/caspar/content/_log/ + c:/caspar/content/_data/ + c:/caspar/content/ + c:/caspar/content/_thumbnails/ + c:/caspar/content/_fonts/ secret + info + + true + - PAL + 720p5000 stereo @@ -21,6 +25,17 @@ + + 720p5000 + stereo + + + 1 + true + stereo + + +