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;
82 *out <<
"--> Order: " << order << std::endl;
83 *out <<
"----------------------------" << std::endl;
92 else if (color_value > 255)
118 root[
"order"] =
Order();
120 root[
"mask_source_id"] = mask_source_id;
124 root[
"mask_reader"] = mask_reader->
JsonValue();
126 root[
"mask_reader"] = Json::objectValue;
142 catch (
const std::exception& e)
145 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
151 const std::string original_id = this->
Id();
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;
168 my_root[
"id"] = this->
Id();
171 my_root = std::move(root);
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"];
185 if (!my_root[
"order"].isNull())
186 Order(my_root[
"order"].asInt());
188 if (!my_root[
"apply_before_clip"].isNull())
191 if (!my_root[
"mask_invert"].isNull())
193 if (!my_root[
"mask_source_id"].isNull())
195 if (!my_root[
"mask_time_mode"].isNull()) {
196 const int time_mode = my_root[
"mask_time_mode"].asInt();
200 if (!my_root[
"mask_loop_mode"].isNull()) {
201 const int loop_mode = my_root[
"mask_loop_mode"].asInt();
208 const Json::Value mask_reader_json =
209 !my_root[
"mask_reader"].isNull() ? my_root[
"mask_reader"] : my_root[
"reader"];
211 if (!mask_reader_json.isNull()) {
212 if (!mask_reader_json[
"type"].isNull()) {
214 }
else if (mask_reader_json.isObject() && mask_reader_json.empty()) {
219 if (!my_root[
"parent_effect_id"].isNull()){
232 std::list<EffectBase*> effects = parentTimeline->
ClipEffects();
237 for (
auto const& effect : effects){
239 if ((effect->info.parent_effect_id == this->Id()) && (effect->Id() != this->
Id()))
240 effect->SetJsonValue(my_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);
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);
294 root[
"mask_reader"] =
add_property_json(
"Mask: Source", 0.0,
"reader", mask_reader->
Json(), NULL, 0, 1,
false, requested_frame);
296 root[
"mask_reader"] =
add_property_json(
"Mask: Source", 0.0,
"reader",
"{}", NULL, 0, 1,
false, requested_frame);
298 root[
"mask_source_id"] =
add_property_json(
"Mask: Effect Source", 0.0,
"string", mask_source_id, NULL, -1, -1,
false, requested_frame);
305 if (reader_json[
"type"].isNull())
309 const std::string type = reader_json[
"type"].asString();
311 if (type ==
"FFmpegReader") {
312 reader =
new FFmpegReader(reader_json[
"path"].asString());
318 }
else if (type ==
"QtImageReader") {
321 }
else if (type ==
"ChunkReader") {
322 reader =
new ChunkReader(reader_json[
"path"].asString(),
323 static_cast<ChunkVersion>(reader_json[
"chunk_version"].asInt()));
325#ifdef USE_IMAGEMAGICK
326 }
else if (type ==
"ImageReader") {
327 reader =
new ImageReader(reader_json[
"path"].asString());
336 if (mask_reader == new_reader)
340 mask_reader->
Close();
344 mask_reader = new_reader;
345 cached_single_mask_image.reset();
346 cached_single_mask_width = 0;
347 cached_single_mask_height = 0;
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;
359EffectBase* EffectBase::ResolveMaskSourceEffect() {
360 if (mask_source_id.empty())
366 if (source && source !=
this)
371 if (parent_timeline) {
374 source = parent_timeline->
GetEffect(mask_source_id);
375 if (source && source !=
this)
418 int64_t requested_index = std::max(int64_t(0), frame_number - 1);
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);
426 int64_t mapped_index = requested_index;
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));
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);
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
450 range_end = std::min(range_end, source_len);
451 if (range_end < range_start)
452 range_end = range_start;
454 const int64_t range_len = std::max(int64_t(1), range_end - range_start + 1);
455 int64_t range_index = mapped_index;
459 range_index = mapped_index % range_len;
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;
474 if (mapped_index < 0)
476 else if (mapped_index >= range_len)
477 range_index = range_len - 1;
479 range_index = mapped_index;
483 int64_t mapped_frame = range_start + range_index;
485 mapped_frame = std::min(std::max(int64_t(1), mapped_frame), source_len);
486 return std::max(int64_t(1), mapped_frame);
489std::shared_ptr<QImage> EffectBase::GetMaskImage(std::shared_ptr<QImage> target_image, int64_t frame_number) {
490 if (!target_image || target_image->isNull())
493 EffectBase* source_effect = ResolveMaskSourceEffect();
495 auto generated_mask = source_effect->
TrackedObjectMask(target_image, frame_number);
496 if (generated_mask && !generated_mask->isNull())
497 return generated_mask;
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));
508 std::shared_ptr<QImage> source_mask;
509 bool used_cached_scaled =
false;
510 #pragma omp critical (open_effect_mask_reader)
513 if (!mask_reader->
IsOpen())
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;
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());
529 }
catch (
const std::exception& e) {
531 std::string(
"EffectBase::GetMaskImage unable to read mask frame: ") + e.what());
536 if (!source_mask || source_mask->isNull())
539 if (used_cached_scaled)
542 auto scaled_mask = std::make_shared<QImage>(
544 target_image->width(), target_image->height(),
545 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
547 cached_single_mask_image = scaled_mask;
548 cached_single_mask_width = target_image->width();
549 cached_single_mask_height = target_image->height();
556 if (!target_image || target_image->isNull() ||
trackedObjects.empty())
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));
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)));
568 bool drew_any_box =
false;
570 auto bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(trackedObject.second);
573 if (!bbox->Contains(frame_number) || bbox->visible.GetValue(frame_number) != 1)
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)
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);
587 if (std::abs(box.
angle) > 0.0001f) {
589 painter.translate(rect.center());
590 painter.rotate(box.
angle);
591 painter.drawRoundedRect(QRectF(-w / 2.0, -h / 2.0, w, h), corner, corner);
594 painter.drawRoundedRect(rect, corner, corner);
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)
614 if (original_image->size() != effected_image->size() || effected_image->size() != mask_image->size())
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();
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]);
636 const float factor =
static_cast<float>(gray) / 255.0f;
637 const float inverse = 1.0f - factor;
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));
653 if (!
info.
has_video || (!mask_reader && mask_source_id.empty()))
654 return GetFrame(frame, frame_number);
658 return GetFrame(frame, frame_number);
660 auto pre_image = frame->GetImage();
661 if (!pre_image || pre_image->isNull())
662 return GetFrame(frame, frame_number);
664 const auto original_image = std::make_shared<QImage>(pre_image->copy());
665 auto output_frame =
GetFrame(frame, frame_number);
668 auto effected_image = output_frame->GetImage();
669 if (!effected_image || effected_image->isNull() ||
670 effected_image->size() != original_image->size())
673 auto mask_image = GetMaskImage(effected_image, frame_number);
674 if (!mask_image || mask_image->isNull())
680 BlendWithMask(original_image, effected_image, mask_image);
708 if (parentEffectPtr){
714 EffectJSON[
"id"] = this->
Id();
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...
This abstract class is the base class, used by all clips in libopenshot.
void SetBaseJsonValue(const Json::Value &root)
Load common clip fields without copying derived animation data.
float Start() const
Get start position (in seconds) of clip (trim start of video)
float Duration() const
Get the length of this clip (in seconds)
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)
std::string Id() const
Get the Id of this clip object.
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
float Position() const
Get position on timeline (in seconds)
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
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.
This class represents a clip (used to arrange readers on the timeline)
openshot::EffectBase * GetEffect(const std::string &id)
Look up an effect by ID.
This abstract class is the base class, used by all effects in libopenshot.
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.
virtual bool UseCustomMaskBlend(int64_t frame_number) const
Optional override for effects that need custom mask behavior.
virtual void SetJson(const std::string value)
Load JSON string into this object.
ReaderBase * MaskReader()
Get the common mask reader.
EffectBase * parentEffect
Parent effect (which properties will set this effect properties)
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().
bool mask_invert
Invert grayscale mask values before blending.
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.
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.
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.
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.
double ResolveMaskSourceDuration() const
Determine mask source duration in seconds.
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
EffectInfoStruct info
Information about the current effect.
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)
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.
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
int den
Denominator for the fraction.
This class uses the ImageMagick++ libraries, to open image files, and return openshot::Frame objects ...
Exception for invalid JSON.
void Log(std::string message, Level level=LevelDebug)
static Logger * Instance()
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.
virtual bool IsOpen()=0
Determine if reader is open or closed.
openshot::ReaderInfo info
Information about the current media file.
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.
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
std::list< openshot::EffectBase * > ClipEffects() const
Return the list of effects on all clips.
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
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,...
const Json::Value stringToJson(const std::string value)
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.
bool apply_before_clip
Apply effect to source before we evaluate the clip's keyframes.
std::string parent_effect_id
Id of the parent effect (if there is one)
bool has_audio
Determines if this effect manipulates the audio of a frame.
std::string class_name
The class name of the effect.
std::string name
The name of the effect.
std::string description
The description of this effect and what it does.
bool has_tracked_object
Determines if this effect track objects through the clip.
bool has_single_image
Determines if this file only contains a single image.
float duration
Length of time (in seconds)
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
int64_t video_length
The number of frames in the video stream.
bool has_audio
Determines if this file has an audio stream.
int audio_stream_index
The index of the audio stream.