mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Conflicts: tests/ImageProcessor.Tests/Filters/FilterTests.cs Former-commit-id: ccd731868e208777bf20c0223c8e04068de292f6 Former-commit-id: 02479f04b2887c8a8202fa6b5a8f48ea87b15cb0 Former-commit-id: c31d19eba83c6d4616af264aaa5c536f5841f846pull/17/head
13 changed files with 512 additions and 9 deletions
@ -0,0 +1,141 @@ |
|||||
|
// <copyright file="PixelOperations.cs" company="James South">
|
||||
|
// Copyright © James South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessor |
||||
|
{ |
||||
|
using System; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs per-pixel operations.
|
||||
|
/// </summary>
|
||||
|
public static class PixelOperations |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The array of bytes representing each possible value of color component
|
||||
|
/// converted from sRGB to the linear color space.
|
||||
|
/// </summary>
|
||||
|
private static readonly Lazy<byte[]> LinearBytes = new Lazy<byte[]>(GetLinearBytes); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The array of bytes representing each possible value of color component
|
||||
|
/// converted from linear to the sRGB color space.
|
||||
|
/// </summary>
|
||||
|
private static readonly Lazy<byte[]> SrgbBytes = new Lazy<byte[]>(GetSrgbBytes); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts an pixel from an sRGB color-space to the equivalent linear color-space.
|
||||
|
/// </summary>
|
||||
|
/// <param name="composite">
|
||||
|
/// The <see cref="Bgra"/> to convert.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="Bgra"/>.
|
||||
|
/// </returns>
|
||||
|
public static Bgra ToLinear(Bgra composite) |
||||
|
{ |
||||
|
// Create only once and lazily.
|
||||
|
byte[] ramp = LinearBytes.Value; |
||||
|
|
||||
|
return new Bgra(composite.B, ramp[composite.G], ramp[composite.R], ramp[composite.A]); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a pixel from a linear color-space to the equivalent sRGB color-space.
|
||||
|
/// </summary>
|
||||
|
/// <param name="linear">
|
||||
|
/// The <see cref="Bgra"/> to convert.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="Bgra"/>.
|
||||
|
/// </returns>
|
||||
|
public static Bgra ToSrgb(Bgra linear) |
||||
|
{ |
||||
|
// Create only once and lazily.
|
||||
|
byte[] ramp = SrgbBytes.Value; |
||||
|
|
||||
|
return new Bgra(linear.B, ramp[linear.G], ramp[linear.R], ramp[linear.A]); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets an array of bytes representing each possible value of color component
|
||||
|
/// converted from sRGB to the linear color space.
|
||||
|
/// </summary>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="T:byte[]"/>.
|
||||
|
/// </returns>
|
||||
|
private static byte[] GetLinearBytes() |
||||
|
{ |
||||
|
byte[] ramp = new byte[256]; |
||||
|
for (int x = 0; x < 256; ++x) |
||||
|
{ |
||||
|
byte val = (byte)(255f * SrgbToLinear(x / 255f)).Clamp(0, 255); |
||||
|
ramp[x] = val; |
||||
|
} |
||||
|
|
||||
|
return ramp; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets an array of bytes representing each possible value of color component
|
||||
|
/// converted from linear to the sRGB color space.
|
||||
|
/// </summary>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="T:byte[]"/>.
|
||||
|
/// </returns>
|
||||
|
private static byte[] GetSrgbBytes() |
||||
|
{ |
||||
|
byte[] ramp = new byte[256]; |
||||
|
for (int x = 0; x < 256; ++x) |
||||
|
{ |
||||
|
byte val = (byte)(255f * LinearToSrgb(x / 255f)).Clamp(0, 255); |
||||
|
ramp[x] = val; |
||||
|
} |
||||
|
|
||||
|
return ramp; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the correct linear value from an sRGB signal.
|
||||
|
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
|
||||
|
/// <see href="http://entropymine.com/imageworsener/srgbformula/"/>
|
||||
|
/// </summary>
|
||||
|
/// <param name="signal">The signal value to convert.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="float"/>.
|
||||
|
/// </returns>
|
||||
|
private static float SrgbToLinear(float signal) |
||||
|
{ |
||||
|
float a = 0.055f; |
||||
|
|
||||
|
if (signal <= 0.04045) |
||||
|
{ |
||||
|
return signal / 12.92f; |
||||
|
} |
||||
|
|
||||
|
return (float)Math.Pow((signal + a) / (1 + a), 2.4); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the correct sRGB value from an linear signal.
|
||||
|
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
|
||||
|
/// <see href="http://entropymine.com/imageworsener/srgbformula/"/>
|
||||
|
/// </summary>
|
||||
|
/// <param name="signal">The signal value to convert.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="float"/>.
|
||||
|
/// </returns>
|
||||
|
private static float LinearToSrgb(float signal) |
||||
|
{ |
||||
|
float a = 0.055f; |
||||
|
|
||||
|
if (signal <= 0.0031308) |
||||
|
{ |
||||
|
return signal * 12.92f; |
||||
|
} |
||||
|
|
||||
|
return ((float)((1 + a) * Math.Pow(signal, 1 / 2.4f))) - a; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
// <copyright file="BicubicResampler.cs" company="James South">
|
||||
|
// Copyright © James South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessor.Samplers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The function implements the bicubic kernel algorithm W(x) as described on
|
||||
|
/// <see href="https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm">Wikipedia</see>
|
||||
|
/// </summary>
|
||||
|
public class BicubicResampler : IResampler |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public double Radius => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public double GetValue(double x) |
||||
|
{ |
||||
|
// The coefficient.
|
||||
|
double a = -0.5; |
||||
|
|
||||
|
if (x < 0) |
||||
|
{ |
||||
|
x = -x; |
||||
|
} |
||||
|
|
||||
|
double result = 0; |
||||
|
|
||||
|
if (x <= 1) |
||||
|
{ |
||||
|
result = (((1.5 * x) - 2.5) * x * x) + 1; |
||||
|
} |
||||
|
else if (x < 2) |
||||
|
{ |
||||
|
result = (((((a * x) + 2.5) * x) - 4) * x) + 2; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
namespace ImageProcessor.Samplers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Encasulates an interpolation algorithm for resampling images.
|
||||
|
/// </summary>
|
||||
|
public interface IResampler |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the radius in which to sample pixels.
|
||||
|
/// </summary>
|
||||
|
double Radius { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the result of the interpolation algorithm.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The value to process.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="double"/>
|
||||
|
/// </returns>
|
||||
|
double GetValue(double x); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// <copyright file="ImageFilterExtensions.cs" company="James South">
|
||||
|
// Copyright © James South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageProcessor.Samplers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exstensions methods for <see cref="Image"/> to apply samplers to the image.
|
||||
|
/// </summary>
|
||||
|
public static class ImageSampleExtensions |
||||
|
{ |
||||
|
public static Image Resize(this Image source, int width, int height) |
||||
|
{ |
||||
|
return source.Process(width, height, default(Rectangle), default(Rectangle), new Resize(new BicubicResampler(), width, height)); |
||||
|
} |
||||
|
|
||||
|
public static Image Resize(this Image source, int width, int height, IResampler sampler) |
||||
|
{ |
||||
|
return source.Process(width, height, default(Rectangle), default(Rectangle), new Resize(sampler, width, height)); |
||||
|
} |
||||
|
|
||||
|
public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle) |
||||
|
{ |
||||
|
return source.Process(width, height, sourceRectangle, targetRectangle, new Resize(sampler, width, height)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace ImageProcessor.Samplers |
||||
|
{ |
||||
|
public class Resize : ParallelImageProcessor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Resize"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="sampler">
|
||||
|
/// The sampler to perform the resize operation.
|
||||
|
/// </param>
|
||||
|
public Resize(IResampler sampler, int width, int height) |
||||
|
{ |
||||
|
Guard.NotNull(sampler, nameof(sampler)); |
||||
|
Guard.MustBeGreaterThan(width, 0, nameof(width)); |
||||
|
Guard.MustBeGreaterThan(height, 0, nameof(height)); |
||||
|
|
||||
|
this.Sampler = sampler; |
||||
|
this.Width = width; |
||||
|
this.Height = height; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the sampler to perform the resize operation.
|
||||
|
/// </summary>
|
||||
|
public IResampler Sampler { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the width.
|
||||
|
/// </summary>
|
||||
|
public int Width { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the height.
|
||||
|
/// </summary>
|
||||
|
public int Height { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Apply( |
||||
|
ImageBase target, |
||||
|
ImageBase source, |
||||
|
Rectangle targetRectangle, |
||||
|
Rectangle sourceRectangle, |
||||
|
int startY, |
||||
|
int endY) |
||||
|
{ |
||||
|
int sourceWidth = source.Width; |
||||
|
int sourceHeight = source.Height; |
||||
|
|
||||
|
int width = target.Width; |
||||
|
int height = target.Height; |
||||
|
|
||||
|
int startX = targetRectangle.X; |
||||
|
int endX = targetRectangle.Right; |
||||
|
int right = (int)(this.Sampler.Radius + .5); |
||||
|
int left = -right; |
||||
|
|
||||
|
// Scaling factors
|
||||
|
double widthFactor = sourceWidth / (double)targetRectangle.Width; |
||||
|
double heightFactor = sourceHeight / (double)targetRectangle.Height; |
||||
|
|
||||
|
// Width and height decreased by 1
|
||||
|
int maxHeight = sourceHeight - 1; |
||||
|
int maxWidth = sourceWidth - 1; |
||||
|
|
||||
|
for (int y = startY; y < endY; y++) |
||||
|
{ |
||||
|
if (y >= 0 && y < height) |
||||
|
{ |
||||
|
// Y coordinates of source points.
|
||||
|
double originY = ((startY - targetRectangle.Y) * heightFactor) - 0.5; |
||||
|
int originY1 = (int)originY; |
||||
|
double dy = originY - originY1; |
||||
|
|
||||
|
// For each row.
|
||||
|
for (int x = startX; x < endX; x++) |
||||
|
{ |
||||
|
if (x >= 0 && x < width) |
||||
|
{ |
||||
|
// X coordinates of source points.
|
||||
|
double originX = ((x - startX) * widthFactor) - 0.5f; |
||||
|
int originX1 = (int)originX; |
||||
|
double dx = originX - originX1; |
||||
|
|
||||
|
// Destination color components
|
||||
|
double r = 0; |
||||
|
double g = 0; |
||||
|
double b = 0; |
||||
|
double a = 0; |
||||
|
|
||||
|
for (int yy = left; yy < right; yy++) |
||||
|
{ |
||||
|
// Get Y cooefficient
|
||||
|
double kernel1 = this.Sampler.GetValue(dy - yy); |
||||
|
|
||||
|
int originY2 = originY1 + yy; |
||||
|
if (originY2 < 0) |
||||
|
{ |
||||
|
originY2 = 0; |
||||
|
} |
||||
|
|
||||
|
if (originY2 > maxHeight) |
||||
|
{ |
||||
|
originY2 = maxHeight; |
||||
|
} |
||||
|
|
||||
|
for (int xx = left; xx < right; xx++) |
||||
|
{ |
||||
|
// Get X cooefficient
|
||||
|
double kernel2 = kernel1 * this.Sampler.GetValue(xx - dx); |
||||
|
|
||||
|
int originX2 = originX1 + xx; |
||||
|
if (originX2 < 0) |
||||
|
{ |
||||
|
originX2 = 0; |
||||
|
} |
||||
|
|
||||
|
if (originX2 > maxWidth) |
||||
|
{ |
||||
|
originX2 = maxWidth; |
||||
|
} |
||||
|
|
||||
|
Bgra sourceColor = source[originX2, originY2]; |
||||
|
sourceColor = PixelOperations.ToLinear(sourceColor); |
||||
|
|
||||
|
r += kernel2 * sourceColor.R; |
||||
|
g += kernel2 * sourceColor.G; |
||||
|
b += kernel2 * sourceColor.B; |
||||
|
a += kernel2 * sourceColor.A; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte()); |
||||
|
destinationColor = PixelOperations.ToSrgb(destinationColor); |
||||
|
target[x, y] = destinationColor; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void Apply(ImageBase target, ImageBase source, Rectangle rectangle, int startY, int endY) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue