OpenShot Library | libopenshot  0.7.0
Glow.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2026 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "Glow.h"
14 
15 #include "Exceptions.h"
16 
17 #include <QPainter>
18 #include <cmath>
19 #include <vector>
20 #include <omp.h>
21 
22 using namespace openshot;
23 
24 namespace {
25  inline float glow_clampf(float value, float low, float high) {
26  if (value < low)
27  return low;
28  if (value > high)
29  return high;
30  return value;
31  }
32 
33  inline void glow_blur_horizontal(const std::vector<float>& src, std::vector<float>& dst, int w, int h, int radius) {
34  if (radius <= 0) {
35  dst = src;
36  return;
37  }
38 
39  const float inv = 1.0f / static_cast<float>((radius * 2) + 1);
40  #pragma omp parallel for schedule(static)
41  for (int y = 0; y < h; ++y) {
42  int ti = y * w;
43  int li = ti;
44  int ri = ti + radius;
45  float first = src[ti];
46  float last = src[ti + w - 1];
47  float value = static_cast<float>(radius + 1) * first;
48  for (int j = 0; j < radius; ++j)
49  value += src[ti + j];
50  for (int x = 0; x <= radius; ++x) {
51  value += src[ri++] - first;
52  dst[ti++] = value * inv;
53  }
54  for (int x = radius + 1; x < w - radius; ++x) {
55  value += src[ri++] - src[li++];
56  dst[ti++] = value * inv;
57  }
58  for (int x = w - radius; x < w; ++x) {
59  value += last - src[li++];
60  dst[ti++] = value * inv;
61  }
62  }
63  }
64 
65  inline void glow_blur_vertical(const std::vector<float>& src, std::vector<float>& dst, int w, int h, int radius) {
66  if (radius <= 0) {
67  dst = src;
68  return;
69  }
70 
71  const float inv = 1.0f / static_cast<float>((radius * 2) + 1);
72  #pragma omp parallel for schedule(static)
73  for (int x = 0; x < w; ++x) {
74  int ti = x;
75  int li = ti;
76  int ri = ti + (radius * w);
77  float first = src[ti];
78  float last = src[ti + (w * (h - 1))];
79  float value = static_cast<float>(radius + 1) * first;
80  for (int j = 0; j < radius; ++j)
81  value += src[ti + (j * w)];
82  for (int y = 0; y <= radius; ++y) {
83  value += src[ri] - first;
84  dst[ti] = value * inv;
85  ri += w;
86  ti += w;
87  }
88  for (int y = radius + 1; y < h - radius; ++y) {
89  value += src[ri] - src[li];
90  dst[ti] = value * inv;
91  li += w;
92  ri += w;
93  ti += w;
94  }
95  for (int y = h - radius; y < h; ++y) {
96  value += last - src[li];
97  dst[ti] = value * inv;
98  li += w;
99  ti += w;
100  }
101  }
102  }
103 
104  inline void glow_apply_box_blur(std::vector<float>& mask, int w, int h, int radius, int iterations) {
105  if (radius <= 0 || iterations <= 0 || w <= 0 || h <= 0)
106  return;
107 
108  std::vector<float> temp(mask.size());
109  for (int i = 0; i < iterations; ++i) {
110  glow_blur_horizontal(mask, temp, w, h, radius);
111  glow_blur_vertical(temp, mask, w, h, radius);
112  }
113  }
114 
115  inline void glow_spread_horizontal(const std::vector<float>& src, std::vector<float>& dst, int w, int h, int radius) {
116  if (radius <= 0) {
117  dst = src;
118  return;
119  }
120 
121  #pragma omp parallel for schedule(static)
122  for (int y = 0; y < h; ++y) {
123  const int row = y * w;
124  for (int x = 0; x < w; ++x) {
125  const int left = std::max(0, x - radius);
126  const int right = std::min(w - 1, x + radius);
127  float value = 0.0f;
128  for (int sample_x = left; sample_x <= right; ++sample_x)
129  value = std::max(value, src[row + sample_x]);
130  dst[row + x] = value;
131  }
132  }
133  }
134 
135  inline void glow_spread_vertical(const std::vector<float>& src, std::vector<float>& dst, int w, int h, int radius) {
136  if (radius <= 0) {
137  dst = src;
138  return;
139  }
140 
141  #pragma omp parallel for schedule(static)
142  for (int y = 0; y < h; ++y) {
143  const int top = std::max(0, y - radius);
144  const int bottom = std::min(h - 1, y + radius);
145  for (int x = 0; x < w; ++x) {
146  float value = 0.0f;
147  for (int sample_y = top; sample_y <= bottom; ++sample_y)
148  value = std::max(value, src[(sample_y * w) + x]);
149  dst[(y * w) + x] = value;
150  }
151  }
152  }
153 
154  inline void glow_apply_spread(std::vector<float>& mask, int w, int h, int radius) {
155  if (radius <= 0 || w <= 0 || h <= 0)
156  return;
157 
158  std::vector<float> temp(mask.size());
159  glow_spread_horizontal(mask, temp, w, h, radius);
160  glow_spread_vertical(temp, mask, w, h, radius);
161  }
162 }
163 
165  : mode(GLOW_MODE_OUTER), opacity(0.45), blur_radius(20.0), spread(0.10), color("#fff4c2") {
166  init_effect_details();
167 }
168 
169 Glow::Glow(Keyframe new_opacity, Keyframe new_blur_radius, Keyframe new_spread, Color new_color)
170  : mode(GLOW_MODE_OUTER), opacity(new_opacity), blur_radius(new_blur_radius), spread(new_spread), color(new_color) {
171  init_effect_details();
172 }
173 
174 void Glow::init_effect_details()
175 {
176  InitEffectInfo();
177  info.class_name = "Glow";
178  info.name = "Glow";
179  info.description = "Add an outer or inner glow based on visible pixels.";
180  info.has_audio = false;
181  info.has_video = true;
182  info.apply_before_clip = false;
183 }
184 
185 std::shared_ptr<openshot::Frame> Glow::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
186 {
187  auto frame_image = frame->GetImage();
188  if (!frame_image || frame_image->isNull())
189  return frame;
190 
191  const int w = frame_image->width();
192  const int h = frame_image->height();
193  if (w <= 0 || h <= 0)
194  return frame;
195 
196  QImage source = frame_image->copy();
197  const unsigned char* source_pixels = reinterpret_cast<const unsigned char*>(source.constBits());
198 
199  const float opacity_value = glow_clampf(static_cast<float>(opacity.GetValue(frame_number)), 0.0f, 1.0f);
200  const int blur_value = std::max(0, blur_radius.GetInt(frame_number));
201  const float spread_value = glow_clampf(static_cast<float>(spread.GetValue(frame_number)), 0.0f, 1.0f);
202  const std::vector<int> rgba = color.GetColorRGBA(frame_number);
203  const float color_r = static_cast<float>(rgba[0]);
204  const float color_g = static_cast<float>(rgba[1]);
205  const float color_b = static_cast<float>(rgba[2]);
206  const float color_a = static_cast<float>(rgba[3]) / 255.0f;
207 
208  std::vector<float> alpha_mask(static_cast<size_t>(w * h));
209  #pragma omp parallel for schedule(static)
210  for (int i = 0; i < (w * h); ++i) {
211  const float alpha = static_cast<float>(source_pixels[(i * 4) + 3]) / 255.0f;
212  alpha_mask[i] = alpha;
213  }
214 
215  const int spread_radius = std::max(0, static_cast<int>(std::lround(static_cast<float>(blur_value) * spread_value)));
216  if (spread_radius > 0)
217  glow_apply_spread(alpha_mask, w, h, spread_radius);
218 
219  std::vector<float> blurred_mask = alpha_mask;
220  if (blur_value > 0)
221  glow_apply_box_blur(blurred_mask, w, h, blur_value, 3);
222 
223  QImage glow_overlay(w, h, QImage::Format_RGBA8888_Premultiplied);
224  glow_overlay.fill(Qt::transparent);
225  unsigned char* glow_pixels = reinterpret_cast<unsigned char*>(glow_overlay.bits());
226 
227  if (mode == GLOW_MODE_OUTER) {
228  #pragma omp parallel for schedule(static)
229  for (int i = 0; i < (w * h); ++i) {
230  const float overlay_alpha = glow_clampf(blurred_mask[i] * opacity_value * color_a, 0.0f, 1.0f);
231  const int idx = i * 4;
232  glow_pixels[idx + 0] = static_cast<unsigned char>(color_r * overlay_alpha);
233  glow_pixels[idx + 1] = static_cast<unsigned char>(color_g * overlay_alpha);
234  glow_pixels[idx + 2] = static_cast<unsigned char>(color_b * overlay_alpha);
235  glow_pixels[idx + 3] = static_cast<unsigned char>(255.0f * overlay_alpha);
236  }
237  }
238  else {
239  std::vector<float> inverse_mask(static_cast<size_t>(w * h));
240  #pragma omp parallel for schedule(static)
241  for (int i = 0; i < (w * h); ++i)
242  inverse_mask[i] = 1.0f - alpha_mask[i];
243 
244  if (blur_value > 0)
245  glow_apply_box_blur(inverse_mask, w, h, blur_value, 3);
246 
247  #pragma omp parallel for schedule(static)
248  for (int i = 0; i < (w * h); ++i) {
249  const float inner = glow_clampf(inverse_mask[i] * alpha_mask[i] * opacity_value * color_a, 0.0f, 1.0f);
250  const int idx = i * 4;
251  glow_pixels[idx + 0] = static_cast<unsigned char>(color_r * inner);
252  glow_pixels[idx + 1] = static_cast<unsigned char>(color_g * inner);
253  glow_pixels[idx + 2] = static_cast<unsigned char>(color_b * inner);
254  glow_pixels[idx + 3] = static_cast<unsigned char>(255.0f * inner);
255  }
256  }
257 
258  QImage result(w, h, QImage::Format_RGBA8888_Premultiplied);
259  result.fill(Qt::transparent);
260  {
261  QPainter painter(&result);
262  painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, true);
263  if (mode == GLOW_MODE_OUTER) {
264  painter.drawImage(0, 0, glow_overlay);
265  painter.drawImage(0, 0, source);
266  } else {
267  painter.drawImage(0, 0, source);
268  painter.drawImage(0, 0, glow_overlay);
269  }
270  }
271 
272  *frame_image = result;
273  return frame;
274 }
275 
276 std::string Glow::Json() const {
277  return JsonValue().toStyledString();
278 }
279 
280 Json::Value Glow::JsonValue() const {
281  Json::Value root = EffectBase::JsonValue();
282  root["type"] = info.class_name;
283  root["mode"] = mode;
284  root["opacity"] = opacity.JsonValue();
285  root["blur_radius"] = blur_radius.JsonValue();
286  root["spread"] = spread.JsonValue();
287  root["color"] = color.JsonValue();
288  return root;
289 }
290 
291 void Glow::SetJson(const std::string value) {
292  try {
293  const Json::Value root = openshot::stringToJson(value);
294  SetJsonValue(root);
295  }
296  catch (const std::exception& e) {
297  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
298  }
299 }
300 
301 void Glow::SetJsonValue(const Json::Value root) {
303  if (!root["mode"].isNull())
304  mode = root["mode"].asInt();
305  if (!root["opacity"].isNull())
306  opacity.SetJsonValue(root["opacity"]);
307  if (!root["blur_radius"].isNull())
308  blur_radius.SetJsonValue(root["blur_radius"]);
309  if (!root["spread"].isNull())
310  spread.SetJsonValue(root["spread"]);
311  if (!root["color"].isNull())
312  color.SetJsonValue(root["color"]);
313 }
314 
315 std::string Glow::PropertiesJSON(int64_t requested_frame) const {
316  Json::Value root = BasePropertiesJSON(requested_frame);
317  root["mode"] = add_property_json("Mode", mode, "int", "", NULL, 0, 1, false, requested_frame);
318  root["mode"]["choices"].append(add_property_choice_json("Outer", GLOW_MODE_OUTER, mode));
319  root["mode"]["choices"].append(add_property_choice_json("Inner", GLOW_MODE_INNER, mode));
320  root["opacity"] = add_property_json("Opacity", opacity.GetValue(requested_frame), "float", "", &opacity, 0.0, 1.0, false, requested_frame);
321  root["blur_radius"] = add_property_json("Blur Radius", blur_radius.GetValue(requested_frame), "float", "", &blur_radius, 0.0, 100.0, false, requested_frame);
322  root["spread"] = add_property_json("Spread", spread.GetValue(requested_frame), "float", "", &spread, 0.0, 1.0, false, requested_frame);
323  root["color"] = add_property_json("Color", 0.0, "color", "", &color.red, 0, 255, false, requested_frame);
324  root["color"]["red"] = add_property_json("Red", color.red.GetValue(requested_frame), "float", "", &color.red, 0, 255, false, requested_frame);
325  root["color"]["green"] = add_property_json("Green", color.green.GetValue(requested_frame), "float", "", &color.green, 0, 255, false, requested_frame);
326  root["color"]["blue"] = add_property_json("Blue", color.blue.GetValue(requested_frame), "float", "", &color.blue, 0, 255, false, requested_frame);
327  root["color"]["alpha"] = add_property_json("Alpha", color.alpha.GetValue(requested_frame), "float", "", &color.alpha, 0, 255, false, requested_frame);
328  return root.toStyledString();
329 }
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::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:110
openshot::EffectInfoStruct::apply_before_clip
bool apply_before_clip
Apply effect before we evaluate the clip's keyframes.
Definition: EffectBase.h:46
openshot::Glow::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Glow.cpp:280
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
openshot::GLOW_MODE_INNER
@ GLOW_MODE_INNER
Definition: Glow.h:29
openshot::GLOW_MODE_OUTER
@ GLOW_MODE_OUTER
Definition: Glow.h:28
openshot::Glow::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: Glow.h:47
openshot::Glow::color
Color color
Glow tint color.
Definition: Glow.h:42
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:96
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
Glow.h
Header file for Glow effect class.
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::Glow::blur_radius
Keyframe blur_radius
Blur radius in pixels.
Definition: Glow.h:40
openshot::Color
This class represents a color (used on the timeline and clips)
Definition: Color.h:27
openshot::Glow::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Glow.cpp:301
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:236
openshot::Glow::opacity
Keyframe opacity
Overall glow opacity.
Definition: Glow.h:39
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::Color::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: Color.cpp:117
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::Glow::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: Glow.cpp:315
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:37
openshot::Color::green
openshot::Keyframe green
Curve representing the green value (0 - 255)
Definition: Color.h:31
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
openshot::Glow::mode
int mode
Outer or inner glow mode.
Definition: Glow.h:38
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
openshot::Color::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: Color.cpp:86
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::Glow::Json
std::string Json() const override
Generate JSON string of this object.
Definition: Glow.cpp:276
openshot::Glow::spread
Keyframe spread
Boosts the source alpha before blur.
Definition: Glow.h:41
openshot::Color::GetColorRGBA
std::vector< int > GetColorRGBA(int64_t frame_number)
Definition: Color.cpp:58
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:40
openshot::Color::alpha
openshot::Keyframe alpha
Curve representing the alpha value (0 - 255)
Definition: Color.h:33
openshot::Glow::Glow
Glow()
Definition: Glow.cpp:164
openshot::Color::red
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition: Color.h:30
openshot::Glow::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Glow.cpp:291
openshot::Color::blue
openshot::Keyframe blue
Curve representing the red value (0 - 255)
Definition: Color.h:32
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:139
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258