This repository has been archived by the owner on Sep 6, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 498
/
ParallelImageProcessor.cs
209 lines (179 loc) · 8.73 KB
/
ParallelImageProcessor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// <copyright file="ParallelImageProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Allows the application of processors using parallel processing.
/// </summary>
public abstract class ParallelImageProcessor : IImageProcessor
{
/// <inheritdoc/>
public event ProgressEventHandler OnProgress;
/// <summary>
/// Gets or sets the count of workers to run the process in parallel.
/// </summary>
public virtual int Parallelism { get; set; } = Environment.ProcessorCount * 2;
/// <summary>
/// The number of rows processed by a derived class.
/// </summary>
private int numRowsProcessed;
/// <summary>
/// The total number of rows that will be processed by a derived class.
/// </summary>
private int totalRows;
/// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
{
try
{
this.OnApply(source, target, target.Bounds, sourceRectangle);
this.numRowsProcessed = 0;
this.totalRows = sourceRectangle.Height;
if (this.Parallelism > 1)
{
int partitionCount = this.Parallelism;
Task[] tasks = new Task[partitionCount];
for (int p = 0; p < partitionCount; p++)
{
int current = p;
tasks[p] = Task.Run(
() =>
{
int batchSize = sourceRectangle.Height / partitionCount;
int yStart = sourceRectangle.Y + (current * batchSize);
int yEnd = current == partitionCount - 1
? sourceRectangle.Bottom
: yStart + batchSize;
this.Apply(target, source, target.Bounds, sourceRectangle, yStart, yEnd);
});
}
Task.WaitAll(tasks);
}
else
{
this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom);
}
this.AfterApply(source, target, target.Bounds, sourceRectangle);
}
catch (Exception ex)
{
throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex);
}
}
/// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
{
try
{
float[] pixels = new float[width * height * 4];
target.SetPixels(width, height, pixels);
if (sourceRectangle == Rectangle.Empty)
{
sourceRectangle = source.Bounds;
}
this.OnApply(source, target, target.Bounds, sourceRectangle);
targetRectangle = target.Bounds;
this.numRowsProcessed = 0;
this.totalRows = targetRectangle.Bottom;
if (this.Parallelism > 1)
{
int partitionCount = this.Parallelism;
Task[] tasks = new Task[partitionCount];
for (int p = 0; p < partitionCount; p++)
{
int current = p;
tasks[p] = Task.Run(() =>
{
int batchSize = targetRectangle.Bottom / partitionCount;
int yStart = current * batchSize;
int yEnd = current == partitionCount - 1 ? targetRectangle.Bottom : yStart + batchSize;
this.Apply(target, source, targetRectangle, sourceRectangle, yStart, yEnd);
});
}
Task.WaitAll(tasks);
}
else
{
this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom);
}
this.AfterApply(source, target, target.Bounds, sourceRectangle);
}
catch (Exception ex)
{
throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex);
}
}
/// <summary>
/// This method is called before the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="target">Target image to apply the process to.</param>
/// <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(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase"/> at the specified location
/// and with the specified size.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="source">The source image. Cannot be null.</param>
/// <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>
/// <param name="startY">The index of the row within the source image to start processing.</param>
/// <param name="endY">The index of the row within the source image to end processing.</param>
/// <remarks>
/// The method keeps the source image unchanged and returns the
/// the result of image process as new image.
/// </remarks>
protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY);
/// <summary>
/// This method is called after the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="target">Target image to apply the process to.</param>
/// <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 AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
}
/// <summary>
/// Must be called by derived classes after processing a single row.
/// </summary>
protected void OnRowProcessed()
{
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.OnProgress(this, new ProgressEventArgs { RowsProcessed = currThreadNumRows, TotalRows = this.totalRows });
}
}
}
}