]> git.sesse.net Git - casparcg/commitdiff
* support for cornerpinning in psd-producer via vector-masks
authorNiklas P Andersson <niklas.p.andersson@svt.se>
Mon, 14 Dec 2015 11:44:05 +0000 (12:44 +0100)
committerNiklas P Andersson <niklas.p.andersson@svt.se>
Mon, 14 Dec 2015 11:44:05 +0000 (12:44 +0100)
* support for rotation and non-uniform scaling of dynamic text-layers in psd-producer
* support for per layer blend modes in psd-producer

core/producer/text/text_producer.cpp
core/producer/text/text_producer.h
core/producer/text/utils/text_info.h
core/producer/text/utils/texture_font.cpp
core/producer/text/utils/texture_font.h
modules/psd/layer.cpp
modules/psd/layer.h
modules/psd/misc.cpp
modules/psd/misc.h
modules/psd/psd_scene_producer.cpp
shell/casparcg.config

index 35c2a045690bc30670c80b76577a1b97c31702db..e00d828d691829c3960df9269c7a7877099e65f6 100644 (file)
@@ -136,6 +136,9 @@ struct text_producer::impl
        variable_impl<std::wstring>                             text_;
        std::shared_ptr<void>                                   text_subscription_;
        variable_impl<double>                                   tracking_;
+       variable_impl<double>                                   scale_x_;
+       variable_impl<double>                                   scale_y_;
+       variable_impl<double>                                   shear_;
        std::shared_ptr<void>                                   tracking_subscription_;
        variable_impl<double>                                   current_bearing_y_;
        variable_impl<double>                                   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<int>(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));
index 93c0474d1a3a33fdca8dad79cba829b20e0f4cda..05069cec5f6dcaf992d6c89a08198f65721dc44f 100644 (file)
@@ -45,7 +45,8 @@ namespace caspar { namespace core {
 class text_producer : public frame_producer_base
 {
 public:
-       text_producer(const spl::shared_ptr<frame_factory>& 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>& 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<text_producer> create(const spl::shared_ptr<frame_factory>& 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;
index 0cb8e050f7803a58d3edd37e075fe9e8a8d6e271..8075eee12e171eddd54be44ceecc392dd26a69b5 100644 (file)
@@ -18,6 +18,9 @@ namespace caspar { namespace core { namespace text {
                //text::color<float>    shadow_color;
                int                                             baseline_shift          = 0;
                int                                             tracking                        = 0;
+               double                                  scale_x                         = 1.0;
+               double                                  scale_y                         = 1.0;
+               double                                  shear                           = 0;
        };
 
 }}}
index 879f3bfda3c3137b591d28523b52fca73610135c..c0e3711752895ffe765cefc530842886cf876f9e 100644 (file)
@@ -105,7 +105,7 @@ public:
                }
        }
 
-       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)
+       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, 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<double>& col) { impl_->load_glyphs(range, col); }
 void texture_font::set_tracking(int tracking) { impl_->set_tracking(tracking); }
-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); }
+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, 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(); }
index 8adb06c117cf22c88b20e0147f91d54ff437e0dc..89662c134845e640b440d7e3cfd3aab4b26ec54f 100644 (file)
@@ -23,7 +23,7 @@ public:
        texture_font(texture_atlas&, const text_info&, bool normalize_coordinates);
        void load_glyphs(unicode_block block, const color<double>& col);
        void set_tracking(int tracking);
-       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);
+       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, double shear = 0.0);
        string_metrics measure_string(const std::wstring& str);
        std::wstring get_name() const;
        double get_size() const;
index d35c8583c79a350cbda67a6056143397f0c44c97..b7435021035bc62b16324ff5d5338e615d8a3cfa 100644 (file)
@@ -46,7 +46,7 @@ void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream)
                break;\r
 \r
        case 20:\r
-       case 36:        //discard total user mask data\r
+       case 36:\r
                rect_.location.y = stream.read_long();\r
                rect_.location.x = stream.read_long();\r
                rect_.size.height = stream.read_long() - rect_.location.y;\r
@@ -78,8 +78,8 @@ void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream)
 \r
 bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream& stream, int doc_width, int doc_height)\r
 {\r
-       typedef std::pair<int, int> path_point;\r
-       bool bFail = false;\r
+       std::vector<point<int>> knots;\r
+       bool smooth_curve = false;\r
 \r
        stream.read_long(); // version\r
        this->flags_ = static_cast<std::uint8_t>(stream.read_long()); // flags\r
@@ -87,8 +87,6 @@ bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream&
 \r
        auto position = stream.current_position();\r
 \r
-       std::vector<path_point> knots;\r
-\r
        const int SELECTOR_SIZE = 2;\r
        const int PATH_POINT_SIZE = 4 + 4;\r
        const int PATH_POINT_RECORD_SIZE = SELECTOR_SIZE + (3 * PATH_POINT_SIZE);\r
@@ -100,21 +98,22 @@ bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream&
                {\r
                        auto p_y = stream.read_long();\r
                        auto p_x = stream.read_long();\r
-                       path_point cp_prev(p_x, p_y);\r
+                       point<int> prev{ static_cast<int>(p_x), static_cast<int>(p_y) };\r
 \r
                        auto a_y = stream.read_long();\r
                        auto a_x = stream.read_long();\r
-                       path_point anchor(a_x, a_y);\r
+                       point<int> anchor{ static_cast<int>(a_x), static_cast<int>(a_y) };\r
 \r
                        auto n_y = stream.read_long();\r
                        auto n_x = stream.read_long();\r
-                       path_point cp_next(n_x, n_y);\r
+                       point<int> next{ static_cast<int>(n_x), static_cast<int>(n_y) };\r
 \r
-                       if (anchor == cp_prev && anchor == cp_next)\r
+                       if (anchor == prev && anchor == next)\r
                                knots.push_back(anchor);\r
-                       else\r
-                       {       //we can't handle smooth curves yet\r
-                               bFail = true;\r
+                       else \r
+                       {\r
+                               //note that we've got a smooth curve, but continue to iterate through the data\r
+                               smooth_curve = true;\r
                        }\r
                }\r
 \r
@@ -122,37 +121,46 @@ bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream&
                stream.set_position(position + offset);\r
        }\r
 \r
-       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)))\r
-               bFail = true;\r
-\r
-       if (bFail) {\r
+       if (smooth_curve || knots.size() != 4)  //we can't handle smooth-curves yet and we only support quad-gons\r
+       {\r
                rect_.clear();\r
-               flags_ = static_cast<std::uint8_t>(flags::unsupported);\r
+               flags_ = static_cast<std::uint8_t>(flags::unsupported | flags::disabled);\r
+               return false;\r
+       }\r
+\r
+       //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.\r
+       float x_ratio = doc_width / 16777215.0f;\r
+       float y_ratio = doc_height / 16777215.0f;\r
+       rect_.clear();\r
+       knots_.clear();\r
+\r
+       //is it an orthogonal rectangle\r
+       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)\r
+       {\r
+               rect_.location.x = static_cast<int>(knots[0].x * x_ratio + 0.5f);                                               //add .5 to get propper rounding when converting to integer\r
+               rect_.location.y = static_cast<int>(knots[0].y * y_ratio + 0.5f);                                               //add .5 to get propper rounding when converting to integer\r
+               rect_.size.width = static_cast<int>(knots[1].x * x_ratio + 0.5f) - rect_.location.x;    //add .5 to get propper rounding when converting to integer\r
+               rect_.size.height = static_cast<int>(knots[2].y * y_ratio + 0.5f) - rect_.location.y;   //add .5 to get propper rounding when converting to integer\r
        }\r
-       else \r
+       else //it's could be any kind of quad-gon\r
        {\r
-               //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.\r
-               float x_ratio = doc_width / 16777215.0f;\r
-               float y_ratio = doc_height / 16777215.0f;\r
-               rect_.location.x = static_cast<int>(knots[0].first * x_ratio + 0.5f);                                                           //add .5 to get propper rounding when converting to integer\r
-               rect_.location.y = static_cast<int>(knots[0].second * y_ratio + 0.5f);                                                          //add .5 to get propper rounding when converting to integer\r
-               rect_.size.width = static_cast<int>(knots[1].first * x_ratio + 0.5f) - rect_.location.x;                //add .5 to get propper rounding when converting to integer\r
-               rect_.size.height = static_cast<int>(knots[2].second * y_ratio + 0.5f) - rect_.location.y;      //add .5 to get propper rounding when converting to integer\r
+               for (auto& k : knots)\r
+                       knots_.push_back(psd::point<int>{static_cast<int>(k.x * x_ratio + 0.5f), static_cast<int>(k.y * y_ratio + 0.5f)});\r
        }\r
 \r
-       return !bFail;\r
+       return true;\r
 }\r
 \r
 struct layer::impl\r
 {\r
        friend class layer;\r
 \r
-       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)\r
+       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)\r
        {}\r
 \r
 private:\r
        std::vector<channel>                    channels_;\r
-       blend_mode                                              blend_mode_;\r
+       caspar::core::blend_mode                blend_mode_;\r
        layer_type                                              layer_type_;\r
        int                                                             link_group_id_;\r
        int                                                             opacity_;\r
@@ -162,7 +170,10 @@ private:
        std::uint32_t                                   protection_flags_;\r
        std::wstring                                    name_;\r
        int                                                             masks_count_;\r
-       double                                                  text_scale_;\r
+       psd::point<double>                              text_pos_;\r
+       psd::point<double>                              scale_;\r
+       double                                                  angle_;\r
+       double                                                  shear_;\r
 \r
        layer::mask_info                                mask_;\r
 \r
@@ -285,6 +296,7 @@ public:
                        case 'lnk3':    //linked layer\r
                                break;\r
 \r
+                       case 'vsms':\r
                        case 'vmsk':\r
                                mask_.read_vector_mask_data(length, stream, doc.width(), doc.height());\r
                                break;\r
@@ -388,12 +400,11 @@ public:
                auto xy = stream.read_double();\r
                auto yx = stream.read_double();\r
                auto yy = stream.read_double();\r
-               stream.read_double(); // tx\r
-               stream.read_double(); // ty\r
-               if(xx != yy || (xy != 0 && yx != 0))\r
-                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Rotation and non-uniform scaling of dynamic textfields is not supported yet"));\r
+               auto tx = stream.read_double(); // tx\r
+               auto ty = stream.read_double(); // ty\r
 \r
-               text_scale_ = xx;\r
+               text_pos_.x = tx;\r
+               text_pos_.y = ty;\r
 \r
                if(stream.read_short() != 50)   //"text version" should be 50\r
                        CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid text version"));\r
@@ -420,10 +431,45 @@ public:
 \r
                descriptor warp_descriptor(L"warp");\r
                warp_descriptor.populate(stream);\r
-               stream.read_double(); // w_top\r
-               stream.read_double();  // w_left\r
+               stream.read_double(); // w_left\r
+               stream.read_double();  // w_top\r
                stream.read_double();  // w_right\r
                stream.read_double();  // w_bottom\r
+\r
+\r
+               //extract scale, angle and shear factor from transformation matrix \r
+               const double PI = 3.141592653589793;\r
+               auto angle = atan2(xy, xx);\r
+\r
+               auto c = cos(angle);\r
+               auto s = sin(angle);\r
+               auto scale_x = (abs(c) > 0.1) ? xx / c : xy / s;\r
+\r
+               if (xx / scale_x < 0) { //fel kvadrant\r
+                       angle += PI;\r
+                       c = cos(angle);\r
+                       s = sin(angle);\r
+                       scale_x = (abs(c) > 0.1) ? xx / c : xy / s;\r
+               }\r
+\r
+               auto shear_factor = (yx*c + yy*s) / (yy*c - yx * s);\r
+               auto scale_y = 1.0;\r
+               if (abs(shear_factor) < 0.0001 || isnan(shear_factor)) {\r
+                       shear_factor = 0;\r
+                       scale_y = (abs(c) > 0.1) ? yy / c : yx / -s;\r
+               }\r
+               else {\r
+                       scale_y = yx / (c*shear_factor - s);\r
+               }\r
+\r
+               scale_.x = scale_x;\r
+               scale_.y = scale_y;\r
+               angle_ = angle * 180 / PI;\r
+               shear_ = shear_factor;\r
+\r
+               \r
+\r
+\r
        }\r
 \r
        //TODO: implement\r
@@ -591,14 +637,19 @@ void layer::read_channel_data(bigendian_file_input_stream& stream) { impl_->read
 \r
 const std::wstring& layer::name() const { return impl_->name_; }\r
 int layer::opacity() const { return impl_->opacity_; }\r
+caspar::core::blend_mode layer::blend_mode() const { return impl_->blend_mode_; }\r
 int layer::sheet_color() const { return impl_->sheet_color_; }\r
 \r
 bool layer::is_visible() { return (impl_->flags_ & 2) == 0; }  //the (PSD file-format) documentation is is saying the opposite but what the heck\r
 bool layer::is_position_protected() { return (impl_->protection_flags_& 4) == 4; }\r
 \r
 const layer::mask_info& layer::mask() const { return impl_->mask_; }\r
+
+const psd::point<double>& layer::text_pos() const { return impl_->text_pos_; }
+const psd::point<double>& layer::scale() const { return impl_->scale_; }
+const double layer::angle() const { return impl_->angle_; }
+const double layer::shear() const { return impl_->shear_; }
 \r
-double layer::text_scale() const { return impl_->text_scale_; }\r
 bool layer::is_text() const { return !impl_->text_layer_info_.empty(); }\r
 const boost::property_tree::wptree& layer::text_data() const { return impl_->text_layer_info_; }\r
 \r
@@ -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; }\r
 bool layer::is_resizable() const { return (impl_->tags_ & layer_tag::resizable) == layer_tag::resizable; }\r
 bool layer::is_placeholder() const { return (impl_->tags_ & layer_tag::placeholder) == layer_tag::placeholder; }\r
+bool layer::is_cornerpin() const { return (impl_->tags_ & layer_tag::cornerpin) == layer_tag::cornerpin; }\r
+\r
 layer_tag layer::tags() const { return impl_->tags_; }\r
 \r
 }      //namespace psd\r
index 7f2ca628c7e3deab005879b5a7ffc32713b1a528..2919ad682f9deccfe99f82bb373a53a7d72639d8 100644 (file)
@@ -48,20 +48,22 @@ public:
 
        class vector_mask_info
        {
+               std::uint8_t                    flags_;
+               psd::rect<int>                  rect_;
+               std::vector<point<int>> knots_;\r
+
+               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<int>          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<std::uint8_t>(flags::inverted)) == static_cast<std::uint8_t>(flags::inverted); }
                bool unsupported() const { return (flags_ & static_cast<std::uint8_t>(flags::unsupported)) == static_cast<std::uint8_t>(flags::unsupported); }
 
-               bool empty() { return rect_.empty(); }
+               bool empty() { return rect_.empty() && knots_.empty(); }
 
                const psd::rect<int>& rect() const { return rect_; }
+               const std::vector<point<int>>& 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<int>          rect_;
+               psd::rect<int>  rect_;
 
                std::unique_ptr<vector_mask_info> vector_mask_;
                std::unique_ptr<mask_info> 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<double>& text_pos() const;
+       const psd::point<double>& 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
index 829a9ee5220454b8e5a24cdf23517bd6bb116cd9..ed098a599093946feb10b74b9ba67eddde8e883b 100644 (file)
@@ -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<blend_mode>(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)
index 1f56808757856278466ecc30ea4ed8c619b68271..840b365b9844f9153aee90f8b05839098b5f5489 100644 (file)
 \r
 #include <common/except.h>\r
 #include <common/enum_class.h>\r
+#include <core/mixer/image/blend_modes.h>\r
 \r
 #include <string>\r
 #include <cstdint>\r
+#include <vector>\r
 \r
 namespace caspar { namespace psd {\r
        \r
@@ -38,6 +40,10 @@ struct point
        T y;\r
 \r
        void clear() { x = 0; y = 0; }\r
+       \r
+       bool operator==(const point& rhs) {\r
+               return x == rhs.x && y == rhs.y;\r
+       }\r
 };\r
 \r
 template<typename T>\r
@@ -97,30 +103,7 @@ enum class layer_type
 layer_type int_to_layer_type(std::uint32_t x, std::uint32_t y);\r
 std::wstring layer_type_to_string(layer_type b);\r
 \r
-enum class blend_mode\r
-{\r
-       InvalidBlendMode = -1,\r
-       Normal = 'norm',\r
-       Darken = 'dark',\r
-       Lighten = 'lite',\r
-       Hue = 'hue ',\r
-       Saturation = 'sat ',\r
-       Color = 'colr',\r
-       Luminosity = 'lum ',\r
-       Multiply = 'mul ',\r
-       Screen = 'scrn',\r
-       Dissolve = 'diss',\r
-       Overlay = 'over',\r
-       HardLight = 'hLit',\r
-       SoftLight = 'sLit',\r
-       Difference = 'diff',\r
-       Exclusion = 'smud',\r
-       ColorDodge = 'div ',\r
-       ColorBurn = 'idiv'\r
-};\r
-\r
-blend_mode int_to_blend_mode(std::uint32_t x);\r
-std::wstring blend_mode_to_string(blend_mode b);\r
+caspar::core::blend_mode int_to_blend_mode(std::uint32_t x);\r
 \r
 enum class color_mode\r
 {\r
index 727ca17f9b7da38db02ab93ed43a9ffc6ffc55b2..f372419b52bfac04712ca700b643baebce0c17bd 100644 (file)
@@ -413,6 +413,12 @@ spl::shared_ptr<core::frame_producer> 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<core::frame_producer> create_psd_scene_producer(const core::fram
                        continue;
                }
 
-               std::shared_ptr<core::frame_producer> 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<core::frame_producer> 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 = &current.scene()->create_layer(spl::make_shared_ptr(layer_producer), static_cast<int>(psd_layer->text_pos().x) + adjustment_x, static_cast<int>(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<core::hotswap_producer>(psd_layer->bitmap()->width(), psd_layer->bitmap()->height());
+
+                                       if (psd_layer->is_explicit_dynamic())
+                                       {
+                                               /*auto& var = root->create_variable<std::wstring>(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<std::wstring>().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<core::hotswap_producer>(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 = &current.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<double>(mask.location.x) - scene_layer.position.x.get();
-                                       auto right = left + static_cast<double>(mask.size.width);
-                                       auto top = static_cast<double>(mask.location.y) - scene_layer.position.y.get();
-                                       auto bottom = top + static_cast<double>(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<int> layer_pos{ static_cast<int>(scene_layer->position.x.get()), static_cast<int>(scene_layer->position.y.get()) };
+                                               auto unbind_and_set = [&layer_pos](caspar::core::scene::coord& c, const caspar::psd::point<int>& 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<int>{mask.location.x + mask.size.width, mask.location.y});
+                                                       unbind_and_set(scene_layer->perspective.lower_right, point<int>{mask.location.x + mask.size.width, mask.location.y + mask.size.width});
+                                                       unbind_and_set(scene_layer->perspective.lower_left, point<int>{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<double>(mask.location.x) - scene_layer->position.x.get();
+                                               auto right = left + static_cast<double>(mask.size.width);
+                                               auto top = static_cast<double>(mask.location.y) - scene_layer->position.y.get();
+                                               auto bottom = top + static_cast<double>(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<core::frame_producer> 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<std::wstring>(boost::to_lower_copy(text_layer.first), true, L""));
+               auto var = root->create_variable<std::wstring>(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<core::frame_producer> 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" },
index 231954018cf47b162f7881dde176623e0590e94b..03fc51de742e858199b4adea5fbc0dc33f102412 100644 (file)
@@ -1,17 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <configuration>\r
   <paths>\r
-    <media-path>media/</media-path>\r
-    <log-path>log/</log-path>\r
-    <data-path>data/</data-path>\r
-    <template-path>template/</template-path>\r
-    <thumbnail-path>thumbnail/</thumbnail-path>\r
-    <font-path>font/</font-path>\r
+    <media-path>c:/caspar/content/_media/</media-path>\r
+    <log-path>c:/caspar/content/_log/</log-path>\r
+    <data-path>c:/caspar/content/_data/</data-path>\r
+    <template-path>c:/caspar/content/</template-path>\r
+    <thumbnail-path>c:/caspar/content/_thumbnails/</thumbnail-path>\r
+    <font-path>c:/caspar/content/_fonts/</font-path>\r
   </paths>\r
   <lock-clear-phrase>secret</lock-clear-phrase>\r
+  <log-level>info</log-level>\r
+  <mixer>\r
+    <blend-modes>true</blend-modes>\r
+  </mixer>\r
   <channels>\r
     <channel>\r
-      <video-mode>PAL</video-mode>\r
+      <video-mode>720p5000</video-mode>\r
       <channel-layout>stereo</channel-layout>\r
       <consumers>\r
         <screen>\r
         <system-audio></system-audio>\r
       </consumers>\r
     </channel>\r
+    <channel>\r
+      <video-mode>720p5000</video-mode>\r
+      <channel-layout>stereo</channel-layout>\r
+      <consumers>\r
+        <decklink>\r
+          <device>1</device>\r
+          <embedded-audio>true</embedded-audio>\r
+          <channel-layout>stereo</channel-layout>\r
+        </decklink>\r
+      </consumers>\r
+    </channel>\r
   </channels>\r
   <controllers>\r
     <tcp>\r