mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 23:17:57 +02:00
924 lines
28 KiB
ObjectPascal
924 lines
28 KiB
ObjectPascal
{
|
|
/***************************************************************************
|
|
lazcanvas.pas
|
|
---------------
|
|
|
|
***************************************************************************/
|
|
|
|
*****************************************************************************
|
|
This file is part of the Lazarus Component Library (LCL)
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
|
|
Author: Felipe Monteiro de Carvalho
|
|
|
|
Abstract:
|
|
Classes and functions for extending TFPImageCanvas to support more stretching
|
|
filters and to support all features from the LCL TCanvas
|
|
|
|
TLazCanvas also fixes various small problems and incompatibilities between
|
|
TFPImageCanvas versions, making the interface smoother for its users
|
|
|
|
Dont use anything from the LCL here as this unit should be kept strictly independent
|
|
}
|
|
unit LazCanvas;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{ $define lazcanvas_debug}
|
|
{ $define lazcanvas_profiling}
|
|
{$ifndef Darwin}// Strangely the new fast copy crashes in Mac OS X in apps with sub-controls
|
|
{$define lazcanvas_new_fast_copy}
|
|
{$endif}
|
|
|
|
interface
|
|
|
|
uses
|
|
// RTL
|
|
Classes, SysUtils, contnrs, Math,
|
|
// FCL-Image
|
|
fpimgcanv, fpcanvas, fpimage, clipping, pixtools, fppixlcanv,
|
|
// LCL
|
|
IntfGraphics, LazRegions
|
|
{$if defined(lazcanvas_debug) or defined(lazcanvas_profiling)}
|
|
, LazSysUtils, LazLoggerBase
|
|
{$endif}
|
|
;
|
|
|
|
type
|
|
|
|
TLazCanvasImageFormat = (
|
|
clfOther,
|
|
clfRGB16_R5G6B5,
|
|
clfRGB24, clfRGB24UpsideDown, clfBGR24,
|
|
clfBGRA32, clfRGBA32, clfARGB32);
|
|
|
|
{ TFPSharpInterpolation }
|
|
|
|
// This does a very sharp and square interpolation for stretching,
|
|
// similar to StretchBlt from the Windows API
|
|
TFPSharpInterpolation = class (TFPCustomInterpolation)
|
|
protected
|
|
procedure Execute (x,y,w,h : integer); override;
|
|
end;
|
|
|
|
{ TLazCanvasState }
|
|
|
|
TLazCanvasState = class
|
|
public
|
|
Brush: TFPCustomBrush;
|
|
Pen: TFPCustomPen;
|
|
Font: TFPCustomFont;
|
|
BaseWindowOrg: TPoint;
|
|
WindowOrg: TPoint;
|
|
Clipping: Boolean;
|
|
ClipRegion: TFPCustomRegion;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
{ TLazCanvas }
|
|
|
|
TLazCanvas = class(TFPImageCanvas)
|
|
private
|
|
FAssignedBrush: TFPCustomBrush;
|
|
FAssignedFont: TFPCustomFont;
|
|
FAssignedPen: TFPCustomPen;
|
|
FBaseWindowOrg: TPoint;
|
|
{$if FPC_FullVersion < 30203}
|
|
PolygonNonZeroWindingRule: Boolean;
|
|
{$endif}
|
|
{$if defined(ver2_6)}
|
|
FLazClipRegion: TFPCustomRegion;
|
|
{$endif}
|
|
FWindowOrg: TPoint; // already in absolute coords with BaseWindowOrg summed up
|
|
GraphicStateList: TFPList; // TLazCanvasState
|
|
function GetAssignedBrush: TFPCustomBrush;
|
|
function GetAssignedPen: TFPCustomPen;
|
|
function GetAssignedFont: TFPCustomFont;
|
|
function GetWindowOrg: TPoint;
|
|
procedure SetWindowOrg(AValue: TPoint);
|
|
protected
|
|
procedure SetColor (x,y:integer; const AValue:TFPColor); override;
|
|
function DoCreateDefaultFont : TFPCustomFont; override;
|
|
// Routines broken/unimplemented/incompatible in FPC
|
|
procedure DoRectangle (const Bounds:TRect); override;
|
|
procedure DoRectangleFill (const Bounds:TRect); override;
|
|
{$if FPC_FullVersion < 30203}
|
|
procedure DoPolygonFill (const points:array of TPoint); override;
|
|
{$endif}
|
|
// Routines which don't work with out extended clipping in TFPImageCanvas
|
|
procedure DoLine (x1,y1,x2,y2:integer); override;
|
|
// Other abstract routines that need implementation
|
|
procedure DoCopyRect(x,y:integer; canvas:TFPCustomCanvas; Const SourceRect:TRect); override;
|
|
procedure DoDraw(x,y:integer; const AImage: TFPCustomImage); override;
|
|
public
|
|
HasNoImage: Boolean;
|
|
NativeDC: PtrInt; // Utilized by LCL-CustomDrawn
|
|
ExtraFontData: TObject; // Utilized by LCL-CustomDrawn
|
|
ImageFormat: TLazCanvasImageFormat; // Utilized by LCL-CustomDrawn for speeding up drawing
|
|
SelectedBitmap: TObject; // Utilized by LCL-CustomDrawn, type TCDBitmap
|
|
constructor create (AnImage : TFPCustomImage);
|
|
destructor destroy; override;
|
|
procedure SetLazClipRegion(ARegion: TLazRegion);
|
|
// Canvas states list
|
|
function SaveState: Integer;
|
|
procedure RestoreState(AIndex: Integer);
|
|
// A simple operation to bring the Canvas in the default LCL TCanvas state
|
|
procedure ResetCanvasState;
|
|
// Alpha blending operations
|
|
procedure AlphaBlend(ASource: TLazCanvas;
|
|
const ADestX, ADestY, ASourceX, ASourceY, ASourceWidth, ASourceHeight: Integer);
|
|
procedure AlphaBlendIgnoringDestPixels(ASource: TLazCanvas;
|
|
const ADestX, ADestY, ASourceX, ASourceY, ASourceWidth, ASourceHeight: Integer);
|
|
procedure AlphaBlend_Image(ASource: TFPCustomImage;
|
|
const ADestX, ADestY, ASourceX, ASourceY, ASourceWidth, ASourceHeight: Integer);
|
|
procedure DoDrawImage(x,y:integer; const AImage: TFPCustomImage);
|
|
procedure CanvasCopyRect(ASource: TFPCustomCanvas;
|
|
const ADestX, ADestY, ASourceX, ASourceY, ASourceWidth, ASourceHeight: Integer);
|
|
// Fills the entire drawing with a color
|
|
// AIgnoreClippingAndWindowOrg speeds up the drawing a lot, but it is dangerous,
|
|
// don't use it unless you know what you are doing!
|
|
procedure FillColor(AColor: TFPColor; AIgnoreClippingAndWindowOrg: Boolean = False);
|
|
// Additional Polygon fill routine supporting non-zero winding rule
|
|
procedure Polygon(const Points: array of TPoint; Winding: Boolean); overload;
|
|
// Utilized by LCLIntf.SelectObject and by RestoreState
|
|
// This needed to be added because Pen/Brush.Assign raises exceptions
|
|
procedure AssignPenData(APen: TFPCustomPen);
|
|
procedure AssignBrushData(ABrush: TFPCustomBrush);
|
|
procedure AssignFontData(AFont: TFPCustomFont);
|
|
// These properties are utilized to implement LCLIntf.SelectObject
|
|
// to keep track of which brush handle was assigned to this canvas
|
|
// They are not utilized by TLazCanvas itself
|
|
property AssignedPen: TFPCustomPen read GetAssignedPen write FAssignedPen;
|
|
property AssignedBrush: TFPCustomBrush read GetAssignedBrush write FAssignedBrush;
|
|
property AssignedFont: TFPCustomFont read GetAssignedFont write FAssignedFont;
|
|
//
|
|
// SetWindowOrg operations will be relative to BaseWindowOrg,
|
|
// This is very useful for implementing the non-native wincontrol,
|
|
// because operations of SetWindowOrg inside a non-native wincontrol will be
|
|
// based upon the BaseWindowOrg which is set relative to the Form canvas
|
|
property BaseWindowOrg: TPoint read FBaseWindowOrg write FBaseWindowOrg;
|
|
{$if defined(ver2_6)}
|
|
property ClipRegion: TFPCustomRegion read FLazClipRegion write FLazClipRegion;
|
|
{$endif}
|
|
property WindowOrg: TPoint read GetWindowOrg write SetWindowOrg;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TLazCanvasState }
|
|
|
|
destructor TLazCanvasState.Destroy;
|
|
begin
|
|
Brush.Free;
|
|
Pen.Free;
|
|
Font.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TLazCanvas }
|
|
|
|
function TLazCanvas.GetAssignedBrush: TFPCustomBrush;
|
|
begin
|
|
if FAssignedBrush = nil then
|
|
Result := TFPEmptyBrush.Create
|
|
else
|
|
Result := FAssignedBrush;
|
|
end;
|
|
|
|
function TLazCanvas.GetAssignedPen: TFPCustomPen;
|
|
begin
|
|
if FAssignedPen = nil then
|
|
Result := TFPEmptyPen.Create
|
|
else
|
|
Result := FAssignedPen;
|
|
end;
|
|
|
|
function TLazCanvas.GetAssignedFont: TFPCustomFont;
|
|
begin
|
|
if FAssignedFont = nil then
|
|
Result := TFPEmptyFont.Create
|
|
else
|
|
Result := FAssignedFont;
|
|
end;
|
|
|
|
function TLazCanvas.GetWindowOrg: TPoint;
|
|
begin
|
|
Result := Point(FWindowOrg.X-FBaseWindowOrg.X, FWindowOrg.Y-FBaseWindowOrg.Y)
|
|
end;
|
|
|
|
procedure TLazCanvas.SetWindowOrg(AValue: TPoint);
|
|
begin
|
|
FWindowOrg.X := AValue.X+FBaseWindowOrg.X;
|
|
FWindowOrg.Y := AValue.Y+FBaseWindowOrg.Y;
|
|
{$ifdef lazcanvas_debug}
|
|
DebugLn(Format('[TLazCanvas.SetWindowOrg] AValue=%d,%d BaseWindowOrg=%d,%d', [AValue.X, AValue.Y, FBaseWindowOrg.X, FBaseWindowOrg.y]));
|
|
{$endif}
|
|
end;
|
|
|
|
procedure TLazCanvas.SetColor(x, y: integer; const AValue: TFPColor);
|
|
var
|
|
lx, ly: Integer;
|
|
begin
|
|
lx := x + FWindowOrg.X;
|
|
ly := y + FWindowOrg.Y;
|
|
{$if defined(ver2_6)}
|
|
if Clipping and (not FLazClipRegion.IsPointInRegion(lx, ly)) then
|
|
Exit;
|
|
if (lx >= 0) and (lx < width) and (ly >= 0) and (ly < height) then
|
|
Image.Colors[lx,ly] := AValue;
|
|
{$else}
|
|
if Clipping and (not FClipRegion.IsPointInRegion(lx, ly)) then
|
|
Exit;
|
|
if (lx >= 0) and (lx < width) and (ly >= 0) and (ly < height) then
|
|
FImage.Colors[lx,ly] := AValue;
|
|
{$endif}
|
|
end;
|
|
|
|
function TLazCanvas.DoCreateDefaultFont: TFPCustomFont;
|
|
begin
|
|
result := TFPEmptyFont.Create;
|
|
Result.Size := 0; // To allow it to use the default platform size
|
|
Result.FPColor := colBlack;
|
|
end;
|
|
|
|
// The coordinates utilized by DoRectangle in fcl-image are not TCanvas compatible
|
|
// so we reimplement it here
|
|
procedure TLazCanvas.DoRectangle (const Bounds:TRect);
|
|
var pattern : longword;
|
|
|
|
procedure CheckLine (x1,y1, x2,y2 : integer);
|
|
begin
|
|
// if clipping then
|
|
// CheckLineClipping (ClipRect, x1,y1, x2,y2);
|
|
if x1 >= 0 then
|
|
DrawSolidLine (self, x1,y1, x2,y2, Pen.FPColor)
|
|
end;
|
|
|
|
procedure CheckPLine (x1,y1, x2,y2 : integer);
|
|
begin
|
|
// if clipping then
|
|
// CheckLineClipping (ClipRect, x1,y1, x2,y2);
|
|
if x1 >= 0 then
|
|
DrawPatternLine (self, x1,y1, x2,y2, pattern, Pen.FPColor)
|
|
end;
|
|
|
|
var b : TRect;
|
|
r : integer;
|
|
|
|
begin
|
|
b := bounds;
|
|
b.right := b.Right-1;
|
|
b.bottom := b.bottom-1;
|
|
if pen.style = psSolid then
|
|
for r := 1 to pen.width do
|
|
begin
|
|
CheckLine (b.left,b.top,b.left,b.bottom);
|
|
CheckLine (b.left,b.bottom,b.right,b.bottom);
|
|
CheckLine (b.right,b.bottom,b.right,b.top);
|
|
CheckLine (b.right,b.top,b.left,b.top);
|
|
DecRect (b);
|
|
end
|
|
else if pen.style <> psClear then
|
|
begin
|
|
if pen.style = psPattern then
|
|
pattern := Pen.pattern
|
|
else
|
|
pattern := PenPatterns[pen.style];
|
|
CheckPLine (b.left,b.top,b.left,b.bottom);
|
|
CheckPLine (b.left,b.bottom,b.right,b.bottom);
|
|
CheckPLine (b.right,b.bottom,b.right,b.top);
|
|
CheckPLine (b.right,b.top,b.left,b.top);
|
|
end;
|
|
end;
|
|
|
|
procedure TLazCanvas.DoRectangleFill(const Bounds: TRect);
|
|
var
|
|
b : TRect;
|
|
begin
|
|
b := Bounds;
|
|
SortRect (b);
|
|
dec(b.Right);
|
|
dec(b.Bottom);
|
|
|
|
// Optimize when filling everything
|
|
if (b.Left = 0) and (b.Top = 0) and (b.Right = Width) and (b.Bottom = Height)
|
|
and (Brush.Style = bsSolid) and (FWindowOrg.X = 0) and (FWindowOrg.Y = 0)
|
|
and ((Clipping=False) {or cliprect=entire area}) then
|
|
begin
|
|
FillColor(Brush.FPColor, True);
|
|
Exit;
|
|
end;
|
|
|
|
case Brush.style of
|
|
bsSolid : FillRectangleColor (self, b.left,b.top, b.right,b.bottom);
|
|
bsPattern : FillRectanglePattern (self, b.left,b.top, b.right,b.bottom, brush.pattern);
|
|
bsImage :
|
|
if assigned (brush.image) then
|
|
if RelativeBrushImage then
|
|
FillRectangleImageRel (self, b.left,b.top, b.right,b.bottom, brush.image)
|
|
else
|
|
FillRectangleImage (self, b.left,b.top, b.right,b.bottom, brush.image)
|
|
else
|
|
raise PixelCanvasException.Create (sErrNoImage);
|
|
bsBDiagonal : FillRectangleHashDiagonal (self, b, HashWidth);
|
|
bsFDiagonal : FillRectangleHashBackDiagonal (self, b, HashWidth);
|
|
bsCross :
|
|
begin
|
|
FillRectangleHashHorizontal (self, b, HashWidth);
|
|
FillRectangleHashVertical (self, b, HashWidth);
|
|
end;
|
|
bsDiagCross :
|
|
begin
|
|
FillRectangleHashDiagonal (self, b, HashWidth);
|
|
FillRectangleHashBackDiagonal (self, b, HashWidth);
|
|
end;
|
|
bsHorizontal : FillRectangleHashHorizontal (self, b, HashWidth);
|
|
bsVertical : FillRectangleHashVertical (self, b, HashWidth);
|
|
end;
|
|
end;
|
|
|
|
{$IF FPC_FullVersion < 30203}
|
|
// unimplemented in FPC
|
|
// algorithm explained here: http://alienryderflex.com/polygon_fill/
|
|
procedure TLazCanvas.DoPolygonFill(const points: array of TPoint);
|
|
|
|
function CrossProduct(P, P1, P2: TPoint): Integer;
|
|
var
|
|
a, b: TPoint;
|
|
begin
|
|
a := P - P1;
|
|
b := P2 - P1;
|
|
Result := a.X * b.Y - b.X * a.Y;
|
|
end;
|
|
|
|
procedure CalcWindingNumber(const P, P1, P2: TPoint; var WindingNumber: Integer);
|
|
begin
|
|
if CrossProduct(P, P1, P2) > 0 then
|
|
inc(windingNumber)
|
|
else
|
|
dec(windingNumber);
|
|
end;
|
|
|
|
type
|
|
TNode = record
|
|
X: Integer;
|
|
Index1, Index2: Integer;
|
|
end;
|
|
var
|
|
lBoundingBox: TRect;
|
|
x, y, i: integer;
|
|
x0: Integer;
|
|
// faster version
|
|
nodeCount, j, polyCorners: Integer;
|
|
windingNumber, oldWindingNumber: Integer;
|
|
nodes: array of TNode;
|
|
swap: TNode;
|
|
begin
|
|
if Brush.Style = bsClear then Exit;
|
|
|
|
// Find the Bounding Box of the Polygon
|
|
lBoundingBox := Rect(0, 0, 0, 0);
|
|
for i := low(Points) to High(Points) do
|
|
begin
|
|
lBoundingBox.Left := Min(Points[i].X, lBoundingBox.Left);
|
|
lBoundingBox.Top := Min(Points[i].Y, lBoundingBox.Top);
|
|
lBoundingBox.Right := Max(Points[i].X, lBoundingBox.Right);
|
|
lBoundingBox.Bottom := Max(Points[i].Y, lBoundingBox.Bottom);
|
|
end;
|
|
|
|
// good but very slow polygon fill function
|
|
{// Now scan all points using IsPointInPolygon
|
|
for x := lBoundingBox.Left to lBoundingBox.Right do
|
|
for y := lBoundingBox.Top to lBoundingBox.Bottom do
|
|
begin
|
|
if IsPointInPolygon(X, Y, Points) then SetColor(X, Y, Brush.FPColor);
|
|
end;
|
|
Exit;
|
|
}
|
|
|
|
// Loop through the rows of the image.
|
|
polyCorners := Length(points);
|
|
for y := lBoundingBox.Top to lBoundingBox.Bottom do
|
|
begin
|
|
// Build a list of nodes.
|
|
nodeCount := 0;
|
|
j := polyCorners-1;
|
|
x0 := lBoundingBox.Left - 10;
|
|
for i := 0 to polyCorners-1 do
|
|
begin
|
|
if (points[i].Y < y) and (points[j].Y >= y) or
|
|
(points[j].Y < y) and (points[i].Y >= Y) then
|
|
begin
|
|
SetLength(nodes, nodeCount+1);
|
|
nodes[nodeCount].X := Round(points[i].X + (y-points[i].Y) / (points[j].Y-points[i].Y) * (points[j].X-points[i].X));
|
|
nodes[nodeCount].Index1 := j;
|
|
nodes[nodeCount].Index2 := i;
|
|
x0 := nodes[nodeCount].X;
|
|
Inc(nodeCount);
|
|
end;
|
|
j := i;
|
|
end;
|
|
|
|
// Sort the nodes, via a simple “Bubble” sort.
|
|
i := 0;
|
|
while (i<nodeCount-1) do
|
|
begin
|
|
if (nodes[i].X > nodes[i+1].X) then
|
|
begin
|
|
swap := nodes[i];
|
|
nodes[i] := nodes[i+1];
|
|
nodes[i+1] := swap;
|
|
if (i <> 0) then Dec(i);
|
|
end
|
|
else
|
|
Inc(i);
|
|
end;
|
|
|
|
i := 0;
|
|
if PolygonNonZeroWindingRule and (Length(nodes) > 2) then
|
|
begin
|
|
// Non-zero winding rule
|
|
windingNumber := 0;
|
|
oldWindingNumber := 0;
|
|
while i < nodeCount do
|
|
begin
|
|
CalcWindingNumber(Point(lBoundingBox.Left-10, y), points[nodes[i].Index1], points[nodes[i].Index2], windingNumber);
|
|
if (oldWindingNumber = 0) and (windingNumber <> 0) then
|
|
x0 := nodes[i].X
|
|
else if (oldWindingNumber <> 0) and (windingNumber = 0) then
|
|
for X := x0 to nodes[i].X-1 do
|
|
DrawPixel(X, Y, Brush.FPColor);
|
|
oldWindingNumber := windingNumber;
|
|
inc(i);
|
|
end;
|
|
end else
|
|
begin
|
|
// Even-odd rule: fill the pixels between node pairs.
|
|
while i<nodeCount do
|
|
begin
|
|
if (nodes[i ].X >= lBoundingBox.Right) then break;
|
|
if (nodes[i+1].X > lBoundingBox.Left) then
|
|
begin
|
|
if (nodes[i ].X < lBoundingBox.Left) then nodes[i].X := lBoundingBox.Left;
|
|
if (nodes[i+1].X > lBoundingBox.Right) then nodes[i+1].X := lBoundingBox.Right;
|
|
for X := nodes[i].X to nodes[i+1].X-1 do
|
|
DrawPixel(X, Y, Brush.FPColor);
|
|
end;
|
|
i := i + 2;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure TLazCanvas.DoLine(x1, y1, x2, y2: integer);
|
|
procedure DrawOneLine (xx1,yy1, xx2,yy2:integer);
|
|
begin
|
|
if Clipping then
|
|
CheckLineClipping (ClipRect, xx1,yy1, xx2,yy2);
|
|
DrawSolidLine (self, xx1,yy1, xx2,yy2, Pen.FPColor);
|
|
end;
|
|
|
|
procedure SolidThickLine;
|
|
var w1, w2, r : integer;
|
|
MoreHor : boolean;
|
|
begin
|
|
// determine lines above and under
|
|
w1 := pen.width div 2;
|
|
w2 := w1;
|
|
if w1+w2 = pen.width then
|
|
dec (w1);
|
|
// determine slanting
|
|
MoreHor := (abs(x2-x1) < abs(y2-y1));
|
|
if MoreHor then
|
|
begin // add lines left/right
|
|
for r := 1 to w1 do
|
|
DrawOneLine (x1-r,y1, x2-r,y2);
|
|
for r := 1 to w2 do
|
|
DrawOneLine (x1+r,y1, x2+r,y2);
|
|
end
|
|
else
|
|
begin // add lines above/under
|
|
for r := 1 to w1 do
|
|
DrawOneLine (x1,y1-r, x2,y2-r);
|
|
for r := 1 to w2 do
|
|
DrawOneLine (x1,y1+r, x2,y2+r);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
{ We can are not clip here because we clip in each drawn pixel
|
|
or introduce a more complex algorithm to take into account lazregions
|
|
if Clipping then
|
|
CheckLineClipping (ClipRect, x1,y1, x2,y2);}
|
|
case Pen.style of
|
|
psSolid :
|
|
begin
|
|
DrawSolidLine (self, x1,y1, x2,y2, Pen.FPColor);
|
|
if pen.width > 1 then
|
|
SolidThickLine;
|
|
end;
|
|
psPattern:
|
|
DrawPatternLine (self, x1,y1, x2,y2, pen.pattern);
|
|
// Patterned lines have width always at 1
|
|
psDash, psDot, psDashDot, psDashDotDot :
|
|
DrawPatternLine (self, x1,y1, x2,y2, PenPatterns[Pen.Style]);
|
|
end;
|
|
end;
|
|
|
|
procedure TLazCanvas.DoCopyRect(x, y: integer; canvas: TFPCustomCanvas;
|
|
const SourceRect: TRect);
|
|
begin
|
|
CanvasCopyRect(canvas, X, Y, SourceRect.Left, SourceRect.Top,
|
|
SourceRect.right-SourceRect.Left, SourceRect.Bottom-SourceRect.Top);
|
|
end;
|
|
|
|
procedure TLazCanvas.DoDraw(x, y: integer; const AImage: TFPCustomImage);
|
|
begin
|
|
AlphaBlend_Image(AImage, X, Y, 0, 0, AImage.Width, AImage.Height);
|
|
end;
|
|
|
|
constructor TLazCanvas.create(AnImage: TFPCustomImage);
|
|
begin
|
|
inherited Create(AnImage);
|
|
GraphicStateList := TFPList.Create;
|
|
HasNoImage := AnImage = nil;
|
|
end;
|
|
|
|
destructor TLazCanvas.destroy;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to GraphicStateList.Count-1 do
|
|
TLazCanvasState(GraphicStateList[i]).Free;
|
|
GraphicStateList.Free;
|
|
FAssignedBrush.Free;
|
|
FAssignedPen.Free;
|
|
inherited destroy;
|
|
end;
|
|
|
|
procedure TLazCanvas.SetLazClipRegion(ARegion: TLazRegion);
|
|
begin
|
|
Clipping := True;
|
|
{$if defined(ver2_6)}
|
|
ClipRect := TLazRegionRect(ARegion.Parts.Items[0]).Rect;
|
|
FLazClipRegion := ARegion;
|
|
{$else}
|
|
ClipRegion := ARegion;
|
|
{$endif}
|
|
end;
|
|
|
|
function TLazCanvas.SaveState: Integer;
|
|
var
|
|
lState: TLazCanvasState;
|
|
begin
|
|
lState := TLazCanvasState.Create;
|
|
|
|
lState.Brush := Brush.CopyBrush;
|
|
lState.Pen := Pen.CopyPen;
|
|
lState.Font := Font.CopyFont;
|
|
lState.BaseWindowOrg := BaseWindowOrg;
|
|
lState.WindowOrg := WindowOrg;
|
|
lState.Clipping := Clipping;
|
|
|
|
Result := GraphicStateList.Add(lState);
|
|
end;
|
|
|
|
// if AIndex is positive, it represents the wished saved dc instance
|
|
// if AIndex is negative, it's a relative number from last pushed state
|
|
procedure TLazCanvas.RestoreState(AIndex: Integer);
|
|
var
|
|
lState: TLazCanvasState;
|
|
begin
|
|
if AIndex < 0 then AIndex := AIndex + GraphicStateList.Count;
|
|
lState := TLazCanvasState(GraphicStateList.Items[AIndex]);
|
|
GraphicStateList.Delete(AIndex);
|
|
if lState = nil then Exit;
|
|
|
|
AssignPenData(lState.Pen);
|
|
AssignBrushData(lState.Brush);
|
|
AssignFontData(lState.Font);
|
|
BaseWindowOrg := lState.BaseWindowOrg;
|
|
WindowOrg := lState.WindowOrg;
|
|
Clipping := lState.Clipping;
|
|
|
|
lState.Free;
|
|
end;
|
|
|
|
procedure TLazCanvas.ResetCanvasState;
|
|
begin
|
|
Pen.FPColor := colBlack;
|
|
Pen.Style := psSolid;
|
|
|
|
Brush.FPColor := colWhite;
|
|
Brush.Style := bsSolid;
|
|
end;
|
|
|
|
procedure TLazCanvas.AlphaBlend(ASource: TLazCanvas;
|
|
const ADestX, ADestY, ASourceX, ASourceY, ASourceWidth, ASourceHeight: Integer);
|
|
var
|
|
x, y, CurDestX, CurDestY, CurSrcX, CurSrcY: Integer;
|
|
MaskValue, InvMaskValue: Word;
|
|
CurColor, SrcColor: TFPColor;
|
|
lDrawWidth, lDrawHeight: Integer;
|
|
begin
|
|
// Take care not to draw outside the destination area
|
|
lDrawWidth := Min(Self.Width - ADestX, ASource.Width - ASourceX);
|
|
lDrawHeight := Min(Self.Height - ADestY, ASource.Height - ASourceY);
|
|
lDrawWidth := Min(lDrawWidth, ASourceWidth);
|
|
lDrawHeight := Min(lDrawHeight, ASourceHeight);
|
|
//DebugLn(Format('[TLazCanvas.AlphaBlend] lDrawWidth=%d lDrawHeight=%d',
|
|
// [lDrawWidth, lDrawHeight]));
|
|
for y := 0 to lDrawHeight - 1 do
|
|
begin
|
|
for x := 0 to lDrawWidth - 1 do
|
|
begin
|
|
CurDestX := ADestX + x;
|
|
CurDestY := ADestY + y;
|
|
CurSrcX := ASourceX + x;
|
|
CurSrcY := ASourceY + y;
|
|
|
|
// Never draw outside the destination
|
|
if (CurDestX < 0) or (CurDestY < 0) then Continue;
|
|
|
|
MaskValue := ASource.Colors[CurSrcX, CurSrcY].alpha;
|
|
InvMaskValue := $FFFF - MaskValue;
|
|
|
|
if MaskValue = $FFFF then
|
|
begin
|
|
Self.Colors[CurDestX, CurDestY] := ASource.Colors[CurSrcX, CurSrcY];
|
|
end
|
|
else if MaskValue > $00 then
|
|
begin
|
|
CurColor := Self.Colors[CurDestX, CurDestY];
|
|
SrcColor := ASource.Colors[CurSrcX, CurSrcY];
|
|
|
|
CurColor.Red := Round(
|
|
CurColor.Red * InvMaskValue / $FFFF +
|
|
SrcColor.Red * MaskValue / $FFFF);
|
|
|
|
CurColor.Green := Round(
|
|
CurColor.Green * InvMaskValue / $FFFF +
|
|
SrcColor.Green * MaskValue / $FFFF);
|
|
|
|
CurColor.Blue := Round(
|
|
CurColor.Blue * InvMaskValue / $FFFF +
|
|
SrcColor.Blue * MaskValue / $FFFF);
|
|
|
|
CurColor.alpha := alphaOpaque;
|
|
|
|
{DebugLn(Format('Alpha blending pixels Old=%d %d Src=%d %d New=%d %d alpha=%d',
|
|
[Self.Colors[CurDestX, CurDestY].Red, Self.Colors[CurDestX, CurDestY].Green,
|
|
SrcColor.Red, SrcColor.Green,
|
|
CurColor.Red, CurColor.Green,
|
|
MaskValue
|
|
]));}
|
|
|
|
Self.Colors[CurDestX, CurDestY] := CurColor;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// This is a safer version in case one doesnt trust the destination pixels
|
|
// It will draw as if the target area contained opaque white
|
|
procedure TLazCanvas.AlphaBlendIgnoringDestPixels(ASource: TLazCanvas;
|
|
const ADestX, ADestY, ASourceX, ASourceY, ASourceWidth, ASourceHeight: Integer
|
|
);
|
|
var
|
|
x, y, CurDestX, CurDestY, CurSrcX, CurSrcY: Integer;
|
|
MaskValue, InvMaskValue: Word;
|
|
CurColor, SrcColor: TFPColor;
|
|
lDrawWidth, lDrawHeight: Integer;
|
|
begin
|
|
// Take care not to draw outside the destination area
|
|
lDrawWidth := Min(Self.Width - ADestX, ASource.Width - ASourceX);
|
|
lDrawHeight := Min(Self.Height - ADestY, ASource.Height - ASourceY);
|
|
lDrawWidth := Min(lDrawWidth, ASourceWidth);
|
|
lDrawHeight := Min(lDrawHeight, ASourceHeight);
|
|
//DebugLn(Format('[TLazCanvas.AlphaBlendIgnoringDestPixels] lDrawWidth=%d lDrawHeight=%d',
|
|
//[lDrawWidth, lDrawHeight]));
|
|
for y := 0 to lDrawHeight - 1 do
|
|
begin
|
|
for x := 0 to lDrawWidth - 1 do
|
|
begin
|
|
CurDestX := ADestX + x;
|
|
CurDestY := ADestY + y;
|
|
CurSrcX := ASourceX + x;
|
|
CurSrcY := ASourceY + y;
|
|
|
|
// Never draw outside the destination
|
|
if (CurDestX < 0) or (CurDestY < 0) then Continue;
|
|
|
|
MaskValue := ASource.Colors[CurSrcX, CurSrcY].alpha;
|
|
InvMaskValue := $FFFF - MaskValue;
|
|
|
|
if MaskValue = $FFFF then
|
|
begin
|
|
Self.Colors[CurDestX, CurDestY] := ASource.Colors[CurSrcX, CurSrcY];
|
|
end
|
|
// Theorically it should be > 0 but we make a filter here to exclude low-alpha pixels
|
|
// because those cause small white pixels in the image
|
|
else if MaskValue > $4000 then
|
|
begin
|
|
SrcColor := ASource.Colors[CurSrcX, CurSrcY];
|
|
|
|
CurColor.Red := InvMaskValue + (SrcColor.Red * MaskValue) div $FFFF;
|
|
CurColor.Green := InvMaskValue + (SrcColor.Green * MaskValue) div $FFFF;
|
|
CurColor.Blue := InvMaskValue + (SrcColor.Blue * MaskValue) div $FFFF;
|
|
CurColor.alpha := alphaOpaque;
|
|
|
|
Self.Colors[CurDestX, CurDestY] := CurColor;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TLazCanvas.AlphaBlend_Image(ASource: TFPCustomImage; const ADestX,
|
|
ADestY, ASourceX, ASourceY, ASourceWidth, ASourceHeight: Integer);
|
|
var
|
|
SrcCanvas: TLazCanvas;
|
|
begin
|
|
SrcCanvas := TLazCanvas.Create(ASource);
|
|
try
|
|
AlphaBlend(SrcCanvas, ADestX, ADestY,
|
|
ASourceX, ASourceY, ASourceWidth, ASourceHeight);
|
|
finally
|
|
SrcCanvas.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TLazCanvas.DoDrawImage(x, y: integer; const AImage: TFPCustomImage);
|
|
begin
|
|
DoDraw(x, y, AImage);
|
|
end;
|
|
|
|
procedure TLazCanvas.CanvasCopyRect(ASource: TFPCustomCanvas; const ADestX, ADestY,
|
|
ASourceX, ASourceY, ASourceWidth, ASourceHeight: Integer);
|
|
var
|
|
ALazSource: TLazCanvas absolute ASource;
|
|
x, y, CurDestX, CurDestY, CurSrcX, CurSrcY: Integer;
|
|
lDrawWidth, lDrawHeight: Integer;
|
|
lColor: TFPColor;
|
|
{$IFDEF lazcanvas_profiling}
|
|
lTimeStart: TDateTime;
|
|
{$ENDIF}
|
|
{$ifdef lazcanvas_new_fast_copy}
|
|
lScanlineSrc, lScanlineDest: PByte;
|
|
lBytesPerPixel: Byte;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF lazcanvas_profiling}
|
|
lTimeStart := NowUTC();
|
|
{$ENDIF}
|
|
|
|
// Take care not to draw outside the source and also not outside the destination area
|
|
lDrawWidth := Min(Self.Width - ADestX - FWindowOrg.X, ASource.Width - ASourceX);
|
|
lDrawHeight := Min(Self.Height - ADestY - FWindowOrg.Y, ASource.Height - ASourceY);
|
|
lDrawWidth := Min(lDrawWidth, ASourceWidth);
|
|
lDrawHeight := Min(lDrawHeight, ASourceHeight);
|
|
|
|
{$ifdef lazcanvas_new_fast_copy}
|
|
// If the formats match, make a fast copy of the data itself, without pixel conversion
|
|
if (ASource is TLazCanvas) and
|
|
(Image is TLazIntfImage) and (ALazSource.Image is TLazIntfImage) and
|
|
(ImageFormat in [clfRGB24, clfRGB24UpsideDown, clfBGR24, clfBGRA32, clfRGBA32, clfARGB32]) and
|
|
(ImageFormat = ALazSource.ImageFormat) then
|
|
begin
|
|
case ImageFormat of
|
|
clfRGB24, clfRGB24UpsideDown, clfBGR24: lBytesPerPixel := 3;
|
|
clfBGRA32, clfRGBA32, clfARGB32: lBytesPerPixel := 4;
|
|
else
|
|
lBytesPerPixel := 4;
|
|
end;
|
|
|
|
for y := 0 to lDrawHeight - 1 do
|
|
begin
|
|
CurDestY := ADestY + y + FWindowOrg.Y;
|
|
if CurDestY >= Height then Continue;
|
|
CurSrcY := ASourceY + y;
|
|
|
|
lScanlineSrc := TLazIntfImage(ALazSource.Image).GetDataLineStart(CurSrcY);
|
|
lScanlineDest := TLazIntfImage(Image).GetDataLineStart(CurDestY);
|
|
if (lScanlineSrc = nil) or (lScanlineDest = nil) then Break;
|
|
Inc(lScanlineSrc, (ASourceX)*lBytesPerPixel);
|
|
Inc(lScanlineDest, (ADestX + FWindowOrg.X)*lBytesPerPixel);
|
|
|
|
move(lScanlineSrc^, lScanlineDest^, lBytesPerPixel * lDrawWidth);
|
|
end;
|
|
end
|
|
// General case of copying
|
|
else
|
|
{$endif}
|
|
begin
|
|
for y := 0 to lDrawHeight - 1 do
|
|
begin
|
|
for x := 0 to lDrawWidth - 1 do
|
|
begin
|
|
CurDestX := ADestX + x;
|
|
CurDestY := ADestY + y;
|
|
CurSrcX := ASourceX + x;
|
|
CurSrcY := ASourceY + y;
|
|
|
|
// Never draw outside the destination
|
|
if (CurDestX < 0) or (CurDestY < 0) then Continue;
|
|
|
|
lColor := ASource.Colors[CurSrcX, CurSrcY];
|
|
Self.Colors[CurDestX, CurDestY] := lColor;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF lazcanvas_profiling}
|
|
DebugLn(Format('[TLazCanvas.CanvasCopyRect] Paint duration: %d ms', [DateTimeToTimeStamp(NowUTC() - lTimeStart).Time]));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TLazCanvas.FillColor(AColor: TFPColor;
|
|
AIgnoreClippingAndWindowOrg: Boolean);
|
|
var
|
|
x, y: Integer;
|
|
begin
|
|
if AIgnoreClippingAndWindowOrg then
|
|
begin
|
|
if Image is TLazIntfImage then
|
|
TLazIntfImage(Image).FillPixels(AColor)
|
|
else
|
|
for y := 0 to Height-1 do
|
|
for x := 0 to Width-1 do
|
|
Image.Colors[x, y] := AColor;
|
|
end
|
|
else
|
|
begin
|
|
for y := 0 to Height-1 do
|
|
for x := 0 to Width-1 do
|
|
SetColor(x, y, AColor);
|
|
end;
|
|
end;
|
|
|
|
procedure TLazCanvas.Polygon(const Points: array of TPoint; Winding: Boolean);
|
|
begin
|
|
PolygonNonZeroWindingRule := Winding;
|
|
inherited Polygon(Points);
|
|
end;
|
|
|
|
procedure TLazCanvas.AssignPenData(APen: TFPCustomPen);
|
|
begin
|
|
if APen = nil then Exit;
|
|
Pen.FPColor := APen.FPColor;
|
|
Pen.Style := APen.Style;
|
|
Pen.Width := APen.Width;
|
|
end;
|
|
|
|
procedure TLazCanvas.AssignBrushData(ABrush: TFPCustomBrush);
|
|
begin
|
|
if ABrush = nil then Exit;
|
|
Brush.FPColor := ABrush.FPColor;
|
|
Brush.Style := ABrush.Style;
|
|
end;
|
|
|
|
procedure TLazCanvas.AssignFontData(AFont: TFPCustomFont);
|
|
begin
|
|
if AFont = nil then Exit;
|
|
Font.FPColor := AFont.FPColor;
|
|
Font.Name := AFont.Name;
|
|
Font.Size := AFont.Size;
|
|
Font.Bold := AFont.Bold;
|
|
Font.Italic := AFont.Italic;
|
|
Font.Underline := AFont.Underline;
|
|
Font.StrikeThrough := AFont.StrikeThrough;
|
|
end;
|
|
|
|
{ TFPWindowsSharpInterpolation }
|
|
|
|
procedure TFPSharpInterpolation.Execute(x, y, w, h: integer);
|
|
// paint Image on Canvas at x,y,w*h
|
|
var
|
|
srcx, srcy: Integer; // current coordinates in the source image
|
|
dx, dy, dw, dh: Integer; // current coordinates in the destination canvas
|
|
lWidth, lHeight: Integer; // Image size
|
|
lColor: TFPColor;
|
|
begin
|
|
if (w<=0) or (h<=0) or (image.Width=0) or (image.Height=0) then
|
|
exit;
|
|
|
|
lWidth := Image.Width-1;
|
|
lHeight := Image.Height-1;
|
|
dw := w - 1;
|
|
dh := h - 1;
|
|
|
|
for dx := 0 to w-1 do
|
|
for dy := 0 to h-1 do
|
|
begin
|
|
srcx := Round((dx / dw) * lWidth);
|
|
srcy := Round((dy / dh) * lHeight);
|
|
lColor := Image.Colors[srcx, srcy];
|
|
Canvas.Colors[dx+x, dy+y] := lColor;
|
|
end;
|
|
end;
|
|
|
|
end.
|
|
|