jfjoch_viewer: Improve performance of image coloring
This commit is contained in:
+35
-40
@@ -9,8 +9,35 @@ float luminance(rgb input) {
|
||||
return 0.2126f * input.r + 0.7152f * input.g + 0.0722f * input.b;
|
||||
}
|
||||
|
||||
ColorScale::ColorScale() : lut_(kLutSize) {
|
||||
CalcLUT();
|
||||
}
|
||||
|
||||
void ColorScale::Select(ColorScaleEnum val) {
|
||||
current = val;
|
||||
CalcLUT();
|
||||
}
|
||||
|
||||
const std::vector<rgb> &ColorScale::LUTData() const {
|
||||
return lut_;
|
||||
}
|
||||
|
||||
void ColorScale::CalcLUT() const {
|
||||
const std::vector<rgb>* map = nullptr;
|
||||
switch (current) {
|
||||
case ColorScaleEnum::Viridis: map = &viridis_colormap; break;
|
||||
case ColorScaleEnum::Heat: map = &heat_colormap; break;
|
||||
case ColorScaleEnum::Indigo: map = &white_to_indigo_colormap; break;
|
||||
case ColorScaleEnum::BW: map = &white_to_black_colormap; break;
|
||||
default:
|
||||
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
||||
"Color scale unknown");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < kLutSize; ++i) {
|
||||
const float f = static_cast<float>(i) / static_cast<float>(kLutSize - 1);
|
||||
lut_[i] = Apply(f, *map);
|
||||
}
|
||||
}
|
||||
|
||||
rgb ColorScale::Apply(float input, const std::vector<rgb> &map) {
|
||||
@@ -45,8 +72,6 @@ rgb ColorScale::Apply(ColorScaleSpecial input) const {
|
||||
}
|
||||
|
||||
rgb ColorScale::Apply(float input, float min, float max) const {
|
||||
// Assume min and max is finite
|
||||
|
||||
if (!std::isfinite(input))
|
||||
return gap;
|
||||
|
||||
@@ -58,19 +83,14 @@ rgb ColorScale::Apply(float input, float min, float max) const {
|
||||
else
|
||||
f = (input - min) / (max - min);
|
||||
|
||||
switch (current) {
|
||||
case ColorScaleEnum::Viridis:
|
||||
return Apply(f, viridis_colormap);
|
||||
case ColorScaleEnum::Heat:
|
||||
return Apply(f, heat_colormap);
|
||||
case ColorScaleEnum::Indigo:
|
||||
return Apply(f, white_to_indigo_colormap);
|
||||
case ColorScaleEnum::BW:
|
||||
return Apply(f, white_to_black_colormap);
|
||||
default:
|
||||
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
||||
"Color scale unknown");
|
||||
}
|
||||
const size_t idx = static_cast<size_t>(f * static_cast<float>(kLutSize - 1));
|
||||
return lut_[idx];
|
||||
}
|
||||
|
||||
rgb ColorScale::ApplyLUTIndex(size_t idx) const {
|
||||
if (idx >= kLutSize)
|
||||
return lut_[kLutSize-1];
|
||||
return lut_[idx];
|
||||
}
|
||||
|
||||
ColorScale &ColorScale::Gap(rgb input) {
|
||||
@@ -82,28 +102,3 @@ ColorScale &ColorScale::BadPixel(rgb input) {
|
||||
bad = input;
|
||||
return *this;
|
||||
}
|
||||
|
||||
rgb rainbowColor(float t) {
|
||||
// Ensure t is in [0,1]
|
||||
t = std::max(0.0f, std::min(1.0f, t));
|
||||
|
||||
// Convert to hue (0 to 6)
|
||||
float hue = t * 6.0f;
|
||||
|
||||
int phase = static_cast<int>(hue);
|
||||
float fract = hue - phase;
|
||||
|
||||
uint8_t p = static_cast<uint8_t>(255 * (1.0f - fract));
|
||||
uint8_t q = static_cast<uint8_t>(255 * fract);
|
||||
uint8_t full = 255;
|
||||
|
||||
switch (phase) {
|
||||
case 0: return {full, q, 0}; // Red to Yellow
|
||||
case 1: return {p, full, 0}; // Yellow to Green
|
||||
case 2: return {0, full, q}; // Green to Cyan
|
||||
case 3: return {0, p, full}; // Cyan to Blue
|
||||
case 4: return {q, 0, full}; // Blue to Magenta
|
||||
case 5: return {full, 0, p}; // Magenta to Red
|
||||
default: return {full, 0, 0}; // Fallback (red)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -29,8 +29,6 @@ enum class ColorScaleSpecial {
|
||||
BadPixel
|
||||
};
|
||||
|
||||
rgb rainbowColor(float t);
|
||||
|
||||
class ColorScale {
|
||||
const std::vector<rgb> viridis_colormap = {
|
||||
{68, 1, 84}, {71, 44, 123}, {59, 81, 139}, {44, 113, 142}, {33, 144, 141},
|
||||
@@ -55,10 +53,20 @@ class ColorScale {
|
||||
rgb bad = {.r = 255, .g = 0, .b = 255}; // Magenta
|
||||
|
||||
static rgb Apply(float input, const std::vector<rgb> &map);
|
||||
|
||||
static constexpr size_t kLutSize = 4096;
|
||||
mutable std::vector<rgb> lut_;
|
||||
|
||||
void CalcLUT() const;
|
||||
|
||||
public:
|
||||
ColorScale();
|
||||
|
||||
void Select(ColorScaleEnum val);
|
||||
[[nodiscard]] rgb Apply(float input, float min = 0.0, float max = 1.0) const;
|
||||
[[nodiscard]] rgb Apply(ColorScaleSpecial input) const;
|
||||
const std::vector<rgb> &LUTData() const;
|
||||
[[nodiscard]] rgb ApplyLUTIndex(size_t idx) const;
|
||||
|
||||
ColorScale &Gap(rgb input);
|
||||
ColorScale &BadPixel(rgb input);
|
||||
|
||||
@@ -592,24 +592,42 @@ void JFJochImage::GeneratePixmap() {
|
||||
if (show_saturation) {
|
||||
sat_color = bad_color;
|
||||
} else
|
||||
sat_color = color_scale.Apply(1.0);
|
||||
sat_color = color_scale.Apply(1.0f);
|
||||
|
||||
// Precompute once
|
||||
const float minv = background;
|
||||
const float maxv = foreground;
|
||||
|
||||
auto lut_data = color_scale.LUTData();
|
||||
const int64_t lutSize = lut_data.size();
|
||||
const float lutScale = static_cast<float>(lutSize - 1);
|
||||
const float invRange = (maxv > minv) ? (1.0f / (maxv - minv)) * lutScale : 0.0f;
|
||||
|
||||
rgb gap_color = color_scale.Apply(ColorScaleSpecial::Gap);
|
||||
for (int y = 0; y < H; ++y) {
|
||||
uchar *scanLine = qimg.scanLine(y); // Get writable pointer to the row
|
||||
uchar *scanLine = qimg.scanLine(y);
|
||||
const float* row = &image_fp[y * W];
|
||||
rgb* out = &image_rgb[y * W];
|
||||
|
||||
for (int x = 0; x < W; ++x) {
|
||||
float fp = image_fp[y * W + x];
|
||||
if (std::isnan(fp))
|
||||
image_rgb[y * W + x] = color_scale.Apply(ColorScaleSpecial::Gap);
|
||||
else if (std::isinf(fp)) {
|
||||
if (std::signbit(fp))
|
||||
image_rgb[y * W + x] = bad_color;
|
||||
else
|
||||
image_rgb[y * W + x] = sat_color;
|
||||
} else
|
||||
image_rgb[y * W + x] = color_scale.Apply(image_fp[y * W + x], background, foreground);
|
||||
scanLine[x * 3 + 0] = image_rgb[y * W + x].r; // Red
|
||||
scanLine[x * 3 + 1] = image_rgb[y * W + x].g; // Green
|
||||
scanLine[x * 3 + 2] = image_rgb[y * W + x].b; // Blue
|
||||
const float fp = row[x];
|
||||
|
||||
rgb c;
|
||||
if (std::isnan(fp)) {
|
||||
c = gap_color;
|
||||
} else if (std::isinf(fp)) {
|
||||
c = std::signbit(fp) ? bad_color : sat_color;
|
||||
} else {
|
||||
auto idx = std::lround((fp - minv) * invRange);
|
||||
if (idx <= 0) idx = 0;
|
||||
else if (idx >= lutSize) idx = lutSize - 1;
|
||||
c = lut_data[idx];
|
||||
}
|
||||
|
||||
out[x] = c;
|
||||
scanLine[x * 3 + 0] = c.r;
|
||||
scanLine[x * 3 + 1] = c.g;
|
||||
scanLine[x * 3 + 2] = c.b;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user