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/monitor/monitor.h>
37 #include <core/help/help_sink.h>
38 #include <core/help/help_repository.h>
40 #include <common/env.h>
41 #include <common/log.h>
42 #include <common/except.h>
43 #include <common/array.h>
44 #include <common/tweener.h>
45 #include <common/param.h>
46 #include <common/os/filesystem.h>
48 #include <boost/filesystem.hpp>
49 #include <boost/lexical_cast.hpp>
50 #include <boost/property_tree/ptree.hpp>
51 #include <boost/scoped_array.hpp>
56 namespace caspar { namespace image {
58 struct image_scroll_producer : public core::frame_producer_base
60 core::monitor::subject monitor_subject_;
62 const std::wstring filename_;
63 std::vector<core::draw_frame> frames_;
64 core::video_format_desc format_desc_;
67 core::constraints constraints_;
72 int start_offset_x_ = 0;
73 int start_offset_y_ = 0;
76 explicit image_scroll_producer(
77 const spl::shared_ptr<core::frame_factory>& frame_factory,
78 const core::video_format_desc& format_desc,
79 const std::wstring& filename,
82 int motion_blur_px = 0,
83 bool premultiply_with_alpha = false,
84 bool progressive = false)
86 , format_desc_(format_desc)
88 , progressive_(progressive)
90 auto bitmap = load_image(filename_);
91 FreeImage_FlipVertical(bitmap.get());
93 width_ = FreeImage_GetWidth(bitmap.get());
94 height_ = FreeImage_GetHeight(bitmap.get());
95 constraints_.width.set(width_);
96 constraints_.height.set(height_);
98 bool vertical = width_ == format_desc_.width;
99 bool horizontal = height_ == format_desc_.height;
101 if (!vertical && !horizontal)
102 CASPAR_THROW_EXCEPTION(
103 caspar::invalid_argument() << msg_info("Neither width nor height matched the video resolution"));
109 double total_num_pixels = format_desc_.height * 2 + height_;
111 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
113 if (std::abs(speed_) > 1.0)
114 speed_ = std::ceil(speed_);
119 start_offset_y_ = height_ + format_desc_.height;
126 double total_num_pixels = format_desc_.width * 2 + width_;
128 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
130 if (std::abs(speed_) > 1.0)
131 speed_ = std::ceil(speed_);
135 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
137 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
140 auto bytes = FreeImage_GetBits(bitmap.get());
141 auto count = width_*height_*4;
142 image_view<bgra_pixel> original_view(bytes, width_, height_);
144 if (premultiply_with_alpha)
145 premultiply(original_view);
147 boost::scoped_array<uint8_t> blurred_copy;
149 if (motion_blur_px > 0)
151 double angle = 3.14159265 / 2; // Up
153 if (horizontal && speed_ < 0)
155 else if (vertical && speed > 0)
157 else if (horizontal && speed > 0)
158 angle = 0.0; // Right
160 blurred_copy.reset(new uint8_t[count]);
161 image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
162 caspar::tweener blur_tweener(L"easeInQuad");
163 blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
164 bytes = blurred_copy.get();
174 core::pixel_format_desc desc = core::pixel_format::bgra;
175 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
176 auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);
178 if(count >= frame.image_data(0).size())
180 std::copy_n(bytes + count - frame.image_data(0).size(), frame.image_data(0).size(), frame.image_data(0).begin());
181 count -= static_cast<int>(frame.image_data(0).size());
185 memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
186 std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);
190 core::draw_frame draw_frame(std::move(frame));
192 // Set the relative position to the other image fragments
193 draw_frame.transform().image_transform.fill_translation[1] = - n++;
195 frames_.push_back(draw_frame);
203 core::pixel_format_desc desc = core::pixel_format::bgra;
204 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
205 auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);
206 if(count >= frame.image_data(0).size())
208 for(int y = 0; y < height_; ++y)
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);
212 count -= static_cast<int>(frame.image_data(0).size());
216 memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
217 auto width2 = width_ % format_desc_.width;
218 for(int y = 0; y < height_; ++y)
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);
224 frames_.push_back(core::draw_frame(std::move(frame)));
227 std::reverse(frames_.begin(), frames_.end());
229 // Set the relative positions of the image fragments.
230 for (size_t n = 0; n < frames_.size(); ++n)
232 double translation = - (static_cast<double>(n) + 1.0);
233 frames_[n].transform().image_transform.fill_translation[0] = translation;
237 CASPAR_LOG(info) << print() << L" Initialized";
240 std::vector<core::draw_frame> get_visible()
242 std::vector<core::draw_frame> result;
243 result.reserve(frames_.size());
245 for (auto& frame : frames_)
247 auto& fill_translation = frame.transform().image_transform.fill_translation;
249 if (width_ == format_desc_.width)
251 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);
252 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;
254 if (vertical_offset < -1.0 || vertical_offset > 1.0)
261 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);
262 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;
264 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
270 result.push_back(frame);
273 return std::move(result);
277 core::draw_frame render_frame(bool allow_eof)
280 return core::draw_frame::empty();
282 core::draw_frame result(get_visible());
283 auto& fill_translation = result.transform().image_transform.fill_translation;
285 if (width_ == format_desc_.width)
287 if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
288 return core::draw_frame::empty();
290 fill_translation[1] =
291 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)
292 + delta_ / static_cast<double>(format_desc_.height);
296 if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
297 return core::draw_frame::empty();
299 fill_translation[0] =
300 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)
301 + (delta_) / static_cast<double>(format_desc_.width);
307 core::draw_frame render_frame(bool allow_eof, bool advance_delta)
309 auto result = render_frame(allow_eof);
324 core::draw_frame receive_impl() override
326 core::draw_frame result;
328 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
330 result = render_frame(true, true);
334 auto field1 = render_frame(true, true);
335 auto field2 = render_frame(true, false);
337 if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())
339 field2 = render_frame(false, true);
346 result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);
349 monitor_subject_ << core::monitor::message("/file/path") % filename_
350 << core::monitor::message("/delta") % delta_
351 << core::monitor::message("/speed") % speed_;
356 core::constraints& pixel_constraints() override
361 std::wstring print() const override
363 return L"image_scroll_producer[" + filename_ + L"]";
366 std::wstring name() const override
368 return L"image-scroll";
371 boost::property_tree::wptree info() const override
373 boost::property_tree::wptree info;
374 info.add(L"type", L"image-scroll");
375 info.add(L"filename", filename_);
379 uint32_t nb_frames() const override
381 if(width_ == format_desc_.width)
383 auto length = (height_ + format_desc_.height * 2);
384 return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
388 auto length = (width_ + format_desc_.width * 2);
389 return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
393 core::monitor::subject& monitor_output()
395 return monitor_subject_;
399 void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
401 sink.short_description(L"Scrolls an image either horizontally or vertically.");
402 sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
404 ->text(L"Scrolls an image either horizontally or vertically. ")
405 ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
406 ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
407 ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
409 ->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.")
410 ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
411 ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
412 ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
413 ->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.");
414 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.");
415 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.");
418 spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
420 static const auto extensions = {
434 std::wstring filename = env::media_folder() + params.at(0);
436 auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool
438 auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
440 return static_cast<bool>(file);
443 if(ext == extensions.end())
444 return core::frame_producer::empty();
446 double duration = 0.0;
447 double speed = get_param(L"SPEED", params, 0.0);
450 duration = get_param(L"DURATION", params, 0.0);
452 if(speed == 0 && duration == 0)
453 return core::frame_producer::empty();
455 int motion_blur_px = get_param(L"BLUR", params, 0);
457 bool premultiply_with_alpha = contains_param(L"PREMULTIPLY", params);
458 bool progressive = contains_param(L"PROGRESSIVE", params);
460 return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(
461 dependencies.frame_factory,
462 dependencies.format_desc,
463 *caspar::find_case_insensitive(filename + *ext),
467 premultiply_with_alpha,