]> git.sesse.net Git - casparcg/blob - modules/psd/layer.cpp
initial fixes for new psd workflow
[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:        //discard total user mask data\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         typedef std::pair<int, int> path_point;\r
82         bool bFail = 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         std::vector<path_point> knots;\r
91 \r
92         const int SELECTOR_SIZE = 2;\r
93         const int PATH_POINT_SIZE = 4 + 4;\r
94         const int PATH_POINT_RECORD_SIZE = SELECTOR_SIZE + (3 * PATH_POINT_SIZE);\r
95 \r
96         for (int i = 1; i <= path_records; ++i)\r
97         {\r
98                 auto selector = stream.read_short();\r
99                 if (selector == 2)      //we only concern ourselves with closed paths \r
100                 {\r
101                         auto p_y = stream.read_long();\r
102                         auto p_x = stream.read_long();\r
103                         path_point cp_prev(p_x, p_y);\r
104 \r
105                         auto a_y = stream.read_long();\r
106                         auto a_x = stream.read_long();\r
107                         path_point anchor(a_x, a_y);\r
108 \r
109                         auto n_y = stream.read_long();\r
110                         auto n_x = stream.read_long();\r
111                         path_point cp_next(n_x, n_y);\r
112 \r
113                         if (anchor == cp_prev && anchor == cp_next)\r
114                                 knots.push_back(anchor);\r
115                         else\r
116                         {       //we can't handle smooth curves yet\r
117                                 bFail = true;\r
118                         }\r
119                 }\r
120 \r
121                 auto offset = PATH_POINT_RECORD_SIZE * i;\r
122                 stream.set_position(position + offset);\r
123         }\r
124 \r
125         if ((knots.size() != 4) || (!(knots[0].first == knots[3].first && knots[1].first == knots[2].first && knots[0].second == knots[1].second && knots[2].second == knots[3].second)))\r
126                 bFail = true;\r
127 \r
128         if (bFail) {\r
129                 rect_.clear();\r
130                 flags_ = static_cast<std::uint8_t>(flags::unsupported);\r
131         }\r
132         else \r
133         {\r
134                 //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
135                 float x_ratio = doc_width / 16777215.0f;\r
136                 float y_ratio = doc_height / 16777215.0f;\r
137                 rect_.location.x = static_cast<int>(knots[0].first * x_ratio + 0.5f);                                                           //add .5 to get propper rounding when converting to integer\r
138                 rect_.location.y = static_cast<int>(knots[0].second * y_ratio + 0.5f);                                                          //add .5 to get propper rounding when converting to integer\r
139                 rect_.size.width = static_cast<int>(knots[1].first * x_ratio + 0.5f) - rect_.location.x;                //add .5 to get propper rounding when converting to integer\r
140                 rect_.size.height = static_cast<int>(knots[2].second * y_ratio + 0.5f) - rect_.location.y;      //add .5 to get propper rounding when converting to integer\r
141         }\r
142 \r
143         return !bFail;\r
144 }\r
145 \r
146 struct layer::impl\r
147 {\r
148         friend class layer;\r
149 \r
150         impl() : blend_mode_(blend_mode::InvalidBlendMode), layer_type_(layer_type::content), link_group_id_(0), opacity_(255), sheet_color_(0), baseClipping_(false), flags_(0), protection_flags_(0), masks_count_(0), text_scale_(1.0f), tags_(layer_tag::none)\r
151         {}\r
152 \r
153 private:\r
154         std::vector<channel>                    channels_;\r
155         blend_mode                                              blend_mode_;\r
156         layer_type                                              layer_type_;\r
157         int                                                             link_group_id_;\r
158         int                                                             opacity_;\r
159         int                                                             sheet_color_;\r
160         bool                                                    baseClipping_;\r
161         std::uint8_t                                    flags_;\r
162         std::uint32_t                                   protection_flags_;\r
163         std::wstring                                    name_;\r
164         int                                                             masks_count_;\r
165         double                                                  text_scale_;\r
166 \r
167         layer::mask_info                                mask_;\r
168 \r
169         rect<int>                                               bitmap_rect_;\r
170         image8bit_ptr                                   bitmap_;\r
171 \r
172         boost::property_tree::wptree    text_layer_info_;\r
173         boost::property_tree::wptree    timeline_info_;\r
174 \r
175         color<std::uint8_t>                             solid_color_;\r
176 \r
177         layer_tag                                               tags_;\r
178 \r
179 public:\r
180         void populate(bigendian_file_input_stream& stream, const psd_document& doc)\r
181         {\r
182                 bitmap_rect_.location.y = stream.read_long();\r
183                 bitmap_rect_.location.x = stream.read_long();\r
184                 bitmap_rect_.size.height = stream.read_long() - bitmap_rect_.location.y;\r
185                 bitmap_rect_.size.width = stream.read_long() - bitmap_rect_.location.x;\r
186 \r
187                 //Get info about the channels in the layer\r
188                 auto channelCount = stream.read_short();\r
189                 for(int channelIndex = 0; channelIndex < channelCount; ++channelIndex)\r
190                 {\r
191                         auto id = static_cast<std::int16_t>(stream.read_short());\r
192                         channel c(id, stream.read_long());\r
193 \r
194                         if(c.id < -1)\r
195                                 masks_count_++;\r
196 \r
197                         channels_.push_back(c);\r
198                 }\r
199 \r
200                 auto blendModeSignature = stream.read_long();\r
201                 if(blendModeSignature != '8BIM')\r
202                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("blendModeSignature != '8BIM'"));\r
203 \r
204                 blend_mode_ = int_to_blend_mode(stream.read_long());\r
205                 opacity_ = stream.read_byte();\r
206                 baseClipping_ = stream.read_byte() == 1 ? false : true;\r
207                 flags_ = stream.read_byte();\r
208 \r
209                 stream.discard_bytes(1);        //padding\r
210 \r
211                 auto extras_size = stream.read_long();\r
212                 auto position = stream.current_position();\r
213                 mask_.read_mask_data(stream);\r
214                 read_blending_ranges(stream);\r
215 \r
216                 stream.read_pascal_string(4);   //throw this away. We'll read the unicode version of the name later\r
217 \r
218                 //Aditional Layer Information\r
219                 auto end_of_layer_info = position + extras_size;\r
220                 try\r
221                 {\r
222                         while(stream.current_position() < end_of_layer_info)\r
223                         {\r
224                                 read_chunk(stream, doc);\r
225                         }\r
226                 }\r
227                 catch(psd_file_format_exception&)\r
228                 {\r
229                         stream.set_position(end_of_layer_info);\r
230                 }\r
231         }\r
232 \r
233         void read_chunk(bigendian_file_input_stream& stream, const psd_document& doc, bool isMetadata = false)\r
234         {\r
235                 auto signature = stream.read_long();\r
236                 if(signature != '8BIM' && signature != '8B64')\r
237                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM' && signature != '8B64'"));\r
238 \r
239                 auto key = stream.read_long();\r
240 \r
241                 if(isMetadata) stream.read_long();\r
242 \r
243                 auto length = stream.read_long();\r
244                 auto end_of_chunk = stream.current_position() + length;\r
245 \r
246                 try\r
247                 {\r
248                         switch(key)\r
249                         {\r
250                         case 'SoCo':\r
251                                 read_solid_color(stream);\r
252                                 break;\r
253 \r
254                         case 'lsct':    //group settings (folders)\r
255                                 read_group_settings(stream, length);\r
256 \r
257                         case 'lspf':    //protection settings\r
258                                 protection_flags_ = stream.read_long();\r
259                                 break;\r
260 \r
261                         case 'Txt2':    //text engine data\r
262                                 break;\r
263 \r
264                         case 'luni':\r
265                                 set_name_and_tags(stream.read_unicode_string());\r
266                                 break;\r
267 \r
268                         case 'TySh':    //type tool object settings\r
269                                 read_text_data(stream);\r
270                                 break;\r
271                                 \r
272                         case 'shmd':    //metadata\r
273                                 read_metadata(stream, doc);\r
274                                 break;\r
275 \r
276                         case 'lclr':\r
277                                 sheet_color_ = static_cast<std::int16_t>(stream.read_short());\r
278                                 break;\r
279                                 \r
280                         case 'lyvr':    //layer version\r
281                                 break;\r
282 \r
283                         case 'lnkD':    //linked layer\r
284                         case 'lnk2':    //linked layer\r
285                         case 'lnk3':    //linked layer\r
286                                 break;\r
287 \r
288                         case 'vmsk':\r
289                                 mask_.read_vector_mask_data(length, stream, doc.width(), doc.height());\r
290                                 break;\r
291                                 \r
292                         case 'tmln':\r
293                                 read_timeline_data(stream);\r
294                                 break;\r
295 \r
296                         default:\r
297                                 break;\r
298                         }\r
299                 }\r
300                 catch(psd_file_format_exception& ex)\r
301                 {\r
302                         //ignore failed chunks silently\r
303                         CASPAR_LOG(warning) << ex.what();\r
304                 }\r
305 \r
306                 stream.set_position(end_of_chunk);\r
307         }\r
308           \r
309         void set_name_and_tags(const std::wstring& name) {\r
310                 auto start_bracket = name.find_first_of(L'[');\r
311                 auto end_bracket = name.find_first_of(L']');\r
312                 if (start_bracket == std::wstring::npos && end_bracket == std::wstring::npos) {\r
313                         //no flags\r
314                         name_ = name;\r
315                 }\r
316                 else if (start_bracket != std::wstring::npos && end_bracket > start_bracket) {\r
317                         //we have tags\r
318                         tags_ = string_to_layer_tags(name.substr(start_bracket+1, end_bracket-start_bracket-1));\r
319                         name_ = name.substr(end_bracket+1);\r
320                 }\r
321                 else {\r
322                         //missmatch\r
323                         name_ = name;\r
324                         CASPAR_LOG(warning) << "Mismatching tag-brackets in layer name";\r
325                 }\r
326 \r
327                 boost::trim(name_);\r
328         }\r
329 \r
330         void read_solid_color(bigendian_file_input_stream& stream)\r
331         {\r
332                 if(stream.read_long() != 16)    //"descriptor version" should be 16\r
333                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16"));\r
334 \r
335                 descriptor solid_descriptor(L"solid_color");\r
336                 solid_descriptor.populate(stream);\r
337                 solid_color_.red = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Rd  ", 0.0) + 0.5);\r
338                 solid_color_.green = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Grn ", 0.0) + 0.5);\r
339                 solid_color_.blue = static_cast<std::uint8_t>(solid_descriptor.items().get(L"Clr .Bl  ", 0.0) + 0.5);\r
340                 solid_color_.alpha = 255;\r
341         }\r
342 \r
343         void read_group_settings(bigendian_file_input_stream& stream, unsigned int length) \r
344         {\r
345                 auto type = stream.read_long();\r
346                 unsigned int sub_type = 0;\r
347 \r
348                 if (length >= 12)\r
349                 {\r
350                         auto signature = stream.read_long();\r
351                         if (signature != '8BIM')\r
352                                 CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("signature != '8BIM'"));\r
353 \r
354                         blend_mode_ = int_to_blend_mode(stream.read_long());\r
355                         \r
356                         if (length >= 16)\r
357                                 sub_type = stream.read_long();\r
358                 }\r
359 \r
360                 layer_type_ = int_to_layer_type(type, sub_type);\r
361         }\r
362 \r
363         void read_metadata(bigendian_file_input_stream& stream, const psd_document& doc)\r
364         {\r
365                 int count = stream.read_long();\r
366                 for(int index = 0; index < count; ++index)\r
367                         read_chunk(stream, doc, true);\r
368         }\r
369 \r
370         void read_timeline_data(bigendian_file_input_stream& stream)\r
371         {\r
372                 if(stream.read_long() != 16)    //"descriptor version" should be 16\r
373                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("descriptor version should be 16"));\r
374 \r
375                 descriptor timeline_descriptor(L"timeline");\r
376                 timeline_descriptor.populate(stream);\r
377                 timeline_info_.swap(timeline_descriptor.items());\r
378         }\r
379 \r
380         void read_text_data(bigendian_file_input_stream& stream)\r
381         {\r
382                 std::wstring text;      //the text in the layer\r
383 \r
384                 stream.read_short();    //should be 1\r
385         \r
386                 //transformation info\r
387                 auto xx = stream.read_double();\r
388                 auto xy = stream.read_double();\r
389                 auto yx = stream.read_double();\r
390                 auto yy = stream.read_double();\r
391                 stream.read_double(); // tx\r
392                 stream.read_double(); // ty\r
393                 if(xx != yy || (xy != 0 && yx != 0))\r
394                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Rotation and non-uniform scaling of dynamic textfields is not supported yet"));\r
395 \r
396                 text_scale_ = xx;\r
397 \r
398                 if(stream.read_short() != 50)   //"text version" should be 50\r
399                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid text version"));\r
400 \r
401                 if(stream.read_long() != 16)    //"descriptor version" should be 16\r
402                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text-data"));\r
403 \r
404                 descriptor text_descriptor(L"text");\r
405                 text_descriptor.populate(stream);\r
406                 auto text_info = text_descriptor.items().get_optional<std::wstring>(L"EngineData");\r
407                 \r
408                 if (text_info)\r
409                 {\r
410                         std::string str(text_info->begin(), text_info->end());\r
411                         read_pdf(text_layer_info_, str);\r
412                         log::print_child(boost::log::trivial::trace, L"", L"text_layer_info", text_layer_info_);\r
413                 }\r
414 \r
415                 if(stream.read_short() != 1)    //"warp version" should be 1\r
416                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("invalid warp version"));\r
417 \r
418                 if(stream.read_long() != 16)    //"descriptor version" should be 16\r
419                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Invalid descriptor version while reading text warp-data"));\r
420 \r
421                 descriptor warp_descriptor(L"warp");\r
422                 warp_descriptor.populate(stream);\r
423                 stream.read_double(); // w_top\r
424                 stream.read_double();  // w_left\r
425                 stream.read_double();  // w_right\r
426                 stream.read_double();  // w_bottom\r
427         }\r
428 \r
429         //TODO: implement\r
430         void read_blending_ranges(bigendian_file_input_stream& stream)\r
431         {\r
432                 auto length = stream.read_long();\r
433                 stream.discard_bytes(length);\r
434         }\r
435 \r
436         bool has_channel(channel_type type)\r
437         {\r
438                 return std::find_if(channels_.begin(), channels_.end(), [=](const channel& c) { return c.id == static_cast<int>(type); }) != channels_.end();\r
439         }\r
440 \r
441         void read_channel_data(bigendian_file_input_stream& stream)\r
442         {\r
443                 image8bit_ptr bitmap;\r
444 \r
445                 bool has_transparency = has_channel(channel_type::transparency);\r
446         \r
447                 if(!bitmap_rect_.empty())\r
448                 {\r
449                         bitmap = std::make_shared<image8bit>(bitmap_rect_.size.width, bitmap_rect_.size.height, 4);\r
450                         if(!has_transparency)\r
451                                 std::memset(bitmap->data(), 255, bitmap->width()*bitmap->height()*bitmap->channel_count());\r
452                 }\r
453 \r
454                 for(auto it = channels_.begin(); it != channels_.end(); ++it)\r
455                 {\r
456                         auto channel = (*it);\r
457                         image8bit_ptr target;\r
458                         int offset = 0;\r
459                         bool discard_channel = false;\r
460 \r
461                         //determine target bitmap and offset\r
462                         if(channel.id >= 3)\r
463                                 discard_channel = true; //discard channels that doesn't contribute to the final image\r
464                         else if(channel.id >= -1)       //BGRA-data\r
465                         {\r
466                                 target = bitmap;\r
467                                 offset = (channel.id >= 0) ? 2 - channel.id : 3;\r
468                         }\r
469                         else    //mask\r
470                         {\r
471                                 offset = 0;\r
472                                 if (channel.id == -2)\r
473                                 {\r
474                                         mask_.create_bitmap();\r
475                                         target = mask_.bitmap_;\r
476                                 }\r
477                                 else if (channel.id == -3)      //total_mask\r
478                                 {\r
479                                         mask_.total_mask_->create_bitmap();\r
480                                         target = mask_.total_mask_->bitmap_;\r
481                                         offset = 0;\r
482                                 }\r
483                         }\r
484 \r
485                         if(!target)\r
486                                 discard_channel = true;\r
487 \r
488                         auto end_of_data = stream.current_position() + channel.data_length;\r
489                         if(!discard_channel)\r
490                         {\r
491                                 auto encoding = stream.read_short();\r
492                                 if(target)\r
493                                 {\r
494                                         if(encoding == 0)\r
495                                                 read_raw_image_data(stream, channel.data_length-2, target, offset);\r
496                                         else if(encoding == 1)\r
497                                                 read_rle_image_data(stream, target, offset);\r
498                                         else\r
499                                                 CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("Unhandled image data encoding: " + boost::lexical_cast<std::string>(encoding)));\r
500                                 }\r
501                         }\r
502                         stream.set_position(end_of_data);\r
503                 }\r
504 \r
505                 if(bitmap && has_transparency)\r
506                 {\r
507                         caspar::image::image_view<caspar::image::bgra_pixel> view(bitmap->data(), bitmap->width(), bitmap->height());\r
508                         caspar::image::premultiply(view);\r
509                 }\r
510 \r
511                 bitmap_ = bitmap;\r
512         }\r
513 \r
514         void read_raw_image_data(bigendian_file_input_stream& stream, int data_length, image8bit_ptr target, int offset)\r
515         {\r
516                 auto total_length = target->width() * target->height();\r
517                 if (total_length != data_length)\r
518                         CASPAR_THROW_EXCEPTION(psd_file_format_exception() << msg_info("total_length != data_length"));\r
519 \r
520                 auto data = target->data();\r
521                 auto stride = target->channel_count();\r
522 \r
523                 if (stride == 1)\r
524                         stream.read(reinterpret_cast<char*>(data + offset), total_length);\r
525                 else\r
526                 {\r
527                         for(int index = 0; index < total_length; ++index)\r
528                                 data[index * stride + offset] = stream.read_byte();\r
529                 }\r
530         }\r
531 \r
532         void read_rle_image_data(bigendian_file_input_stream& stream, image8bit_ptr target, int offset)\r
533         {\r
534                 auto width = target->width();\r
535                 auto height = target->height();\r
536                 auto stride = target->channel_count();\r
537 \r
538                 std::vector<int> scanline_lengths;\r
539                 scanline_lengths.reserve(height);\r
540 \r
541                 for (int scanlineIndex = 0; scanlineIndex < height; ++scanlineIndex)\r
542                         scanline_lengths.push_back(static_cast<std::int16_t>(stream.read_short()));\r
543 \r
544                 auto target_data = target->data();\r
545 \r
546                 std::vector<std::uint8_t> line(width);\r
547 \r
548                 for(int scanlineIndex=0; scanlineIndex < height; ++scanlineIndex)\r
549                 {\r
550                         int colIndex = 0;\r
551 \r
552                         do\r
553                         {\r
554                                 int length = 0;\r
555 \r
556                                 //Get controlbyte\r
557                                 char controlByte = static_cast<char>(stream.read_byte());\r
558                                 if(controlByte >= 0)\r
559                                 {\r
560                                         //Read uncompressed string\r
561                                         length = controlByte+1;\r
562                                         for(int index=0; index < length; ++index)\r
563                                                 line[colIndex+index] = stream.read_byte();\r
564                                 }\r
565                                 else if(controlByte > -128)\r
566                                 {\r
567                                         //Repeat next byte\r
568                                         length = -controlByte+1;\r
569                                         auto value = stream.read_byte();\r
570                                         for(int index=0; index < length; ++index)\r
571                                                 line[colIndex+index] = value;\r
572                                 }\r
573 \r
574                                 colIndex += length;\r
575                         }\r
576                         while(colIndex < width);\r
577 \r
578                         //use line to populate target\r
579                         for(int index = 0; index < width; ++index)\r
580                         {\r
581                                 target_data[(scanlineIndex*width+index) * stride + offset] = line[index];\r
582                         }\r
583                 }\r
584         }\r
585 };\r
586 \r
587 layer::layer() : impl_(spl::make_shared<impl>()) {}\r
588 \r
589 void layer::populate(bigendian_file_input_stream& stream, const psd_document& doc) { impl_->populate(stream, doc); }\r
590 void layer::read_channel_data(bigendian_file_input_stream& stream) { impl_->read_channel_data(stream); }\r
591 \r
592 const std::wstring& layer::name() const { return impl_->name_; }\r
593 int layer::opacity() const { return impl_->opacity_; }\r
594 int layer::sheet_color() const { return impl_->sheet_color_; }\r
595 \r
596 bool layer::is_visible() { return (impl_->flags_ & 2) == 0; }   //the (PSD file-format) documentation is is saying the opposite but what the heck\r
597 bool layer::is_position_protected() { return (impl_->protection_flags_& 4) == 4; }\r
598 \r
599 double layer::text_scale() const { return impl_->text_scale_; }\r
600 bool layer::is_text() const { return !impl_->text_layer_info_.empty(); }\r
601 const boost::property_tree::wptree& layer::text_data() const { return impl_->text_layer_info_; }\r
602 \r
603 bool layer::has_timeline() const { return !impl_->timeline_info_.empty(); }\r
604 const boost::property_tree::wptree& layer::timeline_data() const { return impl_->timeline_info_; }\r
605 \r
606 bool layer::is_solid() const { return impl_->solid_color_.alpha != 0; }\r
607 color<std::uint8_t> layer::solid_color() const { return impl_->solid_color_; }\r
608 \r
609 const point<int>& layer::location() const { return impl_->bitmap_rect_.location; }\r
610 const size<int>& layer::size() const { return impl_->bitmap_rect_.size; }\r
611 const image8bit_ptr& layer::bitmap() const { return impl_->bitmap_; }\r
612 \r
613 layer_type layer::group_mode() const { return impl_->layer_type_; }\r
614 int layer::link_group_id() const { return impl_->link_group_id_; }\r
615 void layer::set_link_group_id(int id) { impl_->link_group_id_ = id; }\r
616 \r
617 bool layer::is_explicit_dynamic() const { return (impl_->tags_ & layer_tag::explicit_dynamic) == layer_tag::explicit_dynamic; }\r
618 bool layer::is_static() const { return (impl_->tags_ & layer_tag::rasterized) == layer_tag::rasterized; }\r
619 bool layer::is_movable() const { return (impl_->tags_ & layer_tag::moveable) == layer_tag::moveable; }\r
620 bool layer::is_resizable() const { return (impl_->tags_ & layer_tag::resizable) == layer_tag::resizable; }\r
621 bool layer::is_placeholder() const { return (impl_->tags_ & layer_tag::placeholder) == layer_tag::placeholder; }\r
622 layer_tag layer::tags() const { return impl_->tags_; }\r
623 \r
624 }       //namespace psd\r
625 }       //namespace caspar\r