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

Implement blend modes (#320) #791

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 112 additions & 1 deletion src/Clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ void Clip::init_settings()
anchor = ANCHOR_CANVAS;
display = FRAME_DISPLAY_NONE;
mixing = VOLUME_MIX_NONE;
blend = BLEND_SOURCE_OVER;
waveform = false;
previous_properties = "";
parentObjectId = "";
Expand Down Expand Up @@ -880,6 +881,7 @@ std::string Clip::PropertiesJSON(int64_t requested_frame) const {

// Keyframes
root["alpha"] = add_property_json("Alpha", alpha.GetValue(requested_frame), "float", "", &alpha, 0.0, 1.0, false, requested_frame);
root["blend"] = add_property_json("Blend Mode", blend.GetValue(requested_frame), "int", "", &blend, 0, 23, false, requested_frame);
root["origin_x"] = add_property_json("Origin X", origin_x.GetValue(requested_frame), "float", "", &origin_x, 0.0, 1.0, false, requested_frame);
root["origin_y"] = add_property_json("Origin Y", origin_y.GetValue(requested_frame), "float", "", &origin_y, 0.0, 1.0, false, requested_frame);
root["volume"] = add_property_json("Volume", volume.GetValue(requested_frame), "float", "", &volume, 0.0, 1.0, false, requested_frame);
Expand All @@ -889,6 +891,32 @@ std::string Clip::PropertiesJSON(int64_t requested_frame) const {
root["has_audio"] = add_property_json("Enable Audio", has_audio.GetValue(requested_frame), "int", "", &has_audio, -1, 1.0, false, requested_frame);
root["has_video"] = add_property_json("Enable Video", has_video.GetValue(requested_frame), "int", "", &has_video, -1, 1.0, false, requested_frame);

// Add video blend choices (dropdown style)
root["blend"]["choices"].append(add_property_choice_json("Source Over", BLEND_SOURCE_OVER, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Destination Over", BLEND_DESTINATION_OVER, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Clear", BLEND_CLEAR, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Source", BLEND_SOURCE, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Destination", BLEND_DESTINATION, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Source In", BLEND_SOURCE_IN, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Destination In", BLEND_DESTINATION_IN, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Source Out", BLEND_SOURCE_OUT, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Destination Out", BLEND_DESTINATION_OUT, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Source Atop", BLEND_SOURCE_ATOP, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Destination Atop", BLEND_DESTINATION_ATOP, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Xor", BLEND_XOR, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Plus", BLEND_PLUS, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Multiply", BLEND_MULTIPLY, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Screen", BLEND_SCREEN, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Overlay", BLEND_OVERLAY, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Darken", BLEND_DARKEN, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Lighten", BLEND_LIGHTEN, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Color Dodge", BLEND_COLOR_DODGE, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Color Burn", BLEND_COLOR_BURN, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Hard Light", BLEND_HARD_LIGHT, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Soft Light", BLEND_SOFT_LIGHT, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Difference", BLEND_DIFFERENCE, blend.GetValue(requested_frame)));
root["blend"]["choices"].append(add_property_choice_json("Exclusion", BLEND_EXCLUSION, blend.GetValue(requested_frame)));

// Add enable audio/video choices (dropdown style)
root["has_audio"]["choices"].append(add_property_choice_json("Auto", -1, has_audio.GetValue(requested_frame)));
root["has_audio"]["choices"].append(add_property_choice_json("Off", 0, has_audio.GetValue(requested_frame)));
Expand Down Expand Up @@ -924,6 +952,7 @@ Json::Value Clip::JsonValue() const {
root["location_x"] = location_x.JsonValue();
root["location_y"] = location_y.JsonValue();
root["alpha"] = alpha.JsonValue();
root["blend"] = blend.JsonValue();
root["rotation"] = rotation.JsonValue();
root["time"] = time.JsonValue();
root["volume"] = volume.JsonValue();
Expand Down Expand Up @@ -1018,6 +1047,8 @@ void Clip::SetJsonValue(const Json::Value root) {
location_y.SetJsonValue(root["location_y"]);
if (!root["alpha"].isNull())
alpha.SetJsonValue(root["alpha"]);
if (!root["blend"].isNull())
blend.SetJsonValue(root["blend"]);
if (!root["rotation"].isNull())
rotation.SetJsonValue(root["rotation"]);
if (!root["time"].isNull())
Expand Down Expand Up @@ -1286,7 +1317,7 @@ void Clip::apply_keyframes(std::shared_ptr<Frame> frame, std::shared_ptr<QImage>
painter.setTransform(transform);

// Composite a new layer onto the image
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.setCompositionMode(blendToQPainter((openshot::BlendMode) blend.GetValue(frame->number)));
painter.drawImage(0, 0, *source_image);

if (timeline) {
Expand Down Expand Up @@ -1582,3 +1613,83 @@ QTransform Clip::get_transform(std::shared_ptr<Frame> frame, int width, int heig

return transform;
}

// Transform a BlendMode to the corresponding QPainter CompositionMode
QPainter::CompositionMode Clip::blendToQPainter(openshot::BlendMode blend) {
QPainter::CompositionMode painter_mode;
switch(blend) {
case BLEND_SOURCE_OVER:
painter_mode = QPainter::CompositionMode_SourceOver;
break;
case BLEND_DESTINATION_OVER:
painter_mode = QPainter::CompositionMode_DestinationOver;
break;
case BLEND_CLEAR:
painter_mode = QPainter::CompositionMode_Clear;
break;
case BLEND_SOURCE:
painter_mode = QPainter::CompositionMode_Source;
break;
case BLEND_DESTINATION:
painter_mode = QPainter::CompositionMode_Destination;
break;
case BLEND_SOURCE_IN:
painter_mode = QPainter::CompositionMode_SourceIn;
break;
case BLEND_DESTINATION_IN:
painter_mode = QPainter::CompositionMode_DestinationIn;
break;
case BLEND_SOURCE_OUT:
painter_mode = QPainter::CompositionMode_SourceOut;
break;
case BLEND_DESTINATION_OUT:
painter_mode = QPainter::CompositionMode_DestinationOut;
break;
case BLEND_SOURCE_ATOP:
painter_mode = QPainter::CompositionMode_SourceAtop;
break;
case BLEND_DESTINATION_ATOP:
painter_mode = QPainter::CompositionMode_DestinationAtop;
break;
case BLEND_XOR:
painter_mode = QPainter::CompositionMode_Xor;
break;
case BLEND_PLUS:
painter_mode = QPainter::CompositionMode_Plus;
break;
case BLEND_MULTIPLY:
painter_mode = QPainter::CompositionMode_Multiply;
break;
case BLEND_SCREEN:
painter_mode = QPainter::CompositionMode_Screen;
break;
case BLEND_OVERLAY:
painter_mode = QPainter::CompositionMode_Overlay;
break;
case BLEND_DARKEN:
painter_mode = QPainter::CompositionMode_Darken;
break;
case BLEND_LIGHTEN:
painter_mode = QPainter::CompositionMode_Lighten;
break;
case BLEND_COLOR_DODGE:
painter_mode = QPainter::CompositionMode_ColorDodge;
break;
case BLEND_COLOR_BURN:
painter_mode = QPainter::CompositionMode_ColorBurn;
break;
case BLEND_HARD_LIGHT:
painter_mode = QPainter::CompositionMode_HardLight;
break;
case BLEND_SOFT_LIGHT:
painter_mode = QPainter::CompositionMode_SoftLight;
break;
case BLEND_DIFFERENCE:
painter_mode = QPainter::CompositionMode_Difference;
break;
case BLEND_EXCLUSION:
painter_mode = QPainter::CompositionMode_Exclusion;
break;
}
return painter_mode;
}
5 changes: 5 additions & 0 deletions src/Clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "TrackedObjectBase.h"

#include <QImage>
#include <QPainter>

namespace openshot {
class AudioResampler;
Expand Down Expand Up @@ -150,6 +151,9 @@ namespace openshot {
/// Reverse an audio buffer
void reverse_buffer(juce::AudioBuffer<float>* buffer);

/// Transform a BlendMode to the corresponding QPainter CompositionMode
QPainter::CompositionMode blendToQPainter(openshot::BlendMode blend);


public:
openshot::GravityType gravity; ///< The gravity of a clip determines where it snaps to its parent
Expand Down Expand Up @@ -288,6 +292,7 @@ namespace openshot {
openshot::Keyframe location_x; ///< Curve representing the relative X position in percent based on the gravity (-1 to 1)
openshot::Keyframe location_y; ///< Curve representing the relative Y position in percent based on the gravity (-1 to 1)
openshot::Keyframe alpha; ///< Curve representing the alpha (1 to 0)
openshot::Keyframe blend; ///< What strategy should be followed when mixing video with other clips

// Rotation and Shear curves (origin point (x,y) is adjustable for both rotation and shear)
openshot::Keyframe rotation; ///< Curve representing the rotation (0 to 360)
Expand Down
30 changes: 29 additions & 1 deletion src/Enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
#ifndef OPENSHOT_ENUMS_H
#define OPENSHOT_ENUMS_H


namespace openshot
{

Expand Down Expand Up @@ -133,6 +132,35 @@ enum ChromaKeyMethod
CHROMAKEY_LAST_METHOD = CHROMAKEY_YCBCR
};

/// This enumeration determines how clips are blended with lower layers
enum BlendMode
{
BLEND_SOURCE_OVER, ///< This is the default mode. The alpha of the current clip is used to blend the pixel on top of the lower layer.
BLEND_DESTINATION_OVER, ///< The alpha of the lower layer is used to blend it on top of the current clip pixels. This mode is the inverse of BLEND_SOURCEOVER.
BLEND_CLEAR, ///< The pixels in the lower layer are cleared (set to fully transparent) independent of the current clip.
BLEND_SOURCE, ///< The output is the current clip pixel. (This means a basic copy operation and is identical to SourceOver when the current clip pixel is opaque).
BLEND_DESTINATION, ///< The output is the lower layer pixel. This means that the blending has no effect. This mode is the inverse of BLEND_SOURCE.
BLEND_SOURCE_IN, ///< The output is the current clip,where the alpha is reduced by that of the lower layer.
BLEND_DESTINATION_IN, ///< The output is the lower layer,where the alpha is reduced by that of the current clip. This mode is the inverse of BLEND_SOURCEIN.
BLEND_SOURCE_OUT, ///< The output is the current clip,where the alpha is reduced by the inverse of lower layer.
BLEND_DESTINATION_OUT, ///< The output is the lower layer,where the alpha is reduced by the inverse of the current clip. This mode is the inverse of BLEND_SOURCEOUT.
BLEND_SOURCE_ATOP, ///< The current clip pixel is blended on top of the lower layer,with the alpha of the current clip pixel reduced by the alpha of the lower layer pixel.
BLEND_DESTINATION_ATOP, ///< The lower layer pixel is blended on top of the current clip,with the alpha of the lower layer pixel is reduced by the alpha of the lower layer pixel. This mode is the inverse of BLEND_SOURCEATOP.
BLEND_XOR, ///< The current clip,whose alpha is reduced with the inverse of the lower layer alpha,is merged with the lower layer,whose alpha is reduced by the inverse of the current clip alpha. BLEND_XOR is not the same as the bitwise Xor.
BLEND_PLUS, ///< Both the alpha and color of the current clip and lower layer pixels are added together.
BLEND_MULTIPLY, ///< The output is the current clip color multiplied by the lower layer. Multiplying a color with white leaves the color unchanged,while multiplying a color with black produces black.
BLEND_SCREEN, ///< The current clip and lower layer colors are inverted and then multiplied. Screening a color with white produces white,whereas screening a color with black leaves the color unchanged.
BLEND_OVERLAY, ///< Multiplies or screens the colors depending on the lower layer color. The lower layer color is mixed with the current clip color to reflect the lightness or darkness of the lower layer.
BLEND_DARKEN, ///< The darker of the current clip and lower layer colors is selected.
BLEND_LIGHTEN, ///< The lighter of the current clip and lower layer colors is selected.
BLEND_COLOR_DODGE, ///< The lower layer color is brightened to reflect the current clip color. A black current clip color leaves the lower layer color unchanged.
BLEND_COLOR_BURN, ///< The lower layer color is darkened to reflect the current clip color. A white current clip color leaves the lower layer color unchanged.
BLEND_HARD_LIGHT, ///< Multiplies or screens the colors depending on the current clip color. A light current clip color will lighten the lower layer color,whereas a dark current clip color will darken the lower layer color.
BLEND_SOFT_LIGHT, ///< Darkens or lightens the colors depending on the current clip color. Similar to BLEND_HARDLIGHT.
BLEND_DIFFERENCE, ///< Subtracts the darker of the colors from the lighter. Painting with white inverts the lower layer color,whereas painting with black leaves the lower layer color unchanged.
BLEND_EXCLUSION ///< Similar to BLEND_DIFFERENCE,but with a lower contrast. Painting with white inverts the lower layer color,whereas painting with black leaves the lower layer color unchanged.
};

} // namespace openshot

#endif