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.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Processing.Transforms |
|||
{ |
|||
|
|||
|
|||
public class PadTest : BaseImageOperationsExtensionTest |
|||
{ |
|||
#pragma warning disable xUnit1004 // Test methods should not be skipped
|
|||
[Fact(Skip = "Skip this is a helper around resize, skip until resize can be refactord")] |
|||
#pragma warning restore xUnit1004 // Test methods should not be skipped
|
|||
public void Pad_width_height_ResizeProcessorWithCorrectOPtionsSet() |
|||
[Fact] |
|||
public void PadWidthHeightResizeProcessorWithCorrectOptionsSet() |
|||
{ |
|||
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.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Primitives; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Processing.Transforms |
|||
{ |
|||
using SixLabors.ImageSharp.Processing.Processors; |
|||
|
|||
public class ResizeTests : BaseImageOperationsExtensionTest |
|||
{ |
|||
#pragma warning disable xUnit1004 // Test methods should not be skipped
|
|||
[Fact(Skip = "Skip resize tests as they need refactoring to be simpler and just pass data into the resize processor.")] |
|||
#pragma warning restore xUnit1004 // Test methods should not be skipped
|
|||
public void TestMissing() |
|||
[Fact] |
|||
public void ResizeWidthAndHeight() |
|||
{ |
|||
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() |
|||
{ |
|||
//
|
|||
throw new NotImplementedException("Write test here"); |
|||
int width = 50; |
|||
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