Browse Source

Updated every filter and sampler to call ParallelImageProcessor.OnRowProcessed to enabled progress notifications for client code

Former-commit-id: 457a0ef5953ccea0c83d4a3c67c4d276eb9172f0
Former-commit-id: 9a6824f576f5c18db44cf8e535676deaae3dc70a
Former-commit-id: 9a90ccef99351f7f19c75041f05f9f2cd0e080a6
pull/17/head
voidstar69 10 years ago
parent
commit
cabef610f4
  1. 1
      src/ImageProcessor/Filters/Alpha.cs
  2. 1
      src/ImageProcessor/Filters/BackgroundColor.cs
  3. 1
      src/ImageProcessor/Filters/Binarization/Threshold.cs
  4. 1
      src/ImageProcessor/Filters/Blend.cs
  5. 1
      src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs
  6. 1
      src/ImageProcessor/Filters/Contrast.cs
  7. 1
      src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs
  8. 1
      src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs
  9. 1
      src/ImageProcessor/Filters/Convolution/ConvolutionFilter.cs
  10. 1
      src/ImageProcessor/Filters/Glow.cs
  11. 1
      src/ImageProcessor/Filters/Invert.cs
  12. 1
      src/ImageProcessor/Filters/Pixelate.cs
  13. 1
      src/ImageProcessor/Filters/Vignette.cs
  14. 11
      src/ImageProcessor/IImageProcessor.cs
  15. 10
      src/ImageProcessor/ParallelImageProcessor.cs
  16. 1
      src/ImageProcessor/Samplers/Crop.cs
  17. 1
      src/ImageProcessor/Samplers/EntropyCrop.cs
  18. 90
      src/ImageProcessor/Samplers/ImageSampleExtensions.cs
  19. 2
      src/ImageProcessor/Samplers/Resize.cs
  20. 2
      src/ImageProcessor/Samplers/Rotate.cs
  21. 15
      src/ImageProcessor/Samplers/RotateFlip.cs
  22. 13
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
  23. 19
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

1
src/ImageProcessor/Filters/Alpha.cs

@ -55,6 +55,7 @@ namespace ImageProcessor.Filters
color *= alphaVector;
target[x, y] = Color.FromNonPremultiplied(new Color(color));
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/BackgroundColor.cs

@ -65,6 +65,7 @@ namespace ImageProcessor.Filters
target[x, y] = color;
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Binarization/Threshold.cs

@ -75,6 +75,7 @@ namespace ImageProcessor.Filters
// Any channel will do since it's greyscale.
target[x, y] = color.B >= threshold ? upper : lower;
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Blend.cs

@ -69,6 +69,7 @@ namespace ImageProcessor.Filters
target[x, y] = color;
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs

@ -39,6 +39,7 @@ namespace ImageProcessor.Filters
{
target[x, y] = this.ApplyMatrix(source[x, y], matrix);
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Contrast.cs

@ -57,6 +57,7 @@ namespace ImageProcessor.Filters
color += shiftVector;
target[x, y] = Color.Compress(new Color(color));
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs

@ -101,6 +101,7 @@ namespace ImageProcessor.Filters
Color targetColor = target[x, y];
target[x, y] = new Color(red, green, blue, targetColor.A);
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs

@ -104,6 +104,7 @@ namespace ImageProcessor.Filters
target[x, y] = destination;
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Convolution/ConvolutionFilter.cs

@ -76,6 +76,7 @@ namespace ImageProcessor.Filters
target[x, y] = new Color(red, green, blue);
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Glow.cs

@ -51,6 +51,7 @@ namespace ImageProcessor.Filters
Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(glowColor, sourceColor, .5f * (distance / maxDistance));
}
this.OnRowProcessed();
});
}
}

1
src/ImageProcessor/Filters/Invert.cs

@ -35,6 +35,7 @@ namespace ImageProcessor.Filters
Vector3 vector = inverseVector - color.ToVector3();
target[x, y] = new Color(vector, color.A);
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Pixelate.cs

@ -85,6 +85,7 @@ namespace ImageProcessor.Filters
}
}
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Filters/Vignette.cs

@ -51,6 +51,7 @@ namespace ImageProcessor.Filters
Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(vignetteColor, sourceColor, 1 - .9f * distance / maxDistance);
}
this.OnRowProcessed();
});
}
}

11
src/ImageProcessor/IImageProcessor.cs

@ -5,13 +5,18 @@
namespace ImageProcessor
{
public struct ProgressedEventArgs
public class ProgressEventArgs : System.EventArgs
{
public int numRowsProcessed;
public int totalRows;
}
public delegate void ProgressedEventHandler(object sender, ProgressedEventArgs e);
/// <summary>
/// A delegate which is called as progress is made processing the image.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void ProgressEventHandler(object sender, ProgressEventArgs e);
/// <summary>
/// Encapsulates methods to alter the pixels of an image.
@ -25,7 +30,7 @@ namespace ImageProcessor
/// This event may be called from threads other than the client thread, and from multiple threads simultaneously.
/// Individual row notifications may arrived out of order.
/// </remarks>
event ProgressedEventHandler OnProgressed;
event ProgressEventHandler OnProgress;
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase"/>.

10
src/ImageProcessor/ParallelImageProcessor.cs

@ -15,7 +15,7 @@ namespace ImageProcessor
public abstract class ParallelImageProcessor : IImageProcessor
{
/// <inheritdoc/>
public event ProgressedEventHandler OnProgressed;
public event ProgressEventHandler OnProgress;
/// <summary>
/// Gets or sets the count of workers to run the process in parallel.
@ -37,12 +37,16 @@ namespace ImageProcessor
/// </summary>
protected void OnRowProcessed()
{
if(this.OnProgressed != null)
if(this.OnProgress != null)
{
int currThreadNumRows = Interlocked.Add(ref this.numRowsProcessed, 1);
// Multi-pass filters process multiple times more rows than totalRows, so update totalRows on the fly
if (currThreadNumRows > this.totalRows)
this.totalRows = currThreadNumRows;
// Report progress. This may be on the client's thread, or on a Task library thread.
this.OnProgressed(this, new ProgressedEventArgs { numRowsProcessed = currThreadNumRows, totalRows = this.totalRows });
this.OnProgress(this, new ProgressEventArgs { numRowsProcessed = currThreadNumRows, totalRows = this.totalRows });
}
}

1
src/ImageProcessor/Samplers/Crop.cs

@ -31,6 +31,7 @@ namespace ImageProcessor.Samplers
{
target[x, y] = source[x, y];
}
this.OnRowProcessed();
}
});
}

1
src/ImageProcessor/Samplers/EntropyCrop.cs

@ -89,6 +89,7 @@ namespace ImageProcessor.Samplers
target[x, y] = source[offsetX, offsetY];
}
this.OnRowProcessed();
});
}
}

90
src/ImageProcessor/Samplers/ImageSampleExtensions.cs

@ -16,10 +16,11 @@ namespace ImageProcessor.Samplers
/// <param name="source">The image to resize.</param>
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Crop(this Image source, int width, int height)
public static Image Crop(this Image source, int width, int height, ProgressEventHandler progressHandler = null)
{
return Crop(source, width, height, source.Bounds);
return Crop(source, width, height, source.Bounds, progressHandler);
}
/// <summary>
@ -35,8 +36,9 @@ namespace ImageProcessor.Samplers
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle)
public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle, ProgressEventHandler progressHandler = null)
{
if (sourceRectangle.Width < width || sourceRectangle.Height < height)
{
@ -45,7 +47,16 @@ namespace ImageProcessor.Samplers
source = source.Resize(sourceRectangle.Width, sourceRectangle.Height);
}
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Crop());
var processor = new Crop();
processor.OnProgress += progressHandler;
try
{
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
/// <summary>
@ -53,10 +64,20 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="source">The image to crop.</param>
/// <param name="threshold">The threshold for entropic density.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image EntropyCrop(this Image source, float threshold = .5f)
public static Image EntropyCrop(this Image source, float threshold = .5f, ProgressEventHandler progressHandler = null)
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new EntropyCrop(threshold));
var processor = new EntropyCrop(threshold);
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
/// <summary>
@ -65,11 +86,12 @@ namespace ImageProcessor.Samplers
/// <param name="source">The image to resize.</param>
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image Resize(this Image source, int width, int height)
public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null)
{
return Resize(source, width, height, new BicubicResampler());
return Resize(source, width, height, new BicubicResampler(), progressHandler);
}
/// <summary>
@ -79,11 +101,12 @@ namespace ImageProcessor.Samplers
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image Resize(this Image source, int width, int height, IResampler sampler)
public static Image Resize(this Image source, int width, int height, IResampler sampler, ProgressEventHandler progressHandler = null)
{
return Resize(source, width, height, sampler, source.Bounds);
return Resize(source, width, height, sampler, source.Bounds, progressHandler);
}
/// <summary>
@ -97,9 +120,10 @@ namespace ImageProcessor.Samplers
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle)
public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, ProgressEventHandler progressHandler = null)
{
if (width == 0 && height > 0)
{
@ -111,7 +135,16 @@ namespace ImageProcessor.Samplers
height = source.Height * width / source.Width;
}
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Resize(sampler));
var processor = new Resize(sampler);
processor.OnProgress += progressHandler;
try
{
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
/// <summary>
@ -119,10 +152,11 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Rotate(this Image source, float degrees)
public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null)
{
return Rotate(source, degrees, new BicubicResampler());
return Rotate(source, degrees, new BicubicResampler(), progressHandler);
}
/// <summary>
@ -131,10 +165,20 @@ namespace ImageProcessor.Samplers
/// <param name="source">The image to resize.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Rotate(this Image source, float degrees, IResampler sampler)
public static Image Rotate(this Image source, float degrees, IResampler sampler, ProgressEventHandler progressHandler = null)
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Rotate(sampler) { Angle = degrees });
var processor = new Rotate(sampler) { Angle = degrees };
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
/// <summary>
@ -143,10 +187,20 @@ namespace ImageProcessor.Samplers
/// <param name="source">The image to resize.</param>
/// <param name="rotateType">The <see cref="RotateType"/> to perform the rotation.</param>
/// <param name="flipType">The <see cref="FlipType"/> to perform the flip.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType)
public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType, ProgressEventHandler progressHandler = null)
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new RotateFlip(rotateType, flipType));
var processor = new RotateFlip(rotateType, flipType);
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

2
src/ImageProcessor/Samplers/Resize.cs

@ -81,6 +81,7 @@ namespace ImageProcessor.Samplers
target[x, y] = source[originX, originY];
}
this.OnRowProcessed();
}
});
@ -141,6 +142,7 @@ namespace ImageProcessor.Samplers
destination = Color.Compress(destination);
target[x, y] = destination;
}
this.OnRowProcessed();
}
});
}

2
src/ImageProcessor/Samplers/Rotate.cs

@ -102,6 +102,7 @@ namespace ImageProcessor.Samplers
target[x, y] = source[rotated.X, rotated.Y];
}
}
this.OnRowProcessed();
}
});
@ -152,6 +153,7 @@ namespace ImageProcessor.Samplers
destination = Color.Compress(destination);
target[x, y] = destination;
}
this.OnRowProcessed();
}
});
}

15
src/ImageProcessor/Samplers/RotateFlip.cs

@ -73,7 +73,7 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private static void Rotate270(ImageBase target, ImageBase source)
private void Rotate270(ImageBase target, ImageBase source)
{
int width = source.Width;
int height = source.Height;
@ -90,6 +90,7 @@ namespace ImageProcessor.Samplers
newY = width - newY - 1;
temp[newX, newY] = source[x, y];
}
this.OnRowProcessed();
});
target.SetPixels(height, width, temp.Pixels);
@ -100,7 +101,7 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private static void Rotate180(ImageBase target, ImageBase source)
private void Rotate180(ImageBase target, ImageBase source)
{
int width = source.Width;
int height = source.Height;
@ -114,6 +115,7 @@ namespace ImageProcessor.Samplers
int newY = height - y - 1;
target[newX, newY] = source[x, y];
}
this.OnRowProcessed();
});
}
@ -122,7 +124,7 @@ namespace ImageProcessor.Samplers
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private static void Rotate90(ImageBase target, ImageBase source)
private void Rotate90(ImageBase target, ImageBase source)
{
int width = source.Width;
int height = source.Height;
@ -136,6 +138,7 @@ namespace ImageProcessor.Samplers
int newX = height - y - 1;
temp[newX, x] = source[x, y];
}
this.OnRowProcessed();
});
target.SetPixels(height, width, temp.Pixels);
@ -146,7 +149,7 @@ namespace ImageProcessor.Samplers
/// at half the height of the image.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
private static void FlipX(ImageBase target)
private void FlipX(ImageBase target)
{
int width = target.Width;
int height = target.Height;
@ -163,6 +166,7 @@ namespace ImageProcessor.Samplers
target[x, y] = temp[x, newY];
target[x, newY] = temp[x, y];
}
this.OnRowProcessed();
});
}
@ -171,7 +175,7 @@ namespace ImageProcessor.Samplers
/// at half of the width of the image.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
private static void FlipY(ImageBase target)
private void FlipY(ImageBase target)
{
int width = target.Width;
int height = target.Height;
@ -188,6 +192,7 @@ namespace ImageProcessor.Samplers
target[x, y] = temp[newX, y];
target[newX, y] = temp[x, y];
}
this.OnRowProcessed();
});
}
}

13
tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs

@ -1,7 +1,6 @@

namespace ImageProcessor.Tests
{
using System;
using System.Diagnostics;
using System.IO;
@ -66,10 +65,9 @@ namespace ImageProcessor.Tests
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Filter/{ Path.GetFileName(filename) }"))
{
lastRowProcessed = 0;
processor.OnProgressed += ProgressUpdate;
processor.OnProgress += ProgressUpdate;
image.Process(processor).Save(output);
processor.OnProgressed -= ProgressUpdate;
processor.OnProgress -= ProgressUpdate;
}
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
@ -77,14 +75,9 @@ namespace ImageProcessor.Tests
}
}
private static int allowedVariability = Environment.ProcessorCount * 4;
private int lastRowProcessed;
private void ProgressUpdate(object sender, ProgressedEventArgs e)
private void ProgressUpdate(object sender, ProgressEventArgs e)
{
Assert.InRange(e.numRowsProcessed, 1, e.totalRows);
Assert.InRange(e.numRowsProcessed, lastRowProcessed - allowedVariability, lastRowProcessed + allowedVariability);
lastRowProcessed = e.numRowsProcessed;
}
}
}

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

@ -57,7 +57,7 @@
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)
image.Resize(image.Width / 2, image.Height / 2, sampler, ProgressUpdate)
.Save(output);
}
@ -85,7 +85,7 @@
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())
image.Resize(image.Width / 3, 0, new TriangleResampler(), ProgressUpdate)
.Save(output);
}
@ -113,7 +113,7 @@
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())
image.Resize(0, image.Height / 3, new TriangleResampler(), ProgressUpdate)
.Save(output);
}
@ -140,7 +140,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
{
image.RotateFlip(rotateType, flipType)
image.RotateFlip(rotateType, flipType, ProgressUpdate)
.Save(output);
}
@ -167,7 +167,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{
image.Rotate(45, sampler)
image.Rotate(45, sampler, ProgressUpdate)
//.BackgroundColor(Color.Aqua)
.Save(output);
}
@ -193,7 +193,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
{
image.EntropyCrop().Save(output);
image.EntropyCrop(.5f, ProgressUpdate).Save(output);
}
}
}
@ -215,7 +215,7 @@
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).Save(output);
image.Crop(image.Width / 2, image.Height / 2, ProgressUpdate).Save(output);
}
}
}
@ -235,5 +235,10 @@
Assert.Equal(result, expected);
}
private void ProgressUpdate(object sender, ProgressEventArgs e)
{
Assert.InRange(e.numRowsProcessed, 1, e.totalRows);
}
}
}

Loading…
Cancel
Save