]> git.sesse.net Git - casparcg/commitdiff
Merge branch '2.1.0' of https://github.com/CasparCG/Server into 2.1.0
authorniklaspandersson <niklas.p.andersson@svt.se>
Fri, 16 Aug 2013 14:34:16 +0000 (16:34 +0200)
committerniklaspandersson <niklas.p.andersson@svt.se>
Fri, 16 Aug 2013 14:35:45 +0000 (16:35 +0200)
Conflicts:
modules/psd/psd_scene_producer.cpp

* added support for solid colors in scene_producer factory

1  2 
core/producer/color/color_producer.cpp
core/producer/color/color_producer.h
modules/psd/layer.cpp
modules/psd/misc.h
modules/psd/psd_scene_producer.cpp

index 91d91afeead349b331425e0f4af752c80e53d18d,91d91afeead349b331425e0f4af752c80e53d18d..b3de8c95b93a8f2bb4a6dbc3f495f4e8dcad1ebe
@@@ -38,7 -38,7 +38,7 @@@
  #include <sstream>
  
  namespace caspar { namespace core {
--      
++
  class color_producer : public frame_producer_base
  {
        monitor::basic_subject  event_subject_;
        draw_frame                              frame_;
  
  public:
--      explicit color_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const std::wstring& color) 
++      color_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, uint32_t value)
++              : color_str_(L"")
++              , constraints_(1, 1)
++              , frame_(create_color_frame(this, frame_factory, value))
++      {
++              CASPAR_LOG(info) << print() << L" Initialized";
++      }
++
++
++      color_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const std::wstring& color) 
                : color_str_(color)
                , constraints_(1, 1)
                , frame_(create_color_frame(this, frame_factory, color))
@@@ -155,6 -155,6 +164,11 @@@ bool try_get_color(const std::wstring& 
        return true;
  }
  
++spl::shared_ptr<frame_producer> create_color_producer(const spl::shared_ptr<frame_factory>& frame_factory, uint32_t value)
++{
++      return spl::make_shared<color_producer>(frame_factory, value);
++}
++
  spl::shared_ptr<frame_producer> create_color_producer(const spl::shared_ptr<frame_factory>& frame_factory, const std::vector<std::wstring>& params)
  {
        if(params.size() < 0)
        return spl::make_shared<color_producer>(frame_factory, params[0]);
  }
  
--draw_frame create_color_frame(void* tag, const spl::shared_ptr<frame_factory>& frame_factory, const std::wstring& str)
++draw_frame create_color_frame(void* tag, const spl::shared_ptr<frame_factory>& frame_factory, uint32_t value)
  {
        core::pixel_format_desc desc(pixel_format::bgra);
        desc.planes.push_back(core::pixel_format_desc::plane(1, 1, 4));
        auto frame = frame_factory->create_frame(tag, desc);
        
--      uint32_t value = 0;
--      if(!try_get_color(str, value))
--              BOOST_THROW_EXCEPTION(invalid_argument() << arg_name_info("color") << arg_value_info(str) << msg_info("Invalid color."));
--
        *reinterpret_cast<uint32_t*>(frame.image_data(0).begin()) = value;
  
        return core::draw_frame(std::move(frame));
  }
  
++draw_frame create_color_frame(void* tag, const spl::shared_ptr<frame_factory>& frame_factory, const std::wstring& str)
++{
++      uint32_t value = 0;
++      if(!try_get_color(str, value))
++              BOOST_THROW_EXCEPTION(invalid_argument() << arg_name_info("color") << arg_value_info(str) << msg_info("Invalid color."));
++      
++      return create_color_frame(tag, frame_factory, value);
++}
++
  }}
index 86950e9ad0d3d82e41344ea4054d93974c259361,86950e9ad0d3d82e41344ea4054d93974c259361..25c08e597282dab77d0c5ca202d81071d8f58966
@@@ -30,7 -30,7 +30,9 @@@ namespace caspar { namespace core 
  
  bool try_get_color(const std::wstring& str, uint32_t& value);
  
++spl::shared_ptr<class frame_producer> create_color_producer(const spl::shared_ptr<class frame_factory>& frame_factory, uint32_t value);
  spl::shared_ptr<class frame_producer> create_color_producer(const spl::shared_ptr<class frame_factory>& frame_factory, const std::vector<std::wstring>& params);
++class draw_frame create_color_frame(void* tag, const spl::shared_ptr<frame_factory>& frame_factory, uint32_t value);
  class draw_frame create_color_frame(void* tag, const spl::shared_ptr<class frame_factory>& frame_factory, const std::wstring& color);
  
  }}
index 0b9d91e3b35d6e79fb1abb56c4e13488fa863005,fc9be9794bc92671fd5f18d829b72665052362ac..567d622e47b0b9c4d1086ebb6b4c7822ede28429
  #include "../image/util/image_algorithms.h"
  #include "../image/util/image_view.h"
  
 -namespace caspar { namespace psd {
 +#include <boost/property_tree/ptree.hpp>
  
 -void read_raw_image_data(BEFileInputStream& stream, const channel_ptr& channel, image8bit_ptr target, unsigned char offset);
 -void read_rle_image_data(BEFileInputStream& stream, const channel_ptr& channel, image8bit_ptr target, unsigned char offset);
 +namespace caspar { namespace psd {
  
 -layer_ptr layer::create(BEFileInputStream& stream)
 +void layer::layer_mask_info::read_mask_data(BEFileInputStream& stream)
  {
 -      layer_ptr result(std::make_shared<layer>());
 -      result->populate(stream);
 -      return result;
 +      unsigned long 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)
 +              {
 +                      stream.discard_bytes(18);       //discard "total user mask" data
 +                      //flags_ = stream.read_byte();
 +                      //default_value_ = stream.read_byte();
 +                      //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;
 +              }
 +              break;
 +
 +      default:
 +              //TODO: Log that we discard a mask that is not supported
 +              stream.discard_bytes(length);
 +              break;
 +      };
  }
  
 -void layer::populate(BEFileInputStream& stream)
 +struct layer::impl
  {
 -      rect_.top = stream.read_long();
 -      rect_.left = stream.read_long();
 -      rect_.bottom = stream.read_long();
 -      rect_.right = stream.read_long();
 -
 -      //Get info about the channels in the layer
 -      unsigned short channelCount = stream.read_short();
 -      for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex)
 -      {
 -              short channel_id = static_cast<short>(stream.read_short());
 -              unsigned long data_length = stream.read_long();
 +      friend class layer;
  
 -              if(channel_id <= -2)
 -                      masks_++;
 +      impl() : blend_mode_(InvalidBlendMode), link_group_id_(0), opacity_(255), baseClipping_(false), flags_(0), protection_flags_(0), masks_count_(0)
 +      {}
  
 -              channel_ptr channel(std::make_shared<Channel>(channel_id, data_length));
 -              channels_.push_back(channel);
 -      }
 +private:
 +      std::vector<channel>                    channels_;
 +      blend_mode                                              blend_mode_;
 +      int                                                             link_group_id_;
 +      unsigned char                                   opacity_;
 +      bool                                                    baseClipping_;
 +      unsigned char                                   flags_;
 +      int                                                             protection_flags_;
 +      std::wstring                                    name_;
 +      char                                                    masks_count_;
  
 -      unsigned long blendModeSignature = stream.read_long();
 -      if(blendModeSignature != '8BIM')
 -              throw PSDFileFormatException();
 +      rect<long>                                              vector_mask_;
 +      layer::layer_mask_info                  mask_;
  
 -      unsigned long blendModeKey = stream.read_long();
 -      blend_mode_ = int_to_blend_mode(blendModeKey);
 +      rect<long>                                              bitmap_rect_;
 +      image8bit_ptr                                   bitmap_;
  
 -      opacity_ = stream.read_byte();
 -      baseClipping_ = stream.read_byte() == 1 ? false : true;
 -      flags_ = stream.read_byte();
 +      boost::property_tree::wptree    text_layer_info_;
 +      boost::property_tree::wptree    timeline_info_;
  
 -      stream.discard_bytes(1);
 +      color<unsigned char>                    solid_color_;
  
 -      unsigned long extraDataSize = stream.read_long();
 -      long position1 = stream.current_position();
 -      mask_.read_mask_data(stream);
 -      read_blending_ranges(stream);
 +public:
 +      void populate(BEFileInputStream& 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
 +              unsigned short channelCount = stream.read_short();
 +              for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex)
 +              {
 +                      short id = static_cast<short>(stream.read_short());
 +                      channel c(id, stream.read_long());
  
 -      name_ = stream.read_pascal_string(4);
 +                      if(c.id < -1)
 +                              masks_count_++;
  
 -      //Aditional Layer Information
 -      long end_of_layer_info = position1 + extraDataSize;
 -      
 -      try
 -      {
 -              while(stream.current_position() < end_of_layer_info)
 +                      channels_.push_back(c);
 +              }
 +
 +              unsigned long blendModeSignature = stream.read_long();
 +              if(blendModeSignature != '8BIM')
 +                      throw PSDFileFormatException();
 +
 +              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
 +
 +              unsigned long extras_size = stream.read_long();
 +              long position = stream.current_position();
 +              mask_.read_mask_data(stream);
 +              read_blending_ranges(stream);
 +              name_ = stream.read_pascal_string(4);
 +
 +              //Aditional Layer Information
 +              long end_of_layer_info = position + extras_size;
 +              try
 +              {
 +                      while(stream.current_position() < end_of_layer_info)
 +                      {
 +                              read_chunk(stream, doc);
 +                      }
 +              }
 +              catch(PSDFileFormatException&)
                {
 -                      read_chunk(stream);
 +                      stream.set_position(end_of_layer_info);
                }
        }
 -      catch(PSDFileFormatException&)
 +
 +      void read_chunk(BEFileInputStream& stream, const psd_document& doc, bool isMetadata = false)
        {
 -              stream.set_position(end_of_layer_info);
 -      }
 -}
 +              unsigned long signature = stream.read_long();
 +              if(signature != '8BIM' && signature != '8B64')
 +                      throw PSDFileFormatException();
  
 -void layer::read_chunk(BEFileInputStream& stream, bool isMetadata)
 -{
 -      unsigned long signature = stream.read_long();
 -      if(signature != '8BIM' && signature != '8B64')
 -              throw PSDFileFormatException();
 +              unsigned long key = stream.read_long();
  
 -      unsigned long key = stream.read_long();
 -      
 -      if(isMetadata) stream.read_long();
 +              if(isMetadata) stream.read_long();
  
 -      unsigned long length = stream.read_long();
 -      unsigned long end_of_chunk = stream.current_position() + length;
 +              unsigned long length = stream.read_long();
 +              unsigned long end_of_chunk = stream.current_position() + length;
  
 -      try
 -      {
 -              switch(key)
 +              try
                {
 -              case 'lspf':    //protection settings
 -                      protection_flags_ = stream.read_long();
 -                      break;
 +                      switch(key)
 +                      {
 +                      case 'SoCo':
 +                              read_solid_color(stream);
 +                              break;
 +
 +                      case 'lspf':    //protection settings
 +                              protection_flags_ = stream.read_long();
 +                              break;
  
 -              case 'Txt2':    //text engine data
 -                      break;
 +                      case 'Txt2':    //text engine data
 +                              break;
  
 -              case 'TySh':    //type tool object settings
 -                      read_text_data(stream);
 -                      break;
 +                      case 'TySh':    //type tool object settings
 +                              read_text_data(stream);
 +                              break;
                                
 -              case 'shmd':    //metadata
 -                      read_metadata(stream);
 -                      break;
 +                      case 'shmd':    //metadata
 +                              read_metadata(stream, doc);
 +                              break;
                                
 -              case 'lyvr':    //layer version
 -                      break;
 +                      case 'lyvr':    //layer version
 +                              break;
  
 -              case 'lnkD':    //linked layer
 -              case 'lnk2':    //linked layer
 -              case 'lnk3':    //linked layer
 -                      break;
 +                      case 'lnkD':    //linked layer
 +                      case 'lnk2':    //linked layer
 +                      case 'lnk3':    //linked layer
 +                              break;
 +
 +                      case 'vmsk':
 +                              read_vector_mask(length, stream, doc.width(), doc.height());
 +                              break;
                                
 -              case 'tmln':
 -                      read_timeline_data(stream);
 -                      break;
 +                      case 'tmln':
 +                              read_timeline_data(stream);
 +                              break;
  
 -              default:
 -                      break;
 +                      default:
 +                              break;
 +                      }
 +              }
 +              catch(PSDFileFormatException&)
 +              {
 +                      //ignore failed chunks silently
                }
 -      }
 -      catch(PSDFileFormatException&)
 -      {
 -              //ignore failed chunks silently
 -      }
 -
 -      stream.set_position(end_of_chunk);
 -}
  
 -void layer::read_metadata(BEFileInputStream& stream)
 -{
 -      unsigned long count = stream.read_long();
 -      for(int index = 0; index < count; ++index)
 -              read_chunk(stream, true);
 -}
 +              stream.set_position(end_of_chunk);
 +      }
  
 -void layer::read_timeline_data(BEFileInputStream& stream)
 -{
 -      stream.read_long();                                     //"descriptor version" should be 16
 -      descriptor timeline_descriptor;
 -      if(!timeline_descriptor.populate(stream))
 -              throw PSDFileFormatException();
 -      else
 +      void read_solid_color(BEFileInputStream& stream)
        {
 -              timeline_info_.swap(timeline_descriptor.items());
++              if(stream.read_long() != 16)    //"descriptor version" should be 16
++                      throw PSDFileFormatException();
++
++              descriptor solid_descriptor;
++              if(!solid_descriptor.populate(stream))
++                      throw PSDFileFormatException();
++              else
++              {
++                      solid_color_.red = static_cast<unsigned char>(solid_descriptor.items().get(L"Clr .Rd  ", 0.0) + 0.5);
++                      solid_color_.green = static_cast<unsigned char>(solid_descriptor.items().get(L"Clr .Grn ", 0.0) + 0.5);
++                      solid_color_.blue = static_cast<unsigned char>(solid_descriptor.items().get(L"Clr .Bl  ", 0.0) + 0.5);
++                      solid_color_.alpha = 255;
++              }
        }
 -}
  
 -void layer::read_text_data(BEFileInputStream& stream)
 -{
 -      std::wstring text;      //the text in the layer
 +      void read_vector_mask(unsigned long length, BEFileInputStream& stream, long doc_width, long doc_height)
 +      {
 +              typedef std::pair<unsigned long, unsigned long> path_point;
  
 -      stream.read_short();    //should be 1
 -      stream.discard_bytes(6*8);      //just throw transformation info for now
 -      stream.read_short();    //"text version" should be 50
 +              unsigned long version = stream.read_long();
 +              unsigned long flags = stream.read_long();
 +              int path_records = (length-8) / 26;
  
 -      //text data descriptor ('descriptor structure')
 -      if(stream.read_long() != 16)    //"descriptor version" should be 16
 -              throw PSDFileFormatException();
 +              long position = stream.current_position();
  
 -      descriptor text_descriptor;
 -      if(!text_descriptor.populate(stream))
 -              throw PSDFileFormatException();
 -      else
 -      {
 -              auto text_info = text_descriptor.items().get_optional<std::wstring>(L"EngineData");
 -              if(text_info.is_initialized())
 +              std::vector<path_point> knots;
 +              for(int i=1; i <= path_records; ++i)
                {
 -                      std::string str(text_info.get().begin(), text_info.get().end());
 -                      read_pdf(text_layer_info_, str);
 -              }
 -      }
 +                      unsigned short selector = stream.read_short();
 +                      if(selector == 2)       //we only concern ourselves with closed paths 
 +                      {
 +                              unsigned long p_y = stream.read_long();
 +                              unsigned long p_x = stream.read_long();
 +                              path_point cp_prev(p_x, p_y);
  
 -      stream.read_short();    //"warp version" should be 1
 +                              unsigned long a_y = stream.read_long();
 +                              unsigned long a_x = stream.read_long();
 +                              path_point anchor(a_x, a_y);
  
 -      //warp data descriptor ('descriptor structure')
 -      stream.read_long();                                     //"descriptor version" should be 16
 -      descriptor warp_descriptor;
 -      if(!warp_descriptor.populate(stream))
 -              throw PSDFileFormatException();
 -      else
 -              stream.discard_bytes(4*8);      //top, left, right, bottom
 -}
 +                              unsigned long n_y = stream.read_long();
 +                              unsigned long n_x = stream.read_long();
 +                              path_point cp_next(n_x, n_y);
  
 -void layer::layer_mask::read_mask_data(BEFileInputStream& stream)
 -{
 -      unsigned long length = stream.read_long();
 -      switch(length)
 -      {
 -      case 0:
 -              break;
 +                              if(anchor == cp_prev && anchor == cp_next)
 +                                      knots.push_back(anchor);
 +                              else
 +                              {       //we can't handle smooth curves yet
 +                                      throw PSDFileFormatException();
 +                              }
 +                      }
  
 -      case 20:
 -              rect_.top = stream.read_long();
 -              rect_.left = stream.read_long();
 -              rect_.bottom = stream.read_long();
 -              rect_.right = stream.read_long();
 +                      stream.set_position(position + 26*i);
 +              }
  
 -              default_value_ = stream.read_byte();
 -              flags_ = stream.read_byte();
 -              stream.discard_bytes(2);
 -              break;
 +              if(knots.size() != 4)   //we only support rectangular vector masks
 +                      throw PSDFileFormatException();
  
 -      case 36:
 -              stream.discard_bytes(18);       //we don't care about the user mask if there is a "total user mask"
 -              flags_ = stream.read_byte();
 -              default_value_ = stream.read_byte();
 -              rect_.top = stream.read_long();
 -              rect_.left = stream.read_long();
 -              rect_.bottom = stream.read_long();
 -              rect_.right = stream.read_long();
 -              break;
 +              //we only support rectangular vector masks
 +              if(!(knots[0].first == knots[3].first && knots[1].first == knots[2].first && knots[0].second == knots[1].second && knots[2].second == knots[3].second))
 +                      throw PSDFileFormatException();
  
 -      default:
 -              stream.discard_bytes(length);
 -              break;
 -      };
 -}
 +              //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;
 +              vector_mask_.location.x = static_cast<long>(knots[0].first * x_ratio +0.5f);                                                            //add .5 to get propper rounding when converting to integer
 +              vector_mask_.location.y = static_cast<long>(knots[0].second * y_ratio +0.5f);                                                           //add .5 to get propper rounding when converting to integer
 +              vector_mask_.size.width = static_cast<long>(knots[1].first * x_ratio +0.5f)     - vector_mask_.location.x;              //add .5 to get propper rounding when converting to integer
 +              vector_mask_.size.height = static_cast<long>(knots[2].second * y_ratio +0.5f) - vector_mask_.location.y;        //add .5 to get propper rounding when converting to integer
 +      }
  
 -bool layer::is_text() const
 -{
 -      return !text_layer_info_.empty();
 -}
 -bool layer::has_timeline() const
 -{
 -      return !timeline_info_.empty();
 -}
 +      void read_metadata(BEFileInputStream& stream, const psd_document& doc)
 +      {
 +              unsigned long count = stream.read_long();
 +              for(unsigned long index = 0; index < count; ++index)
 +                      read_chunk(stream, doc, true);
 +      }
  
 +      void read_timeline_data(BEFileInputStream& stream)
 +      {
 +              if(stream.read_long() != 16)    //"descriptor version" should be 16
 +                      throw PSDFileFormatException();
  
 -//TODO: implement
 -void layer::read_blending_ranges(BEFileInputStream& stream)
 -{
 -      unsigned long length = stream.read_long();
 -      stream.discard_bytes(length);
 -}
 +              descriptor timeline_descriptor;
 +              if(!timeline_descriptor.populate(stream))
 +                      throw PSDFileFormatException();
 +              else
 +              {
 +                      timeline_info_.swap(timeline_descriptor.items());
 +              }
 +      }
  
 -channel_ptr layer::get_channel(channel_type type)
 -{
 -      auto end = channels_.end();
 -      for(auto it = channels_.begin(); it != end; ++it)
 +      void read_text_data(BEFileInputStream& stream)
        {
 -              if((*it)->id() == type)
 +              std::wstring text;      //the text in the layer
 +
 +              stream.read_short();    //should be 1
 +      
 +              //transformation info
 +              double xx = stream.read_double();
 +              double xy = stream.read_double();
 +              double yx = stream.read_double();
 +              double yy = stream.read_double();
 +              double tx = stream.read_double();
 +              double ty = stream.read_double();
 +
 +              if(stream.read_short() != 50)   //"text version" should be 50
 +                      throw PSDFileFormatException();
 +
 +              if(stream.read_long() != 16)    //"descriptor version" should be 16
 +                      throw PSDFileFormatException();
 +
 +              descriptor text_descriptor;
 +              if(!text_descriptor.populate(stream))
 +                      throw PSDFileFormatException();
 +              else
                {
 -                      return (*it);
 +                      auto text_info = text_descriptor.items().get_optional<std::wstring>(L"EngineData");
 +                      if(text_info.is_initialized())
 +                      {
 +                              std::string str(text_info.get().begin(), text_info.get().end());
 +                              read_pdf(text_layer_info_, str);
 +                      }
                }
 -      }
  
 -      return NULL;
 -}
 +              if(stream.read_short() != 1)    //"warp version" should be 1
 +                      throw PSDFileFormatException();
  
 -void layer::read_channel_data(BEFileInputStream& stream)
 -{
 -      image8bit_ptr img;
 -      image8bit_ptr mask;
 +              if(stream.read_long() != 16)    //"descriptor version" should be 16
 +                      throw PSDFileFormatException();
  
 -      bool has_transparency(get_channel(psd::Transparency));
 +              descriptor warp_descriptor;
 +              if(!warp_descriptor.populate(stream))
 +                      throw PSDFileFormatException();
 +              else
 +                      stream.discard_bytes(4*8);      //top, left, right, bottom
 +      }
  
 -      //std::clog << std::endl << "layer: " << std::string(name().begin(), name().end()) << std::endl;
 -      
 -      if(rect_.width() > 0 && rect_.height() > 0)
 +      //TODO: implement
 +      void read_blending_ranges(BEFileInputStream& stream)
        {
 -              img = std::make_shared<image8bit>(rect_.width(), rect_.height(), 4);
 -              //std::clog << std::dec << "has image: [width: " << rect_.width() << " height: " << rect_.height() << "]" << std::endl;
 -
 -              if(!has_transparency)
 -                      std::memset(img->data(), 255, img->width()*img->height()*img->channel_count());
 +              unsigned long length = stream.read_long();
 +              stream.discard_bytes(length);
        }
  
 -      if(masks_ > 0 && mask_.rect_.width() > 0 && mask_.rect_.height() > 0)
 +      bool has_channel(channel_type type)
        {
 -              mask = std::make_shared<image8bit>(mask_.rect_.width(), mask_.rect_.height(), 1);
 -              //std::clog << std::dec << "has mask: [width: " << mask_rect_.width() << " height: " << mask_rect_.height() << "]" << std::endl;
 +              return std::find_if(channels_.begin(), channels_.end(), [=](const channel& c) { return c.id == type; }) != channels_.end();
        }
  
 -      auto end = channels_.end();
 -      for(auto it = channels_.begin(); it != end; ++it)
 +      void read_channel_data(BEFileInputStream& stream)
        {
 -              image8bit_ptr target;
 -              unsigned char offset;
 -              bool discard_channel = false;
 -
 -              //determine target bitmap and offset
 -              if((*it)->id() >= 3)
 -                      discard_channel = true; //discard channels that doesn't contribute to the final image
 -              else if((*it)->id() >= -1)      //BGRA-data
 -              {
 -                      target = img;
 -                      offset = ((*it)->id() >= 0) ? 2 - (*it)->id() : 3;
 -              }
 -              else if(mask)   //mask
 +              image8bit_ptr bitmap;
 +              image8bit_ptr mask;
 +
 +              bool has_transparency = has_channel(psd::transparency);
 +      
 +              rect<long> clip_rect;
 +              if(!bitmap_rect_.empty())
                {
 -                      if((*it)->id() == -2 && masks_ == 2)    //if there are two mask-channels, discard the the one that's not the total mask
 -                              discard_channel = true;
 -                      else
 -                      {
 -                              target = mask;
 -                              offset = 0;
 -                      }
 -              }
 +                      clip_rect = bitmap_rect_;
  
 -              //unsigned long cp = stream.current_position(); //for debug purposes only
 -              //std::clog << std::dec << "channel_id: " << (*it)->id() << ", reading data from: " << std::hex << cp << ", data_length: " << (*it)->data_length() << std::endl;
 +                      if(!vector_mask_.empty())
 +                              clip_rect = vector_mask_;
  
 -              if(!target)
 -                      discard_channel = true;
 +                      bitmap = std::make_shared<image8bit>(clip_rect.size.width, clip_rect.size.height, 4);
  
 -              if(discard_channel)
 +                      if(!has_transparency)
 +                              std::memset(bitmap->data(), 255, bitmap->width()*bitmap->height()*bitmap->channel_count());
 +              }
 +
 +              if(masks_count_ > 0 && !mask_.rect_.empty())
                {
 -                      //std::clog << "        -> discarding" << std::endl;
 -                      stream.discard_bytes((*it)->data_length());
 +                      mask = std::make_shared<image8bit>(mask_.rect_.size.width, mask_.rect_.size.height, 1);
                }
 -              else
 +
 +              for(auto it = channels_.begin(); it != channels_.end(); ++it)
                {
 -                      //std::clog << "        -> reading...";
 -                      unsigned short encoding = stream.read_short();
 -                      if(target)
 +                      psd::rect<long> src_rect;
 +                      image8bit_ptr target;
 +                      unsigned char offset;
 +                      bool discard_channel = false;
 +
 +                      //determine target bitmap and offset
 +                      if((*it).id >= 3)
 +                              discard_channel = true; //discard channels that doesn't contribute to the final image
 +                      else if((*it).id >= -1) //BGRA-data
 +                      {
 +                              target = bitmap;
 +                              offset = ((*it).id >= 0) ? 2 - (*it).id : 3;
 +                              src_rect = bitmap_rect_;
 +                      }
 +                      else if(mask)   //mask
                        {
 -                              if(encoding == 0)
 -                                      read_raw_image_data(stream, *it, target, offset);
 -                              else if(encoding == 1)
 -                                      read_rle_image_data(stream, *it, target, offset);
 +                              if((*it).id == -3)      //discard the "total user mask"
 +                                      discard_channel = true;
                                else
 -                                      throw PSDFileFormatException();
 +                              {
 +                                      target = mask;
 +                                      offset = 0;
 +                                      src_rect = mask_.rect_;
 +                              }
 +                      }
 +
 +                      if(!target || src_rect.empty())
 +                              discard_channel = true;
 +
 +                      unsigned long end_of_data = stream.current_position() + (*it).data_length;
 +                      if(!discard_channel)
 +                      {
 +                              unsigned short encoding = stream.read_short();
 +                              if(target)
 +                              {
 +                                      if(encoding == 0)
 +                                              read_raw_image_data(stream, (*it).data_length-2, target, offset);
 +                                      else if(encoding == 1)
 +                                              read_rle_image_data(stream, src_rect, (target == bitmap) ? clip_rect : mask_.rect_, target, offset);
 +                                      else
 +                                              throw PSDFileFormatException();
 +                              }
                        }
 -                      //std::clog << " " << std::hex << (stream.current_position() - cp) << " bytes read" << std::endl;
 +                      stream.set_position(end_of_data);
                }
 -      }
  
 -      if(img && has_transparency)
 -      {
 -              caspar::image::image_view<caspar::image::bgra_pixel> view(img->data(), img->width(), img->height());
 -              caspar::image::premultiply(view);
 +              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;
 +              bitmap_rect_ = clip_rect;
 +              mask_.bitmap_ = mask;
        }
  
 -      image_ = img;
 -      mask_.mask_ = mask;
 -}
 +      void read_raw_image_data(BEFileInputStream& stream, unsigned long data_length, image8bit_ptr target, unsigned char offset)
 +      {
 +              unsigned long total_length = target->width() * target->height();
 +              if(total_length != data_length)
 +                      throw PSDFileFormatException();
  
 -void read_raw_image_data(BEFileInputStream& stream, const channel_ptr& channel, image8bit_ptr target, unsigned char offset)
 -{
 -      unsigned long total_length = target->width() * target->height();
 -      if(total_length != (channel->data_length() - 2))
 -              throw PSDFileFormatException();
 +              unsigned char* data = target->data();
  
 -      unsigned char* data = target->data();
 +              unsigned char stride = target->channel_count();
 +              if(stride == 1)
 +                      stream.read(reinterpret_cast<char*>(data + offset), total_length);
 +              else
 +              {
 +                      for(unsigned long index=0; index < total_length; ++index)
 +                              data[index*stride+offset] = stream.read_byte();
 +              }
 +      }
  
 -      unsigned char stride = target->channel_count();
 -      if(stride == 1)
 -              stream.read(reinterpret_cast<char*>(data + offset), total_length);
 -      else
 +      void read_rle_image_data(BEFileInputStream& stream, const rect<long>&src_rect, const rect<long>&clip_rect, image8bit_ptr target, unsigned char offset)
        {
 -              for(unsigned long index=0; index < total_length; ++index)
 -                      data[index*stride+offset] = stream.read_byte();
 -      }
 -}
 +              unsigned long width = src_rect.size.width;
 +              unsigned long height = src_rect.size.height;
 +              unsigned char stride = target->channel_count();
  
 -void read_rle_image_data(BEFileInputStream& stream, const channel_ptr& channel, image8bit_ptr target, unsigned char offset)
 -{
 -      unsigned long width = target->width();
 -      unsigned char stride = target->channel_count();
 -      unsigned long height = target->height();
 +              int offset_x = clip_rect.location.x - src_rect.location.x;
 +              int offset_y = clip_rect.location.y - src_rect.location.y;
  
 -      std::vector<unsigned short> scanline_lengths;
 -      scanline_lengths.reserve(height);
 +              std::vector<unsigned short> scanline_lengths;
 +              scanline_lengths.reserve(height);
  
 -      for(unsigned long scanlineIndex=0; scanlineIndex < height; ++scanlineIndex)
 -              scanline_lengths.push_back(stream.read_short());
 +              for(unsigned long scanlineIndex=0; scanlineIndex < height; ++scanlineIndex)
 +                      scanline_lengths.push_back(stream.read_short());
  
 -      unsigned char* data = target->data();
 +              unsigned char *target_data = target->data();
  
 -      for(unsigned long scanlineIndex=0; scanlineIndex < height; ++scanlineIndex)
 -      {
 -              unsigned long colIndex = 0;
 -              unsigned char length = 0;
 -              do
 +              std::vector<unsigned char> line(width);
 +
 +              for(long scanlineIndex=0; scanlineIndex < height; ++scanlineIndex)
                {
 -                      length = 0;
 +                      if(scanlineIndex >= target->height()+offset_y)
 +                              break;
  
 -                      //Get controlbyte
 -                      char controlByte = static_cast<char>(stream.read_byte());
 -                      if(controlByte >= 0)
 -                      {
 -                              //Read uncompressed string
 -                              length = controlByte+1;
 -                              for(unsigned long index=0; index < length; ++index)
 -                                      data[(scanlineIndex*width+colIndex+index) * stride + offset] = stream.read_byte();
 -                      }
 -                      else if(controlByte > -128)
 +                      unsigned long colIndex = 0;
 +                      unsigned char length = 0;
 +                      do
                        {
 -                              //Repeat next byte
 -                              length = -controlByte+1;
 -                              unsigned value = stream.read_byte();
 -                              for(unsigned long index=0; index < length; ++index)
 -                                      data[(scanlineIndex*width+colIndex+index) * stride + offset] = value;
 +                              length = 0;
 +
 +                              //Get controlbyte
 +                              char controlByte = static_cast<char>(stream.read_byte());
 +                              if(controlByte >= 0)
 +                              {
 +                                      //Read uncompressed string
 +                                      length = controlByte+1;
 +                                      for(unsigned long index=0; index < length; ++index)
 +                                              line[colIndex+index] = stream.read_byte();
 +                              }
 +                              else if(controlByte > -128)
 +                              {
 +                                      //Repeat next byte
 +                                      length = -controlByte+1;
 +                                      unsigned char value = stream.read_byte();
 +                                      for(unsigned long index=0; index < length; ++index)
 +                                              line[colIndex+index] = value;
 +                              }
 +
 +                              colIndex += length;
                        }
 -
 -                      colIndex += length;
 +                      while(colIndex < width);
 +
 +                      //use line to populate target
 +                      if(scanlineIndex >= offset_y)
 +                              for(int index = offset_x; index < target->width(); ++index)
 +                              {
 +                                      target_data[((scanlineIndex-offset_y)*target->width()+index-offset_x) * stride + offset] = line[index];
 +                              }
                }
 -              while(colIndex < width);
        }
 -}
 +};
 +
 +layer::layer() : impl_(spl::make_shared<impl>()) {}
 +
 +void layer::populate(BEFileInputStream& stream, const psd_document& doc) { impl_->populate(stream, doc); }
 +void layer::read_channel_data(BEFileInputStream& stream) { impl_->read_channel_data(stream); }
 +
 +const std::wstring& layer::name() const { return impl_->name_; }
 +unsigned char layer::opacity() const { return impl_->opacity_; }
 +
 +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; }
 +
 +
 +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<unsigned char> layer::solid_color() const { return impl_->solid_color_; }
 +
 +const point<long>& layer::location() const { return impl_->bitmap_rect_.location; }
 +const image8bit_ptr& layer::bitmap() const { return impl_->bitmap_; }
 +
 +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
index 7474f487fd51d9a4be362992c9a256716504389c,d443478b0418d62c1cdf9d85cfff197d2e928faa..9651f6becc17e8cdfe31883fbbf188e03feea2c7
  #include <string>
  
  namespace caspar { namespace psd {
 +      
 +template<typename T>
 +struct point
 +{
 +      point() : x(0), y(0) {}
 +      point(T x1, T y1) : x(x1), y(y1) {}
 +      T x;
 +      T y;
 +};
 +
 +template<typename T>
 +struct color
 +{
 +      color() : red(0), green(0), blue(0), alpha(0)
 +      {}
 +
 +      T red;
 +      T green;
 +      T blue;
 +      T alpha;
++
++      unsigned long to_uint32()
++      {
++              return (alpha << 24) + (red << 16) + (green << 8) + blue;
++      }
 +};
 +
 +template<typename T>
 +struct size
 +{
 +      size() : width(0), height(0) {}
 +      size(T w, T h) : width(w), height(h) {}
 +      T width;
 +      T height;
 +};
  
  template<typename T>
  struct rect
index 00bdea137f2e8bc3f81d3b21ed0946394eb2a7ca,e67ee9d1809a6f65aa64db7258e88f2ba1a30fab..e5fde8677a41dd8e0bd30e246686b2cd3eff6421
@@@ -26,6 -26,6 +26,7 @@@
  #include <core/frame/pixel_format.h>
  #include <core/frame/frame_factory.h>
  #include <core/producer/frame_producer.h>
++#include <core/producer/color/color_producer.h>
  #include <core/producer/text/text_producer.h>
  #include <core/producer/scene/scene_producer.h>
  #include <core/producer/scene/const_producer.h>
@@@ -178,6 -184,108 +185,108 @@@ core::text::text_info get_text_info(con
                }
        };
  
 -      double original_pos_x = psd_layer->rect().left;
 -      double original_pos_y = psd_layer->rect().top;
+ int64_t get_frame_number(
+               const core::video_format_desc& format_desc,
+               const boost::rational<int>& at_second)
+ {
+       return static_cast<int64_t>(
+                       boost::rational_cast<double>(at_second) * format_desc.fps);
+ }
+ boost::rational<int> get_rational(const boost::property_tree::wptree& node)
+ {
+       return boost::rational<int>(
+                       node.get<int>(L"numerator"), node.get<int>(L"denominator"));
+ }
+ void create_timelines(
+               const spl::shared_ptr<core::scene::scene_producer>& scene,
+               const core::video_format_desc& format_desc,
+               core::scene::layer& layer,
+               const layer_ptr& psd_layer,
+               double adjustment_x,
+               double adjustment_y)
+ {
+       auto timeline = psd_layer->timeline_data();
+       auto start = get_rational(timeline.get_child(L"timeScope.Strt"));
+       auto end_offset = get_rational(timeline.get_child(L"timeScope.outTime"));
+       auto end = start + end_offset;
+       auto start_frame = get_frame_number(format_desc, start);
+       auto end_frame = get_frame_number(format_desc, end);
+       layer.hidden = scene->frame() < start_frame || scene->frame() > end_frame;
+       auto tracklist = timeline.get_child_optional(L"trackList");
+       if (!tracklist)
+               return;
++      double original_pos_x = psd_layer->location().x;
++      double original_pos_y = psd_layer->location().y;
+       BOOST_FOREACH(auto& track, *tracklist)
+       {
+               auto track_id = track.second.get<std::wstring>(L"stdTrackID");
+               if (track_id == L"sheetPositionTrack")
+               {
+                       BOOST_FOREACH(auto& key, track.second.get_child(L"keyList"))
+                       {
+                               bool tween = key.second.get<std::wstring>(L"animInterpStyle")
+                                               == L"Lnr ";
+                               auto time = get_rational(key.second.get_child(L"time"));
+                               auto hrzn = key.second.get<double>(L"animKey.Hrzn");
+                               auto vrtc = key.second.get<double>(L"animKey.Vrtc");
+                               auto x = original_pos_x + hrzn + adjustment_x;
+                               auto y = original_pos_y + vrtc + adjustment_y;
+                               auto frame = get_frame_number(format_desc, time);
+                               if (frame == 0) // Consider as initial value (rewind)
+                               {
+                                       layer.position.x.set(x);
+                                       layer.position.y.set(y);
+                               }
+                               frame = start_frame + frame; // translate to global timeline
+                               if (tween)
+                               {
+                                       scene->add_keyframe(layer.position.x, x, frame, L"easeOutSine");
+                                       scene->add_keyframe(layer.position.y, y, frame, L"easeOutSine");
+                               }
+                               else
+                               {
+                                       scene->add_keyframe(layer.position.x, x, frame);
+                                       scene->add_keyframe(layer.position.y, y, frame);
+                               }
+                       }
+               }
+               else if (track_id == L"opacityTrack")
+               {
+                       auto& opacity = layer.adjustments.opacity;
+                       BOOST_FOREACH(auto& key, track.second.get_child(L"keyList"))
+                       {
+                               bool tween = key.second.get<std::wstring>(L"animInterpStyle")
+                                               == L"Lnr ";
+                               auto time = get_rational(key.second.get_child(L"time"));
+                               auto opct = key.second.get<double>(L"animKey.Opct.#Prc") / 100.0;
+                               auto frame = get_frame_number(format_desc, time);
+                               if (frame == 0) // Consider as initial value (rewind)
+                                       opacity.set(opct);
+                               frame = start_frame + frame; // translate to global timeline
+                               if (tween)
+                                       scene->add_keyframe(opacity, opct, frame, L"easeOutSine");
+                               else
+                                       scene->add_keyframe(opacity, opct, frame);
+                       }
+               }
+       }
+ }
  spl::shared_ptr<core::frame_producer> create_psd_scene_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
  {
        std::wstring filename = env::media_folder() + L"\\" + params[0] + L".psd";
        auto layers_end = doc.layers().end();
        for(auto it = doc.layers().begin(); it != layers_end; ++it)
        {
 -              if((*it)->is_text() && (*it)->visible())
 +              if((*it)->is_visible())
                {
 -                      std::wstring str = (*it)->text_data().get(L"EngineDict.Editor.Text", L"");
 +                      if((*it)->is_text())
 +                      {
 +                              std::wstring str = (*it)->text_data().get(L"EngineDict.Editor.Text", L"");
                        
 -                      core::text::text_info text_info(std::move(get_text_info((*it)->text_data())));
 -                      auto text_producer = core::text_producer::create(frame_factory, 0, 0, str, text_info, doc.width(), doc.height());
 +                              core::text::text_info text_info(std::move(get_text_info((*it)->text_data())));
 +                              auto text_producer = core::text_producer::create(frame_factory, 0, 0, str, text_info, doc.width(), doc.height());
                        
 -                      core::text::string_metrics metrics = text_producer->measure_string(str);
 +                              core::text::string_metrics metrics = text_producer->measure_string(str);
                        
-                               auto& new_layer = root->create_layer(text_producer, (*it)->location().x - 2, (*it)->location().y + metrics.bearingY, (*it)->name());    //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_x = -2;
 -                      auto adjustment_y = metrics.bearingY;
 -                      auto& new_layer = root->create_layer(text_producer, (*it)->rect().left + adjustment_x, (*it)->rect().top + adjustment_y, (*it)->name());        //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
 -                      new_layer.adjustments.opacity.set((*it)->opacity() / 255.0);
 -                      new_layer.hidden.set(!(*it)->visible());
++                              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& new_layer = root->create_layer(text_producer, (*it)->location().x + adjustment_x, (*it)->location().y + adjustment_y, (*it)->name());     
 +                              new_layer.adjustments.opacity.set((*it)->opacity() / 255.0);
 +                              new_layer.hidden.set(!(*it)->is_visible());
  
 -                      if ((*it)->has_timeline())
 -                              create_timelines(root, format_desc, new_layer, (*it), adjustment_x, adjustment_y);
++                              if ((*it)->has_timeline())
++                                      create_timelines(root, format_desc, new_layer, (*it), adjustment_x, adjustment_y);
 -                      if((*it)->link_group_id() != 0)
 -                              link_constructor.add(&new_layer, (*it)->link_group_id(), (*it)->is_position_protected(), -adjustment_x, -adjustment_y);
 +                              if((*it)->link_group_id() != 0)
-                                       link_constructor.add(&new_layer, (*it)->link_group_id(), true);
++                                      link_constructor.add(&new_layer, (*it)->link_group_id(), (*it)->is_position_protected(), -adjustment_x, -adjustment_y);
  
 -                      text_producers_by_layer_name.push_back(std::make_pair((*it)->name(), text_producer));
 -              }
 -              else if((*it)->image() && (*it)->visible())
 -              {
 -                      std::wstring layer_name = (*it)->name();
 -                      std::shared_ptr<core::frame_producer> layer_producer;
 -
 -                      /*if (boost::algorithm::istarts_with(layer_name, L"[producer]"))
 -                      {
 -                              auto hotswap = std::make_shared<core::hotswap_producer>((*it)->rect().width(), (*it)->rect().height());
 -                              hotswap->producer().set(core::create_producer(frame_factory, format_desc, layer_name.substr(10)));
 -                              layer_producer = hotswap;
 +                              text_producers_by_layer_name.push_back(std::make_pair((*it)->name(), text_producer));
                        }
-                       else if((*it)->bitmap())
 -                      else*/
++                      else
                        {
 -                              core::pixel_format_desc pfd(core::pixel_format::bgra);
 -                              pfd.planes.push_back(core::pixel_format_desc::plane((*it)->rect().width(), (*it)->rect().height(), 4));
 +                              std::wstring layer_name = (*it)->name();
 +                              std::shared_ptr<core::frame_producer> layer_producer;
-                               if (boost::algorithm::istarts_with(layer_name, L"[producer]"))
++                              if((*it)->is_solid())
 +                              {
-                                       auto hotswap = std::make_shared<core::hotswap_producer>((*it)->bitmap()->width(), (*it)->bitmap()->height());
-                                       hotswap->producer().set(core::create_producer(frame_factory, format_desc, layer_name.substr(10)));
-                                       layer_producer = hotswap;
++                                      layer_producer = layer_producer = core::create_const_producer(core::create_color_frame(it->get(), frame_factory, (*it)->solid_color().to_uint32()), (*it)->bitmap()->width(), (*it)->bitmap()->height());
 +                              }
-                               else
++                              else if((*it)->bitmap())
 +                              {
-                                       core::pixel_format_desc pfd(core::pixel_format::bgra);
-                                       pfd.planes.push_back(core::pixel_format_desc::plane((*it)->bitmap()->width(), (*it)->bitmap()->height(), 4));
++                                      /*if (boost::algorithm::istarts_with(layer_name, L"[producer]"))
++                                      {
++                                              auto hotswap = std::make_shared<core::hotswap_producer>((*it)->rect().width(), (*it)->rect().height());
++                                              hotswap->producer().set(core::create_producer(frame_factory, format_desc, layer_name.substr(10)));
++                                              layer_producer = hotswap;
++                                      }
++                                      else*/
++                                      {
++                                              core::pixel_format_desc pfd(core::pixel_format::bgra);
++                                              pfd.planes.push_back(core::pixel_format_desc::plane((*it)->bitmap()->width(), (*it)->bitmap()->height(), 4));
  
-                                       auto frame = frame_factory->create_frame(it->get(), pfd);
-                                       memcpy(frame.image_data().data(), (*it)->bitmap()->data(), frame.image_data().size());
 -                              auto frame = frame_factory->create_frame(it->get(), pfd);
 -                              memcpy(frame.image_data().data(), (*it)->image()->data(), frame.image_data().size());
++                                              auto frame = frame_factory->create_frame(it->get(), pfd);
++                                              memcpy(frame.image_data().data(), (*it)->bitmap()->data(), frame.image_data().size());
  
-                                       layer_producer = core::create_const_producer(core::draw_frame(std::move(frame)), (*it)->bitmap()->width(), (*it)->bitmap()->height());
 -                              layer_producer = core::create_const_producer(core::draw_frame(std::move(frame)), (*it)->rect().width(), (*it)->rect().height());
 -                      }
++                                              layer_producer = core::create_const_producer(core::draw_frame(std::move(frame)), (*it)->bitmap()->width(), (*it)->bitmap()->height());
++                                      }
 +                              }
  
-                               auto& new_layer = root->create_layer(spl::make_shared_ptr(layer_producer), (*it)->location().x, (*it)->location().y);
-                               new_layer.adjustments.opacity.set((*it)->opacity() / 255.0);
-                               new_layer.hidden.set(!(*it)->is_visible());
 -                      auto& new_layer = root->create_layer(spl::make_shared_ptr(layer_producer), (*it)->rect().left, (*it)->rect().top, (*it)->name());
 -                      new_layer.adjustments.opacity.set((*it)->opacity() / 255.0);
 -                      new_layer.hidden.set(!(*it)->visible());
++                              if(layer_producer)
++                              {
++                                      auto& new_layer = root->create_layer(spl::make_shared_ptr(layer_producer), (*it)->location().x, (*it)->location().y, (*it)->name());
++                                      new_layer.adjustments.opacity.set((*it)->opacity() / 255.0);
++                                      new_layer.hidden.set(!(*it)->is_visible());
  
-                               if((*it)->link_group_id() != 0)
-                                       link_constructor.add(&new_layer, (*it)->link_group_id(), false);
 -                      if ((*it)->has_timeline())
 -                              create_timelines(root, format_desc, new_layer, (*it), 0, 0);
++                                      if ((*it)->has_timeline())
++                                              create_timelines(root, format_desc, new_layer, (*it), 0, 0);
 -                      if((*it)->link_group_id() != 0)
 -                              link_constructor.add(&new_layer, (*it)->link_group_id(), (*it)->is_position_protected(), 0, 0);
++                                      if((*it)->link_group_id() != 0)
++                                              link_constructor.add(&new_layer, (*it)->link_group_id(), (*it)->is_position_protected(), 0, 0);
++                              }
 +                      }
                }
        }