X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fpsd%2Flayer.cpp;h=d62cf5119b83e9ff98c4383a5490af5cfb0cd2d5;hb=af4c83635bd3b2210816bdee5141d6e59c24d134;hp=e0aca51a95342dfe64761c3d47af33e1049866bc;hpb=d93cc594f03477ca10f24d86e11a552b1d7cebaa;p=casparcg diff --git a/modules/psd/layer.cpp b/modules/psd/layer.cpp index e0aca51a9..d62cf5119 100644 --- a/modules/psd/layer.cpp +++ b/modules/psd/layer.cpp @@ -1,359 +1,680 @@ -/* -* Copyright (c) 2011 Sveriges Television AB -* -* This file is part of CasparCG (www.casparcg.com). -* -* CasparCG is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* CasparCG is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with CasparCG. If not, see . -* -* Author: Niklas P Andersson, niklas.p.andersson@svt.se -*/ - -#include "layer.h" -#include "descriptor.h" -#include "util\pdf_reader.h" - -typedef unsigned char uint8_t; -#include -#include "../image/util/image_algorithms.h" -#include "../image/util/image_view.h" - -namespace caspar { namespace psd { - -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); - -layer_ptr layer::create(BEFileInputStream& stream) -{ - layer_ptr result(std::make_shared()); - result->populate(stream); - return result; -} - -void layer::populate(BEFileInputStream& stream) -{ - 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(stream.read_short()); - unsigned long data_length = stream.read_long(); - - if(channel_id <= -2) - masks_++; - - channel_ptr channel(std::make_shared(channel_id, data_length)); - channels_.push_back(channel); - } - - unsigned long blendModeSignature = stream.read_long(); - if(blendModeSignature != '8BIM') - throw PSDFileFormatException(); - - unsigned long blendModeKey = stream.read_long(); - blend_mode_ = int_to_blend_mode(blendModeKey); - - opacity_ = stream.read_byte(); - baseClipping_ = stream.read_byte() == 1 ? false : true; - flags_ = stream.read_byte(); - - stream.discard_bytes(1); - - unsigned long extraDataSize = stream.read_long(); - long position1 = stream.current_position(); - mask_.read_mask_data(stream); - read_blending_ranges(stream); - - name_ = stream.read_pascal_string(4); - - //Aditional Layer Information - unsigned long end_of_layer_info = position1 + extraDataSize; - - try - { - while(stream.current_position() < end_of_layer_info) - { - unsigned long signature = stream.read_long(); - if(signature != '8BIM' && signature != '8B64') - throw PSDFileFormatException(); - - unsigned long key = stream.read_long(); - unsigned long length = stream.read_long(); - unsigned long end_of_chunk = stream.current_position() + length; - - if(key == 'lspf') - { - protection_flags_ = stream.read_long(); - } - else if(key == 'TySh') //type tool object settings - { - std::wstring text; //the text in the layer - - 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 - stream.read_long(); //"descriptor version" should be 16 - - //text data descriptor ('descriptor structure') - descriptor text_descriptor; - if(!text_descriptor.populate(stream)) - throw PSDFileFormatException(); - else - { - if(text_descriptor.has_item(L"EngineData")) - { - const descriptor_item& text_data = text_descriptor.get_item(L"EngineData"); - read_pdf(text_layer_info_, text_data.rawdata_data); - } - } - - stream.read_short(); //"warp version" should be 1 - stream.read_long(); //"descriptor version" should be 16 - - //warp data descriptor ('descriptor structure') - descriptor warp_descriptor; - if(!text_descriptor.populate(stream)) - throw PSDFileFormatException(); - - stream.discard_bytes(4*8); //top, left, right, bottom - } - - stream.set_position(end_of_chunk); - } - } - catch(PSDFileFormatException& ex) - { - stream.set_position(end_of_layer_info); - } -} - -void layer::layer_mask::read_mask_data(BEFileInputStream& stream) -{ - unsigned long length = stream.read_long(); - switch(length) - { - case 0: - break; - - case 20: - rect_.top = stream.read_long(); - rect_.left = stream.read_long(); - rect_.bottom = stream.read_long(); - rect_.right = stream.read_long(); - - default_value_ = stream.read_byte(); - flags_ = stream.read_byte(); - stream.discard_bytes(2); - break; - - 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; - - default: - stream.discard_bytes(length); - break; - }; -} - -bool layer::is_text() const -{ - return !text_layer_info_.empty(); -} - -//TODO: implement -void layer::read_blending_ranges(BEFileInputStream& stream) -{ - unsigned long length = stream.read_long(); - stream.discard_bytes(length); -} - -channel_ptr layer::get_channel(channel_type type) -{ - auto end = channels_.end(); - for(auto it = channels_.begin(); it != end; ++it) - { - if((*it)->id() == type) - { - return (*it); - } - } - - return NULL; -} - -void layer::read_channel_data(BEFileInputStream& stream) -{ - image8bit_ptr img; - image8bit_ptr mask; - - bool has_transparency(get_channel(psd::Transparency)); - - //std::clog << std::endl << "layer: " << std::string(name().begin(), name().end()) << std::endl; - - if(rect_.width() > 0 && rect_.height() > 0) - { - img = std::make_shared(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()); - } - - if(masks_ > 0 && mask_.rect_.width() > 0 && mask_.rect_.height() > 0) - { - mask = std::make_shared(mask_.rect_.width(), mask_.rect_.height(), 1); - //std::clog << std::dec << "has mask: [width: " << mask_rect_.width() << " height: " << mask_rect_.height() << "]" << std::endl; - } - - auto end = channels_.end(); - for(auto it = channels_.begin(); it != end; ++it) - { - 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 - { - 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; - } - } - - //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(!target) - discard_channel = true; - - if(discard_channel) - { - //std::clog << " -> discarding" << std::endl; - stream.discard_bytes((*it)->data_length()); - } - else - { - //std::clog << " -> reading..."; - unsigned short encoding = stream.read_short(); - if(target) - { - if(encoding == 0) - read_raw_image_data(stream, *it, target, offset); - else if(encoding == 1) - read_rle_image_data(stream, *it, target, offset); - else - throw PSDFileFormatException(); - } - //std::clog << " " << std::hex << (stream.current_position() - cp) << " bytes read" << std::endl; - } - } - - if(img && has_transparency) - { - caspar::image::image_view view(img->data(), img->width(), img->height()); - caspar::image::premultiply(view); - } - - image_ = img; - mask_.mask_ = mask; -} - -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 stride = target->channel_count(); - if(stride == 1) - stream.read(reinterpret_cast(data + offset), total_length); - else - { - for(unsigned long index=0; index < total_length; ++index) - data[index*stride+offset] = stream.read_byte(); - } -} - -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(); - - std::vector scanline_lengths; - scanline_lengths.reserve(height); - - for(unsigned long scanlineIndex=0; scanlineIndex < height; ++scanlineIndex) - scanline_lengths.push_back(stream.read_short()); - - unsigned char* data = target->data(); - - for(unsigned long scanlineIndex=0; scanlineIndex < height; ++scanlineIndex) - { - unsigned long colIndex = 0; - unsigned char length = 0; - do - { - length = 0; - - //Get controlbyte - char controlByte = static_cast(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) - { - //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; - } - - colIndex += length; - } - while(colIndex < width); - } -} - -} //namespace psd -} //namespace caspar \ No newline at end of file +/* +* Copyright (c) 2011 Sveriges Television AB +* +* This file is part of CasparCG (www.casparcg.com). +* +* CasparCG is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* CasparCG is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with CasparCG. If not, see . +* +* Author: Niklas P Andersson, niklas.p.andersson@svt.se +*/ + +#include "layer.h" +#include "psd_document.h" +#include "descriptor.h" +#include "util/pdf_reader.h" + +#include "../image/util/image_algorithms.h" +#include "../image/util/image_view.h" + +#include + +#include +#include +#include + +#include + +namespace caspar { namespace psd { + +void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream) +{ + auto length = stream.read_long(); + switch(length) + { + case 0: + break; + + case 20: + case 36: + rect_.location.y = stream.read_long(); + rect_.location.x = stream.read_long(); + rect_.size.height = stream.read_long() - rect_.location.y; + rect_.size.width = stream.read_long() - rect_.location.x; + + default_value_ = stream.read_byte(); + flags_ = stream.read_byte(); + stream.discard_bytes(2); + + if(length == 36) + { + //we save the information about the total mask in case the vector-mask has an unsupported shape + total_mask_.reset(new layer::mask_info); + total_mask_->flags_ = stream.read_byte(); + total_mask_->default_value_ = stream.read_byte(); + total_mask_->rect_.location.y = stream.read_long(); + total_mask_->rect_.location.x = stream.read_long(); + total_mask_->rect_.size.height = stream.read_long() - rect_.location.y; + total_mask_->rect_.size.width = stream.read_long() - rect_.location.x; + } + break; + + default: + //TODO: Log that we discard a mask that is not supported + stream.discard_bytes(length); + break; + }; +} + +bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream& stream, int doc_width, int doc_height) +{ + std::vector> knots; + bool smooth_curve = false; + + stream.read_long(); // version + this->flags_ = static_cast(stream.read_long()); // flags + int path_records = (length - 8) / 26; + + auto position = stream.current_position(); + + const int SELECTOR_SIZE = 2; + const int PATH_POINT_SIZE = 4 + 4; + const int PATH_POINT_RECORD_SIZE = SELECTOR_SIZE + (3 * PATH_POINT_SIZE); + + for (int i = 1; i <= path_records; ++i) + { + auto selector = stream.read_short(); + if (selector == 2) //we only concern ourselves with closed paths + { + auto p_y = stream.read_long(); + auto p_x = stream.read_long(); + point prev{ static_cast(p_x), static_cast(p_y) }; + + auto a_y = stream.read_long(); + auto a_x = stream.read_long(); + point anchor{ static_cast(a_x), static_cast(a_y) }; + + auto n_y = stream.read_long(); + auto n_x = stream.read_long(); + point next{ static_cast(n_x), static_cast(n_y) }; + + if (anchor == prev && anchor == next) + knots.push_back(anchor); + else + { + //note that we've got a smooth curve, but continue to iterate through the data + smooth_curve = true; + } + } + + auto offset = PATH_POINT_RECORD_SIZE * i; + stream.set_position(position + offset); + } + + if (smooth_curve || knots.size() != 4) //we can't handle smooth-curves yet and we only support quad-gons + { + rect_.clear(); + flags_ = static_cast(flags::unsupported | flags::disabled); + return false; + } + + //the path_points are given in fixed-point 8.24 as a ratio with regards to the width/height of the document. we need to divide by 16777215.0f to get the real ratio. + float x_ratio = doc_width / 16777215.0f; + float y_ratio = doc_height / 16777215.0f; + rect_.clear(); + knots_.clear(); + + //is it an orthogonal rectangle + if (knots[0].x == knots[3].x && knots[1].x == knots[2].x && knots[0].y == knots[1].y && knots[2].y == knots[3].y) + { + rect_.location.x = static_cast(knots[0].x * x_ratio + 0.5f); //add .5 to get propper rounding when converting to integer + rect_.location.y = static_cast(knots[0].y * y_ratio + 0.5f); //add .5 to get propper rounding when converting to integer + rect_.size.width = static_cast(knots[1].x * x_ratio + 0.5f) - rect_.location.x; //add .5 to get propper rounding when converting to integer + rect_.size.height = static_cast(knots[2].y * y_ratio + 0.5f) - rect_.location.y; //add .5 to get propper rounding when converting to integer + } + else //it's could be any kind of quad-gon + { + for (auto& k : knots) + knots_.push_back(psd::point{static_cast(k.x * x_ratio + 0.5f), static_cast(k.y * y_ratio + 0.5f)}); + } + + return true; +} + +struct layer::impl +{ + friend class layer; + + impl() : blend_mode_(caspar::core::blend_mode::normal), layer_type_(layer_type::content), link_group_id_(0), opacity_(255), sheet_color_(0), baseClipping_(false), flags_(0), protection_flags_(0), masks_count_(0), scale_{ 1.0, 1.0 }, angle_(0), shear_(0), tags_(layer_tag::none) + {} + +private: + std::vector channels_; + caspar::core::blend_mode blend_mode_; + layer_type layer_type_; + int link_group_id_; + int opacity_; + int sheet_color_; + bool baseClipping_; + std::uint8_t flags_; + std::uint32_t protection_flags_; + std::wstring name_; + int masks_count_; + psd::point text_pos_; + psd::point scale_; + double angle_; + double shear_; + + layer::mask_info mask_; + + rect bitmap_rect_; + image8bit_ptr bitmap_; + + boost::property_tree::wptree text_layer_info_; + boost::property_tree::wptree timeline_info_; + + color solid_color_; + + layer_tag tags_; + +public: + void populate(bigendian_file_input_stream& stream, const psd_document& doc) + { + bitmap_rect_.location.y = stream.read_long(); + bitmap_rect_.location.x = stream.read_long(); + bitmap_rect_.size.height = stream.read_long() - bitmap_rect_.location.y; + bitmap_rect_.size.width = stream.read_long() - bitmap_rect_.location.x; + + //Get info about the channels in the layer + auto channelCount = stream.read_short(); + for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex) + { + auto id = static_cast(stream.read_short()); + channel c(id, stream.read_long()); + + if(c.id < -1) + masks_count_++; + + channels_.push_back(c); + } + + auto blendModeSignature = stream.read_long(); + if(blendModeSignature != '8BIM') + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("blendModeSignature != '8BIM'")); + + blend_mode_ = int_to_blend_mode(stream.read_long()); + opacity_ = stream.read_byte(); + baseClipping_ = stream.read_byte() == 1 ? false : true; + flags_ = stream.read_byte(); + + stream.discard_bytes(1); //padding + + auto extras_size = stream.read_long(); + auto position = stream.current_position(); + mask_.read_mask_data(stream); + read_blending_ranges(stream); + + stream.read_pascal_string(4); //throw this away. We'll read the unicode version of the name later + + //Aditional Layer Information + auto end_of_layer_info = position + extras_size; + try + { + while(stream.current_position() < end_of_layer_info) + { + read_chunk(stream, doc); + } + } + catch(psd_file_format_exception&) + { + stream.set_position(end_of_layer_info); + } + } + + void read_chunk(bigendian_file_input_stream& stream, const psd_document& doc, bool isMetadata = false) + { + auto signature = stream.read_long(); + if(signature != '8BIM' && signature != '8B64') + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM' && signature != '8B64'")); + + auto key = stream.read_long(); + + if(isMetadata) stream.read_long(); + + auto length = stream.read_long(); + auto end_of_chunk = stream.current_position() + length; + + try + { + switch(key) + { + case 'SoCo': + read_solid_color(stream); + break; + + case 'lsct': //group settings (folders) + read_group_settings(stream, length); + + case 'lspf': //protection settings + protection_flags_ = stream.read_long(); + break; + + case 'Txt2': //text engine data + break; + + case 'luni': + set_name_and_tags(stream.read_unicode_string()); + break; + + case 'TySh': //type tool object settings + read_text_data(stream); + break; + + case 'shmd': //metadata + read_metadata(stream, doc); + break; + + case 'lclr': + sheet_color_ = static_cast(stream.read_short()); + break; + + case 'lyvr': //layer version + break; + + case 'lnkD': //linked layer + case 'lnk2': //linked layer + case 'lnk3': //linked layer + break; + + case 'vsms': + case 'vmsk': + mask_.read_vector_mask_data(length, stream, doc.width(), doc.height()); + break; + + case 'tmln': + read_timeline_data(stream); + break; + + default: + break; + } + } + catch(psd_file_format_exception& ex) + { + //ignore failed chunks silently + CASPAR_LOG(warning) << ex.what(); + } + + stream.set_position(end_of_chunk); + } + + void set_name_and_tags(const std::wstring& name) { + auto start_bracket = name.find_first_of(L'['); + auto end_bracket = name.find_first_of(L']'); + if (start_bracket == std::wstring::npos && end_bracket == std::wstring::npos) { + //no flags + name_ = name; + } + else if (start_bracket != std::wstring::npos && end_bracket > start_bracket) { + //we have tags + tags_ = string_to_layer_tags(name.substr(start_bracket+1, end_bracket-start_bracket-1)); + name_ = name.substr(end_bracket+1); + } + else { + //missmatch + name_ = name; + CASPAR_LOG(warning) << "Mismatching tag-brackets in layer name"; + } + + boost::trim(name_); + } + + void read_solid_color(bigendian_file_input_stream& stream) + { + if(stream.read_long() != 16) //"descriptor version" should be 16 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16")); + + descriptor solid_descriptor(L"solid_color"); + solid_descriptor.populate(stream); + solid_color_.red = static_cast(solid_descriptor.items().get(L"Clr .Rd ", 0.0) + 0.5); + solid_color_.green = static_cast(solid_descriptor.items().get(L"Clr .Grn ", 0.0) + 0.5); + solid_color_.blue = static_cast(solid_descriptor.items().get(L"Clr .Bl ", 0.0) + 0.5); + solid_color_.alpha = 255; + } + + void read_group_settings(bigendian_file_input_stream& stream, unsigned int length) + { + auto type = stream.read_long(); + unsigned int sub_type = 0; + + if (length >= 12) + { + auto signature = stream.read_long(); + if (signature != '8BIM') + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM'")); + + blend_mode_ = int_to_blend_mode(stream.read_long()); + + if (length >= 16) + sub_type = stream.read_long(); + } + + layer_type_ = int_to_layer_type(type, sub_type); + } + + void read_metadata(bigendian_file_input_stream& stream, const psd_document& doc) + { + int count = stream.read_long(); + for(int index = 0; index < count; ++index) + read_chunk(stream, doc, true); + } + + void read_timeline_data(bigendian_file_input_stream& stream) + { + if(stream.read_long() != 16) //"descriptor version" should be 16 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16")); + + descriptor timeline_descriptor(L"timeline"); + timeline_descriptor.populate(stream); + timeline_info_.swap(timeline_descriptor.items()); + } + + void read_text_data(bigendian_file_input_stream& stream) + { + std::wstring text; //the text in the layer + + stream.read_short(); //should be 1 + + //transformation info + auto xx = stream.read_double(); + auto xy = stream.read_double(); + auto yx = stream.read_double(); + auto yy = stream.read_double(); + auto tx = stream.read_double(); // tx + auto ty = stream.read_double(); // ty + + text_pos_.x = tx; + text_pos_.y = ty; + + if(stream.read_short() != 50) //"text version" should be 50 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid text version")); + + if(stream.read_long() != 16) //"descriptor version" should be 16 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text-data")); + + descriptor text_descriptor(L"text"); + text_descriptor.populate(stream); + auto text_info = text_descriptor.items().get_optional(L"EngineData"); + + if (text_info) + { + std::string str(text_info->begin(), text_info->end()); + read_pdf(text_layer_info_, str); + log::print_child(boost::log::trivial::trace, L"", L"text_layer_info", text_layer_info_); + } + + if(stream.read_short() != 1) //"warp version" should be 1 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid warp version")); + + if(stream.read_long() != 16) //"descriptor version" should be 16 + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text warp-data")); + + descriptor warp_descriptor(L"warp"); + warp_descriptor.populate(stream); + stream.read_double(); // w_left + stream.read_double(); // w_top + stream.read_double(); // w_right + stream.read_double(); // w_bottom + + + //extract scale, angle and shear factor from transformation matrix + const double PI = 3.141592653589793; + auto angle = atan2(xy, xx); + + auto c = cos(angle); + auto s = sin(angle); + auto scale_x = (abs(c) > 0.1) ? xx / c : xy / s; + + if (xx / scale_x < 0) { //fel kvadrant + angle += PI; + c = cos(angle); + s = sin(angle); + scale_x = (abs(c) > 0.1) ? xx / c : xy / s; + } + + auto shear_factor = (yx*c + yy*s) / (yy*c - yx * s); + auto scale_y = 1.0; + if (abs(shear_factor) < 0.0001 || std::isnan(shear_factor)) { + shear_factor = 0; + scale_y = (abs(c) > 0.1) ? yy / c : yx / -s; + } + else { + scale_y = yx / (c*shear_factor - s); + } + + scale_.x = scale_x; + scale_.y = scale_y; + angle_ = angle * 180 / PI; + shear_ = shear_factor; + + + + + } + + //TODO: implement + void read_blending_ranges(bigendian_file_input_stream& stream) + { + auto length = stream.read_long(); + stream.discard_bytes(length); + } + + bool has_channel(channel_type type) + { + return std::find_if(channels_.begin(), channels_.end(), [=](const channel& c) { return c.id == static_cast(type); }) != channels_.end(); + } + + void read_channel_data(bigendian_file_input_stream& stream) + { + image8bit_ptr bitmap; + + bool has_transparency = has_channel(channel_type::transparency); + + if(!bitmap_rect_.empty()) + { + bitmap = std::make_shared(bitmap_rect_.size.width, bitmap_rect_.size.height, 4); + if(!has_transparency) + std::memset(bitmap->data(), 255, bitmap->width()*bitmap->height()*bitmap->channel_count()); + } + + for(auto it = channels_.begin(); it != channels_.end(); ++it) + { + auto channel = (*it); + image8bit_ptr target; + int offset = 0; + bool discard_channel = false; + + //determine target bitmap and offset + if(channel.id >= 3) + discard_channel = true; //discard channels that doesn't contribute to the final image + else if(channel.id >= -1) //BGRA-data + { + target = bitmap; + offset = (channel.id >= 0) ? 2 - channel.id : 3; + } + else //mask + { + offset = 0; + if (channel.id == -2) + { + mask_.create_bitmap(); + target = mask_.bitmap_; + } + else if (channel.id == -3) //total_mask + { + mask_.total_mask_->create_bitmap(); + target = mask_.total_mask_->bitmap_; + offset = 0; + } + } + + if(!target) + discard_channel = true; + + auto end_of_data = stream.current_position() + channel.data_length; + if(!discard_channel) + { + auto encoding = stream.read_short(); + if(target) + { + if(encoding == 0) + read_raw_image_data(stream, channel.data_length-2, target, offset); + else if(encoding == 1) + read_rle_image_data(stream, target, offset); + else + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Unhandled image data encoding: " + boost::lexical_cast(encoding))); + } + } + stream.set_position(end_of_data); + } + + if(bitmap && has_transparency) + { + caspar::image::image_view view(bitmap->data(), bitmap->width(), bitmap->height()); + caspar::image::premultiply(view); + } + + bitmap_ = bitmap; + } + + void read_raw_image_data(bigendian_file_input_stream& stream, int data_length, image8bit_ptr target, int offset) + { + auto total_length = target->width() * target->height(); + if (total_length != data_length) + CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("total_length != data_length")); + + auto data = target->data(); + auto stride = target->channel_count(); + + if (stride == 1) + stream.read(reinterpret_cast(data + offset), total_length); + else + { + for(int index = 0; index < total_length; ++index) + data[index * stride + offset] = stream.read_byte(); + } + } + + void read_rle_image_data(bigendian_file_input_stream& stream, image8bit_ptr target, int offset) + { + auto width = target->width(); + auto height = target->height(); + auto stride = target->channel_count(); + + std::vector scanline_lengths; + scanline_lengths.reserve(height); + + for (int scanlineIndex = 0; scanlineIndex < height; ++scanlineIndex) + scanline_lengths.push_back(static_cast(stream.read_short())); + + auto target_data = target->data(); + + std::vector line(width); + + for(int scanlineIndex=0; scanlineIndex < height; ++scanlineIndex) + { + int colIndex = 0; + + do + { + int length = 0; + + //Get controlbyte + char controlByte = static_cast(stream.read_byte()); + if(controlByte >= 0) + { + //Read uncompressed string + length = controlByte+1; + for(int index=0; index < length; ++index) + line[colIndex+index] = stream.read_byte(); + } + else if(controlByte > -128) + { + //Repeat next byte + length = -controlByte+1; + auto value = stream.read_byte(); + for(int index=0; index < length; ++index) + line[colIndex+index] = value; + } + + colIndex += length; + } + while(colIndex < width); + + //use line to populate target + for(int index = 0; index < width; ++index) + { + target_data[(scanlineIndex*width+index) * stride + offset] = line[index]; + } + } + } +}; + +layer::layer() : impl_(spl::make_shared()) {} + +void layer::populate(bigendian_file_input_stream& stream, const psd_document& doc) { impl_->populate(stream, doc); } +void layer::read_channel_data(bigendian_file_input_stream& stream) { impl_->read_channel_data(stream); } + +const std::wstring& layer::name() const { return impl_->name_; } +int layer::opacity() const { return impl_->opacity_; } +caspar::core::blend_mode layer::blend_mode() const { return impl_->blend_mode_; } +int layer::sheet_color() const { return impl_->sheet_color_; } + +bool layer::is_visible() { return (impl_->flags_ & 2) == 0; } //the (PSD file-format) documentation is is saying the opposite but what the heck +bool layer::is_position_protected() { return (impl_->protection_flags_& 4) == 4; } + +const layer::mask_info& layer::mask() const { return impl_->mask_; } + +const psd::point& layer::text_pos() const { return impl_->text_pos_; } +const psd::point& layer::scale() const { return impl_->scale_; } +const double layer::angle() const { return impl_->angle_; } +const double layer::shear() const { return impl_->shear_; } + +bool layer::is_text() const { return !impl_->text_layer_info_.empty(); } +const boost::property_tree::wptree& layer::text_data() const { return impl_->text_layer_info_; } + +bool layer::has_timeline() const { return !impl_->timeline_info_.empty(); } +const boost::property_tree::wptree& layer::timeline_data() const { return impl_->timeline_info_; } + +bool layer::is_solid() const { return impl_->solid_color_.alpha != 0; } +color layer::solid_color() const { return impl_->solid_color_; } + +const point& layer::location() const { return impl_->bitmap_rect_.location; } +const size& layer::size() const { return impl_->bitmap_rect_.size; } +const image8bit_ptr& layer::bitmap() const { return impl_->bitmap_; } + +layer_type layer::group_mode() const { return impl_->layer_type_; } +int layer::link_group_id() const { return impl_->link_group_id_; } +void layer::set_link_group_id(int id) { impl_->link_group_id_ = id; } + +bool layer::is_explicit_dynamic() const { return (impl_->tags_ & layer_tag::explicit_dynamic) == layer_tag::explicit_dynamic; } +bool layer::is_static() const { return (impl_->tags_ & layer_tag::rasterized) == layer_tag::rasterized; } +bool layer::is_movable() const { return (impl_->tags_ & layer_tag::moveable) == layer_tag::moveable; } +bool layer::is_resizable() const { return (impl_->tags_ & layer_tag::resizable) == layer_tag::resizable; } +bool layer::is_placeholder() const { return (impl_->tags_ & layer_tag::placeholder) == layer_tag::placeholder; } +bool layer::is_cornerpin() const { return (impl_->tags_ & layer_tag::cornerpin) == layer_tag::cornerpin; } + +layer_tag layer::tags() const { return impl_->tags_; } + +} //namespace psd +} //namespace caspar