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