mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
AlertOverlay переписан в общий builder: BuildAlertImage строит RGBA-образ плашки (попиксельная альфа, Canvas один раз на своём битмапе). GL грузит образ текстурой (кэш как был), CPU блендит кэш в raw-кадр — убраны canvas-путь в кадре и полный проход восстановления альфы битмапа. GL-плашка теперь 1:1 с CPU (было: упрощённый бокс по центру), позиция — правый верхний угол в обоих рендерах. Удалена мёртвая подсветочная линия (перекрывалась рамкой). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
193 lines
6.4 KiB
ObjectPascal
193 lines
6.4 KiB
ObjectPascal
unit AlertOverlay;
|
|
|
|
{
|
|
Общая плашка-алерт для CPU- и GL-рендеров спектра.
|
|
|
|
Единственный источник внешнего вида: BuildAlertImage строит готовый
|
|
RGBA-образ (straight alpha, попиксельная) ОДИН раз — Canvas используется
|
|
только здесь, на собственном маленьком битмапе. Дальше каждый рендер
|
|
использует образ по-своему без пер-кадрового жора CPU:
|
|
- GL: загружает как текстуру (один раз) и рисует квадом;
|
|
- CPU: блендит кэшированный образ в raw-кадр (BlendAlertImage,
|
|
~10 тыс. пикселей — без Canvas и синков режима доступа Qt6).
|
|
}
|
|
|
|
{$IFDEF FPC}
|
|
{$MODE Delphi}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, Graphics, Types, Math;
|
|
|
|
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 BuildAlertImage(const Title, Detail: string; out Img: TAlertImage);
|
|
const
|
|
INNER_X = 12; ACCENT_W = 4; BOX_H = 42;
|
|
var
|
|
B: TBitmap;
|
|
TitleW, DetailW, BoxW: Integer;
|
|
|
|
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
|
|
I := (Y * BoxW + Max(0, X1)) * 4;
|
|
for X := Max(0, X1) to Min(BoxW, X2) - 1 do
|
|
begin
|
|
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
|
|
B.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure BlendAlertImage(const Img: TAlertImage; Base: PByte;
|
|
BPL, RawW, RawH, X, Y: Integer);
|
|
var
|
|
SX, SY, DX, DY, I, A, InvA: Integer;
|
|
P: PByte;
|
|
begin
|
|
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.
|