/// <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(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; } }
/// <inheritdoc/> protected override unsafe void OnApply(ImageFrame <TPixel> source, ImageFrame <TPixel> cloned, Rectangle sourceRectangle, Configuration configuration) { // Jump out, we'll deal with that later. 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.GetPixelSpan().CopyTo(cloned.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) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; Parallel.For( minY, maxY, configuration.ParallelOptions, y => { // Y coordinates of source points Span <TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); Span <TPixel> targetRow = cloned.GetPixelRowSpan(y); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } }); return; } // 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. // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! using (var firstPassPixels = new Buffer2D <Vector4>(width, source.Height)) { firstPassPixels.Clear(); Parallel.For( 0, sourceRectangle.Bottom, configuration.ParallelOptions, y => { // 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.GetPixelRowSpan(y); PixelOperations <TPixel> .Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); if (this.Compand) { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); } } else { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); } } } }); // Now process the rows. Parallel.For( minY, maxY, configuration.ParallelOptions, y => { // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; Span <TPixel> targetRow = cloned.GetPixelRowSpan(y); if (this.Compand) { for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); destination = destination.Compress(); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(destination); } } else { for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(destination); } } });