mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
Optimize CPU waterfall/spectrum rendering
WaterfallView: drop per-frame TLazIntfImage + LoadFromIntfImage (device bitmap recreation + full pixel-format conversion every frame). Write pixels directly into the bitmap via BeginUpdate/ScanLine like SpectrumView already does, with platform-native byte order (ARGB on Cocoa, BGRA elsewhere). Replace per-pixel float colorization with a 512-entry dB->color LUT rebuilt only when WfLow/WfHigh/theme change beyond a small threshold. Turn the bitmap itself into a ring buffer: each frame builds a single new row and writes one ScanLine; scrolling is done at paint time via two CopyRect calls. Removes both full-frame passes per frame (image memmove + buffer->bitmap copy), cutting per-frame cost from O(W*H) to O(W). TX mode preserved by leaving out-of-span columns untouched in the row buf. SpectrumView: rewrite the per-frame alpha-restore pass from a strided per-byte loop to a sequential per-dword OR with the alpha mask (bit-identical output, sequential memory access). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
16259ed704
commit
55d5bf01cf
+13
-8
@@ -722,7 +722,8 @@ procedure TSpectrumView.DrawSpectrum;
|
|||||||
var
|
var
|
||||||
C, GC: TCanvas;
|
C, GC: TCanvas;
|
||||||
i, Yp, W, H, GX: Integer;
|
i, Yp, W, H, GX: Integer;
|
||||||
AlphaPtr: PByte;
|
RowLW: PLongWord;
|
||||||
|
AMask: LongWord;
|
||||||
DBmin, DBmax, dB: Double;
|
DBmin, DBmax, dB: Double;
|
||||||
VfoX, X1, X2: Integer;
|
VfoX, X1, X2: Integer;
|
||||||
TXVfoX, TXX1, TXX2: Integer;
|
TXVfoX, TXX1, TXX2: Integer;
|
||||||
@@ -930,18 +931,22 @@ begin
|
|||||||
if Assigned(FVfoOverlay) then
|
if Assigned(FVfoOverlay) then
|
||||||
FVfoOverlay.DrawOverlay(FSpectrumBitmap, W, H);
|
FVfoOverlay.DrawOverlay(FSpectrumBitmap, W, H);
|
||||||
|
|
||||||
|
// Восстанавливаем непрозрачность: Canvas-операции (текст/линии со сглаживанием)
|
||||||
|
// могли занулить альфу. OR маской выставляет альфу в $FF, RGB не меняя.
|
||||||
|
{$IFDEF DARWIN}
|
||||||
|
AMask := $000000FF; // ARGB: альфа в байте 0
|
||||||
|
{$ELSE}
|
||||||
|
AMask := $FF000000; // BGRA: альфа в байте 3
|
||||||
|
{$ENDIF}
|
||||||
FSpectrumBitmap.BeginUpdate(False);
|
FSpectrumBitmap.BeginUpdate(False);
|
||||||
for i := 0 to H - 1 do
|
for i := 0 to H - 1 do
|
||||||
begin
|
begin
|
||||||
{$IFDEF DARWIN}
|
RowLW := PLongWord(FSpectrumBitmap.ScanLine[i]);
|
||||||
AlphaPtr := PByte(FSpectrumBitmap.ScanLine[i]); // ARGB: alpha at byte 0
|
if RowLW = nil then Continue;
|
||||||
{$ELSE}
|
|
||||||
AlphaPtr := PByte(FSpectrumBitmap.ScanLine[i]) + 3; // BGRA: alpha at byte 3
|
|
||||||
{$ENDIF}
|
|
||||||
for Yp := 0 to W - 1 do
|
for Yp := 0 to W - 1 do
|
||||||
begin
|
begin
|
||||||
AlphaPtr^ := $FF;
|
RowLW^ := RowLW^ or AMask;
|
||||||
Inc(AlphaPtr, 4);
|
Inc(RowLW);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
FSpectrumBitmap.EndUpdate(False);
|
FSpectrumBitmap.EndUpdate(False);
|
||||||
|
|||||||
+113
-75
@@ -13,7 +13,7 @@ interface
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
|
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
|
||||||
IntfGraphics, FPImage, LCLIntf, LCLType, GraphType, AppTheme;
|
LCLIntf, LCLType, AppTheme;
|
||||||
|
|
||||||
type
|
type
|
||||||
TWaterfallView = class
|
TWaterfallView = class
|
||||||
@@ -22,8 +22,12 @@ type
|
|||||||
FWfBitmap: TBitmap;
|
FWfBitmap: TBitmap;
|
||||||
FWfBitmapW: Integer;
|
FWfBitmapW: Integer;
|
||||||
FWfBitmapH: Integer;
|
FWfBitmapH: Integer;
|
||||||
FWfIntfImg: TLazIntfImage;
|
FWfRowBuf: array of LongWord; // одна строка (новейшая), нативный порядок
|
||||||
FWfPixels: array of LongWord;
|
FWfHead: Integer; // физ. строка bitmap с новейшей линией
|
||||||
|
FWfLut: array[0..511] of LongWord;
|
||||||
|
FWfLutLow: Double;
|
||||||
|
FWfLutHigh: Double;
|
||||||
|
FWfLutLight: Boolean;
|
||||||
FWaterfallBuf: array[0..1023] of Single;
|
FWaterfallBuf: array[0..1023] of Single;
|
||||||
FWaterfallBufCount: Integer;
|
FWaterfallBufCount: Integer;
|
||||||
FWaterfallDirty: Boolean;
|
FWaterfallDirty: Boolean;
|
||||||
@@ -48,6 +52,7 @@ type
|
|||||||
FPbWaterfall: TControl;
|
FPbWaterfall: TControl;
|
||||||
|
|
||||||
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||||
|
procedure BuildWfLut(WfLow, WfHigh: Double);
|
||||||
public
|
public
|
||||||
constructor Create;
|
constructor Create;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
@@ -147,23 +152,22 @@ begin
|
|||||||
Result := ($FF shl 24) or (R shl 16) or (G shl 8) or B;
|
Result := ($FF shl 24) or (R shl 16) or (G shl 8) or B;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure InitRawDesc32WF(var Desc: TRawImageDescription; AWidth, AHeight: Integer);
|
// Преобразует логический $AARRGGBB в нативный для ScanLine порядок байт.
|
||||||
|
// DARWIN (Cocoa): в памяти [A,R,G,B]; прочие (Qt6/Win32): [B,G,R,A].
|
||||||
|
// Платформы little-endian, поэтому байт 0 — младший байт LongWord.
|
||||||
|
function NativePixel(ARGB: LongWord): LongWord; inline;
|
||||||
|
{$IFDEF DARWIN}
|
||||||
begin
|
begin
|
||||||
FillChar(Desc, SizeOf(Desc), 0);
|
Result := (ARGB shr 24) // A → байт 0
|
||||||
Desc.Format := ricfRGBA;
|
or (((ARGB shr 16) and $FF) shl 8) // R → байт 1
|
||||||
Desc.Width := AWidth;
|
or (((ARGB shr 8) and $FF) shl 16) // G → байт 2
|
||||||
Desc.Height := AHeight;
|
or ((ARGB and $FF) shl 24); // B → байт 3
|
||||||
Desc.Depth := 32;
|
|
||||||
Desc.BitOrder := riboBitsInOrder;
|
|
||||||
Desc.ByteOrder := riboLSBFirst;
|
|
||||||
Desc.LineOrder := riloTopToBottom;
|
|
||||||
Desc.BitsPerPixel := 32;
|
|
||||||
Desc.LineEnd := rileDWordBoundary;
|
|
||||||
Desc.BlueShift := 0; Desc.BluePrec := 8;
|
|
||||||
Desc.GreenShift := 8; Desc.GreenPrec := 8;
|
|
||||||
Desc.RedShift := 16; Desc.RedPrec := 8;
|
|
||||||
Desc.AlphaShift := 24; Desc.AlphaPrec := 8;
|
|
||||||
end;
|
end;
|
||||||
|
{$ELSE}
|
||||||
|
begin
|
||||||
|
Result := ARGB; // $AARRGGBB на LE уже даёт в памяти [B,G,R,A]
|
||||||
|
end;
|
||||||
|
{$ENDIF}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════════════
|
||||||
// TWaterfallView
|
// TWaterfallView
|
||||||
@@ -174,7 +178,7 @@ begin
|
|||||||
inherited Create;
|
inherited Create;
|
||||||
FWaterfallBitmap := TBitmap.Create;
|
FWaterfallBitmap := TBitmap.Create;
|
||||||
FWfBitmap := TBitmap.Create;
|
FWfBitmap := TBitmap.Create;
|
||||||
FWfIntfImg := nil;
|
FWfBitmap.PixelFormat := pf32bit;
|
||||||
FWfBitmapW := 0;
|
FWfBitmapW := 0;
|
||||||
FWfBitmapH := 0;
|
FWfBitmapH := 0;
|
||||||
FWfManualHigh := -80.0;
|
FWfManualHigh := -80.0;
|
||||||
@@ -191,6 +195,9 @@ begin
|
|||||||
FTheme := DarkTheme;
|
FTheme := DarkTheme;
|
||||||
FLightTheme := False;
|
FLightTheme := False;
|
||||||
FSpanHz := 192000;
|
FSpanHz := 192000;
|
||||||
|
FWfLutLow := 1.0; // невалидно → LUT перестроится при первом кадре
|
||||||
|
FWfLutHigh := 0.0;
|
||||||
|
FWfLutLight := False;
|
||||||
ResetWfBuf;
|
ResetWfBuf;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -198,7 +205,6 @@ destructor TWaterfallView.Destroy;
|
|||||||
begin
|
begin
|
||||||
FWaterfallBitmap.Free;
|
FWaterfallBitmap.Free;
|
||||||
FWfBitmap.Free;
|
FWfBitmap.Free;
|
||||||
FreeAndNil(FWfIntfImg);
|
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -210,8 +216,10 @@ begin
|
|||||||
// Сброс размера — чтобы следующий кадр перезаполнил фон новой темой
|
// Сброс размера — чтобы следующий кадр перезаполнил фон новой темой
|
||||||
FWfBitmapW := 0;
|
FWfBitmapW := 0;
|
||||||
FWfBitmapH := 0;
|
FWfBitmapH := 0;
|
||||||
SetLength(FWfPixels, 0);
|
FWfLutLow := 1.0; // невалидно → LUT перестроится
|
||||||
FreeAndNil(FWfIntfImg);
|
FWfLutHigh := 0.0;
|
||||||
|
SetLength(FWfRowBuf, 0);
|
||||||
|
FWfHead := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWaterfallView.ResetWfAvgBuf;
|
procedure TWaterfallView.ResetWfAvgBuf;
|
||||||
@@ -249,8 +257,8 @@ begin
|
|||||||
FWaterfallBitmap.SetSize(W, H);
|
FWaterfallBitmap.SetSize(W, H);
|
||||||
FWfBitmapW := 0;
|
FWfBitmapW := 0;
|
||||||
FWfBitmapH := 0;
|
FWfBitmapH := 0;
|
||||||
SetLength(FWfPixels, 0);
|
SetLength(FWfRowBuf, 0);
|
||||||
FreeAndNil(FWfIntfImg);
|
FWfHead := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWaterfallView.DrawMarkerLine(C: TCanvas; W, H: Integer);
|
procedure TWaterfallView.DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||||
@@ -271,6 +279,30 @@ begin
|
|||||||
C.TextOut(MX - 4 - C.TextWidth(MarkerLbl), 4, MarkerLbl);
|
C.TextOut(MX - 4 - C.TextWidth(MarkerLbl), 4, MarkerLbl);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Перестраивает палитру dB→цвет (512 точек) один раз на кадр вместо
|
||||||
|
// вычисления цвета с плавающей точкой на каждый пиксель.
|
||||||
|
procedure TWaterfallView.BuildWfLut(WfLow, WfHigh: Double);
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
dB, Range: Double;
|
||||||
|
C: LongWord;
|
||||||
|
begin
|
||||||
|
Range := WfHigh - WfLow;
|
||||||
|
if Range < 1E-6 then Range := 1E-6;
|
||||||
|
for i := 0 to High(FWfLut) do
|
||||||
|
begin
|
||||||
|
dB := WfLow + (i / High(FWfLut)) * Range;
|
||||||
|
if FLightTheme then
|
||||||
|
C := WaterfallLightTheme(dB, WfLow, WfHigh)
|
||||||
|
else
|
||||||
|
C := WaterfallEnhancedColorThetis(dB, WfLow, WfHigh);
|
||||||
|
FWfLut[i] := NativePixel(C);
|
||||||
|
end;
|
||||||
|
FWfLutLow := WfLow;
|
||||||
|
FWfLutHigh := WfHigh;
|
||||||
|
FWfLutLight := FLightTheme;
|
||||||
|
end;
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
// DrawWaterfall
|
// DrawWaterfall
|
||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -284,10 +316,8 @@ var
|
|||||||
HistMinDB, HistMaxDB, HistStepDB: Double;
|
HistMinDB, HistMaxDB, HistStepDB: Double;
|
||||||
NoiseFloorDB, SignalTopDB: Double;
|
NoiseFloorDB, SignalTopDB: Double;
|
||||||
CumCount, LowTargetCount, HighTargetCount, HistIdx: Integer;
|
CumCount, LowTargetCount, HighTargetCount, HistIdx: Integer;
|
||||||
WfHigh, WfLow, InvRange, Step: Double;
|
WfHigh, WfLow, LutScale, Step: Double;
|
||||||
Px: PLongWord;
|
Row, Idx: Integer; RowPtr: PByte;
|
||||||
Desc: TRawImageDescription;
|
|
||||||
Row, Col: Integer; RowPtr: PByte; Src: PLongWord; BPPi: Integer;
|
|
||||||
Pal: LongWord; Hist: array[0..191] of Integer; SrcCount: Integer;
|
Pal: LongWord; Hist: array[0..191] of Integer; SrcCount: Integer;
|
||||||
const
|
const
|
||||||
ALPHA_HIGH = 0.10; ALPHA_LOW = 0.08;
|
ALPHA_HIGH = 0.10; ALPHA_LOW = 0.08;
|
||||||
@@ -340,25 +370,44 @@ begin
|
|||||||
if WfHigh < WfLow + 40.0 then WfHigh := WfLow + 40.0;
|
if WfHigh < WfLow + 40.0 then WfHigh := WfLow + 40.0;
|
||||||
if WfHigh > 0.0 then WfHigh := 0.0;
|
if WfHigh > 0.0 then WfHigh := 0.0;
|
||||||
if WfLow < -160 then WfLow := -160;
|
if WfLow < -160 then WfLow := -160;
|
||||||
InvRange := 255.0 / (WfHigh - WfLow);
|
LutScale := High(FWfLut) / (WfHigh - WfLow);
|
||||||
|
|
||||||
|
// Палитра зависит только от WfLow/WfHigh/темы — перестраиваем лишь при
|
||||||
|
// ощутимом изменении (AGC сдвигает границы медленно).
|
||||||
|
if (FWfLutLight <> FLightTheme) or
|
||||||
|
(Abs(WfLow - FWfLutLow) > 0.05) or
|
||||||
|
(Abs(WfHigh - FWfLutHigh) > 0.05) then
|
||||||
|
BuildWfLut(WfLow, WfHigh);
|
||||||
|
|
||||||
if (FWfBitmapW <> W) or (FWfBitmapH <> H) then
|
if (FWfBitmapW <> W) or (FWfBitmapH <> H) then
|
||||||
begin
|
begin
|
||||||
FWfBitmapW := W; FWfBitmapH := H;
|
FWfBitmapW := W; FWfBitmapH := H;
|
||||||
SetLength(FWfPixels, W * H);
|
SetLength(FWfRowBuf, W);
|
||||||
Pal := $FF000000
|
Pal := NativePixel($FF000000
|
||||||
or ((LongWord(FTheme.Panel) and $FF) shl 16)
|
or ((LongWord(FTheme.Panel) and $FF) shl 16)
|
||||||
or (LongWord(FTheme.Panel) and $FF00)
|
or (LongWord(FTheme.Panel) and $FF00)
|
||||||
or ((LongWord(FTheme.Panel) shr 16) and $FF);
|
or ((LongWord(FTheme.Panel) shr 16) and $FF));
|
||||||
FillDWord(FWfPixels[0], W * H, Pal);
|
FillDWord(FWfRowBuf[0], W, Pal);
|
||||||
FWfBitmap.SetSize(W, H);
|
FWfBitmap.SetSize(W, H);
|
||||||
FreeAndNil(FWfIntfImg);
|
FWfHead := 0;
|
||||||
|
// Заливаем всё кольцо фоном
|
||||||
|
FWfBitmap.BeginUpdate(False);
|
||||||
|
try
|
||||||
|
for Row := 0 to H - 1 do
|
||||||
|
begin
|
||||||
|
RowPtr := PByte(FWfBitmap.ScanLine[Row]);
|
||||||
|
if RowPtr <> nil then FillDWord(RowPtr^, W, Pal);
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
FWfBitmap.EndUpdate(False);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if H > 1 then Move(FWfPixels[0], FWfPixels[W], (H - 1) * SizeOf(LongWord) * W);
|
// Строим только новейшую строку в FWfRowBuf. В TX-режиме столбцы вне TX-полосы
|
||||||
|
// НЕ трогаем — FWfRowBuf хранит предыдущую верхнюю строку, поэтому
|
||||||
|
// «замороженные» участки остаются вертикально непрерывными, как и раньше.
|
||||||
Step := (SrcCount - 1.0) / Max(1, W - 1);
|
Step := (SrcCount - 1.0) / Max(1, W - 1);
|
||||||
WatSrcF := 0.0; Px := @FWfPixels[0];
|
WatSrcF := 0.0;
|
||||||
for X := 0 to W - 1 do
|
for X := 0 to W - 1 do
|
||||||
begin
|
begin
|
||||||
if FTXMode and (FTXSpanHz > 0) and (FSpanHz > 0) then
|
if FTXMode and (FTXSpanHz > 0) and (FSpanHz > 0) then
|
||||||
@@ -366,11 +415,8 @@ begin
|
|||||||
dB := FCenterFreq - FSpanHz * 0.5 + X * FSpanHz / Max(1, W - 1) - FTXFreq;
|
dB := FCenterFreq - FSpanHz * 0.5 + X * FSpanHz / Max(1, W - 1) - FTXFreq;
|
||||||
if (dB < -FTXSpanHz * 0.5) or (dB > FTXSpanHz * 0.5) then
|
if (dB < -FTXSpanHz * 0.5) or (dB > FTXSpanHz * 0.5) then
|
||||||
begin
|
begin
|
||||||
if H > 1 then Px^ := FWfPixels[X + W]
|
|
||||||
else Px^ := 0;
|
|
||||||
Inc(Px);
|
|
||||||
WatSrcF := WatSrcF + Step;
|
WatSrcF := WatSrcF + Step;
|
||||||
Continue;
|
Continue; // оставляем FWfRowBuf[X] без изменений
|
||||||
end;
|
end;
|
||||||
frac := (dB + FTXSpanHz * 0.5) / FTXSpanHz * (SrcCount - 1);
|
frac := (dB + FTXSpanHz * 0.5) / FTXSpanHz * (SrcCount - 1);
|
||||||
WatS0 := Trunc(frac);
|
WatS0 := Trunc(frac);
|
||||||
@@ -386,41 +432,23 @@ begin
|
|||||||
frac := WatSrcF - WatS0;
|
frac := WatSrcF - WatS0;
|
||||||
dB := FWaterfallBuf[WatS0] * (1.0 - frac) + FWaterfallBuf[WatS1] * frac;
|
dB := FWaterfallBuf[WatS0] * (1.0 - frac) + FWaterfallBuf[WatS1] * frac;
|
||||||
end;
|
end;
|
||||||
if FLightTheme then
|
Idx := Round((dB - WfLow) * LutScale);
|
||||||
Pal := WaterfallLightTheme(dB, WfLow, WfHigh)
|
if Idx < 0 then Idx := 0 else if Idx > High(FWfLut) then Idx := High(FWfLut);
|
||||||
else
|
FWfRowBuf[X] := FWfLut[Idx];
|
||||||
Pal := WaterfallEnhancedColorThetis(dB, WfLow, WfHigh);
|
|
||||||
Px^ := Pal; Inc(Px);
|
|
||||||
WatSrcF := WatSrcF + Step;
|
WatSrcF := WatSrcF + Step;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if FWfIntfImg = nil then
|
// Кольцевой сдвиг: новая верхняя строка занимает слот выше предыдущей.
|
||||||
begin
|
// Полнокадровый Move изображения и полная перезаливка bitmap больше не нужны —
|
||||||
FWfIntfImg := TLazIntfImage.Create(W, H);
|
// пишем единственную строку, а прокрутка делается в PaintWaterfall (CopyRect).
|
||||||
InitRawDesc32WF(Desc, W, H);
|
FWfHead := (FWfHead - 1 + H) mod H;
|
||||||
FWfIntfImg.DataDescription := Desc;
|
FWfBitmap.BeginUpdate(False);
|
||||||
FWfIntfImg.CreateData;
|
try
|
||||||
|
RowPtr := PByte(FWfBitmap.ScanLine[FWfHead]);
|
||||||
|
if RowPtr <> nil then Move(FWfRowBuf[0], RowPtr^, W * 4);
|
||||||
|
finally
|
||||||
|
FWfBitmap.EndUpdate(False);
|
||||||
end;
|
end;
|
||||||
BPPi := FWfIntfImg.DataDescription.BitsPerPixel div 8;
|
|
||||||
if (BPPi = 4) and (FWfIntfImg.PixelData <> nil) then
|
|
||||||
Move(FWfPixels[0], FWfIntfImg.PixelData^, W * H * 4)
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
Src := @FWfPixels[0];
|
|
||||||
for Row := 0 to H - 1 do
|
|
||||||
begin
|
|
||||||
RowPtr := FWfIntfImg.GetDataLineStart(Row);
|
|
||||||
if RowPtr = nil then begin Inc(Src, W); Continue; end;
|
|
||||||
for Col := 0 to W - 1 do
|
|
||||||
begin
|
|
||||||
RowPtr[0] := Byte(Src^); RowPtr[1] := Byte(Src^ shr 8);
|
|
||||||
RowPtr[2] := Byte(Src^ shr 16);
|
|
||||||
if BPPi >= 4 then RowPtr[3] := $FF;
|
|
||||||
Inc(Src); Inc(RowPtr, BPPi);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
FWfBitmap.LoadFromIntfImage(FWfIntfImg);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -428,13 +456,23 @@ end;
|
|||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
procedure TWaterfallView.PaintWaterfall(Sender: TObject);
|
procedure TWaterfallView.PaintWaterfall(Sender: TObject);
|
||||||
var PB: TPaintBox; W, H: Integer;
|
var PB: TPaintBox; W, H, Top: Integer;
|
||||||
begin
|
begin
|
||||||
PB := TPaintBox(Sender);
|
PB := TPaintBox(Sender);
|
||||||
W := PB.Width; H := PB.Height;
|
W := PB.Width; H := PB.Height;
|
||||||
if (W <= 0) or (H <= 0) then Exit;
|
if (W <= 0) or (H <= 0) then Exit;
|
||||||
if (FWfBitmap <> nil) and (FWfBitmap.Width = W) and (FWfBitmap.Height = H) then
|
if (FWfBitmap <> nil) and (FWfBitmap.Width = W) and (FWfBitmap.Height = H)
|
||||||
PB.Canvas.Draw(0, 0, FWfBitmap)
|
and (Length(FWfRowBuf) = W) then
|
||||||
|
begin
|
||||||
|
// Кольцевой буфер: дисплейная строка 0 = физ. строка FWfHead.
|
||||||
|
// Верхний сегмент — строки FWfHead..H-1, нижний — 0..FWfHead-1.
|
||||||
|
Top := H - FWfHead;
|
||||||
|
PB.Canvas.CopyRect(Rect(0, 0, W, Top),
|
||||||
|
FWfBitmap.Canvas, Rect(0, FWfHead, W, H));
|
||||||
|
if FWfHead > 0 then
|
||||||
|
PB.Canvas.CopyRect(Rect(0, Top, W, H),
|
||||||
|
FWfBitmap.Canvas, Rect(0, 0, W, FWfHead));
|
||||||
|
end
|
||||||
else begin
|
else begin
|
||||||
PB.Canvas.Brush.Color := FTheme.Panel;
|
PB.Canvas.Brush.Color := FTheme.Panel;
|
||||||
PB.Canvas.FillRect(Rect(0, 0, W, H));
|
PB.Canvas.FillRect(Rect(0, 0, W, H));
|
||||||
|
|||||||
Reference in New Issue
Block a user