You've already forked libopenshot
mirror of
https://github.com/OpenShot/libopenshot.git
synced 2026-03-02 08:53:52 -08:00
215 lines
5.5 KiB
C++
215 lines
5.5 KiB
C++
/**
|
||
* @file
|
||
* @brief Unit tests for ColorMap effect
|
||
* @author Jonathan Thomas
|
||
*
|
||
* @ref License
|
||
*/
|
||
|
||
// Copyright (c) 2008-2025 OpenShot Studios, LLC
|
||
//
|
||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||
|
||
#include <memory>
|
||
#include <QImage>
|
||
#include <QColor>
|
||
#include <sstream>
|
||
#include "Frame.h"
|
||
#include "effects/ColorMap.h"
|
||
#include "openshot_catch.h"
|
||
|
||
using namespace openshot;
|
||
|
||
// allow Catch2 to print QColor on failure
|
||
static std::ostream& operator<<(std::ostream& os, QColor const& c)
|
||
{
|
||
os << "QColor(" << c.red() << "," << c.green()
|
||
<< "," << c.blue() << "," << c.alpha() << ")";
|
||
return os;
|
||
}
|
||
|
||
// Build a simple 2×2 frame with one distinct pixel
|
||
static std::shared_ptr<Frame> makeTestFrame()
|
||
{
|
||
QImage img(2, 2, QImage::Format_ARGB32);
|
||
img.fill(QColor(50,100,150,255));
|
||
img.setPixelColor(0,0, QColor(10,20,30,255));
|
||
auto frame = std::make_shared<Frame>();
|
||
*frame->GetImage() = img;
|
||
return frame;
|
||
}
|
||
|
||
// Helper to construct the LUT-path from TEST_MEDIA_PATH
|
||
static std::string lutPath()
|
||
{
|
||
std::stringstream path;
|
||
path << TEST_MEDIA_PATH << "example-lut.cube";
|
||
return path.str();
|
||
}
|
||
|
||
TEST_CASE("Default ColorMap with no LUT path leaves image unchanged", "[effect][colormap]")
|
||
{
|
||
ColorMap effect;
|
||
auto in = makeTestFrame();
|
||
QColor before = in->GetImage()->pixelColor(0,0);
|
||
|
||
auto out = effect.GetFrame(in, 0);
|
||
QColor after = out->GetImage()->pixelColor(0,0);
|
||
|
||
CHECK(after == before);
|
||
}
|
||
|
||
TEST_CASE("Overall intensity = 0 leaves image unchanged even when LUT is set", "[effect][colormap]")
|
||
{
|
||
ColorMap effect(
|
||
lutPath(),
|
||
Keyframe(0.0), // overall off
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0)
|
||
);
|
||
|
||
auto in = makeTestFrame();
|
||
QColor before = in->GetImage()->pixelColor(0,0);
|
||
auto out = effect.GetFrame(in, 1);
|
||
QColor after = out->GetImage()->pixelColor(0,0);
|
||
|
||
CHECK(after == before);
|
||
}
|
||
|
||
TEST_CASE("JSON round-trip preserves LUT path and intensity keyframe values", "[effect][colormap][json]")
|
||
{
|
||
ColorMap A(
|
||
lutPath(),
|
||
Keyframe(0.3), // overall
|
||
Keyframe(0.4),
|
||
Keyframe(0.5),
|
||
Keyframe(0.6)
|
||
);
|
||
|
||
std::string serialized = A.Json();
|
||
ColorMap B;
|
||
B.SetJson(serialized);
|
||
|
||
CHECK(B.JsonValue()["lut_path"].asString() == lutPath());
|
||
CHECK( B.intensity. GetValue(0) == Approx(0.3) );
|
||
CHECK( B.intensity_r.GetValue(0) == Approx(0.4) );
|
||
CHECK( B.intensity_g.GetValue(0) == Approx(0.5) );
|
||
CHECK( B.intensity_b.GetValue(0) == Approx(0.6) );
|
||
}
|
||
|
||
TEST_CASE("Clearing LUT path via JSON leaves LUT path empty", "[effect][colormap][json]")
|
||
{
|
||
ColorMap effect(
|
||
lutPath(),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0)
|
||
);
|
||
Json::Value clear;
|
||
clear["lut_path"] = std::string("");
|
||
effect.SetJsonValue(clear);
|
||
|
||
auto v = effect.JsonValue();
|
||
CHECK(v["lut_path"].asString() == "");
|
||
}
|
||
|
||
TEST_CASE("PropertiesJSON exposes all four intensity properties", "[effect][colormap][ui]")
|
||
{
|
||
ColorMap effect;
|
||
std::string props = effect.PropertiesJSON(0);
|
||
Json::CharReaderBuilder rb;
|
||
Json::Value root;
|
||
std::string errs;
|
||
std::istringstream is(props);
|
||
REQUIRE(Json::parseFromStream(rb, is, &root, &errs));
|
||
|
||
CHECK(root.isMember("lut_path"));
|
||
CHECK(root.isMember("intensity"));
|
||
CHECK(root.isMember("intensity_r"));
|
||
CHECK(root.isMember("intensity_g"));
|
||
CHECK(root.isMember("intensity_b"));
|
||
}
|
||
|
||
TEST_CASE("Full-intensity LUT changes pixel values", "[effect][colormap][lut]")
|
||
{
|
||
ColorMap effect(
|
||
lutPath(),
|
||
Keyframe(1.0), // full overall
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0)
|
||
);
|
||
|
||
auto in = makeTestFrame();
|
||
QColor before = in->GetImage()->pixelColor(0,0);
|
||
auto out = effect.GetFrame(in, 2);
|
||
QColor after = out->GetImage()->pixelColor(0,0);
|
||
|
||
CHECK(after != before);
|
||
}
|
||
|
||
TEST_CASE("Half-intensity LUT changes pixel values less than full-intensity", "[effect][colormap][lut]")
|
||
{
|
||
auto in = makeTestFrame();
|
||
QColor before = in->GetImage()->pixelColor(0,0);
|
||
|
||
ColorMap half(
|
||
lutPath(),
|
||
Keyframe(0.5), // half overall
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0)
|
||
);
|
||
auto out_half = half.GetFrame(in, 3);
|
||
QColor h = out_half->GetImage()->pixelColor(0,0);
|
||
|
||
ColorMap full(
|
||
lutPath(),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0)
|
||
);
|
||
auto out_full = full.GetFrame(in, 3);
|
||
QColor f = out_full->GetImage()->pixelColor(0,0);
|
||
|
||
int diff_half = std::abs(h.red() - before.red())
|
||
+ std::abs(h.green() - before.green())
|
||
+ std::abs(h.blue() - before.blue());
|
||
int diff_full = std::abs(f.red() - before.red())
|
||
+ std::abs(f.green() - before.green())
|
||
+ std::abs(f.blue() - before.blue());
|
||
|
||
CHECK(diff_half < diff_full);
|
||
}
|
||
|
||
TEST_CASE("Disabling red channel produces different result than full-intensity", "[effect][colormap][lut]")
|
||
{
|
||
auto in = makeTestFrame();
|
||
QColor before = in->GetImage()->pixelColor(0,0);
|
||
|
||
ColorMap full(
|
||
lutPath(),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0),
|
||
Keyframe(1.0)
|
||
);
|
||
auto out_full = full.GetFrame(in, 4);
|
||
QColor f = out_full->GetImage()->pixelColor(0,0);
|
||
|
||
ColorMap red_off(
|
||
lutPath(),
|
||
Keyframe(1.0),
|
||
Keyframe(0.0), // red off
|
||
Keyframe(1.0),
|
||
Keyframe(1.0)
|
||
);
|
||
auto out_off = red_off.GetFrame(in, 4);
|
||
QColor r = out_off->GetImage()->pixelColor(0,0);
|
||
|
||
CHECK(r != f);
|
||
}
|