OpenShot Library | libopenshot 1.1.1
Shadow.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 "Shadow.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 shadow_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 shadow_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 shadow_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 shadow_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 shadow_blur_horizontal(mask, temp, w, h, radius);
111 shadow_blur_vertical(temp, mask, w, h, radius);
112 }
113 }
114
115 inline void shadow_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 shadow_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 shadow_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 shadow_spread_horizontal(mask, temp, w, h, radius);
160 shadow_spread_vertical(temp, mask, w, h, radius);
161 }
162
163 inline std::vector<float> shadow_shift_mask(const std::vector<float>& src, int w, int h,
164 int offset_x, int offset_y) {
165 std::vector<float> shifted(static_cast<size_t>(w * h), 0.0f);
166
167 #pragma omp parallel for schedule(static)
168 for (int y = 0; y < h; ++y) {
169 for (int x = 0; x < w; ++x) {
170 const int src_x = x - offset_x;
171 const int src_y = y - offset_y;
172 if (src_x < 0 || src_x >= w || src_y < 0 || src_y >= h)
173 continue;
174
175 shifted[(y * w) + x] = src[(src_y * w) + src_x];
176 }
177 }
178
179 return shifted;
180 }
181
182 inline QImage make_shadow_overlay(const std::vector<float>& mask, int w, int h,
183 const std::vector<int>& rgba, float opacity) {
184 QImage overlay(w, h, QImage::Format_RGBA8888_Premultiplied);
185 overlay.fill(Qt::transparent);
186 unsigned char* pixels = reinterpret_cast<unsigned char*>(overlay.bits());
187
188 const float base_r = static_cast<float>(rgba[0]);
189 const float base_g = static_cast<float>(rgba[1]);
190 const float base_b = static_cast<float>(rgba[2]);
191 const float base_a = static_cast<float>(rgba[3]) / 255.0f;
192
193 #pragma omp parallel for schedule(static)
194 for (int y = 0; y < h; ++y) {
195 for (int x = 0; x < w; ++x) {
196 const float alpha = shadow_clampf(mask[(y * w) + x] * opacity * base_a, 0.0f, 1.0f);
197 if (alpha <= 0.0f)
198 continue;
199
200 const int idx = ((y * w) + x) * 4;
201 pixels[idx + 0] = static_cast<unsigned char>(base_r * alpha);
202 pixels[idx + 1] = static_cast<unsigned char>(base_g * alpha);
203 pixels[idx + 2] = static_cast<unsigned char>(base_b * alpha);
204 pixels[idx + 3] = static_cast<unsigned char>(255.0f * alpha);
205 }
206 }
207
208 return overlay;
209 }
210}
211
212Shadow::Shadow()
213 : opacity(0.6), blur_radius(18.0), spread(0.12), distance(10.0), angle(60.0), color("#000000") {
214 init_effect_details();
215}
216
217Shadow::Shadow(Keyframe new_opacity, Keyframe new_blur_radius, Keyframe new_spread,
218 Keyframe new_distance, Keyframe new_angle, Color new_color)
219 : opacity(new_opacity), blur_radius(new_blur_radius), spread(new_spread),
220 distance(new_distance), angle(new_angle), color(new_color) {
221 init_effect_details();
222}
223
224void Shadow::init_effect_details()
225{
227 info.class_name = "Shadow";
228 info.name = "Shadow";
229 info.description = "Add a soft drop shadow based on visible pixels.";
230 info.has_audio = false;
231 info.has_video = true;
232 info.apply_before_clip = false;
233}
234
235std::shared_ptr<openshot::Frame> Shadow::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
236{
237 auto frame_image = frame->GetImage();
238 if (!frame_image || frame_image->isNull())
239 return frame;
240
241 const int w = frame_image->width();
242 const int h = frame_image->height();
243 if (w <= 0 || h <= 0)
244 return frame;
245
246 QImage source = frame_image->copy();
247 const unsigned char* source_pixels = reinterpret_cast<const unsigned char*>(source.constBits());
248
249 const float opacity_value = shadow_clampf(static_cast<float>(opacity.GetValue(frame_number)), 0.0f, 1.0f);
250 const int blur_value = std::max(0, blur_radius.GetInt(frame_number));
251 const float spread_value = shadow_clampf(static_cast<float>(spread.GetValue(frame_number)), 0.0f, 1.0f);
252 const float distance_value = static_cast<float>(distance.GetValue(frame_number));
253 const float angle_value = static_cast<float>(angle.GetValue(frame_number));
254 const std::vector<int> rgba = color.GetColorRGBA(frame_number);
255 const float radians = angle_value * static_cast<float>(M_PI / 180.0);
256 const int shadow_x = static_cast<int>(std::lround(std::cos(radians) * distance_value));
257 const int shadow_y = static_cast<int>(std::lround(std::sin(radians) * distance_value));
258
259 std::vector<float> alpha_mask(static_cast<size_t>(w * h));
260 #pragma omp parallel for schedule(static)
261 for (int i = 0; i < (w * h); ++i) {
262 const float alpha = static_cast<float>(source_pixels[(i * 4) + 3]) / 255.0f;
263 alpha_mask[i] = alpha;
264 }
265
266 const int spread_radius = std::max(0, static_cast<int>(std::lround(static_cast<float>(blur_value) * spread_value)));
267 if (spread_radius > 0)
268 shadow_apply_spread(alpha_mask, w, h, spread_radius);
269
270 std::vector<float> shadow_mask = shadow_shift_mask(alpha_mask, w, h, shadow_x, shadow_y);
271 if (blur_value > 0)
272 shadow_apply_box_blur(shadow_mask, w, h, blur_value, 3);
273
274 QImage result(w, h, QImage::Format_RGBA8888_Premultiplied);
275 result.fill(Qt::transparent);
276
277 {
278 QPainter painter(&result);
279 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, true);
280 QImage overlay = make_shadow_overlay(shadow_mask, w, h, rgba, opacity_value);
281 painter.drawImage(0, 0, overlay);
282 painter.drawImage(0, 0, source);
283 }
284
285 *frame_image = result;
286 return frame;
287}
288
289std::string Shadow::Json() const {
290 return JsonValue().toStyledString();
291}
292
293Json::Value Shadow::JsonValue() const {
294 Json::Value root = EffectBase::JsonValue();
295 root["type"] = info.class_name;
296 root["opacity"] = opacity.JsonValue();
297 root["blur_radius"] = blur_radius.JsonValue();
298 root["spread"] = spread.JsonValue();
299 root["distance"] = distance.JsonValue();
300 root["angle"] = angle.JsonValue();
301 root["color"] = color.JsonValue();
302 return root;
303}
304
305void Shadow::SetJson(const std::string value) {
306 try {
307 const Json::Value root = openshot::stringToJson(value);
308 SetJsonValue(root);
309 }
310 catch (const std::exception& e) {
311 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
312 }
313}
314
315void Shadow::SetJsonValue(const Json::Value root) {
317 if (!root["opacity"].isNull())
318 opacity.SetJsonValue(root["opacity"]);
319 if (!root["blur_radius"].isNull())
320 blur_radius.SetJsonValue(root["blur_radius"]);
321 if (!root["spread"].isNull())
322 spread.SetJsonValue(root["spread"]);
323 if (!root["distance"].isNull())
324 distance.SetJsonValue(root["distance"]);
325 if (!root["angle"].isNull())
326 angle.SetJsonValue(root["angle"]);
327 if (!root["color"].isNull())
328 color.SetJsonValue(root["color"]);
329}
330
331std::string Shadow::PropertiesJSON(int64_t requested_frame) const {
332 Json::Value root = BasePropertiesJSON(requested_frame);
333 root["opacity"] = add_property_json("Opacity", opacity.GetValue(requested_frame), "float", "", &opacity, 0.0, 1.0, false, requested_frame);
334 root["blur_radius"] = add_property_json("Blur Radius", blur_radius.GetValue(requested_frame), "float", "", &blur_radius, 0.0, 100.0, false, requested_frame);
335 root["spread"] = add_property_json("Spread", spread.GetValue(requested_frame), "float", "", &spread, 0.0, 1.0, false, requested_frame);
336 root["distance"] = add_property_json("Distance", distance.GetValue(requested_frame), "float", "", &distance, -500.0, 500.0, false, requested_frame);
337 root["angle"] = add_property_json("Angle", angle.GetValue(requested_frame), "float", "", &angle, -360.0, 360.0, false, requested_frame);
338 root["color"] = add_property_json("Color", 0.0, "color", "", &color.red, 0, 255, false, requested_frame);
339 root["color"]["red"] = add_property_json("Red", color.red.GetValue(requested_frame), "float", "", &color.red, 0, 255, false, requested_frame);
340 root["color"]["green"] = add_property_json("Green", color.green.GetValue(requested_frame), "float", "", &color.green, 0, 255, false, requested_frame);
341 root["color"]["blue"] = add_property_json("Blue", color.blue.GetValue(requested_frame), "float", "", &color.blue, 0, 255, false, requested_frame);
342 root["color"]["alpha"] = add_property_json("Alpha", color.alpha.GetValue(requested_frame), "float", "", &color.alpha, 0, 255, false, requested_frame);
343 return root.toStyledString();
344}
Header file for all Exception classes.
Header file for Shadow effect class.
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.
Definition: EffectBase.cpp:106
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:261
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:150
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
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Shadow.cpp:293
Keyframe distance
Shadow offset distance in pixels.
Definition: Shadow.h:36
Keyframe angle
Shadow angle in degrees.
Definition: Shadow.h:37
Color color
Shadow tint color.
Definition: Shadow.h:38
Keyframe opacity
Overall shadow opacity.
Definition: Shadow.h:33
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: Shadow.cpp:331
Keyframe blur_radius
Blur radius in pixels.
Definition: Shadow.h:34
Keyframe spread
Boosts the source alpha before blur.
Definition: Shadow.h:35
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: Shadow.h:44
std::string Json() const override
Generate JSON string of this object.
Definition: Shadow.cpp:289
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Shadow.cpp:305
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Shadow.cpp:315
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
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