OpenShot Library | libopenshot  0.4.0
ColorMap.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2025 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "ColorMap.h"
14 #include "Exceptions.h"
15 #include <omp.h>
16 #include <QRegularExpression>
17 
18 using namespace openshot;
19 
20 void ColorMap::load_cube_file()
21 {
22  if (lut_path.empty()) {
23  lut_data.clear();
24  lut_size = 0;
25  needs_refresh = false;
26  return;
27  }
28 
29  int parsed_size = 0;
30  std::vector<float> parsed_data;
31 
32  #pragma omp critical(load_lut)
33  {
34  QFile file(QString::fromStdString(lut_path));
35  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
36  // leave parsed_size == 0
37  } else {
38  QTextStream in(&file);
39  QString line;
40  QRegularExpression ws_re("\\s+");
41 
42  // 1) Find LUT_3D_SIZE
43  while (!in.atEnd()) {
44  line = in.readLine().trimmed();
45  if (line.startsWith("LUT_3D_SIZE")) {
46  auto parts = line.split(ws_re);
47  if (parts.size() >= 2) {
48  parsed_size = parts[1].toInt();
49  }
50  break;
51  }
52  }
53 
54  // 2) Read N³ lines of R G B floats
55  if (parsed_size > 0) {
56  int total = parsed_size * parsed_size * parsed_size;
57  parsed_data.reserve(size_t(total * 3));
58  while (!in.atEnd() && int(parsed_data.size()) < total * 3) {
59  line = in.readLine().trimmed();
60  if (line.isEmpty() ||
61  line.startsWith("#") ||
62  line.startsWith("TITLE") ||
63  line.startsWith("DOMAIN"))
64  {
65  continue;
66  }
67  auto vals = line.split(ws_re);
68  if (vals.size() >= 3) {
69  // .cube file is R G B
70  parsed_data.push_back(vals[0].toFloat());
71  parsed_data.push_back(vals[1].toFloat());
72  parsed_data.push_back(vals[2].toFloat());
73  }
74  }
75  if (int(parsed_data.size()) != total * 3) {
76  parsed_data.clear();
77  parsed_size = 0;
78  }
79  }
80  }
81  }
82 
83  if (parsed_size > 0) {
84  lut_size = parsed_size;
85  lut_data.swap(parsed_data);
86  } else {
87  lut_data.clear();
88  lut_size = 0;
89  }
90  needs_refresh = false;
91 }
92 
93 void ColorMap::init_effect_details()
94 {
96  info.class_name = "ColorMap";
97  info.name = "Color Map / Lookup";
98  info.description = "Adjust colors using 3D LUT lookup tables (.cube format)";
99  info.has_video = true;
100  info.has_audio = false;
101 }
102 
104  : lut_path(""), lut_size(0), needs_refresh(true),
105  intensity(1.0), intensity_r(1.0), intensity_g(1.0), intensity_b(1.0)
106 {
107  init_effect_details();
108  load_cube_file();
109 }
110 
111 ColorMap::ColorMap(const std::string &path,
112  const Keyframe &i,
113  const Keyframe &iR,
114  const Keyframe &iG,
115  const Keyframe &iB)
116  : lut_path(path),
117  lut_size(0),
118  needs_refresh(true),
119  intensity(i),
120  intensity_r(iR),
121  intensity_g(iG),
122  intensity_b(iB)
123 {
124  init_effect_details();
125  load_cube_file();
126 }
127 
128 std::shared_ptr<openshot::Frame>
129 ColorMap::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
130 {
131  // Reload LUT when its path changed; no locking here
132  if (needs_refresh) {
133  load_cube_file();
134  needs_refresh = false;
135  }
136 
137  if (lut_data.empty())
138  return frame;
139 
140  auto image = frame->GetImage();
141  int w = image->width(), h = image->height();
142  unsigned char *pixels = image->bits();
143 
144  float overall = float(intensity.GetValue(frame_number));
145  float tR = float(intensity_r.GetValue(frame_number)) * overall;
146  float tG = float(intensity_g.GetValue(frame_number)) * overall;
147  float tB = float(intensity_b.GetValue(frame_number)) * overall;
148 
149  int pixel_count = w * h;
150  #pragma omp parallel for
151  for (int i = 0; i < pixel_count; ++i) {
152  int idx = i * 4;
153  int A = pixels[idx + 3];
154  float alpha = A / 255.0f;
155  if (alpha == 0.0f) continue;
156 
157  // demultiply premultiplied RGBA
158  float R = pixels[idx + 0] / alpha;
159  float G = pixels[idx + 1] / alpha;
160  float B = pixels[idx + 2] / alpha;
161 
162  // normalize to [0,1]
163  float Rn = R * (1.0f / 255.0f);
164  float Gn = G * (1.0f / 255.0f);
165  float Bn = B * (1.0f / 255.0f);
166 
167  // map into LUT space [0 .. size-1]
168  float rf = Rn * (lut_size - 1);
169  float gf = Gn * (lut_size - 1);
170  float bf = Bn * (lut_size - 1);
171 
172  int r0 = int(floor(rf)), r1 = std::min(r0 + 1, lut_size - 1);
173  int g0 = int(floor(gf)), g1 = std::min(g0 + 1, lut_size - 1);
174  int b0 = int(floor(bf)), b1 = std::min(b0 + 1, lut_size - 1);
175 
176  float dr = rf - r0;
177  float dg = gf - g0;
178  float db = bf - b0;
179 
180  // compute base offsets with red fastest, then green, then blue
181  int base000 = ((b0 * lut_size + g0) * lut_size + r0) * 3;
182  int base100 = ((b0 * lut_size + g0) * lut_size + r1) * 3;
183  int base010 = ((b0 * lut_size + g1) * lut_size + r0) * 3;
184  int base110 = ((b0 * lut_size + g1) * lut_size + r1) * 3;
185  int base001 = ((b1 * lut_size + g0) * lut_size + r0) * 3;
186  int base101 = ((b1 * lut_size + g0) * lut_size + r1) * 3;
187  int base011 = ((b1 * lut_size + g1) * lut_size + r0) * 3;
188  int base111 = ((b1 * lut_size + g1) * lut_size + r1) * 3;
189 
190  // trilinear interpolation
191  // red
192  float c00 = lut_data[base000 + 0] * (1 - dr) + lut_data[base100 + 0] * dr;
193  float c01 = lut_data[base001 + 0] * (1 - dr) + lut_data[base101 + 0] * dr;
194  float c10 = lut_data[base010 + 0] * (1 - dr) + lut_data[base110 + 0] * dr;
195  float c11 = lut_data[base011 + 0] * (1 - dr) + lut_data[base111 + 0] * dr;
196  float c0 = c00 * (1 - dg) + c10 * dg;
197  float c1 = c01 * (1 - dg) + c11 * dg;
198  float lr = c0 * (1 - db) + c1 * db;
199 
200  // green
201  c00 = lut_data[base000 + 1] * (1 - dr) + lut_data[base100 + 1] * dr;
202  c01 = lut_data[base001 + 1] * (1 - dr) + lut_data[base101 + 1] * dr;
203  c10 = lut_data[base010 + 1] * (1 - dr) + lut_data[base110 + 1] * dr;
204  c11 = lut_data[base011 + 1] * (1 - dr) + lut_data[base111 + 1] * dr;
205  c0 = c00 * (1 - dg) + c10 * dg;
206  c1 = c01 * (1 - dg) + c11 * dg;
207  float lg = c0 * (1 - db) + c1 * db;
208 
209  // blue
210  c00 = lut_data[base000 + 2] * (1 - dr) + lut_data[base100 + 2] * dr;
211  c01 = lut_data[base001 + 2] * (1 - dr) + lut_data[base101 + 2] * dr;
212  c10 = lut_data[base010 + 2] * (1 - dr) + lut_data[base110 + 2] * dr;
213  c11 = lut_data[base011 + 2] * (1 - dr) + lut_data[base111 + 2] * dr;
214  c0 = c00 * (1 - dg) + c10 * dg;
215  c1 = c01 * (1 - dg) + c11 * dg;
216  float lb = c0 * (1 - db) + c1 * db;
217 
218  // blend per-channel, re-premultiply alpha
219  float outR = (lr * tR + Rn * (1 - tR)) * alpha;
220  float outG = (lg * tG + Gn * (1 - tG)) * alpha;
221  float outB = (lb * tB + Bn * (1 - tB)) * alpha;
222 
223  pixels[idx + 0] = constrain(outR * 255.0f);
224  pixels[idx + 1] = constrain(outG * 255.0f);
225  pixels[idx + 2] = constrain(outB * 255.0f);
226  // alpha left unchanged
227  }
228 
229  return frame;
230 }
231 
232 
233 std::string ColorMap::Json() const
234 {
235  return JsonValue().toStyledString();
236 }
237 
238 Json::Value ColorMap::JsonValue() const
239 {
240  Json::Value root = EffectBase::JsonValue();
241  root["type"] = info.class_name;
242  root["lut_path"] = lut_path;
243  root["intensity"] = intensity.JsonValue();
244  root["intensity_r"] = intensity_r.JsonValue();
245  root["intensity_g"] = intensity_g.JsonValue();
246  root["intensity_b"] = intensity_b.JsonValue();
247  return root;
248 }
249 
250 void ColorMap::SetJson(const std::string value)
251 {
252  try {
253  const Json::Value root = openshot::stringToJson(value);
254  SetJsonValue(root);
255  }
256  catch (...) {
257  throw InvalidJSON("Invalid JSON for ColorMap effect");
258  }
259 }
260 
261 void ColorMap::SetJsonValue(const Json::Value root)
262 {
264  if (!root["lut_path"].isNull())
265  {
266  lut_path = root["lut_path"].asString();
267  needs_refresh = true;
268  }
269  if (!root["intensity"].isNull())
270  intensity.SetJsonValue(root["intensity"]);
271  if (!root["intensity_r"].isNull())
272  intensity_r.SetJsonValue(root["intensity_r"]);
273  if (!root["intensity_g"].isNull())
274  intensity_g.SetJsonValue(root["intensity_g"]);
275  if (!root["intensity_b"].isNull())
276  intensity_b.SetJsonValue(root["intensity_b"]);
277 }
278 
279 std::string ColorMap::PropertiesJSON(int64_t requested_frame) const
280 {
281  Json::Value root = BasePropertiesJSON(requested_frame);
282 
283  root["lut_path"] = add_property_json(
284  "LUT File", 0.0, "string", lut_path, nullptr, 0, 0, false, requested_frame);
285 
286  root["intensity"] = add_property_json(
287  "Overall Intensity",
288  intensity.GetValue(requested_frame),
289  "float", "", &intensity, 0.0, 1.0, false, requested_frame);
290 
291  root["intensity_r"] = add_property_json(
292  "Red Intensity",
293  intensity_r.GetValue(requested_frame),
294  "float", "", &intensity_r, 0.0, 1.0, false, requested_frame);
295 
296  root["intensity_g"] = add_property_json(
297  "Green Intensity",
298  intensity_g.GetValue(requested_frame),
299  "float", "", &intensity_g, 0.0, 1.0, false, requested_frame);
300 
301  root["intensity_b"] = add_property_json(
302  "Blue Intensity",
303  intensity_b.GetValue(requested_frame),
304  "float", "", &intensity_b, 0.0, 1.0, false, requested_frame);
305 
306  return root.toStyledString();
307 }
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::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:69
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::ColorMap::GetFrame
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number) override
Apply effect to a new frame.
Definition: ColorMap.h:74
openshot::ColorMap::ColorMap
ColorMap()
Blank constructor (used by JSON loader)
Definition: ColorMap.cpp:103
openshot::ColorMap::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: ColorMap.cpp:238
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::ColorMap::intensity
Keyframe intensity
Overall intensity 0–1 (affects all channels)
Definition: ColorMap.h:49
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
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
openshot::ColorMap::intensity_g
Keyframe intensity_g
Blend 0–1 for green channel.
Definition: ColorMap.h:51
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::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:217
openshot::ColorMap::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: ColorMap.cpp:261
openshot::ColorMap::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: ColorMap.cpp:250
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:24
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:41
path
path
Definition: FFmpegWriter.cpp:1476
openshot::ColorMap::Json
std::string Json() const override
Generate JSON string of this object.
Definition: ColorMap.cpp:233
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:36
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::ColorMap::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Expose properties (for UI)
Definition: ColorMap.cpp:279
openshot::ColorMap::intensity_b
Keyframe intensity_b
Blend 0–1 for blue channel.
Definition: ColorMap.h:52
openshot::EffectBase::constrain
int constrain(int color_value)
Constrain a color value from 0 to 255.
Definition: EffectBase.cpp:60
openshot::ColorMap::intensity_r
Keyframe intensity_r
Blend 0–1 for red channel.
Definition: ColorMap.h:50
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:37
ColorMap.h
Header file for ColorMap (LUT) effect.
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:115
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258