]> git.sesse.net Git - casparcg/blob - modules/image/producer/image_scroll_producer.cpp
ffmpeg_producer: Multiple audio streams are now merged (flattened) before the audio...
[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
49 #include <boost/filesystem.hpp>
50 #include <boost/lexical_cast.hpp>
51 #include <boost/property_tree/ptree.hpp>
52 #include <boost/scoped_array.hpp>
53
54 #include <algorithm>
55 #include <array>
56 #include <cstdint>
57
58 namespace caspar { namespace image {
59                 
60 struct image_scroll_producer : public core::frame_producer_base
61 {       
62         core::monitor::subject                  monitor_subject_;
63
64         const std::wstring                              filename_;
65         std::vector<core::draw_frame>   frames_;
66         core::video_format_desc                 format_desc_;
67         int                                                             width_;
68         int                                                             height_;
69         core::constraints                               constraints_;
70
71         double                                                  delta_                          = 0.0;
72         double                                                  speed_;
73
74         int                                                             start_offset_x_         = 0;
75         int                                                             start_offset_y_         = 0;
76         bool                                                    progressive_;
77         
78         explicit image_scroll_producer(
79                         const spl::shared_ptr<core::frame_factory>& frame_factory,
80                         const core::video_format_desc& format_desc,
81                         const std::wstring& filename,
82                         double speed,
83                         double duration,
84                         int motion_blur_px = 0,
85                         bool premultiply_with_alpha = false,
86                         bool progressive = false)
87                 : filename_(filename)
88                 , format_desc_(format_desc)
89                 , speed_(speed)
90                 , progressive_(progressive)
91         {
92                 auto bitmap = load_image(filename_);
93                 FreeImage_FlipVertical(bitmap.get());
94
95                 width_  = FreeImage_GetWidth(bitmap.get());
96                 height_ = FreeImage_GetHeight(bitmap.get());
97                 constraints_.width.set(width_);
98                 constraints_.height.set(height_);
99
100                 bool vertical = width_ == format_desc_.width;
101                 bool horizontal = height_ == format_desc_.height;
102
103                 if (!vertical && !horizontal)
104                         CASPAR_THROW_EXCEPTION(caspar::user_error()
105                                         << msg_info("Neither width nor height matched the video resolution"));
106
107                 if (vertical)
108                 {
109                         if (duration != 0.0)
110                         {
111                                 double total_num_pixels = format_desc_.height * 2 + height_;
112
113                                 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
114
115                                 if (std::abs(speed_) > 1.0)
116                                         speed_ = std::ceil(speed_);
117                         }
118
119                         if (speed_ < 0.0)
120                         {
121                                 start_offset_y_ = height_ + format_desc_.height;
122                         }
123                 }
124                 else
125                 {
126                         if (duration != 0.0)
127                         {
128                                 double total_num_pixels = format_desc_.width * 2 + width_;
129
130                                 speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast<double>(format_desc_.field_count));
131
132                                 if (std::abs(speed_) > 1.0)
133                                         speed_ = std::ceil(speed_);
134                         }
135
136                         if (speed_ > 0.0)
137                                 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width);
138                         else
139                                 start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width;
140                 }
141
142                 auto bytes = FreeImage_GetBits(bitmap.get());
143                 auto count = width_*height_*4;
144                 image_view<bgra_pixel> original_view(bytes, width_, height_);
145
146                 if (premultiply_with_alpha)
147                         premultiply(original_view);
148
149                 boost::scoped_array<uint8_t> blurred_copy;
150
151                 if (motion_blur_px > 0)
152                 {
153                         double angle = 3.14159265 / 2; // Up
154
155                         if (horizontal && speed_ < 0)
156                                 angle *= 2; // Left
157                         else if (vertical && speed > 0)
158                                 angle *= 3; // Down
159                         else if (horizontal && speed  > 0)
160                                 angle = 0.0; // Right
161
162                         blurred_copy.reset(new uint8_t[count]);
163                         image_view<bgra_pixel> blurred_view(blurred_copy.get(), width_, height_);
164                         caspar::tweener blur_tweener(L"easeInQuad");
165                         blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener);
166                         bytes = blurred_copy.get();
167                         bitmap.reset();
168                 }
169
170                 if (vertical)
171                 {
172                         int n = 1;
173
174                         while(count > 0)
175                         {
176                                 core::pixel_format_desc desc = core::pixel_format::bgra;
177                                 desc.planes.push_back(core::pixel_format_desc::plane(width_, format_desc_.height, 4));
178                                 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
179
180                                 if(count >= frame.image_data(0).size())
181                                 {       
182                                         std::copy_n(bytes + count - frame.image_data(0).size(), frame.image_data(0).size(), frame.image_data(0).begin());
183                                         count -= static_cast<int>(frame.image_data(0).size());
184                                 }
185                                 else
186                                 {
187                                         memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());     
188                                         std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count);
189                                         count = 0;
190                                 }
191
192                                 core::draw_frame draw_frame(std::move(frame));
193
194                                 // Set the relative position to the other image fragments
195                                 draw_frame.transform().image_transform.fill_translation[1] = - n++;
196
197                                 frames_.push_back(draw_frame);
198                         }
199                 }
200                 else if (horizontal)
201                 {
202                         int i = 0;
203                         while(count > 0)
204                         {
205                                 core::pixel_format_desc desc = core::pixel_format::bgra;
206                                 desc.planes.push_back(core::pixel_format_desc::plane(format_desc_.width, height_, 4));
207                                 auto frame = frame_factory->create_frame(this, desc, core::audio_channel_layout::invalid());
208                                 if(count >= frame.image_data(0).size())
209                                 {       
210                                         for(int y = 0; y < height_; ++y)
211                                                 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);
212                                         
213                                         ++i;
214                                         count -= static_cast<int>(frame.image_data(0).size());
215                                 }
216                                 else
217                                 {
218                                         memset(frame.image_data(0).begin(), 0, frame.image_data(0).size());     
219                                         auto width2 = width_ % format_desc_.width;
220                                         for(int y = 0; y < height_; ++y)
221                                                 std::copy_n(bytes + i * format_desc_.width*4 + y * width_*4, width2*4, frame.image_data(0).begin() + y * format_desc_.width*4);
222
223                                         count = 0;
224                                 }
225                         
226                                 frames_.push_back(core::draw_frame(std::move(frame)));
227                         }
228
229                         std::reverse(frames_.begin(), frames_.end());
230
231                         // Set the relative positions of the image fragments.
232                         for (size_t n = 0; n < frames_.size(); ++n)
233                         {
234                                 double translation = - (static_cast<double>(n) + 1.0);
235                                 frames_[n].transform().image_transform.fill_translation[0] = translation;
236                         }
237                 }
238
239                 CASPAR_LOG(info) << print() << L" Initialized";
240         }
241
242         std::vector<core::draw_frame> get_visible()
243         {
244                 std::vector<core::draw_frame> result;
245                 result.reserve(frames_.size());
246
247                 for (auto& frame : frames_)
248                 {
249                         auto& fill_translation = frame.transform().image_transform.fill_translation;
250
251                         if (width_ == format_desc_.width)
252                         {
253                                 auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);
254                                 auto vertical_offset = fill_translation[1] + motion_offset_in_screens;
255
256                                 if (vertical_offset < -1.0 || vertical_offset > 1.0)
257                                 {
258                                         continue;
259                                 }
260                         }
261                         else
262                         {
263                                 auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);
264                                 auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;
265
266                                 if (horizontal_offset < -1.0 || horizontal_offset > 1.0)
267                                 {
268                                         continue;
269                                 }
270                         }
271
272                         result.push_back(frame);
273                 }
274
275                 return std::move(result);
276         }
277         
278         // frame_producer
279         core::draw_frame render_frame(bool allow_eof)
280         {
281                 if(frames_.empty())
282                         return core::draw_frame::empty();
283                 
284                 core::draw_frame result(get_visible());
285                 auto& fill_translation = result.transform().image_transform.fill_translation;
286
287                 if (width_ == format_desc_.width)
288                 {
289                         if (static_cast<size_t>(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof)
290                                 return core::draw_frame::empty();
291
292                         fill_translation[1] = 
293                                 static_cast<double>(start_offset_y_) / static_cast<double>(format_desc_.height)
294                                 + delta_ / static_cast<double>(format_desc_.height);
295                 }
296                 else
297                 {
298                         if (static_cast<size_t>(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof)
299                                 return core::draw_frame::empty();
300
301                         fill_translation[0] = 
302                                 static_cast<double>(start_offset_x_) / static_cast<double>(format_desc_.width)
303                                 + (delta_) / static_cast<double>(format_desc_.width);
304                 }
305
306                 return result;
307         }
308
309         core::draw_frame render_frame(bool allow_eof, bool advance_delta)
310         {
311                 auto result = render_frame(allow_eof);
312
313                 if (advance_delta)
314                 {
315                         advance();
316                 }
317
318                 return result;
319         }
320
321         void advance()
322         {
323                 delta_ += speed_;
324         }
325
326         core::draw_frame receive_impl() override
327         {
328                 core::draw_frame result;
329
330                 if (format_desc_.field_mode == core::field_mode::progressive || progressive_)
331                 {
332                         result = render_frame(true, true);
333                 }
334                 else
335                 {
336                         auto field1 = render_frame(true, true);
337                         auto field2 = render_frame(true, false);
338
339                         if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty())
340                         {
341                                 field2 = render_frame(false, true);
342                         }
343                         else
344                         {
345                                 advance();
346                         }
347
348                         result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode);
349                 }
350                 
351                 monitor_subject_ << core::monitor::message("/file/path") % filename_
352                                                  << core::monitor::message("/delta") % delta_ 
353                                                  << core::monitor::message("/speed") % speed_;
354
355                 return result;
356         }
357
358         core::constraints& pixel_constraints() override
359         {
360                 return constraints_;
361         }
362                                 
363         std::wstring print() const override
364         {
365                 return L"image_scroll_producer[" + filename_ + L"]";
366         }
367
368         std::wstring name() const override
369         {
370                 return L"image-scroll";
371         }
372
373         boost::property_tree::wptree info() const override
374         {
375                 boost::property_tree::wptree info;
376                 info.add(L"type", L"image-scroll");
377                 info.add(L"filename", filename_);
378                 return info;
379         }
380
381         uint32_t nb_frames() const override
382         {
383                 if(width_ == format_desc_.width)
384                 {
385                         auto length = (height_ + format_desc_.height * 2);
386                         return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
387                 }
388                 else
389                 {
390                         auto length = (width_ + format_desc_.width * 2);
391                         return static_cast<uint32_t>(length / std::abs(speed_));// + length % std::abs(delta_));
392                 }
393         }
394
395         core::monitor::subject& monitor_output()
396         {
397                 return monitor_subject_;
398         }
399 };
400
401 void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
402 {
403         sink.short_description(L"Scrolls an image either horizontally or vertically.");
404         sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
405         sink.para()
406                 ->text(L"Scrolls an image either horizontally or vertically. ")
407                 ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
408                 ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
409                 ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
410         sink.definitions()
411                 ->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.")
412                 ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
413                 ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
414                 ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
415                 ->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.");
416         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.");
417         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.");
418 }
419
420 spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
421 {
422         static const auto extensions = {
423                 L".png",
424                 L".tga",
425                 L".bmp",
426                 L".jpg",
427                 L".jpeg",
428                 L".gif",
429                 L".tiff",
430                 L".tif",
431                 L".jp2",
432                 L".jpx",
433                 L".j2k",
434                 L".j2c"
435         };
436         std::wstring filename = env::media_folder() + params.at(0);
437         
438         auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool
439         {
440                 auto file = caspar::find_case_insensitive(boost::filesystem::path(filename).replace_extension(ex).wstring());
441
442                 return static_cast<bool>(file);
443         });
444
445         if(ext == extensions.end())
446                 return core::frame_producer::empty();
447         
448         double duration = 0.0;
449         double speed = get_param(L"SPEED", params, 0.0);
450
451         if (speed == 0)
452                 duration = get_param(L"DURATION", params, 0.0);
453
454         if(speed == 0 && duration == 0)
455                 return core::frame_producer::empty();
456
457         int motion_blur_px = get_param(L"BLUR", params, 0);
458
459         bool premultiply_with_alpha = contains_param(L"PREMULTIPLY", params);
460         bool progressive = contains_param(L"PROGRESSIVE", params);
461
462         return core::create_destroy_proxy(spl::make_shared<image_scroll_producer>(
463                         dependencies.frame_factory,
464                         dependencies.format_desc,
465                         *caspar::find_case_insensitive(filename + *ext),
466                         -speed,
467                         -duration,
468                         motion_blur_px,
469                         premultiply_with_alpha,
470                         progressive));
471 }
472
473 }}