return shared_ptr<T>(std::make_shared<T>(std::forward<P0>(p0), std::forward<P1>(p1), std::forward<P2>(p2), std::forward<P3>(p3), std::forward<P4>(p4), std::forward<P5>(p5)));\r
}\r
\r
+template<typename T, typename P0, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6>\r
+shared_ptr<T> make_shared(P0&& p0, P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6)\r
+{\r
+ return shared_ptr<T>(std::make_shared<T>(std::forward<P0>(p0), std::forward<P1>(p1), std::forward<P2>(p2), std::forward<P3>(p3), std::forward<P4>(p4), std::forward<P5>(p5), std::forward<P6>(p6)));\r
+}\r
+\r
+template<typename T, typename P0, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7>\r
+shared_ptr<T> make_shared(P0&& p0, P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7)\r
+{\r
+ return shared_ptr<T>(std::make_shared<T>(std::forward<P0>(p0), std::forward<P1>(p1), std::forward<P2>(p2), std::forward<P3>(p3), std::forward<P4>(p4), std::forward<P5>(p5), std::forward<P6>(p6), std::forward<P7>(p7)));\r
+}\r
+\r
template<typename T>\r
shared_ptr<T>::shared_ptr() \r
: p_(make_shared<T>())\r
<ClInclude Include="image.h" />\r
<ClInclude Include="producer\image_producer.h" />\r
<ClInclude Include="producer\image_scroll_producer.h" />\r
+ <ClInclude Include="util\image_algorithms.h" />\r
<ClInclude Include="util\image_loader.h" />\r
+ <ClInclude Include="util\image_view.h" />\r
</ItemGroup>\r
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
<ImportGroup Label="ExtensionTargets">\r
<ClInclude Include="consumer\image_consumer.h">\r
<Filter>source\consumer</Filter>\r
</ClInclude>\r
+ <ClInclude Include="util\image_algorithms.h">\r
+ <Filter>source\util</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="util\image_view.h">\r
+ <Filter>source\util</Filter>\r
+ </ClInclude>\r
</ItemGroup>\r
</Project>
\ No newline at end of file
* 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 <common/log.h>\r
#include <common/except.h>\r
#include <common/array.h>\r
+#include <common/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/scoped_array.hpp>\r
\r
#include <algorithm>\r
#include <array>\r
int width_;\r
int height_;\r
\r
- int delta_;\r
- int speed_;\r
+ double delta_;\r
+ double speed_;\r
\r
- std::array<double, 2> start_offset_;\r
+ int start_offset_x_;\r
+ int start_offset_y_;\r
+ bool progressive_;\r
\r
- explicit image_scroll_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::wstring& filename, int speed) \r
+ explicit image_scroll_producer(\r
+ const spl::shared_ptr<core::frame_factory>& frame_factory, \r
+ const core::video_format_desc& format_desc, \r
+ const std::wstring& filename, \r
+ double speed,\r
+ double duration,\r
+ int motion_blur_px = 0,\r
+ bool premultiply_with_alpha = false,\r
+ bool progressive = false)\r
: filename_(filename)\r
, delta_(0)\r
, format_desc_(format_desc)\r
, speed_(speed)\r
+ , start_offset_x_(0)\r
+ , start_offset_y_(0)\r
+ , progressive_(progressive)\r
{\r
- start_offset_.assign(0.0);\r
-\r
auto bitmap = load_image(filename_);\r
FreeImage_FlipVertical(bitmap.get());\r
\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
+ {\r
+ start_offset_y_ = height_ + format_desc_.height;\r
+ }\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
auto 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 (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
+ core::tweener blur_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(height_ > format_desc_.height)\r
+ if (vertical)\r
{\r
+ int n = 1;\r
+\r
while(count > 0)\r
{\r
core::pixel_format_desc desc = core::pixel_format::bgra;\r
std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);\r
count = 0;\r
}\r
- \r
- frames_.push_back(core::draw_frame(std::move(frame)));\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
+ core::draw_frame draw_frame(std::move(frame));\r
+\r
+ // Set the relative position to the other image fragments\r
+ draw_frame.transform().image_transform.fill_translation[1] = - n++;\r
+\r
+ frames_.push_back(draw_frame);\r
}\r
}\r
- else\r
+ else if (horizontal)\r
{\r
int i = 0;\r
while(count > 0)\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].transform().image_transform.fill_translation[0] = translation;\r
+ }\r
+ }\r
+\r
+ CASPAR_LOG(info) << print() << L" Initialized";\r
+ }\r
+\r
+ std::vector<core::draw_frame> get_visible()\r
+ {\r
+ std::vector<core::draw_frame> result;\r
+ result.reserve(frames_.size());\r
+\r
+ BOOST_FOREACH(auto& frame, frames_)\r
+ {\r
+ auto& fill_translation = frame.transform().image_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
- CASPAR_LOG(info) << print() << L" Initialized";\r
+ return std::move(result);\r
}\r
\r
// frame_producer\r
-\r
- core::draw_frame receive_impl() override\r
- { \r
- delta_ += speed_;\r
-\r
+ core::draw_frame render_frame(bool allow_eof)\r
+ {\r
if(frames_.empty())\r
- return core::draw_frame::late();\r
+ return core::draw_frame::empty();\r
\r
- if(height_ > format_desc_.height)\r
+ core::draw_frame result(get_visible());\r
+ auto& fill_translation = result.transform().image_transform.fill_translation;\r
+\r
+ if (width_ == format_desc_.width)\r
{\r
- if(static_cast<int>(std::abs(delta_)) >= height_ - format_desc_.height)\r
- return last_frame();\r
+ if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)\r
+ return core::draw_frame::empty();\r
\r
- for(int n = 0; n < frames_.size(); ++n)\r
- {\r
- frames_[n].transform().image_transform.fill_translation[0] = start_offset_[0];\r
- frames_[n].transform().image_transform.fill_translation[1] = start_offset_[1] - (n+1) + delta_ * 0.5/static_cast<double>(format_desc_.height);\r
- }\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 && allow_eof)\r
+ return core::draw_frame::empty();\r
+\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 result;\r
+ }\r
+\r
+ core::draw_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
+ core::draw_frame receive_impl() override\r
+ {\r
+ core::draw_frame result;\r
+\r
+ if (format_desc_.field_mode == core::field_mode::progressive || progressive_)\r
+ {\r
+ result = render_frame(true, true);\r
}\r
else\r
{\r
- if(static_cast<int>(std::abs(delta_)) >= width_ - format_desc_.width)\r
- return last_frame();\r
+ auto field1 = render_frame(true, true);\r
+ auto field2 = render_frame(true, false);\r
\r
- for(int n = 0; n < frames_.size(); ++n)\r
+ if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())\r
{\r
- frames_[n].transform().image_transform.fill_translation[0] = start_offset_[0] - (n+1) + delta_ * 0.5/static_cast<double>(format_desc_.width); \r
- frames_[n].transform().image_transform.fill_translation[1] = start_offset_[1];\r
+ field2 = render_frame(false, true);\r
}\r
+ else\r
+ {\r
+ advance();\r
+ }\r
+\r
+ result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);\r
}\r
\r
event_subject_ << monitor::event("file/path") % filename_\r
<< monitor::event("delta") % delta_ \r
<< monitor::event("speed") % speed_;\r
\r
- return core::draw_frame(frames_);\r
+ return result;\r
}\r
\r
std::wstring print() const override\r
\r
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
spl::shared_ptr<core::frame_producer> create_scroll_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, 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
+ 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
\r
auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool\r
{ \r
- return boost::filesystem::is_regular_file(boost::filesystem::wpath(filename).replace_extension(ex));\r
+ return boost::filesystem::is_regular_file(boost::filesystem::path(filename).replace_extension(ex));\r
});\r
\r
if(ext == extensions.end())\r
return core::frame_producer::empty();\r
\r
- int 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
+ {\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)\r
+ if(speed == 0 && duration == 0)\r
return core::frame_producer::empty();\r
\r
- return spl::make_shared<image_scroll_producer>(frame_factory, format_desc, filename + L"." + *ext, speed);\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
+ bool progressive = std::find(params.begin(), params.end(), L"PROGRESSIVE") != params.end();\r
+\r
+ return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(\r
+ frame_factory, \r
+ format_desc, \r
+ filename + *ext, \r
+ -speed, \r
+ -duration, \r
+ motion_blur_px, \r
+ premultiply_with_alpha,\r
+ progressive));\r
}\r
\r
}}
\ No newline at end of file
--- /dev/null
+/*\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
+*\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
+*\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: Helge Norberg, helge.norberg@svt.se\r
+*/\r
+\r
+#pragma once\r
+\r
+#include <common/tweener.h>\r
+\r
+#include <cmath>\r
+#include <boost/foreach.hpp>\r
+\r
+namespace caspar { namespace image {\r
+\r
+/**\r
+ * Helper for calculating the color of a pixel given any number of of other\r
+ * pixels (each with their own weight).\r
+ */\r
+class rgba_weighting\r
+{\r
+ int r, g, b, a;\r
+ int total_weight;\r
+public:\r
+ rgba_weighting()\r
+ : r(0), g(0), b(0), a(0), total_weight(0)\r
+ {\r
+ }\r
+\r
+ template<class RGBAPixel>\r
+ inline void add_pixel(const RGBAPixel& pixel, uint8_t weight)\r
+ {\r
+ r += pixel.r() * weight;\r
+ g += pixel.g() * weight;\r
+ b += pixel.b() * weight;\r
+ a += pixel.a() * weight;\r
+\r
+ total_weight += weight;\r
+ }\r
+\r
+ template<class RGBAPixel>\r
+ inline void store_result(RGBAPixel& pixel)\r
+ {\r
+ pixel.r() = static_cast<uint8_t>(r / total_weight);\r
+ pixel.g() = static_cast<uint8_t>(g / total_weight);\r
+ pixel.b() = static_cast<uint8_t>(b / total_weight);\r
+ pixel.a() = static_cast<uint8_t>(a / total_weight);\r
+ }\r
+};\r
+\r
+template<class T>\r
+std::vector<T> get_tweened_values(const core::tweener& tweener, size_t num_values, T from, T to)\r
+{\r
+ std::vector<T> result;\r
+ result.reserve(num_values);\r
+\r
+ double start = static_cast<double>(from);\r
+ double delta = static_cast<double>(to - from);\r
+ double duration = static_cast<double>(num_values);\r
+\r
+ for (double t = 0; t < duration; ++t)\r
+ {\r
+ result.push_back(static_cast<T>(tweener(t, start, delta, duration - 1.0)));\r
+ }\r
+\r
+ return std::move(result);\r
+}\r
+\r
+/**\r
+ * Blur a source image and store the blurred result in a destination image.\r
+ * <p>\r
+ * The blur is done by weighting each relative pixel from a destination pixel\r
+ * position using a vector of relative x-y pairs. The further away a related\r
+ * pixel is the less weight it gets. A tweener is used to calculate the actual\r
+ * weights of each related pixel.\r
+ *\r
+ * @param src The source view. Has to model the ImageView\r
+ * concept and have a pixel type modelling the\r
+ * RGBAPixel concept.\r
+ * @param dst The destination view. Has to model the\r
+ * ImageView concept and have a pixel type\r
+ * modelling the RGBAPixel concept.\r
+ * @param motion_trail_coordinates The relative x-y positions to weight in for\r
+ * each pixel.\r
+ * @param tweener The tweener to use for calculating the\r
+ * weights of each relative position in the\r
+ * motion trail.\r
+ */\r
+template<class SrcView, class DstView>\r
+void blur(\r
+ const SrcView& src,\r
+ DstView& dst,\r
+ const std::vector<std::pair<int, int>> motion_trail_coordinates, \r
+ const core::tweener& tweener)\r
+{\r
+ auto blur_px = motion_trail_coordinates.size();\r
+ auto tweened_weights_y = get_tweened_values<uint8_t>(tweener, blur_px + 2, 255, 0);\r
+ tweened_weights_y.pop_back();\r
+ tweened_weights_y.erase(tweened_weights_y.begin());\r
+\r
+ auto src_end = src.end();\r
+ auto dst_iter = dst.begin();\r
+\r
+ for (auto src_iter = src.begin(); src_iter != src_end; ++src_iter, ++dst_iter)\r
+ {\r
+ rgba_weighting w;\r
+\r
+ for (int i = 0; i < blur_px; ++i)\r
+ {\r
+ auto& coordinate = motion_trail_coordinates[i];\r
+ auto other_pixel = src.relative(src_iter, coordinate.first, coordinate.second);\r
+\r
+ if (other_pixel == nullptr)\r
+ break;\r
+\r
+ w.add_pixel(*other_pixel, tweened_weights_y[i]);\r
+ }\r
+\r
+ w.add_pixel(*src_iter, 255);\r
+ w.store_result(*dst_iter);\r
+ }\r
+}\r
+\r
+/**\r
+ * Calculate relative x-y coordinates of a straight line with a given angle and\r
+ * a given number of points.\r
+ *\r
+ * @param num_pixels The number of pixels/points to create.\r
+ * @param angle_radians The angle of the line in radians.\r
+ *\r
+ * @return the x-y pairs.\r
+ */\r
+std::vector<std::pair<int, int>> get_line_points(int num_pixels, double angle_radians)\r
+{\r
+ std::vector<std::pair<int, int>> line_points;\r
+ line_points.reserve(num_pixels);\r
+\r
+ double delta_x = std::cos(angle_radians);\r
+ double delta_y = -std::sin(angle_radians); // In memory is revered\r
+ double max_delta = std::max(std::abs(delta_x), std::abs(delta_y));\r
+ double amplification = 1.0 / max_delta;\r
+ delta_x *= amplification;\r
+ delta_y *= amplification;\r
+\r
+ for (int i = 1; i <= num_pixels; ++i)\r
+ line_points.push_back(std::make_pair(\r
+ static_cast<int>(std::floor(delta_x * static_cast<double>(i) + 0.5)), \r
+ static_cast<int>(std::floor(delta_y * static_cast<double>(i) + 0.5))));\r
+\r
+ return std::move(line_points);\r
+}\r
+\r
+/**\r
+ * Directionally blur a source image modelling the ImageView concept and store\r
+ * the blurred image to a destination image also modelling the ImageView\r
+ * concept.\r
+ * <p>\r
+ * The pixel type of the views must model the RGBAPixel concept.\r
+ *\r
+ * @param src The source image view. Has to model the ImageView\r
+ * concept and have a pixel type that models RGBAPixel.\r
+ * @param dst The destiation image view. Has to model the ImageView\r
+ * concept and have a pixel type that models RGBAPixel.\r
+ * @param angle_radians The angle in radians to directionally blur the image.\r
+ * @param blur_px The number of pixels of the blur.\r
+ * @param tweener The tweener to use to create a pixel weighting curve\r
+ * with.\r
+ */\r
+template<class SrcView, class DstView>\r
+void blur(\r
+ const SrcView& src,\r
+ DstView& dst,\r
+ double angle_radians,\r
+ int blur_px, \r
+ const core::tweener& tweener)\r
+{\r
+ auto motion_trail = get_line_points(blur_px, angle_radians);\r
+\r
+ blur(src, dst, motion_trail, tweener);\r
+}\r
+\r
+/**\r
+ * Premultiply with alpha for each pixel in an ImageView. The modifications is\r
+ * done in place. The pixel type of the ImageView must model the RGBAPixel\r
+ * concept.\r
+ *\r
+ * @param view_to_modify The image view to premultiply in place. Has to model\r
+ * the ImageView concept and have a pixel type that\r
+ * models RGBAPixel.\r
+ */\r
+template<class SrcDstView>\r
+void premultiply(SrcDstView& view_to_modify)\r
+{\r
+ std::for_each(view_to_modify.begin(), view_to_modify.end(), [&](SrcDstView::pixel_type& pixel)\r
+ {\r
+ int alpha = static_cast<int>(pixel.a());\r
+\r
+ if (alpha != 255) // Performance optimization\r
+ {\r
+ // We don't event try to premultiply 0 since it will be unaffected.\r
+ if (pixel.r())\r
+ pixel.r() = static_cast<uint8_t>(static_cast<int>(pixel.r()) * alpha / 255);\r
+\r
+ if (pixel.g())\r
+ pixel.g() = static_cast<uint8_t>(static_cast<int>(pixel.g()) * alpha / 255);\r
+\r
+ if (pixel.b())\r
+ pixel.b() = static_cast<uint8_t>(static_cast<int>(pixel.b()) * alpha / 255);\r
+ }\r
+ });\r
+}\r
+\r
+}}\r
--- /dev/null
+/*\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
+*\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
+*\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: Helge Norberg, helge.norberg@svt.se\r
+*/\r
+\r
+#pragma once\r
+\r
+#include <boost/iterator/filter_iterator.hpp>\r
+\r
+namespace caspar { namespace image {\r
+\r
+/**\r
+ * A POD pixel with a compatible memory layout as a 8bit BGRA pixel (32bits in\r
+ * total).\r
+ * <p>\r
+ * Models the PackedPixel concept used by for example image_view. Also models\r
+ * the RGBAPixel concept which does not care about the order between RGBA but\r
+ * only requires that all 4 channel has accessors.\r
+ */\r
+class bgra_pixel\r
+{\r
+ uint8_t b_;\r
+ uint8_t g_;\r
+ uint8_t r_;\r
+ uint8_t a_;\r
+public:\r
+ bgra_pixel(uint8_t b = 0, uint8_t g = 0, uint8_t r = 0, uint8_t a = 0) : b_(b), g_(g), r_(r), a_(a) {}\r
+ inline const uint8_t& b() const { return b_; }\r
+ inline uint8_t& b() { return b_; }\r
+ inline const uint8_t& g() const { return g_; }\r
+ inline uint8_t& g() { return g_; }\r
+ inline const uint8_t& r() const { return r_; }\r
+ inline uint8_t& r() { return r_; }\r
+ inline const uint8_t& a() const { return a_; }\r
+ inline uint8_t& a() { return a_; }\r
+};\r
+\r
+template<class PackedPixel> class image_sub_view;\r
+\r
+/**\r
+ * An image view abstracting raw packed pixel data\r
+ * <p>\r
+ * This is only a view, it does not own the data.\r
+ * <p>\r
+ * Models the the ImageView concept.\r
+ */\r
+template<class PackedPixel>\r
+class image_view\r
+{\r
+public:\r
+ typedef PackedPixel pixel_type;\r
+\r
+ image_view(void* raw_start, int width, int height)\r
+ : begin_(static_cast<PackedPixel*>(raw_start))\r
+ , end_(begin_ + (width * height))\r
+ , width_(width)\r
+ , height_(height)\r
+ {\r
+ }\r
+\r
+ PackedPixel* begin()\r
+ {\r
+ return begin_;\r
+ }\r
+\r
+ const PackedPixel* begin() const\r
+ {\r
+ return begin_;\r
+ }\r
+\r
+ PackedPixel* end()\r
+ {\r
+ return end_;\r
+ }\r
+\r
+ const PackedPixel* end() const\r
+ {\r
+ return end_;\r
+ }\r
+\r
+ template<class PackedPixelIter>\r
+ inline PackedPixel* relative(PackedPixelIter to, int delta_x, int delta_y)\r
+ {\r
+ auto pixel_distance = delta_x + width_ * delta_y;\r
+ PackedPixel* to_address = &(*to);\r
+ auto result = to_address + pixel_distance;\r
+\r
+ if (result < begin_ || result >= end_)\r
+ return nullptr;\r
+ else\r
+ return result;\r
+ }\r
+\r
+ template<class PackedPixelIter>\r
+ inline const PackedPixel* relative(PackedPixelIter to, int delta_x, int delta_y) const\r
+ {\r
+ //auto x_distance\r
+ auto pixel_distance = delta_x + width_ * delta_y;\r
+ const PackedPixel* to_address = &(*to);\r
+ auto result = to_address + pixel_distance;\r
+\r
+ /*if (delta_x != 0)\r
+ {\r
+ auto actual_delta_y = result % width_\r
+ }*/\r
+\r
+ if (result < begin_ || result >= end_)\r
+ return nullptr;\r
+ else\r
+ return result;\r
+ }\r
+\r
+ int width() const\r
+ {\r
+ return width_;\r
+ }\r
+\r
+ int height() const\r
+ {\r
+ return height_;\r
+ }\r
+\r
+ image_sub_view<PackedPixel> subview(int x, int y, int width, int height)\r
+ {\r
+ return image_sub_view<PackedPixel>(*this, x, y, width, height);\r
+ }\r
+\r
+ const image_sub_view<PackedPixel> subview(int x, int y, int width, int height) const\r
+ {\r
+ return image_sub_view<PackedPixel>(*this, x, y, width, height);\r
+ }\r
+private:\r
+ PackedPixel* begin_;\r
+ PackedPixel* end_;\r
+ int width_;\r
+ int height_;\r
+};\r
+\r
+template<class PackedPixel>\r
+class is_within_view\r
+{\r
+public:\r
+ is_within_view(const PackedPixel* begin, int width, int stride)\r
+ : begin_(begin)\r
+ , width_(width)\r
+ , stride_(stride)\r
+ , no_check_(width == stride)\r
+ {\r
+ }\r
+\r
+ inline bool operator()(const PackedPixel& pixel) const\r
+ {\r
+ if (no_check_)\r
+ return true;\r
+\r
+ const PackedPixel* position = &pixel;\r
+ int distance_from_row_start = (position - begin_) % stride_;\r
+\r
+ return distance_from_row_start < width_;\r
+ }\r
+private:\r
+ const PackedPixel* begin_;\r
+ int width_;\r
+ int stride_;\r
+ bool no_check_;\r
+};\r
+\r
+template <class PackedPixel>\r
+struct image_stride_iterator : public boost::filter_iterator<is_within_view<PackedPixel>, PackedPixel*>\r
+{\r
+ image_stride_iterator(PackedPixel* begin, PackedPixel* end, int width, int stride)\r
+ : boost::filter_iterator<is_within_view<PackedPixel>, PackedPixel*>::filter_iterator(\r
+ is_within_view<PackedPixel>(begin, width, stride), begin, end)\r
+ {\r
+ }\r
+};\r
+\r
+/**\r
+ * A sub view created from an image_view.\r
+ * <p>\r
+ * This also models the ImageView concept.\r
+ */\r
+template<class PackedPixel>\r
+class image_sub_view\r
+{\r
+public:\r
+ typedef PackedPixel pixel_type;\r
+\r
+ image_sub_view(image_view<PackedPixel>& root_view, int x, int y, int width, int height)\r
+ : root_view_(root_view)\r
+ , relative_to_root_x_(x)\r
+ , relative_to_root_y_(y)\r
+ , width_(width)\r
+ , height_(height)\r
+ , raw_begin_(root_view.relative(root_view.begin(), x, y))\r
+ , raw_end_(root_view.relative(raw_begin_, width - 1, height_ - 1) + 1)\r
+ {\r
+ }\r
+\r
+ image_stride_iterator<PackedPixel> begin()\r
+ {\r
+ return image_stride_iterator<PackedPixel>(raw_begin_, raw_end_, width_, root_view_.width());\r
+ }\r
+\r
+ image_stride_iterator<const PackedPixel> begin() const\r
+ {\r
+ return image_stride_iterator<const PackedPixel>(raw_begin_, raw_end_, width_, root_view_.width());\r
+ }\r
+\r
+ image_stride_iterator<PackedPixel> end()\r
+ {\r
+ return image_stride_iterator<PackedPixel>(raw_end_, raw_end_, width_, root_view_.width());\r
+ }\r
+\r
+ image_stride_iterator<const PackedPixel> end() const\r
+ {\r
+ return image_stride_iterator<const PackedPixel>(raw_end_, raw_end_, width_, root_view_.width());\r
+ }\r
+\r
+ template<class PackedPixelIter>\r
+ PackedPixel* relative(PackedPixelIter to, int delta_x, int delta_y)\r
+ {\r
+ return root_view_.relative(to, delta_x, delta_y);\r
+ }\r
+\r
+ template<class PackedPixelIter>\r
+ const PackedPixel* relative(PackedPixelIter to, int delta_x, int delta_y) const\r
+ {\r
+ return root_view_.relative(to, delta_x, delta_y);\r
+ }\r
+\r
+ int width() const\r
+ {\r
+ return width_;\r
+ }\r
+\r
+ int height() const\r
+ {\r
+ return height_;\r
+ }\r
+\r
+ image_sub_view<PackedPixel> subview(int x, int y, int width, int height)\r
+ {\r
+ return root_view_.subview(relative_to_root_x_ + x, relative_to_root_y_ + y, width, height);\r
+ }\r
+\r
+ const image_sub_view<PackedPixel> subview(int x, int y, int width, int height) const\r
+ {\r
+ return root_view_.subview(relative_to_root_x_ + x, relative_to_root_y_ + y, width, height);\r
+ }\r
+private:\r
+ image_view<PackedPixel> root_view_;\r
+ int relative_to_root_x_;\r
+ int relative_to_root_y_;\r
+ int width_;\r
+ int height_;\r
+ PackedPixel* raw_begin_;\r
+ PackedPixel* raw_end_;\r
+};\r
+\r
+}}\r