/// <inheritdoc/> protected override void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { if (this.CropRectangle == sourceRectangle) { return; } int minY = Math.Max(this.CropRectangle.Y, sourceRectangle.Y); int maxY = Math.Min(this.CropRectangle.Bottom, sourceRectangle.Bottom); int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); using (var targetPixels = new PixelAccessor <TPixel>(this.CropRectangle.Width, this.CropRectangle.Height)) { Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> sourceRow = source.GetRowSpan(minX, y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y - minY); SpanHelper.Copy(sourceRow, targetRow, maxX - minX); }); source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { if (this.OptimizedApply(source)) { return; } int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); using (var targetPixels = new PixelAccessor <TPixel>(width, height)) { Parallel.For( 0, height, this.ParallelOptions, y => { Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = 0; x < width; x++) { var transformedPoint = Point.Rotate(new Point(x, y), matrix); if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { targetRow[x] = source[transformedPoint.X, transformedPoint.Y]; } } }); source.SwapPixelsBuffers(targetPixels); } }
/// <summary> /// Rotates the image 180 degrees clockwise at the centre point. /// </summary> /// <param name="source">The source image.</param> private void Rotate180(ImageBase <TPixel> source) { int width = source.Width; int height = source.Height; using (var targetPixels = new PixelAccessor <TPixel>(width, height)) { Parallel.For( 0, height, this.ParallelOptions, y => { Span <TPixel> sourceRow = source.GetRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(height - y - 1); for (int x = 0; x < width; x++) { targetRow[width - x - 1] = sourceRow[x]; } }); source.SwapPixelsBuffers(targetPixels); } }
/// <summary> /// Swaps the image at the Y-axis, which goes vertically through the middle /// at half of the width of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> private void FlipY(ImageBase <TPixel> source) { int width = source.Width; int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); using (var targetPixels = new PixelAccessor <TPixel>(width, height)) { Parallel.For( 0, height, this.ParallelOptions, y => { Span <TPixel> sourceRow = source.GetRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = 0; x < halfWidth; x++) { int newX = width - x - 1; targetRow[x] = sourceRow[newX]; targetRow[newX] = sourceRow[x]; } }); source.SwapPixelsBuffers(targetPixels); } }
/// <summary> /// Rotates the image 270 degrees clockwise at the centre point. /// </summary> /// <param name="source">The source image.</param> private void Rotate270(ImageBase <TPixel> source) { int width = source.Width; int height = source.Height; using (var targetPixels = new PixelAccessor <TPixel>(height, width)) { using (PixelAccessor <TPixel> sourcePixels = source.Lock()) { Parallel.For( 0, height, this.ParallelOptions, y => { for (int x = 0; x < width; x++) { int newX = height - y - 1; newX = height - newX - 1; int newY = width - x - 1; targetPixels[newX, newY] = sourcePixels[x, y]; } }); } source.SwapPixelsBuffers(targetPixels); } }
/// <summary> /// Swaps the image at the X-axis, which goes horizontally through the middle /// at half the height of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> private void FlipX(ImageBase <TPixel> source) { int width = source.Width; int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); using (PixelAccessor <TPixel> targetPixels = new PixelAccessor <TPixel>(width, height)) { using (PixelAccessor <TPixel> sourcePixels = source.Lock()) { Parallel.For( 0, halfHeight, this.ParallelOptions, y => { for (int x = 0; x < width; x++) { int newY = height - y - 1; targetPixels[x, y] = sourcePixels[x, newY]; targetPixels[x, newY] = sourcePixels[x, y]; } }); } source.SwapPixelsBuffers(targetPixels); } }
/// <summary> /// Swaps the image at the Y-axis, which goes vertically through the middle /// at half of the width of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> private void FlipY(ImageBase <TColor> source) { int width = source.Width; int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(width, height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) { Parallel.For( 0, height, this.ParallelOptions, y => { for (int x = 0; x < halfWidth; x++) { int newX = width - x - 1; targetPixels[x, y] = sourcePixels[newX, y]; targetPixels[newX, y] = sourcePixels[x, y]; } }); } source.SwapPixelsBuffers(targetPixels); } }
/// <summary> /// Swaps the image at the X-axis, which goes horizontally through the middle /// at half the height of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> private void FlipX(ImageBase <TPixel> source) { int width = source.Width; int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); using (var targetPixels = new PixelAccessor <TPixel>(width, height)) { Parallel.For( 0, halfHeight, this.ParallelOptions, y => { int newY = height - y - 1; Span <TPixel> sourceRow = source.GetRowSpan(y); Span <TPixel> altSourceRow = source.GetRowSpan(newY); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); Span <TPixel> altTargetRow = targetPixels.GetRowSpan(newY); sourceRow.CopyTo(altTargetRow); altSourceRow.CopyTo(targetRow); }); source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TColor> source, Rectangle sourceRectangle) { if (this.CropRectangle == sourceRectangle) { return; } int minY = Math.Max(this.CropRectangle.Y, sourceRectangle.Y); int maxY = Math.Min(this.CropRectangle.Bottom, sourceRectangle.Bottom); int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(this.CropRectangle.Width, this.CropRectangle.Height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) { Parallel.For( minY, maxY, this.ParallelOptions, y => { for (int x = minX; x < maxX; x++) { targetPixels[x - minX, y - minY] = sourcePixels[x, y]; } }); } source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TColor> source, Rectangle sourceRectangle) { int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(width, height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) { Parallel.For( 0, height, this.ParallelOptions, y => { for (int x = 0; x < width; x++) { Point transformedPoint = Point.Skew(new Point(x, y), matrix); if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; } } }); } source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TColor> source, Rectangle sourceRectangle) { int width = source.Width; int height = source.Height; using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(width, height)) { using (PixelAccessor <TColor> firstPassPixels = new PixelAccessor <TColor>(width, height)) using (PixelAccessor <TColor> sourcePixels = source.Lock()) { this.ApplyConvolution(firstPassPixels, sourcePixels, sourceRectangle, this.KernelX); this.ApplyConvolution(targetPixels, firstPassPixels, sourceRectangle, this.KernelY); } source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TColor> source, Rectangle sourceRectangle) { int kernelLength = this.KernelXY.Height; int radius = kernelLength >> 1; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int maxY = endY - 1; int maxX = endX - 1; using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(source.Width, source.Height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) { Parallel.For( startY, endY, this.ParallelOptions, y => { for (int x = startX; x < endX; x++) { float red = 0; float green = 0; float blue = 0; // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelLength; fy++) { int fyr = fy - radius; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); for (int fx = 0; fx < kernelLength; fx++) { int fxr = fx - radius; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; green += currentColor.Y; blue += currentColor.Z; } } TColor packed = default(TColor); packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); targetPixels[x, y] = packed; } }); } source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { int kernelYHeight = this.KernelY.Height; int kernelYWidth = this.KernelY.Width; int kernelXHeight = this.KernelX.Height; int kernelXWidth = this.KernelX.Width; int radiusY = kernelYHeight >> 1; int radiusX = kernelXWidth >> 1; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int maxY = endY - 1; int maxX = endX - 1; using (PixelAccessor <TPixel> targetPixels = new PixelAccessor <TPixel>(source.Width, source.Height)) using (PixelAccessor <TPixel> sourcePixels = source.Lock()) { sourcePixels.CopyTo(targetPixels); Parallel.For( startY, endY, this.ParallelOptions, y => { for (int x = startX; x < endX; x++) { float rX = 0; float gX = 0; float bX = 0; float rY = 0; float gY = 0; float bY = 0; // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelYHeight; fy++) { int fyr = fy - radiusY; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); for (int fx = 0; fx < kernelXWidth; fx++) { int fxr = fx - radiusX; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); if (fy < kernelXHeight) { Vector4 kx = this.KernelX[fy, fx] * currentColor; rX += kx.X; gX += kx.Y; bX += kx.Z; } if (fx < kernelYWidth) { Vector4 ky = this.KernelY[fy, fx] * currentColor; rY += ky.X; gY += ky.Y; bY += ky.Z; } } } float red = MathF.Sqrt((rX * rX) + (rY * rY)); float green = MathF.Sqrt((gX * gX) + (gY * gY)); float blue = MathF.Sqrt((bX * bX) + (bY * bY)); TPixel packed = default(TPixel); packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); targetPixels[x, y] = packed; } }); source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int radius = this.BrushSize >> 1; int levels = this.Levels; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // Reset offset if necessary. if (minX > 0) { startX = 0; } using (PixelAccessor <TPixel> targetPixels = new PixelAccessor <TPixel>(source.Width, source.Height)) using (PixelAccessor <TPixel> sourcePixels = source.Lock()) { sourcePixels.CopyTo(targetPixels); Parallel.For( minY, maxY, this.ParallelOptions, y => { for (int x = startX; x < endX; x++) { int maxIntensity = 0; int maxIndex = 0; int[] intensityBin = new int[levels]; float[] redBin = new float[levels]; float[] blueBin = new float[levels]; float[] greenBin = new float[levels]; for (int fy = 0; fy <= radius; fy++) { int fyr = fy - radius; int offsetY = y + fyr; // Skip the current row if (offsetY < minY) { continue; } // Outwith the current bounds so break. if (offsetY >= maxY) { break; } for (int fx = 0; fx <= radius; fx++) { int fxr = fx - radius; int offsetX = x + fxr; // Skip the column if (offsetX < 0) { continue; } if (offsetX < maxX) { // ReSharper disable once AccessToDisposedClosure Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); float sourceRed = color.X; float sourceBlue = color.Z; float sourceGreen = color.Y; int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); intensityBin[currentIntensity] += 1; blueBin[currentIntensity] += sourceBlue; greenBin[currentIntensity] += sourceGreen; redBin[currentIntensity] += sourceRed; if (intensityBin[currentIntensity] > maxIntensity) { maxIntensity = intensityBin[currentIntensity]; maxIndex = currentIntensity; } } } float red = MathF.Abs(redBin[maxIndex] / maxIntensity); float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); TPixel packed = default(TPixel); packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); targetPixels[x, y] = packed; } } }); source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TColor> source, Rectangle sourceRectangle) { int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int size = this.Value; int offset = this.Value / 2; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // Reset offset if necessary. if (minX > 0) { startX = 0; } if (minY > 0) { startY = 0; } // Get the range on the y-plane to choose from. IEnumerable <int> range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(source.Width, source.Height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) { Parallel.ForEach( range, this.ParallelOptions, y => { int offsetY = y - startY; int offsetPy = offset; for (int x = minX; x < maxX; x += size) { int offsetX = x - startX; int offsetPx = offset; // Make sure that the offset is within the boundary of the image. while (offsetY + offsetPy >= maxY) { offsetPy--; } while (x + offsetPx >= maxX) { offsetPx--; } // Get the pixel color in the centre of the soon to be pixelated area. // ReSharper disable AccessToDisposedClosure TColor pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; // For each pixel in the pixelate size, set it to the centre color. for (int l = offsetY; l < offsetY + size && l < maxY; l++) { for (int k = offsetX; k < offsetX + size && k < maxX; k++) { targetPixels[k, l] = pixel; } } } }); source.SwapPixelsBuffers(targetPixels); } } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int radius = this.BrushSize >> 1; int levels = this.Levels; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // Reset offset if necessary. if (minX > 0) { startX = 0; } using (var targetPixels = new PixelAccessor <TPixel>(source.Width, source.Height)) { source.CopyTo(targetPixels); Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> sourceRow = source.GetRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = startX; x < endX; x++) { int maxIntensity = 0; int maxIndex = 0; int[] intensityBin = new int[levels]; float[] redBin = new float[levels]; float[] blueBin = new float[levels]; float[] greenBin = new float[levels]; for (int fy = 0; fy <= radius; fy++) { int fyr = fy - radius; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); Span <TPixel> sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx <= radius; fx++) { int fxr = fx - radius; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); var vector = sourceOffsetRow[offsetX].ToVector4(); float sourceRed = vector.X; float sourceBlue = vector.Z; float sourceGreen = vector.Y; int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); intensityBin[currentIntensity] += 1; blueBin[currentIntensity] += sourceBlue; greenBin[currentIntensity] += sourceGreen; redBin[currentIntensity] += sourceRed; if (intensityBin[currentIntensity] > maxIntensity) { maxIntensity = intensityBin[currentIntensity]; maxIndex = currentIntensity; } } float red = MathF.Abs(redBin[maxIndex] / maxIntensity); float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } } }); source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override unsafe void OnApply(ImageBase <TColor> source, Rectangle sourceRectangle) { // Jump out, we'll deal with that later. if (source.Width == this.Width && source.Height == this.Height && sourceRectangle == this.ResizeRectangle) { 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; using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(width, height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) { Parallel.For( minY, maxY, this.ParallelOptions, y => { // Y coordinates of source points int originY = (int)(((y - startY) * heightFactor) + sourceY); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; } }); } // Break out now. source.SwapPixelsBuffers(targetPixels); 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 (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(width, height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) using (Buffer2D <Vector4> firstPassPixels = new Buffer2D <Vector4>(width, source.Height)) { firstPassPixels.Clear(); Parallel.For( 0, sourceRectangle.Bottom, this.ParallelOptions, y => { // TODO: Without Parallel.For() this buffer object could be reused: using (Buffer <Vector4> tempRowBuffer = new Buffer <Vector4>(sourcePixels.Width)) { BufferSpan <TColor> sourceRow = sourcePixels.GetRowSpan(y); BulkPixelOperations <TColor> .Instance.ToVector4( sourceRow, tempRowBuffer, sourceRow.Length); if (this.Compand) { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer); } } else { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); } } } }); // Now process the rows. Parallel.For( minY, maxY, this.ParallelOptions, y => { // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; if (this.Compand) { for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); destination = destination.Compress(); TColor d = default(TColor); d.PackFromVector4(destination); targetPixels[x, y] = d; } } else { for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); TColor d = default(TColor); d.PackFromVector4(destination); targetPixels[x, y] = d; } } }); } source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TColor> source, Rectangle sourceRectangle) { // Jump out, we'll deal with that later. if (source.Width == this.Width && source.Height == this.Height && sourceRectangle == this.ResizeRectangle) { return; } int width = this.Width; int height = this.Height; 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; using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(width, height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) { Parallel.For( minY, maxY, this.ParallelOptions, y => { // Y coordinates of source points int originY = (int)((y - startY) * heightFactor); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; } }); } // Break out now. source.SwapPixelsBuffers(targetPixels); 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. using (PixelAccessor <TColor> targetPixels = new PixelAccessor <TColor>(width, height)) { using (PixelAccessor <TColor> sourcePixels = source.Lock()) using (PixelAccessor <TColor> firstPassPixels = new PixelAccessor <TColor>(width, source.Height)) { Parallel.For( 0, sourceRectangle.Height, this.ParallelOptions, y => { for (int x = minX; x < maxX; x++) { // Ensure offsets are normalised for cropping and padding. Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; // Destination color components Vector4 destination = Vector4.Zero; for (int i = 0; i < horizontalValues.Length; i++) { Weight xw = horizontalValues[i]; destination += sourcePixels[xw.Index, y].ToVector4() * xw.Value; } TColor d = default(TColor); d.PackFromVector4(destination); firstPassPixels[x, y] = d; } }); // Now process the rows. Parallel.For( minY, maxY, this.ParallelOptions, y => { // Ensure offsets are normalised for cropping and padding. Weight[] verticalValues = this.VerticalWeights[y - startY].Values; for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = Vector4.Zero; for (int i = 0; i < verticalValues.Length; i++) { Weight yw = verticalValues[i]; destination += firstPassPixels[x, yw.Index].ToVector4() * yw.Value; } TColor d = default(TColor); d.PackFromVector4(destination); targetPixels[x, y] = d; } }); } source.SwapPixelsBuffers(targetPixels); } }
/// <inheritdoc/> protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle) { int kernelLength = this.KernelXY.Height; int radius = kernelLength >> 1; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int maxY = endY - 1; int maxX = endX - 1; using (var targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height)) { source.CopyTo(targetPixels); Parallel.For( startY, endY, this.ParallelOptions, y => { Span<TPixel> sourceRow = source.GetRowSpan(y); Span<TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = startX; x < endX; x++) { float red = 0; float green = 0; float blue = 0; // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelLength; fy++) { int fyr = fy - radius; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); Span<TPixel> sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelLength; fx++) { int fxr = fx - radius; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); var currentColor = sourceOffsetRow[offsetX].ToVector4(); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; green += currentColor.Y; blue += currentColor.Z; } } ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); source.SwapPixelsBuffers(targetPixels); } }