* support for rotation and non-uniform scaling of dynamic text-layers in psd-producer
* support for per layer blend modes in psd-producer
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_;
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;
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));
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;
//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;
};
}}}
}
}
- 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
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;
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(); }
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;
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
\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
\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
{\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
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
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
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
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
\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
\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
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
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)
{}
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
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_;
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;
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
}
-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)
\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
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
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
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.";
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 = ¤t.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 = ¤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<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) {
}
// 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;
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" },
<?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