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>
49 #include <boost/filesystem.hpp>
50 #include <boost/lexical_cast.hpp>
51 #include <boost/property_tree/ptree.hpp>
52 #include <boost/scoped_array.hpp>
58 namespace caspar { namespace image {
60 struct image_scroll_producer : public core::frame_producer_base
62 core::monitor::subject monitor_subject_;
64 const std::wstring filename_;
65 std::vector<core::draw_frame> frames_;
66 core::video_format_desc format_desc_;
69 core::constraints constraints_;
74 int start_offset_x_ = 0;
75 int start_offset_y_ = 0;
78 explicit image_scroll_producer(
79 const spl::shared_ptr<core::frame_factory>& frame_factory,
80 const core::video_format_desc& format_desc,
81 const std::wstring& filename,
84 int motion_blur_px = 0,
85 bool premultiply_with_alpha = false,
86 bool progressive = false)
88 , format_desc_(format_desc)
90 , progressive_(progressive)
92 auto bitmap = load_image(filename_);
93 FreeImage_FlipVertical(bitmap.get());
95 width_ = FreeImage_GetWidth(bitmap.get());
96 height_ = FreeImage_GetHeight(bitmap.get());
97 constraints_.width.set(width_);
98 constraints_.height.set(height_);
100 bool vertical = width_ == format_desc_.width;
101 bool horizontal = height_ == format_desc_.height;
103 if (!vertical && !horizontal)
104 CASPAR_THROW_EXCEPTION(caspar::user_error()
105 << msg_info("Neither width nor height matched the video resolution"));
111 double total_num_pixels = format_desc_.height * 2 + height_;
113 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
115 if (std::abs(speed_) > 1.0)
116 speed_ = std::ceil(speed_);
121 start_offset_y_ = height_ + format_desc_.height;
128 double total_num_pixels = format_desc_.width * 2 + width_;
130 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
132 if (std::abs(speed_) > 1.0)
133 speed_ = std::ceil(speed_);
137 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
139 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
142 auto bytes = FreeImage_GetBits(bitmap.get());
143 auto count = width_*height_*4;
144 image_view<bgra_pixel> original_view(bytes, width_, height_);
146 if (premultiply_with_alpha)
147 premultiply(original_view);
149 boost::scoped_array<uint8_t> blurred_copy;
151 if (motion_blur_px > 0)
153 double angle = 3.14159265 / 2; // Up
155 if (horizontal && speed_ < 0)
157 else if (vertical && speed > 0)
159 else if (horizontal && speed > 0)
160 angle = 0.0; // Right
162 blurred_copy.reset(new uint8_t[count]);
163 image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
164 caspar::tweener blur_tweener(L"easeInQuad");
165 blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
166 bytes = blurred_copy.get();
176 core::pixel_format_desc desc = core::pixel_format::bgra;
177 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
178 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
180 if(count >= frame.image_data(0).size())
182 std::copy_n(bytes + count - frame.image_data(0).size(), frame.image_data(0).size(), frame.image_data(0).begin());
183 count -= static_cast<int>(frame.image_data(0).size());
187 memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
188 std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);
192 core::draw_frame draw_frame(std::move(frame));
194 // Set the relative position to the other image fragments
195 draw_frame.transform().image_transform.fill_translation[1] = - n++;
197 frames_.push_back(draw_frame);
205 core::pixel_format_desc desc = core::pixel_format::bgra;
206 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
207 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
208 if(count >= frame.image_data(0).size())
210 for(int y = 0; y < height_; ++y)
211 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);
214 count -= static_cast<int>(frame.image_data(0).size());
218 memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());
219 auto width2 = width_ % format_desc_.width;
220 for(int y = 0; y < height_; ++y)
221 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, width2*4, frame.image_data(0).begin() + y * format_desc_.width*4);
226 frames_.push_back(core::draw_frame(std::move(frame)));
229 std::reverse(frames_.begin(), frames_.end());
231 // Set the relative positions of the image fragments.
232 for (size_t n = 0; n < frames_.size(); ++n)
234 double translation = - (static_cast<double>(n) + 1.0);
235 frames_[n].transform().image_transform.fill_translation[0] = translation;
239 CASPAR_LOG(info) << print() << L" Initialized";
242 std::vector<core::draw_frame> get_visible()
244 std::vector<core::draw_frame> result;
245 result.reserve(frames_.size());
247 for (auto& frame : frames_)
249 auto& fill_translation = frame.transform().image_transform.fill_translation;
251 if (width_ == format_desc_.width)
253 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);
254 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;
256 if (vertical_offset < -1.0 || vertical_offset > 1.0)
263 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);
264 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;
266 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
272 result.push_back(frame);
275 return std::move(result);
279 core::draw_frame render_frame(bool allow_eof)
282 return core::draw_frame::empty();
284 core::draw_frame result(get_visible());
285 auto& fill_translation = result.transform().image_transform.fill_translation;
287 if (width_ == format_desc_.width)
289 if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
290 return core::draw_frame::empty();
292 fill_translation[1] =
293 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)
294 + delta_ / static_cast<double>(format_desc_.height);
298 if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
299 return core::draw_frame::empty();
301 fill_translation[0] =
302 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)
303 + (delta_) / static_cast<double>(format_desc_.width);
309 core::draw_frame render_frame(bool allow_eof, bool advance_delta)
311 auto result = render_frame(allow_eof);
326 core::draw_frame receive_impl() override
328 core::draw_frame result;
330 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
332 result = render_frame(true, true);
336 auto field1 = render_frame(true, true);
337 auto field2 = render_frame(true, false);
339 if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())
341 field2 = render_frame(false, true);
348 result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);
351 monitor_subject_ << core::monitor::message("/file/path") % filename_
352 << core::monitor::message("/delta") % delta_
353 << core::monitor::message("/speed") % speed_;
358 core::constraints& pixel_constraints() override
363 std::wstring print() const override
365 return L"image_scroll_producer[" + filename_ + L"]";
368 std::wstring name() const override
370 return L"image-scroll";
373 boost::property_tree::wptree info() const override
375 boost::property_tree::wptree info;
376 info.add(L"type", L"image-scroll");
377 info.add(L"filename", filename_);
381 uint32_t nb_frames() const override
383 if(width_ == format_desc_.width)
385 auto length = (height_ + format_desc_.height * 2);
386 return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
390 auto length = (width_ + format_desc_.width * 2);
391 return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
395 core::monitor::subject& monitor_output()
397 return monitor_subject_;
401 void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
403 sink.short_description(L"Scrolls an image either horizontally or vertically.");
404 sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
406 ->text(L"Scrolls an image either horizontally or vertically. ")
407 ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
408 ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
409 ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
411 ->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.")
412 ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
413 ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
414 ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
415 ->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.");
416 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.");
417 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.");
420 spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
422 static const auto extensions = {
436 std::wstring filename = env::media_folder() + params.at(0);
438 auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool
440 auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
442 return static_cast<bool>(file);
445 if(ext == extensions.end())
446 return core::frame_producer::empty();
448 double duration = 0.0;
449 double speed = get_param(L"SPEED", params, 0.0);
452 duration = get_param(L"DURATION", params, 0.0);
454 if(speed == 0 && duration == 0)
455 return core::frame_producer::empty();
457 int motion_blur_px = get_param(L"BLUR", params, 0);
459 bool premultiply_with_alpha = contains_param(L"PREMULTIPLY", params);
460 bool progressive = contains_param(L"PROGRESSIVE", params);
462 return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(
463 dependencies.frame_factory,
464 dependencies.format_desc,
465 *caspar::find_case_insensitive(filename + *ext),
469 premultiply_with_alpha,