// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageProcessorCore.Processors { using System; using System.Numerics; using System.Threading.Tasks; using GenericImage; /// /// Provides methods that allow the resizing of images using various algorithms. /// public class ResizeProcessor : ImageProcessor { /// /// Initializes a new instance of the class. /// /// /// The sampler to perform the resize operation. /// public ResizeProcessor(IResampler sampler) { Guard.NotNull(sampler, nameof(sampler)); this.Sampler = sampler; } /// /// Gets the sampler to perform the resize operation. /// public IResampler Sampler { get; } /// /// Gets or sets the horizontal weights. /// protected Weights[] HorizontalWeights { get; set; } /// /// Gets or sets the vertical weights. /// protected Weights[] VerticalWeights { get; set; } /// protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { if (!(this.Sampler is NearestNeighborResampler)) { this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); } } /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { // Jump out, we'll deal with that later. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) { return; } int width = target.Width; int height = target.Height; int sourceHeight = sourceRectangle.Height; int targetX = target.Bounds.X; int targetY = target.Bounds.Y; int targetRight = target.Bounds.Right; int targetBottom = target.Bounds.Bottom; int startX = targetRectangle.X; int endX = targetRectangle.Right; bool compand = this.Compand; if (this.Sampler is NearestNeighborResampler) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, y => { if (targetY <= y && y < targetBottom) { // Y coordinates of source points int originY = (int)((y - startY) * heightFactor); for (int x = startX; x < endX; x++) { if (targetX <= x && x < targetRight) { // X coordinates of source points int originX = (int)((x - startX) * widthFactor); targetPixels[x, y] = sourcePixels[originX, originY]; } } this.OnRowProcessed(); } }); } // Break out now. 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. Image firstPass = new Image(target.Width, source.Height); using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor firstPassPixels = firstPass.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, sourceHeight, y => { for (int x = startX; x < endX; x++) { if (x >= 0 && x < width) { // Ensure offsets are normalised for cropping and padding. int offsetX = x - startX; float sum = this.HorizontalWeights[offsetX].Sum; Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; // Destination color components //Color destination = new Color(); //for (int i = 0; i < sum; i++) //{ // Weight xw = horizontalValues[i]; // int originX = xw.Index; // Color sourceColor = compand // ? Color.Expand(sourcePixels[originX, y]) // : sourcePixels[originX, y]; // destination += sourceColor * xw.Value; //} //if (compand) //{ // destination = Color.Compress(destination); //} //firstPassPixels[x, y] = destination; TColor sourceColor; TColor destination = default(TColor); for (int i = 0; i < sum; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; sourceColor = sourcePixels[originX, y]; //Color sourceColor = compand // ? Color.Expand(sourcePixels[originX, y]) // : sourcePixels[originX, y]; //sourceColor.Multiply(xw.Value); //destination.Add(sourceColor); //destination += sourceColor * xw.Value; sourceColor.Multiply(xw.Value); destination.Add(sourceColor); } //if (compand) //{ // destination = Color.Compress(destination); //} //T packed = default(T); //packed.PackVector(destination); firstPassPixels[x, y] = destination; } } }); // Now process the rows. Parallel.For( startY, endY, y => { if (y >= 0 && y < height) { // Ensure offsets are normalised for cropping and padding. int offsetY = y - startY; float sum = this.VerticalWeights[offsetY].Sum; Weight[] verticalValues = this.VerticalWeights[offsetY].Values; for (int x = 0; x < width; x++) { // Destination color components TColor sourceColor; TColor destination = default(TColor); for (int i = 0; i < sum; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; sourceColor = firstPassPixels[x, originY]; //Color sourceColor = compand // ? Color.Expand(firstPassPixels[x, originY]) // : firstPassPixels[x, originY]; //Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); //destination += sourceColor * yw.Value; sourceColor.Multiply(yw.Value); destination.Add(sourceColor); } //if (compand) //{ // destination = Color.Compress(destination); //} //T packed = default(T); //packed.PackVector(destination); targetPixels[x, y] = destination; } } this.OnRowProcessed(); }); } } /// protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { // Copy the pixels over. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) { target.ClonePixels(target.Width, target.Height, source.Pixels); } } /// /// Computes the weights to apply at each pixel when resizing. /// /// The destination section size. /// The source section size. /// /// The . /// protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) { float scale = (float)destinationSize / sourceSize; IResampler sampler = this.Sampler; float radius = sampler.Radius; double left; double right; float weight; int index; int sum; Weights[] result = new Weights[destinationSize]; // When shrinking, broaden the effective kernel support so that we still // visit every source pixel. if (scale < 1) { float width = radius / scale; float filterScale = 1 / scale; // Make the weights slices, one source for each column or row. for (int i = 0; i < destinationSize; i++) { float centre = i / scale; left = Math.Ceiling(centre - width); right = Math.Floor(centre + width); result[i] = new Weights { Values = new Weight[(int)(right - left + 1)] }; for (double j = left; j <= right; j++) { weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale; if (j < 0) { index = (int)-j; } else if (j >= sourceSize) { index = (int)((sourceSize - j) + sourceSize - 1); } else { index = (int)j; } sum = (int)result[i].Sum++; result[i].Values[sum] = new Weight(index, weight); } } } else { // Make the weights slices, one source for each column or row. for (int i = 0; i < destinationSize; i++) { float centre = i / scale; left = Math.Ceiling(centre - radius); right = Math.Floor(centre + radius); result[i] = new Weights { Values = new Weight[(int)(right - left + 1)] }; for (double j = left; j <= right; j++) { weight = sampler.GetValue((float)(centre - j)); if (j < 0) { index = (int)-j; } else if (j >= sourceSize) { index = (int)((sourceSize - j) + sourceSize - 1); } else { index = (int)j; } sum = (int)result[i].Sum++; result[i].Values[sum] = new Weight(index, weight); } } } return result; } /// /// Represents the weight to be added to a scaled pixel. /// protected struct Weight { /// /// Initializes a new instance of the struct. /// /// The index. /// The value. public Weight(int index, float value) { this.Index = index; this.Value = value; } /// /// Gets the pixel index. /// public int Index { get; } /// /// Gets the result of the interpolation algorithm. /// public float Value { get; } } /// /// Represents a collection of weights and their sum. /// protected class Weights { /// /// Gets or sets the values. /// public Weight[] Values { get; set; } /// /// Gets or sets the sum. /// public float Sum { get; set; } } } }