Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Color-separated Saturation #368

Merged
merged 11 commits into from
Oct 16, 2020
14 changes: 10 additions & 4 deletions include/effects/Saturation.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,21 @@ namespace openshot
void init_effect_details();

public:
Keyframe saturation; ///< The color saturation: 0.0 = black and white, 1.0 = normal, 2.0 = double saturation
Keyframe saturation; ///< Overall color saturation: 0.0 = greyscale, 1.0 = normal, 2.0 = double saturation
Keyframe saturation_R; ///< Red color saturation
Keyframe saturation_G; ///< Green color saturation
Keyframe saturation_B; ///< Blue color saturation

/// Blank constructor, useful when using Json to load the effect properties
Saturation();

/// Default constructor, which takes 1 curve, to adjust the color saturation over time.
/// Default constructor, which takes four curves (one common curve and one curve per color), to adjust the color saturation over time.
///
/// @param new_saturation The curve to adjust the saturation of the frame's image (0.0 = black and white, 1.0 = normal, 2.0 = double saturation)
Saturation(Keyframe new_saturation);
/// @param saturation The curve to adjust the saturation of the frame's image (0.0 = greyscale, 1.0 = normal, 2.0 = double saturation)
/// @param saturation_R The curve to adjust red saturation of the frame's image (0.0 = greyscale, 1.0 = normal, 2.0 = double saturation)
/// @param saturation_G The curve to adjust green saturation of the frame's image (0.0 = greyscale, 1.0 = normal, 2.0 = double saturation)
/// @param saturation_B The curve to adjust blue saturation of the frame's image (0.0 = greyscale, 1.0 = normal, 2.0 = double saturation)
Saturation(Keyframe saturation, Keyframe saturation_R, Keyframe saturation_G, Keyframe saturation_B);

/// @brief This method is required for all derived classes of EffectBase, and returns a
/// modified openshot::Frame object
Expand Down
85 changes: 76 additions & 9 deletions src/effects/Saturation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@
using namespace openshot;

/// Blank constructor, useful when using Json to load the effect properties
Saturation::Saturation() : saturation(1.0) {
Saturation::Saturation() : saturation(1.0), saturation_R(1.0), saturation_G(1.0), saturation_B(1.0) {
// Init effect properties
init_effect_details();
}

// Default constructor
Saturation::Saturation(Keyframe new_saturation) : saturation(new_saturation)
Saturation::Saturation(Keyframe saturation, Keyframe saturation_R, Keyframe saturation_G, Keyframe saturation_B) :
saturation(saturation), saturation_R(saturation_R), saturation_G(saturation_G), saturation_B(saturation_B)
{
// Init effect properties
init_effect_details();
Expand Down Expand Up @@ -73,6 +74,9 @@ std::shared_ptr<Frame> Saturation::GetFrame(std::shared_ptr<Frame> frame, int64_

// Get keyframe values for this frame
float saturation_value = saturation.GetValue(frame_number);
float saturation_value_R = saturation_R.GetValue(frame_number);
float saturation_value_G = saturation_G.GetValue(frame_number);
float saturation_value_B = saturation_B.GetValue(frame_number);

// Constants used for color saturation formula
const double pR = .299;
Expand All @@ -90,15 +94,66 @@ std::shared_ptr<Frame> Saturation::GetFrame(std::shared_ptr<Frame> frame, int64_
int G = pixels[pixel * 4 + 1];
int B = pixels[pixel * 4 + 2];

/*
* Common saturation adjustment
*/

// Calculate the saturation multiplier
double p = sqrt( (R * R * pR) +
(G * G * pG) +
(B * B * pB) );

// Apply adjusted and constrained saturation
pixels[pixel * 4] = constrain(p + (R - p) * saturation_value);
pixels[pixel * 4 + 1] = constrain(p + (G - p) * saturation_value);
pixels[pixel * 4 + 2] = constrain(p + (B - p) * saturation_value);
(G * G * pG) +
(B * B * pB) );

// Adjust the saturation
R = p + (R - p) * saturation_value;
G = p + (G - p) * saturation_value;
B = p + (B - p) * saturation_value;

// Constrain the value from 0 to 255
R = constrain(R);
G = constrain(G);
B = constrain(B);

/*
* Color-separated saturation adjustment
*
* Splitting each of the three subpixels (R, G and B) into three distincs sub-subpixels (R, G and B in turn)
* which in their optical sum reproduce the original subpixel's color OR produce white light in the brightness
* of the original subpixel (dependening on the color channel's slider value).
ferdnyc marked this conversation as resolved.
Show resolved Hide resolved
*/

// Compute the brightness ("saturation multiplier") of the replaced subpixels
// Actually mathematical no-ops mostly, verbosity is kept just for clarification
const double p_r = sqrt(R * R * pR);
const double p_g = sqrt(G * G * pG);
const double p_b = sqrt(B * B * pB);

// Adjust the saturation
const int Rr = p_r + (R - p_r) * saturation_value_R;
const int Gr = p_r + (0 - p_r) * saturation_value_R;
const int Br = p_r + (0 - p_r) * saturation_value_R;

const int Rg = p_g + (0 - p_g) * saturation_value_G;
const int Gg = p_g + (G - p_g) * saturation_value_G;
const int Bg = p_g + (0 - p_g) * saturation_value_G;

const int Rb = p_b + (0 - p_b) * saturation_value_B;
const int Gb = p_b + (0 - p_b) * saturation_value_B;
const int Bb = p_b + (B - p_b) * saturation_value_B;

// Recombine brightness of sub-subpixels (Rx, Gx and Bx) into sub-pixels (R, G and B) again
R = Rr + Rg + Rb;
G = Gr + Gg + Gb;
B = Br + Bg + Bb;

// Constrain the value from 0 to 255
R = constrain(R);
G = constrain(G);
B = constrain(B);

// Set all pixels to new value
pixels[pixel * 4] = R;
pixels[pixel * 4 + 1] = G;
pixels[pixel * 4 + 2] = B;
}

// return the modified frame
Expand All @@ -119,6 +174,9 @@ Json::Value Saturation::JsonValue() const {
Json::Value root = EffectBase::JsonValue(); // get parent properties
root["type"] = info.class_name;
root["saturation"] = saturation.JsonValue();
root["saturation_R"] = saturation_R.JsonValue();
root["saturation_G"] = saturation_G.JsonValue();
root["saturation_B"] = saturation_B.JsonValue();

// return JsonValue
return root;
Expand Down Expand Up @@ -150,6 +208,12 @@ void Saturation::SetJsonValue(const Json::Value root) {
// Set data from Json (if key is found)
if (!root["saturation"].isNull())
saturation.SetJsonValue(root["saturation"]);
if (!root["saturation_R"].isNull())
saturation_R.SetJsonValue(root["saturation_R"]);
if (!root["saturation_G"].isNull())
saturation_G.SetJsonValue(root["saturation_G"]);
if (!root["saturation_B"].isNull())
saturation_B.SetJsonValue(root["saturation_B"]);
}

// Get all properties for a specific frame
Expand All @@ -166,6 +230,9 @@ std::string Saturation::PropertiesJSON(int64_t requested_frame) const {

// Keyframes
root["saturation"] = add_property_json("Saturation", saturation.GetValue(requested_frame), "float", "", &saturation, 0.0, 4.0, false, requested_frame);
root["saturation_R"] = add_property_json("Saturation (Red)", saturation_R.GetValue(requested_frame), "float", "", &saturation_R, 0.0, 4.0, false, requested_frame);
root["saturation_G"] = add_property_json("Saturation (Green)", saturation_G.GetValue(requested_frame), "float", "", &saturation_G, 0.0, 4.0, false, requested_frame);
root["saturation_B"] = add_property_json("Saturation (Blue)", saturation_B.GetValue(requested_frame), "float", "", &saturation_B, 0.0, 4.0, false, requested_frame);

// Return formatted string
return root.toStyledString();
Expand Down