Browse Source

Cloneing Image Processor

pull/275/head
Scott Williams 9 years ago
parent
commit
c421e6de0a
  1. 16
      src/ImageSharp/DefaultInternalImageProcessorApplicator.cs
  2. 37
      src/ImageSharp/Image/ICloningImageProcessor.cs
  3. 21
      src/ImageSharp/Image/ImageBase{TPixel}.cs
  4. 23
      src/ImageSharp/Image/ImageFrame{TPixel}.cs
  5. 9
      src/ImageSharp/MetaData/ImageFrameMetaData.cs
  6. 9
      src/ImageSharp/MetaData/ImageMetaData.cs
  7. 166
      src/ImageSharp/Processing/Processors/CLoneingImageProcessor.cs
  8. 8
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  9. 180
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

16
src/ImageSharp/DefaultInternalImageProcessorApplicator.cs

@ -54,9 +54,19 @@ namespace ImageSharp
{
if (!this.mutate && this.destination == null)
{
// TODO check if the processor implements a special interface and if it does then allow it to take
// over and crereate the clone on behalf ImageOperations class.
this.destination = this.source.Clone();
// this will only work if the first processor applied is the cloning one thus
// realistically for this optermissation to work the resize must the first processor
// applied any only up processors will take the douple data path.
if (processor is ICloningImageProcessor<TPixel>)
{
var cloningProcessor = (ICloningImageProcessor<TPixel>)processor;
this.destination = cloningProcessor.CloneAndApply(this.source, rectangle);
return this;
}
else
{
this.destination = this.source.Clone();
}
}
processor.Apply(this.destination, rectangle);

37
src/ImageSharp/Image/ICloningImageProcessor.cs

@ -0,0 +1,37 @@
// <copyright file="IImageProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Processing
{
using System;
using System.Threading.Tasks;
using ImageSharp.PixelFormats;
using SixLabors.Primitives;
/// <summary>
/// Encapsulates methods to alter the pixels of an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase{TPixel}"/>.
/// </summary>
/// <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>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="source"/> is null.
/// </exception>
/// <exception cref="System.ArgumentException">
/// <paramref name="sourceRectangle"/> doesnt fit the dimension of the image.
/// </exception>
/// <returns>Returns the cloned image after thre processor has been applied to it.</returns>
Image<TPixel> CloneAndApply(Image<TPixel> source, Rectangle sourceRectangle);
}
}

21
src/ImageSharp/Image/ImageBase{TPixel}.cs

@ -244,6 +244,27 @@ namespace ImageSharp
this.PixelBuffer = newPixels;
}
/// <summary>
/// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer.
/// </summary>
/// <param name="pixelSource">The pixel source.</param>
internal void SwapPixelsData(ImageBase<TPixel> pixelSource)
{
Guard.NotNull(pixelSource, nameof(pixelSource));
int newWidth = pixelSource.Width;
int newHeight = pixelSource.Height;
TPixel[] newPixels = pixelSource.PixelBuffer;
pixelSource.PixelBuffer = this.PixelBuffer;
pixelSource.Width = this.Width;
pixelSource.Height = this.Height;
this.Width = newWidth;
this.Height = newHeight;
this.PixelBuffer = newPixels;
}
/// <summary>
/// Clones the image
/// </summary>

23
src/ImageSharp/Image/ImageFrame{TPixel}.cs

@ -28,6 +28,23 @@ namespace ImageSharp
public ImageFrame(Configuration configuration, int width, int height)
: base(configuration, width, height)
{
this.MetaData = new ImageFrameMetaData();
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}"/> class.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The metadata of the frame.</param>
public ImageFrame(Configuration configuration, int width, int height, ImageFrameMetaData metadata)
: base(configuration, width, height)
{
Guard.NotNull(metadata, nameof(metadata));
this.MetaData = metadata;
}
/// <summary>
@ -38,6 +55,7 @@ namespace ImageSharp
public ImageFrame(int width, int height)
: this(null, width, height)
{
this.MetaData = new ImageFrameMetaData();
}
/// <summary>
@ -47,6 +65,7 @@ namespace ImageSharp
internal ImageFrame(ImageBase<TPixel> image)
: base(image)
{
this.MetaData = new ImageFrameMetaData();
}
/// <summary>
@ -62,12 +81,12 @@ namespace ImageSharp
/// <summary>
/// Gets the meta data of the frame.
/// </summary>
public ImageFrameMetaData MetaData { get; private set; } = new ImageFrameMetaData();
public ImageFrameMetaData MetaData { get; private set; }
/// <inheritdoc/>
public override string ToString()
{
return $"ImageFrame: {this.Width}x{this.Height}";
return $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
}
/// <summary>

9
src/ImageSharp/MetaData/ImageFrameMetaData.cs

@ -39,5 +39,14 @@ namespace ImageSharp
/// <inheritdoc/>
public DisposalMethod DisposalMethod { get; set; }
/// <summary>
/// Clones this ImageFrameMetaData.
/// </summary>
/// <returns>The cloned instance.</returns>
public ImageFrameMetaData Clone()
{
return new ImageFrameMetaData(this);
}
}
}

9
src/ImageSharp/MetaData/ImageMetaData.cs

@ -132,6 +132,15 @@ namespace ImageSharp
/// </summary>
public ushort RepeatCount { get; set; }
/// <summary>
/// Clones this into a new instance
/// </summary>
/// <returns>The cloned metadata instance</returns>
public ImageMetaData Clone()
{
return new ImageMetaData(this);
}
/// <summary>
/// Synchronizes the profiles with the current meta data.
/// </summary>

166
src/ImageSharp/Processing/Processors/CLoneingImageProcessor.cs

@ -0,0 +1,166 @@
// <copyright file="CloneingImageProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Processing
{
using System;
using System.Threading.Tasks;
using ImageSharp.PixelFormats;
using SixLabors.Primitives;
/// <summary>
/// Allows the application of processors to images.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class CloneingImageProcessor<TPixel> : IImageProcessor<TPixel>, ICloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <inheritdoc/>
public virtual ParallelOptions ParallelOptions { get; set; }
/// <inheritdoc/>
public virtual bool Compand { get; set; } = false;
/// <inheritdoc/>
public Image<TPixel> CloneAndApply(Image<TPixel> source, Rectangle sourceRectangle)
{
if (this.ParallelOptions == null)
{
this.ParallelOptions = source.Configuration.ParallelOptions;
}
try
{
Image<TPixel> clone = this.CreateDestination(source, sourceRectangle);
if (clone.Frames.Count != source.Frames.Count)
{
throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
this.BeforeImageApply(source, clone, sourceRectangle);
this.BeforeApply(source, clone, sourceRectangle);
this.OnApply(source, clone, sourceRectangle);
this.AfterApply(source, clone, sourceRectangle);
for (int i = 0; i < source.Frames.Count; i++)
{
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> clonedFrame = clone.Frames[i];
this.BeforeApply(sourceFrame, clonedFrame, sourceRectangle);
this.OnApply(sourceFrame, clonedFrame, sourceRectangle);
this.AfterApply(sourceFrame, clonedFrame, sourceRectangle);
}
this.AfterImageApply(source, clone, sourceRectangle);
return clone;
}
#if DEBUG
catch (Exception)
{
throw;
#else
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);
#endif
}
}
/// <inheritdoc/>
public void Apply(Image<TPixel> source, Rectangle sourceRectangle)
{
using (Image<TPixel> cloned = this.CloneAndApply(source, sourceRectangle))
{
// we now need to move the pixel data/size data from one image base to another
if (cloned.Frames.Count != source.Frames.Count)
{
throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
source.SwapPixelsData(cloned);
for (int i = 0; i < source.Frames.Count; i++)
{
source.Frames[i].SwapPixelsData(cloned.Frames[i]);
}
}
}
/// <summary>
/// Generates the clone of the source image that operatinos should be applied to.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="sourceRectangle">The source rectangle.</param>
/// <returns>The cloned image.</returns>
protected virtual Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
return source.Clone();
}
/// <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="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void BeforeImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle)
{
}
/// <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="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void BeforeApply(ImageBase<TPixel> source, ImageBase<TPixel> destination, Rectangle sourceRectangle)
{
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase{TPixel}"/> at the specified location
/// and with the specified size.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected abstract void OnApply(ImageBase<TPixel> source, ImageBase<TPixel> destination, Rectangle sourceRectangle);
/// <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="destination">The cloned/destination image. Cannot be null.</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<TPixel> source, ImageBase<TPixel> destination, Rectangle sourceRectangle)
{
}
/// <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="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void AfterImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle)
{
}
}
}

8
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs

@ -16,7 +16,7 @@ namespace ImageSharp.Processing.Processors
/// Adapted from <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract partial class ResamplingWeightedProcessor<TPixel> : ImageProcessor<TPixel>
internal abstract partial class ResamplingWeightedProcessor<TPixel> : CloneingImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -140,7 +140,7 @@ namespace ImageSharp.Processing.Processors
}
/// <inheritdoc/>
protected override void BeforeApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
protected override void BeforeApply(ImageBase<TPixel> source, ImageBase<TPixel> destination, Rectangle sourceRectangle)
{
if (!(this.Sampler is NearestNeighborResampler))
{
@ -155,9 +155,9 @@ namespace ImageSharp.Processing.Processors
}
/// <inheritdoc />
protected override void AfterApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
protected override void AfterApply(ImageBase<TPixel> source, ImageBase<TPixel> destination, Rectangle sourceRectangle)
{
base.AfterApply(source, sourceRectangle);
base.AfterApply(source, destination, sourceRectangle);
this.HorizontalWeights?.Dispose();
this.HorizontalWeights = null;
this.VerticalWeights?.Dispose();

180
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -46,11 +46,33 @@ namespace ImageSharp.Processing.Processors
}
/// <inheritdoc/>
protected override unsafe void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
// we will always be creating the clone even for mutate because thatsa the way this base processor works
// ------------
// for resize we know we are going to populate every pixel with fresh data and we want a different target size so
// lets manually clone an empty set of images at the correct target and then have the base class processs them in.
// turn.
var image = new Image<TPixel>(source.Configuration, this.Width, this.Height, source.MetaData.Clone());
// now 'clone' the ImageFrames
foreach (ImageFrame<TPixel> sourceFrame in source.Frames)
{
var targetFrame = new ImageFrame<TPixel>(sourceFrame.Configuration, this.Width, this.Height, sourceFrame.MetaData.Clone());
image.Frames.Add(targetFrame);
}
return image;
}
/// <inheritdoc/>
protected override unsafe void OnApply(ImageBase<TPixel> source, ImageBase<TPixel> cloned, Rectangle sourceRectangle)
{
// Jump out, we'll deal with that later.
if (source.Width == this.Width && source.Height == this.Height && sourceRectangle == this.ResizeRectangle)
if (source.Width == cloned.Width && source.Height == cloned.Height && sourceRectangle == this.ResizeRectangle)
{
// the cloned will be blank here copy all the pixel data over
source.Pixels.CopyTo(cloned.Pixels);
return;
}
@ -74,29 +96,24 @@ namespace ImageSharp.Processing.Processors
float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height;
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
// Y coordinates of source points
Span<TPixel> sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span<TPixel> targetRow = cloned.GetRowSpan(y);
for (int x = minX; x < maxX; x++)
{
// Y coordinates of source points
Span<TPixel> sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)];
}
});
for (int x = minX; x < maxX; x++)
{
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)];
}
});
// Break out now.
source.SwapPixelsBuffers(targetPixels);
return;
}
return;
}
// Interpolate the image using the calculated weights.
@ -105,82 +122,77 @@ namespace ImageSharp.Processing.Processors
// are the upper and lower bounds of the source rectangle.
// TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed!
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
using (var firstPassPixels = new Buffer2D<Vector4>(width, source.Height))
{
using (var firstPassPixels = new Buffer2D<Vector4>(width, source.Height))
{
firstPassPixels.Clear();
Parallel.For(
0,
sourceRectangle.Bottom,
this.ParallelOptions,
y =>
firstPassPixels.Clear();
Parallel.For(
0,
sourceRectangle.Bottom,
this.ParallelOptions,
y =>
{
// TODO: Without Parallel.For() this buffer object could be reused:
using (var tempRowBuffer = new Buffer<Vector4>(source.Width))
{
// TODO: Without Parallel.For() this buffer object could be reused:
using (var tempRowBuffer = new Buffer<Vector4>(source.Width))
{
Span<Vector4> firstPassRow = firstPassPixels.GetRowSpan(y);
Span<TPixel> sourceRow = source.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length);
Span<Vector4> firstPassRow = firstPassPixels.GetRowSpan(y);
Span<TPixel> sourceRow = source.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length);
if (this.Compand)
if (this.Compand)
{
for (int x = minX; x < maxX; x++)
{
for (int x = minX; x < maxX; x++)
{
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX);
}
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX);
}
else
}
else
{
for (int x = minX; x < maxX; x++)
{
for (int x = minX; x < maxX; x++)
{
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX);
}
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX);
}
}
});
// Now process the rows.
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
// Ensure offsets are normalised for cropping and padding.
WeightsWindow window = this.VerticalWeights.Weights[y - startY];
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
}
});
if (this.Compand)
// Now process the rows.
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
// Ensure offsets are normalised for cropping and padding.
WeightsWindow window = this.VerticalWeights.Weights[y - startY];
Span<TPixel> targetRow = cloned.GetRowSpan(y);
if (this.Compand)
{
for (int x = 0; x < width; x++)
{
for (int x = 0; x < width; x++)
{
// Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
destination = destination.Compress();
// Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
destination = destination.Compress();
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(destination);
}
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(destination);
}
else
}
else
{
for (int x = 0; x < width; x++)
{
for (int x = 0; x < width; x++)
{
// Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
// Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(destination);
}
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(destination);
}
});
}
source.SwapPixelsBuffers(targetPixels);
}
});
}
}
}

Loading…
Cancel
Save