{*************************************************************************}
{                                                                         }
{ written by TMS Software                                                 }
{           copyright © 2013 - 2014                                       }
{           Email : info@tmssoftware.com                                  }
{           Web : http://www.tmssoftware.com                              }
{                                                                         }
{ The source code is given as is. The author is not responsible           }
{ for any possible damage done due to the use of this code.               }
{ The component can be freely used in any application. The complete       }
{ source code remains property of the author and may not be distributed,  }
{ published, given or sold in any form as such. No parts of the source    }
{ code can be included in any other component or application without      }
{ written authorization of the author.                                    }
{*************************************************************************}

unit FMX.TMSHotSpotImage;

interface

{$I TMSDEFS.INC}
{$IFDEF MACOS}
{$DEFINE PATCHTEXTURE}
{$ENDIF}
{$IFDEF IOS}
{$DEFINE PATCHTEXTURE}
{$ENDIF}
{$IFDEF ANDROID}
{$DEFINE PATCHTEXTURE}
{$ENDIF}

uses
  Types, SysUtils, Classes, UITypes, FMX.TMSBitmap, FMX.PlatForm,
  FMX.Types, FMX.Objects, FMX.Controls, FMX.TMSBitmapContainer, Math, FMX.Dialogs
  , UIConsts, FMX.Styles, FMX.TMSBaseControl
  {$IFDEF DELPHIXE5_LVL}
  , FMX.Graphics
  {$ENDIF}
  {$IFDEF DELPHIXE6_LVL}
  , System.Math.Vectors
  {$ENDIF}
  ;

const
  CornerSize = 2;
  EllipseTolerance = 0.15;

  MAJ_VER = 1; // Major version nr.
  MIN_VER = 5; // Minor version nr.
  REL_VER = 0; // Release nr.
  BLD_VER = 1; // Build nr.

  // version history
  // v1.0.0.0 : First release
  // v1.0.0.1 : Improved: Performance for handling hover & click of hotspots
  // v1.5.0.0 : New : support for XE4
  // v1.5.0.1 : Fixed : Issue with using semi opaque hover, selected, down color

type

  TTMSFMXHotSpotImage = class;

  TLineSelected     = (lsNotSelected, lsPoint1, lsPoint2, lsLine);

  // TLineOrientation is used as part of the heuristic algorithm that decides
  // if a line is selected and if lines intersect}
  TLineOrientation  = (loPoint, loHorizontal, loVertical);

  THoverPosition = (hpNone, hpInside, hpBorder);

  TSpotShapeType = (stRectangle, stEllipse, stPolygon);

  TBorderPoly = (bNone,bInside,bPoint,bLine);

  TSelectionMode = (smSingleSelect, smMultiSelectCtrl, smMultiSelect);

  TPoints = TPolygon;
  TIntPoints = TPolygon;
  TTMSFMXHotSpots = class;

  TTMSFMXHotSpotShape = class(TPersistent)
  private
    FTop: Integer;
    FLeft: Integer;
    FHeight: Integer;
    FWidth: Integer;
    FAngle: Integer;
    FShapeType: TSpotShapeType;
    FPoints: TPoints;
    FEx1,FEx2,FEy1,FEy2: Real;
    function EllipsePos(X, Y: Integer): THoverPosition;
    function PolyPos(X, Y: Integer): THoverPosition;
    procedure SetPoints(const Value: TPoints);
    procedure SetAngle(const Value: Integer);
  public
    procedure calcMargins;
    procedure FillEllipse(Canvas: TCanvas;Pts:TIntPoints);
    function BorderPolypos(X,Y:Integer;var p1,p2:integer):TBorderPoly;
    procedure EllipseToBezier;
    constructor Create(Shape: TSpotShapeType);
    destructor Destroy;override;
    function RectPos(X, Y: Integer): THoverPosition;
    function GetHoverPos(X, Y: Integer): THoverPosition;
    procedure Draw(Canvas :TCanvas);
    procedure DrawAt(Canvas :TCanvas; X,Y: Integer);
    property Top: Integer read FTop write FTop;
    property Left: Integer read FLeft write FLeft;
    property Height: Integer read FHeight write FHeight;
    property Width: Integer read FWidth write FWidth;
    property ShapeType: TSpotShapeType read FShapeType write FShapeType;
    property Points: TPoints read FPoints write SetPoints;
    property Angle: Integer read FAngle write SetAngle;
  end;


  TPersistentBrushObject = class(TComponent)
  private
    FBrush: TBrush;
  published
    property Brush: TBrush read FBrush write FBrush;
  end;

  TTMSFMXHotSpot = class(TCollectionItem)
  private
    FHoverPicture: TBitmap;
    FClickPicture: TBitmap;
    FPicture: TBitmap;
    FCClick : TBitmap;
    FCHover : TBitmap;
    FCSelect: TBitmap;
    FCBlink: TBitmap;
    FShape: TTMSFMXHotSpotShape;
    FHint: String;
    FName: String;
    FID: Integer;
    FClipped: Boolean;
    FOwner: TTMSFMXHotSpots;
    FDown: Boolean;
    FShowClick: Boolean;
    FBlink: Boolean;
    FSelected: Boolean;
    FBlinkImageName: String;
    FSelectedImageName: String;
    FClickImageName: String;
    FHoverImageName: String;
    FSelectedImage: TBitmap;
    FBlinkImage: TBitmap;
    FOffsetY: Integer;
    FOffsetX: Integer;
    FSelectable: Boolean;
    FHoverStroke: TBrush;
    FBlinkFill: TBrush;
    FClickFill: TBrush;
    FBlinkStroke: TBrush;
    FSelectedFill: TBrush;
    FClickStroke: TBrush;
    FSelectedStroke: TBrush;
    FHoverFill: TBrush;
    procedure CalcClip(const Pict: TBitmap;var Bitm: TBitmap; ImageName: String; AFill, AStroke: TBrush);
    procedure SetShapeType(const Value: TSpotShapeType);
    procedure SetClickPicture(const Value: TBitmap);
    procedure SetHoverPicture(const Value: TBitmap);
    function GetWidth: Integer;
    function GetHeight: Integer;
    function GetX: Integer;
    function GetY: Integer;
    function GetX2: Integer;
    function GetY2: Integer;
    function GetShapeType: TSpotShapeType;
    procedure SetWidth(const Value: Integer);
    procedure SetHeight(const Value: Integer);
    procedure SetX(const Value: Integer);
    procedure SetY(const Value: Integer);
    procedure SetX2(const Value: Integer);
    procedure SetY2(const Value: Integer);
    procedure setPolyPoints(const Value:TPoints);
    function getPolyPoints:TPoints;
    procedure setPolyPoint(i:Integer;const Value:TPointF);
    function getPolyPoint(i:Integer):TPointF;
    function GetAngle: Integer;
    procedure SetAngle(const Value: Integer);
    procedure SetClipped(const Value: Boolean);
    procedure SetDown(const Value: Boolean);
    procedure SetShowClick(const Value: Boolean);
    procedure SetBlink(const Value: Boolean);
    procedure SetBlinkImage(const Value: TBitmap);
    procedure SetSelected(const Value: Boolean);
    procedure SetSelectedImage(const Value: TBitmap);
    procedure SetSelectedImageName(const Value: String);
    procedure SetOffsetX(const Value: Integer);
    procedure SetOffsetY(const Value: Integer);
    procedure UpdateExclusive;
    procedure SetSelectable(const Value: Boolean);
    procedure SetBlinkFill(const Value: TBrush);
    procedure SetBlinkStroke(const Value: TBrush);
    procedure SetClickFill(const Value: TBrush);
    procedure SetClickStroke(const Value: TBrush);
    procedure SetHoverFill(const Value: TBrush);
    procedure SetHoverStroke(const Value: TBrush);
    procedure SetSelectedFill(const Value: TBrush);
    procedure SetSelectedStroke(const Value: TBrush);
    function GetBitmapContainer: TTMSFMXBitmapContainer;
    function GetSelectable: Boolean;
  protected
    function GetDisplayName: String; override;
    procedure DefineProperties(Filer: TFiler);override;
    procedure StorePoints(Writer: TWriter);
    procedure LoadPoints(Reader: TReader);
    procedure HoverPictureChange(Sender: TObject);
    procedure ClickPictureChange(Sender: TObject);
    procedure SelectedImageChange(Sender: TObject);
    procedure BlinkImageChange(Sender: TObject);
    procedure InternalClick;
    procedure ReCalcClips;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure ScaleEllipse(p1,p2,ps:TPoint);
    procedure DrawShape(Canvas: TCanvas);
    procedure DrawShapeAt(Canvas: TCanvas; X,Y: Integer);
    function AsRect: TRect;
    function GetHoverPos(X, Y: Integer): THoverPosition;
    function GetRectHoverPos(X, Y: Integer): THoverPosition;
    function GetBorderPolypos(X,Y:Integer;var p1,p2:integer):TBorderPoly;
    procedure Assign(Source: TPersistent); override;
    procedure SetRect(AX1, AY1, AX2, AY2: Integer); overload;
    procedure SetRect(R: TRect); overload;
    procedure SaveToStream(S: TStream);
    procedure LoadFromStream(S: TStream; DoVCL: Boolean);

    property X1: Integer read GetX write SetX;
    property Y1: Integer read GetY write SetY;
    property X2: Integer read GetX2 write SetX2;
    property Y2: Integer read GetY2 write SetY2;
    property PolyPoints: TPoints read getPolyPoints write setPolyPoints;
    property PolyPoint[i:Integer]: TPointF read getPolyPoint write setPolyPoint;
    property BitmapContainer: TTMSFMXBitmapContainer read GetBitmapContainer;
  published
    property ShapeType: TSpotShapeType read GetShapeType write SetShapeType;
    property Hint: String read FHint write FHint;
    property Name: String read FName write FName;
    property ID: integer read FID write FID;
    property X: Integer read GetX write SetX;
    property Y: Integer read GetY write SetY;
    property Width: Integer read GetWidth write SetWidth;
    property Height: Integer read GetHeight write SetHeight;
    property HoverImage: TBitmap read FHoverPicture write SetHoverPicture;
    property ClickImage: TBitmap read FClickPicture write SetClickPicture;
    property HoverClippedBitmap: TBitmap read FCHover;
    property ClickClippedBitmap: TBitmap read FCClick;
    property SelectClippedBitmap: TBitmap read FCSelect;
    property BlinkClippedBitmap: TBitmap read FCBlink;
    property Angle: Integer read GetAngle write SetAngle stored False;
    property Clipped : Boolean read FClipped write SetClipped;
    property Down: Boolean read FDown write SetDown;
    property ShowClick: Boolean read FShowClick write SetShowClick default false;

    property HoverFill: TBrush read FHoverFill write SetHoverFill;
    property HoverStroke: TBrush read FHoverStroke write SetHoverStroke;

    property ClickFill: TBrush read FClickFill write SetClickFill;
    property ClickStroke: TBrush read FClickStroke write SetClickStroke;

    property BlinkFill: TBrush read FBlinkFill write SetBlinkFill;
    property BlinkStroke: TBrush read FBlinkStroke write SetBlinkStroke;

    property SelectedFill: TBrush read FSelectedFill write SetSelectedFill;
    property SelectedStroke: TBrush read FSelectedStroke write SetSelectedStroke;


    property HoverImageName: String read FHoverImageName write FHoverImageName;
    property ClickImageName: String read FClickImageName write FClickImageName;
    property SelectedImageName: String read FSelectedImageName write SetSelectedImageName;
    property BlinkImageName: String read FBlinkImageName write FBlinkImageName;

    property Selectable: Boolean read GetSelectable write SetSelectable default True;
    property SelectedImage: TBitmap read FSelectedImage write SetSelectedImage;
    property BlinkImage: TBitmap read FBlinkImage write SetBlinkImage;
    property Blink: Boolean read FBlink write SetBlink default false;
    property Selected: Boolean read FSelected write SetSelected default false;
    property OffsetX: Integer read FOffsetX write SetOffsetX default 4;
    property OffsetY: Integer read FOffsetY write SetOffsetY default 4;
  end;

  TTMSFMXHotSpots = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXHotSpotImage;
    oldHeight, oldWidth: Single;
    function GetItem(Index: Integer): TTMSFMXHotSpot;
    procedure SetItem(Index: Integer; const Value: TTMSFMXHotSpot);
  protected
    function GeTTMSFMXHotSpotImage: TTMSFMXHotSpotImage;
  public
    procedure EndUpdate; override;
    procedure Assign(Source: TPersistent); override;
    constructor Create(AOwner: TTMSFMXHotSpotImage);
    procedure SetDimensions(W,H: integer);
    procedure ReCalclips;
    function Add: TTMSFMXHotSpot;
    procedure SaveToStream(S: TStream);
    procedure LoadFromStream(S: TStream);
    procedure SaveToFile(FName: TFileName);
    procedure LoadFromFile(FName: TFileName);
    procedure ReScale(newWidth, newHeight: Single); overload;
    procedure ReScale(delta: Integer); overload;
    function GetOwner: TPersistent; override;
    property Items[Index: Integer]: TTMSFMXHotSpot read GetItem write SetItem; default;
  end;

  THotSpotEvent = procedure(Sender: TObject; HotSpot: TTMSFMXHotSpot) of object;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXHotSpotImage = class(TTMSFMXCustomBitmap)
  private
    FOwner: TControl;
    FHotSpots: TTMSFMXHotSpots;
    FHoveredItem: Integer;
    FClickedItem: Integer;
    FColor: TAlphaColor;
    isMouseUp: Boolean;
    isDblClick: Boolean;
    FOnHotSpotExit: THotSpotEvent;
    FOnHotSpotEnter: THotSpotEvent;
    FOnHotSpotClick: THotSpotEvent;
    FOnHotSpotRightClick: THotSpotEvent;
    FOnHotSpotDblClick: THotSpotEvent;
    FTransparent: Boolean;
    FAutoSize: Boolean;
    FStretch: Boolean;
    FHotSpotCursor: TCursor;
    FOrigCursor: TCursor;
    FOnPaint: TOnPaintEvent;
    FSelectionMode: TSelectionMode;
    FTimer: TTimer;
    FBlinkInterval: Integer;
    FBlinkCount: Integer;
    FPainting: Boolean;
    FAngle: Integer;
    FRotating: Boolean;
    procedure OnTimerTime(Sender: TObject);
    procedure SetHotSpots(const Value: TTMSFMXHotSpots);
    procedure SetPicture(const Value: TBitmap);
    procedure SetHoveredItem(const Value: Integer);
    procedure SetColor(const Value: TAlphaColor);
    procedure SetTransparent(const Value: Boolean);
    procedure SetAutoSizeEx(const Value: Boolean);
    function  GetHotSpotXY(x, y: Integer): TTMSFMXHotSpot;
    procedure SetStretch(const Value: Boolean);
    procedure SetBlinkInterval(const Value: Integer);
    procedure UpdateHotSpotsBlinking;
    function GetSelectedIndex: Integer;
    function GetVersion: string;
  protected
    procedure DrawImage(HotSpotIndex: Integer; AImage, ACachedImage: TBitmap; AStroke: TBrush);
    procedure RestoreHotSpot(HotSpotIndex: Integer);
    procedure MouseMove(Shift: TShiftState; X, Y: Single); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
    procedure DblClick; override;
    procedure PictureChanged(Sender: TObject);
    procedure Resize; override;
    procedure UpdateControlCanvas;
    property Transparent: Boolean read FTransparent write SetTransparent;
  public
    procedure Paint; override;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Loaded; override;
    property Canvas;
    property HoveredItem: Integer read FHoveredItem write SetHoveredItem;
    property HotSpotXY[x,y: Integer]: TTMSFMXHotSpot read GetHotSpotXY;
    property SelectedIndex: Integer read GetSelectedIndex;
    procedure LoadFromFile(FName: TFileName);
    procedure SaveToFile(FName: TFileName);
    procedure UpdateHotSpots;
  published
    property Bitmap;
    property BitmapContainer;
    property Version: string read GetVersion;
    property AutoSize: Boolean read FAutoSize write SetAutoSizeEx;
    property BlinkInterval: Integer read FBlinkInterval write SetBlinkInterval default 100;
    property Color: TAlphaColor read FColor write SetColor;
    property HotSpotCursor: TCursor read FHotSpotCursor write FHotSpotCursor;
    property HotSpots: TTMSFMXHotSpots read FHotSpots write SetHotSpots;
    property SelectionMode: TSelectionMode read FSelectionMode write FSelectionMode default smSingleSelect;
    property Stretch: Boolean read FStretch write SetStretch;
    property OnHotSpotEnter: THotSpotEvent read FOnHotSpotEnter write FOnHotSpotEnter;
    property OnHotSpotExit: THotSpotEvent read FOnHotSpotExit write FOnHotSpotExit;
    property OnHotSpotClick: THotSpotEvent read FOnHotSpotClick write FOnHotSpotClick;
    property OnHotSpotRightClick: THotSpotEvent read FOnHotSpotRightClick write FOnHotSpotRightClick;
    property OnHotSpotDblClick: THotSpotEvent read FOnHotSpotDblClick write FOnHotSpotDblClick;
    property OnPaint: TOnPaintEvent read FOnPaint write FOnPaint;
  end;

function ReadInteger(S: TStream): Integer;
function ReadCardinal(S: TStream): Cardinal;
function ReadString(S: TStream): string;
procedure WriteString(S: TStream; Buffer: string);
procedure WriteInteger(S: TStream; Buffer: Integer);
procedure WriteCardinal(S: TStream; Buffer: Cardinal);

function Between(Value, Min, Max: Integer): Boolean;overload;
function Between(Value, Min, Max: Real): Boolean;overload;

function Intersect(p1, p2, p3, p4:TPointF): Boolean;
function CCW(p0, p1, p2:TPointF): Integer;
function RPoint(x,y:Real): TPointF;
function EmptyImage(Image: TBitmap): Boolean;

function SubtractPoints(const PointA, PointB:  TPointF):  TPointF;
function AddPoints(const PointA, PointB:  TPointF):  TPointF;

implementation

const
  EToBConst:Real = 0.2761423749154;

function AddPoints(const PointA, PointB:  TPointF):  TPointF;
begin
  with Result do
  begin
    X := PointA.X + PointB.X;
    Y := PointA.Y + PointB.Y
  end
end {AddPoints};

function SubtractPoints(const PointA, PointB:  TPointF):  TPointF;
begin
  with Result DO
  begin
    X := PointA.X - PointB.X;
    Y := PointA.Y - PointB.Y
  end
end {SubtractPoints};

procedure CalcLineParameters (const PointA, PointB  :  TPointF;
                              var   Slope, Intercept:  Double;
                              var   LineOrientation :  TLineOrientation);
var
  Delta:  TPointF;
begin
  Delta := SubtractPoints(PointB, PointA);

  if  (Delta.X = 0) AND (Delta.Y = 0) then
  begin
    // This special case should never happen if iMinPixels > 0
    LineOrientation := loPoint;
    Slope     := 0.0;
    Intercept := 0.0
  end
  else
  begin
    if   ABS(Delta.X) >= ABS(Delta.Y) then
    begin
      // The line is more horizontal than vertical.  Determine values FOR
      // equation:  Y = slope*X + intercept
      LineOrientation := loHorizontal;
      try
        Slope := Delta.Y / Delta.X   {conventional slope in geometry}
      except
        Slope := 0.0
      end;
      Intercept := PointA.Y - PointA.X*Slope
    end
    else
    begin
      // The line is more vertical than horizontal.  Determine values FOR
      // equation:  X = slope*Y + intercept
      LineOrientation := loVertical;
      try
        Slope := Delta.X / Delta.Y  {reciprocal of conventional slope}
      except
        Slope := 0.0
      end;
      Intercept := PointA.X - PointA.Y*Slope;
    end
  end
end {CalcLineParameters};


// Determine if Target1 is "near" line segment between Point1 and Point2
function NearLine(const Target, Point1, Point2:  TPointF):  Boolean;
const
  LineSelectFuzz =  4;  // Pixel "fuzz" used in line selection
var
  Intercept      :  DOUBLE;
  LineOrientation:  TLineOrientation;
  maxX           :  Integer;
  maxY           :  Integer;
  minX           :  Integer;
  minY           :  Integer;
  Slope          :  DOUBLE;
  xCalculated    :  Integer;
  yCalculated    :  Integer;
begin
  Result := False;

  // if an endpoint is not selected, was part of line selected?
  CalcLineParameters (Point1, Point2, Slope, Intercept, LineOrientation);

  case LineOrientation OF
  loHorizontal:
    begin
      minX := MinIntValue([Round(Point1.X), Round(Point2.X)]);
      maxX := MaxIntValue([Round(Point1.X), Round(Point2.X)]);
      // first check if selection within horizontal range of line
      if (Target.X >= minX) and (Target.X <= maxX) then
      begin
        // Since X is within range of line, now see if Y value is close
        // enough to the calculated Y value FOR the line to be selected.
        yCalculated := ROUND( Slope*Target.X + Intercept );
        if ABS(yCalculated - Target.Y) <= LineSelectFuzz then
          Result := TRUE
      end
    end;
  loVertical:
    begin
      minY := MinIntValue([Round(Point1.Y), Round(Point2.Y)]);
      maxY := MaxIntValue([Round(Point1.Y), Round(Point2.Y)]);
      // first check if selection within vertical range of line
      if   (Target.Y >= minY) AND (Target.Y <= maxY) then
      begin
        // Since Y is within range of line, now see if X value is close
        // enough to the calculated X value FOR the line to be selected.
        xCalculated := ROUND( Slope*Target.Y + Intercept );
        if ABS(xCalculated - Target.X) <= LineSelectFuzz then
          Result := TRUE
      end
    end;
  loPoint:
      // do nothing -- should not occur
  end
end {NearLine};

function SquareContainsPoint (const SquareCenter   :  TPoint;
                              const SquareHalfSize :  Integer; {pixels}
                              const TestPoint      :  TPoint):  Boolean;
begin
  Result := (TestPoint.X >= SquareCenter.X - SquareHalfSize) AND
            (TestPoint.X <= SquareCenter.X + SquareHalfSize) AND
            (TestPoint.Y >= SquareCenter.Y - SquareHalfSize) AND
            (TestPoint.Y <= SquareCenter.Y + SquareHalfSize)
end {SquareContainsPoint};

function Vektor(FromP, Top: TPoint): TPoint;
begin
  Result.x := Top.x - FromP.x;
  Result.y := Top.y - FromP.y;
end;

function xComp(Vektor: TPoint; Angle: Extended): Integer;
begin
  Result := Round(Vektor.x * cos(Angle) - (Vektor.y) * sin(Angle));
end;

function yComp(Vektor: TPoint; Angle: Extended): Integer;
begin
  Result := Round((Vektor.x) * (sin(Angle)) + (vektor.y) * cos(Angle));
end;

function EmptyImage(Image: TBitmap): Boolean;
begin
  Result := (Image.Width = 0) and (Image.Height = 0);
end;


function RPoint(x,y:Real):TPointF;
begin
  Result.x := x;
  Result.y := y;
end;

function Between(Value, Min, Max: Integer): Boolean;
begin
  Result := (Value >= Min) and (Value <=  Max);
end;

function EmptyBitmap(bmp: TBitmap): Boolean;
begin
  Result := (bmp.Width = 0) and (bmp.Height = 0);
end;

function Between(Value, Min, Max: Real): Boolean;
begin
  Result := (Value >= Min) and (Value <=  Max);
end;

procedure WriteCardinal(S: TStream; Buffer: Cardinal);
begin
  S.Write(Buffer, SizeOf(Cardinal));
end;

procedure WriteInteger(S: TStream; Buffer: Integer);
begin
  S.Write(Buffer, SizeOf(Integer));
end;

procedure WriteString(S: TStream; Buffer: string);
var
  b: Byte;
  {$IFDEF DELPHI_LLVM}
  ABuffer: String;
  {$ELSE}
  ABuffer: AnsiString;
  {$ENDIF}
begin
  {$IFDEF DELPHI_LLVM}
  ABuffer := String(Buffer);
  {$ELSE}
  ABuffer := Ansistring(Buffer);
  {$ENDIF}

  b := Length(ABuffer);
  s.WriteBuffer(b,1);
  S.Write(ABuffer[1], b);
end;

function ReadString(S: TStream): string;
var
  {$IFDEF DELPHI_LLVM}
  Buffer: array[0..255] of Char;
  {$ELSE}
  Buffer: array[0..255] of AnsiChar;
  {$ENDIF}
  b: byte;
begin
  S.ReadBuffer(b,1);
  S.ReadBuffer(Buffer,b);
  Buffer[b] := #0;
  Result := string(Buffer);
end;

function ReadInteger(S: TStream): Integer;
begin
  S.Read(Result, SizeOf(Integer));
end;

function ReadCardinal(S: TStream): Cardinal;
begin
  S.Read(Result, SizeOf(Cardinal));
end;

function Intersect(p1, p2, p3, p4:TPointF):boolean;
begin
   Result:=((( CCW(p1, p2, p3) * CCW(p1, p2, p4)) <= 0)
        and (( CCW(p3, p4, p1) * CCW(p3, p4, p2)  <= 0) )) ;
end;

function CCW(p0, p1, p2:TPointF):Integer;
var
  dx1, dx2:Single;
  dy1, dy2:Single;
begin
  dx1 := p1.x - p0.x ; dx2 := p2.x - p0.x ;
  dy1 := p1.y - p0.y ; dy2 := p2.y - p0.y ;

  if (dx1*dy2) > (dy1*dx2) then
    Result := 1
  else
    Result := -1;
end;

{ TTMSFMXHotSpotShape }

constructor TTMSFMXHotSpotShape.Create(Shape: TSpotShapeType);
begin
  self.FShapeType := Shape;

  FWidth := 32;
  FHeight := 32;
end;

destructor TTMSFMXHotSpotShape.Destroy;
begin
  inherited;
end;

procedure TTMSFMXHotSpotShape.CalcMargins;
var
  i,minx,miny,maxx,maxy: Integer;
begin
  if Length(FPoints) < 3 then
    Exit;

  minx := Round(FPoints[0].x);
  maxx := Round(FPoints[0].x);
  miny := Round(FPoints[0].y);
  maxy := Round(FPoints[0].y);
  FEx1 := (FPoints[0].x);
  FEx2 := (FPoints[0].x);
  FEy1 := (FPoints[0].y);
  FEy2 := (FPoints[0].y);
  for i := 1 to High(FPoints) do
  begin
    if ((i mod 3) = 0) then
    begin
      if FPoints[i].x < FEx1 then FEx1 := FPoints[i].x;
      if FPoints[i].x > FEx2 then FEx2 := FPoints[i].x;
      if FPoints[i].y > FEy2 then FEy2 := FPoints[i].y;
      if FPoints[i].y < FEy1 then FEy1 := FPoints[i].y;
    end;
    if FPoints[i].x < minx then minx := Round(FPoints[i].x);
    if FPoints[i].x > maxx then maxx := Round(FPoints[i].x);
    if FPoints[i].y > maxy then maxy := Round(FPoints[i].y);
    if FPoints[i].y < miny then miny := Round(FPoints[i].y);
  end;

  FLeft := minx;
  FTop := miny;
  FWidth := maxx - minx;
  FHeight := maxy - miny;
end;

procedure TTMSFMXHotSpotShape.SetPoints;
var
  i: Integer;
begin
  if Length(value) < 3 then
    Exit;

  SetLength(FPoints,Length(value));
  for i := 0 to High(value) do
  begin
    FPoints[i].x := Value[i].x;
    FPoints[i].y := Value[i].y;
  end;
  CalcMargins;
end;

function TTMSFMXHotSpotShape.GetHoverPos(X, Y: Integer): THoverPosition;
begin
  case FShapeType of
  stRectangle:Result := RectPos(x,y);
  stEllipse:Result := EllipsePos(x,y);
  stPolygon:Result := PolyPos(x,y);
  else
    Result := hpNone;
  end;
end;

function TTMSFMXHotSpotShape.RectPos(X, Y: Integer): THoverPosition;
var
  x1,x2,y1,y2: Integer;
begin
  x1 := Left;
  y1 := Top;
  x2 := Left + Width;
  y2 := Top + Height;

  if Between(Y, Y1, Y2) and Between(X, X1, X2) then
    Result := hpInside
  else
    Result := hpNone;
end;

function TTMSFMXHotSpotShape.EllipsePos(X, Y: Integer): THoverPosition;
var
  xc,yc,sinu,cosu,tt,a,b,xe,ye: Extended;
begin
  if (Width = 0) or (Height = 0) then
  begin
    Result := hpNone;
    Exit;
  end;

  if (x < (FLeft - CornerSize)) or (x > (FLeft + Width + CornerSize)) or
     (y < (FTop - CornerSize)) or (y > (FTop + Height + CornerSize)) then
  begin
    Result := hpNone;
    Exit;
  end;


  a := sqrt((FPoints[0].x-FPoints[6].x)*(FPoints[0].x-FPoints[6].x)+
          (FPoints[0].y-FPoints[6].y)*(FPoints[0].y-FPoints[6].y))/2;
  b := sqrt((FPoints[9].x-FPoints[3].x)*(FPoints[9].x-FPoints[3].x)+
          (FPoints[9].y-FPoints[3].y)*(FPoints[9].y-FPoints[3].y))/2;


  xc := FEx1+(FEx2-FEx1)/2;
  yc := FEy1+(FEy2-FEy1)/2;

  xe := x - xc;
  ye := y - yc;
  SinCos((FAngle/180)*Pi,sinu,cosu);
  xe := xe*cosu-ye*sinu;
  ye := (x - xc)*sinu+ye*cosu;

  tt := (xe*xe)/(a*a)+(ye*ye)/(b*b);
  if Abs(tt-1) < EllipseTolerance then
    Result := hpBorder
  else
    if tt < 1 then
      Result := hpInside
    else
      Result := hpNone;
end;

function TTMSFMXHotSpotShape.BorderPolypos(X,Y: Integer;var p1,p2: Integer): TBorderPoly;
var
  i: Integer;
begin
  if Fpoints = nil then
  begin
    Result := bNone;
    Exit;
  end;

  if Between(x,FPoints[0].X-CornerSize,FPoints[0].X+CornerSize) and
     Between(y,FPoints[0].y-CornerSize,FPoints[0].y+CornerSize) then
  begin
    Result := bPoint;
    p1 := 0;
    Exit;
  end;
  for i := 1 to high(FPoints) do
  begin
    if Between(x,FPoints[i].X-CornerSize,FPoints[i].X+CornerSize) and
       Between(y,FPoints[i].y-CornerSize,FPoints[i].y+CornerSize) then
    begin
      Result:=bPoint;
      p1 := i;
      Exit;
    end;
    if NearLine(PointF(x,y),FPoints[i-1],FPoints[i]) then
    begin
      Result := bLine;
      p1 := i - 1;
      p2 := i;
      Exit;
    end;
  end;
  if NearLine(PointF(x,y),FPoints[0],FPoints[High(FPoints)]) then
  begin
    Result := bLine;
    p1 := 0;
    p2 := High(FPoints);
    Exit;
  end;

  if PolyPos(x,y) = hpInside then
    Result := bInside
  else
    Result := bNone;
end;


procedure TTMSFMXHotSpotShape.Draw(Canvas :TCanvas);
var
  tp: TPolygon;
  i: Integer;
  rf: TRectF;
begin
  SetLength(tp,Length(FPoints) + 1);
  for i := 0 to High(FPoints) do
    tp[i] := FPoints[i];
  tp[High(FPoints)+ 1] := tp[0];

  rf := RectF(Left, Top, Left + Width, Top + Height);
  case FShapeType of
  stEllipse: if FPoints <> nil then
    Canvas.DrawEllipse(rf, 1);
  stRectangle,stPolygon: if FPoints <> nil then
      Canvas.DrawPolygon(tp, 1);
  end;
end;

procedure TTMSFMXHotSpotShape.drawAt(Canvas :TCanvas;X,Y :Integer);
var
  i: Integer;
  tp: TIntPoints;
begin
  if Length(FPoints) = 0 then
    Exit;

  SetLength(tp,length(FPoints));
  for i := 0 to High(FPoints) do
    tp[i] := PointF(FPoints[i].x-FLeft,FPoints[i].y-FTop);

  case FShapeType of
  stEllipse:FillEllipse(Canvas,tp);
  stPolygon,stRectangle:Canvas.DrawPolygon(tp, 1);
  end;
end;

procedure TTMSFMXHotSpotShape.SetAngle(const Value: Integer);
var
  i: Integer;
  a,b,tt,cosu,sinu,yc,xc: Extended;
begin
  if Value = FAngle then
    Exit;

  a := ((FAngle-Value)*Pi)/180.0;
  SinCos(a,sinu,cosu);
  xc := FLeft + FWidth/2;
  yc := FTop + FHeight/2;
  a := xc - xc * cosu + yc * sinu;
  b := yc - xc * sinu - yc * cosu;
  for i := 0 to High(FPoints) do
  begin
    tt := (FPoints[i].x*cosu-FPoints[i].y*sinu+a);
    FPoints[i].y := (FPoints[i].y*cosu+FPoints[i].x*sinu+b);
    FPoints[i].x := tt;
  end;

  FAngle := Value;
  CalcMargins;
end;

function TTMSFMXHotSpotShape.PolyPos(X,Y:Integer):THoverPosition;
var
  ppt: TPointF;
  i,wnumintsct: Word;
  pt1,pt2: TPointF;
begin
  if Length(FPoints)<3 then
  begin
    Result := hpNone;
    Exit;
  end;

  if (not Between(X, FLeft, FLeft+FWidth)) or (not Between(Y, FTop, FTop+FHeight)) then
  begin
    Result := hpNone;
    Exit;
  end;

  wnumintsct := 0;
  pt1.x := x;
  pt1.y := y;
  pt2.y := y;
  pt2.x := 2560;

  for i := 0 to Length(FPoints) - 2 do
  begin
    ppt := FPoints[i];
    if (Intersect(pt1, pt2, FPoints[i], FPoints[i+1])) then
      Inc(wnumintsct);
  end;

  if (Intersect(pt1, pt2, FPoints[High(FPoints)], FPoints[0])) then
    Inc(wnumintsct);

  if(Odd(wnumintsct)) then
    Result := hpInside
  else
    Result := hpNone;
end;


procedure TTMSFMXHotSpotShape.EllipseToBezier;
var
  ofx,ofy,xc,yc: Real;
  a: Integer;
begin
  SetLength(FPoints,13);

  ofx := FWidth*EToBConst;
  ofy := FHeight*EToBConst;
  xc := FLeft+FWidth/2;
  yc := FTop+FHeight/2;

  FPoints[0].x  := FLeft;
  FPoints[1].x  := FLeft;
  FPoints[11].x := FLeft;
  FPoints[12].x := FLeft;
  FPoints[5].x  := FLeft + FWidth;
  FPoints[6].x  := FLeft + FWidth;
  FPoints[7].x  := FLeft + FWidth;
  FPoints[2].x  := xc - ofx;
  FPoints[10].x := xc - ofx;
  FPoints[4].x  := xc + ofx;
  FPoints[8].x  := xc + ofx;
  FPoints[3].x  := xc;
  FPoints[9].x  := xc;

  FPoints[2].y  := FTop;
  FPoints[3].y  := FTop;
  FPoints[4].y  := FTop;
  FPoints[8].y  := FTop + FHeight;
  FPoints[9].y  := FTop + FHeight;
  FPoints[10].y := FTop + FHeight;
  FPoints[7].y  := yc + ofy;
  FPoints[11].y := yc + ofy;
  FPoints[1].y  := yc - ofy;
  FPoints[5].y  := yc - ofy;
  FPoints[0].y  := yc;
  FPoints[12].y := yc;
  FPoints[6].y  := yc;

  if FAngle <> 0 then
  begin
    a := FAngle;
    FAngle := 0;
    SetAngle(a);
  end
  else
    CalcMargins;
end;

procedure TTMSFMXHotSpotShape.FillEllipse;
begin
  Canvas.FillPolygon(pts, 1);
end;

{ TTMSFMXHotSpots }

function TTMSFMXHotSpots.Add: TTMSFMXHotSpot;
begin
  Result := TTMSFMXHotSpot(inherited Add);
end;

procedure TTMSFMXHotSpots.Assign(Source: TPersistent);
var
  i: Integer;
  S: TTMSFMXHotSpots;
begin
  inherited;
  S := TTMSFMXHotSpots(Source);
  if S = nil then
    Exit;

  oldHeight := S.oldHeight;
  oldWidth := S.oldWidth;

  Clear;
  for i := 0 to S.Count-1 do
    Add.Assign(S[i]);
end;

procedure TTMSFMXHotSpots.EndUpdate;
begin
  inherited;
  if Assigned(FOwner) then
  begin
    FOwner.FHoveredItem := -1;
    FOwner.FClickedItem := -1;
  end;
end;

constructor TTMSFMXHotSpots.Create(AOwner: TTMSFMXHotSpotImage);
begin
  inherited Create(TTMSFMXHotSpot);
  FOwner := AOwner;
end;

function TTMSFMXHotSpots.GeTTMSFMXHotSpotImage: TTMSFMXHotSpotImage;
begin
  Result := TTMSFMXHotSpotImage(FOwner);
end;

function TTMSFMXHotSpots.GetItem(Index: Integer): TTMSFMXHotSpot;
begin
  Result := TTMSFMXHotSpot(inherited GetItem(Index));
end;

function TTMSFMXHotSpots.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

procedure TTMSFMXHotSpots.LoadFromFile(FName: TFileName);
var
  F: TFileStream;
begin
  Clear;
  F := TFileStream.Create(FName, fmOpenRead or fmShareExclusive);
  try
    LoadFromStream(F);
  finally
    F.Free;
  end;
end;

procedure TTMSFMXHotSpots.LoadFromStream(S: TStream);
var
  i, n: Integer;
  bVCL: Boolean;
  str: string;
begin
  //If HSF1 then VCL LOAD
  str := ReadString(S);
  if (UpperCase(str) = 'HSF1') or (UpperCase(str) = 'HSF2') then
  begin

    if UpperCase(str) = 'HSF1' then
      bVCL := True
    else
      bVCL := False;

    n := ReadInteger(S);
    for i := 1 to n do
      Add.LoadFromStream(S, bVCL);


    if Assigned(FOwner) then
    begin
      for i := 0 to Count - 1 do
      begin
        Items[i].FPicture := FOwner.Bitmap;
        if Items[i].Clipped then
        with Items[i] do
        begin
          ReCalcClips;
        end;
      end;
    end;
  end
  else
    raise Exception.Create('Incorrect data found while attempting to load hot spots.');
end;

procedure TTMSFMXHotSpots.ReScale(newWidth, newHeight: Single);
var
  i:integer;
  deltaX,deltaY,a,b,c,d:real;
begin
  if (oldWidth = 0) or (newWidth = 0)or
     (oldHeight = 0) or (newHeight = 0) then
     Exit;

  if (FOwner <> nil) then
    if (csLoading in FOwner.ComponentState)or (csReading in FOwner.ComponentState) then
      Exit;

  deltaX := newWidth/ oldWidth;
  deltaY := newHeight/ oldHeight;
  oldWidth := newWidth;
  oldHeight := newHeight;

  for i := 0 to Count - 1 do
    with Items[i] do
      if FShape.ShapeType <> stEllipse then
      begin
        a := X1;
        b := X2;
        c := Y1;
        d := Y2;
        X1 := Round(a*deltaX);
        Y1 := Round(c*deltaY);
        X2 := Round(b*deltaX);
        Y2 := Round(d*deltaY);

      end
      else
      begin
        SetRect(Round(X1*deltaX),Round(Y1*deltaY),Round(X2*deltaX),Round(Y2*deltaY));
        ReCalcClips;
      end;

  if (FOwner <> nil) then
    FOwner.Repaint();
end;

procedure TTMSFMXHotSpots.ReCalclips;
var
  i: Integer;
begin
  for i := 0 to Count - 1 do
  begin
    Items[i].ReCalcClips;
  end;
end;

procedure TTMSFMXHotSpots.ReScale(delta: Integer);
begin
  ReScale(oldWidth + delta,oldHeight + delta);
end;

procedure TTMSFMXHotSpots.SaveToFile(FName: TFileName);
var
  F: TFileStream;
begin
  F := TFileStream.Create(FName, fmCreate or fmShareExclusive);
  try
    SaveToStream(F);
  finally
    F.Free;
  end;
end;

procedure TTMSFMXHotSpots.SaveToStream(S: TStream);
var i: Integer;
begin
  WriteString(S, 'HSF2');
  WriteInteger(S, Count);
  for i := 0 to Count - 1 do
    Items[i].SavetoStream(S);
end;

procedure TTMSFMXHotSpots.SetDimensions(W, H: integer);
begin
  oldWidth := W;
  oldHeight := H;
end;

procedure TTMSFMXHotSpots.SetItem(Index: Integer; const Value: TTMSFMXHotSpot);
begin
  inherited SetItem(Index, Value);
end;

{ TTMSFMXHotSpot }

function TTMSFMXHotSpot.GetHeight: Integer;
begin
  Result := FShape.Height;
end;

function TTMSFMXHotSpot.GetWidth: Integer;
begin
  Result := FShape.Width;
end;

procedure TTMSFMXHotSpot.SetWidth(const Value: Integer);
var
  sang,i: Integer;
  f,xf,a: Real;
begin
  if FShape.Width = Value then
    Exit;

  if (FShape.Points = nil) or (FShape.Width = 0) then
  begin
    SetRect(x1,y1,FShape.Left + Value,y2);
    Exit;
  end;

  sang := Angle;

  if ShapeType = stEllipse then
  with FShape do
  begin
    Angle := 0;
    xf := FEx1 + (FEx2 - FEx1) / 2;
  end
  else
    xf := FShape.Left;

  f := Value / FShape.Width;
  a := xf - xf * f;
  for i := 0 to High(FShape.Points) do
    FShape.Points[i].x := FShape.Points[i].x * f + a;

  FShape.Width := Value;
  if ShapeType = stEllipse then
  begin
    Angle := sang;
    FShape.calcMargins;
  end;
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetHeight(const Value: Integer);
var
  sang,i: Integer;
  yf,a,f: Real;
begin
  if (FShape.Points = nil) or (FShape.Height = 0) then
  begin
    SetRect(x1,y1,x2,FShape.Top+Value);
    Exit;
  end;

  sang := Angle;

  if ShapeType = stEllipse then
  with FShape do
  begin
    Angle := 0;
    yf := FEy1 + (FEy2 - FEy1) / 2;
  end
  else
    yf := FShape.Top;

  f := Value/FShape.Height;

  a := yf - yf * f;

  for i := 0 to High(FShape.Points) do
    FShape.Points[i].y := FShape.Points[i].y * f + a;
  FShape.Height := Value;

  if ShapeType = stEllipse then
  begin
    Angle := sang;
    FShape.calcMargins;
  end;

  ReCalcClips;
end;

function TTMSFMXHotSpot.GetX: Integer;
begin
  Result := FShape.Left;
end;

function TTMSFMXHotSpot.GetY: Integer;
begin
  Result := FShape.Top;
end;

procedure TTMSFMXHotSpot.SetX(const Value: Integer);
var
  i,delta: Integer;
begin
  if FShape.Left = Value then
    Exit;

  delta := Value - FShape.Left;
  for i := 0 to High(FShape.Points) do
    FShape.Points[i].x := FShape.Points[i].x + delta;

  if ShapeType = stEllipse then
    FShape.CalcMargins;

  FShape.Left := Value;
end;

procedure TTMSFMXHotSpot.SetY(const Value: Integer);
var
  i,delta: Integer;
begin
  if FShape.Top = Value then
    Exit;

  delta := Value - FShape.Top;

  for i := 0 to High(FShape.Points) do
    FShape.Points[i].y := FShape.Points[i].y + delta;

  if ShapeType = stEllipse then
    FShape.CalcMargins;

  FShape.Top := Value;
end;

function TTMSFMXHotSpot.GetX2: Integer;
begin
  Result := X1 + Width;
end;

function TTMSFMXHotSpot.GetY2: Integer;
begin
  Result := Y1 + Height;
end;

procedure TTMSFMXHotSpot.HoverPictureChange(Sender: TObject);
begin

end;

procedure TTMSFMXHotSpot.SetX2(const Value: Integer);
begin
  Width := Value - X1;
end;

procedure TTMSFMXHotSpot.SetY2(const Value: Integer);
begin
  Height := Value - Y1;
end;

procedure TTMSFMXHotSpot.SetPolyPoints;
begin
  FShape.Points:= Value;
end;

procedure TTMSFMXHotSpot.SetPolyPoint;
begin
  if (i < 0) or (i > High(FShape.Points)) then
    Exit;

  FShape.Points[i] := Value;
  if Value.X < X then
  begin
    FShape.Width := Round(FShape.Width+x-Value.x);
    FShape.Left := Round(Value.X);
  end;

  if Value.Y<Y then
  begin
    FShape.Height := Round(FShape.Height+y-Value.y);
    FShape.Top := Round(Value.Y);
  end;

  if (Value.X-FShape.Left) > FShape.Width then
    FShape.Width := Round(Value.X-FShape.Left);

  if (Value.y-FShape.Top) > FShape.Height then
    FShape.Height := Round(Value.Y-FShape.Top);
end;

procedure TTMSFMXHotSpot.LoadPoints(Reader: TReader);
var
  i,l: Integer;
begin
  Reader.ReadListBegin;
  l := Reader.ReadInteger;
  FShape.FAngle := Reader.ReadInteger;
  if l > 0 then
  begin
    SetLength(FShape.FPoints,l);
    for i := 0 to l - 1 do
    begin
      FShape.FPoints[i].x := Reader.ReadFloat;
      FShape.FPoints[i].y := Reader.ReadFloat;
    end;
    FShape.CalcMargins;
  end;
  Reader.ReadListEnd;
end;

procedure TTMSFMXHotSpot.StorePoints(Writer: TWriter);
var
  i,l: Integer;
begin
  Writer.WriteListBegin;
  l := Length(FShape.FPoints);
  Writer.WriteInteger(l);
  Writer.WriteInteger(FShape.FAngle);
  if l > 0 then
    for i := 0 to l - 1 do
    begin
      Writer.WriteFloat(FShape.FPoints[i].x);
      Writer.writeFloat(FShape.FPoints[i].y);
    end;
  Writer.WriteListEnd;
end;

procedure TTMSFMXHotSpot.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('PolyPoints', LoadPoints, StorePoints, True);
end;


function TTMSFMXHotSpot.getPolyPoints;
begin
  Result := FShape.Points;
end;

function TTMSFMXHotSpot.getPolyPoint;
begin
  if (i < 0) or (i > High(FShape.Points)) then
  begin
    Result.x := -1;
    Result.y := -1;
    Exit;
  end;
  Result := FShape.Points[i];
end;

function TTMSFMXHotSpot.GetAngle: Integer;
begin
  Result := FShape.Angle;
end;

procedure TTMSFMXHotSpot.SetAngle(const Value: Integer);
begin
  FShape.Angle := Value;
end;

procedure TTMSFMXHotSpot.ScaleEllipse;
var
  i: Integer;
  signx,signy,ew,eh,tt,tx,ty,sx,sy,cosu,sinu,a,b,c,d,e,f: Extended;
  pp1,pp2,pps: TPointF;
begin
  if (Shapetype <> stEllipse) or (Length(FShape.FPoints) < 12) then
    Exit;

  tx := -(FShape.FEx1+(FShape.FEx2-FShape.FEx1)/2);
  ty := -(FShape.FEy1+(FShape.FEy2-FShape.FEy1)/2);
  SinCos((FShape.FAngle*Pi)/180,sinu,cosu);
  pp1 := RPoint(p1.x,p1.y);
  pp2 := RPoint(p2.x,p2.y);
  pps := RPoint(ps.x,ps.y);

  pp1.x := pp1.x + tx;
  pp1.y := pp1.y + ty;

  pp2.x := pp2.x + tx;
  pp2.y := pp2.y + ty;

  pps.x := pps.x + tx;
  pps.y := pps.y + ty;

  tt := pp1.x*cosu-pp1.y*sinu;
  pp1.y := pp1.y*cosu+pp1.x*sinu;
  pp1.x := tt;

  tt := pp2.x*cosu-pp2.y*sinu;
  pp2.y := pp2.y*cosu+pp2.x*sinu;
  pp2.x := tt;

  tt := pps.x*cosu-pps.y*sinu;
  pps.y := pps.y*cosu+pps.x*sinu;
  pps.x := tt;
  if pps.x < 0 then signx := -1
               else signx := 1;
  if pps.y < 0 then signy := -1
               else signy := 1;

  with FShape do
  begin
    ew := sqrt((FPoints[6].x-FPoints[0].x)*(FPoints[6].x-FPoints[0].x)+
              (FPoints[6].y-FPoints[0].y)*(FPoints[6].y-FPoints[0].y));
    eh := sqrt((FPoints[9].x-FPoints[3].x)*(FPoints[9].x-FPoints[3].x)+
              (FPoints[9].y-FPoints[3].y)*(FPoints[9].y-FPoints[3].y));
  end;
  if (ew = 0) or (eh = 0) then
    Exit;

  sx := (ew-signx*(pp2.x-pp1.x))/ew;
  sy := (eh-signy*(pp2.y-pp1.y))/eh;

  if ((ew*sx) < 10) and (sx < 1) then
    sx := 1;
  if ((eh*sy) < 10) and (sy < 1) then
    sy := 1;

  a := cosu*cosu*sx+sy-cosu*cosu*sy;
  b := -cosu*sinu*(sx-sy);
  c := b;
  d := sx+cosu*cosu*(sy-sx);
  e := sx*cosu*cosu*tx-sx*cosu*ty*sinu+sy*tx-sy*tx*cosu*cosu+sy*sinu*ty*cosu-tx;
  f := -sx*sinu*tx*cosu+sx*ty-sx*ty*cosu*cosu+sy*cosu*tx*sinu+sy*cosu*cosu*ty-ty;
  for i := 0 to High(FShape.FPoints) do with FShape.FPoints[i] do
  begin
   tt := x*a+y*c+e;
   y := x*b+y*d+f;
   x := tt;
  end;
  FShape.CalcMargins;
end;


procedure TTMSFMXHotSpot.SetClipped(const Value: Boolean);
begin
  FClipped := Value;
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetDown(const Value: Boolean);
begin
  FDown := Value;
  if ((Collection as TTMSFMXHotSpots).GeTTMSFMXHotSpotImage <> nil) then
    (Collection as TTMSFMXHotSpots).GeTTMSFMXHotSpotImage.Repaint;
end;

procedure TTMSFMXHotSpot.CalcClip(const Pict: TBitmap; var Bitm: TBitmap; ImageName: String; AFill, AStroke: TBrush);
var
  source: TBitmap;
  images: TTMSFMXHotSpotImage;  
  a,b,c,d,okW,okH: Integer;
  R1, R2: TRectF;  
  bmp: TBitmap;
begin
  images := (Collection as TTMSFMXHotSpots).GeTTMSFMXHotSpotImage;

  if (images = nil) or (EmptyImage(Pict) and (AFill.Color = claNull) and (AStroke.Color = claNull) and (ImageName = '')) then
  begin
    Bitm.SetSize(0, 0);
    Exit;
  end;

  if images.FStretch then
  begin
    okW := Width;
    okH := Height;
    a := Round(X1 * images.Bitmap.Width / images.Width);
    b := Round(Y1 * images.Bitmap.Height / images.Height);
    c := Round(X2 * images.Bitmap.Width / images.Width);
    d := Round(Y2 * images.Bitmap.Height / images.Height);
  end
  else
  begin
    if not EmptyImage(Pict) then
    begin
      okW := min(Width,Pict.Width);
      okH := min(Height,Pict.Height);
    end
    else
    begin
      okW := Width;
      okH := Height;
    end;
    a := X1;
    b := Y1;
    c := X2;
    d := Y2;
  end;

  bitm.SetSize(okW, okH);

  if Assigned(FPicture) and (not EmptyImage(FPicture)) then
  begin
    if (not EmptyImage(Pict) and not images.Stretch) or (EmptyImage(Pict) and (AFill.Color = claNull)) then
    begin
      Source := TBitmap.Create(0, 0);
      try
        Source.SetSize(FPicture.Width, FPicture.Height);
        R1 := RectF(0, 0, FPicture.Width, FPicture.Height);
        Source.Canvas.BeginScene;
        Source.Canvas.DrawBitmap(FPicture, R1, R1, 1);
        Source.Canvas.EndScene;
        R1 := RectF(0,0,okW,okH);

        {$IFDEF PATCHTEXTURE}
        R2 := RectF(0,0, c-a, d-b);
        {$ENDIF}
        {$IFNDEF PATCHTEXTURE}
        R2 := RectF(a,b, c, d);
        {$ENDIF}
        bitm.Canvas.BeginScene;
        bitm.Canvas.DrawBitmap(Source, R2, R1, 1);
        bitm.Canvas.EndScene;
      finally
        source.free;
      end;
    end;

    {
    j := High(FShape.Points);
    SetLength(tempPoints,(j + 1));
    for i := 0 to j do
      begin
        tempPoints[i].x:=Round(FShape.Points[i].X) - X1;
        tempPoints[i].y:=Round(FShape.Points[i].Y) - Y1;
      end;
    }
    if not EmptyImage(Pict) then
    begin
      if FClipped and (FShape.FShapeType <> stRectangle) and
        Assigned(FPicture) and (not EmptyImage(FPicture)) then
      begin
        begin
          if images.Stretch then
          begin
            bitm.SetSize(Round(images.Width), Round(images.Height));
            {$IFDEF PATCHTEXTURE}
            R1 := RectF(0,0, x2-x1, y2-y1);
            {$ENDIF}
            {$IFNDEF PATCHTEXTURE}
            R1 := RectF(x1,y1, x2, y2);
            {$ENDIF}
            R2 := RectF(0,0, Pict.Width, Pict.Height);
            bitm.Canvas.BeginScene;
            bitm.Canvas.DrawBitmap(Pict, R2, R1, 1);
            bitm.Canvas.EndScene;
          end
          else
          begin
            bitm.SetSize(FPicture.Width, FPicture.Height);
            {$IFDEF PATCHTEXTURE}
            R1 := RectF(0,0, c-a, d-b);
            {$ENDIF}
            {$IFNDEF PATCHTEXTURE}
            R1 := RectF(a,b, c, d);
            {$ENDIF}
            R2 := RectF(0,0, Pict.Width, Pict.Height);
            bitm.Canvas.BeginScene;
            bitm.Canvas.DrawBitmap(Pict, R2, R1, 1);
            bitm.Canvas.EndScene;
          end;
        end;
      end
      else
      begin
        R1 := RectF(0,0,okW,okH);
        R2 := RectF(0,0, Pict.Width, Pict.Height);
        bitm.Canvas.BeginScene;
        bitm.Canvas.DrawBitmap(Pict, R2, R1, 1);
        bitm.Canvas.EndScene;
      end;
    end
    else if AFill.Color <> claNull then
    begin
      bitm.Canvas.Fill.Assign(AFill);
      R2 := RectF(0, 0, bitm.Width, bitm.Height);
      bitm.Canvas.BeginScene;

      bitm.Canvas.ClearRect(R2,0);

      {$IFDEF DELPHIXE6_LVL}
      bitm.Canvas.FillRect(R2, 0, 0, AllCorners, images.AbsoluteOpacity, AFill, TCornerType.Round);
      {$ELSE}
      bitm.Canvas.FillRect(R2, 0, 0, AllCorners, images.AbsoluteOpacity, AFill, TCornerType.ctRound);
      {$ENDIF}
      bitm.Canvas.EndScene;
    end;

    if Assigned(Images.BitmapContainer) and (ImageName <> '') then
    begin
      bmp := images.BitmapContainer.FindBitmap(ImageName);
      if Assigned(bmp) then
      begin
        {$IFDEF PATCHTEXTURE}
        R1 := RectF(0,0, c-a, d-b);
        {$ENDIF}
        {$IFNDEF PATCHTEXTURE}
        R1 := RectF(a,b, c, d);
        {$ENDIF}
        R2 := RectF(0,0, bmp.Width, bmp.Height);
        bitm.Canvas.BeginScene;
        bitm.Canvas.DrawBitmap(bmp, R2, R1, 1);
        bitm.Canvas.EndScene;
      end;
    end;
  end;
end;

procedure TTMSFMXHotSpot.ClickPictureChange(Sender: TObject);
begin

end;

procedure TTMSFMXHotSpot.ReCalcClips;
begin
  if (FOwner <> nil) and (FOwner.FOwner <> nil) then
  begin
    FPicture := (FOwner.GetOwner as TTMSFMXHotSpotImage).Bitmap;
    CalcClip(FHoverPicture,FCHover, HoverImageName, HoverFill, HoverStroke);
    CalcClip(FClickPicture,FCClick, ClickImageName, ClickFill, ClickStroke);
    CalcClip(FSelectedImage, FCSelect, SelectedImageName, SelectedFill, SelectedStroke);
    if Selected then
      CalcClip(FSelectedImage, FCBlink, SelectedImageName, SelectedFill, SelectedStroke)
    else
      CalcClip(FBlinkImage, FCBlink, BlinkImageName, BlinkFill, BlinkStroke);
  end;
end;

procedure TTMSFMXHotSpot.SetOffsetX(const Value: Integer);
begin
  FOffsetX := Value;
end;

procedure TTMSFMXHotSpot.SetOffsetY(const Value: Integer);
begin
  FOffsetY := Value;
end;

function TTMSFMXHotSpot.GetBitmapContainer: TTMSFMXBitmapContainer;
begin
  Result := FOwner.FOwner.BitmapContainer;
end;

function TTMSFMXHotSpot.GetBorderPolypos(X,Y:Integer;var p1,p2:integer):TBorderPoly;
begin
  Result := FShape.BorderPolypos(x,y,p1,p2);
end;

function TTMSFMXHotSpot.GetDisplayName: String;
begin
  Result := 'HotSpot' + IntToStr(Index);
end;

procedure TTMSFMXHotSpot.Assign(Source: TPersistent);
var
  S: TTMSFMXHotSpot;
begin
  S := TTMSFMXHotSpot(Source);
  Hint := S.Hint;
  SetRect(S.X1, S.Y1, S.X2, S.Y2);
  FClipped := S.FClipped;
  HoverImage.Assign(S.HoverImage);
  ClickImage.Assign(S.ClickImage);

  FBlink := S.Blink;
  FSelected := S.FSelected;
  Selectable := S.Selectable;
  FBlinkImageName := S.FBlinkImageName;
  FSelectedImageName := S.FSelectedImageName;
  FClickImageName := S.FClickImageName;
  FHoverImageName := S.FHoverImageName;
  FBlinkFill.Assign(S.BlinkFill);
  FHoverFill.Assign(S.HoverFill);
  FSelectedFill.Assign(S.SelectedFill);
  FClickFill.Assign(S.ClickFill);
  FBlinkStroke.Assign(S.BlinkStroke);
  FHoverStroke.Assign(S.HoverStroke);
  FSelectedStroke.Assign(S.SelectedStroke);
  FClickStroke.Assign(S.ClickStroke);

  FSelectedImage.Assign(S.FSelectedImage);
  FBlinkImage.Assign(S.FBlinkImage);
  FOffsetY := S.OffsetY;
  FOffsetX := S.OffsetX;

  Name := S.Name;
  Id := S.Id;
  ShapeType := S.ShapeType;
  PolyPoints := S.PolyPoints;
  FShape.FAngle := S.FShape.FAngle;
end;

function TTMSFMXHotSpot.GetHoverPos(X, Y: Integer): THoverPosition;
begin
  if FPicture = nil then
  begin
    if (FOwner <> nil) and (FOwner.FOwner <> nil) then
    begin
      FPicture := (FOwner.getOwner as TTMSFMXHotSpotImage).Bitmap;
      ReCalcClips;
    end;
  end
  else
    if (EmptyImage(FPicture)) then
    begin
      if (FOwner <> nil) and (FOwner.FOwner <> nil) then
      begin
        FPicture := (FOwner.GetOwner as TTMSFMXHotSpotImage).Bitmap;
        ReCalcClips;
      end;
    end;
  Result := FShape.GetHoverPos(x,y);
end;

function TTMSFMXHotSpot.GetRectHoverPos(X, Y: Integer): THoverPosition;
begin
  Result := FShape.RectPos(x,y);
end;

procedure TTMSFMXHotSpot.DrawShape(Canvas: TCanvas);
begin
  FShape.Draw(Canvas);
end;

procedure TTMSFMXHotSpot.DrawShapeAt(Canvas: TCanvas;X,Y: Integer);
begin
  FShape.DrawAt(Canvas,X,Y);
end;

function TTMSFMXHotSpot.AsRect: TRect;
begin
  Result := Rect(X1, Y1, X2, Y2);
end;

function AddBackslash(const s: string): string;
begin
  if (Length(s) >= 1) and (s[Length(s)]<>'\') then
    Result := s + '\'
  else
    Result := s;
end;

procedure WritePicture(Stream: TStream; Picture: TBitmap);
var
  Str: TmemoryStream;
begin
  Str := TMemoryStream.Create;
  try
    Picture.SaveToStream(Str);
    WriteInteger(Stream, Str.Size);
    Str.Position := 0;
    Stream.CopyFrom(Str, Str.Size)
  finally
    Str.free
  end;
end;

procedure ReadPicture(Stream: TStream; Picture: TBitMap; DoVCL: Boolean);
var
  m: TMemoryStream;
  i: integer;
  s: String;
begin
  if DoVCL then
    s := ReadString(Stream);
  i := ReadInteger(Stream);
  m := TMemoryStream.Create;
  try
    m.CopyFrom(Stream, i);
    Picture.LoadFromStream(m);
  finally
    m.Free;
  end;
end;

procedure WriteBrush(ABrush: TBrush; AStream: TStream);
var
  ps: TPersistentBrushObject;
begin
  ps := TPersistentBrushObject.Create(nil);
  try
    ps.Brush := ABrush;
    AStream.WriteComponent(ps);
  finally
    ps.Free;
  end;
end;

procedure ReadBrush(ABrush: TBrush; AStream: TStream);
var
  ps: TPersistentBrushObject;
begin
  ps := TPersistentBrushObject.Create(nil);
  try
    ps.Brush := ABrush;
    AStream.ReadComponent(ps);
  finally
    ps.Free;
  end;
end;

procedure TTMSFMXHotSpot.LoadFromStream(S: TStream; DoVCL: Boolean);
var
  tt: TPoints;
  i,j, hc, cc, sc, bc: Integer;
  r, g, b: Byte;

  procedure GetRGB(Col: Integer; var R, G, B: Byte);
  begin
    R := ($000000FF and Col);
    G := ($0000FF00 and Col) Shr 8;
    B := ($00FF0000 and Col) Shr 16;
  end;

begin
  FName := ReadString(S);

  X1 := ReadInteger(S);
  Y1 := ReadInteger(S);
  X2 := ReadInteger(S);
  Y2 := ReadInteger(S);
  FID := ReadInteger(S);
  Shapetype := TSpotShapeType(ReadInteger(S));

  if ShapeType = stPolygon then
  begin
    j := ReadInteger(S);

    SetLength(tt,j);

    for i := 0 to High(tt) do
    begin
      tt[i].x := ReadInteger(S);
      tt[i].y := ReadInteger(S);
    end;
    FShape.Points := tt;
  end;

  FShape.FAngle := ReadInteger(S);
  if ReadInteger(S) = 0 then
    Clipped := False
  else
    Clipped := True;

  FHint := ReadString(S);

  i := ReadInteger(S);
  if i = 1 then
    ReadPicture(S,FHoverPicture, DoVCL);

  i := ReadInteger(S);
  if i = 1 then
    ReadPicture(S,FClickPicture, DoVCL);

  FDown := Boolean(ReadInteger(S));

  i := ReadInteger(S);
  if i = 1 then
    ReadPicture(S, FSelectedImage, DoVCL);

  i := ReadInteger(S);
  if i = 1 then
    ReadPicture(S, FBlinkImage, DoVCL);

  FSelected := Boolean(ReadInteger(S));
  FBlink := Boolean(ReadInteger(S));

  if not DoVCL then
  begin
    ReadBrush(HoverFill, S);
    ReadBrush(HoverStroke, S);
    ReadBrush(ClickFill, S);
    ReadBrush(ClickStroke, S);
    ReadBrush(SelectedFill, S);
    ReadBrush(SelectedStroke, S);
    ReadBrush(BlinkFill, S);
    ReadBrush(BlinkStroke, S);
    FHoverImageName := ReadString(S);
    FClickImageName := ReadString(S);
    FSelectedImageName := ReadString(S);
    FBlinkImageName := ReadString(S);
  end
  else
  begin
    // VCL compat
    hc := ReadInteger(S);
    cc := ReadInteger(S);
    sc := ReadInteger(S);
    bc := ReadInteger(S);

    GetRGB(hc, r, g, b);

    if hc = $1FFFFFFF then
      HoverFill.Color := claNull
    else
      HoverFill.Color := MakeColor(r, g, b);

    GetRGB(cc, r, g, b);
    if cc = $1FFFFFFF then
      ClickFill.Color := claNull
    else
      ClickFill.Color := MakeColor(r, g, b);

    GetRGB(sc, r, g, b);
    if sc = $1FFFFFFF then
      SelectedFill.Color := claNull
    else
      SelectedFill.Color := MakeColor(r, g, b);

    GetRGB(bc, r, g, b);
    if bc = $1FFFFFFF then
      BlinkFill.Color := claNull
    else
      BlinkFill.Color := MakeColor(r, g, b);

    FSelectedImage.Assign(FClickPicture);
  end;
end;

procedure TTMSFMXHotSpot.SaveToStream(S: TStream);
var
  i: Integer;
begin
  WriteString(S, FName);
  WriteInteger(S, X1);
  WriteInteger(S, Y1);
  WriteInteger(S, X2);
  WriteInteger(S, Y2);
  WriteInteger(S, FID);
  WriteInteger(S, ord(ShapeType));
  if ShapeType = stPolygon then
  begin
    WriteInteger(S, Length(FShape.Points));
    for i := 0 to High(FShape.Points) do
    begin
      WriteInteger(S, Round(FShape.Points[i].x));
      WriteInteger(S, Round(FShape.Points[i].y));
    end;
  end;
  WriteInteger(S, FShape.FAngle);
  if Clipped then
    WriteInteger(S,1)
  else
    WriteInteger(S,0);

  WriteString(S, FHint);

  if Assigned(FHoverPicture) and not FHoverPicture.IsEmpty then
  begin
    WriteInteger(S, 1);
    WritePicture(S, FHoverPicture);
  end
  else
    WriteInteger(S,0);

  if Assigned(FClickPicture) and not FClickPicture.IsEmpty then
  begin
    WriteInteger(S, 1);
    WritePicture(S, FClickPicture);
  end
  else
    WriteInteger(S,0);

  WriteInteger(S,Integer(FDown));

  if Assigned(FSelectedImage) and not FSelectedImage.IsEmpty then
  begin
    WriteInteger(S, 1);
    WritePicture(S, FSelectedImage);
  end
  else
    WriteInteger(S,0);

  if Assigned(FBlinkImage) and FBlinkImage.IsEmpty then
  begin
    WriteInteger(S, 1);
    WritePicture(S, FBlinkImage);
  end
  else
    WriteInteger(S,0);

  WriteInteger(S,Integer(FSelected));
  WriteInteger(S,Integer(FBlink));

  WriteBrush(HoverFill, S);
  WriteBrush(HoverStroke, S);
  WriteBrush(ClickFill, S);
  WriteBrush(ClickStroke, S);
  WriteBrush(SelectedFill, S);
  WriteBrush(SelectedStroke, S);
  WriteBrush(BlinkFill, S);
  WriteBrush(BlinkStroke, S);
  WriteString(S,FHoverImageName);
  WriteString(S,FClickImageName);
  WriteString(S,FSelectedImageName);
  WriteString(S,FBlinkImageName);
end;

procedure TTMSFMXHotSpot.SetRect(AX1, AY1, AX2, AY2: Integer);
var
  tp: TPoints;
begin
  case FShape.ShapeType of
  stRectangle:
    begin
      SetLength(tp,4);
      tp[0].x := Min(AX1, AX2);
      tp[0].y := Min(AY1, AY2);
      tp[2].x := Max(AX1, AX2);
      tp[2].y := Max(AY1, AY2);

      tp[1].x := tp[2].x;
      tp[1].y := tp[0].y;
      tp[3].x := tp[0].x;
      tp[3].y := tp[2].y;
      FShape.CalcMargins;
      FShape.Points := tp;
    end;
  stEllipse:
    begin
      FShape.Left := Min(AX1, AX2);
      FShape.Top := Min(AY1, AY2);
      FShape.Width := Max(AX1, AX2)-FShape.Left;
      FShape.Height := Max(AY1, AY2)-FShape.Top;
      FShape.EllipseToBezier;
    end;
 end;
end;

procedure TTMSFMXHotSpot.SetRect(R: TRect);
var
  tp: TPoints;
begin
  case FShape.ShapeType of
  stRectangle:
    begin
      SetLength(tp,4);
      tp[0].x := Min(R.Left, R.Right);
      tp[0].y := Min(R.Top, R.Bottom);
      tp[1].x := Max(R.Left, R.Right);
      tp[1].y := Max(R.Top, R.Bottom);

      tp[1].x := tp[2].x;
      tp[1].y := tp[0].y;
      tp[3].x := tp[0].x;
      tp[3].y := tp[2].y;

      FShape.Points := tp;
      FShape.CalcMargins;
    end;
  stEllipse:
    begin
      FShape.Left := Min(R.Left, R.Right);
      FShape.Top := Min(R.Top, R.Bottom);
      FShape.Width := Max(R.Left, R.Right)-FShape.Left;
      FShape.Height := Max(R.Top, R.Bottom)-FShape.Top;
      FShape.EllipseToBezier;
    end;
 end;
end;

procedure TTMSFMXHotSpot.SetShapetype(const Value: TSpotShapeType );
begin
  FShape.ShapeType := Value;
  if Value = stEllipse then
    FShape.EllipseToBezier;

  ReCalcClips;
end;

function TTMSFMXHotSpot.GetSelectable: Boolean;
begin
  Result := FSelectable;
end;

function TTMSFMXHotSpot.GetShapeType: TSpotShapeType;
begin
  Result := FShape.ShapeType;
end;

procedure TTMSFMXHotSpot.SetClickPicture(const Value: TBitmap);
begin
  FClickPicture.Assign(Value);
end;

procedure TTMSFMXHotSpot.SetClickStroke(const Value: TBrush);
begin
  FClickStroke.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetHoverFill(const Value: TBrush);
begin
  FHoverFill.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetHoverPicture(const Value: TBitmap);
begin
  FHoverPicture.Assign(Value);
end;

procedure TTMSFMXHotSpot.SetHoverStroke(const Value: TBrush);
begin
  FHoverStroke.Assign(Value);
  ReCalcClips;
end;

constructor TTMSFMXHotSpot.Create(Collection: TCollection);
begin
  inherited;
  FHoverPicture := TBitmap.Create(0, 0);
  FClickPicture := TBitmap.Create(0, 0);
  FHoverPicture.OnChange := HoverPictureChange;
  FClickPicture.OnChange := ClickPictureChange;
  FCClick := TBitmap.Create(0, 0);
  FCHover := TBitmap.Create(0, 0);
  FCSelect := TBitmap.Create(0, 0);
  FCBlink := TBitmap.Create(0, 0);
  FShape := TTMSFMXHotSpotShape.Create(stRectangle);
  FShowClick := false;

  if Collection is TTMSFMXHotSpots then
    FOwner := Collection as TTMSFMXHotSpots;

  FSelectedImage := TBitmap.Create(0, 0);
  FSelectedImage.OnChange := SelectedImageChange;
  FBlinkImage := TBitmap.Create(0, 0);
  FBlinkImage.OnChange := BlinkImageChange;
  FBlink := False;
  FSelectable := True;
  FSelected := False;
  {$IFDEF DELPHIXE6_LVL}
  FBlinkFill := TBrush.Create(TBrushKind.Solid, claWhite);
  FHoverFill := TBrush.Create(TBrushKind.Solid, claYellow);
  FSelectedFill := TBrush.Create(TBrushKind.Solid, claRed);
  FClickFill := TBrush.Create(TBrushKind.Solid, claGray);

  FBlinkStroke := TBrush.Create(TBrushKind.Solid, claNull);
  FHoverStroke := TBrush.Create(TBrushKind.Solid, claNull);
  FSelectedStroke := TBrush.Create(TBrushKind.Solid, claNull);
  FClickStroke := TBrush.Create(TBrushKind.Solid, claNull);
  {$ELSE}
  FBlinkFill := TBrush.Create(TBrushKind.bkSolid, claWhite);
  FHoverFill := TBrush.Create(TBrushKind.bkSolid, claYellow);
  FSelectedFill := TBrush.Create(TBrushKind.bkSolid, claRed);
  FClickFill := TBrush.Create(TBrushKind.bkSolid, claGray);

  FBlinkStroke := TBrush.Create(TBrushKind.bkSolid, claNull);
  FHoverStroke := TBrush.Create(TBrushKind.bkSolid, claNull);
  FSelectedStroke := TBrush.Create(TBrushKind.bkSolid, claNull);
  FClickStroke := TBrush.Create(TBrushKind.bkSolid, claNull);
  {$ENDIF}
  FOffsetY := 4;
  FOffsetX := 4;
end;

destructor TTMSFMXHotSpot.Destroy;
begin
  FBlinkFill.Free;
  FBlinkStroke.Free;
  FSelectedFill.Free;
  FSelectedStroke.Free;
  FClickFill.Free;
  FClickStroke.Free;
  FHoverFill.Free;
  FHoverStroke.Free;
  FHoverPicture.Free;
  FClickPicture.Free;
  FCClick.Free;
  FShape.Free;
  FCHover.Free;
  FCSelect.Free;
  FCBlink.Free;
  FSelectedImage.Free;
  FBlinkImage.Free;
  inherited;
end;

procedure TTMSFMXHotSpot.SetShowClick(const Value: Boolean);
begin
  FShowClick := Value;
  (Collection as TTMSFMXHotSpots).GeTTMSFMXHotSpotImage.Repaint;
end;

procedure TTMSFMXHotSpot.SetBlink(const Value: Boolean);
var
  HSImage: TTMSFMXHotSpotImage;
begin
  if (FBlink <> Value) then
  begin
    FBlink := Value;
    ReCalcClips;

    HSImage := (Collection as TTMSFMXHotSpots).GeTTMSFMXHotSpotImage;
    if FBlink and Assigned(HSImage) and not (csDesigning in HSImage.ComponentState) and not (csLoading in HSImage.ComponentState) then
      HSImage.FTimer.Enabled := True;
  end;
end;

procedure TTMSFMXHotSpot.SetBlinkFill(const Value: TBrush);
begin
  FBlinkFill.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetBlinkImage(const Value: TBitmap);
begin
  FBlinkImage.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetBlinkStroke(const Value: TBrush);
begin
  FBlinkStroke.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.InternalClick;
begin
  Selected := not Selected;
end;

procedure TTMSFMXHotSpot.UpdateExclusive;
var
  i: Integer;
begin
  for i:= 0 to (Collection as TTMSFMXHotSpots).Count - 1 do
  begin
    if ((Collection as TTMSFMXHotSpots).Items[i] <> Self) and ((Collection as TTMSFMXHotSpots).Items[i].Selected) then
      (Collection as TTMSFMXHotSpots).Items[i].Selected := False;
  end;
end;

procedure TTMSFMXHotSpot.SetSelectable(const Value: Boolean);
begin
  if (FSelectable <> Value) then
  begin
    if not Value and Selected then
      Selected := False;
    FSelectable := Value;
  end;
end;

procedure TTMSFMXHotSpot.SetSelected(const Value: Boolean);
var
  images: TTMSFMXHotSpotImage;
begin
  if (FSelected <> Value) then
  begin
    images := (Collection as TTMSFMXHotSpots).GeTTMSFMXHotSpotImage;
    if (images = nil) or (not Selectable and Value) then
      Exit;

    FSelected := Value;

    BlinkImageChange(BlinkImage);
    if FSelected then
    begin
      case images.SelectionMode of
        smSingleSelect:
        begin
          UpdateExclusive;
        end;
        smMultiSelectCtrl:
        begin
        end;
        smMultiSelect:
        begin
        end;
      end;
      images.Repaint;
    end
    else
      images.RestoreHotSpot(Index);
  end;
end;


procedure TTMSFMXHotSpot.SetClickFill(const Value: TBrush);
begin
  FClickFill.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetSelectedFill(const Value: TBrush);
begin
  FSelectedFill.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetSelectedImage(const Value: TBitmap);
begin
  FSelectedImage.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetSelectedImageName(const Value: String);
begin
  FSelectedImageName := Value;
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SetSelectedStroke(const Value: TBrush);
begin
  FSelectedStroke.Assign(Value);
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.BlinkImageChange(Sender: TObject);
begin
  ReCalcClips;
end;

procedure TTMSFMXHotSpot.SelectedImageChange(Sender: TObject);
begin
  ReCalcClips;
end;

{ TTMSFMXHotSpotImage }

constructor TTMSFMXHotSpotImage.Create(AOwner: TComponent);
begin
  inherited;
  Fill.DefaultColor := $FF000000;
  Fill.Color := $FF000000;

  {$IFDEF DELPHIXE6_LVL}
  Stroke.DefaultKind := TBrushKind.None;
  Stroke.Kind := TBrushKind.None;
  {$ELSE}
  Stroke.DefaultKind := TBrushKind.bkNone;
  Stroke.Kind := TBrushKind.bkNone;
  {$ENDIF}

  if AOwner is TControl then
    FOwner := TControl(AOwner)
  else
    FOwner := nil;

  FHotSpotCursor := crDefault;
  FHotSpots := TTMSFMXHotSpots.Create(Self);
  FHoveredItem := -1;
  FClickedItem := -1;
  FSelectionMode := smSingleSelect;

  FBlinkInterval := 100;
  FBlinkCount := 0;
  FTimer := TTimer.Create(Self);
  FTimer.Enabled := False;
  FTimer.Interval := BlinkInterval;
  FTimer.OnTimer := OnTimerTime;

  SetBounds(0, 0, 200, 200);
  FAngle := 0;
end;

destructor TTMSFMXHotSpotImage.Destroy;
begin
  FHotSpots.Free;
  FTimer.Free;
  inherited;
end;

procedure TTMSFMXHotSpotImage.Loaded;
begin
  inherited;
  FTimer.Interval := BlinkInterval;
  FTimer.Enabled := (not (csDesigning in ComponentState)) and (FTimer.Interval > 0);

  FOrigCursor := Cursor;
  SetPicture(Bitmap);
end;

procedure TTMSFMXHotSpotImage.LoadFromFile(FName: TFileName);
begin
  HotSpots.LoadFromFile(FName);
  Repaint;
end;

procedure TTMSFMXHotSpotImage.Paint;
var
  i: Integer;
  CR, R: TRectF;
begin
  CR := LocalRect;

  if Bitmap.IsEmpty then
  begin
    if (csDesigning in ComponentState) then
    begin
      Canvas.StrokeThickness := 1;
      {$IFDEF DELPHIXE6_LVL}
      Canvas.StrokeDash := TStrokeDash.Dash;
      Canvas.Stroke.Kind := TBrushKind.Solid;
      {$ELSE}
      Canvas.StrokeDash := TStrokeDash.sdDash;
      Canvas.Stroke.Kind := TBrushKind.bkSolid;
      {$ENDIF}
      Canvas.DrawRect(CR, 0, 0, AllCorners, AbsoluteOpacity);
      {$IFDEF DELPHIXE6_LVL}
      Canvas.StrokeDash := TStrokeDash.Solid;
      {$ELSE}
      Canvas.StrokeDash := TStrokeDash.sdSolid;
      {$ENDIF}
    end;

    if Assigned(OnPaint) then
      OnPaint(Self, Canvas, CR);
    Exit;
  end;

  FPainting := True;

  R.Left := 0;
  R.Top := 0;
  R.Width := Bitmap.Width;
  R.Height := Bitmap.Height;

  if FStretch then
    Canvas.DrawBitmap(Bitmap, R, CR, 1)
  else
    Canvas.DrawBitmap(Bitmap, R, R, 1);

  if (csDesigning in ComponentState) then
  begin
    Canvas.StrokeThickness := 2;
    Canvas.Stroke.Color := TAlphaColorRec.White;
    {$IFDEF DELPHIXE6_LVL}
    Canvas.Stroke.Kind := TBrushKind.Solid;
    Canvas.StrokeDash := TStrokeDash.Dash;
    Canvas.Fill.Kind := TBrushKind.None;
    {$ELSE}
    Canvas.Stroke.Kind := TBrushKind.bkSolid;
    Canvas.StrokeDash := TStrokeDash.sdDash;
    Canvas.Fill.Kind := TBrushKind.bkNone;
    {$ENDIF}
    for i := 0 to FHotSpots.Count-1 do
      FHotSpots[i].DrawShape(Canvas);
  end
  else
  begin
    if (FHoveredItem >= 0) and (FClickedItem = -1) and (not FHotSpots[FHoveredItem].FShowClick) and not FHotSpots[FHoveredItem].Selected and not FHotSpots[FHoveredItem].Blink then
      DrawImage(FHoveredItem, FHotSpots[FHoveredItem].HoverImage, FHotSpots[FHoveredItem].FCHover, FHotSpots[FHoveredItem].HoverStroke);

    if (FClickedItem <> -1) and not FHotSpots[FClickedItem].Blink then
      DrawImage(FClickedItem, FHotSpots[FClickedItem].ClickImage, FHotSpots[FClickedItem].FCClick, FHotSpots[FClickedItem].ClickStroke);

    for i := 1 to FHotSpots.Count do
    begin
      if HotSpots[i - 1].Blink then
      begin
        if (FBlinkCount = 0) then
          DrawImage(i - 1, FHotSpots[i-1].BlinkImage, FHotSpots[i-1].FCBlink, FHotSpots[i-1].BlinkStroke);
      end
      else if FHotSpots[i - 1].Down or FHotSpots[i - 1].ShowClick then
        DrawImage(i-1, FHotSpots[i-1].ClickImage, FHotSpots[i-1].FCClick, FHotSpots[i-1].ClickStroke)
      else if FHotSpots[i - 1].Selected and not FHotSpots[i-1].Blink and (FClickedItem <> i-1) then
        DrawImage(i-1, FHotSpots[i-1].SelectedImage, FHotSpots[i-1].FCSelect, FHotSpots[i-1].SelectedStroke);
    end;
  end;

  if Assigned(OnPaint) then
    OnPaint(Self, Canvas, CR);
end;

procedure TTMSFMXHotSpotImage.DrawImage(HotSpotIndex: Integer; AImage, ACachedImage: TBitmap; AStroke: TBrush);
var
  R: TRectF;
  tempPoints: TPolygon;
  i, j: Integer;
  okW, okH: Integer;
  Done: Boolean;
begin
  if (HotSpotIndex < 0) or (HotSpotIndex>= FHotSpots.Count) then
    Exit;

  Done := False;
  //clipRGN := 0;
  if FHotSpots[HotSpotIndex].Clipped then
  begin
    if FStretch then
    begin
      okW := FHotSpots[HotSpotIndex].Width;
      okH := FHotSpots[HotSpotIndex].Height;
    end
    else
    begin
      if not EmptyImage(AImage) then
      begin
        okW := min(Round(Width), Round(AImage.Width));
        okH := min(Round(Height), Round(AImage.Height));
      end
      else
      begin
        okW := FHotSpots[HotSpotIndex].Width;
        okH := FHotSpots[HotSpotIndex].Height;
      end;
    end;

    if FHotSpots[HotSpotIndex].FShape.FShapeType = stPolygon then
    begin
      j := High(FHotSpots[HotSpotIndex].FShape.Points);
      SetLength(tempPoints,(j+1));
      for i:=0 to j do
        begin
          tempPoints[i].x:=Round(FHotSpots[HotSpotIndex].FShape.Points[i].X);
          tempPoints[i].y:=Round(FHotSpots[HotSpotIndex].FShape.Points[i].Y);
        end;

      if not EmptyBitmap(ACachedImage) then
      begin
        Canvas.Fill.Bitmap.Bitmap.Assign(ACachedImage);
        {$IFDEF DELPHIXE6_LVL}
        Canvas.Fill.Kind := TBrushKind.Bitmap;
        {$ELSE}
        Canvas.Fill.Kind := TBrushKind.bkBitmap;
        {$ENDIF}
        Canvas.Stroke.Assign(AStroke);
        Canvas.FillPolygon(tempPoints, AbsoluteOpacity);
        SetLength(tempPoints,Length(tempPoints) + 1);
        tempPoints[Length(TempPoints)-1].X := tempPoints[0].X;
        tempPoints[Length(TempPoints)-1].Y := tempPoints[0].Y;
        Canvas.DrawPolygon(tempPoints, AbsoluteOpacity);
        Done := True;
      end;
    end
    else if FHotSpots[HotSpotIndex].FShape.FShapeType = stEllipse then
    begin
      if not EmptyBitmap(ACachedImage) then
      begin
        Canvas.Fill.Bitmap.Bitmap.Assign(ACachedImage);
        {$IFDEF DELPHIXE6_LVL}
        Canvas.Fill.Kind := TBrushKind.Bitmap;
        {$ELSE}
        Canvas.Fill.Kind := TBrushKind.bkBitmap;
        {$ENDIF}
        Canvas.Stroke.Assign(AStroke);
        Canvas.FillEllipse(RectF(FHotSpots[HotSpotIndex].X,FHotSpots[HotSpotIndex].Y, FHotSpots[HotSpotIndex].X + okW, FHotSpots[HotSpotIndex].Y + okH), AbsoluteOpacity);
        Canvas.DrawEllipse(RectF(FHotSpots[HotSpotIndex].X,FHotSpots[HotSpotIndex].Y, FHotSpots[HotSpotIndex].X + okW, FHotSpots[HotSpotIndex].Y + okH), AbsoluteOpacity);
        Done := True;
      end;
    end;
  end;

  if not Done then
  begin
    with FHotSpots[HotSpotIndex] do
      if not EmptyImage(AImage) then
      begin
        R := RectF(X, Y, X + AImage.Width, y + AImage.Height);
        Canvas.DrawBitmap(AImage, RectF(0, 0, AImage.Width, AImage.Height), R, 1);
      end
      else
      begin
        if not EmptyBitmap(ACachedImage) then
        begin
          R := RectF(X, Y, X + ACachedImage.Width, y + ACachedImage.Height);
          Canvas.DrawBitmap(ACachedImage, RectF(0, 0, ACachedImage.Width, ACachedImage.Height), R, 1);
        end;
      end;
  end;
  if not FPainting and Assigned(OnPaint) then
    OnPaint(Self, Canvas, LocalRect);
end;

procedure TTMSFMXHotSpotImage.RestoreHotSpot(HotSpotIndex: Integer);
begin
  Repaint;
end;

procedure TTMSFMXHotSpotImage.UpdateControlCanvas;
begin
  if not (csLoading in ComponentState) then
    if FUpdating = 0 then Repaint;
end;

procedure TTMSFMXHotSpotImage.SetBlinkInterval(const Value: Integer);
begin
  FBlinkInterval := Value;
  if not (csDesigning in ComponentState) then
  begin
    FTimer.Interval := FBlinkInterval;
    FTimer.Enabled := (not (csDesigning in ComponentState)) and (FTimer.Interval > 0);
  end;
end;

procedure TTMSFMXHotSpotImage.OnTimerTime(Sender: TObject);
begin
  UpdateHotSpotsBlinking;
end;

procedure TTMSFMXHotSpotImage.UpdateHotSpots;
begin
  HotSpots.ReCalclips;
end;

procedure TTMSFMXHotSpotImage.UpdateHotSpotsBlinking;
var
  i: Integer;
  found: Boolean;
begin
  if (csDesigning in ComponentState) then
    Exit;

  found := False;
  for i := 0 to FHotSpots.Count - 1 do
  begin
    if HotSpots[i].Blink then
    begin
      found := True;
    end;
  end;

  if FBlinkCount > 0 then
    FBlinkCount := 0
  else
    FBlinkCount := 1;

  if not found then
    FTimer.Enabled := False
  else
    Repaint;
end;



function TTMSFMXHotSpotImage.GetSelectedIndex: Integer;
var
  i: Integer;
begin
  Result := -1;
  for i := 0 to FHotSpots.Count - 1 do
  begin
    if FHotSpots[i].Selected then
    begin
      Result := i;
      Break;
    end;
  end;
end;



function TTMSFMXHotSpotImage.GetVersion: string;
begin

end;

function TTMSFMXHotSpotImage.GetHotSpotXY(x, y: Integer): TTMSFMXHotSpot;
var
  i: Integer;
begin
  Result := nil;
  for i := HotSpots.Count - 1 downto 0 do
    if HotSpots[i].GetHoverPos(X, Y) <> hpNone then
    begin
      Result := HotSpots[i];
      Exit;
    end;
end;

procedure TTMSFMXHotSpotImage.SaveToFile(FName: TFileName);
begin
  HotSpots.SaveToFile(FName);
end;

procedure TTMSFMXHotSpotImage.SetAutoSizeEx(const Value: Boolean);
begin
  FAutoSize := Value;
  if FAutoSize then Stretch:=false;
  if FAutoSize and Assigned (Bitmap) then
  begin
    if not Bitmap.IsEmpty then
    begin
      Width := Bitmap.Width;
      Height := Bitmap.Height;
    end;
  end;
end;


procedure TTMSFMXHotSpotImage.PictureChanged(Sender: TObject);
begin
  if FAutoSize then
    SetAutoSizeEx(FAutoSize);
  if not FStretch then
  begin
    HotSpots.oldHeight := Bitmap.Height;
    HotSpots.oldWidth := Bitmap.Width;
  end;
end;

procedure TTMSFMXHotSpotImage.SetColor(const Value: TAlphaColor);
begin

end;

procedure TTMSFMXHotSpotImage.SetTransparent(const Value: Boolean);
begin
  FTransparent := Value;
  Repaint;
end;

procedure TTMSFMXHotSpotImage.SetStretch(const Value: Boolean);
begin
  if FStretch = value then
    Exit;

  FStretch := Value;
  if FStretch then
  Begin
    AutoSize := false;
    if (csLoading in ComponentState) or (csReading in ComponentState) then
    Begin
      HotSpots.oldHeight := Round(Height);
      HotSpots.oldWidth := Round(Width);
    End
    else
    Begin
      HotSpots.oldHeight := Bitmap.Height;
      HotSpots.oldWidth := Bitmap.Width;
      HotSpots.ReScale(Round(Width), Round(Height));
    End;
  End
  else
    HotSpots.ReScale(Bitmap.Width, Bitmap.Height);
end;

procedure TTMSFMXHotSpotImage.Resize;
begin
  if FStretch then
    HotSpots.ReScale(Round(Width), Round(Height));
  inherited;
end;

procedure TTMSFMXHotSpotImage.DblClick;
var
  pt: TPoint;
  i: Integer;
  PM,PC1,PC2: TPoint;
  pf: TPointF;
  MouseService: IFMXMouseService;
begin
  inherited;
  isDblClick := true;

  if TPlatformServices.Current.SupportsPlatformService(IFMXMouseService, IInterface(MouseService)) then
    pf := MouseService.GetMousePos;

  pf := Scene.ScreenToLocal(pf);
  pt.X := Round(pf.X);
  pt.Y := Round(pf.Y);

  if FHoveredItem = -1 then
  begin
      PM.x := pt.X;
      PM.y := pt.Y;
      PC1.x := Round(Left);
      PC1.y := Round(Top);
      PC2.x := Round(Width);
      PC2.y := Round(Height);
      if (PM.x < PC1.x) or (PM.y < PC1.y) or (PM.x > PC2.x) or (PM.y > PC2.y)then
      begin
        HoveredItem := -1;
        Exit;
      end;

    for i := HotSpots.Count - 1 downto 0 do
      if HotSpots[i].GetHoverPos(pt.X,pt.Y) <> hpNone then
      begin
        HoveredItem := i;

        isMouseUp := False;
        FClickedItem := i;

        if Assigned(OnHotSpotDblClick) then
          FOnHotSpotDblClick(Self,HotSpots[i]);

        Exit;
      end;
    HoveredItem := -1;
  end
  else
  begin
    isMouseUp := False;
    FClickedItem := FHoveredItem;
    if Assigned(OnHotSpotDblClick) then
      FOnHotSpotDblClick(Self,HotSpots[FClickedItem]);
  end;
end;

procedure TTMSFMXHotSpotImage.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Single);
var
  i: Integer;
  PM,PC1,PC2: TPoint;
begin
  inherited;
  if isDblClick then
  begin
    isDblClick := False;
    Exit;
  end;

  if FHoveredItem = -1 then
  begin
      PM.x := Round(X);
      PM.y := Round(Y);
      PC1.x := Round(Left);
      PC1.y := Round(Top);
      PC2.x := Round(Width);
      PC2.y := Round(Height);
      if (PM.x < PC1.x) or (PM.y < PC1.y) or (PM.x > PC2.x) or (PM.y > PC2.y)then
      begin
        HoveredItem := -1;
        Exit;
      end;

    for i := HotSpots.Count - 1 downto 0 do
      if HotSpots[i].GetHoverPos(Round(X), Round(Y)) <> hpNone then
      begin
        HoveredItem := i;

        if (Button = TMouseButton.mbRight) then
          if Assigned(FOnHotSpotRightClick) then
            FOnHotSpotRightClick(Self,HotSpots[i]);

        if (Button = TMouseButton.mbLeft) then
        begin
          isMouseUp := False;
          FClickedItem := i;
          Repaint;
        end;

        Exit;
      end;
    HoveredItem := -1;
  end
  else
  begin
    if (Button = TMouseButton.mbRight) then
      if Assigned(FOnHotSpotRightClick) then
        FOnHotSpotRightClick(Self,HotSpots[HoveredItem]);

     if (Button = TMouseButton.mbLeft) then
     begin
       isMouseUp := False;
       FClickedItem := FHoveredItem;
       Repaint;
     end;
  end;
end;


procedure TTMSFMXHotSpotImage.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Single);
begin
  inherited;

  if isMouseUp then
    Exit
  else
    isMouseUp := True;

  if FClickedItem <> -1 then
  begin
    HotSpots[FClickedItem].InternalClick;

    if FHotSpots[FClickedItem].GetHoverPos(Round(X), Round(Y)) <> hpNone then
    begin
      if Assigned(FOnHotSpotClick) then
        FOnHotSpotClick(Self, FHotSpots[FClickedItem]);
      FClickedItem := -1;
    end
    else
    begin
      if Assigned(FOnHotSpotExit) then
        FOnHotSpotExit(Self, FHotSpots[FClickedItem]);
      FClickedItem := -1;
      FHoveredItem := -1;
    end;
  end;

  Repaint;
end;

procedure TTMSFMXHotSpotImage.MouseMove(Shift: TShiftState; X, Y: Single);
var
  i: Integer;
  PM,PC1,PC2: TPointF;
begin
  inherited;
  if (csDesigning in ComponentState) then
    Exit;

  PM.x := X;
  PM.y := Y;
  PC1.x := 0;
  PC1.y := 0;
  PC2.x := Width;
  PC2.y := Height;
  if (PM.x < PC1.x) or (PM.y < PC1.y) or (PM.x > PC2.x) or (PM.y > PC2.y) then
  begin
    HoveredItem := -1;
    Cursor := FOrigCursor;
    Exit;
  end;

  for i := HotSpots.Count - 1 downto 0 do
    if HotSpots[i].GetHoverPos(Round(X), Round(Y)) <> hpNone then
    begin
      HoveredItem := i;
      Cursor := FHotSpotCursor;
      Exit;
    end;

  Cursor := FOrigCursor;
  HoveredItem := -1;
end;


procedure TTMSFMXHotSpotImage.SetHotSpots(const Value: TTMSFMXHotSpots);
begin
  FHotSpots.Assign(Value);
  FHoveredItem := -1;
  FClickedItem := -1;
  Repaint;
end;


procedure TTMSFMXHotSpotImage.SetHoveredItem(const Value: Integer);
var
  i: integer;
  oldhovereditem: integer;
begin
  if FHoveredItem <> Value then
  begin
    if (FHoveredItem <> -1) and (FClickedItem = -1) then
    begin
      if not HotSpots[FHoveredItem].FShowClick then
        RestoreHotSpot(FHoveredItem);

      if Assigned(FOnHotSpotExit)
        then FOnHotSpotExit(Self, FHotSpots[FHoveredItem]);
    end;

    if (Value <> -1) and (FClickedItem = -1) then
    begin
      if not HotSpots[Value].FShowClick then
        Repaint;

      if Assigned(FOnHotSpotEnter) then
        FOnHotSpotEnter(Self, FHotSpots[Value]);
    end;

    oldhovereditem := FHoveredItem;

    FHoveredItem := Value;

    for i := 0 to HotSpots.Count - 1 do
    begin
      if HotSpots[i].Down and (i = OldHoveredItem) then
      begin
        Repaint;
        break;
      end;
    end;

  end;
end;

procedure TTMSFMXHotSpotImage.SetPicture(const Value: TBitmap);
var
  i: Integer;
begin
  if Bitmap.IsEmpty then
    Bitmap.Assign(Value);
  for i := 0 to FHotSpots.Count - 1 do
  begin
    FHotSpots[i].FPicture := Bitmap;
    if FHotSpots[i].Clipped then
    begin
      with FHotSpots[i] do
      begin
        ReCalcClips;
      end;
    end;
  end;
  if not FRotating then
    Repaint;
end;


initialization
  RegisterFmxClasses([TTMSFMXHotSpotImage]);

end.
