OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
Blur.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 "Blur.h"
14#include "Exceptions.h"
15
16#include <QMargins>
17#include <QPainter>
18#include <QPoint>
19#include <QRect>
20
21#include <algorithm>
22
23using namespace openshot;
24
25namespace {
26double clamp_margin(double value) {
27 if (value < 0.0)
28 return 0.0;
29 if (value > 1.0)
30 return 1.0;
31 return value;
32}
33}
34
36Blur::Blur() : horizontal_radius(6.0), vertical_radius(6.0), sigma(3.0), iterations(3.0),
37 left(0.0), top(0.0), right(0.0), bottom(0.0),
38 mask_mode(BLUR_MASK_POST_BLEND) {
39 // Init effect properties
40 init_effect_details();
41}
42
43// Default constructor
44Blur::Blur(Keyframe new_horizontal_radius, Keyframe new_vertical_radius, Keyframe new_sigma, Keyframe new_iterations) :
45 horizontal_radius(new_horizontal_radius), vertical_radius(new_vertical_radius),
46 sigma(new_sigma), iterations(new_iterations),
47 left(0.0), top(0.0), right(0.0), bottom(0.0),
48 mask_mode(BLUR_MASK_POST_BLEND)
49{
50 // Init effect properties
51 init_effect_details();
52}
53
54// Default constructor
55Blur::Blur(Keyframe new_horizontal_radius, Keyframe new_vertical_radius, Keyframe new_sigma, Keyframe new_iterations,
56 Keyframe new_left, Keyframe new_top, Keyframe new_right, Keyframe new_bottom) :
57 horizontal_radius(new_horizontal_radius), vertical_radius(new_vertical_radius),
58 sigma(new_sigma), iterations(new_iterations),
59 left(new_left), top(new_top), right(new_right), bottom(new_bottom),
60 mask_mode(BLUR_MASK_POST_BLEND)
61{
62 // Init effect properties
63 init_effect_details();
64}
65
66// Init effect settings
67void Blur::init_effect_details()
68{
71
73 info.class_name = "Blur";
74 info.name = "Blur";
75 info.description = "Adjust the blur of the frame's image.";
76 info.has_audio = false;
77 info.has_video = true;
78}
79
80// This method is required for all derived classes of EffectBase, and returns a
81// modified openshot::Frame object
82std::shared_ptr<openshot::Frame> Blur::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
83{
84 // Get the frame's image
85 std::shared_ptr<QImage> frame_image = frame->GetImage();
86
87 // Get the current blur radius
88 int horizontal_radius_value = horizontal_radius.GetValue(frame_number);
89 int vertical_radius_value = vertical_radius.GetValue(frame_number);
90 float sigma_value = sigma.GetValue(frame_number);
91 int iteration_value = iterations.GetInt(frame_number);
92 (void) sigma_value;
93
94 int w = frame_image->width();
95 int h = frame_image->height();
96 if (w <= 0 || h <= 0 || iteration_value <= 0)
97 return frame;
98
99 // Define area we're working on in terms of a QRect with QMargins applied.
100 QRect area(QPoint(0, 0), frame_image->size());
101 area = area.marginsRemoved({
102 int(clamp_margin(left.GetValue(frame_number)) * w),
103 int(clamp_margin(top.GetValue(frame_number)) * h),
104 int(clamp_margin(right.GetValue(frame_number)) * w),
105 int(clamp_margin(bottom.GetValue(frame_number)) * h)
106 });
107 area = area.intersected(QRect(QPoint(0, 0), frame_image->size()));
108 if (area.isEmpty())
109 return frame;
110
111 // Grab two copies of the image pixel data
112 QImage image_copy = frame_image->copy(area);
113 std::shared_ptr<QImage> blur_image = std::make_shared<QImage>(image_copy);
114 std::shared_ptr<QImage> frame_image_2 = std::make_shared<QImage>(image_copy);
115 const int area_w = blur_image->width();
116 const int area_h = blur_image->height();
117 const int horizontal_area_radius = std::min(std::max(0, horizontal_radius_value), std::max(0, area_w - 1));
118 const int vertical_area_radius = std::min(std::max(0, vertical_radius_value), std::max(0, area_h - 1));
119 bool blurred = false;
120
121 // Loop through each iteration
122 for (int iteration = 0; iteration < iteration_value; ++iteration)
123 {
124 // HORIZONTAL BLUR (if any)
125 if (horizontal_area_radius > 0.0) {
126 // Apply horizontal blur to target RGBA channels
127 boxBlurH(blur_image->bits(), frame_image_2->bits(), area_w, area_h, horizontal_area_radius);
128
129 // Swap output image back to input
130 blur_image.swap(frame_image_2);
131 blurred = true;
132 }
133
134 // VERTICAL BLUR (if any)
135 if (vertical_area_radius > 0.0) {
136 // Apply vertical blur to target RGBA channels
137 boxBlurT(blur_image->bits(), frame_image_2->bits(), area_w, area_h, vertical_area_radius);
138
139 // Swap output image back to input
140 blur_image.swap(frame_image_2);
141 blurred = true;
142 }
143 }
144
145 if (blurred) {
146 QPainter painter(frame_image.get());
147 painter.drawImage(area, *blur_image);
148 painter.end();
149 }
150
151 // return the modified frame
152 return frame;
153}
154
155bool Blur::UseCustomMaskBlend(int64_t frame_number) const {
156 (void) frame_number;
158}
159
160void Blur::ApplyCustomMaskBlend(std::shared_ptr<QImage> original_image, std::shared_ptr<QImage> effected_image,
161 std::shared_ptr<QImage> mask_image, int64_t frame_number) const {
162 (void) frame_number;
163 if (!original_image || !effected_image || !mask_image)
164 return;
165 if (original_image->size() != effected_image->size() || effected_image->size() != mask_image->size())
166 return;
167
168 unsigned char* original_pixels = reinterpret_cast<unsigned char*>(original_image->bits());
169 unsigned char* effected_pixels = reinterpret_cast<unsigned char*>(effected_image->bits());
170 unsigned char* mask_pixels = reinterpret_cast<unsigned char*>(mask_image->bits());
171 const int pixel_count = effected_image->width() * effected_image->height();
172
173 #pragma omp parallel for schedule(static)
174 for (int i = 0; i < pixel_count; ++i) {
175 const int idx = i * 4;
176 float factor = static_cast<float>(qGray(mask_pixels[idx], mask_pixels[idx + 1], mask_pixels[idx + 2])) / 255.0f;
177 if (mask_invert)
178 factor = 1.0f - factor;
179 // Use a non-linear response curve for custom blur drive mode.
180 factor = factor * factor;
181 const float inverse = 1.0f - factor;
182
183 // Drive blur amount with the grayscale mask while preserving source alpha.
184 effected_pixels[idx] = static_cast<unsigned char>(
185 (original_pixels[idx] * inverse) + (effected_pixels[idx] * factor));
186 effected_pixels[idx + 1] = static_cast<unsigned char>(
187 (original_pixels[idx + 1] * inverse) + (effected_pixels[idx + 1] * factor));
188 effected_pixels[idx + 2] = static_cast<unsigned char>(
189 (original_pixels[idx + 2] * inverse) + (effected_pixels[idx + 2] * factor));
190 effected_pixels[idx + 3] = original_pixels[idx + 3];
191 }
192}
193
194// Credit: http://blog.ivank.net/fastest-gaussian-blur.html (MIT License)
195// Modified to process all four channels in a pixel array
196void Blur::boxBlurH(unsigned char *scl, unsigned char *tcl, int w, int h, int r) {
197 const float iarr = 1.0f / (r + r + 1);
198
199 #pragma omp parallel for shared(scl, tcl)
200 for (int i = 0; i < h; ++i) {
201 const unsigned char* src = scl + i * w * 4;
202 unsigned char* dst = tcl + i * w * 4;
203
204 int val[4] = {0, 0, 0, 0};
205 for (int j = -r; j <= r; ++j) {
206 const int sample_x = std::min(std::max(j, 0), w - 1);
207 const unsigned char* p = src + sample_x * 4;
208 for (int c = 0; c < 4; ++c)
209 val[c] += p[c];
210 }
211
212 for (int j = 0; j < w; ++j) {
213 unsigned char* out = dst + j * 4;
214 for (int c = 0; c < 4; ++c) {
215 out[c] = (unsigned char)(val[c] * iarr + 0.5f);
216 }
217
218 const int remove_x = std::min(std::max(j - r, 0), w - 1);
219 const int add_x = std::min(std::max(j + r + 1, 0), w - 1);
220 const unsigned char* remove = src + remove_x * 4;
221 const unsigned char* add = src + add_x * 4;
222 for (int c = 0; c < 4; ++c) {
223 val[c] += add[c] - remove[c];
224 }
225 }
226 }
227}
228
229void Blur::boxBlurT(unsigned char *scl, unsigned char *tcl, int w, int h, int r) {
230 const float iarr = 1.0f / (r + r + 1);
231 const int stride = w * 4;
232
233 #pragma omp parallel for shared(scl, tcl)
234 for (int i = 0; i < w; ++i) {
235 const unsigned char* col_src = scl + i * 4;
236 unsigned char* col_dst = tcl + i * 4;
237
238 int val[4] = {0, 0, 0, 0};
239 for (int j = -r; j <= r; ++j) {
240 const int sample_y = std::min(std::max(j, 0), h - 1);
241 const unsigned char* p = col_src + sample_y * stride;
242 for (int c = 0; c < 4; ++c)
243 val[c] += p[c];
244 }
245
246 for (int j = 0; j < h; ++j) {
247 unsigned char* out = col_dst + j * stride;
248 for (int c = 0; c < 4; ++c) {
249 out[c] = (unsigned char)(val[c] * iarr + 0.5f);
250 }
251
252 const int remove_y = std::min(std::max(j - r, 0), h - 1);
253 const int add_y = std::min(std::max(j + r + 1, 0), h - 1);
254 const unsigned char* remove = col_src + remove_y * stride;
255 const unsigned char* add = col_src + add_y * stride;
256 for (int c = 0; c < 4; ++c) {
257 val[c] += add[c] - remove[c];
258 }
259 }
260 }
261}
262
263// Generate JSON string of this object
264std::string Blur::Json() const {
265
266 // Return formatted string
267 return JsonValue().toStyledString();
268}
269
270// Generate Json::Value for this object
271Json::Value Blur::JsonValue() const {
272
273 // Create root json object
274 Json::Value root = EffectBase::JsonValue(); // get parent properties
275 root["type"] = info.class_name;
276 root["horizontal_radius"] = horizontal_radius.JsonValue();
277 root["vertical_radius"] = vertical_radius.JsonValue();
278 root["sigma"] = sigma.JsonValue();
279 root["iterations"] = iterations.JsonValue();
280 root["left"] = left.JsonValue();
281 root["top"] = top.JsonValue();
282 root["right"] = right.JsonValue();
283 root["bottom"] = bottom.JsonValue();
284 root["mask_mode"] = mask_mode;
285
286 // return JsonValue
287 return root;
288}
289
290// Load JSON string into this object
291void Blur::SetJson(const std::string value) {
292
293 // Parse JSON string into JSON objects
294 try
295 {
296 const Json::Value root = openshot::stringToJson(value);
297 // Set all values that match
298 SetJsonValue(root);
299 }
300 catch (const std::exception& e)
301 {
302 // Error parsing JSON (or missing keys)
303 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
304 }
305}
306
307// Load Json::Value into this object
308void Blur::SetJsonValue(const Json::Value root) {
309
310 // Set parent data
312
313 // Set data from Json (if key is found)
314 if (!root["horizontal_radius"].isNull())
315 horizontal_radius.SetJsonValue(root["horizontal_radius"]);
316 if (!root["vertical_radius"].isNull())
317 vertical_radius.SetJsonValue(root["vertical_radius"]);
318 if (!root["sigma"].isNull())
319 sigma.SetJsonValue(root["sigma"]);
320 if (!root["iterations"].isNull())
321 iterations.SetJsonValue(root["iterations"]);
322 if (!root["left"].isNull())
323 left.SetJsonValue(root["left"]);
324 if (!root["top"].isNull())
325 top.SetJsonValue(root["top"]);
326 if (!root["right"].isNull())
327 right.SetJsonValue(root["right"]);
328 if (!root["bottom"].isNull())
329 bottom.SetJsonValue(root["bottom"]);
330 if (!root["mask_mode"].isNull())
331 mask_mode = root["mask_mode"].asInt();
332}
333
334// Get all properties for a specific frame
335std::string Blur::PropertiesJSON(int64_t requested_frame) const {
336
337 // Generate JSON properties list
338 Json::Value root = BasePropertiesJSON(requested_frame);
339
340 // Keyframes
341 root["horizontal_radius"] = add_property_json("Horizontal Radius", horizontal_radius.GetValue(requested_frame), "float", "", &horizontal_radius, 0, 100, false, requested_frame);
342 root["vertical_radius"] = add_property_json("Vertical Radius", vertical_radius.GetValue(requested_frame), "float", "", &vertical_radius, 0, 100, false, requested_frame);
343 root["sigma"] = add_property_json("Sigma", sigma.GetValue(requested_frame), "float", "", &sigma, 0, 100, false, requested_frame);
344 root["iterations"] = add_property_json("Iterations", iterations.GetValue(requested_frame), "float", "", &iterations, 0, 100, false, requested_frame);
345 root["left"] = add_property_json("Margin: Left", left.GetValue(requested_frame), "float", "", &left, 0.0, 1.0, false, requested_frame);
346 root["top"] = add_property_json("Margin: Top", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
347 root["right"] = add_property_json("Margin: Right", right.GetValue(requested_frame), "float", "", &right, 0.0, 1.0, false, requested_frame);
348 root["bottom"] = add_property_json("Margin: Bottom", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 1.0, false, requested_frame);
349 root["mask_mode"] = add_property_json("Mask Mode", mask_mode, "int", "", NULL, 0, 1, false, requested_frame);
350 root["mask_mode"]["choices"].append(add_property_choice_json("Limit to Mask", BLUR_MASK_POST_BLEND, mask_mode));
351 root["mask_mode"]["choices"].append(add_property_choice_json("Vary Strength", BLUR_MASK_DRIVE_AMOUNT, mask_mode));
352
353 // Return formatted string
354 return root.toStyledString();
355}
Header file for Blur effect class.
Header file for all Exception classes.
Keyframe bottom
Size of bottom margin.
Definition Blur.h:63
Keyframe vertical_radius
Vertical blur radius keyframe. The size of the vertical blur operation in pixels.
Definition Blur.h:57
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number) override
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
Definition Blur.h:97
Keyframe left
Size of left margin.
Definition Blur.h:60
Keyframe top
Size of top margin.
Definition Blur.h:61
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 override
Optional override for effects with custom mask implementation.
Definition Blur.cpp:160
int mask_mode
How to apply common masks to blur (post-blend or drive-amount).
Definition Blur.h:64
bool UseCustomMaskBlend(int64_t frame_number) const override
Optional override for effects that need custom mask behavior.
Definition Blur.cpp:155
std::string Json() const override
Generate JSON string of this object.
Definition Blur.cpp:264
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition Blur.cpp:271
Blur()
Blank constructor, useful when using Json to load the effect properties.
Definition Blur.cpp:36
Keyframe iterations
Iterations keyframe. The # of blur iterations per pixel. 3 iterations = Gaussian.
Definition Blur.h:59
std::string PropertiesJSON(int64_t requested_frame) const override
Definition Blur.cpp:335
void SetJson(const std::string value) override
Load JSON string into this object.
Definition Blur.cpp:291
Keyframe sigma
Sigma keyframe. The amount of spread in the blur operation. Should be larger than radius.
Definition Blur.h:58
Keyframe right
Size of right margin.
Definition Blur.h:62
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition Blur.cpp:308
Keyframe horizontal_radius
Horizontal blur radius keyframe. The size of the horizontal blur operation in pixels.
Definition Blur.h:56
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
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
bool mask_invert
Invert grayscale mask values before blending.
Definition EffectBase.h:115
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
EffectInfoStruct info
Information about the current effect.
Definition EffectBase.h:114
Exception for invalid JSON.
Definition Exceptions.h:224
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition KeyFrame.h:53
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
Definition KeyFrame.cpp:282
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition KeyFrame.cpp:372
double GetValue(int64_t index) const
Get the value at a specific index.
Definition KeyFrame.cpp:258
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition KeyFrame.cpp:339
This namespace is the default namespace for all code in the openshot library.
@ BLUR_MASK_DRIVE_AMOUNT
Definition Blur.h:29
@ BLUR_MASK_POST_BLEND
Definition Blur.h:28
const Json::Value stringToJson(const std::string value)
Definition Json.cpp:16
bool has_video
Determines if this effect manipulates the image of a frame.
Definition EffectBase.h:43
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