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