mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Not working correctly just now. Y calc is wrong. Former-commit-id: 21b81ada54ca20a5ba1f08cb0fce9c57dd7237b2 Former-commit-id: b9800debdc5306d6935be95808bef72b34c2b9bc Former-commit-id: b06159adfd6ec098e13f9509a14b0f5bb3d8369epull/17/head
14 changed files with 540 additions and 11 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