/// <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; } }
protected override void Initialize() { // Build top corner data + one period of the mosaic data: int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; for (int i = 0; i < startOfFirstRepeatedMosaic; i++) { ResizeKernel kernel = this.BuildKernel(i, i); this.kernels[i] = kernel; } // Copy the mosaics: int bottomStartDest = this.DestinationLength - this.cornerInterval; for (int i = startOfFirstRepeatedMosaic; i < bottomStartDest; i++) { double center = ((i + .5) * this.ratio) - .5; int left = (int)TolerantMath.Ceiling(center - this.radius); ResizeKernel kernel = this.kernels[i - this.period]; this.kernels[i] = kernel.AlterLeftValue(left); } // Build bottom corner data: int bottomStartData = this.cornerInterval + this.period; for (int i = 0; i < this.cornerInterval; i++) { ResizeKernel kernel = this.BuildKernel(bottomStartDest + i, bottomStartData + i); this.kernels[bottomStartDest + i] = kernel; } }
protected virtual void Initialize() { for (int i = 0; i < this.DestinationLength; i++) { ResizeKernel kernel = this.BuildKernel(i, i); this.kernels[i] = kernel; } }
public void FillDestinationPixels(RowInterval rowInterval, Buffer2D <TPixel> destination) { Span <Vector4> tempColSpan = this.tempColumnBuffer.GetSpan(); for (int y = rowInterval.Min; y < rowInterval.Max; y++) { // Ensure offsets are normalized for cropping and padding. ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - this.targetOrigin.Y); if (kernel.StartIndex + kernel.Length > this.currentWindow.Max) { this.Slide(); } ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); int top = kernel.StartIndex - this.currentWindow.Min; ref Vector4 fpBase = ref this.transposedFirstPassBuffer.Span[top];
/// <summary> /// Builds a <see cref="ResizeKernel"/> for the row <paramref name="destRowIndex"/> (in <see cref="kernels"/>) /// referencing the data at row <paramref name="dataRowIndex"/> within <see cref="data"/>, /// so the data reusable by other data rows. /// </summary> private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex) { double center = ((destRowIndex + .5) * this.ratio) - .5; // Keep inside bounds. int left = (int)TolerantMath.Ceiling(center - this.radius); if (left < 0) { left = 0; } int right = (int)TolerantMath.Floor(center + this.radius); if (right > this.sourceLength - 1) { right = this.sourceLength - 1; } ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right); Span <double> kernelValues = this.tempValues.AsSpan().Slice(0, kernel.Length); double sum = 0; for (int j = left; j <= right; j++) { double value = this.sampler.GetValue((float)((j - center) / this.scale)); sum += value; kernelValues[j - left] = value; } // Normalize, best to do it here rather than in the pixel loop later on. if (sum > 0) { for (int j = 0; j < kernel.Length; j++) { // weights[w] = weights[w] / sum: ref double kRef = ref kernelValues[j]; kRef /= sum; } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Rectangle sourceRectangle, Configuration configuration) { // Handle resize dimensions identical to the original if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.ResizeRectangle) { // The cloned will be blank here copy all the pixel data over source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); return; } int width = this.Width; int height = this.Height; int sourceX = sourceRectangle.X; int sourceY = sourceRectangle.Y; int startY = this.ResizeRectangle.Y; int endY = this.ResizeRectangle.Bottom; int startX = this.ResizeRectangle.X; int endX = this.ResizeRectangle.Right; int minX = Math.Max(0, startX); int maxX = Math.Min(width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); if (this.Sampler is NearestNeighborResampler) { var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; ParallelHelper.IterateRows( workingRect, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { // Y coordinates of source points Span <TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); Span <TPixel> targetRow = destination.GetPixelRowSpan(y); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } } }); return; } int sourceHeight = source.Height; PixelConversionModifiers conversionModifiers = PixelConversionModifiers.Premultiply; if (this.Compand) { conversionModifiers |= PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand; } // Interpolate the image using the calculated weights. // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. using (Buffer2D <Vector4> firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D <Vector4>(sourceHeight, width)) { firstPassPixelsTransposed.MemorySource.Clear(); var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom); ParallelHelper.IterateRowsWithTempBuffer <Vector4>( processColsRect, configuration, (rows, tempRowBuffer) => { for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(sourceX); Span <Vector4> tempRowSpan = tempRowBuffer.Span.Slice(sourceX); PixelOperations <TPixel> .Instance.ToVector4(configuration, sourceRow, tempRowSpan, conversionModifiers); ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; for (int x = minX; x < maxX; x++) { ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX); Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = kernel.Convolve(tempRowSpan); } } }); var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY); // Now process the rows. ParallelHelper.IterateRowsWithTempBuffer <Vector4>( processRowsRect, configuration, (rows, tempRowBuffer) => { Span <Vector4> tempRowSpan = tempRowBuffer.Span; for (int y = rows.Min; y < rows.Max; y++) { // Ensure offsets are normalized for cropping and padding. ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - startY); ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); for (int x = 0; x < width; x++) { Span <Vector4> firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY); // Destination color components Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn); } Span <TPixel> targetRowSpan = destination.GetPixelRowSpan(y); PixelOperations <TPixel> .Instance.FromVector4Destructive(configuration, tempRowSpan, targetRowSpan, conversionModifiers); } }); } }