35 #ifdef USE_IMAGEMAGICK
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;
78 *out <<
"--> Order: " << order << std::endl;
79 *out <<
"----------------------------" << std::endl;
88 else if (color_value > 255)
114 root[
"order"] =
Order();
116 root[
"mask_source_id"] = mask_source_id;
120 root[
"mask_reader"] = mask_reader->
JsonValue();
122 root[
"mask_reader"] = Json::objectValue;
138 catch (
const std::exception& e)
141 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
147 const std::string original_id = this->
Id();
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) {
160 my_root[
"id"] = original_id;
161 my_root[
"parent_effect_id"] = original_parent_effect_id;
164 my_root[
"id"] = this->
Id();
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"];
181 if (!my_root[
"order"].isNull())
182 Order(my_root[
"order"].asInt());
184 if (!my_root[
"apply_before_clip"].isNull())
187 if (!my_root[
"mask_invert"].isNull())
189 if (!my_root[
"mask_source_id"].isNull())
191 if (!my_root[
"mask_time_mode"].isNull()) {
192 const int time_mode = my_root[
"mask_time_mode"].asInt();
196 if (!my_root[
"mask_loop_mode"].isNull()) {
197 const int loop_mode = my_root[
"mask_loop_mode"].asInt();
204 const Json::Value mask_reader_json =
205 !my_root[
"mask_reader"].isNull() ? my_root[
"mask_reader"] : my_root[
"reader"];
207 if (!mask_reader_json.isNull()) {
208 if (!mask_reader_json[
"type"].isNull()) {
210 }
else if (mask_reader_json.isObject() && mask_reader_json.empty()) {
215 if (!my_root[
"parent_effect_id"].isNull()){
223 if (ParentTimeline()){
228 std::list<EffectBase*> effects = parentTimeline->
ClipEffects();
233 for (
auto const& effect : effects){
235 if ((effect->info.parent_effect_id == this->Id()) && (effect->Id() != this->
Id()))
236 effect->SetJsonValue(my_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);
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);
290 root[
"mask_reader"] =
add_property_json(
"Mask: Source", 0.0,
"reader", mask_reader->
Json(), NULL, 0, 1,
false, requested_frame);
292 root[
"mask_reader"] =
add_property_json(
"Mask: Source", 0.0,
"reader",
"{}", NULL, 0, 1,
false, requested_frame);
294 root[
"mask_source_id"] =
add_property_json(
"Mask: Effect Source", 0.0,
"string", mask_source_id, NULL, -1, -1,
false, requested_frame);
301 if (reader_json[
"type"].isNull())
305 const std::string type = reader_json[
"type"].asString();
307 if (type ==
"FFmpegReader") {
308 reader =
new FFmpegReader(reader_json[
"path"].asString());
314 }
else if (type ==
"QtImageReader") {
317 }
else if (type ==
"ChunkReader") {
318 reader =
new ChunkReader(reader_json[
"path"].asString(),
319 static_cast<ChunkVersion>(reader_json[
"chunk_version"].asInt()));
321 #ifdef USE_IMAGEMAGICK
322 }
else if (type ==
"ImageReader") {
323 reader =
new ImageReader(reader_json[
"path"].asString());
332 if (mask_reader == new_reader)
336 mask_reader->
Close();
340 mask_reader = new_reader;
341 cached_single_mask_image.reset();
342 cached_single_mask_width = 0;
343 cached_single_mask_height = 0;
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;
355 EffectBase* EffectBase::ResolveMaskSourceEffect() {
356 if (mask_source_id.empty())
362 if (source && source !=
this)
367 if (parent_timeline) {
370 source = parent_timeline->
GetEffect(mask_source_id);
371 if (source && source !=
this)
414 int64_t requested_index = std::max(int64_t(0), frame_number - 1);
415 if (!
clip && ParentTimeline()) {
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);
422 int64_t mapped_index = requested_index;
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));
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);
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
446 range_end = std::min(range_end, source_len);
447 if (range_end < range_start)
448 range_end = range_start;
450 const int64_t range_len = std::max(int64_t(1), range_end - range_start + 1);
451 int64_t range_index = mapped_index;
455 range_index = mapped_index % range_len;
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;
470 if (mapped_index < 0)
472 else if (mapped_index >= range_len)
473 range_index = range_len - 1;
475 range_index = mapped_index;
479 int64_t mapped_frame = range_start + range_index;
481 mapped_frame = std::min(std::max(int64_t(1), mapped_frame), source_len);
482 return std::max(int64_t(1), mapped_frame);
485 std::shared_ptr<QImage> EffectBase::GetMaskImage(std::shared_ptr<QImage> target_image, int64_t frame_number) {
486 if (!target_image || target_image->isNull())
489 EffectBase* source_effect = ResolveMaskSourceEffect();
491 auto generated_mask = source_effect->
TrackedObjectMask(target_image, frame_number);
492 if (generated_mask && !generated_mask->isNull())
493 return generated_mask;
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));
504 std::shared_ptr<QImage> source_mask;
505 bool used_cached_scaled =
false;
506 #pragma omp critical (open_effect_mask_reader)
509 if (!mask_reader->
IsOpen())
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;
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());
525 }
catch (
const std::exception& e) {
527 std::string(
"EffectBase::GetMaskImage unable to read mask frame: ") + e.what());
532 if (!source_mask || source_mask->isNull())
535 if (used_cached_scaled)
538 auto scaled_mask = std::make_shared<QImage>(
540 target_image->width(), target_image->height(),
541 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
543 cached_single_mask_image = scaled_mask;
544 cached_single_mask_width = target_image->width();
545 cached_single_mask_height = target_image->height();
551 if (!target_image || target_image->isNull() ||
trackedObjects.empty())
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));
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)));
563 bool drew_any_box =
false;
565 auto bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(trackedObject.second);
568 if (!bbox->Contains(frame_number) || bbox->visible.GetValue(frame_number) != 1)
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)
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);
582 if (std::abs(box.
angle) > 0.0001f) {
584 painter.translate(rect.center());
585 painter.rotate(box.
angle);
586 painter.drawRoundedRect(QRectF(-w / 2.0, -h / 2.0, w, h), corner, corner);
589 painter.drawRoundedRect(rect, corner, corner);
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)
604 if (original_image->size() != effected_image->size() || effected_image->size() != mask_image->size())
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();
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]);
626 const float factor =
static_cast<float>(gray) / 255.0f;
627 const float inverse = 1.0f - factor;
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));
643 if (!
info.
has_video || (!mask_reader && mask_source_id.empty()))
644 return GetFrame(frame, frame_number);
648 return GetFrame(frame, frame_number);
650 auto pre_image = frame->GetImage();
651 if (!pre_image || pre_image->isNull())
652 return GetFrame(frame, frame_number);
654 const auto original_image = std::make_shared<QImage>(pre_image->copy());
655 auto output_frame =
GetFrame(frame, frame_number);
658 auto effected_image = output_frame->GetImage();
659 if (!effected_image || effected_image->isNull() ||
660 effected_image->size() != original_image->size())
663 auto mask_image = GetMaskImage(effected_image, frame_number);
664 if (!mask_image || mask_image->isNull())
670 BlendWithMask(original_image, effected_image, mask_image);
698 if (parentEffectPtr){
704 EffectJSON[
"id"] = this->
Id();