]> git.sesse.net Git - casparcg/blob - modules/image/producer/image_scroll_producer.cpp
Fix a few Clang warnings.
[casparcg] / modules / image / producer / image_scroll_producer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 * Author: Helge Norberg, helge.norberg@svt.se
21 */
22
23 #include "image_scroll_producer.h"
24
25 #include "../util/image_loader.h"
26 #include "../util/image_view.h"
27 #include "../util/image_algorithms.h"
28
29 #include <core/video_format.h>
30
31 #include <core/frame/frame.h>
32 #include <core/frame/draw_frame.h>
33 #include <core/frame/frame_factory.h>
34 #include <core/frame/frame_transform.h>
35 #include <core/frame/pixel_format.h>
36 #include <core/frame/audio_channel_layout.h>
37 #include <core/monitor/monitor.h>
38 #include <core/help/help_sink.h>
39 #include <core/help/help_repository.h>
40
41 #include <common/env.h>
42 #include <common/log.h>
43 #include <common/except.h>
44 #include <common/array.h>
45 #include <common/tweener.h>
46 #include <common/param.h>
47 #include <common/os/filesystem.h>
48 #include <common/future.h>
49
50 #include <boost/filesystem.hpp>
51 #include <boost/property_tree/ptree.hpp>
52 #include <boost/scoped_array.hpp>
53 #include <boost/date_time.hpp>
54 #include <boost/date_time/posix_time/ptime.hpp>
55
56 #include <algorithm>
57 #include <array>
58 #include <cstdint>
59
60 namespace caspar { namespace image {
61
62 // Like tweened_transform but for speed
63 class speed_tweener
64 {
65         double  source_         = 0.0;
66         double  dest_           = 0.0;
67         int             duration_       = 0;
68         int             time_           = 0;
69         tweener tweener_;
70 public:
71         speed_tweener() = default;
72         speed_tweener(
73                         double source,
74                         double dest,
75                         int duration,
76                         const tweener& tween)
77                 : source_(source)
78                 , dest_(dest)
79                 , duration_(duration)
80                 , time_(0)
81                 , tweener_(tween)
82         {
83         }
84
85         double dest() const
86         {
87                 return dest_;
88         }
89
90         double fetch() const
91         {
92                 if (time_ == duration_)
93                         return dest_;
94
95                 double delta = dest_ - source_;
96                 double result = tweener_(time_, source_, delta, duration_);
97
98                 return result;
99         }
100
101         double fetch_and_tick()
102         {
103                 time_ = std::min(time_ + 1, duration_);
104                 return fetch();
105         }
106 };
107
108 struct image_scroll_producer : public core::frame_producer_base
109 {
110         core::monitor::subject                                          monitor_subject_;
111
112         const std::wstring                                                      filename_;
113         std::vector<core::draw_frame>                           frames_;
114         core::video_format_desc                                         format_desc_;
115         int                                                                                     width_;
116         int                                                                                     height_;
117         core::constraints                                                       constraints_;
118
119         double                                                                          delta_                          = 0.0;
120         speed_tweener                                                           speed_;
121         boost::optional<boost::posix_time::ptime>       end_time_;
122
123         int                                                                                     start_offset_x_         = 0;
124         int                                                                                     start_offset_y_         = 0;
125         bool                                                                            progressive_;
126
127         explicit image_scroll_producer(
128                         const spl::shared_ptr<core::frame_factory>& frame_factory,
129                         const core::video_format_desc& format_desc,
130                         const std::wstring& filename,
131                         double s,
132                         double duration,
133                         boost::optional<boost::posix_time::ptime> end_time,
134                         int motion_blur_px = 0,
135                         bool premultiply_with_alpha = false,
136                         bool progressive = false)
137                 : filename_(filename)
138                 , format_desc_(format_desc)
139                 , end_time_(std::move(end_time))
140                 , progressive_(progressive)
141         {
142                 double speed = s;
143
144                 if (end_time_)
145                         speed = -1.0;
146
147                 auto bitmap = load_image(filename_);
148                 FreeImage_FlipVertical(bitmap.get());
149
150                 width_  = FreeImage_GetWidth(bitmap.get());
151                 height_ = FreeImage_GetHeight(bitmap.get());
152                 constraints_.width.set(width_);
153                 constraints_.height.set(height_);
154
155                 bool vertical = width_ == format_desc_.width;
156                 bool horizontal = height_ == format_desc_.height;
157
158                 if (!vertical && !horizontal)
159                         CASPAR_THROW_EXCEPTION(caspar::user_error()
160                                         << msg_info("Neither width nor height matched the video resolution"));
161
162                 if (duration != 0.0)
163                         speed = speed_from_duration(duration);
164
165                 if (vertical)
166                 {
167                         if (speed < 0.0)
168                         {
169                                 start_offset_y_ = height_ + format_desc_.height;
170                         }
171                 }
172                 else
173                 {
174                         if (speed > 0.0)
175                                 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
176                         else
177                                 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
178                 }
179
180                 speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
181
182                 auto bytes = FreeImage_GetBits(bitmap.get());
183                 auto count = width_*height_*4;
184                 image_view<bgra_pixel> original_view(bytes, width_, height_);
185
186                 if (premultiply_with_alpha)
187                         premultiply(original_view);
188
189                 boost::scoped_array<uint8_t> blurred_copy;
190
191                 if (motion_blur_px > 0)
192                 {
193                         double angle = 3.14159265 / 2; // Up
194
195                         if (horizontal && speed < 0)
196                                 angle *= 2; // Left
197                         else if (vertical && speed > 0)
198                                 angle *= 3; // Down
199                         else if (horizontal && speed  > 0)
200                                 angle = 0.0; // Right
201
202                         blurred_copy.reset(new uint8_t[count]);
203                         image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
204                         caspar::tweener blur_tweener(L"easeInQuad");
205                         blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
206                         bytes = blurred_copy.get();
207                         bitmap.reset();
208                 }
209
210                 if (vertical)
211                 {
212                         int n = 1;
213
214                         while(count > 0)
215                         {
216                                 core::pixel_format_desc desc = core::pixel_format::bgra;
217                                 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
218                                 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
219
220                                 if(count >= frame.image_data(0).size())
221                                 {
222                                         std::copy_n(bytes + count - frame.image_data(0).size(), frame.image_data(0).size(), frame.image_data(0).begin());
223                                         count -= static_cast<int>(frame.image_data(0).size());
224                                 }
225                                 else
226                                 {
227                                         memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
228                                         std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);
229                                         count = 0;
230                                 }
231
232                                 core::draw_frame draw_frame(std::move(frame));
233
234                                 // Set the relative position to the other image fragments
235                                 draw_frame.transform().image_transform.fill_translation[1] = - n++;
236
237                                 frames_.push_back(draw_frame);
238                         }
239                 }
240                 else if (horizontal)
241                 {
242                         int i = 0;
243                         while(count > 0)
244                         {
245                                 core::pixel_format_desc desc = core::pixel_format::bgra;
246                                 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
247                                 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
248                                 if(count >= frame.image_data(0).size())
249                                 {
250                                         for(int y = 0; y < height_; ++y)
251                                                 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);
252
253                                         ++i;
254                                         count -= static_cast<int>(frame.image_data(0).size());
255                                 }
256                                 else
257                                 {
258                                         memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
259                                         auto width2 = width_ % format_desc_.width;
260                                         for(int y = 0; y < height_; ++y)
261                                                 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, width2*4, frame.image_data(0).begin() + y * format_desc_.width*4);
262
263                                         count = 0;
264                                 }
265
266                                 frames_.push_back(core::draw_frame(std::move(frame)));
267                         }
268
269                         std::reverse(frames_.begin(), frames_.end());
270
271                         // Set the relative positions of the image fragments.
272                         for (size_t n = 0; n < frames_.size(); ++n)
273                         {
274                                 double translation = - (static_cast<double>(n) + 1.0);
275                                 frames_[n].transform().image_transform.fill_translation[0] = translation;
276                         }
277                 }
278
279                 CASPAR_LOG(info) << print() << L" Initialized";
280         }
281
282         double get_total_num_pixels() const
283         {
284                 bool vertical = width_ == format_desc_.width;
285
286                 if (vertical)
287                         return height_ + format_desc_.height;
288                 else
289                         return width_ + format_desc_.width;
290         }
291
292         double speed_from_duration(double duration_seconds) const
293         {
294                 return get_total_num_pixels() / (duration_seconds * format_desc_.fps * static_cast<double>(format_desc_.field_count));
295         }
296
297         std::future<std::wstring> call(const std::vector<std::wstring>& params) override
298         {
299                 auto cmd = params.at(0);
300
301                 if (boost::iequals(cmd, L"SPEED"))
302                 {
303                         if (params.size() == 1)
304                                 return make_ready_future(std::to_wstring(-speed_.fetch()));
305
306                         auto val = std::stod(params.at(1));
307                         int duration = params.size() > 2 ? std::stoi(params.at(2)) : 0;
308                         std::wstring tween = params.size() > 3 ? params.at(3) : L"linear";
309                         speed_ = speed_tweener(speed_.fetch(), -val, duration, tween);
310                 }
311
312                 return make_ready_future<std::wstring>(L"");
313         }
314
315         std::vector<core::draw_frame> get_visible()
316         {
317                 std::vector<core::draw_frame> result;
318                 result.reserve(frames_.size());
319
320                 for (auto& frame : frames_)
321                 {
322                         auto& fill_translation = frame.transform().image_transform.fill_translation;
323
324                         if (width_ == format_desc_.width)
325                         {
326                                 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);
327                                 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;
328
329                                 if (vertical_offset < -1.0 || vertical_offset > 1.0)
330                                 {
331                                         continue;
332                                 }
333                         }
334                         else
335                         {
336                                 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);
337                                 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;
338
339                                 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
340                                 {
341                                         continue;
342                                 }
343                         }
344
345                         result.push_back(frame);
346                 }
347
348                 return std::move(result);
349         }
350
351         // frame_producer
352         core::draw_frame render_frame(bool allow_eof)
353         {
354                 if(frames_.empty())
355                         return core::draw_frame::empty();
356
357                 core::draw_frame result(get_visible());
358                 auto& fill_translation = result.transform().image_transform.fill_translation;
359
360                 if (width_ == format_desc_.width)
361                 {
362                         if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
363                                 return core::draw_frame::empty();
364
365                         fill_translation[1] =
366                                 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)
367                                 + delta_ / static_cast<double>(format_desc_.height);
368                 }
369                 else
370                 {
371                         if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
372                                 return core::draw_frame::empty();
373
374                         fill_translation[0] =
375                                 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)
376                                 + (delta_) / static_cast<double>(format_desc_.width);
377                 }
378
379                 return result;
380         }
381
382         core::draw_frame render_frame(bool allow_eof, bool advance_delta)
383         {
384                 auto result = render_frame(allow_eof);
385
386                 if (advance_delta)
387                 {
388                         advance();
389                 }
390
391                 return result;
392         }
393
394         void advance()
395         {
396                 if (end_time_)
397                 {
398                         boost::posix_time::ptime now(boost::posix_time::second_clock::local_time());
399
400                         auto diff = *end_time_ - now;
401                         auto seconds = diff.total_seconds();
402
403                         set_speed(-speed_from_duration(seconds));
404                         end_time_ = boost::none;
405                 }
406                 else
407                         delta_ += speed_.fetch_and_tick();
408         }
409
410         void set_speed(double speed)
411         {
412                 speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
413         }
414
415         core::draw_frame receive_impl() override
416         {
417                 core::draw_frame result;
418
419                 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
420                 {
421                         result = render_frame(true, true);
422                 }
423                 else
424                 {
425                         auto field1 = render_frame(true, true);
426                         auto field2 = render_frame(true, false);
427
428                         if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())
429                         {
430                                 field2 = render_frame(false, true);
431                         }
432                         else
433                         {
434                                 advance();
435                         }
436
437                         result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);
438                 }
439
440                 monitor_subject_ << core::monitor::message("/file/path") % filename_
441                                                  << core::monitor::message("/delta") % delta_
442                                                  << core::monitor::message("/speed") % speed_.fetch();
443
444                 return result;
445         }
446
447         core::constraints& pixel_constraints() override
448         {
449                 return constraints_;
450         }
451
452         std::wstring print() const override
453         {
454                 return L"image_scroll_producer[" + filename_ + L"]";
455         }
456
457         std::wstring name() const override
458         {
459                 return L"image-scroll";
460         }
461
462         boost::property_tree::wptree info() const override
463         {
464                 boost::property_tree::wptree info;
465                 info.add(L"type", L"image-scroll");
466                 info.add(L"filename", filename_);
467                 info.add(L"speed", speed_.fetch());
468                 return info;
469         }
470
471         uint32_t nb_frames() const override
472         {
473                 if(width_ == format_desc_.width)
474                 {
475                         auto length = (height_ + format_desc_.height * 2);
476                         return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
477                 }
478                 else
479                 {
480                         auto length = (width_ + format_desc_.width * 2);
481                         return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
482                 }
483         }
484
485         core::monitor::subject& monitor_output() override
486         {
487                 return monitor_subject_;
488         }
489 };
490
491 void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
492 {
493         sink.short_description(L"Scrolls an image either horizontally or vertically.");
494         sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
495         sink.syntax(L"[image_file:string] DURATION [duration:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
496         sink.syntax(L"[image_file:string] END_TIME [end_time:string] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
497         sink.para()
498                 ->text(L"Scrolls an image either horizontally or vertically. ")
499                 ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
500                 ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
501                 ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
502         sink.definitions()
503                 ->item(L"image_file", L"The image without extension. The file has to have either the same width or the same height as the video format.")
504                 ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
505                 ->item(L"duration", L"A positive or negative float defining how many seconds the scroll should take.")
506                 ->item(L"end_time", L"An absolute date in the format yyyy-MM-dd HH:mm:ss for when the scroll should end, based on the system clock.")
507                 ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
508                 ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
509                 ->item(L"progressive", L"When an interlaced video format is used, by default the image is moved every field. This can be overridden by specifying this option, causing the image to only move on full frames.");
510         sink.para()->text(L"If ")->code(L"SPEED [speed]")->text(L" is ommitted, the ordinary ")->see(L"Image Producer")->text(L" will be used instead.");
511         sink.example(L">> PLAY 1-10 cred_1280 SPEED 8 BLUR 2", L"Given that cred_1280 is a as wide as the video mode, this will create a rolling end credits with a little bit of blur and a speed of 8 pixels per frame.");
512         sink.example(L">> PLAY 1-10 cred_1280 DURATION 60", L"Will adjust the speed to make the scroll take 60 seconds.");
513         sink.example(L">> PLAY 1-10 cred_1920 END_TIME \"2016-05-15 00:46:17\"", L"Will adjust the speed to make the scroll end at the specific date and time 2016-05-15 00:46:17.");
514 }
515
516 spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
517 {
518         std::wstring filename = env::media_folder() + params.at(0);
519
520         auto ext = std::find_if(supported_extensions().begin(), supported_extensions().end(), [&](const std::wstring& ex) -> bool
521         {
522                 auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
523
524                 return static_cast<bool>(file);
525         });
526
527         if(ext == supported_extensions().end())
528                 return core::frame_producer::empty();
529
530         double duration = 0.0;
531         double speed = get_param(L"SPEED", params, 0.0);
532         boost::optional<boost::posix_time::ptime> end_time;
533
534         if (speed == 0)
535                 duration = get_param(L"DURATION", params, 0.0);
536
537         if (duration == 0)
538         {
539                 auto end_time_str = get_param(L"END_TIME", params);
540
541                 if (!end_time_str.empty())
542                 {
543                         end_time = boost::posix_time::time_from_string(u8(end_time_str));
544                 }
545         }
546
547         if(speed == 0 && duration == 0 && !end_time)
548                 return core::frame_producer::empty();
549
550         int motion_blur_px = get_param(L"BLUR", params, 0);
551
552         bool premultiply_with_alpha = contains_param(L"PREMULTIPLY", params);
553         bool progressive = contains_param(L"PROGRESSIVE", params);
554
555         return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(
556                         dependencies.frame_factory,
557                         dependencies.format_desc,
558                         *caspar::find_case_insensitive(filename + *ext),
559                         -speed,
560                         -duration,
561                         end_time,
562                         motion_blur_px,
563                         premultiply_with_alpha,
564                         progressive));
565 }
566
567 }}