From eb4af61fad3ae9410c1969e11f113e11c078f22f Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 14 Aug 2026 23:14:59 +0300 Subject: [PATCH] =?UTF-8?q?ui(spectrum):=20=D0=BF=D0=BE=D0=BB=D0=BE=D1=81?= =?UTF-8?q?=D0=B0=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0=20=E2=80=94=20=D0=BA?= =?UTF-8?q?=D0=B0=D0=BA=20=D1=83=20=D1=81=D0=BB=D0=B0=D0=B9=D1=81=D0=BE?= =?UTF-8?q?=D0=B2,=20=D1=81=D0=BE=20=D1=81=D0=B2=D0=BE=D0=B8=D0=BC=20?= =?UTF-8?q?=D0=BE=D1=82=D1=82=D0=B5=D0=BD=D0=BA=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Главный фильтр рисовался иначе, чем слайсовые полосы, причём в 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 --- AppTheme.pas | 15 ++++++- SpectrumView.pas | 88 +++++++++++++++--------------------------- SpectrumViewOpengl.pas | 8 +++- VfoOverlay.pas | 11 +++++- 4 files changed, 61 insertions(+), 61 deletions(-) diff --git a/AppTheme.pas b/AppTheme.pas index 02a8bcb..144376a 100644 --- a/AppTheme.pas +++ b/AppTheme.pas @@ -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); diff --git a/SpectrumView.pas b/SpectrumView.pas index 83d9264..73420f2 100644 --- a/SpectrumView.pas +++ b/SpectrumView.pas @@ -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); diff --git a/SpectrumViewOpengl.pas b/SpectrumViewOpengl.pas index 4d1417d..50104cc 100644 --- a/SpectrumViewOpengl.pas +++ b/SpectrumViewOpengl.pas @@ -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). diff --git a/VfoOverlay.pas b/VfoOverlay.pas index 71f9ce4..7ef6f0e 100644 --- a/VfoOverlay.pas +++ b/VfoOverlay.pas @@ -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;