public void Convolve <TPixel>( Vector2 transformedPoint, int column, ref float ySpanRef, ref float xSpanRef, Buffer2D <TPixel> sourcePixels, Span <Vector4> targetRow) where TPixel : struct, IPixel <TPixel> { // Clamp sampling pixel radial extents to the source image edges Vector2 minXY = transformedPoint - this.extents; Vector2 maxXY = transformedPoint + this.extents; // left, top, right, bottom var extents = new Vector4( MathF.Ceiling(minXY.X - .5F), MathF.Ceiling(minXY.Y - .5F), MathF.Floor(maxXY.X + .5F), MathF.Floor(maxXY.Y + .5F)); extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents); int left = (int)extents.X; int top = (int)extents.Y; int right = (int)extents.Z; int bottom = (int)extents.W; if (left == right || top == bottom) { return; } this.CalculateWeights(top, bottom, transformedPoint.Y, ref ySpanRef); this.CalculateWeights(left, right, transformedPoint.X, ref xSpanRef); Vector4 sum = Vector4.Zero; for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) { float yWeight = Unsafe.Add(ref ySpanRef, kernelY); for (int kernelX = 0, x = left; x <= right; x++, kernelX++) { float xWeight = Unsafe.Add(ref xSpanRef, kernelX); // Values are first premultiplied to prevent darkening of edge pixels. var current = sourcePixels[x, y].ToVector4(); Vector4Utils.Premultiply(ref current); sum += current * xWeight * yWeight; } } // Reverse the premultiplication Vector4Utils.UnPremultiply(ref sum); targetRow[column] = sum; }
public void UnPremultiply_VectorSpan(int length) { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); Vector4[] expected = source.Select(v => { Vector4Utils.UnPremultiply(ref v); return(v); }).ToArray(); Vector4Utils.UnPremultiply(source); Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); }
internal static void ApplyBackwardConversionModifiers(Span <Vector4> vectors, PixelConversionModifiers modifiers) { if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) { Vector4Utils.UnPremultiply(vectors); } if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) { SRgbCompanding.Compress(vectors); } }
/// <inheritdoc/> protected override void OnFrameApply( ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Rectangle sourceRectangle, Configuration configuration) { int height = this.TargetDimensions.Height; int width = this.TargetDimensions.Width; Rectangle sourceBounds = source.Bounds(); var targetBounds = new Rectangle(0, 0, width, height); // Since could potentially be resizing the canvas we might need to re-calculate the matrix Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); // Convert from screen to world space. Matrix3x2.Invert(matrix, out matrix); if (this.Sampler is NearestNeighborResampler) { ParallelHelper.IterateRows( targetBounds, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> destRow = destination.GetPixelRowSpan(y); for (int x = 0; x < width; x++) { var point = Point.Transform(new Point(x, y), matrix); if (sourceBounds.Contains(point.X, point.Y)) { destRow[x] = source[point.X, point.Y]; } } } }); return; } int maxSourceX = source.Width - 1; int maxSourceY = source.Height - 1; (float radius, float scale, float ratio)xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width); (float radius, float scale, float ratio)yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height); float xScale = xRadiusScale.scale; float yScale = yRadiusScale.scale; var radius = new Vector2(xRadiusScale.radius, yRadiusScale.radius); IResampler sampler = this.Sampler; var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY); int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); MemoryAllocator memoryAllocator = configuration.MemoryAllocator; using (Buffer2D <float> yBuffer = memoryAllocator.Allocate2D <float>(yLength, height)) using (Buffer2D <float> xBuffer = memoryAllocator.Allocate2D <float>(xLength, height)) { ParallelHelper.IterateRows( targetBounds, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y)); for (int x = 0; x < width; x++) { // Use the single precision position to calculate correct bounding pixels // otherwise we get rogue pixels outside of the bounds. var point = Vector2.Transform(new Vector2(x, y), matrix); // Clamp sampling pixel radial extents to the source image edges Vector2 maxXY = point + radius; Vector2 minXY = point - radius; // max, maxY, minX, minY var extents = new Vector4( MathF.Floor(maxXY.X + .5F), MathF.Floor(maxXY.Y + .5F), MathF.Ceiling(minXY.X - .5F), MathF.Ceiling(minXY.Y - .5F)); int right = (int)extents.X; int bottom = (int)extents.Y; int left = (int)extents.Z; int top = (int)extents.W; extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); int maxX = (int)extents.X; int maxY = (int)extents.Y; int minX = (int)extents.Z; int minY = (int)extents.W; if (minX == maxX || minY == maxY) { continue; } // It appears these have to be calculated on-the-fly. // Precalculating transformed weights would require prior knowledge of every transformed pixel location // since they can be at sub-pixel positions on both axis. // I've optimized where I can but am always open to suggestions. if (yScale > 1 && xScale > 1) { CalculateWeightsDown( top, bottom, minY, maxY, point.Y, sampler, yScale, ref ySpanRef, yLength); CalculateWeightsDown( left, right, minX, maxX, point.X, sampler, xScale, ref xSpanRef, xLength); } else { CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef); CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef); } // Now multiply the results against the offsets Vector4 sum = Vector4.Zero; for (int yy = 0, j = minY; j <= maxY; j++, yy++) { float yWeight = Unsafe.Add(ref ySpanRef, yy); for (int xx = 0, i = minX; i <= maxX; i++, xx++) { float xWeight = Unsafe.Add(ref xSpanRef, xx); // Values are first premultiplied to prevent darkening of edge pixels var current = source[i, j].ToVector4(); Vector4Utils.Premultiply(ref current); sum += current * xWeight * yWeight; } } ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); // Reverse the premultiplication Vector4Utils.UnPremultiply(ref sum); dest.FromVector4(sum); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Rectangle sourceRectangle, Configuration configuration) { // Handle resize dimensions identical to the original if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.ResizeRectangle) { // The cloned will be blank here copy all the pixel data over source.GetPixelSpan().CopyTo(destination.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) { var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; ParallelHelper.IterateRows( workingRect, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { // Y coordinates of source points Span <TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); Span <TPixel> targetRow = destination.GetPixelRowSpan(y); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } } }); return; } int sourceHeight = source.Height; // 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 (Buffer2D <Vector4> firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D <Vector4>(sourceHeight, width)) { firstPassPixelsTransposed.MemorySource.Clear(); var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom); ParallelHelper.IterateRowsWithTempBuffer <Vector4>( processColsRect, configuration, (rows, tempRowBuffer) => { for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); Span <Vector4> tempRowSpan = tempRowBuffer.Span; PixelOperations <TPixel> .Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); Vector4Utils.Premultiply(tempRowSpan); ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; if (this.Compand) { SRgbCompanding.Expand(tempRowSpan); } for (int x = minX; x < maxX; x++) { ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX]; Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = window.Convolve(tempRowSpan, sourceX); } } }); var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY); // Now process the rows. ParallelHelper.IterateRowsWithTempBuffer <Vector4>( processRowsRect, configuration, (rows, tempRowBuffer) => { Span <Vector4> tempRowSpan = tempRowBuffer.Span; for (int y = rows.Min; y < rows.Max; y++) { // Ensure offsets are normalized for cropping and padding. ResizeKernel window = this.verticalKernelMap.Kernels[y - startY]; ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); for (int x = 0; x < width; x++) { Span <Vector4> firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x); // Destination color components Unsafe.Add(ref tempRowBase, x) = window.Convolve(firstPassColumn, sourceY); } Vector4Utils.UnPremultiply(tempRowSpan); if (this.Compand) { SRgbCompanding.Compress(tempRowSpan); } Span <TPixel> targetRowSpan = destination.GetPixelRowSpan(y); PixelOperations <TPixel> .Instance.PackFromVector4(tempRowSpan, targetRowSpan, tempRowSpan.Length); } }); } }