2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
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.
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.
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/>.
19 * Author: Robert Nagy, ronag89@gmail.com
20 * Author: Helge Norberg, helge.norberg@svt.se
23 #include "image_scroll_producer.h"
25 #include "../util/image_loader.h"
26 #include "../util/image_view.h"
27 #include "../util/image_algorithms.h"
29 #include <core/video_format.h>
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>
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>
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>
60 namespace caspar { namespace image {
62 // Like tweened_transform but for speed
71 speed_tweener() = default;
92 if (time_ == duration_)
95 double delta = dest_ - source_;
96 double result = tweener_(time_, source_, delta, duration_);
101 double fetch_and_tick()
103 time_ = std::min(time_ + 1, duration_);
108 struct image_scroll_producer : public core::frame_producer_base
110 core::monitor::subject monitor_subject_;
112 const std::wstring filename_;
113 std::vector<core::draw_frame> frames_;
114 core::video_format_desc format_desc_;
117 core::constraints constraints_;
120 speed_tweener speed_;
121 boost::optional<boost::posix_time::ptime> end_time_;
123 int start_offset_x_ = 0;
124 int start_offset_y_ = 0;
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,
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)
147 auto bitmap = load_image(filename_);
148 FreeImage_FlipVertical(bitmap.get());
150 width_ = FreeImage_GetWidth(bitmap.get());
151 height_ = FreeImage_GetHeight(bitmap.get());
152 constraints_.width.set(width_);
153 constraints_.height.set(height_);
155 bool vertical = width_ == format_desc_.width;
156 bool horizontal = height_ == format_desc_.height;
158 if (!vertical && !horizontal)
159 CASPAR_THROW_EXCEPTION(caspar::user_error()
160 << msg_info("Neither width nor height matched the video resolution"));
163 speed = speed_from_duration(duration);
169 start_offset_y_ = height_ + format_desc_.height;
175 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
177 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
180 speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
182 auto bytes = FreeImage_GetBits(bitmap.get());
183 auto count = width_*height_*4;
184 image_view<bgra_pixel> original_view(bytes, width_, height_);
186 if (premultiply_with_alpha)
187 premultiply(original_view);
189 boost::scoped_array<uint8_t> blurred_copy;
191 if (motion_blur_px > 0)
193 double angle = 3.14159265 / 2; // Up
195 if (horizontal && speed < 0)
197 else if (vertical && speed > 0)
199 else if (horizontal && speed > 0)
200 angle = 0.0; // Right
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();
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());
220 if(count >= frame.image_data(0).size())
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());
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);
232 core::draw_frame draw_frame(std::move(frame));
234 // Set the relative position to the other image fragments
235 draw_frame.transform().image_transform.fill_translation[1] = - n++;
237 frames_.push_back(draw_frame);
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())
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);
254 count -= static_cast<int>(frame.image_data(0).size());
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);
266 frames_.push_back(core::draw_frame(std::move(frame)));
269 std::reverse(frames_.begin(), frames_.end());
271 // Set the relative positions of the image fragments.
272 for (size_t n = 0; n < frames_.size(); ++n)
274 double translation = - (static_cast<double>(n) + 1.0);
275 frames_[n].transform().image_transform.fill_translation[0] = translation;
279 CASPAR_LOG(info) << print() << L" Initialized";
282 double get_total_num_pixels() const
284 bool vertical = width_ == format_desc_.width;
287 return height_ + format_desc_.height;
289 return width_ + format_desc_.width;
292 double speed_from_duration(double duration_seconds) const
294 return get_total_num_pixels() / (duration_seconds * format_desc_.fps * static_cast<double>(format_desc_.field_count));
297 std::future<std::wstring> call(const std::vector<std::wstring>& params) override
299 auto cmd = params.at(0);
301 if (boost::iequals(cmd, L"SPEED"))
303 if (params.size() == 1)
304 return make_ready_future(std::to_wstring(-speed_.fetch()));
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);
312 return make_ready_future<std::wstring>(L"");
315 std::vector<core::draw_frame> get_visible()
317 std::vector<core::draw_frame> result;
318 result.reserve(frames_.size());
320 for (auto& frame : frames_)
322 auto& fill_translation = frame.transform().image_transform.fill_translation;
324 if (width_ == format_desc_.width)
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;
329 if (vertical_offset < -1.0 || vertical_offset > 1.0)
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;
339 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
345 result.push_back(frame);
348 return std::move(result);
352 core::draw_frame render_frame(bool allow_eof)
355 return core::draw_frame::empty();
357 core::draw_frame result(get_visible());
358 auto& fill_translation = result.transform().image_transform.fill_translation;
360 if (width_ == format_desc_.width)
362 if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
363 return core::draw_frame::empty();
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);
371 if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
372 return core::draw_frame::empty();
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);
382 core::draw_frame render_frame(bool allow_eof, bool advance_delta)
384 auto result = render_frame(allow_eof);
398 boost::posix_time::ptime now(boost::posix_time::second_clock::local_time());
400 auto diff = *end_time_ - now;
401 auto seconds = diff.total_seconds();
403 set_speed(-speed_from_duration(seconds));
404 end_time_ = boost::none;
407 delta_ += speed_.fetch_and_tick();
410 void set_speed(double speed)
412 speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
415 core::draw_frame receive_impl() override
417 core::draw_frame result;
419 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
421 result = render_frame(true, true);
425 auto field1 = render_frame(true, true);
426 auto field2 = render_frame(true, false);
428 if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())
430 field2 = render_frame(false, true);
437 result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);
440 monitor_subject_ << core::monitor::message("/file/path") % filename_
441 << core::monitor::message("/delta") % delta_
442 << core::monitor::message("/speed") % speed_.fetch();
447 core::constraints& pixel_constraints() override
452 std::wstring print() const override
454 return L"image_scroll_producer[" + filename_ + L"]";
457 std::wstring name() const override
459 return L"image-scroll";
462 boost::property_tree::wptree info() const override
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());
471 uint32_t nb_frames() const override
473 if(width_ == format_desc_.width)
475 auto length = (height_ + format_desc_.height * 2);
476 return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
480 auto length = (width_ + format_desc_.width * 2);
481 return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
485 core::monitor::subject& monitor_output() override
487 return monitor_subject_;
491 void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
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]}");
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.");
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.");
516 spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
518 std::wstring filename = env::media_folder() + params.at(0);
520 auto ext = std::find_if(supported_extensions().begin(), supported_extensions().end(), [&](const std::wstring& ex) -> bool
522 auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
524 return static_cast<bool>(file);
527 if(ext == supported_extensions().end())
528 return core::frame_producer::empty();
530 double duration = 0.0;
531 double speed = get_param(L"SPEED", params, 0.0);
532 boost::optional<boost::posix_time::ptime> end_time;
535 duration = get_param(L"DURATION", params, 0.0);
539 auto end_time_str = get_param(L"END_TIME", params);
541 if (!end_time_str.empty())
543 end_time = boost::posix_time::time_from_string(u8(end_time_str));
547 if(speed == 0 && duration == 0 && !end_time)
548 return core::frame_producer::empty();
550 int motion_blur_px = get_param(L"BLUR", params, 0);
552 bool premultiply_with_alpha = contains_param(L"PREMULTIPLY", params);
553 bool progressive = contains_param(L"PROGRESSIVE", params);
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),
563 premultiply_with_alpha,