// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace SixLabors.ImageSharp.Benchmarks { using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using CoreSize = SixLabors.Primitives.Size; using System.Numerics; using System; using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.Primitives; using SixLabors.ImageSharp.Processing.Overlays.Processors; using SixLabors.ImageSharp.Processing.Processors; public class Glow : BenchmarkBase { private GlowProcessor bulk; private GlowProcessorParallel parallel; [GlobalSetup] public void Setup() { this.bulk = new GlowProcessor(NamedColors.Beige, 800 * .5f, GraphicsOptions.Default); this.parallel = new GlowProcessorParallel(NamedColors.Beige) { Radius = 800 * .5f, }; } [Benchmark(Description = "ImageSharp Glow - Bulk")] public CoreSize GlowBulk() { using (Image image = new Image(800, 800)) { this.bulk.Apply(image, image.Bounds()); return new CoreSize(image.Width, image.Height); } } [Benchmark(Description = "ImageSharp Glow - Parallel")] public CoreSize GLowSimple() { using (Image image = new Image(800, 800)) { this.parallel.Apply(image, image.Bounds()); return new CoreSize(image.Width, image.Height); } } internal class GlowProcessorParallel : ImageProcessor where TPixel : struct, IPixel { /// /// Initializes a new instance of the class. /// /// The color or the glow. public GlowProcessorParallel(TPixel color) { this.GlowColor = color; } /// /// Gets or sets the glow color to apply. /// public TPixel GlowColor { get; set; } /// /// Gets or sets the the radius. /// public float Radius { get; set; } /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel glowColor = this.GlowColor; Vector2 centre = Rectangle.Center(sourceRectangle); float maxDistance = this.Radius > 0 ? Math.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; // 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 rowColors = Configuration.Default.MemoryManager.Allocate(width)) using (PixelAccessor sourcePixels = source.Lock()) { rowColors.Span.Fill(glowColor); Parallel.For( minY, maxY, configuration.ParallelOptions, y => { int offsetY = y - startY; for (int x = minX; x < maxX; x++) { int offsetX = x - startX; float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); TPixel packed = default(TPixel); packed.PackFromVector4(PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); sourcePixels[offsetX, offsetY] = packed; } }); } } public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) { amount = amount.Clamp(0, 1); // Santize on zero alpha if (Math.Abs(backdrop.W) < Constants.Epsilon) { source.W *= amount; return source; } if (Math.Abs(source.W) < Constants.Epsilon) { return backdrop; } // Premultiply the source vector. // Oddly premultiplying the background vector creates dark outlines when pixels // Have low alpha values. source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); // This should be implementing the following formula // https://en.wikipedia.org/wiki/Alpha_compositing // Vout = Vs + Vb (1 - Vsa) // Aout = Vsa + Vsb (1 - Vsa) Vector3 inverseW = new Vector3(1 - source.W); Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); } } } }