// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Overlays { /// /// An that applies a radial vignette effect to an . /// /// The pixel format. internal class VignetteProcessor : ImageProcessor where TPixel : unmanaged, IPixel { private readonly PixelBlender blender; private readonly VignetteProcessor definition; /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. public VignetteProcessor(Configuration configuration, VignetteProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { this.definition = definition; this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); } /// protected override void OnFrameApply(ImageFrame source) { TPixel vignetteColor = this.definition.VignetteColor.ToPixel(); float blendPercent = this.definition.GraphicsOptions.BlendPercentage; var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); Vector2 center = Rectangle.Center(interest); float finalRadiusX = this.definition.RadiusX.Calculate(interest.Size); float finalRadiusY = this.definition.RadiusY.Calculate(interest.Size); float rX = finalRadiusX > 0 ? MathF.Min(finalRadiusX, interest.Width * .5F) : interest.Width * .5F; float rY = finalRadiusY > 0 ? MathF.Min(finalRadiusY, interest.Height * .5F) : interest.Height * .5F; float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); Configuration configuration = this.Configuration; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); ParallelRowIterator.IterateRows( configuration, interest, in operation); } private readonly struct RowOperation : IRowOperation { private readonly Configuration configuration; private readonly Rectangle bounds; private readonly PixelBlender blender; private readonly Vector2 center; private readonly float maxDistance; private readonly float blendPercent; private readonly IMemoryOwner colors; private readonly ImageFrame source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( Configuration configuration, Rectangle bounds, IMemoryOwner colors, PixelBlender blender, Vector2 center, float maxDistance, float blendPercent, ImageFrame source) { this.configuration = configuration; this.bounds = bounds; this.colors = colors; this.blender = blender; this.center = center; this.maxDistance = maxDistance; this.blendPercent = blendPercent; this.source = source; } [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { Span colorSpan = this.colors.GetSpan(); for (int i = 0; i < this.bounds.Width; i++) { float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); } Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); this.blender.Blend( this.configuration, destination, destination, colorSpan, span); } } } }