Skip to content

Instantly share code, notes, and snippets.

@viniciusfbb
Last active June 13, 2022 03:11
Show Gist options
  • Save viniciusfbb/7a85025bc787a83e0b445a38008a237f to your computer and use it in GitHub Desktop.
Save viniciusfbb/7a85025bc787a83e0b445a38008a237f to your computer and use it in GitHub Desktop.
[Skia4Delphi] Paint gradient color following a stroke path
unit StrokePathGradient;
interface
uses
System.UITypes, Skia;
procedure DrawPathEndSmothing(const ACanvas: ISkCanvas; const APath: ISkPath;
const APaint: ISkPaint; const AInnerColor, AOuterColor: TAlphaColor;
AStart, AStop: Single; const AInterpolations: Integer);
implementation
uses
System.Types, System.Math, System.Math.Vectors;
procedure DrawPathEndSmothing(const ACanvas: ISkCanvas; const APath: ISkPath;
const APaint: ISkPaint; const AInnerColor, AOuterColor: TAlphaColor;
AStart, AStop: Single; const AInterpolations: Integer);
var
LDrawBounds: TRectF;
function InterpolateColor(const Start, Stop: TAlphaColor; T: Single): TAlphaColor;
begin
TAlphaColorRec(Result).A := TAlphaColorRec(Start).A + Trunc((TAlphaColorRec(Stop).A - TAlphaColorRec(Start).A) * T);
TAlphaColorRec(Result).R := TAlphaColorRec(Start).R + Trunc((TAlphaColorRec(Stop).R - TAlphaColorRec(Start).R) * T);
TAlphaColorRec(Result).G := TAlphaColorRec(Start).G + Trunc((TAlphaColorRec(Stop).G - TAlphaColorRec(Start).G) * T);
TAlphaColorRec(Result).B := TAlphaColorRec(Start).B + Trunc((TAlphaColorRec(Stop).B - TAlphaColorRec(Start).B) * T);
end;
procedure ClipTrimPath(const ACanvas: ISkCanvas; APaint: ISkPaint;
const APath: ISkPath; const AStart, AStop: Single);
begin
APaint := TSkPaint.Create(APaint);
APaint.PathEffect := TSkPathEffect.MakeTrim(AStart, AStop, TSkPathEffectTrimMode.Normal);
ACanvas.ClipPath(APaint.GetFillPath(APath), TSkClipOp.Intersect, True);
end;
function GetBeginPicture(const APaint: ISkPaint): ISkPicture;
var
LPictureRecorder: ISkPictureRecorder;
LCanvas: ISkCanvas;
LProgress: Single;
I: Integer;
begin
if AStart < TEpsilon.Position then
Exit(nil);
LPictureRecorder := TSkPictureRecorder.Create;
LCanvas := LPictureRecorder.BeginRecording(LDrawBounds);
try
for I := AInterpolations downto 0 do
begin
LProgress := I / AInterpolations;
APaint.Color := InterpolateColor(AOuterColor, AInnerColor, I / Max(AInterpolations - 1, 1));
APaint.PathEffect := TSkPathEffect.MakeTrim(LProgress * AStart, Min(1 - AStop, LProgress * AStart + AStart / AInterpolations), TSkPathEffectTrimMode.Normal);
LCanvas.DrawPath(APath, APaint);
end;
finally
Result := LPictureRecorder.FinishRecording;
end;
end;
function GetEndPicture(const APaint: ISkPaint): ISkPicture;
var
LPictureRecorder: ISkPictureRecorder;
LCanvas: ISkCanvas;
LProgress: Single;
I: Integer;
begin
if AStop < TEpsilon.Position then
Exit(nil);
LPictureRecorder := TSkPictureRecorder.Create;
LCanvas := LPictureRecorder.BeginRecording(LDrawBounds);
try
for I := AInterpolations downto 0 do
begin
LProgress := I / AInterpolations;
APaint.Color := InterpolateColor(AOuterColor, AInnerColor, I / Max(AInterpolations - 1, 1));
APaint.PathEffect := TSkPathEffect.MakeTrim(Max(AStart, 1 - LProgress * AStop - AStop / AInterpolations), 1 - LProgress * AStop, TSkPathEffectTrimMode.Normal);
LCanvas.DrawPath(APath, APaint);
end;
finally
Result := LPictureRecorder.FinishRecording;
end;
end;
function GetInnerPicture(APaint: ISkPaint): ISkPicture;
var
LPictureRecorder: ISkPictureRecorder;
LCanvas: ISkCanvas;
begin
LPictureRecorder := TSkPictureRecorder.Create;
LCanvas := LPictureRecorder.BeginRecording(LDrawBounds);
try
APaint := TSkPaint.Create(APaint);
if (AStop >= TEpsilon.Position) and (AStart >= TEpsilon.Position) then
APaint.StrokeCap := TSkStrokeCap.Butt;
APaint.Color := AInnerColor;
APaint.PathEffect := TSkPathEffect.MakeTrim(AStart, 1 - AStop, TSkPathEffectTrimMode.Normal);
LCanvas.DrawPath(APath, APaint);
finally
Result := LPictureRecorder.FinishRecording;
end;
end;
procedure DrawElement(APicture: ISkPicture; const AStart, AStop: Single);
begin
if APicture = nil then
Exit;
ACanvas.Save;
try
ClipTrimPath(ACanvas, APaint, APath, AStart, AStop);
ACanvas.DrawPicture(APicture, APaint);
finally
ACanvas.Restore;
end;
end;
var
LSegmentPaint: ISkPaint;
begin
AStart := EnsureRange(AStart, 0, 1);
AStop := EnsureRange(AStop, 0, 1);
if (AInterpolations <= 0) or ((AStart < TEpsilon.Position) and (AStop < TEpsilon.Position)) then
begin
LSegmentPaint := TSkPaint.Create(APaint);
LSegmentPaint.Color := AInnerColor;
ACanvas.DrawPath(APath, LSegmentPaint);
end
else
begin
LSegmentPaint := TSkPaint.Create(APaint);
LSegmentPaint.Blender := TSkBlender.MakeMode(TSkBlendMode.Src);
LSegmentPaint.StrokeWidth := LSegmentPaint.StrokeWidth + (1 / ACanvas.GetLocalToDeviceAs3x3.ExtractScale.Length);
LDrawBounds := APath.Bounds;
LDrawBounds.Inflate(2 * LSegmentPaint.StrokeWidth, 2 * LSegmentPaint.StrokeWidth);
DrawElement(GetBeginPicture(LSegmentPaint), 0, AStart);
DrawElement(GetEndPicture(LSegmentPaint), 1 - AStop, 1);
DrawElement(GetInnerPicture(LSegmentPaint), AStart, 1 - AStop);
end;
end;
end.
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 480
ClientWidth = 640
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
DesignerMasterStyle = 0
object SkPaintBox1: TSkPaintBox
Align = Client
HitTest = True
Size.Width = 640.000000000000000000
Size.Height = 480.000000000000000000
Size.PlatformDefault = False
OnDraw = SkPaintBox1Draw
end
end
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, FMX.Types,
FMX.Controls, FMX.Forms, FMX.Graphics, Skia, Skia.FMX;
type
TForm1 = class(TForm)
SkPaintBox1: TSkPaintBox;
procedure SkPaintBox1Draw(ASender: TObject; const ACanvas: ISkCanvas;
const ADest: TRectF; const AOpacity: Single);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
uses
StrokePathGradient;
procedure TForm1.SkPaintBox1Draw(ASender: TObject; const ACanvas: ISkCanvas;
const ADest: TRectF; const AOpacity: Single);
function CreatePath: ISkPath;
begin
var LPathBuilder: ISkPathBuilder := TSkPathBuilder.Create;
LPathBuilder.MoveTo(PointF(36, 48));
LPathBuilder.QuadTo(PointF(66, 88), PointF(120, 36));
LPathBuilder.LineTo(250, 300);
LPathBuilder.LineTo(180, 300);
LPathBuilder.LineTo(250, 200);
Result := LPathBuilder.Detach;
end;
var
LPaint: ISkPaint;
begin
LPaint := TSkPaint.Create(TSkPaintStyle.Stroke);
LPaint.StrokeWidth := 8;
LPaint.AntiAlias := True;
LPaint.StrokeCap := TSkStrokeCap.Round;
//ACanvas.DrawPath(CreatePath, LPaint);
//DrawPathEndSmothing(ACanvas, CreatePath, LPaint, TAlphaColors.Tomato, TAlphaColors.Blueviolet, 0.2, 0.3, 30);
//DrawPathEndSmothing(ACanvas, CreatePath, LPaint, TAlphaColors.Black, TAlphaColors.Null, 0.15, 0.3, 30);
DrawPathEndSmothing(ACanvas, CreatePath, LPaint, TAlphaColors.Tomato, TAlphaColors.Blueviolet, 0, 1, 30);
end;
end.
@viniciusfbb
Copy link
Author

image
image
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment