692 lines
20 KiB
ObjectPascal

// SPDX-License-Identifier: LGPL-3.0-linking-exception
{ @abstract(Supplies a bitmap in XYZ colorspace with floating-point values (16-bit per channel)
and transparency.)
Pixels are in TXYZA format, based on
[CIE 1931 XYZ colorspace](https://en.wikipedia.org/wiki/CIE_1931_color_space) (TXYZAColorspace).
This format is generally used as an intermediary format.
**Bitmap units**: BGRABitmap, ExpandedBitmap, BGRAGrayscaleMask, LinearRGBABitmap, WordXYZABitmap, XYZABitmap.
}
unit XYZABitmap;
{$mode objfpc}{$H+}
{$i bgrabitmap.inc}
{$IFNDEF BGRABITMAP_EXTENDED_COLORSPACE}{$STOP This unit need extended colorspaces}{$ENDIF}
interface
uses
BGRAClasses, SysUtils, BGRABitmapTypes, UniversalDrawer;
type
{* Bitmap with TXYZA pixel format, [CIE 1931 XYZ](https://en.wikipedia.org/wiki/CIE_1931_color_space)
with floating point values. }
TXYZABitmap = class(specialize TGenericUniversalBitmap<TXYZA,TXYZAColorspace>)
protected
function InternalNew: TCustomUniversalBitmap; override;
procedure AssignTransparentPixel(out ADest); override;
public
class procedure SolidBrush(out ABrush: TUniversalBrush; const AColor: TXYZA; ADrawMode: TDrawMode = dmDrawWithTransparency); override;
class procedure ScannerBrush(out ABrush: TUniversalBrush; AScanner: IBGRAScanner; ADrawMode: TDrawMode = dmDrawWithTransparency;
AOffsetX: integer = 0; AOffsetY: integer = 0); override;
class procedure MaskBrush(out ABrush: TUniversalBrush; AScanner: IBGRAScanner;
AOffsetX: integer = 0; AOffsetY: integer = 0); override;
class procedure EraseBrush(out ABrush: TUniversalBrush; AAlpha: Word); override;
class procedure AlphaBrush(out ABrush: TUniversalBrush; AAlpha: Word); override;
{** Replaces imaginary colors by the _AAfter_ }
procedure ReplaceImaginary(const AAfter: TXYZA);
end;
const
XYZATransparent : TXYZA = (X:0; Y:0; Z:0; alpha:0);
operator = (const c1, c2: TXYZA): boolean; inline;
{ Checks that the color is real, meaning that it can be experienced. To be real a color
need to have positive values and be a possible stimulation of the cones. The latter
are not independant so for example the green cones cannot be stimulated alone. }
function IsRealColor(xyza: TXYZA): boolean;
implementation
uses BGRAFillInfo;
operator = (const c1, c2: TXYZA): boolean;
begin
if (c1.alpha = 0) and (c2.alpha = 0) then
Result := True
else
Result := (c1.alpha = c2.alpha) and (c1.X = c2.X) and
(c1.Y = c2.Y) and (c1.Z = c2.Z);
end;
var
xyHorseshoePolygon: TFillShapeInfo;
procedure MakeXYHorseshoePolygon;
var
pts: array of TPointF;
i: Integer;
n: Single;
begin
setlength(pts, length(SpectralLocus));
for i := 0 to high(pts) do
begin
n := SpectralLocus[i].X+SpectralLocus[i].Y+SpectralLocus[i].Z;
pts[i].x := SpectralLocus[i].X/n;
pts[i].y := SpectralLocus[i].Y/n;
end;
xyHorseshoePolygon := TFillPolyInfo.Create(pts, false);
pts := nil;
end;
function IsRealColor(xyza: TXYZA): boolean;
const dim = 0.015;
var
n: Single;
begin
xyza.ChromaticAdapt(GetReferenceWhiteIndirect^, ReferenceWhite2E);
if (xyza.Y < 0) or (xyza.Y > 1) or (xyza.X < 0) or (xyza.Z < 0) then exit(false);
if (xyza.Y = 0) then exit((xyza.X=0) and (xyza.Z=0));
if xyHorseshoePolygon = nil then MakeXYHorseshoePolygon;
n := xyza.X + xyza.Y + xyza.Z;
result := xyHorseshoePolygon.IsPointInside(xyza.X/n*(1-dim)+1/3*dim, xyza.Y/n*(1-dim)+1/3*dim, false);
end;
procedure XYZASolidBrushSkipPixels({%H-}AFixedData: Pointer;
AContextData: PUniBrushContext; {%H-}AAlpha: Word; ACount: integer);
begin
inc(PXYZA(AContextData^.Dest), ACount);
end;
procedure XYZAChunkSetPixels(
ASource: PXYZA; ADest: PXYZA;
AAlpha: Word; ACount: integer; ASourceStride: integer); inline;
const oneOver65535 = 1/65535;
var
alphaOver, finalAlpha, finalAlphaInv, residualAlpha: single;
begin
if AAlpha=0 then exit;
if AAlpha=65535 then
begin
while ACount > 0 do
begin
ADest^ := ASource^;
inc(ADest);
dec(ACount);
inc(PByte(ASource), ASourceStride);
end;
end else
begin
alphaOver := AAlpha*single(oneOver65535);
while ACount > 0 do
begin
residualAlpha := ADest^.alpha*(1-alphaOver);
finalAlpha := residualAlpha + ASource^.alpha*alphaOver;
if finalAlpha <= 0 then ADest^ := XYZATransparent else
begin
if finalAlpha > 1 then finalAlpha := 1;
ADest^.alpha:= finalAlpha;
finalAlphaInv := 1/finalAlpha;
ADest^.X := (ADest^.X*residualAlpha +
ASource^.X*(finalAlpha-residualAlpha) ) * finalAlphaInv;
ADest^.Y := (ADest^.Y*residualAlpha +
ASource^.Y*(finalAlpha-residualAlpha) ) * finalAlphaInv;
ADest^.Z := (ADest^.Z*residualAlpha +
ASource^.Z*(finalAlpha-residualAlpha) ) * finalAlphaInv;
end;
inc(ADest);
dec(ACount);
inc(PByte(ASource), ASourceStride);
end;
end;
end;
procedure XYZASolidBrushSetPixels(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
pDest: PXYZA;
begin
pDest := PXYZA(AContextData^.Dest);
XYZAChunkSetPixels( PXYZA(AFixedData), pDest, AAlpha, ACount, 0);
inc(pDest, ACount);
AContextData^.Dest := pDest;
end;
procedure XYZAChunkDrawPixels(
ASource: PXYZA; ADest: PXYZA;
AAlpha: Word; ACount: integer; ASourceStride: integer); inline;
const oneOver65535 = 1/65535;
var
alphaOver, srcAlphaOver, finalAlpha, finalAlphaInv, residualAlpha: single;
begin
if AAlpha=0 then exit;
alphaOver := AAlpha*single(oneOver65535);
while ACount > 0 do
begin
srcAlphaOver := ASource^.alpha*alphaOver;
if srcAlphaOver >= 1 then
ADest^ := ASource^
else
begin
residualAlpha := ADest^.alpha*(1-srcAlphaOver);
finalAlpha := residualAlpha + srcAlphaOver;
if finalAlpha <= 0 then ADest^ := XYZATransparent else
begin
if finalAlpha > 1 then finalAlpha := 1;
ADest^.alpha:= finalAlpha;
finalAlphaInv := 1/finalAlpha;
ADest^.X := (ADest^.X*residualAlpha +
ASource^.X*srcAlphaOver ) * finalAlphaInv;
ADest^.Y := (ADest^.Y*residualAlpha +
ASource^.Y*srcAlphaOver ) * finalAlphaInv;
ADest^.Z := (ADest^.Z*residualAlpha +
ASource^.Z*srcAlphaOver ) * finalAlphaInv;
end;
end;
inc(ADest);
dec(ACount);
inc(PByte(ASource), ASourceStride);
end;
end;
procedure XYZASolidBrushDrawPixels(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
pDest: PXYZA;
begin
pDest := PXYZA(AContextData^.Dest);
XYZAChunkDrawPixels( PXYZA(AFixedData), pDest, AAlpha, ACount, 0);
inc(pDest, ACount);
AContextData^.Dest := pDest;
end;
type
PXYZAScannerBrushFixedData = ^TXYZAScannerBrushFixedData;
TXYZAScannerBrushFixedData = record
Scanner: Pointer; //avoid ref count by using pointer type
OffsetX, OffsetY: integer;
Conversion: TBridgedConversion;
end;
procedure XYZAScannerBrushInitContext(AFixedData: Pointer;
AContextData: PUniBrushContext);
begin
with PXYZAScannerBrushFixedData(AFixedData)^ do
IBGRAScanner(Scanner).ScanMoveTo(AContextData^.Ofs.X + OffsetX,
AContextData^.Ofs.Y + OffsetY);
end;
procedure XYZAScannerConvertBrushSetPixels(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
psrc: Pointer;
pDest: PXYZA;
qty, pixSize: Integer;
buf: packed array[0..7] of TXYZA;
begin
with PXYZAScannerBrushFixedData(AFixedData)^ do
begin
if AAlpha = 0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
IBGRAScanner(Scanner).ScanSkipPixels(ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
pixSize := IBGRAScanner(Scanner).GetScanCustomColorspace.GetSize;
while ACount > 0 do
begin
if ACount > length(buf) then qty := length(buf) else qty := ACount;
IBGRAScanner(Scanner).ScanNextCustomChunk(qty, psrc);
Conversion.Convert(psrc, @buf, qty, pixSize, sizeof(TXYZA), nil);
XYZAChunkSetPixels(@buf, pDest, AAlpha, qty, sizeof(TXYZA) );
inc(pDest, qty);
dec(ACount, qty);
end;
AContextData^.Dest := pDest;
end;
end;
procedure XYZAScannerChunkBrushSetPixels(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
psrc: Pointer;
pDest: PXYZA;
qty: Integer;
begin
with PXYZAScannerBrushFixedData(AFixedData)^ do
begin
if AAlpha = 0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
IBGRAScanner(Scanner).ScanSkipPixels(ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
while ACount > 0 do
begin
qty := ACount;
IBGRAScanner(Scanner).ScanNextCustomChunk(qty, psrc);
XYZAChunkSetPixels(PXYZA(psrc), pDest, AAlpha, qty, sizeof(TXYZA) );
inc(pDest, qty);
dec(ACount, qty);
end;
AContextData^.Dest := pDest;
end;
end;
procedure XYZAChunkSetPixelsExceptTransparent(
ASource: PXYZA; ADest: PXYZA;
AAlpha: Word; ACount: integer; ASourceStride: integer); inline;
const oneOver65535 = 1/65535;
var
alphaOver, finalAlpha, finalAlphaInv, residualAlpha: single;
begin
if AAlpha=0 then exit;
if AAlpha=65535 then
begin
while ACount > 0 do
begin
if ASource^.alpha >= 1 then
ADest^ := ASource^;
inc(ADest);
dec(ACount);
inc(PByte(ASource), ASourceStride);
end;
end else
begin
alphaOver := AAlpha*single(oneOver65535);
while ACount > 0 do
begin
if ASource^.alpha >= 1 then
begin
residualAlpha := ADest^.alpha*(1-alphaOver);
finalAlpha := residualAlpha + alphaOver;
if finalAlpha <= 0 then ADest^ := XYZATransparent else
begin
ADest^.alpha:= finalAlpha;
finalAlphaInv := 1/finalAlpha;
ADest^.X := (ADest^.X*residualAlpha +
ASource^.X*(finalAlpha-residualAlpha) ) * finalAlphaInv;
ADest^.Y := (ADest^.Y*residualAlpha +
ASource^.Y*(finalAlpha-residualAlpha) ) * finalAlphaInv;
ADest^.Z := (ADest^.Z*residualAlpha +
ASource^.Z*(finalAlpha-residualAlpha) ) * finalAlphaInv;
end;
end;
inc(ADest);
dec(ACount);
inc(PByte(ASource), ASourceStride);
end;
end;
end;
procedure XYZAScannerChunkBrushSetPixelsExceptTransparent(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
pDest: PXYZA;
qty: Integer;
psrc: Pointer;
begin
with PXYZAScannerBrushFixedData(AFixedData)^ do
begin
if AAlpha = 0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
IBGRAScanner(Scanner).ScanSkipPixels(ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
while ACount > 0 do
begin
qty := ACount;
IBGRAScanner(Scanner).ScanNextCustomChunk(qty, psrc);
XYZAChunkSetPixelsExceptTransparent(PXYZA(psrc), pDest, AAlpha, qty, sizeof(TXYZA) );
inc(pDest, qty);
dec(ACount, qty);
end;
AContextData^.Dest := pDest;
end;
end;
procedure XYZAScannerConvertBrushSetPixelsExceptTransparent(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
psrc: Pointer;
pDest: PXYZA;
qty, pixSize: Integer;
buf: packed array[0..7] of TXYZA;
begin
with PXYZAScannerBrushFixedData(AFixedData)^ do
begin
if AAlpha = 0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
IBGRAScanner(Scanner).ScanSkipPixels(ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
pixSize := IBGRAScanner(Scanner).GetScanCustomColorspace.GetSize;
while ACount > 0 do
begin
if ACount > length(buf) then qty := length(buf) else qty := ACount;
IBGRAScanner(Scanner).ScanNextCustomChunk(qty, psrc);
Conversion.Convert(psrc, @buf, qty, pixSize, sizeof(TXYZA), nil);
XYZAChunkSetPixelsExceptTransparent(@buf, pDest, AAlpha, qty, sizeof(TXYZA) );
inc(pDest, qty);
dec(ACount, qty);
end;
AContextData^.Dest := pDest;
end;
end;
procedure XYZAScannerChunkBrushDrawPixels(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
psrc: Pointer;
qty: Integer;
pDest: PXYZA;
begin
with PXYZAScannerBrushFixedData(AFixedData)^ do
begin
if AAlpha = 0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
IBGRAScanner(Scanner).ScanSkipPixels(ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
while ACount > 0 do
begin
qty := ACount;
IBGRAScanner(Scanner).ScanNextCustomChunk(qty, psrc);
XYZAChunkDrawPixels(PXYZA(psrc), pDest, AAlpha, qty, sizeof(TXYZA) );
inc(pDest, qty);
dec(ACount, qty);
end;
AContextData^.Dest := pDest;
end;
end;
procedure XYZAScannerConvertBrushDrawPixels(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
psrc: Pointer;
pDest: PXYZA;
qty, pixSize: Integer;
buf: packed array[0..7] of TXYZA;
begin
with PXYZAScannerBrushFixedData(AFixedData)^ do
begin
if AAlpha = 0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
IBGRAScanner(Scanner).ScanSkipPixels(ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
pixSize := IBGRAScanner(Scanner).GetScanCustomColorspace.GetSize;
while ACount > 0 do
begin
if ACount > length(buf) then qty := length(buf) else qty := ACount;
IBGRAScanner(Scanner).ScanNextCustomChunk(qty, psrc);
Conversion.Convert(psrc, @buf, qty, pixSize, sizeof(TXYZA), nil);
XYZAChunkDrawPixels(@buf, pDest, AAlpha, qty, sizeof(TXYZA) );
inc(pDest, qty);
dec(ACount, qty);
end;
AContextData^.Dest := pDest;
end;
end;
procedure XYZAMaskBrushApply(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
var
pDest: PXYZA;
qty, maskStride: Integer;
pMask: PByteMask;
factor: single;
begin
with PXYZAScannerBrushFixedData(AFixedData)^ do
begin
if AAlpha = 0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
IBGRAScanner(Scanner).ScanSkipPixels(ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
factor := AAlpha/(65535*255);
while ACount > 0 do
begin
qty := ACount;
IBGRAScanner(Scanner).ScanNextMaskChunk(qty, pMask, maskStride);
dec(ACount,qty);
while qty > 0 do
begin
pDest^.alpha := pDest^.alpha*pMask^.gray*factor;
if pDest^.alpha = 0 then pDest^ := XYZATransparent;
inc(pDest);
inc(pMask, maskStride);
dec(qty);
end;
end;
PXYZA(AContextData^.Dest) := pDest;
end;
end;
procedure XYZAAlphaBrushSetPixels(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
const oneOver65535 = 1/65535;
var
pDest: PXYZA;
alphaOver, residualAlpha, finalAlpha: single;
begin
if AAlpha=0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
if AAlpha=65535 then
begin
finalAlpha := PSingle(AFixedData)^;
while ACount > 0 do
begin
pDest^.alpha := finalAlpha;
inc(pDest);
dec(ACount);
end;
end else
begin
alphaOver := AAlpha*single(oneOver65535);
while ACount > 0 do
begin
residualAlpha := pDest^.alpha*(1-alphaOver);
finalAlpha := residualAlpha + PSingle(AFixedData)^*alphaOver;
pDest^.alpha:= finalAlpha;
inc(pDest);
dec(ACount);
end;
end;
PXYZA(AContextData^.Dest) := pDest;
end;
procedure XYZAAlphaBrushErasePixels(AFixedData: Pointer;
AContextData: PUniBrushContext; AAlpha: Word; ACount: integer);
const oneOver65535 = 1/65535;
var
pDest: PXYZA;
alphaMul, finalAlpha: single;
begin
if AAlpha=0 then
begin
inc(PXYZA(AContextData^.Dest), ACount);
exit;
end;
pDest := PXYZA(AContextData^.Dest);
if AAlpha<>65535 then
alphaMul := 1-PSingle(AFixedData)^*AAlpha*single(oneOver65535)
else
alphaMul := 1-PSingle(AFixedData)^;
while ACount > 0 do
begin
finalAlpha := pDest^.alpha*alphaMul;
if finalAlpha <= 0 then pDest^ := XYZATransparent else
pDest^.alpha:= finalAlpha;
inc(pDest);
dec(ACount);
end;
PXYZA(AContextData^.Dest) := pDest;
end;
{ TXYZABitmap }
function TXYZABitmap.InternalNew: TCustomUniversalBitmap;
begin
Result:= TXYZABitmap.Create;
end;
procedure TXYZABitmap.AssignTransparentPixel(out ADest);
begin
TXYZA(ADest) := XYZATransparent;
end;
class procedure TXYZABitmap.SolidBrush(out ABrush: TUniversalBrush;
const AColor: TXYZA; ADrawMode: TDrawMode);
begin
ABrush.Colorspace:= TXYZAColorspace;
PXYZA(@ABrush.FixedData)^ := AColor;
case ADrawMode of
dmSet: ABrush.InternalPutNextPixels:= @XYZASolidBrushSetPixels;
dmSetExceptTransparent:
if AColor.alpha < 1 then
ABrush.InternalPutNextPixels:= @XYZASolidBrushSkipPixels
else
begin
ABrush.InternalPutNextPixels:= @XYZASolidBrushSetPixels;
ABrush.DoesNothing := true;
end;
dmDrawWithTransparency,dmLinearBlend:
if AColor.alpha<=0 then
begin
ABrush.InternalPutNextPixels:= @XYZASolidBrushSkipPixels;
ABrush.DoesNothing := true;
end
else if AColor.alpha>=1 then
ABrush.InternalPutNextPixels:= @XYZASolidBrushSetPixels
else
ABrush.InternalPutNextPixels:= @XYZASolidBrushDrawPixels;
dmXor: raise exception.Create('Xor mode not available with floating point values');
end;
end;
class procedure TXYZABitmap.ScannerBrush(out ABrush: TUniversalBrush;
AScanner: IBGRAScanner; ADrawMode: TDrawMode;
AOffsetX: integer; AOffsetY: integer);
var
sourceSpace: TColorspaceAny;
begin
ABrush.Colorspace:= TXYZAColorspace;
with PXYZAScannerBrushFixedData(@ABrush.FixedData)^ do
begin
Scanner := Pointer(AScanner);
OffsetX := AOffsetX;
OffsetY := AOffsetY;
end;
ABrush.InternalInitContext:= @XYZAScannerBrushInitContext;
sourceSpace := AScanner.GetScanCustomColorspace;
if sourceSpace = TXYZAColorspace then
begin
case ADrawMode of
dmSet: ABrush.InternalPutNextPixels:= @XYZAScannerChunkBrushSetPixels;
dmSetExceptTransparent: ABrush.InternalPutNextPixels:= @XYZAScannerChunkBrushSetPixelsExceptTransparent;
dmDrawWithTransparency,dmLinearBlend:
ABrush.InternalPutNextPixels:= @XYZAScannerChunkBrushDrawPixels;
dmXor: raise exception.Create('Xor mode not available with floating point values');
end;
end else
begin
with PXYZAScannerBrushFixedData(@ABrush.FixedData)^ do
Conversion := sourceSpace.GetBridgedConversion(TXYZAColorspace);
case ADrawMode of
dmSet: ABrush.InternalPutNextPixels:= @XYZAScannerConvertBrushSetPixels;
dmSetExceptTransparent: ABrush.InternalPutNextPixels:= @XYZAScannerConvertBrushSetPixelsExceptTransparent;
dmDrawWithTransparency,dmLinearBlend:
ABrush.InternalPutNextPixels:= @XYZAScannerConvertBrushDrawPixels;
dmXor: raise exception.Create('Xor mode not available with floating point values');
end;
end;
end;
class procedure TXYZABitmap.MaskBrush(out ABrush: TUniversalBrush;
AScanner: IBGRAScanner; AOffsetX: integer; AOffsetY: integer);
begin
ABrush.Colorspace:= TXYZAColorspace;
with PXYZAScannerBrushFixedData(@ABrush.FixedData)^ do
begin
Scanner := Pointer(AScanner);
OffsetX := AOffsetX;
OffsetY := AOffsetY;
end;
ABrush.InternalInitContext:= @XYZAScannerBrushInitContext;
ABrush.InternalPutNextPixels:= @XYZAMaskBrushApply;
end;
class procedure TXYZABitmap.EraseBrush(out ABrush: TUniversalBrush;
AAlpha: Word);
begin
if AAlpha = 0 then
begin
SolidBrush(ABrush, XYZATransparent, dmDrawWithTransparency);
exit;
end;
ABrush.Colorspace:= TXYZAColorspace;
PSingle(@ABrush.FixedData)^ := AAlpha/65535;
ABrush.InternalInitContext:= nil;
ABrush.InternalPutNextPixels:= @XYZAAlphaBrushErasePixels;
end;
class procedure TXYZABitmap.AlphaBrush(out ABrush: TUniversalBrush;
AAlpha: Word);
begin
if AAlpha = 0 then
begin
SolidBrush(ABrush, XYZATransparent, dmSet);
exit;
end;
ABrush.Colorspace:= TXYZAColorspace;
PSingle(@ABrush.FixedData)^ := AAlpha/65535;
ABrush.InternalInitContext:= nil;
ABrush.InternalPutNextPixels:= @XYZAAlphaBrushSetPixels;
end;
procedure TXYZABitmap.ReplaceImaginary(const AAfter: TXYZA);
var
p: PXYZA;
n: integer;
begin
p := Data;
for n := NbPixels - 1 downto 0 do
begin
if (p^.alpha>0) and not IsRealColor(p^) then p^ := AAfter;
Inc(p);
end;
InvalidateBitmap;
end;
finalization
xyHorseshoePolygon.Free;
end.