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>
57 namespace caspar { namespace image {
59 struct image_scroll_producer : public core::frame_producer_base
61 core::monitor::subject monitor_subject_;
63 const std::wstring filename_;
64 std::vector<core::draw_frame> frames_;
65 core::video_format_desc format_desc_;
68 core::constraints constraints_;
73 int start_offset_x_ = 0;
74 int start_offset_y_ = 0;
77 explicit image_scroll_producer(
78 const spl::shared_ptr<core::frame_factory>& frame_factory,
79 const core::video_format_desc& format_desc,
80 const std::wstring& filename,
83 int motion_blur_px = 0,
84 bool premultiply_with_alpha = false,
85 bool progressive = false)
87 , format_desc_(format_desc)
89 , progressive_(progressive)
91 auto bitmap = load_image(filename_);
92 FreeImage_FlipVertical(bitmap.get());
94 width_ = FreeImage_GetWidth(bitmap.get());
95 height_ = FreeImage_GetHeight(bitmap.get());
96 constraints_.width.set(width_);
97 constraints_.height.set(height_);
99 bool vertical = width_ == format_desc_.width;
100 bool horizontal = height_ == format_desc_.height;
102 if (!vertical && !horizontal)
103 CASPAR_THROW_EXCEPTION(
104 caspar::invalid_argument() << msg_info("Neither width nor height matched the video resolution"));
110 double total_num_pixels = format_desc_.height * 2 + height_;
112 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
114 if (std::abs(speed_) > 1.0)
115 speed_ = std::ceil(speed_);
120 start_offset_y_ = height_ + format_desc_.height;
127 double total_num_pixels = format_desc_.width * 2 + width_;
129 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
131 if (std::abs(speed_) > 1.0)
132 speed_ = std::ceil(speed_);
136 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
138 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
141 auto bytes = FreeImage_GetBits(bitmap.get());
142 auto count = width_*height_*4;
143 image_view<bgra_pixel> original_view(bytes, width_, height_);
145 if (premultiply_with_alpha)
146 premultiply(original_view);
148 boost::scoped_array<uint8_t> blurred_copy;
150 if (motion_blur_px > 0)
152 double angle = 3.14159265 / 2; // Up
154 if (horizontal && speed_ < 0)
156 else if (vertical && speed > 0)
158 else if (horizontal && speed > 0)
159 angle = 0.0; // Right
161 blurred_copy.reset(new uint8_t[count]);
162 image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
163 caspar::tweener blur_tweener(L"easeInQuad");
164 blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
165 bytes = blurred_copy.get();
175 core::pixel_format_desc desc = core::pixel_format::bgra;
176 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
177 auto frame = frame_factory->create_frame(this, desc);
179 if(count >= frame.image_data(0).size())
181 std::copy_n(bytes + count - frame.image_data(0).size(), frame.image_data(0).size(), frame.image_data(0).begin());
182 count -= static_cast<int>(frame.image_data(0).size());
186 memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
187 std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);
191 core::draw_frame draw_frame(std::move(frame));
193 // Set the relative position to the other image fragments
194 draw_frame.transform().image_transform.fill_translation[1] = - n++;
196 frames_.push_back(draw_frame);
204 core::pixel_format_desc desc = core::pixel_format::bgra;
205 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
206 auto frame = frame_factory->create_frame(this, desc);
207 if(count >= frame.image_data(0).size())
209 for(int y = 0; y < height_; ++y)
210 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);
213 count -= static_cast<int>(frame.image_data(0).size());
217 memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
218 auto width2 = width_ % format_desc_.width;
219 for(int y = 0; y < height_; ++y)
220 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, width2*4, frame.image_data(0).begin() + y * format_desc_.width*4);
225 frames_.push_back(core::draw_frame(std::move(frame)));
228 std::reverse(frames_.begin(), frames_.end());
230 // Set the relative positions of the image fragments.
231 for (size_t n = 0; n < frames_.size(); ++n)
233 double translation = - (static_cast<double>(n) + 1.0);
234 frames_[n].transform().image_transform.fill_translation[0] = translation;
238 CASPAR_LOG(info) << print() << L" Initialized";
241 std::vector<core::draw_frame> get_visible()
243 std::vector<core::draw_frame> result;
244 result.reserve(frames_.size());
246 for (auto& frame : frames_)
248 auto& fill_translation = frame.transform().image_transform.fill_translation;
250 if (width_ == format_desc_.width)
252 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);
253 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;
255 if (vertical_offset < -1.0 || vertical_offset > 1.0)
262 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);
263 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;
265 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
271 result.push_back(frame);
274 return std::move(result);
278 core::draw_frame render_frame(bool allow_eof)
281 return core::draw_frame::empty();
283 core::draw_frame result(get_visible());
284 auto& fill_translation = result.transform().image_transform.fill_translation;
286 if (width_ == format_desc_.width)
288 if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
289 return core::draw_frame::empty();
291 fill_translation[1] =
292 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)
293 + delta_ / static_cast<double>(format_desc_.height);
297 if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
298 return core::draw_frame::empty();
300 fill_translation[0] =
301 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)
302 + (delta_) / static_cast<double>(format_desc_.width);
308 core::draw_frame render_frame(bool allow_eof, bool advance_delta)
310 auto result = render_frame(allow_eof);
325 core::draw_frame receive_impl() override
327 core::draw_frame result;
329 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
331 result = render_frame(true, true);
335 auto field1 = render_frame(true, true);
336 auto field2 = render_frame(true, false);
338 if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())
340 field2 = render_frame(false, true);
347 result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);
350 monitor_subject_ << core::monitor::message("/file/path") % filename_
351 << core::monitor::message("/delta") % delta_
352 << core::monitor::message("/speed") % speed_;
357 core::constraints& pixel_constraints() override
362 std::wstring print() const override
364 return L"image_scroll_producer[" + filename_ + L"]";
367 std::wstring name() const override
369 return L"image-scroll";
372 boost::property_tree::wptree info() const override
374 boost::property_tree::wptree info;
375 info.add(L"type", L"image-scroll");
376 info.add(L"filename", filename_);
380 uint32_t nb_frames() const override
382 if(width_ == format_desc_.width)
384 auto length = (height_ + format_desc_.height * 2);
385 return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
389 auto length = (width_ + format_desc_.width * 2);
390 return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
394 core::monitor::subject& monitor_output()
396 return monitor_subject_;
400 void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
402 sink.short_description(L"Scrolls an image either horizontally or vertically.");
403 sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
405 ->text(L"Scrolls an image either horizontally or vertically. ")
406 ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
407 ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
408 ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
410 ->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.")
411 ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
412 ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
413 ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
414 ->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.");
415 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.");
416 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.");
419 spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
421 static const auto extensions = {
435 std::wstring filename = env::media_folder() + params.at(0);
437 auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool
439 auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
441 return static_cast<bool>(file);
444 if(ext == extensions.end())
445 return core::frame_producer::empty();
447 double duration = 0.0;
448 double speed = get_param(L"SPEED", params, 0.0);
451 duration = get_param(L"DURATION", params, 0.0);
453 if(speed == 0 && duration == 0)
454 return core::frame_producer::empty();
456 int motion_blur_px = get_param(L"BLUR", params, 0);
458 bool premultiply_with_alpha = contains_param(L"PREMULTIPLY", params);
459 bool progressive = contains_param(L"PROGRESSIVE", params);
461 return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(
462 dependencies.frame_factory,
463 dependencies.format_desc,
464 *caspar::find_case_insensitive(filename + *ext),
468 premultiply_with_alpha,