mirror of https://github.com/SixLabors/ImageSharp
7 changed files with 225 additions and 194 deletions
@ -0,0 +1,130 @@ |
|||||
|
// 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.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Holds the <see cref="ResizeKernel"/> values in an optimized contigous memory region.
|
||||
|
/// </summary>
|
||||
|
internal class KernelMap : IDisposable |
||||
|
{ |
||||
|
private readonly Buffer2D<float> data; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="KernelMap"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for allocations.</param>
|
||||
|
/// <param name="destinationSize">The size of the destination window</param>
|
||||
|
/// <param name="kernelRadius">The radius of the kernel</param>
|
||||
|
public KernelMap(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius) |
||||
|
{ |
||||
|
int width = (int)Math.Ceiling(kernelRadius * 2); |
||||
|
this.data = memoryAllocator.Allocate2D<float>(width, destinationSize, AllocationOptions.Clean); |
||||
|
this.Kernels = new ResizeKernel[destinationSize]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the calculated <see cref="Kernels"/> values.
|
||||
|
/// </summary>
|
||||
|
public ResizeKernel[] Kernels { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disposes <see cref="KernelMap"/> instance releasing it's backing buffer.
|
||||
|
/// </summary>
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
this.data.Dispose(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Computes the weights to apply at each pixel when resizing.
|
||||
|
/// </summary>
|
||||
|
/// <param name="sampler">The <see cref="IResampler"/></param>
|
||||
|
/// <param name="destinationSize">The destination size</param>
|
||||
|
/// <param name="sourceSize">The source size</param>
|
||||
|
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations</param>
|
||||
|
/// <returns>The <see cref="KernelMap"/></returns>
|
||||
|
public static KernelMap Calculate( |
||||
|
IResampler sampler, |
||||
|
int destinationSize, |
||||
|
int sourceSize, |
||||
|
MemoryAllocator memoryAllocator) |
||||
|
{ |
||||
|
float ratio = (float)sourceSize / destinationSize; |
||||
|
float scale = ratio; |
||||
|
|
||||
|
if (scale < 1F) |
||||
|
{ |
||||
|
scale = 1F; |
||||
|
} |
||||
|
|
||||
|
float radius = MathF.Ceiling(scale * sampler.Radius); |
||||
|
var result = new KernelMap(memoryAllocator, destinationSize, radius); |
||||
|
|
||||
|
for (int i = 0; i < destinationSize; i++) |
||||
|
{ |
||||
|
float center = ((i + .5F) * ratio) - .5F; |
||||
|
|
||||
|
// Keep inside bounds.
|
||||
|
int left = (int)MathF.Ceiling(center - radius); |
||||
|
if (left < 0) |
||||
|
{ |
||||
|
left = 0; |
||||
|
} |
||||
|
|
||||
|
int right = (int)MathF.Floor(center + radius); |
||||
|
if (right > sourceSize - 1) |
||||
|
{ |
||||
|
right = sourceSize - 1; |
||||
|
} |
||||
|
|
||||
|
float sum = 0; |
||||
|
|
||||
|
ResizeKernel ws = result.CreateKernel(i, left, right); |
||||
|
result.Kernels[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; |
||||
|
} |
||||
|
|
||||
|
// Normalize, 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 /= sum; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Slices a weights value at the given positions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destIdx">The index in destination buffer</param>
|
||||
|
/// <param name="leftIdx">The local left index value</param>
|
||||
|
/// <param name="rightIdx">The local right index value</param>
|
||||
|
/// <returns>The weights</returns>
|
||||
|
private ResizeKernel CreateKernel(int destIdx, int leftIdx, int rightIdx) |
||||
|
{ |
||||
|
return new ResizeKernel(destIdx, leftIdx, this.data, rightIdx - leftIdx + 1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,56 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
|
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.Memory; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Holds the <see cref="WeightsWindow"/> values in an optimized contigous memory region.
|
|
||||
/// </summary>
|
|
||||
internal class WeightsBuffer : IDisposable |
|
||||
{ |
|
||||
private readonly Buffer2D<float> dataBuffer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="WeightsBuffer"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for allocations.</param>
|
|
||||
/// <param name="destinationSize">The size of the destination window</param>
|
|
||||
/// <param name="kernelRadius">The radius of the kernel</param>
|
|
||||
public WeightsBuffer(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius) |
|
||||
{ |
|
||||
int width = (int)Math.Ceiling(kernelRadius * 2); |
|
||||
this.dataBuffer = memoryAllocator.Allocate2D<float>(width, destinationSize, AllocationOptions.Clean); |
|
||||
this.Weights = new WeightsWindow[destinationSize]; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the calculated <see cref="Weights"/> values.
|
|
||||
/// </summary>
|
|
||||
public WeightsWindow[] Weights { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Disposes <see cref="WeightsBuffer"/> instance releasing it's backing buffer.
|
|
||||
/// </summary>
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
this.dataBuffer.Dispose(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Slices a weights value at the given positions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destIdx">The index in destination buffer</param>
|
|
||||
/// <param name="leftIdx">The local left index value</param>
|
|
||||
/// <param name="rightIdx">The local right index value</param>
|
|
||||
/// <returns>The weights</returns>
|
|
||||
public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) |
|
||||
{ |
|
||||
return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,61 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
using Xunit; |
||||
|
using Xunit.Abstractions; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms |
||||
|
{ |
||||
|
public class KernelMapTests |
||||
|
{ |
||||
|
private ITestOutputHelper Output { get; } |
||||
|
|
||||
|
public KernelMapTests(ITestOutputHelper output) |
||||
|
{ |
||||
|
this.Output = output; |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(500, 200, nameof(KnownResamplers.Bicubic))] |
||||
|
[InlineData(50, 40, nameof(KnownResamplers.Bicubic))] |
||||
|
[InlineData(40, 30, nameof(KnownResamplers.Bicubic))] |
||||
|
[InlineData(500, 200, nameof(KnownResamplers.Lanczos8))] |
||||
|
[InlineData(100, 80, nameof(KnownResamplers.Lanczos8))] |
||||
|
[InlineData(100, 10, nameof(KnownResamplers.Lanczos8))] |
||||
|
[InlineData(10, 100, nameof(KnownResamplers.Lanczos8))] |
||||
|
public void PrintKernelMap(int srcSize, int destSize, string resamplerName) |
||||
|
{ |
||||
|
var resampler = (IResampler)typeof(KnownResamplers).GetProperty(resamplerName).GetValue(null); |
||||
|
|
||||
|
var kernelMap = KernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); |
||||
|
|
||||
|
var bld = new StringBuilder(); |
||||
|
|
||||
|
foreach (ResizeKernel window in kernelMap.Kernels) |
||||
|
{ |
||||
|
Span<float> span = window.GetSpan(); |
||||
|
for (int i = 0; i < window.Length; i++) |
||||
|
{ |
||||
|
float value = span[i]; |
||||
|
bld.Append($"{value,7:F4}"); |
||||
|
bld.Append("| "); |
||||
|
} |
||||
|
|
||||
|
bld.AppendLine(); |
||||
|
} |
||||
|
|
||||
|
string outDir = TestEnvironment.CreateOutputDirectory("." + nameof(this.PrintKernelMap)); |
||||
|
string fileName = $@"{outDir}\{resamplerName}_{srcSize}_{destSize}.MD"; |
||||
|
|
||||
|
File.WriteAllText(fileName, bld.ToString()); |
||||
|
|
||||
|
this.Output.WriteLine(bld.ToString()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue