|
|
|
@ -10,12 +10,6 @@ namespace ImageProcessor.Samplers |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Provides methods that allow the resizing of images using various resampling algorithms.
|
|
|
|
/// <remarks>
|
|
|
|
/// TODO: There is a bug in this class. Whenever the processor is set to use parallel processing, the output image becomes distorted
|
|
|
|
/// at the join points when startY is greater than 0. Uncomment the Parallelism overload and run the ImageShouldResize method in the SamplerTests
|
|
|
|
/// class to see the error manifest.
|
|
|
|
/// It is imperative that the issue is solved or resampling will be too slow to be practical and the project will have to cease.
|
|
|
|
/// </remarks>
|
|
|
|
/// </summary>
|
|
|
|
public class Resize : ParallelImageProcessor |
|
|
|
{ |
|
|
|
@ -24,6 +18,16 @@ namespace ImageProcessor.Samplers |
|
|
|
/// </summary>
|
|
|
|
private const float Epsilon = 0.0001f; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The horizontal weights.
|
|
|
|
/// </summary>
|
|
|
|
private Weights[] horizontalWeights; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The vertical weights.
|
|
|
|
/// </summary>
|
|
|
|
private Weights[] verticalWeights; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="Resize"/> class.
|
|
|
|
/// </summary>
|
|
|
|
@ -37,97 +41,74 @@ namespace ImageProcessor.Samplers |
|
|
|
this.Sampler = sampler; |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
public override int Parallelism => 1; // Uncomment this to see bug.
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the sampler to perform the resize operation.
|
|
|
|
/// </summary>
|
|
|
|
public IResampler Sampler { get; } |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|
|
|
protected override void OnApply(Rectangle targetRectangle, Rectangle sourceRectangle) |
|
|
|
{ |
|
|
|
int sourceWidth = source.Width; |
|
|
|
int sourceHeight = source.Height; |
|
|
|
|
|
|
|
int width = target.Width; |
|
|
|
int height = target.Height; |
|
|
|
this.horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); |
|
|
|
this.verticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|
|
|
{ |
|
|
|
int targetY = targetRectangle.Y; |
|
|
|
int targetBottom = targetRectangle.Bottom; |
|
|
|
int startX = targetRectangle.X; |
|
|
|
int endX = targetRectangle.Right; |
|
|
|
|
|
|
|
// Scaling factors
|
|
|
|
double heightFactor = sourceHeight / (double)targetRectangle.Height; |
|
|
|
int targetSectionHeight = endY - startY; |
|
|
|
int sourceSectionHeight = (int)((targetSectionHeight * heightFactor) + .5); |
|
|
|
|
|
|
|
int offsetY = this.CalculateOffset(startY, targetSectionHeight, sourceSectionHeight); |
|
|
|
int offsetX = this.CalculateOffset(startX, targetRectangle.Width, sourceRectangle.Width); |
|
|
|
Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); |
|
|
|
Weights[] verticalWeights = this.PrecomputeWeights(targetSectionHeight, sourceSectionHeight); |
|
|
|
|
|
|
|
// 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) |
|
|
|
if (y >= targetY && y < targetBottom) |
|
|
|
{ |
|
|
|
List<Weight> verticalValues = verticalWeights[y - startY].Values; |
|
|
|
double verticalSum = verticalWeights[y - startY].Sum; |
|
|
|
List<Weight> verticalValues = this.verticalWeights[y].Values; |
|
|
|
double verticalSum = this.verticalWeights[y].Sum; |
|
|
|
|
|
|
|
for (int x = startX; x < endX; x++) |
|
|
|
{ |
|
|
|
if (x >= 0 && x < width) |
|
|
|
List<Weight> horizontalValues = this.horizontalWeights[x].Values; |
|
|
|
double horizontalSum = this.horizontalWeights[x].Sum; |
|
|
|
|
|
|
|
// Destination color components
|
|
|
|
double r = 0; |
|
|
|
double g = 0; |
|
|
|
double b = 0; |
|
|
|
double a = 0; |
|
|
|
|
|
|
|
foreach (Weight yw in verticalValues) |
|
|
|
{ |
|
|
|
List<Weight> horizontalValues = horizontalWeights[x - startX].Values; |
|
|
|
double horizontalSum = horizontalWeights[x - startX].Sum; |
|
|
|
if (Math.Abs(yw.Value) < Epsilon) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// Destination color components
|
|
|
|
double r = 0; |
|
|
|
double g = 0; |
|
|
|
double b = 0; |
|
|
|
double a = 0; |
|
|
|
int originY = yw.Index; |
|
|
|
|
|
|
|
foreach (Weight yw in verticalValues) |
|
|
|
foreach (Weight xw in horizontalValues) |
|
|
|
{ |
|
|
|
if (Math.Abs(yw.Value) < Epsilon) |
|
|
|
if (Math.Abs(xw.Value) < Epsilon) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// TODO: This offset is wrong.
|
|
|
|
int originY = offsetY == 0 ? yw.Index : yw.Index + offsetY; |
|
|
|
originY = originY.Clamp(0, maxHeight); |
|
|
|
int originX = xw.Index; |
|
|
|
Bgra sourceColor = source[originX, originY]; |
|
|
|
sourceColor = PixelOperations.ToLinear(sourceColor); |
|
|
|
|
|
|
|
foreach (Weight xw in horizontalValues) |
|
|
|
{ |
|
|
|
if (Math.Abs(xw.Value) < Epsilon) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// TODO: This offset is wrong.
|
|
|
|
int originX = xw.Index + offsetX; |
|
|
|
originX = originX.Clamp(0, maxWidth); |
|
|
|
|
|
|
|
Bgra sourceColor = source[originX, originY]; |
|
|
|
sourceColor = PixelOperations.ToLinear(sourceColor); |
|
|
|
|
|
|
|
r += sourceColor.R * (yw.Value / verticalSum) * (xw.Value / horizontalSum); |
|
|
|
g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum); |
|
|
|
b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum); |
|
|
|
a += sourceColor.A * (yw.Value / verticalSum) * (xw.Value / horizontalSum); |
|
|
|
} |
|
|
|
r += sourceColor.R * (yw.Value / verticalSum) * (xw.Value / horizontalSum); |
|
|
|
g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum); |
|
|
|
b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum); |
|
|
|
a += sourceColor.A * (yw.Value / verticalSum) * (xw.Value / horizontalSum); |
|
|
|
} |
|
|
|
|
|
|
|
Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte()); |
|
|
|
destinationColor = PixelOperations.ToSrgb(destinationColor); |
|
|
|
target[x, y] = destinationColor; |
|
|
|
} |
|
|
|
|
|
|
|
Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte()); |
|
|
|
destinationColor = PixelOperations.ToSrgb(destinationColor); |
|
|
|
target[x, y] = destinationColor; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -192,44 +173,6 @@ namespace ImageProcessor.Samplers |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Calculates the scaled offset caused by parallelism.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="offset">The offset position.</param>
|
|
|
|
/// <param name="destinationSize">The destination size.</param>
|
|
|
|
/// <param name="sourceSize">The source size.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="int"/>.
|
|
|
|
/// </returns>
|
|
|
|
private int CalculateOffset(int offset, int destinationSize, int sourceSize) |
|
|
|
{ |
|
|
|
if (offset == 0) |
|
|
|
{ |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
IResampler sampler = this.Sampler; |
|
|
|
double du = sourceSize / (double)destinationSize; |
|
|
|
double scale = du; |
|
|
|
|
|
|
|
if (scale < 1) |
|
|
|
{ |
|
|
|
scale = 1; |
|
|
|
} |
|
|
|
|
|
|
|
double ru = Math.Ceiling(scale * sampler.Radius); |
|
|
|
|
|
|
|
double fu = ((offset + .5) * du) - 0.5; |
|
|
|
int result = (int)Math.Ceiling(fu - ru); |
|
|
|
|
|
|
|
if (result < 0) |
|
|
|
{ |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Represents the weight to be added to a scaled pixel.
|
|
|
|
/// </summary>
|
|
|
|
|