/// <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 <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); } }
/// <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); } }
/// <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; TPixel glowColor = this.GlowColor; Vector2 centre = Rectangle.Center(sourceRectangle); float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; // 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; } int width = maxX - minX; using (var rowColors = new Buffer <TPixel>(width)) { for (int i = 0; i < width; i++) { rowColors[i] = glowColor; } Parallel.For( minY, maxY, this.ParallelOptions, y => { using (var amounts = new Buffer <float>(width)) { int offsetY = y - startY; int offsetX = minX - startX; for (int i = 0; i < width; i++) { float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); } Span <TPixel> destination = source.GetRowSpan(offsetY).Slice(offsetX, width); this.blender.Blend(destination, destination, rowColors, amounts); } }); } }
/// <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; // 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; } Matrix4x4 matrix = this.Matrix; bool compand = this.Compand; using (PixelAccessor <TPixel> sourcePixels = source.Lock()) { Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> row = source.GetRowSpan(y - startY); for (int x = minX; x < maxX; x++) { ref TPixel pixel = ref row[x - startX]; var vector = pixel.ToVector4(); if (compand) { vector = vector.Expand(); } vector = Vector4.Transform(vector, matrix); pixel.PackFromVector4(compand ? vector.Compress() : vector); } }); } }
/// <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; // 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; } int width = maxX - minX; using (var colors = new Buffer <TPixel>(width)) using (var amount = new Buffer <float>(width)) { for (int i = 0; i < width; i++) { colors[i] = this.Value; amount[i] = this.options.BlendPercentage; } PixelBlender <TPixel> blender = PixelOperations <TPixel> .Instance.GetPixelBlender(this.options.BlenderMode); Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> destination = source.GetRowSpan(y - startY).Slice(minX - startX, width); // This switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one blender.Blend(destination, colors, destination, amount); }); } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { float contrast = (100F + this.Value) / 100F; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; var contrastVector = new Vector4(contrast, contrast, contrast, 1); var shiftVector = new Vector4(.5F, .5F, .5F, 1); // 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; } Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> row = source.GetRowSpan(y - startY); for (int x = minX; x < maxX; x++) { ref TPixel pixel = ref row[x - startX]; Vector4 vector = pixel.ToVector4().Expand(); vector -= shiftVector; vector *= contrastVector; vector += shiftVector; pixel.PackFromVector4(vector.Compress()); } }); }
/// <summary> /// Execute the first pass through the pixels in the image /// </summary> /// <param name="source">The source data</param> /// <param name="width">The width in pixels of the image.</param> /// <param name="height">The height in pixels of the image.</param> protected virtual void FirstPass(ImageBase <TPixel> source, int width, int height) { // Loop through each row for (int y = 0; y < height; y++) { Span <TPixel> row = source.GetRowSpan(y); // And loop through each column for (int x = 0; x < width; x++) { // Now I have the pixel, call the FirstPassQuantize function... this.InitialQuantizePixel(row[x]); } } }
/// <inheritdoc/> protected override void SecondPass(ImageBase <TPixel> source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. TPixel sourcePixel = source[0, 0]; TPixel previousPixel = sourcePixel; byte pixelValue = this.QuantizePixel(sourcePixel); TPixel[] colorPalette = this.GetPalette(); TPixel transformedPixel = colorPalette[pixelValue]; for (int y = 0; y < height; y++) { Span <TPixel> row = source.GetRowSpan(y); // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. if (!previousPixel.Equals(sourcePixel)) { // Quantize the pixel pixelValue = this.QuantizePixel(sourcePixel); // And setup the previous pointer previousPixel = sourcePixel; if (this.Dither) { transformedPixel = colorPalette[pixelValue]; } } if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false); } output[(y * source.Width) + x] = pixelValue; } } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { float brightness = this.Value / 100F; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; // 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; } Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> row = source.GetRowSpan(y - startY); for (int x = minX; x < maxX; x++) { ref TPixel pixel = ref row[x - startX]; // TODO: Check this with other formats. Vector4 vector = pixel.ToVector4().Expand(); Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); vector = new Vector4(transformed, vector.W); pixel.PackFromVector4(vector.Compress()); } }); }
public void Dither <TPixel>(ImageBase <TPixel> image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) where TPixel : struct, IPixel <TPixel> { if (replacePixel) { // Assign the transformed pixel to the array. image[x, y] = transformed; } // Calculate the error Vector4 error = source.ToVector4() - transformed.ToVector4(); // Loop through and distribute the error amongst neighbouring pixels. for (int row = 0; row < this.matrixHeight; row++) { int matrixY = y + row; if (matrixY > 0 && matrixY < height) { Span <TPixel> rowSpan = image.GetRowSpan(matrixY); for (int col = 0; col < this.matrixWidth; col++) { int matrixX = x + (col - this.startingOffset); if (matrixX > 0 && matrixX < width) { float coefficient = this.matrix[row, col]; // Good to disable here as we are not comparing mathematical output. // ReSharper disable once CompareOfFloatsByEqualityOperator if (coefficient == 0) { continue; } ref TPixel pixel = ref rowSpan[matrixX]; var offsetColor = pixel.ToVector4(); var coefficientVector = new Vector4(coefficient); Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; result.W = offsetColor.W; pixel.PackFromVector4(result); } } } }
/// <inheritdoc/> protected override void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { float threshold = this.Threshold; TPixel upper = this.UpperColor; TPixel lower = this.LowerColor; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; // 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; } Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> row = source.GetRowSpan(y - startY); for (int x = minX; x < maxX; x++) { ref TPixel color = ref row[x - startX]; // Any channel will do since it's Grayscale. color = color.ToVector4().X >= threshold ? upper : lower; } }); }
/// <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; Vector3 inverseVector = Vector3.One; // 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; } Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> row = source.GetRowSpan(y - startY); for (int x = minX; x < maxX; x++) { ref TPixel pixel = ref row[x - startX]; var vector = pixel.ToVector4(); Vector3 vector3 = inverseVector - new Vector3(vector.X, vector.Y, vector.Z); pixel.PackFromVector4(new Vector4(vector3, vector.W)); } }); }
/// <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; // 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; } var alphaVector = new Vector4(1, 1, 1, this.Value); Parallel.For( minY, maxY, this.ParallelOptions, y => { Span <TPixel> row = source.GetRowSpan(y - startY); for (int x = minX; x < maxX; x++) { ref TPixel pixel = ref row[x - startX]; pixel.PackFromVector4(pixel.ToVector4() * alphaVector); } }); }
/// <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; // 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; } byte[] bytes = new byte[4]; for (int y = minY; y < maxY; y++) { int offsetY = y - startY; Span <TPixel> row = source.GetRowSpan(offsetY); for (int x = minX; x < maxX; x++) { int offsetX = x - startX; TPixel sourceColor = row[offsetX]; this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); } } }
/// <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; // 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; } for (int y = minY; y < maxY; y++) { int offsetY = y - startY; Span <TPixel> row = source.GetRowSpan(offsetY); for (int x = minX; x < maxX; x++) { int offsetX = x - startX; TPixel sourceColor = row[offsetX]; TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; this.Diffuser.Dither(source, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); } } }
/// <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 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); Parallel.ForEach( range, this.ParallelOptions, y => { int offsetY = y - startY; int offsetPy = offset; // Make sure that the offset is within the boundary of the image. while (offsetY + offsetPy >= maxY) { offsetPy--; } Span <TPixel> row = source.GetRowSpan(offsetY + offsetPy); for (int x = minX; x < maxX; x += size) { int offsetX = x - startX; int offsetPx = offset; while (x + offsetPx >= maxX) { offsetPx--; } // Get the pixel color in the centre of the soon to be pixelated area. TPixel pixel = row[offsetX + offsetPx]; // 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++) { source[k, l] = pixel; } } } }); }
/// <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 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); } }
/// <inheritdoc/> protected override unsafe void OnApply(ImageBase <TPixel> 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 (var targetPixels = new PixelAccessor <TPixel>(width, height)) { Parallel.For( minY, maxY, this.ParallelOptions, y => { // Y coordinates of source points Span <TPixel> sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY)); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } }); // 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 (var targetPixels = new PixelAccessor <TPixel>(width, height)) { using (var 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 (var tempRowBuffer = new Buffer <Vector4>(source.Width)) { Span <Vector4> firstPassRow = firstPassPixels.GetRowSpan(y); Span <TPixel> sourceRow = source.GetRowSpan(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, this.ParallelOptions, y => { // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; Span <TPixel> targetRow = targetPixels.GetRowSpan(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); } } });
/// <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 (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 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); Span <TPixel> sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelXWidth; fx++) { int fxr = fx - radiusX; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); var currentColor = sourceOffsetRow[offsetX].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)); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); source.SwapPixelsBuffers(targetPixels); } }