#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
#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>
}
};
- 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);
++ }
+ }
}
}