Exemple #1
0
        /// <summary>
        /// Reads a run length encoded TGA image with a palette.
        /// </summary>
        /// <typeparam name="TPixel">The pixel type.</typeparam>
        /// <param name="width">The width of the image.</param>
        /// <param name="height">The height of the image.</param>
        /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
        /// <param name="palette">The color palette.</param>
        /// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
        /// <param name="origin">The image origin.</param>
        private void ReadPalettedRle <TPixel>(int width, int height, Buffer2D <TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            int bytesPerPixel = 1;

            using (IMemoryOwner <byte> buffer = this.memoryAllocator.Allocate <byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
            {
                TPixel      color      = default;
                var         alphaBits  = this.tgaMetadata.AlphaChannelBits;
                Span <byte> bufferSpan = buffer.GetSpan();
                this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1);

                for (int y = 0; y < height; y++)
                {
                    int           newY        = InvertY(y, height, origin);
                    Span <TPixel> pixelRow    = pixels.GetRowSpan(newY);
                    int           rowStartIdx = y * width * bytesPerPixel;
                    for (int x = 0; x < width; x++)
                    {
                        int idx = rowStartIdx + x;
                        switch (colorMapPixelSizeInBytes)
                        {
                        case 1:
                            color.FromL8(Unsafe.As <byte, L8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
                            break;

                        case 2:

                            Bgra5551 bgra = Unsafe.As <byte, Bgra5551>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]);
                            if (!this.hasAlpha)
                            {
                                // Set alpha value to 1, to treat it as opaque for Bgra5551.
                                bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
                            }

                            color.FromBgra5551(bgra);
                            break;

                        case 3:
                            color.FromBgr24(Unsafe.As <byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
                            break;

                        case 4:
                            if (this.hasAlpha)
                            {
                                color.FromBgra32(Unsafe.As <byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
                            }
                            else
                            {
                                var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3];
                                color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha));
                            }

                            break;
                        }

                        int newX = InvertX(x, width, origin);
                        pixelRow[newX] = color;
                    }
                }
            }
        }
Exemple #2
0
        /// <summary>
        /// Reads a uncompressed TGA image where each pixels has 16 bit.
        /// </summary>
        /// <typeparam name="TPixel">The pixel type.</typeparam>
        /// <param name="width">The width of the image.</param>
        /// <param name="height">The height of the image.</param>
        /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
        /// <param name="origin">The image origin.</param>
        private void ReadBgra16 <TPixel>(int width, int height, Buffer2D <TPixel> pixels, TgaImageOrigin origin)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            TPixel color   = default;
            bool   invertX = InvertX(origin);

            using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
            {
                for (int y = 0; y < height; y++)
                {
                    int           newY      = InvertY(y, height, origin);
                    Span <TPixel> pixelSpan = pixels.GetRowSpan(newY);

                    if (invertX)
                    {
                        for (int x = width - 1; x >= 0; x--)
                        {
                            this.currentStream.Read(this.scratchBuffer, 0, 2);
                            if (!this.hasAlpha)
                            {
                                this.scratchBuffer[1] |= 1 << 7;
                            }

                            if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
                            {
                                color.FromLa16(Unsafe.As <byte, La16>(ref this.scratchBuffer[0]));
                            }
                            else
                            {
                                color.FromBgra5551(Unsafe.As <byte, Bgra5551>(ref this.scratchBuffer[0]));
                            }

                            pixelSpan[x] = color;
                        }
                    }
                    else
                    {
                        this.currentStream.Read(row);
                        Span <byte> rowSpan = row.GetSpan();

                        if (!this.hasAlpha)
                        {
                            // We need to set the alpha component value to fully opaque.
                            for (int x = 1; x < rowSpan.Length; x += 2)
                            {
                                rowSpan[x] |= 1 << 7;
                            }
                        }

                        if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
                        {
                            PixelOperations <TPixel> .Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width);
                        }
                        else
                        {
                            PixelOperations <TPixel> .Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width);
                        }
                    }
                }
            }
        }
Exemple #3
0
        /// <inheritdoc/>
        protected override void OnApply(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
            Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds);

            // Convert from screen to world space.
            Matrix4x4.Invert(matrix, out matrix);

            if (this.Sampler is NearestNeighborResampler)
            {
                Parallel.For(
                    0,
                    height,
                    configuration.ParallelOptions,
                    y =>
                {
                    Span <TPixel> destRow = destination.GetPixelRowSpan(y);

                    for (int x = 0; x < width; x++)
                    {
                        var point = Point.Round(Vector2.Transform(new Vector2(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);

            MemoryManager memoryManager = configuration.MemoryManager;

            using (Buffer2D <float> yBuffer = memoryManager.Allocate2D <float>(yLength, height))
                using (Buffer2D <float> xBuffer = memoryManager.Allocate2D <float>(xLength, height))
                {
                    Parallel.For(
                        0,
                        height,
                        configuration.ParallelOptions,
                        y =>
                    {
                        Span <TPixel> destRow = destination.GetPixelRowSpan(y);
                        Span <float> ySpan    = yBuffer.GetRowSpan(y);
                        Span <float> xSpan    = 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.
                            // Precalulating 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, ySpan);
                                CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, xSpan);
                            }
                            else
                            {
                                CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ySpan);
                                CalculateWeightsScaleUp(minX, maxX, point.X, sampler, xSpan);
                            }

                            // Now multiply the results against the offsets
                            Vector4 sum = Vector4.Zero;
                            for (int yy = 0, j = minY; j <= maxY; j++, yy++)
                            {
                                float yWeight = ySpan[yy];

                                for (int xx = 0, i = minX; i <= maxX; i++, xx++)
                                {
                                    float xWeight = xSpan[xx];
                                    var vector    = source[i, j].ToVector4();

                                    // Values are first premultiplied to prevent darkening of edge pixels
                                    Vector4 multiplied = vector.Premultiply();
                                    sum += multiplied * xWeight * yWeight;
                                }
                            }

                            ref TPixel dest = ref destRow[x];

                            // Reverse the premultiplication
                            dest.PackFromVector4(sum.UnPremultiply());
                        }
                    });
                }
        }
Exemple #4
0
        /// <summary>
        /// Reads a uncompressed TGA image with a palette.
        /// </summary>
        /// <typeparam name="TPixel">The pixel type.</typeparam>
        /// <param name="width">The width of the image.</param>
        /// <param name="height">The height of the image.</param>
        /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
        /// <param name="palette">The color palette.</param>
        /// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
        /// <param name="origin">The image origin.</param>
        private void ReadPaletted <TPixel>(int width, int height, Buffer2D <TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            TPixel color   = default;
            bool   invertX = InvertX(origin);

            for (int y = 0; y < height; y++)
            {
                int           newY     = InvertY(y, height, origin);
                Span <TPixel> pixelRow = pixels.GetRowSpan(newY);

                switch (colorMapPixelSizeInBytes)
                {
                case 2:
                    if (invertX)
                    {
                        for (int x = width - 1; x >= 0; x--)
                        {
                            this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
                        }
                    }
                    else
                    {
                        for (int x = 0; x < width; x++)
                        {
                            this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
                        }
                    }

                    break;

                case 3:
                    if (invertX)
                    {
                        for (int x = width - 1; x >= 0; x--)
                        {
                            this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
                        }
                    }
                    else
                    {
                        for (int x = 0; x < width; x++)
                        {
                            this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
                        }
                    }

                    break;

                case 4:
                    if (invertX)
                    {
                        for (int x = width - 1; x >= 0; x--)
                        {
                            this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
                        }
                    }
                    else
                    {
                        for (int x = 0; x < width; x++)
                        {
                            this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
                        }
                    }

                    break;
                }
            }
        }
Exemple #5
0
            private Buffer2D <float> Render(IPath path)
            {
                Size size = Rectangle.Ceiling(path.Bounds).Size;

                size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2));

                float subpixelCount = 4;
                float offset        = 0.5f;

                if (this.Options.Antialias)
                {
                    offset        = 0f; // we are antialising skip offsetting as real antalising should take care of offset.
                    subpixelCount = this.Options.AntialiasSubpixelDepth;
                    if (subpixelCount < 4)
                    {
                        subpixelCount = 4;
                    }
                }

                // take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it.
                Buffer2D <float> fullBuffer = this.MemoryAllocator.Allocate2D <float>(size.Width + 1, size.Height + 1, AllocationOptions.Clean);

                using (IMemoryOwner <float> bufferBacking = this.MemoryAllocator.Allocate <float>(path.MaxIntersections))
                    using (IMemoryOwner <PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate <PointF>(size.Width))
                    {
                        float subpixelFraction      = 1f / subpixelCount;
                        float subpixelFractionPoint = subpixelFraction / subpixelCount;

                        for (int y = 0; y <= size.Height; y++)
                        {
                            Span <float> scanline      = fullBuffer.GetRowSpan(y);
                            bool         scanlineDirty = false;
                            float        yPlusOne      = y + 1;

                            for (float subPixel = (float)y; subPixel < yPlusOne; subPixel += subpixelFraction)
                            {
                                var           start            = new PointF(path.Bounds.Left - 1, subPixel);
                                var           end              = new PointF(path.Bounds.Right + 1, subPixel);
                                Span <PointF> intersectionSpan = rowIntersectionBuffer.GetSpan();
                                Span <float>  buffer           = bufferBacking.GetSpan();
                                int           pointsFound      = path.FindIntersections(start, end, intersectionSpan);

                                if (pointsFound == 0)
                                {
                                    // nothing on this line skip
                                    continue;
                                }

                                for (int i = 0; i < pointsFound && i < intersectionSpan.Length; i++)
                                {
                                    buffer[i] = intersectionSpan[i].X;
                                }

                                QuickSort.Sort(buffer.Slice(0, pointsFound));

                                for (int point = 0; point < pointsFound; point += 2)
                                {
                                    // points will be paired up
                                    float scanStart = buffer[point];
                                    float scanEnd   = buffer[point + 1];
                                    int   startX    = (int)MathF.Floor(scanStart + offset);
                                    int   endX      = (int)MathF.Floor(scanEnd + offset);

                                    if (startX >= 0 && startX < scanline.Length)
                                    {
                                        for (float x = scanStart; x < startX + 1; x += subpixelFraction)
                                        {
                                            scanline[startX] += subpixelFractionPoint;
                                            scanlineDirty     = true;
                                        }
                                    }

                                    if (endX >= 0 && endX < scanline.Length)
                                    {
                                        for (float x = endX; x < scanEnd; x += subpixelFraction)
                                        {
                                            scanline[endX] += subpixelFractionPoint;
                                            scanlineDirty   = true;
                                        }
                                    }

                                    int nextX = startX + 1;
                                    endX  = Math.Min(endX, scanline.Length); // reduce to end to the right edge
                                    nextX = Math.Max(nextX, 0);
                                    for (int x = nextX; x < endX; x++)
                                    {
                                        scanline[x]  += subpixelFraction;
                                        scanlineDirty = true;
                                    }
                                }
                            }

                            if (scanlineDirty)
                            {
                                if (!this.Options.Antialias)
                                {
                                    for (int x = 0; x < size.Width; x++)
                                    {
                                        if (scanline[x] >= 0.5)
                                        {
                                            scanline[x] = 1;
                                        }
                                        else
                                        {
                                            scanline[x] = 0;
                                        }
                                    }
                                }
                            }
                        }
                    }

                return(fullBuffer);
            }
        /// <inheritdoc />
        protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration)
        {
            DenseMatrix <float>[] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast };

            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);

            // we need a clean copy for each pass to start from
            using (ImageFrame <TPixel> cleanCopy = source.Clone())
            {
                new ConvolutionProcessor <TPixel>(kernels[0]).Apply(source, sourceRectangle, configuration);

                if (kernels.Length == 1)
                {
                    return;
                }

                int shiftY = startY;
                int shiftX = startX;

                // Reset offset if necessary.
                if (minX > 0)
                {
                    shiftX = 0;
                }

                if (minY > 0)
                {
                    shiftY = 0;
                }

                // Additional runs.
                // ReSharper disable once ForCanBeConvertedToForeach
                for (int i = 1; i < kernels.Length; i++)
                {
                    using (ImageFrame <TPixel> pass = cleanCopy.Clone())
                    {
                        new ConvolutionProcessor <TPixel>(kernels[i]).Apply(pass, sourceRectangle, configuration);

                        Buffer2D <TPixel> passPixels   = pass.PixelBuffer;
                        Buffer2D <TPixel> targetPixels = source.PixelBuffer;

                        Parallel.For(
                            minY,
                            maxY,
                            configuration.ParallelOptions,
                            y =>
                        {
                            int offsetY = y - shiftY;

                            ref TPixel passPixelsBase   = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY));
                            ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY));

                            for (int x = minX; x < maxX; x++)
                            {
                                int offsetX = x - shiftX;

                                // Grab the max components of the two pixels
                                ref TPixel currentPassPixel   = ref Unsafe.Add(ref passPixelsBase, offsetX);
                                ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX);

                                var pixelValue = Vector4.Max(
                                    currentPassPixel.ToVector4(),
                                    currentTargetPixel.ToVector4());

                                currentTargetPixel.PackFromVector4(pixelValue);
                            }
                        });
        /// <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 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)
            {
                Parallel.For(
                    0,
                    height,
                    configuration.ParallelOptions,
                    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);

            MemoryManager memoryManager = configuration.MemoryManager;

            using (Buffer2D <float> yBuffer = memoryManager.Allocate2D <float>(yLength, height))
                using (Buffer2D <float> xBuffer = memoryManager.Allocate2D <float>(xLength, height))
                {
                    Parallel.For(
                        0,
                        height,
                        configuration.ParallelOptions,
                        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));
Exemple #9
0
        /// <inheritdoc />
        protected override void OnFrameApply(ImageFrame <TPixel> source)
        {
            DenseMatrix <float>[] kernels = this.Kernels.Flatten();

            int startY = this.SourceRectangle.Y;
            int endY   = this.SourceRectangle.Bottom;
            int startX = this.SourceRectangle.X;
            int endX   = this.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);

            // we need a clean copy for each pass to start from
            using (ImageFrame <TPixel> cleanCopy = source.Clone())
            {
                using (var processor = new ConvolutionProcessor <TPixel>(kernels[0], true, this.Source, this.SourceRectangle))
                {
                    processor.Apply(source);
                }

                if (kernels.Length == 1)
                {
                    return;
                }

                int shiftY = startY;
                int shiftX = startX;

                // Reset offset if necessary.
                if (minX > 0)
                {
                    shiftX = 0;
                }

                if (minY > 0)
                {
                    shiftY = 0;
                }

                var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);

                // Additional runs.
                // ReSharper disable once ForCanBeConvertedToForeach
                for (int i = 1; i < kernels.Length; i++)
                {
                    using (ImageFrame <TPixel> pass = cleanCopy.Clone())
                    {
                        using (var processor = new ConvolutionProcessor <TPixel>(kernels[i], true, this.Source, this.SourceRectangle))
                        {
                            processor.Apply(pass);
                        }

                        Buffer2D <TPixel> passPixels   = pass.PixelBuffer;
                        Buffer2D <TPixel> targetPixels = source.PixelBuffer;

                        ParallelHelper.IterateRows(
                            workingRect,
                            this.Configuration,
                            rows =>
                        {
                            for (int y = rows.Min; y < rows.Max; y++)
                            {
                                int offsetY = y - shiftY;

                                ref TPixel passPixelsBase   = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY));
                                ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY));

                                for (int x = minX; x < maxX; x++)
                                {
                                    int offsetX = x - shiftX;

                                    // Grab the max components of the two pixels
                                    ref TPixel currentPassPixel   = ref Unsafe.Add(ref passPixelsBase, offsetX);
                                    ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX);

                                    var pixelValue = Vector4.Max(
                                        currentPassPixel.ToVector4(),
                                        currentTargetPixel.ToVector4());

                                    currentTargetPixel.FromVector4(pixelValue);
                                }
                            }
                        });
        /// <inheritdoc/>
        protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration)
        {
            int kernelYHeight = this.KernelY.Rows;
            int kernelYWidth  = this.KernelY.Columns;
            int kernelXHeight = this.KernelX.Rows;
            int kernelXWidth  = this.KernelX.Columns;
            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 (Buffer2D <TPixel> targetPixels = configuration.MemoryManager.Allocate2D <TPixel>(source.Width, source.Height))
            {
                source.CopyTo(targetPixels);

                Parallel.For(
                    startY,
                    endY,
                    configuration.ParallelOptions,
                    y =>
                {
                    Span <TPixel> sourceRow = source.GetPixelRowSpan(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.GetPixelRowSpan(offsetY);

                            for (int fx = 0; fx < kernelXWidth; fx++)
                            {
                                int fxr     = fx - radiusX;
                                int offsetX = x + fxr;

                                offsetX = offsetX.Clamp(0, maxX);
                                Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply();

                                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).UnPremultiply());
                    }
                });

                Buffer2D <TPixel> .SwapContents(source.PixelBuffer, targetPixels);
            }
        }
Exemple #11
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;

            PixelConversionModifiers conversionModifiers = PixelConversionModifiers.Premultiply;

            if (this.Compand)
            {
                conversionModifiers |= PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand;
            }

            // 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).Slice(sourceX);
                        Span <Vector4> tempRowSpan = tempRowBuffer.Span.Slice(sourceX);

                        PixelOperations <TPixel> .Instance.ToVector4(configuration, sourceRow, tempRowSpan, conversionModifiers);

                        ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y];

                        for (int x = minX; x < maxX; x++)
                        {
                            ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX);
                            Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = kernel.Convolve(tempRowSpan);
                        }
                    }
                });

                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 kernel = this.verticalKernelMap.GetKernel(y - startY);

                        ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan);

                        for (int x = 0; x < width; x++)
                        {
                            Span <Vector4> firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY);

                            // Destination color components
                            Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn);
                        }

                        Span <TPixel> targetRowSpan = destination.GetPixelRowSpan(y);

                        PixelOperations <TPixel> .Instance.FromVector4Destructive(configuration, tempRowSpan, targetRowSpan, conversionModifiers);
                    }
                });
            }
        }
        /// <summary>
        /// Reads a run length encoded TGA image.
        /// </summary>
        /// <typeparam name="TPixel">The pixel type.</typeparam>
        /// <param name="width">The width of the image.</param>
        /// <param name="height">The height of the image.</param>
        /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
        /// <param name="bytesPerPixel">The bytes per pixel.</param>
        /// <param name="origin">The image origin.</param>
        private void ReadRle <TPixel>(int width, int height, Buffer2D <TPixel> pixels, int bytesPerPixel, TgaImageOrigin origin)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            TPixel color     = default;
            var    alphaBits = this.tgaMetadata.AlphaChannelBits;

            using (IMemoryOwner <byte> buffer = this.memoryAllocator.Allocate <byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
            {
                Span <byte> bufferSpan = buffer.GetSpan();
                this.UncompressRle(width, height, bufferSpan, bytesPerPixel);
                for (int y = 0; y < height; y++)
                {
                    int           newY        = InvertY(y, height, origin);
                    Span <TPixel> pixelRow    = pixels.GetRowSpan(newY);
                    int           rowStartIdx = y * width * bytesPerPixel;
                    for (int x = 0; x < width; x++)
                    {
                        int idx = rowStartIdx + (x * bytesPerPixel);
                        switch (bytesPerPixel)
                        {
                        case 1:
                            color.FromL8(Unsafe.As <byte, L8>(ref bufferSpan[idx]));
                            break;

                        case 2:
                            if (!this.hasAlpha)
                            {
                                // Set alpha value to 1, to treat it as opaque for Bgra5551.
                                bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128);
                            }

                            if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite)
                            {
                                color.FromLa16(Unsafe.As <byte, La16>(ref bufferSpan[idx]));
                            }
                            else
                            {
                                color.FromBgra5551(Unsafe.As <byte, Bgra5551>(ref bufferSpan[idx]));
                            }

                            break;

                        case 3:
                            color.FromBgr24(Unsafe.As <byte, Bgr24>(ref bufferSpan[idx]));
                            break;

                        case 4:
                            if (this.hasAlpha)
                            {
                                color.FromBgra32(Unsafe.As <byte, Bgra32>(ref bufferSpan[idx]));
                            }
                            else
                            {
                                var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3];
                                color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha));
                            }

                            break;
                        }

                        int newX = InvertX(x, width, origin);
                        pixelRow[newX] = color;
                    }
                }
            }
        }
        /// <inheritdoc/>
        protected override void OnFrameApply(
            ImageFrame <TPixel> source,
            Rectangle sourceRectangle,
            Configuration configuration)
        {
            if (this.BrushSize <= 0 || this.BrushSize > source.Height || this.BrushSize > source.Width)
            {
                throw new ArgumentOutOfRangeException(nameof(this.BrushSize));
            }

            int startY = sourceRectangle.Y;
            int endY   = sourceRectangle.Bottom;
            int startX = sourceRectangle.X;
            int endX   = sourceRectangle.Right;
            int maxY   = endY - 1;
            int maxX   = endX - 1;

            int radius = this.BrushSize >> 1;
            int levels = this.Levels;

            using (Buffer2D <TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size()))
            {
                source.CopyTo(targetPixels);

                var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
                ParallelHelper.IterateRows(
                    workingRect,
                    configuration,
                    rows =>
                {
                    for (int y = rows.Min; y < rows.Max; y++)
                    {
                        Span <TPixel> sourceRow = source.GetPixelRowSpan(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.GetPixelRowSpan(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]++;
                                    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.FromVector4(
                                    new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
                            }
                        }
                    }
                });

                Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels);
            }
        }
        /// <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
            Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds);

            // Convert from screen to world space.
            Matrix4x4.Invert(matrix, out matrix);
            const float Epsilon = 0.0000001F;

            if (this.Sampler is NearestNeighborResampler)
            {
                ParallelFor.WithConfiguration(
                    0,
                    height,
                    configuration,
                    y =>
                {
                    Span <TPixel> destRow = destination.GetPixelRowSpan(y);

                    for (int x = 0; x < width; x++)
                    {
                        var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);

                        float z = MathF.Max(v3.Z, Epsilon);
                        int px  = (int)MathF.Round(v3.X / z);
                        int py  = (int)MathF.Round(v3.Y / z);

                        if (sourceBounds.Contains(px, py))
                        {
                            destRow[x] = source[px, py];
                        }
                    }
                });

                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;

            // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable:
            var radius = new Vector4(xRadiusScale.radius, yRadiusScale.radius, 0, 0);

            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))
                {
                    ParallelFor.WithConfiguration(
                        0,
                        height,
                        configuration,
                        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));
Exemple #15
0
        /// <summary>
        /// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel.
        /// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
        /// <param name="width">The width of the bitmap.</param>
        /// <param name="height">The height of the bitmap.</param>
        /// <param name="inverted">Whether the bitmap is inverted.</param>
        private void ReadRgb32Slow <TPixel>(Buffer2D <TPixel> pixels, int width, int height, bool inverted)
            where TPixel : struct, IPixel <TPixel>
        {
            int padding = CalculatePadding(width, 4);

            using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding))
                using (IMemoryOwner <Bgra32> bgraRow = this.memoryAllocator.Allocate <Bgra32>(width))
                {
                    Span <Bgra32> bgraRowSpan     = bgraRow.GetSpan();
                    long          currentPosition = this.stream.Position;
                    bool          hasAlpha        = false;

                    // Loop though the rows checking each pixel. We start by assuming it's
                    // an BGR0 image. If we hit a non-zero alpha value, then we know it's
                    // actually a BGRA image, and change tactics accordingly.
                    for (int y = 0; y < height; y++)
                    {
                        this.stream.Read(row);

                        PixelOperations <Bgra32> .Instance.FromBgra32Bytes(
                            this.configuration,
                            row.GetSpan(),
                            bgraRowSpan,
                            width);

                        // Check each pixel in the row to see if it has an alpha value.
                        for (int x = 0; x < width; x++)
                        {
                            Bgra32 bgra = bgraRowSpan[x];
                            if (bgra.A > 0)
                            {
                                hasAlpha = true;
                                break;
                            }
                        }

                        if (hasAlpha)
                        {
                            break;
                        }
                    }

                    // Reset our stream for a second pass.
                    this.stream.Position = currentPosition;

                    // Process the pixels in bulk taking the raw alpha component value.
                    if (hasAlpha)
                    {
                        for (int y = 0; y < height; y++)
                        {
                            this.stream.Read(row);

                            int           newY      = Invert(y, height, inverted);
                            Span <TPixel> pixelSpan = pixels.GetRowSpan(newY);

                            PixelOperations <TPixel> .Instance.FromBgra32Bytes(
                                this.configuration,
                                row.GetSpan(),
                                pixelSpan,
                                width);
                        }

                        return;
                    }

                    // Slow path. We need to set each alpha component value to fully opaque.
                    for (int y = 0; y < height; y++)
                    {
                        this.stream.Read(row);
                        PixelOperations <Bgra32> .Instance.FromBgra32Bytes(
                            this.configuration,
                            row.GetSpan(),
                            bgraRowSpan,
                            width);

                        int           newY      = Invert(y, height, inverted);
                        Span <TPixel> pixelSpan = pixels.GetRowSpan(newY);

                        for (int x = 0; x < width; x++)
                        {
                            Bgra32 bgra = bgraRowSpan[x];
                            bgra.A = byte.MaxValue;
                            ref TPixel pixel = ref pixelSpan[x];
                            pixel.FromBgra32(bgra);
                        }
                    }
                }