Fix macOS pixel format: use ARGB byte order in all raw ScanLine blending

On macOS Cocoa, TBitmap.pf32bit stores pixels in ARGB order
   (byte0=A, byte1=R, byte2=G, byte3=B) rather than BGRA used on
   Linux/Windows. All raw ScanLine pixel blending and channel mapping
   is now guarded with { DARWIN} to use the correct offsets.

   Also fix PortAudio library discovery on macOS: probe .dylib names
   (including Homebrew paths) before falling through to .so/.dll.
This commit is contained in:
Uladzimir Karpenka
2026-05-28 21:47:54 +03:00
parent 4f127a30ad
commit a02d35fcce
8 changed files with 132 additions and 24 deletions
+6
View File
@@ -42,9 +42,15 @@ begin
Inc(Row, RR.Left * 4); Inc(Row, RR.Left * 4);
for X := RR.Left to RR.Right - 1 do for X := RR.Left to RR.Right - 1 do
begin 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[0] := Byte((Alpha * B + InvA * Row[0]) div 255);
Row[1] := Byte((Alpha * G + InvA * Row[1]) div 255); Row[1] := Byte((Alpha * G + InvA * Row[1]) div 255);
Row[2] := Byte((Alpha * R + InvA * Row[2]) div 255); Row[2] := Byte((Alpha * R + InvA * Row[2]) div 255);
{$ENDIF}
Inc(Row, 4); Inc(Row, 4);
end; end;
end; end;
+16 -3
View File
@@ -152,14 +152,22 @@ function PaInCallback(inputBuffer, outputBuffer: Pointer;
implementation implementation
const const
{$IFDEF UNIX} {$IFDEF DARWIN}
PA_IN_LIBS: array[0..3] of AnsiString = (
'libportaudio.dylib',
'libportaudio.2.dylib',
'/usr/local/lib/libportaudio.dylib',
'/opt/homebrew/lib/libportaudio.dylib'
);
{$ELSE}
{$IFDEF UNIX}
PA_IN_LIBS: array[0..3] of AnsiString = ( PA_IN_LIBS: array[0..3] of AnsiString = (
'libportaudio.so.2', 'libportaudio.so.2',
'libportaudio.so', 'libportaudio.so',
'libportaudio.so.2.0.0', 'libportaudio.so.2.0.0',
'libportaudio.so.0' 'libportaudio.so.0'
); );
{$ELSE} {$ELSE}
PA_IN_LIBS: array[0..5] of AnsiString = ( PA_IN_LIBS: array[0..5] of AnsiString = (
'portaudio_x64.dll', 'portaudio_x64.dll',
'portaudio_x86.dll', 'portaudio_x86.dll',
@@ -168,6 +176,7 @@ const
'libportaudio.dll', 'libportaudio.dll',
'libportaudio64.dll' 'libportaudio64.dll'
); );
{$ENDIF}
{$ENDIF} {$ENDIF}
PA_NO_ERROR = 0; PA_NO_ERROR = 0;
PA_FLOAT32 = LongWord(1); PA_FLOAT32 = LongWord(1);
@@ -276,7 +285,11 @@ begin
{$IFDEF WINDOWS} {$IFDEF WINDOWS}
FLastError := 'PortAudio DLL not found. Put portaudio_x64.dll, portaudio.dll, or libportaudio-2.dll next to ewsdr.exe'; FLastError := 'PortAudio DLL not found. Put portaudio_x64.dll, portaudio.dll, or libportaudio-2.dll next to ewsdr.exe';
{$ELSE} {$ELSE}
FLastError := 'libportaudio not found'; {$IFDEF DARWIN}
FLastError := 'libportaudio not found. Install via: brew install portaudio';
{$ELSE}
FLastError := 'libportaudio not found. sudo apt install libportaudio2';
{$ENDIF}
{$ENDIF} {$ENDIF}
Exit; Exit;
end; end;
+15 -2
View File
@@ -163,14 +163,22 @@ function PaOutCallback(inputBuffer, outputBuffer: Pointer;
implementation implementation
const const
{$IFDEF UNIX} {$IFDEF DARWIN}
PA_LIBS: array[0..3] of AnsiString = (
'libportaudio.dylib',
'libportaudio.2.dylib',
'/usr/local/lib/libportaudio.dylib',
'/opt/homebrew/lib/libportaudio.dylib'
);
{$ELSE}
{$IFDEF UNIX}
PA_LIBS: array[0..3] of AnsiString = ( PA_LIBS: array[0..3] of AnsiString = (
'libportaudio.so.2', 'libportaudio.so.2',
'libportaudio.so', 'libportaudio.so',
'libportaudio.so.2.0.0', 'libportaudio.so.2.0.0',
'libportaudio.so.0' 'libportaudio.so.0'
); );
{$ELSE} {$ELSE}
PA_LIBS: array[0..5] of AnsiString = ( PA_LIBS: array[0..5] of AnsiString = (
'portaudio_x64.dll', 'portaudio_x64.dll',
'portaudio_x86.dll', 'portaudio_x86.dll',
@@ -179,6 +187,7 @@ const
'libportaudio.dll', 'libportaudio.dll',
'libportaudio64.dll' 'libportaudio64.dll'
); );
{$ENDIF}
{$ENDIF} {$ENDIF}
PA_NO_ERROR = 0; PA_NO_ERROR = 0;
PA_FLOAT32 = LongWord(1); PA_FLOAT32 = LongWord(1);
@@ -292,7 +301,11 @@ begin
{$IFDEF WINDOWS} {$IFDEF WINDOWS}
FLastError := 'PortAudio DLL not found. Put portaudio_x64.dll, portaudio.dll, or libportaudio-2.dll next to ewsdr.exe'; FLastError := 'PortAudio DLL not found. Put portaudio_x64.dll, portaudio.dll, or libportaudio-2.dll next to ewsdr.exe';
{$ELSE} {$ELSE}
{$IFDEF DARWIN}
FLastError := 'libportaudio not found. Install via: brew install portaudio';
{$ELSE}
FLastError := 'libportaudio not found. sudo apt install libportaudio2'; FLastError := 'libportaudio not found. sudo apt install libportaudio2';
{$ENDIF}
{$ENDIF} {$ENDIF}
Exit; Exit;
end; end;
+6
View File
@@ -98,9 +98,15 @@ begin
Inc(Row, RR.Left * 4); Inc(Row, RR.Left * 4);
for X := RR.Left to RR.Right - 1 do for X := RR.Left to RR.Right - 1 do
begin 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[0] := Byte((Alpha * B + InvA * Row[0]) div 255);
Row[1] := Byte((Alpha * G + InvA * Row[1]) div 255); Row[1] := Byte((Alpha * G + InvA * Row[1]) div 255);
Row[2] := Byte((Alpha * R + InvA * Row[2]) div 255); Row[2] := Byte((Alpha * R + InvA * Row[2]) div 255);
{$ENDIF}
Inc(Row, 4); Inc(Row, 4);
end; end;
end; end;
+25 -1
View File
@@ -495,9 +495,15 @@ begin
Inc(Row, X1 * 4); Inc(Row, X1 * 4);
for X := X1 to X2 - 1 do for X := X1 to X2 - 1 do
begin 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[0] := Byte((Alpha * B + InvA * Row[0]) div 255);
Row[1] := Byte((Alpha * G + InvA * Row[1]) div 255); Row[1] := Byte((Alpha * G + InvA * Row[1]) div 255);
Row[2] := Byte((Alpha * R + InvA * Row[2]) div 255); Row[2] := Byte((Alpha * R + InvA * Row[2]) div 255);
{$ENDIF}
Inc(Row, 4); Inc(Row, 4);
end; end;
end; end;
@@ -513,6 +519,13 @@ var
begin begin
if (FSpectrumBitmap = nil) or (FGridBitmap = nil) then Exit; if (FSpectrumBitmap = nil) or (FGridBitmap = nil) then Exit;
if (W <= 0) or (H <= 0) then Exit; if (W <= 0) or (H <= 0) then Exit;
{$IFDEF DARWIN}
// On macOS/Cocoa, Canvas writes into a CGBitmapContext; ScanLine reads the
// raw backing buffer which is not synced until BeginUpdate is called on the
// source bitmap. Canvas.Draw goes through CoreGraphics and sees the live
// CGContext, so use it instead of the direct ScanLine copy.
FSpectrumBitmap.Canvas.Draw(0, 0, FGridBitmap);
{$ELSE}
FSpectrumBitmap.BeginUpdate(False); FSpectrumBitmap.BeginUpdate(False);
try try
for Y := 0 to H - 1 do for Y := 0 to H - 1 do
@@ -525,6 +538,7 @@ begin
finally finally
FSpectrumBitmap.EndUpdate(False); FSpectrumBitmap.EndUpdate(False);
end; end;
{$ENDIF}
end; end;
procedure TSpectrumView.DrawADCOverloadOverlay(C: TCanvas; W, H: Integer); procedure TSpectrumView.DrawADCOverloadOverlay(C: TCanvas; W, H: Integer);
@@ -576,9 +590,15 @@ begin
if SpPts[Col].Y <= Row then if SpPts[Col].Y <= Row then
begin begin
Off := Col * 4; Off := Col * 4;
{$IFDEF DARWIN}
RowPtr[Off+1] := (GradR * Alpha + RowPtr[Off+1] * (200 - Alpha)) div 200;
RowPtr[Off+2] := (GradG * Alpha + RowPtr[Off+2] * (200 - Alpha)) div 200;
RowPtr[Off+3] := (GradB * Alpha + RowPtr[Off+3] * (200 - Alpha)) div 200;
{$ELSE}
RowPtr[Off] := (GradB * Alpha + RowPtr[Off] * (200 - Alpha)) div 200; RowPtr[Off] := (GradB * Alpha + RowPtr[Off] * (200 - Alpha)) div 200;
RowPtr[Off+1] := (GradG * Alpha + RowPtr[Off+1] * (200 - Alpha)) div 200; RowPtr[Off+1] := (GradG * Alpha + RowPtr[Off+1] * (200 - Alpha)) div 200;
RowPtr[Off+2] := (GradR * Alpha + RowPtr[Off+2] * (200 - Alpha)) div 200; RowPtr[Off+2] := (GradR * Alpha + RowPtr[Off+2] * (200 - Alpha)) div 200;
{$ENDIF}
end; end;
end; end;
FSpectrumBitmap.EndUpdate(False); FSpectrumBitmap.EndUpdate(False);
@@ -913,7 +933,11 @@ begin
FSpectrumBitmap.BeginUpdate(False); FSpectrumBitmap.BeginUpdate(False);
for i := 0 to H - 1 do for i := 0 to H - 1 do
begin begin
AlphaPtr := PByte(FSpectrumBitmap.ScanLine[i]) + 3; {$IFDEF DARWIN}
AlphaPtr := PByte(FSpectrumBitmap.ScanLine[i]); // ARGB: alpha at byte 0
{$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; AlphaPtr^ := $FF;
+13 -4
View File
@@ -185,10 +185,15 @@ begin
Src := PByte(B.ScanLine[Y]); Src := PByte(B.ScanLine[Y]);
for X := 0 to T.W - 1 do for X := 0 to T.W - 1 do
begin begin
{$IFDEF DARWIN}
// ARGB: byte0=A, byte1=R, byte2=G, byte3=B
IsKey := Keyed and (Src[1] = $FF) and (Src[2] = $00) and (Src[3] = $FF);
Buf[I] := Src[1]; Buf[I+1] := Src[2]; Buf[I+2] := Src[3];
{$ELSE}
// BGRA: byte0=B, byte1=G, byte2=R
IsKey := Keyed and (Src[0] = $FF) and (Src[1] = $00) and (Src[2] = $FF); IsKey := Keyed and (Src[0] = $FF) and (Src[1] = $00) and (Src[2] = $FF);
Buf[I] := Src[2]; Buf[I] := Src[2]; Buf[I+1] := Src[1]; Buf[I+2] := Src[0];
Buf[I + 1] := Src[1]; {$ENDIF}
Buf[I + 2] := Src[0];
if IsKey then Buf[I + 3] := 0 else Buf[I + 3] := FixedAlpha; if IsKey then Buf[I + 3] := 0 else Buf[I + 3] := FixedAlpha;
Inc(Src, 4); Inc(Src, 4);
Inc(I, 4); Inc(I, 4);
@@ -230,7 +235,11 @@ begin
Src := PByte(B.ScanLine[Y]); Src := PByte(B.ScanLine[Y]);
for X := 0 to T.W - 1 do for X := 0 to T.W - 1 do
begin begin
A := Max(Src[0], Max(Src[1], Src[2])); {$IFDEF DARWIN}
A := Max(Src[1], Max(Src[2], Src[3])); // ARGB: R,G,B at bytes 1,2,3
{$ELSE}
A := Max(Src[0], Max(Src[1], Src[2])); // BGRA: B,G,R at bytes 0,1,2
{$ENDIF}
Buf[I] := R; Buf[I] := R;
Buf[I + 1] := G; Buf[I + 1] := G;
Buf[I + 2] := BB; Buf[I + 2] := BB;
+11 -1
View File
@@ -173,13 +173,23 @@ begin
Inc(DstRow, DstX * 4); Inc(DstRow, DstX * 4);
for X := SX0 to SX1 - 1 do for X := SX0 to SX1 - 1 do
begin begin
// KEY_COLOR is clFuchsia in pf32bit B,G,R order. {$IFDEF DARWIN}
// ARGB layout: byte0=A, byte1=R, byte2=G, byte3=B
if not ((SrcRow[1] = $FF) and (SrcRow[2] = $00) and (SrcRow[3] = $FF)) then
begin
DstRow[1] := Byte((Alpha * SrcRow[1] + InvA * DstRow[1]) div 255);
DstRow[2] := Byte((Alpha * SrcRow[2] + InvA * DstRow[2]) div 255);
DstRow[3] := Byte((Alpha * SrcRow[3] + InvA * DstRow[3]) div 255);
end;
{$ELSE}
// BGRA layout: byte0=B, byte1=G, byte2=R
if not ((SrcRow[0] = $FF) and (SrcRow[1] = $00) and (SrcRow[2] = $FF)) then if not ((SrcRow[0] = $FF) and (SrcRow[1] = $00) and (SrcRow[2] = $FF)) then
begin begin
DstRow[0] := Byte((Alpha * SrcRow[0] + InvA * DstRow[0]) div 255); DstRow[0] := Byte((Alpha * SrcRow[0] + InvA * DstRow[0]) div 255);
DstRow[1] := Byte((Alpha * SrcRow[1] + InvA * DstRow[1]) div 255); DstRow[1] := Byte((Alpha * SrcRow[1] + InvA * DstRow[1]) div 255);
DstRow[2] := Byte((Alpha * SrcRow[2] + InvA * DstRow[2]) div 255); DstRow[2] := Byte((Alpha * SrcRow[2] + InvA * DstRow[2]) div 255);
end; end;
{$ENDIF}
Inc(SrcRow, 4); Inc(SrcRow, 4);
Inc(DstRow, 4); Inc(DstRow, 4);
end; end;
+39 -12
View File
@@ -472,12 +472,17 @@ begin
Inc(Row, RR.Left * 4); Inc(Row, RR.Left * 4);
for X := RR.Left to RR.Right - 1 do for X := RR.Left to RR.Right - 1 do
begin begin
BB := Row[0]; {$IFDEF DARWIN}
BG := Row[1]; BB := Row[3]; BG := Row[2]; BR := Row[1];
BR := Row[2]; Row[1] := Byte((SR * Alpha + BR * (255 - Alpha)) div 255);
Row[2] := Byte((SG * Alpha + BG * (255 - Alpha)) div 255);
Row[3] := Byte((SB * Alpha + BB * (255 - Alpha)) div 255);
{$ELSE}
BB := Row[0]; BG := Row[1]; BR := Row[2];
Row[0] := Byte((SB * Alpha + BB * (255 - Alpha)) div 255); Row[0] := Byte((SB * Alpha + BB * (255 - Alpha)) div 255);
Row[1] := Byte((SG * Alpha + BG * (255 - Alpha)) div 255); Row[1] := Byte((SG * Alpha + BG * (255 - Alpha)) div 255);
Row[2] := Byte((SR * Alpha + BR * (255 - Alpha)) div 255); Row[2] := Byte((SR * Alpha + BR * (255 - Alpha)) div 255);
{$ENDIF}
Inc(Row, 4); Inc(Row, 4);
end; end;
end; end;
@@ -574,9 +579,15 @@ begin
if SpPts[Col].Y <= Row then if SpPts[Col].Y <= Row then
begin begin
Off := Col * 4; Off := Col * 4;
{$IFDEF DARWIN}
RowPtr[Off+1] := (GradR * Alpha + RowPtr[Off+1] * (200 - Alpha)) div 200;
RowPtr[Off+2] := (GradG * Alpha + RowPtr[Off+2] * (200 - Alpha)) div 200;
RowPtr[Off+3] := (GradB * Alpha + RowPtr[Off+3] * (200 - Alpha)) div 200;
{$ELSE}
RowPtr[Off] := (GradB * Alpha + RowPtr[Off] * (200 - Alpha)) div 200; RowPtr[Off] := (GradB * Alpha + RowPtr[Off] * (200 - Alpha)) div 200;
RowPtr[Off + 1] := (GradG * Alpha + RowPtr[Off + 1] * (200 - Alpha)) div 200; RowPtr[Off+1] := (GradG * Alpha + RowPtr[Off+1] * (200 - Alpha)) div 200;
RowPtr[Off + 2] := (GradR * Alpha + RowPtr[Off + 2] * (200 - Alpha)) div 200; RowPtr[Off+2] := (GradR * Alpha + RowPtr[Off+2] * (200 - Alpha)) div 200;
{$ENDIF}
end; end;
end; end;
finally finally
@@ -984,21 +995,37 @@ begin
Src := PByte(FLabelBitmap.ScanLine[Y]); Src := PByte(FLabelBitmap.ScanLine[Y]);
for X := 0 to FLabelBitmap.Width - 1 do for X := 0 to FLabelBitmap.Width - 1 do
begin begin
A := Max(Src[0], Max(Src[1], Src[2])); {$IFDEF DARWIN}
// ARGB: byte0=A, byte1=R, byte2=G, byte3=B
A := Max(Src[1], Max(Src[2], Src[3]));
if A < 6 then A := 0; if A < 6 then A := 0;
if A > 0 then if A > 0 then
begin begin
Buf[I] := Byte(Min(255, Src[2] * 255 div A)); Buf[I] := Byte(Min(255, Src[1] * 255 div A)); // R
Buf[I + 1] := Byte(Min(255, Src[1] * 255 div A)); Buf[I + 1] := Byte(Min(255, Src[2] * 255 div A)); // G
Buf[I + 2] := Byte(Min(255, Src[0] * 255 div A)); Buf[I + 2] := Byte(Min(255, Src[3] * 255 div A)); // B
A := Byte(Min(255, A + (255 - A) div 3)); A := Byte(Min(255, A + (255 - A) div 3));
end end
else else
begin begin
Buf[I] := 0; Buf[I] := 0; Buf[I + 1] := 0; Buf[I + 2] := 0;
Buf[I + 1] := 0;
Buf[I + 2] := 0;
end; end;
{$ELSE}
// BGRA: byte0=B, byte1=G, byte2=R
A := Max(Src[0], Max(Src[1], Src[2]));
if A < 6 then A := 0;
if A > 0 then
begin
Buf[I] := Byte(Min(255, Src[2] * 255 div A)); // R
Buf[I + 1] := Byte(Min(255, Src[1] * 255 div A)); // G
Buf[I + 2] := Byte(Min(255, Src[0] * 255 div A)); // B
A := Byte(Min(255, A + (255 - A) div 3));
end
else
begin
Buf[I] := 0; Buf[I + 1] := 0; Buf[I + 2] := 0;
end;
{$ENDIF}
Buf[I + 3] := A; Buf[I + 3] := A;
Inc(Src, 4); Inc(Src, 4);
Inc(I, 4); Inc(I, 4);