From 5dcb5475a4968fa3f1265826f6160f708b5a1add Mon Sep 17 00:00:00 2001 From: Niklas P Andersson Date: Tue, 1 Dec 2015 15:36:02 +0100 Subject: [PATCH] initial fixes for new psd workflow --- core/producer/text/text_producer.cpp | 2 +- modules/flash/producer/FlashAxContainer.cpp | 2 +- modules/psd/layer.cpp | 1218 ++++++++++--------- modules/psd/layer.h | 7 + modules/psd/misc.cpp | 34 + modules/psd/misc.h | 29 + modules/psd/psd_scene_producer.cpp | 237 ++-- 7 files changed, 819 insertions(+), 710 deletions(-) diff --git a/core/producer/text/text_producer.cpp b/core/producer/text/text_producer.cpp index 58bfa5598..35c2a0456 100644 --- a/core/producer/text/text_producer.cpp +++ b/core/producer/text/text_producer.cpp @@ -140,7 +140,7 @@ struct text_producer::impl variable_impl current_bearing_y_; variable_impl current_protrude_under_y_; draw_frame frame_; - text::texture_atlas atlas_ { 512, 512, 4 }; + text::texture_atlas atlas_ { 1024, 512, 4 }; text::texture_font font_; bool dirty_ = false; diff --git a/modules/flash/producer/FlashAxContainer.cpp b/modules/flash/producer/FlashAxContainer.cpp index c0da13a5b..7ec8ea152 100644 --- a/modules/flash/producer/FlashAxContainer.cpp +++ b/modules/flash/producer/FlashAxContainer.cpp @@ -771,7 +771,7 @@ HRESULT FlashAxContainer::CreateAxControl() HRESULT hr2 = m_spOleObject->QueryInterface(__uuidof(IShockwaveFlash), (void**) &spFlash); if(hr2 == S_OK && spFlash) { - if(FAILED(spFlash->put_WMode(L"Transparent"))) + if(FAILED(spFlash->put_WMode(L"direct"))) CASPAR_LOG(warning) << print_() << L" Failed to set flash container to transparent mode."; //spFlash->put_WMode(TEXT("ogl")); hResultQuality = spFlash->put_Quality2(L"Best"); diff --git a/modules/psd/layer.cpp b/modules/psd/layer.cpp index c866927f6..acc595e33 100644 --- a/modules/psd/layer.cpp +++ b/modules/psd/layer.cpp @@ -1,593 +1,625 @@ -/* -* Copyright (c) 2011 Sveriges Television AB -* -* This file is part of CasparCG (www.casparcg.com). -* -* CasparCG is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* CasparCG is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with CasparCG. If not, see . -* -* Author: Niklas P Andersson, niklas.p.andersson@svt.se -*/ - -#include "layer.h" -#include "psd_document.h" -#include "descriptor.h" -#include "util/pdf_reader.h" - -#include "../image/util/image_algorithms.h" -#include "../image/util/image_view.h" - -#include - -#include -#include - -#include - -namespace caspar { namespace psd { - -void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream) -{ - auto length = stream.read_long(); - switch(length) - { - case 0: - break; - - case 20: - case 36: //discard total user mask data - rect_.location.y = stream.read_long(); - rect_.location.x = stream.read_long(); - rect_.size.height = stream.read_long() - rect_.location.y; - rect_.size.width = stream.read_long() - rect_.location.x; - - default_value_ = stream.read_byte(); - flags_ = stream.read_byte(); - stream.discard_bytes(2); - - if(length == 36) - { - //we save the information about the total mask in case the vector-mask has an unsupported shape - total_mask_.reset(new layer::mask_info); - total_mask_->flags_ = stream.read_byte(); - total_mask_->default_value_ = stream.read_byte(); - total_mask_->rect_.location.y = stream.read_long(); - total_mask_->rect_.location.x = stream.read_long(); - total_mask_->rect_.size.height = stream.read_long() - rect_.location.y; - total_mask_->rect_.size.width = stream.read_long() - rect_.location.x; - } - break; - - default: - //TODO: Log that we discard a mask that is not supported - stream.discard_bytes(length); - break; - }; -} - -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; - - stream.read_long(); // version - this->flags_ = static_cast(stream.read_long()); // flags - int path_records = (length - 8) / 26; - - 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); - - for (int i = 1; i <= path_records; ++i) - { - auto selector = stream.read_short(); - if (selector == 2) //we only concern ourselves with closed paths - { - auto p_y = stream.read_long(); - auto p_x = stream.read_long(); - path_point cp_prev(p_x, p_y); - - auto a_y = stream.read_long(); - auto a_x = stream.read_long(); - path_point anchor(a_x, a_y); - - auto n_y = stream.read_long(); - auto n_x = stream.read_long(); - path_point cp_next(n_x, n_y); - - if (anchor == cp_prev && anchor == cp_next) - knots.push_back(anchor); - else - { //we can't handle smooth curves yet - bFail = true; - } - } - - auto offset = PATH_POINT_RECORD_SIZE * i; - 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) { - rect_.clear(); - flags_ = static_cast(flags::unsupported); - } - else - { - //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 - } - - return !bFail; -} - -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) - {} - -private: - std::vector channels_; - blend_mode blend_mode_; - layer_type layer_type_; - int link_group_id_; - int opacity_; - int sheet_color_; - bool baseClipping_; - std::uint8_t flags_; - std::uint32_t protection_flags_; - std::wstring name_; - int masks_count_; - double text_scale_; - - layer::mask_info mask_; - - rect bitmap_rect_; - image8bit_ptr bitmap_; - - boost::property_tree::wptree text_layer_info_; - boost::property_tree::wptree timeline_info_; - - color solid_color_; - -public: - void populate(bigendian_file_input_stream& stream, const psd_document& doc) - { - bitmap_rect_.location.y = stream.read_long(); - bitmap_rect_.location.x = stream.read_long(); - bitmap_rect_.size.height = stream.read_long() - bitmap_rect_.location.y; - bitmap_rect_.size.width = stream.read_long() - bitmap_rect_.location.x; - - //Get info about the channels in the layer - auto channelCount = stream.read_short(); - for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex) - { - auto id = static_cast(stream.read_short()); - channel c(id, stream.read_long()); - - if(c.id < -1) - masks_count_++; - - channels_.push_back(c); - } - - auto blendModeSignature = stream.read_long(); - if(blendModeSignature != '8BIM') - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("blendModeSignature != '8BIM'")); - - blend_mode_ = int_to_blend_mode(stream.read_long()); - opacity_ = stream.read_byte(); - baseClipping_ = stream.read_byte() == 1 ? false : true; - flags_ = stream.read_byte(); - - stream.discard_bytes(1); //padding - - auto extras_size = stream.read_long(); - auto position = stream.current_position(); - mask_.read_mask_data(stream); - read_blending_ranges(stream); - name_ = stream.read_pascal_string(4); - - //Aditional Layer Information - auto end_of_layer_info = position + extras_size; - try - { - while(stream.current_position() < end_of_layer_info) - { - read_chunk(stream, doc); - } - } - catch(psd_file_format_exception&) - { - stream.set_position(end_of_layer_info); - } - } - - void read_chunk(bigendian_file_input_stream& stream, const psd_document& doc, bool isMetadata = false) - { - auto signature = stream.read_long(); - if(signature != '8BIM' && signature != '8B64') - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM' && signature != '8B64'")); - - auto key = stream.read_long(); - - if(isMetadata) stream.read_long(); - - auto length = stream.read_long(); - auto end_of_chunk = stream.current_position() + length; - - try - { - switch(key) - { - case 'SoCo': - read_solid_color(stream); - break; - - case 'lsct': //group settings (folders) - read_group_settings(stream, length); - - case 'lspf': //protection settings - protection_flags_ = stream.read_long(); - break; - - case 'Txt2': //text engine data - break; - - case 'luni': - name_ = stream.read_unicode_string(); - break; - - case 'TySh': //type tool object settings - read_text_data(stream); - break; - - case 'shmd': //metadata - read_metadata(stream, doc); - break; - - case 'lclr': - sheet_color_ = static_cast(stream.read_short()); - break; - - case 'lyvr': //layer version - break; - - case 'lnkD': //linked layer - case 'lnk2': //linked layer - case 'lnk3': //linked layer - break; - - case 'vmsk': - mask_.read_vector_mask_data(length, stream, doc.width(), doc.height()); - break; - - case 'tmln': - read_timeline_data(stream); - break; - - default: - break; - } - } - catch(psd_file_format_exception& ex) - { - //ignore failed chunks silently - CASPAR_LOG(warning) << ex.what(); - } - - stream.set_position(end_of_chunk); - } - - void read_solid_color(bigendian_file_input_stream& stream) - { - if(stream.read_long() != 16) //"descriptor version" should be 16 - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16")); - - descriptor solid_descriptor(L"solid_color"); - solid_descriptor.populate(stream); - solid_color_.red = static_cast(solid_descriptor.items().get(L"Clr .Rd ", 0.0) + 0.5); - solid_color_.green = static_cast(solid_descriptor.items().get(L"Clr .Grn ", 0.0) + 0.5); - solid_color_.blue = static_cast(solid_descriptor.items().get(L"Clr .Bl ", 0.0) + 0.5); - solid_color_.alpha = 255; - } - - void read_group_settings(bigendian_file_input_stream& stream, unsigned int length) - { - auto type = stream.read_long(); - unsigned int sub_type = 0; - - if (length >= 12) - { - auto signature = stream.read_long(); - if (signature != '8BIM') - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM'")); - - blend_mode_ = int_to_blend_mode(stream.read_long()); - - if (length >= 16) - sub_type = stream.read_long(); - } - - layer_type_ = int_to_layer_type(type, sub_type); - } - - void read_metadata(bigendian_file_input_stream& stream, const psd_document& doc) - { - int count = stream.read_long(); - for(int index = 0; index < count; ++index) - read_chunk(stream, doc, true); - } - - void read_timeline_data(bigendian_file_input_stream& stream) - { - if(stream.read_long() != 16) //"descriptor version" should be 16 - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16")); - - descriptor timeline_descriptor(L"timeline"); - timeline_descriptor.populate(stream); - timeline_info_.swap(timeline_descriptor.items()); - } - - void read_text_data(bigendian_file_input_stream& stream) - { - std::wstring text; //the text in the layer - - stream.read_short(); //should be 1 - - //transformation info - auto xx = stream.read_double(); - 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")); - - text_scale_ = xx; - - if(stream.read_short() != 50) //"text version" should be 50 - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid text version")); - - if(stream.read_long() != 16) //"descriptor version" should be 16 - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text-data")); - - descriptor text_descriptor(L"text"); - text_descriptor.populate(stream); - auto text_info = text_descriptor.items().get_optional(L"EngineData"); - - if (text_info) - { - std::string str(text_info->begin(), text_info->end()); - read_pdf(text_layer_info_, str); - log::print_child(boost::log::trivial::trace, L"", L"text_layer_info", text_layer_info_); - } - - if(stream.read_short() != 1) //"warp version" should be 1 - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid warp version")); - - if(stream.read_long() != 16) //"descriptor version" should be 16 - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text warp-data")); - - descriptor warp_descriptor(L"warp"); - warp_descriptor.populate(stream); - stream.read_double(); // w_top - stream.read_double(); // w_left - stream.read_double(); // w_right - stream.read_double(); // w_bottom - } - - //TODO: implement - void read_blending_ranges(bigendian_file_input_stream& stream) - { - auto length = stream.read_long(); - stream.discard_bytes(length); - } - - bool has_channel(channel_type type) - { - return std::find_if(channels_.begin(), channels_.end(), [=](const channel& c) { return c.id == static_cast(type); }) != channels_.end(); - } - - void read_channel_data(bigendian_file_input_stream& stream) - { - image8bit_ptr bitmap; - - bool has_transparency = has_channel(channel_type::transparency); - - if(!bitmap_rect_.empty()) - { - bitmap = std::make_shared(bitmap_rect_.size.width, bitmap_rect_.size.height, 4); - if(!has_transparency) - std::memset(bitmap->data(), 255, bitmap->width()*bitmap->height()*bitmap->channel_count()); - } - - for(auto it = channels_.begin(); it != channels_.end(); ++it) - { - auto channel = (*it); - image8bit_ptr target; - int offset = 0; - bool discard_channel = false; - - //determine target bitmap and offset - if(channel.id >= 3) - discard_channel = true; //discard channels that doesn't contribute to the final image - else if(channel.id >= -1) //BGRA-data - { - target = bitmap; - offset = (channel.id >= 0) ? 2 - channel.id : 3; - } - else //mask - { - offset = 0; - if (channel.id == -2) - { - mask_.create_bitmap(); - target = mask_.bitmap_; - } - else if (channel.id == -3) //total_mask - { - mask_.total_mask_->create_bitmap(); - target = mask_.total_mask_->bitmap_; - offset = 0; - } - } - - if(!target) - discard_channel = true; - - auto end_of_data = stream.current_position() + channel.data_length; - if(!discard_channel) - { - auto encoding = stream.read_short(); - if(target) - { - if(encoding == 0) - read_raw_image_data(stream, channel.data_length-2, target, offset); - else if(encoding == 1) - read_rle_image_data(stream, target, offset); - else - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Unhandled image data encoding: " + boost::lexical_cast(encoding))); - } - } - stream.set_position(end_of_data); - } - - if(bitmap && has_transparency) - { - caspar::image::image_view view(bitmap->data(), bitmap->width(), bitmap->height()); - caspar::image::premultiply(view); - } - - bitmap_ = bitmap; - } - - void read_raw_image_data(bigendian_file_input_stream& stream, int data_length, image8bit_ptr target, int offset) - { - auto total_length = target->width() * target->height(); - if (total_length != data_length) - CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("total_length != data_length")); - - auto data = target->data(); - auto stride = target->channel_count(); - - if (stride == 1) - stream.read(reinterpret_cast(data + offset), total_length); - else - { - for(int index = 0; index < total_length; ++index) - data[index * stride + offset] = stream.read_byte(); - } - } - - void read_rle_image_data(bigendian_file_input_stream& stream, image8bit_ptr target, int offset) - { - auto width = target->width(); - auto height = target->height(); - auto stride = target->channel_count(); - - std::vector scanline_lengths; - scanline_lengths.reserve(height); - - for (int scanlineIndex = 0; scanlineIndex < height; ++scanlineIndex) - scanline_lengths.push_back(static_cast(stream.read_short())); - - auto target_data = target->data(); - - std::vector line(width); - - for(int scanlineIndex=0; scanlineIndex < height; ++scanlineIndex) - { - int colIndex = 0; - - do - { - int length = 0; - - //Get controlbyte - char controlByte = static_cast(stream.read_byte()); - if(controlByte >= 0) - { - //Read uncompressed string - length = controlByte+1; - for(int index=0; index < length; ++index) - line[colIndex+index] = stream.read_byte(); - } - else if(controlByte > -128) - { - //Repeat next byte - length = -controlByte+1; - auto value = stream.read_byte(); - for(int index=0; index < length; ++index) - line[colIndex+index] = value; - } - - colIndex += length; - } - while(colIndex < width); - - //use line to populate target - for(int index = 0; index < width; ++index) - { - target_data[(scanlineIndex*width+index) * stride + offset] = line[index]; - } - } - } -}; - -layer::layer() : impl_(spl::make_shared()) {} - -void layer::populate(bigendian_file_input_stream& stream, const psd_document& doc) { impl_->populate(stream, doc); } -void layer::read_channel_data(bigendian_file_input_stream& stream) { impl_->read_channel_data(stream); } - -const std::wstring& layer::name() const { return impl_->name_; } -int layer::opacity() const { return impl_->opacity_; } -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; } - -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_; } - -bool layer::has_timeline() const { return !impl_->timeline_info_.empty(); } -const boost::property_tree::wptree& layer::timeline_data() const { return impl_->timeline_info_; } - -bool layer::is_solid() const { return impl_->solid_color_.alpha != 0; } -color layer::solid_color() const { return impl_->solid_color_; } - -const point& layer::location() const { return impl_->bitmap_rect_.location; } -const size& layer::size() const { return impl_->bitmap_rect_.size; } -const image8bit_ptr& layer::bitmap() const { return impl_->bitmap_; } - -layer_type layer::group_mode() const { return impl_->layer_type_; } -int layer::link_group_id() const { return impl_->link_group_id_; } -void layer::set_link_group_id(int id) { impl_->link_group_id_ = id; } - -} //namespace psd -} //namespace caspar +/* +* Copyright (c) 2011 Sveriges Television AB +* +* This file is part of CasparCG (www.casparcg.com). +* +* CasparCG is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* CasparCG is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with CasparCG. If not, see . +* +* Author: Niklas P Andersson, niklas.p.andersson@svt.se +*/ + +#include "layer.h" +#include "psd_document.h" +#include "descriptor.h" +#include "util/pdf_reader.h" + +#include "../image/util/image_algorithms.h" +#include "../image/util/image_view.h" + +#include + +#include +#include +#include + +#include + +namespace caspar { namespace psd { + +void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream) +{ + auto length = stream.read_long(); + switch(length) + { + case 0: + break; + + case 20: + case 36: //discard total user mask data + rect_.location.y = stream.read_long(); + rect_.location.x = stream.read_long(); + rect_.size.height = stream.read_long() - rect_.location.y; + rect_.size.width = stream.read_long() - rect_.location.x; + + default_value_ = stream.read_byte(); + flags_ = stream.read_byte(); + stream.discard_bytes(2); + + if(length == 36) + { + //we save the information about the total mask in case the vector-mask has an unsupported shape + total_mask_.reset(new layer::mask_info); + total_mask_->flags_ = stream.read_byte(); + total_mask_->default_value_ = stream.read_byte(); + total_mask_->rect_.location.y = stream.read_long(); + total_mask_->rect_.location.x = stream.read_long(); + total_mask_->rect_.size.height = stream.read_long() - rect_.location.y; + total_mask_->rect_.size.width = stream.read_long() - rect_.location.x; + } + break; + + default: + //TODO: Log that we discard a mask that is not supported + stream.discard_bytes(length); + break; + }; +} + +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; + + stream.read_long(); // version + this->flags_ = static_cast(stream.read_long()); // flags + int path_records = (length - 8) / 26; + + 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); + + for (int i = 1; i <= path_records; ++i) + { + auto selector = stream.read_short(); + if (selector == 2) //we only concern ourselves with closed paths + { + auto p_y = stream.read_long(); + auto p_x = stream.read_long(); + path_point cp_prev(p_x, p_y); + + auto a_y = stream.read_long(); + auto a_x = stream.read_long(); + path_point anchor(a_x, a_y); + + auto n_y = stream.read_long(); + auto n_x = stream.read_long(); + path_point cp_next(n_x, n_y); + + if (anchor == cp_prev && anchor == cp_next) + knots.push_back(anchor); + else + { //we can't handle smooth curves yet + bFail = true; + } + } + + auto offset = PATH_POINT_RECORD_SIZE * i; + 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) { + rect_.clear(); + flags_ = static_cast(flags::unsupported); + } + else + { + //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 + } + + return !bFail; +} + +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) + {} + +private: + std::vector channels_; + blend_mode blend_mode_; + layer_type layer_type_; + int link_group_id_; + int opacity_; + int sheet_color_; + bool baseClipping_; + std::uint8_t flags_; + std::uint32_t protection_flags_; + std::wstring name_; + int masks_count_; + double text_scale_; + + layer::mask_info mask_; + + rect bitmap_rect_; + image8bit_ptr bitmap_; + + boost::property_tree::wptree text_layer_info_; + boost::property_tree::wptree timeline_info_; + + color solid_color_; + + layer_tag tags_; + +public: + void populate(bigendian_file_input_stream& stream, const psd_document& doc) + { + bitmap_rect_.location.y = stream.read_long(); + bitmap_rect_.location.x = stream.read_long(); + bitmap_rect_.size.height = stream.read_long() - bitmap_rect_.location.y; + bitmap_rect_.size.width = stream.read_long() - bitmap_rect_.location.x; + + //Get info about the channels in the layer + auto channelCount = stream.read_short(); + for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex) + { + auto id = static_cast(stream.read_short()); + channel c(id, stream.read_long()); + + if(c.id < -1) + masks_count_++; + + channels_.push_back(c); + } + + auto blendModeSignature = stream.read_long(); + if(blendModeSignature != '8BIM') + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("blendModeSignature != '8BIM'")); + + blend_mode_ = int_to_blend_mode(stream.read_long()); + opacity_ = stream.read_byte(); + baseClipping_ = stream.read_byte() == 1 ? false : true; + flags_ = stream.read_byte(); + + stream.discard_bytes(1); //padding + + auto extras_size = stream.read_long(); + auto position = stream.current_position(); + mask_.read_mask_data(stream); + read_blending_ranges(stream); + + stream.read_pascal_string(4); //throw this away. We'll read the unicode version of the name later + + //Aditional Layer Information + auto end_of_layer_info = position + extras_size; + try + { + while(stream.current_position() < end_of_layer_info) + { + read_chunk(stream, doc); + } + } + catch(psd_file_format_exception&) + { + stream.set_position(end_of_layer_info); + } + } + + void read_chunk(bigendian_file_input_stream& stream, const psd_document& doc, bool isMetadata = false) + { + auto signature = stream.read_long(); + if(signature != '8BIM' && signature != '8B64') + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM' && signature != '8B64'")); + + auto key = stream.read_long(); + + if(isMetadata) stream.read_long(); + + auto length = stream.read_long(); + auto end_of_chunk = stream.current_position() + length; + + try + { + switch(key) + { + case 'SoCo': + read_solid_color(stream); + break; + + case 'lsct': //group settings (folders) + read_group_settings(stream, length); + + case 'lspf': //protection settings + protection_flags_ = stream.read_long(); + break; + + case 'Txt2': //text engine data + break; + + case 'luni': + set_name_and_tags(stream.read_unicode_string()); + break; + + case 'TySh': //type tool object settings + read_text_data(stream); + break; + + case 'shmd': //metadata + read_metadata(stream, doc); + break; + + case 'lclr': + sheet_color_ = static_cast(stream.read_short()); + break; + + case 'lyvr': //layer version + break; + + case 'lnkD': //linked layer + case 'lnk2': //linked layer + case 'lnk3': //linked layer + break; + + case 'vmsk': + mask_.read_vector_mask_data(length, stream, doc.width(), doc.height()); + break; + + case 'tmln': + read_timeline_data(stream); + break; + + default: + break; + } + } + catch(psd_file_format_exception& ex) + { + //ignore failed chunks silently + CASPAR_LOG(warning) << ex.what(); + } + + stream.set_position(end_of_chunk); + } + + void set_name_and_tags(const std::wstring& name) { + auto start_bracket = name.find_first_of(L'['); + auto end_bracket = name.find_first_of(L']'); + if (start_bracket == std::wstring::npos && end_bracket == std::wstring::npos) { + //no flags + name_ = name; + } + else if (start_bracket != std::wstring::npos && end_bracket > start_bracket) { + //we have tags + tags_ = string_to_layer_tags(name.substr(start_bracket+1, end_bracket-start_bracket-1)); + name_ = name.substr(end_bracket+1); + } + else { + //missmatch + name_ = name; + CASPAR_LOG(warning) << "Mismatching tag-brackets in layer name"; + } + + boost::trim(name_); + } + + void read_solid_color(bigendian_file_input_stream& stream) + { + if(stream.read_long() != 16) //"descriptor version" should be 16 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16")); + + descriptor solid_descriptor(L"solid_color"); + solid_descriptor.populate(stream); + solid_color_.red = static_cast(solid_descriptor.items().get(L"Clr .Rd ", 0.0) + 0.5); + solid_color_.green = static_cast(solid_descriptor.items().get(L"Clr .Grn ", 0.0) + 0.5); + solid_color_.blue = static_cast(solid_descriptor.items().get(L"Clr .Bl ", 0.0) + 0.5); + solid_color_.alpha = 255; + } + + void read_group_settings(bigendian_file_input_stream& stream, unsigned int length) + { + auto type = stream.read_long(); + unsigned int sub_type = 0; + + if (length >= 12) + { + auto signature = stream.read_long(); + if (signature != '8BIM') + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM'")); + + blend_mode_ = int_to_blend_mode(stream.read_long()); + + if (length >= 16) + sub_type = stream.read_long(); + } + + layer_type_ = int_to_layer_type(type, sub_type); + } + + void read_metadata(bigendian_file_input_stream& stream, const psd_document& doc) + { + int count = stream.read_long(); + for(int index = 0; index < count; ++index) + read_chunk(stream, doc, true); + } + + void read_timeline_data(bigendian_file_input_stream& stream) + { + if(stream.read_long() != 16) //"descriptor version" should be 16 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16")); + + descriptor timeline_descriptor(L"timeline"); + timeline_descriptor.populate(stream); + timeline_info_.swap(timeline_descriptor.items()); + } + + void read_text_data(bigendian_file_input_stream& stream) + { + std::wstring text; //the text in the layer + + stream.read_short(); //should be 1 + + //transformation info + auto xx = stream.read_double(); + 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")); + + text_scale_ = xx; + + if(stream.read_short() != 50) //"text version" should be 50 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid text version")); + + if(stream.read_long() != 16) //"descriptor version" should be 16 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text-data")); + + descriptor text_descriptor(L"text"); + text_descriptor.populate(stream); + auto text_info = text_descriptor.items().get_optional(L"EngineData"); + + if (text_info) + { + std::string str(text_info->begin(), text_info->end()); + read_pdf(text_layer_info_, str); + log::print_child(boost::log::trivial::trace, L"", L"text_layer_info", text_layer_info_); + } + + if(stream.read_short() != 1) //"warp version" should be 1 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid warp version")); + + if(stream.read_long() != 16) //"descriptor version" should be 16 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text warp-data")); + + descriptor warp_descriptor(L"warp"); + warp_descriptor.populate(stream); + stream.read_double(); // w_top + stream.read_double(); // w_left + stream.read_double(); // w_right + stream.read_double(); // w_bottom + } + + //TODO: implement + void read_blending_ranges(bigendian_file_input_stream& stream) + { + auto length = stream.read_long(); + stream.discard_bytes(length); + } + + bool has_channel(channel_type type) + { + return std::find_if(channels_.begin(), channels_.end(), [=](const channel& c) { return c.id == static_cast(type); }) != channels_.end(); + } + + void read_channel_data(bigendian_file_input_stream& stream) + { + image8bit_ptr bitmap; + + bool has_transparency = has_channel(channel_type::transparency); + + if(!bitmap_rect_.empty()) + { + bitmap = std::make_shared(bitmap_rect_.size.width, bitmap_rect_.size.height, 4); + if(!has_transparency) + std::memset(bitmap->data(), 255, bitmap->width()*bitmap->height()*bitmap->channel_count()); + } + + for(auto it = channels_.begin(); it != channels_.end(); ++it) + { + auto channel = (*it); + image8bit_ptr target; + int offset = 0; + bool discard_channel = false; + + //determine target bitmap and offset + if(channel.id >= 3) + discard_channel = true; //discard channels that doesn't contribute to the final image + else if(channel.id >= -1) //BGRA-data + { + target = bitmap; + offset = (channel.id >= 0) ? 2 - channel.id : 3; + } + else //mask + { + offset = 0; + if (channel.id == -2) + { + mask_.create_bitmap(); + target = mask_.bitmap_; + } + else if (channel.id == -3) //total_mask + { + mask_.total_mask_->create_bitmap(); + target = mask_.total_mask_->bitmap_; + offset = 0; + } + } + + if(!target) + discard_channel = true; + + auto end_of_data = stream.current_position() + channel.data_length; + if(!discard_channel) + { + auto encoding = stream.read_short(); + if(target) + { + if(encoding == 0) + read_raw_image_data(stream, channel.data_length-2, target, offset); + else if(encoding == 1) + read_rle_image_data(stream, target, offset); + else + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Unhandled image data encoding: " + boost::lexical_cast(encoding))); + } + } + stream.set_position(end_of_data); + } + + if(bitmap && has_transparency) + { + caspar::image::image_view view(bitmap->data(), bitmap->width(), bitmap->height()); + caspar::image::premultiply(view); + } + + bitmap_ = bitmap; + } + + void read_raw_image_data(bigendian_file_input_stream& stream, int data_length, image8bit_ptr target, int offset) + { + auto total_length = target->width() * target->height(); + if (total_length != data_length) + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("total_length != data_length")); + + auto data = target->data(); + auto stride = target->channel_count(); + + if (stride == 1) + stream.read(reinterpret_cast(data + offset), total_length); + else + { + for(int index = 0; index < total_length; ++index) + data[index * stride + offset] = stream.read_byte(); + } + } + + void read_rle_image_data(bigendian_file_input_stream& stream, image8bit_ptr target, int offset) + { + auto width = target->width(); + auto height = target->height(); + auto stride = target->channel_count(); + + std::vector scanline_lengths; + scanline_lengths.reserve(height); + + for (int scanlineIndex = 0; scanlineIndex < height; ++scanlineIndex) + scanline_lengths.push_back(static_cast(stream.read_short())); + + auto target_data = target->data(); + + std::vector line(width); + + for(int scanlineIndex=0; scanlineIndex < height; ++scanlineIndex) + { + int colIndex = 0; + + do + { + int length = 0; + + //Get controlbyte + char controlByte = static_cast(stream.read_byte()); + if(controlByte >= 0) + { + //Read uncompressed string + length = controlByte+1; + for(int index=0; index < length; ++index) + line[colIndex+index] = stream.read_byte(); + } + else if(controlByte > -128) + { + //Repeat next byte + length = -controlByte+1; + auto value = stream.read_byte(); + for(int index=0; index < length; ++index) + line[colIndex+index] = value; + } + + colIndex += length; + } + while(colIndex < width); + + //use line to populate target + for(int index = 0; index < width; ++index) + { + target_data[(scanlineIndex*width+index) * stride + offset] = line[index]; + } + } + } +}; + +layer::layer() : impl_(spl::make_shared()) {} + +void layer::populate(bigendian_file_input_stream& stream, const psd_document& doc) { impl_->populate(stream, doc); } +void layer::read_channel_data(bigendian_file_input_stream& stream) { impl_->read_channel_data(stream); } + +const std::wstring& layer::name() const { return impl_->name_; } +int layer::opacity() const { return impl_->opacity_; } +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; } + +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_; } + +bool layer::has_timeline() const { return !impl_->timeline_info_.empty(); } +const boost::property_tree::wptree& layer::timeline_data() const { return impl_->timeline_info_; } + +bool layer::is_solid() const { return impl_->solid_color_.alpha != 0; } +color layer::solid_color() const { return impl_->solid_color_; } + +const point& layer::location() const { return impl_->bitmap_rect_.location; } +const size& layer::size() const { return impl_->bitmap_rect_.size; } +const image8bit_ptr& layer::bitmap() const { return impl_->bitmap_; } + +layer_type layer::group_mode() const { return impl_->layer_type_; } +int layer::link_group_id() const { return impl_->link_group_id_; } +void layer::set_link_group_id(int id) { impl_->link_group_id_ = id; } + +bool layer::is_explicit_dynamic() const { return (impl_->tags_ & layer_tag::explicit_dynamic) == layer_tag::explicit_dynamic; } +bool layer::is_static() const { return (impl_->tags_ & layer_tag::rasterized) == 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; } +layer_tag layer::tags() const { return impl_->tags_; } + +} //namespace psd +} //namespace caspar diff --git a/modules/psd/layer.h b/modules/psd/layer.h index 74edc0cd1..e8a920457 100644 --- a/modules/psd/layer.h +++ b/modules/psd/layer.h @@ -146,6 +146,13 @@ public: int link_group_id() const; void set_link_group_id(int id); + + bool is_explicit_dynamic() const; + bool is_static() const; + bool is_movable() const; + bool is_resizable() const; + bool is_placeholder() const; + layer_tag tags() const; }; } //namespace psd diff --git a/modules/psd/misc.cpp b/modules/psd/misc.cpp index 5ea9a8f45..17df31ca6 100644 --- a/modules/psd/misc.cpp +++ b/modules/psd/misc.cpp @@ -19,7 +19,9 @@ * Author: Niklas P Andersson, niklas.p.andersson@svt.se */ +#include #include + #include "misc.h" namespace caspar { namespace psd { @@ -137,5 +139,37 @@ std::wstring color_mode_to_string(color_mode c) }; } +layer_tag string_to_layer_tags(const std::wstring& str) { + std::vector flags; + boost::split(flags, str, boost::is_any_of(L", "), boost::token_compress_on); + + layer_tag result = layer_tag::none; + for (auto& flag : flags) { + if (boost::algorithm::iequals(flag, "producer")) + result = result | layer_tag::placeholder; + else if (boost::algorithm::iequals(flag, "dynamic")) { + result = result | layer_tag::explicit_dynamic; + result = result & (~layer_tag::rasterized); + } + else if (boost::algorithm::iequals(flag, "static")) { + result = result | layer_tag::rasterized; + result = result & (~layer_tag::explicit_dynamic); + } + else if (boost::algorithm::iequals(flag, "movable")) { + result = result | layer_tag::moveable; + result = result & (~layer_tag::resizable); + result = result & (~layer_tag::explicit_dynamic); + } + else if (boost::algorithm::iequals(flag, "resizable")) { + result = result | layer_tag::resizable; + result = result & (~layer_tag::moveable); + result = result & (~layer_tag::explicit_dynamic); + } + } + + return result; +} + + } //namespace psd } //namespace caspar diff --git a/modules/psd/misc.h b/modules/psd/misc.h index 86496bc6f..8fe8de9cd 100644 --- a/modules/psd/misc.h +++ b/modules/psd/misc.h @@ -137,6 +137,35 @@ enum class color_mode color_mode int_to_color_mode(std::uint16_t x); std::wstring color_mode_to_string(color_mode c); + +enum class layer_tag : int { + none = 0, + placeholder = 1, + explicit_dynamic = 2, + moveable = 4, + resizable = 8, + rasterized = 16, + all = 31 +}; +inline layer_tag operator | (layer_tag lhs, layer_tag rhs) +{ + return (layer_tag)(static_cast(lhs) | static_cast(rhs)); +} +inline layer_tag operator & (layer_tag lhs, layer_tag rhs) +{ + return (layer_tag)(static_cast(lhs) & static_cast(rhs)); +} +inline layer_tag operator ^ (layer_tag lhs, layer_tag rhs) +{ + return (layer_tag)(static_cast(lhs) ^ static_cast(rhs)); +} +inline layer_tag operator ~ (layer_tag rhs) +{ + return (layer_tag)(static_cast(layer_tag::all) ^ static_cast(rhs)); +} + +layer_tag string_to_layer_tags(const std::wstring& str); + } //namespace psd } //namespace caspar diff --git a/modules/psd/psd_scene_producer.cpp b/modules/psd/psd_scene_producer.cpp index 704017ba5..cfbd9876b 100644 --- a/modules/psd/psd_scene_producer.cpp +++ b/modules/psd/psd_scene_producer.cpp @@ -98,103 +98,103 @@ core::text::text_info get_text_info(const boost::property_tree::wptree& ptree) } - class layer_link_constructor +class dependency_resolver +{ + struct layer_record { - struct linked_layer_record - { - caspar::core::scene::layer* layer; - int link_id; - bool is_master; - double adjustment_x; - double adjustment_y; - }; - - std::vector layers; - std::vector masters; - - public: - void add(caspar::core::scene::layer* layer, int link_group, bool master, double adjustment_x, double adjustment_y) + layer_record() : layer(nullptr), tags(layer_tag::none), adjustment_x(0), adjustment_y(0) + {} + + caspar::core::scene::layer* layer; + layer_tag tags; + double adjustment_x; + double adjustment_y; + }; + + std::list layers; + layer_record master; + + spl::shared_ptr scene_; + bool is_root_; + +public: + dependency_resolver(const spl::shared_ptr& s) : + scene_(s), is_root_(false) + {} + dependency_resolver(const spl::shared_ptr& s, bool root) : + scene_(s), is_root_(root) + {} + + spl::shared_ptr scene() { return scene_; } + + void add(caspar::core::scene::layer* layer, layer_tag tags, double adjustment_x, double adjustment_y) + { + layer_record rec; + rec.layer = layer; + rec.tags = tags; + rec.adjustment_x = adjustment_x; + rec.adjustment_y = adjustment_y; + layers.push_back(rec); + + //if the layer is either explicitly tagged as dynamic or at least not tagged as static/rasterized we should try to set it as master + if((rec.tags & layer_tag::explicit_dynamic) == layer_tag::explicit_dynamic || (rec.tags & layer_tag::rasterized) == layer_tag::none) { - linked_layer_record rec; - rec.layer = layer; - rec.link_id = link_group; - rec.is_master = master; - rec.adjustment_x = adjustment_x; - rec.adjustment_y = adjustment_y; - layers.push_back(rec); - - if(rec.is_master) + //if we don't have a master already, just assign this + if (master.layer == nullptr) + master = rec; + else if ((rec.tags & layer_tag::explicit_dynamic) == layer_tag::explicit_dynamic) { - auto it = std::find_if(masters.begin(), masters.end(), [=](linked_layer_record const &r){ return r.link_id == link_group; }); - if(it == masters.end()) - masters.push_back(rec); - else - masters.erase(it); //ambigous, two linked layers with locked position + if((master.tags & layer_tag::explicit_dynamic) == layer_tag::none) + { + //if we have a master that's not explicitly tagged as dynamic but this layer is; use this as master + master = rec; + } + else { + //we have multiple layers that're tagged as dynamic, warn that the first one will be used + CASPAR_LOG(warning) << "Multiple explicitly dynamic layers in the same group. The first one will be used"; + } } } + } + + void calculate() + { + if (layers.size() == 0) + return; + + if (master.layer == nullptr) { + CASPAR_LOG(warning) << "Dependent layers without any dynamic layer"; + return; + } - void calculate() + for (auto &r : layers) { - for(auto it = masters.begin(); it != masters.end(); ++it) + if (r.layer != master.layer) { - auto& master = (*it); - //std::for_each(layers.begin(), layers.end(), [&master](linked_layer_record &r) mutable { - for (auto &r : layers) { - if(r.link_id == master.link_id && r.layer != master.layer) - { - { //x-coords - double slave_left = r.layer->position.x.get() + r.adjustment_x; - double slave_right = slave_left + r.layer->producer.get()->pixel_constraints().width.get(); - - double master_left = master.layer->position.x.get() + master.adjustment_x; - double master_right = master_left + master.layer->producer.get()->pixel_constraints().width.get(); - - if((slave_left >= master_left && slave_right <= master_right) || (slave_left <= master_left && slave_right >= master_right)) - { - //change width of slave - r.layer->position.x.bind(master.layer->position.x - r.adjustment_x + master.adjustment_x + (slave_left - master_left)); - r.layer->producer.get()->pixel_constraints().width.bind(master.layer->producer.get()->pixel_constraints().width + (slave_right - slave_left - master_right + master_left)); - } - else if(slave_left >= master_right) - { - //push slave ahead of master - r.layer->position.x.bind(master.layer->position.x - r.adjustment_x + master.adjustment_x + master.layer->producer.get()->pixel_constraints().width + (slave_left - master_right)); - } - else if(slave_right <= master_left) - { - r.layer->position.x.bind(master.layer->position.x - r.adjustment_x + master.adjustment_x - (master_left - slave_left)); - } - } - - { //y-coords - double slave_top = r.layer->position.y.get() + r.adjustment_y; - double slave_bottom = slave_top + r.layer->producer.get()->pixel_constraints().height.get(); - - double master_top = master.layer->position.y.get() + master.adjustment_y; - double master_bottom = master_top + master.layer->producer.get()->pixel_constraints().height.get(); - - if((slave_top >= master_top && slave_bottom <= master_bottom) || (slave_top <= master_top && slave_bottom >= master_bottom)) - { - //change height of slave - r.layer->position.y.bind(master.layer->position.y - r.adjustment_y + master.adjustment_y + (slave_top - master_top)); - r.layer->producer.get()->pixel_constraints().height.bind(master.layer->producer.get()->pixel_constraints().height + (slave_bottom - slave_top - master_bottom + master_top)); - } - else if(slave_top >= master_bottom) - { - //push slave ahead of master - r.layer->position.y.bind(master.layer->position.y - r.adjustment_y + master.adjustment_y + master.layer->producer.get()->pixel_constraints().height + (slave_top - master_bottom)); - } - else if(slave_bottom <= master_top) - { - r.layer->position.y.bind(master.layer->position.y - r.adjustment_y + master.adjustment_y - (master_top - slave_top)); - } - } - - } + //x-coords + double slave_left = r.layer->position.x.get(); + double master_left = master.layer->position.x.get(); + double slave_right = slave_left + r.layer->producer.get()->pixel_constraints().width.get(); + double master_right = master_left + master.layer->producer.get()->pixel_constraints().width.get(); + + double slave_width = slave_right - slave_left; + double master_width = master_right - master_left; + + double offset_start_x = slave_left - master_left; + double offset_end_x = slave_left - master_right; + + if((r.tags & layer_tag::moveable) == layer_tag::moveable) + r.layer->position.x.bind(master.layer->position.x + master.layer->producer.get()->pixel_constraints().width + offset_end_x + r.adjustment_x); + else if ((r.tags & layer_tag::resizable) == layer_tag::resizable) { + r.layer->position.x.bind(master.layer->position.x + offset_start_x + r.adjustment_x); + r.layer->producer.get()->pixel_constraints().width.bind(master.layer->producer.get()->pixel_constraints().width + (slave_width - master_width)); } + + //no support for changes in the y-direction yet } } - }; + } +}; int64_t get_frame_number( const core::video_format_desc& format_desc, @@ -365,48 +365,54 @@ spl::shared_ptr create_psd_scene_producer(const core::fram auto root = spl::make_shared(L"psd", doc.width(), doc.height(), dependencies.format_desc); - layer_link_constructor link_constructor; - std::vector>> text_producers_by_layer_name; - std::stack> current_scene_stack; - current_scene_stack.push(root); + std::stack scene_stack; + scene_stack.push(dependency_resolver{ root, true }); auto layers_end = doc.layers().rend(); for(auto it = doc.layers().rbegin(); it != layers_end; ++it) { - auto psd_layer = (*it); - auto current_scene = current_scene_stack.top(); + auto& psd_layer = (*it); + auto& current = scene_stack.top(); if (psd_layer->group_mode() == layer_type::group) { + //we've found a group. Create a new scene and add it to the scene-stack + auto group = spl::make_shared(psd_layer->name(), doc.width(), doc.height(), dependencies.format_desc); - auto& scene_layer = current_scene->create_layer(group, psd_layer->location().x, psd_layer->location().y, psd_layer->name()); + auto& scene_layer = current.scene()->create_layer(group, psd_layer->location().x, psd_layer->location().y, psd_layer->name()); scene_layer.adjustments.opacity.set(psd_layer->opacity() / 255.0); scene_layer.hidden.set(!psd_layer->is_visible()); if (psd_layer->has_timeline()) - create_timelines(current_scene, dependencies.format_desc, scene_layer, psd_layer, 0, 0); + create_timelines(root, dependencies.format_desc, scene_layer, psd_layer, 0, 0); + + if (psd_layer->is_movable()) + current.add(&scene_layer, psd_layer->tags(), 0, 0); - if (psd_layer->link_group_id() != 0) - link_constructor.add(&scene_layer, psd_layer->link_group_id(), psd_layer->is_position_protected(), 0, 0); + if (psd_layer->is_resizable()) + CASPAR_LOG(warning) << "Groups doesn't support the \"resizable\"-tag."; - current_scene_stack.push(group); + if (psd_layer->is_explicit_dynamic()) + CASPAR_LOG(warning) << "Groups doesn't support the \"dynamic\"-tag."; + + scene_stack.push(group); continue; } else if (psd_layer->group_mode() == layer_type::group_delimiter) { - current_scene->reverse_layers(); - current_scene_stack.pop(); + //a group-terminator. Finish the scene + current.scene()->reverse_layers(); + current.calculate(); + scene_stack.pop(); continue; } if(psd_layer->is_visible()) { - bool is_text = psd_layer->is_text(); - bool is_marked_as_dynamic = psd_layer->sheet_color() != 1; - std::wstring layer_name = psd_layer->name(); + auto layer_name = psd_layer->name(); - if(is_text && is_marked_as_dynamic) + if(psd_layer->is_text() && !psd_layer->is_static()) { std::wstring str = psd_layer->text_data().get(L"EngineDict.Editor.Text", L""); @@ -420,16 +426,14 @@ spl::shared_ptr create_psd_scene_producer(const core::fram auto 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 auto adjustment_y = metrics.bearingY; - auto& scene_layer = current_scene->create_layer(text_producer, psd_layer->location().x + adjustment_x, psd_layer->location().y + adjustment_y, psd_layer->name()); + auto& scene_layer = current.scene()->create_layer(text_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()); if (psd_layer->has_timeline()) - create_timelines(current_scene, dependencies.format_desc, scene_layer, psd_layer, adjustment_x, adjustment_y); - - if(psd_layer->link_group_id() != 0) - link_constructor.add(&scene_layer, psd_layer->link_group_id(), psd_layer->is_position_protected(), -adjustment_x, -adjustment_y); + create_timelines(root, dependencies.format_desc, scene_layer, psd_layer, adjustment_x, adjustment_y); + current.add(&scene_layer, psd_layer->tags(), -adjustment_x, -adjustment_y); text_producers_by_layer_name.push_back(std::make_pair(layer_name, text_producer)); } else @@ -441,10 +445,10 @@ spl::shared_ptr create_psd_scene_producer(const core::fram } else if(psd_layer->bitmap()) { - if (boost::algorithm::istarts_with(layer_name, L"[producer]")) + 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.substr(10))); + hotswap->producer().set(dependencies.producer_registry->create_producer(dependencies, layer_name)); layer_producer = hotswap; } else @@ -463,22 +467,25 @@ spl::shared_ptr create_psd_scene_producer(const core::fram if(layer_producer) { - auto& scene_layer = current_scene->create_layer(spl::make_shared_ptr(layer_producer), psd_layer->location().x, psd_layer->location().y, psd_layer->name()); + auto& scene_layer = current.scene()->create_layer(spl::make_shared_ptr(layer_producer), psd_layer->location().x, psd_layer->location().y, psd_layer->name()); scene_layer.adjustments.opacity.set(psd_layer->opacity() / 255.0); scene_layer.hidden.set(!psd_layer->is_visible()); if (psd_layer->has_timeline()) - create_timelines(current_scene, dependencies.format_desc, scene_layer, psd_layer, 0, 0); + create_timelines(root, dependencies.format_desc, scene_layer, psd_layer, 0, 0); - if(psd_layer->link_group_id() != 0) - link_constructor.add(&scene_layer, psd_layer->link_group_id(), psd_layer->is_position_protected(), 0, 0); + if(psd_layer->is_movable() || psd_layer->is_resizable()) + current.add(&scene_layer, psd_layer->tags(), 0, 0); } } } + } + if (scene_stack.size() != 1) { + } root->reverse_layers(); + scene_stack.top().calculate(); - link_constructor.calculate(); // Reset all dynamic text fields to empty strings and expose them as a scene parameter. for (auto& text_layer : text_producers_by_layer_name) -- 2.39.2