Skip to content

Commit 4a4c8dd

Browse files
Merge pull request #1011 from SixLabors/feature/processors
Make processors public, refactor cloning.
2 parents b004888 + 464598c commit 4a4c8dd

30 files changed

+343
-350
lines changed

src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ protected override void OnFrameApply(ImageFrame<TPixelBg> source)
9191

9292
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
9393

94-
// not a valid operation because rectangle does not overlap with this image.
94+
// Not a valid operation because rectangle does not overlap with this image.
9595
if (workingRect.Width <= 0 || workingRect.Height <= 0)
9696
{
9797
throw new ImageProcessingException(
@@ -102,14 +102,14 @@ protected override void OnFrameApply(ImageFrame<TPixelBg> source)
102102
workingRect,
103103
configuration,
104104
rows =>
105+
{
106+
for (int y = rows.Min; y < rows.Max; y++)
105107
{
106-
for (int y = rows.Min; y < rows.Max; y++)
107-
{
108-
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
109-
Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
110-
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
111-
}
112-
});
108+
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
109+
Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
110+
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
111+
}
112+
});
113113
}
114114
}
115115
}

src/ImageSharp/Advanced/AotCompilerTools.cs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System.Numerics;
5+
using System.Runtime.CompilerServices;
56
using SixLabors.ImageSharp.Formats;
67
using SixLabors.ImageSharp.Formats.Jpeg.Components;
78
using SixLabors.ImageSharp.PixelFormats;
8-
using SixLabors.ImageSharp.Processing;
99
using SixLabors.ImageSharp.Processing.Processors.Dithering;
1010
using SixLabors.ImageSharp.Processing.Processors.Quantization;
11-
using SixLabors.ImageSharp.Processing.Processors.Transforms;
1211

1312
namespace SixLabors.ImageSharp.Advanced
1413
{
@@ -81,9 +80,8 @@ private static void Seed<TPixel>()
8180
AotCompileWuQuantizer<TPixel>();
8281
AotCompileDithering<TPixel>();
8382
AotCompilePixelOperations<TPixel>();
84-
AotCompileResizeOperations<TPixel>();
8583

86-
System.Runtime.CompilerServices.Unsafe.SizeOf<TPixel>();
84+
Unsafe.SizeOf<TPixel>();
8785

8886
AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder());
8987
AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder());
@@ -107,8 +105,10 @@ private static void Seed<TPixel>()
107105
private static void AotCompileOctreeQuantizer<TPixel>()
108106
where TPixel : struct, IPixel<TPixel>
109107
{
110-
var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false));
111-
test.AotGetPalette();
108+
using (var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)))
109+
{
110+
test.AotGetPalette();
111+
}
112112
}
113113

114114
/// <summary>
@@ -118,9 +118,11 @@ private static void AotCompileOctreeQuantizer<TPixel>()
118118
private static void AotCompileWuQuantizer<TPixel>()
119119
where TPixel : struct, IPixel<TPixel>
120120
{
121-
var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false));
122-
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
123-
test.AotGetPalette();
121+
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)))
122+
{
123+
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
124+
test.AotGetPalette();
125+
}
124126
}
125127

126128
/// <summary>
@@ -132,7 +134,10 @@ private static void AotCompileDithering<TPixel>()
132134
{
133135
var test = new FloydSteinbergDiffuser();
134136
TPixel pixel = default;
135-
test.Dither(new ImageFrame<TPixel>(Configuration.Default, 1, 1), pixel, pixel, 0, 0, 0, 0, 0, 0);
137+
using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
138+
{
139+
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0, 0);
140+
}
136141
}
137142

138143
/// <summary>
@@ -171,16 +176,5 @@ private static void AotCompilePixelOperations<TPixel>()
171176
var pixelOp = new PixelOperations<TPixel>();
172177
pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear);
173178
}
174-
175-
/// <summary>
176-
/// This method pre-seeds the ResizeProcessor for the AoT compiler on iOS.
177-
/// </summary>
178-
/// <typeparam name="TPixel">The pixel format.</typeparam>
179-
private static void AotCompileResizeOperations<TPixel>()
180-
where TPixel : struct, IPixel<TPixel>
181-
{
182-
var genericResizeProcessor = (ResizeProcessor<TPixel>)new ResizeProcessor(new ResizeOptions(), default).CreatePixelSpecificProcessor(new Image<TPixel>(0, 0), default);
183-
genericResizeProcessor.AotCreateDestination();
184-
}
185179
}
186180
}

src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public DefaultImageProcessorContext(Image<TPixel> source, bool mutate)
2929
{
3030
this.mutate = mutate;
3131
this.source = source;
32+
33+
// Mutate acts upon the source image only.
3234
if (this.mutate)
3335
{
3436
this.destination = source;
@@ -43,7 +45,8 @@ public Image<TPixel> GetResultImage()
4345
{
4446
if (!this.mutate && this.destination is null)
4547
{
46-
// Ensure we have cloned it if we are not mutating as we might have failed to register any processors
48+
// Ensure we have cloned the source if we are not mutating as we might have failed
49+
// to register any processors.
4750
this.destination = this.source.Clone();
4851
}
4952

@@ -64,26 +67,25 @@ public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectang
6467
{
6568
if (!this.mutate && this.destination is null)
6669
{
67-
// This will only work if the first processor applied is the cloning one thus
68-
// realistically for this optimization to work the resize must the first processor
69-
// applied any only up processors will take the double data path.
70-
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.source, rectangle))
70+
// When cloning an image we can optimize the processing pipeline by avoiding an unnecessary
71+
// interim clone if the first processor in the pipeline is a cloning processor.
72+
if (processor is ICloningImageProcessor cloningImageProcessor)
7173
{
72-
// TODO: if 'specificProcessor' is not an ICloningImageProcessor<TPixel> we are unnecessarily disposing and recreating it.
73-
// This should be solved in a future refactor.
74-
if (specificProcessor is ICloningImageProcessor<TPixel> cloningImageProcessor)
74+
using (ICloningImageProcessor<TPixel> pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.source, rectangle))
7575
{
76-
this.destination = cloningImageProcessor.CloneAndApply();
76+
this.destination = pixelProcessor.CloneAndExecute();
7777
return this;
7878
}
7979
}
8080

81+
// Not a cloning processor? We need to create a clone to operate on.
8182
this.destination = this.source.Clone();
8283
}
8384

85+
// Standard processing pipeline.
8486
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle))
8587
{
86-
specificProcessor.Apply();
88+
specificProcessor.Execute();
8789
}
8890

8991
return this;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using SixLabors.ImageSharp.PixelFormats;
5+
using SixLabors.Primitives;
6+
7+
namespace SixLabors.ImageSharp.Processing.Processors
8+
{
9+
/// <summary>
10+
/// The base class for all cloning image processors.
11+
/// </summary>
12+
public abstract class CloningImageProcessor : ICloningImageProcessor
13+
{
14+
/// <inheritdoc/>
15+
public abstract ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
16+
where TPixel : struct, IPixel<TPixel>;
17+
18+
/// <inheritdoc/>
19+
IImageProcessor<TPixel> IImageProcessor.CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
20+
=> this.CreatePixelSpecificCloningProcessor(source, sourceRectangle);
21+
}
22+
}

src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
57
using SixLabors.ImageSharp.Advanced;
68
using SixLabors.ImageSharp.PixelFormats;
79
using SixLabors.Primitives;
810

911
namespace SixLabors.ImageSharp.Processing.Processors
1012
{
1113
/// <summary>
12-
/// Allows the application of processing algorithms to a clone of the original image.
14+
/// The base class for all pixel specific cloning image processors.
15+
/// Allows the application of processing algorithms to the image.
16+
/// The image is cloned before operating upon and the buffers swapped upon completion.
1317
/// </summary>
1418
/// <typeparam name="TPixel">The pixel format.</typeparam>
15-
internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
19+
public abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
1620
where TPixel : struct, IPixel<TPixel>
1721
{
1822
/// <summary>
@@ -38,21 +42,17 @@ protected CloningImageProcessor(Image<TPixel> source, Rectangle sourceRectangle)
3842
protected Rectangle SourceRectangle { get; }
3943

4044
/// <summary>
41-
/// Gets the <see cref="ImageSharp.Configuration"/> instance to use when performing operations.
45+
/// Gets the <see cref="Configuration"/> instance to use when performing operations.
4246
/// </summary>
4347
protected Configuration Configuration { get; }
4448

4549
/// <inheritdoc/>
46-
public Image<TPixel> CloneAndApply()
50+
Image<TPixel> ICloningImageProcessor<TPixel>.CloneAndExecute()
4751
{
4852
try
4953
{
50-
Image<TPixel> clone = this.CreateDestination();
51-
52-
if (clone.Frames.Count != this.Source.Frames.Count)
53-
{
54-
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
55-
}
54+
Image<TPixel> clone = this.CreateTarget();
55+
this.CheckFrameCount(this.Source, clone);
5656

5757
Configuration configuration = this.Source.GetConfiguration();
5858
this.BeforeImageApply(clone);
@@ -84,17 +84,24 @@ public Image<TPixel> CloneAndApply()
8484
}
8585

8686
/// <inheritdoc/>
87-
public void Apply()
87+
void IImageProcessor<TPixel>.Execute()
8888
{
89-
using (Image<TPixel> cloned = this.CloneAndApply())
89+
// Create an interim clone of the source image to operate on.
90+
// Doing this allows for the application of transforms that will alter
91+
// the dimensions of the image.
92+
Image<TPixel> clone = default;
93+
try
9094
{
91-
// we now need to move the pixel data/size data from one image base to another
92-
if (cloned.Frames.Count != this.Source.Frames.Count)
93-
{
94-
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
95-
}
95+
clone = ((ICloningImageProcessor<TPixel>)this).CloneAndExecute();
9696

97-
this.Source.SwapOrCopyPixelsBuffersFrom(cloned);
97+
// We now need to move the pixel data/size data from the clone to the source.
98+
this.CheckFrameCount(this.Source, clone);
99+
this.Source.SwapOrCopyPixelsBuffersFrom(clone);
100+
}
101+
finally
102+
{
103+
// Dispose of the clone now that we have swapped the pixel/size data.
104+
clone?.Dispose();
98105
}
99106
}
100107

@@ -106,10 +113,10 @@ public void Dispose()
106113
}
107114

108115
/// <summary>
109-
/// Generates a deep clone of the source image that operations should be applied to.
116+
/// Gets the size of the target image.
110117
/// </summary>
111-
/// <returns>The cloned image.</returns>
112-
protected virtual Image<TPixel> CreateDestination() => this.Source.Clone();
118+
/// <returns>The <see cref="Size"/>.</returns>
119+
protected abstract Size GetTargetSize();
113120

114121
/// <summary>
115122
/// This method is called before the process is applied to prepare the processor.
@@ -160,5 +167,30 @@ protected virtual void AfterImageApply(Image<TPixel> destination)
160167
protected virtual void Dispose(bool disposing)
161168
{
162169
}
170+
171+
private Image<TPixel> CreateTarget()
172+
{
173+
Image<TPixel> source = this.Source;
174+
Size targetSize = this.GetTargetSize();
175+
176+
// We will always be creating the clone even for mutate because we may need to resize the canvas
177+
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
178+
x => new ImageFrame<TPixel>(
179+
source.GetConfiguration(),
180+
targetSize.Width,
181+
targetSize.Height,
182+
x.Metadata.DeepClone()));
183+
184+
// Use the overload to prevent an extra frame being added
185+
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), frames);
186+
}
187+
188+
private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b)
189+
{
190+
if (a.Frames.Count != b.Frames.Count)
191+
{
192+
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
193+
}
194+
}
163195
}
164196
}

src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ protected override void BeforeImageApply()
5454
{
5555
if (this.Grayscale)
5656
{
57-
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
57+
new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
5858
}
5959

6060
base.BeforeImageApply();

src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ protected override void BeforeImageApply()
4545
{
4646
if (this.Grayscale)
4747
{
48-
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
48+
new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
4949
}
5050

5151
base.BeforeImageApply();

src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected override void BeforeImageApply()
4141
{
4242
if (this.Grayscale)
4343
{
44-
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
44+
new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
4545
}
4646

4747
base.BeforeImageApply();

src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public LomographProcessor(LomographProcessor definition, Image<TPixel> source, R
2929
/// <inheritdoc/>
3030
protected override void AfterImageApply()
3131
{
32-
new VignetteProcessor(VeryDarkGreen).Apply(this.Source, this.SourceRectangle);
32+
new VignetteProcessor(VeryDarkGreen).Execute(this.Source, this.SourceRectangle);
3333
base.AfterImageApply();
3434
}
3535
}

src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public PolaroidProcessor(PolaroidProcessor definition, Image<TPixel> source, Rec
3131
/// <inheritdoc/>
3232
protected override void AfterImageApply()
3333
{
34-
new VignetteProcessor(VeryDarkOrange).Apply(this.Source, this.SourceRectangle);
35-
new GlowProcessor(LightOrange, this.Source.Width / 4F).Apply(this.Source, this.SourceRectangle);
34+
new VignetteProcessor(VeryDarkOrange).Execute(this.Source, this.SourceRectangle);
35+
new GlowProcessor(LightOrange, this.Source.Width / 4F).Execute(this.Source, this.SourceRectangle);
3636
base.AfterImageApply();
3737
}
3838
}

0 commit comments

Comments
 (0)