]> git.sesse.net Git - casparcg/blob - modules/image/producer/image_scroll_producer.cpp
Merged image_scroll_producer changes to 2.1
[casparcg] / modules / image / producer / image_scroll_producer.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: Robert Nagy, ronag89@gmail.com\r
20 * Author: Helge Norberg, helge.norberg@svt.se\r
21 */\r
22 \r
23 #include "image_scroll_producer.h"\r
24 \r
25 #include "../util/image_loader.h"\r
26 #include "../util/image_view.h"\r
27 #include "../util/image_algorithms.h"\r
28 \r
29 #include <core/video_format.h>\r
30 \r
31 #include <core/frame/frame.h>\r
32 #include <core/frame/draw_frame.h>\r
33 #include <core/frame/frame_factory.h>\r
34 #include <core/frame/frame_transform.h>\r
35 #include <core/frame/pixel_format.h>\r
36 #include <core/monitor/monitor.h>\r
37 \r
38 #include <common/env.h>\r
39 #include <common/log.h>\r
40 #include <common/except.h>\r
41 #include <common/array.h>\r
42 #include <common/tweener.h>\r
43 \r
44 #include <boost/assign.hpp>\r
45 #include <boost/filesystem.hpp>\r
46 #include <boost/foreach.hpp>\r
47 #include <boost/lexical_cast.hpp>\r
48 #include <boost/property_tree/ptree.hpp>\r
49 #include <boost/scoped_array.hpp>\r
50 \r
51 #include <algorithm>\r
52 #include <array>\r
53 \r
54 using namespace boost::assign;\r
55 \r
56 namespace caspar { namespace image {\r
57                 \r
58 struct image_scroll_producer : public core::frame_producer_base\r
59 {       \r
60         monitor::basic_subject                  event_subject_;\r
61 \r
62         const std::wstring                              filename_;\r
63         std::vector<core::draw_frame>   frames_;\r
64         core::video_format_desc                 format_desc_;\r
65         int                                                             width_;\r
66         int                                                             height_;\r
67 \r
68         double                                                  delta_;\r
69         double                                                  speed_;\r
70 \r
71         int                                                             start_offset_x_;\r
72         int                                                             start_offset_y_;\r
73         bool                                                    progressive_;\r
74         \r
75         explicit image_scroll_producer(\r
76                 const spl::shared_ptr<core::frame_factory>& frame_factory, \r
77                 const core::video_format_desc& format_desc, \r
78                 const std::wstring& filename, \r
79                 double speed,\r
80                 double duration,\r
81                 int motion_blur_px = 0,\r
82                 bool premultiply_with_alpha = false,\r
83                 bool progressive = false)\r
84                 : filename_(filename)\r
85                 , delta_(0)\r
86                 , format_desc_(format_desc)\r
87                 , speed_(speed)\r
88                 , start_offset_x_(0)\r
89                 , start_offset_y_(0)\r
90                 , progressive_(progressive)\r
91         {\r
92                 auto bitmap = load_image(filename_);\r
93                 FreeImage_FlipVertical(bitmap.get());\r
94 \r
95                 width_  = FreeImage_GetWidth(bitmap.get());\r
96                 height_ = FreeImage_GetHeight(bitmap.get());\r
97 \r
98                 bool vertical = width_ == format_desc_.width;\r
99                 bool horizontal = height_ == format_desc_.height;\r
100 \r
101                 if (!vertical && !horizontal)\r
102                         BOOST_THROW_EXCEPTION(\r
103                                 caspar::invalid_argument() << msg_info("Neither width nor height matched the video resolution"));\r
104 \r
105                 if (vertical)\r
106                 {\r
107                         if (duration != 0.0)\r
108                         {\r
109                                 double total_num_pixels = format_desc_.height * 2 + height_;\r
110 \r
111                                 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));\r
112 \r
113                                 if (std::abs(speed_) > 1.0)\r
114                                         speed_ = std::ceil(speed_);\r
115                         }\r
116 \r
117                         if (speed_ < 0.0)\r
118                         {\r
119                                 start_offset_y_ = height_ + format_desc_.height;\r
120                         }\r
121                 }\r
122                 else\r
123                 {\r
124                         if (duration != 0.0)\r
125                         {\r
126                                 double total_num_pixels = format_desc_.width * 2 + width_;\r
127 \r
128                                 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));\r
129 \r
130                                 if (std::abs(speed_) > 1.0)\r
131                                         speed_ = std::ceil(speed_);\r
132                         }\r
133 \r
134                         if (speed_ > 0.0)\r
135                                 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);\r
136                         else\r
137                                 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;\r
138                 }\r
139 \r
140                 auto bytes = FreeImage_GetBits(bitmap.get());\r
141                 auto count = width_*height_*4;\r
142                 image_view<bgra_pixel> original_view(bytes, width_, height_);\r
143 \r
144                 if (premultiply_with_alpha)\r
145                         premultiply(original_view);\r
146 \r
147                 boost::scoped_array<uint8_t> blurred_copy;\r
148 \r
149                 if (motion_blur_px > 0)\r
150                 {\r
151                         double angle = 3.14159265 / 2; // Up\r
152 \r
153                         if (horizontal && speed_ < 0)\r
154                                 angle *= 2; // Left\r
155                         else if (vertical && speed > 0)\r
156                                 angle *= 3; // Down\r
157                         else if (horizontal && speed  > 0)\r
158                                 angle = 0.0; // Right\r
159 \r
160                         blurred_copy.reset(new uint8_t[count]);\r
161                         image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);\r
162                         core::tweener blur_tweener(L"easeInQuad");\r
163                         blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);\r
164                         bytes = blurred_copy.get();\r
165                         bitmap.reset();\r
166                 }\r
167 \r
168                 if (vertical)\r
169                 {\r
170                         int n = 1;\r
171 \r
172                         while(count > 0)\r
173                         {\r
174                                 core::pixel_format_desc desc = core::pixel_format::bgra;\r
175                                 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));\r
176                                 auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);\r
177 \r
178                                 if(count >= frame.image_data(0).size())\r
179                                 {       \r
180                                         std::copy_n(bytes + count - frame.image_data(0).size(), frame.image_data(0).size(), frame.image_data(0).begin());\r
181                                         count -= static_cast<int>(frame.image_data(0).size());\r
182                                 }\r
183                                 else\r
184                                 {\r
185                                         memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());     \r
186                                         std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);\r
187                                         count = 0;\r
188                                 }\r
189 \r
190                                 core::draw_frame draw_frame(std::move(frame));\r
191 \r
192                                 // Set the relative position to the other image fragments\r
193                                 draw_frame.transform().image_transform.fill_translation[1] = - n++;\r
194 \r
195                                 frames_.push_back(draw_frame);\r
196                         }\r
197                 }\r
198                 else if (horizontal)\r
199                 {\r
200                         int i = 0;\r
201                         while(count > 0)\r
202                         {\r
203                                 core::pixel_format_desc desc = core::pixel_format::bgra;\r
204                                 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));\r
205                                 auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);\r
206                                 if(count >= frame.image_data(0).size())\r
207                                 {       \r
208                                         for(int y = 0; y < height_; ++y)\r
209                                                 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, format_desc_.width*4, frame.image_data(0).begin() + y * format_desc_.width*4);\r
210                                         \r
211                                         ++i;\r
212                                         count -= static_cast<int>(frame.image_data(0).size());\r
213                                 }\r
214                                 else\r
215                                 {\r
216                                         memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());     \r
217                                         auto width2 = width_ % format_desc_.width;\r
218                                         for(int y = 0; y < height_; ++y)\r
219                                                 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, width2*4, frame.image_data(0).begin() + y * format_desc_.width*4);\r
220 \r
221                                         count = 0;\r
222                                 }\r
223                         \r
224                                 frames_.push_back(core::draw_frame(std::move(frame)));\r
225                         }\r
226 \r
227                         std::reverse(frames_.begin(), frames_.end());\r
228 \r
229                         // Set the relative positions of the image fragments.\r
230                         for (size_t n = 0; n < frames_.size(); ++n)\r
231                         {\r
232                                 double translation = - (static_cast<double>(n) + 1.0);\r
233                                 frames_[n].transform().image_transform.fill_translation[0] = translation;\r
234                         }\r
235                 }\r
236 \r
237                 CASPAR_LOG(info) << print() << L" Initialized";\r
238         }\r
239 \r
240         std::vector<core::draw_frame> get_visible()\r
241         {\r
242                 std::vector<core::draw_frame> result;\r
243                 result.reserve(frames_.size());\r
244 \r
245                 BOOST_FOREACH(auto& frame, frames_)\r
246                 {\r
247                         auto& fill_translation = frame.transform().image_transform.fill_translation;\r
248 \r
249                         if (width_ == format_desc_.width)\r
250                         {\r
251                                 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);\r
252                                 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;\r
253 \r
254                                 if (vertical_offset < -1.0 || vertical_offset > 1.0)\r
255                                 {\r
256                                         continue;\r
257                                 }\r
258                         }\r
259                         else\r
260                         {\r
261                                 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);\r
262                                 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;\r
263 \r
264                                 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)\r
265                                 {\r
266                                         continue;\r
267                                 }\r
268                         }\r
269 \r
270                         result.push_back(frame);\r
271                 }\r
272 \r
273                 return std::move(result);\r
274         }\r
275         \r
276         // frame_producer\r
277         core::draw_frame render_frame(bool allow_eof)\r
278         {\r
279                 if(frames_.empty())\r
280                         return core::draw_frame::empty();\r
281                 \r
282                 core::draw_frame result(get_visible());\r
283                 auto& fill_translation = result.transform().image_transform.fill_translation;\r
284 \r
285                 if (width_ == format_desc_.width)\r
286                 {\r
287                         if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)\r
288                                 return core::draw_frame::empty();\r
289 \r
290                         fill_translation[1] = \r
291                                 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)\r
292                                 + delta_ / static_cast<double>(format_desc_.height);\r
293                 }\r
294                 else\r
295                 {\r
296                         if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)\r
297                                 return core::draw_frame::empty();\r
298 \r
299                         fill_translation[0] = \r
300                                 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)\r
301                                 + (delta_) / static_cast<double>(format_desc_.width);\r
302                 }\r
303 \r
304                 return result;\r
305         }\r
306 \r
307         core::draw_frame render_frame(bool allow_eof, bool advance_delta)\r
308         {\r
309                 auto result = render_frame(allow_eof);\r
310 \r
311                 if (advance_delta)\r
312                 {\r
313                         advance();\r
314                 }\r
315 \r
316                 return result;\r
317         }\r
318 \r
319         void advance()\r
320         {\r
321                 delta_ += speed_;\r
322         }\r
323 \r
324         core::draw_frame receive_impl() override\r
325         {\r
326                 core::draw_frame result;\r
327 \r
328                 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)\r
329                 {\r
330                         result = render_frame(true, true);\r
331                 }\r
332                 else\r
333                 {\r
334                         auto field1 = render_frame(true, true);\r
335                         auto field2 = render_frame(true, false);\r
336 \r
337                         if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())\r
338                         {\r
339                                 field2 = render_frame(false, true);\r
340                         }\r
341                         else\r
342                         {\r
343                                 advance();\r
344                         }\r
345 \r
346                         result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);\r
347                 }\r
348                 \r
349                 event_subject_ << monitor::event("file/path") % filename_\r
350                                            << monitor::event("delta") % delta_ \r
351                                            << monitor::event("speed") % speed_;\r
352 \r
353                 return result;\r
354         }\r
355                                 \r
356         std::wstring print() const override\r
357         {\r
358                 return L"image_scroll_producer[" + filename_ + L"]";\r
359         }\r
360 \r
361         std::wstring name() const override\r
362         {\r
363                 return L"image-scroll";\r
364         }\r
365 \r
366         boost::property_tree::wptree info() const override\r
367         {\r
368                 boost::property_tree::wptree info;\r
369                 info.add(L"type", L"image-scroll");\r
370                 info.add(L"filename", filename_);\r
371                 return info;\r
372         }\r
373 \r
374         uint32_t nb_frames() const override\r
375         {\r
376                 if(width_ == format_desc_.width)\r
377                 {\r
378                         auto length = (height_ + format_desc_.height * 2);\r
379                         return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));\r
380                 }\r
381                 else\r
382                 {\r
383                         auto length = (width_ + format_desc_.width * 2);\r
384                         return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));\r
385                 }\r
386         }\r
387 \r
388         void subscribe(const monitor::observable::observer_ptr& o) override                                                                                                                     \r
389         {\r
390                 return event_subject_.subscribe(o);\r
391         }\r
392 \r
393         void unsubscribe(const monitor::observable::observer_ptr& o) override           \r
394         {\r
395                 return event_subject_.unsubscribe(o);\r
396         }\r
397 };\r
398 \r
399 spl::shared_ptr<core::frame_producer> create_scroll_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)\r
400 {\r
401         static const std::vector<std::wstring> extensions = list_of(L".png")(L".tga")(L".bmp")(L".jpg")(L".jpeg")(L".gif")(L".tiff")(L".tif")(L".jp2")(L".jpx")(L".j2k")(L".j2c");\r
402         std::wstring filename = env::media_folder() + L"\\" + params[0];\r
403         \r
404         auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool\r
405                 {                                       \r
406                         return boost::filesystem::is_regular_file(boost::filesystem::path(filename).replace_extension(ex));\r
407                 });\r
408 \r
409         if(ext == extensions.end())\r
410                 return core::frame_producer::empty();\r
411         \r
412         double speed = 0.0;\r
413         double duration = 0.0;\r
414         auto speed_it = std::find(params.begin(), params.end(), L"SPEED");\r
415         if(speed_it != params.end())\r
416         {\r
417                 if(++speed_it != params.end())\r
418                         speed = boost::lexical_cast<double>(*speed_it);\r
419         }\r
420 \r
421         if (speed == 0)\r
422         {\r
423                 auto duration_it = std::find(params.begin(), params.end(), L"DURATION");\r
424 \r
425                 if (duration_it != params.end() && ++duration_it != params.end())\r
426                 {\r
427                         duration = boost::lexical_cast<double>(*duration_it);\r
428                 }\r
429         }\r
430 \r
431         if(speed == 0 && duration == 0)\r
432                 return core::frame_producer::empty();\r
433 \r
434         int motion_blur_px = 0;\r
435         auto blur_it = std::find(params.begin(), params.end(), L"BLUR");\r
436         if (blur_it != params.end() && ++blur_it != params.end())\r
437         {\r
438                 motion_blur_px = boost::lexical_cast<int>(*blur_it);\r
439         }\r
440 \r
441         bool premultiply_with_alpha = std::find(params.begin(), params.end(), L"PREMULTIPLY") != params.end();\r
442         bool progressive = std::find(params.begin(), params.end(), L"PROGRESSIVE") != params.end();\r
443 \r
444         return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(\r
445                 frame_factory, \r
446                 format_desc, \r
447                 filename + *ext, \r
448                 -speed, \r
449                 -duration, \r
450                 motion_blur_px, \r
451                 premultiply_with_alpha,\r
452                 progressive));\r
453 }\r
454 \r
455 }}