OpenShot Library | libopenshot  0.7.0
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 
23 using namespace openshot;
24 
25 namespace {
26 double 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 
36 Blur::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
44 Blur::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
55 Blur::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
67 void 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
82 std::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 
155 bool Blur::UseCustomMaskBlend(int64_t frame_number) const {
156  (void) frame_number;
158 }
159 
160 void 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
196 void 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  const unsigned char* first = src;
205  const unsigned char* last = src + (w - 1) * 4;
206 
207  int val[4];
208  for (int c = 0; c < 4; ++c)
209  val[c] = (r + 1) * first[c];
210  for (int j = 0; j < r; ++j) {
211  const unsigned char* p = src + j * 4;
212  for (int c = 0; c < 4; ++c)
213  val[c] += p[c];
214  }
215 
216  int li = 0, ri = r;
217  for (int j = 0; j <= r; ++j, ++ri) {
218  const unsigned char* add = src + ri * 4;
219  unsigned char* out = dst + j * 4;
220  for (int c = 0; c < 4; ++c) {
221  val[c] += add[c] - first[c];
222  out[c] = (unsigned char)(val[c] * iarr + 0.5f);
223  }
224  }
225  for (int j = r + 1; j < w - r; ++j, ++li, ++ri) {
226  const unsigned char* add = src + ri * 4;
227  const unsigned char* sub = src + li * 4;
228  unsigned char* out = dst + j * 4;
229  for (int c = 0; c < 4; ++c) {
230  val[c] += add[c] - sub[c];
231  out[c] = (unsigned char)(val[c] * iarr + 0.5f);
232  }
233  }
234  for (int j = w - r; j < w; ++j, ++li) {
235  const unsigned char* sub = src + li * 4;
236  unsigned char* out = dst + j * 4;
237  for (int c = 0; c < 4; ++c) {
238  val[c] += last[c] - sub[c];
239  out[c] = (unsigned char)(val[c] * iarr + 0.5f);
240  }
241  }
242  }
243 }
244 
245 void Blur::boxBlurT(unsigned char *scl, unsigned char *tcl, int w, int h, int r) {
246  const float iarr = 1.0f / (r + r + 1);
247  const int stride = w * 4;
248 
249  #pragma omp parallel for shared(scl, tcl)
250  for (int i = 0; i < w; ++i) {
251  const unsigned char* col_src = scl + i * 4;
252  unsigned char* col_dst = tcl + i * 4;
253 
254  const unsigned char* first = col_src;
255  const unsigned char* last = col_src + (h - 1) * stride;
256 
257  int val[4];
258  for (int c = 0; c < 4; ++c)
259  val[c] = (r + 1) * first[c];
260  for (int j = 0; j < r; ++j) {
261  const unsigned char* p = col_src + j * stride;
262  for (int c = 0; c < 4; ++c)
263  val[c] += p[c];
264  }
265 
266  int li = 0, ri = r;
267  for (int j = 0; j <= r; ++j, ++ri) {
268  const unsigned char* add = col_src + ri * stride;
269  unsigned char* out = col_dst + j * stride;
270  for (int c = 0; c < 4; ++c) {
271  val[c] += add[c] - first[c];
272  out[c] = (unsigned char)(val[c] * iarr + 0.5f);
273  }
274  }
275  for (int j = r + 1; j < h - r; ++j, ++li, ++ri) {
276  const unsigned char* add = col_src + ri * stride;
277  const unsigned char* sub = col_src + li * stride;
278  unsigned char* out = col_dst + j * stride;
279  for (int c = 0; c < 4; ++c) {
280  val[c] += add[c] - sub[c];
281  out[c] = (unsigned char)(val[c] * iarr + 0.5f);
282  }
283  }
284  for (int j = h - r; j < h; ++j, ++li) {
285  const unsigned char* sub = col_src + li * stride;
286  unsigned char* out = col_dst + j * stride;
287  for (int c = 0; c < 4; ++c) {
288  val[c] += last[c] - sub[c];
289  out[c] = (unsigned char)(val[c] * iarr + 0.5f);
290  }
291  }
292  }
293 }
294 
295 // Generate JSON string of this object
296 std::string Blur::Json() const {
297 
298  // Return formatted string
299  return JsonValue().toStyledString();
300 }
301 
302 // Generate Json::Value for this object
303 Json::Value Blur::JsonValue() const {
304 
305  // Create root json object
306  Json::Value root = EffectBase::JsonValue(); // get parent properties
307  root["type"] = info.class_name;
308  root["horizontal_radius"] = horizontal_radius.JsonValue();
309  root["vertical_radius"] = vertical_radius.JsonValue();
310  root["sigma"] = sigma.JsonValue();
311  root["iterations"] = iterations.JsonValue();
312  root["left"] = left.JsonValue();
313  root["top"] = top.JsonValue();
314  root["right"] = right.JsonValue();
315  root["bottom"] = bottom.JsonValue();
316  root["mask_mode"] = mask_mode;
317 
318  // return JsonValue
319  return root;
320 }
321 
322 // Load JSON string into this object
323 void Blur::SetJson(const std::string value) {
324 
325  // Parse JSON string into JSON objects
326  try
327  {
328  const Json::Value root = openshot::stringToJson(value);
329  // Set all values that match
330  SetJsonValue(root);
331  }
332  catch (const std::exception& e)
333  {
334  // Error parsing JSON (or missing keys)
335  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
336  }
337 }
338 
339 // Load Json::Value into this object
340 void Blur::SetJsonValue(const Json::Value root) {
341 
342  // Set parent data
344 
345  // Set data from Json (if key is found)
346  if (!root["horizontal_radius"].isNull())
347  horizontal_radius.SetJsonValue(root["horizontal_radius"]);
348  if (!root["vertical_radius"].isNull())
349  vertical_radius.SetJsonValue(root["vertical_radius"]);
350  if (!root["sigma"].isNull())
351  sigma.SetJsonValue(root["sigma"]);
352  if (!root["iterations"].isNull())
353  iterations.SetJsonValue(root["iterations"]);
354  if (!root["left"].isNull())
355  left.SetJsonValue(root["left"]);
356  if (!root["top"].isNull())
357  top.SetJsonValue(root["top"]);
358  if (!root["right"].isNull())
359  right.SetJsonValue(root["right"]);
360  if (!root["bottom"].isNull())
361  bottom.SetJsonValue(root["bottom"]);
362  if (!root["mask_mode"].isNull())
363  mask_mode = root["mask_mode"].asInt();
364 }
365 
366 // Get all properties for a specific frame
367 std::string Blur::PropertiesJSON(int64_t requested_frame) const {
368 
369  // Generate JSON properties list
370  Json::Value root = BasePropertiesJSON(requested_frame);
371 
372  // Keyframes
373  root["horizontal_radius"] = add_property_json("Horizontal Radius", horizontal_radius.GetValue(requested_frame), "float", "", &horizontal_radius, 0, 100, false, requested_frame);
374  root["vertical_radius"] = add_property_json("Vertical Radius", vertical_radius.GetValue(requested_frame), "float", "", &vertical_radius, 0, 100, false, requested_frame);
375  root["sigma"] = add_property_json("Sigma", sigma.GetValue(requested_frame), "float", "", &sigma, 0, 100, false, requested_frame);
376  root["iterations"] = add_property_json("Iterations", iterations.GetValue(requested_frame), "float", "", &iterations, 0, 100, false, requested_frame);
377  root["left"] = add_property_json("Margin: Left", left.GetValue(requested_frame), "float", "", &left, 0.0, 1.0, false, requested_frame);
378  root["top"] = add_property_json("Margin: Top", top.GetValue(requested_frame), "float", "", &top, 0.0, 1.0, false, requested_frame);
379  root["right"] = add_property_json("Margin: Right", right.GetValue(requested_frame), "float", "", &right, 0.0, 1.0, false, requested_frame);
380  root["bottom"] = add_property_json("Margin: Bottom", bottom.GetValue(requested_frame), "float", "", &bottom, 0.0, 1.0, false, requested_frame);
381  root["mask_mode"] = add_property_json("Mask Mode", mask_mode, "int", "", NULL, 0, 1, false, requested_frame);
382  root["mask_mode"]["choices"].append(add_property_choice_json("Limit to Mask", BLUR_MASK_POST_BLEND, mask_mode));
383  root["mask_mode"]["choices"].append(add_property_choice_json("Vary Strength", BLUR_MASK_DRIVE_AMOUNT, mask_mode));
384 
385  // Return formatted string
386  return root.toStyledString();
387 }
openshot::ClipBase::add_property_json
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:96
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::Blur::left
Keyframe left
Size of left margin.
Definition: Blur.h:60
openshot::Blur::UseCustomMaskBlend
bool UseCustomMaskBlend(int64_t frame_number) const override
Optional override for effects that need custom mask behavior.
Definition: Blur.cpp:155
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:114
openshot::Blur::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: Blur.cpp:367
openshot::Blur::iterations
Keyframe iterations
Iterations keyframe. The # of blur iterations per pixel. 3 iterations = Gaussian.
Definition: Blur.h:59
openshot::Blur::ApplyCustomMaskBlend
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
openshot::EffectBase::mask_invert
bool mask_invert
Invert grayscale mask values before blending.
Definition: EffectBase.h:115
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
openshot::ClipBase::add_property_choice_json
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:132
openshot::EffectBase::JsonValue
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:102
openshot::Blur::Json
std::string Json() const override
Generate JSON string of this object.
Definition: Blur.cpp:296
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::Blur::GetFrame
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
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::Blur::bottom
Keyframe bottom
Size of bottom margin.
Definition: Blur.h:63
openshot::EffectBase::BasePropertiesJSON
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:245
openshot::Blur::mask_mode
int mask_mode
How to apply common masks to blur (post-blend or drive-amount).
Definition: Blur.h:64
openshot::Blur::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Blur.cpp:340
openshot::Keyframe
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition: KeyFrame.h:53
openshot::Blur::top
Keyframe top
Size of top margin.
Definition: Blur.h:61
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:42
openshot::BLUR_MASK_DRIVE_AMOUNT
@ BLUR_MASK_DRIVE_AMOUNT
Definition: Blur.h:29
openshot::Blur::vertical_radius
Keyframe vertical_radius
Vertical blur radius keyframe. The size of the vertical blur operation in pixels.
Definition: Blur.h:57
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
Blur.h
Header file for Blur effect class.
openshot::BLUR_MASK_POST_BLEND
@ BLUR_MASK_POST_BLEND
Definition: Blur.h:28
openshot::Blur::right
Keyframe right
Size of right margin.
Definition: Blur.h:62
openshot::Blur::Blur
Blur()
Blank constructor, useful when using Json to load the effect properties.
Definition: Blur.cpp:36
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
openshot::Blur::horizontal_radius
Keyframe horizontal_radius
Horizontal blur radius keyframe. The size of the horizontal blur operation in pixels.
Definition: Blur.h:56
openshot::Keyframe::GetInt
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
Definition: KeyFrame.cpp:282
openshot::EffectInfoStruct::description
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41
openshot::EffectInfoStruct::has_video
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:40
openshot::Blur::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Blur.cpp:303
openshot::Blur::sigma
Keyframe sigma
Sigma keyframe. The amount of spread in the blur operation. Should be larger than radius.
Definition: Blur.h:58
openshot::Blur::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Blur.cpp:323
Exceptions.h
Header file for all Exception classes.
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:146
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258