diff --git a/src/ImageProcessor/Filters/Brightness.cs b/src/ImageProcessor/Filters/Brightness.cs
index 6a5ae3985..73c8b377f 100644
--- a/src/ImageProcessor/Filters/Brightness.cs
+++ b/src/ImageProcessor/Filters/Brightness.cs
@@ -57,6 +57,7 @@ namespace ImageProcessor.Filters
target[x, y] = Color.Compress(new Color(vector3, color.A));
}
+ this.OnRowProcessed();
}
});
}
diff --git a/src/ImageProcessor/IImageProcessor.cs b/src/ImageProcessor/IImageProcessor.cs
index b9b90cbe0..8e31c4e1d 100644
--- a/src/ImageProcessor/IImageProcessor.cs
+++ b/src/ImageProcessor/IImageProcessor.cs
@@ -5,11 +5,28 @@
namespace ImageProcessor
{
+ public struct ProgressedEventArgs
+ {
+ public int numRowsProcessed;
+ public int totalRows;
+ }
+
+ public delegate void ProgressedEventHandler(object sender, ProgressedEventArgs e);
+
///
/// Encapsulates methods to alter the pixels of an image.
///
public interface IImageProcessor
{
+ ///
+ /// Event fires when each row of the source image has been processed.
+ ///
+ ///
+ /// 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.
+ ///
+ event ProgressedEventHandler OnProgressed;
+
///
/// Applies the process to the specified portion of the specified .
///
diff --git a/src/ImageProcessor/ParallelImageProcessor.cs b/src/ImageProcessor/ParallelImageProcessor.cs
index a5ea2ad76..4f34dd61d 100644
--- a/src/ImageProcessor/ParallelImageProcessor.cs
+++ b/src/ImageProcessor/ParallelImageProcessor.cs
@@ -6,6 +6,7 @@
namespace ImageProcessor
{
using System;
+ using System.Threading;
using System.Threading.Tasks;
///
@@ -13,11 +14,38 @@ namespace ImageProcessor
///
public abstract class ParallelImageProcessor : IImageProcessor
{
+ ///
+ public event ProgressedEventHandler OnProgressed;
+
///
/// Gets or sets the count of workers to run the process in parallel.
///
public virtual int Parallelism { get; set; } = Environment.ProcessorCount * 2;
+ ///
+ /// The number of rows processed by a derived class.
+ ///
+ private int numRowsProcessed;
+
+ ///
+ /// The total number of rows that will be processed by a derived class.
+ ///
+ private int totalRows;
+
+ ///
+ /// Must be called by derived classes after processing a single row.
+ ///
+ protected void OnRowProcessed()
+ {
+ if(this.OnProgressed != null)
+ {
+ int currThreadNumRows = Interlocked.Add(ref this.numRowsProcessed, 1);
+
+ // 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 });
+ }
+ }
+
///
public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
{
@@ -26,6 +54,9 @@ namespace ImageProcessor
Image temp = frame != null ? new Image(frame) : new Image((Image)source);
this.OnApply(temp, target, target.Bounds, sourceRectangle);
+ this.numRowsProcessed = 0;
+ this.totalRows = sourceRectangle.Height;
+
if (this.Parallelism > 1)
{
int partitionCount = this.Parallelism;
@@ -72,6 +103,8 @@ namespace ImageProcessor
this.OnApply(temp, target, target.Bounds, sourceRectangle);
targetRectangle = target.Bounds;
+ this.numRowsProcessed = 0;
+ this.totalRows = targetRectangle.Bottom;
if (this.Parallelism > 1)
{
diff --git a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
index c3b26590c..3a24da33e 100644
--- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
+++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
@@ -1,6 +1,7 @@
namespace ImageProcessor.Tests
{
+ using System;
using System.Diagnostics;
using System.IO;
@@ -65,12 +66,25 @@ 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;
image.Process(processor).Save(output);
+ processor.OnProgressed -= ProgressUpdate;
}
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
}
}
}
+
+ private static int allowedVariability = Environment.ProcessorCount * 4;
+ private int lastRowProcessed;
+
+ private void ProgressUpdate(object sender, ProgressedEventArgs e)
+ {
+ Assert.InRange(e.numRowsProcessed, 1, e.totalRows);
+ Assert.InRange(e.numRowsProcessed, lastRowProcessed - allowedVariability, lastRowProcessed + allowedVariability);
+ lastRowProcessed = e.numRowsProcessed;
+ }
}
}