protected override void Initialize()
        {
            // Create a timer that will simulate the gameloop
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromSeconds(1/60);
            _timer.Tick += TimerTick;
            _timer.Start();

            // Create the graphics options and initialize FRB with them
            var graphicsOptions = new GraphicsOptions(this, Graphics);
            graphicsOptions.SuspendDeviceReset();
            graphicsOptions.ResolutionWidth = RenderWidth;
            graphicsOptions.ResolutionHeight = RenderHeight;
            graphicsOptions.ResumeDeviceReset();
            FlatRedBallServices.InitializeFlatRedBall(this, Graphics, graphicsOptions);

            base.Initialize();
        }
            /// <summary>
            /// Initializes a new instance of the <see cref="RecolorBrushApplicator" /> class.
            /// </summary>
            /// <param name="sourcePixels">The source pixels.</param>
            /// <param name="sourceColor">Color of the source.</param>
            /// <param name="targetColor">Color of the target.</param>
            /// <param name="threshold">The threshold .</param>
            /// <param name="options">The options</param>
            public RecolorBrushApplicator(PixelAccessor <TPixel> sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options)
                : base(sourcePixels, options)
            {
                this.sourceColor      = sourceColor.ToVector4();
                this.targetColor      = targetColor.ToVector4();
                this.targetColorPixel = targetColor;

                // Lets hack a min max extreams for a color space by letteing the IPackedPixel clamp our values to something in the correct spaces :)
                TPixel maxColor = default(TPixel);

                maxColor.PackFromVector4(new Vector4(float.MaxValue));
                TPixel minColor = default(TPixel);

                minColor.PackFromVector4(new Vector4(float.MinValue));
                this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold;
            }
Exemple #3
0
 /// <inheritdoc />
 public BrushApplicator <TPixel> CreateApplicator(ImageFrame <TPixel> source, RectangleF region, GraphicsOptions options)
 {
     return(new PatternBrushApplicator(source, this.pattern, this.patternVector, options));
 }
Exemple #4
0
 /// <summary>
 /// Initializes a new instance of the <see cref="BrushApplicator{TPixel}"/> class.
 /// </summary>
 /// <param name="target">The target.</param>
 /// <param name="options">The options.</param>
 internal BrushApplicator(ImageFrame <TPixel> target, GraphicsOptions options)
 {
     this.Target  = target;
     this.Options = options;
     this.Blender = PixelOperations <TPixel> .Instance.GetPixelBlender(options);
 }
Exemple #5
0
 /// <summary>
 /// Applies a radial vignette effect to an image.
 /// </summary>
 /// <typeparam name="TPixel">The pixel format.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options effecting pixel blending.</param>
 /// <param name="color">The color to set as the vignette.</param>
 /// <param name="radiusX">The the x-radius.</param>
 /// <param name="radiusY">The the y-radius.</param>
 /// <param name="rectangle">
 /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
 /// </param>
 /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
 public static IImageProcessingContext <TPixel> Vignette <TPixel>(this IImageProcessingContext <TPixel> source, GraphicsOptions options, TPixel color, float radiusX, float radiusY, Rectangle rectangle)
     where TPixel : struct, IPixel <TPixel>
 => source.VignetteInternal(options, color, radiusX, radiusY, rectangle);
Exemple #6
0
 /// <summary>
 /// Applies a radial vignette effect to an image.
 /// </summary>
 /// <typeparam name="TPixel">The pixel format.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options effecting pixel blending.</param>
 /// <param name="color">The color to set as the vignette.</param>
 /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
 public static IImageProcessingContext <TPixel> Vignette <TPixel>(this IImageProcessingContext <TPixel> source, GraphicsOptions options, TPixel color)
     where TPixel : struct, IPixel <TPixel>
 => source.VignetteInternal(options, color, ValueSize.PercentageOfWidth(.5f), ValueSize.PercentageOfHeight(.5f));
 /// <summary>
 /// Initializes a new instance of the <see cref="ImageBrushApplicator"/> class.
 /// </summary>
 /// <param name="target">The target image.</param>
 /// <param name="image">The image.</param>
 /// <param name="region">The region.</param>
 /// <param name="options">The options</param>
 public ImageBrushApplicator(ImageFrame <TPixel> target, ImageFrame <TPixel> image, RectangleF region, GraphicsOptions options)
     : base(target, options)
 {
     this.source  = image;
     this.xLength = image.Width;
     this.yLength = image.Height;
     this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0);
     this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0);
 }
Exemple #8
0
 /// <summary>
 /// Applies a radial vignette effect to an image.
 /// </summary>
 /// <typeparam name="TPixel">The pixel format.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options effecting pixel blending.</param>
 /// <param name="radiusX">The the x-radius.</param>
 /// <param name="radiusY">The the y-radius.</param>
 /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
 public static IImageProcessingContext <TPixel> Vignette <TPixel>(this IImageProcessingContext <TPixel> source, GraphicsOptions options, float radiusX, float radiusY)
     where TPixel : struct, IPixel <TPixel>
 => source.VignetteInternal(options, NamedColors <TPixel> .Black, radiusX, radiusY);
        /// <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;

            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);
                            }
                        });
                    }
            }
        }
Exemple #10
0
        /// <inheritdoc/>
        protected override void OnFrameApply(ImageFrame <TPixel> source)
        {
            Configuration   configuration = this.Configuration;
            GraphicsOptions options       = this.definition.Options;
            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;
                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.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);
                                if (pointsFound == 0)
                                {
                                    // nothing on this line, skip
                                    continue;
                                }

                                QuickSort.Sort(buffer.Slice(0, pointsFound));

                                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);
                            }
                        }
                    }
            }
        }
Exemple #11
0
 /// <summary>
 /// Initializes a new instance of the <see cref="GlowProcessor{TPixel}" /> class.
 /// </summary>
 /// <param name="color">The color or the glow.</param>
 /// <param name="options">The options effecting blending and composition.</param>
 public GlowProcessor(TPixel color, GraphicsOptions options)
 {
     this.options   = options;
     this.GlowColor = color;
     this.blender   = PixelOperations <TPixel> .Instance.GetPixelBlender(this.options.BlenderMode);
 }
 /// <inheritdoc cref="IBrush{TPixel}" />
 public abstract BrushApplicator <TPixel> CreateApplicator(
     ImageFrame <TPixel> source,
     RectangleF region,
     GraphicsOptions options);
 /// <summary>
 /// Flood fills the image with the specified brush.
 /// </summary>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The graphics options.</param>
 /// <param name="brush">The details how to fill the region of interest.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext Fill(
     this IImageProcessingContext source,
     GraphicsOptions options,
     IBrush brush) =>
 source.ApplyProcessor(new FillProcessor(brush, options));
 /// <summary>
 /// Flood fills the image with in the region with the specified color.
 /// </summary>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options.</param>
 /// <param name="color">The color.</param>
 /// <param name="region">The region.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext Fill(
     this IImageProcessingContext source,
     GraphicsOptions options,
     Color color,
     Region region) =>
 source.Fill(options, new SolidBrush(color), region);
Exemple #15
0
 /// <summary>
 /// Draws the outline of the polygon with the provided pen.
 /// </summary>
 /// <typeparam name="TPixel">The type of the color.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options.</param>
 /// <param name="pen">The pen.</param>
 /// <param name="path">The path.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext <TPixel> Draw <TPixel>(this IImageProcessingContext <TPixel> source, GraphicsOptions options, IPen <TPixel> pen, IPath path)
     where TPixel : struct, IPixel <TPixel>
 => source.Fill(options, pen.StrokeFill, new ShapePath(path, pen));
 /// <summary>
 /// Flood fills the image in the shape of the provided rectangle with the specified brush.
 /// </summary>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options.</param>
 /// <param name="color">The color.</param>
 /// <param name="shape">The shape.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext Fill(
     this IImageProcessingContext source,
     GraphicsOptions options,
     Color color,
     RectangleF shape) =>
 source.Fill(options, new SolidBrush(color), shape);
Exemple #17
0
 /// <summary>
 /// Draws the outline of the polygon with the provided brush at the provided thickness.
 /// </summary>
 /// <typeparam name="TPixel">The type of the color.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options.</param>
 /// <param name="brush">The brush.</param>
 /// <param name="thickness">The thickness.</param>
 /// <param name="path">The shape.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext <TPixel> Draw <TPixel>(this IImageProcessingContext <TPixel> source, GraphicsOptions options, IBrush <TPixel> brush, float thickness, IPath path)
     where TPixel : struct, IPixel <TPixel>
 => source.Draw(options, new Pen <TPixel>(brush, thickness), path);
Exemple #18
0
 /// <summary>
 /// Applies a radial vignette effect to an image.
 /// </summary>
 /// <typeparam name="TPixel">The pixel format.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options effecting pixel blending.</param>
 /// <param name="rectangle">
 /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
 /// </param>
 /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
 public static IImageProcessingContext <TPixel> Vignette <TPixel>(this IImageProcessingContext <TPixel> source, GraphicsOptions options, Rectangle rectangle)
     where TPixel : struct, IPixel <TPixel>
 => source.VignetteInternal(options, NamedColors <TPixel> .Black, ValueSize.PercentageOfWidth(.5f), ValueSize.PercentageOfHeight(.5f), rectangle);
Exemple #19
0
 /// <summary>
 /// Draws the outline of the polygon with the provided brush at the provided thickness.
 /// </summary>
 /// <typeparam name="TPixel">The type of the color.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options.</param>
 /// <param name="color">The color.</param>
 /// <param name="thickness">The thickness.</param>
 /// <param name="path">The path.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext <TPixel> Draw <TPixel>(this IImageProcessingContext <TPixel> source, GraphicsOptions options, TPixel color, float thickness, IPath path)
     where TPixel : struct, IPixel <TPixel>
 => source.Draw(options, new SolidBrush <TPixel>(color), thickness, path);
Exemple #20
0
 private static IImageProcessingContext <TPixel> VignetteInternal <TPixel>(this IImageProcessingContext <TPixel> source, GraphicsOptions options, TPixel color, ValueSize radiusX, ValueSize radiusY)
     where TPixel : struct, IPixel <TPixel>
 => source.ApplyProcessor(new VignetteProcessor <TPixel>(color, radiusX, radiusY, options));
Exemple #21
0
 /// <summary>
 /// Initializes a new instance of the <see cref="BackgroundColorProcessor{TPixel}"/> class.
 /// </summary>
 /// <param name="color">The <typeparamref name="TPixel"/> to set the background color to.</param>
 /// <param name="options">The options defining blending algorithm and amount.</param>
 public BackgroundColorProcessor(TPixel color, GraphicsOptions options)
 {
     this.Color           = color;
     this.GraphicsOptions = options;
 }
 /// <summary>
 /// Flood fills the image in the shape of the provided polygon with the specified brush.
 /// </summary>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options.</param>
 /// <param name="color">The color.</param>
 /// <param name="paths">The paths.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext Fill(
     this IImageProcessingContext source,
     GraphicsOptions options,
     Color color,
     IPathCollection paths) =>
 source.Fill(options, new SolidBrush(color), paths);
 /// <summary>
 /// Initializes a new instance of the <see cref="ShapeGraphicsOptions"/> class.
 /// </summary>
 public ShapeGraphicsOptions()
 {
     this.graphicsOptions = new GraphicsOptions();
     this.shapeOptions    = new ShapeOptions();
 }
 /// <summary>
 /// Draws the provided points as an open Bezier path with the supplied pen
 /// </summary>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options.</param>
 /// <param name="pen">The pen.</param>
 /// <param name="points">The points.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext DrawBeziers(
     this IImageProcessingContext source,
     GraphicsOptions options,
     IPen pen,
     params PointF[] points) =>
 source.Draw(options, pen, new Path(new CubicBezierLineSegment(points)));
Exemple #25
0
 /// <inheritdoc />
 public BrushApplicator <TPixel> CreateApplicator(PixelAccessor <TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
 {
     return(new ImageBrushApplicator(sourcePixels, this.image, region, options));
 }
Exemple #26
0
 /// <summary>
 /// Initializes a new instance of the <see cref="PatternBrushApplicator" /> class.
 /// </summary>
 /// <param name="source">The source image.</param>
 /// <param name="pattern">The pattern.</param>
 /// <param name="patternVector">The patternVector.</param>
 /// <param name="options">The options</param>
 public PatternBrushApplicator(ImageFrame <TPixel> source, Fast2DArray <TPixel> pattern, Fast2DArray <Vector4> patternVector, GraphicsOptions options)
     : base(source, options)
 {
     this.pattern       = pattern;
     this.patternVector = patternVector;
 }
Exemple #27
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ImageBrushApplicator"/> class.
 /// </summary>
 /// <param name="image">
 /// The image.
 /// </param>
 /// <param name="region">
 /// The region.
 /// </param>
 /// <param name="options">The options</param>
 /// <param name="sourcePixels">
 /// The sourcePixels.
 /// </param>
 public ImageBrushApplicator(PixelAccessor <TPixel> sourcePixels, IImageBase <TPixel> image, RectangleF region, GraphicsOptions options)
     : base(sourcePixels, options)
 {
     this.source  = image.Lock();
     this.xLength = image.Width;
     this.yLength = image.Height;
     this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0);
     this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0);
 }
 /// <inheritdoc />
 public BrushApplicator <TPixel> CreateApplicator(PixelAccessor <TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
 {
     return(new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold, options));
 }
 /// <summary>
 /// Flood fills the image in the shape of the provided rectangle with the specified brush.
 /// </summary>
 /// <param name="source">The image this method extends.</param>
 /// <param name="options">The options.</param>
 /// <param name="brush">The brush.</param>
 /// <param name="shape">The shape.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext Fill(
     this IImageProcessingContext source,
     GraphicsOptions options,
     IBrush brush,
     RectangleF shape) =>
 source.Fill(options, brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height));
 /// <summary>
 /// Draws the given image together with the current one by blending their pixels.
 /// </summary>
 /// <typeparam name="TPixel">The pixel format.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="image">The image to blend with the currently processing image.</param>
 /// <param name="location">The location to draw the blended image.</param>
 /// <param name="options">The options containing the blend mode and opacity.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext <TPixel> DrawImage <TPixel>(this IImageProcessingContext <TPixel> source, Image <TPixel> image, Point location, GraphicsOptions options)
     where TPixel : struct, IPixel <TPixel>
 => source.ApplyProcessor(new DrawImageProcessor <TPixel>(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
Exemple #31
0
 /// <summary>
 /// Draws the provided points as an open Bezier path with the supplied pen
 /// </summary>
 /// <typeparam name="TPixel">The type of the color.</typeparam>
 /// <param name="source">The image this method extends.</param>
 /// <param name="pen">The pen.</param>
 /// <param name="points">The points.</param>
 /// <param name="options">The options.</param>
 /// <returns>The <see cref="Image{TPixel}"/>.</returns>
 public static IImageProcessingContext <TPixel> DrawBeziers <TPixel>(this IImageProcessingContext <TPixel> source, IPen <TPixel> pen, PointF[] points, GraphicsOptions options)
     where TPixel : struct, IPixel <TPixel>
 => source.Draw(pen, new Path(new CubicBezierLineSegment(points)), options);