mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 13:30:06 +00:00
Color table implementation
This commit is contained in:
parent
6c25ce4230
commit
d734bc6a0a
12 changed files with 437 additions and 5 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -7,3 +7,6 @@
|
|||
[submodule "test/data"]
|
||||
path = test/data
|
||||
url = ../supercell-wx-test-data
|
||||
[submodule "external/hsluv-c"]
|
||||
path = external/hsluv-c
|
||||
url = https://github.com/hsluv/hsluv-c.git
|
||||
|
|
|
|||
7
external/CMakeLists.txt
vendored
7
external/CMakeLists.txt
vendored
|
|
@ -1,4 +1,11 @@
|
|||
cmake_minimum_required(VERSION 3.11)
|
||||
set(PROJECT_NAME scwx-external)
|
||||
|
||||
set_property(DIRECTORY
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
hsluv-c.cmake
|
||||
mapbox-gl-native.cmake)
|
||||
|
||||
include(hsluv-c.cmake)
|
||||
include(mapbox-gl-native.cmake)
|
||||
|
|
|
|||
1
external/hsluv-c
vendored
Submodule
1
external/hsluv-c
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 59539e04a6fa648935cbe57c2104041f23136c4a
|
||||
9
external/hsluv-c.cmake
vendored
Normal file
9
external/hsluv-c.cmake
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
cmake_minimum_required(VERSION 3.11)
|
||||
set(PROJECT_NAME scwx-hsluv-c)
|
||||
|
||||
set(HSLUV_C_TESTS OFF)
|
||||
add_subdirectory(hsluv-c)
|
||||
|
||||
set(HSLUV_C_INCLUDE_DIR ${hsluv-c_SOURCE_DIR}/src PARENT_SCOPE)
|
||||
|
||||
set_target_properties(hsluv-c PROPERTIES FOLDER hsluv)
|
||||
|
|
@ -1,2 +1,8 @@
|
|||
cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
set_property(DIRECTORY
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
test.cmake)
|
||||
|
||||
include(test.cmake)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 40f68fe08977e5168679ce0419834312a6369205
|
||||
Subproject commit 4bc8b8283fcd9d3ed0bcb3099d36db269209b7df
|
||||
34
test/source/scwx/common/color_table.test.cpp
Normal file
34
test/source/scwx/common/color_table.test.cpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#include <scwx/common/color_table.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace common
|
||||
{
|
||||
|
||||
TEST(color_table, reflectivity)
|
||||
{
|
||||
std::string filename(std::string(SCWX_TEST_DATA_DIR) +
|
||||
"/colors/reflectivity.pal");
|
||||
|
||||
std::shared_ptr<ColorTable> ct = ColorTable::Load(filename);
|
||||
|
||||
EXPECT_EQ(ct->Color(5), boost::gil::rgba8_pixel_t(164, 164, 255, 255));
|
||||
EXPECT_EQ(ct->Color(10), boost::gil::rgba8_pixel_t(164, 164, 255, 255));
|
||||
EXPECT_EQ(ct->Color(20), boost::gil::rgba8_pixel_t(64, 128, 255, 255));
|
||||
EXPECT_EQ(ct->Color(30), boost::gil::rgba8_pixel_t(0, 255, 0, 255));
|
||||
EXPECT_EQ(ct->Color(32), boost::gil::rgba8_pixel_t(0, 230, 0, 255));
|
||||
EXPECT_EQ(ct->Color(35), boost::gil::rgba8_pixel_t(0, 192, 0, 255));
|
||||
EXPECT_EQ(ct->Color(40), boost::gil::rgba8_pixel_t(255, 255, 0, 255));
|
||||
EXPECT_EQ(ct->Color(50), boost::gil::rgba8_pixel_t(255, 0, 0, 255));
|
||||
EXPECT_EQ(ct->Color(55), boost::gil::rgba8_pixel_t(208, 0, 0, 255));
|
||||
EXPECT_EQ(ct->Color(60), boost::gil::rgba8_pixel_t(255, 0, 255, 255));
|
||||
EXPECT_EQ(ct->Color(65), boost::gil::rgba8_pixel_t(192, 0, 192, 255));
|
||||
EXPECT_EQ(ct->Color(70), boost::gil::rgba8_pixel_t(255, 255, 255, 255));
|
||||
EXPECT_EQ(ct->Color(80), boost::gil::rgba8_pixel_t(128, 128, 128, 255));
|
||||
EXPECT_EQ(ct->Color(85), boost::gil::rgba8_pixel_t(128, 128, 128, 255));
|
||||
}
|
||||
|
||||
} // namespace common
|
||||
} // namespace scwx
|
||||
|
|
@ -8,15 +8,18 @@ find_package(BZip2)
|
|||
find_package(GTest)
|
||||
|
||||
set(SRC_MAIN source/scwx/wxtest.cpp)
|
||||
set(SRC_COMMON_TESTS source/scwx/common/color_table.test.cpp)
|
||||
set(SRC_UTIL_TESTS source/scwx/util/rangebuf.test.cpp
|
||||
source/scwx/util/vectorbuf.test.cpp)
|
||||
set(SRC_WSR88D_TESTS source/scwx/wsr88d/ar2v_file.test.cpp)
|
||||
|
||||
add_executable(wxtest ${SRC_MAIN}
|
||||
${SRC_COMMON_TESTS}
|
||||
${SRC_UTIL_TESTS}
|
||||
${SRC_WSR88D_TESTS})
|
||||
|
||||
source_group("Source Files\\main" FILES ${SRC_MAIN})
|
||||
source_group("Source Files\\common" FILES ${SRC_COMMON_TESTS})
|
||||
source_group("Source Files\\util" FILES ${SRC_UTIL_TESTS})
|
||||
source_group("Source Files\\wsr88d" FILES ${SRC_WSR88D_TESTS})
|
||||
|
||||
|
|
@ -38,6 +41,7 @@ target_link_libraries(wxtest Boost::iostreams
|
|||
Boost::log
|
||||
BZip2::BZip2
|
||||
GTest::gtest
|
||||
hsluv-c
|
||||
wxdata)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
|||
48
wxdata/include/scwx/common/color_table.hpp
Normal file
48
wxdata/include/scwx/common/color_table.hpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/gil.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace common
|
||||
{
|
||||
|
||||
class ColorTableImpl;
|
||||
|
||||
/**
|
||||
* @brief Color Table
|
||||
*
|
||||
* Implementation based on:
|
||||
* Color Table File Specification
|
||||
* Mike Gibson
|
||||
* Gibson Ridge Software, LLC. Used with permission.
|
||||
* http://www.grlevelx.com/manuals/color_tables/files_color_table.htm
|
||||
*/
|
||||
class ColorTable
|
||||
{
|
||||
public:
|
||||
explicit ColorTable();
|
||||
~ColorTable();
|
||||
|
||||
ColorTable(const ColorTable&) = delete;
|
||||
ColorTable& operator=(const ColorTable&) = delete;
|
||||
|
||||
ColorTable(ColorTable&&) noexcept;
|
||||
ColorTable& operator=(ColorTable&&) noexcept;
|
||||
|
||||
boost::gil::rgba8_pixel_t Color(float value) const;
|
||||
|
||||
static std::shared_ptr<ColorTable> Load(const std::string& filename);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ColorTableImpl> p;
|
||||
|
||||
void ProcessLine(const std::vector<std::string>& tokenList);
|
||||
};
|
||||
|
||||
} // namespace common
|
||||
} // namespace scwx
|
||||
313
wxdata/source/scwx/common/color_table.cpp
Normal file
313
wxdata/source/scwx/common/color_table.cpp
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
#include <scwx/common/color_table.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/gil.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <hsluv.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace common
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ {"[scwx::common::color_table] "};
|
||||
|
||||
enum class ColorMode
|
||||
{
|
||||
RGBA,
|
||||
HSLuv
|
||||
};
|
||||
|
||||
static boost::gil::rgba8_pixel_t
|
||||
ParseColor(const std::vector<std::string>& tokenList,
|
||||
size_t startIndex,
|
||||
ColorMode colorMode,
|
||||
bool hasAlpha = true);
|
||||
template<typename T>
|
||||
T RoundChannel(double value);
|
||||
template<typename T>
|
||||
T StringToDecimal(const std::string& str);
|
||||
|
||||
class ColorTableImpl
|
||||
{
|
||||
public:
|
||||
explicit ColorTableImpl() :
|
||||
product_ {},
|
||||
units_ {},
|
||||
scale_ {1.0f},
|
||||
offset_ {0.0f},
|
||||
step_ {10},
|
||||
rfColor_ {0, 0, 0, 0},
|
||||
colorMode_ {ColorMode::RGBA},
|
||||
colorMap_ {} {};
|
||||
~ColorTableImpl() = default;
|
||||
|
||||
std::string product_;
|
||||
std::string units_;
|
||||
float scale_;
|
||||
float offset_;
|
||||
long step_;
|
||||
boost::gil::rgba8_pixel_t rfColor_;
|
||||
ColorMode colorMode_;
|
||||
|
||||
std::map<float,
|
||||
std::pair<boost::gil::rgba8_pixel_t,
|
||||
std::optional<boost::gil::rgba8_pixel_t>>>
|
||||
colorMap_;
|
||||
};
|
||||
|
||||
ColorTable::ColorTable() : p(std::make_unique<ColorTableImpl>()) {}
|
||||
ColorTable::~ColorTable() = default;
|
||||
|
||||
ColorTable::ColorTable(ColorTable&&) noexcept = default;
|
||||
ColorTable& ColorTable::operator=(ColorTable&&) noexcept = default;
|
||||
|
||||
boost::gil::rgba8_pixel_t ColorTable::Color(float value) const
|
||||
{
|
||||
boost::gil::rgba8_pixel_t color;
|
||||
bool found = false;
|
||||
|
||||
value = value * p->scale_ + p->offset_;
|
||||
|
||||
auto prev = p->colorMap_.cbegin();
|
||||
for (auto it = p->colorMap_.cbegin(); it != p->colorMap_.cend(); ++it)
|
||||
{
|
||||
if (value < it->first)
|
||||
{
|
||||
if (it == p->colorMap_.cbegin())
|
||||
{
|
||||
color = it->second.first;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interpolate
|
||||
float key1 = prev->first;
|
||||
float key2 = it->first;
|
||||
|
||||
boost::gil::rgba8_pixel_t color1 = prev->second.first;
|
||||
boost::gil::rgba8_pixel_t color2 = (prev->second.second) ?
|
||||
prev->second.second.value() :
|
||||
it->second.first;
|
||||
|
||||
float t = (value - key1) / (key2 - key1);
|
||||
color[0] =
|
||||
RoundChannel<uint8_t>(std::lerp(color1[0], color2[0], t));
|
||||
color[1] =
|
||||
RoundChannel<uint8_t>(std::lerp(color1[1], color2[1], t));
|
||||
color[2] =
|
||||
RoundChannel<uint8_t>(std::lerp(color1[2], color2[2], t));
|
||||
color[3] =
|
||||
RoundChannel<uint8_t>(std::lerp(color1[3], color2[3], t));
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
prev = it;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
color = prev->second.first;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
std::shared_ptr<ColorTable> ColorTable::Load(const std::string& filename)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< logPrefix_ << "Loading color table: " << filename;
|
||||
|
||||
std::shared_ptr<ColorTable> p = std::make_shared<ColorTable>();
|
||||
|
||||
std::ifstream f(filename, std::ios_base::in);
|
||||
|
||||
std::string line;
|
||||
while (std::getline(f, line))
|
||||
{
|
||||
std::string token;
|
||||
std::istringstream tokens(line);
|
||||
std::vector<std::string> tokenList;
|
||||
|
||||
while (tokens >> token)
|
||||
{
|
||||
if (token.find(';') != std::string::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
tokenList.push_back(std::move(token));
|
||||
}
|
||||
|
||||
if (tokenList.size() >= 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
p->ProcessLine(tokenList);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< logPrefix_ << "Could not parse line: " << line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void ColorTable::ProcessLine(const std::vector<std::string>& tokenList)
|
||||
{
|
||||
if (tokenList[0] == "Product:")
|
||||
{
|
||||
// Product: string
|
||||
p->product_ = tokenList[1];
|
||||
}
|
||||
else if (tokenList[0] == "Units:")
|
||||
{
|
||||
// Units: string
|
||||
p->units_ = tokenList[1];
|
||||
}
|
||||
else if (tokenList[0] == "Scale:")
|
||||
{
|
||||
// Scale: float
|
||||
p->scale_ = std::stof(tokenList[1]);
|
||||
}
|
||||
else if (tokenList[0] == "Offset:")
|
||||
{
|
||||
// Offset: float
|
||||
p->offset_ = std::stof(tokenList[1]);
|
||||
}
|
||||
else if (tokenList[0] == "Step:")
|
||||
{
|
||||
// Step: float
|
||||
p->step_ = std::stof(tokenList[1]);
|
||||
}
|
||||
else if (tokenList[0] == "RF")
|
||||
{
|
||||
// RF: R G B [A]
|
||||
p->rfColor_ = ParseColor(tokenList, 1, p->colorMode_);
|
||||
}
|
||||
else if (tokenList[0] == "Color:")
|
||||
{
|
||||
// Color: value R G B [R G B]
|
||||
float key = std::stof(tokenList[1]);
|
||||
|
||||
boost::gil::rgba8_pixel_t color1 =
|
||||
ParseColor(tokenList, 2, p->colorMode_, false);
|
||||
std::optional<boost::gil::rgba8_pixel_t> color2;
|
||||
|
||||
if (tokenList.size() >= 8)
|
||||
{
|
||||
color2 = ParseColor(tokenList, 5, p->colorMode_, false);
|
||||
}
|
||||
|
||||
p->colorMap_[key] = std::make_pair(color1, color2);
|
||||
}
|
||||
else if (tokenList[0] == "Color4:")
|
||||
{
|
||||
// Color4: value R G B A [R G B A]
|
||||
float key = std::stof(tokenList[1]);
|
||||
|
||||
boost::gil::rgba8_pixel_t color1 =
|
||||
ParseColor(tokenList, 2, p->colorMode_);
|
||||
std::optional<boost::gil::rgba8_pixel_t> color2;
|
||||
|
||||
if (tokenList.size() >= 10)
|
||||
{
|
||||
color2 = ParseColor(tokenList, 6, p->colorMode_);
|
||||
}
|
||||
|
||||
p->colorMap_[key] = std::make_pair(color1, color2);
|
||||
}
|
||||
else if (tokenList[0] == "SolidColor:")
|
||||
{
|
||||
// SolidColor: value R G B
|
||||
float key = std::stof(tokenList[1]);
|
||||
|
||||
boost::gil::rgba8_pixel_t color1 =
|
||||
ParseColor(tokenList, 2, p->colorMode_, false);
|
||||
|
||||
p->colorMap_[key] = std::make_pair(color1, color1);
|
||||
}
|
||||
else if (tokenList[0] == "SolidColor4:")
|
||||
{
|
||||
// SolidColor4: value R G B A
|
||||
float key = std::stof(tokenList[1]);
|
||||
|
||||
boost::gil::rgba8_pixel_t color1 =
|
||||
ParseColor(tokenList, 2, p->colorMode_);
|
||||
|
||||
p->colorMap_[key] = std::make_pair(color1, color1);
|
||||
}
|
||||
}
|
||||
|
||||
static boost::gil::rgba8_pixel_t
|
||||
ParseColor(const std::vector<std::string>& tokenList,
|
||||
size_t startIndex,
|
||||
ColorMode colorMode,
|
||||
bool hasAlpha)
|
||||
{
|
||||
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a = 255;
|
||||
|
||||
if (colorMode == ColorMode::RGBA)
|
||||
{
|
||||
r = StringToDecimal<uint8_t>(tokenList[startIndex + 0]);
|
||||
g = StringToDecimal<uint8_t>(tokenList[startIndex + 1]);
|
||||
b = StringToDecimal<uint8_t>(tokenList[startIndex + 2]);
|
||||
|
||||
if (hasAlpha && tokenList.size() >= startIndex + 4)
|
||||
{
|
||||
a = StringToDecimal<uint8_t>(tokenList[startIndex + 3]);
|
||||
}
|
||||
}
|
||||
else // if (colorMode == ColorMode::HSLuv)
|
||||
{
|
||||
double h = std::stod(tokenList[startIndex + 0]);
|
||||
double s = std::stod(tokenList[startIndex + 1]);
|
||||
double l = std::stod(tokenList[startIndex + 2]);
|
||||
double dr;
|
||||
double dg;
|
||||
double db;
|
||||
|
||||
hsluv2rgb(h, s, l, &dr, &dg, &db);
|
||||
|
||||
r = RoundChannel<uint8_t>(dr * 255.0);
|
||||
g = RoundChannel<uint8_t>(dg * 255.0);
|
||||
b = RoundChannel<uint8_t>(db * 255.0);
|
||||
}
|
||||
|
||||
return boost::gil::rgba8_pixel_t {r, g, b, a};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T RoundChannel(double value)
|
||||
{
|
||||
return std::clamp<int>(std::lround(value),
|
||||
std::numeric_limits<T>::min(),
|
||||
std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T StringToDecimal(const std::string& str)
|
||||
{
|
||||
return std::clamp<int>(std::stoi(str),
|
||||
std::numeric_limits<T>::min(),
|
||||
std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
} // namespace common
|
||||
} // namespace scwx
|
||||
|
|
@ -38,7 +38,7 @@ public:
|
|||
void HandleMessage(std::shared_ptr<rda::Message>& message);
|
||||
void LoadLDMRecords(std::ifstream& f);
|
||||
void ParseLDMRecords();
|
||||
void ProcessRadarData(std::shared_ptr<rda::DigitalRadarData>& message);
|
||||
void ProcessRadarData(std::shared_ptr<rda::DigitalRadarData> message);
|
||||
void ProcessVcpData();
|
||||
|
||||
std::string tapeFilename_;
|
||||
|
|
@ -245,7 +245,7 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr<rda::Message>& message)
|
|||
}
|
||||
|
||||
void Ar2vFileImpl::ProcessRadarData(
|
||||
std::shared_ptr<rda::DigitalRadarData>& message)
|
||||
std::shared_ptr<rda::DigitalRadarData> message)
|
||||
{
|
||||
uint16_t azimuthIndex = message->azimuth_number() - 1;
|
||||
uint16_t elevationIndex = message->elevation_number() - 1;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ project(scwx-data)
|
|||
|
||||
find_package(Boost)
|
||||
|
||||
set(HDR_COMMON include/scwx/common/color_table.hpp)
|
||||
set(SRC_COMMON source/scwx/common/color_table.cpp)
|
||||
set(HDR_UTIL include/scwx/util/rangebuf.hpp
|
||||
include/scwx/util/vectorbuf.hpp)
|
||||
set(SRC_UTIL source/scwx/util/rangebuf.cpp
|
||||
|
|
@ -28,13 +30,17 @@ set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_map.cpp
|
|||
source/scwx/wsr88d/rda/rda_status_data.cpp
|
||||
source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp)
|
||||
|
||||
add_library(wxdata OBJECT ${HDR_UTIL}
|
||||
add_library(wxdata OBJECT ${HDR_COMMON}
|
||||
${SRC_COMMON}
|
||||
${HDR_UTIL}
|
||||
${SRC_UTIL}
|
||||
${HDR_WSR88D}
|
||||
${SRC_WSR88D}
|
||||
${HDR_WSR88D_RDA}
|
||||
${SRC_WSR88D_RDA})
|
||||
|
||||
source_group("Header Files\\common" FILES ${HDR_COMMON})
|
||||
source_group("Source Files\\common" FILES ${SRC_COMMON})
|
||||
source_group("Header Files\\util" FILES ${HDR_UTIL})
|
||||
source_group("Source Files\\util" FILES ${SRC_UTIL})
|
||||
source_group("Header Files\\wsr88d" FILES ${HDR_WSR88D})
|
||||
|
|
@ -43,6 +49,7 @@ source_group("Header Files\\wsr88d\\rda" FILES ${HDR_WSR88D_RDA})
|
|||
source_group("Source Files\\wsr88d\\rda" FILES ${SRC_WSR88D_RDA})
|
||||
|
||||
target_include_directories(wxdata PRIVATE ${Boost_INCLUDE_DIR}
|
||||
${HSLUV_C_INCLUDE_DIR}
|
||||
${scwx-data_SOURCE_DIR}/include
|
||||
${scwx-data_SOURCE_DIR}/source)
|
||||
target_include_directories(wxdata INTERFACE ${scwx-data_SOURCE_DIR}/include)
|
||||
|
|
@ -51,6 +58,6 @@ if(MSVC)
|
|||
target_compile_options(wxdata PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
set_target_properties(wxdata PROPERTIES CXX_STANDARD 17
|
||||
set_target_properties(wxdata PROPERTIES CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS OFF)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue