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