OpenShot Library | libopenshot  1.0.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 }
Header file for ChunkReader class.
Header file for Clip class.
Header file for EffectBase class.
Header file for all Exception classes.
Header file for FFmpegReader class.
Header file for ImageReader class.
Header file for QtImageReader class.
Header file for ReaderBase class.
Header file for Timeline class.
Header file for the TrackedObjectBBox class.
Header file for ZeroMQ-based Logger class.
This class reads a special chunk-formatted file, which can be easily shared in a distributed environm...
Definition: ChunkReader.h:79
This abstract class is the base class, used by all clips in libopenshot.
Definition: ClipBase.h:32
float Start() const
Get start position (in seconds) of clip (trim start of video)
Definition: ClipBase.h:88
float Duration() const
Get the length of this clip (in seconds)
Definition: ClipBase.h:90
virtual float End() const
Get end position (in seconds) of clip (trim end of video)
Definition: ClipBase.h:89
std::string Id() const
Get the Id of this clip object.
Definition: ClipBase.h:85
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ClipBase.cpp:64
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
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...
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
Definition: ClipBase.h:87
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ClipBase.cpp:80
float Position() const
Get position on timeline (in seconds)
Definition: ClipBase.h:86
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
Definition: ClipBase.h:91
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
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
openshot::EffectBase * GetEffect(const std::string &id)
Look up an effect by ID.
Definition: Clip.cpp:543
This abstract class is the base class, used by all effects in libopenshot.
Definition: EffectBase.h:57
ReaderBase * CreateReaderFromJson(const Json::Value &reader_json) const
Create a reader instance from reader JSON.
Definition: EffectBase.cpp:300
int mask_loop_mode
Behavior when mask range reaches the end.
Definition: EffectBase.h:129
virtual bool UseCustomMaskBlend(int64_t frame_number) const
Optional override for effects that need custom mask behavior.
Definition: EffectBase.h:97
virtual void SetJson(const std::string value)
Load JSON string into this object.
Definition: EffectBase.cpp:129
EffectBase * parentEffect
Parent effect (which properties will set this effect properties)
Definition: EffectBase.h:108
Json::Value JsonInfo() const
Generate JSON object of meta data / info.
Definition: EffectBase.cpp:242
virtual bool HandlesMaskInternally() const
Optional override for effects that apply mask processing inside GetFrame().
Definition: EffectBase.h:104
ReaderBase * MaskReader()
Get the common mask reader.
Definition: EffectBase.h:182
bool mask_invert
Invert grayscale mask values before blending.
Definition: EffectBase.h:115
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
std::string ParentClipId() const
Return the ID of this effect's parent clip.
Definition: EffectBase.cpp:713
void SetParentEffect(std::string parentEffect_id)
Set the parent effect from which this properties will be set to.
Definition: EffectBase.cpp:688
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:102
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
Definition: EffectBase.cpp:676
std::string MaskSourceId() const
Get/Set effect ID used as a generated mask source.
Definition: EffectBase.h:189
int constrain(int color_value)
Constrain a color value from 0 to 255.
Definition: EffectBase.cpp:83
virtual std::string Json() const
Generate JSON string of this object.
Definition: EffectBase.cpp:95
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
double ResolveMaskHostFps()
Determine host FPS used to convert timeline frames to mask source FPS.
Definition: EffectBase.cpp:378
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
int mask_time_mode
How effect frames map to mask source frames.
Definition: EffectBase.h:128
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
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:146
int Order() const
Get the order that this effect should be executed.
Definition: EffectBase.h:193
double ResolveMaskSourceDuration() const
Determine mask source duration in seconds.
Definition: EffectBase.cpp:395
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
Definition: EffectBase.h:77
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:114
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
int64_t MapMaskFrameNumber(int64_t frame_number)
Convert an effect frame number to a mask source frame number.
Definition: EffectBase.cpp:410
void DisplayInfo(std::ostream *out=&std::cout)
Display effect information in the standard output stream (stdout)
Definition: EffectBase.cpp:68
This class uses the FFmpeg libraries, to open video files and audio files, and return openshot::Frame...
Definition: FFmpegReader.h:103
int num
Numerator for the fraction.
Definition: Fraction.h:32
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:40
int den
Denominator for the fraction.
Definition: Fraction.h:33
This class uses the ImageMagick++ libraries, to open image files, and return openshot::Frame objects ...
Definition: ImageReader.h:56
Exception for invalid JSON.
Definition: Exceptions.h:224
This class uses the Qt library, to open image files, and return openshot::Frame objects containing th...
Definition: QtImageReader.h:75
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:76
virtual bool IsOpen()=0
Determine if reader is open or closed.
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:91
virtual std::string Json() const =0
Generate JSON string of this object.
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:161
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:110
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
virtual void Open()=0
Open the reader (and start consuming resources, such as images or video files)
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
Definition: ReaderBase.cpp:244
virtual void Close()=0
Close the reader (and any resources it was consuming)
This class represents a timeline.
Definition: Timeline.h:153
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
Definition: Timeline.cpp:443
std::list< openshot::EffectBase * > ClipEffects() const
Return the list of effects on all clips.
Definition: Timeline.cpp:456
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
Definition: Timeline.cpp:432
void Log(std::string message)
Log message to all subscribers of this logger (if any)
Definition: ZmqLogger.cpp:103
static ZmqLogger * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition: ZmqLogger.cpp:35
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
ChunkVersion
This enumeration allows the user to choose which version of the chunk they would like (low,...
Definition: ChunkReader.h:50
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
This struct holds the information of a bounding-box.
float cy
y-coordinate of the bounding box center
float height
bounding box height
float cx
x-coordinate of the bounding box center
float width
bounding box width
float angle
bounding box rotation angle [degrees]
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
bool apply_before_clip
Apply effect to source before we evaluate the clip's keyframes.
Definition: EffectBase.h:46
std::string parent_effect_id
Id of the parent effect (if there is one)
Definition: EffectBase.h:42
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
std::string name
The name of the effect.
Definition: EffectBase.h:40
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41
bool has_tracked_object
Determines if this effect track objects through the clip.
Definition: EffectBase.h:45
bool has_single_image
Determines if this file only contains a single image.
Definition: ReaderBase.h:42
float duration
Length of time (in seconds)
Definition: ReaderBase.h:43
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
int64_t video_length
The number of frames in the video stream.
Definition: ReaderBase.h:53
bool has_audio
Determines if this file has an audio stream.
Definition: ReaderBase.h:41
int audio_stream_index
The index of the audio stream.
Definition: ReaderBase.h:63