From c020ac4133fbd3b3a6753255f4380e5d00e4ccbf Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 29 Jan 2026 22:25:55 +0100 Subject: [PATCH] jfjoch_viewer: Improve performance of image coloring --- common/ColorScale.cpp | 75 ++++++++++++++--------------- common/ColorScale.h | 12 ++++- viewer/image_viewer/JFJochImage.cpp | 48 ++++++++++++------ 3 files changed, 78 insertions(+), 57 deletions(-) diff --git a/common/ColorScale.cpp b/common/ColorScale.cpp index 910100ce..9747afac 100644 --- a/common/ColorScale.cpp +++ b/common/ColorScale.cpp @@ -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 &ColorScale::LUTData() const { + return lut_; +} + +void ColorScale::CalcLUT() const { + const std::vector* 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(i) / static_cast(kLutSize - 1); + lut_[i] = Apply(f, *map); + } } rgb ColorScale::Apply(float input, const std::vector &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(f * static_cast(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(hue); - float fract = hue - phase; - - uint8_t p = static_cast(255 * (1.0f - fract)); - uint8_t q = static_cast(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) - } -} diff --git a/common/ColorScale.h b/common/ColorScale.h index f94bfa2e..dcd7995b 100644 --- a/common/ColorScale.h +++ b/common/ColorScale.h @@ -29,8 +29,6 @@ enum class ColorScaleSpecial { BadPixel }; -rgb rainbowColor(float t); - class ColorScale { const std::vector 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 &map); + + static constexpr size_t kLutSize = 4096; + mutable std::vector 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 &LUTData() const; + [[nodiscard]] rgb ApplyLUTIndex(size_t idx) const; ColorScale &Gap(rgb input); ColorScale &BadPixel(rgb input); diff --git a/viewer/image_viewer/JFJochImage.cpp b/viewer/image_viewer/JFJochImage.cpp index c433d5ef..8c932a31 100644 --- a/viewer/image_viewer/JFJochImage.cpp +++ b/viewer/image_viewer/JFJochImage.cpp @@ -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(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; } }