From 56bd9557aeff22e8b0a9cfb7560a238bdea9e9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Fri, 7 Jan 2022 13:11:43 +0100 Subject: [PATCH] Implement blend modes (#320) --- src/Clip.cpp | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/Clip.h | 5 +++ src/Enums.h | 30 +++++++++++++- 3 files changed, 146 insertions(+), 2 deletions(-) diff --git a/src/Clip.cpp b/src/Clip.cpp index f68b0ff99..dbdd1d158 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -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 = ""; @@ -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); @@ -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))); @@ -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(); @@ -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()) @@ -1286,7 +1317,7 @@ void Clip::apply_keyframes(std::shared_ptr frame, std::shared_ptr 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) { @@ -1582,3 +1613,83 @@ QTransform Clip::get_transform(std::shared_ptr 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; +} diff --git a/src/Clip.h b/src/Clip.h index c6dffbd2f..380fef928 100644 --- a/src/Clip.h +++ b/src/Clip.h @@ -37,6 +37,7 @@ #include "TrackedObjectBase.h" #include +#include namespace openshot { class AudioResampler; @@ -150,6 +151,9 @@ namespace openshot { /// Reverse an audio buffer void reverse_buffer(juce::AudioBuffer* 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 @@ -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) diff --git a/src/Enums.h b/src/Enums.h index 14b693166..873423058 100644 --- a/src/Enums.h +++ b/src/Enums.h @@ -13,7 +13,6 @@ #ifndef OPENSHOT_ENUMS_H #define OPENSHOT_ENUMS_H - namespace openshot { @@ -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