Files
libopenshot/tests/ColorMap.cpp

297 lines
7.5 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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;
}
// Frame that keeps the example pixel in the bright range used by the domain tests
static std::shared_ptr<Frame> makeBrightTestFrame()
{
QImage img(2, 2, QImage::Format_ARGB32);
img.fill(QColor(50,100,150,255));
img.setPixelColor(0,0, QColor(230,230,230,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();
}
static std::string lut1dPath()
{
std::stringstream path;
path << TEST_MEDIA_PATH << "example-1d-lut.cube";
return path.str();
}
static std::string lutDomain1dPath()
{
std::stringstream path;
path << TEST_MEDIA_PATH << "domain-1d-lut.cube";
return path.str();
}
static std::string lutDomain3dPath()
{
std::stringstream path;
path << TEST_MEDIA_PATH << "domain-3d-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("1D LUT files alter pixel values", "[effect][colormap][lut][1d]")
{
ColorMap effect(
lut1dPath(),
Keyframe(1.0),
Keyframe(1.0),
Keyframe(1.0),
Keyframe(1.0)
);
auto in = makeTestFrame();
QColor before = in->GetImage()->pixelColor(0,0);
auto out = effect.GetFrame(in, 4);
QColor after = out->GetImage()->pixelColor(0,0);
CHECK(after != before);
}
TEST_CASE("1D LUT obeys DOMAIN_MIN and DOMAIN_MAX", "[effect][colormap][lut][domain]")
{
ColorMap effect(
lutDomain1dPath(),
Keyframe(1.0),
Keyframe(1.0),
Keyframe(1.0),
Keyframe(1.0)
);
auto out = effect.GetFrame(makeBrightTestFrame(), 0);
QColor after = out->GetImage()->pixelColor(0,0);
CHECK(after == QColor(255,0,0,255));
}
TEST_CASE("3D LUT obeys DOMAIN_MIN and DOMAIN_MAX", "[effect][colormap][lut][domain]")
{
ColorMap effect(
lutDomain3dPath(),
Keyframe(1.0),
Keyframe(1.0),
Keyframe(1.0),
Keyframe(1.0)
);
auto out = effect.GetFrame(makeBrightTestFrame(), 0);
QColor after = out->GetImage()->pixelColor(0,0);
CHECK(after == QColor(255,0,0,255));
}
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);
}