OpenShot Library | libopenshot 1.1.1
LensFlare.cpp
Go to the documentation of this file.
1/*
2* Based on the FlareFX plug-in for GIMP 0.99 (version 1.05)
3 * Original Copyright (C) 1997-1998 Karl-Johan Andersson <t96kja@student.tdb.uu.se>
4 * Modifications May 2000 by Tim Copperfield <timecop@japan.co.jp>
5 *
6 * This code is available under the GNU GPL v2 (or any later version):
7 * You may redistribute and/or modify it under the terms of
8 * the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License,
10 * or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this code; if not, write to the Free Software Foundation,
19 * Inc., 59 Temple Place – Suite 330, Boston, MA 02111-1307, USA.
20 */
21
30// Copyright (c) 2008-2025 OpenShot Studios, LLC
31//
32// SPDX-License-Identifier: LGPL-3.0-or-later
33
34#include "LensFlare.h"
35#include "Exceptions.h"
36#include <QImage>
37#include <QPainter>
38#include <QColor>
39#include <cmath>
40#include <vector>
41#include <algorithm>
42#include <omp.h>
43
44using namespace openshot;
45
46// Default constructor
47LensFlare::LensFlare()
48 : x(-0.5), y(-0.5), brightness(1.0), size(1.0), spread(1.0),
49 color(Color("#ffffff"))
50{
51 init_effect_details();
52}
53
54// Parameterized constructor
56 const Keyframe &yPos,
57 const Keyframe &intensity,
58 const Keyframe &scale,
59 const Keyframe &spreadVal,
60 const Keyframe &bladeCount,
61 const Keyframe &shapeType,
62 const Color &tint)
63 : x(xPos), y(yPos), brightness(intensity), size(scale),
64 spread(spreadVal), color(tint)
65{
66 init_effect_details();
67}
68
69// Destructor
70LensFlare::~LensFlare() = default;
71
72// Initialize effect metadata
73void LensFlare::init_effect_details()
74{
76 info.class_name = "LensFlare";
77 info.name = "Lens Flare";
78 info.description = "Simulate sunlight hitting a lens with flares and spectral colors.";
79 info.has_video = true;
80 info.has_audio = false;
81}
82
83// Reflector definition
84struct Reflect {
85 float xp, yp, size;
86 QColor col;
87 int type; // 1..4
88};
89
90// Blend a color onto a pixel using additive blending
91static inline QRgb blendAdd(QRgb dst, const QColor &c, float p)
92{
93 int dr = (255 - qRed(dst)) * p * c.redF();
94 int dg = (255 - qGreen(dst)) * p * c.greenF();
95 int db = (255 - qBlue(dst)) * p * c.blueF();
96 int da = (255 - qAlpha(dst)) * p * c.alphaF();
97 return qRgba(
98 std::clamp(qRed(dst) + dr, 0, 255),
99 std::clamp(qGreen(dst) + dg, 0, 255),
100 std::clamp(qBlue(dst) + db, 0, 255),
101 std::clamp(qAlpha(dst) + da, 0, 255)
102 );
103}
104
105// Shift HSV values by given factors
106static QColor shifted_hsv(const QColor &base, float h_shift,
107 float s_scale, float v_scale,
108 float a_scale = 1.0f)
109{
110 // Qt6 switched getHsvF/setHsvF to float; keep compatibility with Qt5 (qreal)
111#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
112 float h, s, v, a;
113#else
114 qreal h, s, v, a;
115#endif
116 base.getHsvF(&h, &s, &v, &a);
117 if (s == 0.0)
118 h = 0.0;
119 h = static_cast<decltype(h)>(std::fmod(static_cast<double>(h + h_shift + 1.0), 1.0));
120 s = std::clamp(s * s_scale, static_cast<decltype(s)>(0.0), static_cast<decltype(s)>(1.0));
121 v = std::clamp(v * v_scale, static_cast<decltype(v)>(0.0), static_cast<decltype(v)>(1.0));
122 a = std::clamp(a * a_scale, static_cast<decltype(a)>(0.0), static_cast<decltype(a)>(1.0));
123
124 QColor out;
125 out.setHsvF(h, s, v, a);
126 return out;
127}
128
129// Initialize reflectors
130static void init_reflectors(std::vector<Reflect> &refs, float DX, float DY,
131 int width, int height, const QColor &tint,
132 float S)
133{
134 float halfW = width * 0.5f;
135 float halfH = height * 0.5f;
136 float matt = width;
137
138 struct Rdef { int type; float fx, fy, fsize, r, g, b; };
139 Rdef defs[] = {
140 {1, 0.6699f, 0.6699f, 0.027f, 0.0f, 14/255.0f, 113/255.0f},
141 {1, 0.2692f, 0.2692f, 0.010f, 90/255.0f, 181/255.0f, 142/255.0f},
142 {1, -0.0112f, -0.0112f, 0.005f, 56/255.0f, 140/255.0f, 106/255.0f},
143 {2, 0.6490f, 0.6490f, 0.031f, 9/255.0f, 29/255.0f, 19/255.0f},
144 {2, 0.4696f, 0.4696f, 0.015f, 24/255.0f, 14/255.0f, 0.0f},
145 {2, 0.4087f, 0.4087f, 0.037f, 24/255.0f, 14/255.0f, 0.0f},
146 {2, -0.2003f, -0.2003f, 0.022f, 42/255.0f, 19/255.0f, 0.0f},
147 {2, -0.4103f, -0.4103f, 0.025f, 0.0f, 9/255.0f, 17/255.0f},
148 {2, -0.4503f, -0.4503f, 0.058f, 10/255.0f, 4/255.0f, 0.0f},
149 {2, -0.5112f, -0.5112f, 0.017f, 5/255.0f, 5/255.0f, 14/255.0f},
150 {2, -1.4960f, -1.4960f, 0.20f, 9/255.0f, 4/255.0f, 0.0f},
151 {2, -1.4960f, -1.4960f, 0.50f, 9/255.0f, 4/255.0f, 0.0f},
152 {3, 0.4487f, 0.4487f, 0.075f, 34/255.0f, 19/255.0f, 0.0f},
153 {3, 1.0000f, 1.0000f, 0.10f, 14/255.0f, 26/255.0f, 0.0f},
154 {3, -1.3010f, -1.3010f, 0.039f, 10/255.0f, 25/255.0f, 13/255.0f},
155 {4, 1.3090f, 1.3090f, 0.19f, 9/255.0f, 0.0f, 17/255.0f},
156 {4, 1.3090f, 1.3090f, 0.195f, 9/255.0f, 16/255.0f, 5/255.0f},
157 {4, 1.3090f, 1.3090f, 0.20f, 17/255.0f, 4/255.0f, 0.0f},
158 {4, -1.3010f, -1.3010f, 0.038f, 17/255.0f, 4/255.0f, 0.0f}
159 };
160
161 refs.clear();
162 refs.reserve(std::size(defs));
163 bool whiteTint = (tint.saturationF() < 0.01f);
164
165 for (auto &d : defs) {
166 Reflect r;
167 r.type = d.type;
168 r.size = d.fsize * matt * S;
169 r.xp = halfW + d.fx * DX;
170 r.yp = halfH + d.fy * DY;
171
172 QColor base = QColor::fromRgbF(d.r, d.g, d.b, 1.0f);
173 r.col = whiteTint ? base
174 : shifted_hsv(base,
175 tint.hueF(),
176 tint.saturationF(),
177 tint.valueF(),
178 tint.alphaF());
179 refs.push_back(r);
180 }
181}
182
183// Apply a single reflector to a pixel
184static void apply_reflector(QRgb &pxl, const Reflect &r, int cx, int cy)
185{
186 float d = std::hypot(r.xp - cx, r.yp - cy);
187 float p = 0.0f;
188
189 switch (r.type) {
190 case 1:
191 p = (r.size - d) / r.size;
192 if (p > 0.0f) {
193 p *= p;
194 pxl = blendAdd(pxl, r.col, p);
195 }
196 break;
197 case 2:
198 p = (r.size - d) / (r.size * 0.15f);
199 if (p > 0.0f) {
200 p = std::min(p, 1.0f);
201 pxl = blendAdd(pxl, r.col, p);
202 }
203 break;
204 case 3:
205 p = (r.size - d) / (r.size * 0.12f);
206 if (p > 0.0f) {
207 p = std::min(p, 1.0f);
208 p = 1.0f - (p * 0.12f);
209 pxl = blendAdd(pxl, r.col, p);
210 }
211 break;
212 case 4:
213 p = std::abs((d - r.size) / (r.size * 0.04f));
214 if (p < 1.0f) {
215 pxl = blendAdd(pxl, r.col, 1.0f - p);
216 }
217 break;
218 }
219}
220
221// Render lens flare onto the frame
222std::shared_ptr<openshot::Frame>
223LensFlare::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t f)
224{
225 auto img = frame->GetImage();
226 int w = img->width();
227 int h = img->height();
228
229 // Fetch keyframe values
230 float X = x.GetValue(f),
231 Y = y.GetValue(f),
232 I = brightness.GetValue(f),
233 S = size.GetValue(f),
234 SP = spread.GetValue(f);
235
236 // Compute lens center + spread
237 float halfW = w * 0.5f, halfH = h * 0.5f;
238 float px = (X * 0.5f + 0.5f) * w;
239 float py = (Y * 0.5f + 0.5f) * h;
240 float DX = (halfW - px) * SP;
241 float DY = (halfH - py) * SP;
242
243 // Tint color
244 QColor tint = QColor::fromRgbF(
245 color.red.GetValue(f) / 255.0f,
246 color.green.GetValue(f) / 255.0f,
247 color.blue.GetValue(f) / 255.0f,
248 color.alpha.GetValue(f) / 255.0f
249 );
250
251 // Calculate radii for rings
252 float matt = w;
253 float scolor = matt * 0.0375f * S;
254 float sglow = matt * 0.078125f * S;
255 float sinner = matt * 0.1796875f * S;
256 float souter = matt * 0.3359375f * S;
257 float shalo = matt * 0.084375f * S;
258
259 // Helper to tint base hues
260 auto tintify = [&](float br, float bg, float bb) {
261 return QColor::fromRgbF(
262 br * tint.redF(),
263 bg * tint.greenF(),
264 bb * tint.blueF(),
265 tint.alphaF()
266 );
267 };
268
269 QColor c_color = tintify(239/255.0f, 239/255.0f, 239/255.0f);
270 QColor c_glow = tintify(245/255.0f, 245/255.0f, 245/255.0f);
271 QColor c_inner = tintify(1.0f, 38/255.0f, 43/255.0f);
272 QColor c_outer = tintify(69/255.0f, 59/255.0f, 64/255.0f);
273 QColor c_halo = tintify(80/255.0f, 15/255.0f, 4/255.0f);
274
275 // Precompute reflectors
276 std::vector<Reflect> refs;
277 init_reflectors(refs, DX, DY, w, h, tint, S);
278
279 // Build an un-premultiplied overlay
280 QImage overlay(w, h, QImage::Format_ARGB32);
281 overlay.fill(Qt::transparent);
282
283 #pragma omp parallel for schedule(dynamic)
284 for (int yy = 0; yy < h; ++yy) {
285 QRgb *scan = reinterpret_cast<QRgb*>(overlay.scanLine(yy));
286 for (int xx = 0; xx < w; ++xx) {
287 // start fully transparent
288 int r=0, g=0, b=0;
289 float d = std::hypot(xx - px, yy - py);
290
291 // bright core
292 if (d < scolor) {
293 float p = (scolor - d)/scolor; p*=p;
294 QRgb tmp = blendAdd(qRgba(r,g,b,0), c_color, p);
295 r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
296 }
297 // outer glow
298 if (d < sglow) {
299 float p = (sglow - d)/sglow; p*=p;
300 QRgb tmp = blendAdd(qRgba(r,g,b,0), c_glow, p);
301 r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
302 }
303 // inner ring
304 if (d < sinner) {
305 float p = (sinner - d)/sinner; p*=p;
306 QRgb tmp = blendAdd(qRgba(r,g,b,0), c_inner, p);
307 r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
308 }
309 // outer ring
310 if (d < souter) {
311 float p = (souter - d)/souter;
312 QRgb tmp = blendAdd(qRgba(r,g,b,0), c_outer, p);
313 r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
314 }
315 // halo ring
316 {
317 float p = std::abs((d - shalo)/(shalo*0.07f));
318 if (p < 1.0f) {
319 QRgb tmp = blendAdd(qRgba(r,g,b,0), c_halo, 1.0f-p);
320 r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
321 }
322 }
323 // little reflectors
324 for (auto &rf : refs) {
325 QRgb tmp = qRgba(r,g,b,0);
326 apply_reflector(tmp, rf, xx, yy);
327 r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
328 }
329
330 // force alpha = max(R,G,B)
331 int a = std::max({r,g,b});
332 scan[xx] = qRgba(r,g,b,a);
333 }
334 }
335
336 // Get original alpha
337 QImage origAlpha = img->convertToFormat(QImage::Format_Alpha8);
338
339 // Additive-light the overlay onto your frame
340 QPainter p(img.get());
341 p.setCompositionMode(QPainter::CompositionMode_Plus);
342 p.setOpacity(I);
343 p.drawImage(0, 0, overlay);
344 p.end();
345
346 // Rebuild alpha = max(orig, flare×I)
347 QImage finalA(w,h, QImage::Format_Alpha8);
348 auto overlayA = overlay.convertToFormat(QImage::Format_Alpha8);
349
350 for (int yy=0; yy<h; ++yy) {
351 uchar *oL = origAlpha.scanLine(yy);
352 uchar *fL = overlayA.scanLine(yy);
353 uchar *nL = finalA.scanLine(yy);
354 for (int xx=0; xx<w; ++xx) {
355 float oa = oL[xx]/255.0f;
356 float fa = (fL[xx]/255.0f)*I;
357 nL[xx] = static_cast<uchar>(std::clamp(std::max(oa,fa)*255.0f, 0.0f, 255.0f));
358 }
359 }
360 img->setAlphaChannel(finalA);
361 return frame;
362}
363
364// Create a new frame for this effect
365std::shared_ptr<openshot::Frame>
366LensFlare::GetFrame(int64_t frame_number)
367{
368 return GetFrame(std::make_shared<openshot::Frame>(), frame_number);
369}
370
371// Convert effect to JSON string
372std::string LensFlare::Json() const
373{
374 return JsonValue().toStyledString();
375}
376
377// Convert effect to JSON value
378Json::Value LensFlare::JsonValue() const
379{
380 Json::Value r = EffectBase::JsonValue();
381 r["type"] = info.class_name;
382 r["x"] = x.JsonValue();
383 r["y"] = y.JsonValue();
384 r["brightness"] = brightness.JsonValue();
385 r["size"] = size.JsonValue();
386 r["spread"] = spread.JsonValue();
387 r["color"] = color.JsonValue();
388 return r;
389}
390
391// Parse JSON from string
392void LensFlare::SetJson(const std::string v)
393{
395 catch (...) { throw InvalidJSON("LensFlare JSON"); }
396}
397
398// Apply JSON values to effect
399void LensFlare::SetJsonValue(const Json::Value r)
400{
402 if (!r["x"].isNull()) x.SetJsonValue(r["x"]);
403 if (!r["y"].isNull()) y.SetJsonValue(r["y"]);
404 if (!r["brightness"].isNull()) brightness.SetJsonValue(r["brightness"]);
405 if (!r["size"].isNull()) size.SetJsonValue(r["size"]);
406 if (!r["spread"].isNull()) spread.SetJsonValue(r["spread"]);
407 if (!r["color"].isNull()) color.SetJsonValue(r["color"]);
408}
409
410// Get properties as JSON for UI
411std::string LensFlare::PropertiesJSON(int64_t f) const
412{
413 Json::Value r = BasePropertiesJSON(f);
414 r["x"] = add_property_json("X", x.GetValue(f), "float", "-1..1", &x, -1, 1, false, f);
415 r["y"] = add_property_json("Y", y.GetValue(f), "float", "-1..1", &y, -1, 1, false, f);
416 r["brightness"] = add_property_json("Brightness", brightness.GetValue(f), "float", "0..1", &brightness, 0, 1, false, f);
417 r["size"] = add_property_json("Size", size.GetValue(f), "float", "0.1..3", &size, 0.1, 3, false, f);
418 r["spread"] = add_property_json("Spread", spread.GetValue(f), "float", "0..1", &spread, 0, 1, false, f);
419 r["color"] = add_property_json("Tint Color", 0.0, "color", "", &color.red, 0, 255, false, f);
420 r["color"]["red"] = add_property_json("Red", color.red.GetInt(f), "float", "0..255", &color.red, 0, 255, false, f);
421 r["color"]["green"] = add_property_json("Green", color.green.GetInt(f), "float", "0..255", &color.green, 0, 255, false, f);
422 r["color"]["blue"] = add_property_json("Blue", color.blue.GetInt(f), "float", "0..255", &color.blue, 0, 255, false, f);
423 r["color"]["alpha"] = add_property_json("Alpha", color.alpha.GetInt(f), "float", "0..255", &color.alpha, 0, 255, false, f);
424 return r.toStyledString();
425}
Header file for all Exception classes.
if(!codec) codec
Header file for LensFlare 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
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
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: LensFlare.cpp:411
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: LensFlare.cpp:399
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: LensFlare.cpp:378
~LensFlare() override
Keyframe brightness
Definition: LensFlare.h:32
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: LensFlare.cpp:366
std::string Json() const override
Generate JSON string of this object.
Definition: LensFlare.cpp:372
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: LensFlare.cpp:392
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
float size
Definition: LensFlare.cpp:85
float xp
Definition: LensFlare.cpp:85
QColor col
Definition: LensFlare.cpp:86
float yp
Definition: LensFlare.cpp:85
int type
Definition: LensFlare.cpp:87
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
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