25 inline float glow_clampf(
float value,
float low,
float high) {
33 inline void glow_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 glow_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 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)
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);
115 inline void glow_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 glow_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 glow_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 glow_spread_horizontal(mask, temp, w, h, radius);
160 glow_spread_vertical(temp, mask, w, h, radius);
165 : mode(
GLOW_MODE_OUTER), opacity(0.45), blur_radius(20.0), spread(0.10), color(
"#fff4c2") {
166 init_effect_details();
170 : mode(
GLOW_MODE_OUTER), opacity(new_opacity), blur_radius(new_blur_radius), spread(new_spread), color(new_color) {
171 init_effect_details();
174 void Glow::init_effect_details()
179 info.
description =
"Add an outer or inner glow based on visible pixels.";
185 std::shared_ptr<openshot::Frame>
Glow::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
187 auto frame_image = frame->GetImage();
188 if (!frame_image || frame_image->isNull())
191 const int w = frame_image->width();
192 const int h = frame_image->height();
193 if (w <= 0 || h <= 0)
196 QImage source = frame_image->copy();
197 const unsigned char* source_pixels =
reinterpret_cast<const unsigned char*
>(source.constBits());
199 const float opacity_value = glow_clampf(
static_cast<float>(
opacity.
GetValue(frame_number)), 0.0f, 1.0f);
201 const float spread_value = glow_clampf(
static_cast<float>(
spread.
GetValue(frame_number)), 0.0f, 1.0f);
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;
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;
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);
219 std::vector<float> blurred_mask = alpha_mask;
221 glow_apply_box_blur(blurred_mask, w, h, blur_value, 3);
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());
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);
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];
245 glow_apply_box_blur(inverse_mask, w, h, blur_value, 3);
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);
258 QImage result(w, h, QImage::Format_RGBA8888_Premultiplied);
259 result.fill(Qt::transparent);
261 QPainter painter(&result);
262 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform,
true);
264 painter.drawImage(0, 0, glow_overlay);
265 painter.drawImage(0, 0, source);
267 painter.drawImage(0, 0, source);
268 painter.drawImage(0, 0, glow_overlay);
272 *frame_image = result;
296 catch (
const std::exception& e) {
297 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
303 if (!root[
"mode"].isNull())
304 mode = root[
"mode"].asInt();
305 if (!root[
"opacity"].isNull())
307 if (!root[
"blur_radius"].isNull())
309 if (!root[
"spread"].isNull())
311 if (!root[
"color"].isNull())
328 return root.toStyledString();