/*\r
-* copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
*\r
-* This file is part of CasparCG.\r
+* This file is part of CasparCG (www.casparcg.com).\r
*\r
-* CasparCG is free software: you can redistribute it and/or modify\r
-* it under the terms of the GNU General Public License as published by\r
-* the Free Software Foundation, either version 3 of the License, or\r
-* (at your option) any later version.\r
+* CasparCG is free software: you can redistribute it and/or modify\r
+* it under the terms of the GNU General Public License as published by\r
+* the Free Software Foundation, either version 3 of the License, or\r
+* (at your option) any later version.\r
*\r
-* CasparCG is distributed in the hope that it will be useful,\r
-* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
-* GNU General Public License for more details.\r
-\r
-* You should have received a copy of the GNU General Public License\r
-* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
+* CasparCG is distributed in the hope that it will be useful,\r
+* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+* GNU General Public License for more details.\r
+*\r
+* You should have received a copy of the GNU General Public License\r
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
*\r
+* Author: Robert Nagy, ronag89@gmail.com\r
+* Author: Helge Norberg, helge.norberg@svt.se\r
*/\r
+\r
#include "image_scroll_producer.h"\r
\r
#include "../util/image_loader.h"\r
+#include "../util/image_view.h"\r
+#include "../util/image_algorithms.h"\r
\r
#include <core/video_format.h>\r
\r
#include <core/producer/frame/basic_frame.h>\r
-#include <core/producer/frame/image_transform.h>\r
+#include <core/producer/frame/frame_factory.h>\r
+#include <core/producer/frame/frame_transform.h>\r
#include <core/mixer/write_frame.h>\r
\r
#include <common/env.h>\r
+#include <common/log/log.h>\r
#include <common/memory/memclr.h>\r
#include <common/exception/exceptions.h>\r
+#include <common/utility/tweener.h>\r
\r
#include <boost/assign.hpp>\r
#include <boost/filesystem.hpp>\r
#include <boost/foreach.hpp>\r
+#include <boost/lexical_cast.hpp>\r
+#include <boost/property_tree/ptree.hpp>\r
+#include <boost/gil/gil_all.hpp>\r
\r
#include <algorithm>\r
+#include <array>\r
+#include <boost/math/special_functions/round.hpp>\r
+#include <boost/scoped_array.hpp>\r
\r
using namespace boost::assign;\r
\r
-namespace caspar {\r
- \r
+namespace caspar { namespace image {\r
+\r
struct image_scroll_producer : public core::frame_producer\r
{ \r
const std::wstring filename_;\r
size_t width_;\r
size_t height_;\r
\r
- int delta_;\r
- int speed_;\r
+ double delta_;\r
+ double speed_;\r
+\r
+ int start_offset_x_;\r
+ int start_offset_y_;\r
\r
- std::array<double, 2> start_offset_;\r
+ safe_ptr<core::basic_frame> last_frame_;\r
\r
- explicit image_scroll_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, int speed) \r
+ explicit image_scroll_producer(\r
+ const safe_ptr<core::frame_factory>& frame_factory, \r
+ const std::wstring& filename, \r
+ double speed,\r
+ double duration,\r
+ int motion_blur_px = 0,\r
+ bool premultiply_with_alpha = false) \r
: filename_(filename)\r
, delta_(0)\r
, format_desc_(frame_factory->get_video_format_desc())\r
, speed_(speed)\r
+ , last_frame_(core::basic_frame::empty())\r
{\r
- start_offset_.assign(0.0);\r
+ start_offset_x_ = 0;\r
+ start_offset_y_ = 0;\r
\r
auto bitmap = load_image(filename_);\r
FreeImage_FlipVertical(bitmap.get());\r
width_ = FreeImage_GetWidth(bitmap.get());\r
height_ = FreeImage_GetHeight(bitmap.get());\r
\r
+ bool vertical = width_ == format_desc_.width;\r
+ bool horizontal = height_ == format_desc_.height;\r
+\r
+ if (!vertical && !horizontal)\r
+ BOOST_THROW_EXCEPTION(\r
+ caspar::invalid_argument() << msg_info("Neither width nor height matched the video resolution"));\r
+\r
+ if (vertical)\r
+ {\r
+ if (duration != 0.0)\r
+ {\r
+ double total_num_pixels = format_desc_.height * 2 + height_;\r
+\r
+ speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));\r
+\r
+ if (std::abs(speed_) > 1.0)\r
+ speed_ = std::ceil(speed_);\r
+ }\r
+\r
+ if (speed_ < 0.0)\r
+ start_offset_y_ = height_ + format_desc_.height;\r
+ }\r
+ else\r
+ {\r
+ if (duration != 0.0)\r
+ {\r
+ double total_num_pixels = format_desc_.width * 2 + width_;\r
+\r
+ speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));\r
+\r
+ if (std::abs(speed_) > 1.0)\r
+ speed_ = std::ceil(speed_);\r
+ }\r
+\r
+ if (speed_ > 0.0)\r
+ start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);\r
+ else\r
+ start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;\r
+ }\r
+\r
auto bytes = FreeImage_GetBits(bitmap.get());\r
int count = width_*height_*4;\r
+ image_view<bgra_pixel> original_view(bytes, width_, height_);\r
+\r
+ if (premultiply_with_alpha)\r
+ premultiply(original_view);\r
+\r
+ boost::scoped_array<uint8_t> blurred_copy;\r
\r
- if(height_ > format_desc_.height)\r
+ if (motion_blur_px > 0)\r
{\r
+ double angle = 3.14159265 / 2; // Up\r
+\r
+ if (horizontal && speed_ < 0)\r
+ angle *= 2; // Left\r
+ else if (vertical && speed > 0)\r
+ angle *= 3; // Down\r
+ else if (horizontal && speed > 0)\r
+ angle = 0.0; // Right\r
+\r
+ blurred_copy.reset(new uint8_t[count]);\r
+ image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);\r
+ tweener_t blur_tweener = get_tweener(L"easeInQuad");\r
+ blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);\r
+ bytes = blurred_copy.get();\r
+ bitmap.reset();\r
+ }\r
+\r
+ if (vertical)\r
+ {\r
+ int n = 1;\r
+\r
while(count > 0)\r
{\r
- auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), width_, format_desc_.height);\r
+ core::pixel_format_desc desc;\r
+ desc.pix_fmt = core::pixel_format::bgra;\r
+ desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));\r
+ auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);\r
+\r
if(count >= frame->image_data().size())\r
{ \r
std::copy_n(bytes + count - frame->image_data().size(), frame->image_data().size(), frame->image_data().begin());\r
std::copy_n(bytes, count, frame->image_data().begin() + format_desc_.size - count);\r
count = 0;\r
}\r
- \r
+\r
frame->commit();\r
frames_.push_back(frame);\r
+\r
+ // Set the relative position to the other image fragments\r
+ frame->get_frame_transform().fill_translation[1] = - n++;\r
}\r
- \r
- if(speed_ < 0.0)\r
- {\r
- auto offset = format_desc_.height - (height_ % format_desc_.height);\r
- auto offset2 = offset * 0.5/static_cast<double>(format_desc_.height);\r
- start_offset_[1] = (std::ceil(static_cast<double>(height_) / static_cast<double>(format_desc_.height)) + 1.0) * 0.5 - offset2;// - 1.5;\r
- }\r
+\r
}\r
- else\r
+ else if (horizontal)\r
{\r
int i = 0;\r
while(count > 0)\r
{\r
- auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), format_desc_.width, height_);\r
+ core::pixel_format_desc desc;\r
+ desc.pix_fmt = core::pixel_format::bgra;\r
+ desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));\r
+ auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);\r
if(count >= frame->image_data().size())\r
{ \r
for(size_t y = 0; y < height_; ++y)\r
\r
std::reverse(frames_.begin(), frames_.end());\r
\r
- if(speed_ > 0.0)\r
+ // Set the relative positions of the image fragments.\r
+ for (size_t n = 0; n < frames_.size(); ++n)\r
{\r
- auto offset = format_desc_.width - (width_ % format_desc_.width);\r
- start_offset_[0] = offset * 0.5/static_cast<double>(format_desc_.width);\r
+ double translation = - (static_cast<double>(n) + 1.0);\r
+ frames_[n]->get_frame_transform().fill_translation[0] = translation;\r
+ }\r
+ }\r
+\r
+ CASPAR_LOG(info) << print() << L" Initialized";\r
+ }\r
+\r
+ std::vector<safe_ptr<core::basic_frame>> get_visible()\r
+ {\r
+ std::vector<safe_ptr<core::basic_frame>> result;\r
+ result.reserve(frames_.size());\r
+\r
+ BOOST_FOREACH(auto& frame, frames_)\r
+ {\r
+ auto& fill_translation = frame->get_frame_transform().fill_translation;\r
+\r
+ if (width_ == format_desc_.width)\r
+ {\r
+ auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);\r
+ auto vertical_offset = fill_translation[1] + motion_offset_in_screens;\r
+\r
+ if (vertical_offset < -1.0 || vertical_offset > 1.0)\r
+ {\r
+ continue;\r
+ }\r
}\r
else\r
{\r
- start_offset_[0] = (std::ceil(static_cast<double>(width_) / static_cast<double>(format_desc_.width)) + 1.0) * 0.5;// - 1.5;\r
+ auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);\r
+ auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;\r
+\r
+ if (horizontal_offset < -1.0 || horizontal_offset > 1.0)\r
+ {\r
+ continue;\r
+ }\r
}\r
+\r
+ result.push_back(frame);\r
}\r
+\r
+ return std::move(result);\r
}\r
\r
// frame_producer\r
\r
- virtual safe_ptr<core::basic_frame> receive()\r
- { \r
- delta_ = 1;//+= speed_;\r
-\r
+ safe_ptr<core::basic_frame> render_frame(bool allow_eof)\r
+ {\r
if(frames_.empty())\r
return core::basic_frame::eof();\r
\r
- if(height_ > format_desc_.height)\r
+ auto result = make_safe<core::basic_frame>(get_visible());\r
+ auto& fill_translation = result->get_frame_transform().fill_translation;\r
+\r
+ if (width_ == format_desc_.width)\r
{\r
- if(static_cast<size_t>(std::abs(delta_)) >= height_ - format_desc_.height)\r
+ if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)\r
return core::basic_frame::eof();\r
\r
- for(size_t n = 0; n < frames_.size(); ++n)\r
- frames_[n]->get_image_transform().set_fill_translation(start_offset_[0], start_offset_[1] -0.5*(n+1) + delta_ * 0.5/static_cast<double>(format_desc_.height));\r
+ fill_translation[1] = \r
+ static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)\r
+ + delta_ / static_cast<double>(format_desc_.height);\r
}\r
else\r
{\r
- if(static_cast<size_t>(std::abs(delta_)) >= width_ - format_desc_.width)\r
+ if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)\r
return core::basic_frame::eof();\r
\r
- for(size_t n = 0; n < frames_.size(); ++n)\r
- frames_[n]->get_image_transform().set_fill_translation(start_offset_[0] -0.5*(n+1) + delta_ * 0.5/static_cast<double>(format_desc_.height), start_offset_[1]);\r
+ fill_translation[0] = \r
+ static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)\r
+ + (delta_) / static_cast<double>(format_desc_.width);\r
}\r
\r
- return core::basic_frame(frames_);\r
+ return result;\r
+ }\r
+\r
+ safe_ptr<core::basic_frame> render_frame(bool allow_eof, bool advance_delta)\r
+ {\r
+ auto result = render_frame(allow_eof);\r
+\r
+ if (advance_delta)\r
+ {\r
+ advance();\r
+ }\r
+\r
+ return result;\r
+ }\r
+\r
+ void advance()\r
+ {\r
+ delta_ += speed_;\r
+ }\r
+\r
+ virtual safe_ptr<core::basic_frame> receive(int) override\r
+ {\r
+ if (format_desc_.field_mode == core::field_mode::progressive)\r
+ {\r
+ return last_frame_ = render_frame(true, true);\r
+ }\r
+ else\r
+ {\r
+ auto field1 = render_frame(true, true);\r
+ auto field2 = render_frame(true, false);\r
+\r
+ if (field1 != core::basic_frame::eof() && field2 == core::basic_frame::eof())\r
+ {\r
+ field2 = render_frame(false, true);\r
+ }\r
+ else\r
+ {\r
+ advance();\r
+ }\r
+\r
+ last_frame_ = field2;\r
+\r
+ return core::basic_frame::interlace(field1, field2, format_desc_.field_mode);\r
+ }\r
+ }\r
+\r
+ virtual safe_ptr<core::basic_frame> last_frame() const override\r
+ {\r
+ return last_frame_;\r
}\r
\r
- virtual std::wstring print() const\r
+ virtual std::wstring print() const override\r
{\r
return L"image_scroll_producer[" + filename_ + L"]";\r
}\r
\r
- virtual int64_t nb_frames() const \r
+ virtual boost::property_tree::wptree info() const override\r
+ {\r
+ boost::property_tree::wptree info;\r
+ info.add(L"type", L"image-scroll-producer");\r
+ info.add(L"filename", filename_);\r
+ return info;\r
+ }\r
+\r
+ virtual uint32_t nb_frames() const override\r
{\r
- if(height_ > format_desc_.height)\r
+ if(width_ == format_desc_.width)\r
{\r
- auto length = (height_ - format_desc_.height);\r
- return (length/std::abs(speed_) + length % std::abs(delta_));\r
+ auto length = (height_ + format_desc_.height * 2);\r
+ return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));\r
}\r
else\r
{\r
- auto length = (width_ - format_desc_.width);\r
- auto result = (length/std::abs(speed_) + length % std::abs(delta_));\r
- return result;\r
+ auto length = (width_ + format_desc_.width * 2);\r
+ return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));\r
}\r
}\r
};\r
\r
-safe_ptr<core::frame_producer> create_image_scroll_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
+safe_ptr<core::frame_producer> create_scroll_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
{\r
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
std::wstring filename = env::media_folder() + L"\\" + params[0];\r
if(ext == extensions.end())\r
return core::frame_producer::empty();\r
\r
- size_t speed = 0;\r
+ double speed = 0.0;\r
+ double duration = 0.0;\r
auto speed_it = std::find(params.begin(), params.end(), L"SPEED");\r
if(speed_it != params.end())\r
{\r
if(++speed_it != params.end())\r
- speed = boost::lexical_cast<int>(*speed_it);\r
+ speed = boost::lexical_cast<double>(*speed_it);\r
}\r
\r
- if(speed == 0)\r
+ if (speed == 0)\r
+ {\r
+ auto duration_it = std::find(params.begin(), params.end(), L"DURATION");\r
+\r
+ if (duration_it != params.end() && ++duration_it != params.end())\r
+ {\r
+ duration = boost::lexical_cast<double>(*duration_it);\r
+ }\r
+ }\r
+\r
+ if(speed == 0 && duration == 0)\r
return core::frame_producer::empty();\r
\r
- return make_safe<image_scroll_producer>(frame_factory, filename + L"." + *ext, speed);\r
-}\r
+ int motion_blur_px = 0;\r
+ auto blur_it = std::find(params.begin(), params.end(), L"BLUR");\r
+ if (blur_it != params.end() && ++blur_it != params.end())\r
+ {\r
+ motion_blur_px = boost::lexical_cast<int>(*blur_it);\r
+ }\r
+\r
+ bool premultiply_with_alpha = std::find(params.begin(), params.end(), L"PREMULTIPLY") != params.end();\r
\r
+ return create_producer_print_proxy(make_safe<image_scroll_producer>(\r
+ frame_factory, \r
+ filename + L"." + *ext, \r
+ -speed, \r
+ -duration, \r
+ motion_blur_px, \r
+ premultiply_with_alpha));\r
+}\r
\r
-}
\ No newline at end of file
+}}
\ No newline at end of file