/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { // this is a no-op as we have processes all as an image, we should be able to pass out of before email apply a skip frames outcome Draw(this.textRenderer.FillOperations, this.Brush); Draw(this.textRenderer.OutlineOperations, this.Pen?.StrokeFill); void Draw(List <DrawingOperation> operations, IBrush brush) { if (operations?.Count > 0) { using (BrushApplicator <TPixel> app = brush.CreateApplicator(this.Configuration, this.textRenderer.Options, source, this.SourceRectangle)) { foreach (DrawingOperation operation in operations) { Buffer2D <float> buffer = operation.Map; int startY = operation.Location.Y; int startX = operation.Location.X; int offsetSpan = 0; if (startX + buffer.Height < 0) { continue; } if (startX + buffer.Width < 0) { continue; } if (startX < 0) { offsetSpan = -startX; startX = 0; } if (startX >= source.Width) { continue; } int firstRow = 0; if (startY < 0) { firstRow = -startY; } int maxHeight = source.Height - startY; int end = Math.Min(operation.Map.Height, maxHeight); for (int row = firstRow; row < end; row++) { int y = startY + row; Span <float> span = buffer.GetRowSpan(row).Slice(offsetSpan); app.Apply(span, startX, y); } } } } } }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // Reset offset if necessary. if (minX > 0) { startX = 0; } if (minY > 0) { startY = 0; } int width = maxX - minX; using (var amount = new Buffer <float>(width)) using (BrushApplicator <TPixel> applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options)) { for (int i = 0; i < width; i++) { amount[i] = this.options.BlendPercentage; } Parallel.For( minY, maxY, configuration.ParallelOptions, y => { int offsetY = y - startY; int offsetX = minX - startX; applicator.Apply(amount, offsetX, offsetY); }); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // Reset offset if necessary. if (minX > 0) { startX = 0; } if (minY > 0) { startY = 0; } int width = maxX - minX; using (IBuffer <float> amount = source.MemoryManager.Allocate <float>(width)) using (BrushApplicator <TPixel> applicator = this.brush.CreateApplicator( source, sourceRectangle, this.options)) { amount.Span.Fill(1f); Parallel.For( minY, maxY, configuration.ParallelOptions, y => { int offsetY = y - startY; int offsetX = minX - startX; applicator.Apply(amount.Span, offsetX, offsetY); }); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { Region region = this.Region; Rectangle rect = region.Bounds; // Align start/end positions. int minX = Math.Max(0, rect.Left); int maxX = Math.Min(source.Width, rect.Right); int minY = Math.Max(0, rect.Top); int maxY = Math.Min(source.Height, rect.Bottom); if (minX >= maxX) { return; // no effect inside image; } if (minY >= maxY) { return; // no effect inside image; } int maxIntersections = region.MaxIntersections; float subpixelCount = 4; // we need to offset the pixel grid to account for when we outline a path. // basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5] // and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the# // region to align with the pixel grid. float offset = 0.5f; if (this.Options.Antialias) { offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset. subpixelCount = this.Options.AntialiasSubpixelDepth; if (subpixelCount < 4) { subpixelCount = 4; } } using (BrushApplicator <TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options)) { int scanlineWidth = maxX - minX; using (IMemoryOwner <float> bBuffer = source.MemoryAllocator.Allocate <float>(maxIntersections)) using (IMemoryOwner <float> bScanline = source.MemoryAllocator.Allocate <float>(scanlineWidth)) { bool scanlineDirty = true; float subpixelFraction = 1f / subpixelCount; float subpixelFractionPoint = subpixelFraction / subpixelCount; Span <float> buffer = bBuffer.GetSpan(); Span <float> scanline = bScanline.GetSpan(); bool isSolidBrushWithoutBlending = this.IsSolidBrushWithoutBlending(out SolidBrush <TPixel> solidBrush); for (int y = minY; y < maxY; y++) { if (scanlineDirty) { scanline.Clear(); scanlineDirty = false; } float yPlusOne = y + 1; for (float subPixel = (float)y; subPixel < yPlusOne; subPixel += subpixelFraction) { int pointsFound = region.Scan(subPixel + offset, buffer, configuration); if (pointsFound == 0) { // nothing on this line, skip continue; } QuickSort.Sort(buffer.Slice(0, pointsFound)); for (int point = 0; point < pointsFound; point += 2) { // points will be paired up float scanStart = buffer[point] - minX; float scanEnd = buffer[point + 1] - minX; int startX = (int)MathF.Floor(scanStart + offset); int endX = (int)MathF.Floor(scanEnd + offset); if (startX >= 0 && startX < scanline.Length) { for (float x = scanStart; x < startX + 1; x += subpixelFraction) { scanline[startX] += subpixelFractionPoint; scanlineDirty = true; } } if (endX >= 0 && endX < scanline.Length) { for (float x = endX; x < scanEnd; x += subpixelFraction) { scanline[endX] += subpixelFractionPoint; scanlineDirty = true; } } int nextX = startX + 1; endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge nextX = Math.Max(nextX, 0); for (int x = nextX; x < endX; x++) { scanline[x] += subpixelFraction; scanlineDirty = true; } } } if (scanlineDirty) { if (!this.Options.Antialias) { bool hasOnes = false; bool hasZeros = false; for (int x = 0; x < scanlineWidth; x++) { if (scanline[x] >= 0.5) { scanline[x] = 1; hasOnes = true; } else { scanline[x] = 0; hasZeros = true; } } if (isSolidBrushWithoutBlending && hasOnes != hasZeros) { if (hasOnes) { source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrush.Color); } continue; } } applicator.Apply(scanline, minX, y); } } } } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); int width = maxX - minX; var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); IBrush brush = this.definition.Brush; GraphicsOptions options = this.definition.Options; // If there's no reason for blending, then avoid it. if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) { ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); TPixel colorPixel = solidBrush.Color.ToPixel <TPixel>(); ParallelHelper.IterateRows( workingRect, parallelSettings, rows => { for (int y = rows.Min; y < rows.Max; y++) { source.GetPixelRowSpan(y).Slice(minX, width).Fill(colorPixel); } }); } else { // Reset offset if necessary. if (minX > 0) { startX = 0; } if (minY > 0) { startY = 0; } using (IMemoryOwner <float> amount = source.MemoryAllocator.Allocate <float>(width)) using (BrushApplicator <TPixel> applicator = brush.CreateApplicator( source, sourceRectangle, options)) { amount.GetSpan().Fill(1f); ParallelHelper.IterateRows( workingRect, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { int offsetY = y - startY; int offsetX = minX - startX; applicator.Apply(amount.GetSpan(), offsetX, offsetY); } }); } } }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { Region region = this.Region; Rectangle rect = region.Bounds; // Align start/end positions. int minX = Math.Max(0, rect.Left); int maxX = Math.Min(source.Width, rect.Right); int minY = Math.Max(0, rect.Top); int maxY = Math.Min(source.Height, rect.Bottom); if (minX >= maxX) { return; // no effect inside image; } if (minY >= maxY) { return; // no effect inside image; } ArrayPool <float> arrayPool = ArrayPool <float> .Shared; int maxIntersections = region.MaxIntersections; float subpixelCount = 4; if (this.Options.Antialias) { subpixelCount = this.Options.AntialiasSubpixelDepth; if (subpixelCount < 4) { subpixelCount = 4; } } using (BrushApplicator <TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options)) { float[] buffer = arrayPool.Rent(maxIntersections); int scanlineWidth = maxX - minX; using (var scanline = new Buffer <float>(scanlineWidth)) { try { bool scanlineDirty = true; for (int y = minY; y < maxY; y++) { if (scanlineDirty) { // clear the buffer for (int x = 0; x < scanlineWidth; x++) { scanline[x] = 0; } scanlineDirty = false; } float subpixelFraction = 1f / subpixelCount; float subpixelFractionPoint = subpixelFraction / subpixelCount; for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { int pointsFound = region.Scan(subPixel, buffer, 0); if (pointsFound == 0) { // nothing on this line skip continue; } QuickSort(new Span <float>(buffer, 0, pointsFound)); for (int point = 0; point < pointsFound; point += 2) { // points will be paired up float scanStart = buffer[point] - minX; float scanEnd = buffer[point + 1] - minX; int startX = (int)MathF.Floor(scanStart); int endX = (int)MathF.Floor(scanEnd); if (startX >= 0 && startX < scanline.Length) { for (float x = scanStart; x < startX + 1; x += subpixelFraction) { scanline[startX] += subpixelFractionPoint; scanlineDirty = true; } } if (endX >= 0 && endX < scanline.Length) { for (float x = endX; x < scanEnd; x += subpixelFraction) { scanline[endX] += subpixelFractionPoint; scanlineDirty = true; } } int nextX = startX + 1; endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge nextX = Math.Max(nextX, 0); for (int x = nextX; x < endX; x++) { scanline[x] += subpixelFraction; scanlineDirty = true; } } } if (scanlineDirty) { if (!this.Options.Antialias) { for (int x = 0; x < scanlineWidth; x++) { if (scanline[x] > 0.5) { scanline[x] = 1; } else { scanline[x] = 0; } } } applicator.Apply(scanline, minX, y); } } } finally { arrayPool.Return(buffer); } } } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { Configuration configuration = this.Configuration; ShapeOptions shapeOptions = this.definition.Options.ShapeOptions; GraphicsOptions graphicsOptions = this.definition.Options.GraphicsOptions; IBrush brush = this.definition.Brush; Region region = this.definition.Region; Rectangle rect = region.Bounds; bool isSolidBrushWithoutBlending = IsSolidBrushWithoutBlending(graphicsOptions, this.definition.Brush, out SolidBrush solidBrush); TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel <TPixel>() : default; // Align start/end positions. int minX = Math.Max(0, rect.Left); int maxX = Math.Min(source.Width, rect.Right); int minY = Math.Max(0, rect.Top); int maxY = Math.Min(source.Height, rect.Bottom); if (minX >= maxX) { return; // no effect inside image; } if (minY >= maxY) { return; // no effect inside image; } int subpixelCount = FillRegionProcessor.MinimumSubpixelCount; // we need to offset the pixel grid to account for when we outline a path. // basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5] // and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the# // region to align with the pixel grid. if (graphicsOptions.Antialias) { subpixelCount = Math.Max(subpixelCount, graphicsOptions.AntialiasSubpixelDepth); } using BrushApplicator <TPixel> applicator = brush.CreateApplicator(configuration, graphicsOptions, source, rect); int scanlineWidth = maxX - minX; MemoryAllocator allocator = this.Configuration.MemoryAllocator; bool scanlineDirty = true; var scanner = PolygonScanner.Create( region.Shape, minY, maxY, subpixelCount, shapeOptions.IntersectionRule, configuration.MemoryAllocator); try { using IMemoryOwner <float> bScanline = allocator.Allocate <float>(scanlineWidth); Span <float> scanline = bScanline.Memory.Span; while (scanner.MoveToNextPixelLine()) { if (scanlineDirty) { scanline.Clear(); } scanlineDirty = scanner.ScanCurrentPixelLineInto(minX, 0, scanline); if (scanlineDirty) { int y = scanner.PixelLineY; if (!graphicsOptions.Antialias) { bool hasOnes = false; bool hasZeros = false; for (int x = 0; x < scanline.Length; x++) { if (scanline[x] >= 0.5) { scanline[x] = 1; hasOnes = true; } else { scanline[x] = 0; hasZeros = true; } } if (isSolidBrushWithoutBlending && hasOnes != hasZeros) { if (hasOnes) { source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor); } continue; } } applicator.Apply(scanline, minX, y); } } } finally { // ref structs can't implement interfaces so technically PolygonScanner is not IDisposable scanner.Dispose(); } }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { Region region = this.Region; Rectangle rect = region.Bounds; // Align start/end positions. int minX = Math.Max(0, rect.Left); int maxX = Math.Min(source.Width, rect.Right); int minY = Math.Max(0, rect.Top); int maxY = Math.Min(source.Height, rect.Bottom); if (minX >= maxX) { return; // no effect inside image; } if (minY >= maxY) { return; // no effect inside image; } ArrayPool <float> arrayPool = ArrayPool <float> .Shared; int maxIntersections = region.MaxIntersections; float subpixelCount = 4; // we need to offset the pixel grid to account for when we outline a path. // basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5] // and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the# // region to alline with the pixel grid. float offset = 0.5f; if (this.Options.Antialias) { offset = 0f; // we are antialising skip offsetting as real antalising should take care of offset. subpixelCount = this.Options.AntialiasSubpixelDepth; if (subpixelCount < 4) { subpixelCount = 4; } } using (BrushApplicator <TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options)) { float[] buffer = arrayPool.Rent(maxIntersections); int scanlineWidth = maxX - minX; using (var scanline = new Buffer <float>(scanlineWidth)) { try { bool scanlineDirty = true; for (int y = minY; y < maxY; y++) { if (scanlineDirty) { // clear the buffer for (int x = 0; x < scanlineWidth; x++) { scanline[x] = 0; } scanlineDirty = false; } float subpixelFraction = 1f / subpixelCount; float subpixelFractionPoint = subpixelFraction / subpixelCount; for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { int pointsFound = region.Scan(subPixel + offset, buffer, 0); if (pointsFound == 0) { // nothing on this line skip continue; } QuickSort(new Span <float>(buffer, 0, pointsFound)); for (int point = 0; point < pointsFound; point += 2) { // points will be paired up float scanStart = buffer[point] - minX; float scanEnd = buffer[point + 1] - minX; int startX = (int)MathF.Floor(scanStart + offset); int endX = (int)MathF.Floor(scanEnd + offset); if (startX >= 0 && startX < scanline.Length) { for (float x = scanStart; x < startX + 1; x += subpixelFraction) { scanline[startX] += subpixelFractionPoint; scanlineDirty = true; } } if (endX >= 0 && endX < scanline.Length) { for (float x = endX; x < scanEnd; x += subpixelFraction) { scanline[endX] += subpixelFractionPoint; scanlineDirty = true; } } int nextX = startX + 1; endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge nextX = Math.Max(nextX, 0); for (int x = nextX; x < endX; x++) { scanline[x] += subpixelFraction; scanlineDirty = true; } } } if (scanlineDirty) { if (!this.Options.Antialias) { for (int x = 0; x < scanlineWidth; x++) { if (scanline[x] >= 0.5) { scanline[x] = 1; } else { scanline[x] = 0; } } } applicator.Apply(scanline, minX, y); } } } finally { arrayPool.Return(buffer); } } } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); int width = maxX - minX; // If there's no reason for blending, then avoid it. if (this.IsSolidBrushWithoutBlending(out SolidBrush <TPixel> solidBrush)) { Parallel.For( minY, maxY, configuration.ParallelOptions, y => { source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color); }); } else { // Reset offset if necessary. if (minX > 0) { startX = 0; } if (minY > 0) { startY = 0; } using (IBuffer <float> amount = source.MemoryManager.Allocate <float>(width)) using (BrushApplicator <TPixel> applicator = this.brush.CreateApplicator( source, sourceRectangle, this.options)) { amount.Span.Fill(1f); Parallel.For( minY, maxY, configuration.ParallelOptions, y => { int offsetY = y - startY; int offsetX = minX - startX; applicator.Apply(amount.Span, offsetX, offsetY); }); } } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { Configuration configuration = this.Configuration; ShapeOptions shapeOptions = this.definition.Options.ShapeOptions; GraphicsOptions graphicsOptions = this.definition.Options.GraphicsOptions; IBrush brush = this.definition.Brush; bool isSolidBrushWithoutBlending = IsSolidBrushWithoutBlending(graphicsOptions, brush, out SolidBrush solidBrush); TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel <TPixel>() : default; // Align start/end positions. var interest = Rectangle.Intersect(this.bounds, source.Bounds()); if (interest.Equals(Rectangle.Empty)) { return; // No effect inside image; } int minX = interest.Left; int subpixelCount = FillPathProcessor.MinimumSubpixelCount; // We need to offset the pixel grid to account for when we outline a path. // basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5] // and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the# // region to align with the pixel grid. if (graphicsOptions.Antialias) { subpixelCount = Math.Max(subpixelCount, graphicsOptions.AntialiasSubpixelDepth); } using BrushApplicator <TPixel> applicator = brush.CreateApplicator(configuration, graphicsOptions, source, interest); int scanlineWidth = interest.Width; MemoryAllocator allocator = this.Configuration.MemoryAllocator; bool scanlineDirty = true; var scanner = PolygonScanner.Create( this.path, interest.Top, interest.Bottom, subpixelCount, shapeOptions.IntersectionRule, configuration.MemoryAllocator); try { using IMemoryOwner <float> bScanline = allocator.Allocate <float>(scanlineWidth); Span <float> scanline = bScanline.Memory.Span; while (scanner.MoveToNextPixelLine()) { if (scanlineDirty) { scanline.Clear(); } scanlineDirty = scanner.ScanCurrentPixelLineInto(minX, 0F, scanline); if (scanlineDirty) { int y = scanner.PixelLineY; if (!graphicsOptions.Antialias) { bool hasOnes = false; bool hasZeros = false; for (int x = 0; x < scanline.Length; x++) { if (scanline[x] >= 0.5F) { scanline[x] = 1F; hasOnes = true; } else { scanline[x] = 0F; hasZeros = true; } } if (isSolidBrushWithoutBlending && hasOnes != hasZeros) { if (hasOnes) { source.PixelBuffer.DangerousGetRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor); } continue; } } applicator.Apply(scanline, minX, y); } } } finally { scanner.Dispose(); } }