Skip to content

Commit 1714a6e

Browse files
Precompute fixed values.
1 parent 4df8f1a commit 1714a6e

File tree

1 file changed

+120
-28
lines changed

1 file changed

+120
-28
lines changed

src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs

Lines changed: 120 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,10 @@ private static void CaptureTileScanline(int y, Span<float> scanline, ref TileCap
565565
/// <summary>
566566
/// Builds a compact edge table in scanner-local coordinates.
567567
/// </summary>
568+
/// <remarks>
569+
/// Edges are converted to 24.8 fixed-point once during table construction so the hot
570+
/// band/tile rasterization loop does not pay float-to-fixed conversion costs repeatedly.
571+
/// </remarks>
568572
/// <param name="multipolygon">Input tessellated rings.</param>
569573
/// <param name="minX">Interest left in absolute coordinates.</param>
570574
/// <param name="minY">Interest top in absolute coordinates.</param>
@@ -609,7 +613,16 @@ private static int BuildEdgeTable(
609613
continue;
610614
}
611615

612-
destination[count++] = new EdgeData(x0, y0, x1, y1, minRow, maxRow);
616+
int fx0 = FloatToFixed24Dot8(x0);
617+
int fy0 = FloatToFixed24Dot8(y0);
618+
int fx1 = FloatToFixed24Dot8(x1);
619+
int fy1 = FloatToFixed24Dot8(y1);
620+
if (fy0 == fy1)
621+
{
622+
continue;
623+
}
624+
625+
destination[count++] = new EdgeData(fx0, fy0, fx1, fy1, minRow, maxRow);
613626
}
614627
}
615628

@@ -703,6 +716,49 @@ private static bool TryGetBandHeight(int width, int height, int wordsPerRow, lon
703716
[MethodImpl(MethodImplOptions.AggressiveInlining)]
704717
private static int FloatToFixed24Dot8(float value) => (int)MathF.Round(value * FixedOne);
705718

719+
/// <summary>
720+
/// Clips a fixed-point segment against vertical bounds.
721+
/// </summary>
722+
/// <param name="x0">Segment start X in 24.8 fixed-point (updated in place).</param>
723+
/// <param name="y0">Segment start Y in 24.8 fixed-point (updated in place).</param>
724+
/// <param name="x1">Segment end X in 24.8 fixed-point (updated in place).</param>
725+
/// <param name="y1">Segment end Y in 24.8 fixed-point (updated in place).</param>
726+
/// <param name="minY">Minimum Y bound in 24.8 fixed-point.</param>
727+
/// <param name="maxY">Maximum Y bound in 24.8 fixed-point.</param>
728+
/// <returns><see langword="true"/> when a non-horizontal clipped segment remains.</returns>
729+
private static bool ClipToVerticalBoundsFixed(ref int x0, ref int y0, ref int x1, ref int y1, int minY, int maxY)
730+
{
731+
double t0 = 0D;
732+
double t1 = 1D;
733+
int originX0 = x0;
734+
int originY0 = y0;
735+
long dx = (long)x1 - originX0;
736+
long dy = (long)y1 - originY0;
737+
if (!ClipTestFixed(-(double)dy, originY0 - (double)minY, ref t0, ref t1))
738+
{
739+
return false;
740+
}
741+
742+
if (!ClipTestFixed(dy, maxY - (double)originY0, ref t0, ref t1))
743+
{
744+
return false;
745+
}
746+
747+
if (t1 < 1D)
748+
{
749+
x1 = originX0 + (int)Math.Round(dx * t1);
750+
y1 = originY0 + (int)Math.Round(dy * t1);
751+
}
752+
753+
if (t0 > 0D)
754+
{
755+
x0 = originX0 + (int)Math.Round(dx * t0);
756+
y0 = originY0 + (int)Math.Round(dy * t0);
757+
}
758+
759+
return y0 != y1;
760+
}
761+
706762
/// <summary>
707763
/// Clips a segment against vertical bounds using Liang-Barsky style parametric tests.
708764
/// </summary>
@@ -785,6 +841,46 @@ private static bool ClipTest(float p, float q, ref float t0, ref float t1)
785841
return true;
786842
}
787843

844+
/// <summary>
845+
/// One Liang-Barsky clip test step for fixed-point clipping.
846+
/// </summary>
847+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
848+
private static bool ClipTestFixed(double p, double q, ref double t0, ref double t1)
849+
{
850+
if (p == 0D)
851+
{
852+
return q >= 0D;
853+
}
854+
855+
double r = q / p;
856+
if (p < 0D)
857+
{
858+
if (r > t1)
859+
{
860+
return false;
861+
}
862+
863+
if (r > t0)
864+
{
865+
t0 = r;
866+
}
867+
}
868+
else
869+
{
870+
if (r < t0)
871+
{
872+
return false;
873+
}
874+
875+
if (r < t1)
876+
{
877+
t1 = r;
878+
}
879+
}
880+
881+
return true;
882+
}
883+
788884
/// <summary>
789885
/// Returns one when a fixed-point value lies exactly on a cell boundary at or below zero.
790886
/// This is used to keep edge ownership consistent for vertical lines.
@@ -922,34 +1018,27 @@ public void RasterizeMultipolygon(TessellatedMultipolygon multipolygon, int minX
9221018
/// <param name="bandTop">Top row of this context in global scanner-local coordinates.</param>
9231019
public void RasterizeEdgeTable(ReadOnlySpan<EdgeData> edges, ReadOnlySpan<int> edgeIndices, int bandTop)
9241020
{
925-
float minY = bandTop;
926-
float maxY = bandTop + this.height;
1021+
int bandTopFixed = bandTop * FixedOne;
1022+
int bandBottomFixed = bandTopFixed + (this.height * FixedOne);
9271023

9281024
for (int i = 0; i < edgeIndices.Length; i++)
9291025
{
9301026
EdgeData edge = edges[edgeIndices[i]];
931-
float x0 = edge.X0;
932-
float y0 = edge.Y0;
933-
float x1 = edge.X1;
934-
float y1 = edge.Y1;
1027+
int x0 = edge.X0;
1028+
int y0 = edge.Y0;
1029+
int x1 = edge.X1;
1030+
int y1 = edge.Y1;
9351031

936-
if (!ClipToVerticalBounds(ref x0, ref y0, ref x1, ref y1, minY, maxY))
1032+
if (!ClipToVerticalBoundsFixed(ref x0, ref y0, ref x1, ref y1, bandTopFixed, bandBottomFixed))
9371033
{
9381034
continue;
9391035
}
9401036

941-
// Convert to fixed-point in band-local Y coordinates so downstream walkers can
942-
// index 0..bandHeight-1 directly without extra subtraction in hot loops.
943-
int fx0 = FloatToFixed24Dot8(x0);
944-
int fy0 = FloatToFixed24Dot8(y0 - bandTop);
945-
int fx1 = FloatToFixed24Dot8(x1);
946-
int fy1 = FloatToFixed24Dot8(y1 - bandTop);
947-
if (fy0 == fy1)
948-
{
949-
continue;
950-
}
1037+
// Convert global scanner Y to band-local Y after clipping.
1038+
y0 -= bandTopFixed;
1039+
y1 -= bandTopFixed;
9511040

952-
this.RasterizeLine(fx0, fy0, fx1, fy1);
1041+
this.RasterizeLine(x0, y0, x1, y1);
9531042
}
9541043
}
9551044

@@ -1862,12 +1951,15 @@ private void RasterizeLine(int x0, int y0, int x1, int y1)
18621951
/// <summary>
18631952
/// Immutable scanner-local edge record with precomputed affected-row bounds.
18641953
/// </summary>
1954+
/// <remarks>
1955+
/// Coordinates are stored as signed 24.8 fixed-point values.
1956+
/// </remarks>
18651957
private readonly struct EdgeData
18661958
{
18671959
/// <summary>
18681960
/// Initializes a new instance of the <see cref="EdgeData"/> struct.
18691961
/// </summary>
1870-
public EdgeData(float x0, float y0, float x1, float y1, int minRow, int maxRow)
1962+
public EdgeData(int x0, int y0, int x1, int y1, int minRow, int maxRow)
18711963
{
18721964
this.X0 = x0;
18731965
this.Y0 = y0;
@@ -1878,24 +1970,24 @@ public EdgeData(float x0, float y0, float x1, float y1, int minRow, int maxRow)
18781970
}
18791971

18801972
/// <summary>
1881-
/// Gets edge start X in scanner-local coordinates.
1973+
/// Gets edge start X in scanner-local coordinates (24.8 fixed-point).
18821974
/// </summary>
1883-
public float X0 { get; }
1975+
public int X0 { get; }
18841976

18851977
/// <summary>
1886-
/// Gets edge start Y in scanner-local coordinates.
1978+
/// Gets edge start Y in scanner-local coordinates (24.8 fixed-point).
18871979
/// </summary>
1888-
public float Y0 { get; }
1980+
public int Y0 { get; }
18891981

18901982
/// <summary>
1891-
/// Gets edge end X in scanner-local coordinates.
1983+
/// Gets edge end X in scanner-local coordinates (24.8 fixed-point).
18921984
/// </summary>
1893-
public float X1 { get; }
1985+
public int X1 { get; }
18941986

18951987
/// <summary>
1896-
/// Gets edge end Y in scanner-local coordinates.
1988+
/// Gets edge end Y in scanner-local coordinates (24.8 fixed-point).
18971989
/// </summary>
1898-
public float Y1 { get; }
1990+
public int Y1 { get; }
18991991

19001992
/// <summary>
19011993
/// Gets the first scanner row affected by this edge.

0 commit comments

Comments
 (0)