Added top/bottom/left/right margins to Blur effect, similar to Pixelate one (for consistency). Renamed the user-facing JsonProperty labels across all effects with similar keyframes to:

- Margin: Left
- Margin: Top
- Margin: Right
- Margin: Bottom
This commit is contained in:
Jonathan Thomas
2026-05-09 14:50:36 -05:00
parent a6e7d85c93
commit ec71b7335c
8 changed files with 177 additions and 23 deletions
+4 -4
View File
@@ -167,10 +167,10 @@ std::string Bars::PropertiesJSON(int64_t requested_frame) const {
root["color"]["red"] = add_property_json("Red", color.red.GetValue(requested_frame), "float", "", &color.red, 0, 255, false, requested_frame);
root["color"]["blue"] = add_property_json("Blue", color.blue.GetValue(requested_frame), "float", "", &color.blue, 0, 255, false, requested_frame);
root["color"]["green"] = add_property_json("Green", color.green.GetValue(requested_frame), "float", "", &color.green, 0, 255, false, requested_frame);
root["left"] = add_property_json("Left Size", left.GetValue(requested_frame), "float", "", &left, 0.0, 0.5, false, requested_frame);
root["top"] = add_property_json("Top Size", top.GetValue(requested_frame), "float", "", &top, 0.0, 0.5, false, requested_frame);
root["right"] = add_property_json("Right Size", right.GetValue(requested_frame), "float", "", &right, 0.0, 0.5, false, requested_frame);
root["bottom"] = add_property_json("Bottom Size", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 0.5, false, requested_frame);
root["left"] = add_property_json("Margin: Left", left.GetValue(requested_frame), "float", "", &left, 0.0, 0.5, false, requested_frame);
root["top"] = add_property_json("Margin: Top", top.GetValue(requested_frame), "float", "", &top, 0.0, 0.5, false, requested_frame);
root["right"] = add_property_json("Margin: Right", right.GetValue(requested_frame), "float", "", &right, 0.0, 0.5, false, requested_frame);
root["bottom"] = add_property_json("Margin: Bottom", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 0.5, false, requested_frame);
// Return formatted string
return root.toStyledString();
+84 -8
View File
@@ -13,10 +13,28 @@
#include "Blur.h"
#include "Exceptions.h"
#include <QMargins>
#include <QPainter>
#include <QPoint>
#include <QRect>
#include <algorithm>
using namespace openshot;
namespace {
double clamp_margin(double value) {
if (value < 0.0)
return 0.0;
if (value > 1.0)
return 1.0;
return value;
}
}
/// Blank constructor, useful when using Json to load the effect properties
Blur::Blur() : horizontal_radius(6.0), vertical_radius(6.0), sigma(3.0), iterations(3.0),
left(0.0), top(0.0), right(0.0), bottom(0.0),
mask_mode(BLUR_MASK_POST_BLEND) {
// Init effect properties
init_effect_details();
@@ -25,7 +43,21 @@ Blur::Blur() : horizontal_radius(6.0), vertical_radius(6.0), sigma(3.0), iterati
// Default constructor
Blur::Blur(Keyframe new_horizontal_radius, Keyframe new_vertical_radius, Keyframe new_sigma, Keyframe new_iterations) :
horizontal_radius(new_horizontal_radius), vertical_radius(new_vertical_radius),
sigma(new_sigma), iterations(new_iterations), mask_mode(BLUR_MASK_POST_BLEND)
sigma(new_sigma), iterations(new_iterations),
left(0.0), top(0.0), right(0.0), bottom(0.0),
mask_mode(BLUR_MASK_POST_BLEND)
{
// Init effect properties
init_effect_details();
}
// Default constructor
Blur::Blur(Keyframe new_horizontal_radius, Keyframe new_vertical_radius, Keyframe new_sigma, Keyframe new_iterations,
Keyframe new_left, Keyframe new_top, Keyframe new_right, Keyframe new_bottom) :
horizontal_radius(new_horizontal_radius), vertical_radius(new_vertical_radius),
sigma(new_sigma), iterations(new_iterations),
left(new_left), top(new_top), right(new_right), bottom(new_bottom),
mask_mode(BLUR_MASK_POST_BLEND)
{
// Init effect properties
init_effect_details();
@@ -61,33 +93,61 @@ std::shared_ptr<openshot::Frame> Blur::GetFrame(std::shared_ptr<openshot::Frame>
int w = frame_image->width();
int h = frame_image->height();
if (w <= 0 || h <= 0 || iteration_value <= 0)
return frame;
// Define area we're working on in terms of a QRect with QMargins applied.
QRect area(QPoint(0, 0), frame_image->size());
area = area.marginsRemoved({
int(clamp_margin(left.GetValue(frame_number)) * w),
int(clamp_margin(top.GetValue(frame_number)) * h),
int(clamp_margin(right.GetValue(frame_number)) * w),
int(clamp_margin(bottom.GetValue(frame_number)) * h)
});
area = area.intersected(QRect(QPoint(0, 0), frame_image->size()));
if (area.isEmpty())
return frame;
// Grab two copies of the image pixel data
QImage image_copy = frame_image->copy();
QImage image_copy = frame_image->copy(area);
std::shared_ptr<QImage> blur_image = std::make_shared<QImage>(image_copy);
std::shared_ptr<QImage> frame_image_2 = std::make_shared<QImage>(image_copy);
const int area_w = blur_image->width();
const int area_h = blur_image->height();
const int horizontal_area_radius = std::min(std::max(0, horizontal_radius_value), std::max(0, area_w - 1));
const int vertical_area_radius = std::min(std::max(0, vertical_radius_value), std::max(0, area_h - 1));
bool blurred = false;
// Loop through each iteration
for (int iteration = 0; iteration < iteration_value; ++iteration)
{
// HORIZONTAL BLUR (if any)
if (horizontal_radius_value > 0.0) {
if (horizontal_area_radius > 0.0) {
// Apply horizontal blur to target RGBA channels
boxBlurH(frame_image->bits(), frame_image_2->bits(), w, h, horizontal_radius_value);
boxBlurH(blur_image->bits(), frame_image_2->bits(), area_w, area_h, horizontal_area_radius);
// Swap output image back to input
frame_image.swap(frame_image_2);
blur_image.swap(frame_image_2);
blurred = true;
}
// VERTICAL BLUR (if any)
if (vertical_radius_value > 0.0) {
if (vertical_area_radius > 0.0) {
// Apply vertical blur to target RGBA channels
boxBlurT(frame_image->bits(), frame_image_2->bits(), w, h, vertical_radius_value);
boxBlurT(blur_image->bits(), frame_image_2->bits(), area_w, area_h, vertical_area_radius);
// Swap output image back to input
frame_image.swap(frame_image_2);
blur_image.swap(frame_image_2);
blurred = true;
}
}
if (blurred) {
QPainter painter(frame_image.get());
painter.drawImage(area, *blur_image);
painter.end();
}
// return the modified frame
return frame;
}
@@ -249,6 +309,10 @@ Json::Value Blur::JsonValue() const {
root["vertical_radius"] = vertical_radius.JsonValue();
root["sigma"] = sigma.JsonValue();
root["iterations"] = iterations.JsonValue();
root["left"] = left.JsonValue();
root["top"] = top.JsonValue();
root["right"] = right.JsonValue();
root["bottom"] = bottom.JsonValue();
root["mask_mode"] = mask_mode;
// return JsonValue
@@ -287,6 +351,14 @@ void Blur::SetJsonValue(const Json::Value root) {
sigma.SetJsonValue(root["sigma"]);
if (!root["iterations"].isNull())
iterations.SetJsonValue(root["iterations"]);
if (!root["left"].isNull())
left.SetJsonValue(root["left"]);
if (!root["top"].isNull())
top.SetJsonValue(root["top"]);
if (!root["right"].isNull())
right.SetJsonValue(root["right"]);
if (!root["bottom"].isNull())
bottom.SetJsonValue(root["bottom"]);
if (!root["mask_mode"].isNull())
mask_mode = root["mask_mode"].asInt();
}
@@ -302,6 +374,10 @@ std::string Blur::PropertiesJSON(int64_t requested_frame) const {
root["vertical_radius"] = add_property_json("Vertical Radius", vertical_radius.GetValue(requested_frame), "float", "", &vertical_radius, 0, 100, false, requested_frame);
root["sigma"] = add_property_json("Sigma", sigma.GetValue(requested_frame), "float", "", &sigma, 0, 100, false, requested_frame);
root["iterations"] = add_property_json("Iterations", iterations.GetValue(requested_frame), "float", "", &iterations, 0, 100, false, requested_frame);
root["left"] = add_property_json("Margin: Left", left.GetValue(requested_frame), "float", "", &left, 0.0, 1.0, false, requested_frame);
root["top"] = add_property_json("Margin: Top", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
root["right"] = add_property_json("Margin: Right", right.GetValue(requested_frame), "float", "", &right, 0.0, 1.0, false, requested_frame);
root["bottom"] = add_property_json("Margin: Bottom", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 1.0, false, requested_frame);
root["mask_mode"] = add_property_json("Mask Mode", mask_mode, "int", "", NULL, 0, 1, false, requested_frame);
root["mask_mode"]["choices"].append(add_property_choice_json("Limit to Mask", BLUR_MASK_POST_BLEND, mask_mode));
root["mask_mode"]["choices"].append(add_property_choice_json("Vary Strength", BLUR_MASK_DRIVE_AMOUNT, mask_mode));
+17
View File
@@ -57,6 +57,10 @@ namespace openshot
Keyframe vertical_radius; ///< Vertical blur radius keyframe. The size of the vertical blur operation in pixels.
Keyframe sigma; ///< Sigma keyframe. The amount of spread in the blur operation. Should be larger than radius.
Keyframe iterations; ///< Iterations keyframe. The # of blur iterations per pixel. 3 iterations = Gaussian.
Keyframe left; ///< Size of left margin
Keyframe top; ///< Size of top margin
Keyframe right; ///< Size of right margin
Keyframe bottom; ///< Size of bottom margin
int mask_mode; ///< How to apply common masks to blur (post-blend or drive-amount).
/// Blank constructor, useful when using Json to load the effect properties
@@ -71,6 +75,19 @@ namespace openshot
/// @param new_iterations The curve to adjust the # of iterations (between 1 and 100)
Blur(Keyframe new_horizontal_radius, Keyframe new_vertical_radius, Keyframe new_sigma, Keyframe new_iterations);
/// Default constructor, which takes blur curves and an affected area.
///
/// @param new_horizontal_radius The curve to adjust the horizontal blur radius (between 0 and 100, rounded to int)
/// @param new_vertical_radius The curve to adjust the vertical blur radius (between 0 and 100, rounded to int)
/// @param new_sigma The curve to adjust the sigma amount (the size of the blur brush (between 0 and 100), float values accepted)
/// @param new_iterations The curve to adjust the # of iterations (between 1 and 100)
/// @param new_left The curve to adjust the left margin size (between 0 and 1)
/// @param new_top The curve to adjust the top margin size (between 0 and 1)
/// @param new_right The curve to adjust the right margin size (between 0 and 1)
/// @param new_bottom The curve to adjust the bottom margin size (between 0 and 1)
Blur(Keyframe new_horizontal_radius, Keyframe new_vertical_radius, Keyframe new_sigma, Keyframe new_iterations,
Keyframe new_left, Keyframe new_top, Keyframe new_right, Keyframe new_bottom);
/// @brief This method is required for all derived classes of ClipBase, and returns a
/// new openshot::Frame object. All Clip keyframes and effects are resolved into
/// pixels.
+3 -3
View File
@@ -494,9 +494,9 @@ std::string Caption::PropertiesJSON(int64_t requested_frame) const {
root["fade_in"] = add_property_json("Fade In (Seconds)", fade_in.GetValue(requested_frame), "float", "", &fade_in, 0.0, 3.0, false, requested_frame);
root["fade_out"] = add_property_json("Fade Out (Seconds)", fade_out.GetValue(requested_frame), "float", "", &fade_out, 0.0, 3.0, false, requested_frame);
root["line_spacing"] = add_property_json("Line Spacing", line_spacing.GetValue(requested_frame), "float", "", &line_spacing, 0.0, 5.0, false, requested_frame);
root["left"] = add_property_json("Left Size", left.GetValue(requested_frame), "float", "", &left, 0.0, 0.5, false, requested_frame);
root["top"] = add_property_json("Top Size", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
root["right"] = add_property_json("Right Size", right.GetValue(requested_frame), "float", "", &right, 0.0, 0.5, false, requested_frame);
root["left"] = add_property_json("Margin: Left", left.GetValue(requested_frame), "float", "", &left, 0.0, 0.5, false, requested_frame);
root["top"] = add_property_json("Margin: Top", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
root["right"] = add_property_json("Margin: Right", right.GetValue(requested_frame), "float", "", &right, 0.0, 0.5, false, requested_frame);
root["caption_text"] = add_property_json("Captions", 0.0, "caption", caption_text, NULL, -1, -1, false, requested_frame);
root["caption_font"] = add_property_json("Font", 0.0, "font", font_name, NULL, -1, -1, false, requested_frame);
+4 -4
View File
@@ -187,10 +187,10 @@ std::string Crop::PropertiesJSON(int64_t requested_frame) const {
Json::Value root = BasePropertiesJSON(requested_frame);
// Keyframes
root["left"] = add_property_json("Left Size", left.GetValue(requested_frame), "float", "", &left, 0.0, 1.0, false, requested_frame);
root["top"] = add_property_json("Top Size", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
root["right"] = add_property_json("Right Size", right.GetValue(requested_frame), "float", "", &right, 0.0, 1.0, false, requested_frame);
root["bottom"] = add_property_json("Bottom Size", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 1.0, false, requested_frame);
root["left"] = add_property_json("Margin: Left", left.GetValue(requested_frame), "float", "", &left, 0.0, 1.0, false, requested_frame);
root["top"] = add_property_json("Margin: Top", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
root["right"] = add_property_json("Margin: Right", right.GetValue(requested_frame), "float", "", &right, 0.0, 1.0, false, requested_frame);
root["bottom"] = add_property_json("Margin: Bottom", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 1.0, false, requested_frame);
root["x"] = add_property_json("X Offset", x.GetValue(requested_frame), "float", "", &x, -1.0, 1.0, false, requested_frame);
root["y"] = add_property_json("Y Offset", y.GetValue(requested_frame), "float", "", &y, -1.0, 1.0, false, requested_frame);
+4 -4
View File
@@ -198,10 +198,10 @@ std::string Pixelate::PropertiesJSON(int64_t requested_frame) const {
// Keyframes
root["pixelization"] = add_property_json("Pixelization", pixelization.GetValue(requested_frame), "float", "", &pixelization, 0.0, 0.9999, false, requested_frame);
root["left"] = add_property_json("Left Margin", left.GetValue(requested_frame), "float", "", &left, 0.0, 1.0, false, requested_frame);
root["top"] = add_property_json("Top Margin", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
root["right"] = add_property_json("Right Margin", right.GetValue(requested_frame), "float", "", &right, 0.0, 1.0, false, requested_frame);
root["bottom"] = add_property_json("Bottom Margin", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 1.0, false, requested_frame);
root["left"] = add_property_json("Margin: Left", left.GetValue(requested_frame), "float", "", &left, 0.0, 1.0, false, requested_frame);
root["top"] = add_property_json("Margin: Top", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
root["right"] = add_property_json("Margin: Right", right.GetValue(requested_frame), "float", "", &right, 0.0, 1.0, false, requested_frame);
root["bottom"] = add_property_json("Margin: Bottom", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 1.0, false, requested_frame);
root["mask_mode"] = add_property_json("Mask Mode", mask_mode, "int", "", NULL, 0, 1, false, requested_frame);
root["mask_mode"]["choices"].append(add_property_choice_json("Limit to Mask", PIXELATE_MASK_LIMIT_TO_AREA, mask_mode));
root["mask_mode"]["choices"].append(add_property_choice_json("Vary Strength", PIXELATE_MASK_VARY_STRENGTH, mask_mode));
+60
View File
@@ -0,0 +1,60 @@
/**
* @file
* @brief Unit tests for Blur effect
*
* @ref License
*/
// Copyright (c) 2008-2026 OpenShot Studios, LLC
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "openshot_catch.h"
#include "Frame.h"
#include "effects/Blur.h"
#include <QColor>
using namespace openshot;
TEST_CASE("Blur margins limit affected area", "[effect][blur]") {
auto frame = std::make_shared<Frame>(1, 8, 1, "#000000");
auto image = frame->GetImage();
for (int x = 0; x < image->width(); ++x)
image->setPixelColor(x, 0, (x % 2 == 0) ? QColor(255, 255, 255, 255) : QColor(0, 0, 0, 255));
Blur effect(Keyframe(1.0), Keyframe(0.0), Keyframe(3.0), Keyframe(1.0),
Keyframe(0.5), Keyframe(0.0), Keyframe(0.0), Keyframe(0.0));
auto out = effect.GetFrame(frame, 1);
auto out_image = out->GetImage();
CHECK(out_image->pixelColor(0, 0) == QColor(255, 255, 255, 255));
CHECK(out_image->pixelColor(1, 0) == QColor(0, 0, 0, 255));
CHECK(out_image->pixelColor(2, 0) == QColor(255, 255, 255, 255));
CHECK(out_image->pixelColor(3, 0) == QColor(0, 0, 0, 255));
CHECK(out_image->pixelColor(4, 0).red() < 255);
CHECK(out_image->pixelColor(4, 0).red() > 0);
CHECK(out_image->pixelColor(5, 0).red() > 0);
}
TEST_CASE("Blur margin properties serialize", "[effect][blur][json]") {
Blur effect(Keyframe(6.0), Keyframe(6.0), Keyframe(3.0), Keyframe(3.0),
Keyframe(0.1), Keyframe(0.2), Keyframe(0.3), Keyframe(0.4));
Json::Value json = effect.JsonValue();
REQUIRE(json["left"].isObject());
REQUIRE(json["top"].isObject());
REQUIRE(json["right"].isObject());
REQUIRE(json["bottom"].isObject());
Blur copy;
copy.SetJsonValue(json);
CHECK(copy.left.GetValue(1) == Approx(0.1));
CHECK(copy.top.GetValue(1) == Approx(0.2));
CHECK(copy.right.GetValue(1) == Approx(0.3));
CHECK(copy.bottom.GetValue(1) == Approx(0.4));
}
+1
View File
@@ -54,6 +54,7 @@ set(OPENSHOT_TESTS
# Effects
AudioVisualization
BeatSync
Blur
ColorGrade
ColorMap
ChromaKey