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  const std::string original_id = this->Id();
148  const std::string original_parent_effect_id = this->info.parent_effect_id;
149 
150  // Set this effect properties with the parent effect properties (except the id and parent_effect_id)
151  Json::Value my_root;
152  const bool applying_parent_payload =
153  !original_id.empty() &&
154  !original_parent_effect_id.empty() &&
155  !root["id"].isNull() &&
156  root["id"].asString() == original_parent_effect_id &&
157  root["id"].asString() != original_id;
158  if (applying_parent_payload) {
159  my_root = root;
160  my_root["id"] = original_id;
161  my_root["parent_effect_id"] = original_parent_effect_id;
162  } else if (parentEffect){
163  my_root = parentEffect->JsonValue();
164  my_root["id"] = this->Id();
165  my_root["parent_effect_id"] = this->info.parent_effect_id;
166  } else {
167  my_root = root;
168  }
169 
170  // Legacy compatibility: older shared-mask JSON stored source trim
171  // separately from the effect trim. Canonical trim now uses ClipBase.
172  if (my_root["start"].isNull() && !my_root["mask_start"].isNull())
173  my_root["start"] = my_root["mask_start"];
174  if (my_root["end"].isNull() && !my_root["mask_end"].isNull())
175  my_root["end"] = my_root["mask_end"];
176 
177  // Set parent data
178  ClipBase::SetJsonValue(my_root);
179 
180  // Set data from Json (if key is found)
181  if (!my_root["order"].isNull())
182  Order(my_root["order"].asInt());
183 
184  if (!my_root["apply_before_clip"].isNull())
185  info.apply_before_clip = my_root["apply_before_clip"].asBool();
186 
187  if (!my_root["mask_invert"].isNull())
188  mask_invert = my_root["mask_invert"].asBool();
189  if (!my_root["mask_source_id"].isNull())
190  MaskSourceId(my_root["mask_source_id"].asString());
191  if (!my_root["mask_time_mode"].isNull()) {
192  const int time_mode = my_root["mask_time_mode"].asInt();
193  mask_time_mode = (time_mode == MASK_TIME_TIMELINE || time_mode == MASK_TIME_SOURCE_FPS)
194  ? time_mode : MASK_TIME_SOURCE_FPS;
195  }
196  if (!my_root["mask_loop_mode"].isNull()) {
197  const int loop_mode = my_root["mask_loop_mode"].asInt();
198  if (loop_mode >= MASK_LOOP_PLAY_ONCE && loop_mode <= MASK_LOOP_PING_PONG)
199  mask_loop_mode = loop_mode;
200  else
202  }
203 
204  const Json::Value mask_reader_json =
205  !my_root["mask_reader"].isNull() ? my_root["mask_reader"] : my_root["reader"];
206 
207  if (!mask_reader_json.isNull()) {
208  if (!mask_reader_json["type"].isNull()) {
209  MaskReader(CreateReaderFromJson(mask_reader_json));
210  } else if (mask_reader_json.isObject() && mask_reader_json.empty()) {
211  MaskReader(NULL);
212  }
213  }
214 
215  if (!my_root["parent_effect_id"].isNull()){
216  info.parent_effect_id = my_root["parent_effect_id"].asString();
217  if (info.parent_effect_id.size() > 0 && info.parent_effect_id != "" && parentEffect == NULL)
219  else
220  parentEffect = NULL;
221  }
222 
223  if (ParentTimeline()){
224  // Get parent timeline
225  Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
226 
227  // Get the list of effects on the timeline
228  std::list<EffectBase*> effects = parentTimeline->ClipEffects();
229 
230  // TODO: Fix recursive call for Object Detection
231 
232  // Loop through the effects and check if we have a child effect linked to this effect
233  for (auto const& effect : effects){
234  // Set the properties of all effects which parentEffect points to this
235  if ((effect->info.parent_effect_id == this->Id()) && (effect->Id() != this->Id()))
236  effect->SetJsonValue(my_root);
237  }
238  }
239 }
240 
241 // Generate Json::Value for this object
242 Json::Value EffectBase::JsonInfo() const {
243 
244  // Create root json object
245  Json::Value root;
246  root["name"] = info.name;
247  root["class_name"] = info.class_name;
248  root["description"] = info.description;
249  root["has_video"] = info.has_video;
250  root["has_audio"] = info.has_audio;
251 
252  // return JsonValue
253  return root;
254 }
255 
256 // Get all properties for a specific frame
257 Json::Value EffectBase::BasePropertiesJSON(int64_t requested_frame) const {
258  // Generate JSON properties list
259  Json::Value root;
260  root["id"] = add_property_json("ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame);
261  root["position"] = add_property_json("Position", Position(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
262  root["layer"] = add_property_json("Track", Layer(), "int", "", NULL, 0, 20, false, requested_frame);
263  root["start"] = add_property_json("Start", Start(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
264  root["end"] = add_property_json("End", End(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
265  root["duration"] = add_property_json("Duration", Duration(), "float", "", NULL, 0, 30 * 60 * 60 * 48, true, requested_frame);
266 
267  // Add replace_image choices (dropdown style)
268  root["apply_before_clip"] = add_property_json("Apply to Source", info.apply_before_clip, "int", "", NULL, 0, 1, false, requested_frame);
269  root["apply_before_clip"]["choices"].append(add_property_choice_json("Yes", true, info.apply_before_clip));
270  root["apply_before_clip"]["choices"].append(add_property_choice_json("No", false, info.apply_before_clip));
271 
272  // Set the parent effect which properties this effect will inherit
273  root["parent_effect_id"] = add_property_json("Parent", 0.0, "string", info.parent_effect_id, NULL, -1, -1, false, requested_frame);
274 
275  if (info.has_video) {
276  root["mask_invert"] = add_property_json("Mask: Invert", mask_invert, "int", "", NULL, 0, 1, false, requested_frame);
277  root["mask_invert"]["choices"].append(add_property_choice_json("Yes", true, mask_invert));
278  root["mask_invert"]["choices"].append(add_property_choice_json("No", false, mask_invert));
279 
280  root["mask_time_mode"] = add_property_json("Mask: Time Mode", mask_time_mode, "int", "", NULL, 0, 1, false, requested_frame);
281  root["mask_time_mode"]["choices"].append(add_property_choice_json("Timeline", MASK_TIME_TIMELINE, mask_time_mode));
282  root["mask_time_mode"]["choices"].append(add_property_choice_json("Source FPS", MASK_TIME_SOURCE_FPS, mask_time_mode));
283 
284  root["mask_loop_mode"] = add_property_json("Mask: Loop", mask_loop_mode, "int", "", NULL, 0, 2, false, requested_frame);
285  root["mask_loop_mode"]["choices"].append(add_property_choice_json("Play Once", MASK_LOOP_PLAY_ONCE, mask_loop_mode));
286  root["mask_loop_mode"]["choices"].append(add_property_choice_json("Repeat", MASK_LOOP_REPEAT, mask_loop_mode));
287  root["mask_loop_mode"]["choices"].append(add_property_choice_json("Ping-Pong", MASK_LOOP_PING_PONG, mask_loop_mode));
288 
289  if (mask_reader)
290  root["mask_reader"] = add_property_json("Mask: Source", 0.0, "reader", mask_reader->Json(), NULL, 0, 1, false, requested_frame);
291  else
292  root["mask_reader"] = add_property_json("Mask: Source", 0.0, "reader", "{}", NULL, 0, 1, false, requested_frame);
293 
294  root["mask_source_id"] = add_property_json("Mask: Effect Source", 0.0, "string", mask_source_id, NULL, -1, -1, false, requested_frame);
295  }
296 
297  return root;
298 }
299 
300 ReaderBase* EffectBase::CreateReaderFromJson(const Json::Value& reader_json) const {
301  if (reader_json["type"].isNull())
302  return NULL;
303 
304  ReaderBase* reader = NULL;
305  const std::string type = reader_json["type"].asString();
306 
307  if (type == "FFmpegReader") {
308  reader = new FFmpegReader(reader_json["path"].asString());
309  reader->SetJsonValue(reader_json);
310  // Mask readers are video-only sources. Disabling audio avoids FFmpeg
311  // A/V readiness fallbacks that can repeat stale video frames.
312  reader->info.has_audio = false;
313  reader->info.audio_stream_index = -1;
314  } else if (type == "QtImageReader") {
315  reader = new QtImageReader(reader_json["path"].asString());
316  reader->SetJsonValue(reader_json);
317  } else if (type == "ChunkReader") {
318  reader = new ChunkReader(reader_json["path"].asString(),
319  static_cast<ChunkVersion>(reader_json["chunk_version"].asInt()));
320  reader->SetJsonValue(reader_json);
321 #ifdef USE_IMAGEMAGICK
322  } else if (type == "ImageReader") {
323  reader = new ImageReader(reader_json["path"].asString());
324  reader->SetJsonValue(reader_json);
325 #endif
326  }
327 
328  return reader;
329 }
330 
332  if (mask_reader == new_reader)
333  return;
334 
335  if (mask_reader) {
336  mask_reader->Close();
337  delete mask_reader;
338  }
339 
340  mask_reader = new_reader;
341  cached_single_mask_image.reset();
342  cached_single_mask_width = 0;
343  cached_single_mask_height = 0;
344  if (mask_reader)
345  mask_reader->ParentClip(clip);
346 }
347 
348 void EffectBase::MaskSourceId(const std::string& new_mask_source_id) {
349  mask_source_id = new_mask_source_id;
350  cached_single_mask_image.reset();
351  cached_single_mask_width = 0;
352  cached_single_mask_height = 0;
353 }
354 
355 EffectBase* EffectBase::ResolveMaskSourceEffect() {
356  if (mask_source_id.empty())
357  return NULL;
358 
359  Clip* parent_clip = dynamic_cast<Clip*>(clip);
360  if (parent_clip) {
361  EffectBase* source = parent_clip->GetEffect(mask_source_id);
362  if (source && source != this)
363  return source;
364  }
365 
366  Timeline* parent_timeline = dynamic_cast<Timeline*>(ParentTimeline());
367  if (parent_timeline) {
368  EffectBase* source = parent_timeline->GetClipEffect(mask_source_id);
369  if (!source)
370  source = parent_timeline->GetEffect(mask_source_id);
371  if (source && source != this)
372  return source;
373  }
374 
375  return NULL;
376 }
377 
379  if (clip) {
380  Clip* parent_clip = dynamic_cast<Clip*>(clip);
381  if (parent_clip && parent_clip->info.fps.num > 0 && parent_clip->info.fps.den > 0)
382  return parent_clip->info.fps.ToDouble();
383  }
384 
385  Timeline* parent_timeline = dynamic_cast<Timeline*>(ParentTimeline());
386  if (parent_timeline && parent_timeline->info.fps.num > 0 && parent_timeline->info.fps.den > 0)
387  return parent_timeline->info.fps.ToDouble();
388 
389  if (mask_reader && mask_reader->info.fps.num > 0 && mask_reader->info.fps.den > 0)
390  return mask_reader->info.fps.ToDouble();
391 
392  return 30.0;
393 }
394 
396  if (!mask_reader)
397  return 0.0;
398 
399  if (mask_reader->info.duration > 0.0f)
400  return static_cast<double>(mask_reader->info.duration);
401 
402  if (mask_reader->info.video_length > 0 &&
403  mask_reader->info.fps.num > 0 && mask_reader->info.fps.den > 0) {
404  return static_cast<double>(mask_reader->info.video_length) / mask_reader->info.fps.ToDouble();
405  }
406 
407  return 0.0;
408 }
409 
410 int64_t EffectBase::MapMaskFrameNumber(int64_t frame_number) {
411  if (!mask_reader)
412  return frame_number;
413 
414  int64_t requested_index = std::max(int64_t(0), frame_number - 1);
415  if (!clip && ParentTimeline()) {
416  const double host_fps = ResolveMaskHostFps();
417  if (host_fps > 0.0) {
418  const int64_t start_offset = static_cast<int64_t>(std::llround(std::max(0.0f, Start()) * host_fps));
419  requested_index = std::max(int64_t(0), requested_index - start_offset);
420  }
421  }
422  int64_t mapped_index = requested_index;
423 
425  mask_reader->info.fps.num > 0 && mask_reader->info.fps.den > 0) {
426  const double host_fps = ResolveMaskHostFps();
427  const double source_fps = mask_reader->info.fps.ToDouble();
428  if (host_fps > 0.0 && source_fps > 0.0) {
429  const double seconds = static_cast<double>(requested_index) / host_fps;
430  mapped_index = static_cast<int64_t>(std::llround(seconds * source_fps));
431  }
432  }
433 
434  const int64_t source_len = mask_reader->info.video_length;
435  const double source_fps = (mask_reader->info.fps.num > 0 && mask_reader->info.fps.den > 0)
436  ? mask_reader->info.fps.ToDouble() : 30.0;
437  const double source_duration = ResolveMaskSourceDuration();
438  const double start_sec = std::min<double>(std::max(0.0f, Start()), source_duration);
439  const double end_sec = std::min<double>(std::max(0.0f, End()), source_duration);
440 
441  const int64_t range_start = std::max(int64_t(1), static_cast<int64_t>(std::llround(start_sec * source_fps)) + 1);
442  int64_t range_end = (end_sec > 0.0)
443  ? static_cast<int64_t>(std::llround(end_sec * source_fps)) + 1
444  : source_len;
445  if (source_len > 0)
446  range_end = std::min(range_end, source_len);
447  if (range_end < range_start)
448  range_end = range_start;
449 
450  const int64_t range_len = std::max(int64_t(1), range_end - range_start + 1);
451  int64_t range_index = mapped_index;
452 
453  switch (mask_loop_mode) {
454  case MASK_LOOP_REPEAT:
455  range_index = mapped_index % range_len;
456  break;
457  case MASK_LOOP_PING_PONG:
458  if (range_len > 1) {
459  const int64_t cycle_len = (range_len * 2) - 2;
460  int64_t phase = mapped_index % cycle_len;
461  if (phase >= range_len)
462  phase = cycle_len - phase;
463  range_index = phase;
464  } else {
465  range_index = 0;
466  }
467  break;
468  case MASK_LOOP_PLAY_ONCE:
469  default:
470  if (mapped_index < 0)
471  range_index = 0;
472  else if (mapped_index >= range_len)
473  range_index = range_len - 1;
474  else
475  range_index = mapped_index;
476  break;
477  }
478 
479  int64_t mapped_frame = range_start + range_index;
480  if (source_len > 0)
481  mapped_frame = std::min(std::max(int64_t(1), mapped_frame), source_len);
482  return std::max(int64_t(1), mapped_frame);
483 }
484 
485 std::shared_ptr<QImage> EffectBase::GetMaskImage(std::shared_ptr<QImage> target_image, int64_t frame_number) {
486  if (!target_image || target_image->isNull())
487  return {};
488 
489  EffectBase* source_effect = ResolveMaskSourceEffect();
490  if (source_effect) {
491  auto generated_mask = source_effect->TrackedObjectMask(target_image, frame_number);
492  if (generated_mask && !generated_mask->isNull())
493  return generated_mask;
494 
495  auto empty_mask = std::make_shared<QImage>(
496  target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
497  empty_mask->fill(QColor(0, 0, 0, 255));
498  return empty_mask;
499  }
500 
501  if (!mask_reader)
502  return {};
503 
504  std::shared_ptr<QImage> source_mask;
505  bool used_cached_scaled = false;
506  #pragma omp critical (open_effect_mask_reader)
507  {
508  try {
509  if (!mask_reader->IsOpen())
510  mask_reader->Open();
511 
512  if (mask_reader->info.has_single_image &&
513  cached_single_mask_image &&
514  cached_single_mask_width == target_image->width() &&
515  cached_single_mask_height == target_image->height()) {
516  source_mask = cached_single_mask_image;
517  used_cached_scaled = true;
518  }
519  else {
520  const int64_t mapped_frame = MapMaskFrameNumber(frame_number);
521  auto source_frame = mask_reader->GetFrame(mapped_frame);
522  if (source_frame && source_frame->GetImage() && !source_frame->GetImage()->isNull())
523  source_mask = std::make_shared<QImage>(*source_frame->GetImage());
524  }
525  } catch (const std::exception& e) {
527  std::string("EffectBase::GetMaskImage unable to read mask frame: ") + e.what());
528  source_mask.reset();
529  }
530  }
531 
532  if (!source_mask || source_mask->isNull())
533  return {};
534 
535  if (used_cached_scaled)
536  return source_mask;
537 
538  auto scaled_mask = std::make_shared<QImage>(
539  source_mask->scaled(
540  target_image->width(), target_image->height(),
541  Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
542  if (mask_reader->info.has_single_image) {
543  cached_single_mask_image = scaled_mask;
544  cached_single_mask_width = target_image->width();
545  cached_single_mask_height = target_image->height();
546  }
547  return scaled_mask;
548 }
549 
550 std::shared_ptr<QImage> EffectBase::TrackedObjectMask(std::shared_ptr<QImage> target_image, int64_t frame_number) const {
551  if (!target_image || target_image->isNull() || trackedObjects.empty())
552  return {};
553 
554  auto mask_image = std::make_shared<QImage>(
555  target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
556  mask_image->fill(QColor(0, 0, 0, 255));
557 
558  QPainter painter(mask_image.get());
559  painter.setRenderHint(QPainter::Antialiasing, true);
560  painter.setPen(Qt::NoPen);
561  painter.setBrush(QBrush(QColor(255, 255, 255, 255)));
562 
563  bool drew_any_box = false;
564  for (auto const& trackedObject : trackedObjects) {
565  auto bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(trackedObject.second);
566  if (!bbox)
567  continue;
568  if (!bbox->Contains(frame_number) || bbox->visible.GetValue(frame_number) != 1)
569  continue;
570 
571  BBox box = bbox->GetBox(frame_number);
572  if (box.width <= 0.0f || box.height <= 0.0f || box.cx < 0.0f || box.cy < 0.0f)
573  continue;
574 
575  const double x = (box.cx - box.width / 2.0) * target_image->width();
576  const double y = (box.cy - box.height / 2.0) * target_image->height();
577  const double w = box.width * target_image->width();
578  const double h = box.height * target_image->height();
579  const double corner = bbox->background_corner.GetValue(frame_number);
580  QRectF rect(x, y, w, h);
581 
582  if (std::abs(box.angle) > 0.0001f) {
583  painter.save();
584  painter.translate(rect.center());
585  painter.rotate(box.angle);
586  painter.drawRoundedRect(QRectF(-w / 2.0, -h / 2.0, w, h), corner, corner);
587  painter.restore();
588  } else {
589  painter.drawRoundedRect(rect, corner, corner);
590  }
591  drew_any_box = true;
592  }
593 
594  painter.end();
595  if (!drew_any_box)
596  return {};
597  return mask_image;
598 }
599 
600 void EffectBase::BlendWithMask(std::shared_ptr<QImage> original_image, std::shared_ptr<QImage> effected_image,
601  std::shared_ptr<QImage> mask_image) const {
602  if (!original_image || !effected_image || !mask_image)
603  return;
604  if (original_image->size() != effected_image->size() || effected_image->size() != mask_image->size())
605  return;
606 
607  unsigned char* original_pixels = reinterpret_cast<unsigned char*>(original_image->bits());
608  unsigned char* effected_pixels = reinterpret_cast<unsigned char*>(effected_image->bits());
609  unsigned char* mask_pixels = reinterpret_cast<unsigned char*>(mask_image->bits());
610  const int width = effected_image->width();
611  const int height = effected_image->height();
612  const int original_stride = original_image->bytesPerLine();
613  const int effected_stride = effected_image->bytesPerLine();
614  const int mask_stride = mask_image->bytesPerLine();
615 
616  #pragma omp parallel for schedule(static)
617  for (int y = 0; y < height; ++y) {
618  unsigned char* original_row = original_pixels + y * original_stride;
619  unsigned char* effected_row = effected_pixels + y * effected_stride;
620  unsigned char* mask_row = mask_pixels + y * mask_stride;
621  for (int x = 0; x < width; ++x) {
622  const int idx = x * 4;
623  int gray = qGray(mask_row[idx], mask_row[idx + 1], mask_row[idx + 2]);
624  if (mask_invert)
625  gray = 255 - gray;
626  const float factor = static_cast<float>(gray) / 255.0f;
627  const float inverse = 1.0f - factor;
628 
629  effected_row[idx] = static_cast<unsigned char>(
630  (original_row[idx] * inverse) + (effected_row[idx] * factor));
631  effected_row[idx + 1] = static_cast<unsigned char>(
632  (original_row[idx + 1] * inverse) + (effected_row[idx + 1] * factor));
633  effected_row[idx + 2] = static_cast<unsigned char>(
634  (original_row[idx + 2] * inverse) + (effected_row[idx + 2] * factor));
635  effected_row[idx + 3] = static_cast<unsigned char>(
636  (original_row[idx + 3] * inverse) + (effected_row[idx + 3] * factor));
637  }
638  }
639 }
640 
641 std::shared_ptr<openshot::Frame> EffectBase::ProcessFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number) {
642  // Audio-only effects skip common mask handling.
643  if (!info.has_video || (!mask_reader && mask_source_id.empty()))
644  return GetFrame(frame, frame_number);
645 
646  // Effects that already apply masks inside GetFrame() should bypass common blend handling.
647  if (HandlesMaskInternally())
648  return GetFrame(frame, frame_number);
649 
650  auto pre_image = frame->GetImage();
651  if (!pre_image || pre_image->isNull())
652  return GetFrame(frame, frame_number);
653 
654  const auto original_image = std::make_shared<QImage>(pre_image->copy());
655  auto output_frame = GetFrame(frame, frame_number);
656  if (!output_frame)
657  return output_frame;
658  auto effected_image = output_frame->GetImage();
659  if (!effected_image || effected_image->isNull() ||
660  effected_image->size() != original_image->size())
661  return output_frame;
662 
663  auto mask_image = GetMaskImage(effected_image, frame_number);
664  if (!mask_image || mask_image->isNull())
665  return output_frame;
666 
667  if (UseCustomMaskBlend(frame_number))
668  ApplyCustomMaskBlend(original_image, effected_image, mask_image, frame_number);
669  else
670  BlendWithMask(original_image, effected_image, mask_image);
671 
672  return output_frame;
673 }
674 
677  return clip;
678 }
679 
682  clip = new_clip;
683  if (mask_reader)
684  mask_reader->ParentClip(new_clip);
685 }
686 
687 // Set the parent effect from which this properties will be set to
688 void EffectBase::SetParentEffect(std::string parentEffect_id) {
689 
690  // Get parent Timeline
691  Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
692 
693  if (parentTimeline){
694 
695  // Get a pointer to the parentEffect
696  EffectBase* parentEffectPtr = parentTimeline->GetClipEffect(parentEffect_id);
697 
698  if (parentEffectPtr){
699  // Set the parent Effect
700  parentEffect = parentEffectPtr;
701 
702  // Set the properties of this effect with the parent effect's properties
703  Json::Value EffectJSON = parentEffect->JsonValue();
704  EffectJSON["id"] = this->Id();
705  EffectJSON["parent_effect_id"] = this->info.parent_effect_id;
706  this->SetJsonValue(EffectJSON);
707  }
708  }
709  return;
710 }
711 
712 // Return the ID of this effect's parent clip
713 std::string EffectBase::ParentClipId() const{
714  if(clip)
715  return clip->Id();
716  else
717  return "";
718 }
719 
721  MaskReader(NULL);
722 }
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:720
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:242
Clip.h
Header file for Clip class.
openshot::BBox::height
float height
bounding box height
Definition: TrackedObjectBBox.h:51
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:676
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:49
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:300
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:688
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:641
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:257
openshot::BBox::angle
float angle
bounding box rotation angle [degrees]
Definition: TrackedObjectBBox.h:52
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:50
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:456
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:395
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:550
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:713
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:378
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:46
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:432
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:48
openshot::Timeline::GetClipEffect
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
Definition: Timeline.cpp:443
openshot::EffectBase::MapMaskFrameNumber
int64_t MapMaskFrameNumber(int64_t frame_number)
Convert an effect frame number to a mask source frame number.
Definition: EffectBase.cpp:410
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