24 init_effect_details();
29 saturation(saturation), saturation_R(saturation_R), saturation_G(saturation_G), saturation_B(saturation_B),
33 init_effect_details();
42 std::shared_ptr<QImage> mask_image, int64_t frame_number)
const {
44 if (!original_image || !effected_image || !mask_image)
46 if (original_image->size() != effected_image->size() || effected_image->size() != mask_image->size())
49 unsigned char* original_pixels =
reinterpret_cast<unsigned char*
>(original_image->bits());
50 unsigned char* effected_pixels =
reinterpret_cast<unsigned char*
>(effected_image->bits());
51 unsigned char* mask_pixels =
reinterpret_cast<unsigned char*
>(mask_image->bits());
52 const int pixel_count = effected_image->width() * effected_image->height();
55 #pragma omp parallel for schedule(static)
56 for (
int i = 0; i < pixel_count; ++i) {
57 const int idx = i * 4;
58 float factor =
static_cast<float>(qGray(mask_pixels[idx], mask_pixels[idx + 1], mask_pixels[idx + 2])) / 255.0f;
59 factor = 1.0f - factor;
61 factor = factor * factor;
62 const float inverse = 1.0f - factor;
65 effected_pixels[idx] =
static_cast<unsigned char>(
66 (original_pixels[idx] * inverse) + (effected_pixels[idx] * factor));
67 effected_pixels[idx + 1] =
static_cast<unsigned char>(
68 (original_pixels[idx + 1] * inverse) + (effected_pixels[idx + 1] * factor));
69 effected_pixels[idx + 2] =
static_cast<unsigned char>(
70 (original_pixels[idx + 2] * inverse) + (effected_pixels[idx + 2] * factor));
71 effected_pixels[idx + 3] = original_pixels[idx + 3];
74 #pragma omp parallel for schedule(static)
75 for (
int i = 0; i < pixel_count; ++i) {
76 const int idx = i * 4;
77 float factor =
static_cast<float>(qGray(mask_pixels[idx], mask_pixels[idx + 1], mask_pixels[idx + 2])) / 255.0f;
79 factor = factor * factor;
80 const float inverse = 1.0f - factor;
83 effected_pixels[idx] =
static_cast<unsigned char>(
84 (original_pixels[idx] * inverse) + (effected_pixels[idx] * factor));
85 effected_pixels[idx + 1] =
static_cast<unsigned char>(
86 (original_pixels[idx + 1] * inverse) + (effected_pixels[idx + 1] * factor));
87 effected_pixels[idx + 2] =
static_cast<unsigned char>(
88 (original_pixels[idx + 2] * inverse) + (effected_pixels[idx + 2] * factor));
89 effected_pixels[idx + 3] = original_pixels[idx + 3];
95 void Saturation::init_effect_details()
110 std::shared_ptr<openshot::Frame>
Saturation::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
113 std::shared_ptr<QImage> frame_image = frame->GetImage();
118 const int pixel_count = frame_image->width() * frame_image->height();
127 const float pR = 0.299f;
128 const float pG = 0.587f;
129 const float pB = 0.114f;
130 const float sqrt_pR = std::sqrt(pR);
131 const float sqrt_pG = std::sqrt(pG);
132 const float sqrt_pB = std::sqrt(pB);
135 static const std::array<float, 65026> sqrt_lut = [] {
136 std::array<float, 65026> lut{};
137 for (
int i = 0; i <= 65025; ++i)
138 lut[i] = std::sqrt(
static_cast<float>(i));
143 unsigned char *pixels =
reinterpret_cast<unsigned char *
>(frame_image->bits());
145 static const std::array<float, 256> inv_alpha = [] {
146 std::array<float, 256> lut{};
148 for (
int i = 1; i < 256; ++i)
149 lut[i] = 255.0f /
static_cast<float>(i);
152 const auto clamp_i = [](
int value) ->
int {
153 if (value < 0)
return 0;
154 if (value > 255)
return 255;
158 const auto apply_saturation = [&](
int &R,
int &G,
int &B) {
161 const int weighted_sq = (77 * R * R + 150 * G * G + 29 * B * B + 128) >> 8;
162 const float p = sqrt_lut[weighted_sq];
165 R = clamp_i(
static_cast<int>(p + (R - p) * saturation_value));
166 G = clamp_i(
static_cast<int>(p + (G - p) * saturation_value));
167 B = clamp_i(
static_cast<int>(p + (B - p) * saturation_value));
170 const float p_r = R * sqrt_pR;
171 const float p_g = G * sqrt_pG;
172 const float p_b = B * sqrt_pB;
175 const int Rr =
static_cast<int>(p_r + (R - p_r) * saturation_value_R);
176 const int Gr =
static_cast<int>(p_r - p_r * saturation_value_R);
177 const int Br =
static_cast<int>(p_r - p_r * saturation_value_R);
179 const int Rg =
static_cast<int>(p_g - p_g * saturation_value_G);
180 const int Gg =
static_cast<int>(p_g + (G - p_g) * saturation_value_G);
181 const int Bg =
static_cast<int>(p_g - p_g * saturation_value_G);
183 const int Rb =
static_cast<int>(p_b - p_b * saturation_value_B);
184 const int Gb =
static_cast<int>(p_b - p_b * saturation_value_B);
185 const int Bb =
static_cast<int>(p_b + (B - p_b) * saturation_value_B);
188 R = clamp_i(Rr + Rg + Rb);
189 G = clamp_i(Gr + Gg + Gb);
190 B = clamp_i(Br + Bg + Bb);
193 #pragma omp parallel for if(pixel_count >= 16384) schedule(static) shared (pixels)
194 for (
int pixel = 0; pixel < pixel_count; ++pixel)
196 const int idx = pixel * 4;
199 const int A = pixels[idx + 3];
209 apply_saturation(R, G, B);
210 pixels[idx + 0] =
static_cast<unsigned char>(R);
211 pixels[idx + 1] =
static_cast<unsigned char>(G);
212 pixels[idx + 2] =
static_cast<unsigned char>(B);
214 const float alpha_percent =
static_cast<float>(A) * (1.0f / 255.0f);
215 const float inv_alpha_percent = inv_alpha[A];
218 R =
static_cast<int>(pixels[idx + 0] * inv_alpha_percent);
219 G =
static_cast<int>(pixels[idx + 1] * inv_alpha_percent);
220 B =
static_cast<int>(pixels[idx + 2] * inv_alpha_percent);
221 apply_saturation(R, G, B);
224 pixels[idx + 0] =
static_cast<unsigned char>(R * alpha_percent);
225 pixels[idx + 1] =
static_cast<unsigned char>(G * alpha_percent);
226 pixels[idx + 2] =
static_cast<unsigned char>(B * alpha_percent);
267 catch (
const std::exception& e)
270 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
281 if (!root[
"saturation"].isNull())
283 if (!root[
"saturation_R"].isNull())
285 if (!root[
"saturation_G"].isNull())
287 if (!root[
"saturation_B"].isNull())
289 if (!root[
"mask_mode"].isNull())
309 return root.toStyledString();