]> git.sesse.net Git - casparcg/commitdiff
initial fixes for new psd workflow
authorNiklas P Andersson <niklas.p.andersson@svt.se>
Tue, 1 Dec 2015 14:36:02 +0000 (15:36 +0100)
committerNiklas P Andersson <niklas.p.andersson@svt.se>
Tue, 1 Dec 2015 14:39:22 +0000 (15:39 +0100)
core/producer/text/text_producer.cpp
modules/flash/producer/FlashAxContainer.cpp
modules/psd/layer.cpp
modules/psd/layer.h
modules/psd/misc.cpp
modules/psd/misc.h
modules/psd/psd_scene_producer.cpp

index 58bfa55988ad6a6c587f8122956b82b73408b4e6..35c2a045690bc30670c80b76577a1b97c31702db 100644 (file)
@@ -140,7 +140,7 @@ struct text_producer::impl
        variable_impl<double>                                   current_bearing_y_;
        variable_impl<double>                                   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;
 
index c0da13a5be2295ad814c71e8971215e9635918f7..7ec8ea1521a671fff68fe2db6962d75c0b907067 100644 (file)
@@ -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");
index c866927f66f55e40f0c3fb1b39faa238df508857..acc595e33f886964116e857139058a893fc19796 100644 (file)
-/*
-* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
-*
-* 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 <http://www.gnu.org/licenses/>.
-*
-* 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 <common/log.h>
-
-#include <boost/property_tree/ptree.hpp>
-#include <boost/lexical_cast.hpp>
-
-#include <algorithm>
-
-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<int, int> path_point;
-       bool bFail = false;
-
-       stream.read_long(); // version
-       this->flags_ = static_cast<std::uint8_t>(stream.read_long()); // flags
-       int path_records = (length - 8) / 26;
-
-       auto position = stream.current_position();
-
-       std::vector<path_point> 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<std::uint8_t>(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<int>(knots[0].first * x_ratio + 0.5f);                                                           //add .5 to get propper rounding when converting to integer
-               rect_.location.y = static_cast<int>(knots[0].second * y_ratio + 0.5f);                                                          //add .5 to get propper rounding when converting to integer
-               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
-               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
-       }
-
-       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<channel>                    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<int>                                               bitmap_rect_;
-       image8bit_ptr                                   bitmap_;
-
-       boost::property_tree::wptree    text_layer_info_;
-       boost::property_tree::wptree    timeline_info_;
-
-       color<std::uint8_t>                             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<std::int16_t>(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<std::int16_t>(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<std::uint8_t>(solid_descriptor.items().get(L"Clr .Rd  ", 0.0) + 0.5);
-               solid_color_.green = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Grn ", 0.0) + 0.5);
-               solid_color_.blue = static_cast<std::uint8_t>(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<std::wstring>(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<int>(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<image8bit>(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<std::string>(encoding)));
-                               }
-                       }
-                       stream.set_position(end_of_data);
-               }
-
-               if(bitmap && has_transparency)
-               {
-                       caspar::image::image_view<caspar::image::bgra_pixel> 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<char*>(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<int> scanline_lengths;
-               scanline_lengths.reserve(height);
-
-               for (int scanlineIndex = 0; scanlineIndex < height; ++scanlineIndex)
-                       scanline_lengths.push_back(static_cast<std::int16_t>(stream.read_short()));
-
-               auto target_data = target->data();
-
-               std::vector<std::uint8_t> line(width);
-
-               for(int scanlineIndex=0; scanlineIndex < height; ++scanlineIndex)
-               {
-                       int colIndex = 0;
-
-                       do
-                       {
-                               int length = 0;
-
-                               //Get controlbyte
-                               char controlByte = static_cast<char>(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<impl>()) {}
-
-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<std::uint8_t> layer::solid_color() const { return impl_->solid_color_; }
-
-const point<int>& layer::location() const { return impl_->bitmap_rect_.location; }
-const size<int>& 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
+/*\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
+*\r
+* This file is part of CasparCG (www.casparcg.com).\r
+*\r
+* CasparCG is free software: you can redistribute it and/or modify\r
+* it under the terms of the GNU General Public License as published by\r
+* the Free Software Foundation, either version 3 of the License, or\r
+* (at your option) any later version.\r
+*\r
+* CasparCG is distributed in the hope that it will be useful,\r
+* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+* GNU General Public License for more details.\r
+*\r
+* You should have received a copy of the GNU General Public License\r
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
+*\r
+* Author: Niklas P Andersson, niklas.p.andersson@svt.se\r
+*/\r
+\r
+#include "layer.h"\r
+#include "psd_document.h"\r
+#include "descriptor.h"\r
+#include "util/pdf_reader.h"\r
+\r
+#include "../image/util/image_algorithms.h"\r
+#include "../image/util/image_view.h"\r
+\r
+#include <common/log.h>\r
+\r
+#include <boost/property_tree/ptree.hpp>\r
+#include <boost/algorithm/string.hpp>\r
+#include <boost/lexical_cast.hpp>\r
+\r
+#include <algorithm>\r
+\r
+namespace caspar { namespace psd {\r
+\r
+void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream)\r
+{\r
+       auto length = stream.read_long();\r
+       switch(length)\r
+       {\r
+       case 0:\r
+               break;\r
+\r
+       case 20:\r
+       case 36:        //discard total user mask data\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
+               rect_.size.width = stream.read_long() - rect_.location.x;\r
+\r
+               default_value_ = stream.read_byte();\r
+               flags_ = stream.read_byte();\r
+               stream.discard_bytes(2);\r
+               \r
+               if(length == 36)\r
+               {\r
+                       //we save the information about the total mask in case the vector-mask has an unsupported shape\r
+                       total_mask_.reset(new layer::mask_info);\r
+                       total_mask_->flags_ = stream.read_byte();\r
+                       total_mask_->default_value_ = stream.read_byte();\r
+                       total_mask_->rect_.location.y = stream.read_long();\r
+                       total_mask_->rect_.location.x = stream.read_long();\r
+                       total_mask_->rect_.size.height = stream.read_long() - rect_.location.y;\r
+                       total_mask_->rect_.size.width = stream.read_long() - rect_.location.x;\r
+               }\r
+               break;\r
+\r
+       default:\r
+               //TODO: Log that we discard a mask that is not supported\r
+               stream.discard_bytes(length);\r
+               break;\r
+       };\r
+}\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
+\r
+       stream.read_long(); // version\r
+       this->flags_ = static_cast<std::uint8_t>(stream.read_long()); // flags\r
+       int path_records = (length - 8) / 26;\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
+       for (int i = 1; i <= path_records; ++i)\r
+       {\r
+               auto selector = stream.read_short();\r
+               if (selector == 2)      //we only concern ourselves with closed paths \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
+\r
+                       auto a_y = stream.read_long();\r
+                       auto a_x = stream.read_long();\r
+                       path_point anchor(a_x, 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
+\r
+                       if (anchor == cp_prev && anchor == cp_next)\r
+                               knots.push_back(anchor);\r
+                       else\r
+                       {       //we can't handle smooth curves yet\r
+                               bFail = true;\r
+                       }\r
+               }\r
+\r
+               auto offset = PATH_POINT_RECORD_SIZE * i;\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
+               rect_.clear();\r
+               flags_ = static_cast<std::uint8_t>(flags::unsupported);\r
+       }\r
+       else \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
+       }\r
+\r
+       return !bFail;\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
+       {}\r
+\r
+private:\r
+       std::vector<channel>                    channels_;\r
+       blend_mode                                              blend_mode_;\r
+       layer_type                                              layer_type_;\r
+       int                                                             link_group_id_;\r
+       int                                                             opacity_;\r
+       int                                                             sheet_color_;\r
+       bool                                                    baseClipping_;\r
+       std::uint8_t                                    flags_;\r
+       std::uint32_t                                   protection_flags_;\r
+       std::wstring                                    name_;\r
+       int                                                             masks_count_;\r
+       double                                                  text_scale_;\r
+\r
+       layer::mask_info                                mask_;\r
+\r
+       rect<int>                                               bitmap_rect_;\r
+       image8bit_ptr                                   bitmap_;\r
+\r
+       boost::property_tree::wptree    text_layer_info_;\r
+       boost::property_tree::wptree    timeline_info_;\r
+\r
+       color<std::uint8_t>                             solid_color_;\r
+\r
+       layer_tag                                               tags_;\r
+\r
+public:\r
+       void populate(bigendian_file_input_stream& stream, const psd_document& doc)\r
+       {\r
+               bitmap_rect_.location.y = stream.read_long();\r
+               bitmap_rect_.location.x = stream.read_long();\r
+               bitmap_rect_.size.height = stream.read_long() - bitmap_rect_.location.y;\r
+               bitmap_rect_.size.width = stream.read_long() - bitmap_rect_.location.x;\r
+\r
+               //Get info about the channels in the layer\r
+               auto channelCount = stream.read_short();\r
+               for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex)\r
+               {\r
+                       auto id = static_cast<std::int16_t>(stream.read_short());\r
+                       channel c(id, stream.read_long());\r
+\r
+                       if(c.id < -1)\r
+                               masks_count_++;\r
+\r
+                       channels_.push_back(c);\r
+               }\r
+\r
+               auto blendModeSignature = stream.read_long();\r
+               if(blendModeSignature != '8BIM')\r
+                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("blendModeSignature != '8BIM'"));\r
+\r
+               blend_mode_ = int_to_blend_mode(stream.read_long());\r
+               opacity_ = stream.read_byte();\r
+               baseClipping_ = stream.read_byte() == 1 ? false : true;\r
+               flags_ = stream.read_byte();\r
+\r
+               stream.discard_bytes(1);        //padding\r
+\r
+               auto extras_size = stream.read_long();\r
+               auto position = stream.current_position();\r
+               mask_.read_mask_data(stream);\r
+               read_blending_ranges(stream);\r
+\r
+               stream.read_pascal_string(4);   //throw this away. We'll read the unicode version of the name later\r
+\r
+               //Aditional Layer Information\r
+               auto end_of_layer_info = position + extras_size;\r
+               try\r
+               {\r
+                       while(stream.current_position() < end_of_layer_info)\r
+                       {\r
+                               read_chunk(stream, doc);\r
+                       }\r
+               }\r
+               catch(psd_file_format_exception&)\r
+               {\r
+                       stream.set_position(end_of_layer_info);\r
+               }\r
+       }\r
+\r
+       void read_chunk(bigendian_file_input_stream& stream, const psd_document& doc, bool isMetadata = false)\r
+       {\r
+               auto signature = stream.read_long();\r
+               if(signature != '8BIM' && signature != '8B64')\r
+                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM' && signature != '8B64'"));\r
+\r
+               auto key = stream.read_long();\r
+\r
+               if(isMetadata) stream.read_long();\r
+\r
+               auto length = stream.read_long();\r
+               auto end_of_chunk = stream.current_position() + length;\r
+\r
+               try\r
+               {\r
+                       switch(key)\r
+                       {\r
+                       case 'SoCo':\r
+                               read_solid_color(stream);\r
+                               break;\r
+\r
+                       case 'lsct':    //group settings (folders)\r
+                               read_group_settings(stream, length);\r
+\r
+                       case 'lspf':    //protection settings\r
+                               protection_flags_ = stream.read_long();\r
+                               break;\r
+\r
+                       case 'Txt2':    //text engine data\r
+                               break;\r
+\r
+                       case 'luni':\r
+                               set_name_and_tags(stream.read_unicode_string());\r
+                               break;\r
+\r
+                       case 'TySh':    //type tool object settings\r
+                               read_text_data(stream);\r
+                               break;\r
+                               \r
+                       case 'shmd':    //metadata\r
+                               read_metadata(stream, doc);\r
+                               break;\r
+\r
+                       case 'lclr':\r
+                               sheet_color_ = static_cast<std::int16_t>(stream.read_short());\r
+                               break;\r
+                               \r
+                       case 'lyvr':    //layer version\r
+                               break;\r
+\r
+                       case 'lnkD':    //linked layer\r
+                       case 'lnk2':    //linked layer\r
+                       case 'lnk3':    //linked layer\r
+                               break;\r
+\r
+                       case 'vmsk':\r
+                               mask_.read_vector_mask_data(length, stream, doc.width(), doc.height());\r
+                               break;\r
+                               \r
+                       case 'tmln':\r
+                               read_timeline_data(stream);\r
+                               break;\r
+\r
+                       default:\r
+                               break;\r
+                       }\r
+               }\r
+               catch(psd_file_format_exception& ex)\r
+               {\r
+                       //ignore failed chunks silently\r
+                       CASPAR_LOG(warning) << ex.what();\r
+               }\r
+\r
+               stream.set_position(end_of_chunk);\r
+       }\r
+         \r
+       void set_name_and_tags(const std::wstring& name) {\r
+               auto start_bracket = name.find_first_of(L'[');\r
+               auto end_bracket = name.find_first_of(L']');\r
+               if (start_bracket == std::wstring::npos && end_bracket == std::wstring::npos) {\r
+                       //no flags\r
+                       name_ = name;\r
+               }\r
+               else if (start_bracket != std::wstring::npos && end_bracket > start_bracket) {\r
+                       //we have tags\r
+                       tags_ = string_to_layer_tags(name.substr(start_bracket+1, end_bracket-start_bracket-1));\r
+                       name_ = name.substr(end_bracket+1);\r
+               }\r
+               else {\r
+                       //missmatch\r
+                       name_ = name;\r
+                       CASPAR_LOG(warning) << "Mismatching tag-brackets in layer name";\r
+               }\r
+\r
+               boost::trim(name_);\r
+       }\r
+\r
+       void read_solid_color(bigendian_file_input_stream& stream)\r
+       {\r
+               if(stream.read_long() != 16)    //"descriptor version" should be 16\r
+                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16"));\r
+\r
+               descriptor solid_descriptor(L"solid_color");\r
+               solid_descriptor.populate(stream);\r
+               solid_color_.red = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Rd  ", 0.0) + 0.5);\r
+               solid_color_.green = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Grn ", 0.0) + 0.5);\r
+               solid_color_.blue = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Bl  ", 0.0) + 0.5);\r
+               solid_color_.alpha = 255;\r
+       }\r
+\r
+       void read_group_settings(bigendian_file_input_stream& stream, unsigned int length) \r
+       {\r
+               auto type = stream.read_long();\r
+               unsigned int sub_type = 0;\r
+\r
+               if (length >= 12)\r
+               {\r
+                       auto signature = stream.read_long();\r
+                       if (signature != '8BIM')\r
+                               CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM'"));\r
+\r
+                       blend_mode_ = int_to_blend_mode(stream.read_long());\r
+                       \r
+                       if (length >= 16)\r
+                               sub_type = stream.read_long();\r
+               }\r
+\r
+               layer_type_ = int_to_layer_type(type, sub_type);\r
+       }\r
+\r
+       void read_metadata(bigendian_file_input_stream& stream, const psd_document& doc)\r
+       {\r
+               int count = stream.read_long();\r
+               for(int index = 0; index < count; ++index)\r
+                       read_chunk(stream, doc, true);\r
+       }\r
+\r
+       void read_timeline_data(bigendian_file_input_stream& stream)\r
+       {\r
+               if(stream.read_long() != 16)    //"descriptor version" should be 16\r
+                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16"));\r
+\r
+               descriptor timeline_descriptor(L"timeline");\r
+               timeline_descriptor.populate(stream);\r
+               timeline_info_.swap(timeline_descriptor.items());\r
+       }\r
+\r
+       void read_text_data(bigendian_file_input_stream& stream)\r
+       {\r
+               std::wstring text;      //the text in the layer\r
+\r
+               stream.read_short();    //should be 1\r
+       \r
+               //transformation info\r
+               auto xx = stream.read_double();\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
+\r
+               text_scale_ = xx;\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
+               if(stream.read_long() != 16)    //"descriptor version" should be 16\r
+                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text-data"));\r
+\r
+               descriptor text_descriptor(L"text");\r
+               text_descriptor.populate(stream);\r
+               auto text_info = text_descriptor.items().get_optional<std::wstring>(L"EngineData");\r
+               \r
+               if (text_info)\r
+               {\r
+                       std::string str(text_info->begin(), text_info->end());\r
+                       read_pdf(text_layer_info_, str);\r
+                       log::print_child(boost::log::trivial::trace, L"", L"text_layer_info", text_layer_info_);\r
+               }\r
+\r
+               if(stream.read_short() != 1)    //"warp version" should be 1\r
+                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid warp version"));\r
+\r
+               if(stream.read_long() != 16)    //"descriptor version" should be 16\r
+                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text warp-data"));\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_right\r
+               stream.read_double();  // w_bottom\r
+       }\r
+\r
+       //TODO: implement\r
+       void read_blending_ranges(bigendian_file_input_stream& stream)\r
+       {\r
+               auto length = stream.read_long();\r
+               stream.discard_bytes(length);\r
+       }\r
+\r
+       bool has_channel(channel_type type)\r
+       {\r
+               return std::find_if(channels_.begin(), channels_.end(), [=](const channel& c) { return c.id == static_cast<int>(type); }) != channels_.end();\r
+       }\r
+\r
+       void read_channel_data(bigendian_file_input_stream& stream)\r
+       {\r
+               image8bit_ptr bitmap;\r
+\r
+               bool has_transparency = has_channel(channel_type::transparency);\r
+       \r
+               if(!bitmap_rect_.empty())\r
+               {\r
+                       bitmap = std::make_shared<image8bit>(bitmap_rect_.size.width, bitmap_rect_.size.height, 4);\r
+                       if(!has_transparency)\r
+                               std::memset(bitmap->data(), 255, bitmap->width()*bitmap->height()*bitmap->channel_count());\r
+               }\r
+\r
+               for(auto it = channels_.begin(); it != channels_.end(); ++it)\r
+               {\r
+                       auto channel = (*it);\r
+                       image8bit_ptr target;\r
+                       int offset = 0;\r
+                       bool discard_channel = false;\r
+\r
+                       //determine target bitmap and offset\r
+                       if(channel.id >= 3)\r
+                               discard_channel = true; //discard channels that doesn't contribute to the final image\r
+                       else if(channel.id >= -1)       //BGRA-data\r
+                       {\r
+                               target = bitmap;\r
+                               offset = (channel.id >= 0) ? 2 - channel.id : 3;\r
+                       }\r
+                       else    //mask\r
+                       {\r
+                               offset = 0;\r
+                               if (channel.id == -2)\r
+                               {\r
+                                       mask_.create_bitmap();\r
+                                       target = mask_.bitmap_;\r
+                               }\r
+                               else if (channel.id == -3)      //total_mask\r
+                               {\r
+                                       mask_.total_mask_->create_bitmap();\r
+                                       target = mask_.total_mask_->bitmap_;\r
+                                       offset = 0;\r
+                               }\r
+                       }\r
+\r
+                       if(!target)\r
+                               discard_channel = true;\r
+\r
+                       auto end_of_data = stream.current_position() + channel.data_length;\r
+                       if(!discard_channel)\r
+                       {\r
+                               auto encoding = stream.read_short();\r
+                               if(target)\r
+                               {\r
+                                       if(encoding == 0)\r
+                                               read_raw_image_data(stream, channel.data_length-2, target, offset);\r
+                                       else if(encoding == 1)\r
+                                               read_rle_image_data(stream, target, offset);\r
+                                       else\r
+                                               CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Unhandled image data encoding: " + boost::lexical_cast<std::string>(encoding)));\r
+                               }\r
+                       }\r
+                       stream.set_position(end_of_data);\r
+               }\r
+\r
+               if(bitmap && has_transparency)\r
+               {\r
+                       caspar::image::image_view<caspar::image::bgra_pixel> view(bitmap->data(), bitmap->width(), bitmap->height());\r
+                       caspar::image::premultiply(view);\r
+               }\r
+\r
+               bitmap_ = bitmap;\r
+       }\r
+\r
+       void read_raw_image_data(bigendian_file_input_stream& stream, int data_length, image8bit_ptr target, int offset)\r
+       {\r
+               auto total_length = target->width() * target->height();\r
+               if (total_length != data_length)\r
+                       CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("total_length != data_length"));\r
+\r
+               auto data = target->data();\r
+               auto stride = target->channel_count();\r
+\r
+               if (stride == 1)\r
+                       stream.read(reinterpret_cast<char*>(data + offset), total_length);\r
+               else\r
+               {\r
+                       for(int index = 0; index < total_length; ++index)\r
+                               data[index * stride + offset] = stream.read_byte();\r
+               }\r
+       }\r
+\r
+       void read_rle_image_data(bigendian_file_input_stream& stream, image8bit_ptr target, int offset)\r
+       {\r
+               auto width = target->width();\r
+               auto height = target->height();\r
+               auto stride = target->channel_count();\r
+\r
+               std::vector<int> scanline_lengths;\r
+               scanline_lengths.reserve(height);\r
+\r
+               for (int scanlineIndex = 0; scanlineIndex < height; ++scanlineIndex)\r
+                       scanline_lengths.push_back(static_cast<std::int16_t>(stream.read_short()));\r
+\r
+               auto target_data = target->data();\r
+\r
+               std::vector<std::uint8_t> line(width);\r
+\r
+               for(int scanlineIndex=0; scanlineIndex < height; ++scanlineIndex)\r
+               {\r
+                       int colIndex = 0;\r
+\r
+                       do\r
+                       {\r
+                               int length = 0;\r
+\r
+                               //Get controlbyte\r
+                               char controlByte = static_cast<char>(stream.read_byte());\r
+                               if(controlByte >= 0)\r
+                               {\r
+                                       //Read uncompressed string\r
+                                       length = controlByte+1;\r
+                                       for(int index=0; index < length; ++index)\r
+                                               line[colIndex+index] = stream.read_byte();\r
+                               }\r
+                               else if(controlByte > -128)\r
+                               {\r
+                                       //Repeat next byte\r
+                                       length = -controlByte+1;\r
+                                       auto value = stream.read_byte();\r
+                                       for(int index=0; index < length; ++index)\r
+                                               line[colIndex+index] = value;\r
+                               }\r
+\r
+                               colIndex += length;\r
+                       }\r
+                       while(colIndex < width);\r
+\r
+                       //use line to populate target\r
+                       for(int index = 0; index < width; ++index)\r
+                       {\r
+                               target_data[(scanlineIndex*width+index) * stride + offset] = line[index];\r
+                       }\r
+               }\r
+       }\r
+};\r
+\r
+layer::layer() : impl_(spl::make_shared<impl>()) {}\r
+\r
+void layer::populate(bigendian_file_input_stream& stream, const psd_document& doc) { impl_->populate(stream, doc); }\r
+void layer::read_channel_data(bigendian_file_input_stream& stream) { impl_->read_channel_data(stream); }\r
+\r
+const std::wstring& layer::name() const { return impl_->name_; }\r
+int layer::opacity() const { return impl_->opacity_; }\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
+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::has_timeline() const { return !impl_->timeline_info_.empty(); }\r
+const boost::property_tree::wptree& layer::timeline_data() const { return impl_->timeline_info_; }\r
+\r
+bool layer::is_solid() const { return impl_->solid_color_.alpha != 0; }\r
+color<std::uint8_t> layer::solid_color() const { return impl_->solid_color_; }\r
+\r
+const point<int>& layer::location() const { return impl_->bitmap_rect_.location; }\r
+const size<int>& layer::size() const { return impl_->bitmap_rect_.size; }\r
+const image8bit_ptr& layer::bitmap() const { return impl_->bitmap_; }\r
+\r
+layer_type layer::group_mode() const { return impl_->layer_type_; }\r
+int layer::link_group_id() const { return impl_->link_group_id_; }\r
+void layer::set_link_group_id(int id) { impl_->link_group_id_ = id; }\r
+\r
+bool layer::is_explicit_dynamic() const { return (impl_->tags_ & layer_tag::explicit_dynamic) == layer_tag::explicit_dynamic; }\r
+bool layer::is_static() const { return (impl_->tags_ & layer_tag::rasterized) == layer_tag::rasterized; }\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
+layer_tag layer::tags() const { return impl_->tags_; }\r
+\r
+}      //namespace psd\r
+}      //namespace caspar\r
index 74edc0cd1da6db519828c7f43b3e3887f52855ba..e8a92045717b2899fe3f32226ab3719bb91aab7b 100644 (file)
@@ -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
index 5ea9a8f45da8ed21ecd19047baf44993089fe9a8..17df31ca67ea11355e7f0f1e875f762844e0776f 100644 (file)
@@ -19,7 +19,9 @@
 * Author: Niklas P Andersson, niklas.p.andersson@svt.se
 */
 
+#include <boost/algorithm/string.hpp>
 #include <vector>
+
 #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) {\r
+       std::vector<std::wstring> flags;\r
+       boost::split(flags, str, boost::is_any_of(L", "), boost::token_compress_on);\r
+\r
+       layer_tag result = layer_tag::none;\r
+       for (auto& flag : flags) {\r
+               if (boost::algorithm::iequals(flag, "producer"))\r
+                       result = result | layer_tag::placeholder;\r
+               else if (boost::algorithm::iequals(flag, "dynamic")) {\r
+                       result = result | layer_tag::explicit_dynamic;\r
+                       result = result & (~layer_tag::rasterized);\r
+               }\r
+               else if (boost::algorithm::iequals(flag, "static")) {\r
+                       result = result | layer_tag::rasterized;\r
+                       result = result & (~layer_tag::explicit_dynamic);\r
+               }\r
+               else if (boost::algorithm::iequals(flag, "movable")) {\r
+                       result = result | layer_tag::moveable;\r
+                       result = result & (~layer_tag::resizable);\r
+                       result = result & (~layer_tag::explicit_dynamic);\r
+               }\r
+               else if (boost::algorithm::iequals(flag, "resizable")) {\r
+                       result = result | layer_tag::resizable;\r
+                       result = result & (~layer_tag::moveable);\r
+                       result = result & (~layer_tag::explicit_dynamic);\r
+               }\r
+       }\r
+\r
+       return result;\r
+}
+
+
 }      //namespace psd
 }      //namespace caspar
index 86496bc6ff935c8489f03387f77d6b30e5008f50..8fe8de9cd679746aa002f3b741d0bf97c0e9ee43 100644 (file)
@@ -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);
 
+\r
+enum class layer_tag : int {\r
+       none = 0,\r
+       placeholder = 1,\r
+       explicit_dynamic = 2,\r
+       moveable = 4,\r
+       resizable = 8,\r
+       rasterized = 16,\r
+       all = 31\r
+};\r
+inline layer_tag operator | (layer_tag lhs, layer_tag rhs)\r
+{\r
+       return (layer_tag)(static_cast<int>(lhs) | static_cast<int>(rhs));\r
+}\r
+inline layer_tag operator & (layer_tag lhs, layer_tag rhs)\r
+{\r
+       return (layer_tag)(static_cast<int>(lhs) & static_cast<int>(rhs));\r
+}\r
+inline layer_tag operator ^ (layer_tag lhs, layer_tag rhs)\r
+{\r
+       return (layer_tag)(static_cast<int>(lhs) ^ static_cast<int>(rhs));\r
+}\r
+inline layer_tag operator ~ (layer_tag rhs)\r
+{\r
+       return (layer_tag)(static_cast<int>(layer_tag::all) ^ static_cast<int>(rhs));\r
+}\r
+\r
+layer_tag string_to_layer_tags(const std::wstring& str);
+
 }      //namespace psd
 }      //namespace caspar
 
index 704017ba5f02acb7b28b40fbeffc60113d06ddfe..cfbd9876b15cea3e773eedb1636c5227fbbccbcf 100644 (file)
@@ -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<linked_layer_record> layers;
-               std::vector<linked_layer_record> 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<layer_record> layers;
+       layer_record master;
+       
+       spl::shared_ptr<core::scene::scene_producer> scene_;
+       bool is_root_;
+
+public:
+       dependency_resolver(const spl::shared_ptr<core::scene::scene_producer>& s) :
+               scene_(s), is_root_(false)
+       {}
+       dependency_resolver(const spl::shared_ptr<core::scene::scene_producer>& s, bool root) :
+               scene_(s), is_root_(root)
+       {}
+
+       spl::shared_ptr<core::scene::scene_producer> 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<core::frame_producer> create_psd_scene_producer(const core::fram
 
        auto root = spl::make_shared<core::scene::scene_producer>(L"psd", doc.width(), doc.height(), dependencies.format_desc);
 
-       layer_link_constructor link_constructor;
-
        std::vector<std::pair<std::wstring, spl::shared_ptr<core::text_producer>>> text_producers_by_layer_name;
 
-       std::stack<spl::shared_ptr<core::scene::scene_producer>> current_scene_stack;
-       current_scene_stack.push(root);
+       std::stack<dependency_resolver> 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<core::scene::scene_producer>(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<core::frame_producer> 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<core::frame_producer> 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<core::hotswap_producer>(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<core::frame_producer> 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)