mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-30 11:02:56 +02:00
1878 lines
58 KiB
ObjectPascal
1878 lines
58 KiB
ObjectPascal
{ -----------------------------------------
|
||
carboncanvas.pp - Carbon device context
|
||
-----------------------------------------
|
||
|
||
*****************************************************************************
|
||
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.
|
||
*****************************************************************************
|
||
}
|
||
unit CarbonCanvas;
|
||
|
||
{$mode objfpc}{$H+}
|
||
|
||
interface
|
||
|
||
// defines
|
||
{$I carbondefines.inc}
|
||
|
||
uses
|
||
// rtl+ftl
|
||
Types, Classes, SysUtils, Math, Contnrs,
|
||
// carbon bindings
|
||
MacOSAll,
|
||
// LCL
|
||
LCLProc, LCLType, Graphics, GraphType, IntfGraphics, Controls, Forms,
|
||
// LCL Carbon
|
||
CarbonDef, CarbonUtils,
|
||
{$ifdef DebugBitmaps}
|
||
CarbonDebug,
|
||
{$endif}
|
||
CarbonGDIObjects;
|
||
|
||
type
|
||
// device context data for SaveDC/RestoreDC
|
||
TCarbonDCData = class
|
||
public
|
||
CurrentFont: TCarbonFont;
|
||
CurrentBrush: TCarbonBrush;
|
||
CurrentPen: TCarbonPen;
|
||
CurrentRegion: TCarbonRegion;
|
||
|
||
BkColor: TColor;
|
||
BkMode: Integer;
|
||
BkBrush: TCarbonBrush;
|
||
|
||
TextColor: TColor;
|
||
TextBrush: TCarbonBrush;
|
||
|
||
ROP2: Integer;
|
||
PenPos: TPoint;
|
||
|
||
WindowOfs: TPoint;
|
||
ViewportOfs: TPoint;
|
||
|
||
isClipped: Boolean;
|
||
ClipShape: HIShapeRef;
|
||
end;
|
||
|
||
TCarbonBitmapContext = class;
|
||
|
||
{ TCarbonDeviceContext }
|
||
|
||
TCarbonDeviceContext = class(TCarbonContext)
|
||
private
|
||
FCurrentFont: TCarbonFont;
|
||
FCurrentBrush: TCarbonBrush;
|
||
FCurrentPen: TCarbonPen;
|
||
FCurrentRegion: TCarbonRegion;
|
||
|
||
FBkColor: TColor;
|
||
FBkMode: Integer;
|
||
FBkBrush: TCarbonBrush;
|
||
|
||
FTextColor: TColor;
|
||
FTextBrush: TCarbonBrush; // text color is fill color
|
||
|
||
FROP2: Integer;
|
||
FPenPos: TPoint;
|
||
FClipRegion: TCarbonRegion;
|
||
|
||
FSavedDCList: TFPObjectList;
|
||
FTextFractional: Boolean;
|
||
FViewPortOfs,
|
||
FWindowOfs: TPoint;
|
||
|
||
isClipped : Boolean;
|
||
|
||
procedure SetBkColor(AValue: TColor);
|
||
procedure SetBkMode(const AValue: Integer);
|
||
procedure SetCurrentBrush(const AValue: TCarbonBrush);
|
||
procedure SetCurrentFont(const AValue: TCarbonFont);
|
||
procedure SetCurrentPen(const AValue: TCarbonPen);
|
||
procedure SetCurrentRegion(const AValue: TCarbonRegion);
|
||
procedure SetROP2(const AValue: Integer);
|
||
procedure SetTextColor(AValue: TColor);
|
||
protected
|
||
function GetSize: TPoint; virtual; abstract;
|
||
function SaveDCData: TCarbonDCData; virtual;
|
||
procedure RestoreDCData(const AData: TCarbonDCData); virtual;
|
||
procedure ExcludeClipRect(Left, Top, Right, Bottom: Integer);
|
||
procedure ApplyTransform(Trans: CGAffineTransform);
|
||
procedure ClearClipping;
|
||
public
|
||
constructor Create;
|
||
destructor Destroy; override;
|
||
procedure Reset; override;
|
||
|
||
function SaveDC: Integer;
|
||
function RestoreDC(ASavedDC: Integer): Boolean;
|
||
|
||
function BeginTextRender(AStr: PChar; ACount: Integer; out ALayout: TCarbonTextLayout): Boolean;
|
||
procedure EndTextRender(var ALayout: TCarbonTextLayout);
|
||
|
||
procedure SetAntialiasing(AValue: Boolean);
|
||
function DrawCGImage(X, Y, Width, Height: Integer; CGImage: CGImageRef): Boolean;
|
||
procedure SetCGFillping(Ctx: CGContextRef; Width, Height: Integer); // Width and Height must be negative to flip the image
|
||
procedure RestoreCGFillping(Ctx: CGContextRef; Width, Height: Integer); // Width and Height must be negative to restore
|
||
public
|
||
procedure DrawFocusRect(ARect: TRect);
|
||
procedure DrawGrid(const ARect: TRect; DX, DY: Integer);
|
||
|
||
procedure Ellipse(X1, Y1, X2, Y2: Integer);
|
||
function ExtTextOut(X, Y: Integer; Options: Longint; Rect: PRect; Str: PChar; Count: Longint; Dx: PInteger): Boolean;
|
||
procedure FillRect(Rect: TRect; Brush: TCarbonBrush);
|
||
procedure Frame(X1, Y1, X2, Y2: Integer);
|
||
procedure Frame3D(var ARect: TRect; const FrameWidth: integer; const Style: TBevelCut);
|
||
function GetClipRect: TRect;
|
||
function GetLineLastPixelPos(PrevPos, NewPos: TPoint): TPoint;
|
||
function GetPixel({%H-}X, {%H-}Y: Integer): TGraphicsColor; virtual;
|
||
function GetTextExtentPoint(Str: PChar; Count: Integer; var Size: TSize): Boolean;
|
||
function GetTextMetrics(var TM: TTextMetric): Boolean;
|
||
procedure InvertRectangle(X1, Y1, X2, Y2: Integer);
|
||
procedure LineTo(X, Y: Integer);
|
||
procedure PolyBezier(Points: PPoint; NumPts: Integer; Filled, Continuous: boolean);
|
||
procedure Polygon(Points: PPoint; NumPts: Integer; Winding: boolean);
|
||
procedure Polyline(Points: PPoint; NumPts: Integer);
|
||
procedure Rectangle(X1, Y1, X2, Y2: Integer);
|
||
procedure SetPixel(X, Y: Integer; AColor: TGraphicsColor);
|
||
function StretchDraw(X, Y, Width, Height: Integer; SrcDC: TCarbonBitmapContext;
|
||
XSrc, YSrc, SrcWidth, SrcHeight: Integer; Msk: TCarbonBitmap; XMsk,
|
||
YMsk: Integer; {%H-}Rop: DWORD): Boolean;
|
||
function SetClipRegion(AClipRegion: TCarbonRegion; Mode: Integer): Integer;
|
||
function CopyClipRegion(ADstRegion: TCarbonRegion): Integer;
|
||
|
||
procedure UpdateContextOfs(const AWindowOfs, AViewOfs: TPoint);
|
||
procedure SetWindowOfs(const AWindowOfs: TPoint);
|
||
procedure SetViewPortOfs(const AViewOfs: TPoint);
|
||
function GetLogicalOffset: TPoint; override;
|
||
public
|
||
property Size: TPoint read GetSize;
|
||
|
||
property CurrentFont: TCarbonFont read FCurrentFont write SetCurrentFont;
|
||
property CurrentBrush: TCarbonBrush read FCurrentBrush write SetCurrentBrush;
|
||
property CurrentPen: TCarbonPen read FCurrentPen write SetCurrentPen;
|
||
property CurrentRegion: TCarbonRegion read FCurrentRegion write SetCurrentRegion;
|
||
|
||
property BkColor: TColor read FBkColor write SetBkColor;
|
||
property BkMode: Integer read FBkMode write SetBkMode;
|
||
property BkBrush: TCarbonBrush read FBkBrush;
|
||
|
||
property TextColor: TColor read FTextColor write SetTextColor;
|
||
property TextBrush: TCarbonBrush read FTextBrush;
|
||
|
||
property ROP2: Integer read FROP2 write SetROP2;
|
||
property PenPos: TPoint read FPenPos write FPenPos;
|
||
|
||
property TextFractional: Boolean read FTextFractional write FTextFractional;
|
||
property WindowOfs: TPoint read FWindowOfs write SetWindowOfs;
|
||
property ViewPortOfs: TPoint read FViewPortOfs write SetViewPortOfs;
|
||
end;
|
||
|
||
{ TCarbonScreenContext }
|
||
|
||
TCarbonScreenContext = class(TCarbonDeviceContext)
|
||
protected
|
||
function GetSize: TPoint; override;
|
||
public
|
||
constructor Create; // TODO
|
||
end;
|
||
|
||
{ TCarbonControlContext }
|
||
|
||
TCarbonControlContext = class(TCarbonDeviceContext)
|
||
private
|
||
FOwner: TCarbonWidget; // owner widget
|
||
protected
|
||
function GetSize: TPoint; override;
|
||
public
|
||
constructor Create(AOwner: TCarbonWidget);
|
||
|
||
property Owner: TCarbonWidget read FOwner;
|
||
end;
|
||
|
||
{ TCarbonBitmapContext }
|
||
|
||
TCarbonBitmapContext = class(TCarbonDeviceContext)
|
||
private
|
||
FBitmap: TCarbonBitmap;
|
||
function GetBitmap: TCarbonBitmap;
|
||
procedure SetBitmap(const AValue: TCarbonBitmap);
|
||
protected
|
||
function GetSize: TPoint; override;
|
||
public
|
||
constructor Create;
|
||
destructor Destroy; override;
|
||
procedure Reset; override;
|
||
|
||
function GetPixel(X, Y: Integer): TGraphicsColor; override;
|
||
public
|
||
property Bitmap: TCarbonBitmap read GetBitmap write SetBitmap;
|
||
end;
|
||
|
||
// TODO: TCarbonPrinterContext
|
||
|
||
function CheckDC(const DC: HDC; const AMethodName: String; AParamName: String = ''): Boolean;
|
||
|
||
var
|
||
// context for calculating text parameters for invisible controls
|
||
DefaultContext: TCarbonBitmapContext;
|
||
ScreenContext: TCarbonScreenContext;
|
||
|
||
implementation
|
||
|
||
uses LCLIntf, CarbonProc, CarbonDbgConsts;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Name: CheckDC
|
||
Params: DC - Handle to a device context (TCarbonDeviceContext)
|
||
AMethodName - Method name
|
||
AParamName - Param name
|
||
Returns: If the DC is valid
|
||
------------------------------------------------------------------------------}
|
||
function CheckDC(const DC: HDC; const AMethodName: String;
|
||
AParamName: String): Boolean;
|
||
begin
|
||
if TObject(DC) is TCarbonDeviceContext then Result := True
|
||
else
|
||
begin
|
||
Result := False;
|
||
|
||
if Pos('.', AMethodName) = 0 then
|
||
DebugLn(SCarbonWSPrefix + AMethodName + ' Error - invalid DC ' +
|
||
AParamName + ' = ' + DbgS(DC) + '!')
|
||
else
|
||
DebugLn(AMethodName + ' Error - invalid DC ' + AParamName + ' = ' +
|
||
DbgS(DC) + '!');
|
||
end;
|
||
end;
|
||
|
||
{ TCarbonDeviceContext }
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetBkColor
|
||
Params: AValue - New background color
|
||
|
||
Sets the background color
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetBkColor(AValue: TColor);
|
||
begin
|
||
AValue := TColor(ColorToRGB(AValue));
|
||
FBkColor := AValue;
|
||
FBkBrush.SetColor(AValue, BkMode = OPAQUE);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetBkMode
|
||
Params: AValue - New background mode (OPAQUE, TRANSPARENT)
|
||
|
||
Sets the background mode
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetBkMode(const AValue: Integer);
|
||
begin
|
||
if FBkMode <> AValue then
|
||
begin
|
||
FBkMode := AValue;
|
||
FBkBrush.SetColor(FBkColor, FBkMode = OPAQUE);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetCurrentBrush
|
||
Params: AValue - New brush
|
||
|
||
Sets the current brush
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetCurrentBrush(const AValue: TCarbonBrush);
|
||
begin
|
||
if AValue = nil then
|
||
begin
|
||
DebugLn('TCarbonDeviceContext.SetCurrentBrush Error - Value is nil!');
|
||
Exit;
|
||
end;
|
||
|
||
if FCurrentBrush <> AValue then
|
||
begin
|
||
if FCurrentBrush <> nil then FCurrentBrush.Unselect;
|
||
|
||
FCurrentBrush := AValue;
|
||
FCurrentBrush.Select;
|
||
FCurrentBrush.Apply(Self);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetCurrentFont
|
||
Params: AValue - New font
|
||
|
||
Sets the current font
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetCurrentFont(const AValue: TCarbonFont);
|
||
begin
|
||
if AValue = nil then
|
||
begin
|
||
DebugLn('TCarbonDeviceContext.SetCurrentFont Error - Value is nil!');
|
||
Exit;
|
||
end;
|
||
|
||
if FCurrentFont <> AValue then
|
||
begin
|
||
//DebugLn('TCarbonDeviceContext.SetCurrentFont ', DbgS(FCurrentFont), '->',
|
||
// DbgS(AValue));
|
||
if FCurrentFont <> nil then FCurrentFont.Unselect;
|
||
FCurrentFont := AValue;
|
||
FCurrentFont.Select;
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetCurrentPen
|
||
Params: AValue - New pen
|
||
|
||
Sets the current pen
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetCurrentPen(const AValue: TCarbonPen);
|
||
begin
|
||
if AValue = nil then
|
||
begin
|
||
DebugLn('TCarbonDeviceContext.SetCurrentPen Error - Value is nil!');
|
||
Exit;
|
||
end;
|
||
|
||
if FCurrentPen <> AValue then
|
||
begin
|
||
if FCurrentPen <> nil then FCurrentPen.Unselect;
|
||
|
||
FCurrentPen := AValue;
|
||
FCurrentPen.Select;
|
||
FCurrentPen.Apply(Self);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetCurrentRegion
|
||
Params: AValue - New region
|
||
|
||
Sets the current region
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetCurrentRegion(const AValue: TCarbonRegion);
|
||
begin
|
||
if AValue = nil then
|
||
begin
|
||
DebugLn('TCarbonDeviceContext.SetCurrentRegion Error - Value is nil!');
|
||
Exit;
|
||
end;
|
||
|
||
if FCurrentRegion <> AValue then
|
||
begin
|
||
if FCurrentRegion <> nil then FCurrentRegion.Unselect;
|
||
|
||
FCurrentRegion := AValue;
|
||
FCurrentRegion.Select;
|
||
FCurrentRegion.Apply(Self);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetROP2
|
||
Params: AValue - New binary raster operation
|
||
|
||
Sets the binary raster operation
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetROP2(const AValue: Integer);
|
||
begin
|
||
if FROP2 <> AValue then
|
||
begin
|
||
FROP2 := AValue;
|
||
CurrentPen.Apply(Self);
|
||
CurrentBrush.Apply(Self);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetTextColor
|
||
Params: AValue - New text color
|
||
|
||
Sets the text color
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetTextColor(AValue: TColor);
|
||
begin
|
||
AValue := TColor(ColorToRGB(AValue));
|
||
FTextColor := AValue;
|
||
TextBrush.SetColor(AValue, True);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Create
|
||
|
||
Creates new Carbon device context
|
||
------------------------------------------------------------------------------}
|
||
constructor TCarbonDeviceContext.Create;
|
||
begin
|
||
inherited Create;
|
||
FBkBrush := TCarbonBrush.Create(False);
|
||
FTextBrush := TCarbonBrush.Create(False);
|
||
|
||
FCurrentPen := DefaultPen;
|
||
FCurrentPen.Select;
|
||
FCurrentBrush := DefaultBrush;
|
||
FCurrentBrush.Select;
|
||
FCurrentFont := DefaultFont;
|
||
FCurrentFont.Select;
|
||
|
||
FClipRegion := TCarbonRegion.Create;
|
||
|
||
FCurrentRegion := FClipRegion;
|
||
FCurrentRegion.Select;
|
||
|
||
FTextFractional := True;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Destroy
|
||
|
||
Frees Carbon device context
|
||
------------------------------------------------------------------------------}
|
||
destructor TCarbonDeviceContext.Destroy;
|
||
begin
|
||
BkBrush.Free;
|
||
TextBrush.Free;
|
||
|
||
FSavedDCList.Free;
|
||
|
||
if FCurrentPen <> nil then FCurrentPen.Unselect;
|
||
if FCurrentBrush <> nil then FCurrentBrush.Unselect;
|
||
if FCurrentFont <> nil then FCurrentFont.Unselect;
|
||
if FCurrentRegion <> nil then FCurrentRegion.Unselect;
|
||
|
||
FClipRegion.Free;
|
||
|
||
inherited Destroy;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Reset
|
||
|
||
Resets the device context properties to defaults (pen, brush, ...)
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.Reset;
|
||
begin
|
||
FPenPos.x := 0;
|
||
FPenPos.y := 0;
|
||
|
||
// create brush for bk color and mode
|
||
FBkColor := clWhite;
|
||
FBkMode := TRANSPARENT;
|
||
FBkBrush.SetColor(clWhite, False);
|
||
|
||
// create brush for text color
|
||
FTextColor := clBlack;
|
||
FTextBrush.SetColor(clBlack, True);
|
||
|
||
// set raster operation to copy
|
||
FROP2 := R2_COPYPEN;
|
||
|
||
CurrentFont := DefaultFont;
|
||
|
||
if CGContext <> nil then
|
||
begin
|
||
{$IFDEF VerboseCanvas}
|
||
DebugLn('TCarbonDeviceContext.Reset set defaults');
|
||
{$ENDIF}
|
||
|
||
// enable anti-aliasing
|
||
CGContextSetShouldAntialias(CGContext, 1);
|
||
|
||
// set initial pen, brush and font
|
||
CurrentPen := DefaultPen;
|
||
CurrentBrush := DefaultBrush;
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SaveDC
|
||
Returns: Index of saved device context state
|
||
|
||
Note: must be used in pair with RestoreDC!
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.SaveDC: Integer;
|
||
begin
|
||
ClearClipping;
|
||
|
||
Result := 0;
|
||
if CGContext = nil then
|
||
begin
|
||
DebugLn('TCarbonDeviceContext.SaveDC CGContext is nil!');
|
||
Exit;
|
||
end;
|
||
|
||
if FSavedDCList = nil then FSavedDCList := TFPObjectList.Create(True);
|
||
|
||
CGContextSaveGState(CGContext);
|
||
Result := FSavedDCList.Add(SaveDCData) + 1;
|
||
|
||
{$IFDEF VerboseCanvas}
|
||
DebugLn('TCarbonDeviceContext.SaveDC Result: ', DbgS(Result));
|
||
{$ENDIF}
|
||
|
||
if isClipped then
|
||
begin
|
||
CGContextSaveGState(CGContext);
|
||
FClipRegion.Apply(Self);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.RestoreDC
|
||
Params: ASavedDC - Index of saved device context
|
||
Returns: If the function succeeds
|
||
|
||
Restores the previously saved state of device context
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.RestoreDC(ASavedDC: Integer): Boolean;
|
||
begin
|
||
ClearClipping;
|
||
|
||
Result := False;
|
||
if (FSavedDCList = nil) or (ASavedDC <= 0) or (ASavedDC > FSavedDCList.Count) then
|
||
begin
|
||
DebugLn(Format('TCarbonDeviceContext.RestoreDC SavedDC %d is not valid!', [ASavedDC]));
|
||
Exit;
|
||
end;
|
||
|
||
if FSavedDCList.Count > ASavedDC then
|
||
DebugLn(Format('TCarbonDeviceContext.RestoreDC Warning: SaveDC - RestoreDC' +
|
||
' not used in pair, skipped %d saved states!', [FSavedDCList.Count - ASavedDC]));
|
||
|
||
while FSavedDCList.Count > ASavedDC do
|
||
begin
|
||
CGContextRestoreGState(CGContext);
|
||
FSavedDCList.Delete(FSavedDCList.Count - 1);
|
||
end;
|
||
|
||
{$IFDEF VerboseCanvas}
|
||
DebugLn('TCarbonDeviceContext.RestoreDC SavedDC: ', DbgS(ASavedDC));
|
||
{$ENDIF}
|
||
|
||
CGContextRestoreGState(CGContext);
|
||
RestoreDCData(TCarbonDCData(FSavedDCList[ASavedDC - 1]));
|
||
FSavedDCList.Delete(ASavedDC - 1);
|
||
Result := True;
|
||
|
||
{$IFDEF VerboseCanvas}
|
||
DebugLn('TCarbonDeviceContext.RestoreDC End');
|
||
{$ENDIF}
|
||
|
||
if FSavedDCList.Count = 0 then FreeAndNil(FSavedDCList);
|
||
|
||
|
||
if isClipped then
|
||
begin
|
||
CGContextSaveGState(CGContext);
|
||
FClipRegion.Apply(Self);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SaveDCData
|
||
Returns: The device context data
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.SaveDCData: TCarbonDCData;
|
||
begin
|
||
Result := TCarbonDCData.Create;
|
||
|
||
Result.CurrentFont := FCurrentFont;
|
||
Result.CurrentBrush := FCurrentBrush;
|
||
Result.CurrentPen := FCurrentPen;
|
||
Result.CurrentRegion := FCurrentRegion;
|
||
|
||
Result.BkColor := FBkColor;
|
||
Result.BkMode := FBkMode;
|
||
Result.BkBrush := FBkBrush;
|
||
|
||
Result.TextColor := FTextColor;
|
||
Result.TextBrush := FTextBrush;
|
||
|
||
Result.ROP2 := FROP2;
|
||
Result.PenPos := FPenPos;
|
||
|
||
Result.WindowOfs := FWindowOfs;
|
||
Result.ViewportOfs := FViewportOfs;
|
||
|
||
Result.isClipped := isClipped;
|
||
Result.ClipShape := FClipRegion.GetShapeCopy;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.RestoreDCData
|
||
Params: AData - Device context data
|
||
|
||
Restores device context data
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.RestoreDCData(const AData: TCarbonDCData);
|
||
begin
|
||
if (FCurrentFont <> AData.CurrentFont) then
|
||
begin
|
||
if (FCurrentFont <> nil) then
|
||
FCurrentFont.Unselect;
|
||
if (AData.CurrentFont <> nil) then
|
||
AData.CurrentFont.Select;
|
||
end;
|
||
FCurrentFont := AData.CurrentFont;
|
||
|
||
if (FCurrentBrush <> AData.CurrentBrush) then
|
||
begin
|
||
if (FCurrentBrush <> nil) then
|
||
FCurrentBrush.Unselect;
|
||
if (AData.CurrentBrush <> nil) then
|
||
AData.CurrentBrush.Select;
|
||
end;
|
||
FCurrentBrush := AData.CurrentBrush;
|
||
FCurrentBrush.Apply(Self);
|
||
|
||
if (FCurrentPen <> AData.CurrentPen) then
|
||
begin
|
||
if (FCurrentPen <> nil) then
|
||
FCurrentPen.Unselect;
|
||
if (AData.CurrentPen <> nil) then
|
||
AData.CurrentPen.Select;
|
||
end;
|
||
FCurrentPen := AData.CurrentPen;
|
||
FCurrentPen.Apply(Self);
|
||
|
||
if (FCurrentRegion <> AData.CurrentRegion) then
|
||
begin
|
||
if (FCurrentRegion <> nil) then
|
||
FCurrentRegion.Unselect;
|
||
if (AData.CurrentRegion <> nil) then
|
||
AData.CurrentRegion.Select;
|
||
end;
|
||
FCurrentRegion := AData.CurrentRegion;
|
||
|
||
FBkColor := AData.BkColor;
|
||
FBkMode := AData.BkMode;
|
||
FBkBrush := AData.BkBrush;
|
||
|
||
FTextColor := AData.TextColor;
|
||
FTextBrush := AData.TextBrush;
|
||
|
||
FROP2 := AData.ROP2;
|
||
FPenPos := AData.PenPos;
|
||
|
||
FWindowOfs := AData.WindowOfs;
|
||
FViewportOfs := AData.ViewportOfs;
|
||
|
||
isClipped := AData.isClipped;
|
||
FClipRegion.Shape := AData.ClipShape;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.BeginTextRender
|
||
Params: AStr - UTF8 string to render
|
||
ACount - Count of chars to render
|
||
ALayout - ATSU layout
|
||
Returns: If the function suceeds
|
||
|
||
Creates the ATSU text layout for the specified text and manages the device
|
||
context to render the text.
|
||
NOTE: Coordination system is set upside-down!
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.BeginTextRender(AStr: PChar; ACount: Integer; out
|
||
ALayout: TCarbonTextLayout): Boolean;
|
||
var
|
||
S: String;
|
||
begin
|
||
Result := False;
|
||
|
||
if ACount = 0 then Exit;
|
||
|
||
// save context
|
||
CGContextSaveGState(CGContext);
|
||
|
||
// change coordination system
|
||
CGContextScaleCTM(CGContext, 1, -1);
|
||
|
||
if ACount < 0 then S := AStr
|
||
else S := Copy(AStr, 1, ACount);
|
||
|
||
ALayout := CurrentFont.CreateTextLayout(S, TextFractional);
|
||
ALayout.Apply(Self);
|
||
|
||
Result := True;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.EndTextRender
|
||
Params: ALayout - ATSU layout
|
||
|
||
Frees the ATSU text layout and manages the device
|
||
context to render ordinary graphic
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.EndTextRender(var ALayout: TCarbonTextLayout);
|
||
begin
|
||
// restore context
|
||
CGContextRestoreGState(CGContext);
|
||
|
||
ALayout.Release;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetAntialiasing
|
||
Params: AValue - If should antialias
|
||
|
||
Sets whether device context should antialias
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetAntialiasing(AValue: Boolean);
|
||
begin
|
||
if not AValue then
|
||
CGContextSetInterpolationQuality(CGContext, kCGInterpolationNone)
|
||
else
|
||
CGContextSetInterpolationQuality(CGContext, kCGInterpolationDefault);
|
||
CGContextSetShouldAntialias(CGContext, CBool(AValue));
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.DrawCGImage
|
||
Params: X, Y - Left, Top
|
||
Width, Height
|
||
CGImage
|
||
Returns: If the function succeeds
|
||
|
||
Draws CGImage into CGContext
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.DrawCGImage(X, Y, Width, Height: Integer;
|
||
CGImage: CGImageRef): Boolean;
|
||
begin
|
||
Result := False;
|
||
|
||
// save dest context
|
||
CGContextSaveGState(CGContext);
|
||
|
||
CGContextSetBlendMode(CGContext, kCGBlendModeNormal);
|
||
try
|
||
SetCGFillping(CGContext, Width, Height);
|
||
if OSError(
|
||
HIViewDrawCGImage(CGContext,
|
||
GetCGRectSorted(X, Y, X + Width, Y + Height), CGImage),
|
||
'DrawCGImage', 'HIViewDrawCGImage') then Exit;
|
||
RestoreCGFillping(CGContext, Width, Height);
|
||
finally
|
||
CGContextRestoreGState(CGContext);
|
||
end;
|
||
|
||
Result := True;
|
||
end;
|
||
|
||
procedure TCarbonDeviceContext.SetCGFillping(Ctx: CGContextRef; Width, Height: Integer);
|
||
begin
|
||
if Width < 0 then begin
|
||
CGContextTranslateCTM(Ctx, -Width, 0);
|
||
CGContextScaleCTM(Ctx, -1, 1);
|
||
end;
|
||
|
||
if Height < 0 then begin
|
||
CGContextTranslateCTM(Ctx, 0, -Height);
|
||
CGContextScaleCTM(Ctx, 1, -1);
|
||
end;
|
||
end;
|
||
|
||
procedure TCarbonDeviceContext.RestoreCGFillping(Ctx: CGContextRef; Width, Height: Integer);
|
||
begin
|
||
if Height < 0 then begin
|
||
CGContextTranslateCTM(Ctx, 0, Height);
|
||
CGContextScaleCTM(Ctx, 1, -1);
|
||
end;
|
||
|
||
if Width < 0 then begin
|
||
CGContextScaleCTM(Ctx, -1, 1);
|
||
CGContextTranslateCTM(Ctx, Width, 0);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.DrawFocusRect
|
||
Params: ARect - Bounding rectangle
|
||
Returns: If the function succeeds
|
||
|
||
Draws a focus rectangle
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.DrawFocusRect(ARect: TRect);
|
||
var
|
||
AOutSet: SInt32;
|
||
begin
|
||
// LCL thinks that focus cannot be drawn outside focus rects, but carbon do that
|
||
// => correct rect
|
||
OSError(GetThemeMetric(kThemeMetricFocusRectOutset, AOutSet{%H-}),
|
||
Self, 'DrawFocusRect', 'GetThemeMetric');
|
||
InflateRect(ARect, -AOutSet, -AOutSet);
|
||
OSError(
|
||
HIThemeDrawFocusRect(RectToCGRect(ARect), True, CGContext, kHIThemeOrientationNormal),
|
||
Self, 'DrawFocusRect', 'HIThemeDrawFocusRect');
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.DrawGrid
|
||
Params: Arect - Grid rectangle
|
||
DX, DY - Grid cell width and height
|
||
|
||
Draws the point grid
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.DrawGrid(const ARect: TRect; DX, DY: Integer);
|
||
var
|
||
Y: Integer;
|
||
GridLine: Array [0..1] of Single;
|
||
begin
|
||
CGContextSaveGState(CGContext);
|
||
try
|
||
CGContextSetShouldAntialias(CGContext, 0);
|
||
|
||
GridLine[0] := 1;
|
||
GridLine[1] := DX - 1;
|
||
CGContextSetLineDash(CGContext, 0, @GridLine[0], Length(GridLine));
|
||
|
||
CGContextBeginPath(CGContext);
|
||
|
||
// draw horzontal dotted lines
|
||
for Y := 0 to (ARect.Bottom - ARect.Top - 1) div DY do
|
||
begin
|
||
CGContextMoveToPoint(CGContext, ARect.Left, ARect.Top + Y * DY + 1);
|
||
CGContextAddLineToPoint(CGContext, ARect.Right, ARect.Top + Y * DY + 1);
|
||
end;
|
||
|
||
CGContextStrokePath(CGContext);
|
||
finally
|
||
CGContextRestoreGState(CGContext);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Ellipse
|
||
Params:
|
||
X1 - X-coord. of bounding rectangle's upper-left corner
|
||
Y1 - Y-coord. of bounding rectangle's upper-left corner
|
||
X2 - X-coord. of bounding rectangle's lower-right corner
|
||
Y2 - Y-coord. of bounding rectangle's lower-right corner
|
||
|
||
Draws a ellipse. The ellipse is outlined by using the current pen and filled
|
||
by using the current brush.
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.Ellipse(X1, Y1, X2, Y2: Integer);
|
||
var
|
||
R: CGRect;
|
||
begin
|
||
if (X1 = X2) or (Y1 = Y2) then Exit;
|
||
|
||
R := GetCGRectSorted(X1, Y1, X2, Y2);
|
||
R.origin.x := R.origin.x + 0.5;
|
||
R.origin.y := R.origin.y + 0.5;
|
||
R.size.width := R.size.width - 1;
|
||
R.size.height := R.size.height - 1;
|
||
|
||
CGContextBeginPath(CGContext);
|
||
CGContextAddEllipseInRect(CGContext, R);
|
||
CGContextDrawPath(CGContext, kCGPathFillStroke);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.ExcludeClipRect
|
||
Params: Left, Top, Right, Bottom - Rectangle coordinates
|
||
|
||
Subtracts all intersecting points of the passed bounding rectangle from the
|
||
current clipping region of the device context.
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.ExcludeClipRect(Left, Top, Right, Bottom: Integer);
|
||
var
|
||
ClipBox: TRect;
|
||
Rects: CGRectArray;
|
||
begin
|
||
if (Left < Right) and (Top < Bottom) then
|
||
begin
|
||
// get clip bounding box, exclude passed rect and intersect result
|
||
// with clip region
|
||
ClipBox := CGRectToRect(CGContextGetClipBoundingBox(CGContext));
|
||
|
||
Rects := ExcludeRect(ClipBox, Classes.Rect(Left, Top, Right, Bottom));
|
||
|
||
if Length(Rects) > 0 then
|
||
CGContextClipToRects(CGContext, @Rects[0], Length(Rects))
|
||
else
|
||
CGContextClipToRect(CGContext, CGRectZero);
|
||
end;
|
||
end;
|
||
|
||
procedure TCarbonDeviceContext.ApplyTransform(Trans: CGAffineTransform);
|
||
var
|
||
T2: CGAffineTransform;
|
||
begin
|
||
T2 := CGContextGetCTM(CGContext);
|
||
// restore old CTM since CTM may changed after the clipping
|
||
if CGAffineTransformEqualToTransform(Trans, T2) = 0 then
|
||
CGContextTranslateCTM(CGContext, Trans.a * Trans.tx - T2.a * T2.tx,
|
||
Trans.d * Trans.ty - T2.d * T2.ty);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.ExtTextOut
|
||
Params: X - X-coordinate of reference point
|
||
Y - Y-coordinate of reference point
|
||
Options - Text-output options
|
||
Rect - Optional clipping and/or opaquing rectangle (TODO)
|
||
Str - Character string to be drawn
|
||
Count - Number of characters in string
|
||
Dx - Pointer to array of intercharacter spacing values
|
||
Returns: If the string was drawn
|
||
|
||
Draws a character string by using the currently selected font
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.ExtTextOut(X, Y: Integer; Options: Longint;
|
||
Rect: PRect; Str: PChar; Count: Longint; Dx: PInteger): Boolean;
|
||
var
|
||
TextLayout: TCarbonTextLayout;
|
||
BrushSolid: Boolean;
|
||
begin
|
||
Result := False;
|
||
//DebugLn('TCarbonDeviceContext.ExtTextOut ' + DbgS(X) + ', ' + DbgS(Y) + ' R: ' + DbgS(Rect^) +
|
||
// ' S: ' + Str + ' C: ' + DbgS(Count));
|
||
|
||
if Assigned(Rect) then
|
||
begin
|
||
// fill background
|
||
if (Options and ETO_OPAQUE) > 0 then
|
||
begin
|
||
BrushSolid := BkBrush.Solid; // must ignore BkMode
|
||
BkBrush.Solid := True;
|
||
FillRect(Rect^, BkBrush);
|
||
BkBrush.Solid := BrushSolid;
|
||
end;
|
||
//DebugLn('TCarbonDeviceContext.ExtTextOut fill ' + DbgS(Rect^));
|
||
end;
|
||
|
||
if not BeginTextRender(Str, Count, TextLayout) then Exit;
|
||
try
|
||
if CurrentFont.LineRotation = 0 then // TODO: fill rotated text background
|
||
begin
|
||
// fill drawed text background
|
||
if BkMode = OPAQUE then
|
||
begin
|
||
BrushSolid := BkBrush.Solid; // must ignore BkMode
|
||
BkBrush.Solid := True;
|
||
BkBrush.Apply(Self, False); // do not use ROP2
|
||
CGContextFillRect(CGContext, TextLayout.GetDrawBounds(X, Y));
|
||
BkBrush.Solid := BrushSolid;
|
||
end;
|
||
end;
|
||
|
||
// apply text color
|
||
TextBrush.Apply(Self, False); // do not use ROP2
|
||
|
||
// finally draw the text
|
||
if not TextLayout.Draw(X, Y, DX, Count) then Exit;
|
||
Result := True;
|
||
finally
|
||
EndTextRender(TextLayout);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.FillRect
|
||
Params: Rect - Record with rectangle coordinates
|
||
Brush - Carbon brush
|
||
|
||
Fills the rectangle by using the specified brush
|
||
It includes the left and top borders, but excludes the right and
|
||
bottom borders of the rectangle!
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.FillRect(Rect: TRect; Brush: TCarbonBrush);
|
||
begin
|
||
Brush.Apply(Self, False); // do not use ROP2
|
||
try
|
||
CGContextFillRect(CGContext, GetCGRectSorted(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom));
|
||
finally
|
||
CurrentBrush.Apply(Self); // apply current brush
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Frame
|
||
Params: X1 - X-coordinate of bounding rectangle's upper-left corner
|
||
Y1 - Y-coordinate of bounding rectangle's upper-left corner
|
||
X2 - X-coordinate of bounding rectangle's lower-right corner
|
||
Y2 - Y-coordinate of bounding rectangle's lower-right corner
|
||
|
||
Draws a border in Carbon native style
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.Frame(X1, Y1, X2, Y2: Integer);
|
||
begin
|
||
StockNullBrush.Apply(Self, False); // do not use ROP2
|
||
try
|
||
Rectangle(X1, Y1, X2 + 1, Y2 + 1);
|
||
finally
|
||
CurrentBrush.Apply(Self); // apply current brush
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Frame3D
|
||
Params: ARect - Bounding box of frame
|
||
FrameWidth - Frame width
|
||
Style - Frame style
|
||
|
||
Draws a 3D border in Carbon native style
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.Frame3D(var ARect: TRect;
|
||
const FrameWidth: integer; const Style: TBevelCut);
|
||
var
|
||
I, D: Integer;
|
||
DrawInfo: HIThemeGroupBoxDrawInfo;
|
||
const
|
||
SName = 'Frame3D';
|
||
begin
|
||
if Style = bvRaised then
|
||
begin
|
||
D := GetCarbonThemeMetric(kThemeMetricPrimaryGroupBoxContentInset, 1);
|
||
|
||
// draw frame as group box
|
||
DrawInfo.version := 0;
|
||
DrawInfo.state := kThemeStateActive;
|
||
DrawInfo.kind := kHIThemeGroupBoxKindPrimary;
|
||
|
||
for I := 1 to FrameWidth do
|
||
begin
|
||
OSError(
|
||
HIThemeDrawGroupBox(RectToCGRect(ARect), DrawInfo, CGContext,
|
||
kHIThemeOrientationNormal),
|
||
Self, SName, 'HIThemeDrawGroupBox');
|
||
|
||
InflateRect(ARect, -D, -D);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.GetClipRect
|
||
Returns: Clipping rectangle
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.GetClipRect: TRect;
|
||
begin
|
||
Result := CGRectToRect(CGContextGetClipBoundingBox(CGContext));
|
||
end;
|
||
|
||
function TCarbonDeviceContext.GetLineLastPixelPos(PrevPos, NewPos: TPoint
|
||
): TPoint;
|
||
begin
|
||
Result := NewPos;
|
||
|
||
if NewPos.X > PrevPos.X then
|
||
dec(Result.X)
|
||
else
|
||
if NewPos.X < PrevPos.X then
|
||
inc(Result.X);
|
||
|
||
if NewPos.Y > PrevPos.Y then
|
||
dec(Result.Y)
|
||
else
|
||
if NewPos.Y < PrevPos.Y then
|
||
inc(Result.Y);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.GetPixel
|
||
Params: X, Y - Coordinates of pixel
|
||
Returns: Specified pixel color
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.GetPixel(X, Y: Integer): TGraphicsColor;
|
||
begin
|
||
Result := clNone;
|
||
DebugLn('TODO: Implement get pixel for CGContext.');
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.GetTextExtentPoint
|
||
Params: Str - Text string
|
||
Count - Number of characters in string
|
||
Size - The record for the dimensions of the string
|
||
Returns: If the function succeeds
|
||
|
||
Computes the width and height of the specified string of text
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.GetTextExtentPoint(Str: PChar; Count: Integer;
|
||
var Size: TSize): Boolean;
|
||
var
|
||
TextLayout: TCarbonTextLayout;
|
||
begin
|
||
Result := False;
|
||
Size.cx := 0;
|
||
Size.cy := 0;
|
||
|
||
if not BeginTextRender(Str, Count, TextLayout) then Exit;
|
||
try
|
||
Size.cx := TextLayout.GetWidth;
|
||
Size.cy := TextLayout.GetHeight;
|
||
|
||
Result := True;
|
||
finally
|
||
EndTextRender(TextLayout);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.GetTextMetrics
|
||
Params: TM - The Record for the text metrics
|
||
Returns: If the function succeeds
|
||
|
||
Fills the specified buffer with the metrics for the currently selected font
|
||
TODO: get exact max. and av. char width, pitch and charset
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.GetTextMetrics(var TM: TTextMetric): Boolean;
|
||
var
|
||
TextStyle: ATSUStyle;
|
||
M: ATSUTextMeasurement;
|
||
B: Boolean;
|
||
TextLayout: TCarbonTextLayout;
|
||
const
|
||
SName = 'GetTextMetrics';
|
||
SGetAttrName = 'ATSUGetAttribute';
|
||
begin
|
||
Result := False;
|
||
|
||
TextStyle := CurrentFont.Style;
|
||
|
||
FillChar(TM, SizeOf(TM), 0);
|
||
|
||
// According to the MSDN library, TEXTMETRIC:
|
||
// the average char width is generally defined as the width of the letter x
|
||
if not BeginTextRender('x', 1, TextLayout) then Exit;
|
||
try
|
||
|
||
TM.tmAscent := RoundFixed(TextLayout.Ascent);
|
||
TM.tmDescent := RoundFixed(TextLayout.Descent);
|
||
TM.tmHeight := RoundFixed(TextLayout.Ascent + TextLayout.Descent);
|
||
|
||
if OSError(ATSUGetAttribute(TextStyle, kATSULeadingTag, SizeOf(M), @M, nil),
|
||
Self, SName, SGetAttrName, 'kATSULeadingTag', kATSUNotSetErr) then Exit;
|
||
TM.tmInternalLeading := RoundFixed(M);
|
||
TM.tmExternalLeading := 0;
|
||
|
||
TM.tmAveCharWidth := RoundFixed(TextLayout.TextAfter - TextLayout.TextBefore);
|
||
finally
|
||
EndTextRender(TextLayout);
|
||
end;
|
||
|
||
TM.tmMaxCharWidth := TM.tmAscent; // TODO: don't know how to determine this right
|
||
TM.tmOverhang := 0;
|
||
TM.tmDigitizedAspectX := 0;
|
||
TM.tmDigitizedAspectY := 0;
|
||
TM.tmFirstChar := 'a';
|
||
TM.tmLastChar := 'z';
|
||
TM.tmDefaultChar := 'x';
|
||
TM.tmBreakChar := '?';
|
||
|
||
if OSError(ATSUGetAttribute(TextStyle, kATSUQDBoldfaceTag, SizeOf(B), @B, nil),
|
||
Self, SName, SGetAttrName, 'kATSUQDBoldfaceTag', kATSUNotSetErr) then Exit;
|
||
if B then TM.tmWeight := FW_NORMAL
|
||
else TM.tmWeight := FW_BOLD;
|
||
|
||
if OSError(ATSUGetAttribute(TextStyle, kATSUQDItalicTag, SizeOf(B), @B, nil),
|
||
Self, SName, SGetAttrName, 'kATSUQDItalicTag', kATSUNotSetErr) then Exit;
|
||
TM.tmItalic := Byte(B);
|
||
|
||
if OSError(ATSUGetAttribute(TextStyle, kATSUQDUnderlineTag, SizeOf(B), @B, nil),
|
||
Self, SName, SGetAttrName, 'kATSUQDUnderlineTag', kATSUNotSetErr) then Exit;
|
||
TM.tmUnderlined := Byte(B);
|
||
|
||
if OSError(ATSUGetAttribute(TextStyle, kATSUStyleStrikeThroughTag, SizeOf(B), @B, nil),
|
||
Self, SName, SGetAttrName, 'kATSUStyleStrikeThroughTag', kATSUNotSetErr) then Exit;
|
||
TM.tmStruckOut := Byte(B);
|
||
|
||
// TODO: get these from font
|
||
TM.tmPitchAndFamily := FIXED_PITCH or TRUETYPE_FONTTYPE;
|
||
TM.tmCharSet := DEFAULT_CHARSET;
|
||
|
||
Result := True;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.InvertRectangle
|
||
Params: X1 - X-coordinate of bounding rectangle's upper-left corner
|
||
Y1 - Y-coordinate of bounding rectangle's upper-left corner
|
||
X2 - X-coordinate of bounding rectangle's lower-right corner
|
||
Y2 - Y-coordinate of bounding rectangle's lower-right corner
|
||
|
||
Draws an inverted rectangle.
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.InvertRectangle(X1, Y1, X2, Y2: Integer);
|
||
begin
|
||
// save dest context
|
||
CGContextSaveGState(CGContext);
|
||
try
|
||
WhiteBrush.Apply(Self, False);
|
||
CGContextSetBlendMode(CGContext, kCGBlendModeDifference);
|
||
|
||
CGContextFillRect(CGContext, GetCGRectSorted(X1, Y1, X2, Y2));
|
||
finally
|
||
CGContextRestoreGState(CGContext);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.LineTo
|
||
Params: X - X-coordinate of line's ending point
|
||
Y - Y-coordinate of line's ending point
|
||
|
||
Draws a line from the current position up to the specified point and updates
|
||
the current position
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.LineTo(X, Y: Integer);
|
||
var
|
||
deltaX, deltaY, absDeltaX, absDeltaY: Integer;
|
||
clipDeltaX, clipDeltaY: Float32;
|
||
tx,ty:Float32;
|
||
begin
|
||
deltaX := X - PenPos.x;
|
||
deltaY := Y - PenPos.y;
|
||
if (deltaX=0) and (deltaY=0) then Exit;
|
||
|
||
absDeltaX := Abs(deltaX);
|
||
absDeltaY := Abs(deltaY);
|
||
if (absDeltaX<=1) and (absDeltaY<=1) then
|
||
begin
|
||
// special case for 1-pixel lines
|
||
tx := FPenPos.x + 0.55;
|
||
ty := FPenPos.y + 0.55;
|
||
end
|
||
else
|
||
begin
|
||
// exclude the last pixel from the line
|
||
if absDeltaX > absDeltaY then
|
||
begin
|
||
if deltaX > 0 then clipDeltaX := -1.0 else clipDeltaX := 1.0;
|
||
clipDeltaY := clipDeltaX * deltaY / deltaX;
|
||
end
|
||
else
|
||
begin
|
||
if deltaY > 0 then clipDeltaY := -1.0 else clipDeltaY := 1.0;
|
||
clipDeltaX := clipDeltaY * deltaX / deltaY;
|
||
end;
|
||
tx := X + clipDeltaX + 0.5;
|
||
ty := Y + clipDeltaY + 0.5;
|
||
end;
|
||
|
||
CGContextBeginPath(CGContext);
|
||
CGContextMoveToPoint(CGContext, PenPos.x + 0.5, PenPos.y + 0.5);
|
||
CGContextAddLineToPoint(CGContext, tx, ty);
|
||
CGContextStrokePath(CGContext);
|
||
|
||
FPenPos.x := X;
|
||
FPenPos.y := Y;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.PolyBezier
|
||
Params: Points - Points defining the cubic B<>zier curve
|
||
NumPts - Number of points passed
|
||
Filled - Fill the drawed shape
|
||
Continous - Connect B<>zier curves
|
||
|
||
Draws a cubic B<>zier curves. The first curve is drawn from the first point to
|
||
the fourth point with the second and third points being the control points.
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.PolyBezier(Points: PPoint; NumPts: Integer;
|
||
Filled, Continuous: boolean);
|
||
var
|
||
C1, C2: TPoint;
|
||
begin
|
||
if Points = nil then Exit;
|
||
if NumPts < 4 then Exit;
|
||
|
||
CGContextBeginPath(CGContext);
|
||
|
||
if Continuous then
|
||
begin
|
||
CGContextMoveToPoint(CGContext, Points^.x + 0.5, Points^.y + 0.5);
|
||
Dec(NumPts);
|
||
|
||
while NumPts >= 3 do
|
||
begin
|
||
Inc(Points);
|
||
C1 := Points^;
|
||
Inc(Points);
|
||
C2 := Points^;
|
||
Inc(Points);
|
||
|
||
CGContextAddCurveToPoint(CGContext, C1.x + 0.5, C1.y + 0.5, C2.x + 0.5, C2.y + 0.5,
|
||
Points^.x + 0.5, Points^.y + 0.5);
|
||
|
||
Dec(NumPts, 3);
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
while NumPts >= 4 do
|
||
begin
|
||
CGContextMoveToPoint(CGContext, Points^.x + 0.5, Points^.y + 0.5);
|
||
|
||
Inc(Points);
|
||
C1 := Points^;
|
||
Inc(Points);
|
||
C2 := Points^;
|
||
Inc(Points);
|
||
|
||
CGContextAddCurveToPoint(CGContext, C1.x + 0.5, C1.y + 0.5, C2.x + 0.5, C2.y + 0.5,
|
||
Points^.x + 0.5, Points^.y + 0.5);
|
||
|
||
Inc(Points);
|
||
Dec(NumPts, 4);
|
||
end;
|
||
end;
|
||
|
||
if Filled and Continuous then
|
||
CGContextDrawPath(CGContext, kCGPathFillStroke)
|
||
else
|
||
CGContextDrawPath(CGContext, kCGPathStroke);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Polygon
|
||
Params: Points - Pointer to polygon's vertices
|
||
NumPts - Number of polygon's vertices
|
||
Winding - Use winding fill rule
|
||
|
||
Draws a closed, many-sided shape on the canvas, using the pen and brush.
|
||
If Winding is set, Polygon fills the shape using the Winding fill algorithm.
|
||
Otherwise, Polygon uses the even-odd (alternative) fill algorithm. The first
|
||
point is always connected to the last point.
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.Polygon(Points: PPoint; NumPts: Integer;
|
||
Winding: boolean);
|
||
begin
|
||
if Points = nil then Exit;
|
||
if NumPts < 2 then Exit;
|
||
|
||
CGContextBeginPath(CGContext);
|
||
CGContextMoveToPoint(CGContext, Points^.x + 0.5, Points^.y + 0.5);
|
||
Dec(NumPts);
|
||
|
||
while NumPts > 0 do
|
||
begin
|
||
Inc(Points);
|
||
CGContextAddLineToPoint(CGContext, Points^.x + 0.5, Points^.y + 0.5);
|
||
Dec(NumPts);
|
||
end;
|
||
|
||
CGContextClosePath(CGContext);
|
||
|
||
if Winding then
|
||
CGContextDrawPath(CGContext, kCGPathFillStroke)
|
||
else
|
||
CGContextDrawPath(CGContext, kCGPathEOFillStroke);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Polyline
|
||
Params: Points - Pointer to array containing points
|
||
NumPts - Number of points in the array
|
||
|
||
Draws a series of line segments by connecting the points in the specified
|
||
array
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.Polyline(Points: PPoint; NumPts: Integer);
|
||
var
|
||
P: TPoint;
|
||
begin
|
||
if Points = nil then Exit;
|
||
if NumPts < 1 then Exit;
|
||
|
||
CGContextBeginPath(CGContext);
|
||
CGContextMoveToPoint(CGContext, Points^.x + 0.5, Points^.y + 0.5);
|
||
Dec(NumPts);
|
||
|
||
while NumPts > 1 do
|
||
begin
|
||
Inc(Points);
|
||
CGContextAddLineToPoint(CGContext, Points^.x + 0.5, Points^.y + 0.5);
|
||
Dec(NumPts);
|
||
end;
|
||
P := GetLineLastPixelPos(Points[0], Points[1]);
|
||
CGContextAddLineToPoint(CGContext, P.x + 0.5, P.y + 0.5);
|
||
|
||
CGContextStrokePath(CGContext);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.Rectangle
|
||
Params: X1 - X-coordinate of bounding rectangle's upper-left corner
|
||
Y1 - Y-coordinate of bounding rectangle's upper-left corner
|
||
X2 - X-coordinate of bounding rectangle's lower-right corner
|
||
Y2 - Y-coordinate of bounding rectangle's lower-right corner
|
||
|
||
Draws a rectangle. The rectangle is outlined by using the current pen and
|
||
filled by using the current brush.
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.Rectangle(X1, Y1, X2, Y2: Integer);
|
||
var
|
||
R: CGRect;
|
||
begin
|
||
//DebugLn('TCarbonDeviceContext.Rectangle ' + DbgS(Classes.Rect(X1, Y1, X2, Y2)));
|
||
if (X1 = X2) or (Y1 = Y2) then Exit;
|
||
|
||
R := GetCGRectSorted(X1, Y1, X2, Y2);
|
||
R.origin.x := R.origin.x + 0.5;
|
||
R.origin.y := R.origin.y + 0.5;
|
||
R.size.width := R.size.width - 1;
|
||
R.size.height := R.size.height - 1;
|
||
|
||
CGContextBeginPath(CGContext);
|
||
CGContextAddRect(CGContext, R);
|
||
CGContextDrawPath(CGContext, kCGPathFillStroke);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonDeviceContext.SetPixel
|
||
Params: X, Y - Position
|
||
AColor - New color for specified position
|
||
|
||
Sets the color of the specified pixel on the canvas
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonDeviceContext.SetPixel(X, Y: Integer; AColor: TGraphicsColor);
|
||
var
|
||
R, G, B: Byte;
|
||
begin
|
||
CGContextSaveGState(CGContext);
|
||
try
|
||
// apply color to fill
|
||
CGContextSetBlendMode(CGContext, kCGBlendModeNormal);
|
||
RedGreenBlue(ColorToRGB(AColor), R, G, B);
|
||
CGContextSetRGBFillColor(CGContext, R / 255, G / 255, B / 255, 1.0);
|
||
|
||
CGContextFillRect(CGContext, GetCGRect(X, Y, X + 1, Y + 1));
|
||
finally
|
||
CGContextRestoreGState(CGContext);
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: StretchMaskBlt
|
||
Params: X, Y - Left/top corner of the destination rectangle
|
||
Width, Height - Size of the destination rectangle
|
||
SrcDC - Carbon device context
|
||
XSrc, YSrc - Left/top corner of the source rectangle
|
||
SrcWidth, SrcHeight - Size of the source rectangle
|
||
Mask - mask bitmap
|
||
XMask, YMask - Left/top corner of the mask rectangle
|
||
Rop - Raster operation to be performed (TODO)
|
||
Returns: If the function succeeds
|
||
|
||
Copies a bitmap from a source rectangle into a destination rectangle using
|
||
the specified raster operations. If needed it resizes the bitmap to
|
||
fit the dimensions of the destination rectangle. Sizing is done according to
|
||
the stretching mode currently set in the destination device context.
|
||
TODO: copy from any canvas
|
||
ROP
|
||
stretch mode (should be set by winapi call in DC (MWE))
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonDeviceContext.StretchDraw(X, Y, Width, Height: Integer;
|
||
SrcDC: TCarbonBitmapContext; XSrc, YSrc, SrcWidth, SrcHeight: Integer;
|
||
Msk: TCarbonBitmap; XMsk, YMsk: Integer; Rop: DWORD): Boolean;
|
||
var
|
||
Image, MskImage: CGImageRef;
|
||
SubImage, SubMask: Boolean;
|
||
Bitmap: TCarbonBitmap;
|
||
LayRect, DstRect: CGRect;
|
||
ImgRect: CGRect;
|
||
LayerContext: CGContextRef;
|
||
Layer: CGLayerRef;
|
||
UseLayer: Boolean;
|
||
begin
|
||
Result := False;
|
||
|
||
Image := nil;
|
||
Bitmap := SrcDC.GetBitmap;
|
||
if Bitmap <> nil then Image := Bitmap.CGImage;
|
||
|
||
if Image = nil then Exit;
|
||
|
||
DstRect := CGRectMake(X, Y, Abs(Width), Abs(Height));
|
||
|
||
SubMask := (Msk <> nil)
|
||
and (Msk.CGImage <> nil)
|
||
and ( (XMsk <> 0)
|
||
or (YMsk <> 0)
|
||
or (Msk.Width <> SrcWidth)
|
||
or (Msk.Height <> SrcHeight));
|
||
|
||
SubImage := ((Msk <> nil) and (Msk.CGImage <> nil))
|
||
or (XSrc <> 0)
|
||
or (YSrc <> 0)
|
||
or (SrcWidth <> Bitmap.Width)
|
||
or (SrcHeight <> Bitmap.Height);
|
||
|
||
|
||
if SubMask then
|
||
MskImage := Msk.CreateSubImage(Bounds(XMsk, YMsk, SrcWidth, SrcHeight))
|
||
else
|
||
if Msk <> nil then MskImage := Msk.CGImage
|
||
else MskImage := nil;
|
||
|
||
if SubImage then
|
||
Image := Bitmap.CreateSubImage(Bounds(XSrc, YSrc, SrcWidth, SrcHeight));
|
||
|
||
|
||
UseLayer:=Assigned(MskImage)
|
||
or (CGImageGetWidth(Image){%H-}<>SrcWidth)
|
||
or (CGImageGetHeight(Image){%H-}<>SrcHeight);
|
||
|
||
try
|
||
if not UseLayer then
|
||
begin
|
||
// Normal drawing
|
||
Result := DrawCGImage(X, Y, Width, Height, Image);
|
||
end
|
||
else
|
||
begin
|
||
// use temp layer to mask source image
|
||
// todo find a way to mask "hard" when stretching, now some soft remains are visible
|
||
LayRect := CGRectMake(0, 0, SrcWidth, SrcHeight);
|
||
Layer := CGLayerCreateWithContext(SrcDC.CGContext, LayRect.size, nil);
|
||
|
||
// the sub-image is out of edges
|
||
if (CGImageGetWidth(Image){%H-}<>SrcWidth) or (CGImageGetHeight(Image){%H-}<>SrcHeight) then
|
||
begin
|
||
with ImgRect do
|
||
if XSrc<0 then origin.x:=SrcWidth-CGImageGetWidth(Image) else origin.x:=0;
|
||
with ImgRect do
|
||
if YSrc<0 then origin.y:=0 else origin.y:=SrcHeight-CGImageGetHeight(Image);
|
||
|
||
ImgRect.size.width:=CGImageGetWidth(Image);
|
||
ImgRect.size.height:=CGImageGetHeight(Image);
|
||
end
|
||
else
|
||
ImgRect:=LayRect;
|
||
|
||
try
|
||
LayerContext := CGLayerGetContext(Layer);
|
||
CGContextScaleCTM(LayerContext, 1, -1);
|
||
CGContextTranslateCTM(LayerContext, 0, -SrcHeight);
|
||
|
||
SetCGFillping(LayerContext, Width, Height);
|
||
if Assigned(MskImage) then CGContextClipToMask(LayerContext, ImgRect, MskImage);
|
||
CGContextDrawImage(LayerContext, ImgRect, Image);
|
||
|
||
CGContextDrawLayerInRect(CGContext, DstRect, Layer);
|
||
|
||
Result := True;
|
||
finally
|
||
CGLayerRelease(Layer);
|
||
end;
|
||
end;
|
||
|
||
finally
|
||
if SubImage then CGImageRelease(Image);
|
||
if SubMask then CGImageRelease(MskImage);
|
||
end;
|
||
|
||
//DebugLn('StretchMaskBlt succeeds: ', Format('Dest %d Src %d X %d Y %d',
|
||
// [Integer(CGContext),
|
||
// Integer(Image),
|
||
// X, Y]));
|
||
end;
|
||
|
||
procedure TCarbonDeviceContext.ClearClipping;
|
||
var
|
||
Trans: CGAffineTransform;
|
||
begin
|
||
if isClipped then
|
||
begin
|
||
Trans := CGContextGetCTM(CGContext);
|
||
CGContextRestoreGState(CGContext);
|
||
ApplyTransform(Trans);
|
||
end;
|
||
end;
|
||
|
||
function TCarbonDeviceContext.SetClipRegion(AClipRegion: TCarbonRegion; Mode: Integer): Integer;
|
||
begin
|
||
ClearClipping;
|
||
isClipped := False;
|
||
|
||
if not Assigned(AClipRegion) then
|
||
HIShapeSetEmpty(FClipRegion.Shape)
|
||
else
|
||
begin
|
||
CGContextSaveGState(CGContext);
|
||
FClipRegion.CombineWith(AClipRegion, Mode);
|
||
FClipRegion.Apply(Self);
|
||
isClipped := true;
|
||
end;
|
||
Result := FClipRegion.GetType;
|
||
end;
|
||
|
||
function TCarbonDeviceContext.CopyClipRegion(ADstRegion: TCarbonRegion): Integer;
|
||
begin
|
||
if Assigned(ADstRegion)
|
||
then Result := ADstRegion.CombineWith(FClipRegion, RGN_COPY)
|
||
else Result := LCLType.Error;
|
||
end;
|
||
|
||
procedure GetWindowViewTranslate(const AWindowOfs, AViewOfs: TPoint; out dx, dy: Integer); inline;
|
||
begin
|
||
dx := AViewOfs.x - AWindowOfs.x;
|
||
dy := AViewOfs.y - AWindowOfs.y;
|
||
end;
|
||
|
||
function isSamePoint(const p1, p2: TPoint): Boolean;
|
||
begin
|
||
Result:=(p1.x=p2.x) and (p1.y=p2.y);
|
||
end;
|
||
|
||
procedure TCarbonDeviceContext.UpdateContextOfs(const AWindowOfs, AViewOfs: TPoint);
|
||
var
|
||
dx, dy: Integer;
|
||
begin
|
||
if isSamePoint(AWindowOfs, fWindowOfs) and isSamePoint(AViewOfs, fViewPortOfs) then Exit;
|
||
GetWindowViewTranslate(FWindowOfs, FViewPortOfs, dx{%H-}, dy{%H-});
|
||
CGContextTranslateCTM(CGContext, -dx, -dy);
|
||
|
||
FWindowOfs := AWindowOfs;
|
||
FViewPortOfs := AViewOfs;
|
||
GetWindowViewTranslate(FWindowOfs, FViewPortOfs, dx, dy);
|
||
CGContextTranslateCTM(CGContext, dx, dy);
|
||
end;
|
||
|
||
procedure TCarbonDeviceContext.SetWindowOfs(const AWindowOfs: TPoint);
|
||
begin
|
||
UpdateContextOfs(AWindowOfs, ViewPortOfs);
|
||
end;
|
||
|
||
procedure TCarbonDeviceContext.SetViewPortOfs(const AViewOfs: TPoint);
|
||
begin
|
||
UpdateContextOfs(WindowOfs, AViewOfs);
|
||
end;
|
||
|
||
function TCarbonDeviceContext.GetLogicalOffset: TPoint;
|
||
begin
|
||
GetWindowViewTranslate(WindowOfs, ViewportOfs, Result.X, Result.Y);
|
||
end;
|
||
|
||
{ TCarbonScreenContext }
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonScreenContext.GetSize
|
||
Returns: Size of screen context
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonScreenContext.GetSize: TPoint;
|
||
begin
|
||
Result.X := CGDisplayPixelsWide(CGMainDisplayID);
|
||
Result.Y := CGDisplayPixelsHigh(CGMainDisplayID);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonScreenContext.Create
|
||
|
||
Creates new screen context
|
||
------------------------------------------------------------------------------}
|
||
constructor TCarbonScreenContext.Create;
|
||
begin
|
||
inherited Create;
|
||
Reset;
|
||
end;
|
||
|
||
{ TCarbonControlContext }
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonControlContext.GetSize
|
||
Returns: Size of control context
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonControlContext.GetSize: TPoint;
|
||
var
|
||
R: TRect;
|
||
begin
|
||
FOwner.GetClientRect(R{%H-});
|
||
Result.X := (R.Right - R.Left);
|
||
Result.Y := (R.Bottom - R.Top);
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonControlContext.Create
|
||
Params: AOwner - Context widget
|
||
|
||
Creates new control context
|
||
------------------------------------------------------------------------------}
|
||
constructor TCarbonControlContext.Create(AOwner: TCarbonWidget);
|
||
begin
|
||
inherited Create;
|
||
|
||
FOwner := AOwner;
|
||
Reset;
|
||
end;
|
||
|
||
{ TCarbonBitmapContext }
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonBitmapContext.SetBitmap
|
||
Params: AValue - New bitmap
|
||
|
||
Sets the bitmap
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonBitmapContext.SetBitmap(const AValue: TCarbonBitmap);
|
||
begin
|
||
if AValue = nil then
|
||
begin
|
||
DebugLn('TCarbonBitmapContext.SetBitmap Error - Value is nil!');
|
||
Exit;
|
||
end;
|
||
|
||
if FBitmap <> AValue then
|
||
begin
|
||
FBitmap := AValue;
|
||
|
||
Reset;
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonBitmapContext.GetSize
|
||
Returns: Size of bitmap context
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonBitmapContext.GetSize: TPoint;
|
||
begin
|
||
if FBitmap <> nil then
|
||
begin
|
||
Result.X := FBitmap.Width;
|
||
Result.Y := FBitmap.Height;
|
||
end
|
||
else
|
||
begin
|
||
Result.X := 0;
|
||
Result.Y := 0;
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonBitmapContext.Create
|
||
|
||
Creates new bitmap context
|
||
------------------------------------------------------------------------------}
|
||
constructor TCarbonBitmapContext.Create;
|
||
begin
|
||
inherited Create;
|
||
FBitmap := DefaultBitmap;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonBitmapContext.Destroy
|
||
|
||
Frees bitmap context
|
||
------------------------------------------------------------------------------}
|
||
destructor TCarbonBitmapContext.Destroy;
|
||
begin
|
||
if CGContext <> nil then CGContextRelease(CGContext);
|
||
|
||
inherited Destroy;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonBitmapContext.Reset
|
||
|
||
Resets the bitmap context properties to defaults (pen, brush, ...)
|
||
------------------------------------------------------------------------------}
|
||
procedure TCarbonBitmapContext.Reset;
|
||
var
|
||
Info: CGBitmapInfo;
|
||
begin
|
||
if CGContext <> nil then
|
||
begin
|
||
CGContextRelease(CGContext);
|
||
CGContext := nil;
|
||
end;
|
||
|
||
|
||
if FBitmap <> nil then
|
||
begin
|
||
{$note TODO: convert data if image format is incomatible with context}
|
||
// MWE:
|
||
// A CGContext only supports a few formats of all the available image formats.
|
||
// When a format doesn't match we should convert the image data to the closest
|
||
// format supported. In order to do so, we should create the context and not
|
||
// the bitmap, since if a conversion is needed, we need to manage our own data.
|
||
// See QA1037 (for all)
|
||
//
|
||
// supported formats might use (there are more)
|
||
// Gray 8 kCGImageAlphaNone WWWWWWWW
|
||
// RGB 5 kCGImageAlphaNoneSkipFirst -RRRRRGGGGGBBBBB
|
||
// RGB 8 kCGImageAlphaNoneSkipFirst --------RRRRRRRRRGGGGGGGGBBBBBBBB
|
||
// RGB 8 kCGImageAlphaNoneSkipLast RRRRRRRRRGGGGGGGGBBBBBBBB--------
|
||
// RGB 8 kCGImageAlphaPremultipliedFirst AAAAAAAARRRRRRRRRGGGGGGGGBBBBBBBB
|
||
// RGB 8 kCGImageAlphaPremultipliedLast RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA
|
||
|
||
// create CGBitmapContext
|
||
|
||
Info := FBitmap.Info;
|
||
// convert kCGImageAlphaFirst -> kCGImageAlphaPremultipliedFirst
|
||
if (Info and kCGImageAlphaFirst > 0) then
|
||
Info := (Info and (not kCGImageAlphaFirst)) or kCGImageAlphaPremultipliedFirst;
|
||
|
||
CGContext := CGBitmapContextCreate(FBitmap.Data, FBitmap.Width, FBitmap.Height,
|
||
FBitmap.BitsPerComponent, FBitmap.BytesPerRow, FBitmap.ColorSpace,
|
||
Info);
|
||
|
||
if CGContext = nil then
|
||
DebugLn('Unable to create Canvas Handle for Bitmap. Format "', DbgS(Info), '" is not supported!');
|
||
|
||
// flip and offset CTM to upper left corner
|
||
CGContextTranslateCTM(CGContext, 0, FBitmap.Height);
|
||
CGContextScaleCTM(CGContext, 1, -1);
|
||
end;
|
||
|
||
inherited Reset;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonBitmapContext.GetPixel
|
||
Params: X, Y - Coordinates of pixel
|
||
Returns: Specified pixel color
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonBitmapContext.GetPixel(X, Y: Integer): TGraphicsColor;
|
||
var
|
||
S: TPoint;
|
||
R: TRect;
|
||
RawImage: TRawImage;
|
||
IntfImage: TLazIntfImage;
|
||
begin
|
||
Result := clNone;
|
||
|
||
S := GetSize;
|
||
if (X < 0) or (Y < 0) or (X > S.X - 1) or (Y > S.Y - 1) then Exit;
|
||
|
||
R := Classes.Rect(X, Y, 1, 1);
|
||
if not RawImage_FromBitmap(RawImage, HBITMAP(Bitmap), 0, @R) then Exit;
|
||
IntfImage := TLazIntfImage.Create(RawImage, True);
|
||
try
|
||
Result := IntfImage.TColors[X, Y];
|
||
finally
|
||
IntfImage.Free;
|
||
end;
|
||
end;
|
||
|
||
{------------------------------------------------------------------------------
|
||
Method: TCarbonBitmapContext.GetBitmap
|
||
Returns: The bitmap of bitmap context
|
||
------------------------------------------------------------------------------}
|
||
function TCarbonBitmapContext.GetBitmap: TCarbonBitmap;
|
||
begin
|
||
if FBitmap = nil then Result := nil
|
||
else
|
||
begin
|
||
{$note TODO: convert data if image format is incomatible with context}
|
||
// See also comments in Reset.
|
||
// Before we update the bitmap, if needed, first the context data need to be
|
||
// converted
|
||
|
||
// update bitmap to reflect changes made via canvas
|
||
FBitmap.UpdateImage;
|
||
Result := FBitmap;
|
||
end;
|
||
end;
|
||
|
||
end.
|