/// <summary> /// Initializes a new instance of the <see cref="FillRegionProcessor" /> class. /// </summary> /// <param name="options">The graphics options.</param> /// <param name="brush">The details how to fill the region of interest.</param> /// <param name="region">The region of interest to be filled.</param> public FillRegionProcessor(ShapeGraphicsOptions options, IBrush brush, Region region) { this.Region = region; this.Brush = brush; this.ShapeOptions = options; this.Options = (GraphicsOptions)options; }
/// <summary> /// Creates a <see cref="FontGlyphSource"/> instance. /// </summary> public FontGlyphSource(IFontInstance fontInstance, float size, string name, char firstChar = ' ', char lastChar = '~') { if (!float.IsFinite(size) || float.IsNegative(size)) { throw new ArgumentOutOfRangeException(nameof(size), size, nameof(size) + " must be finite and positive."); } if (lastChar < firstChar) { throw new ArgumentException(nameof(LastChar) + " can't be lower than " + nameof(firstChar)); } FontInstance = fontInstance ?? throw new ArgumentNullException(nameof(fontInstance)); FirstChar = firstChar; LastChar = lastChar; Size = size; Name = name; glyphPaths = CreatePaths(out pathColors, out glyphSizes, out renderOffsets); ShapeGraphicsOptions = new ShapeGraphicsOptions { ShapeOptions = { IntersectionRule = IntersectionRule.Nonzero }, }; }
public void LargeGeoJson_Lines(TestImageProvider <Rgba32> provider, string geoJsonFile, int aa, float sx, float sy) { string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(geoJsonFile)); PointF[][] points = PolygonFactory.GetGeoJsonPoints(jsonContent, Matrix3x2.CreateScale(sx, sy)); using Image <Rgba32> image = provider.GetImage(); var options = new ShapeGraphicsOptions() { GraphicsOptions = new GraphicsOptions() { Antialias = aa > 0, AntialiasSubpixelDepth = aa }, }; foreach (PointF[] loop in points) { image.Mutate(c => c.DrawLines(options, Color.White, 1.0f, loop)); } string details = $"_{System.IO.Path.GetFileName(geoJsonFile)}_{sx}x{sy}_aa{aa}"; image.DebugSave( provider, details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); }
private Image <Rgba32> FillGeoJsonPolygons(TestImageProvider <Rgba32> provider, string geoJsonFile, int aa, Vector2 scale, Vector2 pixelOffset) { string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(geoJsonFile)); PointF[][] points = PolygonFactory.GetGeoJsonPoints(jsonContent, Matrix3x2.CreateScale(scale) * Matrix3x2.CreateTranslation(pixelOffset)); Image <Rgba32> image = provider.GetImage(); var options = new ShapeGraphicsOptions() { GraphicsOptions = new GraphicsOptions() { Antialias = aa > 0, AntialiasSubpixelDepth = aa }, }; var rnd = new Random(42); byte[] rgb = new byte[3]; foreach (PointF[] loop in points) { rnd.NextBytes(rgb); var color = Color.FromRgb(rgb[0], rgb[1], rgb[2]); image.Mutate(c => c.FillPolygon(options, color, loop)); } return(image); }
public static void DrawLine(Image tex, int x0, int y0, int x1, int y1, Color col, int thickness) { tex.Mutate(ctx => { var options = new ShapeGraphicsOptions(); ctx.DrawLines(options, col, thickness, new PointF(x0, y0), new PointF(x1, y1)); }); }
public void DrawTextCoreOld() { using (var image = new Image <Rgba32>(800, 800)) { Fonts.Font font = Fonts.SystemFonts.CreateFont("Arial", 12); image.Mutate(x => DrawTextOldVersion( x, new TextGraphicsOptions { GraphicsOptions = { Antialias = true }, TextOptions = { WrapTextWidth = 780 } }, this.TextToRender, font, Processing.Brushes.Solid(Color.HotPink), null, new PointF(10, 10))); } IImageProcessingContext DrawTextOldVersion( IImageProcessingContext source, TextGraphicsOptions options, string text, Fonts.Font font, IBrush brush, IPen pen, PointF location) { const float dpiX = 72; const float dpiY = 72; var style = new Fonts.RendererOptions(font, dpiX, dpiY, location) { ApplyKerning = options.TextOptions.ApplyKerning, TabWidth = options.TextOptions.TabWidth, WrappingWidth = options.TextOptions.WrapTextWidth, HorizontalAlignment = options.TextOptions.HorizontalAlignment, VerticalAlignment = options.TextOptions.VerticalAlignment }; IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, style); var pathOptions = new ShapeGraphicsOptions() { GraphicsOptions = options.GraphicsOptions }; if (brush != null) { source.Fill(pathOptions, brush, glyphs); } if (pen != null) { source.Draw(pathOptions, pen, glyphs); } return(source); } }
/// <inheritdoc /> public System.IO.Stream FillPolygon(System.IO.Stream sourceImageStream, string polygonColorCode, Polygon polygon) { #region validation if (sourceImageStream == null) { throw new ArgumentNullException(nameof(sourceImageStream)); } if (string.IsNullOrEmpty(polygonColorCode)) { throw new ArgumentNullException(nameof(polygonColorCode)); } Regex regex = HexColorUtil.GetHexColorRegex(); if (!regex.IsMatch(polygonColorCode)) { throw new ArgumentException($"Color code {polygonColorCode } is malformed!"); } if (polygon == null) { throw new ArgumentNullException(nameof(polygon)); } if (polygon.Points == null) { throw new ArgumentException(nameof(polygon.Points)); } if (polygon.Points.Count() < 2) { throw new ArgumentException($"Polygon {nameof(polygon)} has to contain more than one entry!"); } #endregion IBrush brush = new SolidBrush(Rgba32Util.InitFromHex(polygonColorCode)); ShapeGraphicsOptions graphicsOptions = new ShapeGraphicsOptions() { GraphicsOptions = new GraphicsOptions() { ColorBlendingMode = PixelColorBlendingMode.Normal } }; using (Image <Rgba32> originalImage = LoadImageFromStream <Rgba32>(sourceImageStream, out IImageFormat imageFormat)) { originalImage.Mutate(c => c.FillPolygon(graphicsOptions, brush, polygon.Points.Select(p => new PointF(p.X, p.Y)).ToArray())); return(SaveAsStream(originalImage, imageFormat)); } }
public void Brush() { this.operations.Clear(this.nonDefaultOptions, this.brush, this.path); FillPathProcessor processor = this.Verify <FillPathProcessor>(); ShapeGraphicsOptions expectedOptions = this.nonDefaultOptions; Assert.Equal(expectedOptions.ShapeOptions, processor.Options.ShapeOptions); Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); Assert.Equal(this.path, processor.Shape); Assert.Equal(this.brush, processor.Brush); }
private void RenderShapeToCanvas(SvgGraphicsElement svgGraphicsElement, IPath path) { var matrix = CalulateUpdatedMatrix(svgGraphicsElement); var brush = svgGraphicsElement.CreateFillPaintServer()?.Accept(BrushGenerator <TPixel> .Instance); IBrush strokFill = null; IPath outline = null; if (svgGraphicsElement.StrokeWidth > 0) { strokFill = svgGraphicsElement.CreateStrokePaintServer()?.Accept(BrushGenerator <TPixel> .Instance); if (strokFill != null) { var pattern = svgGraphicsElement.Style.StrokeDashArray.Value?.Select(X => X.Value).ToArray(); var joint = svgGraphicsElement.Style.StrokeLineJoin.AsJointStyle(); var end = svgGraphicsElement.Style.StrokeLineCap.AsEndCapStyle(); if (pattern == null || pattern.Length == 0) { outline = path.GenerateOutline(svgGraphicsElement.StrokeWidth, joint, end); } else { outline = path.GenerateOutline(svgGraphicsElement.StrokeWidth, pattern, false, joint, end); } } } var shapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.Nonzero }; var shapeGraphicsOptions = new ShapeGraphicsOptions(new GraphicsOptions(), shapeOptions); if (brush != null) { image.Fill(shapeGraphicsOptions, brush, path.Transform(matrix)); } if (outline != null && strokFill != null) { image.Fill(shapeGraphicsOptions, strokFill, outline.Transform(matrix)); } }
protected override Task <byte[]> RenderGlyphs(IPathCollection glyphs, Int32 squareSize, Rgba32 foregroundColor, Rgba32 backgroundColor, CancellationToken cancellationToken) { using (var img = new Image <Rgba32>(squareSize, squareSize)) { var graphicsOptions = new ShapeGraphicsOptions(); var brush = new SolidBrush(foregroundColor); img.Mutate(ctx => ctx .Fill(backgroundColor) .Fill(graphicsOptions, brush, glyphs)); using (var ms = new MemoryStream()) { img.SaveAsPng(ms); ms.Seek(0, SeekOrigin.Begin); return(Task.FromResult(ms.ToArray())); } } }
public void ColorSet() { this.operations.Clear(this.nonDefaultOptions, Color.Red, this.path); FillRegionProcessor processor = this.Verify <FillRegionProcessor>(); ShapeGraphicsOptions expectedOptions = this.nonDefaultOptions; Assert.Equal(expectedOptions.ShapeOptions, processor.Options.ShapeOptions); Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); Assert.Equal(this.path, processor.Region); Assert.NotEqual(this.brush, processor.Brush); SolidBrush brush = Assert.IsType <SolidBrush>(processor.Brush); Assert.Equal(Color.Red, brush.Color); }
public void RectangleHasMissingBottomRightCorner() { var testImage = new Image <Rgb24>(Configuration.Default, 100, 100, Color.Black); var rectangle = new RectangleF(10.5f, 10.5f, 79, 79); var pen = new Pen(Color.Red, 1f); var options = new ShapeGraphicsOptions() { BlendPercentage = 1, Antialias = false, ColorBlendingMode = PixelColorBlendingMode.Normal, AntialiasSubpixelDepth = 0, }; testImage.Mutate(x => x.Draw(pen, rectangle)); testImage.Save(this.TestOutputDirectory.CombinePath("rectangle.png")); // expected // 88 | 89 | 90 // 88 | B | R | B // 89 | R | R | B // 90 | B | B | B // smoke test: when this test fails, bug in ImageSharp has been fixed // should be black Assert.AreEqual( Color.Black.ToPixel <Rgb24>(), testImage[88, 88]); // should be red (bug is that it is blended) Assert.AreEqual( new Rgb24(249, 0, 0), testImage[89, 89]); // should be black Assert.AreEqual( Color.Black.ToPixel <Rgb24>(), testImage[90, 90]); }
private Visualizer() { var fontDir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts); var dir = new DirectoryInfo(fontDir); var arialFile = dir.EnumerateFiles("arial.ttf").First(); var fonts = new FontCollection(); var arial = fonts.Install(arialFile.FullName); shapeGraphicsOptions = new ShapeGraphicsOptions(); foregroundBrush = new SolidBrush(Color.Black); edgeThickness = 1; font = arial.CreateFont(12); nodeHeight = 24; nodeSegmentWidth = 50; layerHeight = nodeHeight * 3; pageMargin = 12; drawables = new List <Drawable>(); }
static IImageProcessingContext DrawTextOldVersion( IImageProcessingContext source, TextGraphicsOptions options, string text, Fonts.Font font, IBrush brush, IPen pen, PointF location) { var style = new Fonts.RendererOptions(font, options.TextOptions.DpiX, options.TextOptions.DpiY, location) { ApplyKerning = options.TextOptions.ApplyKerning, TabWidth = options.TextOptions.TabWidth, WrappingWidth = options.TextOptions.WrapTextWidth, HorizontalAlignment = options.TextOptions.HorizontalAlignment, VerticalAlignment = options.TextOptions.VerticalAlignment }; IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, style); var pathOptions = new ShapeGraphicsOptions() { GraphicsOptions = options.GraphicsOptions }; if (brush != null) { source.Fill(pathOptions, brush, glyphs); } if (pen != null) { source.Draw(pathOptions, pen, glyphs); } return(source); }
public RecursiveImageProcessor(ShapeGraphicsOptions options, IPath path, Action <IImageProcessingContext> innerProcessing) { this.Options = options; this.Path = path; this.InnerProcessingOperations = innerProcessing; }
/// <summary> /// Initializes a new instance of the <see cref="FillPathProcessor" /> class. /// </summary> /// <param name="options">The graphics options.</param> /// <param name="brush">The details how to fill the region of interest.</param> /// <param name="shape">The shape to be filled.</param> public FillPathProcessor(ShapeGraphicsOptions options, IBrush brush, IPath shape) { this.Shape = shape; this.Brush = brush; this.Options = options; }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { Configuration configuration = this.Configuration; GraphicsOptions options = this.definition.Options; ShapeGraphicsOptions shapeOptions = this.definition.ShapeOptions; IBrush brush = this.definition.Brush; Region region = this.definition.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 (options.Antialias) { offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset. subpixelCount = options.AntialiasSubpixelDepth; if (subpixelCount < 4) { subpixelCount = 4; } } using (BrushApplicator <TPixel> applicator = brush.CreateApplicator(configuration, options, source, rect)) { int scanlineWidth = maxX - minX; MemoryAllocator allocator = this.Configuration.MemoryAllocator; using (IMemoryOwner <float> bBuffer = allocator.Allocate <float>(maxIntersections)) using (IMemoryOwner <float> bScanline = allocator.Allocate <float>(scanlineWidth)) { bool scanlineDirty = true; float subpixelFraction = 1f / subpixelCount; float subpixelFractionPoint = subpixelFraction / subpixelCount; Span <float> buffer = bBuffer.Memory.Span; Span <float> scanline = bScanline.Memory.Span; bool isSolidBrushWithoutBlending = this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush); TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel <TPixel>() : default; for (int y = minY; y < maxY; y++) { if (scanlineDirty) { scanline.Clear(); scanlineDirty = false; } float yPlusOne = y + 1; for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction) { int pointsFound = region.Scan(subPixel + offset, buffer, configuration, shapeOptions.IntersectionRule); if (pointsFound == 0) { // nothing on this line, skip continue; } for (int point = 0; point < pointsFound && point < buffer.Length - 1; 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 (!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(solidBrushColor); } continue; } } applicator.Apply(scanline, minX, y); } } } } }
/// <summary> /// Initializes a new instance of the <see cref="DrawPathProcessor" /> class. /// </summary> /// <param name="options">The graphics options.</param> /// <param name="pen">The details how to outline the region of interest.</param> /// <param name="shape">The shape to be filled.</param> public DrawPathProcessor(ShapeGraphicsOptions options, IPen pen, IPath shape) { this.Shape = shape; this.Pen = pen; this.Options = options; }