]> git.sesse.net Git - casparcg/commitdiff
Modified the image_scroll_producer
authorhellgore <hellgore@362d55ac-95cf-4e76-9f9a-cbaa9c17b72d>
Wed, 8 Aug 2012 13:46:53 +0000 (13:46 +0000)
committerhellgore <hellgore@362d55ac-95cf-4e76-9f9a-cbaa9c17b72d>
Wed, 8 Aug 2012 13:46:53 +0000 (13:46 +0000)
==================================

* The SPEED parameter is now expressed as pixels to move per frame/field.
  Previously it was expressed as number of half pixels to move per frame.
* The scrolling direction is now reversed so that a positive speed is now
  correct for an ordinary end credits roll.
* Previously both fields in interlaced video formats represented the same
  temporal image position causing non smooth film like motion. This has now
  been corrected so that both fields represents two different temporal image
  positions for smooth field-rate motion instead of frame-rate motion.
* The image now starts at a position just outside of the screen and ends at a
  position just outside of the screen. Previously it started at a strange
  somewhere in the middle of the image position for end credit rolls.
* The SPEED parameter now is a decimal value instead of an integer value.
  using a value of for example 0.5 will move the image one half pixel each
  frame/field placed with subpixel accuracy. SPEED 0.5 is the previous
  equivalent of SPEED -1 since the speed previously was measured in half
  pixels instead of pixels.
* The producer now internally is more exact in the positioning of each
  frame/field. Previously the image could be placed a little bit "off" because
  of double precision rounding errors (now calculates offsets in pixels instead
  of screens, which is more accurate) which could make the image being rendered
  in between two pixels with subpixel accuracy once in a while even though
  speed was 2 or more (no half pixels should be involved).
* Added support for motion blur via a new BLUR parameter with the number of
  pixels to blur as parameter.
* Added support for premultiplying the color channels of the input image with
  its alpha, useful when the software used to creates the image saves with
  straight alpha (caspar expects premultiplied alpha). This is done with the
  parameter PREMULTIPLY.

git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/trunk@3206 362d55ac-95cf-4e76-9f9a-cbaa9c17b72d

modules/image/image.vcxproj
modules/image/image.vcxproj.filters
modules/image/producer/image_scroll_producer.cpp
modules/image/util/image_algorithms.h [new file with mode: 0644]
modules/image/util/image_view.h [new file with mode: 0644]

index 7cb810d3b74bda297fff20db35641f8aae845edd..e539957463e39aadbf2650ecfcb0fe354be32b51 100644 (file)
     <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
index d5c88013878359bf09ebcb9144217e393c300a5c..05665fc739f4d5f69ab3a1c064899501efa46e50 100644 (file)
     <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
index d7a11f65e44480105a75c2b23f852e15f00d114e..37dbf115971f3e7695805a2f6f250ca7158ba448 100644 (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/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 { namespace image {\r
-               \r
+\r
 struct image_scroll_producer : public core::frame_producer\r
 {      \r
        const std::wstring                                                      filename_;\r
@@ -56,21 +63,29 @@ struct image_scroll_producer : public core::frame_producer
        size_t                                                                          width_;\r
        size_t                                                                          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
 \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
@@ -78,11 +93,78 @@ struct image_scroll_producer : public core::frame_producer
                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 (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(height_ > format_desc_.height)\r
+               if (vertical)\r
                {\r
+                       int n = 1;\r
+\r
                        while(count > 0)\r
                        {\r
                                core::pixel_format_desc desc;\r
@@ -101,19 +183,16 @@ struct image_scroll_producer : public core::frame_producer
                                        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
@@ -146,53 +225,126 @@ struct image_scroll_producer : public core::frame_producer
 \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
-               CASPAR_LOG(info) << print() << L" Initialized";\r
+               return std::move(result);\r
        }\r
        \r
        // frame_producer\r
 \r
-       virtual safe_ptr<core::basic_frame> receive(int) override\r
-       {               \r
-               delta_ += 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
-                       {\r
-                               frames_[n]->get_frame_transform().fill_translation[0] = start_offset_[0];\r
-                               frames_[n]->get_frame_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)\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
+                       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
+       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
-                               frames_[n]->get_frame_transform().fill_translation[0] = start_offset_[0] - (n+1) + delta_ * 0.5/static_cast<double>(format_desc_.width);                                \r
-                               frames_[n]->get_frame_transform().fill_translation[1] = start_offset_[1];\r
+                               field2 = render_frame(false, true);\r
+                       }\r
+                       else\r
+                       {\r
+                               advance();\r
                        }\r
-               }\r
 \r
-               return last_frame_ = make_safe<core::basic_frame>(frames_);\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
@@ -215,16 +367,15 @@ struct image_scroll_producer : public core::frame_producer
 \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
@@ -242,19 +393,44 @@ safe_ptr<core::frame_producer> create_scroll_producer(const safe_ptr<core::frame
        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
+       {\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 create_producer_print_proxy(\r
-                       make_safe<image_scroll_producer>(frame_factory, 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
+\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
diff --git a/modules/image/util/image_algorithms.h b/modules/image/util/image_algorithms.h
new file mode 100644 (file)
index 0000000..22845d4
--- /dev/null
@@ -0,0 +1,233 @@
+/*\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/utility/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(caspar::tweener_t& 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
+       caspar::tweener_t& tweener)\r
+{\r
+       int 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
+                       /*other_pixel = src.relative(src_iter, -coordinate.first, -coordinate.second);\r
+\r
+                       if (other_pixel)\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
+       caspar::tweener_t& 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
diff --git a/modules/image/util/image_view.h b/modules/image/util/image_view.h
new file mode 100644 (file)
index 0000000..422ab3c
--- /dev/null
@@ -0,0 +1,277 @@
+/*\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