Browse Source

Fixed resize. 😄

Former-commit-id: b76ea5a37afec493edc37fa84af3f2e7407498a4
Former-commit-id: 1e557c3c26bd777ba0f16f2d2b87fe10b3aa1073
Former-commit-id: 53e49322bc6608b6d2dbb4a9a54473c093c69a5c
af/merge-core
James Jackson-South 10 years ago
parent
commit
ded3de02c3
  1. 15
      src/ImageProcessor/ParallelImageProcessor.cs
  2. 4
      src/ImageProcessor/Samplers/ImageSampleExtensions.cs
  3. 155
      src/ImageProcessor/Samplers/Resize.cs
  4. 2
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

15
src/ImageProcessor/ParallelImageProcessor.cs

@ -21,7 +21,7 @@ namespace ImageProcessor
/// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
{
this.OnApply();
this.OnApply(target.Bounds, sourceRectangle);
if (this.Parallelism > 1)
{
@ -53,8 +53,6 @@ namespace ImageProcessor
/// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
{
this.OnApply();
byte[] pixels = new byte[width * height * 4];
target.SetPixels(width, height, pixels);
@ -68,6 +66,8 @@ namespace ImageProcessor
sourceRectangle = source.Bounds;
}
this.OnApply(target.Bounds, sourceRectangle);
if (this.Parallelism > 1)
{
int partitionCount = this.Parallelism;
@ -98,7 +98,14 @@ namespace ImageProcessor
/// <summary>
/// This method is called before the process is applied to prepare the processor.
/// </summary>
protected virtual void OnApply()
/// <param name="targetRectangle">
/// The <see cref="Rectangle"/> structure that specifies the location and size of the drawn image.
/// The image is scaled to fit the rectangle.
/// </param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void OnApply(Rectangle targetRectangle, Rectangle sourceRectangle)
{
}

4
src/ImageProcessor/Samplers/ImageSampleExtensions.cs

@ -19,7 +19,7 @@ namespace ImageProcessor.Samplers
/// <returns>The <see cref="Image"/></returns>
public static Image Resize(this Image source, int width, int height)
{
return source.Process(width, height, default(Rectangle), default(Rectangle), new Resize(new BicubicResampler()));
return Resize(source, width, height, new RobidouxResampler());
}
/// <summary>
@ -32,7 +32,7 @@ namespace ImageProcessor.Samplers
/// <returns>The <see cref="Image"/></returns>
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));
return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height));
}
/// <summary>

155
src/ImageProcessor/Samplers/Resize.cs

@ -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>

2
tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

@ -45,7 +45,7 @@ namespace ImageProcessor.Tests
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"Resized/{filename}"))
{
image.Resize(500, 500, sampler).Save(output);
image.Resize(image.Width / 2, image.Height / 2, sampler).Save(output);
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");

Loading…
Cancel
Save