OpenShot Library | libopenshot  0.7.0
EffectBase.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2019 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include <iostream>
14 #include <iomanip>
15 #include <algorithm>
16 #include <cmath>
17 
18 #include "EffectBase.h"
19 
20 #include "Exceptions.h"
21 #include "Clip.h"
22 #include "Timeline.h"
23 #include "TrackedObjectBBox.h"
24 #include "ReaderBase.h"
25 #include "ChunkReader.h"
26 #include "FFmpegReader.h"
27 #include "QtImageReader.h"
28 #include "ZmqLogger.h"
29 #include <omp.h>
30 #include <QBrush>
31 #include <QColor>
32 #include <QPainter>
33 #include <QRectF>
34 
35 #ifdef USE_IMAGEMAGICK
36  #include "ImageReader.h"
37 #endif
38 
39 using namespace openshot;
40 
41 // Initialize the values of the EffectInfo struct
43 {
44  // Init clip settings
45  Position(0.0);
46  Layer(0);
47  Start(0.0);
48  End(0.0);
49  Order(0);
50  ParentClip(NULL);
51  parentEffect = NULL;
52  mask_invert = false;
53  mask_reader = NULL;
54  mask_source_id = "";
57 
58  info.has_video = false;
59  info.has_audio = false;
60  info.has_tracked_object = false;
61  info.name = "";
62  info.description = "";
64  info.apply_before_clip = true;
65 }
66 
67 // Display file information
68 void EffectBase::DisplayInfo(std::ostream* out) {
69  *out << std::fixed << std::setprecision(2) << std::boolalpha;
70  *out << "----------------------------" << std::endl;
71  *out << "----- Effect Information -----" << std::endl;
72  *out << "----------------------------" << std::endl;
73  *out << "--> Name: " << info.name << std::endl;
74  *out << "--> Description: " << info.description << std::endl;
75  *out << "--> Has Video: " << info.has_video << std::endl;
76  *out << "--> Has Audio: " << info.has_audio << std::endl;
77  *out << "--> Apply to Source: " << info.apply_before_clip << std::endl;
78  *out << "--> Order: " << order << std::endl;
79  *out << "----------------------------" << std::endl;
80 }
81 
82 // Constrain a color value from 0 to 255
83 int EffectBase::constrain(int color_value)
84 {
85  // Constrain new color from 0 to 255
86  if (color_value < 0)
87  color_value = 0;
88  else if (color_value > 255)
89  color_value = 255;
90 
91  return color_value;
92 }
93 
94 // Generate JSON string of this object
95 std::string EffectBase::Json() const {
96 
97  // Return formatted string
98  return JsonValue().toStyledString();
99 }
100 
101 // Generate Json::Value for this object
102 Json::Value EffectBase::JsonValue() const {
103 
104  // Create root json object
105  Json::Value root = ClipBase::JsonValue(); // get parent properties
106  root["name"] = info.name;
107  root["class_name"] = info.class_name;
108  root["description"] = info.description;
109  root["parent_effect_id"] = info.parent_effect_id;
110  root["has_video"] = info.has_video;
111  root["has_audio"] = info.has_audio;
112  root["has_tracked_object"] = info.has_tracked_object;
113  root["apply_before_clip"] = info.apply_before_clip;
114  root["order"] = Order();
115  root["mask_invert"] = mask_invert;
116  root["mask_source_id"] = mask_source_id;
117  root["mask_time_mode"] = mask_time_mode;
118  root["mask_loop_mode"] = mask_loop_mode;
119  if (mask_reader)
120  root["mask_reader"] = mask_reader->JsonValue();
121  else
122  root["mask_reader"] = Json::objectValue;
123 
124  // return JsonValue
125  return root;
126 }
127 
128 // Load JSON string into this object
129 void EffectBase::SetJson(const std::string value) {
130 
131  // Parse JSON string into JSON objects
132  try
133  {
134  Json::Value root = openshot::stringToJson(value);
135  // Set all values that match
136  SetJsonValue(root);
137  }
138  catch (const std::exception& e)
139  {
140  // Error parsing JSON (or missing keys)
141  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
142  }
143 }
144 
145 // Load Json::Value into this object
146 void EffectBase::SetJsonValue(const Json::Value root) {
147 
148  if (ParentTimeline()){
149  // Get parent timeline
150  Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
151 
152  // Get the list of effects on the timeline
153  std::list<EffectBase*> effects = parentTimeline->ClipEffects();
154 
155  // TODO: Fix recursive call for Object Detection
156 
157  // // Loop through the effects and check if we have a child effect linked to this effect
158  for (auto const& effect : effects){
159  // Set the properties of all effects which parentEffect points to this
160  if ((effect->info.parent_effect_id == this->Id()) && (effect->Id() != this->Id()))
161  effect->SetJsonValue(root);
162  }
163  }
164 
165  // Set this effect properties with the parent effect properties (except the id and parent_effect_id)
166  Json::Value my_root;
167  if (parentEffect){
168  my_root = parentEffect->JsonValue();
169  my_root["id"] = this->Id();
170  my_root["parent_effect_id"] = this->info.parent_effect_id;
171  } else {
172  my_root = root;
173  }
174 
175  // Legacy compatibility: older shared-mask JSON stored source trim
176  // separately from the effect trim. Canonical trim now uses ClipBase.
177  if (my_root["start"].isNull() && !my_root["mask_start"].isNull())
178  my_root["start"] = my_root["mask_start"];
179  if (my_root["end"].isNull() && !my_root["mask_end"].isNull())
180  my_root["end"] = my_root["mask_end"];
181 
182  // Set parent data
183  ClipBase::SetJsonValue(my_root);
184 
185  // Set data from Json (if key is found)
186  if (!my_root["order"].isNull())
187  Order(my_root["order"].asInt());
188 
189  if (!my_root["apply_before_clip"].isNull())
190  info.apply_before_clip = my_root["apply_before_clip"].asBool();
191 
192  if (!my_root["mask_invert"].isNull())
193  mask_invert = my_root["mask_invert"].asBool();
194  if (!my_root["mask_source_id"].isNull())
195  MaskSourceId(my_root["mask_source_id"].asString());
196  if (!my_root["mask_time_mode"].isNull()) {
197  const int time_mode = my_root["mask_time_mode"].asInt();
198  mask_time_mode = (time_mode == MASK_TIME_TIMELINE || time_mode == MASK_TIME_SOURCE_FPS)
199  ? time_mode : MASK_TIME_SOURCE_FPS;
200  }
201  if (!my_root["mask_loop_mode"].isNull()) {
202  const int loop_mode = my_root["mask_loop_mode"].asInt();
203  if (loop_mode >= MASK_LOOP_PLAY_ONCE && loop_mode <= MASK_LOOP_PING_PONG)
204  mask_loop_mode = loop_mode;
205  else
207  }
208 
209  const Json::Value mask_reader_json =
210  !my_root["mask_reader"].isNull() ? my_root["mask_reader"] : my_root["reader"];
211 
212  if (!mask_reader_json.isNull()) {
213  if (!mask_reader_json["type"].isNull()) {
214  MaskReader(CreateReaderFromJson(mask_reader_json));
215  } else if (mask_reader_json.isObject() && mask_reader_json.empty()) {
216  MaskReader(NULL);
217  }
218  }
219 
220  if (!my_root["parent_effect_id"].isNull()){
221  info.parent_effect_id = my_root["parent_effect_id"].asString();
222  if (info.parent_effect_id.size() > 0 && info.parent_effect_id != "" && parentEffect == NULL)
224  else
225  parentEffect = NULL;
226  }
227 }
228 
229 // Generate Json::Value for this object
230 Json::Value EffectBase::JsonInfo() const {
231 
232  // Create root json object
233  Json::Value root;
234  root["name"] = info.name;
235  root["class_name"] = info.class_name;
236  root["description"] = info.description;
237  root["has_video"] = info.has_video;
238  root["has_audio"] = info.has_audio;
239 
240  // return JsonValue
241  return root;
242 }
243 
244 // Get all properties for a specific frame
245 Json::Value EffectBase::BasePropertiesJSON(int64_t requested_frame) const {
246  // Generate JSON properties list
247  Json::Value root;
248  root["id"] = add_property_json("ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame);
249  root["position"] = add_property_json("Position", Position(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
250  root["layer"] = add_property_json("Track", Layer(), "int", "", NULL, 0, 20, false, requested_frame);
251  root["start"] = add_property_json("Start", Start(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
252  root["end"] = add_property_json("End", End(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
253  root["duration"] = add_property_json("Duration", Duration(), "float", "", NULL, 0, 30 * 60 * 60 * 48, true, requested_frame);
254 
255  // Add replace_image choices (dropdown style)
256  root["apply_before_clip"] = add_property_json("Apply to Source", info.apply_before_clip, "int", "", NULL, 0, 1, false, requested_frame);
257  root["apply_before_clip"]["choices"].append(add_property_choice_json("Yes", true, info.apply_before_clip));
258  root["apply_before_clip"]["choices"].append(add_property_choice_json("No", false, info.apply_before_clip));
259 
260  // Set the parent effect which properties this effect will inherit
261  root["parent_effect_id"] = add_property_json("Parent", 0.0, "string", info.parent_effect_id, NULL, -1, -1, false, requested_frame);
262 
263  if (info.has_video) {
264  root["mask_invert"] = add_property_json("Mask: Invert", mask_invert, "int", "", NULL, 0, 1, false, requested_frame);
265  root["mask_invert"]["choices"].append(add_property_choice_json("Yes", true, mask_invert));
266  root["mask_invert"]["choices"].append(add_property_choice_json("No", false, mask_invert));
267 
268  root["mask_time_mode"] = add_property_json("Mask: Time Mode", mask_time_mode, "int", "", NULL, 0, 1, false, requested_frame);
269  root["mask_time_mode"]["choices"].append(add_property_choice_json("Timeline", MASK_TIME_TIMELINE, mask_time_mode));
270  root["mask_time_mode"]["choices"].append(add_property_choice_json("Source FPS", MASK_TIME_SOURCE_FPS, mask_time_mode));
271 
272  root["mask_loop_mode"] = add_property_json("Mask: Loop", mask_loop_mode, "int", "", NULL, 0, 2, false, requested_frame);
273  root["mask_loop_mode"]["choices"].append(add_property_choice_json("Play Once", MASK_LOOP_PLAY_ONCE, mask_loop_mode));
274  root["mask_loop_mode"]["choices"].append(add_property_choice_json("Repeat", MASK_LOOP_REPEAT, mask_loop_mode));
275  root["mask_loop_mode"]["choices"].append(add_property_choice_json("Ping-Pong", MASK_LOOP_PING_PONG, mask_loop_mode));
276 
277  if (mask_reader)
278  root["mask_reader"] = add_property_json("Mask: Source", 0.0, "reader", mask_reader->Json(), NULL, 0, 1, false, requested_frame);
279  else
280  root["mask_reader"] = add_property_json("Mask: Source", 0.0, "reader", "{}", NULL, 0, 1, false, requested_frame);
281 
282  root["mask_source_id"] = add_property_json("Mask: Effect Source", 0.0, "string", mask_source_id, NULL, -1, -1, false, requested_frame);
283  }
284 
285  return root;
286 }
287 
288 ReaderBase* EffectBase::CreateReaderFromJson(const Json::Value& reader_json) const {
289  if (reader_json["type"].isNull())
290  return NULL;
291 
292  ReaderBase* reader = NULL;
293  const std::string type = reader_json["type"].asString();
294 
295  if (type == "FFmpegReader") {
296  reader = new FFmpegReader(reader_json["path"].asString());
297  reader->SetJsonValue(reader_json);
298  // Mask readers are video-only sources. Disabling audio avoids FFmpeg
299  // A/V readiness fallbacks that can repeat stale video frames.
300  reader->info.has_audio = false;
301  reader->info.audio_stream_index = -1;
302  } else if (type == "QtImageReader") {
303  reader = new QtImageReader(reader_json["path"].asString());
304  reader->SetJsonValue(reader_json);
305  } else if (type == "ChunkReader") {
306  reader = new ChunkReader(reader_json["path"].asString(),
307  static_cast<ChunkVersion>(reader_json["chunk_version"].asInt()));
308  reader->SetJsonValue(reader_json);
309 #ifdef USE_IMAGEMAGICK
310  } else if (type == "ImageReader") {
311  reader = new ImageReader(reader_json["path"].asString());
312  reader->SetJsonValue(reader_json);
313 #endif
314  }
315 
316  return reader;
317 }
318 
320  if (mask_reader == new_reader)
321  return;
322 
323  if (mask_reader) {
324  mask_reader->Close();
325  delete mask_reader;
326  }
327 
328  mask_reader = new_reader;
329  cached_single_mask_image.reset();
330  cached_single_mask_width = 0;
331  cached_single_mask_height = 0;
332  if (mask_reader)
333  mask_reader->ParentClip(clip);
334 }
335 
336 void EffectBase::MaskSourceId(const std::string& new_mask_source_id) {
337  mask_source_id = new_mask_source_id;
338  cached_single_mask_image.reset();
339  cached_single_mask_width = 0;
340  cached_single_mask_height = 0;
341 }
342 
343 EffectBase* EffectBase::ResolveMaskSourceEffect() {
344  if (mask_source_id.empty())
345  return NULL;
346 
347  Clip* parent_clip = dynamic_cast<Clip*>(clip);
348  if (parent_clip) {
349  EffectBase* source = parent_clip->GetEffect(mask_source_id);
350  if (source && source != this)
351  return source;
352  }
353 
354  Timeline* parent_timeline = dynamic_cast<Timeline*>(ParentTimeline());
355  if (parent_timeline) {
356  EffectBase* source = parent_timeline->GetClipEffect(mask_source_id);
357  if (!source)
358  source = parent_timeline->GetEffect(mask_source_id);
359  if (source && source != this)
360  return source;
361  }
362 
363  return NULL;
364 }
365 
367  if (clip) {
368  Clip* parent_clip = dynamic_cast<Clip*>(clip);
369  if (parent_clip && parent_clip->info.fps.num > 0 && parent_clip->info.fps.den > 0)
370  return parent_clip->info.fps.ToDouble();
371  }
372 
373  Timeline* parent_timeline = dynamic_cast<Timeline*>(ParentTimeline());
374  if (parent_timeline && parent_timeline->info.fps.num > 0 && parent_timeline->info.fps.den > 0)
375  return parent_timeline->info.fps.ToDouble();
376 
377  if (mask_reader && mask_reader->info.fps.num > 0 && mask_reader->info.fps.den > 0)
378  return mask_reader->info.fps.ToDouble();
379 
380  return 30.0;
381 }
382 
384  if (!mask_reader)
385  return 0.0;
386 
387  if (mask_reader->info.duration > 0.0f)
388  return static_cast<double>(mask_reader->info.duration);
389 
390  if (mask_reader->info.video_length > 0 &&
391  mask_reader->info.fps.num > 0 && mask_reader->info.fps.den > 0) {
392  return static_cast<double>(mask_reader->info.video_length) / mask_reader->info.fps.ToDouble();
393  }
394 
395  return 0.0;
396 }
397 
398 int64_t EffectBase::MapMaskFrameNumber(int64_t frame_number) {
399  if (!mask_reader)
400  return frame_number;
401 
402  int64_t requested_index = std::max(int64_t(0), frame_number - 1);
403  if (!clip && ParentTimeline()) {
404  const double host_fps = ResolveMaskHostFps();
405  if (host_fps > 0.0) {
406  const int64_t start_offset = static_cast<int64_t>(std::llround(std::max(0.0f, Start()) * host_fps));
407  requested_index = std::max(int64_t(0), requested_index - start_offset);
408  }
409  }
410  int64_t mapped_index = requested_index;
411 
413  mask_reader->info.fps.num > 0 && mask_reader->info.fps.den > 0) {
414  const double host_fps = ResolveMaskHostFps();
415  const double source_fps = mask_reader->info.fps.ToDouble();
416  if (host_fps > 0.0 && source_fps > 0.0) {
417  const double seconds = static_cast<double>(requested_index) / host_fps;
418  mapped_index = static_cast<int64_t>(std::llround(seconds * source_fps));
419  }
420  }
421 
422  const int64_t source_len = mask_reader->info.video_length;
423  const double source_fps = (mask_reader->info.fps.num > 0 && mask_reader->info.fps.den > 0)
424  ? mask_reader->info.fps.ToDouble() : 30.0;
425  const double source_duration = ResolveMaskSourceDuration();
426  const double start_sec = std::min<double>(std::max(0.0f, Start()), source_duration);
427  const double end_sec = std::min<double>(std::max(0.0f, End()), source_duration);
428 
429  const int64_t range_start = std::max(int64_t(1), static_cast<int64_t>(std::llround(start_sec * source_fps)) + 1);
430  int64_t range_end = (end_sec > 0.0)
431  ? static_cast<int64_t>(std::llround(end_sec * source_fps)) + 1
432  : source_len;
433  if (source_len > 0)
434  range_end = std::min(range_end, source_len);
435  if (range_end < range_start)
436  range_end = range_start;
437 
438  const int64_t range_len = std::max(int64_t(1), range_end - range_start + 1);
439  int64_t range_index = mapped_index;
440 
441  switch (mask_loop_mode) {
442  case MASK_LOOP_REPEAT:
443  range_index = mapped_index % range_len;
444  break;
445  case MASK_LOOP_PING_PONG:
446  if (range_len > 1) {
447  const int64_t cycle_len = (range_len * 2) - 2;
448  int64_t phase = mapped_index % cycle_len;
449  if (phase >= range_len)
450  phase = cycle_len - phase;
451  range_index = phase;
452  } else {
453  range_index = 0;
454  }
455  break;
456  case MASK_LOOP_PLAY_ONCE:
457  default:
458  if (mapped_index < 0)
459  range_index = 0;
460  else if (mapped_index >= range_len)
461  range_index = range_len - 1;
462  else
463  range_index = mapped_index;
464  break;
465  }
466 
467  int64_t mapped_frame = range_start + range_index;
468  if (source_len > 0)
469  mapped_frame = std::min(std::max(int64_t(1), mapped_frame), source_len);
470  return std::max(int64_t(1), mapped_frame);
471 }
472 
473 std::shared_ptr<QImage> EffectBase::GetMaskImage(std::shared_ptr<QImage> target_image, int64_t frame_number) {
474  if (!target_image || target_image->isNull())
475  return {};
476 
477  EffectBase* source_effect = ResolveMaskSourceEffect();
478  if (source_effect) {
479  auto generated_mask = source_effect->TrackedObjectMask(target_image, frame_number);
480  if (generated_mask && !generated_mask->isNull())
481  return generated_mask;
482 
483  auto empty_mask = std::make_shared<QImage>(
484  target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
485  empty_mask->fill(QColor(0, 0, 0, 255));
486  return empty_mask;
487  }
488 
489  if (!mask_reader)
490  return {};
491 
492  std::shared_ptr<QImage> source_mask;
493  bool used_cached_scaled = false;
494  #pragma omp critical (open_effect_mask_reader)
495  {
496  try {
497  if (!mask_reader->IsOpen())
498  mask_reader->Open();
499 
500  if (mask_reader->info.has_single_image &&
501  cached_single_mask_image &&
502  cached_single_mask_width == target_image->width() &&
503  cached_single_mask_height == target_image->height()) {
504  source_mask = cached_single_mask_image;
505  used_cached_scaled = true;
506  }
507  else {
508  const int64_t mapped_frame = MapMaskFrameNumber(frame_number);
509  auto source_frame = mask_reader->GetFrame(mapped_frame);
510  if (source_frame && source_frame->GetImage() && !source_frame->GetImage()->isNull())
511  source_mask = std::make_shared<QImage>(*source_frame->GetImage());
512  }
513  } catch (const std::exception& e) {
515  std::string("EffectBase::GetMaskImage unable to read mask frame: ") + e.what());
516  source_mask.reset();
517  }
518  }
519 
520  if (!source_mask || source_mask->isNull())
521  return {};
522 
523  if (used_cached_scaled)
524  return source_mask;
525 
526  auto scaled_mask = std::make_shared<QImage>(
527  source_mask->scaled(
528  target_image->width(), target_image->height(),
529  Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
530  if (mask_reader->info.has_single_image) {
531  cached_single_mask_image = scaled_mask;
532  cached_single_mask_width = target_image->width();
533  cached_single_mask_height = target_image->height();
534  }
535  return scaled_mask;
536 }
537 
538 std::shared_ptr<QImage> EffectBase::TrackedObjectMask(std::shared_ptr<QImage> target_image, int64_t frame_number) const {
539  if (!target_image || target_image->isNull() || trackedObjects.empty())
540  return {};
541 
542  auto mask_image = std::make_shared<QImage>(
543  target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
544  mask_image->fill(QColor(0, 0, 0, 255));
545 
546  QPainter painter(mask_image.get());
547  painter.setRenderHint(QPainter::Antialiasing, false);
548  painter.setPen(Qt::NoPen);
549  painter.setBrush(QBrush(QColor(255, 255, 255, 255)));
550 
551  bool drew_any_box = false;
552  for (auto const& trackedObject : trackedObjects) {
553  auto bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(trackedObject.second);
554  if (!bbox)
555  continue;
556  if (!bbox->Contains(frame_number) || bbox->visible.GetValue(frame_number) != 1)
557  continue;
558 
559  BBox box = bbox->GetBox(frame_number);
560  if (box.width <= 0.0f || box.height <= 0.0f || box.cx < 0.0f || box.cy < 0.0f)
561  continue;
562 
563  const double x = (box.cx - box.width / 2.0) * target_image->width();
564  const double y = (box.cy - box.height / 2.0) * target_image->height();
565  const double w = box.width * target_image->width();
566  const double h = box.height * target_image->height();
567  QRectF rect(x, y, w, h);
568 
569  if (std::abs(box.angle) > 0.0001f) {
570  painter.save();
571  painter.translate(rect.center());
572  painter.rotate(box.angle);
573  painter.drawRect(QRectF(-w / 2.0, -h / 2.0, w, h));
574  painter.restore();
575  } else {
576  painter.drawRect(rect);
577  }
578  drew_any_box = true;
579  }
580 
581  painter.end();
582  if (!drew_any_box)
583  return {};
584  return mask_image;
585 }
586 
587 void EffectBase::BlendWithMask(std::shared_ptr<QImage> original_image, std::shared_ptr<QImage> effected_image,
588  std::shared_ptr<QImage> mask_image) const {
589  if (!original_image || !effected_image || !mask_image)
590  return;
591  if (original_image->size() != effected_image->size() || effected_image->size() != mask_image->size())
592  return;
593 
594  unsigned char* original_pixels = reinterpret_cast<unsigned char*>(original_image->bits());
595  unsigned char* effected_pixels = reinterpret_cast<unsigned char*>(effected_image->bits());
596  unsigned char* mask_pixels = reinterpret_cast<unsigned char*>(mask_image->bits());
597  const int pixel_count = effected_image->width() * effected_image->height();
598 
599  #pragma omp parallel for schedule(static)
600  for (int i = 0; i < pixel_count; ++i) {
601  const int idx = i * 4;
602  int gray = qGray(mask_pixels[idx], mask_pixels[idx + 1], mask_pixels[idx + 2]);
603  if (mask_invert)
604  gray = 255 - gray;
605  const float factor = static_cast<float>(gray) / 255.0f;
606  const float inverse = 1.0f - factor;
607 
608  effected_pixels[idx] = static_cast<unsigned char>(
609  (original_pixels[idx] * inverse) + (effected_pixels[idx] * factor));
610  effected_pixels[idx + 1] = static_cast<unsigned char>(
611  (original_pixels[idx + 1] * inverse) + (effected_pixels[idx + 1] * factor));
612  effected_pixels[idx + 2] = static_cast<unsigned char>(
613  (original_pixels[idx + 2] * inverse) + (effected_pixels[idx + 2] * factor));
614  effected_pixels[idx + 3] = static_cast<unsigned char>(
615  (original_pixels[idx + 3] * inverse) + (effected_pixels[idx + 3] * factor));
616  }
617 }
618 
619 std::shared_ptr<openshot::Frame> EffectBase::ProcessFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number) {
620  // Audio-only effects skip common mask handling.
621  if (!info.has_video || (!mask_reader && mask_source_id.empty()))
622  return GetFrame(frame, frame_number);
623 
624  // Effects that already apply masks inside GetFrame() should bypass common blend handling.
625  if (HandlesMaskInternally())
626  return GetFrame(frame, frame_number);
627 
628  auto pre_image = frame->GetImage();
629  if (!pre_image || pre_image->isNull())
630  return GetFrame(frame, frame_number);
631 
632  const auto original_image = std::make_shared<QImage>(pre_image->copy());
633  auto output_frame = GetFrame(frame, frame_number);
634  if (!output_frame)
635  return output_frame;
636  auto effected_image = output_frame->GetImage();
637  if (!effected_image || effected_image->isNull() ||
638  effected_image->size() != original_image->size())
639  return output_frame;
640 
641  auto mask_image = GetMaskImage(effected_image, frame_number);
642  if (!mask_image || mask_image->isNull())
643  return output_frame;
644 
645  if (UseCustomMaskBlend(frame_number))
646  ApplyCustomMaskBlend(original_image, effected_image, mask_image, frame_number);
647  else
648  BlendWithMask(original_image, effected_image, mask_image);
649 
650  return output_frame;
651 }
652 
655  return clip;
656 }
657 
660  clip = new_clip;
661  if (mask_reader)
662  mask_reader->ParentClip(new_clip);
663 }
664 
665 // Set the parent effect from which this properties will be set to
666 void EffectBase::SetParentEffect(std::string parentEffect_id) {
667 
668  // Get parent Timeline
669  Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
670 
671  if (parentTimeline){
672 
673  // Get a pointer to the parentEffect
674  EffectBase* parentEffectPtr = parentTimeline->GetClipEffect(parentEffect_id);
675 
676  if (parentEffectPtr){
677  // Set the parent Effect
678  parentEffect = parentEffectPtr;
679 
680  // Set the properties of this effect with the parent effect's properties
681  Json::Value EffectJSON = parentEffect->JsonValue();
682  EffectJSON["id"] = this->Id();
683  EffectJSON["parent_effect_id"] = this->info.parent_effect_id;
684  this->SetJsonValue(EffectJSON);
685  }
686  }
687  return;
688 }
689 
690 // Return the ID of this effect's parent clip
691 std::string EffectBase::ParentClipId() const{
692  if(clip)
693  return clip->Id();
694  else
695  return "";
696 }
697 
699  MaskReader(NULL);
700 }
openshot::ClipBase::add_property_json
Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe *keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const
Generate JSON for a property.
Definition: ClipBase.cpp:96
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::EffectBase::~EffectBase
virtual ~EffectBase()
Definition: EffectBase.cpp:698
openshot::EffectBase
This abstract class is the base class, used by all effects in libopenshot.
Definition: EffectBase.h:56
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:114
openshot::ReaderBase::JsonValue
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:110
openshot::EffectBase::JsonInfo
Json::Value JsonInfo() const
Generate JSON object of meta data / info.
Definition: EffectBase.cpp:230
Clip.h
Header file for Clip class.
openshot::BBox::height
float height
bounding box height
Definition: TrackedObjectBBox.h:42
openshot::ChunkReader
This class reads a special chunk-formatted file, which can be easily shared in a distributed environm...
Definition: ChunkReader.h:78
openshot::ReaderBase::GetFrame
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
openshot::EffectInfoStruct::apply_before_clip
bool apply_before_clip
Apply effect to source before we evaluate the clip's keyframes.
Definition: EffectBase.h:46
openshot::ReaderBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:161
openshot::Clip::GetEffect
openshot::EffectBase * GetEffect(const std::string &id)
Look up an effect by ID.
Definition: Clip.cpp:540
openshot::ReaderBase::Json
virtual std::string Json() const =0
Generate JSON string of this object.
openshot::ClipBase::End
virtual void End(float value)
Set end position (in seconds) of clip (trim end of video)
Definition: ClipBase.cpp:53
openshot::EffectBase::mask_invert
bool mask_invert
Invert grayscale mask values before blending.
Definition: EffectBase.h:115
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
openshot::EffectBase::ParentClip
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
Definition: EffectBase.cpp:654
openshot::ClipBase::add_property_choice_json
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
Definition: ClipBase.cpp:132
openshot::ZmqLogger::Log
void Log(std::string message)
Log message to all subscribers of this logger (if any)
Definition: ZmqLogger.cpp:103
openshot::Clip
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
openshot::EffectBase::MaskSourceId
std::string MaskSourceId() const
Get/Set effect ID used as a generated mask source.
Definition: EffectBase.h:189
openshot::EffectBase::JsonValue
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:102
openshot::BBox::cy
float cy
y-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:40
openshot::EffectBase::mask_time_mode
int mask_time_mode
How effect frames map to mask source frames.
Definition: EffectBase.h:128
openshot::ReaderBase::info
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:91
Timeline.h
Header file for Timeline class.
openshot::EffectBase::DisplayInfo
void DisplayInfo(std::ostream *out=&std::cout)
Display effect information in the standard output stream (stdout)
Definition: EffectBase.cpp:68
openshot::EffectBase::CreateReaderFromJson
ReaderBase * CreateReaderFromJson(const Json::Value &reader_json) const
Create a reader instance from reader JSON.
Definition: EffectBase.cpp:288
openshot::ReaderInfo::duration
float duration
Length of time (in seconds)
Definition: ReaderBase.h:43
openshot::EffectBase::trackedObjects
std::map< int, std::shared_ptr< openshot::TrackedObjectBase > > trackedObjects
Map of Tracked Object's by their indices (used by Effects that track objects on clips)
Definition: EffectBase.h:111
EffectBase.h
Header file for EffectBase class.
openshot::ClipBase::Position
void Position(float value)
Set the Id of this clip object
Definition: ClipBase.cpp:19
openshot::EffectBase::Json
virtual std::string Json() const
Generate JSON string of this object.
Definition: EffectBase.cpp:95
openshot::EffectBase::UseCustomMaskBlend
virtual bool UseCustomMaskBlend(int64_t frame_number) const
Optional override for effects that need custom mask behavior.
Definition: EffectBase.h:97
openshot::Fraction::ToDouble
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:40
openshot::EffectBase::SetParentEffect
void SetParentEffect(std::string parentEffect_id)
Set the parent effect from which this properties will be set to.
Definition: EffectBase.cpp:666
openshot::ClipBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ClipBase.cpp:80
openshot::QtImageReader
This class uses the Qt library, to open image files, and return openshot::Frame objects containing th...
Definition: QtImageReader.h:74
openshot::EffectBase::MASK_TIME_TIMELINE
@ MASK_TIME_TIMELINE
Definition: EffectBase.h:118
openshot::ClipBase::JsonValue
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ClipBase.cpp:64
openshot::EffectBase::ProcessFrame
std::shared_ptr< openshot::Frame > ProcessFrame(std::shared_ptr< openshot::Frame > frame, int64_t frame_number)
Apply effect processing with common mask support (if enabled).
Definition: EffectBase.cpp:619
openshot::ReaderInfo::video_length
int64_t video_length
The number of frames in the video stream.
Definition: ReaderBase.h:53
openshot::EffectBase::BasePropertiesJSON
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:245
openshot::BBox::angle
float angle
bounding box rotation angle [degrees]
Definition: TrackedObjectBBox.h:43
openshot::Fraction::num
int num
Numerator for the fraction.
Definition: Fraction.h:32
ZmqLogger.h
Header file for ZeroMQ-based Logger class.
openshot::Fraction::den
int den
Denominator for the fraction.
Definition: Fraction.h:33
openshot::EffectBase::MASK_LOOP_PLAY_ONCE
@ MASK_LOOP_PLAY_ONCE
Definition: EffectBase.h:123
openshot::ReaderBase::Open
virtual void Open()=0
Open the reader (and start consuming resources, such as images or video files)
openshot::ReaderInfo::has_audio
bool has_audio
Determines if this file has an audio stream.
Definition: ReaderBase.h:41
openshot::EffectBase::parentEffect
EffectBase * parentEffect
Parent effect (which properties will set this effect properties)
Definition: EffectBase.h:108
openshot::ReaderBase::IsOpen
virtual bool IsOpen()=0
Determine if reader is open or closed.
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::BBox::width
float width
bounding box width
Definition: TrackedObjectBBox.h:41
openshot::Timeline
This class represents a timeline.
Definition: Timeline.h:153
openshot::ImageReader
This class uses the ImageMagick++ libraries, to open image files, and return openshot::Frame objects ...
Definition: ImageReader.h:55
openshot::Timeline::ClipEffects
std::list< openshot::EffectBase * > ClipEffects() const
Return the list of effects on all clips.
Definition: Timeline.cpp:454
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:42
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
openshot::ReaderInfo::has_single_image
bool has_single_image
Determines if this file only contains a single image.
Definition: ReaderBase.h:42
openshot::EffectInfoStruct::has_tracked_object
bool has_tracked_object
Determines if this effect track objects through the clip.
Definition: EffectBase.h:45
openshot::EffectBase::ResolveMaskSourceDuration
double ResolveMaskSourceDuration() const
Determine mask source duration in seconds.
Definition: EffectBase.cpp:383
openshot::ClipBase::Start
void Start(float value)
Set start position (in seconds) of clip (trim start of video)
Definition: ClipBase.cpp:42
openshot::FFmpegReader
This class uses the FFmpeg libraries, to open video files and audio files, and return openshot::Frame...
Definition: FFmpegReader.h:103
openshot::EffectBase::TrackedObjectMask
virtual std::shared_ptr< QImage > TrackedObjectMask(std::shared_ptr< QImage > target_image, int64_t frame_number) const
Generate a black/white mask from tracked object data.
Definition: EffectBase.cpp:538
openshot::EffectBase::MaskReader
ReaderBase * MaskReader()
Get the common mask reader.
Definition: EffectBase.h:182
ChunkReader.h
Header file for ChunkReader class.
openshot::EffectBase::ParentClipId
std::string ParentClipId() const
Return the ID of this effect's parent clip.
Definition: EffectBase.cpp:691
openshot::ReaderInfo::audio_stream_index
int audio_stream_index
The index of the audio stream.
Definition: ReaderBase.h:63
openshot::ZmqLogger::Instance
static ZmqLogger * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition: ZmqLogger.cpp:35
openshot::EffectBase::ResolveMaskHostFps
double ResolveMaskHostFps()
Determine host FPS used to convert timeline frames to mask source FPS.
Definition: EffectBase.cpp:366
openshot::EffectBase::Order
int Order() const
Get the order that this effect should be executed.
Definition: EffectBase.h:193
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
ReaderBase.h
Header file for ReaderBase class.
openshot::EffectInfoStruct::description
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41
openshot::BBox
This struct holds the information of a bounding-box.
Definition: TrackedObjectBBox.h:37
openshot::EffectInfoStruct::has_video
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
openshot::Timeline::GetEffect
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
Definition: Timeline.cpp:430
openshot::EffectBase::MASK_LOOP_REPEAT
@ MASK_LOOP_REPEAT
Definition: EffectBase.h:124
openshot::ClipBase::Id
void Id(std::string value)
Definition: ClipBase.h:94
openshot::EffectBase::MASK_LOOP_PING_PONG
@ MASK_LOOP_PING_PONG
Definition: EffectBase.h:125
openshot::EffectBase::constrain
int constrain(int color_value)
Constrain a color value from 0 to 255.
Definition: EffectBase.cpp:83
openshot::ReaderInfo::fps
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
openshot::EffectInfoStruct::parent_effect_id
std::string parent_effect_id
Id of the parent effect (if there is one)
Definition: EffectBase.h:42
openshot::ReaderBase
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:75
openshot::EffectBase::mask_loop_mode
int mask_loop_mode
Behavior when mask range reaches the end.
Definition: EffectBase.h:129
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:40
openshot::ClipBase::GetFrame
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
openshot::ReaderBase::Close
virtual void Close()=0
Close the reader (and any resources it was consuming)
TrackedObjectBBox.h
Header file for the TrackedObjectBBox class.
openshot::EffectBase::MASK_TIME_SOURCE_FPS
@ MASK_TIME_SOURCE_FPS
Definition: EffectBase.h:119
QtImageReader.h
Header file for QtImageReader class.
openshot::EffectBase::HandlesMaskInternally
virtual bool HandlesMaskInternally() const
Optional override for effects that apply mask processing inside GetFrame().
Definition: EffectBase.h:104
openshot::BBox::cx
float cx
x-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:39
openshot::Timeline::GetClipEffect
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
Definition: Timeline.cpp:441
openshot::EffectBase::MapMaskFrameNumber
int64_t MapMaskFrameNumber(int64_t frame_number)
Convert an effect frame number to a mask source frame number.
Definition: EffectBase.cpp:398
ImageReader.h
Header file for ImageReader class.
openshot::ClipBase
This abstract class is the base class, used by all clips in libopenshot.
Definition: ClipBase.h:32
openshot::EffectBase::SetJson
virtual void SetJson(const std::string value)
Load JSON string into this object.
Definition: EffectBase.cpp:129
openshot::ChunkVersion
ChunkVersion
This enumeration allows the user to choose which version of the chunk they would like (low,...
Definition: ChunkReader.h:49
openshot::ClipBase::Layer
void Layer(int value)
Set layer of clip on timeline (lower number is covered by higher numbers)
Definition: ClipBase.cpp:31
openshot::EffectBase::ApplyCustomMaskBlend
virtual void ApplyCustomMaskBlend(std::shared_ptr< QImage > original_image, std::shared_ptr< QImage > effected_image, std::shared_ptr< QImage > mask_image, int64_t frame_number) const
Optional override for effects with custom mask implementation.
Definition: EffectBase.h:100
Exceptions.h
Header file for all Exception classes.
FFmpegReader.h
Header file for FFmpegReader class.
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:146
openshot::ReaderBase::ParentClip
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
Definition: ReaderBase.cpp:244
openshot::EffectBase::clip
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
Definition: EffectBase.h:77