mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
27 changed files with 521 additions and 559 deletions
@ -1,171 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
|
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides methods that allow the resizing of images using various algorithms.
|
|
||||
/// Adapted from <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
internal abstract class ResamplingWeightedProcessor<TPixel> : TransformProcessorBase<TPixel> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="ResamplingWeightedProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
|
|
||||
/// <param name="sampler">The sampler to perform the resize operation.</param>
|
|
||||
/// <param name="width">The target width.</param>
|
|
||||
/// <param name="height">The target height.</param>
|
|
||||
/// <param name="resizeRectangle">
|
|
||||
/// The <see cref="Rectangle"/> structure that specifies the portion of the target image object to draw to.
|
|
||||
/// </param>
|
|
||||
protected ResamplingWeightedProcessor(MemoryManager memoryManager, IResampler sampler, int width, int height, Rectangle resizeRectangle) |
|
||||
{ |
|
||||
Guard.NotNull(memoryManager, nameof(memoryManager)); |
|
||||
Guard.NotNull(sampler, nameof(sampler)); |
|
||||
Guard.MustBeGreaterThan(width, 0, nameof(width)); |
|
||||
Guard.MustBeGreaterThan(height, 0, nameof(height)); |
|
||||
|
|
||||
this.MemoryManager = memoryManager; |
|
||||
this.Sampler = sampler; |
|
||||
this.Width = width; |
|
||||
this.Height = height; |
|
||||
this.ResizeRectangle = resizeRectangle; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the sampler to perform the resize operation.
|
|
||||
/// </summary>
|
|
||||
public IResampler Sampler { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the width.
|
|
||||
/// </summary>
|
|
||||
public int Width { get; protected set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the height.
|
|
||||
/// </summary>
|
|
||||
public int Height { get; protected set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the resize rectangle.
|
|
||||
/// </summary>
|
|
||||
public Rectangle ResizeRectangle { get; protected set; } |
|
||||
|
|
||||
protected MemoryManager MemoryManager { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the horizontal weights.
|
|
||||
/// </summary>
|
|
||||
protected WeightsBuffer HorizontalWeights { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the vertical weights.
|
|
||||
/// </summary>
|
|
||||
protected WeightsBuffer VerticalWeights { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Computes the weights to apply at each pixel when resizing.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destinationSize">The destination size</param>
|
|
||||
/// <param name="sourceSize">The source size</param>
|
|
||||
/// <returns>The <see cref="WeightsBuffer"/></returns>
|
|
||||
// TODO: Made internal to simplify experimenting with weights data. Make it protected again when finished figuring out how to optimize all the stuff!
|
|
||||
internal unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize) |
|
||||
{ |
|
||||
float ratio = (float)sourceSize / destinationSize; |
|
||||
float scale = ratio; |
|
||||
|
|
||||
if (scale < 1F) |
|
||||
{ |
|
||||
scale = 1F; |
|
||||
} |
|
||||
|
|
||||
IResampler sampler = this.Sampler; |
|
||||
float radius = MathF.Ceiling(scale * sampler.Radius); |
|
||||
var result = new WeightsBuffer(this.MemoryManager, sourceSize, destinationSize); |
|
||||
|
|
||||
for (int i = 0; i < destinationSize; i++) |
|
||||
{ |
|
||||
float center = ((i + .5F) * ratio) - .5F; |
|
||||
|
|
||||
// Keep inside bounds.
|
|
||||
int left = (int)Math.Ceiling(center - radius); |
|
||||
if (left < 0) |
|
||||
{ |
|
||||
left = 0; |
|
||||
} |
|
||||
|
|
||||
int right = (int)Math.Floor(center + radius); |
|
||||
if (right > sourceSize - 1) |
|
||||
{ |
|
||||
right = sourceSize - 1; |
|
||||
} |
|
||||
|
|
||||
float sum = 0; |
|
||||
|
|
||||
WeightsWindow ws = result.GetWeightsWindow(i, left, right); |
|
||||
result.Weights[i] = ws; |
|
||||
|
|
||||
ref float weightsBaseRef = ref ws.GetStartReference(); |
|
||||
|
|
||||
for (int j = left; j <= right; j++) |
|
||||
{ |
|
||||
float weight = sampler.GetValue((j - center) / scale); |
|
||||
sum += weight; |
|
||||
|
|
||||
// weights[j - left] = weight:
|
|
||||
Unsafe.Add(ref weightsBaseRef, j - left) = weight; |
|
||||
} |
|
||||
|
|
||||
// Normalise, best to do it here rather than in the pixel loop later on.
|
|
||||
if (sum > 0) |
|
||||
{ |
|
||||
for (int w = 0; w < ws.Length; w++) |
|
||||
{ |
|
||||
// weights[w] = weights[w] / sum:
|
|
||||
ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w); |
|
||||
wRef = wRef / sum; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void BeforeApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
if (!(this.Sampler is NearestNeighborResampler)) |
|
||||
{ |
|
||||
this.HorizontalWeights = this.PrecomputeWeights( |
|
||||
this.ResizeRectangle.Width, |
|
||||
sourceRectangle.Width); |
|
||||
|
|
||||
this.VerticalWeights = this.PrecomputeWeights( |
|
||||
this.ResizeRectangle.Height, |
|
||||
sourceRectangle.Height); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override void AfterApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
base.AfterApply(source, destination, sourceRectangle, configuration); |
|
||||
this.HorizontalWeights?.Dispose(); |
|
||||
this.HorizontalWeights = null; |
|
||||
this.VerticalWeights?.Dispose(); |
|
||||
this.VerticalWeights = null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,58 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.Primitives; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests |
||||
|
{ |
||||
|
public class ImageProcessingContextTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void MutatedSizeIsAccuratePerOperation() |
||||
|
{ |
||||
|
var x500 = new Size(500, 500); |
||||
|
var x400 = new Size(400, 400); |
||||
|
var x300 = new Size(300, 300); |
||||
|
var x200 = new Size(200, 200); |
||||
|
var x100 = new Size(100, 100); |
||||
|
using (var image = new Image<Rgba32>(500, 500)) |
||||
|
{ |
||||
|
image.Mutate(x => |
||||
|
x.AssertSize(x500) |
||||
|
.Resize(x400).AssertSize(x400) |
||||
|
.Resize(x300).AssertSize(x300) |
||||
|
.Resize(x200).AssertSize(x200) |
||||
|
.Resize(x100).AssertSize(x100)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ClonedSizeIsAccuratePerOperation() |
||||
|
{ |
||||
|
var x500 = new Size(500, 500); |
||||
|
var x400 = new Size(400, 400); |
||||
|
var x300 = new Size(300, 300); |
||||
|
var x200 = new Size(200, 200); |
||||
|
var x100 = new Size(100, 100); |
||||
|
using (var image = new Image<Rgba32>(500, 500)) |
||||
|
{ |
||||
|
image.Clone(x => |
||||
|
x.AssertSize(x500) |
||||
|
.Resize(x400).AssertSize(x400) |
||||
|
.Resize(x300).AssertSize(x300) |
||||
|
.Resize(x200).AssertSize(x200) |
||||
|
.Resize(x100).AssertSize(x100)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static class SizeAssertationExtensions |
||||
|
{ |
||||
|
public static IImageProcessingContext<Rgba32> AssertSize(this IImageProcessingContext<Rgba32> context, Size size) |
||||
|
{ |
||||
|
Assert.Equal(size, context.GetCurrentSize()); |
||||
|
return context; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,20 +1,30 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System; |
using SixLabors.ImageSharp.Processing; |
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.Processing.Processors; |
||||
|
|
||||
using Xunit; |
using Xunit; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.Processing.Transforms |
namespace SixLabors.ImageSharp.Tests.Processing.Transforms |
||||
{ |
{ |
||||
|
|
||||
|
|
||||
public class PadTest : BaseImageOperationsExtensionTest |
public class PadTest : BaseImageOperationsExtensionTest |
||||
{ |
{ |
||||
#pragma warning disable xUnit1004 // Test methods should not be skipped
|
[Fact] |
||||
[Fact(Skip = "Skip this is a helper around resize, skip until resize can be refactord")] |
public void PadWidthHeightResizeProcessorWithCorrectOptionsSet() |
||||
#pragma warning restore xUnit1004 // Test methods should not be skipped
|
|
||||
public void Pad_width_height_ResizeProcessorWithCorrectOPtionsSet() |
|
||||
{ |
{ |
||||
throw new NotImplementedException("Write test here"); |
int width = 500; |
||||
|
int height = 565; |
||||
|
IResampler sampler = KnownResamplers.NearestNeighbor; |
||||
|
|
||||
|
this.operations.Pad(width, height); |
||||
|
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>(); |
||||
|
|
||||
|
Assert.Equal(width, resizeProcessor.Width); |
||||
|
Assert.Equal(height, resizeProcessor.Height); |
||||
|
Assert.Equal(sampler, resizeProcessor.Sampler); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1,23 +1,91 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing; |
using SixLabors.ImageSharp.Processing; |
||||
using SixLabors.Primitives; |
using SixLabors.Primitives; |
||||
using Xunit; |
using Xunit; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.Processing.Transforms |
namespace SixLabors.ImageSharp.Tests.Processing.Transforms |
||||
{ |
{ |
||||
|
using SixLabors.ImageSharp.Processing.Processors; |
||||
|
|
||||
public class ResizeTests : BaseImageOperationsExtensionTest |
public class ResizeTests : BaseImageOperationsExtensionTest |
||||
{ |
{ |
||||
#pragma warning disable xUnit1004 // Test methods should not be skipped
|
[Fact] |
||||
[Fact(Skip = "Skip resize tests as they need refactoring to be simpler and just pass data into the resize processor.")] |
public void ResizeWidthAndHeight() |
||||
#pragma warning restore xUnit1004 // Test methods should not be skipped
|
{ |
||||
public void TestMissing() |
int width = 50; |
||||
|
int height = 100; |
||||
|
this.operations.Resize(width, height); |
||||
|
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>(); |
||||
|
|
||||
|
Assert.Equal(width, resizeProcessor.Width); |
||||
|
Assert.Equal(height, resizeProcessor.Height); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ResizeWidthAndHeightAndSampler() |
||||
|
{ |
||||
|
int width = 50; |
||||
|
int height = 100; |
||||
|
IResampler sampler = KnownResamplers.Lanczos3; |
||||
|
this.operations.Resize(width, height, sampler); |
||||
|
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>(); |
||||
|
|
||||
|
Assert.Equal(width, resizeProcessor.Width); |
||||
|
Assert.Equal(height, resizeProcessor.Height); |
||||
|
Assert.Equal(sampler, resizeProcessor.Sampler); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ResizeWidthAndHeightAndSamplerAndCompand() |
||||
{ |
{ |
||||
//
|
int width = 50; |
||||
throw new NotImplementedException("Write test here"); |
int height = 100; |
||||
|
IResampler sampler = KnownResamplers.Lanczos3; |
||||
|
bool compand = true; |
||||
|
|
||||
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
|
this.operations.Resize(width, height, sampler, compand); |
||||
|
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>(); |
||||
|
|
||||
|
Assert.Equal(width, resizeProcessor.Width); |
||||
|
Assert.Equal(height, resizeProcessor.Height); |
||||
|
Assert.Equal(sampler, resizeProcessor.Sampler); |
||||
|
Assert.Equal(compand, resizeProcessor.Compand); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ResizeWithOptions() |
||||
|
{ |
||||
|
int width = 50; |
||||
|
int height = 100; |
||||
|
IResampler sampler = KnownResamplers.Lanczos3; |
||||
|
bool compand = true; |
||||
|
ResizeMode mode = ResizeMode.Stretch; |
||||
|
|
||||
|
var resizeOptions = new ResizeOptions |
||||
|
{ |
||||
|
Size = new Size(width, height), |
||||
|
Sampler = sampler, |
||||
|
Compand = compand, |
||||
|
Mode = mode |
||||
|
}; |
||||
|
|
||||
|
this.operations.Resize(resizeOptions); |
||||
|
ResizeProcessor<Rgba32> resizeProcessor = this.Verify<ResizeProcessor<Rgba32>>(); |
||||
|
|
||||
|
Assert.Equal(width, resizeProcessor.Width); |
||||
|
Assert.Equal(height, resizeProcessor.Height); |
||||
|
Assert.Equal(sampler, resizeProcessor.Sampler); |
||||
|
Assert.Equal(compand, resizeProcessor.Compand); |
||||
|
|
||||
|
// Ensure options are not altered.
|
||||
|
Assert.Equal(width, resizeOptions.Size.Width); |
||||
|
Assert.Equal(height, resizeOptions.Size.Height); |
||||
|
Assert.Equal(sampler, resizeOptions.Sampler); |
||||
|
Assert.Equal(compand, resizeOptions.Compand); |
||||
|
Assert.Equal(mode, resizeOptions.Mode); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
Loading…
Reference in new issue