// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Processing.Processors { using System; using System.Numerics; using System.Threading.Tasks; /// /// An to apply an oil painting effect to an . /// /// Adapted from by Dewald Esterhuizen. /// The pixel format. public class OilPaintingProcessor : ImageProcessor where TColor : struct, IPackedPixel, IEquatable { /// /// Initializes a new instance of the class. /// /// /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. /// /// /// The number of neighboring pixels used in calculating each individual pixel value. /// public OilPaintingProcessor(int levels, int brushSize) { Guard.MustBeGreaterThan(levels, 0, nameof(levels)); Guard.MustBeGreaterThan(brushSize, 0, nameof(brushSize)); this.Levels = levels; this.BrushSize = brushSize; } /// /// Gets the intensity levels /// public int Levels { get; } /// /// Gets the brush size /// public int BrushSize { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int radius = this.BrushSize >> 1; int levels = this.Levels; // 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; } using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { using (PixelAccessor sourcePixels = source.Lock()) { Parallel.For( minY, maxY, this.ParallelOptions, y => { for (int x = startX; x < endX; x++) { int maxIntensity = 0; int maxIndex = 0; int[] intensityBin = new int[levels]; float[] redBin = new float[levels]; float[] blueBin = new float[levels]; float[] greenBin = new float[levels]; for (int fy = 0; fy <= radius; fy++) { int fyr = fy - radius; int offsetY = y + fyr; // Skip the current row if (offsetY < minY) { continue; } // Outwith the current bounds so break. if (offsetY >= maxY) { break; } for (int fx = 0; fx <= radius; fx++) { int fxr = fx - radius; int offsetX = x + fxr; // Skip the column if (offsetX < 0) { continue; } if (offsetX < maxX) { // ReSharper disable once AccessToDisposedClosure Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); float sourceRed = color.X; float sourceBlue = color.Z; float sourceGreen = color.Y; int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); intensityBin[currentIntensity] += 1; blueBin[currentIntensity] += sourceBlue; greenBin[currentIntensity] += sourceGreen; redBin[currentIntensity] += sourceRed; if (intensityBin[currentIntensity] > maxIntensity) { maxIntensity = intensityBin[currentIntensity]; maxIndex = currentIntensity; } } } float red = Math.Abs(redBin[maxIndex] / maxIntensity); float green = Math.Abs(greenBin[maxIndex] / maxIntensity); float blue = Math.Abs(blueBin[maxIndex] / maxIntensity); TColor packed = default(TColor); packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); targetPixels[x, y] = packed; } } }); } source.SwapPixelsBuffers(targetPixels); } } } }