692 lines
20 KiB
ObjectPascal
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.
|
|
|