调色性能优化 + 新增色域方块拾色 + 取值胶囊高对比配色 + 数据点标记修复
1. 修复自定义颜色滑动卡顿(VERY_BIG_DATA 全信号可见时 36ms/步 → 约 12ms/步)
- 根因:场景缓存的内容指纹把逐信号的颜色/不透明度/线宽/模式都算了进去,
改一次颜色就整幅失效,于是所有网格的波形都要重新光栅化。
- 新增"不含外观样式"的几何指纹与样式快照:只有在网格结构、视图几何、信号集合与
文字都不变、仅外观样式变化时,走"局部重画"快路径 —— 只重画颜色变了的那个网格,
再把结果写回场景缓存(下一帧回到纯贴图)。
- 补丁矩形向内对齐到整数像素,避免把属于相邻网格的一行擦掉而留下 1px 接缝;
底色填充按补丁矩形本身进行(大面积不透明填充会走 GDI 快路径,不受 GDI+ 裁剪约束)。
- 该优化对调色板点击、线宽/不透明度滑块、绘制模式切换同样生效。
2. 自定义颜色新增"方块色域图"拾色
- 新增饱和度-明度方块(横轴饱和度、纵轴明度)与纵向色相条,可直接在色域图上取色。
- 与面板内其它控件完全联动:RGB 滑块、十六进制输入、预览色块同源(RGB 为唯一数据源),
并记忆色相,消色差时仍可用色相条换回颜色。
- 色域方块按当前色相预渲染缓存,色相不变时不重复绘制;色域内用十字光标、色相条用纵向光标。
3. 取值胶囊的对比度描边与文本色改为对信号配色线性响应
- 原实现只有一个亮度阈值,文字/描边只有黑、白两套固定取值。现按底色亮度选定亮/暗一侧,
再与底色的互补色按固定比例线性混合(互补色亮度恰为 1−底色亮度),
因此文字与描边的亮度随信号配色线性变化,且任意底色下都保持高对比。
4. 修复光标数据点越界三角的判定与绘制
- 是否改用三角,以"数据点本身是否超出绘制边界"为准,不再因为数值标注被贴边而改变形状。
- 三角方向修正为尖端朝外(此前上越界画下三角、左越界画右三角,方向完全相反),
内三角改按质心收缩,描边不再是歪的;上下越界时三角与显示的数值纵向居中对齐。
This commit is contained in:
1 parent
d3c83237f1
commit
40b12d6e8b
7 files changed
+877
-124
No files matched your search
@@ -11,11 +11,11 @@ namespace jdi
|
||||
|
||||
namespace
|
||||
{
|
||||
const float kPanelWidth = 214.0f; /**< 面板宽度(逻辑像素)。 */
|
||||
const float kPanelWidth = 324.0f; /**< 面板宽度(逻辑像素)。 */
|
||||
const float kPadH = 12.0f; /**< 面板左右内边距。 */
|
||||
const float kPadV = 10.0f; /**< 面板上下内边距。 */
|
||||
const float kTitleHeight = 20.0f; /**< 标题行高度。 */
|
||||
const float kTitleGap = 8.0f; /**< 标题与滑块区间距。 */
|
||||
const float kTitleGap = 8.0f; /**< 标题与内容区间距。 */
|
||||
const float kRowHeight = 26.0f; /**< 单个滑块行高度。 */
|
||||
const float kRowGap = 2.0f; /**< 滑块行间距。 */
|
||||
const float kPreviewGap = 10.0f; /**< 滑块区与预览区间距。 */
|
||||
@@ -25,6 +25,11 @@ namespace jdi
|
||||
const float kValueWidth = 40.0f; /**< 数值文本宽度。 */
|
||||
const float kTrackHeight = 6.0f; /**< 滑块轨道高度。 */
|
||||
const float kCorner = 6.0f; /**< 面板圆角半径。 */
|
||||
const float kGamutSize = 112.0f; /**< 色域方块边长。 */
|
||||
const float kHueWidth = 14.0f; /**< 色相条宽度。 */
|
||||
const float kGamutGap = 10.0f; /**< 色域方块与色相条间距。 */
|
||||
const float kColumnGap = 12.0f; /**< 色相条与滑块列间距。 */
|
||||
const float kGamutBorder = 1.0f; /**< 色域方块/色相条描边宽度。 */
|
||||
|
||||
/** @brief 通道显示名称。 */
|
||||
const wchar_t* kChannelNames[3] = {L"R", L"G", L"B"};
|
||||
@@ -32,6 +37,116 @@ namespace jdi
|
||||
/** @brief 通道滑块填充色。 */
|
||||
const std::uint32_t kChannelColors[3] = {0xFFDC322Fu, 0xFF859900u, 0xFF268BD2u};
|
||||
|
||||
/**
|
||||
* @brief 把数值限制到 0-1。
|
||||
* @param [IN] value 输入值。
|
||||
* @return 限制后的值。
|
||||
*/
|
||||
double Clamp01(double value)
|
||||
{
|
||||
if (value < 0.0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
if (value > 1.0)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HSV 颜色(色相/饱和度/明度,均为 0-1)。
|
||||
*/
|
||||
struct Hsv
|
||||
{
|
||||
double h = 0.0; /**< 色相。 */
|
||||
double s = 0.0; /**< 饱和度。 */
|
||||
double v = 0.0; /**< 明度。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief RGB(0-1 分量)转 HSV。
|
||||
* @param [IN] rgb RGB 分量。
|
||||
* @return HSV 值(灰色时色相为 0)。
|
||||
*/
|
||||
Hsv ToHsv(const std::array<double, 3>& rgb)
|
||||
{
|
||||
const double maximum = std::max(rgb[0], std::max(rgb[1], rgb[2]));
|
||||
const double minimum = std::min(rgb[0], std::min(rgb[1], rgb[2]));
|
||||
const double delta = maximum - minimum;
|
||||
|
||||
Hsv hsv;
|
||||
hsv.v = maximum;
|
||||
hsv.s = (maximum > 1.0e-9) ? (delta / maximum) : 0.0;
|
||||
if (delta <= 1.0e-9)
|
||||
{
|
||||
hsv.h = 0.0;
|
||||
return hsv;
|
||||
}
|
||||
|
||||
double hue = 0.0;
|
||||
if (maximum == rgb[0])
|
||||
{
|
||||
hue = (rgb[1] - rgb[2]) / delta;
|
||||
}
|
||||
else if (maximum == rgb[1])
|
||||
{
|
||||
hue = 2.0 + (rgb[2] - rgb[0]) / delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
hue = 4.0 + (rgb[0] - rgb[1]) / delta;
|
||||
}
|
||||
hue /= 6.0;
|
||||
if (hue < 0.0)
|
||||
{
|
||||
hue += 1.0;
|
||||
}
|
||||
hsv.h = hue;
|
||||
return hsv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HSV 转 RGB(0-1 分量)。
|
||||
* @param [IN] h 色相(0-1,自动取模)。
|
||||
* @param [IN] s 饱和度(0-1)。
|
||||
* @param [IN] v 明度(0-1)。
|
||||
* @return RGB 分量。
|
||||
*/
|
||||
std::array<double, 3> FromHsv(double h, double s, double v)
|
||||
{
|
||||
h = h - std::floor(h);
|
||||
s = Clamp01(s);
|
||||
v = Clamp01(v);
|
||||
if (s <= 1.0e-9)
|
||||
{
|
||||
return {v, v, v};
|
||||
}
|
||||
|
||||
const double sector = h * 6.0;
|
||||
const int index = static_cast<int>(std::floor(sector)) % 6;
|
||||
const double fraction = sector - std::floor(sector);
|
||||
const double p = v * (1.0 - s);
|
||||
const double q = v * (1.0 - s * fraction);
|
||||
const double t = v * (1.0 - s * (1.0 - fraction));
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return {v, t, p};
|
||||
case 1:
|
||||
return {q, v, p};
|
||||
case 2:
|
||||
return {p, v, t};
|
||||
case 3:
|
||||
return {p, q, v};
|
||||
case 4:
|
||||
return {t, p, v};
|
||||
default:
|
||||
return {v, p, q};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绘制水平滑块(与样式区共用同一实现,保证行为一致)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
@@ -102,9 +217,11 @@ namespace jdi
|
||||
void ColorPickerPopup::BuildLayout(const gd::RectF& bounds, const gd::RectF& anchor)
|
||||
{
|
||||
const float width = kPanelWidth * m_scale;
|
||||
const float height = (kPadV * 2.0f + kTitleHeight + kTitleGap + kRowHeight * 3.0f + kRowGap * 2.0f +
|
||||
kPreviewGap + kPreviewHeight) *
|
||||
m_scale;
|
||||
// 高度:标题 + 内容区。内容区里"色域方块"是最高的控件,
|
||||
// 预览行/色号行落在它与滑块列的下方,仍在其纵向范围内,不再额外叠加高度。
|
||||
const float contentHeight = std::max(kGamutSize, kRowHeight * 3.0f + kRowGap * 2.0f +
|
||||
kPreviewGap + kPreviewHeight);
|
||||
const float height = (kPadV * 2.0f + kTitleHeight + kTitleGap + contentHeight) * m_scale;
|
||||
|
||||
const float gap = 4.0f * m_scale;
|
||||
|
||||
@@ -129,14 +246,26 @@ namespace jdi
|
||||
m_titleRect = gd::RectF(left + kPadH * m_scale, top + kPadV * m_scale,
|
||||
width - kPadH * 2.0f * m_scale, kTitleHeight * m_scale);
|
||||
|
||||
const float trackLeft = left + (kPadH + kLabelWidth + kLabelGap) * m_scale;
|
||||
const float contentTop = m_titleRect.Y + m_titleRect.Height + kTitleGap * m_scale;
|
||||
|
||||
// 左侧:色域方块(横轴饱和度、纵轴明度)+ 色相条。
|
||||
m_gamutRect = gd::RectF(left + kPadH * m_scale, contentTop, kGamutSize * m_scale,
|
||||
kGamutSize * m_scale);
|
||||
m_hueRect = gd::RectF(m_gamutRect.X + m_gamutRect.Width + kGamutGap * m_scale, contentTop,
|
||||
kHueWidth * m_scale, kGamutSize * m_scale);
|
||||
|
||||
// 右侧:R/G/B 滑块 + 数值,紧随色相条之后。
|
||||
const float columnLeft = m_hueRect.X + m_hueRect.Width + kColumnGap * m_scale;
|
||||
const float columnWidth =
|
||||
std::max(80.0f * m_scale, m_rect.X + m_rect.Width - kPadH * m_scale - columnLeft);
|
||||
const float trackLeft = columnLeft + (kLabelWidth + kLabelGap) * m_scale;
|
||||
const float trackWidth =
|
||||
std::max(40.0f, width - (trackLeft - left) - (kPadH + kValueWidth) * m_scale);
|
||||
const float firstTop = m_titleRect.Y + m_titleRect.Height + kTitleGap * m_scale;
|
||||
std::max(24.0f, columnWidth - (kLabelWidth + kLabelGap + kValueWidth) * m_scale);
|
||||
|
||||
for (int index = 0; index < 3; ++index)
|
||||
{
|
||||
const float rowTop = firstTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
|
||||
const float rowTop =
|
||||
contentTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
|
||||
m_tracks[static_cast<std::size_t>(index)] =
|
||||
gd::RectF(trackLeft, rowTop + (kRowHeight * m_scale - kTrackHeight * m_scale) * 0.5f,
|
||||
trackWidth, kTrackHeight * m_scale);
|
||||
@@ -145,10 +274,11 @@ namespace jdi
|
||||
kValueWidth * m_scale - 6.0f * m_scale, kRowHeight * m_scale);
|
||||
}
|
||||
|
||||
const float previewTop = firstTop + 3.0f * (kRowHeight + kRowGap) * m_scale + kPreviewGap * m_scale;
|
||||
const float previewTop =
|
||||
contentTop + (3.0f * (kRowHeight + kRowGap) + kPreviewGap) * m_scale;
|
||||
m_previewRect = gd::RectF(trackLeft, previewTop + 2.0f * m_scale, 52.0f * m_scale, 20.0f * m_scale);
|
||||
m_hexRect = gd::RectF(m_previewRect.X + m_previewRect.Width + 8.0f * m_scale, previewTop + 2.0f * m_scale,
|
||||
96.0f * m_scale, 20.0f * m_scale);
|
||||
m_hexRect = gd::RectF(m_previewRect.X + m_previewRect.Width + 8.0f * m_scale,
|
||||
previewTop + 2.0f * m_scale, 96.0f * m_scale, 20.0f * m_scale);
|
||||
}
|
||||
|
||||
void ColorPickerPopup::Open(const gd::RectF& bounds, const gd::RectF& anchor, std::uint32_t color)
|
||||
@@ -161,6 +291,10 @@ namespace jdi
|
||||
m_changed = false;
|
||||
m_draggingSlider = -1;
|
||||
m_hoverSlider = -1;
|
||||
m_drag = DragTarget::NONE;
|
||||
// 记住初始色相:之后即使把饱和度拖到 0(灰色)色相也不会丢失。
|
||||
m_hue = ToHsv(m_channels).h;
|
||||
InvalidateGamutCache();
|
||||
|
||||
BuildLayout(bounds, anchor);
|
||||
}
|
||||
@@ -175,6 +309,118 @@ namespace jdi
|
||||
m_open = false;
|
||||
m_draggingSlider = -1;
|
||||
m_hoverSlider = -1;
|
||||
m_drag = DragTarget::NONE;
|
||||
// 释放色域方块缓存(它是 GDI+ 对象,必须在 GdiplusShutdown 之前销毁)。
|
||||
// 下次打开时按需重建,代价只有一张小位图的填充。
|
||||
m_gamutCache.reset();
|
||||
m_gamutCacheSize = 0;
|
||||
m_gamutCacheHue = -1.0;
|
||||
}
|
||||
|
||||
double ColorPickerPopup::CurrentHue() const
|
||||
{
|
||||
const Hsv hsv = ToHsv(m_channels);
|
||||
// 灰度/黑色时 HSV 的色相没有定义:沿用记忆色相,避免拖动 RGB 滑块时色相跳变、
|
||||
// 也保证"饱和度归零后仍能靠色相条换回颜色"。
|
||||
return (hsv.s > 1.0e-4) ? hsv.h : m_hue;
|
||||
}
|
||||
|
||||
void ColorPickerPopup::RefreshHueFromChannels()
|
||||
{
|
||||
const Hsv hsv = ToHsv(m_channels);
|
||||
if (hsv.s > 1.0e-4)
|
||||
{
|
||||
m_hue = hsv.h;
|
||||
}
|
||||
}
|
||||
|
||||
void ColorPickerPopup::InvalidateGamutCache()
|
||||
{
|
||||
m_gamutCacheHue = -1.0;
|
||||
}
|
||||
|
||||
bool ColorPickerPopup::EnsureGamutCache()
|
||||
{
|
||||
const int size = std::max(8, static_cast<int>(std::lround(kGamutSize * m_scale)));
|
||||
if (m_gamutCache == nullptr || m_gamutCacheSize != size)
|
||||
{
|
||||
m_gamutCache.reset(new gd::Bitmap(size, size, PixelFormat32bppRGB));
|
||||
m_gamutCacheSize = size;
|
||||
m_gamutCacheHue = -1.0;
|
||||
}
|
||||
|
||||
const double hue = CurrentHue();
|
||||
if (std::fabs(m_gamutCacheHue - hue) < 1.0e-4)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
gd::BitmapData data = {};
|
||||
const gd::Rect lockRect(0, 0, size, size);
|
||||
if (m_gamutCache->LockBits(&lockRect, gd::ImageLockModeWrite, PixelFormat32bppRGB, &data) != gd::Ok)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
unsigned char* base = static_cast<unsigned char*>(data.Scan0);
|
||||
for (int y = 0; y < size; ++y)
|
||||
{
|
||||
// 纵轴明度(上 1 → 下 0),横轴饱和度(左 0 → 右 1)——与常见取色器一致。
|
||||
const double value = 1.0 - (static_cast<double>(y) + 0.5) / static_cast<double>(size);
|
||||
unsigned char* row = (data.Stride >= 0)
|
||||
? (base + static_cast<std::ptrdiff_t>(y) * data.Stride)
|
||||
: (base + static_cast<std::ptrdiff_t>(size - 1 - y) * (-data.Stride));
|
||||
for (int x = 0; x < size; ++x)
|
||||
{
|
||||
const double saturation =
|
||||
(static_cast<double>(x) + 0.5) / static_cast<double>(size);
|
||||
const std::array<double, 3> rgb = FromHsv(hue, saturation, value);
|
||||
row[x * 4 + 0] = static_cast<unsigned char>(ToByte(rgb[2])); // B
|
||||
row[x * 4 + 1] = static_cast<unsigned char>(ToByte(rgb[1])); // G
|
||||
row[x * 4 + 2] = static_cast<unsigned char>(ToByte(rgb[0])); // R
|
||||
row[x * 4 + 3] = 0xFF;
|
||||
}
|
||||
}
|
||||
m_gamutCache->UnlockBits(&data);
|
||||
m_gamutCacheHue = hue;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ColorPickerPopup::ApplyGamut(float x, float y)
|
||||
{
|
||||
if (m_gamutRect.Width <= 0.0f || m_gamutRect.Height <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const double saturation = Clamp01((x - m_gamutRect.X) / m_gamutRect.Width);
|
||||
const double value = 1.0 - Clamp01((y - m_gamutRect.Y) / m_gamutRect.Height);
|
||||
const std::array<double, 3> rgb = FromHsv(CurrentHue(), saturation, value);
|
||||
if (std::fabs(rgb[0] - m_channels[0]) < 1.0e-6 && std::fabs(rgb[1] - m_channels[1]) < 1.0e-6 &&
|
||||
std::fabs(rgb[2] - m_channels[2]) < 1.0e-6)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_channels = rgb;
|
||||
// 色相不变(明度归零时 FromHsv 会给出黑色,但记忆色相仍要保留)。
|
||||
m_changed = true;
|
||||
}
|
||||
|
||||
void ColorPickerPopup::ApplyHue(float y)
|
||||
{
|
||||
if (m_hueRect.Height <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const double hue = Clamp01((y - m_hueRect.Y) / m_hueRect.Height);
|
||||
const Hsv hsv = ToHsv(m_channels);
|
||||
m_hue = hue;
|
||||
const std::array<double, 3> rgb = FromHsv(m_hue, hsv.s, hsv.v);
|
||||
if (std::fabs(rgb[0] - m_channels[0]) < 1.0e-6 && std::fabs(rgb[1] - m_channels[1]) < 1.0e-6 &&
|
||||
std::fabs(rgb[2] - m_channels[2]) < 1.0e-6)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_channels = rgb;
|
||||
m_changed = true;
|
||||
}
|
||||
|
||||
gd::RectF ColorPickerPopup::EditFieldRect() const
|
||||
@@ -292,6 +538,7 @@ namespace jdi
|
||||
m_channels[0] = static_cast<double>((value >> 16) & 0xFFu) / 255.0;
|
||||
m_channels[1] = static_cast<double>((value >> 8) & 0xFFu) / 255.0;
|
||||
m_channels[2] = static_cast<double>(value & 0xFFu) / 255.0;
|
||||
RefreshHueFromChannels();
|
||||
m_changed = true;
|
||||
}
|
||||
}
|
||||
@@ -323,6 +570,7 @@ namespace jdi
|
||||
if (index >= 0 && index < 3)
|
||||
{
|
||||
m_channels[static_cast<std::size_t>(index)] = value / 255.0;
|
||||
RefreshHueFromChannels();
|
||||
m_changed = true;
|
||||
}
|
||||
return true;
|
||||
@@ -417,8 +665,18 @@ namespace jdi
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 拖拽过程中始终保持横向调整光标,避免指针移出轨道后光标跳变。
|
||||
if (m_draggingSlider >= 0 || SliderAtPoint(x, y) >= 0)
|
||||
// 拖拽过程中始终保持对应光标,避免指针移出控件后光标跳变。
|
||||
if (m_drag == DragTarget::GAMUT || UiPointInRect(m_gamutRect, static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_CROSS);
|
||||
}
|
||||
if (m_drag == DragTarget::HUE ||
|
||||
UiPointInRect(m_hueRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZENS);
|
||||
}
|
||||
if (m_drag == DragTarget::SLIDER || m_draggingSlider >= 0 || SliderAtPoint(x, y) >= 0)
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
}
|
||||
@@ -461,6 +719,7 @@ namespace jdi
|
||||
return;
|
||||
}
|
||||
m_channels[slot] = ratio;
|
||||
RefreshHueFromChannels();
|
||||
m_changed = true;
|
||||
}
|
||||
|
||||
@@ -471,10 +730,20 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_draggingSlider >= 0)
|
||||
switch (m_drag)
|
||||
{
|
||||
case DragTarget::GAMUT:
|
||||
ApplyGamut(static_cast<float>(x), static_cast<float>(y));
|
||||
return true;
|
||||
case DragTarget::HUE:
|
||||
ApplyHue(static_cast<float>(y));
|
||||
return true;
|
||||
case DragTarget::SLIDER:
|
||||
ApplySlider(m_draggingSlider, static_cast<float>(x));
|
||||
return true;
|
||||
case DragTarget::NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const int hover = SliderAtPoint(x, y);
|
||||
@@ -523,9 +792,24 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
// 色域方块与色相条:按下即取值,之后拖动持续更新。
|
||||
if (UiPointInRect(m_gamutRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
m_drag = DragTarget::GAMUT;
|
||||
ApplyGamut(static_cast<float>(x), static_cast<float>(y));
|
||||
return true;
|
||||
}
|
||||
if (UiPointInRect(m_hueRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
m_drag = DragTarget::HUE;
|
||||
ApplyHue(static_cast<float>(y));
|
||||
return true;
|
||||
}
|
||||
|
||||
m_draggingSlider = SliderAtPoint(x, y);
|
||||
if (m_draggingSlider >= 0)
|
||||
{
|
||||
m_drag = DragTarget::SLIDER;
|
||||
ApplySlider(m_draggingSlider, static_cast<float>(x));
|
||||
return true;
|
||||
}
|
||||
@@ -534,11 +818,12 @@ namespace jdi
|
||||
|
||||
bool ColorPickerPopup::OnLButtonUp()
|
||||
{
|
||||
if (!m_open || m_draggingSlider < 0)
|
||||
if (!m_open || (m_draggingSlider < 0 && m_drag == DragTarget::NONE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_draggingSlider = -1;
|
||||
m_drag = DragTarget::NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -617,6 +902,54 @@ namespace jdi
|
||||
|
||||
UiText(g, L"自定义颜色", m_fonts->uiSmallBold, m_titleRect, m_theme->textMuted, TextAlign::LEFT);
|
||||
|
||||
// ---- 色域方块(横轴饱和度、纵轴明度)----
|
||||
// 方块按当前色相预渲染到缓存位图里,避免每帧重画上百条渐变;
|
||||
// 色相变化时才重建一次。
|
||||
if (EnsureGamutCache())
|
||||
{
|
||||
g.DrawImage(m_gamutCache.get(), m_gamutRect);
|
||||
}
|
||||
UiDrawRoundRect(g, m_gamutRect, 2.0f * m_scale, m_theme->borderStrong, kGamutBorder);
|
||||
|
||||
// ---- 色相条(纵向 0-1,用 6 段线性渐变近似色相环)----
|
||||
{
|
||||
const Hsv hsv = ToHsv(m_channels);
|
||||
const double saturation = (hsv.s > 1.0e-4) ? hsv.s : 1.0;
|
||||
const double value = (hsv.v > 1.0e-4) ? hsv.v : 1.0;
|
||||
const int bands = std::max(12, static_cast<int>(std::lround(m_hueRect.Height)));
|
||||
for (int index = 0; index < bands; ++index)
|
||||
{
|
||||
const double hue = (static_cast<double>(index) + 0.5) / static_cast<double>(bands);
|
||||
const std::array<double, 3> rgb = FromHsv(hue, 1.0, 1.0);
|
||||
const std::uint32_t band = 0xFF000000u | (ToByte(rgb[0]) << 16) | (ToByte(rgb[1]) << 8) |
|
||||
ToByte(rgb[2]);
|
||||
const float top = m_hueRect.Y + m_hueRect.Height * index / static_cast<float>(bands);
|
||||
const float bottom =
|
||||
m_hueRect.Y + m_hueRect.Height * (index + 1) / static_cast<float>(bands);
|
||||
UiFillRect(g, gd::RectF(m_hueRect.X, top, m_hueRect.Width, bottom - top), band);
|
||||
}
|
||||
UiDrawRoundRect(g, m_hueRect, 2.0f * m_scale, m_theme->borderStrong, kGamutBorder);
|
||||
|
||||
// 控件指示器:色域方块上标出 (S, V),色相条上标出当前色相。
|
||||
// 两者都由 m_channels 反推,因此与 R/G/B 滑块、十六进制输入天然联动。
|
||||
const float markerX = m_gamutRect.X + static_cast<float>(saturation) * m_gamutRect.Width;
|
||||
const float markerY = m_gamutRect.Y + static_cast<float>(1.0 - value) * m_gamutRect.Height;
|
||||
gd::Pen outerPen(ToGdiColor(0xFF1B1B1Bu), 2.0f * m_scale);
|
||||
gd::Pen innerPen(ToGdiColor(0xFFFFFFFFu), 1.4f * m_scale);
|
||||
const float outerRadius = 5.0f * m_scale;
|
||||
g.DrawEllipse(&outerPen, markerX - outerRadius, markerY - outerRadius, outerRadius * 2.0f,
|
||||
outerRadius * 2.0f);
|
||||
const float innerRadius = 3.4f * m_scale;
|
||||
g.DrawEllipse(&innerPen, markerX - innerRadius, markerY - innerRadius, innerRadius * 2.0f,
|
||||
innerRadius * 2.0f);
|
||||
|
||||
const float hueMarkerY = m_hueRect.Y + static_cast<float>(CurrentHue()) * m_hueRect.Height;
|
||||
UiFillRect(g, gd::RectF(m_hueRect.X - 2.0f * m_scale, hueMarkerY - 1.5f * m_scale,
|
||||
m_hueRect.Width + 4.0f * m_scale, 3.0f * m_scale),
|
||||
0xFF1B1B1Bu);
|
||||
UiDrawRoundRect(g, m_hueRect, 2.0f * m_scale, m_theme->borderStrong, kGamutBorder);
|
||||
}
|
||||
|
||||
for (int index = 0; index < 3; ++index)
|
||||
{
|
||||
const std::size_t slot = static_cast<std::size_t>(index);
|
||||
|
||||
Reference in new issue
Block a user