Fixing color interpolation of false color maps

Problem was when an interval was separated from
the rest through a discontinuous color and did not have any
color range (start and end color same).

The new scheme fixes the problem by computing a
data mapping that has two slightly distinct values
for the two discontinuous colors.
This commit is contained in:
Matthias Koefferlein 2026-05-24 19:17:33 +02:00
parent 472a7aa7fd
commit 95271e3edc
3 changed files with 141 additions and 73 deletions

View File

@ -47,6 +47,65 @@
namespace img
{
// --------------------------------------------------------------------------------------
namespace
{
struct compare_first_of_node
{
bool operator() (const std::pair <double, std::pair<tl::Color, tl::Color> > &a, const std::pair <double, std::pair<tl::Color, tl::Color> > &b) const
{
return a.first < b.first;
}
};
}
static tl::Color
interpolated_color2 (const std::pair<double, std::pair<tl::Color, tl::Color> > &n1, const std::pair<double, std::pair<tl::Color, tl::Color> > &n2, double x)
{
double x1 = n1.first;
double x2 = n2.first;
unsigned int h1 = 0, s1 = 0, v1 = 0;
n1.second.second.get_hsv (h1, s1, v1);
unsigned int h2 = 0, s2 = 0, v2 = 0;
n2.second.first.get_hsv (h2, s2, v2);
int h = int (0.5 + h1 + double(x - x1) * double (int (h2) - int (h1)) / double(x2 - x1));
int s = int (0.5 + s1 + double(x - x1) * double (int (s2) - int (s1)) / double(x2 - x1));
int v = int (0.5 + v1 + double(x - x1) * double (int (v2) - int (v1)) / double(x2 - x1));
return tl::Color::from_hsv ((unsigned int) h, (unsigned int) s, (unsigned int) v);
}
tl::Color
interpolated_color (const DataMapping::false_color_nodes_type &nodes, double x)
{
if (nodes.size () < 1) {
return tl::Color ();
} else if (nodes.size () < 2) {
return x < nodes[0].first ? nodes[0].second.first : nodes[0].second.second;
} else {
std::vector<std::pair<double, std::pair<tl::Color, tl::Color> > >::const_iterator p = std::lower_bound (nodes.begin (), nodes.end (), std::make_pair (x, std::make_pair (tl::Color (), tl::Color ())), compare_first_of_node ());
if (p == nodes.end ()) {
return nodes.back ().second.second;
} else if (p == nodes.begin ()) {
return nodes.front ().second.first;
} else {
return interpolated_color2 (p[-1], *p, x);
}
}
}
// --------------------------------------------------------------------------------------
// img::DataMapping implementation
@ -153,6 +212,21 @@ DataMapping::operator< (const DataMapping &d) const
return false;
}
static inline double color_to_channel_value (const tl::Color &c, unsigned int channel)
{
double y = 0.0;
if (channel == 0) {
y = c.red ();
} else if (channel == 1) {
y = c.green ();
} else if (channel == 2) {
y = c.blue ();
}
return y / 255.0;
}
tl::DataMappingBase *
DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, unsigned int channel) const
{
@ -191,6 +265,8 @@ DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, uns
if (monochrome && false_color_nodes.size () > 1) {
tl::TableDataMapping *gray_to_color = new tl::TableDataMapping ();
double xfence = false_color_nodes.front ().first - 1.0;
double xeps = 1e-5;
for (unsigned int i = 1; i < false_color_nodes.size (); ++i) {
@ -212,35 +288,35 @@ DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, uns
for (int j = 0; j < n; ++j) {
tl::Color c = interpolated_color (false_color_nodes, x);
tl::Color c = interpolated_color2 (false_color_nodes [i - 1], false_color_nodes [i], x);
double y = 0.0;
if (channel == 0) {
y = c.red ();
} else if (channel == 1) {
y = c.green ();
} else if (channel == 2) {
y = c.blue ();
}
gray_to_color->push_back (x, y / 255.0);
double xx = std::max (xfence * (1.0 + xeps), x);
xfence = xx;
double y = color_to_channel_value (c, channel);
gray_to_color->push_back (xx, y);
x += dx;
}
// on discontinuous colors add another entry that manifests the discontinuity
if (i + 1 < false_color_nodes.size () && false_color_nodes [i].second.first != false_color_nodes [i].second.second) {
double x = false_color_nodes [i].first;
double xx = std::max (xfence * (1.0 + xeps), x);
xfence = xx;
double y = color_to_channel_value (false_color_nodes [i].second.first, channel);
gray_to_color->push_back (xx, y);
}
}
double ylast = 0.0;
if (channel == 0) {
ylast = false_color_nodes.back ().second.second.red ();
} else if (channel == 1) {
ylast = false_color_nodes.back ().second.second.green ();
} else if (channel == 2) {
ylast = false_color_nodes.back ().second.second.blue ();
}
gray_to_color->push_back (false_color_nodes.back ().first, ylast / 255.0);
double x = false_color_nodes.back ().first;
double xx = std::max (xfence * (1.0 + xeps), x);
xfence = xx;
double ylast = color_to_channel_value (false_color_nodes.back ().second.first, channel);
gray_to_color->push_back (xx, ylast);
dm = new tl::CombinedDataMapping (
to_pixel,
@ -266,57 +342,6 @@ DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, uns
return dm;
}
// --------------------------------------------------------------------------------------
namespace
{
struct compare_first_of_node
{
bool operator() (const std::pair <double, std::pair<tl::Color, tl::Color> > &a, const std::pair <double, std::pair<tl::Color, tl::Color> > &b) const
{
return a.first < b.first;
}
};
}
tl::Color
interpolated_color (const DataMapping::false_color_nodes_type &nodes, double x)
{
if (nodes.size () < 1) {
return tl::Color ();
} else if (nodes.size () < 2) {
return x < nodes[0].first ? nodes[0].second.first : nodes[0].second.second;
} else {
std::vector<std::pair<double, std::pair<tl::Color, tl::Color> > >::const_iterator p = std::lower_bound (nodes.begin (), nodes.end (), std::make_pair (x, std::make_pair (tl::Color (), tl::Color ())), compare_first_of_node ());
if (p == nodes.end ()) {
return nodes.back ().second.second;
} else if (p == nodes.begin ()) {
return nodes.front ().second.first;
} else {
double x1 = p[-1].first;
double x2 = p->first;
unsigned int h1 = 0, s1 = 0, v1 = 0;
p[-1].second.second.get_hsv (h1, s1, v1);
unsigned int h2 = 0, s2 = 0, v2 = 0;
p->second.first.get_hsv (h2, s2, v2);
int h = int (0.5 + h1 + double(x - x1) * double (int (h2) - int (h1)) / double(x2 - x1));
int s = int (0.5 + s1 + double(x - x1) * double (int (s2) - int (s1)) / double(x2 - x1));
int v = int (0.5 + v1 + double(x - x1) * double (int (v2) - int (v1)) / double(x2 - x1));
return tl::Color::from_hsv ((unsigned int) h, (unsigned int) s, (unsigned int) v);
}
}
}
// --------------------------------------------------------------------------------------
// img::DataHeader definition and implementation

View File

@ -145,7 +145,7 @@ public:
/**
* @brief A helper function to interpolate a color in the color bar at a given x
*/
tl::Color interpolated_color (const DataMapping::false_color_nodes_type &nodes, double x);
IMG_PUBLIC tl::Color interpolated_color (const DataMapping::false_color_nodes_type &nodes, double x);
/**
* @brief A image object

View File

@ -328,3 +328,46 @@ TEST(3)
EXPECT_EQ (image.mask (1, 2), false);
}
// color interpolation
TEST(4)
{
img::DataMapping::false_color_nodes_type nodes;
nodes.push_back (std::make_pair (0.0, std::make_pair (tl::color_t (0xff0000), tl::color_t (0xff0000))));
nodes.push_back (std::make_pair (1.0, std::make_pair (tl::color_t (0x0000ff), tl::color_t (0xffffff))));
nodes.push_back (std::make_pair (2.0, std::make_pair (tl::color_t (0x000000), tl::color_t (0xffffff))));
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.0)).to_string (), "#ff0000");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.001)).to_string (), "#ff0000");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.5)).to_string (), "#00ff00");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.999)).to_string (), "#0000ff");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.0)).to_string (), "#0000ff");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.001)).to_string (), "#ffffff");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.5)).to_string (), "#808080");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.999)).to_string (), "#000000");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 2.0)).to_string (), "#000000");
nodes.clear ();
nodes.push_back (std::make_pair (0.0, std::make_pair (tl::color_t (0xff0000), tl::color_t (0xff0000))));
nodes.push_back (std::make_pair (1.0, std::make_pair (tl::color_t (0xff0000), tl::color_t (0x0000ff))));
nodes.push_back (std::make_pair (2.0, std::make_pair (tl::color_t (0x0000ff), tl::color_t (0x0000ff))));
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.0)).to_string (), "#ff0000");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.5)).to_string (), "#ff0000");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.999)).to_string (), "#ff0000");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.0)).to_string (), "#ff0000");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.001)).to_string (), "#0000ff");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.5)).to_string (), "#0000ff");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.999)).to_string (), "#0000ff");
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 2.0)).to_string (), "#0000ff");
nodes.clear ();
nodes.push_back (std::make_pair (0.0, std::make_pair (tl::color_t (0x0000ff), tl::color_t (0x0000ff))));
nodes.push_back (std::make_pair (0.275591, std::make_pair (tl::color_t (0x0000ff), tl::color_t (0x000000))));
nodes.push_back (std::make_pair (0.643701, std::make_pair (tl::color_t (0xffffff), tl::color_t (0xff0000))));
nodes.push_back (std::make_pair (1.0, std::make_pair (tl::color_t (0xff0000), tl::color_t (0xff0000))));
EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.7)).to_string (), "#ff0000");
}