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/lexical_cast.hpp>
52 #include <boost/property_tree/ptree.hpp>
53 #include <boost/scoped_array.hpp>
54 #include <boost/date_time.hpp>
55 #include <boost/date_time/posix_time/ptime.hpp>
61 namespace caspar { namespace image {
63 // Like tweened_transform but for speed
72 speed_tweener() = default;
93 if (time_ == duration_)
96 double delta = dest_ - source_;
97 double result = tweener_(time_, source_, delta, duration_);
102 double fetch_and_tick()
104 time_ = std::min(time_ + 1, duration_);
109 struct image_scroll_producer : public core::frame_producer_base
111 core::monitor::subject monitor_subject_;
113 const std::wstring filename_;
114 std::vector<core::draw_frame> frames_;
115 core::video_format_desc format_desc_;
118 core::constraints constraints_;
121 speed_tweener speed_;
122 boost::optional<boost::posix_time::ptime> end_time_;
124 int start_offset_x_ = 0;
125 int start_offset_y_ = 0;
128 explicit image_scroll_producer(
129 const spl::shared_ptr<core::frame_factory>& frame_factory,
130 const core::video_format_desc& format_desc,
131 const std::wstring& filename,
134 boost::optional<boost::posix_time::ptime> end_time,
135 int motion_blur_px = 0,
136 bool premultiply_with_alpha = false,
137 bool progressive = false)
138 : filename_(filename)
139 , format_desc_(format_desc)
140 , end_time_(std::move(end_time))
141 , progressive_(progressive)
148 auto bitmap = load_image(filename_);
149 FreeImage_FlipVertical(bitmap.get());
151 width_ = FreeImage_GetWidth(bitmap.get());
152 height_ = FreeImage_GetHeight(bitmap.get());
153 constraints_.width.set(width_);
154 constraints_.height.set(height_);
156 bool vertical = width_ == format_desc_.width;
157 bool horizontal = height_ == format_desc_.height;
159 if (!vertical && !horizontal)
160 CASPAR_THROW_EXCEPTION(caspar::user_error()
161 << msg_info("Neither width nor height matched the video resolution"));
164 speed = speed_from_duration(duration);
170 start_offset_y_ = height_ + format_desc_.height;
176 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
178 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
181 speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
183 auto bytes = FreeImage_GetBits(bitmap.get());
184 auto count = width_*height_*4;
185 image_view<bgra_pixel> original_view(bytes, width_, height_);
187 if (premultiply_with_alpha)
188 premultiply(original_view);
190 boost::scoped_array<uint8_t> blurred_copy;
192 if (motion_blur_px > 0)
194 double angle = 3.14159265 / 2; // Up
196 if (horizontal && speed < 0)
198 else if (vertical && speed > 0)
200 else if (horizontal && speed > 0)
201 angle = 0.0; // Right
203 blurred_copy.reset(new uint8_t[count]);
204 image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
205 caspar::tweener blur_tweener(L"easeInQuad");
206 blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
207 bytes = blurred_copy.get();
217 core::pixel_format_desc desc = core::pixel_format::bgra;
218 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
219 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
221 if(count >= frame.image_data(0).size())
223 std::copy_n(bytes + count - frame.image_data(0).size(), frame.image_data(0).size(), frame.image_data(0).begin());
224 count -= static_cast<int>(frame.image_data(0).size());
228 memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
229 std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);
233 core::draw_frame draw_frame(std::move(frame));
235 // Set the relative position to the other image fragments
236 draw_frame.transform().image_transform.fill_translation[1] = - n++;
238 frames_.push_back(draw_frame);
246 core::pixel_format_desc desc = core::pixel_format::bgra;
247 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
248 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
249 if(count >= frame.image_data(0).size())
251 for(int y = 0; y < height_; ++y)
252 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);
255 count -= static_cast<int>(frame.image_data(0).size());
259 memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
260 auto width2 = width_ % format_desc_.width;
261 for(int y = 0; y < height_; ++y)
262 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, width2*4, frame.image_data(0).begin() + y * format_desc_.width*4);
267 frames_.push_back(core::draw_frame(std::move(frame)));
270 std::reverse(frames_.begin(), frames_.end());
272 // Set the relative positions of the image fragments.
273 for (size_t n = 0; n < frames_.size(); ++n)
275 double translation = - (static_cast<double>(n) + 1.0);
276 frames_[n].transform().image_transform.fill_translation[0] = translation;
280 CASPAR_LOG(info) << print() << L" Initialized";
283 double get_total_num_pixels() const
285 bool vertical = width_ == format_desc_.width;
288 return height_ + format_desc_.height;
290 return width_ + format_desc_.width;
293 double speed_from_duration(double duration_seconds) const
295 return get_total_num_pixels() / (duration_seconds * format_desc_.fps * static_cast<double>(format_desc_.field_count));
298 std::future<std::wstring> call(const std::vector<std::wstring>& params) override
300 auto cmd = params.at(0);
302 if (boost::iequals(cmd, L"SPEED"))
304 if (params.size() == 1)
305 return make_ready_future(boost::lexical_cast<std::wstring>(speed_.fetch()));
307 auto val = boost::lexical_cast<double>(params.at(1));
308 int duration = params.size() > 2 ? boost::lexical_cast<int>(params.at(2)) : 0;
309 std::wstring tween = params.size() > 3 ? params.at(3) : L"linear";
310 speed_ = speed_tweener(speed_.fetch(), val, duration, tween);
313 return make_ready_future<std::wstring>(L"");
316 std::vector<core::draw_frame> get_visible()
318 std::vector<core::draw_frame> result;
319 result.reserve(frames_.size());
321 for (auto& frame : frames_)
323 auto& fill_translation = frame.transform().image_transform.fill_translation;
325 if (width_ == format_desc_.width)
327 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);
328 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;
330 if (vertical_offset < -1.0 || vertical_offset > 1.0)
337 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);
338 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;
340 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
346 result.push_back(frame);
349 return std::move(result);
353 core::draw_frame render_frame(bool allow_eof)
356 return core::draw_frame::empty();
358 core::draw_frame result(get_visible());
359 auto& fill_translation = result.transform().image_transform.fill_translation;
361 if (width_ == format_desc_.width)
363 if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
364 return core::draw_frame::empty();
366 fill_translation[1] =
367 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)
368 + delta_ / static_cast<double>(format_desc_.height);
372 if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
373 return core::draw_frame::empty();
375 fill_translation[0] =
376 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)
377 + (delta_) / static_cast<double>(format_desc_.width);
383 core::draw_frame render_frame(bool allow_eof, bool advance_delta)
385 auto result = render_frame(allow_eof);
399 boost::posix_time::ptime now(boost::posix_time::second_clock::local_time());
401 auto diff = *end_time_ - now;
402 auto seconds = diff.total_seconds();
404 set_speed(-speed_from_duration(seconds));
405 end_time_ = boost::none;
408 delta_ += speed_.fetch_and_tick();
411 void set_speed(double speed)
413 speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
416 core::draw_frame receive_impl() override
418 core::draw_frame result;
420 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
422 result = render_frame(true, true);
426 auto field1 = render_frame(true, true);
427 auto field2 = render_frame(true, false);
429 if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())
431 field2 = render_frame(false, true);
438 result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);
441 monitor_subject_ << core::monitor::message("/file/path") % filename_
442 << core::monitor::message("/delta") % delta_
443 << core::monitor::message("/speed") % speed_.fetch();
448 core::constraints& pixel_constraints() override
453 std::wstring print() const override
455 return L"image_scroll_producer[" + filename_ + L"]";
458 std::wstring name() const override
460 return L"image-scroll";
463 boost::property_tree::wptree info() const override
465 boost::property_tree::wptree info;
466 info.add(L"type", L"image-scroll");
467 info.add(L"filename", filename_);
468 info.add(L"speed", speed_.fetch());
472 uint32_t nb_frames() const override
474 if(width_ == format_desc_.width)
476 auto length = (height_ + format_desc_.height * 2);
477 return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
481 auto length = (width_ + format_desc_.width * 2);
482 return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
486 core::monitor::subject& monitor_output()
488 return monitor_subject_;
492 void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
494 sink.short_description(L"Scrolls an image either horizontally or vertically.");
495 sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
497 ->text(L"Scrolls an image either horizontally or vertically. ")
498 ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
499 ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
500 ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
502 ->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.")
503 ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
504 ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
505 ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
506 ->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.");
507 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.");
508 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.");
511 spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
513 static const auto extensions = {
527 std::wstring filename = env::media_folder() + params.at(0);
529 auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool
531 auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
533 return static_cast<bool>(file);
536 if(ext == extensions.end())
537 return core::frame_producer::empty();
539 double duration = 0.0;
540 double speed = get_param(L"SPEED", params, 0.0);
541 boost::optional<boost::posix_time::ptime> end_time;
544 duration = get_param(L"DURATION", params, 0.0);
548 auto end_time_str = get_param(L"END_TIME", params);
550 if (!end_time_str.empty())
552 end_time = boost::posix_time::time_from_string(u8(end_time_str));
556 if(speed == 0 && duration == 0 && !end_time)
557 return core::frame_producer::empty();
559 int motion_blur_px = get_param(L"BLUR", params, 0);
561 bool premultiply_with_alpha = contains_param(L"PREMULTIPLY", params);
562 bool progressive = contains_param(L"PROGRESSIVE", params);
564 return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(
565 dependencies.frame_factory,
566 dependencies.format_desc,
567 *caspar::find_case_insensitive(filename + *ext),
572 premultiply_with_alpha,