ui(spectrum): полоса главного фильтра — как у слайсов, со своим оттенком

Главный фильтр рисовался иначе, чем слайсовые полосы, причём в CPU-пути
сильнее, чем в GL.

1. Порядок отрисовки. Кромки, несущая и её треугольник у главного шли ПОСЛЕ
   кривой спектра, у слайсов — до неё, а в GL-вьюхе весь блок фильтров идёт
   перед DrawSpectrumCurve. Из-за этого на CPU главная полоса единственная
   лезла поверх сигнала и выглядела жирнее и ярче слайсовых при тех же цвете и
   толщине линий. Блок перенесён к слайсам, до кривой: порядок кадра теперь
   одинаков у обоих путей.

2. Заливка. Была НЕПРОЗРАЧНОЙ, рядом с просвечивающими слайсовыми смотрелась
   плотным блоком. Теперь та же полупрозрачность, что у слайсов; прозрачность
   вынесена в общие SPEC_BAND_ALPHA/SPEC_BAND_ALPHA_TX (VfoOverlay, рядом с
   палитрой слайсов) — раньше это были четыре магических числа по двум вьюхам.
   Осиротевшая FillBandRaw удалена.

3. Цвет. С полупрозрачностью полоса перестала читаться: SpecFilter (34,78,106)
   почти совпадает с нижним цветом фонового градиента спектра (31,79,108), и
   заливка давала не оттенок, а затемнение — над низом градиента выходило
   ровно (32,79,108), то есть фон. Полосе дан свой цвет темы SpecFilterBand:
   тёплый янтарь RGB(255,150,40) на тёмной (фон сине-стальной, тёплое на нём
   читается) и насыщённый зелёный RGB(60,150,60) на светлой. SpecFilter
   остался подложкой подписей AGC — иначе перекрасились бы и они.

4. Слайс B перекрашен из оранжевого в васильковый RGB(97,118,255): с янтарной
   полосой главного фильтра оранжевый стал неразличим. Оттенок выбран по
   свободному месту в круге: заняты 0°(TX), 31°(полоса), 55°(E), 132°(A),
   190°(C), 195°(G), 276°(F), 308°(D); самый широкий промежуток 195..276°,
   середина ≈236°. Палитра одна на всё, поэтому вместе с полосой и несущей
   перекрашиваются буква слайса на спектре и бейдж во флаге VfoOverlay.

Сборка ewsdr (--ws=qt6) и ewsdrd — ОК. На экране не проверено: нужен эфир со
слайсами; цвета правятся одной строкой (SpecFilterBand в AppTheme,
SLICE_COLORS в VfoOverlay).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 23:14:59 +03:00
co-authored by Claude Opus 5
parent d0c82ade73
commit eb4af61fad
4 changed files with 61 additions and 61 deletions
+14 -1
View File
@@ -54,6 +54,12 @@ type
// Спектр — сигнал и маркеры
SpecLine: TColor;
SpecFilter: TColor;
// Заливка полосы главного фильтра. Отдельно от SpecFilter (тот остался
// подложкой подписей AGC): полоса теперь полупрозрачная, как у слайсов, и
// цвет ей нужен СВОЙ — SpecFilter почти совпадает с нижним цветом фонового
// градиента спектра, поэтому такая заливка читалась не оттенком, а
// затемнением.
SpecFilterBand: TColor;
SpecFilterEdge: TColor;
SpecAgcColor: TColor;
SpecAgcHangColor: TColor;
@@ -158,6 +164,10 @@ begin
Result.SpecGrid := TColor($00685624);
Result.SpecLine := TColor($0040FF80);
Result.SpecFilter := TColor($006A4E22);
// Оттенок полосы — тёплый янтарный, из семейства курсора VFO (Amber): фон
// спектра сине-стальной, поэтому янтарь на нём читается как оттенок, а не
// как «потемнее фона». RGB(255,150,40).
Result.SpecFilterBand := TColor($002896FF);
Result.SpecFilterEdge := TColor($0040FFCC);
Result.SpecAgcColor := TColor($0000AAFF);
Result.SpecAgcHangColor := TColor($00FFCC00);
@@ -266,7 +276,10 @@ begin
Result.SpecLabelText := TColor($00404038);
Result.SpecGrid := TColor($00B8B4A4);
Result.SpecLine := TColor($00006400); // тёмно-зелёная линия
Result.SpecFilter := TColor($00C8DCC8); // светло-зелёная полоса фильтра
Result.SpecFilter := TColor($00C8DCC8); // подложка подписей AGC
// Полоса фильтра на светлой теме: насыщённее фона (тот пергаментный), иначе
// при той же полупрозрачности её не видно. RGB(60,150,60).
Result.SpecFilterBand := TColor($003C963C);
Result.SpecFilterEdge := TColor($00407840);
Result.SpecAgcColor := TColor($00904000); // тёмно-оранжевый AGC
Result.SpecAgcHangColor := TColor($00802000);
+31 -57
View File
@@ -185,7 +185,6 @@ type
procedure DrawIMDMarkersRaw(W, H: Integer; DBmax, InvRange: Double);
procedure RawCircle(CX, CY, R: Integer; Color: TColor);
procedure BlendBand(X1, X2, H: Integer; R, G, B, Alpha: Byte);
procedure FillBandRaw(X1, X2, H: Integer; Color: TColor);
procedure CopyGridToSpectrum(W, H: Integer);
procedure DrawADCOverloadRaw(W, H: Integer);
procedure CalcFilterBandX(VfoFreq: Double; W: Integer;
@@ -1168,41 +1167,6 @@ end;
// Canvas.FillRect для полосы фильтра в DrawSpectrum: полоса рисуется в
// raw-фазе (между memcpy сетки и градиентом), а FillRect дёргал бы Canvas
// и форсил лишнюю синхронизацию битмапа на Qt6.
procedure TSpectrumView.FillBandRaw(X1, X2, H: Integer; Color: TColor);
var
Y, W: Integer;
Row: PLongWord;
Px: LongWord;
R, G, B: Byte;
begin
if FSpectrumBitmap = nil then Exit;
W := FSpectrumBitmap.Width;
if X1 < 0 then X1 := 0;
if X2 > W then X2 := W;
if X2 <= X1 then Exit;
R := Color and $FF; G := (Color shr 8) and $FF; B := (Color shr 16) and $FF;
{$IFDEF DARWIN}
// ARGB в памяти (см. BlendBand): байт 0 = альфа, дальше R,G,B.
Px := LongWord($FF) or (LongWord(R) shl 8) or (LongWord(G) shl 16) or
(LongWord(B) shl 24);
{$ELSE}
// BGRA в памяти: байты B,G,R,A.
Px := LongWord(B) or (LongWord(G) shl 8) or (LongWord(R) shl 16) or $FF000000;
{$ENDIF}
FSpectrumBitmap.BeginUpdate(False);
try
for Y := 0 to H - 1 do
begin
Row := PLongWord(FSpectrumBitmap.ScanLine[Y]);
if Row = nil then Continue;
Inc(Row, X1);
FillDWord(Row^, X2 - X1, Px);
end;
finally
FSpectrumBitmap.EndUpdate(False);
end;
end;
// Маркер фильтра каждого слайса (B+) на спектре — полупрозрачная полоса
// пропускания + края + центральная несущая. Янтарный цвет, как в GL-вьюхе
// (SpectrumViewOpengl.DrawSliceFilterMarkers). Полосы (raw ScanLine-блендинг)
@@ -1224,7 +1188,7 @@ begin
CalcSliceBandX(O, W, SX1, SX2, SVfoX);
if SX2 > SX1 then
BlendBand(SX1, SX2, H, Clr and $FF, (Clr shr 8) and $FF, (Clr shr 16) and $FF,
IfThen(O.TxActive, 100, 60));
IfThen(O.TxActive, SPEC_BAND_ALPHA_TX, SPEC_BAND_ALPHA));
end;
end;
@@ -1789,7 +1753,14 @@ begin
end else
if X2 > X1 then BlendBand(X1, X2, H, $E0, $30, $30, 100);
end else
if X2 > X1 then FillBandRaw(X1, X2, H, FTheme.SpecFilter);
// Полупрозрачная заливка тем же способом, что у слайсов (SPEC_BAND_ALPHA):
// раньше главная полоса заливалась НЕПРОЗРАЧНО и выглядела плотным блоком
// рядом с просвечивающими слайсовыми. Цвет — SpecFilterBand (свой, не
// подложка подписей AGC): см. комментарий в AppTheme.
if X2 > X1 then
BlendBand(X1, X2, H, FTheme.SpecFilterBand and $FF,
(FTheme.SpecFilterBand shr 8) and $FF,
(FTheme.SpecFilterBand shr 16) and $FF, SPEC_BAND_ALPHA);
// Полосы фильтра слайсов (B+) — под градиентом/кривой, как в GL-вьюхе.
DrawSliceFilterBands(W, H);
@@ -1800,16 +1771,34 @@ begin
DrawSpectrumGradient(FSpPts, W, H);
// ★ Кромки, несущая и буква ГЛАВНОГО фильтра — здесь, ДО кривой спектра,
// ровно как у слайсов ниже и как в GL-вьюхе (там весь блок фильтров идёт
// перед DrawSpectrumCurve). Раньше этот кусок стоял ПОСЛЕ кривой, и на
// CPU-пути главный фильтр единственный лез поверх сигнала — с виду толще и
// ярче слайсовых, хотя цвета и толщина те же.
RawVLine(X1, 0, H - 1, FTheme.SpecFilterEdge);
RawVLine(X2, 0, H - 1, FTheme.SpecFilterEdge);
// Буква главного флага (A) на его полосе — только когда есть слайсы,
// иначе одиночный приём не засоряем. Букву запоминаем: под неё ниже
// иначе одиночный приём не засоряем. Букву запоминаем: под неё
// подстраиваются вертикаль несущей и её треугольник.
MainLetter := #0;
if (FSliceOverlays <> nil) and (FSliceOverlays.Count > 0) and
(FActiveVfo = 0) and Assigned(FVfoOverlay) then
begin
MainLetter := FVfoOverlay.SliceLetter;
DrawBandLetterRaw(X1, X2, MainLetter, SliceColor(MainLetter));
CarrY := CarrierTopY(X1, X2, VfoX, MainLetter);
RawVLine(VfoX, CarrY, H - 13, FTheme.SpecVfoCursor, 2);
RawTriangleDown(VfoX, CarrY, 5, 8, FTheme.SpecVfoCursor);
if FTXOverlay and (FTXVfoIndex <> FActiveVfo) then
begin
RawVLine(TXX1, 0, H - 1, TColor($002030E0));
RawVLine(TXX2, 0, H - 1, TColor($002030E0));
// Буква нарисована на RX-полосе (X1..X2) — с ней и сверяемся.
CarrY := CarrierTopY(X1, X2, TXVfoX, MainLetter);
RawVLine(TXVfoX, CarrY, H - 13, TColor($002030E0), 2);
RawTriangleDown(TXVfoX, CarrY, 5, 8, TColor($002030E0));
end;
if MainLetter <> #0 then
DrawBandLetterRaw(X1, X2, MainLetter, SliceColor(MainLetter));
// Края/несущие/буквы слайсов (полосы уже нарисованы выше).
DrawSliceFilterLinesRaw(W, H);
@@ -1845,22 +1834,7 @@ begin
// Линия спектра
RawCurve(W, FTheme.SpecLine);
// Края фильтра + VFO
RawVLine(X1, 0, H - 1, FTheme.SpecFilterEdge);
RawVLine(X2, 0, H - 1, FTheme.SpecFilterEdge);
// Несущая и её треугольник — под буквой полосы, если она там есть.
CarrY := CarrierTopY(X1, X2, VfoX, MainLetter);
RawVLine(VfoX, CarrY, H - 13, FTheme.SpecVfoCursor, 2);
RawTriangleDown(VfoX, CarrY, 5, 8, FTheme.SpecVfoCursor);
if FTXOverlay and (FTXVfoIndex <> FActiveVfo) then
begin
RawVLine(TXX1, 0, H - 1, TColor($002030E0));
RawVLine(TXX2, 0, H - 1, TColor($002030E0));
// Буква нарисована на RX-полосе (X1..X2) — с ней и сверяемся.
CarrY := CarrierTopY(X1, X2, TXVfoX, MainLetter);
RawVLine(TXVfoX, CarrY, H - 13, TColor($002030E0), 2);
RawTriangleDown(TXVfoX, CarrY, 5, 8, TColor($002030E0));
end;
// (кромки/несущая главного фильтра нарисованы выше — до кривой)
if FMarkerActive then DrawMarkerLineRaw(W, H);
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkersRaw(W, H);
+6 -2
View File
@@ -622,7 +622,10 @@ begin
else if X2 > X1 then DrawRect(X1, 0, X2, H, CLR_TX_BAND, 100 / 255);
end
else if X2 > X1 then
DrawRect(X1, 0, X2, H, FTheme.SpecFilter, 1);
// Как у слайсов: полупрозрачная заливка (раньше здесь была alpha=1, и
// главная полоса выглядела плотным блоком рядом с просвечивающими).
// Цвет — SpecFilterBand, свой у полосы (см. AppTheme).
DrawRect(X1, 0, X2, H, FTheme.SpecFilterBand, SPEC_BAND_ALPHA / 255);
// Буква главного флага (A) на его полосе — только когда есть слайсы. Саму
// букву рисуем ниже, но знать о ней надо здесь: под неё уходит начало
@@ -669,7 +672,8 @@ begin
else Clr := SliceColor(O.SliceLetter);
CalcSliceBandX(O, W, SX1, SX2, SVfoX);
if SX2 > SX1 then
DrawRect(SX1, 0, SX2, H, Clr, IfThen(O.TxActive, 100, 60) / 255);
DrawRect(SX1, 0, SX2, H, Clr,
IfThen(O.TxActive, SPEC_BAND_ALPHA_TX, SPEC_BAND_ALPHA) / 255);
DrawLine(SX1, 0, SX1, H, Clr, 1, False);
DrawLine(SX2, 0, SX2, H, Clr, 1, False);
// Несущая — под буквой, если она приходится на неё (AM/FM/DSB).
+10 -1
View File
@@ -38,7 +38,11 @@ const
// бейджа-карточки во флаге и для буквы на полосе фильтра в спектре.
SLICE_COLORS: array[0..7] of TColor = (
TColor($0060E040), // A — зелёный
TColor($0010A8FF), // B оранжевый
// B был оранжевым и спорил с янтарной полосой главного фильтра
// (AppTheme.SpecFilterBand). Васильковый выбран по свободному месту в круге
// оттенков: занято 0°(TX)/31°(полоса)/38/55/132/190/195/276/308, самый
// широкий незанятый промежуток — 195..276°, середина ≈236°. RGB(97,118,255).
TColor($00FF7661), // B — васильковый (indigo)
TColor($00F0D030), // C — голубой
TColor($00E070F0), // D — розовый/маджента
TColor($0030E0F0), // E — жёлтый
@@ -46,6 +50,11 @@ const
TColor($00E0C060), // G — бирюзовый
TColor($00C0C0C0)); // H — серый
// Прозрачность заливки полосы фильтра на спектре — одна на главный VFO и на
// слайсы, чтобы полосы читались одинаково (и в CPU-, и в GL-вьюхе).
SPEC_BAND_ALPHA = 60; // приём
SPEC_BAND_ALPHA_TX = 100; // передающая полоса — плотнее
// Цвет слайса по его букве (A..). За пределами таблицы — по кругу.
function SliceColor(L: Char): TColor;