diff --git a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs
index 43f7e7dc8..30ae55c71 100644
--- a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs
+++ b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs
@@ -69,7 +69,7 @@ namespace ImageProcessor.Samplers
/// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
public static Image Resize(this Image source, int width, int height)
{
- return Resize(source, width, height, new RobidouxResampler());
+ return Resize(source, width, height, new BicubicResampler());
}
///
@@ -105,12 +105,13 @@ namespace ImageProcessor.Samplers
{
width = source.Width * height / source.Height;
}
+
if (height == 0 && width > 0)
{
height = source.Height * width / source.Width;
}
- return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Resampler(sampler));
+ return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Resize(sampler));
}
///
@@ -121,7 +122,7 @@ namespace ImageProcessor.Samplers
/// The
public static Image Rotate(this Image source, float degrees)
{
- return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(new BicubicResampler()) { Angle = degrees });
+ return Rotate(source, degrees, new BicubicResampler());
}
///
@@ -133,7 +134,7 @@ namespace ImageProcessor.Samplers
/// The
public static Image Rotate(this Image source, float degrees, IResampler sampler)
{
- return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(sampler) { Angle = degrees });
+ return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Rotate(sampler) { Angle = degrees });
}
///
diff --git a/src/ImageProcessor/Samplers/Resampler.cs b/src/ImageProcessor/Samplers/Resampler.cs
index 4fd105f16..6c00a604d 100644
--- a/src/ImageProcessor/Samplers/Resampler.cs
+++ b/src/ImageProcessor/Samplers/Resampler.cs
@@ -12,30 +12,15 @@ namespace ImageProcessor.Samplers
///
/// Provides methods that allow the resampling of images using various algorithms.
///
- public class Resampler : ParallelImageProcessor
+ public abstract class Resampler : ParallelImageProcessor
{
- ///
- /// The angle of rotation.
- ///
- private float angle;
-
- ///
- /// The horizontal weights.
- ///
- private Weights[] horizontalWeights;
-
- ///
- /// The vertical weights.
- ///
- private Weights[] verticalWeights;
-
///
/// Initializes a new instance of the class.
///
///
/// The sampler to perform the resize operation.
///
- public Resampler(IResampler sampler)
+ protected Resampler(IResampler sampler)
{
Guard.NotNull(sampler, nameof(sampler));
@@ -48,272 +33,14 @@ namespace ImageProcessor.Samplers
public IResampler Sampler { get; }
///
- /// Gets or sets the angle of rotation.
- ///
- public float Angle
- {
- get
- {
- return this.angle;
- }
-
- set
- {
- if (value > 360)
- {
- value -= 360;
- }
-
- if (value < 0)
- {
- value += 360;
- }
-
- this.angle = value;
- }
- }
-
- ///
- protected override void OnApply(ImageBase source, ImageBase target, 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 Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
- {
- bool rotate = this.angle > 0 && this.angle < 360;
-
- // Split the two methods up so we can keep standard resize as performant as possible.
- if (rotate)
- {
- this.ApplyResizeAndRotate(target, source, targetRectangle, sourceRectangle, startY, endY);
- }
- else
- {
- this.ApplyResizeOnly(target, source, targetRectangle, startY, endY);
- }
- }
-
- ///
- protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
- {
- // Copy the pixels over.
- if (source.Bounds == target.Bounds && Math.Abs(this.angle) < 0.001f)
- {
- target.ClonePixels(target.Width, target.Height, source.Pixels);
- }
- }
-
- ///
- /// Resamples the specified at the specified location
- /// and with the specified size.
+ /// Gets or sets the horizontal weights.
///
- /// Target image to apply the process to.
- /// The source image. Cannot be null.
- ///
- /// The structure that specifies the location and size of the drawn image.
- /// The image is scaled to fit the rectangle.
- ///
- /// The index of the row within the source image to start processing.
- /// The index of the row within the source image to end processing.
- ///
- /// The method keeps the source image unchanged and returns the
- /// the result of image process as new image.
- ///
- private void ApplyResizeOnly(ImageBase target, ImageBase source, Rectangle targetRectangle, int startY, int endY)
- {
- // Jump out, we'll deal with that later.
- if (source.Bounds == target.Bounds)
- {
- return;
- }
-
- int targetY = targetRectangle.Y;
- int targetBottom = targetRectangle.Bottom;
- int startX = targetRectangle.X;
- int endX = targetRectangle.Right;
-
- if (this.Sampler is NearestNeighborResampler)
- {
- // Scaling factors
- float widthFactor = source.Width / (float)target.Width;
- float heightFactor = source.Height / (float)target.Height;
-
- Parallel.For(
- startY,
- endY,
- y =>
- {
- if (y >= targetY && y < targetBottom)
- {
- // Y coordinates of source points
- int originY = (int)((y - targetY) * heightFactor);
-
- for (int x = startX; x < endX; x++)
- {
- // X coordinates of source points
- int originX = (int)((x - startX) * widthFactor);
-
- target[x, y] = source[originX, originY];
- }
- }
- });
-
- // Break out now.
- return;
- }
-
- // Interpolate the image using the calculated weights.
- // TODO: Figure out a way to split this up so we can reduce complexity and speed things up.
- Parallel.For(
- startY,
- endY,
- y =>
- {
- if (y >= targetY && y < targetBottom)
- {
- Weight[] verticalValues = this.verticalWeights[y].Values;
-
- for (int x = startX; x < endX; x++)
- {
- Weight[] horizontalValues = this.horizontalWeights[x].Values;
-
- // Destination color components
- Color destination = new Color();
-
- foreach (Weight yw in verticalValues)
- {
- int originY = yw.Index;
-
- foreach (Weight xw in horizontalValues)
- {
- int originX = xw.Index;
- Color sourceColor = Color.Expand(source[originX, originY]);
- destination += sourceColor * yw.Value * xw.Value;
- }
- }
-
- destination = Color.Compress(destination);
- target[x, y] = destination;
- }
- }
- });
- }
+ protected Weights[] HorizontalWeights { get; set; }
///
- /// Resamples and rotates the specified at the specified location
- /// and with the specified size.
+ /// Gets or sets the vertical weights.
///
- /// Target image to apply the process to.
- /// The source image. Cannot be null.
- ///
- /// The structure that specifies the location and size of the drawn image.
- /// The image is scaled to fit the rectangle.
- ///
- ///
- /// The structure that specifies the portion of the image object to draw.
- ///
- /// The index of the row within the source image to start processing.
- /// The index of the row within the source image to end processing.
- ///
- /// The method keeps the source image unchanged and returns the
- /// the result of image process as new image.
- ///
- private void ApplyResizeAndRotate(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;
- float negativeAngle = -this.angle;
- Point centre = Rectangle.Center(sourceRectangle);
-
- if (this.Sampler is NearestNeighborResampler)
- {
- // Scaling factors
- float widthFactor = source.Width / (float)target.Width;
- float heightFactor = source.Height / (float)target.Height;
-
- Parallel.For(
- startY,
- endY,
- y =>
- {
- if (y >= targetY && y < targetBottom)
- {
- // Y coordinates of source points
- int originY = (int)((y - targetY) * heightFactor);
-
- for (int x = startX; x < endX; x++)
- {
- // X coordinates of source points
- int originX = (int)((x - startX) * widthFactor);
-
- // Rotate at the centre point
- Point rotated = Point.Rotate(new Point(originX, originY), centre, negativeAngle);
- if (sourceRectangle.Contains(rotated.X, rotated.Y))
- {
- target[x, y] = source[rotated.X, rotated.Y];
- }
- }
- }
- });
-
- // Break out now.
- return;
- }
-
- // Interpolate the image using the calculated weights.
- Parallel.For(
- startY,
- endY,
- y =>
- {
- if (y >= targetY && y < targetBottom)
- {
- Weight[] verticalValues = this.verticalWeights[y].Values;
-
- for (int x = startX; x < endX; x++)
- {
- Weight[] horizontalValues = this.horizontalWeights[x].Values;
-
- // Destination color components
- Color destination = new Color();
-
- foreach (Weight yw in verticalValues)
- {
- int originY = yw.Index;
-
- foreach (Weight xw in horizontalValues)
- {
- int originX = xw.Index;
-
- // Rotate at the centre point
- Point rotated = Point.Rotate(new Point(originX, originY), centre, negativeAngle);
- if (sourceRectangle.Contains(rotated.X, rotated.Y))
- {
- target[x, y] = source[rotated.X, rotated.Y];
- }
-
- if (sourceRectangle.Contains(rotated.X, rotated.Y))
- {
- Color sourceColor = Color.Expand(source[rotated.X, rotated.Y]);
- destination += sourceColor * yw.Value * xw.Value;
- }
- }
- }
-
- destination = Color.Compress(destination);
- target[x, y] = destination;
- }
- }
- });
- }
+ protected Weights[] VerticalWeights { get; set; }
///
/// Computes the weights to apply at each pixel when resizing.
@@ -323,7 +50,7 @@ namespace ImageProcessor.Samplers
///
/// The .
///
- private Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
+ protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{
IResampler sampler = this.Sampler;
float ratio = sourceSize / (float)destinationSize;
@@ -344,51 +71,51 @@ namespace ImageProcessor.Samplers
0,
destinationSize,
i =>
+ {
+ float center = ((i + .5f) * ratio) - 0.5f;
+ int start = (int)Math.Ceiling(center - scaledRadius);
+
+ if (start < 0)
{
- float center = ((i + .5f) * ratio) - 0.5f;
- int start = (int)Math.Ceiling(center - scaledRadius);
+ start = 0;
+ }
- if (start < 0)
- {
- start = 0;
- }
+ int end = (int)Math.Floor(center + scaledRadius);
- int end = (int)Math.Floor(center + scaledRadius);
+ if (end > sourceSize)
+ {
+ end = sourceSize;
- if (end > sourceSize)
+ if (end < start)
{
- end = sourceSize;
-
- if (end < start)
- {
- end = start;
- }
+ end = start;
}
+ }
- float sum = 0;
- result[i] = new Weights();
-
- List builder = new List();
- for (int a = start; a < end; a++)
- {
- float w = sampler.GetValue((a - center) / scale);
+ float sum = 0;
+ result[i] = new Weights();
- if (w < 0 || w > 0)
- {
- sum += w;
- builder.Add(new Weight(a, w));
- }
- }
+ List builder = new List();
+ for (int a = start; a < end; a++)
+ {
+ float w = sampler.GetValue((a - center) / scale);
- // Normalise the values
- if (sum > 0 || sum < 0)
+ if (w < 0 || w > 0)
{
- builder.ForEach(w => w.Value /= sum);
+ sum += w;
+ builder.Add(new Weight(a, w));
}
+ }
- result[i].Values = builder.ToArray();
- result[i].Sum = sum;
- });
+ // Normalise the values
+ if (sum > 0 || sum < 0)
+ {
+ builder.ForEach(w => w.Value /= sum);
+ }
+
+ result[i].Values = builder.ToArray();
+ result[i].Sum = sum;
+ });
return result;
}
@@ -436,4 +163,4 @@ namespace ImageProcessor.Samplers
public float Sum { get; set; }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageProcessor/Samplers/Resamplers/BoxResampler.cs b/src/ImageProcessor/Samplers/Resamplers/BoxResampler.cs
index 71aca2eeb..37106a8ff 100644
--- a/src/ImageProcessor/Samplers/Resamplers/BoxResampler.cs
+++ b/src/ImageProcessor/Samplers/Resamplers/BoxResampler.cs
@@ -12,7 +12,7 @@ namespace ImageProcessor.Samplers
public class BoxResampler : IResampler
{
///
- public float Radius => 0.5f;
+ public float Radius => 0.5F;
///
public float GetValue(float x)
diff --git a/src/ImageProcessor/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageProcessor/Samplers/Resamplers/RobidouxResampler.cs
index 253f8b723..4331d6cc8 100644
--- a/src/ImageProcessor/Samplers/Resamplers/RobidouxResampler.cs
+++ b/src/ImageProcessor/Samplers/Resamplers/RobidouxResampler.cs
@@ -17,8 +17,8 @@ namespace ImageProcessor.Samplers
///
public float GetValue(float x)
{
- const float B = 0.3782f;
- const float C = 0.3109f;
+ const float B = 0.3782158F;
+ const float C = 0.3108921F;
return ImageMaths.GetBcValue(x, B, C);
}
diff --git a/src/ImageProcessor/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageProcessor/Samplers/Resamplers/RobidouxSharpResampler.cs
index 14db96d8e..e4b3c09a4 100644
--- a/src/ImageProcessor/Samplers/Resamplers/RobidouxSharpResampler.cs
+++ b/src/ImageProcessor/Samplers/Resamplers/RobidouxSharpResampler.cs
@@ -17,8 +17,8 @@ namespace ImageProcessor.Samplers
///
public float GetValue(float x)
{
- const float B = 0.2620f;
- const float C = 0.3690f;
+ const float B = 0.26201451F;
+ const float C = 0.36899274F;
return ImageMaths.GetBcValue(x, B, C);
}
diff --git a/src/ImageProcessor/Samplers/Resize.cs b/src/ImageProcessor/Samplers/Resize.cs
new file mode 100644
index 000000000..b7eea3c7b
--- /dev/null
+++ b/src/ImageProcessor/Samplers/Resize.cs
@@ -0,0 +1,158 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.Samplers
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the resizing of images using various algorithms.
+ ///
+ public class Resize : Resampler
+ {
+ ///
+ /// The image used for storing the first pass pixels.
+ ///
+ private Image firstPass;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The sampler to perform the resize operation.
+ ///
+ public Resize(IResampler sampler)
+ : base(sampler)
+ {
+ }
+
+ ///
+ public override int Parallelism { get; set; } = 1;
+
+ ///
+ protected override void OnApply(ImageBase source, ImageBase target, 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);
+ }
+
+ this.firstPass = new Image(target.Width, source.Height);
+ }
+
+ ///
+ 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)
+ {
+ return;
+ }
+
+ int sourceBottom = source.Bounds.Bottom;
+ int targetY = targetRectangle.Y;
+ int targetBottom = targetRectangle.Bottom;
+ int startX = targetRectangle.X;
+ int endX = targetRectangle.Right;
+
+ if (this.Sampler is NearestNeighborResampler)
+ {
+ // Scaling factors
+ float widthFactor = source.Width / (float)target.Width;
+ float heightFactor = source.Height / (float)target.Height;
+
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= targetY && y < targetBottom)
+ {
+ // Y coordinates of source points
+ int originY = (int)((y - targetY) * heightFactor);
+
+ for (int x = startX; x < endX; x++)
+ {
+ // X coordinates of source points
+ int originX = (int)((x - startX) * widthFactor);
+
+ target[x, y] = source[originX, originY];
+ }
+ }
+ });
+
+ // 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.
+ Parallel.For(
+ 0,
+ sourceBottom,
+ y =>
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ Weight[] horizontalValues = this.HorizontalWeights[x].Values;
+
+ // Destination color components
+ Color destination = new Color();
+
+ foreach (Weight xw in horizontalValues)
+ {
+ int originX = xw.Index;
+ Color sourceColor = Color.Expand(source[originX, y]);
+ destination += sourceColor * xw.Value;
+ }
+
+ destination = Color.Compress(destination);
+ this.firstPass[x, y] = destination;
+ }
+ });
+
+ // Now process the rows.
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= targetY && y < targetBottom)
+ {
+ Weight[] verticalValues = this.VerticalWeights[y].Values;
+
+ for (int x = startX; x < endX; x++)
+ {
+ // Destination color components
+ Color destination = new Color();
+
+ foreach (Weight yw in verticalValues)
+ {
+ int originY = yw.Index;
+ int originX = x;
+ Color sourceColor = Color.Expand(this.firstPass[originX, originY]);
+ destination += sourceColor * yw.Value;
+ }
+
+ destination = Color.Compress(destination);
+ target[x, y] = destination;
+ }
+ }
+ });
+ }
+
+ ///
+ protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ // Copy the pixels over.
+ if (source.Bounds == target.Bounds)
+ {
+ target.ClonePixels(target.Width, target.Height, source.Pixels);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessor/Samplers/Rotate.cs b/src/ImageProcessor/Samplers/Rotate.cs
new file mode 100644
index 000000000..71eb174c1
--- /dev/null
+++ b/src/ImageProcessor/Samplers/Rotate.cs
@@ -0,0 +1,159 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.Samplers
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the rotating of images using various algorithms.
+ ///
+ public class Rotate : Resampler
+ {
+ ///
+ /// The angle of rotation.
+ ///
+ private float angle;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The sampler to perform the resize operation.
+ ///
+ public Rotate(IResampler sampler)
+ : base(sampler)
+ {
+ }
+
+ ///
+ /// Gets or sets the angle of rotation.
+ ///
+ public float Angle
+ {
+ get
+ {
+ return this.angle;
+ }
+
+ set
+ {
+ if (value > 360)
+ {
+ value -= 360;
+ }
+
+ if (value < 0)
+ {
+ value += 360;
+ }
+
+ this.angle = value;
+ }
+ }
+
+ ///
+ protected override void OnApply(ImageBase source, ImageBase target, 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 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;
+ float negativeAngle = -this.angle;
+ Point centre = Rectangle.Center(sourceRectangle);
+
+ if (this.Sampler is NearestNeighborResampler)
+ {
+ // Scaling factors
+ float widthFactor = source.Width / (float)target.Width;
+ float heightFactor = source.Height / (float)target.Height;
+
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= targetY && y < targetBottom)
+ {
+ // Y coordinates of source points
+ int originY = (int)((y - targetY) * heightFactor);
+
+ for (int x = startX; x < endX; x++)
+ {
+ // X coordinates of source points
+ int originX = (int)((x - startX) * widthFactor);
+
+ // Rotate at the centre point
+ Point rotated = Point.Rotate(new Point(originX, originY), centre, negativeAngle);
+ if (sourceRectangle.Contains(rotated.X, rotated.Y))
+ {
+ target[x, y] = source[rotated.X, rotated.Y];
+ }
+ }
+ }
+ });
+
+ // Break out now.
+ return;
+ }
+
+ // Interpolate the image using the calculated weights.
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= targetY && y < targetBottom)
+ {
+ Weight[] verticalValues = this.VerticalWeights[y].Values;
+
+ for (int x = startX; x < endX; x++)
+ {
+ Weight[] horizontalValues = this.HorizontalWeights[x].Values;
+
+ // Destination color components
+ Color destination = new Color();
+
+ foreach (Weight yw in verticalValues)
+ {
+ int originY = yw.Index;
+
+ foreach (Weight xw in horizontalValues)
+ {
+ int originX = xw.Index;
+
+ // Rotate at the centre point
+ Point rotated = Point.Rotate(new Point(originX, originY), centre, negativeAngle);
+ if (sourceRectangle.Contains(rotated.X, rotated.Y))
+ {
+ target[x, y] = source[rotated.X, rotated.Y];
+ }
+
+ if (sourceRectangle.Contains(rotated.X, rotated.Y))
+ {
+ Color sourceColor = Color.Expand(source[rotated.X, rotated.Y]);
+ destination += sourceColor * yw.Value * xw.Value;
+ }
+ }
+ }
+
+ destination = Color.Compress(destination);
+ target[x, y] = destination;
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file