OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
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
22using namespace openshot;
23
24namespace {
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
169Glow::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
174void Glow::init_effect_details()
175{
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
185std::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
276std::string Glow::Json() const {
277 return JsonValue().toStyledString();
278}
279
280Json::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
291void 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
301void 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
315std::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}
Header file for all Exception classes.
Header file for Glow effect class.
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
This class represents a color (used on the timeline and clips)
Definition Color.h:27
openshot::Keyframe blue
Curve representing the red value (0 - 255)
Definition Color.h:32
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition Color.h:30
openshot::Keyframe green
Curve representing the green value (0 - 255)
Definition Color.h:31
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition Color.cpp:118
openshot::Keyframe alpha
Curve representing the alpha value (0 - 255)
Definition Color.h:33
std::vector< int > GetColorRGBA(int64_t frame_number)
Definition Color.cpp:59
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition Color.cpp:87
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
std::string Json() const override
Generate JSON string of this object.
Definition Glow.cpp:276
Keyframe blur_radius
Blur radius in pixels.
Definition Glow.h:40
std::string PropertiesJSON(int64_t requested_frame) const override
Definition Glow.cpp:315
void SetJson(const std::string value) override
Load JSON string into this object.
Definition Glow.cpp:291
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
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition Glow.cpp:301
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition Glow.cpp:280
Keyframe opacity
Overall glow opacity.
Definition Glow.h:39
Color color
Glow tint color.
Definition Glow.h:42
Keyframe spread
Boosts the source alpha before blur.
Definition Glow.h:41
int mode
Outer or inner glow mode.
Definition Glow.h:38
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.
@ GLOW_MODE_INNER
Definition Glow.h:29
@ GLOW_MODE_OUTER
Definition Glow.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 apply_before_clip
Apply effect to source before we evaluate the clip's keyframes.
Definition EffectBase.h:46
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