Browse Source

Merge remote-tracking branch 'refs/remotes/origin/Core-Resize' into Core

Former-commit-id: fa9b8556f76f28c1f6d6b4d569f7825773b882f9
Former-commit-id: 371911bc45ac62835eebc2e81161bbf311c40f5d
Former-commit-id: 21d0de778f18f52aff9590f0bf3b95547f23bdec
pull/1/head
James South 10 years ago
parent
commit
6c1cdec9d8
  1. 181
      src/ImageProcessorCore/Samplers/Resampler.cs
  2. 67
      src/ImageProcessorCore/Samplers/Resize.cs
  3. 97
      tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

181
src/ImageProcessorCore/Samplers/Resampler.cs

@ -11,6 +11,8 @@ namespace ImageProcessorCore.Samplers
/// <summary>
/// Provides methods that allow the resampling of images using various algorithms.
/// <see href="http://www.realtimerendering.com/resources/GraphicsGems/category.html#Image Processing_link"/>
/// <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>
/// </summary>
public abstract class Resampler : ImageSampler
{
@ -52,74 +54,171 @@ namespace ImageProcessorCore.Samplers
/// </returns>
protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{
float xscale = destinationSize / (float)sourceSize;
float width;
IResampler sampler = this.Sampler;
float ratio = sourceSize / (float)destinationSize;
float scale = ratio;
float fwidth = sampler.Radius;
float fscale;
double left;
double right;
double weight = 0;
int n = 0;
int k;
// When shrinking, broaden the effective kernel support so that we still
Weights[] result = new Weights[destinationSize];
// When expanding, broaden the effective kernel support so that we still
// visit every source pixel.
if (scale < 1)
if (xscale < 0)
{
scale = 1;
}
width = sampler.Radius / xscale;
fscale = 1 / xscale;
float scaledRadius = (float)Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[destinationSize];
// Make the weights slices, one source for each column or row.
Parallel.For(
0,
destinationSize,
i =>
// Make the weights slices, one source for each column or row.
for (int i = 0; i < destinationSize; i++)
{
float center = ((i + .5f) * ratio) - 0.5f;
int start = (int)Math.Ceiling(center - scaledRadius);
if (start < 0)
{
start = 0;
}
int end = (int)Math.Floor(center + scaledRadius);
if (end > sourceSize)
float centre = i / xscale;
left = Math.Ceiling(centre - width);
right = Math.Floor(centre + width);
float sum = 0;
result[i] = new Weights();
List<Weight> builder = new List<Weight>();
for (double j = left; j <= right; j++)
{
end = sourceSize;
if (end < start)
weight = centre - j;
weight = sampler.GetValue((float)weight / fscale) / fscale;
if (j < 0)
{
n = (int)-j;
}
else if (j >= sourceSize)
{
n = (int)((sourceSize - j) + sourceSize - 1);
}
else
{
end = start;
n = (int)j;
}
sum++;
builder.Add(new Weight(n, (float)weight));
}
result[i].Values = builder.ToArray();
result[i].Sum = sum;
}
}
else
{
// Make the weights slices, one source for each column or row.
for (int i = 0; i < destinationSize; i++)
{
float centre = i / xscale;
left = Math.Ceiling(centre - fwidth);
right = Math.Floor(centre + fwidth);
float sum = 0;
result[i] = new Weights();
List<Weight> builder = new List<Weight>();
for (int a = start; a < end; a++)
for (double j = left; j <= right; j++)
{
float w = sampler.GetValue((a - center) / scale);
if (w < 0 || w > 0)
weight = centre - j;
weight = sampler.GetValue((float)weight);
if (j < 0)
{
sum += w;
builder.Add(new Weight(a, w));
n = (int)-j;
}
else if (j >= sourceSize)
{
n = (int)((sourceSize - j) + sourceSize - 1);
}
else
{
n = (int)j;
}
}
// Normalise the values
if (sum > 0 || sum < 0)
{
builder.ForEach(w => w.Value /= sum);
sum++;
builder.Add(new Weight(n, (float)weight));
}
result[i].Values = builder.ToArray();
result[i].Sum = sum;
});
}
}
return result;
}
//protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
//{
// IResampler sampler = this.Sampler;
// float ratio = sourceSize / (float)destinationSize;
// float scale = ratio;
// // When shrinking, broaden the effective kernel support so that we still
// // visit every source pixel.
// if (scale < 1)
// {
// scale = 1;
// }
// float scaledRadius = (float)Math.Ceiling(scale * sampler.Radius);
// Weights[] result = new Weights[destinationSize];
// // Make the weights slices, one source for each column or row.
// Parallel.For(
// 0,
// destinationSize,
// i =>
// {
// float center = ((i + .5f) * ratio) - 0.5f;
// int start = (int)Math.Ceiling(center - scaledRadius);
// if (start < 0)
// {
// start = 0;
// }
// int end = (int)Math.Floor(center + scaledRadius);
// if (end > sourceSize)
// {
// end = sourceSize;
// if (end < start)
// {
// end = start;
// }
// }
// float sum = 0;
// result[i] = new Weights();
// List<Weight> builder = new List<Weight>();
// for (int a = start; a < end; a++)
// {
// float w = sampler.GetValue((a - center) / scale);
// if (w < 0 || w > 0)
// {
// sum += w;
// builder.Add(new Weight(a, w));
// }
// }
// // 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;
//}
/// <summary>
/// Represents the weight to be added to a scaled pixel.
/// </summary>

67
src/ImageProcessorCore/Samplers/Resize.cs

@ -54,6 +54,7 @@ namespace ImageProcessorCore.Samplers
int sourceBottom = source.Bounds.Bottom;
int targetY = targetRectangle.Y;
int targetBottom = targetRectangle.Bottom;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
bool compand = this.Compand;
@ -69,18 +70,20 @@ namespace ImageProcessorCore.Samplers
endY,
y =>
{
// Y coordinates of source points
int originY = (int)((y - targetY) * heightFactor);
for (int x = startX; x < endX; x++)
if (y >= targetY && y < targetBottom)
{
// X coordinates of source points
int originX = (int)((x - startX) * widthFactor);
// Y coordinates of source points
int originY = (int)((y - targetY) * heightFactor);
target[x, y] = source[originX, originY];
}
for (int x = startX; x < endX; x++)
{
// X coordinates of source points
int originX = (int)((x - startX) * widthFactor);
this.OnRowProcessed();
target[x, y] = source[originX, originY];
}
this.OnRowProcessed();
}
});
// Break out now.
@ -97,13 +100,15 @@ namespace ImageProcessorCore.Samplers
{
for (int x = startX; x < endX; x++)
{
float sum = this.HorizontalWeights[x].Sum;
Weight[] horizontalValues = this.HorizontalWeights[x].Values;
// Destination color components
Color destination = new Color();
foreach (Weight xw in horizontalValues)
for (int i = 0; i < sum; i++)
{
Weight xw = horizontalValues[i];
int originX = xw.Index;
Color sourceColor = compand ? Color.Expand(source[originX, y]) : source[originX, y];
destination += sourceColor * xw.Value;
@ -124,30 +129,34 @@ namespace ImageProcessorCore.Samplers
endY,
y =>
{
Weight[] verticalValues = this.VerticalWeights[y].Values;
for (int x = startX; x < endX; x++)
if (y >= targetY && y < targetBottom)
{
// Destination color components
Color destination = new Color();
float sum = this.VerticalWeights[y].Sum;
Weight[] verticalValues = this.VerticalWeights[y].Values;
foreach (Weight yw in verticalValues)
for (int x = startX; x < endX; x++)
{
int originY = yw.Index;
int originX = x;
Color sourceColor = compand ? Color.Expand(this.firstPass[originX, originY]) : this.firstPass[originX, originY];
destination += sourceColor * yw.Value;
// Destination color components
Color destination = new Color();
for (int i = 0; i < sum; i++)
{
Weight yw = verticalValues[i];
int originY = yw.Index;
Color sourceColor = compand ? Color.Expand(this.firstPass[x, originY]) : this.firstPass[x, originY];
destination += sourceColor * yw.Value;
}
if (compand)
{
destination = Color.Compress(destination);
}
target[x, y] = destination;
}
if (compand)
{
destination = Color.Compress(destination);
}
target[x, y] = destination;
this.OnRowProcessed();
}
this.OnRowProcessed();
});
}
@ -159,8 +168,6 @@ namespace ImageProcessorCore.Samplers
{
target.ClonePixels(target.Width, target.Height, source.Pixels);
}
this.firstPass?.Dispose();
}
}
}

97
tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

@ -15,13 +15,13 @@
{
{ "Bicubic", new BicubicResampler() },
{ "Triangle", new TriangleResampler() },
{ "NearestNeighbor", new NearestNeighborResampler() },
// Perf: Enable for local testing only
//{ "Box", new BoxResampler() },
//{ "Lanczos3", new Lanczos3Resampler() },
//{ "Lanczos5", new Lanczos5Resampler() },
//{ "Lanczos8", new Lanczos8Resampler() },
//{ "MitchellNetravali", new MitchellNetravaliResampler() },
{ "NearestNeighbor", new NearestNeighborResampler() },
//{ "Hermite", new HermiteResampler() },
//{ "Spline", new SplineResampler() },
//{ "Robidoux", new RobidouxResampler() },
@ -60,17 +60,14 @@
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Sample/{Path.GetFileName(filename)}"))
using (FileStream output = File.OpenWrite($"TestOutput/Sample/{ Path.GetFileName(filename) }"))
{
processor.OnProgress += this.ProgressUpdate;
// Not Chainable.
image = image.Process(image.Width / 2, image.Height / 2, processor);
image.Save(output);
processor.OnProgress -= this.ProgressUpdate;
}
image.Dispose();
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
}
@ -91,15 +88,14 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate)
.Save(output);
}
image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate)
.Save(output);
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
}
}
@ -120,14 +116,12 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output);
}
image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output);
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
@ -150,14 +144,12 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output);
}
image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate)
.Save(output);
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
@ -179,14 +171,12 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType
+ Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
{
image.RotateFlip(rotateType, flipType, this.ProgressUpdate).Save(output);
}
image.RotateFlip(rotateType, flipType, this.ProgressUpdate)
.Save(output);
}
Trace.WriteLine($"{rotateType + "-" + flipType}: {watch.ElapsedMilliseconds}ms");
@ -194,9 +184,8 @@
}
}
[Theory]
[MemberData("ReSamplers")]
public void ImageShouldRotate(string name, IResampler sampler)
[Fact]
public void ImageShouldRotate()
{
if (!Directory.Exists("TestOutput/Rotate"))
{
@ -208,18 +197,16 @@
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
Image image = new Image(stream);
string filename = Path.GetFileName(file);
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{
image.Rotate(45, sampler, false, this.ProgressUpdate)
//.BackgroundColor(Color.Aqua)
.Save(output);
}
image.Rotate(45, this.ProgressUpdate)
.BackgroundColor(Color.Pink)
.Save(output);
}
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");
Trace.WriteLine($"{watch.ElapsedMilliseconds}ms");
}
}
}
@ -236,13 +223,11 @@
{
using (FileStream stream = File.OpenRead(file))
{
using (Image image = new Image(stream))
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
{
image.EntropyCrop(.5f, this.ProgressUpdate).Save(output);
}
image.EntropyCrop(.5f, this.ProgressUpdate).Save(output);
}
}
}
@ -260,13 +245,11 @@
{
using (FileStream stream = File.OpenRead(file))
{
using (Image image = new Image(stream))
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
{
image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output);
}
image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output);
}
}
}

Loading…
Cancel
Save