satdump/src-core/image/hue_saturation.cpp

254 lines
8 KiB
C++
Raw Permalink Normal View History

#include "hue_saturation.h"
#include <limits>
namespace satdump
{
namespace image
{
template <typename T>
T clamp(T value, T max, T min)
{
if (value > max)
value = max;
else if (value < min)
value = min;
return value;
};
HueSaturation::HueSaturation()
{
overlap = 0.0f;
for (int i = HUE_RANGE_ALL; i <= HUE_RANGE_MAGENTA; i++)
{
HueRange partition = (HueRange)i;
hue[partition] = 0.0;
lightness[partition] = 0.0;
saturation[partition] = 0.0;
}
}
double map_hue(HueSaturation config, HueRange range, double value)
{
value += (config.hue[HUE_RANGE_ALL] + config.hue[range]) / 2.0;
if (value < 0)
return value + 1.0;
else if (value > 1.0)
return value - 1.0;
else
return value;
}
double map_hue_overlap(HueSaturation config, HueRange primary_range, HueRange secondary_range, double value, double primary_intensity, double secondary_intensity)
{
double v = config.hue[primary_range] * primary_intensity + config.hue[secondary_range] * secondary_intensity;
value += (config.hue[HUE_RANGE_ALL] + v) / 2.0;
if (value < 0)
return value + 1.0;
else if (value > 1.0)
return value - 1.0;
else
return value;
}
double map_saturation(HueSaturation config, HueRange range, double value)
{
double v = config.saturation[HUE_RANGE_ALL] + config.saturation[range];
value *= (v + 1.0);
return clamp<double>(value, 1.0, 0.0);
}
double map_lightness(HueSaturation config, HueRange range, double value)
{
double v = (config.lightness[HUE_RANGE_ALL] + config.lightness[range]);
if (v < 0)
return value * (v + 1.0);
else
return value + (v * (1.0 - value));
}
void hue_saturation(Image &image, HueSaturation config)
{
float scale = image.maxval() - 1;
double rgb_r, rgb_g, rgb_b;
double hsl_h, hsl_s, hsl_l;
2025-07-05 04:08:58 +02:00
float overlap = (config.overlap / 100.0) / 2.0;
for (size_t pixel = 0; pixel < image.width() * image.height(); pixel++)
{
rgb_r = image.get(image.width() * image.height() * 0 + pixel) / scale;
rgb_g = image.get(image.width() * image.height() * 1 + pixel) / scale;
rgb_b = image.get(image.width() * image.height() * 2 + pixel) / scale;
rgb_to_hsl(rgb_r, rgb_g, rgb_b, hsl_h, hsl_s, hsl_l);
{
double h;
int hue_counter;
int hue = 0;
int secondary_hue = 0;
bool use_secondary_hue = false;
float primary_intensity = 0.0;
float secondary_intensity = 0.0;
h = hsl_h * 6.0;
for (hue_counter = 0; hue_counter < 7; hue_counter++)
{
double hue_threshold = (double)hue_counter + 0.5;
if (h < ((double)hue_threshold + overlap))
{
hue = hue_counter;
if (overlap > 0.0 && h > ((double)hue_threshold - overlap))
{
use_secondary_hue = true;
secondary_hue = hue_counter + 1;
secondary_intensity = (h - (double)hue_threshold + overlap) / (2.0 * overlap);
primary_intensity = 1.0 - secondary_intensity;
}
else
{
use_secondary_hue = false;
}
break;
}
}
if (hue >= 6)
{
hue = 0;
use_secondary_hue = false;
}
if (secondary_hue >= 6)
{
secondary_hue = 0;
}
hue++;
secondary_hue++;
if (use_secondary_hue)
{
hsl_h = map_hue_overlap(config, (HueRange)hue, (HueRange)secondary_hue, hsl_h, primary_intensity, secondary_intensity);
hsl_s = (map_saturation(config, (HueRange)hue, hsl_s) * primary_intensity + map_saturation(config, (HueRange)secondary_hue, hsl_s) * secondary_intensity);
hsl_l = (map_lightness(config, (HueRange)hue, hsl_l) * primary_intensity + map_lightness(config, (HueRange)secondary_hue, hsl_l) * secondary_intensity);
}
else
{
hsl_h = map_hue(config, (HueRange)hue, hsl_h);
hsl_s = map_saturation(config, (HueRange)hue, hsl_s);
hsl_l = map_lightness(config, (HueRange)hue, hsl_l);
}
}
hsl_to_rgb(hsl_h, hsl_s, hsl_l, rgb_r, rgb_g, rgb_b);
image.set(image.width() * image.height() * 0 + pixel, rgb_r * scale);
image.set(image.width() * image.height() * 1 + pixel, rgb_g * scale);
image.set(image.width() * image.height() * 2 + pixel, rgb_b * scale);
}
}
void rgb_to_hsl(double r, double g, double b, double &h, double &s, double &l)
{
double max, min, delta;
max = std::max<double>(r, std::max<double>(g, b));
min = std::min<double>(r, std::min<double>(g, b));
l = (max + min) / 2.0;
if (max == min)
{
s = 0.0;
h = -1.0;
}
else
{
if (l <= 0.5)
s = (max - min) / (max + min);
else
s = (max - min) / (2.0 - max - min);
delta = max - min;
if (delta == 0.0)
delta = 1.0;
if (r == max)
{
h = (g - b) / delta;
}
else if (g == max)
{
h = 2.0 + (b - r) / delta;
}
else
{
h = 4.0 + (r - g) / delta;
}
h /= 6.0;
if (h < 0.0)
h += 1.0;
}
}
double hsl_value(double n1, double n2, double hue)
{
double val;
if (hue > 6.0)
hue -= 6.0;
else if (hue < 0.0)
hue += 6.0;
if (hue < 1.0)
val = n1 + (n2 - n1) * hue;
else if (hue < 3.0)
val = n2;
else if (hue < 4.0)
val = n1 + (n2 - n1) * (4.0 - hue);
else
val = n1;
return val;
}
void hsl_to_rgb(double h, double s, double l, double &r, double &g, double &b)
{
if (s == 0)
{
r = l;
g = l;
b = l;
}
else
{
double m1, m2;
if (l <= 0.5)
m2 = l * (1.0 + s);
else
m2 = l + s - l * s;
m1 = 2.0 * l - m2;
r = hsl_value(m1, m2, h * 6.0 + 2.0);
g = hsl_value(m1, m2, h * 6.0);
b = hsl_value(m1, m2, h * 6.0 - 2.0);
}
}
} // namespace image
} // namespace satdump