// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Drawing.Processing.Backends {
internal static partial class DefaultRasterizer
{
///
/// Base class that lowers translated geometry into retained per-row line storage.
///
/// The mutable per-row line collector type.
private abstract class Linearizer
where TL : class
{
private bool hasAnyCoverage;
protected Linearizer(
LinearGeometry geometry,
Matrix4x4 residual,
int translateX,
int translateY,
int minX,
int minY,
int width,
int height,
int firstBandIndex,
int rowBandCount,
float samplingOffsetX,
float samplingOffsetY,
MemoryAllocator allocator)
{
this.Geometry = geometry;
this.Residual = residual;
this.HasResidual = !residual.IsIdentity;
this.TranslateX = translateX;
this.TranslateY = translateY;
this.MinX = minX;
this.MinY = minY;
this.Width = width;
this.Height = height;
this.FirstBandIndex = firstBandIndex;
this.RowBandCount = rowBandCount;
this.SamplingOffsetX = samplingOffsetX;
this.SamplingOffsetY = samplingOffsetY;
this.Allocator = allocator;
this.BandTopStart = (firstBandIndex * PreferredRowHeight) - minY;
this.FirstBlockLineCounts = new int[rowBandCount];
this.LineCounts = new int[rowBandCount];
this.StartCoverTable = new IMemoryOwner?[rowBandCount];
this.LineArrays = new TL?[rowBandCount];
}
///
/// Gets the source geometry being lowered.
///
protected LinearGeometry Geometry { get; }
///
/// Gets the residual transform applied to each source point during emission.
///
protected Matrix4x4 Residual { get; }
///
/// Gets a value indicating whether is non-identity.
///
protected bool HasResidual { get; }
///
/// Gets the translated X offset applied to the geometry.
///
protected int TranslateX { get; }
///
/// Gets the translated Y offset applied to the geometry.
///
protected int TranslateY { get; }
///
/// Gets the minimum destination X bound after clipping.
///
protected int MinX { get; }
///
/// Gets the minimum destination Y bound after clipping.
///
protected int MinY { get; }
///
/// Gets the visible destination width in pixels.
///
protected int Width { get; }
///
/// Gets the visible destination height in pixels.
///
protected int Height { get; }
///
/// Gets the first retained row-band index touched by the geometry.
///
protected int FirstBandIndex { get; }
///
/// Gets the number of retained row bands owned by the geometry.
///
protected int RowBandCount { get; }
///
/// Gets the horizontal sampling offset applied before fixed-point conversion.
///
protected float SamplingOffsetX { get; }
///
/// Gets the vertical sampling offset applied before fixed-point conversion.
///
protected float SamplingOffsetY { get; }
///
/// Gets the allocator used for retained start-cover storage.
///
protected MemoryAllocator Allocator { get; }
///
/// Gets the top offset, in whole pixels, of the first retained row band.
///
protected int BandTopStart { get; }
///
/// Gets the mutable per-row line collectors used during lowering.
///
protected TL?[] LineArrays { get; }
///
/// Gets the valid front-block line count for each retained row band.
///
protected int[] FirstBlockLineCounts { get; }
///
/// Gets the total retained line count for each row band.
///
protected int[] LineCounts { get; }
///
/// Gets the retained start-cover storage for each row band.
///
protected IMemoryOwner?[] StartCoverTable { get; }
///
/// Gets a value indicating whether any retained payload was produced.
///
protected ref bool HasAnyCoverage => ref this.hasAnyCoverage;
///
/// Executes the linearization pass and finalizes the retained row payloads.
///
/// when any retained coverage was produced; otherwise .
protected virtual bool ProcessCore()
{
RectangleF translatedBounds = this.HasResidual
? RectangleF.Transform(this.Geometry.Info.Bounds, this.Residual)
: this.Geometry.Info.Bounds;
translatedBounds.Offset(this.TranslateX + this.SamplingOffsetX - this.MinX, this.TranslateY + this.SamplingOffsetY - this.MinY);
bool contains =
translatedBounds.Left >= 0F &&
translatedBounds.Top >= 0F &&
translatedBounds.Right <= this.Width &&
translatedBounds.Bottom <= this.Height;
// Contained geometry can skip clipping and go straight to the fixed-point band splitter.
if (contains)
{
this.ProcessContained();
}
else
{
// Geometry that touches the interest edges needs clipping so start covers and line
// segments still match the destination bounds seen by the rasterizer.
this.ProcessUncontained();
}
if (!this.hasAnyCoverage)
{
return false;
}
this.FinalizeLines();
return true;
}
///
/// Linearizes geometry that is fully contained inside the destination interest.
///
protected void ProcessContained()
{
SegmentEnumerator enumerator = this.Geometry.GetSegments();
Matrix4x4 residual = this.Residual;
bool hasResidual = this.HasResidual;
while (enumerator.MoveNext())
{
LinearSegment segment = enumerator.Current;
PointF p0 = segment.Start;
PointF p1 = segment.End;
if (hasResidual)
{
p0 = PointF.Transform(p0, residual);
p1 = PointF.Transform(p1, residual);
}
this.AddContainedLineF24Dot8(
FloatToFixed24Dot8(((p0.X + this.TranslateX) - this.MinX) + this.SamplingOffsetX),
FloatToFixed24Dot8(((p0.Y + this.TranslateY) - this.MinY) + this.SamplingOffsetY),
FloatToFixed24Dot8(((p1.X + this.TranslateX) - this.MinX) + this.SamplingOffsetX),
FloatToFixed24Dot8(((p1.Y + this.TranslateY) - this.MinY) + this.SamplingOffsetY));
}
}
///
/// Linearizes geometry that intersects the destination interest bounds and requires clipping.
///
protected void ProcessUncontained()
{
SegmentEnumerator enumerator = this.Geometry.GetSegments();
Matrix4x4 residual = this.Residual;
bool hasResidual = this.HasResidual;
while (enumerator.MoveNext())
{
LinearSegment segment = enumerator.Current;
PointF p0 = segment.Start;
PointF p1 = segment.End;
if (hasResidual)
{
p0 = PointF.Transform(p0, residual);
p1 = PointF.Transform(p1, residual);
}
this.AddUncontainedLine(
((p0.X + this.TranslateX) - this.MinX) + this.SamplingOffsetX,
((p0.Y + this.TranslateY) - this.MinY) + this.SamplingOffsetY,
((p1.X + this.TranslateX) - this.MinX) + this.SamplingOffsetX,
((p1.Y + this.TranslateY) - this.MinY) + this.SamplingOffsetY);
}
}
///
/// Clips one geometry line against the destination interest and adds the retained result.
///
/// The starting X coordinate in translated float space.
/// The starting Y coordinate in translated float space.
/// The ending X coordinate in translated float space.
/// The ending Y coordinate in translated float space.
protected void AddUncontainedLine(float x0, float y0, float x1, float y1)
{
if (y0 == y1)
{
return;
}
if (y0 <= 0F && y1 <= 0F)
{
return;
}
if (y0 >= this.Height && y1 >= this.Height)
{
return;
}
if (x0 >= this.Width && x1 >= this.Width)
{
return;
}
if (x0 == x1)
{
int x0c = Math.Clamp(FloatToFixed24Dot8(x0), 0, this.Width * FixedOne);
int p0y = Math.Clamp(FloatToFixed24Dot8(y0), 0, this.Height * FixedOne);
int p1y = Math.Clamp(FloatToFixed24Dot8(y1), 0, this.Height * FixedOne);
if (x0c == 0)
{
// Segments clipped fully to the left edge do not produce a visible line, but they
// still change winding for rows they cross. Retain that effect as start covers.
this.UpdateStartCoversClipped(p0y, p1y);
this.hasAnyCoverage = true;
}
else
{
this.AddContainedLineF24Dot8(x0c, p0y, x0c, p1y);
}
return;
}
double deltayV = Math.Abs(y1 - y0);
double deltaxV = x1 - x0;
double rx0 = x0;
double ry0 = y0;
double rx1 = x1;
double ry1 = y1;
if (y1 > y0)
{
if (y0 < 0F)
{
double t = -y0 / deltayV;
rx0 = x0 + (deltaxV * t);
ry0 = 0D;
}
if (y1 > this.Height)
{
double t = (this.Height - y0) / deltayV;
rx1 = x0 + (deltaxV * t);
ry1 = this.Height;
}
}
else
{
if (y0 > this.Height)
{
double t = (y0 - this.Height) / deltayV;
rx0 = x0 + (deltaxV * t);
ry0 = this.Height;
}
if (y1 < 0F)
{
double t = y0 / deltayV;
rx1 = x0 + (deltaxV * t);
ry1 = 0D;
}
}
if (rx0 >= this.Width && rx1 >= this.Width)
{
return;
}
if (rx0 > 0D && rx1 > 0D && rx0 < this.Width && rx1 < this.Width)
{
this.AddContainedLineF24Dot8(
Math.Clamp(FloatToFixed24Dot8((float)rx0), 0, this.Width * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)ry0), 0, this.Height * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)rx1), 0, this.Width * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)ry1), 0, this.Height * FixedOne));
return;
}
if (rx0 <= 0D && rx1 <= 0D)
{
// A segment that stays left of the visible band contributes winding only.
this.UpdateStartCoversClipped(
Math.Clamp(FloatToFixed24Dot8((float)ry0), 0, this.Height * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)ry1), 0, this.Height * FixedOne));
this.hasAnyCoverage = true;
return;
}
double deltayH = ry1 - ry0;
double deltaxH = Math.Abs(rx1 - rx0);
if (rx1 > rx0)
{
double bx1 = rx1;
double by1 = ry1;
if (rx1 > this.Width)
{
double t = (this.Width - rx0) / deltaxH;
by1 = ry0 + (deltayH * t);
bx1 = this.Width;
}
if (rx0 < 0D)
{
double t = -rx0 / deltaxH;
int a = Math.Clamp(FloatToFixed24Dot8((float)ry0), 0, this.Height * FixedOne);
int by = Math.Clamp(FloatToFixed24Dot8((float)(ry0 + (deltayH * t))), 0, this.Height * FixedOne);
int cx = Math.Clamp(FloatToFixed24Dot8((float)bx1), 0, this.Width * FixedOne);
int cy = Math.Clamp(FloatToFixed24Dot8((float)by1), 0, this.Height * FixedOne);
this.UpdateStartCoversClipped(a, by);
this.hasAnyCoverage = true;
// The visible portion begins exactly at x == 0 after the left-edge clip.
this.AddContainedLineF24Dot8(0, by, cx, cy);
}
else
{
this.AddContainedLineF24Dot8(
Math.Clamp(FloatToFixed24Dot8((float)rx0), 0, this.Width * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)ry0), 0, this.Height * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)bx1), 0, this.Width * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)by1), 0, this.Height * FixedOne));
}
}
else
{
double bx0 = rx0;
double by0 = ry0;
if (rx0 > this.Width)
{
double t = (rx0 - this.Width) / deltaxH;
by0 = ry0 + (deltayH * t);
bx0 = this.Width;
}
if (rx1 < 0D)
{
double t = rx0 / deltaxH;
int ax = Math.Clamp(FloatToFixed24Dot8((float)bx0), 0, this.Width * FixedOne);
int ay = Math.Clamp(FloatToFixed24Dot8((float)by0), 0, this.Height * FixedOne);
int by = Math.Clamp(FloatToFixed24Dot8((float)(ry0 + (deltayH * t))), 0, this.Height * FixedOne);
int c = Math.Clamp(FloatToFixed24Dot8((float)ry1), 0, this.Height * FixedOne);
// The right-to-left case mirrors the left-edge handling above: emit the
// visible portion first, then retain the winding-only tail as start covers.
this.AddContainedLineF24Dot8(ax, ay, 0, by);
this.UpdateStartCoversClipped(by, c);
this.hasAnyCoverage = true;
}
else
{
this.AddContainedLineF24Dot8(
Math.Clamp(FloatToFixed24Dot8((float)bx0), 0, this.Width * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)by0), 0, this.Height * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)rx1), 0, this.Width * FixedOne),
Math.Clamp(FloatToFixed24Dot8((float)ry1), 0, this.Height * FixedOne));
}
}
}
///
/// Adds one fully-contained line segment in 24.8 fixed-point coordinates.
///
/// The starting X coordinate.
/// The starting Y coordinate.
/// The ending X coordinate.
/// The ending Y coordinate.
protected void AddContainedLineF24Dot8(int x0, int y0, int x1, int y1)
{
if (y0 == y1)
{
return;
}
if (x0 == x1)
{
if (y0 < y1)
{
this.VerticalDown(x0, y0, y1);
}
else
{
this.VerticalUp(x0, y0, y1);
}
return;
}
int dx = Math.Abs(x1 - x0);
int dy = Math.Abs(y1 - y0);
if (dx > MaximumDelta || dy > MaximumDelta)
{
int mx = (x0 + x1) >> 1;
int my = (y0 + y1) >> 1;
this.AddContainedLineF24Dot8(x0, y0, mx, my);
this.AddContainedLineF24Dot8(mx, my, x1, y1);
return;
}
int rowIndex0;
int rowIndex1;
int bandTopStart = this.BandTopStart * FixedOne;
int bandHeight = PreferredRowHeight * FixedOne;
if (y0 < y1)
{
rowIndex0 = (y0 - bandTopStart) / bandHeight;
rowIndex1 = ((y1 - 1) - bandTopStart) / bandHeight;
}
else
{
rowIndex0 = ((y0 - 1) - bandTopStart) / bandHeight;
rowIndex1 = (y1 - bandTopStart) / bandHeight;
}
if ((uint)rowIndex0 >= (uint)this.RowBandCount || (uint)rowIndex1 >= (uint)this.RowBandCount)
{
return;
}
if (rowIndex0 == rowIndex1)
{
int rowTop = bandTopStart + (rowIndex0 * bandHeight);
this.AppendLine(rowIndex0, x0, y0 - rowTop, x1, y1 - rowTop);
this.LineCounts[rowIndex0]++;
this.hasAnyCoverage = true;
return;
}
this.SplitAcrossBands(x0, y0, x1, y1);
}
///
/// Creates the mutable line collector used for one row band.
///
/// The mutable line collector.
protected abstract TL CreateLineArray();
///
/// Appends one line segment into the retained row-band collector.
///
/// The local row-band index.
/// The starting X coordinate relative to the row band.
/// The starting Y coordinate relative to the row band.
/// The ending X coordinate relative to the row band.
/// The ending Y coordinate relative to the row band.
protected abstract void AppendLine(int rowIndex, int x0, int y0, int x1, int y1);
///
/// Finalizes the mutable collectors into the retained line-block representation.
///
protected abstract void FinalizeLines();
///
/// Gets the mutable line collector for a row band, creating it on first use.
///
/// The local row-band index.
/// The mutable line collector.
protected TL GetOrCreateLineArray(int rowIndex)
{
TL? lineArray = this.LineArrays[rowIndex];
if (lineArray is not null)
{
return lineArray;
}
lineArray = this.CreateLineArray();
this.LineArrays[rowIndex] = lineArray;
return lineArray;
}
///
/// Adds a downward vertical segment by delegating to the shared band-splitting path.
///
/// The fixed-point X coordinate.
/// The starting fixed-point Y coordinate.
/// The ending fixed-point Y coordinate.
private void VerticalDown(int x, int y0, int y1) => this.SplitAcrossBands(x, y0, x, y1);
///
/// Adds an upward vertical segment by delegating to the shared band-splitting path.
///
/// The fixed-point X coordinate.
/// The starting fixed-point Y coordinate.
/// The ending fixed-point Y coordinate.
private void VerticalUp(int x, int y0, int y1) => this.SplitAcrossBands(x, y0, x, y1);
///
/// Splits a contained line segment at row-band boundaries and appends each retained piece.
///
/// The starting X coordinate.
/// The starting Y coordinate.
/// The ending X coordinate.
/// The ending Y coordinate.
private void SplitAcrossBands(int x0, int y0, int x1, int y1)
{
int dy = y1 - y0;
int dx = x1 - x0;
int bandTopStart = this.BandTopStart * FixedOne;
int bandHeight = PreferredRowHeight * FixedOne;
int startBand = dy > 0 ? (y0 - bandTopStart) / bandHeight : ((y0 - 1) - bandTopStart) / bandHeight;
int endBand = dy > 0 ? ((y1 - 1) - bandTopStart) / bandHeight : (y1 - bandTopStart) / bandHeight;
int step = dy > 0 ? 1 : -1;
int currentBand = startBand;
int currentX = x0;
int currentY = y0;
while (currentBand != endBand)
{
int bandBoundaryY = dy > 0 ? bandTopStart + ((currentBand + 1) * bandHeight) : bandTopStart + (currentBand * bandHeight);
int deltaY = bandBoundaryY - currentY;
int nextX = currentX + (int)(((long)dx * deltaY) / dy);
int rowTop = bandTopStart + (currentBand * bandHeight);
// Each retained segment is stored in the local coordinate space of its owning band.
this.AppendLine(currentBand, currentX, currentY - rowTop, nextX, bandBoundaryY - rowTop);
this.LineCounts[currentBand]++;
this.hasAnyCoverage = true;
currentX = nextX;
currentY = bandBoundaryY;
currentBand += step;
if ((uint)currentBand >= (uint)this.RowBandCount)
{
return;
}
}
int finalRowTop = bandTopStart + (endBand * bandHeight);
this.AppendLine(endBand, currentX, currentY - finalRowTop, x1, y1 - finalRowTop);
this.LineCounts[endBand]++;
this.hasAnyCoverage = true;
}
///
/// Updates retained start-cover rows for a line that has been clipped against the visible band.
///
/// The clipped starting Y coordinate.
/// The clipped ending Y coordinate.
private void UpdateStartCoversClipped(int y0, int y1)
{
if (y0 == y1)
{
return;
}
if (y0 < y1)
{
int bandTopStart = this.BandTopStart * FixedOne;
int bandHeight = PreferredRowHeight * FixedOne;
int rowIndex0 = (y0 - bandTopStart) / bandHeight;
int rowIndex1 = ((y1 - 1) - bandTopStart) / bandHeight;
rowIndex0 = Math.Clamp(rowIndex0, 0, this.RowBandCount - 1);
rowIndex1 = Math.Clamp(rowIndex1, 0, this.RowBandCount - 1);
int fy0 = y0 - (bandTopStart + (rowIndex0 * bandHeight));
int fy1 = y1 - (bandTopStart + (rowIndex1 * bandHeight));
this.UpdateStartCovers(rowIndex0, fy0, rowIndex0 == rowIndex1 ? fy1 : bandHeight);
for (int i = rowIndex0 + 1; i < rowIndex1; i++)
{
// Full interior bands receive a constant winding contribution.
this.FillStartCovers(i, -FixedOne);
}
if (rowIndex0 != rowIndex1)
{
this.UpdateStartCovers(rowIndex1, 0, fy1);
}
}
else
{
int bandTopStart = this.BandTopStart * FixedOne;
int bandHeight = PreferredRowHeight * FixedOne;
int rowIndex0 = ((y0 - 1) - bandTopStart) / bandHeight;
int rowIndex1 = (y1 - bandTopStart) / bandHeight;
rowIndex0 = Math.Clamp(rowIndex0, 0, this.RowBandCount - 1);
rowIndex1 = Math.Clamp(rowIndex1, 0, this.RowBandCount - 1);
int fy0 = y0 - (bandTopStart + (rowIndex0 * bandHeight));
int fy1 = y1 - (bandTopStart + (rowIndex1 * bandHeight));
this.UpdateStartCovers(rowIndex0, fy0, rowIndex0 == rowIndex1 ? fy1 : 0);
for (int i = rowIndex0 - 1; i > rowIndex1; i--)
{
// Full interior bands receive a constant winding contribution.
this.FillStartCovers(i, FixedOne);
}
if (rowIndex0 != rowIndex1)
{
this.UpdateStartCovers(rowIndex1, bandHeight, fy1);
}
}
}
///
/// Fills an entire retained start-cover row with a constant winding value.
///
/// The local row-band index.
/// The constant winding value to add.
private void FillStartCovers(int localBandIndex, int value)
{
IMemoryOwner? owner = this.StartCoverTable[localBandIndex];
if (owner is null)
{
owner = this.Allocator.Allocate(PreferredRowHeight, AllocationOptions.Clean);
this.StartCoverTable[localBandIndex] = owner;
owner.Memory.Span[..PreferredRowHeight].Fill(value);
return;
}
Span covers = owner.Memory.Span[..PreferredRowHeight];
for (int i = 0; i < PreferredRowHeight; i++)
{
covers[i] += value;
}
}
///
/// Updates a retained start-cover row for one clipped vertical interval.
///
/// The local row-band index.
/// The starting Y coordinate relative to the row band.
/// The ending Y coordinate relative to the row band.
private void UpdateStartCovers(int localBandIndex, int y0, int y1)
{
IMemoryOwner? owner = this.StartCoverTable[localBandIndex];
if (owner is null)
{
owner = this.Allocator.Allocate(PreferredRowHeight, AllocationOptions.Clean);
this.StartCoverTable[localBandIndex] = owner;
}
Span covers = owner.Memory.Span[..PreferredRowHeight];
if (y0 < y1)
{
UpdateCoverTableDown(covers, y0, y1);
}
else
{
UpdateCoverTableUp(covers, y0, y1);
}
}
///
/// Applies a downward winding contribution to one retained start-cover table.
///
/// The retained start-cover rows.
/// The starting Y coordinate relative to the row band.
/// The ending Y coordinate relative to the row band.
private static void UpdateCoverTableDown(Span covers, int y0, int y1)
{
int rowIndex0 = y0 >> FixedShift;
int rowIndex1 = (y1 - 1) >> FixedShift;
int fy0 = y0 - (rowIndex0 << FixedShift);
int fy1 = y1 - (rowIndex1 << FixedShift);
if (rowIndex0 == rowIndex1)
{
covers[rowIndex0] -= fy1 - fy0;
return;
}
covers[rowIndex0] -= FixedOne - fy0;
for (int i = rowIndex0 + 1; i < rowIndex1; i++)
{
covers[i] -= FixedOne;
}
covers[rowIndex1] -= fy1;
}
///
/// Applies an upward winding contribution to one retained start-cover table.
///
/// The retained start-cover rows.
/// The starting Y coordinate relative to the row band.
/// The ending Y coordinate relative to the row band.
private static void UpdateCoverTableUp(Span covers, int y0, int y1)
{
int rowIndex0 = (y0 - 1) >> FixedShift;
int rowIndex1 = y1 >> FixedShift;
int fy0 = y0 - (rowIndex0 << FixedShift);
int fy1 = y1 - (rowIndex1 << FixedShift);
if (rowIndex0 == rowIndex1)
{
covers[rowIndex0] += fy0 - fy1;
return;
}
covers[rowIndex0] += fy0;
for (int i = rowIndex0 - 1; i > rowIndex1; i--)
{
covers[i] += FixedOne;
}
covers[rowIndex1] += FixedOne - fy1;
}
}
///
/// Linearizer that finalizes retained lines into the 32-bit-X encoding.
///
private sealed class LinearizerX32Y16 : Linearizer
{
///
/// Initializes a new instance of the class.
///
public LinearizerX32Y16(
LinearGeometry geometry,
Matrix4x4 residual,
int translateX,
int translateY,
int minX,
int minY,
int width,
int height,
int firstBandIndex,
int rowBandCount,
float samplingOffsetX,
float samplingOffsetY,
MemoryAllocator allocator)
: base(geometry, residual, translateX, translateY, minX, minY, width, height, firstBandIndex, rowBandCount, samplingOffsetX, samplingOffsetY, allocator)
=> this.FinalLines = new LineArrayX32Y16Block?[rowBandCount];
///
/// Gets the finalized retained line blocks for each row band.
///
public LineArrayX32Y16Block?[] FinalLines { get; }
///
protected override LineArrayX32Y16 CreateLineArray() => new();
///
protected override void AppendLine(int rowIndex, int x0, int y0, int x1, int y1)
=> this.GetOrCreateLineArray(rowIndex).AppendLine(x0, y0, x1, y1);
///
protected override void FinalizeLines()
{
for (int i = 0; i < this.RowBandCount; i++)
{
LineArrayX32Y16? lineArray = this.LineArrays[i];
this.FinalLines[i] = lineArray?.GetFrontBlock();
this.FirstBlockLineCounts[i] = lineArray?.GetFrontBlockLineCount() ?? 0;
}
}
///
/// Executes the 32-bit-X linearization pass and returns the retained result.
///
/// The finalized retained raster data.
/// when retained coverage was produced; otherwise .
internal bool TryProcess(out LinearizedRasterData result)
{
if (!this.ProcessCore())
{
result = null!;
return false;
}
result = new LinearizedRasterData(
this.Geometry,
new TileBounds(this.MinX, this.FirstBandIndex, this.Width, this.RowBandCount),
this.FinalLines,
this.FirstBlockLineCounts,
this.StartCoverTable);
return true;
}
}
///
/// Linearizer that finalizes retained lines into the packed 16-bit-X encoding.
///
private sealed class LinearizerX16Y16 : Linearizer
{
///
/// Initializes a new instance of the class.
///
public LinearizerX16Y16(
LinearGeometry geometry,
Matrix4x4 residual,
int translateX,
int translateY,
int minX,
int minY,
int width,
int height,
int firstBandIndex,
int rowBandCount,
float samplingOffsetX,
float samplingOffsetY,
MemoryAllocator allocator)
: base(geometry, residual, translateX, translateY, minX, minY, width, height, firstBandIndex, rowBandCount, samplingOffsetX, samplingOffsetY, allocator)
=> this.FinalLines = new LineArrayX16Y16Block?[rowBandCount];
///
/// Gets the finalized retained line blocks for each row band.
///
public LineArrayX16Y16Block?[] FinalLines { get; }
///
protected override LineArrayX16Y16 CreateLineArray() => new();
///
protected override void AppendLine(int rowIndex, int x0, int y0, int x1, int y1)
=> this.GetOrCreateLineArray(rowIndex).AppendLine(x0, y0, x1, y1);
///
protected override void FinalizeLines()
{
for (int i = 0; i < this.RowBandCount; i++)
{
LineArrayX16Y16? lineArray = this.LineArrays[i];
this.FinalLines[i] = lineArray?.GetFrontBlock();
this.FirstBlockLineCounts[i] = lineArray?.GetFrontBlockLineCount() ?? 0;
}
}
///
/// Executes the 16-bit-X linearization pass and returns the retained result.
///
/// The finalized retained raster data.
/// when retained coverage was produced; otherwise .
internal bool TryProcess(out LinearizedRasterData result)
{
if (!this.ProcessCore())
{
result = null!;
return false;
}
result = new LinearizedRasterData(
this.Geometry,
new TileBounds(this.MinX, this.FirstBandIndex, this.Width, this.RowBandCount),
this.FinalLines,
this.FirstBlockLineCounts,
this.StartCoverTable);
return true;
}
}
}
}