]> git.sesse.net Git - casparcg/commitdiff
* some refactoring i psd + support for rectangular vector-masks
authorniklaspandersson <niklas.p.andersson@svt.se>
Fri, 16 Aug 2013 13:10:50 +0000 (15:10 +0200)
committerniklaspandersson <niklas.p.andersson@svt.se>
Fri, 16 Aug 2013 13:10:50 +0000 (15:10 +0200)
modules/psd/channel.h
modules/psd/doc.cpp
modules/psd/doc.h
modules/psd/layer.cpp
modules/psd/layer.h
modules/psd/misc.h
modules/psd/psd.vcxproj
test/psd-test/psd-test.cpp
test/psd-test/psd-test.vcxproj

index 4f8fc97a35ce352c372e4ee5a5bef4363a5e7901..6e46765afe9b244467eba9e21f321cc193e41139 100644 (file)
 
 namespace caspar { namespace psd {
 
-class Channel
+struct channel
 {
 public:
-       Channel(short id, unsigned long len) : id_(id), data_length_(len)
-       {}
-
-       short id() const
-       {
-               return id_;
-       }
-       unsigned long data_length() const
-       {
-               return data_length_;
-       }
-
-private:
-       unsigned long data_length_;
-       short id_;
-};
+       channel(short id1, unsigned long len) : id(id1), data_length(len) {}
 
-typedef std::shared_ptr<Channel> channel_ptr;
+       short id;
+       unsigned long data_length;
+};
 
 }      //namespace psd
 }      //namespace caspar
index 46cb3cd109c2822b022d56418451cdbc3efcd145..857b34fb397c821f079b62705a05ad8553b6ceaa 100644 (file)
@@ -170,7 +170,7 @@ void psd_document::read_image_resources()
                                                break;
                                        }
 
-                                       if(resource_length & 1 == 1)    //each resource is padded to an even amount of bytes
+                                       if((resource_length & 1) == 1)  //each resource is padded to an even amount of bytes
                                                input_.discard_bytes(1);
                                }
                                catch(PSDFileFormatException&)
@@ -209,7 +209,7 @@ void psd_document::read_layers()
                                if(layer_index  == layers_.size())
                                        layers_.push_back(std::make_shared<layer>());
                                
-                               layers_[layer_index]->populate(input_);
+                               layers_[layer_index]->populate(input_, *this);
                                //std::clog << "Added layer: " << std::string(layers_[layerIndex]->name().begin(), layers_[layerIndex]->name().end()) << std::endl;
                        }
 
index 762d283a36b977e9021490c90ee790e82b963d8a..87f30fd2ddbb02678a9dd796779dd5ed734db426 100644 (file)
@@ -31,6 +31,9 @@
 #include "misc.h"
 #include "layer.h"
 
+#include <boost/property_tree/ptree.hpp>
+
+
 namespace caspar { namespace psd {
 
 class psd_document
index fc9be9794bc92671fd5f18d829b72665052362ac..0b9d91e3b35d6e79fb1abb56c4e13488fa863005 100644 (file)
 */
 
 #include "layer.h"
+#include "doc.h"
 #include "descriptor.h"
 #include "util\pdf_reader.h"
 
-typedef unsigned char uint8_t;
 #include <algorithm>
 #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 'Txt2':    //text engine data
-                       break;
+                       case 'lspf':    //protection settings
+                               protection_flags_ = stream.read_long();
+                               break;
 
-               case 'TySh':    //type tool object settings
-                       read_text_data(stream);
-                       break;
+                       case 'Txt2':    //text engine data
+                               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());
        }
-}
 
-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
                        {
-                               if(encoding == 0)
-                                       read_raw_image_data(stream, *it, target, offset);
-                               else if(encoding == 1)
-                                       read_rle_image_data(stream, *it, target, offset);
+                               target = bitmap;
+                               offset = ((*it).id >= 0) ? 2 - (*it).id : 3;
+                               src_rect = bitmap_rect_;
+                       }
+                       else if(mask)   //mask
+                       {
+                               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
\ No newline at end of file
index ebffbaf8781323f128975ca00954cef2738b91cf..5677e02ab5f1e88492d092f0892d7deab4eb8894 100644 (file)
 
 #include <vector>
 #include <string>
-#include <memory>
+#include "common/memory.h"
 #include "util\bigendian_file_input_stream.h"
 
 #include "image.h"
 #include "misc.h"
 #include "channel.h"
-#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/ptree_fwd.hpp>
 
 namespace caspar { namespace psd {
 
 class layer;
 typedef std::shared_ptr<layer> layer_ptr;
+class psd_document;
 
 class layer
 {
+       struct impl;
+       spl::shared_ptr<impl> impl_;
+
 public:
-       class layer_mask
+       class layer_mask_info
        {
-               friend class layer;
-       public:
-
-               bool enabled() const { return (flags_ & 2) == 0; }
-               bool linked() const { return (flags_ & 1) == 0;  }
-               bool inverted() const { return (flags_ & 4) == 4; }
+               friend struct layer::impl;
 
                void read_mask_data(BEFileInputStream&);
 
-               char                    mask_id_;
-               image8bit_ptr   mask_;
-               psd::rect<long> rect_;
+               image8bit_ptr   bitmap_;
                unsigned char   default_value_;
                unsigned char   flags_;
-       };
+               char                    mask_id_;
+               rect<long>              rect_;
 
-       layer() : blend_mode_(InvalidBlendMode), link_group_id_(0), opacity_(255), baseClipping_(false), flags_(0), protection_flags_(0), masks_(0)
-       {}
+       public:
+               bool enabled() const { return (flags_ & 2) == 0; }
+               bool linked() const { return (flags_ & 1) == 0;  }
+               bool inverted() const { return (flags_ & 4) == 4; }
 
-       static std::shared_ptr<layer> create(BEFileInputStream&);
-       void populate(BEFileInputStream&);
-       void read_channel_data(BEFileInputStream&);
+               const point<long>& location() const { return rect_.location; }
+               const image8bit_ptr& bitmap() const { return bitmap_; }
+       };
 
-       const std::wstring& name() const
-       {
-               return name_;
-       }
-       const psd::rect<long>& rect() const
-       {
-               return rect_;
-       }
+       layer();
 
-       unsigned char opacity() const
-       {
-               return opacity_;
-       }
+       void populate(BEFileInputStream&, const psd_document&);
+       void read_channel_data(BEFileInputStream&);
 
-       unsigned short flags() const
-       {
-               return flags_;
-       }
+       const std::wstring& name() const;
+       unsigned char opacity() const;
+       bool is_visible();
+       bool is_position_protected();
 
-       bool visible() { return (flags_ & 2) == 0; }    //the (PSD file-format) documentation is is saying the opposite but what the heck
-       bool is_position_protected() { return (protection_flags_& 4) == 4; }
-       int     protection_flags() const { return protection_flags_; }
        bool is_text() const;
-       bool has_timeline() const;
+       const boost::property_tree::wptree& text_data() const;
 
-       const boost::property_tree::wptree& text_data() const { return text_layer_info_; }
-       const boost::property_tree::wptree& timeline_data() const { return timeline_info_; }
+       bool is_solid() const;
+       color<unsigned char> solid_color() const;
 
-       const image8bit_ptr& image() const { return image_; }
-
-       const layer_mask& mask_info() const { return mask_; }
-       const image8bit_ptr& mask() const { return mask_.mask_; }
-
-       int link_group_id() { return link_group_id_; }
-       void set_link_group_id(int id) { link_group_id_ = id; }
-
-private:
-       channel_ptr get_channel(channel_type);
-       void read_chunk(BEFileInputStream& stream, bool isMetadata = false);
-       void read_blending_ranges(BEFileInputStream&);
-       void read_text_data(BEFileInputStream&);
-       void read_metadata(BEFileInputStream& stream);
-       void read_timeline_data(BEFileInputStream&);
-
-       caspar::psd::rect<long>                 rect_;
-       std::vector<channel_ptr>                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_;
-
-       layer_mask                                              mask_;
+       bool has_timeline() const;
+       const boost::property_tree::wptree& timeline_data() const;
 
-       image8bit_ptr                                   image_;
+       const point<long>& location() const;
+       const image8bit_ptr& bitmap() const;
 
-       boost::property_tree::wptree    text_layer_info_;
-       boost::property_tree::wptree    timeline_info_;
+       int link_group_id() const;
+       void set_link_group_id(int id);
 };
 
 }      //namespace psd
index d443478b0418d62c1cdf9d85cfff197d2e928faa..7474f487fd51d9a4be362992c9a256716504389c 100644 (file)
 #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;
+};
+
+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
 {
-       T top;
-       T right;
-       T bottom;
-       T left;
-       T width() const { return right-left; }
-       T height() const { return bottom-top; }
+       point<T>        location;
+       size<T>         size;
+
+       bool empty() const { return size.width == 0 || size.height == 0; }
 };
 
+
+
 class PSDFileFormatException : public std::exception
 {
 public:
@@ -50,12 +80,12 @@ public:
 
 enum channel_type
 {
-       TotalUserMask = -3,
-       UserMask = -2,
-       Transparency = -1,
-       ColorRed = 0,
-       ColorGreen = 1,
-       ColorBlue = 2
+       total_user_mask = -3,
+       user_mask = -2,
+       transparency = -1,
+       color_red = 0,
+       color_green = 1,
+       color_blue = 2
 };
 
 enum blend_mode
index 10724a80524a1047462e8b04e775c8d4f18c1f69..912e4d7a62de3b275c13f47ed7a807589248379f 100644 (file)
@@ -47,7 +47,8 @@
     <ProjectGuid>{866A164B-6F7A-450E-8452-C6AE4E176436}</ProjectGuid>
     <Keyword>Win32Proj</Keyword>
     <RootNamespace>psd</RootNamespace>
-  </PropertyGroup>
+    <ProjectName>psd</ProjectName>
+ </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
     </Link>
+    <Lib>
+      <LinkTimeCodeGeneration>false</LinkTimeCodeGeneration>
+    </Lib>     
   </ItemDefinitionGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
index c1777f6a094ae45629e5604bf799330af911cb57..e302f06a9322ea4b06b4d2d2a0d5f1f1887fd359 100644 (file)
@@ -38,11 +38,11 @@ int _tmain(int argc, _TCHAR* argv[])
                        for(auto it = doc.layers().begin(); it != end; ++it)
                        {
                                caspar::psd::layer_ptr layer = (*it);
-                               trace << L"     <layer name='" << layer->name() << L"' opacity='" << layer->opacity() << L"' visible='" << layer->visible() << L"' protection-flags='" << std::hex << layer->protection_flags() << std::dec << L"'>" << std::endl;
-                               if(layer->image())
-                                       trace << L"             <bounding-box left='" << layer->rect().left << L"' top='" << layer->rect().top << L"' right='" << layer->rect().right << L"' bottom='" << layer->rect().bottom << L"' />" << std::endl;
-                               if(layer->mask())
-                                       trace << L"             <mask default-value='" << layer->mask_info().default_value_ << L"' enabled=" << layer->mask_info().enabled() << L" linked=" << layer->mask_info().linked() << L" inverted=" << layer->mask_info().inverted() << L" left='" << layer->mask_info().rect_.left << L"' top='" << layer->mask_info().rect_.top << "' right='" << layer->mask_info().rect_.right << "' bottom='" << layer->mask_info().rect_.bottom << "' />" << std::endl;
+                               trace << L"     <layer name='" << layer->name() << L"' opacity='" << layer->opacity() << L"' visible='" << layer->is_visible() << L"' protected='" << layer->is_position_protected() << L"'>" << std::endl;
+                               if(layer->bitmap())
+                                       trace << L"             <bounding-box x='" << layer->location().x << L"' y='" << layer->location().y << L"' width='" << layer->bitmap()->width() << L"' height='" << layer->bitmap()->height() << L"' />" << std::endl;
+                               //if(layer->mask())
+                               //      trace << L"             <mask default-value='" << layer->mask_info().default_value_ << L"' enabled=" << layer->mask_info().enabled() << L" linked=" << layer->mask_info().linked() << L" inverted=" << layer->mask_info().inverted() << L" left='" << layer->mask_info().rect_.left << L"' top='" << layer->mask_info().rect_.top << "' right='" << layer->mask_info().rect_.right << "' bottom='" << layer->mask_info().rect_.bottom << "' />" << std::endl;
                                if(layer->is_text())
                                {
                                        trace << L"                     <text value='" << (*it)->text_data().get(L"EngineDict.Editor.Text", L"") << L"' />" << std::endl;
index cb90ce27560cd5efe09347d70bca0de8da0a1d44..387b19f30a47ad0ad7b9d79dd382456d9cd75707 100644 (file)
@@ -40,7 +40,7 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <LinkIncremental>true</LinkIncremental>
     <LibraryPath>..\..\dependencies64\boost\stage\lib;$(LibraryPath)</LibraryPath>
-    <IncludePath>..\..\dependencies64\boost;$(IncludePath)</IncludePath>
+    <IncludePath>..\..\dependencies64\boost;..\..\;$(IncludePath)</IncludePath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <LinkIncremental>false</LinkIncremental>