{
    $Id: ellipses.pp,v 1.1 2003/10/27 10:33:00 marco Exp $
    This file is part of the Free Pascal run time library.
    Copyright (c) 2003 by the Free Pascal development team

    Drawing of ellipses and arcs, and filling ellipses and pies.
    
    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}
{$mode objfpc}
unit Ellipses;

interface

uses classes, FPImage, FPCanvas;

procedure DrawSolidEllipse (Canv:TFPCustomCanvas; const Bounds:TRect; c:TFPColor);
procedure DrawSolidEllipse (Canv:TFPCustomCanvas; const Bounds:TRect; Width:integer; c:TFPColor);
procedure DrawPatternEllipse (Canv:TFPCustomCanvas; const Bounds:TRect; Pattern:TPenPattern; c:TFPColor);
procedure FillEllipseColor (Canv:TFPCustomCanvas; const Bounds:TRect; c:TFPColor);
procedure FillEllipsePattern (Canv:TFPCustomCanvas; const Bounds:TRect; Pattern:TBrushPattern; c:TFPColor);
procedure FillEllipseHashHorizontal (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
procedure FillEllipseHashVertical (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
procedure FillEllipseHashDiagonal (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
procedure FillEllipseHashBackDiagonal (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
procedure FillEllipseHashDiagCross (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
procedure FillEllipseHashCross (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
procedure FillEllipseImage (Canv:TFPCustomCanvas; const Bounds:TRect; const Image:TFPCustomImage);
procedure FillEllipseImageRel (Canv:TFPCustomCanvas; const Bounds:TRect; const Image:TFPCustomImage);

type

  PEllipseInfoData = ^TEllipseInfoData;
  TEllipseInfoData = record
    x, ytopmax, ytopmin, ybotmax, ybotmin : integer;
    OnlyTop : boolean;
  end;

  TEllipseInfo = class
  private
    fcx, fcy, frx,fry,
    fa1, fa2, frot : real;
    fx1,fy1, fx2,fy2 : integer;
    InfoList : TList;
    procedure FreeList;
    procedure ClearList;
    function FindXIndex (x:integer) : integer;
    procedure PrepareCalculation (var np:integer; var delta:real);
    function NewInfoRec (anX:integer) : PEllipseInfoData;
    procedure CalculateCircular (const b:TRect; var x,y,rx,ry:real);
  public
    constructor create;
    destructor destroy; override;
    function GetInfoForX (x:integer; var ytopmax,ytopmin,ybotmax,ybotmin:integer):boolean;
    function GetInfoForX (x:integer; var Info:PEllipseInfoData):boolean;
    procedure GatherEllipseInfo (const bounds:TRect);
    procedure GatherArcInfo (const bounds:TRect; alpha1,alpha2:real);
    property cx : real read fcx; // center point
    property cy : real read fcy;
    property rhor : real read frx; // radius
    property rver : real read fry;
    { only usable when created with GatherArcInfo }
    property a1 : real read fa1;    // angle 1 and point on ellipse
    property x1 : integer read fx1;
    property y1 : integer read fy1;
    property a2 : real read fa2;    // angle 2 and point on ellipse
    property x2 : integer read fx2;
    property y2 : integer read fy2;
  end;

implementation

constructor TEllipseInfo.Create;
begin
  inherited;
  InfoList := TList.Create;
end;

destructor TEllipseInfo.Destroy;
begin
  FreeList;
  inherited;
end;

procedure TEllipseInfo.ClearList;
var r : integer;
    d : PEllipseInfoData;
begin
  if assigned (InfoList) then
    begin
    for r := 0 to infolist.count-1 do
      begin
      d := PEllipseInfoData(InfoList[r]);
      dispose (d);
      end;
    InfoList.clear;
    end;
end;

procedure TEllipseInfo.FreeList;
begin
  if assigned (InfoList) then
    begin
    ClearList;
    InfoList.Free;
    InfoList := nil;
    end;
end;

function TEllipseInfo.GetInfoForX (x:integer; var ytopmax,ytopmin,ybotmax,ybotmin:integer):boolean;
var r : PEllipseInfoData;
begin
  result := GetInfoForX (x, r);
  if assigned(r) then
    begin
    ytopmax := ytopmax;
    ytopmin := ytopmin;
    ybotmax := ybotmax;
    ybotmin := ybotmin;
    end;
end;

function TEllipseInfo.FindXIndex (x : integer) : integer;
begin
  result := InfoList.Count;
  repeat
    dec (result);
  until (result < 0) or (x = PEllipseInfoData(InfoList[result])^.x);
end;

function TEllipseInfo.GetInfoForX (x:integer; var Info:PEllipseInfoData):boolean;
var r : integer;
begin
  r := FindXIndex (x);
  result := (r >= 0);
  if result then
    Info := PEllipseInfoData(InfoList[r])
end;

procedure TEllipseInfo.PrepareCalculation (var np:integer; var delta:real);
begin
  np := round(1.5708 * sqrt(sqr(frx)+sqr(fry)) );
  // number of pixel in quarter circel to calculate without gaps in drawing
  delta := pi / (2 * np);
end;

function TEllipseInfo.NewInfoRec (anX:integer) : PEllipseInfoData;
begin
  new (result);
  result^.x := anX;
  infolist.Add (result);
  with result^ do
    begin
    ytopmax := -1;
    ytopmin := maxint;
    ybotmax := -1;
    ybotmin := maxint;
    end;
end;

procedure TEllipseInfo.CalculateCircular (const b:TRect; var x,y,rx,ry:real);
begin
  with b do
    begin
    x := (right+left) / 2;
    y := (top+bottom) / 2;
    rx := abs(right-left) / 2;
    ry := abs(bottom-top) / 2;
    end;
end;

procedure TEllipseInfo.GatherEllipseInfo (const bounds:TRect);
var infoP, infoM : PEllipseInfoData;
    halfnumber,
    r, NumberPixels, xtemp,yt,yb : integer;
    pPy, pMy, x,y, rx,ry, xd,yd,ra, rdelta : real;
begin
  ClearList;
  CalculateCircular (bounds, x,y,rx,ry);
  with bounds do
  fcx := x;
  fcy := y;
  frx := rx;
  fry := ry;
  if (rx < 0.5) and (ry < 0.5) then
    with NewInfoRec (round(x))^ do
      begin
      ytopmax := round(y);
      ytopmin := ytopmax;
      ybotmax := ytopmax;
      ybotmin := ytopmax;
      end
  else
    begin
    PrepareCalculation (NumberPixels, rdelta);
    halfnumber := NumberPixels div 2;
    pPy := maxint;
    pMy := maxint;
    ra := 0;
    infoP := NewInfoRec (round(x + rx));
    infoM := NewInfoRec (round(x - rx));
    for r := 0 to NumberPixels do
      begin
      xd := rx * cos(ra);
      yd := ry * sin(ra);
      // take all 4 quarters
      yt := round(y - yd);
      yb := round(y + yd);
      xtemp := round (x + xd);
      // quarter 1 and 4 at the same x line
      if infoP^.x <> xtemp then                  // has correct record ?
        begin
        with infoP^ do                           // ensure single width
          begin
          if r < halfnumber then
            begin
            if ytopmin = yt then
              begin
              inc (ytopmin);
              dec (ybotmax);
              end;
            end
          else
            begin
            if (ytopmax = pPy) and (ytopmax <> ytopmin) then
              begin
              dec (ytopmax);
              inc (ybotmin);
              end;
            end;
          pPy := ytopmin;
          end;
        if not GetInfoForX (xtemp, infoP) then  // record exists already ?
          infoP := NewInfoRec (xtemp);          // create a new recod
        end;
      // lower y is top, min is lowest
      with InfoP^ do
        begin
        if yt < ytopmin then
          ytopmin := yt;
        if yb < ybotmin then
          ybotmin := yb;
        if yt > ytopmax then
          ytopmax := yt;
        if yb > ybotmax then
          ybotmax := yb;
        end;
      // quarter 2 and 3 on the same x line
      xtemp := round(x - xd);
      if infoM^.x <> xtemp then                  // has correct record ?
        begin
        with infoM^ do             // ensure single width
          begin
          if r < halfnumber then
            begin
            if ytopmin = yt then
              begin
              inc (ytopmin);
              dec (ybotmax);
              end;
            end
          else
            begin
            if (ytopmax = pMy) and (ytopmax <> ytopmin) then
              begin
              dec (ytopmax);
              inc (ybotmin);
              end;
            end;
          pMy := ytopmin;
          end;
        if not GetInfoForX (xtemp, infoM) then  // record exists already ?
          infoM := NewInfoRec (xtemp);          // create a new recod
        end;
      // lower y is top, min is lowest
      with InfoM^ do
        begin
        if yt < ytopmin then
          ytopmin := yt;
        if yb < ybotmin then
          ybotmin := yb;
        if yt > ytopmax then
          ytopmax := yt;
        if yb > ybotmax then
          ybotmax := yb;
        end;
      ra := ra + rdelta;
      end;
    end;
end;

procedure TEllipseInfo.GatherArcInfo (const bounds:TRect; alpha1,alpha2:real);
var stAngle,endAngle:real;

  procedure CheckAngles;
  begin
    if a1 < a2 then
      begin
      stAngle := a1;
      endAngle := a2;
      end
    else
      begin
      stAngle := a2;
      endAngle := a1;
      end;
  end;

begin
end;

{ The drawing routines }

type
  TPutPixelProc = procedure (Canv:TFPCustomCanvas; x,y:integer; color:TFPColor);
  TLinePoints = array[0..PatternBitCount-1] of boolean;
  PLinePoints = ^TLinePoints;

procedure PatternToPoints (const APattern:TPenPattern; LinePoints:PLinePoints);
var r : integer;
    i : longword;
begin
  i := 1;
  for r := PatternBitCount-1 downto 1 do
    begin
    LinePoints^[r] := (APattern and i) <> 0;
    i := i shl 1;
    end;
  LinePoints^[0] := (APattern and i) <> 0;
end;

procedure PutPixelCopy(Canv:TFPCustomCanvas; x,y:integer; color:TFPColor);
begin
  with Canv do
    Colors[x,y] := color;
end;

procedure PutPixelXor(Canv:TFPCustomCanvas; x,y:integer; color:TFPColor);
begin
  with Canv do
    Colors[x,y] := Colors[x,y] xor color;
end;

procedure PutPixelOr(Canv:TFPCustomCanvas; x,y:integer; color:TFPColor);
begin
  with Canv do
    Colors[x,y] := Colors[x,y] or color;
end;

procedure PutPixelAnd(Canv:TFPCustomCanvas; x,y:integer; color:TFPColor);
begin
  with Canv do
    Colors[x,y] := Colors[x,y] and color;
end;

procedure DrawSolidEllipse (Canv:TFPCustomCanvas; const Bounds:TRect; c:TFPColor);
var info : TEllipseInfo;
    r, y : integer;
    MyPutPix : TPutPixelProc;
begin
  with canv.pen do
    case mode of
      pmAnd : MyPutPix := @PutPixelAnd;
      pmOr : MyPutPix := @PutPixelOr;
      pmXor : MyPutPix := @PutPixelXor;
      else MyPutPix := @PutPixelCopy;
    end;
  info := TEllipseInfo.Create;
  with Canv, info do
    try
      GatherEllipseInfo (bounds);
      for r := 0 to InfoList.count-1 do
        with PEllipseInfoData(InfoList[r])^ do
          begin
          for y := ytopmin to ytopmax do
            MyPutPix (Canv, x,y, c);
          for y := ybotmin to ybotmax do
            MyPutPix (Canv, x,y, c);
          end;
    finally
      info.Free;
    end;
end;

procedure DrawSolidEllipse (Canv:TFPCustomCanvas; const Bounds:TRect; Width:integer; c:TFPColor);
var infoOut, infoIn : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
    MyPutPix : TPutPixelProc;
begin
  with canv.pen do
    case mode of
      pmAnd : MyPutPix := @PutPixelAnd;
      pmOr : MyPutPix := @PutPixelOr;
      pmXor : MyPutPix := @PutPixelXor;
      else MyPutPix := @PutPixelCopy;
    end;
  infoIn := TEllipseInfo.Create;
  infoOut := TEllipseInfo.Create;
  dec (width);
  try
    infoOut.GatherEllipseInfo(bounds);
    with bounds do
      infoIn.GatherEllipseInfo (Rect(left+width,top+width,right-width,bottom-width));
    with Canv do
      for r := 0 to infoOut.infolist.count-1 do
        with PEllipseInfoData (infoOut.infolist[r])^ do
          begin
          if infoIn.GetInfoForX (x, id) then
            begin
            for y := ytopmin to id^.ytopmax do
              MyPutPix (canv, x,y, c);
            for y := id^.ybotmin to ybotmax do
              MyPutPix (canv, x,y, c);
            end
          else
            begin // no inner circle found: draw all points between top and bottom
            for y := ytopmin to ybotmax do
              MyPutPix (canv, x,y, c);
            end;
          end;
    finally
      infoOut.Free;
      infoIn.Free;
    end;
end;

procedure DrawPatternEllipse (Canv:TFPCustomCanvas; const Bounds:TRect; Pattern:TPenPattern; c:TFPColor);
var info : TEllipseInfo;
    xx, y : integer;
    LinePoints : TLinePoints;
    MyPutPix : TPutPixelProc;
    id : PEllipseInfoData;
    CountDown, CountUp, half : integer;
begin
  with canv.pen do
    case mode of
      pmAnd : MyPutPix := @PutPixelAnd;
      pmOr : MyPutPix := @PutPixelOr;
      pmXor : MyPutPix := @PutPixelXor;
      else MyPutPix := @PutPixelCopy;
    end;
  PatternToPoints (pattern, @LinePoints);
  info := TEllipseInfo.Create;
  with Canv, info do
    try
      GatherEllipseInfo (bounds);
      CountUp := 0;
      CountDown := PatternBitCount - 1;
      half := round (cx);
      for xx := bounds.left to half do
        if GetInfoForX (xx, id) then
          begin
          with id^ do
            begin
            for y := ytopmax downto ytopmin do
              begin
              if LinePoints[CountUp mod PatternBitCount] then
                MyPutPix (Canv, xx,y, c);
              inc (CountUp);
              end;
            for y := ybotmin to ybotmax do
              begin
              if LinePoints[PatternBitCount - (CountDown mod PatternBitCount) - 1] then
                MyPutPix (Canv, xx,y, c);
              inc (CountDown);
              end;
            end;
          end;
      for xx := half+1 to bounds.right do
        if GetInfoForX (xx, id) then
          begin
          with id^ do
            begin
            for y := ytopmin to ytopmax do
              begin
              if LinePoints[CountUp mod PatternBitCount] then
                MyPutPix (Canv, xx,y, c);
              inc (CountUp);
              end;
            for y := ybotmax downto ybotmin do
              begin
              if LinePoints[Patternbitcount - (CountDown mod PatternBitCount) - 1] then
                MyPutPix (Canv, xx,y, c);
              inc (CountDown);
              end;
            end;
          end;
    finally
      info.Free;
    end;
end;

procedure FillEllipseColor (Canv:TFPCustomCanvas; const Bounds:TRect; c:TFPColor);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
begin
  info := TEllipseInfo.Create;
  try
    info.GatherEllipseInfo(bounds);
    with Canv do
      for r := 0 to info.infolist.count-1 do
        with PEllipseInfoData (info.infolist[r])^ do
          for y := ytopmin to ybotmax do
            colors[x,y] := c;
  finally
    info.Free;
  end;
end;

procedure FillEllipsePattern (Canv:TFPCustomCanvas; const Bounds:TRect; Pattern:TBrushPattern; c:TFPColor);
begin
end;

procedure FillEllipseHashHorizontal (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
begin
  info := TEllipseInfo.Create;
  try
    info.GatherEllipseInfo(bounds);
    for r := 0 to info.infolist.count-1 do
      with PEllipseInfoData (info.infolist[r])^ do
        for y := ytopmin to ybotmax do
          if (y mod width) = 0 then
            canv.colors[x,y] := c;
  finally
    info.Free;
  end;
end;

procedure FillEllipseHashVertical (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
begin
  info := TEllipseInfo.Create;
  try
    info.GatherEllipseInfo(bounds);
    for r := 0 to info.infolist.count-1 do
      with PEllipseInfoData (info.infolist[r])^ do
        if (x mod width) = 0 then
          for y := ytopmin to ybotmax do
            canv.colors[x,y] := c;
  finally
    info.Free;
  end;
end;

procedure FillEllipseHashDiagonal (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
    w : integer;
begin
  info := TEllipseInfo.Create;
  try
    info.GatherEllipseInfo(bounds);
    for r := 0 to info.infolist.count-1 do
      with PEllipseInfoData (info.infolist[r])^ do
        begin
        w := width - 1 - (x mod width);
        for y := ytopmin to ybotmax do
          if (y mod width) = w then
            canv.colors[x,y] := c;
        end;
  finally
    info.Free;
  end;
end;

procedure FillEllipseHashBackDiagonal (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
    w : integer;
begin
  info := TEllipseInfo.Create;
  try
    info.GatherEllipseInfo(bounds);
    for r := 0 to info.infolist.count-1 do
      with PEllipseInfoData (info.infolist[r])^ do
        begin
        w := (x mod width);
        for y := ytopmin to ybotmax do
          if (y mod width) = w then
            canv.colors[x,y] := c;
        end;
  finally
    info.Free;
  end;
end;

procedure FillEllipseHashDiagCross (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
    wy,w1,w2 : integer;
begin
  info := TEllipseInfo.Create;
  try
    info.GatherEllipseInfo(bounds);
    for r := 0 to info.infolist.count-1 do
      with PEllipseInfoData (info.infolist[r])^ do
        begin
        w1 := (x mod width);
        w2 := width - 1 - (x mod width);
        for y := ytopmin to ybotmax do
          begin
          wy := y mod width;
          if (wy = w1) or (wy = w2) then
            canv.colors[x,y] := c;
          end;
        end;
  finally
    info.Free;
  end;
end;

procedure FillEllipseHashCross (Canv:TFPCustomCanvas; const Bounds:TRect; width:integer; const c:TFPColor);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
begin
  info := TEllipseInfo.Create;
  try
    info.GatherEllipseInfo(bounds);
    for r := 0 to info.infolist.count-1 do
      with PEllipseInfoData (info.infolist[r])^ do
        if (x mod width) = 0 then
          for y := ytopmin to ybotmax do
            canv.colors[x,y] := c
        else
          for y := ytopmin to ybotmax do
            if (y mod width) = 0 then
              canv.colors[x,y] := c;
  finally
    info.Free;
  end;
end;

procedure FillEllipseImage (Canv:TFPCustomCanvas; const Bounds:TRect; const Image:TFPCustomImage);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
    w : integer;
begin
  info := TEllipseInfo.Create;
  try
    info.GatherEllipseInfo(bounds);
    for r := 0 to info.infolist.count-1 do
      with PEllipseInfoData (info.infolist[r])^ do
        begin
        w := (x mod image.width);
        for y := ytopmin to ybotmax do
          canv.colors[x,y] := Image.colors[w, (y mod image.height)];
        end;
  finally
    info.Free;
  end;
end;

procedure FillEllipseImageRel (Canv:TFPCustomCanvas; const Bounds:TRect; const Image:TFPCustomImage);
var info : TEllipseInfo;
    r, y : integer;
    id : PEllipseInfoData;
    xo,yo, xi,yi : integer;
begin
  info := TEllipseInfo.Create;
  try
    with info do
      begin
      GatherEllipseInfo(bounds);
      xo := round(cx) - (image.width div 2);
      yo := round(cy) - (image.height div 2);
      end;
    for r := 0 to info.infolist.count-1 do
      with PEllipseInfoData (info.infolist[r])^ do
        begin
        xi := (x - xo) mod image.width;
        if xi < 0 then
          inc (xi, image.width);
        for y := ytopmin to ybotmax do
          begin
          yi := (y - yo) mod image.height;
          if yi < 0 then
            inc (yi, image.height);
          canv.colors[x,y] := Image.colors[xi, yi];
          end;
        end;
  finally
    info.Free;
  end;
end;

end.
