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