Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ protected override void OnFrameApply(ImageFrame<TPixelBg> source)

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

// not a valid operation because rectangle does not overlap with this image.
// Not a valid operation because rectangle does not overlap with this image.
if (workingRect.Width <= 0 || workingRect.Height <= 0)
{
throw new ImageProcessingException(
Expand All @@ -102,14 +102,14 @@ protected override void OnFrameApply(ImageFrame<TPixelBg> source)
workingRect,
configuration,
rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
}
});
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
}
});
}
}
}
22 changes: 12 additions & 10 deletions src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public DefaultImageProcessorContext(Image<TPixel> source, bool mutate)
{
this.mutate = mutate;
this.source = source;

// Mutate acts upon the source image only.
if (this.mutate)
{
this.destination = source;
Expand All @@ -43,7 +45,8 @@ public Image<TPixel> GetResultImage()
{
if (!this.mutate && this.destination is null)
{
// Ensure we have cloned it if we are not mutating as we might have failed to register any processors
// Ensure we have cloned the source if we are not mutating as we might have failed
// to register any processors.
this.destination = this.source.Clone();
}

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

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

// Standard processing pipeline.
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle))
{
specificProcessor.Apply();
specificProcessor.Execute();
}

return this;
Expand Down
31 changes: 31 additions & 0 deletions src/ImageSharp/Processing/Processors/CloningImageProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// The base class for all cloning image processors.
/// </summary>
public abstract class CloningImageProcessor : IImageProcessor
{
/// <summary>
/// Creates a pixel specific <see cref="ICloningImageProcessor{TPixel}"/> that is capable of executing
/// the processing algorithm on an <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <returns>The <see cref="ICloningImageProcessor{TPixel}"/></returns>
public abstract ICloningImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>;

/// <inheritdoc/>
IImageProcessor<TPixel> IImageProcessor.CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> this.CreatePixelSpecificProcessor(source, sourceRectangle);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Allows the application of processing algorithms to a clone of the original image.
/// The base class for all pixel specific cloning image processors.
/// Allows the application of processing algorithms to the image.
/// The image is cloned before operating upon and the buffers swapped upon completion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
public abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
Expand Down Expand Up @@ -45,16 +47,12 @@ protected CloningImageProcessor(Image<TPixel> source, Rectangle sourceRectangle)
protected Configuration Configuration { get; }

/// <inheritdoc/>
public Image<TPixel> CloneAndApply()
public Image<TPixel> CloneAndExecute()
{
try
{
Image<TPixel> clone = this.CreateDestination();

if (clone.Frames.Count != this.Source.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
this.CheckFrameCount(this.Source, clone);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the checks are necessary. The centralized cloning logic should guarantee that the frame counts are matching.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually thought that but was too lazy to check and just merged the code into a single method

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an iterative way to delete bloated code 😉


Configuration configuration = this.Source.GetConfiguration();
this.BeforeImageApply(clone);
Expand Down Expand Up @@ -86,17 +84,24 @@ public Image<TPixel> CloneAndApply()
}

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

this.Source.SwapOrCopyPixelsBuffersFrom(cloned);
// We now need to move the pixel data/size data from the clone to the source.
this.CheckFrameCount(this.Source, clone);
this.Source.SwapOrCopyPixelsBuffersFrom(clone);
}
finally
{
// Dispose of the clone now that we have swapped the pixel/size data.
clone?.Dispose();
}
}

Expand Down Expand Up @@ -165,5 +170,13 @@ protected virtual void Dispose(bool disposing)
this.isDisposed = true;
}
}

private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b)
{
if (a.Frames.Count != b.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,20 @@
// Licensed under the Apache License, Version 2.0.

using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Encapsulates methods to alter the pixels of a new image, cloned from the original image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel>
public interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/>.
/// Clones the specified <see cref="Image{TPixel}"/> and executes the process against the clone.
/// </summary>
/// <exception cref="System.ArgumentNullException">
/// The target <see cref="Image{TPixel}"/> is null.
/// </exception>
/// <exception cref="System.ArgumentException">
/// The target <see cref="Rectangle"/> doesn't fit the dimension of the image.
/// </exception>
/// <returns>Returns the cloned image after there processor has been applied to it.</returns>
Image<TPixel> CloneAndApply();
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
Image<TPixel> CloneAndExecute();
}
}
2 changes: 1 addition & 1 deletion src/ImageSharp/Processing/Processors/IImageProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
public interface IImageProcessor
{
/// <summary>
/// Creates a pixel specific <see cref="IImageProcessor{TPixel}"/> that is capable for executing
/// Creates a pixel specific <see cref="IImageProcessor{TPixel}"/> that is capable of executing
/// the processing algorithm on an <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
Expand Down
11 changes: 2 additions & 9 deletions src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Processing.Processors
{
Expand All @@ -15,14 +14,8 @@ public interface IImageProcessor<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="Image{TPixel}"/>.
/// Executes the process against the specified <see cref="Image{TPixel}"/>.
/// </summary>
/// <exception cref="ArgumentNullException">
/// The target <see cref="Image{TPixel}"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// The target <see cref="Rectangle"/> doesn't fit the dimension of the image.
/// </exception>
void Apply();
void Execute();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void Visit<TPixel>(Image<TPixel> image)
{
using (IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle))
{
processorImpl.Apply();
processorImpl.Execute();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Allows the application of processors to images.
/// The base class for all pixel specific image processors.
/// Allows the application of processing algorithms to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class ImageProcessor<TPixel> : IImageProcessor<TPixel>
public abstract class ImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
Expand Down Expand Up @@ -45,7 +46,7 @@ protected ImageProcessor(Image<TPixel> source, Rectangle sourceRectangle)
protected Configuration Configuration { get; }

/// <inheritdoc/>
public void Apply()
public void Execute()
{
try
{
Expand All @@ -71,7 +72,7 @@ public void Apply()
}

/// <summary>
/// Applies the processor to just a single ImageBase.
/// Applies the processor to a single image frame.
/// </summary>
/// <param name="source">the source image.</param>
public void Apply(ImageFrame<TPixel> source)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
// Licensed under the Apache License, Version 2.0.

using System.Numerics;

using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Defines an affine transformation applicable on an <see cref="Image"/>.
/// </summary>
public class AffineTransformProcessor : IImageProcessor
public class AffineTransformProcessor : CloningImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor"/> class.
Expand Down Expand Up @@ -42,11 +40,8 @@ public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targe
/// </summary>
public Size TargetDimensions { get; }

/// <inheritdoc />
public virtual IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
{
return new AffineTransformProcessor<TPixel>(this, source, sourceRectangle);
}
/// <inheritdoc/>
public override ICloningImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> new AffineTransformProcessor<TPixel>(this, source, sourceRectangle);
}
}
11 changes: 4 additions & 7 deletions src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Defines a crop operation on an image.
/// </summary>
public sealed class CropProcessor : IImageProcessor
public sealed class CropProcessor : CloningImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="CropProcessor"/> class.
Expand All @@ -23,6 +22,7 @@ public CropProcessor(Rectangle cropRectangle, Size sourceSize)
new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle),
nameof(cropRectangle),
"Crop rectangle should be smaller than the source bounds.");

this.CropRectangle = cropRectangle;
}

Expand All @@ -32,10 +32,7 @@ public CropProcessor(Rectangle cropRectangle, Size sourceSize)
public Rectangle CropRectangle { get; }

/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
{
return new CropProcessor<TPixel>(this, source, sourceRectangle);
}
public override ICloningImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> new CropProcessor<TPixel>(this, source, sourceRectangle);
}
}
Loading