From bd2a6bc2ea5f062a939c6405b8c1010a9df95016 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sat, 18 Jul 2026 10:36:23 +0300 Subject: [PATCH] =?UTF-8?q?ui:=20=D0=BF=D0=BB=D0=B0=D1=88=D0=BA=D0=B0=20AD?= =?UTF-8?q?C=20OVERLOAD=20=E2=80=94=20=D0=BE=D0=B1=D1=89=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=20CPU/GL=20=D0=B1=D0=B5=D0=B7=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80-=D0=BA=D0=B0=D0=B4=D1=80=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=B6=D0=BE=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AlertOverlay переписан в общий builder: BuildAlertImage строит RGBA-образ плашки (попиксельная альфа, Canvas один раз на своём битмапе). GL грузит образ текстурой (кэш как был), CPU блендит кэш в raw-кадр — убраны canvas-путь в кадре и полный проход восстановления альфы битмапа. GL-плашка теперь 1:1 с CPU (было: упрощённый бокс по центру), позиция — правый верхний угол в обоих рендерах. Удалена мёртвая подсветочная линия (перекрывалась рамкой). Co-Authored-By: Claude Fable 5 --- AlertOverlay.pas | 245 +++++++++++++++++++++++++++-------------- SpectrumView.pas | 40 +++---- SpectrumViewOpengl.pas | 43 +++----- 3 files changed, 193 insertions(+), 135 deletions(-) diff --git a/AlertOverlay.pas b/AlertOverlay.pas index afcfe87..1103131 100644 --- a/AlertOverlay.pas +++ b/AlertOverlay.pas @@ -1,7 +1,15 @@ unit AlertOverlay; { - Reusable overlays for spectrum-like bitmap surfaces. + Общая плашка-алерт для CPU- и GL-рендеров спектра. + + Единственный источник внешнего вида: BuildAlertImage строит готовый + RGBA-образ (straight alpha, попиксельная) ОДИН раз — Canvas используется + только здесь, на собственном маленьком битмапе. Дальше каждый рендер + использует образ по-своему без пер-кадрового жора CPU: + - GL: загружает как текстуру (один раз) и рисует квадом; + - CPU: блендит кэшированный образ в raw-кадр (BlendAlertImage, + ~10 тыс. пикселей — без Canvas и синков режима доступа Qt6). } {$IFDEF FPC} @@ -11,105 +19,174 @@ unit AlertOverlay; interface uses - Graphics, Types, Math; + Classes, Graphics, Types, Math; -procedure DrawAlertOverlay(Target: TBitmap; C: TCanvas; W, H: Integer; - const Title, Detail: string); +const + ALERT_PAD = 12; // отступ плашки от правого/верхнего края спектра + +type + TAlertImage = record + W, H: Integer; + RGBA: array of Byte; // W*H*4, порядок R,G,B,A; straight alpha + end; + +// Построить образ плашки (полупрозрачный красный фон, рамка, акцент-полоса, +// заголовок + деталь). Вызывать один раз и кэшировать результат. +procedure BuildAlertImage(const Title, Detail: string; out Img: TAlertImage); + +// Смешать образ в raw-битмап кадра (32bpp, BGRA / ARGB на Darwin) по +// straight-alpha. (X,Y) — левый верхний угол плашки; клиппинг внутри. +procedure BlendAlertImage(const Img: TAlertImage; Base: PByte; + BPL, RawW, RawH, X, Y: Integer); implementation -procedure BlendRect(Target: TBitmap; const Rct: TRect; R, G, B, Alpha: Byte); +procedure BuildAlertImage(const Title, Detail: string; out Img: TAlertImage); +const + INNER_X = 12; ACCENT_W = 4; BOX_H = 42; var - Y, X, InvA: Integer; - Row: PByte; - RR: TRect; -begin - if (Target = nil) or (Alpha = 0) then Exit; - RR := Rct; - if RR.Left < 0 then RR.Left := 0; - if RR.Top < 0 then RR.Top := 0; - if RR.Right > Target.Width then RR.Right := Target.Width; - if RR.Bottom > Target.Height then RR.Bottom := Target.Height; - if (RR.Right <= RR.Left) or (RR.Bottom <= RR.Top) then Exit; + B: TBitmap; + TitleW, DetailW, BoxW: Integer; - InvA := 255 - Alpha; - Target.BeginUpdate(False); - try - for Y := RR.Top to RR.Bottom - 1 do + procedure FillBuf(X1, Y1, X2, Y2: Integer; R, G, Bl, A: Byte); // [X1..X2) + var X, Y, I: Integer; + begin + for Y := Max(0, Y1) to Min(BOX_H, Y2) - 1 do begin - Row := PByte(Target.ScanLine[Y]); - if Row = nil then Continue; - Inc(Row, RR.Left * 4); - for X := RR.Left to RR.Right - 1 do + I := (Y * BoxW + Max(0, X1)) * 4; + for X := Max(0, X1) to Min(BoxW, X2) - 1 do begin -{$IFDEF DARWIN} - Row[1] := Byte((Alpha * R + InvA * Row[1]) div 255); - Row[2] := Byte((Alpha * G + InvA * Row[2]) div 255); - Row[3] := Byte((Alpha * B + InvA * Row[3]) div 255); -{$ELSE} - Row[0] := Byte((Alpha * B + InvA * Row[0]) div 255); - Row[1] := Byte((Alpha * G + InvA * Row[1]) div 255); - Row[2] := Byte((Alpha * R + InvA * Row[2]) div 255); -{$ENDIF} - Inc(Row, 4); + Img.RGBA[I] := R; Img.RGBA[I+1] := G; + Img.RGBA[I+2] := Bl; Img.RGBA[I+3] := A; + Inc(I, 4); end; end; + end; + + procedure BlendText(TxtB: TBitmap); + // Композит текстового слоя (белым на чёрном) в образ: покрытие = max(R,G,B), + // цвет по строке (заголовок/деталь), сложение по straight-alpha. + var + X, Y, I, MaxC: Integer; + Src: PByte; + CR, CG, CB: Byte; + AT, AB, AO: Double; + begin + TxtB.BeginUpdate(False); + try + for Y := 0 to BOX_H - 1 do + begin + Src := PByte(TxtB.ScanLine[Y]); + I := Y * BoxW * 4; + for X := 0 to BoxW - 1 do + begin +{$IFDEF DARWIN} + MaxC := Max(Src[1], Max(Src[2], Src[3])); +{$ELSE} + MaxC := Max(Src[0], Max(Src[1], Src[2])); +{$ENDIF} + if MaxC > 0 then + begin + if Y < 24 then begin CR := $FF; CG := $88; CB := $88; end // $008888FF + else begin CR := $E8; CG := $C8; CB := $C8; end; // $00C8C8E8 + AT := MaxC / 255.0; + AB := Img.RGBA[I+3] / 255.0 * (1.0 - AT); + AO := AT + AB; + Img.RGBA[I] := Round((CR * AT + Img.RGBA[I] * AB) / AO); + Img.RGBA[I+1] := Round((CG * AT + Img.RGBA[I+1] * AB) / AO); + Img.RGBA[I+2] := Round((CB * AT + Img.RGBA[I+2] * AB) / AO); + Img.RGBA[I+3] := Round(AO * 255.0); + end; + Inc(Src, 4); + Inc(I, 4); + end; + end; + finally + TxtB.EndUpdate(False); + end; + end; + +begin + Img.W := 0; Img.H := 0; Img.RGBA := nil; + B := TBitmap.Create; + try + B.PixelFormat := pf32bit; + B.SetSize(8, 8); + B.Canvas.Font.Name := 'Courier New'; + B.Canvas.Font.Style := [fsBold]; + B.Canvas.Font.Size := 9; + TitleW := B.Canvas.TextWidth(Title); + B.Canvas.Font.Style := []; + B.Canvas.Font.Size := 7; + DetailW := B.Canvas.TextWidth(Detail); + BoxW := Max(TitleW, DetailW) + INNER_X * 2 + ACCENT_W + 8; + + SetLength(Img.RGBA, BoxW * BOX_H * 4); + Img.W := BoxW; + Img.H := BOX_H; + FillBuf(0, 0, BoxW, BOX_H, $A8, $10, $16, 118); // фон + FillBuf(0, 0, BoxW, 1, $C8, $38, $38, 255); // рамка + FillBuf(0, BOX_H - 1, BoxW, BOX_H, $C8, $38, $38, 255); + FillBuf(0, 0, 1, BOX_H, $C8, $38, $38, 255); + FillBuf(BoxW - 1, 0, BoxW, BOX_H, $C8, $38, $38, 255); + FillBuf(7, 7, 7 + ACCENT_W, BOX_H - 7, $F0, $28, $28, 255); // акцент + + B.SetSize(BoxW, BOX_H); + B.Canvas.Brush.Color := clBlack; + B.Canvas.Brush.Style := bsSolid; + B.Canvas.FillRect(Rect(0, 0, BoxW, BOX_H)); + B.Canvas.Brush.Style := bsClear; + B.Canvas.Font.Name := 'Courier New'; + B.Canvas.Font.Color := clWhite; + B.Canvas.Font.Style := [fsBold]; + B.Canvas.Font.Size := 9; + B.Canvas.TextOut(INNER_X + ACCENT_W + 8, 7, Title); + B.Canvas.Font.Style := []; + B.Canvas.Font.Size := 7; + B.Canvas.TextOut(INNER_X + ACCENT_W + 8, 25, Detail); + BlendText(B); finally - Target.EndUpdate(False); + B.Free; end; end; -procedure DrawAlertOverlay(Target: TBitmap; C: TCanvas; W, H: Integer; - const Title, Detail: string); -const - PAD = 12; - INNER_X = 12; - ACCENT_W = 4; +procedure BlendAlertImage(const Img: TAlertImage; Base: PByte; + BPL, RawW, RawH, X, Y: Integer); var - R, AccentR: TRect; - TW, DW, BoxW, BoxH: Integer; + SX, SY, DX, DY, I, A, InvA: Integer; + P: PByte; begin - if (Target = nil) or (C = nil) then Exit; - if (W < 220) or (H < 60) then Exit; - - C.Font.Name := 'Courier New'; - C.Font.Style := [fsBold]; - C.Font.Size := 9; - TW := C.TextWidth(Title); - C.Font.Style := []; - C.Font.Size := 7; - DW := C.TextWidth(Detail); - - BoxW := Max(TW, DW) + INNER_X * 2 + ACCENT_W + 8; - BoxH := 42; - R := Rect(W - PAD - BoxW, PAD, W - PAD, PAD + BoxH); - - BlendRect(Target, R, $A8, $10, $16, 118); - BlendRect(Target, Rect(R.Left, R.Top, R.Right, R.Top + 1), $FF, $68, $68, 80); - - C.Brush.Style := bsClear; - C.Pen.Style := psSolid; - C.Pen.Width := 1; - C.Pen.Color := TColor($003838C8); - C.Rectangle(R); - - AccentR := Rect(R.Left + 7, R.Top + 7, R.Left + 7 + ACCENT_W, R.Bottom - 7); - C.Brush.Style := bsSolid; - C.Brush.Color := TColor($002828F0); - C.Pen.Style := psClear; - C.FillRect(AccentR); - - C.Brush.Style := bsClear; - C.Font.Name := 'Courier New'; - C.Font.Style := [fsBold]; - C.Font.Size := 9; - C.Font.Color := TColor($008888FF); - C.TextOut(R.Left + INNER_X + ACCENT_W + 8, R.Top + 7, Title); - - C.Font.Style := []; - C.Font.Size := 7; - C.Font.Color := TColor($00C8C8E8); - C.TextOut(R.Left + INNER_X + ACCENT_W + 8, R.Top + 25, Detail); + if (Img.W <= 0) or (Base = nil) then Exit; + for SY := 0 to Img.H - 1 do + begin + DY := Y + SY; + if (DY < 0) or (DY >= RawH) then Continue; + I := SY * Img.W * 4; + for SX := 0 to Img.W - 1 do + begin + DX := X + SX; + A := Img.RGBA[I + 3]; + if (DX >= 0) and (DX < RawW) and (A > 0) then + begin + InvA := 255 - A; + P := Base + DY * BPL + DX * 4; +{$IFDEF DARWIN} + // ARGB: byte0=A, byte1=R, byte2=G, byte3=B + P[1] := Byte((Img.RGBA[I] * A + P[1] * InvA) div 255); + P[2] := Byte((Img.RGBA[I+1] * A + P[2] * InvA) div 255); + P[3] := Byte((Img.RGBA[I+2] * A + P[3] * InvA) div 255); + P[0] := $FF; +{$ELSE} + // BGRA: byte0=B, byte1=G, byte2=R + P[0] := Byte((Img.RGBA[I+2] * A + P[0] * InvA) div 255); + P[1] := Byte((Img.RGBA[I+1] * A + P[1] * InvA) div 255); + P[2] := Byte((Img.RGBA[I] * A + P[2] * InvA) div 255); + P[3] := $FF; +{$ENDIF} + end; + Inc(I, 4); + end; + end; end; end. diff --git a/SpectrumView.pas b/SpectrumView.pas index 4cc1d76..28484cc 100644 --- a/SpectrumView.pas +++ b/SpectrumView.pas @@ -94,6 +94,7 @@ type FTXOverlay: Boolean; // ── ADC overload overlay ───────────────────────────────────────────────── FADCOverloadVisible: Boolean; + FADCAlertImg: TAlertImage; // кэш образа плашки (BuildAlertImage, 1 раз) FSampleRateOverlay: TSampleRateOverlay; FVfoOverlay: TVfoOverlay; FSliceOverlays: TFPList; // доп. слайс-флаги (B+); не владеет (владелец MainForm) @@ -173,7 +174,7 @@ type 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 DrawADCOverloadOverlay(C: TCanvas; W, H: Integer); + procedure DrawADCOverloadRaw(W, H: Integer); procedure CalcFilterBandX(VfoFreq: Double; W: Integer; out X1, X2, VfoX: Integer); procedure CalcFilterBandXFor(VfoFreq: Double; Mode, BW, W: Integer; @@ -1240,10 +1241,18 @@ begin {$ENDIF} end; -procedure TSpectrumView.DrawADCOverloadOverlay(C: TCanvas; W, H: Integer); +procedure TSpectrumView.DrawADCOverloadRaw(W, H: Integer); +// Плашка ADC OVERLOAD: общий с GL-рендером образ (AlertOverlay.BuildAlertImage, +// строится один раз и кэшируется), в кадре — только straight-alpha бленд +// внутри RawBegin/RawEnd: без Canvas (дорогой raw↔canvas синк Qt6) и без +// пофреймового восстановления альфы всего битмапа. begin if not FADCOverloadVisible then Exit; - DrawAlertOverlay(FSpectrumBitmap, C, W, H, 'ADC OVERLOAD', 'Input clipping detected'); + if (W < 220) or (H < 60) or (FRawBase = nil) then Exit; + if FADCAlertImg.W = 0 then + BuildAlertImage('ADC OVERLOAD', 'Input clipping detected', FADCAlertImg); + BlendAlertImage(FADCAlertImg, FRawBase, FRawBPL, FRawW, FRawH, + W - ALERT_PAD - FADCAlertImg.W, ALERT_PAD); end; procedure TSpectrumView.CalcFilterBandX(VfoFreq: Double; W: Integer; @@ -1732,33 +1741,12 @@ begin for i := 0 to FSliceOverlays.Count - 1 do TVfoOverlay(FSliceOverlays[i]).DrawOverlay(FSpectrumBitmap, W, H); + DrawADCOverloadRaw(W, H); + finally RawEnd; end; - // ADC overload — редкий canvas-путь (DrawAlertOverlay). Ломает альфу и - // режим доступа только в кадрах, где реально виден; после его canvas- - // операций восстанавливаем непрозрачность (пофреймовый OR-проход для - // обычных кадров удалён — Canvas на битмапе кадра больше не бывает). - if FADCOverloadVisible then - begin - DrawADCOverloadOverlay(C, W, H); -{$IFNDEF DARWIN} - FSpectrumBitmap.BeginUpdate(False); - for i := 0 to H - 1 do - begin - RowLW := PLongWord(FSpectrumBitmap.ScanLine[i]); - if RowLW = nil then Continue; - for Yp := 0 to W - 1 do - begin - RowLW^ := RowLW^ or $FF000000; // BGRA: альфа в байте 3 - Inc(RowLW); - end; - end; - FSpectrumBitmap.EndUpdate(False); -{$ENDIF} - end; - end; // ──────────────────────────────────────────────────────────────────────────── diff --git a/SpectrumViewOpengl.pas b/SpectrumViewOpengl.pas index cdfb03c..98d11a4 100644 --- a/SpectrumViewOpengl.pas +++ b/SpectrumViewOpengl.pas @@ -17,7 +17,7 @@ interface uses Classes, SysUtils, Graphics, Controls, Math, Types, OpenGLContextEx, GL, - AppTheme, SpectrumView, VfoOverlay, BandPlanOverlay, + AppTheme, AlertOverlay, SpectrumView, VfoOverlay, BandPlanOverlay, WaterfallView, WaterfallViewOpengl; type @@ -887,37 +887,30 @@ begin end; procedure TSpectrumViewOpenGL.DrawADCOverlay(W, H: Integer); +// Тот же образ, что CPU-путь (AlertOverlay.BuildAlertImage — единственный +// источник внешнего вида): грузится текстурой ОДИН раз при появлении плашки +// (кэш FADCOverlayTex), в кадре — только готовый DrawTexture. var - B: TBitmap; + Img: TAlertImage; begin if not FADCOverloadVisible then Exit; + if (W < 220) or (H < 60) then Exit; // как CPU-путь if (not FLastADCVisible) or FADCOverlayTex.Dirty then begin - B := TBitmap.Create; - try - B.PixelFormat := pf32bit; - B.SetSize(210, 38); - B.Canvas.Brush.Color := TColor($00202020); - B.Canvas.FillRect(Rect(0, 0, B.Width, B.Height)); - B.Canvas.Pen.Color := TColor($000030C0); - B.Canvas.Brush.Style := bsClear; - B.Canvas.Rectangle(0, 0, B.Width, B.Height); - B.Canvas.Font.Name := 'Courier New'; - B.Canvas.Font.Size := 10; - B.Canvas.Font.Style := [fsBold]; - B.Canvas.Font.Color := TColor($000030C0); - B.Canvas.TextOut(12, 5, 'ADC OVERLOAD'); - B.Canvas.Font.Size := 7; - B.Canvas.Font.Style := []; - B.Canvas.Font.Color := TColor($00D0D0D0); - B.Canvas.TextOut(12, 21, 'Input clipping detected'); - UploadBitmap(FADCOverlayTex, B, False, 232); - finally - B.Free; - end; + BuildAlertImage('ADC OVERLOAD', 'Input clipping detected', Img); + if FADCOverlayTex.Tex = 0 then glGenTextures(1, @FADCOverlayTex.Tex); + FADCOverlayTex.W := Img.W; + FADCOverlayTex.H := Img.H; + glBindTexture(GL_TEXTURE_2D, FADCOverlayTex.Tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Img.W, Img.H, 0, GL_RGBA, + GL_UNSIGNED_BYTE, @Img.RGBA[0]); + glBindTexture(GL_TEXTURE_2D, 0); + FADCOverlayTex.Dirty := False; end; FLastADCVisible := True; - DrawTexture(FADCOverlayTex, (W - FADCOverlayTex.W) div 2, 8); + DrawTexture(FADCOverlayTex, W - FADCOverlayTex.W - ALERT_PAD, ALERT_PAD); end; procedure TSpectrumViewOpenGL.DrawCachedOverlays(W, H: Integer);