Skip to content

Commit

Permalink
Merge pull request #1011 from SixLabors/feature/processors
Browse files Browse the repository at this point in the history
Make processors public, refactor cloning.
  • Loading branch information
JimBobSquarePants authored Sep 25, 2019
2 parents 8606c08 + 40b69d7 commit 8e6a4f0
Show file tree
Hide file tree
Showing 30 changed files with 343 additions and 350 deletions.
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);
}
});
}
}
}
36 changes: 15 additions & 21 deletions src/ImageSharp/Advanced/AotCompilerTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0.

using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Processing.Processors.Transforms;

namespace SixLabors.ImageSharp.Advanced
{
Expand Down Expand Up @@ -81,9 +80,8 @@ private static void Seed<TPixel>()
AotCompileWuQuantizer<TPixel>();
AotCompileDithering<TPixel>();
AotCompilePixelOperations<TPixel>();
AotCompileResizeOperations<TPixel>();

System.Runtime.CompilerServices.Unsafe.SizeOf<TPixel>();
Unsafe.SizeOf<TPixel>();

AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder());
AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder());
Expand All @@ -107,8 +105,10 @@ private static void Seed<TPixel>()
private static void AotCompileOctreeQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false));
test.AotGetPalette();
using (var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)))
{
test.AotGetPalette();
}
}

/// <summary>
Expand All @@ -118,9 +118,11 @@ private static void AotCompileOctreeQuantizer<TPixel>()
private static void AotCompileWuQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false));
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
test.AotGetPalette();
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)))
{
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
test.AotGetPalette();
}
}

/// <summary>
Expand All @@ -132,7 +134,10 @@ private static void AotCompileDithering<TPixel>()
{
var test = new FloydSteinbergDiffuser();
TPixel pixel = default;
test.Dither(new ImageFrame<TPixel>(Configuration.Default, 1, 1), pixel, pixel, 0, 0, 0, 0, 0, 0);
using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
{
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0, 0);
}
}

/// <summary>
Expand Down Expand Up @@ -171,16 +176,5 @@ private static void AotCompilePixelOperations<TPixel>()
var pixelOp = new PixelOperations<TPixel>();
pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear);
}

/// <summary>
/// This method pre-seeds the ResizeProcessor for the AoT compiler on iOS.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileResizeOperations<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var genericResizeProcessor = (ResizeProcessor<TPixel>)new ResizeProcessor(new ResizeOptions(), default).CreatePixelSpecificProcessor(new Image<TPixel>(0, 0), default);
genericResizeProcessor.AotCreateDestination();
}
}
}
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 ICloningImageProcessor 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.CreatePixelSpecificCloningProcessor(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
22 changes: 22 additions & 0 deletions src/ImageSharp/Processing/Processors/CloningImageProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 : ICloningImageProcessor
{
/// <inheritdoc/>
public abstract ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>;

/// <inheritdoc/>
IImageProcessor<TPixel> IImageProcessor.CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> this.CreatePixelSpecificCloningProcessor(source, sourceRectangle);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;

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>
{
/// <summary>
Expand All @@ -38,21 +42,17 @@ protected CloningImageProcessor(Image<TPixel> source, Rectangle sourceRectangle)
protected Rectangle SourceRectangle { get; }

/// <summary>
/// Gets the <see cref="ImageSharp.Configuration"/> instance to use when performing operations.
/// Gets the <see cref="Configuration"/> instance to use when performing operations.
/// </summary>
protected Configuration Configuration { get; }

/// <inheritdoc/>
public Image<TPixel> CloneAndApply()
Image<TPixel> ICloningImageProcessor<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.");
}
Image<TPixel> clone = this.CreateTarget();
this.CheckFrameCount(this.Source, clone);

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

/// <inheritdoc/>
public void Apply()
void IImageProcessor<TPixel>.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 = ((ICloningImageProcessor<TPixel>)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 All @@ -106,10 +113,10 @@ public void Dispose()
}

/// <summary>
/// Generates a deep clone of the source image that operations should be applied to.
/// Gets the size of the target image.
/// </summary>
/// <returns>The cloned image.</returns>
protected virtual Image<TPixel> CreateDestination() => this.Source.Clone();
/// <returns>The <see cref="Size"/>.</returns>
protected abstract Size GetTargetSize();

/// <summary>
/// This method is called before the process is applied to prepare the processor.
Expand Down Expand Up @@ -160,5 +167,30 @@ protected virtual void AfterImageApply(Image<TPixel> destination)
protected virtual void Dispose(bool disposing)
{
}

private Image<TPixel> CreateTarget()
{
Image<TPixel> source = this.Source;
Size targetSize = this.GetTargetSize();

// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
source.GetConfiguration(),
targetSize.Width,
targetSize.Height,
x.Metadata.DeepClone()));

// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), frames);
}

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 @@ -54,7 +54,7 @@ protected override void BeforeImageApply()
{
if (this.Grayscale)
{
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
}

base.BeforeImageApply();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected override void BeforeImageApply()
{
if (this.Grayscale)
{
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
}

base.BeforeImageApply();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected override void BeforeImageApply()
{
if (this.Grayscale)
{
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
}

base.BeforeImageApply();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public LomographProcessor(LomographProcessor definition, Image<TPixel> source, R
/// <inheritdoc/>
protected override void AfterImageApply()
{
new VignetteProcessor(VeryDarkGreen).Apply(this.Source, this.SourceRectangle);
new VignetteProcessor(VeryDarkGreen).Execute(this.Source, this.SourceRectangle);
base.AfterImageApply();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public PolaroidProcessor(PolaroidProcessor definition, Image<TPixel> source, Rec
/// <inheritdoc/>
protected override void AfterImageApply()
{
new VignetteProcessor(VeryDarkOrange).Apply(this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).Apply(this.Source, this.SourceRectangle);
new VignetteProcessor(VeryDarkOrange).Execute(this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).Execute(this.Source, this.SourceRectangle);
base.AfterImageApply();
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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>
/// Defines an algorithm to alter the pixels of a cloned image.
/// </summary>
public interface ICloningImageProcessor : 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>
ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>;
}
}
Loading

0 comments on commit 8e6a4f0

Please sign in to comment.