2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
\r
4 * This file is part of CasparCG (www.casparcg.com).
\r
6 * CasparCG is free software: you can redistribute it and/or modify
\r
7 * it under the terms of the GNU General Public License as published by
\r
8 * the Free Software Foundation, either version 3 of the License, or
\r
9 * (at your option) any later version.
\r
11 * CasparCG is distributed in the hope that it will be useful,
\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 * GNU General Public License for more details.
\r
16 * You should have received a copy of the GNU General Public License
\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
\r
19 * Author: Robert Nagy, ronag89@gmail.com
\r
20 * Author: Helge Norberg, helge.norberg@svt.se
\r
23 #include "image_scroll_producer.h"
\r
25 #include "../util/image_loader.h"
\r
26 #include "../util/image_view.h"
\r
27 #include "../util/image_algorithms.h"
\r
29 #include <core/video_format.h>
\r
31 #include <core/producer/frame/basic_frame.h>
\r
32 #include <core/producer/frame/frame_factory.h>
\r
33 #include <core/producer/frame/frame_transform.h>
\r
34 #include <core/mixer/write_frame.h>
\r
36 #include <common/env.h>
\r
37 #include <common/log/log.h>
\r
38 #include <common/memory/memclr.h>
\r
39 #include <common/exception/exceptions.h>
\r
40 #include <common/utility/tweener.h>
\r
42 #include <boost/assign.hpp>
\r
43 #include <boost/filesystem.hpp>
\r
44 #include <boost/foreach.hpp>
\r
45 #include <boost/lexical_cast.hpp>
\r
46 #include <boost/property_tree/ptree.hpp>
\r
47 #include <boost/gil/gil_all.hpp>
\r
49 #include <algorithm>
\r
51 #include <boost/math/special_functions/round.hpp>
\r
52 #include <boost/scoped_array.hpp>
\r
54 using namespace boost::assign;
\r
56 namespace caspar { namespace image {
\r
58 struct image_scroll_producer : public core::frame_producer
\r
60 const std::wstring filename_;
\r
61 std::vector<safe_ptr<core::basic_frame>> frames_;
\r
62 core::video_format_desc format_desc_;
\r
69 int start_offset_x_;
\r
70 int start_offset_y_;
\r
73 safe_ptr<core::basic_frame> last_frame_;
\r
75 explicit image_scroll_producer(
\r
76 const safe_ptr<core::frame_factory>& frame_factory,
\r
77 const std::wstring& filename,
\r
80 int motion_blur_px = 0,
\r
81 bool premultiply_with_alpha = false,
\r
82 bool progressive = false)
\r
83 : filename_(filename)
\r
85 , format_desc_(frame_factory->get_video_format_desc())
\r
87 , progressive_(progressive)
\r
88 , last_frame_(core::basic_frame::empty())
\r
90 start_offset_x_ = 0;
\r
91 start_offset_y_ = 0;
\r
93 auto bitmap = load_image(filename_);
\r
94 FreeImage_FlipVertical(bitmap.get());
\r
96 width_ = FreeImage_GetWidth(bitmap.get());
\r
97 height_ = FreeImage_GetHeight(bitmap.get());
\r
99 bool vertical = width_ == format_desc_.width;
\r
100 bool horizontal = height_ == format_desc_.height;
\r
102 if (!vertical && !horizontal)
\r
103 BOOST_THROW_EXCEPTION(
\r
104 caspar::invalid_argument() << msg_info("Neither width nor height matched the video resolution"));
\r
108 if (duration != 0.0)
\r
110 double total_num_pixels = format_desc_.height * 2 + height_;
\r
112 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
\r
114 if (std::abs(speed_) > 1.0)
\r
115 speed_ = std::ceil(speed_);
\r
119 start_offset_y_ = height_ + format_desc_.height;
\r
123 if (duration != 0.0)
\r
125 double total_num_pixels = format_desc_.width * 2 + width_;
\r
127 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
\r
129 if (std::abs(speed_) > 1.0)
\r
130 speed_ = std::ceil(speed_);
\r
134 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
\r
136 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
\r
139 auto bytes = FreeImage_GetBits(bitmap.get());
\r
140 int count = width_*height_*4;
\r
141 image_view<bgra_pixel> original_view(bytes, width_, height_);
\r
143 if (premultiply_with_alpha)
\r
144 premultiply(original_view);
\r
146 boost::scoped_array<uint8_t> blurred_copy;
\r
148 if (motion_blur_px > 0)
\r
150 double angle = 3.14159265 / 2; // Up
\r
152 if (horizontal && speed_ < 0)
\r
153 angle *= 2; // Left
\r
154 else if (vertical && speed > 0)
\r
155 angle *= 3; // Down
\r
156 else if (horizontal && speed > 0)
\r
157 angle = 0.0; // Right
\r
159 blurred_copy.reset(new uint8_t[count]);
\r
160 image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
\r
161 tweener_t blur_tweener = get_tweener(L"easeInQuad");
\r
162 blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
\r
163 bytes = blurred_copy.get();
\r
173 core::pixel_format_desc desc;
\r
174 desc.pix_fmt = core::pixel_format::bgra;
\r
175 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
\r
176 auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);
\r
178 if(count >= frame->image_data().size())
\r
180 std::copy_n(bytes + count - frame->image_data().size(), frame->image_data().size(), frame->image_data().begin());
\r
181 count -= frame->image_data().size();
\r
185 fast_memclr(frame->image_data().begin(), frame->image_data().size());
\r
186 std::copy_n(bytes, count, frame->image_data().begin() + format_desc_.size - count);
\r
191 frames_.push_back(frame);
\r
193 // Set the relative position to the other image fragments
\r
194 frame->get_frame_transform().fill_translation[1] = - n++;
\r
198 else if (horizontal)
\r
203 core::pixel_format_desc desc;
\r
204 desc.pix_fmt = core::pixel_format::bgra;
\r
205 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
\r
206 auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);
\r
207 if(count >= frame->image_data().size())
\r
209 for(size_t y = 0; y < height_; ++y)
\r
210 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, format_desc_.width*4, frame->image_data().begin() + y * format_desc_.width*4);
\r
213 count -= frame->image_data().size();
\r
217 fast_memclr(frame->image_data().begin(), frame->image_data().size());
\r
218 int width2 = width_ % format_desc_.width;
\r
219 for(size_t y = 0; y < height_; ++y)
\r
220 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, width2*4, frame->image_data().begin() + y * format_desc_.width*4);
\r
226 frames_.push_back(frame);
\r
229 std::reverse(frames_.begin(), frames_.end());
\r
231 // Set the relative positions of the image fragments.
\r
232 for (size_t n = 0; n < frames_.size(); ++n)
\r
234 double translation = - (static_cast<double>(n) + 1.0);
\r
235 frames_[n]->get_frame_transform().fill_translation[0] = translation;
\r
239 CASPAR_LOG(info) << print() << L" Initialized";
\r
242 std::vector<safe_ptr<core::basic_frame>> get_visible()
\r
244 std::vector<safe_ptr<core::basic_frame>> result;
\r
245 result.reserve(frames_.size());
\r
247 BOOST_FOREACH(auto& frame, frames_)
\r
249 auto& fill_translation = frame->get_frame_transform().fill_translation;
\r
251 if (width_ == format_desc_.width)
\r
253 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);
\r
254 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;
\r
256 if (vertical_offset < -1.0 || vertical_offset > 1.0)
\r
263 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);
\r
264 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;
\r
266 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
\r
272 result.push_back(frame);
\r
275 return std::move(result);
\r
280 safe_ptr<core::basic_frame> render_frame(bool allow_eof)
\r
282 if(frames_.empty())
\r
283 return core::basic_frame::eof();
\r
285 auto result = make_safe<core::basic_frame>(get_visible());
\r
286 auto& fill_translation = result->get_frame_transform().fill_translation;
\r
288 if (width_ == format_desc_.width)
\r
290 if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
\r
291 return core::basic_frame::eof();
\r
293 fill_translation[1] =
\r
294 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)
\r
295 + delta_ / static_cast<double>(format_desc_.height);
\r
299 if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
\r
300 return core::basic_frame::eof();
\r
302 fill_translation[0] =
\r
303 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)
\r
304 + (delta_) / static_cast<double>(format_desc_.width);
\r
310 safe_ptr<core::basic_frame> render_frame(bool allow_eof, bool advance_delta)
\r
312 auto result = render_frame(allow_eof);
\r
327 virtual safe_ptr<core::basic_frame> receive(int) override
\r
329 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
\r
331 return last_frame_ = render_frame(true, true);
\r
335 auto field1 = render_frame(true, true);
\r
336 auto field2 = render_frame(true, false);
\r
338 if (field1 != core::basic_frame::eof() && field2 == core::basic_frame::eof())
\r
340 field2 = render_frame(false, true);
\r
347 last_frame_ = field2;
\r
349 return core::basic_frame::interlace(field1, field2, format_desc_.field_mode);
\r
353 virtual safe_ptr<core::basic_frame> last_frame() const override
\r
355 return last_frame_;
\r
358 virtual std::wstring print() const override
\r
360 return L"image_scroll_producer[" + filename_ + L"]";
\r
363 virtual boost::property_tree::wptree info() const override
\r
365 boost::property_tree::wptree info;
\r
366 info.add(L"type", L"image-scroll-producer");
\r
367 info.add(L"filename", filename_);
\r
371 virtual uint32_t nb_frames() const override
\r
373 if(width_ == format_desc_.width)
\r
375 auto length = (height_ + format_desc_.height * 2);
\r
376 return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
\r
380 auto length = (width_ + format_desc_.width * 2);
\r
381 return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
\r
386 safe_ptr<core::frame_producer> create_scroll_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)
\r
388 static const std::vector<std::wstring> extensions = list_of(L"png")(L"tga")(L"bmp")(L"jpg")(L"jpeg")(L"gif")(L"tiff")(L"tif")(L"jp2")(L"jpx")(L"j2k")(L"j2c");
\r
389 std::wstring filename = env::media_folder() + L"\\" + params[0];
\r
391 auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool
\r
393 return boost::filesystem::is_regular_file(boost::filesystem::wpath(filename).replace_extension(ex));
\r
396 if(ext == extensions.end())
\r
397 return core::frame_producer::empty();
\r
399 double speed = 0.0;
\r
400 double duration = 0.0;
\r
401 auto speed_it = std::find(params.begin(), params.end(), L"SPEED");
\r
402 if(speed_it != params.end())
\r
404 if(++speed_it != params.end())
\r
405 speed = boost::lexical_cast<double>(*speed_it);
\r
410 auto duration_it = std::find(params.begin(), params.end(), L"DURATION");
\r
412 if (duration_it != params.end() && ++duration_it != params.end())
\r
414 duration = boost::lexical_cast<double>(*duration_it);
\r
418 if(speed == 0 && duration == 0)
\r
419 return core::frame_producer::empty();
\r
421 int motion_blur_px = 0;
\r
422 auto blur_it = std::find(params.begin(), params.end(), L"BLUR");
\r
423 if (blur_it != params.end() && ++blur_it != params.end())
\r
425 motion_blur_px = boost::lexical_cast<int>(*blur_it);
\r
428 bool premultiply_with_alpha = std::find(params.begin(), params.end(), L"PREMULTIPLY") != params.end();
\r
429 bool progressive = std::find(params.begin(), params.end(), L"PROGRESSIVE") != params.end();
\r
431 return create_producer_print_proxy(make_safe<image_scroll_producer>(
\r
433 filename + L"." + *ext,
\r
437 premultiply_with_alpha,
\r