public void DoesNotOverwriteIsDirtyFlagWhenOnlyFillingSubpixels() { var scanner = PolygonScanner.Create(new RectangularPolygon(0.3f, 0.2f, 0.7f, 1.423f), 0, 20, 1, IntersectionRule.OddEven, MemoryAllocator.Default); float[] buffer = new float[12]; scanner.MoveToNextPixelLine(); // offset bool isDirty = scanner.ScanCurrentPixelLineInto(0, 0, buffer.AsSpan()); Assert.True(isDirty); }
/// <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 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(); } }