Browse Source

Add oil painting effect.

Former-commit-id: c9c4f16067cbca67fd055702fa15e71995e8ff35
Former-commit-id: 6ffebcceb66d94c8a7920cb02edfcd572db29ac3
Former-commit-id: 09721ad14a254868241f53d43095212ab20fefc8
af/merge-core
James Jackson-South 10 years ago
parent
commit
ec5bb8feb8
  1. 1
      README.md
  2. 70
      src/ImageProcessorCore/Samplers/OilPainting.cs
  3. 4
      src/ImageProcessorCore/Samplers/Pixelate.cs
  4. 154
      src/ImageProcessorCore/Samplers/Processors/OilPaintingProcessor.cs
  5. 74
      tests/ImageProcessorCore.Tests/Processors/Samplers/OilPaintTest.cs

1
README.md

@ -144,6 +144,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
- [x] Pixelate
- [x] Blend
- [ ] Mask
- [x] Oil Painting
- [x] Vignette
- [x] Glow
- [x] Threshold

70
src/ImageProcessorCore/Samplers/OilPainting.cs

@ -0,0 +1,70 @@
// <copyright file="Pixelate.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using Processors;
using System;
/// <summary>
/// Extension methods for the <see cref="Image{TColor, TPacked}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Alters the colors of the image recreating an oil painting effect.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="levels">The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image.</param>
/// <param name="brushSize">The number of neighbouring pixels used in calculating each individual pixel value.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{TColor, TPacked}"/>.</returns>
public static Image<TColor, TPacked> OilPaint<TColor, TPacked>(this Image<TColor, TPacked> source, int levels = 10, int brushSize = 15, ProgressEventHandler progressHandler = null)
where TColor : IPackedVector<TPacked>
where TPacked : struct
{
return OilPaint(source, levels, brushSize, source.Bounds, progressHandler);
}
/// <summary>
/// Alters the colors of the image recreating an oil painting effect.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="levels">The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image.</param>
/// <param name="brushSize">The number of neighbouring pixels used in calculating each individual pixel value.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{TColor, TPacked}"/>.</returns>
public static Image<TColor, TPacked> OilPaint<TColor, TPacked>(this Image<TColor, TPacked> source, int levels, int brushSize, Rectangle rectangle, ProgressEventHandler progressHandler = null)
where TColor : IPackedVector<TPacked>
where TPacked : struct
{
Guard.MustBeGreaterThan(levels, 0, nameof(levels));
if (brushSize <= 0 || brushSize > source.Height || brushSize > source.Width)
{
throw new ArgumentOutOfRangeException(nameof(brushSize));
}
OilPaintingProcessor<TColor, TPacked> processor = new OilPaintingProcessor<TColor, TPacked>(levels, brushSize);
processor.OnProgress += progressHandler;
try
{
return source.Process(rectangle, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

4
src/ImageProcessorCore/Samplers/Pixelate.cs

@ -14,7 +14,7 @@ namespace ImageProcessorCore
public static partial class ImageExtensions
{
/// <summary>
/// Pixelates and image with the given pixel size.
/// Pixelates an image with the given pixel size.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
@ -30,7 +30,7 @@ namespace ImageProcessorCore
}
/// <summary>
/// Pixelates and image with the given pixel size.
/// Pixelates an image with the given pixel size.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>

154
src/ImageProcessorCore/Samplers/Processors/OilPaintingProcessor.cs

@ -0,0 +1,154 @@
// <copyright file="OilPaintingProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// An <see cref="IImageSampler{TColor,TPacked}"/> to apply an oil painting effect to an <see cref="Image{TColor, TPacked}"/>.
/// </summary>
/// <remarks>Adapted from <see href="https://softwarebydefault.com/2013/06/29/oil-painting-cartoon-filter/"/> by Dewald Esterhuizen.</remarks>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
public class OilPaintingProcessor<TColor, TPacked> : ImageSampler<TColor, TPacked>
where TColor : IPackedVector<TPacked>
where TPacked : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="OilPaintingProcessor{T,TP}"/> class.
/// </summary>
/// <param name="levels">The number of intensity levels. Higher values result in a broader range of colour intensities forming part of the result image.</param>
/// <param name="brushSize">The number of neighbouring pixels used in calculating each individual pixel value.</param>
public OilPaintingProcessor(int levels, int brushSize)
{
Guard.MustBeGreaterThan(levels, 0, nameof(levels));
Guard.MustBeGreaterThan(brushSize, 0, nameof(brushSize));
this.Levels = levels;
this.BrushSize = brushSize;
}
/// <summary>
/// Gets the intensity levels
/// </summary>
public int Levels { get; }
/// <summary>
/// Gets the brush size
/// </summary>
public int BrushSize { get; }
/// <inheritdoc/>
protected override void Apply(ImageBase<TColor, TPacked> target, ImageBase<TColor, TPacked> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
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<TColor, TPacked> sourcePixels = source.Lock())
using (PixelAccessor<TColor, TPacked> targetPixels = target.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);
Vector4 targetColor = targetPixels[x, y].ToVector4();
TColor packed = default(TColor);
packed.PackFromVector4(new Vector4(red, green, blue, targetColor.Z));
targetPixels[x, y] = packed;
}
}
this.OnRowProcessed();
});
}
}
}
}

74
tests/ImageProcessorCore.Tests/Processors/Samplers/OilPaintTest.cs

@ -0,0 +1,74 @@
// <copyright file="OilPaintTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Tests
{
using System;
using System.IO;
using Xunit;
public class OilPaintTest : FileTestBase
{
public static readonly TheoryData<Tuple<int, int>> OilPaintValues
= new TheoryData<Tuple<int, int>>
{
new Tuple<int, int>(15,10),
new Tuple<int, int>(6,5)
};
[Theory]
[MemberData(nameof(OilPaintValues))]
public void ImageShouldApplyOilPaintFilter(Tuple<int, int> value)
{
const string path = "TestOutput/OilPaint";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file);
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
image.OilPaint(value.Item1, value.Item2)
.Save(output);
}
}
}
}
[Theory]
[MemberData(nameof(OilPaintValues))]
public void ImageShouldApplyOilPaintFilterInBox(Tuple<int, int> value)
{
const string path = "TestOutput/OilPaint";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + "-InBox" + Path.GetExtension(file);
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
image.OilPaint(value.Item1, value.Item2, new Rectangle(10, 10, image.Width / 2, image.Height / 2))
.Save(output);
}
}
}
}
}
}
Loading…
Cancel
Save