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