Exemple #1
0
        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;
        }
Exemple #2
0
        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);
                            }
Exemple #5
0
        /// <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);
                    }
                });
            }
        }