]> git.sesse.net Git - casparcg/blobdiff - modules/image/producer/image_scroll_producer.cpp
[image_scroll_producer]:
[casparcg] / modules / image / producer / image_scroll_producer.cpp
index 3140f2c11af00ba7403967875f57a8a795f7019b..83fa31ba21f98a8d5e6b18b3378c06a6789748ee 100644 (file)
 #include <core/frame/frame_factory.h>
 #include <core/frame/frame_transform.h>
 #include <core/frame/pixel_format.h>
+#include <core/frame/audio_channel_layout.h>
 #include <core/monitor/monitor.h>
+#include <core/help/help_sink.h>
+#include <core/help/help_repository.h>
 
 #include <common/env.h>
 #include <common/log.h>
 #include <common/array.h>
 #include <common/tweener.h>
 #include <common/param.h>
+#include <common/os/filesystem.h>
+#include <common/future.h>
 
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/property_tree/ptree.hpp>
 #include <boost/scoped_array.hpp>
+#include <boost/date_time.hpp>
+#include <boost/date_time/posix_time/ptime.hpp>
 
 #include <algorithm>
 #include <array>
+#include <cstdint>
 
 namespace caspar { namespace image {
                
-struct image_scroll_producer : public core::frame_producer_base
-{      
-       core::monitor::subject                  monitor_subject_;
+// Like tweened_transform but for speed
+class speed_tweener
+{
+       double  source_         = 0.0;
+       double  dest_           = 0.0;
+       int             duration_       = 0;
+       int             time_           = 0;
+       tweener tweener_;
+public:
+       speed_tweener() = default;
+       speed_tweener(
+                       double source,
+                       double dest,
+                       int duration,
+                       const tweener& tween)
+               : source_(source)
+               , dest_(dest)
+               , duration_(duration)
+               , time_(0)
+               , tweener_(tween)
+       {
+       }
+
+       double dest() const
+       {
+               return dest_;
+       }
+
+       double fetch() const
+       {
+               if (time_ == duration_)
+                       return dest_;
 
-       const std::wstring                              filename_;
-       std::vector<core::draw_frame>   frames_;
-       core::video_format_desc                 format_desc_;
-       int                                                             width_;
-       int                                                             height_;
-       core::constraints                               constraints_;
+               double delta = dest_ - source_;
+               double result = tweener_(time_, source_, delta, duration_);
 
-       double                                                  delta_                          = 0.0;
-       double                                                  speed_;
+               return result;
+       }
 
-       int                                                             start_offset_x_         = 0;
-       int                                                             start_offset_y_         = 0;
-       bool                                                    progressive_;
+       double fetch_and_tick()
+       {
+               time_ = std::min(time_ + 1, duration_);
+               return fetch();
+       }
+};
+       
+struct image_scroll_producer : public core::frame_producer_base
+{      
+       core::monitor::subject                                          monitor_subject_;
+
+       const std::wstring                                                      filename_;
+       std::vector<core::draw_frame>                           frames_;
+       core::video_format_desc                                         format_desc_;
+       int                                                                                     width_;
+       int                                                                                     height_;
+       core::constraints                                                       constraints_;
+
+       double                                                                          delta_                          = 0.0;
+       speed_tweener                                                           speed_;
+       boost::optional<boost::posix_time::ptime>       end_time_;
+
+       int                                                                                     start_offset_x_         = 0;
+       int                                                                                     start_offset_y_         = 0;
+       bool                                                                            progressive_;
        
        explicit image_scroll_producer(
                        const spl::shared_ptr<core::frame_factory>& frame_factory,
                        const core::video_format_desc& format_desc,
                        const std::wstring& filename,
-                       double speed,
+                       double s,
                        double duration,
+                       boost::optional<boost::posix_time::ptime> end_time,
                        int motion_blur_px = 0,
                        bool premultiply_with_alpha = false,
                        bool progressive = false)
                : filename_(filename)
                , format_desc_(format_desc)
-               , speed_(speed)
+               , end_time_(std::move(end_time))
                , progressive_(progressive)
        {
+               double speed = s;
+
+               if (end_time_)
+                       speed = -1.0;
+
                auto bitmap = load_image(filename_);
                FreeImage_FlipVertical(bitmap.get());
 
@@ -96,44 +157,29 @@ struct image_scroll_producer : public core::frame_producer_base
                bool horizontal = height_ == format_desc_.height;
 
                if (!vertical && !horizontal)
-                       BOOST_THROW_EXCEPTION(
-                               caspar::invalid_argument() << msg_info("Neither width nor height matched the video resolution"));
+                       CASPAR_THROW_EXCEPTION(caspar::user_error()
+                                       << msg_info("Neither width nor height matched the video resolution"));
+
+               if (duration != 0.0)
+                       speed = speed_from_duration(duration);
 
                if (vertical)
                {
-                       if (duration != 0.0)
-                       {
-                               double total_num_pixels = format_desc_.height * 2 + height_;
-
-                               speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
-
-                               if (std::abs(speed_) > 1.0)
-                                       speed_ = std::ceil(speed_);
-                       }
-
-                       if (speed_ < 0.0)
+                       if (speed < 0.0)
                        {
                                start_offset_y_ = height_ + format_desc_.height;
                        }
                }
                else
                {
-                       if (duration != 0.0)
-                       {
-                               double total_num_pixels = format_desc_.width * 2 + width_;
-
-                               speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
-
-                               if (std::abs(speed_) > 1.0)
-                                       speed_ = std::ceil(speed_);
-                       }
-
-                       if (speed_ > 0.0)
+                       if (speed > 0.0)
                                start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
                        else
                                start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
                }
 
+               speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
+
                auto bytes = FreeImage_GetBits(bitmap.get());
                auto count = width_*height_*4;
                image_view<bgra_pixel> original_view(bytes, width_, height_);
@@ -147,7 +193,7 @@ struct image_scroll_producer : public core::frame_producer_base
                {
                        double angle = 3.14159265 / 2; // Up
 
-                       if (horizontal && speed_ < 0)
+                       if (horizontal && speed < 0)
                                angle *= 2; // Left
                        else if (vertical && speed > 0)
                                angle *= 3; // Down
@@ -156,7 +202,7 @@ struct image_scroll_producer : public core::frame_producer_base
 
                        blurred_copy.reset(new uint8_t[count]);
                        image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
-                       core::tweener blur_tweener(L"easeInQuad");
+                       caspar::tweener blur_tweener(L"easeInQuad");
                        blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
                        bytes = blurred_copy.get();
                        bitmap.reset();
@@ -170,7 +216,7 @@ struct image_scroll_producer : public core::frame_producer_base
                        {
                                core::pixel_format_desc desc = core::pixel_format::bgra;
                                desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
-                               auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);
+                               auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
 
                                if(count >= frame.image_data(0).size())
                                {       
@@ -199,7 +245,7 @@ struct image_scroll_producer : public core::frame_producer_base
                        {
                                core::pixel_format_desc desc = core::pixel_format::bgra;
                                desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
-                               auto frame = frame_factory->create_frame(reinterpret_cast<void*>(rand()), desc);
+                               auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
                                if(count >= frame.image_data(0).size())
                                {       
                                        for(int y = 0; y < height_; ++y)
@@ -234,6 +280,39 @@ struct image_scroll_producer : public core::frame_producer_base
                CASPAR_LOG(info) << print() << L" Initialized";
        }
 
+       double get_total_num_pixels() const
+       {
+               bool vertical = width_ == format_desc_.width;
+
+               if (vertical)
+                       return height_ + format_desc_.height;
+               else
+                       return width_ + format_desc_.width;
+       }
+
+       double speed_from_duration(double duration_seconds) const
+       {
+               return get_total_num_pixels() / (duration_seconds * format_desc_.fps * static_cast<double>(format_desc_.field_count));
+       }
+
+       std::future<std::wstring> call(const std::vector<std::wstring>& params) override
+       {
+               auto cmd = params.at(0);
+
+               if (boost::iequals(cmd, L"SPEED"))
+               {
+                       if (params.size() == 1)
+                               return make_ready_future(boost::lexical_cast<std::wstring>(speed_.fetch()));
+
+                       auto val = boost::lexical_cast<double>(params.at(1));
+                       int duration = params.size() > 2 ? boost::lexical_cast<int>(params.at(2)) : 0;
+                       std::wstring tween = params.size() > 3 ? params.at(3) : L"linear";
+                       speed_ = speed_tweener(speed_.fetch(), val, duration, tween);
+               }
+
+               return make_ready_future<std::wstring>(L"");
+       }
+
        std::vector<core::draw_frame> get_visible()
        {
                std::vector<core::draw_frame> result;
@@ -315,7 +394,23 @@ struct image_scroll_producer : public core::frame_producer_base
 
        void advance()
        {
-               delta_ += speed_;
+               if (end_time_)
+               {
+                       boost::posix_time::ptime now(boost::posix_time::second_clock::local_time());
+
+                       auto diff = *end_time_ - now;
+                       auto seconds = diff.total_seconds();
+
+                       set_speed(-speed_from_duration(seconds));
+                       end_time_ = boost::none;
+               }
+               else
+                       delta_ += speed_.fetch_and_tick();
+       }
+
+       void set_speed(double speed)
+       {
+               speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
        }
 
        core::draw_frame receive_impl() override
@@ -345,7 +440,7 @@ struct image_scroll_producer : public core::frame_producer_base
                
                monitor_subject_ << core::monitor::message("/file/path") % filename_
                                                 << core::monitor::message("/delta") % delta_ 
-                                                << core::monitor::message("/speed") % speed_;
+                                                << core::monitor::message("/speed") % speed_.fetch();
 
                return result;
        }
@@ -370,6 +465,7 @@ struct image_scroll_producer : public core::frame_producer_base
                boost::property_tree::wptree info;
                info.add(L"type", L"image-scroll");
                info.add(L"filename", filename_);
+               info.add(L"speed", speed_.fetch());
                return info;
        }
 
@@ -378,12 +474,12 @@ struct image_scroll_producer : public core::frame_producer_base
                if(width_ == format_desc_.width)
                {
                        auto length = (height_ + format_desc_.height * 2);
-                       return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
+                       return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
                }
                else
                {
                        auto length = (width_ + format_desc_.width * 2);
-                       return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
+                       return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
                }
        }
 
@@ -393,7 +489,26 @@ struct image_scroll_producer : public core::frame_producer_base
        }
 };
 
-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)
+void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Scrolls an image either horizontally or vertically.");
+       sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
+       sink.para()
+               ->text(L"Scrolls an image either horizontally or vertically. ")
+               ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
+               ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
+               ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
+       sink.definitions()
+               ->item(L"image_file", L"The image without extension. The file has to have either the same width or the same height as the video format.")
+               ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
+               ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
+               ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
+               ->item(L"progressive", L"When an interlaced video format is used, by default the image is moved every field. This can be overridden by specifying this option, causing the image to only move on full frames.");
+       sink.para()->text(L"If ")->code(L"SPEED [speed]")->text(L" is ommitted, the ordinary ")->see(L"Image Producer")->text(L" will be used instead.");
+       sink.example(L">> PLAY 1-10 cred_1280 SPEED 8 BLUR 2", L"Given that cred_1280 is a as wide as the video mode, this will create a rolling end credits with a little bit of blur and a speed of 8 pixels per frame.");
+}
+
+spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
 {
        static const auto extensions = {
                L".png",
@@ -409,23 +524,36 @@ spl::shared_ptr<core::frame_producer> create_scroll_producer(const spl::shared_p
                L".j2k",
                L".j2c"
        };
-       std::wstring filename = env::media_folder() + L"\\" + params[0];
+       std::wstring filename = env::media_folder() + params.at(0);
        
        auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool
-               {                                       
-                       return boost::filesystem::is_regular_file(boost::filesystem::path(filename).replace_extension(ex));
-               });
+       {
+               auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
+
+               return static_cast<bool>(file);
+       });
 
        if(ext == extensions.end())
                return core::frame_producer::empty();
        
        double duration = 0.0;
        double speed = get_param(L"SPEED", params, 0.0);
+       boost::optional<boost::posix_time::ptime> end_time;
 
        if (speed == 0)
                duration = get_param(L"DURATION", params, 0.0);
 
-       if(speed == 0 && duration == 0)
+       if (duration == 0)
+       {
+               auto end_time_str = get_param(L"END_TIME", params);
+
+               if (!end_time_str.empty())
+               {
+                       end_time = boost::posix_time::time_from_string(u8(end_time_str));
+               }
+       }
+
+       if(speed == 0 && duration == 0 && !end_time)
                return core::frame_producer::empty();
 
        int motion_blur_px = get_param(L"BLUR", params, 0);
@@ -434,14 +562,15 @@ spl::shared_ptr<core::frame_producer> create_scroll_producer(const spl::shared_p
        bool progressive = contains_param(L"PROGRESSIVE", params);
 
        return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(
-               frame_factory, 
-               format_desc, 
-               filename + *ext, 
-               -speed, 
-               -duration, 
-               motion_blur_px, 
-               premultiply_with_alpha,
-               progressive));
+                       dependencies.frame_factory,
+                       dependencies.format_desc,
+                       *caspar::find_case_insensitive(filename + *ext),
+                       -speed,
+                       -duration,
+                       end_time,
+                       motion_blur_px,
+                       premultiply_with_alpha,
+                       progressive));
 }
 
-}}
\ No newline at end of file
+}}