From 868c49ca2cdb0b94867b5d7a5c8f7253731378e0 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Sun, 7 Jun 2026 19:49:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E9=98=B6=E6=A2=AF=E8=89=B2?= =?UTF-8?q?=E9=98=B6=20colorAt+=E9=A2=9C=E8=89=B2=E8=A7=A3=E6=9E=90(alpha?= =?UTF-8?q?=20=E9=87=8F=E7=BA=B2=E6=8C=89=E6=9D=A5=E6=BA=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/CMakeLists.txt | 1 + src/core/model/ColorScale.cpp | 46 +++++++++++++++++++++++++++++++++ src/core/model/ColorScale.hpp | 27 +++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/core/test_color_scale.cpp | 29 +++++++++++++++++++++ 5 files changed, 104 insertions(+) create mode 100644 src/core/model/ColorScale.cpp create mode 100644 src/core/model/ColorScale.hpp create mode 100644 tests/core/test_color_scale.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c8f2086..9a4a31c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2,6 +2,7 @@ find_package(Eigen3 CONFIG REQUIRED) add_library(geopro_core STATIC geo/LocalFrame.cpp + model/ColorScale.cpp ) target_include_directories(geopro_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/core/model/ColorScale.cpp b/src/core/model/ColorScale.cpp new file mode 100644 index 0000000..62eb2ef --- /dev/null +++ b/src/core/model/ColorScale.cpp @@ -0,0 +1,46 @@ +#define _CRT_SECURE_NO_WARNINGS // MSVC: std::sscanf 解析颜色字符串,长度受限输入安全 +#include "model/ColorScale.hpp" +#include +#include +#include +namespace geopro::core { + +static unsigned char clampByte(double v) { + if (v < 0) v = 0; + if (v > 255) v = 255; + return static_cast(v + 0.5); +} + +Rgba parseColor(const std::string& s, AlphaScale alpha) { + if (!s.empty() && s[0] == '#') { + auto hex = [&](int i) { + return static_cast(std::stoi(s.substr(static_cast(i), 2), nullptr, 16)); + }; + return Rgba{hex(1), hex(3), hex(5), 255}; + } + double r = 0, g = 0, b = 0, a = 1; + auto p = s.find('('); + if (p != std::string::npos) { + std::sscanf(s.c_str() + p, "(%lf,%lf,%lf,%lf", &r, &g, &b, &a); + } + unsigned char av = (alpha == AlphaScale::Unit) ? clampByte(a * 255.0) : clampByte(a); + return Rgba{clampByte(r), clampByte(g), clampByte(b), av}; +} + +void ColorScale::addStop(double value, Rgba color) { + stops_.push_back({value, color}); + std::sort(stops_.begin(), stops_.end(), + [](const Stop& x, const Stop& y) { return x.value < y.value; }); +} + +Rgba ColorScale::colorAt(double value) const { + if (std::isnan(value)) return nan_.value_or(Rgba{0, 0, 0, 0}); + if (stops_.empty()) return Rgba{0, 0, 0, 0}; + if (value < stops_.front().value) return under_.value_or(stops_.front().color); + if (value >= stops_.back().value) return over_.value_or(stops_.back().color); + auto it = std::upper_bound(stops_.begin(), stops_.end(), value, + [](double v, const Stop& s) { return v < s.value; }); + return (it == stops_.begin() ? stops_.front() : *(it - 1)).color; +} + +} // namespace geopro::core diff --git a/src/core/model/ColorScale.hpp b/src/core/model/ColorScale.hpp new file mode 100644 index 0000000..c744162 --- /dev/null +++ b/src/core/model/ColorScale.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include +namespace geopro::core { + +struct Rgba { unsigned char r, g, b, a; }; +enum class AlphaScale { Unit, Bit255 }; // rgba alpha 量纲:0-1 或 0-255 + +Rgba parseColor(const std::string& s, AlphaScale alpha = AlphaScale::Bit255); + +// 阶梯色阶:值落 [stop_i, stop_{i+1}) 取 stop_i 的颜色(与平台一致)。 +class ColorScale { +public: + void addStop(double value, Rgba color); // 内部保持按 value 升序 + Rgba colorAt(double value) const; // 含 under/over/NaN 处理 + void setUnder(Rgba c) { under_ = c; } + void setOver(Rgba c) { over_ = c; } + void setNan(Rgba c) { nan_ = c; } + bool empty() const { return stops_.empty(); } +private: + struct Stop { double value; Rgba color; }; + std::vector stops_; + std::optional under_, over_, nan_; +}; + +} // namespace geopro::core diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0174dc6..2533525 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,7 @@ gtest_discover_tests(geopro_tests) target_sources(geopro_tests PRIVATE core/test_local_frame.cpp) target_sources(geopro_tests PRIVATE core/test_model.cpp) +target_sources(geopro_tests PRIVATE core/test_color_scale.cpp) target_link_libraries(geopro_tests PRIVATE geopro_core) add_subdirectory(spike) # spike S3: banded contour 渲染验证 diff --git a/tests/core/test_color_scale.cpp b/tests/core/test_color_scale.cpp new file mode 100644 index 0000000..decdf51 --- /dev/null +++ b/tests/core/test_color_scale.cpp @@ -0,0 +1,29 @@ +#include +#include "model/ColorScale.hpp" +using namespace geopro::core; + +TEST(ColorScale, SteppedLookupTakesLowerStop) { + ColorScale cs; + cs.addStop(0.0, Rgba{0,0,255,255}); + cs.addStop(10.0, Rgba{0,255,0,255}); + cs.addStop(20.0, Rgba{255,0,0,255}); + EXPECT_EQ(cs.colorAt(5.0).g, 0); // [0,10) -> 蓝 + EXPECT_EQ(cs.colorAt(5.0).b, 255); + EXPECT_EQ(cs.colorAt(15.0).r, 0); // [10,20) -> 绿 + EXPECT_EQ(cs.colorAt(15.0).g, 255); +} + +TEST(ColorScale, UnderOverHandling) { + ColorScale cs; cs.addStop(0.0, Rgba{0,0,0,255}); cs.addStop(10.0, Rgba{255,255,255,255}); + cs.setUnder(Rgba{1,2,3,255}); cs.setOver(Rgba{4,5,6,255}); + EXPECT_EQ(cs.colorAt(-1.0).r, 1); // under + EXPECT_EQ(cs.colorAt(99.0).r, 4); // over +} + +TEST(ColorScale, ParseHexAndRgba) { + EXPECT_EQ(parseColor("#0000B3").b, 0xB3); + auto c255 = parseColor("rgba(0,0,170,255)", AlphaScale::Bit255); + EXPECT_EQ(c255.b, 170); EXPECT_EQ(c255.a, 255); + auto c1 = parseColor("rgba(0,0,170,1)", AlphaScale::Unit); + EXPECT_EQ(c1.b, 170); EXPECT_EQ(c1.a, 255); // 0-1 的 1.0 -> 255 +}