예제 #1
0
        /// <inheritdoc />
        protected override void OnFrameApply(ImageFrame <TPixel> source)
        {
            var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());

            // We need a clean copy for each pass to start from
            using ImageFrame <TPixel> cleanCopy = source.Clone();

            using (var processor = new ConvolutionProcessor <TPixel>(this.Configuration, in this.kernels[0], true, this.Source, interest))
            {
                processor.Apply(source);
            }

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

            // Additional runs
            for (int i = 1; i < this.kernels.Length; i++)
            {
                using ImageFrame <TPixel> pass = cleanCopy.Clone();

                using (var processor = new ConvolutionProcessor <TPixel>(this.Configuration, in this.kernels[i], true, this.Source, interest))
                {
                    processor.Apply(pass);
                }

                var operation = new RowOperation(source.PixelBuffer, pass.PixelBuffer, interest);
                ParallelRowIterator.IterateRows(
                    this.Configuration,
                    interest,
                    in operation);
            }
        }
예제 #2
0
        /// <summary>
        /// Quantizes an image frame and return the resulting output pixels.
        /// </summary>
        /// <typeparam name="TFrameQuantizer">The type of frame quantizer.</typeparam>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="quantizer">The frame </param>
        /// <param name="source">The source image frame to quantize.</param>
        /// <param name="bounds">The bounds within the frame to quantize.</param>
        /// <returns>
        /// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source frame pixels.
        /// </returns>
        public static QuantizedFrame <TPixel> QuantizeFrame <TFrameQuantizer, TPixel>(
            ref TFrameQuantizer quantizer,
            ImageFrame <TPixel> source,
            Rectangle bounds)
            where TFrameQuantizer : struct, IFrameQuantizer <TPixel>
            where TPixel : unmanaged, IPixel <TPixel>
        {
            Guard.NotNull(source, nameof(source));
            var interest = Rectangle.Intersect(source.Bounds(), bounds);

            // Collect the palette. Required before the second pass runs.
            ReadOnlyMemory <TPixel> palette         = quantizer.BuildPalette(source, interest);
            MemoryAllocator         memoryAllocator = quantizer.Configuration.MemoryAllocator;

            var           quantizedFrame = new QuantizedFrame <TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
            Memory <byte> output         = quantizedFrame.GetWritablePixelMemory();

            if (quantizer.Options.Dither is null)
            {
                SecondPass(ref quantizer, source, interest, output, palette);
            }
            else
            {
                // We clone the image as we don't want to alter the original via error diffusion based dithering.
                using (ImageFrame <TPixel> clone = source.Clone())
                {
                    SecondPass(ref quantizer, clone, interest, output, palette);
                }
            }

            return(quantizedFrame);
        }
예제 #3
0
        /// <inheritdoc/>
        public IQuantizedFrame <TPixel> QuantizeFrame(ImageFrame <TPixel> image)
        {
            Guard.NotNull(image, nameof(image));

            // Get the size of the source image
            int height = image.Height;
            int width  = image.Width;

            // Call the FirstPass function if not a single pass algorithm.
            // For something like an Octree quantizer, this will run through
            // all image pixels, build a data structure, and create a palette.
            if (!this.singlePass)
            {
                this.FirstPass(image, width, height);
            }

            // Collect the palette. Required before the second pass runs.
            ReadOnlyMemory <TPixel> palette         = this.GetPalette();
            MemoryAllocator         memoryAllocator = this.Configuration.MemoryAllocator;

            this.paletteVector = memoryAllocator.Allocate <Vector4>(palette.Length);
            PixelOperations <TPixel> .Instance.ToVector4(
                this.Configuration,
                palette.Span,
                this.paletteVector.Memory.Span,
                PixelConversionModifiers.Scale);

            var quantizedFrame = new QuantizedFrame <TPixel>(memoryAllocator, width, height, palette);

            Span <byte> pixelSpan = quantizedFrame.GetWritablePixelSpan();

            if (this.Dither)
            {
                // We clone the image as we don't want to alter the original via dithering.
                using (ImageFrame <TPixel> clone = image.Clone())
                {
                    this.SecondPass(clone, pixelSpan, palette.Span, width, height);
                }
            }
            else
            {
                this.SecondPass(image, pixelSpan, palette.Span, width, height);
            }

            return(quantizedFrame);
        }
예제 #4
0
        /// <inheritdoc/>
        protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration)
        {
            using (ImageFrame <TPixel> temp = source.Clone())
            {
                // Detect the edges.
                new SobelProcessor <TPixel>().Apply(temp, sourceRectangle, configuration);

                // Apply threshold binarization filter.
                new BinaryThresholdProcessor <TPixel>(this.Threshold).Apply(temp, sourceRectangle, configuration);

                // Search for the first white pixels
                Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);

                if (rectangle == sourceRectangle)
                {
                    return;
                }

                new CropProcessor <TPixel>(rectangle).Apply(source, sourceRectangle, configuration);
            }
        }
        /// <inheritdoc/>
        public virtual QuantizedFrame <TPixel> QuantizeFrame(ImageFrame <TPixel> image)
        {
            Guard.NotNull(image, nameof(image));

            // Get the size of the source image
            int height = image.Height;
            int width  = image.Width;

            // Call the FirstPass function if not a single pass algorithm.
            // For something like an Octree quantizer, this will run through
            // all image pixels, build a data structure, and create a palette.
            if (!this.singlePass)
            {
                this.FirstPass(image, width, height);
            }

            // Collect the palette. Required before the second pass runs.
            TPixel[] palette = this.GetPalette();
            this.paletteVector = new Vector4[palette.Length];
            PixelOperations <TPixel> .Instance.ToScaledVector4(image.Configuration, palette, this.paletteVector);

            var quantizedFrame = new QuantizedFrame <TPixel>(image.MemoryAllocator, width, height, palette);

            if (this.Dither)
            {
                // We clone the image as we don't want to alter the original via dithering.
                using (ImageFrame <TPixel> clone = image.Clone())
                {
                    this.SecondPass(clone, quantizedFrame.GetPixelSpan(), palette, width, height);
                }
            }
            else
            {
                this.SecondPass(image, quantizedFrame.GetPixelSpan(), palette, width, height);
            }

            return(quantizedFrame);
        }
예제 #6
0
        /// <inheritdoc/>
        public virtual QuantizedImage <TPixel> Quantize(ImageFrame <TPixel> image, int maxColors)
        {
            Guard.NotNull(image, nameof(image));

            // Get the size of the source image
            int height = image.Height;
            int width  = image.Width;

            byte[] quantizedPixels = new byte[width * height];

            // Call the FirstPass function if not a single pass algorithm.
            // For something like an Octree quantizer, this will run through
            // all image pixels, build a data structure, and create a palette.
            if (!this.singlePass)
            {
                this.FirstPass(image, width, height);
            }

            // Collect the palette. Required before the second pass runs.
            TPixel[] colorPalette = this.GetPalette();

            if (this.Dither)
            {
                // We clone the image as we don't want to alter the original.
                using (ImageFrame <TPixel> clone = image.Clone())
                {
                    this.SecondPass(clone, quantizedPixels, width, height);
                }
            }
            else
            {
                this.SecondPass(image, quantizedPixels, width, height);
            }

            return(new QuantizedImage <TPixel>(width, height, colorPalette, quantizedPixels));
        }
예제 #7
0
        /// <inheritdoc />
        protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration)
        {
            Fast2DArray <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);

                        using (PixelAccessor <TPixel> passPixels = pass.Lock())
                            using (PixelAccessor <TPixel> targetPixels = source.Lock())
                            {
                                Parallel.For(
                                    minY,
                                    maxY,
                                    configuration.ParallelOptions,
                                    y =>
                                {
                                    int offsetY = y - shiftY;
                                    for (int x = minX; x < maxX; x++)
                                    {
                                        int offsetX = x - shiftX;

                                        // Grab the max components of the two pixels
                                        TPixel packed = default(TPixel);
                                        packed.PackFromVector4(Vector4.Max(passPixels[offsetX, offsetY].ToVector4(), targetPixels[offsetX, offsetY].ToVector4()));
                                        targetPixels[offsetX, offsetY] = packed;
                                    }
                                });
                            }
                    }
                }
            }
        }
        /// <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>(this.Configuration, 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>(this.Configuration, 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);
                                }
                            }
                        });
예제 #9
0
        /// <summary>
        /// Reads the frames colors, mapping indices to colors.
        /// </summary>
        /// <param name="indices">The indexed pixels.</param>
        /// <param name="colorTable">The color table containing the available colors.</param>
        /// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
        private unsafe void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor)
        {
            int imageWidth  = this.logicalScreenDescriptor.Width;
            int imageHeight = this.logicalScreenDescriptor.Height;

            ImageFrame <TColor, TPacked> previousFrame = null;

            ImageFrame <TColor, TPacked> currentFrame = null;

            ImageBase <TColor, TPacked> image;

            if (this.nextFrame == null)
            {
                image = this.decodedImage;

                image.Quality = colorTable.Length / 3;

                // This initializes the image to become fully transparent because the alpha channel is zero.
                image.InitPixels(imageWidth, imageHeight);
            }
            else
            {
                if (this.graphicsControlExtension != null &&
                    this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
                {
                    previousFrame = this.nextFrame;
                }

                currentFrame = this.nextFrame.Clone();

                image = currentFrame;

                this.RestoreToBackground(image);

                this.decodedImage.Frames.Add(currentFrame);
            }

            if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
            {
                image.FrameDelay = this.graphicsControlExtension.DelayTime;
            }

            int i                  = 0;
            int interlacePass      = 0; // The interlace pass
            int interlaceIncrement = 8; // The interlacing line increment
            int interlaceY         = 0; // The current interlaced line

            using (PixelAccessor <TColor, TPacked> pixelAccessor = image.Lock())
            {
                using (PixelArea <TColor, TPacked> pixelRow = new PixelArea <TColor, TPacked>(imageWidth, ComponentOrder.XYZW))
                {
                    for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
                    {
                        // Check if this image is interlaced.
                        int writeY; // the target y offset to write to
                        if (descriptor.InterlaceFlag)
                        {
                            // If so then we read lines at predetermined offsets.
                            // When an entire image height worth of offset lines has been read we consider this a pass.
                            // With each pass the number of offset lines changes and the starting line changes.
                            if (interlaceY >= descriptor.Height)
                            {
                                interlacePass++;
                                switch (interlacePass)
                                {
                                case 1:
                                    interlaceY = 4;
                                    break;

                                case 2:
                                    interlaceY         = 2;
                                    interlaceIncrement = 4;
                                    break;

                                case 3:
                                    interlaceY         = 1;
                                    interlaceIncrement = 2;
                                    break;
                                }
                            }

                            writeY = interlaceY + descriptor.Top;

                            interlaceY += interlaceIncrement;
                        }
                        else
                        {
                            writeY = y;
                        }

                        pixelRow.Reset();

                        byte *pixelBase = pixelRow.PixelBase;
                        for (int x = 0; x < descriptor.Width; x++)
                        {
                            int index = indices[i];

                            if (this.graphicsControlExtension == null ||
                                this.graphicsControlExtension.TransparencyFlag == false ||
                                this.graphicsControlExtension.TransparencyIndex != index)
                            {
                                int indexOffset = index * 3;
                                *(pixelBase + 0) = colorTable[indexOffset];
                                *(pixelBase + 1) = colorTable[indexOffset + 1];
                                *(pixelBase + 2) = colorTable[indexOffset + 2];
                                *(pixelBase + 3) = 255;
                            }

                            i++;
                            pixelBase += 4;
                        }

                        pixelAccessor.CopyFrom(pixelRow, writeY, descriptor.Left);
                    }
                }
            }

            if (previousFrame != null)
            {
                this.nextFrame = previousFrame;
                return;
            }

            if (currentFrame == null)
            {
                this.nextFrame = this.decodedImage.ToFrame();
            }
            else
            {
                this.nextFrame = currentFrame.Clone();
            }

            if (this.graphicsControlExtension != null &&
                this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
            {
                this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height);
            }
        }
        /// <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;

                        ParallelFor.WithConfiguration(
                            minY,
                            maxY,
                            configuration,
                            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);
                            }
                        });
예제 #11
0
 public TiffBiColorWriter(ImageFrame <TPixel> image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
     : base(image, memoryAllocator, configuration, entriesCollector)
 {
     // Convert image to black and white.
     this.imageBlackWhite = new Image <TPixel>(configuration, new ImageMetadata(), new[] { image.Clone() });
     this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg));
 }