25 inline float shadow_clampf(
float value,
float low,
float high) {
33 inline void shadow_blur_horizontal(
const std::vector<float>& src, std::vector<float>& dst,
int w,
int h,
int radius) {
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) {
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)
50 for (
int x = 0; x <= radius; ++x) {
51 value += src[ri++] - first;
52 dst[ti++] = value * inv;
54 for (
int x = radius + 1; x < w - radius; ++x) {
55 value += src[ri++] - src[li++];
56 dst[ti++] = value * inv;
58 for (
int x = w - radius; x < w; ++x) {
59 value += last - src[li++];
60 dst[ti++] = value * inv;
65 inline void shadow_blur_vertical(
const std::vector<float>& src, std::vector<float>& dst,
int w,
int h,
int radius) {
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) {
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;
88 for (
int y = radius + 1; y < h - radius; ++y) {
89 value += src[ri] - src[li];
90 dst[ti] = value * inv;
95 for (
int y = h - radius; y < h; ++y) {
96 value += last - src[li];
97 dst[ti] = value * inv;
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)
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);
115 inline void shadow_spread_horizontal(
const std::vector<float>& src, std::vector<float>& dst,
int w,
int h,
int radius) {
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);
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;
135 inline void shadow_spread_vertical(
const std::vector<float>& src, std::vector<float>& dst,
int w,
int h,
int radius) {
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) {
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;
154 inline void shadow_apply_spread(std::vector<float>& mask,
int w,
int h,
int radius) {
155 if (radius <= 0 || w <= 0 || h <= 0)
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);
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);
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)
175 shifted[(y * w) + x] = src[(src_y * w) + src_x];
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());
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;
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);
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);
213 : opacity(0.6), blur_radius(18.0), spread(0.12), distance(10.0), angle(60.0), color(
"#000000") {
214 init_effect_details();
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();
224 void Shadow::init_effect_details()
235 std::shared_ptr<openshot::Frame>
Shadow::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
237 auto frame_image = frame->GetImage();
238 if (!frame_image || frame_image->isNull())
241 const int w = frame_image->width();
242 const int h = frame_image->height();
243 if (w <= 0 || h <= 0)
246 QImage source = frame_image->copy();
247 const unsigned char* source_pixels =
reinterpret_cast<const unsigned char*
>(source.constBits());
249 const float opacity_value = shadow_clampf(
static_cast<float>(
opacity.
GetValue(frame_number)), 0.0f, 1.0f);
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));
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));
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;
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);
270 std::vector<float> shadow_mask = shadow_shift_mask(alpha_mask, w, h, shadow_x, shadow_y);
272 shadow_apply_box_blur(shadow_mask, w, h, blur_value, 3);
274 QImage result(w, h, QImage::Format_RGBA8888_Premultiplied);
275 result.fill(Qt::transparent);
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);
285 *frame_image = result;
310 catch (
const std::exception& e) {
311 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
317 if (!root[
"opacity"].isNull())
319 if (!root[
"blur_radius"].isNull())
321 if (!root[
"spread"].isNull())
323 if (!root[
"distance"].isNull())
325 if (!root[
"angle"].isNull())
327 if (!root[
"color"].isNull())
343 return root.toStyledString();