mirror of https://github.com/SixLabors/ImageSharp
9 changed files with 288 additions and 236 deletions
@ -1,50 +1,45 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing.Processors.Effects; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds pixelation effect extensions to the <see cref="Image{TPixel}"/> type.
|
|||
/// Adds pixelation effect extensions to the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public static class PixelateExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Pixelates an image with the given pixel size.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext<TPixel> Pixelate<TPixel>(this IImageProcessingContext<TPixel> source) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> Pixelate(source, 4); |
|||
public static IImageProcessingContext Pixelate(this IImageProcessingContext source) => Pixelate(source, 4); |
|||
|
|||
/// <summary>
|
|||
/// Pixelates an image with the given pixel size.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="size">The size of the pixels.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext<TPixel> Pixelate<TPixel>(this IImageProcessingContext<TPixel> source, int size) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new PixelateProcessor<TPixel>(size)); |
|||
public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size) => |
|||
source.ApplyProcessor(new PixelateProcessor(size)); |
|||
|
|||
/// <summary>
|
|||
/// Pixelates an image with the given pixel size.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="size">The size of the pixels.</param>
|
|||
/// <param name="rectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
|
|||
/// </param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext<TPixel> Pixelate<TPixel>(this IImageProcessingContext<TPixel> source, int size, Rectangle rectangle) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.ApplyProcessor(new PixelateProcessor<TPixel>(size), rectangle); |
|||
public static IImageProcessingContext Pixelate( |
|||
this IImageProcessingContext source, |
|||
int size, |
|||
Rectangle rectangle) => |
|||
source.ApplyProcessor(new PixelateProcessor(size), rectangle); |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
/// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.ParallelUtils; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Effects |
|||
{ |
|||
/// <summary>
|
|||
/// Applies oil painting effect processing to the image.
|
|||
/// </summary>
|
|||
/// <remarks>Adapted from <see href="https://softwarebydefault.com/2013/06/29/oil-painting-cartoon-filter/"/> by Dewald Esterhuizen.</remarks>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class OilPaintingProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly OilPaintingProcessor definition; |
|||
|
|||
public OilPaintingProcessor(OilPaintingProcessor definition) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply( |
|||
ImageFrame<TPixel> source, |
|||
Rectangle sourceRectangle, |
|||
Configuration configuration) |
|||
{ |
|||
int brushSize = this.definition.BrushSize; |
|||
if (brushSize <= 0 || brushSize > source.Height || brushSize > source.Width) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(brushSize)); |
|||
} |
|||
|
|||
int startY = sourceRectangle.Y; |
|||
int endY = sourceRectangle.Bottom; |
|||
int startX = sourceRectangle.X; |
|||
int endX = sourceRectangle.Right; |
|||
int maxY = endY - 1; |
|||
int maxX = endX - 1; |
|||
|
|||
int radius = brushSize >> 1; |
|||
int levels = this.definition.Levels; |
|||
|
|||
using (Buffer2D<TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size())) |
|||
{ |
|||
source.CopyTo(targetPixels); |
|||
|
|||
var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); |
|||
ParallelHelper.IterateRows( |
|||
workingRect, |
|||
configuration, |
|||
rows => |
|||
{ |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
Span<TPixel> sourceRow = source.GetPixelRowSpan(y); |
|||
Span<TPixel> targetRow = targetPixels.GetRowSpan(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; |
|||
|
|||
offsetY = offsetY.Clamp(0, maxY); |
|||
|
|||
Span<TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY); |
|||
|
|||
for (int fx = 0; fx <= radius; fx++) |
|||
{ |
|||
int fxr = fx - radius; |
|||
int offsetX = x + fxr; |
|||
offsetX = offsetX.Clamp(0, maxX); |
|||
|
|||
var vector = sourceOffsetRow[offsetX].ToVector4(); |
|||
|
|||
float sourceRed = vector.X; |
|||
float sourceBlue = vector.Z; |
|||
float sourceGreen = vector.Y; |
|||
|
|||
int currentIntensity = (int)MathF.Round( |
|||
(sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); |
|||
|
|||
intensityBin[currentIntensity]++; |
|||
blueBin[currentIntensity] += sourceBlue; |
|||
greenBin[currentIntensity] += sourceGreen; |
|||
redBin[currentIntensity] += sourceRed; |
|||
|
|||
if (intensityBin[currentIntensity] > maxIntensity) |
|||
{ |
|||
maxIntensity = intensityBin[currentIntensity]; |
|||
maxIndex = currentIntensity; |
|||
} |
|||
} |
|||
|
|||
float red = MathF.Abs(redBin[maxIndex] / maxIntensity); |
|||
float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); |
|||
float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); |
|||
|
|||
ref TPixel pixel = ref targetRow[x]; |
|||
pixel.FromVector4( |
|||
new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
|
|||
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Common; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Effects |
|||
{ |
|||
/// <summary>
|
|||
/// Applies a pixelation effect processing to the image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class PixelateProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly PixelateProcessor definition; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PixelateProcessor{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="definition">The <see cref="PixelateProcessor"/>.</param>
|
|||
public PixelateProcessor(PixelateProcessor definition) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
private int Size => this.definition.Size; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
if (this.Size <= 0 || this.Size > source.Height || this.Size > source.Width) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(this.Size)); |
|||
} |
|||
|
|||
int startY = sourceRectangle.Y; |
|||
int endY = sourceRectangle.Bottom; |
|||
int startX = sourceRectangle.X; |
|||
int endX = sourceRectangle.Right; |
|||
int size = this.Size; |
|||
int offset = this.Size / 2; |
|||
|
|||
// 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; |
|||
} |
|||
|
|||
// Get the range on the y-plane to choose from.
|
|||
IEnumerable<int> range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); |
|||
|
|||
Parallel.ForEach( |
|||
range, |
|||
configuration.GetParallelOptions(), |
|||
y => |
|||
{ |
|||
int offsetY = y - startY; |
|||
int offsetPy = offset; |
|||
|
|||
// Make sure that the offset is within the boundary of the image.
|
|||
while (offsetY + offsetPy >= maxY) |
|||
{ |
|||
offsetPy--; |
|||
} |
|||
|
|||
Span<TPixel> row = source.GetPixelRowSpan(offsetY + offsetPy); |
|||
|
|||
for (int x = minX; x < maxX; x += size) |
|||
{ |
|||
int offsetX = x - startX; |
|||
int offsetPx = offset; |
|||
|
|||
while (x + offsetPx >= maxX) |
|||
{ |
|||
offsetPx--; |
|||
} |
|||
|
|||
// Get the pixel color in the centre of the soon to be pixelated area.
|
|||
TPixel pixel = row[offsetX + offsetPx]; |
|||
|
|||
// For each pixel in the pixelate size, set it to the centre color.
|
|||
for (int l = offsetY; l < offsetY + size && l < maxY; l++) |
|||
{ |
|||
for (int k = offsetX; k < offsetX + size && k < maxX; k++) |
|||
{ |
|||
source[k, l] = pixel; |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue