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; SpecLine: TColor;
SpecFilter: TColor; SpecFilter: TColor;
// Заливка полосы главного фильтра. Отдельно от SpecFilter (тот остался
// подложкой подписей AGC): полоса теперь полупрозрачная, как у слайсов, и
// цвет ей нужен СВОЙ — SpecFilter почти совпадает с нижним цветом фонового
// градиента спектра, поэтому такая заливка читалась не оттенком, а
// затемнением.
SpecFilterBand: TColor;
SpecFilterEdge: TColor; SpecFilterEdge: TColor;
SpecAgcColor: TColor; SpecAgcColor: TColor;
SpecAgcHangColor: TColor; SpecAgcHangColor: TColor;
@@ -158,6 +164,10 @@ begin
Result.SpecGrid := TColor($00685624); Result.SpecGrid := TColor($00685624);
Result.SpecLine := TColor($0040FF80); Result.SpecLine := TColor($0040FF80);
Result.SpecFilter := TColor($006A4E22); Result.SpecFilter := TColor($006A4E22);
// Оттенок полосы — тёплый янтарный, из семейства курсора VFO (Amber): фон
// спектра сине-стальной, поэтому янтарь на нём читается как оттенок, а не
// как «потемнее фона». RGB(255,150,40).
Result.SpecFilterBand := TColor($002896FF);
Result.SpecFilterEdge := TColor($0040FFCC); Result.SpecFilterEdge := TColor($0040FFCC);
Result.SpecAgcColor := TColor($0000AAFF); Result.SpecAgcColor := TColor($0000AAFF);
Result.SpecAgcHangColor := TColor($00FFCC00); Result.SpecAgcHangColor := TColor($00FFCC00);
@@ -266,7 +276,10 @@ begin
Result.SpecLabelText := TColor($00404038); Result.SpecLabelText := TColor($00404038);
Result.SpecGrid := TColor($00B8B4A4); Result.SpecGrid := TColor($00B8B4A4);
Result.SpecLine := TColor($00006400); // тёмно-зелёная линия 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.SpecFilterEdge := TColor($00407840);
Result.SpecAgcColor := TColor($00904000); // тёмно-оранжевый AGC Result.SpecAgcColor := TColor($00904000); // тёмно-оранжевый AGC
Result.SpecAgcHangColor := TColor($00802000); Result.SpecAgcHangColor := TColor($00802000);
+31 -57
View File
@@ -185,7 +185,6 @@ type
procedure DrawIMDMarkersRaw(W, H: Integer; DBmax, InvRange: Double); procedure DrawIMDMarkersRaw(W, H: Integer; DBmax, InvRange: Double);
procedure RawCircle(CX, CY, R: Integer; Color: TColor); procedure RawCircle(CX, CY, R: Integer; Color: TColor);
procedure BlendBand(X1, X2, H: Integer; R, G, B, Alpha: Byte); 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 CopyGridToSpectrum(W, H: Integer);
procedure DrawADCOverloadRaw(W, H: Integer); procedure DrawADCOverloadRaw(W, H: Integer);
procedure CalcFilterBandX(VfoFreq: Double; W: Integer; procedure CalcFilterBandX(VfoFreq: Double; W: Integer;
@@ -1168,41 +1167,6 @@ end;
// Canvas.FillRect для полосы фильтра в DrawSpectrum: полоса рисуется в // Canvas.FillRect для полосы фильтра в DrawSpectrum: полоса рисуется в
// raw-фазе (между memcpy сетки и градиентом), а FillRect дёргал бы Canvas // raw-фазе (между memcpy сетки и градиентом), а FillRect дёргал бы Canvas
// и форсил лишнюю синхронизацию битмапа на Qt6. // и форсил лишнюю синхронизацию битмапа на 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+) на спектре — полупрозрачная полоса // Маркер фильтра каждого слайса (B+) на спектре — полупрозрачная полоса
// пропускания + края + центральная несущая. Янтарный цвет, как в GL-вьюхе // пропускания + края + центральная несущая. Янтарный цвет, как в GL-вьюхе
// (SpectrumViewOpengl.DrawSliceFilterMarkers). Полосы (raw ScanLine-блендинг) // (SpectrumViewOpengl.DrawSliceFilterMarkers). Полосы (raw ScanLine-блендинг)
@@ -1224,7 +1188,7 @@ begin
CalcSliceBandX(O, W, SX1, SX2, SVfoX); CalcSliceBandX(O, W, SX1, SX2, SVfoX);
if SX2 > SX1 then if SX2 > SX1 then
BlendBand(SX1, SX2, H, Clr and $FF, (Clr shr 8) and $FF, (Clr shr 16) and $FF, 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;
end; end;
@@ -1789,7 +1753,14 @@ begin
end else end else
if X2 > X1 then BlendBand(X1, X2, H, $E0, $30, $30, 100); if X2 > X1 then BlendBand(X1, X2, H, $E0, $30, $30, 100);
end else 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-вьюхе. // Полосы фильтра слайсов (B+) — под градиентом/кривой, как в GL-вьюхе.
DrawSliceFilterBands(W, H); DrawSliceFilterBands(W, H);
@@ -1800,16 +1771,34 @@ begin
DrawSpectrumGradient(FSpPts, W, H); DrawSpectrumGradient(FSpPts, W, H);
// ★ Кромки, несущая и буква ГЛАВНОГО фильтра — здесь, ДО кривой спектра,
// ровно как у слайсов ниже и как в GL-вьюхе (там весь блок фильтров идёт
// перед DrawSpectrumCurve). Раньше этот кусок стоял ПОСЛЕ кривой, и на
// CPU-пути главный фильтр единственный лез поверх сигнала — с виду толще и
// ярче слайсовых, хотя цвета и толщина те же.
RawVLine(X1, 0, H - 1, FTheme.SpecFilterEdge);
RawVLine(X2, 0, H - 1, FTheme.SpecFilterEdge);
// Буква главного флага (A) на его полосе — только когда есть слайсы, // Буква главного флага (A) на его полосе — только когда есть слайсы,
// иначе одиночный приём не засоряем. Букву запоминаем: под неё ниже // иначе одиночный приём не засоряем. Букву запоминаем: под неё
// подстраиваются вертикаль несущей и её треугольник. // подстраиваются вертикаль несущей и её треугольник.
MainLetter := #0; MainLetter := #0;
if (FSliceOverlays <> nil) and (FSliceOverlays.Count > 0) and if (FSliceOverlays <> nil) and (FSliceOverlays.Count > 0) and
(FActiveVfo = 0) and Assigned(FVfoOverlay) then (FActiveVfo = 0) and Assigned(FVfoOverlay) then
begin
MainLetter := FVfoOverlay.SliceLetter; 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; end;
if MainLetter <> #0 then
DrawBandLetterRaw(X1, X2, MainLetter, SliceColor(MainLetter));
// Края/несущие/буквы слайсов (полосы уже нарисованы выше). // Края/несущие/буквы слайсов (полосы уже нарисованы выше).
DrawSliceFilterLinesRaw(W, H); DrawSliceFilterLinesRaw(W, H);
@@ -1845,22 +1834,7 @@ begin
// Линия спектра // Линия спектра
RawCurve(W, FTheme.SpecLine); 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 FMarkerActive then DrawMarkerLineRaw(W, H);
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkersRaw(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); else if X2 > X1 then DrawRect(X1, 0, X2, H, CLR_TX_BAND, 100 / 255);
end end
else if X2 > X1 then 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) на его полосе — только когда есть слайсы. Саму // Буква главного флага (A) на его полосе — только когда есть слайсы. Саму
// букву рисуем ниже, но знать о ней надо здесь: под неё уходит начало // букву рисуем ниже, но знать о ней надо здесь: под неё уходит начало
@@ -669,7 +672,8 @@ begin
else Clr := SliceColor(O.SliceLetter); else Clr := SliceColor(O.SliceLetter);
CalcSliceBandX(O, W, SX1, SX2, SVfoX); CalcSliceBandX(O, W, SX1, SX2, SVfoX);
if SX2 > SX1 then 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(SX1, 0, SX1, H, Clr, 1, False);
DrawLine(SX2, 0, SX2, H, Clr, 1, False); DrawLine(SX2, 0, SX2, H, Clr, 1, False);
// Несущая — под буквой, если она приходится на неё (AM/FM/DSB). // Несущая — под буквой, если она приходится на неё (AM/FM/DSB).
+10 -1
View File
@@ -38,7 +38,11 @@ const
// бейджа-карточки во флаге и для буквы на полосе фильтра в спектре. // бейджа-карточки во флаге и для буквы на полосе фильтра в спектре.
SLICE_COLORS: array[0..7] of TColor = ( SLICE_COLORS: array[0..7] of TColor = (
TColor($0060E040), // A — зелёный 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($00F0D030), // C — голубой
TColor($00E070F0), // D — розовый/маджента TColor($00E070F0), // D — розовый/маджента
TColor($0030E0F0), // E — жёлтый TColor($0030E0F0), // E — жёлтый
@@ -46,6 +50,11 @@ const
TColor($00E0C060), // G — бирюзовый TColor($00E0C060), // G — бирюзовый
TColor($00C0C0C0)); // H — серый TColor($00C0C0C0)); // H — серый
// Прозрачность заливки полосы фильтра на спектре — одна на главный VFO и на
// слайсы, чтобы полосы читались одинаково (и в CPU-, и в GL-вьюхе).
SPEC_BAND_ALPHA = 60; // приём
SPEC_BAND_ALPHA_TX = 100; // передающая полоса — плотнее
// Цвет слайса по его букве (A..). За пределами таблицы — по кругу. // Цвет слайса по его букве (A..). За пределами таблицы — по кругу.
function SliceColor(L: Char): TColor; function SliceColor(L: Char): TColor;