You've already forked libopenshot
mirror of
https://github.com/OpenShot/libopenshot.git
synced 2026-03-02 08:53:52 -08:00
Adding composite/blend modes to libopenshot:
-Normal -Darken -Multiply -Color Burn -Lighten -Screen -Color Dodge -Add -Overlay -Soft Light -Hard Light -Difference -Exclusion
This commit is contained in:
41
src/Clip.cpp
41
src/Clip.cpp
@@ -32,6 +32,34 @@
|
||||
|
||||
using namespace openshot;
|
||||
|
||||
namespace {
|
||||
struct CompositeChoice { const char* name; CompositeType value; };
|
||||
const CompositeChoice composite_choices[] = {
|
||||
{"Normal", COMPOSITE_SOURCE_OVER},
|
||||
|
||||
// Darken group
|
||||
{"Darken", COMPOSITE_DARKEN},
|
||||
{"Multiply", COMPOSITE_MULTIPLY},
|
||||
{"Color Burn", COMPOSITE_COLOR_BURN},
|
||||
|
||||
// Lighten group
|
||||
{"Lighten", COMPOSITE_LIGHTEN},
|
||||
{"Screen", COMPOSITE_SCREEN},
|
||||
{"Color Dodge", COMPOSITE_COLOR_DODGE},
|
||||
{"Add", COMPOSITE_PLUS},
|
||||
|
||||
// Contrast group
|
||||
{"Overlay", COMPOSITE_OVERLAY},
|
||||
{"Soft Light", COMPOSITE_SOFT_LIGHT},
|
||||
{"Hard Light", COMPOSITE_HARD_LIGHT},
|
||||
|
||||
// Compare
|
||||
{"Difference", COMPOSITE_DIFFERENCE},
|
||||
{"Exclusion", COMPOSITE_EXCLUSION},
|
||||
};
|
||||
const int composite_choices_count = sizeof(composite_choices)/sizeof(CompositeChoice);
|
||||
}
|
||||
|
||||
// Init default settings for a clip
|
||||
void Clip::init_settings()
|
||||
{
|
||||
@@ -45,6 +73,7 @@ void Clip::init_settings()
|
||||
anchor = ANCHOR_CANVAS;
|
||||
display = FRAME_DISPLAY_NONE;
|
||||
mixing = VOLUME_MIX_NONE;
|
||||
composite = COMPOSITE_SOURCE_OVER;
|
||||
waveform = false;
|
||||
previous_properties = "";
|
||||
parentObjectId = "";
|
||||
@@ -766,6 +795,7 @@ std::string Clip::PropertiesJSON(int64_t requested_frame) const {
|
||||
root["scale"] = add_property_json("Scale", scale, "int", "", NULL, 0, 3, false, requested_frame);
|
||||
root["display"] = add_property_json("Frame Number", display, "int", "", NULL, 0, 3, false, requested_frame);
|
||||
root["mixing"] = add_property_json("Volume Mixing", mixing, "int", "", NULL, 0, 2, false, requested_frame);
|
||||
root["composite"] = add_property_json("Composite", composite, "int", "", NULL, 0, composite_choices_count - 1, false, requested_frame);
|
||||
root["waveform"] = add_property_json("Waveform", waveform, "int", "", NULL, 0, 1, false, requested_frame);
|
||||
root["parentObjectId"] = add_property_json("Parent", 0.0, "string", parentObjectId, NULL, -1, -1, false, requested_frame);
|
||||
|
||||
@@ -797,6 +827,10 @@ std::string Clip::PropertiesJSON(int64_t requested_frame) const {
|
||||
root["mixing"]["choices"].append(add_property_choice_json("Average", VOLUME_MIX_AVERAGE, mixing));
|
||||
root["mixing"]["choices"].append(add_property_choice_json("Reduce", VOLUME_MIX_REDUCE, mixing));
|
||||
|
||||
// Add composite choices (dropdown style)
|
||||
for (int i = 0; i < composite_choices_count; ++i)
|
||||
root["composite"]["choices"].append(add_property_choice_json(composite_choices[i].name, composite_choices[i].value, composite));
|
||||
|
||||
// Add waveform choices (dropdown style)
|
||||
root["waveform"]["choices"].append(add_property_choice_json("Yes", true, waveform));
|
||||
root["waveform"]["choices"].append(add_property_choice_json("No", false, waveform));
|
||||
@@ -879,6 +913,7 @@ Json::Value Clip::JsonValue() const {
|
||||
root["anchor"] = anchor;
|
||||
root["display"] = display;
|
||||
root["mixing"] = mixing;
|
||||
root["composite"] = composite;
|
||||
root["waveform"] = waveform;
|
||||
root["scale_x"] = scale_x.JsonValue();
|
||||
root["scale_y"] = scale_y.JsonValue();
|
||||
@@ -967,6 +1002,8 @@ void Clip::SetJsonValue(const Json::Value root) {
|
||||
display = (FrameDisplayType) root["display"].asInt();
|
||||
if (!root["mixing"].isNull())
|
||||
mixing = (VolumeMixType) root["mixing"].asInt();
|
||||
if (!root["composite"].isNull())
|
||||
composite = (CompositeType) root["composite"].asInt();
|
||||
if (!root["waveform"].isNull())
|
||||
waveform = root["waveform"].asBool();
|
||||
if (!root["scale_x"].isNull())
|
||||
@@ -1197,7 +1234,7 @@ void Clip::apply_background(std::shared_ptr<openshot::Frame> frame, std::shared_
|
||||
QPainter painter(background_canvas.get());
|
||||
|
||||
// Composite a new layer onto the image
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
painter.setCompositionMode(static_cast<QPainter::CompositionMode>(composite));
|
||||
painter.drawImage(0, 0, *frame->GetImage());
|
||||
painter.end();
|
||||
|
||||
@@ -1260,7 +1297,7 @@ void Clip::apply_keyframes(std::shared_ptr<Frame> frame, QSize timeline_size) {
|
||||
painter.setTransform(transform);
|
||||
|
||||
// Composite a new layer onto the image
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
painter.setCompositionMode(static_cast<QPainter::CompositionMode>(composite));
|
||||
|
||||
// Apply opacity via painter instead of per-pixel alpha manipulation
|
||||
const float alpha_value = alpha.GetValue(frame->number);
|
||||
|
||||
@@ -169,6 +169,7 @@ namespace openshot {
|
||||
openshot::AnchorType anchor; ///< The anchor determines what parent a clip should snap to
|
||||
openshot::FrameDisplayType display; ///< The format to display the frame number (if any)
|
||||
openshot::VolumeMixType mixing; ///< What strategy should be followed when mixing audio with other clips
|
||||
openshot::CompositeType composite; ///< How this clip is composited onto lower layers
|
||||
|
||||
#ifdef USE_OPENCV
|
||||
bool COMPILED_WITH_CV = true;
|
||||
|
||||
31
src/Enums.h
31
src/Enums.h
@@ -64,6 +64,37 @@ enum VolumeMixType
|
||||
VOLUME_MIX_REDUCE ///< Reduce volume by about %25, and then mix (louder, but could cause pops if the sum exceeds 100%)
|
||||
};
|
||||
|
||||
/// This enumeration determines how clips are composited onto lower layers.
|
||||
enum CompositeType {
|
||||
COMPOSITE_SOURCE_OVER,
|
||||
COMPOSITE_DESTINATION_OVER,
|
||||
COMPOSITE_CLEAR,
|
||||
COMPOSITE_SOURCE,
|
||||
COMPOSITE_DESTINATION,
|
||||
COMPOSITE_SOURCE_IN,
|
||||
COMPOSITE_DESTINATION_IN,
|
||||
COMPOSITE_SOURCE_OUT,
|
||||
COMPOSITE_DESTINATION_OUT,
|
||||
COMPOSITE_SOURCE_ATOP,
|
||||
COMPOSITE_DESTINATION_ATOP,
|
||||
COMPOSITE_XOR,
|
||||
|
||||
// svg 1.2 blend modes
|
||||
COMPOSITE_PLUS,
|
||||
COMPOSITE_MULTIPLY,
|
||||
COMPOSITE_SCREEN,
|
||||
COMPOSITE_OVERLAY,
|
||||
COMPOSITE_DARKEN,
|
||||
COMPOSITE_LIGHTEN,
|
||||
COMPOSITE_COLOR_DODGE,
|
||||
COMPOSITE_COLOR_BURN,
|
||||
COMPOSITE_HARD_LIGHT,
|
||||
COMPOSITE_SOFT_LIGHT,
|
||||
COMPOSITE_DIFFERENCE,
|
||||
COMPOSITE_EXCLUSION,
|
||||
|
||||
COMPOSITE_LAST = COMPOSITE_EXCLUSION
|
||||
};
|
||||
|
||||
/// This enumeration determines the distortion type of Distortion Effect.
|
||||
enum DistortionType
|
||||
|
||||
@@ -42,6 +42,7 @@ TEST_CASE( "default constructor", "[libopenshot][clip]" )
|
||||
CHECK(c1.anchor == ANCHOR_CANVAS);
|
||||
CHECK(c1.gravity == GRAVITY_CENTER);
|
||||
CHECK(c1.scale == SCALE_FIT);
|
||||
CHECK(c1.composite == COMPOSITE_SOURCE_OVER);
|
||||
CHECK(c1.Layer() == 0);
|
||||
CHECK(c1.Position() == Approx(0.0f).margin(0.00001));
|
||||
CHECK(c1.Start() == Approx(0.0f).margin(0.00001));
|
||||
@@ -60,6 +61,7 @@ TEST_CASE( "path string constructor", "[libopenshot][clip]" )
|
||||
CHECK(c1.anchor == ANCHOR_CANVAS);
|
||||
CHECK(c1.gravity == GRAVITY_CENTER);
|
||||
CHECK(c1.scale == SCALE_FIT);
|
||||
CHECK(c1.composite == COMPOSITE_SOURCE_OVER);
|
||||
CHECK(c1.Layer() == 0);
|
||||
CHECK(c1.Position() == Approx(0.0f).margin(0.00001));
|
||||
CHECK(c1.Start() == Approx(0.0f).margin(0.00001));
|
||||
@@ -76,6 +78,7 @@ TEST_CASE( "basic getters and setters", "[libopenshot][clip]" )
|
||||
CHECK(c1.anchor == ANCHOR_CANVAS);
|
||||
CHECK(c1.gravity == GRAVITY_CENTER);
|
||||
CHECK(c1.scale == SCALE_FIT);
|
||||
CHECK(c1.composite == COMPOSITE_SOURCE_OVER);
|
||||
CHECK(c1.Layer() == 0);
|
||||
CHECK(c1.Position() == Approx(0.0f).margin(0.00001));
|
||||
CHECK(c1.Start() == Approx(0.0f).margin(0.00001));
|
||||
@@ -541,9 +544,9 @@ TEST_CASE( "painter_opacity_applied_no_per_pixel_mutation", "[libopenshot][clip]
|
||||
// In Qt, pixelColor() returns unpremultiplied values, so expect alpha ≈ 127 and red ≈ 255.
|
||||
QColor p = img->pixelColor(70, 50);
|
||||
CHECK(p.alpha() == Approx(127).margin(10));
|
||||
CHECK(p.red() == Approx(255).margin(2));
|
||||
CHECK(p.red() == Approx(255).margin(2));
|
||||
CHECK(p.green() == Approx(0).margin(2));
|
||||
CHECK(p.blue() == Approx(0).margin(2));
|
||||
CHECK(p.blue() == Approx(0).margin(2));
|
||||
}
|
||||
|
||||
TEST_CASE( "composite_over_opaque_background_blend", "[libopenshot][clip][pr]" )
|
||||
@@ -652,9 +655,9 @@ TEST_CASE( "transform_path_identity_vs_scaled", "[libopenshot][clip][pr]" )
|
||||
REQUIRE(img);
|
||||
// Pick a mid pixel that is white in the grid (multiple of 4)
|
||||
QColor c = img->pixelColor(20, 20);
|
||||
CHECK(c.red() >= 240);
|
||||
CHECK(c.red() >= 240);
|
||||
CHECK(c.green() >= 240);
|
||||
CHECK(c.blue() >= 240);
|
||||
CHECK(c.blue() >= 240);
|
||||
}
|
||||
|
||||
// Case B: Downscale (trigger transform path). Clear the clip cache so we don't
|
||||
@@ -684,7 +687,7 @@ TEST_CASE( "transform_path_identity_vs_scaled", "[libopenshot][clip][pr]" )
|
||||
|
||||
// Optional diagnostic: scaled typically yields <= number of pure whites vs identity.
|
||||
int white_id = count_white(*out_identity->GetImage(), x0, y0, x1, y1);
|
||||
int white_sc = count_white(*img_scaled, x0, y0, x1, y1);
|
||||
int white_sc = count_white(*img_scaled, x0, y0, x1, y1);
|
||||
CHECK(white_sc <= white_id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user