]> git.sesse.net Git - casparcg/blob - modules/image/producer/image_scroll_producer.cpp
[image_scroll_producer]:
[casparcg] / modules / image / producer / image_scroll_producer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 * Author: Helge Norberg, helge.norberg@svt.se
21 */
22
23 #include "image_scroll_producer.h"
24
25 #include "../util/image_loader.h"
26 #include "../util/image_view.h"
27 #include "../util/image_algorithms.h"
28
29 #include <core/video_format.h>
30
31 #include <core/frame/frame.h>
32 #include <core/frame/draw_frame.h>
33 #include <core/frame/frame_factory.h>
34 #include <core/frame/frame_transform.h>
35 #include <core/frame/pixel_format.h>
36 #include <core/frame/audio_channel_layout.h>
37 #include <core/monitor/monitor.h>
38 #include <core/help/help_sink.h>
39 #include <core/help/help_repository.h>
40
41 #include <common/env.h>
42 #include <common/log.h>
43 #include <common/except.h>
44 #include <common/array.h>
45 #include <common/tweener.h>
46 #include <common/param.h>
47 #include <common/os/filesystem.h>
48 #include <common/future.h>
49
50 #include <boost/filesystem.hpp>
51 #include <boost/lexical_cast.hpp>
52 #include <boost/property_tree/ptree.hpp>
53 #include <boost/scoped_array.hpp>
54 #include <boost/date_time.hpp>
55 #include <boost/date_time/posix_time/ptime.hpp>
56
57 #include <algorithm>
58 #include <array>
59 #include <cstdint>
60
61 namespace caspar { namespace image {
62                 
63 // Like tweened_transform but for speed
64 class speed_tweener
65 {
66         double  source_         = 0.0;
67         double  dest_           = 0.0;
68         int             duration_       = 0;
69         int             time_           = 0;
70         tweener tweener_;
71 public:
72         speed_tweener() = default;
73         speed_tweener(
74                         double source,
75                         double dest,
76                         int duration,
77                         const tweener& tween)
78                 : source_(source)
79                 , dest_(dest)
80                 , duration_(duration)
81                 , time_(0)
82                 , tweener_(tween)
83         {
84         }
85
86         double dest() const
87         {
88                 return dest_;
89         }
90
91         double fetch() const
92         {
93                 if (time_ == duration_)
94                         return dest_;
95
96                 double delta = dest_ - source_;
97                 double result = tweener_(time_, source_, delta, duration_);
98
99                 return result;
100         }
101
102         double fetch_and_tick()
103         {
104                 time_ = std::min(time_ + 1, duration_);
105                 return fetch();
106         }
107 };
108         
109 struct image_scroll_producer : public core::frame_producer_base
110 {       
111         core::monitor::subject                                          monitor_subject_;
112
113         const std::wstring                                                      filename_;
114         std::vector<core::draw_frame>                           frames_;
115         core::video_format_desc                                         format_desc_;
116         int                                                                                     width_;
117         int                                                                                     height_;
118         core::constraints                                                       constraints_;
119
120         double                                                                          delta_                          = 0.0;
121         speed_tweener                                                           speed_;
122         boost::optional<boost::posix_time::ptime>       end_time_;
123
124         int                                                                                     start_offset_x_         = 0;
125         int                                                                                     start_offset_y_         = 0;
126         bool                                                                            progressive_;
127         
128         explicit image_scroll_producer(
129                         const spl::shared_ptr<core::frame_factory>& frame_factory,
130                         const core::video_format_desc& format_desc,
131                         const std::wstring& filename,
132                         double s,
133                         double duration,
134                         boost::optional<boost::posix_time::ptime> end_time,
135                         int motion_blur_px = 0,
136                         bool premultiply_with_alpha = false,
137                         bool progressive = false)
138                 : filename_(filename)
139                 , format_desc_(format_desc)
140                 , end_time_(std::move(end_time))
141                 , progressive_(progressive)
142         {
143                 double speed = s;
144
145                 if (end_time_)
146                         speed = -1.0;
147
148                 auto bitmap = load_image(filename_);
149                 FreeImage_FlipVertical(bitmap.get());
150
151                 width_  = FreeImage_GetWidth(bitmap.get());
152                 height_ = FreeImage_GetHeight(bitmap.get());
153                 constraints_.width.set(width_);
154                 constraints_.height.set(height_);
155
156                 bool vertical = width_ == format_desc_.width;
157                 bool horizontal = height_ == format_desc_.height;
158
159                 if (!vertical && !horizontal)
160                         CASPAR_THROW_EXCEPTION(caspar::user_error()
161                                         << msg_info("Neither width nor height matched the video resolution"));
162
163                 if (duration != 0.0)
164                         speed = speed_from_duration(duration);
165
166                 if (vertical)
167                 {
168                         if (speed < 0.0)
169                         {
170                                 start_offset_y_ = height_ + format_desc_.height;
171                         }
172                 }
173                 else
174                 {
175                         if (speed > 0.0)
176                                 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
177                         else
178                                 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
179                 }
180
181                 speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
182
183                 auto bytes = FreeImage_GetBits(bitmap.get());
184                 auto count = width_*height_*4;
185                 image_view<bgra_pixel> original_view(bytes, width_, height_);
186
187                 if (premultiply_with_alpha)
188                         premultiply(original_view);
189
190                 boost::scoped_array<uint8_t> blurred_copy;
191
192                 if (motion_blur_px > 0)
193                 {
194                         double angle = 3.14159265 / 2; // Up
195
196                         if (horizontal && speed < 0)
197                                 angle *= 2; // Left
198                         else if (vertical && speed > 0)
199                                 angle *= 3; // Down
200                         else if (horizontal && speed  > 0)
201                                 angle = 0.0; // Right
202
203                         blurred_copy.reset(new uint8_t[count]);
204                         image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
205                         caspar::tweener blur_tweener(L"easeInQuad");
206                         blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
207                         bytes = blurred_copy.get();
208                         bitmap.reset();
209                 }
210
211                 if (vertical)
212                 {
213                         int n = 1;
214
215                         while(count > 0)
216                         {
217                                 core::pixel_format_desc desc = core::pixel_format::bgra;
218                                 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
219                                 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
220
221                                 if(count >= frame.image_data(0).size())
222                                 {       
223                                         std::copy_n(bytes + count - frame.image_data(0).size(), frame.image_data(0).size(), frame.image_data(0).begin());
224                                         count -= static_cast<int>(frame.image_data(0).size());
225                                 }
226                                 else
227                                 {
228                                         memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());     
229                                         std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);
230                                         count = 0;
231                                 }
232
233                                 core::draw_frame draw_frame(std::move(frame));
234
235                                 // Set the relative position to the other image fragments
236                                 draw_frame.transform().image_transform.fill_translation[1] = - n++;
237
238                                 frames_.push_back(draw_frame);
239                         }
240                 }
241                 else if (horizontal)
242                 {
243                         int i = 0;
244                         while(count > 0)
245                         {
246                                 core::pixel_format_desc desc = core::pixel_format::bgra;
247                                 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
248                                 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
249                                 if(count >= frame.image_data(0).size())
250                                 {       
251                                         for(int y = 0; y < height_; ++y)
252                                                 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, format_desc_.width*4, frame.image_data(0).begin() + y * format_desc_.width*4);
253                                         
254                                         ++i;
255                                         count -= static_cast<int>(frame.image_data(0).size());
256                                 }
257                                 else
258                                 {
259                                         memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());     
260                                         auto width2 = width_ % format_desc_.width;
261                                         for(int y = 0; y < height_; ++y)
262                                                 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, width2*4, frame.image_data(0).begin() + y * format_desc_.width*4);
263
264                                         count = 0;
265                                 }
266                         
267                                 frames_.push_back(core::draw_frame(std::move(frame)));
268                         }
269
270                         std::reverse(frames_.begin(), frames_.end());
271
272                         // Set the relative positions of the image fragments.
273                         for (size_t n = 0; n < frames_.size(); ++n)
274                         {
275                                 double translation = - (static_cast<double>(n) + 1.0);
276                                 frames_[n].transform().image_transform.fill_translation[0] = translation;
277                         }
278                 }
279
280                 CASPAR_LOG(info) << print() << L" Initialized";
281         }
282
283         double get_total_num_pixels() const
284         {
285                 bool vertical = width_ == format_desc_.width;
286
287                 if (vertical)
288                         return height_ + format_desc_.height;
289                 else
290                         return width_ + format_desc_.width;
291         }
292
293         double speed_from_duration(double duration_seconds) const
294         {
295                 return get_total_num_pixels() / (duration_seconds * format_desc_.fps * static_cast<double>(format_desc_.field_count));
296         }
297
298         std::future<std::wstring> call(const std::vector<std::wstring>& params) override
299         {
300                 auto cmd = params.at(0);
301
302                 if (boost::iequals(cmd, L"SPEED"))
303                 {
304                         if (params.size() == 1)
305                                 return make_ready_future(boost::lexical_cast<std::wstring>(speed_.fetch()));
306
307                         auto val = boost::lexical_cast<double>(params.at(1));
308                         int duration = params.size() > 2 ? boost::lexical_cast<int>(params.at(2)) : 0;
309                         std::wstring tween = params.size() > 3 ? params.at(3) : L"linear";
310                         speed_ = speed_tweener(speed_.fetch(), val, duration, tween);
311                 }
312
313                 return make_ready_future<std::wstring>(L"");
314         }
315
316         std::vector<core::draw_frame> get_visible()
317         {
318                 std::vector<core::draw_frame> result;
319                 result.reserve(frames_.size());
320
321                 for (auto& frame : frames_)
322                 {
323                         auto& fill_translation = frame.transform().image_transform.fill_translation;
324
325                         if (width_ == format_desc_.width)
326                         {
327                                 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);
328                                 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;
329
330                                 if (vertical_offset < -1.0 || vertical_offset > 1.0)
331                                 {
332                                         continue;
333                                 }
334                         }
335                         else
336                         {
337                                 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);
338                                 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;
339
340                                 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
341                                 {
342                                         continue;
343                                 }
344                         }
345
346                         result.push_back(frame);
347                 }
348
349                 return std::move(result);
350         }
351         
352         // frame_producer
353         core::draw_frame render_frame(bool allow_eof)
354         {
355                 if(frames_.empty())
356                         return core::draw_frame::empty();
357                 
358                 core::draw_frame result(get_visible());
359                 auto& fill_translation = result.transform().image_transform.fill_translation;
360
361                 if (width_ == format_desc_.width)
362                 {
363                         if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
364                                 return core::draw_frame::empty();
365
366                         fill_translation[1] = 
367                                 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)
368                                 + delta_ / static_cast<double>(format_desc_.height);
369                 }
370                 else
371                 {
372                         if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
373                                 return core::draw_frame::empty();
374
375                         fill_translation[0] = 
376                                 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)
377                                 + (delta_) / static_cast<double>(format_desc_.width);
378                 }
379
380                 return result;
381         }
382
383         core::draw_frame render_frame(bool allow_eof, bool advance_delta)
384         {
385                 auto result = render_frame(allow_eof);
386
387                 if (advance_delta)
388                 {
389                         advance();
390                 }
391
392                 return result;
393         }
394
395         void advance()
396         {
397                 if (end_time_)
398                 {
399                         boost::posix_time::ptime now(boost::posix_time::second_clock::local_time());
400
401                         auto diff = *end_time_ - now;
402                         auto seconds = diff.total_seconds();
403
404                         set_speed(-speed_from_duration(seconds));
405                         end_time_ = boost::none;
406                 }
407                 else
408                         delta_ += speed_.fetch_and_tick();
409         }
410
411         void set_speed(double speed)
412         {
413                 speed_ = speed_tweener(speed, speed, 0, tweener(L"linear"));
414         }
415
416         core::draw_frame receive_impl() override
417         {
418                 core::draw_frame result;
419
420                 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
421                 {
422                         result = render_frame(true, true);
423                 }
424                 else
425                 {
426                         auto field1 = render_frame(true, true);
427                         auto field2 = render_frame(true, false);
428
429                         if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())
430                         {
431                                 field2 = render_frame(false, true);
432                         }
433                         else
434                         {
435                                 advance();
436                         }
437
438                         result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);
439                 }
440                 
441                 monitor_subject_ << core::monitor::message("/file/path") % filename_
442                                                  << core::monitor::message("/delta") % delta_ 
443                                                  << core::monitor::message("/speed") % speed_.fetch();
444
445                 return result;
446         }
447
448         core::constraints& pixel_constraints() override
449         {
450                 return constraints_;
451         }
452                                 
453         std::wstring print() const override
454         {
455                 return L"image_scroll_producer[" + filename_ + L"]";
456         }
457
458         std::wstring name() const override
459         {
460                 return L"image-scroll";
461         }
462
463         boost::property_tree::wptree info() const override
464         {
465                 boost::property_tree::wptree info;
466                 info.add(L"type", L"image-scroll");
467                 info.add(L"filename", filename_);
468                 info.add(L"speed", speed_.fetch());
469                 return info;
470         }
471
472         uint32_t nb_frames() const override
473         {
474                 if(width_ == format_desc_.width)
475                 {
476                         auto length = (height_ + format_desc_.height * 2);
477                         return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
478                 }
479                 else
480                 {
481                         auto length = (width_ + format_desc_.width * 2);
482                         return static_cast<uint32_t>(length / std::abs(speed_.fetch()));// + length % std::abs(delta_));
483                 }
484         }
485
486         core::monitor::subject& monitor_output()
487         {
488                 return monitor_subject_;
489         }
490 };
491
492 void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
493 {
494         sink.short_description(L"Scrolls an image either horizontally or vertically.");
495         sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
496         sink.para()
497                 ->text(L"Scrolls an image either horizontally or vertically. ")
498                 ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
499                 ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
500                 ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
501         sink.definitions()
502                 ->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.")
503                 ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
504                 ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
505                 ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
506                 ->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.");
507         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.");
508         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.");
509 }
510
511 spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
512 {
513         static const auto extensions = {
514                 L".png",
515                 L".tga",
516                 L".bmp",
517                 L".jpg",
518                 L".jpeg",
519                 L".gif",
520                 L".tiff",
521                 L".tif",
522                 L".jp2",
523                 L".jpx",
524                 L".j2k",
525                 L".j2c"
526         };
527         std::wstring filename = env::media_folder() + params.at(0);
528         
529         auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool
530         {
531                 auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
532
533                 return static_cast<bool>(file);
534         });
535
536         if(ext == extensions.end())
537                 return core::frame_producer::empty();
538         
539         double duration = 0.0;
540         double speed = get_param(L"SPEED", params, 0.0);
541         boost::optional<boost::posix_time::ptime> end_time;
542
543         if (speed == 0)
544                 duration = get_param(L"DURATION", params, 0.0);
545
546         if (duration == 0)
547         {
548                 auto end_time_str = get_param(L"END_TIME", params);
549
550                 if (!end_time_str.empty())
551                 {
552                         end_time = boost::posix_time::time_from_string(u8(end_time_str));
553                 }
554         }
555
556         if(speed == 0 && duration == 0 && !end_time)
557                 return core::frame_producer::empty();
558
559         int motion_blur_px = get_param(L"BLUR", params, 0);
560
561         bool premultiply_with_alpha = contains_param(L"PREMULTIPLY", params);
562         bool progressive = contains_param(L"PROGRESSIVE", params);
563
564         return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(
565                         dependencies.frame_factory,
566                         dependencies.format_desc,
567                         *caspar::find_case_insensitive(filename + *ext),
568                         -speed,
569                         -duration,
570                         end_time,
571                         motion_blur_px,
572                         premultiply_with_alpha,
573                         progressive));
574 }
575
576 }}