Browse Source

Cleanup for debugging.

If Parallelism > 1 then something goes wrong with the pixel sampling. It
could be as simple as a rounding error but I'm struggling to see what
the bug cause is.


Former-commit-id: 222bb4f5982a37ad03dc1ddd47a7d437699ba6b6
Former-commit-id: 1e85ef9bfa7f17bb703c84dee5b6b18734ca7401
Former-commit-id: f6bbb2fbae3df6e9753e16dbcf80c75c7d7f93a3
af/merge-core
James Jackson-South 11 years ago
parent
commit
933f6d8f55
  1. 2
      src/ImageProcessor/ImageProcessor.csproj
  2. 4
      src/ImageProcessor/ParallelImageProcessor.cs
  3. 180
      src/ImageProcessor/Samplers/Resize - Copy.cs
  4. 172
      src/ImageProcessor/Samplers/Resize.cs
  5. 147
      src/ImageProcessor/Samplers/ResizeOld.cs
  6. 24
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

2
src/ImageProcessor/ImageProcessor.csproj

@ -196,8 +196,8 @@
<Compile Include="Samplers\Resamplers\BicubicResampler.cs" />
<Compile Include="Samplers\ImageSampleExtensions.cs" />
<Compile Include="Samplers\Resamplers\IResampler.cs" />
<Compile Include="Samplers\Resize - Copy.cs" />
<Compile Include="Samplers\Resize.cs" />
<Compile Include="Samplers\ResizeOld.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib.Portable, Version=0.86.0.51802, Culture=neutral, processorArchitecture=MSIL">

4
src/ImageProcessor/ParallelImageProcessor.cs

@ -16,7 +16,7 @@ namespace ImageProcessor
/// <summary>
/// Gets or sets the count of workers to run the process in parallel.
/// </summary>
public int Parallelism { get; set; } = Environment.ProcessorCount;
public virtual int Parallelism { get; set; } = Environment.ProcessorCount;
/// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
@ -67,7 +67,7 @@ namespace ImageProcessor
{
sourceRectangle = source.Bounds;
}
this.Parallelism = 1;
if (this.Parallelism > 1)
{
int partitionCount = this.Parallelism;

180
src/ImageProcessor/Samplers/Resize - Copy.cs

@ -1,180 +0,0 @@
// <copyright file="Resize.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Samplers
{
using System;
using System.Collections.Generic;
/// <summary>
/// Provides methods that allow the resizing of images using various resampling algorithms.
/// </summary>
public class Resize : ParallelImageProcessor
{
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <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)
{
Guard.NotNull(sampler, nameof(sampler));
this.Sampler = sampler;
}
/// <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)
{
int sourceWidth = source.Width;
int sourceHeight = source.Height;
int width = target.Width;
int height = target.Height;
int targetY = targetRectangle.Y;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
// Scaling factors
double widthFactor = sourceWidth / (double)targetRectangle.Width;
double heightFactor = sourceHeight / (double)targetRectangle.Height;
int sectionHeight = endY - startY;
Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width, this.Sampler);
Weights[] verticalWeights = this.PrecomputeWeights(sectionHeight, (int)((sectionHeight * heightFactor) + .5), this.Sampler);
// Width and height decreased by 1
int maxHeight = sourceHeight - 1;
int maxWidth = sourceWidth - 1;
for (int y = startY; y < endY; y++)
{
List<Weight> verticalValues = verticalWeights[y - startY].Values;
double verticalSum = verticalWeights[y - startY].Sum;
for (int x = startX; x < endX; x++)
{
List<Weight> horizontalValues = horizontalWeights[x - startX].Values;
double horizontalSum = horizontalWeights[x - startX].Sum;
// Destination color components
double r = 0;
double g = 0;
double b = 0;
double a = 0;
foreach (Weight yw in verticalValues)
{
int originY = yw.Index + startY; //- targetY;//(int)(((y - targetY) * heightFactor) - 0.5);
originY = originY.Clamp(0, maxHeight);
foreach (Weight xw in horizontalValues)
{
int originX = xw.Index;//(int)(((x - startX) * widthFactor) - 0.5);
originX = originX.Clamp(0, maxWidth);
Bgra sourceColor = source[originX, originY];
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());
target[x, y] = destinationColor;
}
}
}
private Weights[] PrecomputeWeights(int dstSize, int srcSize, IResampler sampler)
{
float du = srcSize / (float)dstSize;
float scale = du;
if (scale < 1)
{
scale = 1;
}
double ru = Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[dstSize];
for (int i = 0; i < dstSize; i++)
{
double fu = ((i + .5) * du) - 0.5;
int startU = (int)Math.Ceiling(fu - ru);
if (startU < 0)
{
startU = 0;
}
int endU = (int)Math.Floor(fu + ru);
if (endU > srcSize - 1)
{
endU = srcSize - 1;
}
double sum = 0;
result[i] = new Weights();
for (int a = startU; a <= endU; a++)
{
double w = 255 * sampler.GetValue((a - fu) / scale);
if (Math.Abs(w) > Epsilon)
{
sum += w;
result[i].Values.Add(new Weight(a, w));
}
}
result[i].Sum = sum;
}
return result;
}
public struct Weight
{
public Weight(int index, double value)
{
this.Index = index;
this.Value = value;
}
public readonly int Index;
public readonly double Value;
}
public class Weights
{
public Weights()
{
this.Values = new List<Weight>();
}
public List<Weight> Values { get; set; }
public double Sum { get; set; }
}
}
}

172
src/ImageProcessor/Samplers/Resize.cs

@ -6,11 +6,12 @@
namespace ImageProcessor.Samplers
{
using System;
using System.Collections.Generic;
/// <summary>
/// Provides methods that allow the resizing of images using various resampling algorithms.
/// </summary>
public class ResizeB : ParallelImageProcessor
public class Resize : ParallelImageProcessor
{
/// <summary>
/// The epsilon for comparing floating point numbers.
@ -23,13 +24,16 @@ namespace ImageProcessor.Samplers
/// <param name="sampler">
/// The sampler to perform the resize operation.
/// </param>
public ResizeB(IResampler sampler)
public Resize(IResampler sampler)
{
Guard.NotNull(sampler, nameof(sampler));
this.Sampler = sampler;
}
/// <inheritdoc/>
public override int Parallelism => 1; // Uncomment this to see bug.
/// <summary>
/// Gets the sampler to perform the resize operation.
/// </summary>
@ -47,12 +51,14 @@ namespace ImageProcessor.Samplers
int targetY = targetRectangle.Y;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
int right = (int)(this.Sampler.Radius + .5);
int left = (-right) + 1;
// Scaling factors
double widthFactor = sourceWidth / (double)targetRectangle.Width;
double heightFactor = sourceHeight / (double)targetRectangle.Height;
int targetSectionHeight = endY - startY;
int sourceSectionHeight = (int)((targetSectionHeight * heightFactor) + .5);
Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width, this.Sampler);
Weights[] verticalWeights = this.PrecomputeWeights(targetSectionHeight, sourceSectionHeight, this.Sampler);
// Width and height decreased by 1
int maxHeight = sourceHeight - 1;
@ -62,20 +68,15 @@ namespace ImageProcessor.Samplers
{
if (y >= 0 && y < height)
{
// Y coordinates of source points.
double originY = ((y - targetY) * heightFactor) - 0.5;
int originY1 = (int)originY;
double dy = originY - originY1;
List<Weight> verticalValues = verticalWeights[y - startY].Values;
double verticalSum = verticalWeights[y - startY].Sum;
// 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;
List<Weight> horizontalValues = horizontalWeights[x - startX].Values;
double horizontalSum = horizontalWeights[x - startX].Sum;
// Destination color components
double r = 0;
@ -83,65 +84,132 @@ namespace ImageProcessor.Samplers
double b = 0;
double a = 0;
for (int yy = left; yy <= right; yy++)
foreach (Weight yw in verticalValues)
{
// Get Y cooefficient
double kernel1 = this.Sampler.GetValue(yy - dy);
if (Math.Abs(kernel1) < Epsilon)
if (Math.Abs(yw.Value) < Epsilon)
{
continue;
}
int originY2 = originY1 + yy;
if (originY2 < 0)
{
originY2 = 0;
}
if (originY2 > maxHeight)
{
originY2 = maxHeight;
}
// TODO: This is wrong. Adding (int)((startY * heightFactor) - .5) gets close but no cigar.
int originY = yw.Index + (int)((startY * heightFactor) - .5);
originY = originY.Clamp(0, maxHeight);
for (int xx = left; xx <= right; xx++)
foreach (Weight xw in horizontalValues)
{
// Get X cooefficient
double kernel2 = kernel1 * this.Sampler.GetValue(xx - dx);
if (Math.Abs(kernel2) < Epsilon)
if (Math.Abs(xw.Value) < Epsilon)
{
continue;
}
int originX2 = originX1 + xx;
if (originX2 < 0)
{
originX2 = 0;
}
if (originX2 > maxWidth)
{
originX2 = maxWidth;
}
Bgra sourceColor = source[originX2, originY2];
// sourceColor = PixelOperations.ToLinear(sourceColor);
// TODO: This need updating to take into account the target rectangle.
int originX = xw.Index;
originX = originX.Clamp(0, maxWidth);
r += kernel2 * sourceColor.R;
g += kernel2 * sourceColor.G;
b += kernel2 * sourceColor.B;
a += kernel2 * sourceColor.A;
Bgra sourceColor = source[originX, originY];
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;
}
}
}
}
}
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="destinationSize">
/// The destination section size.
/// </param>
/// <param name="sourceSize">
/// The source section size.
/// </param>
/// <param name="sampler">
/// The <see cref="IResampler"/> containing the resampling algorithm.
/// </param>
/// <returns>
/// The <see cref="T:Weights[]"/>.
/// </returns>
private Weights[] PrecomputeWeights(int destinationSize, int sourceSize, IResampler sampler)
{
float du = sourceSize / (float)destinationSize;
float scale = du;
if (scale < 1)
{
scale = 1;
}
double ru = Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[destinationSize];
for (int i = 0; i < destinationSize; i++)
{
double fu = ((i + .5) * du) - 0.5;
int startU = (int)Math.Ceiling(fu - ru);
if (startU < 0)
{
startU = 0;
}
int endU = (int)Math.Floor(fu + ru);
if (endU > sourceSize - 1)
{
endU = sourceSize - 1;
}
double sum = 0;
result[i] = new Weights();
for (int a = startU; a <= endU; a++)
{
double w = 255 * sampler.GetValue((a - fu) / scale);
if (Math.Abs(w) > Epsilon)
{
sum += w;
result[i].Values.Add(new Weight(a, w));
}
}
result[i].Sum = sum;
}
return result;
}
protected struct Weight
{
public Weight(int index, double value)
{
this.Index = index;
this.Value = value;
}
public readonly int Index;
public readonly double Value;
}
protected class Weights
{
public Weights()
{
this.Values = new List<Weight>();
}
public List<Weight> Values { get; set; }
public double Sum { get; set; }
}
}
}

147
src/ImageProcessor/Samplers/ResizeOld.cs

@ -0,0 +1,147 @@
// <copyright file="Resize.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Samplers
{
using System;
/// <summary>
/// Provides methods that allow the resizing of images using various resampling algorithms.
/// </summary>
public class ResizeB : ParallelImageProcessor
{
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// Initializes a new instance of the <see cref="Resize"/> class.
/// </summary>
/// <param name="sampler">
/// The sampler to perform the resize operation.
/// </param>
public ResizeB(IResampler sampler)
{
Guard.NotNull(sampler, nameof(sampler));
this.Sampler = sampler;
}
/// <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)
{
int sourceWidth = source.Width;
int sourceHeight = source.Height;
int width = target.Width;
int height = target.Height;
int targetY = targetRectangle.Y;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
int right = (int)(this.Sampler.Radius + .5);
int left = (-right) + 1;
// 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 = ((y - targetY) * 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(yy - dy);
if (Math.Abs(kernel1) < Epsilon)
{
continue;
}
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);
if (Math.Abs(kernel2) < Epsilon)
{
continue;
}
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;
}
}
}
}
}
}
}

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

@ -13,18 +13,18 @@ namespace ImageProcessor.Tests
public static readonly TheoryData<string, IResampler> Samplers =
new TheoryData<string, IResampler>
{
//{ "Bicubic", new BicubicResampler() },
//{ "Bilinear", new TriangleResampler() },
{ "NearestNeighbour", new BoxResampler() },
//{ "Lanczos3", new Lanczos3Resampler() },
{ "Bicubic", new BicubicResampler() },
{ "Triangle", new TriangleResampler() },
{ "Box", new BoxResampler() },
{ "Lanczos3", new Lanczos3Resampler() },
{ "Lanczos8", new Lanczos8Resampler() },
//{ "MitchellNetravali", new MitchellNetravaliResampler() },
//{ "Hermite", new HermiteResampler() },
//{ "Spline", new SplineResampler() },
//{ "Robidoux", new RobidouxResampler() },
//{ "RobidouxSharp", new RobidouxSharpResampler() },
//{ "RobidouxSoft", new RobidouxSoftResampler() },
//{ "Welch", new WelchResampler() }
{ "MitchellNetravali", new MitchellNetravaliResampler() },
{ "Hermite", new HermiteResampler() },
{ "Spline", new SplineResampler() },
{ "Robidoux", new RobidouxResampler() },
{ "RobidouxSharp", new RobidouxSharpResampler() },
{ "RobidouxSoft", new RobidouxSoftResampler() },
{ "Welch", new WelchResampler() }
};
[Theory]
@ -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(100, 100, sampler).Save(output);
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");

Loading…
Cancel
Save