]> git.sesse.net Git - casparcg/blob - modules/psd/layer.cpp
b7435021035bc62b16324ff5d5338e615d8a3cfa
[casparcg] / modules / psd / layer.cpp
1 /*\r
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 * This file is part of CasparCG (www.casparcg.com).\r
5 *\r
6 * CasparCG is free software: you can redistribute it and/or modify\r
7 * it under the terms of the GNU General Public License as published by\r
8 * the Free Software Foundation, either version 3 of the License, or\r
9 * (at your option) any later version.\r
10 *\r
11 * CasparCG is distributed in the hope that it will be useful,\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 * GNU General Public License for more details.\r
15 *\r
16 * You should have received a copy of the GNU General Public License\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 * Author: Niklas P Andersson, niklas.p.andersson@svt.se\r
20 */\r
21 \r
22 #include "layer.h"\r
23 #include "psd_document.h"\r
24 #include "descriptor.h"\r
25 #include "util/pdf_reader.h"\r
26 \r
27 #include "../image/util/image_algorithms.h"\r
28 #include "../image/util/image_view.h"\r
29 \r
30 #include <common/log.h>\r
31 \r
32 #include <boost/property_tree/ptree.hpp>\r
33 #include <boost/algorithm/string.hpp>\r
34 #include <boost/lexical_cast.hpp>\r
35 \r
36 #include <algorithm>\r
37 \r
38 namespace caspar { namespace psd {\r
39 \r
40 void layer::mask_info::read_mask_data(bigendian_file_input_stream& stream)\r
41 {\r
42         auto length = stream.read_long();\r
43         switch(length)\r
44         {\r
45         case 0:\r
46                 break;\r
47 \r
48         case 20:\r
49         case 36:\r
50                 rect_.location.y = stream.read_long();\r
51                 rect_.location.x = stream.read_long();\r
52                 rect_.size.height = stream.read_long() - rect_.location.y;\r
53                 rect_.size.width = stream.read_long() - rect_.location.x;\r
54 \r
55                 default_value_ = stream.read_byte();\r
56                 flags_ = stream.read_byte();\r
57                 stream.discard_bytes(2);\r
58                 \r
59                 if(length == 36)\r
60                 {\r
61                         //we save the information about the total mask in case the vector-mask has an unsupported shape\r
62                         total_mask_.reset(new layer::mask_info);\r
63                         total_mask_->flags_ = stream.read_byte();\r
64                         total_mask_->default_value_ = stream.read_byte();\r
65                         total_mask_->rect_.location.y = stream.read_long();\r
66                         total_mask_->rect_.location.x = stream.read_long();\r
67                         total_mask_->rect_.size.height = stream.read_long() - rect_.location.y;\r
68                         total_mask_->rect_.size.width = stream.read_long() - rect_.location.x;\r
69                 }\r
70                 break;\r
71 \r
72         default:\r
73                 //TODO: Log that we discard a mask that is not supported\r
74                 stream.discard_bytes(length);\r
75                 break;\r
76         };\r
77 }\r
78 \r
79 bool layer::vector_mask_info::populate(int length, bigendian_file_input_stream& stream, int doc_width, int doc_height)\r
80 {\r
81         std::vector<point<int>> knots;\r
82         bool smooth_curve = false;\r
83 \r
84         stream.read_long(); // version\r
85         this->flags_ = static_cast<std::uint8_t>(stream.read_long()); // flags\r
86         int path_records = (length - 8) / 26;\r
87 \r
88         auto position = stream.current_position();\r
89 \r
90         const int SELECTOR_SIZE = 2;\r
91         const int PATH_POINT_SIZE = 4 + 4;\r
92         const int PATH_POINT_RECORD_SIZE = SELECTOR_SIZE + (3 * PATH_POINT_SIZE);\r
93 \r
94         for (int i = 1; i <= path_records; ++i)\r
95         {\r
96                 auto selector = stream.read_short();\r
97                 if (selector == 2)      //we only concern ourselves with closed paths \r
98                 {\r
99                         auto p_y = stream.read_long();\r
100                         auto p_x = stream.read_long();\r
101                         point<int> prev{ static_cast<int>(p_x), static_cast<int>(p_y) };\r
102 \r
103                         auto a_y = stream.read_long();\r
104                         auto a_x = stream.read_long();\r
105                         point<int> anchor{ static_cast<int>(a_x), static_cast<int>(a_y) };\r
106 \r
107                         auto n_y = stream.read_long();\r
108                         auto n_x = stream.read_long();\r
109                         point<int> next{ static_cast<int>(n_x), static_cast<int>(n_y) };\r
110 \r
111                         if (anchor == prev && anchor == next)\r
112                                 knots.push_back(anchor);\r
113                         else \r
114                         {\r
115                                 //note that we've got a smooth curve, but continue to iterate through the data\r
116                                 smooth_curve = true;\r
117                         }\r
118                 }\r
119 \r
120                 auto offset = PATH_POINT_RECORD_SIZE * i;\r
121                 stream.set_position(position + offset);\r
122         }\r
123 \r
124         if (smooth_curve || knots.size() != 4)  //we can't handle smooth-curves yet and we only support quad-gons\r
125         {\r
126                 rect_.clear();\r
127                 flags_ = static_cast<std::uint8_t>(flags::unsupported | flags::disabled);\r
128                 return false;\r
129         }\r
130 \r
131         //the path_points are given in fixed-point 8.24 as a ratio with regards to the width/height of the document. we need to divide by 16777215.0f to get the real ratio.\r
132         float x_ratio = doc_width / 16777215.0f;\r
133         float y_ratio = doc_height / 16777215.0f;\r
134         rect_.clear();\r
135         knots_.clear();\r
136 \r
137         //is it an orthogonal rectangle\r
138         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)\r
139         {\r
140                 rect_.location.x = static_cast<int>(knots[0].x * x_ratio + 0.5f);                                               //add .5 to get propper rounding when converting to integer\r
141                 rect_.location.y = static_cast<int>(knots[0].y * y_ratio + 0.5f);                                               //add .5 to get propper rounding when converting to integer\r
142                 rect_.size.width = static_cast<int>(knots[1].x * x_ratio + 0.5f) - rect_.location.x;    //add .5 to get propper rounding when converting to integer\r
143                 rect_.size.height = static_cast<int>(knots[2].y * y_ratio + 0.5f) - rect_.location.y;   //add .5 to get propper rounding when converting to integer\r
144         }\r
145         else //it's could be any kind of quad-gon\r
146         {\r
147                 for (auto& k : knots)\r
148                         knots_.push_back(psd::point<int>{static_cast<int>(k.x * x_ratio + 0.5f), static_cast<int>(k.y * y_ratio + 0.5f)});\r
149         }\r
150 \r
151         return true;\r
152 }\r
153 \r
154 struct layer::impl\r
155 {\r
156         friend class layer;\r
157 \r
158         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)\r
159         {}\r
160 \r
161 private:\r
162         std::vector<channel>                    channels_;\r
163         caspar::core::blend_mode                blend_mode_;\r
164         layer_type                                              layer_type_;\r
165         int                                                             link_group_id_;\r
166         int                                                             opacity_;\r
167         int                                                             sheet_color_;\r
168         bool                                                    baseClipping_;\r
169         std::uint8_t                                    flags_;\r
170         std::uint32_t                                   protection_flags_;\r
171         std::wstring                                    name_;\r
172         int                                                             masks_count_;\r
173         psd::point<double>                              text_pos_;\r
174         psd::point<double>                              scale_;\r
175         double                                                  angle_;\r
176         double                                                  shear_;\r
177 \r
178         layer::mask_info                                mask_;\r
179 \r
180         rect<int>                                               bitmap_rect_;\r
181         image8bit_ptr                                   bitmap_;\r
182 \r
183         boost::property_tree::wptree    text_layer_info_;\r
184         boost::property_tree::wptree    timeline_info_;\r
185 \r
186         color<std::uint8_t>                             solid_color_;\r
187 \r
188         layer_tag                                               tags_;\r
189 \r
190 public:\r
191         void populate(bigendian_file_input_stream& stream, const psd_document& doc)\r
192         {\r
193                 bitmap_rect_.location.y = stream.read_long();\r
194                 bitmap_rect_.location.x = stream.read_long();\r
195                 bitmap_rect_.size.height = stream.read_long() - bitmap_rect_.location.y;\r
196                 bitmap_rect_.size.width = stream.read_long() - bitmap_rect_.location.x;\r
197 \r
198                 //Get info about the channels in the layer\r
199                 auto channelCount = stream.read_short();\r
200                 for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex)\r
201                 {\r
202                         auto id = static_cast<std::int16_t>(stream.read_short());\r
203                         channel c(id, stream.read_long());\r
204 \r
205                         if(c.id < -1)\r
206                                 masks_count_++;\r
207 \r
208                         channels_.push_back(c);\r
209                 }\r
210 \r
211                 auto blendModeSignature = stream.read_long();\r
212                 if(blendModeSignature != '8BIM')\r
213                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("blendModeSignature != '8BIM'"));\r
214 \r
215                 blend_mode_ = int_to_blend_mode(stream.read_long());\r
216                 opacity_ = stream.read_byte();\r
217                 baseClipping_ = stream.read_byte() == 1 ? false : true;\r
218                 flags_ = stream.read_byte();\r
219 \r
220                 stream.discard_bytes(1);        //padding\r
221 \r
222                 auto extras_size = stream.read_long();\r
223                 auto position = stream.current_position();\r
224                 mask_.read_mask_data(stream);\r
225                 read_blending_ranges(stream);\r
226 \r
227                 stream.read_pascal_string(4);   //throw this away. We'll read the unicode version of the name later\r
228 \r
229                 //Aditional Layer Information\r
230                 auto end_of_layer_info = position + extras_size;\r
231                 try\r
232                 {\r
233                         while(stream.current_position() < end_of_layer_info)\r
234                         {\r
235                                 read_chunk(stream, doc);\r
236                         }\r
237                 }\r
238                 catch(psd_file_format_exception&)\r
239                 {\r
240                         stream.set_position(end_of_layer_info);\r
241                 }\r
242         }\r
243 \r
244         void read_chunk(bigendian_file_input_stream& stream, const psd_document& doc, bool isMetadata = false)\r
245         {\r
246                 auto signature = stream.read_long();\r
247                 if(signature != '8BIM' && signature != '8B64')\r
248                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM' && signature != '8B64'"));\r
249 \r
250                 auto key = stream.read_long();\r
251 \r
252                 if(isMetadata) stream.read_long();\r
253 \r
254                 auto length = stream.read_long();\r
255                 auto end_of_chunk = stream.current_position() + length;\r
256 \r
257                 try\r
258                 {\r
259                         switch(key)\r
260                         {\r
261                         case 'SoCo':\r
262                                 read_solid_color(stream);\r
263                                 break;\r
264 \r
265                         case 'lsct':    //group settings (folders)\r
266                                 read_group_settings(stream, length);\r
267 \r
268                         case 'lspf':    //protection settings\r
269                                 protection_flags_ = stream.read_long();\r
270                                 break;\r
271 \r
272                         case 'Txt2':    //text engine data\r
273                                 break;\r
274 \r
275                         case 'luni':\r
276                                 set_name_and_tags(stream.read_unicode_string());\r
277                                 break;\r
278 \r
279                         case 'TySh':    //type tool object settings\r
280                                 read_text_data(stream);\r
281                                 break;\r
282                                 \r
283                         case 'shmd':    //metadata\r
284                                 read_metadata(stream, doc);\r
285                                 break;\r
286 \r
287                         case 'lclr':\r
288                                 sheet_color_ = static_cast<std::int16_t>(stream.read_short());\r
289                                 break;\r
290                                 \r
291                         case 'lyvr':    //layer version\r
292                                 break;\r
293 \r
294                         case 'lnkD':    //linked layer\r
295                         case 'lnk2':    //linked layer\r
296                         case 'lnk3':    //linked layer\r
297                                 break;\r
298 \r
299                         case 'vsms':\r
300                         case 'vmsk':\r
301                                 mask_.read_vector_mask_data(length, stream, doc.width(), doc.height());\r
302                                 break;\r
303                                 \r
304                         case 'tmln':\r
305                                 read_timeline_data(stream);\r
306                                 break;\r
307 \r
308                         default:\r
309                                 break;\r
310                         }\r
311                 }\r
312                 catch(psd_file_format_exception& ex)\r
313                 {\r
314                         //ignore failed chunks silently\r
315                         CASPAR_LOG(warning) << ex.what();\r
316                 }\r
317 \r
318                 stream.set_position(end_of_chunk);\r
319         }\r
320           \r
321         void set_name_and_tags(const std::wstring& name) {\r
322                 auto start_bracket = name.find_first_of(L'[');\r
323                 auto end_bracket = name.find_first_of(L']');\r
324                 if (start_bracket == std::wstring::npos && end_bracket == std::wstring::npos) {\r
325                         //no flags\r
326                         name_ = name;\r
327                 }\r
328                 else if (start_bracket != std::wstring::npos && end_bracket > start_bracket) {\r
329                         //we have tags\r
330                         tags_ = string_to_layer_tags(name.substr(start_bracket+1, end_bracket-start_bracket-1));\r
331                         name_ = name.substr(end_bracket+1);\r
332                 }\r
333                 else {\r
334                         //missmatch\r
335                         name_ = name;\r
336                         CASPAR_LOG(warning) << "Mismatching tag-brackets in layer name";\r
337                 }\r
338 \r
339                 boost::trim(name_);\r
340         }\r
341 \r
342         void read_solid_color(bigendian_file_input_stream& stream)\r
343         {\r
344                 if(stream.read_long() != 16)    //"descriptor version" should be 16\r
345                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16"));\r
346 \r
347                 descriptor solid_descriptor(L"solid_color");\r
348                 solid_descriptor.populate(stream);\r
349                 solid_color_.red = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Rd  ", 0.0) + 0.5);\r
350                 solid_color_.green = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Grn ", 0.0) + 0.5);\r
351                 solid_color_.blue = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Bl  ", 0.0) + 0.5);\r
352                 solid_color_.alpha = 255;\r
353         }\r
354 \r
355         void read_group_settings(bigendian_file_input_stream& stream, unsigned int length) \r
356         {\r
357                 auto type = stream.read_long();\r
358                 unsigned int sub_type = 0;\r
359 \r
360                 if (length >= 12)\r
361                 {\r
362                         auto signature = stream.read_long();\r
363                         if (signature != '8BIM')\r
364                                 CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM'"));\r
365 \r
366                         blend_mode_ = int_to_blend_mode(stream.read_long());\r
367                         \r
368                         if (length >= 16)\r
369                                 sub_type = stream.read_long();\r
370                 }\r
371 \r
372                 layer_type_ = int_to_layer_type(type, sub_type);\r
373         }\r
374 \r
375         void read_metadata(bigendian_file_input_stream& stream, const psd_document& doc)\r
376         {\r
377                 int count = stream.read_long();\r
378                 for(int index = 0; index < count; ++index)\r
379                         read_chunk(stream, doc, true);\r
380         }\r
381 \r
382         void read_timeline_data(bigendian_file_input_stream& stream)\r
383         {\r
384                 if(stream.read_long() != 16)    //"descriptor version" should be 16\r
385                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16"));\r
386 \r
387                 descriptor timeline_descriptor(L"timeline");\r
388                 timeline_descriptor.populate(stream);\r
389                 timeline_info_.swap(timeline_descriptor.items());\r
390         }\r
391 \r
392         void read_text_data(bigendian_file_input_stream& stream)\r
393         {\r
394                 std::wstring text;      //the text in the layer\r
395 \r
396                 stream.read_short();    //should be 1\r
397         \r
398                 //transformation info\r
399                 auto xx = stream.read_double();\r
400                 auto xy = stream.read_double();\r
401                 auto yx = stream.read_double();\r
402                 auto yy = stream.read_double();\r
403                 auto tx = stream.read_double(); // tx\r
404                 auto ty = stream.read_double(); // ty\r
405 \r
406                 text_pos_.x = tx;\r
407                 text_pos_.y = ty;\r
408 \r
409                 if(stream.read_short() != 50)   //"text version" should be 50\r
410                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid text version"));\r
411 \r
412                 if(stream.read_long() != 16)    //"descriptor version" should be 16\r
413                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text-data"));\r
414 \r
415                 descriptor text_descriptor(L"text");\r
416                 text_descriptor.populate(stream);\r
417                 auto text_info = text_descriptor.items().get_optional<std::wstring>(L"EngineData");\r
418                 \r
419                 if (text_info)\r
420                 {\r
421                         std::string str(text_info->begin(), text_info->end());\r
422                         read_pdf(text_layer_info_, str);\r
423                         log::print_child(boost::log::trivial::trace, L"", L"text_layer_info", text_layer_info_);\r
424                 }\r
425 \r
426                 if(stream.read_short() != 1)    //"warp version" should be 1\r
427                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid warp version"));\r
428 \r
429                 if(stream.read_long() != 16)    //"descriptor version" should be 16\r
430                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text warp-data"));\r
431 \r
432                 descriptor warp_descriptor(L"warp");\r
433                 warp_descriptor.populate(stream);\r
434                 stream.read_double(); // w_left\r
435                 stream.read_double();  // w_top\r
436                 stream.read_double();  // w_right\r
437                 stream.read_double();  // w_bottom\r
438 \r
439 \r
440                 //extract scale, angle and shear factor from transformation matrix \r
441                 const double PI = 3.141592653589793;\r
442                 auto angle = atan2(xy, xx);\r
443 \r
444                 auto c = cos(angle);\r
445                 auto s = sin(angle);\r
446                 auto scale_x = (abs(c) > 0.1) ? xx / c : xy / s;\r
447 \r
448                 if (xx / scale_x < 0) { //fel kvadrant\r
449                         angle += PI;\r
450                         c = cos(angle);\r
451                         s = sin(angle);\r
452                         scale_x = (abs(c) > 0.1) ? xx / c : xy / s;\r
453                 }\r
454 \r
455                 auto shear_factor = (yx*c + yy*s) / (yy*c - yx * s);\r
456                 auto scale_y = 1.0;\r
457                 if (abs(shear_factor) < 0.0001 || isnan(shear_factor)) {\r
458                         shear_factor = 0;\r
459                         scale_y = (abs(c) > 0.1) ? yy / c : yx / -s;\r
460                 }\r
461                 else {\r
462                         scale_y = yx / (c*shear_factor - s);\r
463                 }\r
464 \r
465                 scale_.x = scale_x;\r
466                 scale_.y = scale_y;\r
467                 angle_ = angle * 180 / PI;\r
468                 shear_ = shear_factor;\r
469 \r
470                 \r
471 \r
472 \r
473         }\r
474 \r
475         //TODO: implement\r
476         void read_blending_ranges(bigendian_file_input_stream& stream)\r
477         {\r
478                 auto length = stream.read_long();\r
479                 stream.discard_bytes(length);\r
480         }\r
481 \r
482         bool has_channel(channel_type type)\r
483         {\r
484                 return std::find_if(channels_.begin(), channels_.end(), [=](const channel& c) { return c.id == static_cast<int>(type); }) != channels_.end();\r
485         }\r
486 \r
487         void read_channel_data(bigendian_file_input_stream& stream)\r
488         {\r
489                 image8bit_ptr bitmap;\r
490 \r
491                 bool has_transparency = has_channel(channel_type::transparency);\r
492         \r
493                 if(!bitmap_rect_.empty())\r
494                 {\r
495                         bitmap = std::make_shared<image8bit>(bitmap_rect_.size.width, bitmap_rect_.size.height, 4);\r
496                         if(!has_transparency)\r
497                                 std::memset(bitmap->data(), 255, bitmap->width()*bitmap->height()*bitmap->channel_count());\r
498                 }\r
499 \r
500                 for(auto it = channels_.begin(); it != channels_.end(); ++it)\r
501                 {\r
502                         auto channel = (*it);\r
503                         image8bit_ptr target;\r
504                         int offset = 0;\r
505                         bool discard_channel = false;\r
506 \r
507                         //determine target bitmap and offset\r
508                         if(channel.id >= 3)\r
509                                 discard_channel = true; //discard channels that doesn't contribute to the final image\r
510                         else if(channel.id >= -1)       //BGRA-data\r
511                         {\r
512                                 target = bitmap;\r
513                                 offset = (channel.id >= 0) ? 2 - channel.id : 3;\r
514                         }\r
515                         else    //mask\r
516                         {\r
517                                 offset = 0;\r
518                                 if (channel.id == -2)\r
519                                 {\r
520                                         mask_.create_bitmap();\r
521                                         target = mask_.bitmap_;\r
522                                 }\r
523                                 else if (channel.id == -3)      //total_mask\r
524                                 {\r
525                                         mask_.total_mask_->create_bitmap();\r
526                                         target = mask_.total_mask_->bitmap_;\r
527                                         offset = 0;\r
528                                 }\r
529                         }\r
530 \r
531                         if(!target)\r
532                                 discard_channel = true;\r
533 \r
534                         auto end_of_data = stream.current_position() + channel.data_length;\r
535                         if(!discard_channel)\r
536                         {\r
537                                 auto encoding = stream.read_short();\r
538                                 if(target)\r
539                                 {\r
540                                         if(encoding == 0)\r
541                                                 read_raw_image_data(stream, channel.data_length-2, target, offset);\r
542                                         else if(encoding == 1)\r
543                                                 read_rle_image_data(stream, target, offset);\r
544                                         else\r
545                                                 CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Unhandled image data encoding: " + boost::lexical_cast<std::string>(encoding)));\r
546                                 }\r
547                         }\r
548                         stream.set_position(end_of_data);\r
549                 }\r
550 \r
551                 if(bitmap && has_transparency)\r
552                 {\r
553                         caspar::image::image_view<caspar::image::bgra_pixel> view(bitmap->data(), bitmap->width(), bitmap->height());\r
554                         caspar::image::premultiply(view);\r
555                 }\r
556 \r
557                 bitmap_ = bitmap;\r
558         }\r
559 \r
560         void read_raw_image_data(bigendian_file_input_stream& stream, int data_length, image8bit_ptr target, int offset)\r
561         {\r
562                 auto total_length = target->width() * target->height();\r
563                 if (total_length != data_length)\r
564                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("total_length != data_length"));\r
565 \r
566                 auto data = target->data();\r
567                 auto stride = target->channel_count();\r
568 \r
569                 if (stride == 1)\r
570                         stream.read(reinterpret_cast<char*>(data + offset), total_length);\r
571                 else\r
572                 {\r
573                         for(int index = 0; index < total_length; ++index)\r
574                                 data[index * stride + offset] = stream.read_byte();\r
575                 }\r
576         }\r
577 \r
578         void read_rle_image_data(bigendian_file_input_stream& stream, image8bit_ptr target, int offset)\r
579         {\r
580                 auto width = target->width();\r
581                 auto height = target->height();\r
582                 auto stride = target->channel_count();\r
583 \r
584                 std::vector<int> scanline_lengths;\r
585                 scanline_lengths.reserve(height);\r
586 \r
587                 for (int scanlineIndex = 0; scanlineIndex < height; ++scanlineIndex)\r
588                         scanline_lengths.push_back(static_cast<std::int16_t>(stream.read_short()));\r
589 \r
590                 auto target_data = target->data();\r
591 \r
592                 std::vector<std::uint8_t> line(width);\r
593 \r
594                 for(int scanlineIndex=0; scanlineIndex < height; ++scanlineIndex)\r
595                 {\r
596                         int colIndex = 0;\r
597 \r
598                         do\r
599                         {\r
600                                 int length = 0;\r
601 \r
602                                 //Get controlbyte\r
603                                 char controlByte = static_cast<char>(stream.read_byte());\r
604                                 if(controlByte >= 0)\r
605                                 {\r
606                                         //Read uncompressed string\r
607                                         length = controlByte+1;\r
608                                         for(int index=0; index < length; ++index)\r
609                                                 line[colIndex+index] = stream.read_byte();\r
610                                 }\r
611                                 else if(controlByte > -128)\r
612                                 {\r
613                                         //Repeat next byte\r
614                                         length = -controlByte+1;\r
615                                         auto value = stream.read_byte();\r
616                                         for(int index=0; index < length; ++index)\r
617                                                 line[colIndex+index] = value;\r
618                                 }\r
619 \r
620                                 colIndex += length;\r
621                         }\r
622                         while(colIndex < width);\r
623 \r
624                         //use line to populate target\r
625                         for(int index = 0; index < width; ++index)\r
626                         {\r
627                                 target_data[(scanlineIndex*width+index) * stride + offset] = line[index];\r
628                         }\r
629                 }\r
630         }\r
631 };\r
632 \r
633 layer::layer() : impl_(spl::make_shared<impl>()) {}\r
634 \r
635 void layer::populate(bigendian_file_input_stream& stream, const psd_document& doc) { impl_->populate(stream, doc); }\r
636 void layer::read_channel_data(bigendian_file_input_stream& stream) { impl_->read_channel_data(stream); }\r
637 \r
638 const std::wstring& layer::name() const { return impl_->name_; }\r
639 int layer::opacity() const { return impl_->opacity_; }\r
640 caspar::core::blend_mode layer::blend_mode() const { return impl_->blend_mode_; }\r
641 int layer::sheet_color() const { return impl_->sheet_color_; }\r
642 \r
643 bool layer::is_visible() { return (impl_->flags_ & 2) == 0; }   //the (PSD file-format) documentation is is saying the opposite but what the heck\r
644 bool layer::is_position_protected() { return (impl_->protection_flags_& 4) == 4; }\r
645 \r
646 const layer::mask_info& layer::mask() const { return impl_->mask_; }\r
647
648 const psd::point<double>& layer::text_pos() const { return impl_->text_pos_; }
649 const psd::point<double>& layer::scale() const { return impl_->scale_; }
650 const double layer::angle() const { return impl_->angle_; }
651 const double layer::shear() const { return impl_->shear_; }
652 \r
653 bool layer::is_text() const { return !impl_->text_layer_info_.empty(); }\r
654 const boost::property_tree::wptree& layer::text_data() const { return impl_->text_layer_info_; }\r
655 \r
656 bool layer::has_timeline() const { return !impl_->timeline_info_.empty(); }\r
657 const boost::property_tree::wptree& layer::timeline_data() const { return impl_->timeline_info_; }\r
658 \r
659 bool layer::is_solid() const { return impl_->solid_color_.alpha != 0; }\r
660 color<std::uint8_t> layer::solid_color() const { return impl_->solid_color_; }\r
661 \r
662 const point<int>& layer::location() const { return impl_->bitmap_rect_.location; }\r
663 const size<int>& layer::size() const { return impl_->bitmap_rect_.size; }\r
664 const image8bit_ptr& layer::bitmap() const { return impl_->bitmap_; }\r
665 \r
666 layer_type layer::group_mode() const { return impl_->layer_type_; }\r
667 int layer::link_group_id() const { return impl_->link_group_id_; }\r
668 void layer::set_link_group_id(int id) { impl_->link_group_id_ = id; }\r
669 \r
670 bool layer::is_explicit_dynamic() const { return (impl_->tags_ & layer_tag::explicit_dynamic) == layer_tag::explicit_dynamic; }\r
671 bool layer::is_static() const { return (impl_->tags_ & layer_tag::rasterized) == layer_tag::rasterized; }\r
672 bool layer::is_movable() const { return (impl_->tags_ & layer_tag::moveable) == layer_tag::moveable; }\r
673 bool layer::is_resizable() const { return (impl_->tags_ & layer_tag::resizable) == layer_tag::resizable; }\r
674 bool layer::is_placeholder() const { return (impl_->tags_ & layer_tag::placeholder) == layer_tag::placeholder; }\r
675 bool layer::is_cornerpin() const { return (impl_->tags_ & layer_tag::cornerpin) == layer_tag::cornerpin; }\r
676 \r
677 layer_tag layer::tags() const { return impl_->tags_; }\r
678 \r
679 }       //namespace psd\r
680 }       //namespace caspar\r