话不多说,直接 Show code:
1. 声明
TZPathData = class helper for TPathData
private
function GetAsBytes:TBytes;
function SetAsBytes(const ABytes:TBytes);
function GetAsString:String;
function SetAsString(const S:String);
public
procedure Rotate(const Angle:Single);
property AsString:String read GetAsString write SetAsString;
property AsBytes:TBytes read GetAsBytes write SetAsBytes;
end;
- Rotate : 将当前路径旋转指定的角度
- AsString : 用于替换 Data 属性,重新实际编码函数,以减少生成的路径字符串体积
- AsBytes : 按二进制的方式来进一步编码压缩路径数据
2. 实现
function TZPathData.GetAsBytes: TBytes;
// 压缩下指令,直接后面跟最多63个同一类型的值
// [Op:2][Count:6][DataArray]
// DataArray 存储的是绝对坐标,单精度是32位,这块我们不再压缩,毕竟和损失精度相比,收益并不明显
var
I, ACount: Integer;
const
OpCodes: array [TZPathPointKind] of Byte = (OP_MOVE_TO, OP_LINE_TO, OP_CURVE_TO, OP_CLOSE);
procedure MovePoints(AKind: TZPathPointKind; AItemPoints: Integer);
var
pValue: PPointF;
c: Integer;
begin
Result[ACount] := OpCodes[AKind];
pValue := @Result[ACount + 1];
repeat
for c := 0 to AItemPoints - 1 do
begin
pValue^ := FPathData[I].Point;
Inc(pValue);
Inc(I);
end;
Result[ACount] := OpCodes[AKind] or ((Result[ACount] and $3F) + 1);
until (I = Count) or (FPathData[I].Kind <> AKind) or (I = 63);
Inc(ACount, 1 + (Result[ACount] and $3F) * AItemPoints * SizeOf(TPointF));
end;
begin
I := 0;
ACount := 0;
SetLength(Result, (1 + Count * SizeOf(Single) * 3) * Count);
while I < Count do
begin
case FPathData[I].Kind of
TZPathPointKind.MoveTo, TZPathPointKind.LineTo:
MovePoints(FPathData[I].Kind, 1);
TZPathPointKind.CurveTo:
MovePoints(FPathData[I].Kind, 3);
TZPathPointKind.Close:
begin
Result[ACount] := OP_CLOSE;
Inc(ACount);
Inc(I);
end;
end;
end;
SetLength(Result, ACount);
end;
procedure TZPathData.SetAsBytes(const ABytes: TBytes);
var
I, ACount: Integer;
begin
I := 0;
FPathData.Clear;
while I < Length(ABytes) do
begin
case ABytes[I] and $C0 of
OP_MOVE_TO:
begin
ACount := ABytes[I] and $3F;
Inc(I);
repeat
MoveTo(PPointF(@ABytes[I])^);
Inc(I, SizeOf(TPointF));
Dec(ACount);
until (ACount = 0) or (I = Length(ABytes));
end;
OP_LINE_TO:
begin
ACount := ABytes[I] and $3F;
Inc(I);
repeat
LineTo(PPointF(@ABytes[I])^);
Inc(I, SizeOf(TPointF));
Dec(ACount);
until (ACount = 0) or (I = Length(ABytes));
end;
OP_CURVE_TO:
begin
ACount := ABytes[I] and $3F;
Inc(I);
repeat
CurveTo(PPointF(@ABytes[I])^, PPointF(@ABytes[I + SizeOf(TPointF)])^,
PPointF(@ABytes[I + SizeOf(TPointF) * 2])^);
Inc(I, SizeOf(TPointF) * 3);
Dec(ACount);
until (ACount = 0) or (I = Length(ABytes));
end;
OP_CLOSE:
begin
ClosePath;
Inc(I);
end;
end;
end;
end;
procedure TZPathData.Rotate(const Angle: Single);
begin
ApplyMatrix(TMatrix.CreateRotation(Angle));
end;
function TZPathData.GetAsString: string;
var
I: Integer;
Builder: TStringBuilder;
ALastPos, pt: TPointF;
ALastKind: TZPathPointKind;
AIsRelative: Boolean;
begin
// 代码优化自 FMX 的 TPathData,通过减少重复指令和使用相对坐标来减少内容
Builder := TStringBuilder.Create;
try
I := 0;
AIsRelative := False;
ALastKind := TZPathPointKind.Close;
while I < Count do
begin
if AIsRelative then
begin
pt.X := FPathData[I].Point.X - ALastPos.X;
pt.Y := FPathData[I].Point.Y - ALastPos.Y;
case FPathData[I].Kind of
TZPathPointKind.MoveTo:
begin
if ALastKind <> FPathData[I].Kind then
Builder.Append('m');
Builder.Append(FloatToStr(pt.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(pt.Y, TFormatSettings.Invariant)).Append(' ');
end;
TZPathPointKind.LineTo:
begin
if ALastKind <> FPathData[I].Kind then
Builder.Append('l');
Builder.Append(FloatToStr(pt.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(pt.Y, TFormatSettings.Invariant)).Append(' ');
end;
TZPathPointKind.CurveTo:
begin
if ALastKind <> FPathData[I].Kind then
Builder.Append('c');
Builder.Append(FloatToStr(pt.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(pt.Y, TFormatSettings.Invariant)).Append(' ');
Builder.Append(FloatToStr(FPathData[I + 1].Point.X - ALastPos.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(FPathData[I + 1].Point.Y - ALastPos.Y, TFormatSettings.Invariant)).Append(' ');
Builder.Append(FloatToStr(FPathData[I + 2].Point.X - ALastPos.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(FPathData[I + 2].Point.Y - ALastPos.Y, TFormatSettings.Invariant)).Append(' ');
Inc(I, 2);
end;
TZPathPointKind.Close:
begin
Builder.Append('Z ');
AIsRelative := False;
end;
end;
end
else
begin
AIsRelative := True;
case FPathData[I].Kind of
TZPathPointKind.MoveTo:
Builder.Append('M').Append(FloatToStr(FPathData[I].Point.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(FPathData[I].Point.Y, TFormatSettings.Invariant)).Append(' ');
TZPathPointKind.LineTo:
Builder.Append('L').Append(FloatToStr(FPathData[I].Point.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(FPathData[I].Point.Y, TFormatSettings.Invariant)).Append(' ');
TZPathPointKind.CurveTo:
begin
Builder.Append('C').Append(FloatToStr(FPathData[I].Point.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(FPathData[I].Point.Y, TFormatSettings.Invariant)).Append(' ');
Builder.Append(FloatToStr(FPathData[I + 1].Point.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(FPathData[I + 1].Point.Y, TFormatSettings.Invariant)).Append(' ');
Builder.Append(FloatToStr(FPathData[I + 2].Point.X, TFormatSettings.Invariant)).Append(',')
.Append(FloatToStr(FPathData[I + 2].Point.Y, TFormatSettings.Invariant)).Append(' ');
Inc(I, 2);
end;
TZPathPointKind.Close:
begin
Builder.Append('Z ');
AIsRelative := False;
end;
end;
end;
ALastKind := FPathData[I].Kind;
ALastPos := FPathData[I].Point;
Inc(I);
end;
Result := Builder.ToString(True);
finally
Builder.Free;
end;
end;
procedure TZPathData.SetAsString(const Value: string);
begin
Data:=Value;
end;
3. 实际效果
原始路径图像:

- 通过 Data 属性获取路径字符串长度: 2303 字节 (100%)
- 使用 AsString 属性获取路径字符串长度: 1841 字节 (79.9%)
- 通过 AsBytes 生成的二进制序列长度: 796 字节 (34.6%)
旋转 45 度后的图像:
