OpenShot Library | libopenshot  0.5.0
ChromaKey.cpp
Go to the documentation of this file.
1 
10 // Copyright (c) 2008-2019 OpenShot Studios, LLC
11 //
12 // SPDX-License-Identifier: LGPL-3.0-or-later
13 
14 #include "ChromaKey.h"
15 #include "Exceptions.h"
16 #if USE_BABL
17 #include <babl/babl.h>
18 #endif
19 #include <array>
20 #include <cstdint>
21 #include <vector>
22 #include <cmath>
23 
24 using namespace openshot;
25 
27 ChromaKey::ChromaKey() : fuzz(20.0), halo(10.0), method(CHROMAKEY_BASIC_SOFT) {
28  // Init default color
29  color = Color();
30 
31  // Init effect properties
32  init_effect_details();
33 }
34 
35 // Standard constructor, which takes an openshot::Color object, a 'fuzz' factor,
36 // an optional halo distance and an optional keying method.
38  color(color), fuzz(fuzz), halo(halo), method(method)
39 {
40  // Init effect properties
41  init_effect_details();
42 }
43 
44 // Init effect settings
45 void ChromaKey::init_effect_details()
46 {
49 
51  info.class_name = "ChromaKey";
52  info.name = "Chroma Key (Greenscreen)";
53  info.description = "Replaces the color (or chroma) of the frame with transparency (i.e. keys out the color).";
54  info.has_audio = false;
55  info.has_video = true;
56 }
57 
58 // This method is required for all derived classes of EffectBase, and returns a
59 // modified openshot::Frame object
60 //
61 // Because a frame's QImage is always in Format_RGB8888_Premultiplied, we do not
62 // need to muck about with QRgb and its helper functions, qRed, QGreen, QBlue and
63 // qAlpha, and indeed doing so will get wrong results on almost every platform
64 // when we operate on the pixel buffers instead of calling the pixel methods in
65 // QImage. QRgb is always in the form 0xAARRGGBB, but treating the pixel buffer
66 // as an array of QRgb will yield values of the form 0xAABBGGRR on little endian
67 // systems and 0xRRGGBBAA on big endian systems.
68 //
69 // We need to operate on the pixel buffers here because doing this all pixel by
70 // pixel is be horribly slow, especially with keying methods other than basic.
71 // The babl conversion functions are very slow if iterating over pixels and every
72 // effort should be made to do babl conversions in blocks of as many pixels as
73 // can be done at once.
74 //
75 // The default keying method tries to ascertain the original pixel color by
76 // dividing the red, green and blue channels by the alpha (and multiplying by
77 // 255). The other methods do not do this for several reasons:
78 //
79 // 1. The calculation will not necessarily return the original value, because
80 // the premultiplication of alpha using unsigned 8 bit integers loses
81 // accuracy at the least significant bit. Even an alpha of 0xfe means that
82 // we are left with only 255 values to start with and cannot regain the full
83 // 256 values that could have been in the input. At an alpha of 0x7f the
84 // entire least significant bit has been lost, and at an alpho of 0x3f the
85 // two entire least significant bits have been lost. Chroma keying is very
86 // sensitive to these losses of precision so if the alpha has been applied
87 // already at anything other than 0xff and 0x00, we are already screwed and
88 // this calculation will not help.
89 //
90 // 2. The calculation used for the default method seems to be wrong anyway as
91 // it always rounds down rather than to the nearest whole number.
92 //
93 // 3. As mentioned above, babl conversion functions are very slow when iterating
94 // over individual pixels. We would have to convert the entire input buffer
95 // in one go to avoid this. It just does not seem worth it given the loss
96 // of accuracy we already have.
97 //
98 // 4. It is difficult to see how it could make sense to apply chroma keying
99 // after other non-chroma-key effects. The purpose is to remove an unwanted
100 // background in the input stream, rather than removing some calculated
101 // value that is the output of another effect.
102 std::shared_ptr<openshot::Frame> ChromaKey::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
103 {
104  int threshold = fuzz.GetInt(frame_number);
105  int halothreshold = halo.GetInt(frame_number);
106  long mask_R = color.red.GetInt(frame_number);
107  long mask_G = color.green.GetInt(frame_number);
108  long mask_B = color.blue.GetInt(frame_number);
109 
110  // Get source image
111  std::shared_ptr<QImage> image = frame->GetImage();
112 
113  int width = image->width();
114  int height = image->height();
115 
116  int pixelcount = width * height;
117 
118 #if USE_BABL
119  if (method != CHROMAKEY_BASIC && method != CHROMAKEY_BASIC_SOFT
120  && method <= CHROMAKEY_LAST_METHOD)
121  {
122  static bool need_init = true;
123 
124  if (need_init)
125  {
126  babl_init();
127  need_init = false;
128  }
129 
130  Babl const *rgb = babl_format("R'G'B'A u8");
131  Babl const *format = 0;
132  Babl const *fish = 0;
133  std::vector<unsigned char> pixelbuf;
134  int rowwidth = 0;
135 
136  switch(method)
137  {
138  case CHROMAKEY_HSVL_H:
139  case CHROMAKEY_HSV_S:
140  case CHROMAKEY_HSV_V:
141  format = babl_format("HSV float");
142  rowwidth = width * sizeof(float) * 3;
143  break;
144 
145  case CHROMAKEY_HSL_S:
146  case CHROMAKEY_HSL_L:
147  format = babl_format("HSL float");
148  rowwidth = width * sizeof(float) * 3;
149  break;
150 
151  case CHROMAKEY_CIE_LCH_L:
152  case CHROMAKEY_CIE_LCH_C:
153  case CHROMAKEY_CIE_LCH_H:
154  format = babl_format("CIE LCH(ab) float");
155  rowwidth = width * sizeof(float) * 3;
156  break;
157 
159  format = babl_format("CIE Lab u8");
160  rowwidth = width * 3;
161  break;
162 
163  case CHROMAKEY_YCBCR:
164  format = babl_format("Y'CbCr u8");
165  rowwidth = width * 3;
166  break;
167 
168  case CHROMAKEY_BASIC:
169  break;
170  }
171 
172  pixelbuf.resize(rowwidth * height);
173 
174  if (rgb && format && (fish = babl_fish(rgb, format)) != 0)
175  {
176  int idx = 0;
177  unsigned char mask_in[4];
178  union { float f[4]; unsigned char u[4]; } mask;
179  float const *pf = (float *) pixelbuf.data();
180  unsigned char const *pc = pixelbuf.data();
181 
182  mask_in[0] = mask_R;
183  mask_in[1] = mask_G;
184  mask_in[2] = mask_B;
185  mask_in[3] = 255;
186  babl_process(fish, mask_in, &mask, 1);
187 
188  if (0) //image->bytesPerLine() == width * 4)
189  {
190  // Because babl_process is expensive to call, but efficient
191  // with long sequences of pixels, attempt to convert the
192  // entire buffer at once if we can
193  babl_process(fish, image->bits(), pixelbuf.data(), pixelcount);
194  }
195  else
196  {
197  unsigned char *rowdata = pixelbuf.data();
198 
199  for (int y = 0; y < height; ++y, rowdata += rowwidth)
200  babl_process(fish, image->scanLine(y), rowdata, width);
201  }
202 
203  switch(method)
204  {
205  case CHROMAKEY_HSVL_H:
206  for (int y = 0; y < height; ++y)
207  {
208  unsigned char *pixel = image->scanLine(y);
209 
210  for (int x = 0; x < width; ++x, pixel += 4, pf += 3)
211  {
212  float tmp = fabs(pf[0] - mask.f[0]);
213 
214  if (tmp > 0.5)
215  tmp = 1.0 - tmp;
216  tmp *= 500;
217  if (tmp <= threshold)
218  {
219  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
220  }
221  else if (tmp <= threshold + halothreshold)
222  {
223  float alphamult = (tmp - threshold) / halothreshold;
224 
225  pixel[0] *= alphamult;
226  pixel[1] *= alphamult;
227  pixel[2] *= alphamult;
228  pixel[3] *= alphamult;
229  }
230  }
231  }
232  break;
233 
234  case CHROMAKEY_HSV_S:
235  case CHROMAKEY_HSL_S:
236  for (int y = 0; y < height; ++y)
237  {
238  unsigned char *pixel = image->scanLine(y);
239 
240  for (int x = 0; x < width; ++x, pixel += 4, pf += 3)
241  {
242  float tmp = fabs(pf[1] - mask.f[1]) * 255;
243 
244  if (tmp <= threshold)
245  {
246  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
247  }
248  else if (tmp <= threshold + halothreshold)
249  {
250  float alphamult = (tmp - threshold) / halothreshold;
251 
252  pixel[0] *= alphamult;
253  pixel[1] *= alphamult;
254  pixel[2] *= alphamult;
255  pixel[3] *= alphamult;
256  }
257  }
258  }
259  break;
260 
261  case CHROMAKEY_HSV_V:
262  case CHROMAKEY_HSL_L:
263  for (int y = 0; y < height; ++y)
264  {
265  unsigned char *pixel = image->scanLine(y);
266 
267  for (int x = 0; x < width; ++x, pixel += 4, pf += 3)
268  {
269  float tmp = fabs(pf[2] - mask.f[2]) * 255;
270 
271  if (tmp <= threshold)
272  {
273  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
274  }
275  else if (tmp <= threshold + halothreshold)
276  {
277  float alphamult = (tmp - threshold) / halothreshold;
278 
279  pixel[0] *= alphamult;
280  pixel[1] *= alphamult;
281  pixel[2] *= alphamult;
282  pixel[3] *= alphamult;
283  }
284  }
285  }
286  break;
287 
288  case CHROMAKEY_YCBCR:
289  {
290  // sqrt(db^2 + dr^2), with db/dr in [-255, 255]
291  static const std::array<float, 130051> sqrt_lut = [] {
292  std::array<float, 130051> lut{};
293  for (int i = 0; i <= 130050; ++i)
294  lut[i] = std::sqrt(static_cast<float>(i));
295  return lut;
296  }();
297 
298  const int threshold_sq = threshold * threshold;
299  const int halo_upper = threshold + halothreshold;
300  const int halo_upper_sq = halo_upper * halo_upper;
301  const float inv_halo = (halothreshold > 0) ? (1.0f / halothreshold) : 0.0f;
302  const unsigned char key_cb = mask.u[1];
303  const unsigned char key_cr = mask.u[2];
304  unsigned char *img_bits = image->bits();
305  const int img_bpl = image->bytesPerLine();
306 
307  #pragma omp parallel for schedule(static)
308  for (int y = 0; y < height; ++y)
309  {
310  unsigned char *pixel = img_bits + (y * img_bpl);
311  const unsigned char *pc_row = pixelbuf.data() + (y * rowwidth);
312 
313  for (int x = 0; x < width; ++x, pixel += 4)
314  {
315  const unsigned char *pc_px = pc_row + (x * 3);
316  int db = static_cast<int>(pc_px[1]) - key_cb;
317  int dr = static_cast<int>(pc_px[2]) - key_cr;
318  int dist_sq = db * db + dr * dr;
319 
320  if (dist_sq <= threshold_sq)
321  {
322  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
323  }
324  else if (halothreshold > 0 && dist_sq <= halo_upper_sq)
325  {
326  float tmp = sqrt_lut[dist_sq];
327  float alphamult = (tmp - threshold) * inv_halo;
328 
329  pixel[0] *= alphamult;
330  pixel[1] *= alphamult;
331  pixel[2] *= alphamult;
332  pixel[3] *= alphamult;
333  }
334  }
335  }
336  }
337  break;
338 
339  case CHROMAKEY_CIE_LCH_L:
340  for (int y = 0; y < height; ++y)
341  {
342  unsigned char *pixel = image->scanLine(y);
343 
344  for (int x = 0; x < width; ++x, pixel += 4, pf += 3)
345  {
346  float tmp = fabs(pf[0] - mask.f[0]);
347 
348  if (tmp <= threshold)
349  {
350  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
351  }
352  else if (tmp <= threshold + halothreshold)
353  {
354  float alphamult = (tmp - threshold) / halothreshold;
355 
356  pixel[0] *= alphamult;
357  pixel[1] *= alphamult;
358  pixel[2] *= alphamult;
359  pixel[3] *= alphamult;
360  }
361  }
362  }
363  break;
364 
365  case CHROMAKEY_CIE_LCH_C:
366  for (int y = 0; y < height; ++y)
367  {
368  unsigned char *pixel = image->scanLine(y);
369 
370  for (int x = 0; x < width; ++x, pixel += 4, pf += 3)
371  {
372  float tmp = fabs(pf[1] - mask.f[1]);
373 
374  if (tmp <= threshold)
375  {
376  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
377  }
378  else if (tmp <= threshold + halothreshold)
379  {
380  float alphamult = (tmp - threshold) / halothreshold;
381 
382  pixel[0] *= alphamult;
383  pixel[1] *= alphamult;
384  pixel[2] *= alphamult;
385  pixel[3] *= alphamult;
386  }
387  }
388  }
389  break;
390 
391  case CHROMAKEY_CIE_LCH_H:
392  for (int y = 0; y < height; ++y)
393  {
394  unsigned char *pixel = image->scanLine(y);
395 
396  for (int x = 0; x < width; ++x, pixel += 4, pf += 3)
397  {
398  // Hues in LCH(ab) are an angle on a color wheel.
399  // We are tring to find the angular distance
400  // between the two angles. It can never be more
401  // than 180 degrees - if it is, there is a closer
402  // angle that can be calculated by going in the
403  // other diretion, which can be found by
404  // subtracting the angle we have from 360.
405  float tmp = fabs(pf[2] - mask.f[2]);
406 
407  if (tmp > 180.0)
408  tmp = 360.0 - tmp;
409  if (tmp <= threshold)
410  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
411  }
412  }
413  break;
414 
416  {
417  float KL = 1.0;
418  float KC = 1.0;
419  float KH = 1.0;
420  float pi = 4 * std::atan(1);
421 
422  float L1 = ((float) mask.u[0]) / 2.55;
423  float a1 = mask.u[1] - 127;
424  float b1 = mask.u[2] - 127;
425  float C1 = std::sqrt(a1 * a1 + b1 * b1);
426 
427  for (int y = 0; y < height; ++y)
428  {
429  unsigned char *pixel = image->scanLine(y);
430 
431  for (int x = 0; x < width; ++x, pixel += 4, pc += 3)
432  {
433  float L2 = ((float) pc[0]) / 2.55;
434  int a2 = pc[1] - 127;
435  int b2 = pc[2] - 127;
436  float C2 = std::sqrt(a2 * a2 + b2 * b2);
437 
438  float delta_L_prime = L2 - L1;
439  float L_bar = (L1 + L2) / 2;
440  float C_bar = (C1 + C2) / 2;
441 
442  float a_prime_multiplier = 1 + 0.5 * (1 - std::sqrt(C_bar / (C_bar + 25)));
443  float a1_prime = a1 * a_prime_multiplier;
444  float a2_prime = a2 * a_prime_multiplier;
445 
446  float C1_prime = std::sqrt(a1_prime * a1_prime + b1 * b1);
447  float C2_prime = std::sqrt(a2_prime * a2_prime + b2 * b2);
448  float C_prime_bar = (C1_prime + C2_prime) / 2;
449  float delta_C_prime = C2_prime - C1_prime;
450 
451  float h1_prime = std::atan2(b1, a1_prime) * 180 / pi;
452  float h2_prime = std::atan2(b2, a2_prime) * 180 / pi;
453 
454  float delta_h_prime = h2_prime - h1_prime;
455  double H_prime_bar = (C1_prime != 0 && C2_prime != 0) ? (h1_prime + h2_prime) / 2 : (h1_prime + h2_prime);
456 
457  if (delta_h_prime < -180)
458  {
459  delta_h_prime += 360;
460  if (H_prime_bar < 180)
461  H_prime_bar += 180;
462  else
463  H_prime_bar -= 180;
464  }
465  else if (delta_h_prime > 180)
466  {
467  delta_h_prime -= 360;
468  if (H_prime_bar < 180)
469  H_prime_bar += 180;
470  else
471  H_prime_bar -= 180;
472  }
473 
474  float delta_H_prime = 2 * std::sqrt(C1_prime * C2_prime) * std::sin(delta_h_prime * pi / 360);
475 
476  float T = 1
477  - 0.17 * std::cos((H_prime_bar - 30) * pi / 180)
478  + 0.24 * std::cos(H_prime_bar * pi / 90)
479  + 0.32 * std::cos((3 * H_prime_bar + 6) * pi / 180)
480  - 0.20 * std::cos((4 * H_prime_bar - 64) * pi / 180);
481 
482  float SL = 1 + 0.015 * std::pow(L_bar - 50, 2) / std::sqrt(20 + std::pow(L_bar - 50, 2));
483  float SC = 1 + 0.045 * C_prime_bar;
484  float SH = 1 + 0.015 * C_prime_bar * T;
485  float RT = -2 * std::sqrt(C_prime_bar / (C_prime_bar + 25)) * std::sin(pi / 3 * std::exp(-std::pow((H_prime_bar - 275) / 25, 2)));
486  float delta_E = std::sqrt(std::pow(delta_L_prime / KL / SL, 2)
487  + std::pow(delta_C_prime / KC / SC, 2)
488  + std::pow(delta_h_prime / KH / SH, 2)
489  + RT * delta_C_prime / KC / SC * delta_H_prime / KH / SH);
490  if (delta_E <= threshold)
491  {
492  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
493  }
494  else if (delta_E <= threshold + halothreshold)
495  {
496  float alphamult = (delta_E - threshold) / halothreshold;
497 
498  pixel[0] *= alphamult;
499  pixel[1] *= alphamult;
500  pixel[2] *= alphamult;
501  pixel[3] *= alphamult;
502  }
503  }
504  }
505  }
506  break;
507  }
508 
509  return frame;
510  }
511  }
512 #endif
513 
514  // Metric upper bound for Color::GetDistance internal term is below 589825.
515  static const std::array<uint16_t, 589826> dist_lut = [] {
516  std::array<uint16_t, 589826> lut{};
517  for (int i = 0; i <= 589825; ++i)
518  lut[i] = static_cast<uint16_t>(std::sqrt(static_cast<float>(i)));
519  return lut;
520  }();
521  static const std::array<float, 256> inv_alpha = [] {
522  std::array<float, 256> lut{};
523  lut[0] = 0.0f;
524  for (int i = 1; i < 256; ++i)
525  lut[i] = 255.0f / static_cast<float>(i);
526  return lut;
527  }();
528 
529  if (method == CHROMAKEY_BASIC) {
530  // Legacy BASIC behavior (hard threshold, no halo), optimized with OpenMP.
531  unsigned char *img_bits = image->bits();
532  const int img_bpl = image->bytesPerLine();
533 
534  #pragma omp parallel for schedule(static)
535  for (int y = 0; y < height; ++y)
536  {
537  unsigned char * pixel = img_bits + (y * img_bpl);
538  for (int x = 0; x < width; ++x, pixel += 4)
539  {
540  const int A = pixel[3];
541  if (A <= 0)
542  continue;
543 
544  // Preserve legacy 8-bit conversion behavior by casting to unsigned char.
545  unsigned char R = 0;
546  unsigned char G = 0;
547  unsigned char B = 0;
548  if (A == 255) {
549  R = pixel[0];
550  G = pixel[1];
551  B = pixel[2];
552  } else {
553  const float inv_a = inv_alpha[A];
554  R = static_cast<unsigned char>(pixel[0] * inv_a);
555  G = static_cast<unsigned char>(pixel[1] * inv_a);
556  B = static_cast<unsigned char>(pixel[2] * inv_a);
557  }
558 
559  const int rmean = (static_cast<int>(R) + static_cast<int>(mask_R)) / 2;
560  const int r = static_cast<int>(R) - static_cast<int>(mask_R);
561  const int g = static_cast<int>(G) - static_cast<int>(mask_G);
562  const int b = static_cast<int>(B) - static_cast<int>(mask_B);
563  const int dist_sq = (((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8);
564  const int distance = dist_lut[dist_sq];
565 
566  if (distance <= threshold)
567  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
568  }
569  }
570  } else {
571  // BASIC_SOFT: same metric, optional halo feathering, optimized + OpenMP.
572  static const std::array<float, 589826> dist_f_lut = [] {
573  std::array<float, 589826> lut{};
574  for (int i = 0; i <= 589825; ++i)
575  lut[i] = std::sqrt(static_cast<float>(i));
576  return lut;
577  }();
578  const int halo_upper = threshold + halothreshold;
579  const int halo_upper_sq = halo_upper * halo_upper;
580  const float inv_halo = (halothreshold > 0) ? (1.0f / halothreshold) : 0.0f;
581  unsigned char *img_bits = image->bits();
582  const int img_bpl = image->bytesPerLine();
583 
584  #pragma omp parallel for schedule(static)
585  for (int y = 0; y < height; ++y)
586  {
587  unsigned char * pixel = img_bits + (y * img_bpl);
588  for (int x = 0; x < width; ++x, pixel += 4)
589  {
590  const int A = pixel[3];
591  if (A <= 0)
592  continue;
593 
594  int R = 0;
595  int G = 0;
596  int B = 0;
597  if (A == 255) {
598  R = pixel[0];
599  G = pixel[1];
600  B = pixel[2];
601  } else {
602  const float inv_a = inv_alpha[A];
603  R = std::clamp(static_cast<int>(pixel[0] * inv_a), 0, 255);
604  G = std::clamp(static_cast<int>(pixel[1] * inv_a), 0, 255);
605  B = std::clamp(static_cast<int>(pixel[2] * inv_a), 0, 255);
606  }
607 
608  const int rmean = (R + static_cast<int>(mask_R)) / 2;
609  const int r = R - static_cast<int>(mask_R);
610  const int g = G - static_cast<int>(mask_G);
611  const int b = B - static_cast<int>(mask_B);
612  const int dist_sq = (((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8);
613 
614  const int distance = dist_lut[dist_sq];
615  if (distance <= threshold) {
616  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
617  }
618  else if (halothreshold > 0 && dist_sq <= halo_upper_sq) {
619  const float distance_f = dist_f_lut[dist_sq];
620  const float alphamult = (distance_f - threshold) * inv_halo;
621  pixel[0] = static_cast<unsigned char>(pixel[0] * alphamult);
622  pixel[1] = static_cast<unsigned char>(pixel[1] * alphamult);
623  pixel[2] = static_cast<unsigned char>(pixel[2] * alphamult);
624  pixel[3] = static_cast<unsigned char>(pixel[3] * alphamult);
625  }
626  }
627  }
628  }
629 
630  // return the modified frame
631  return frame;
632 }
633 
634 // Generate JSON string of this object
635 std::string ChromaKey::Json() const {
636 
637  // Return formatted string
638  return JsonValue().toStyledString();
639 }
640 
641 // Generate Json::Value for this object
642 Json::Value ChromaKey::JsonValue() const {
643 
644  // Create root json object
645  Json::Value root = EffectBase::JsonValue(); // get parent properties
646  root["type"] = info.class_name;
647  root["color"] = color.JsonValue();
648  root["fuzz"] = fuzz.JsonValue();
649  root["halo"] = halo.JsonValue();
650  root["keymethod"] = method;
651 
652  // return JsonValue
653  return root;
654 }
655 
656 // Load JSON string into this object
657 void ChromaKey::SetJson(const std::string value) {
658 
659  // Parse JSON string into JSON objects
660  try
661  {
662  const Json::Value root = openshot::stringToJson(value);
663  // Set all values that match
664  SetJsonValue(root);
665  }
666  catch (const std::exception& e)
667  {
668  // Error parsing JSON (or missing keys)
669  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
670  }
671 }
672 
673 // Load Json::Value into this object
674 void ChromaKey::SetJsonValue(const Json::Value root) {
675 
676  // Set parent data
678 
679  // Set data from Json (if key is found)
680  if (!root["color"].isNull())
681  color.SetJsonValue(root["color"]);
682  if (!root["fuzz"].isNull())
683  fuzz.SetJsonValue(root["fuzz"]);
684  if (!root["halo"].isNull())
685  halo.SetJsonValue(root["halo"]);
686  if (!root["keymethod"].isNull())
687  method = (ChromaKeyMethod) root["keymethod"].asInt();
688 }
689 
690 // Get all properties for a specific frame
691 std::string ChromaKey::PropertiesJSON(int64_t requested_frame) const {
692 
693  // Generate JSON properties list
694  Json::Value root = BasePropertiesJSON(requested_frame);
695 
696  // Keyframes
697  root["color"] = add_property_json("Key Color", 0.0, "color", "", &color.red, 0, 255, false, requested_frame);
698  root["color"]["red"] = add_property_json("Red", color.red.GetValue(requested_frame), "float", "", &color.red, 0, 255, false, requested_frame);
699  root["color"]["blue"] = add_property_json("Blue", color.blue.GetValue(requested_frame), "float", "", &color.blue, 0, 255, false, requested_frame);
700  root["color"]["green"] = add_property_json("Green", color.green.GetValue(requested_frame), "float", "", &color.green, 0, 255, false, requested_frame);
701  root["fuzz"] = add_property_json("Threshold", fuzz.GetValue(requested_frame), "float", "", &fuzz, 0, 125, false, requested_frame);
702  root["halo"] = add_property_json("Halo", halo.GetValue(requested_frame), "float", "", &halo, 0, 125, false, requested_frame);
703  root["keymethod"] = add_property_json("Key Method", method, "int", "", NULL, 0, CHROMAKEY_LAST_METHOD, false, requested_frame);
704  root["keymethod"]["choices"].append(add_property_choice_json("Basic keying", 0, method));
705  root["keymethod"]["choices"].append(add_property_choice_json("Basic keying (soft)", CHROMAKEY_BASIC_SOFT, method));
706  root["keymethod"]["choices"].append(add_property_choice_json("HSV/HSL hue", 1, method));
707  root["keymethod"]["choices"].append(add_property_choice_json("HSV saturation", 2, method));
708  root["keymethod"]["choices"].append(add_property_choice_json("HSL saturation", 3, method));
709  root["keymethod"]["choices"].append(add_property_choice_json("HSV value", 4, method));
710  root["keymethod"]["choices"].append(add_property_choice_json("HSL luminance", 5, method));
711  root["keymethod"]["choices"].append(add_property_choice_json("LCH luminosity", 6, method));
712  root["keymethod"]["choices"].append(add_property_choice_json("LCH chroma", 7, method));
713  root["keymethod"]["choices"].append(add_property_choice_json("LCH hue", 8, method));
714  root["keymethod"]["choices"].append(add_property_choice_json("CIE Distance", 9, method));
715  root["keymethod"]["choices"].append(add_property_choice_json("Cb,Cr vector", 10, method));
716 
717  // Return formatted string
718  return root.toStyledString();
719 }
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::CHROMAKEY_BASIC_SOFT
@ CHROMAKEY_BASIC_SOFT
BASIC metric + optional halo feathering.
Definition: Enums.h:171
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:110
openshot::ChromaKey::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: ChromaKey.cpp:674
openshot::CHROMAKEY_HSL_S
@ CHROMAKEY_HSL_S
Difference between HSL saturations.
Definition: Enums.h:163
openshot::CHROMAKEY_BASIC
@ CHROMAKEY_BASIC
Length of difference between RGB vectors.
Definition: Enums.h:160
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
openshot::ClipBase::add_property_choice_json
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
Definition: ClipBase.cpp:132
openshot::EffectBase::JsonValue
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:96
openshot::CHROMAKEY_HSV_V
@ CHROMAKEY_HSV_V
Difference between HSV values.
Definition: Enums.h:164
openshot::ChromaKeyMethod
ChromaKeyMethod
This enumeration determines the algorithm used by the ChromaKey filter.
Definition: Enums.h:158
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::CHROMAKEY_CIE_LCH_H
@ CHROMAKEY_CIE_LCH_H
Difference between CIE LCH(ab) hues.
Definition: Enums.h:168
ChromaKey.h
Header file for ChromaKey class.
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
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:236
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::CHROMAKEY_HSV_S
@ CHROMAKEY_HSV_S
Difference between HSV saturations.
Definition: Enums.h:162
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::CHROMAKEY_YCBCR
@ CHROMAKEY_YCBCR
YCbCr vector difference of CbCr.
Definition: Enums.h:170
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:37
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:44
openshot::ChromaKey::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: ChromaKey.cpp:642
openshot::CHROMAKEY_CIE_LCH_L
@ CHROMAKEY_CIE_LCH_L
Difference between CIE LCH(ab) luminousities.
Definition: Enums.h:166
openshot::CHROMAKEY_HSVL_H
@ CHROMAKEY_HSVL_H
Difference between HSV/HSL hues.
Definition: Enums.h:161
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
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::CHROMAKEY_CIE_DISTANCE
@ CHROMAKEY_CIE_DISTANCE
CIEDE2000 perceptual difference.
Definition: Enums.h:169
openshot::EffectInfoStruct::description
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41
openshot::ChromaKey::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: ChromaKey.cpp:657
openshot::EffectInfoStruct::has_video
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:40
openshot::CHROMAKEY_HSL_L
@ CHROMAKEY_HSL_L
Difference between HSL luminances.
Definition: Enums.h:165
openshot::Color::red
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition: Color.h:30
openshot::ChromaKey::Json
std::string Json() const override
Generate JSON string of this object.
Definition: ChromaKey.cpp:635
openshot::ChromaKey::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: ChromaKey.h:88
openshot::CHROMAKEY_CIE_LCH_C
@ CHROMAKEY_CIE_LCH_C
Difference between CIE LCH(ab) chromas.
Definition: Enums.h:167
openshot::Color::blue
openshot::Keyframe blue
Curve representing the red value (0 - 255)
Definition: Color.h:32
openshot::ChromaKey::ChromaKey
ChromaKey()
Blank constructor, useful when using Json to load the effect properties.
Definition: ChromaKey.cpp:27
Exceptions.h
Header file for all Exception classes.
openshot::ChromaKey::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: ChromaKey.cpp:691
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:139
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258
openshot::CHROMAKEY_LAST_METHOD
@ CHROMAKEY_LAST_METHOD
Definition: Enums.h:172