OpenShot Library | libopenshot  0.4.0
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 
44 using namespace openshot;
45 
46 // Default constructor
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
70 LensFlare::~LensFlare() = default;
71 
72 // Initialize effect metadata
73 void 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
84 struct 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
91 static 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
106 static QColor shifted_hsv(const QColor &base, float h_shift,
107  float s_scale, float v_scale,
108  float a_scale = 1.0f)
109 {
110  qreal h, s, v, a;
111  base.getHsvF(&h, &s, &v, &a);
112  if (s == 0.0)
113  h = 0.0;
114  h = std::fmod(h + h_shift + 1.0, 1.0);
115  s = std::clamp(s * s_scale, 0.0, 1.0);
116  v = std::clamp(v * v_scale, 0.0, 1.0);
117  a = std::clamp(a * a_scale, 0.0, 1.0);
118 
119  QColor out;
120  out.setHsvF(h, s, v, a);
121  return out;
122 }
123 
124 // Initialize reflectors
125 static void init_reflectors(std::vector<Reflect> &refs, float DX, float DY,
126  int width, int height, const QColor &tint,
127  float S)
128 {
129  float halfW = width * 0.5f;
130  float halfH = height * 0.5f;
131  float matt = width;
132 
133  struct Rdef { int type; float fx, fy, fsize, r, g, b; };
134  Rdef defs[] = {
135  {1, 0.6699f, 0.6699f, 0.027f, 0.0f, 14/255.0f, 113/255.0f},
136  {1, 0.2692f, 0.2692f, 0.010f, 90/255.0f, 181/255.0f, 142/255.0f},
137  {1, -0.0112f, -0.0112f, 0.005f, 56/255.0f, 140/255.0f, 106/255.0f},
138  {2, 0.6490f, 0.6490f, 0.031f, 9/255.0f, 29/255.0f, 19/255.0f},
139  {2, 0.4696f, 0.4696f, 0.015f, 24/255.0f, 14/255.0f, 0.0f},
140  {2, 0.4087f, 0.4087f, 0.037f, 24/255.0f, 14/255.0f, 0.0f},
141  {2, -0.2003f, -0.2003f, 0.022f, 42/255.0f, 19/255.0f, 0.0f},
142  {2, -0.4103f, -0.4103f, 0.025f, 0.0f, 9/255.0f, 17/255.0f},
143  {2, -0.4503f, -0.4503f, 0.058f, 10/255.0f, 4/255.0f, 0.0f},
144  {2, -0.5112f, -0.5112f, 0.017f, 5/255.0f, 5/255.0f, 14/255.0f},
145  {2, -1.4960f, -1.4960f, 0.20f, 9/255.0f, 4/255.0f, 0.0f},
146  {2, -1.4960f, -1.4960f, 0.50f, 9/255.0f, 4/255.0f, 0.0f},
147  {3, 0.4487f, 0.4487f, 0.075f, 34/255.0f, 19/255.0f, 0.0f},
148  {3, 1.0000f, 1.0000f, 0.10f, 14/255.0f, 26/255.0f, 0.0f},
149  {3, -1.3010f, -1.3010f, 0.039f, 10/255.0f, 25/255.0f, 13/255.0f},
150  {4, 1.3090f, 1.3090f, 0.19f, 9/255.0f, 0.0f, 17/255.0f},
151  {4, 1.3090f, 1.3090f, 0.195f, 9/255.0f, 16/255.0f, 5/255.0f},
152  {4, 1.3090f, 1.3090f, 0.20f, 17/255.0f, 4/255.0f, 0.0f},
153  {4, -1.3010f, -1.3010f, 0.038f, 17/255.0f, 4/255.0f, 0.0f}
154  };
155 
156  refs.clear();
157  refs.reserve(std::size(defs));
158  bool whiteTint = (tint.saturationF() < 0.01f);
159 
160  for (auto &d : defs) {
161  Reflect r;
162  r.type = d.type;
163  r.size = d.fsize * matt * S;
164  r.xp = halfW + d.fx * DX;
165  r.yp = halfH + d.fy * DY;
166 
167  QColor base = QColor::fromRgbF(d.r, d.g, d.b, 1.0f);
168  r.col = whiteTint ? base
169  : shifted_hsv(base,
170  tint.hueF(),
171  tint.saturationF(),
172  tint.valueF(),
173  tint.alphaF());
174  refs.push_back(r);
175  }
176 }
177 
178 // Apply a single reflector to a pixel
179 static void apply_reflector(QRgb &pxl, const Reflect &r, int cx, int cy)
180 {
181  float d = std::hypot(r.xp - cx, r.yp - cy);
182  float p = 0.0f;
183 
184  switch (r.type) {
185  case 1:
186  p = (r.size - d) / r.size;
187  if (p > 0.0f) {
188  p *= p;
189  pxl = blendAdd(pxl, r.col, p);
190  }
191  break;
192  case 2:
193  p = (r.size - d) / (r.size * 0.15f);
194  if (p > 0.0f) {
195  p = std::min(p, 1.0f);
196  pxl = blendAdd(pxl, r.col, p);
197  }
198  break;
199  case 3:
200  p = (r.size - d) / (r.size * 0.12f);
201  if (p > 0.0f) {
202  p = std::min(p, 1.0f);
203  p = 1.0f - (p * 0.12f);
204  pxl = blendAdd(pxl, r.col, p);
205  }
206  break;
207  case 4:
208  p = std::abs((d - r.size) / (r.size * 0.04f));
209  if (p < 1.0f) {
210  pxl = blendAdd(pxl, r.col, 1.0f - p);
211  }
212  break;
213  }
214 }
215 
216 // Render lens flare onto the frame
217 std::shared_ptr<openshot::Frame>
218 LensFlare::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t f)
219 {
220  auto img = frame->GetImage();
221  int w = img->width();
222  int h = img->height();
223 
224  // Fetch keyframe values
225  float X = x.GetValue(f),
226  Y = y.GetValue(f),
227  I = brightness.GetValue(f),
228  S = size.GetValue(f),
229  SP = spread.GetValue(f);
230 
231  // Compute lens center + spread
232  float halfW = w * 0.5f, halfH = h * 0.5f;
233  float px = (X * 0.5f + 0.5f) * w;
234  float py = (Y * 0.5f + 0.5f) * h;
235  float DX = (halfW - px) * SP;
236  float DY = (halfH - py) * SP;
237 
238  // Tint color
239  QColor tint = QColor::fromRgbF(
240  color.red.GetValue(f) / 255.0f,
241  color.green.GetValue(f) / 255.0f,
242  color.blue.GetValue(f) / 255.0f,
243  color.alpha.GetValue(f) / 255.0f
244  );
245 
246  // Calculate radii for rings
247  float matt = w;
248  float scolor = matt * 0.0375f * S;
249  float sglow = matt * 0.078125f * S;
250  float sinner = matt * 0.1796875f * S;
251  float souter = matt * 0.3359375f * S;
252  float shalo = matt * 0.084375f * S;
253 
254  // Helper to tint base hues
255  auto tintify = [&](float br, float bg, float bb) {
256  return QColor::fromRgbF(
257  br * tint.redF(),
258  bg * tint.greenF(),
259  bb * tint.blueF(),
260  tint.alphaF()
261  );
262  };
263 
264  QColor c_color = tintify(239/255.0f, 239/255.0f, 239/255.0f);
265  QColor c_glow = tintify(245/255.0f, 245/255.0f, 245/255.0f);
266  QColor c_inner = tintify(1.0f, 38/255.0f, 43/255.0f);
267  QColor c_outer = tintify(69/255.0f, 59/255.0f, 64/255.0f);
268  QColor c_halo = tintify(80/255.0f, 15/255.0f, 4/255.0f);
269 
270  // Precompute reflectors
271  std::vector<Reflect> refs;
272  init_reflectors(refs, DX, DY, w, h, tint, S);
273 
274  // Build an un-premultiplied overlay
275  QImage overlay(w, h, QImage::Format_ARGB32);
276  overlay.fill(Qt::transparent);
277 
278  #pragma omp parallel for schedule(dynamic)
279  for (int yy = 0; yy < h; ++yy) {
280  QRgb *scan = reinterpret_cast<QRgb*>(overlay.scanLine(yy));
281  for (int xx = 0; xx < w; ++xx) {
282  // start fully transparent
283  int r=0, g=0, b=0;
284  float d = std::hypot(xx - px, yy - py);
285 
286  // bright core
287  if (d < scolor) {
288  float p = (scolor - d)/scolor; p*=p;
289  QRgb tmp = blendAdd(qRgba(r,g,b,0), c_color, p);
290  r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
291  }
292  // outer glow
293  if (d < sglow) {
294  float p = (sglow - d)/sglow; p*=p;
295  QRgb tmp = blendAdd(qRgba(r,g,b,0), c_glow, p);
296  r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
297  }
298  // inner ring
299  if (d < sinner) {
300  float p = (sinner - d)/sinner; p*=p;
301  QRgb tmp = blendAdd(qRgba(r,g,b,0), c_inner, p);
302  r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
303  }
304  // outer ring
305  if (d < souter) {
306  float p = (souter - d)/souter;
307  QRgb tmp = blendAdd(qRgba(r,g,b,0), c_outer, p);
308  r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
309  }
310  // halo ring
311  {
312  float p = std::abs((d - shalo)/(shalo*0.07f));
313  if (p < 1.0f) {
314  QRgb tmp = blendAdd(qRgba(r,g,b,0), c_halo, 1.0f-p);
315  r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
316  }
317  }
318  // little reflectors
319  for (auto &rf : refs) {
320  QRgb tmp = qRgba(r,g,b,0);
321  apply_reflector(tmp, rf, xx, yy);
322  r = qRed(tmp); g = qGreen(tmp); b = qBlue(tmp);
323  }
324 
325  // force alpha = max(R,G,B)
326  int a = std::max({r,g,b});
327  scan[xx] = qRgba(r,g,b,a);
328  }
329  }
330 
331  // Get original alpha
332  QImage origAlpha = img->convertToFormat(QImage::Format_Alpha8);
333 
334  // Additive-light the overlay onto your frame
335  QPainter p(img.get());
336  p.setCompositionMode(QPainter::CompositionMode_Plus);
337  p.setOpacity(I);
338  p.drawImage(0, 0, overlay);
339  p.end();
340 
341  // Rebuild alpha = max(orig, flare×I)
342  QImage finalA(w,h, QImage::Format_Alpha8);
343  auto overlayA = overlay.convertToFormat(QImage::Format_Alpha8);
344 
345  for (int yy=0; yy<h; ++yy) {
346  uchar *oL = origAlpha.scanLine(yy);
347  uchar *fL = overlayA.scanLine(yy);
348  uchar *nL = finalA.scanLine(yy);
349  for (int xx=0; xx<w; ++xx) {
350  float oa = oL[xx]/255.0f;
351  float fa = (fL[xx]/255.0f)*I;
352  nL[xx] = static_cast<uchar>(std::clamp(std::max(oa,fa)*255.0f, 0.0f, 255.0f));
353  }
354  }
355  img->setAlphaChannel(finalA);
356  return frame;
357 }
358 
359 // Create a new frame for this effect
360 std::shared_ptr<openshot::Frame>
361 LensFlare::GetFrame(int64_t frame_number)
362 {
363  return GetFrame(std::make_shared<openshot::Frame>(), frame_number);
364 }
365 
366 // Convert effect to JSON string
367 std::string LensFlare::Json() const
368 {
369  return JsonValue().toStyledString();
370 }
371 
372 // Convert effect to JSON value
373 Json::Value LensFlare::JsonValue() const
374 {
375  Json::Value r = EffectBase::JsonValue();
376  r["type"] = info.class_name;
377  r["x"] = x.JsonValue();
378  r["y"] = y.JsonValue();
379  r["brightness"] = brightness.JsonValue();
380  r["size"] = size.JsonValue();
381  r["spread"] = spread.JsonValue();
382  r["color"] = color.JsonValue();
383  return r;
384 }
385 
386 // Parse JSON from string
387 void LensFlare::SetJson(const std::string v)
388 {
390  catch (...) { throw InvalidJSON("LensFlare JSON"); }
391 }
392 
393 // Apply JSON values to effect
394 void LensFlare::SetJsonValue(const Json::Value r)
395 {
397  if (!r["x"].isNull()) x.SetJsonValue(r["x"]);
398  if (!r["y"].isNull()) y.SetJsonValue(r["y"]);
399  if (!r["brightness"].isNull()) brightness.SetJsonValue(r["brightness"]);
400  if (!r["size"].isNull()) size.SetJsonValue(r["size"]);
401  if (!r["spread"].isNull()) spread.SetJsonValue(r["spread"]);
402  if (!r["color"].isNull()) color.SetJsonValue(r["color"]);
403 }
404 
405 // Get properties as JSON for UI
406 std::string LensFlare::PropertiesJSON(int64_t f) const
407 {
408  Json::Value r = BasePropertiesJSON(f);
409  r["x"] = add_property_json("X", x.GetValue(f), "float", "-1..1", &x, -1, 1, false, f);
410  r["y"] = add_property_json("Y", y.GetValue(f), "float", "-1..1", &y, -1, 1, false, f);
411  r["brightness"] = add_property_json("Brightness", brightness.GetValue(f), "float", "0..1", &brightness, 0, 1, false, f);
412  r["size"] = add_property_json("Size", size.GetValue(f), "float", "0.1..3", &size, 0.1, 3, false, f);
413  r["spread"] = add_property_json("Spread", spread.GetValue(f), "float", "0..1", &spread, 0, 1, false, f);
414  r["color"] = add_property_json("Tint Color", 0.0, "color", "", &color.red, 0, 255, false, f);
415  r["color"]["red"] = add_property_json("Red", color.red.GetInt(f), "float", "0..255", &color.red, 0, 255, false, f);
416  r["color"]["green"] = add_property_json("Green", color.green.GetInt(f), "float", "0..255", &color.green, 0, 255, false, f);
417  r["color"]["blue"] = add_property_json("Blue", color.blue.GetInt(f), "float", "0..255", &color.blue, 0, 255, false, f);
418  r["color"]["alpha"] = add_property_json("Alpha", color.alpha.GetInt(f), "float", "0..255", &color.alpha, 0, 255, false, f);
419  return r.toStyledString();
420 }
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::LensFlare::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: LensFlare.cpp:361
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:69
openshot::LensFlare::y
Keyframe y
Definition: LensFlare.h:31
Reflect::yp
float yp
Definition: LensFlare.cpp:85
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:79
openshot::LensFlare::size
Keyframe size
Definition: LensFlare.h:33
LensFlare.h
Header file for LensFlare class.
Reflect::size
float size
Definition: LensFlare.cpp:85
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::LensFlare::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: LensFlare.cpp:406
openshot::LensFlare::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: LensFlare.cpp:394
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::LensFlare::LensFlare
LensFlare()
Definition: LensFlare.cpp:47
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:179
if
if(!codec) codec
openshot::LensFlare::x
Keyframe x
Definition: LensFlare.h:30
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::LensFlare::Json
std::string Json() const override
Generate JSON string of this object.
Definition: LensFlare.cpp:367
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:217
Reflect::xp
float xp
Definition: LensFlare.cpp:85
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:24
openshot::Color::green
openshot::Keyframe green
Curve representing the green value (0 - 255)
Definition: Color.h:31
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:41
Reflect::col
QColor col
Definition: LensFlare.cpp:86
Reflect::type
int type
Definition: LensFlare.cpp:87
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:36
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:38
openshot::EffectInfoStruct::has_video
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:40
openshot::LensFlare::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: LensFlare.cpp:387
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:37
openshot::LensFlare::spread
Keyframe spread
Definition: LensFlare.h:34
openshot::LensFlare::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: LensFlare.cpp:373
openshot::LensFlare::~LensFlare
~LensFlare() override
openshot::Color::alpha
openshot::Keyframe alpha
Curve representing the alpha value (0 - 255)
Definition: Color.h:33
openshot::LensFlare::brightness
Keyframe brightness
Definition: LensFlare.h:32
openshot::LensFlare::color
Color color
Definition: LensFlare.h:35
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.
Reflect
Definition: LensFlare.cpp:84
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:115
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258