// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Numerics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Overlays; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Benchmarks { using CoreSize = SixLabors.Primitives.Size; public class Glow : BenchmarkBase { private GlowProcessor bulk; private GlowProcessorParallel parallel; [GlobalSetup] public void Setup() { this.bulk = new GlowProcessor(Color.Beige, 800 * .5f, GraphicsOptions.Default); this.parallel = new GlowProcessorParallel(NamedColors.Beige) { Radius = 800 * .5f, }; } [Benchmark(Description = "ImageSharp Glow - Bulk")] public CoreSize GlowBulk() { using (var 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 (var 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 (IMemoryOwner rowColors = Configuration.Default.MemoryAllocator.Allocate(width)) { Buffer2D sourcePixels = source.PixelBuffer; rowColors.GetSpan().Fill(glowColor); var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); ParallelHelper.IterateRows( workingRect, configuration, rows => { for (int y = rows.Min; y < rows.Max; 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; packed.FromVector4( 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) var inverseW = new Vector3(1 - source.W); var xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); var xyzS = new Vector3(source.X, source.Y, source.Z); return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); } } } }