mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Split SpectrumView into WaterfallView, SMeterView, RulerView modules
TSpectrumView remains the public facade for MainForm; waterfall, S-meter, and ruler rendering are now in isolated units. Dead code removed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,445 @@
|
||||
unit WaterfallView;
|
||||
|
||||
{
|
||||
WaterfallView.pas — рендеринг водопада.
|
||||
TWaterfallView — изолированный класс, не зависит от SpectrumView.
|
||||
}
|
||||
|
||||
{$IFDEF FPC}
|
||||
{$MODE Delphi}
|
||||
{$ENDIF}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
|
||||
IntfGraphics, FPImage, LCLIntf, LCLType, GraphType, AppTheme;
|
||||
|
||||
type
|
||||
TWaterfallView = class
|
||||
private
|
||||
FWaterfallBitmap: TBitmap;
|
||||
FWfBitmap: TBitmap;
|
||||
FWfBitmapW: Integer;
|
||||
FWfBitmapH: Integer;
|
||||
FWfIntfImg: TLazIntfImage;
|
||||
FWfPixels: array of LongWord;
|
||||
FWaterfallBuf: array[0..1023] of Single;
|
||||
FWaterfallBufCount: Integer;
|
||||
FWaterfallDirty: Boolean;
|
||||
FWfFrameCounter: Integer;
|
||||
FWfFrameInterval: Integer;
|
||||
FWfAGCEnabled: Boolean;
|
||||
FWfNFEnabled: Boolean;
|
||||
FWfManualHigh: Double;
|
||||
FWfManualLow: Double;
|
||||
FWfAGCOffset: Double;
|
||||
FWfHigh: Double;
|
||||
FWfLow: Double;
|
||||
FCenterFreq: Double;
|
||||
FSpanHz: Double;
|
||||
FTXMode: Boolean;
|
||||
FTXFreq: Double;
|
||||
FTXSpanHz: Double;
|
||||
FMarkerActive: Boolean;
|
||||
FMarkerX: Integer;
|
||||
FTheme: TAppTheme;
|
||||
FLightTheme: Boolean;
|
||||
FPbWaterfall: TPaintBox;
|
||||
|
||||
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
property CenterFreq: Double read FCenterFreq write FCenterFreq;
|
||||
property SpanHz: Double read FSpanHz write FSpanHz;
|
||||
property TXMode: Boolean read FTXMode write FTXMode;
|
||||
property TXFreq: Double read FTXFreq write FTXFreq;
|
||||
property TXSpanHz: Double read FTXSpanHz write FTXSpanHz;
|
||||
property WfAGCEnabled: Boolean read FWfAGCEnabled write FWfAGCEnabled;
|
||||
property WfNFEnabled: Boolean read FWfNFEnabled write FWfNFEnabled;
|
||||
property WfManualHigh: Double read FWfManualHigh write FWfManualHigh;
|
||||
property WfManualLow: Double read FWfManualLow write FWfManualLow;
|
||||
property WfAGCOffset: Double read FWfAGCOffset write FWfAGCOffset;
|
||||
property WaterfallDirty: Boolean read FWaterfallDirty write FWaterfallDirty;
|
||||
property WfFrameInterval: Integer read FWfFrameInterval write FWfFrameInterval;
|
||||
property MarkerActive: Boolean read FMarkerActive write FMarkerActive;
|
||||
property MarkerX: Integer read FMarkerX write FMarkerX;
|
||||
property PbWaterfall: TPaintBox write FPbWaterfall;
|
||||
|
||||
procedure SetWaterfallData(const Pixels: array of Single; Count: Integer);
|
||||
procedure DrawWaterfall;
|
||||
procedure PaintWaterfall(Sender: TObject);
|
||||
procedure SetWaterfallBitmapSize(W, H: Integer);
|
||||
procedure ResetWfAvgBuf;
|
||||
procedure ResetWfBuf;
|
||||
procedure SetTheme(const T: TAppTheme);
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// Вспомогательные функции
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
function FormatFreqWF(Hz: Double): string;
|
||||
var Mhz: Int64; KHz, Rest: Integer;
|
||||
begin
|
||||
Mhz := Trunc(Hz / 1000000);
|
||||
KHz := Trunc((Hz - Mhz * 1000000) / 1000);
|
||||
Rest := Trunc(Hz) mod 1000;
|
||||
Result := Format('%d.%3.3d.%3.3d', [Mhz, KHz, Rest]);
|
||||
end;
|
||||
|
||||
function WaterfallEnhancedColorThetis(ValueDB, LowDB, HighDB: Double): LongWord;
|
||||
var
|
||||
Overall, Local: Double;
|
||||
R, G, B: Integer;
|
||||
begin
|
||||
if ValueDB <= LowDB then begin Result := $FF000000; Exit; end;
|
||||
if ValueDB >= HighDB then begin Result := ($FF shl 24) or (255 shl 16) or (124 shl 8) or 192; Exit; end;
|
||||
Overall := (ValueDB - LowDB) / Max(1E-9, HighDB - LowDB);
|
||||
if Overall < (2.0 / 9.0) then
|
||||
begin Local := Overall / (2.0/9.0); R := 0; G := 0; B := Round(Local * 255.0); end
|
||||
else if Overall < (3.0 / 9.0) then
|
||||
begin Local := (Overall - 2.0/9.0) / (1.0/9.0); R := 0; G := Round(Local*255.0); B := 255; end
|
||||
else if Overall < (4.0 / 9.0) then
|
||||
begin Local := (Overall - 3.0/9.0) / (1.0/9.0); R := 0; G := 255; B := Round((1.0-Local)*255.0); end
|
||||
else if Overall < (5.0 / 9.0) then
|
||||
begin Local := (Overall - 4.0/9.0) / (1.0/9.0); R := Round(Local*255.0); G := 255; B := 0; end
|
||||
else if Overall < (7.0 / 9.0) then
|
||||
begin Local := (Overall - 5.0/9.0) / (2.0/9.0); R := 255; G := Round((1.0-Local)*255.0); B := 0; end
|
||||
else if Overall < (8.0 / 9.0) then
|
||||
begin Local := (Overall - 7.0/9.0) / (1.0/9.0); R := 255; G := 0; B := Round(Local*255.0); end
|
||||
else
|
||||
begin
|
||||
Local := (Overall - 8.0/9.0) / (1.0/9.0);
|
||||
R := Round((0.75 + 0.25 * (1.0 - Local)) * 255.0);
|
||||
G := Round(Local * 255.0 * 0.5);
|
||||
B := 255;
|
||||
end;
|
||||
R := EnsureRange(R, 0, 255);
|
||||
G := EnsureRange(G, 0, 255);
|
||||
B := EnsureRange(B, 0, 255);
|
||||
Result := ($FF shl 24) or (R shl 16) or (G shl 8) or B;
|
||||
end;
|
||||
|
||||
function WaterfallLightTheme(ValueDB, LowDB, HighDB: Double): LongWord;
|
||||
const
|
||||
NSTOPS = 8;
|
||||
SR: array[0..NSTOPS-1] of Integer = (224, 160, 20, 0, 0, 220, 255, 255);
|
||||
SG: array[0..NSTOPS-1] of Integer = (230, 185, 80, 185, 200, 210, 90, 20);
|
||||
SB: array[0..NSTOPS-1] of Integer = (232, 210, 200, 210, 80, 0, 0, 20);
|
||||
var
|
||||
T: Double;
|
||||
Seg: Integer;
|
||||
R, G, B: Integer;
|
||||
begin
|
||||
if ValueDB <= LowDB then begin Result := $FFE0E6E8; Exit; end;
|
||||
if ValueDB >= HighDB then begin Result := ($FF shl 24) or (SR[NSTOPS-1] shl 16) or (SG[NSTOPS-1] shl 8) or SB[NSTOPS-1]; Exit; end;
|
||||
T := (ValueDB - LowDB) / Max(1E-9, HighDB - LowDB) * (NSTOPS - 1);
|
||||
Seg := Min(NSTOPS - 2, Trunc(T));
|
||||
T := T - Seg;
|
||||
R := EnsureRange(Round(SR[Seg] * (1.0 - T) + SR[Seg+1] * T), 0, 255);
|
||||
G := EnsureRange(Round(SG[Seg] * (1.0 - T) + SG[Seg+1] * T), 0, 255);
|
||||
B := EnsureRange(Round(SB[Seg] * (1.0 - T) + SB[Seg+1] * T), 0, 255);
|
||||
Result := ($FF shl 24) or (R shl 16) or (G shl 8) or B;
|
||||
end;
|
||||
|
||||
procedure InitRawDesc32WF(var Desc: TRawImageDescription; AWidth, AHeight: Integer);
|
||||
begin
|
||||
FillChar(Desc, SizeOf(Desc), 0);
|
||||
Desc.Format := ricfRGBA;
|
||||
Desc.Width := AWidth;
|
||||
Desc.Height := AHeight;
|
||||
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;
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// TWaterfallView
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
constructor TWaterfallView.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FWaterfallBitmap := TBitmap.Create;
|
||||
FWfBitmap := TBitmap.Create;
|
||||
FWfIntfImg := nil;
|
||||
FWfBitmapW := 0;
|
||||
FWfBitmapH := 0;
|
||||
FWfManualHigh := -80.0;
|
||||
FWfManualLow := -130.0;
|
||||
FWfAGCOffset := 0.0;
|
||||
FWfHigh := -80.0;
|
||||
FWfLow := -130.0;
|
||||
FWfAGCEnabled := True;
|
||||
FWfNFEnabled := False;
|
||||
FWaterfallBufCount := 1024;
|
||||
FWfFrameInterval := 2;
|
||||
FWfFrameCounter := 0;
|
||||
FWaterfallDirty := True;
|
||||
FTheme := DarkTheme;
|
||||
FLightTheme := False;
|
||||
FSpanHz := 192000;
|
||||
ResetWfBuf;
|
||||
end;
|
||||
|
||||
destructor TWaterfallView.Destroy;
|
||||
begin
|
||||
FWaterfallBitmap.Free;
|
||||
FWfBitmap.Free;
|
||||
FreeAndNil(FWfIntfImg);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TWaterfallView.SetTheme(const T: TAppTheme);
|
||||
begin
|
||||
FTheme := T;
|
||||
FLightTheme := T.BG > TColor($00808080);
|
||||
FWaterfallDirty := True;
|
||||
// Сброс размера — чтобы следующий кадр перезаполнил фон новой темой
|
||||
FWfBitmapW := 0;
|
||||
FWfBitmapH := 0;
|
||||
SetLength(FWfPixels, 0);
|
||||
FreeAndNil(FWfIntfImg);
|
||||
end;
|
||||
|
||||
procedure TWaterfallView.ResetWfAvgBuf;
|
||||
begin
|
||||
FWfHigh := FWfManualHigh;
|
||||
FWfLow := FWfManualLow;
|
||||
FWaterfallDirty := True;
|
||||
end;
|
||||
|
||||
procedure TWaterfallView.ResetWfBuf;
|
||||
var i: Integer;
|
||||
begin
|
||||
for i := 0 to 1023 do FWaterfallBuf[i] := -130.0;
|
||||
FWfHigh := FWfManualHigh;
|
||||
FWfLow := FWfManualLow;
|
||||
FWaterfallDirty := True;
|
||||
end;
|
||||
|
||||
procedure TWaterfallView.SetWaterfallData(const Pixels: array of Single; Count: Integer);
|
||||
var i, N: Integer;
|
||||
begin
|
||||
N := Min(Count, 1024);
|
||||
for i := 0 to N - 1 do FWaterfallBuf[i] := Pixels[i];
|
||||
FWaterfallBufCount := N;
|
||||
Inc(FWfFrameCounter);
|
||||
if FWfFrameCounter >= Max(1, FWfFrameInterval) then
|
||||
begin
|
||||
FWfFrameCounter := 0;
|
||||
FWaterfallDirty := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWaterfallView.SetWaterfallBitmapSize(W, H: Integer);
|
||||
begin
|
||||
FWaterfallBitmap.SetSize(W, H);
|
||||
FWfBitmapW := 0;
|
||||
FWfBitmapH := 0;
|
||||
SetLength(FWfPixels, 0);
|
||||
FreeAndNil(FWfIntfImg);
|
||||
end;
|
||||
|
||||
procedure TWaterfallView.DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||
var MX: Integer; MarkerFreq: Double; MarkerLbl: string;
|
||||
begin
|
||||
MX := Round(FMarkerX / 1000.0 * W);
|
||||
if (MX < 0) or (MX >= W) then Exit;
|
||||
MarkerFreq := (FCenterFreq - FSpanHz / 2) + FMarkerX / 1000.0 * FSpanHz;
|
||||
MarkerLbl := FormatFreqWF(Round(MarkerFreq));
|
||||
C.Pen.Color := TColor($004444FF);
|
||||
C.Pen.Width := 1; C.Pen.Style := psSolid;
|
||||
C.MoveTo(MX, 0); C.LineTo(MX, H);
|
||||
C.Font.Color := TColor($004444FF);
|
||||
C.Font.Size := 7; C.Font.Name := 'Courier New';
|
||||
if MX + 4 + C.TextWidth(MarkerLbl) < W then
|
||||
C.TextOut(MX + 4, 4, MarkerLbl)
|
||||
else
|
||||
C.TextOut(MX - 4 - C.TextWidth(MarkerLbl), 4, MarkerLbl);
|
||||
end;
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// DrawWaterfall
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
procedure TWaterfallView.DrawWaterfall;
|
||||
var
|
||||
W, H, X: Integer;
|
||||
dB, frac, WatSrcF: Double;
|
||||
WatS0, WatS1: Integer;
|
||||
TargetLow, TargetHigh: Double;
|
||||
HistMinDB, HistMaxDB, HistStepDB: Double;
|
||||
NoiseFloorDB, SignalTopDB: Double;
|
||||
CumCount, LowTargetCount, HighTargetCount, HistIdx: Integer;
|
||||
WfHigh, WfLow, InvRange, Step: Double;
|
||||
Px: PLongWord;
|
||||
Desc: TRawImageDescription;
|
||||
Row, Col: Integer; RowPtr: PByte; Src: PLongWord; BPPi: Integer;
|
||||
Pal: LongWord; Hist: array[0..191] of Integer; SrcCount: Integer;
|
||||
const
|
||||
ALPHA_HIGH = 0.10; ALPHA_LOW = 0.08;
|
||||
WF_AUTO_OFFSET = -4.0; WF_MIN_RANGE = 48.0; WF_MAX_RANGE = 62.0;
|
||||
begin
|
||||
if FWaterfallBitmap = nil then Exit;
|
||||
W := FWaterfallBitmap.Width; H := FWaterfallBitmap.Height;
|
||||
if (W <= 0) or (H <= 0) then Exit;
|
||||
SrcCount := EnsureRange(FWaterfallBufCount, 2, 1024);
|
||||
|
||||
FillChar(Hist, SizeOf(Hist), 0);
|
||||
HistMinDB := -170.0; HistMaxDB := 22.0;
|
||||
HistStepDB := (HistMaxDB - HistMinDB) / Length(Hist);
|
||||
for X := 0 to SrcCount - 1 do
|
||||
begin
|
||||
HistIdx := EnsureRange(Trunc((FWaterfallBuf[X] - HistMinDB) / HistStepDB), 0, High(Hist));
|
||||
Inc(Hist[HistIdx]);
|
||||
end;
|
||||
|
||||
LowTargetCount := Round(SrcCount * 0.30);
|
||||
HighTargetCount := Round(SrcCount * 0.98);
|
||||
CumCount := 0; NoiseFloorDB := FWfLow; SignalTopDB := FWfHigh;
|
||||
for HistIdx := 0 to High(Hist) do
|
||||
begin
|
||||
CumCount := CumCount + Hist[HistIdx];
|
||||
if CumCount >= LowTargetCount then
|
||||
begin NoiseFloorDB := HistMinDB + (HistIdx + 0.5) * HistStepDB; Break; end;
|
||||
end;
|
||||
CumCount := 0;
|
||||
for HistIdx := 0 to High(Hist) do
|
||||
begin
|
||||
CumCount := CumCount + Hist[HistIdx];
|
||||
if CumCount >= HighTargetCount then
|
||||
begin SignalTopDB := HistMinDB + (HistIdx + 0.5) * HistStepDB; Break; end;
|
||||
end;
|
||||
|
||||
if FWfAGCEnabled then
|
||||
begin
|
||||
TargetLow := NoiseFloorDB + WF_AUTO_OFFSET + FWfAGCOffset;
|
||||
if FWfNFEnabled then
|
||||
TargetHigh := Max(TargetLow + WF_MIN_RANGE, SignalTopDB + 6.0)
|
||||
else
|
||||
TargetHigh := TargetLow + 52.0;
|
||||
if TargetHigh > TargetLow + WF_MAX_RANGE then TargetHigh := TargetLow + WF_MAX_RANGE;
|
||||
FWfLow := FWfLow + ALPHA_LOW * (TargetLow - FWfLow);
|
||||
FWfHigh := FWfHigh + ALPHA_HIGH * (TargetHigh - FWfHigh);
|
||||
end;
|
||||
WfHigh := FWfHigh; WfLow := FWfLow;
|
||||
if not FWfAGCEnabled then begin WfHigh := FWfManualHigh; WfLow := FWfManualLow; end;
|
||||
if WfHigh < WfLow + 40.0 then WfHigh := WfLow + 40.0;
|
||||
if WfHigh > 0.0 then WfHigh := 0.0;
|
||||
if WfLow < -160 then WfLow := -160;
|
||||
InvRange := 255.0 / (WfHigh - WfLow);
|
||||
|
||||
if (FWfBitmapW <> W) or (FWfBitmapH <> H) then
|
||||
begin
|
||||
FWfBitmapW := W; FWfBitmapH := H;
|
||||
SetLength(FWfPixels, W * H);
|
||||
Pal := $FF000000
|
||||
or ((LongWord(FTheme.Panel) and $FF) shl 16)
|
||||
or (LongWord(FTheme.Panel) and $FF00)
|
||||
or ((LongWord(FTheme.Panel) shr 16) and $FF);
|
||||
FillDWord(FWfPixels[0], W * H, Pal);
|
||||
FWfBitmap.SetSize(W, H);
|
||||
FreeAndNil(FWfIntfImg);
|
||||
end;
|
||||
|
||||
if H > 1 then Move(FWfPixels[0], FWfPixels[W], (H - 1) * SizeOf(LongWord) * W);
|
||||
|
||||
Step := (SrcCount - 1.0) / Max(1, W - 1);
|
||||
WatSrcF := 0.0; Px := @FWfPixels[0];
|
||||
for X := 0 to W - 1 do
|
||||
begin
|
||||
if FTXMode and (FTXSpanHz > 0) and (FSpanHz > 0) then
|
||||
begin
|
||||
dB := FCenterFreq - FSpanHz * 0.5 + X * FSpanHz / Max(1, W - 1) - FTXFreq;
|
||||
if (dB < -FTXSpanHz * 0.5) or (dB > FTXSpanHz * 0.5) then
|
||||
begin
|
||||
if H > 1 then Px^ := FWfPixels[X + W]
|
||||
else Px^ := 0;
|
||||
Inc(Px);
|
||||
WatSrcF := WatSrcF + Step;
|
||||
Continue;
|
||||
end;
|
||||
frac := (dB + FTXSpanHz * 0.5) / FTXSpanHz * (SrcCount - 1);
|
||||
WatS0 := Trunc(frac);
|
||||
if WatS0 > SrcCount - 2 then WatS0 := SrcCount - 2;
|
||||
WatS1 := WatS0 + 1;
|
||||
frac := frac - WatS0;
|
||||
dB := FWaterfallBuf[WatS0] * (1.0 - frac) + FWaterfallBuf[WatS1] * frac;
|
||||
end else
|
||||
begin
|
||||
WatS0 := Trunc(WatSrcF);
|
||||
if WatS0 > SrcCount - 2 then WatS0 := SrcCount - 2;
|
||||
WatS1 := WatS0 + 1;
|
||||
frac := WatSrcF - WatS0;
|
||||
dB := FWaterfallBuf[WatS0] * (1.0 - frac) + FWaterfallBuf[WatS1] * frac;
|
||||
end;
|
||||
if FLightTheme then
|
||||
Pal := WaterfallLightTheme(dB, WfLow, WfHigh)
|
||||
else
|
||||
Pal := WaterfallEnhancedColorThetis(dB, WfLow, WfHigh);
|
||||
Px^ := Pal; Inc(Px);
|
||||
WatSrcF := WatSrcF + Step;
|
||||
end;
|
||||
|
||||
if FWfIntfImg = nil then
|
||||
begin
|
||||
FWfIntfImg := TLazIntfImage.Create(W, H);
|
||||
InitRawDesc32WF(Desc, W, H);
|
||||
FWfIntfImg.DataDescription := Desc;
|
||||
FWfIntfImg.CreateData;
|
||||
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;
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// PaintWaterfall
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
procedure TWaterfallView.PaintWaterfall(Sender: TObject);
|
||||
var PB: TPaintBox; W, H: Integer;
|
||||
begin
|
||||
PB := TPaintBox(Sender);
|
||||
W := PB.Width; H := PB.Height;
|
||||
if (W <= 0) or (H <= 0) then Exit;
|
||||
if (FWfBitmap <> nil) and (FWfBitmap.Width = W) and (FWfBitmap.Height = H) then
|
||||
PB.Canvas.Draw(0, 0, FWfBitmap)
|
||||
else begin
|
||||
PB.Canvas.Brush.Color := FTheme.Panel;
|
||||
PB.Canvas.FillRect(Rect(0, 0, W, H));
|
||||
end;
|
||||
if FMarkerActive then DrawMarkerLine(PB.Canvas, W, H);
|
||||
end;
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user