OpenShot Library | libopenshot  0.7.0
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 
22 using namespace openshot;
23 
24 namespace {
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 
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 
217 Shadow::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 
224 void Shadow::init_effect_details()
225 {
226  InitEffectInfo();
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 
235 std::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 
289 std::string Shadow::Json() const {
290  return JsonValue().toStyledString();
291 }
292 
293 Json::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 
305 void 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 
315 void 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 
331 std::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 }
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::Shadow::spread
Keyframe spread
Boosts the source alpha before blur.
Definition: Shadow.h:35
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:110
openshot::Shadow::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Shadow.cpp:293
openshot::EffectInfoStruct::apply_before_clip
bool apply_before_clip
Apply effect before we evaluate the clip's keyframes.
Definition: EffectBase.h:46
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
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
openshot::Shadow::Shadow
Shadow()
Definition: Shadow.cpp:212
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::Shadow::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: Shadow.h:44
openshot::Color
This class represents a color (used on the timeline and clips)
Definition: Color.h:27
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::Shadow::distance
Keyframe distance
Shadow offset distance in pixels.
Definition: Shadow.h:36
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::Shadow::angle
Keyframe angle
Shadow angle in degrees.
Definition: Shadow.h:37
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::Shadow::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Shadow.cpp:305
Shadow.h
Header file for Shadow effect class.
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::Shadow::opacity
Keyframe opacity
Overall shadow opacity.
Definition: Shadow.h:33
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
openshot::Shadow::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: Shadow.cpp:331
openshot::Shadow::color
Color color
Shadow tint color.
Definition: Shadow.h:38
openshot::Shadow::Json
std::string Json() const override
Generate JSON string of this object.
Definition: Shadow.cpp:289
openshot::Shadow::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Shadow.cpp:315
openshot::Shadow::blur_radius
Keyframe blur_radius
Blur radius in pixels.
Definition: Shadow.h:34
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::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::Color::red
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition: Color.h:30
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