/// <summary> /// Create scanline interpolation filter to be applied with ScaleScan /// This routine creates a scalescanfilter for 1-D interpolation of samples at /// the locations /// XStart + n*XStep, n = 0, ..., DestWidth - 1, /// where the pixels of the source are logically located at the integers. Half- /// sample even symmetric extension is used to handle the boundaries. /// </summary> /// <param name="destWidth">width after interpolation</param> /// <param name="xstart">leftmost sampling location (in input coordinates)</param> /// <param name="xstep">the length between successive samples (in input coordinates)</param> /// <param name="srcWidth">width of the input</param> /// <param name="kernel">interpolation kernel function to use</param> /// <param name="kernelRadius">kernel support radius</param> /// <param name="kernelNormalize">if set to <c>true</c> filter rows are normalized to sum to 1</param> /// <param name="boundary">boundary handling</param> /// <returns></returns> /// /// private static ScaleScanFilter _MakeScaleScanFilter(int destWidth, float xstart, float xstep, int srcWidth, Kernels.FixedRadiusKernelMethod kernel, float kernelRadius, bool kernelNormalize, OutOfBoundsUtils.OutOfBoundsHandler boundary) { Contract.Requires(kernel != null); var kernelWidth = (int)Math.Ceiling(2 * kernelRadius); var filterWidth = (srcWidth < kernelWidth) ? srcWidth : kernelWidth; var filterCoeff = new float[filterWidth * destWidth]; var filterPos = new short[destWidth]; var result = new ScaleScanFilter { Coeff = filterCoeff, Pos = filterPos, Width = filterWidth }; var maxPos = srcWidth - filterWidth; var coeffIndex = 0; for (var destX = 0; destX < destWidth; destX++) { var srcX = xstart + xstep * destX; var pos = (int)Math.Ceiling(srcX - kernelRadius); if (pos < 0 || maxPos < pos) { filterPos[destX] = (short)(pos < 0 ? 0 : pos > maxPos ? maxPos : pos); for (var n = 0; n < filterWidth; n++) filterCoeff[coeffIndex + n] = 0; for (var n = 0; n < kernelWidth; n++) { var index = pos + n; if (index < 0 || index >= srcWidth) index = boundary(index, srcWidth,index<0); filterCoeff[coeffIndex + index - filterPos[destX]] += (float) kernel(srcX - index); } } else { filterPos[destX] = (short)pos; for (var n = 0; n < filterWidth; n++) filterCoeff[coeffIndex + n] = (float)kernel(srcX - (pos + n)); } if (kernelNormalize) /* Normalize */ { var sum = 0f; for (var n = 0; n < filterWidth; n++) sum += filterCoeff[coeffIndex + n]; for (var n = 0; n < filterWidth; n++) filterCoeff[coeffIndex + n] /= sum; } coeffIndex += filterWidth; } return (result); }
/// <summary> /// Scale image with a compact support interpolation kernel /// This is a generic linear interpolation routine to scale an image using any /// compactly supported interpolation kernel. The kernel is applied separably /// along both dimensions. Half-sample even symmetric extension is used to /// handle the boundaries. /// /// The interpolation is computed so that Dest[m + DestWidth*n] is the /// interpolation of Src at sampling location /// (XStart + m*XStep, YStart + n*YStep) /// for m = 0, ..., DestWidth - 1, n = 0, ..., DestHeight - 1, where the /// pixels of Src are located at the integers. /// /// The implementation follows the approach taken in ffmpeg's swscale library. /// First a "scanline filter" is constructed, a sparse matrix such that /// multiplying with a row of the input image produces an interpolated row in /// the output image. Similarly a second matrix is constructed for /// interpolating columns. The interpolation itself is then essentially two /// sparse matrix times dense matrix multiplies. /// </summary> /// <param name="destination">pointer to memory for holding the interpolated image</param> /// <param name="xStart">leftmost sampling location (in input coordinates)</param> /// <param name="xStep">the length between successive samples (in input coordinates)</param> /// <param name="yStart">uppermost sampling location (in input coordinates)</param> /// <param name="yStep">the length between successive samples (in input coordinates)</param> /// <param name="kernel">interpolation kernel function to use</param> /// <param name="kernelRadius">kernel support radius</param> /// <param name="kernelNormalize">if set to <c>true</c> filter rows are normalized to sum to 1</param> /// <param name="horizontalOutOfBoundsHandler">The horizontal out of bounds handler.</param> /// <param name="verticalOutOfBoundsHandler">The vertical out of bounds handler.</param> private void _LinScale2D(FloatImage destination, float xStart, float xStep, float yStart, float yStep, Kernels.FixedRadiusKernelMethod kernel, float kernelRadius, bool kernelNormalize, OutOfBoundsUtils.OutOfBoundsHandler horizontalOutOfBoundsHandler, OutOfBoundsUtils.OutOfBoundsHandler verticalOutOfBoundsHandler) { Contract.Requires(destination != null); Contract.Requires(kernel != null); Contract.Requires(!(kernelRadius < 0)); var srcWidth = this.Width; var srcHeight = this.Height; var destHeight = destination.Height; var destWidth = destination.Width; var buffer = new float[srcWidth * destHeight]; var hFilter = _MakeScaleScanFilter(destWidth, xStart, xStep, srcWidth, kernel, kernelRadius, kernelNormalize, horizontalOutOfBoundsHandler); var vFilter = _MakeScaleScanFilter(destHeight, yStart, yStep, srcHeight, kernel, kernelRadius, kernelNormalize, verticalOutOfBoundsHandler); foreach (var plane in new[] { Tuple.Create(this._redPlane,destination._redPlane), Tuple.Create(this._greenPlane,destination._greenPlane), Tuple.Create(this._bluePlane,destination._bluePlane), Tuple.Create(this._alphaPlane,destination._alphaPlane), }) { for (var x = 0; x < srcWidth; x++) _ScaleScan(buffer, x, srcWidth, destHeight, plane.Item1, x, srcWidth, vFilter); for (var y = 0; y < destHeight; y++) _ScaleScan(plane.Item2, y * destWidth, 1, destWidth, buffer, y * srcWidth, 1, hFilter); } }
/* * This region is a C++ -> C# conversion from the original sources of Pascal Getreuer <*****@*****.**> */ /// <summary> /// Resizes the image to the specified dimensions using the given kernel type. /// </summary> /// <param name="destWidth">Width of the destination.</param> /// <param name="destHeight">Height of the destination.</param> /// <param name="interpolationInfo">The interpolation method info.</param> /// <param name="centeredGrid">if set to <c>true</c> using a centered grid; otherwise, using top-left aligned.</param> /// <returns> /// The resized image /// </returns> private FloatImage _Resize(int destWidth, int destHeight, Kernels.FixedRadiusKernelInfo interpolationInfo, bool centeredGrid) { var xScale = (float)destWidth / this.Width; var yScale = (float)destHeight / this.Height; var xStep = 1 / xScale; var yStep = 1 / yScale; float xStart, yStart; if (centeredGrid) { xStart = (xStep - 1) / 2; yStart = (yStep - 1) / 2; } else { xStart = yStart = 0; } var result = new FloatImage(destWidth, destHeight, this._horizontalOutOfBoundsMode, this._verticalOutOfBoundsMode); // prefilter image if necessary if (interpolationInfo.PrefilterAlpha != null && interpolationInfo.PrefilterAlpha.Length > 0) this._PrefilterImage(interpolationInfo.PrefilterAlpha, interpolationInfo.PrefilterScale); // resample this._LinScale2D( result, xStart, xStep, yStart, yStep, interpolationInfo.Kernel, interpolationInfo.KernelRadius, interpolationInfo.KernelNormalize, OutOfBoundsUtils.GetHandlerOrCrash(this.HorizontalOutOfBoundsMode), OutOfBoundsUtils.GetHandlerOrCrash(this.VerticalOutOfBoundsMode) ); return (result); }