diff --git a/src/ImageProcessorCore/Samplers/Processors/CompandingResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/CompandingResizeProcessor.cs
new file mode 100644
index 0000000000..23887af23b
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/CompandingResizeProcessor.cs
@@ -0,0 +1,177 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the resizing of images using various algorithms.
+ /// This version will expand and compress the image to and from a linear color space during processing.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ public class CompandingResizeProcessor : ResamplingWeightedProcessor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The sampler to perform the resize operation.
+ ///
+ public CompandingResizeProcessor(IResampler sampler)
+ : base(sampler)
+ {
+ }
+
+ ///
+ public override bool Compand { get; set; } = true;
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ // Jump out, we'll deal with that later.
+ if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)
+ {
+ return;
+ }
+
+ int width = target.Width;
+ int height = target.Height;
+ int sourceHeight = sourceRectangle.Height;
+ int targetX = target.Bounds.X;
+ int targetY = target.Bounds.Y;
+ int targetRight = target.Bounds.Right;
+ int targetBottom = target.Bounds.Bottom;
+ int startX = targetRectangle.X;
+ int endX = targetRectangle.Right;
+ bool compand = this.Compand;
+
+ if (this.Sampler is NearestNeighborResampler)
+ {
+ // Scaling factors
+ float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width;
+ float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height;
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ startY,
+ endY,
+ this.ParallelOptions,
+ y =>
+ {
+ if (targetY <= y && y < targetBottom)
+ {
+ // Y coordinates of source points
+ int originY = (int)((y - startY) * heightFactor);
+
+ for (int x = startX; x < endX; x++)
+ {
+ if (targetX <= x && x < targetRight)
+ {
+ // X coordinates of source points
+ int originX = (int)((x - startX) * widthFactor);
+ targetPixels[x, y] = sourcePixels[originX, originY];
+ }
+ }
+
+ this.OnRowProcessed();
+ }
+ });
+ }
+
+ // Break out now.
+ return;
+ }
+
+ // Interpolate the image using the calculated weights.
+ // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm
+ // First process the columns. Since we are not using multiple threads startY and endY
+ // are the upper and lower bounds of the source rectangle.
+ Image firstPass = new Image(target.Width, source.Height);
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor firstPassPixels = firstPass.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ 0,
+ sourceHeight,
+ this.ParallelOptions,
+ y =>
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ if (x >= 0 && x < width)
+ {
+ // Ensure offsets are normalised for cropping and padding.
+ int offsetX = x - startX;
+ double sum = this.HorizontalWeights[offsetX].Sum;
+ Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values;
+
+ // Destination color components
+ Vector4 destination = Vector4.Zero;
+
+ for (int i = 0; i < sum; i++)
+ {
+ Weight xw = horizontalValues[i];
+ int originX = xw.Index;
+ Vector4 sourceColor = sourcePixels[originX, y].ToVector4().Expand();
+
+ destination += sourceColor * xw.Value;
+ }
+
+ T d = default(T);
+ d.PackVector(destination.Compress());
+ firstPassPixels[x, y] = d;
+ }
+ }
+ });
+
+ // Now process the rows.
+ Parallel.For(
+ startY,
+ endY,
+ this.ParallelOptions,
+ y =>
+ {
+ if (y >= 0 && y < height)
+ {
+ // Ensure offsets are normalised for cropping and padding.
+ int offsetY = y - startY;
+ double sum = this.VerticalWeights[offsetY].Sum;
+ Weight[] verticalValues = this.VerticalWeights[offsetY].Values;
+
+ for (int x = 0; x < width; x++)
+ {
+ // Destination color components
+ Vector4 destination = Vector4.Zero;
+
+ for (int i = 0; i < sum; i++)
+ {
+ Weight yw = verticalValues[i];
+ int originY = yw.Index;
+ Vector4 sourceColor = firstPassPixels[x, originY].ToVector4().Expand();
+
+ destination += sourceColor * yw.Value;
+ }
+
+ T d = default(T);
+ d.PackVector(destination.Compress());
+ targetPixels[x, y] = d;
+ }
+ }
+
+ this.OnRowProcessed();
+ });
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Processors/ResamplingWeightedProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResamplingWeightedProcessor.cs
new file mode 100644
index 0000000000..5630ec431f
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/ResamplingWeightedProcessor.cs
@@ -0,0 +1,209 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System;
+
+ ///
+ /// Provides methods that allow the resizing of images using various algorithms.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ public abstract class ResamplingWeightedProcessor : ImageSampler
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The sampler to perform the resize operation.
+ ///
+ protected ResamplingWeightedProcessor(IResampler sampler)
+ {
+ Guard.NotNull(sampler, nameof(sampler));
+
+ this.Sampler = sampler;
+ }
+
+ ///
+ /// Gets the sampler to perform the resize operation.
+ ///
+ public IResampler Sampler { get; }
+
+ ///
+ /// Gets or sets the horizontal weights.
+ ///
+ protected Weights[] HorizontalWeights { get; set; }
+
+ ///
+ /// Gets or sets the vertical weights.
+ ///
+ protected Weights[] VerticalWeights { get; set; }
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ if (!(this.Sampler is NearestNeighborResampler))
+ {
+ this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width);
+ this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height);
+ }
+ }
+
+ ///
+ protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ // Copy the pixels over.
+ if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)
+ {
+ target.ClonePixels(target.Width, target.Height, source.Pixels);
+ }
+ }
+
+ ///
+ /// Computes the weights to apply at each pixel when resizing.
+ ///
+ /// The destination section size.
+ /// The source section size.
+ ///
+ /// The .
+ ///
+ protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
+ {
+ float scale = (float)destinationSize / sourceSize;
+ IResampler sampler = this.Sampler;
+ float radius = sampler.Radius;
+ double left;
+ double right;
+ float weight;
+ int index;
+ int sum;
+
+ Weights[] result = new Weights[destinationSize];
+
+ // When shrinking, broaden the effective kernel support so that we still
+ // visit every source pixel.
+ if (scale < 1)
+ {
+ float width = radius / scale;
+ float filterScale = 1 / scale;
+
+ // Make the weights slices, one source for each column or row.
+ for (int i = 0; i < destinationSize; i++)
+ {
+ float centre = i / scale;
+ left = Math.Ceiling(centre - width);
+ right = Math.Floor(centre + width);
+
+ result[i] = new Weights
+ {
+ Values = new Weight[(int)(right - left + 1)]
+ };
+
+ for (double j = left; j <= right; j++)
+ {
+ weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale;
+ if (j < 0)
+ {
+ index = (int)-j;
+ }
+ else if (j >= sourceSize)
+ {
+ index = (int)((sourceSize - j) + sourceSize - 1);
+ }
+ else
+ {
+ index = (int)j;
+ }
+
+ sum = (int)result[i].Sum++;
+ result[i].Values[sum] = new Weight(index, weight);
+ }
+ }
+ }
+ else
+ {
+ // Make the weights slices, one source for each column or row.
+ for (int i = 0; i < destinationSize; i++)
+ {
+ float centre = i / scale;
+ left = Math.Ceiling(centre - radius);
+ right = Math.Floor(centre + radius);
+ result[i] = new Weights
+ {
+ Values = new Weight[(int)(right - left + 1)]
+ };
+
+ for (double j = left; j <= right; j++)
+ {
+ weight = sampler.GetValue((float)(centre - j));
+ if (j < 0)
+ {
+ index = (int)-j;
+ }
+ else if (j >= sourceSize)
+ {
+ index = (int)((sourceSize - j) + sourceSize - 1);
+ }
+ else
+ {
+ index = (int)j;
+ }
+
+ sum = (int)result[i].Sum++;
+ result[i].Values[sum] = new Weight(index, weight);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Represents the weight to be added to a scaled pixel.
+ ///
+ protected struct Weight
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The index.
+ /// The value.
+ public Weight(int index, float value)
+ {
+ this.Index = index;
+ this.Value = value;
+ }
+
+ ///
+ /// Gets the pixel index.
+ ///
+ public int Index { get; }
+
+ ///
+ /// Gets the result of the interpolation algorithm.
+ ///
+ public float Value { get; }
+ }
+
+ ///
+ /// Represents a collection of weights and their sum.
+ ///
+ protected class Weights
+ {
+ ///
+ /// Gets or sets the values.
+ ///
+ public Weight[] Values { get; set; }
+
+ ///
+ /// Gets or sets the sum.
+ ///
+ public float Sum { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
index b912a53eee..da588c9978 100644
--- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
+++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
@@ -5,55 +5,30 @@
namespace ImageProcessorCore.Processors
{
- using System;
using System.Numerics;
using System.Threading.Tasks;
///
/// Provides methods that allow the resizing of images using various algorithms.
///
+ ///
+ /// This version and the have been separated out to improve performance.
+ ///
/// The pixel format.
/// The packed format. long, float.
- public class ResizeProcessor : ImageSampler
+ public class ResizeProcessor : ResamplingWeightedProcessor
where T : IPackedVector
where TP : struct
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
///
/// The sampler to perform the resize operation.
///
public ResizeProcessor(IResampler sampler)
+ : base(sampler)
{
- Guard.NotNull(sampler, nameof(sampler));
-
- this.Sampler = sampler;
- }
-
- ///
- /// Gets the sampler to perform the resize operation.
- ///
- public IResampler Sampler { get; }
-
- ///
- /// Gets or sets the horizontal weights.
- ///
- protected Weights[] HorizontalWeights { get; set; }
-
- ///
- /// Gets or sets the vertical weights.
- ///
- protected Weights[] VerticalWeights { get; set; }
-
- ///
- protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
- {
- if (!(this.Sampler is NearestNeighborResampler))
- {
- this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width);
- this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height);
- }
}
///
@@ -74,7 +49,6 @@ namespace ImageProcessorCore.Processors
int targetBottom = target.Bounds.Bottom;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
- bool compand = this.Compand;
if (this.Sampler is NearestNeighborResampler)
{
@@ -148,19 +122,9 @@ namespace ImageProcessorCore.Processors
int originX = xw.Index;
Vector4 sourceColor = sourcePixels[originX, y].ToVector4();
- if (compand)
- {
- sourceColor = sourceColor.Expand();
- }
-
destination += sourceColor * xw.Value;
}
- if (compand)
- {
- destination = destination.Compress();
- }
-
T d = default(T);
d.PackVector(destination);
firstPassPixels[x, y] = d;
@@ -193,19 +157,9 @@ namespace ImageProcessorCore.Processors
int originY = yw.Index;
Vector4 sourceColor = firstPassPixels[x, originY].ToVector4();
- if (compand)
- {
- sourceColor = sourceColor.Expand();
- }
-
destination += sourceColor * yw.Value;
}
- if (compand)
- {
- destination = destination.Compress();
- }
-
T d = default(T);
d.PackVector(destination);
targetPixels[x, y] = d;
@@ -217,157 +171,5 @@ namespace ImageProcessorCore.Processors
}
}
-
- ///
- protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
- {
- // Copy the pixels over.
- if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)
- {
- target.ClonePixels(target.Width, target.Height, source.Pixels);
- }
- }
-
- ///
- /// Computes the weights to apply at each pixel when resizing.
- ///
- /// The destination section size.
- /// The source section size.
- ///
- /// The .
- ///
- protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
- {
- float scale = (float)destinationSize / sourceSize;
- IResampler sampler = this.Sampler;
- float radius = sampler.Radius;
- double left;
- double right;
- float weight;
- int index;
- int sum;
-
- Weights[] result = new Weights[destinationSize];
-
- // When shrinking, broaden the effective kernel support so that we still
- // visit every source pixel.
- if (scale < 1)
- {
- float width = radius / scale;
- float filterScale = 1 / scale;
-
- // Make the weights slices, one source for each column or row.
- for (int i = 0; i < destinationSize; i++)
- {
- float centre = i / scale;
- left = Math.Ceiling(centre - width);
- right = Math.Floor(centre + width);
-
- result[i] = new Weights
- {
- Values = new Weight[(int)(right - left + 1)]
- };
-
- for (double j = left; j <= right; j++)
- {
- weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale;
- if (j < 0)
- {
- index = (int)-j;
- }
- else if (j >= sourceSize)
- {
- index = (int)((sourceSize - j) + sourceSize - 1);
- }
- else
- {
- index = (int)j;
- }
-
- sum = (int)result[i].Sum++;
- result[i].Values[sum] = new Weight(index, weight);
- }
- }
- }
- else
- {
- // Make the weights slices, one source for each column or row.
- for (int i = 0; i < destinationSize; i++)
- {
- float centre = i / scale;
- left = Math.Ceiling(centre - radius);
- right = Math.Floor(centre + radius);
- result[i] = new Weights
- {
- Values = new Weight[(int)(right - left + 1)]
- };
-
- for (double j = left; j <= right; j++)
- {
- weight = sampler.GetValue((float)(centre - j));
- if (j < 0)
- {
- index = (int)-j;
- }
- else if (j >= sourceSize)
- {
- index = (int)((sourceSize - j) + sourceSize - 1);
- }
- else
- {
- index = (int)j;
- }
-
- sum = (int)result[i].Sum++;
- result[i].Values[sum] = new Weight(index, weight);
- }
- }
- }
-
- return result;
- }
-
- ///
- /// Represents the weight to be added to a scaled pixel.
- ///
- protected struct Weight
- {
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The index.
- /// The value.
- public Weight(int index, float value)
- {
- this.Index = index;
- this.Value = value;
- }
-
- ///
- /// Gets the pixel index.
- ///
- public int Index { get; }
-
- ///
- /// Gets the result of the interpolation algorithm.
- ///
- public float Value { get; }
- }
-
- ///
- /// Represents a collection of weights and their sum.
- ///
- protected class Weights
- {
- ///
- /// Gets or sets the values.
- ///
- public Weight[] Values { get; set; }
-
- ///
- /// Gets or sets the sum.
- ///
- public float Sum { get; set; }
- }
}
}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs
index 40b07469ec..5fa2eff702 100644
--- a/src/ImageProcessorCore/Samplers/Resize.cs
+++ b/src/ImageProcessorCore/Samplers/Resize.cs
@@ -138,7 +138,17 @@ namespace ImageProcessorCore
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
- ResizeProcessor processor = new ResizeProcessor(sampler) { Compand = compand };
+ ResamplingWeightedProcessor processor;
+
+ if (compand)
+ {
+ processor = new CompandingResizeProcessor(sampler);
+ }
+ else
+ {
+ processor = new ResizeProcessor(sampler);
+ }
+
processor.OnProgress += progressHandler;
try
diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs
index e5eb412f29..1312a91ad3 100644
--- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs
+++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs
@@ -36,5 +36,13 @@
image.Resize(400, 400);
return new CoreSize(image.Width, image.Height);
}
+
+ [Benchmark(Description = "ImageProcessorCore Compand Resize")]
+ public CoreSize ResizeCore()
+ {
+ CoreImage image = new CoreImage(2000, 2000);
+ image.Resize(400, 400, true);
+ return new CoreSize(image.Width, image.Height);
+ }
}
}
diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs
index dbd36bee7a..cdf5ae8b90 100644
--- a/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs
+++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs
@@ -51,7 +51,7 @@ namespace ImageProcessorCore.Tests
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
- image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate)
+ image.Resize(image.Width / 2, image.Height / 2, sampler, true, this.ProgressUpdate)
.Save(output);
}
}