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)");
148 if (ParentTimeline()){
153 std::list<EffectBase*> effects = parentTimeline->
ClipEffects();
158 for (
auto const& effect : effects){
160 if ((effect->info.parent_effect_id == this->Id()) && (effect->Id() != this->
Id()))
161 effect->SetJsonValue(root);
169 my_root[
"id"] = this->
Id();
177 if (my_root[
"start"].isNull() && !my_root[
"mask_start"].isNull())
178 my_root[
"start"] = my_root[
"mask_start"];
179 if (my_root[
"end"].isNull() && !my_root[
"mask_end"].isNull())
180 my_root[
"end"] = my_root[
"mask_end"];
186 if (!my_root[
"order"].isNull())
187 Order(my_root[
"order"].asInt());
189 if (!my_root[
"apply_before_clip"].isNull())
192 if (!my_root[
"mask_invert"].isNull())
194 if (!my_root[
"mask_source_id"].isNull())
196 if (!my_root[
"mask_time_mode"].isNull()) {
197 const int time_mode = my_root[
"mask_time_mode"].asInt();
201 if (!my_root[
"mask_loop_mode"].isNull()) {
202 const int loop_mode = my_root[
"mask_loop_mode"].asInt();
209 const Json::Value mask_reader_json =
210 !my_root[
"mask_reader"].isNull() ? my_root[
"mask_reader"] : my_root[
"reader"];
212 if (!mask_reader_json.isNull()) {
213 if (!mask_reader_json[
"type"].isNull()) {
215 }
else if (mask_reader_json.isObject() && mask_reader_json.empty()) {
220 if (!my_root[
"parent_effect_id"].isNull()){
248 root[
"id"] =
add_property_json(
"ID", 0.0,
"string",
Id(), NULL, -1, -1,
true, requested_frame);
249 root[
"position"] =
add_property_json(
"Position",
Position(),
"float",
"", NULL, 0, 30 * 60 * 60 * 48,
false, requested_frame);
251 root[
"start"] =
add_property_json(
"Start",
Start(),
"float",
"", NULL, 0, 30 * 60 * 60 * 48,
false, requested_frame);
252 root[
"end"] =
add_property_json(
"End",
End(),
"float",
"", NULL, 0, 30 * 60 * 60 * 48,
false, requested_frame);
253 root[
"duration"] =
add_property_json(
"Duration", Duration(),
"float",
"", NULL, 0, 30 * 60 * 60 * 48,
true, requested_frame);
278 root[
"mask_reader"] =
add_property_json(
"Mask: Source", 0.0,
"reader", mask_reader->
Json(), NULL, 0, 1,
false, requested_frame);
280 root[
"mask_reader"] =
add_property_json(
"Mask: Source", 0.0,
"reader",
"{}", NULL, 0, 1,
false, requested_frame);
282 root[
"mask_source_id"] =
add_property_json(
"Mask: Effect Source", 0.0,
"string", mask_source_id, NULL, -1, -1,
false, requested_frame);
289 if (reader_json[
"type"].isNull())
293 const std::string type = reader_json[
"type"].asString();
295 if (type ==
"FFmpegReader") {
296 reader =
new FFmpegReader(reader_json[
"path"].asString());
302 }
else if (type ==
"QtImageReader") {
305 }
else if (type ==
"ChunkReader") {
306 reader =
new ChunkReader(reader_json[
"path"].asString(),
307 static_cast<ChunkVersion>(reader_json[
"chunk_version"].asInt()));
309 #ifdef USE_IMAGEMAGICK
310 }
else if (type ==
"ImageReader") {
311 reader =
new ImageReader(reader_json[
"path"].asString());
320 if (mask_reader == new_reader)
324 mask_reader->
Close();
328 mask_reader = new_reader;
329 cached_single_mask_image.reset();
330 cached_single_mask_width = 0;
331 cached_single_mask_height = 0;
337 mask_source_id = new_mask_source_id;
338 cached_single_mask_image.reset();
339 cached_single_mask_width = 0;
340 cached_single_mask_height = 0;
343 EffectBase* EffectBase::ResolveMaskSourceEffect() {
344 if (mask_source_id.empty())
350 if (source && source !=
this)
355 if (parent_timeline) {
358 source = parent_timeline->
GetEffect(mask_source_id);
359 if (source && source !=
this)
402 int64_t requested_index = std::max(int64_t(0), frame_number - 1);
403 if (!
clip && ParentTimeline()) {
405 if (host_fps > 0.0) {
406 const int64_t start_offset =
static_cast<int64_t
>(std::llround(std::max(0.0f,
Start()) * host_fps));
407 requested_index = std::max(int64_t(0), requested_index - start_offset);
410 int64_t mapped_index = requested_index;
416 if (host_fps > 0.0 && source_fps > 0.0) {
417 const double seconds =
static_cast<double>(requested_index) / host_fps;
418 mapped_index =
static_cast<int64_t
>(std::llround(seconds * source_fps));
426 const double start_sec = std::min<double>(std::max(0.0f,
Start()), source_duration);
427 const double end_sec = std::min<double>(std::max(0.0f,
End()), source_duration);
429 const int64_t range_start = std::max(int64_t(1),
static_cast<int64_t
>(std::llround(start_sec * source_fps)) + 1);
430 int64_t range_end = (end_sec > 0.0)
431 ?
static_cast<int64_t
>(std::llround(end_sec * source_fps)) + 1
434 range_end = std::min(range_end, source_len);
435 if (range_end < range_start)
436 range_end = range_start;
438 const int64_t range_len = std::max(int64_t(1), range_end - range_start + 1);
439 int64_t range_index = mapped_index;
443 range_index = mapped_index % range_len;
447 const int64_t cycle_len = (range_len * 2) - 2;
448 int64_t phase = mapped_index % cycle_len;
449 if (phase >= range_len)
450 phase = cycle_len - phase;
458 if (mapped_index < 0)
460 else if (mapped_index >= range_len)
461 range_index = range_len - 1;
463 range_index = mapped_index;
467 int64_t mapped_frame = range_start + range_index;
469 mapped_frame = std::min(std::max(int64_t(1), mapped_frame), source_len);
470 return std::max(int64_t(1), mapped_frame);
473 std::shared_ptr<QImage> EffectBase::GetMaskImage(std::shared_ptr<QImage> target_image, int64_t frame_number) {
474 if (!target_image || target_image->isNull())
477 EffectBase* source_effect = ResolveMaskSourceEffect();
479 auto generated_mask = source_effect->
TrackedObjectMask(target_image, frame_number);
480 if (generated_mask && !generated_mask->isNull())
481 return generated_mask;
483 auto empty_mask = std::make_shared<QImage>(
484 target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
485 empty_mask->fill(QColor(0, 0, 0, 255));
492 std::shared_ptr<QImage> source_mask;
493 bool used_cached_scaled =
false;
494 #pragma omp critical (open_effect_mask_reader)
497 if (!mask_reader->
IsOpen())
501 cached_single_mask_image &&
502 cached_single_mask_width == target_image->width() &&
503 cached_single_mask_height == target_image->height()) {
504 source_mask = cached_single_mask_image;
505 used_cached_scaled =
true;
509 auto source_frame = mask_reader->
GetFrame(mapped_frame);
510 if (source_frame && source_frame->GetImage() && !source_frame->GetImage()->isNull())
511 source_mask = std::make_shared<QImage>(*source_frame->GetImage());
513 }
catch (
const std::exception& e) {
515 std::string(
"EffectBase::GetMaskImage unable to read mask frame: ") + e.what());
520 if (!source_mask || source_mask->isNull())
523 if (used_cached_scaled)
526 auto scaled_mask = std::make_shared<QImage>(
528 target_image->width(), target_image->height(),
529 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
531 cached_single_mask_image = scaled_mask;
532 cached_single_mask_width = target_image->width();
533 cached_single_mask_height = target_image->height();
539 if (!target_image || target_image->isNull() ||
trackedObjects.empty())
542 auto mask_image = std::make_shared<QImage>(
543 target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
544 mask_image->fill(QColor(0, 0, 0, 255));
546 QPainter painter(mask_image.get());
547 painter.setRenderHint(QPainter::Antialiasing,
false);
548 painter.setPen(Qt::NoPen);
549 painter.setBrush(QBrush(QColor(255, 255, 255, 255)));
551 bool drew_any_box =
false;
553 auto bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(trackedObject.second);
556 if (!bbox->Contains(frame_number) || bbox->visible.GetValue(frame_number) != 1)
559 BBox box = bbox->GetBox(frame_number);
560 if (box.
width <= 0.0f || box.
height <= 0.0f || box.
cx < 0.0f || box.
cy < 0.0f)
563 const double x = (box.
cx - box.
width / 2.0) * target_image->
width();
564 const double y = (box.
cy - box.
height / 2.0) * target_image->height();
565 const double w = box.
width * target_image->width();
566 const double h = box.
height * target_image->height();
567 QRectF rect(x, y, w, h);
569 if (std::abs(box.
angle) > 0.0001f) {
571 painter.translate(rect.center());
572 painter.rotate(box.
angle);
573 painter.drawRect(QRectF(-w / 2.0, -h / 2.0, w, h));
576 painter.drawRect(rect);
587 void EffectBase::BlendWithMask(std::shared_ptr<QImage> original_image, std::shared_ptr<QImage> effected_image,
588 std::shared_ptr<QImage> mask_image)
const {
589 if (!original_image || !effected_image || !mask_image)
591 if (original_image->size() != effected_image->size() || effected_image->size() != mask_image->size())
594 unsigned char* original_pixels =
reinterpret_cast<unsigned char*
>(original_image->bits());
595 unsigned char* effected_pixels =
reinterpret_cast<unsigned char*
>(effected_image->bits());
596 unsigned char* mask_pixels =
reinterpret_cast<unsigned char*
>(mask_image->bits());
597 const int pixel_count = effected_image->width() * effected_image->height();
599 #pragma omp parallel for schedule(static)
600 for (
int i = 0; i < pixel_count; ++i) {
601 const int idx = i * 4;
602 int gray = qGray(mask_pixels[idx], mask_pixels[idx + 1], mask_pixels[idx + 2]);
605 const float factor =
static_cast<float>(gray) / 255.0f;
606 const float inverse = 1.0f - factor;
608 effected_pixels[idx] =
static_cast<unsigned char>(
609 (original_pixels[idx] * inverse) + (effected_pixels[idx] * factor));
610 effected_pixels[idx + 1] =
static_cast<unsigned char>(
611 (original_pixels[idx + 1] * inverse) + (effected_pixels[idx + 1] * factor));
612 effected_pixels[idx + 2] =
static_cast<unsigned char>(
613 (original_pixels[idx + 2] * inverse) + (effected_pixels[idx + 2] * factor));
614 effected_pixels[idx + 3] =
static_cast<unsigned char>(
615 (original_pixels[idx + 3] * inverse) + (effected_pixels[idx + 3] * factor));
621 if (!
info.
has_video || (!mask_reader && mask_source_id.empty()))
622 return GetFrame(frame, frame_number);
626 return GetFrame(frame, frame_number);
628 auto pre_image = frame->GetImage();
629 if (!pre_image || pre_image->isNull())
630 return GetFrame(frame, frame_number);
632 const auto original_image = std::make_shared<QImage>(pre_image->copy());
633 auto output_frame =
GetFrame(frame, frame_number);
636 auto effected_image = output_frame->GetImage();
637 if (!effected_image || effected_image->isNull() ||
638 effected_image->size() != original_image->size())
641 auto mask_image = GetMaskImage(effected_image, frame_number);
642 if (!mask_image || mask_image->isNull())
648 BlendWithMask(original_image, effected_image, mask_image);
676 if (parentEffectPtr){
682 EffectJSON[
"id"] = this->
Id();