123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- unit LCSuperformulaOriginal;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils, BGRALayerOriginal, BGRABitmap, BGRABitmapTypes, Math,
- Types;
- const
- MaxDenominator = 20;
- type
- { TSuperformulaOriginal }
- TSuperformulaOriginal = class(TBGRALayerCustomOriginal)
- private
- FSpikeOverlap: boolean;
- Fa: double;
- Fb: double;
- FBackColor: TBGRAPixel;
- FPenColor: TBGRAPixel;
- FLineWidth: double;
- Fm: double;
- FMRational: boolean;
- FMultiplier: double;
- Fn1: double;
- Fn2: double;
- Fn3: double;
- FDiff: TBGRAOriginalStorageDiff;
- FUpdateCount: integer;
- function FloatToFraction(ARatio: single; out num, denom: integer; AMaxDenominator: integer): string;
- function GetRadius: double;
- function GetSize: double;
- function GetSizeWithoutMultiplier: double;
- procedure SetA(AValue: double);
- procedure SetB(AValue: double);
- procedure SetBackColor(AValue: TBGRAPixel);
- procedure SetPenColor(AValue: TBGRAPixel);
- procedure SetLineWidth(AValue: double);
- procedure SetM(AValue: double);
- procedure SetMultiplier(AValue: double);
- procedure SetN1(AValue: double);
- procedure SetN2(AValue: double);
- procedure SetN3(AValue: double);
- procedure SetMRational(AValue: boolean);
- procedure SetSize(AValue: double);
- procedure SetSpikeOverlap(AValue: boolean);
- protected
- procedure GetCurve(AMatrix: TAffineMatrix; out ABackPoints: ArrayOfTPointF;
- out APenOutlinePoints: ArrayOfTPointF);
- function PenVisible: boolean;
- function BackVisible: boolean;
- procedure BeginUpdate;
- procedure EndUpdate;
- public
- constructor Create; override;
- procedure Render(ADest: TBGRABitmap; AMatrix: TAffineMatrix;
- ADraft: boolean); override;
- function GetRenderBounds(ADestRect: TRect; {%H-}AMatrix: TAffineMatrix): TRect; override;
- procedure GetMFraction(out ANumerator, ADenominator: integer);
- procedure LoadFromStorage(AStorage: TBGRACustomOriginalStorage); override;
- procedure SaveToStorage(AStorage: TBGRACustomOriginalStorage); override;
- class function StorageClassName: RawByteString; override;
- property SpikeOverlap: boolean read FSpikeOverlap write SetSpikeOverlap;
- property a: double read Fa write SetA;
- property b: double read Fb write SetB;
- property m: double read Fm write SetM;
- property mRational: boolean read FMRational write SetMRational;
- property n1: double read Fn1 write SetN1;
- property n2: double read Fn2 write SetN2;
- property n3: double read Fn3 write SetN3;
- property Radius: double read GetRadius;
- property LineWidth: double read FLineWidth write SetLineWidth;
- property PenColor: TBGRAPixel read FPenColor write SetPenColor;
- property BackColor: TBGRAPixel read FBackColor write SetBackColor;
- property Multiplier: double read FMultiplier write SetMultiplier;
- property Size: double read GetSize write SetSize;
- end;
- implementation
- uses BGRATransform, BGRAPen, BGRAGraphics;
- var
- MAX_LOG: double = 0.0;
- MIN_LOG: double = 0.0;
- MAX_R: double = 100.0;
- function SafePower(a, b: double; out c: double): boolean;
- var
- tmp: double;
- begin
- if a < 0 then
- begin
- result := SafePower(-a, b, c);
- c := -c;
- exit;
- end;
- Result := True;
- if a = 0 then
- begin
- if b = 0 then
- c := 1
- else
- c := 0;
- exit;
- end;
- if MAX_LOG = 0.0 then
- MAX_LOG := ln(MaxDouble);
- if MIN_LOG = 0.0 then
- MIN_LOG := ln(MinDouble);
- // ln(a^b) = b ln(a)
- tmp := b * ln(a);
- if tmp > MAX_LOG then
- Result := False
- else
- if tmp < MIN_LOG then
- c := 0.0
- else
- c := exp(tmp);
- end;
- function ComputeR(theta, a, b, m, n1, n2, n3: double): double;
- const
- EPS = 1E-9;
- var
- c, pc, s, ps: double;
- begin
- if (a = 0) or (b = 0) or (m = 0) or (n1 = 0) or (n2 = 0) or (n3 = 0) then
- exit(0);
- c := abs(cos(m * theta / 4) / a);
- if c < EPS then
- pc := 0
- else
- if not SafePower(c, n2, pc) then
- begin
- Result := MAX_R;
- exit;
- end;
- s := abs(sin(m * theta / 4) / b);
- if s < EPS then
- ps := 0
- else
- if not SafePower(s, n3, ps) then
- begin
- Result := MAX_R;
- exit;
- end;
- if pc + ps < EPS then
- Result := 0
- else
- if not SafePower(pc + ps, -1 / n1, Result) then
- Result := MAX_R;
- if Result > MAX_R then
- Result := MAX_R;
- end;
- { TSuperformulaOriginal }
- procedure TSuperformulaOriginal.SetA(AValue: double);
- begin
- if Fa = AValue then
- Exit;
- BeginUpdate;
- Fa := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetB(AValue: double);
- begin
- if Fb = AValue then
- Exit;
- BeginUpdate;
- Fb := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetBackColor(AValue: TBGRAPixel);
- begin
- if FBackColor = AValue then
- Exit;
- BeginUpdate;
- FBackColor := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetPenColor(AValue: TBGRAPixel);
- begin
- if FPenColor = AValue then
- Exit;
- BeginUpdate;
- FPenColor := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetLineWidth(AValue: double);
- begin
- if FLineWidth = AValue then
- Exit;
- BeginUpdate;
- FLineWidth := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetM(AValue: double);
- begin
- if Fm = AValue then
- Exit;
- BeginUpdate;
- Fm := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetMultiplier(AValue: double);
- begin
- if FMultiplier = AValue then
- Exit;
- BeginUpdate;
- FMultiplier := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetMRational(AValue: boolean);
- begin
- if FMRational=AValue then Exit;
- BeginUpdate;
- FMRational:=AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetSize(AValue: double);
- var
- curSizeWithoutMultiplier: Double;
- begin
- curSizeWithoutMultiplier := GetSizeWithoutMultiplier;
- if curSizeWithoutMultiplier = 0 then exit;
- Multiplier:= AValue/curSizeWithoutMultiplier;
- end;
- procedure TSuperformulaOriginal.SetN1(AValue: double);
- begin
- if Fn1 = AValue then
- Exit;
- BeginUpdate;
- Fn1 := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetN2(AValue: double);
- begin
- if Fn2 = AValue then
- Exit;
- BeginUpdate;
- Fn2 := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetN3(AValue: double);
- begin
- if Fn3 = AValue then
- Exit;
- BeginUpdate;
- Fn3 := AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.SetSpikeOverlap(AValue: boolean);
- begin
- if FSpikeOverlap=AValue then Exit;
- BeginUpdate;
- FSpikeOverlap:=AValue;
- EndUpdate;
- end;
- procedure TSuperformulaOriginal.GetCurve(AMatrix: TAffineMatrix;
- out ABackPoints: ArrayOfTPointF; out APenOutlinePoints: ArrayOfTPointF);
- var
- i, num, denom, precision, turns: integer;
- r, theta, usedM, approxM, correction: double;
- stroker: TBGRACustomPenStroker;
- begin
- ABackPoints := nil;
- APenOutlinePoints := nil;
- FloatToFraction(m, num, denom, MaxDenominator);
- approxM := num/denom;
- precision := max(num * 100, 100 * 3);
- if precision > 3000 then
- precision := (3000 div num)*num;
- if mRational then
- begin
- usedM := approxM;
- correction := 1;
- end else
- begin
- usedM:= m;
- correction := approxM / m;
- end;
- turns := denom * (1 + integer(SpikeOverlap and odd(num) and ((a <> b) or (n2 <> n3))));
- SetLength(ABackPoints, precision * turns);
- for i := 0 to precision * turns - 1 do
- begin
- theta := i * 2 * Pi * correction / precision;
- r := ComputeR(theta, a, b, usedM, n1, n2, n3) * multiplier;
- ABackPoints[i] := AMatrix * PointF(r * cos(theta), r * sin(theta));
- end;
- if PenVisible then
- begin
- stroker := TBGRAPenStroker.Create;
- try
- stroker.StrokeMatrix := AMatrix;
- stroker.JoinStyle := pjsMiter;
- APenOutlinePoints := stroker.ComputePolygon(ABackPoints, LineWidth);
- finally
- stroker.Free;
- end;
- end;
- if not BackVisible then
- ABackPoints := nil;
- end;
- function TSuperformulaOriginal.PenVisible: boolean;
- begin
- result := (LineWidth > 0) and (PenColor.alpha > 0);
- end;
- function TSuperformulaOriginal.BackVisible: boolean;
- begin
- result := BackColor.alpha > 0;
- end;
- procedure TSuperformulaOriginal.BeginUpdate;
- begin
- if FUpdateCount = 0 then
- begin
- FDiff := TBGRAOriginalStorageDiff.Create(self);
- end;
- Inc(FUpdateCount);
- end;
- procedure TSuperformulaOriginal.EndUpdate;
- begin
- if FUpdateCount = 0 then exit;
- Dec(FUpdateCount);
- if FUpdateCount = 0 then
- begin
- if Assigned(FDiff) then
- FDiff.ComputeDifference(self);
- NotifyChange(FDiff);
- FDiff := nil;
- end;
- end;
- constructor TSuperformulaOriginal.Create;
- begin
- inherited Create;
- FSpikeOverlap:= true;
- Fa := 1;
- Fb := 1;
- Fm := 24;
- Fn1 := 2;
- Fn2 := 1;
- Fn3 := 2;
- FLineWidth := 0;
- FPenColor := BGRA($00, $40, $80);
- FBackColor := BGRA($00, $80, $C0);
- FMultiplier := 200;
- FMRational:= true;
- FUpdateCount:= 0;
- FDiff := nil;
- end;
- function TSuperformulaOriginal.FloatToFraction(ARatio: single; out num,
- denom: integer; AMaxDenominator: integer): string;
- procedure InvFrac;
- var temp: integer;
- begin
- temp := num;
- num := denom;
- denom := temp;
- end;
- procedure AddFrac(AValue: integer);
- begin
- inc(num, AValue*denom);
- end;
- const MaxDev = 6;
- var
- dev: array[1..MaxDev] of integer;
- devCount, i: integer;
- curVal, remain: Single;
- begin
- if ARatio < 0 then ARatio := -ARatio;
- curVal := ARatio;
- devCount := 0;
- repeat
- inc(devCount);
- dev[devCount] := trunc(curVal);
- remain := frac(curVal);
- if abs(remain) < 1e-3 then break;
- if devCount = MaxDev then
- begin
- if remain > 0.5 then inc(dev[devCount]);
- break;
- end;
- curVal := 1/remain;
- until false;
- repeat
- num := dev[devCount];
- denom := 1;
- for i := devCount-1 downto 1 do
- begin
- InvFrac;
- AddFrac(dev[i]);
- end;
- if ((num >= denom) and (denom <= AMaxDenominator))
- or ((num < denom) and (num <= AMaxDenominator))
- or (devCount = 1) then break;
- dec(devCount);
- until false;
- result := IntToStr(num)+':'+IntToStr(denom);
- end;
- function TSuperformulaOriginal.GetRadius: double;
- begin
- result := 1;
- end;
- function TSuperformulaOriginal.GetSize: double;
- begin
- result := GetSizeWithoutMultiplier * Multiplier;
- end;
- function TSuperformulaOriginal.GetSizeWithoutMultiplier: double;
- const SizePrecision = 50;
- var
- r, factor: Double;
- i: Integer;
- begin
- if m = 0 then exit(0);
- factor := 2*Pi/m/SizePrecision;
- r := 0;
- for i := 0 to SizePrecision-1 do
- begin
- r += ComputeR(i * factor, a, b, m, n1, n2, n3);
- end;
- r /= SizePrecision;
- result := r;
- end;
- procedure TSuperformulaOriginal.Render(ADest: TBGRABitmap;
- AMatrix: TAffineMatrix; ADraft: boolean);
- var
- backPoints, penOutlinePoints: ArrayOfTPointF;
- begin
- try
- GetCurve(AMatrix, backPoints, penOutlinePoints);
- if ADraft then
- begin
- if backPoints <> nil then
- ADest.FillPoly(backPoints, FBackColor, dmDrawWithTransparency, false);
- if penOutlinePoints <> nil then
- ADest.FillPoly(penOutlinePoints, FPenColor, dmDrawWithTransparency, false);
- end else
- begin
- if backPoints <> nil then
- ADest.FillPolyAntialias(backPoints, FBackColor, false);
- if penOutlinePoints <> nil then
- ADest.FillPolyAntialias(penOutlinePoints, FPenColor, false);
- end;
- except
- // ignore exceptions
- end;
- end;
- function TSuperformulaOriginal.GetRenderBounds(ADestRect: TRect;
- AMatrix: TAffineMatrix): TRect;
- var
- backPoints, penOutlinePoints: ArrayOfTPointF;
- resultF: TRectF;
- ptF: TPointF;
- begin
- GetCurve(AMatrix, backPoints, penOutlinePoints);
- if (backPoints = nil) and (penOutlinePoints = nil) then
- exit(EmptyRect);
- resultF.Left := MaxSingle;
- resultF.Top := MaxSingle;
- resultF.Right := -MaxSingle;
- resultF.Bottom := -MaxSingle;
- if backPoints <> nil then
- for ptF in backPoints do resultF.Include(ptF);
- if penOutlinePoints <> nil then
- for ptF in penOutlinePoints do resultF.Include(ptF);
- result.Left := floor(resultF.Left);
- result.Top := floor(resultF.Top);
- result.Right := ceil(resultF.Right);
- result.Bottom := ceil(resultF.Bottom);
- end;
- procedure TSuperformulaOriginal.GetMFraction(out ANumerator,
- ADenominator: integer);
- begin
- FloatToFraction(m, ANumerator, ADenominator, MaxDenominator);
- end;
- procedure TSuperformulaOriginal.LoadFromStorage(
- AStorage: TBGRACustomOriginalStorage);
- begin
- FSpikeOverlap:= AStorage.BoolDef['spike-overlap', false];
- Fa := AStorage.Float['a'];
- Fb := AStorage.Float['b'];
- Fm := AStorage.Float['m'];
- FMRational:= AStorage.BoolDef['m-rational', true];
- Fn1 := AStorage.Float['n1'];
- Fn2 := AStorage.Float['n2'];
- Fn3 := AStorage.Float['n3'];
- FMultiplier := AStorage.Float['multiplier'];
- FLineWidth := AStorage.Float['line-width'];
- FPenColor := AStorage.Color['pen-color'];
- FBackColor := AStorage.Color['back-color'];
- end;
- procedure TSuperformulaOriginal.SaveToStorage(
- AStorage: TBGRACustomOriginalStorage);
- begin
- AStorage.Bool['spike-overlap'] := FSpikeOverlap;
- AStorage.Float['a'] := Fa;
- AStorage.Float['b'] := Fb;
- AStorage.Float['m'] := Fm;
- AStorage.Bool['m-rational'] := FMRational;
- AStorage.Float['n1'] := Fn1;
- AStorage.Float['n2'] := Fn2;
- AStorage.Float['n3'] := Fn3;
- AStorage.Float['multiplier'] := FMultiplier;
- AStorage.Float['line-width'] := FLineWidth;
- AStorage.Color['pen-color'] := FPenColor;
- AStorage.Color['back-color'] := FBackColor;
- end;
- class function TSuperformulaOriginal.StorageClassName: RawByteString;
- begin
- Result := 'superformula';
- end;
- initialization
- RegisterLayerOriginal(TSuperformulaOriginal);
- end.
|