/// <summary> /// Rotates the image 90 degrees clockwise at the centre point. /// </summary> /// <param name="source">The source image.</param> /// <param name="destination">The destination image.</param> /// <param name="configuration">The configuration.</param> private void Rotate90(ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Configuration configuration) { int width = source.Width; int height = source.Height; Rectangle destinationBounds = destination.Bounds(); ParallelHelper.IterateRows( source.Bounds(), configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); int newX = height - y - 1; for (int x = 0; x < width; x++) { // TODO: Optimize this: if (destinationBounds.Contains(newX, x)) { destination[newX, x] = sourceRow[x]; } } } }); }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { float threshold = this.Threshold * 255F; TPixel upper = this.UpperColor; TPixel lower = this.LowerColor; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; int startX = interest.X; int endX = interest.Right; bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); Parallel.For( startY, endY, configuration.ParallelOptions, y => { Span <TPixel> row = source.GetPixelRowSpan(y); var rgba = default(Rgba32); for (int x = startX; x < endX; x++) { ref TPixel color = ref row[x]; color.ToRgba32(ref rgba); // Convert to grayscale using ITU-R Recommendation BT.709 if required float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); color = luminance >= threshold ? upper : lower; } }); }
/// <summary> /// Creates the quantized frame. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="options">The options.</param> /// <param name="image">The image.</param> public static IndexedImageFrame <TPixel> CreateQuantizedFrame <TPixel>( PngEncoderOptions options, Image <TPixel> image) where TPixel : unmanaged, IPixel <TPixel> { if (options.ColorType != PngColorType.Palette) { return(null); } byte bits = (byte)options.BitDepth; if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bits) == -1) { throw new NotSupportedException("Bit depth is not supported or not valid."); } // Use the metadata to determine what quantization depth to use if no quantizer has been set. if (options.Quantizer is null) { var maxColors = ImageMaths.GetColorCountForBitDepth(bits); options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); } // Create quantized frame returning the palette and set the bit depth. using (IFrameQuantizer <TPixel> frameQuantizer = options.Quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration())) { ImageFrame <TPixel> frame = image.Frames.RootFrame; return(frameQuantizer.QuantizeFrame(frame, frame.Bounds())); } }
public TiffPaletteWriter( ImageFrame <TPixel> image, IQuantizer quantizer, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector, int bitsPerPixel) : base(image, memoryAllocator, configuration, entriesCollector) { DebugGuard.NotNull(quantizer, nameof(quantizer)); DebugGuard.NotNull(configuration, nameof(configuration)); DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); this.BitsPerPixel = bitsPerPixel; this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; this.colorPaletteSize = this.maxColors * 3; this.colorPaletteBytes = this.colorPaletteSize * 2; using IQuantizer <TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer <TPixel>(this.Configuration, new QuantizerOptions() { MaxColors = this.maxColors }); this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); this.AddColorMapTag(); }
/// <summary> /// Rotates the image 270 degrees clockwise at the centre point. /// </summary> /// <param name="source">The source image.</param> /// <param name="destination">The destination image.</param> /// <param name="configuration">The configuration.</param> private void Rotate270(ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Configuration configuration) { int width = source.Width; int height = source.Height; Rectangle destinationBounds = destination.Bounds(); Parallel.For( 0, height, configuration.ParallelOptions, y => { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); for (int x = 0; x < width; x++) { int newX = height - y - 1; newX = height - newX - 1; int newY = width - x - 1; if (destinationBounds.Contains(newX, newY)) { destination[newX, newY] = sourceRow[x]; } } }); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startX = interest.X; ColorMatrix matrix = this.definition.Matrix; ParallelHelper.IterateRowsWithTempBuffer <Vector4>( interest, configuration, (rows, vectorBuffer) => { for (int y = rows.Min; y < rows.Max; y++) { Span <Vector4> vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; Span <TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); PixelOperations <TPixel> .Instance.ToVector4(configuration, rowSpan, vectorSpan); Vector4Utils.Transform(vectorSpan, ref matrix); PixelOperations <TPixel> .Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan); } }); }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); Rectangle sourceBounds = source.Bounds(); using (var targetPixels = new PixelAccessor <TPixel>(width, height)) { Parallel.For( 0, height, configuration.ParallelOptions, y => { Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = 0; x < width; x++) { var transformedPoint = Point.Skew(new Point(x, y), matrix); if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y)) { targetRow[x] = source[transformedPoint.X, transformedPoint.Y]; } } }); source.SwapPixelsBuffers(targetPixels); } }
/// <summary> /// Creates the quantized frame. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="options">The options.</param> /// <param name="image">The image.</param> public static IndexedImageFrame <TPixel> CreateQuantizedFrame <TPixel>( PngEncoderOptions options, Image <TPixel> image) where TPixel : unmanaged, IPixel <TPixel> { if (options.ColorType != PngColorType.Palette) { return(null); } // Use the metadata to determine what quantization depth to use if no quantizer has been set. if (options.Quantizer is null) { byte bits = (byte)options.BitDepth; var maxColors = ImageMaths.GetColorCountForBitDepth(bits); options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); } // Create quantized frame returning the palette and set the bit depth. using (IQuantizer <TPixel> frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer <TPixel>(image.GetConfiguration())) { ImageFrame <TPixel> frame = image.Frames.RootFrame; return(frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { TPixel glowColor = this.definition.GlowColor.ToPixel <TPixel>(); float blendPercent = this.definition.GraphicsOptions.BlendPercentage; var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); Vector2 center = Rectangle.Center(interest); float finalRadius = this.definition.Radius.Calculate(interest.Size); float maxDistance = finalRadius > 0 ? MathF.Min(finalRadius, interest.Width * .5F) : interest.Width * .5F; Configuration configuration = this.Configuration; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner <TPixel> rowColors = allocator.Allocate <TPixel>(interest.Width); rowColors.GetSpan().Fill(glowColor); var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); ParallelRowIterator.IterateRows <RowOperation, float>( configuration, interest, in operation); }
/// <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); } }
/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); Configuration configuration = this.Configuration; using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); ReadOnlySpan <TPixel> paletteSpan = quantized.Palette.Span; int offsetY = interest.Top; int offsetX = interest.Left; Buffer2D <TPixel> sourceBuffer = source.PixelBuffer; for (int y = interest.Y; y < interest.Height; y++) { Span <TPixel> row = sourceBuffer.DangerousGetRowSpan(y); ReadOnlySpan <byte> quantizedRow = quantized.DangerousGetRowSpan(y - offsetY); for (int x = interest.Left; x < interest.Right; x++) { row[x] = paletteSpan[quantizedRow[x - offsetX]]; } } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; int startX = interest.X; int endX = interest.Right; Matrix4x4 matrix = this.Matrix; ParallelFor.WithConfiguration( startY, endY, configuration, y => { Span <TPixel> row = source.GetPixelRowSpan(y); for (int x = startX; x < endX; x++) { ref TPixel pixel = ref row[x]; var vector = Vector4.Transform(pixel.ToVector4(), matrix); pixel.PackFromVector4(vector); } }); }
/// <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); }
private void EncodeLocal <TPixel>(Image <TPixel> image, IndexedImageFrame <TPixel> quantized, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { ImageFrame <TPixel> previousFrame = null; GifFrameMetadata previousMeta = null; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame <TPixel> frame = image.Frames[i]; ImageFrameMetadata metadata = frame.Metadata; GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); if (quantized is null) { // Allow each frame to be encoded at whatever color depth the frame designates if set. if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength && frameMetadata.ColorTableLength > 0) { var options = new QuantizerOptions { Dither = this.quantizer.Options.Dither, DitherScale = this.quantizer.Options.DitherScale, MaxColors = frameMetadata.ColorTableLength }; using IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration, options); quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } else { using IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration); quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream); this.WriteImageDescriptor(frame, true, stream); this.WriteColorTable(quantized, stream); this.WriteImageData(quantized, stream); quantized.Dispose(); quantized = null; // So next frame can regenerate it previousFrame = frame; previousMeta = frameMetadata; } }
protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var operation = new OpaqueRowOperation(this.Configuration, source.PixelBuffer, interest); ParallelRowIterator.IterateRows <OpaqueRowOperation, Vector4>(this.Configuration, interest, in operation); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { using (Buffer2D <TPixel> firstPassPixels = configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size())) { this.ApplyConvolution(firstPassPixels, source.PixelBuffer, source.Bounds(), this.KernelX, configuration); this.ApplyConvolution(source.PixelBuffer, firstPassPixels, sourceRectangle, this.KernelY, configuration); } }
/// <summary> /// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> private static void AotCompilePaletteQuantizer <TPixel>() where TPixel : unmanaged, IPixel <TPixel> { using (var test = (PaletteFrameQuantizer <TPixel>) new PaletteQuantizer(Array.Empty <Color>()).CreateFrameQuantizer <TPixel>(Configuration.Default)) { var frame = new ImageFrame <TPixel>(Configuration.Default, 1, 1); test.QuantizeFrame(frame, frame.Bounds()); } }
/// <summary> /// This method pre-seeds the WuQuantizer in the AoT compiler for iOS. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> private static void AotCompileWuQuantizer <TPixel>() where TPixel : unmanaged, IPixel <TPixel> { using (var test = new WuFrameQuantizer <TPixel>(Configuration.Default, new WuQuantizer().Options)) { var frame = new ImageFrame <TPixel>(Configuration.Default, 1, 1); test.QuantizeFrame(frame, frame.Bounds()); } }
/// <summary> /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> /// <param name="configuration">The configuration.</param> private void FlipY(ImageFrame <TPixel> source, Configuration configuration) { var operation = new RowOperation(source); ParallelRowIterator.IterateRows( configuration, source.Bounds(), in operation); }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { ParallelOptions parallelOptions = configuration.ParallelOptions; using (Buffer2D <TPixel> firstPassPixels = configuration.MemoryManager.Allocate2D <TPixel>(source.Size())) { this.ApplyConvolution(firstPassPixels, source.PixelBuffer, source.Bounds(), this.KernelX, parallelOptions); this.ApplyConvolution(source.PixelBuffer, firstPassPixels, sourceRectangle, this.KernelY, parallelOptions); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var operation = new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration); ParallelRowIterator.IterateRows <RowIntervalOperation, Vector4>( this.Configuration, interest, in operation); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); this.dither.ApplyPaletteDither( this.Configuration, this.palette.Memory, source, interest, this.ditherScale); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F); bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; int startX = interest.X; int endX = interest.Right; // Collect the values before looping so we can reduce our calculation count for identical sibling pixels TPixel sourcePixel = source[startX, startY]; TPixel previousPixel = sourcePixel; PixelPair <TPixel> pair = this.GetClosestPixelPair(ref sourcePixel); Rgba32 rgba = default; sourcePixel.ToRgba32(ref rgba); // Convert to grayscale using ITU-R Recommendation BT.709 if required byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); for (int y = startY; y < endY; y++) { Span <TPixel> row = source.GetPixelRowSpan(y); for (int x = startX; x < endX; x++) { sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. if (!previousPixel.Equals(sourcePixel)) { pair = this.GetClosestPixelPair(ref sourcePixel); // No error to spread, exact match. if (sourcePixel.Equals(pair.First)) { continue; } sourcePixel.ToRgba32(ref rgba); luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); // Setup the previous pointer previousPixel = sourcePixel; } TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); } } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { Rgba32 rgba = default; bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; int startX = interest.X; int endX = interest.Right; // Collect the values before looping so we can reduce our calculation count for identical sibling pixels TPixel sourcePixel = source[startX, startY]; TPixel previousPixel = sourcePixel; PixelPair <TPixel> pair = this.GetClosestPixelPair(ref sourcePixel); sourcePixel.ToRgba32(ref rgba); // Convert to grayscale using ITU-R Recommendation BT.709 if required float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); for (int y = startY; y < endY; y++) { Span <TPixel> row = source.GetPixelRowSpan(y); for (int x = startX; x < endX; x++) { sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. if (!previousPixel.Equals(sourcePixel)) { pair = this.GetClosestPixelPair(ref sourcePixel); // No error to spread, exact match. if (sourcePixel.Equals(pair.First)) { continue; } sourcePixel.ToRgba32(ref rgba); luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); // Setup the previous pointer previousPixel = sourcePixel; } this.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y); } } }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int width = source.Width; int height = source.Height; ParallelOptions parallelOptions = configuration.ParallelOptions; using (var firstPassPixels = new PixelAccessor <TPixel>(width, height)) using (PixelAccessor <TPixel> sourcePixels = source.Lock()) { this.ApplyConvolution(firstPassPixels, sourcePixels, source.Bounds(), this.KernelX, parallelOptions); this.ApplyConvolution(sourcePixels, firstPassPixels, sourceRectangle, this.KernelY, parallelOptions); } }
/// <summary> /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> /// <param name="configuration">The configuration.</param> private void FlipY(ImageFrame <TPixel> source, Configuration configuration) { ParallelHelper.IterateRows( source.Bounds(), configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { source.GetPixelRowSpan(y).Reverse(); } }); }
/// <summary> /// This method pre-seeds the default dithering engine (FloydSteinbergDiffuser) in the AoT compiler for iOS. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> private static void AotCompileDithering <TPixel>() where TPixel : unmanaged, IPixel <TPixel> { ErrorDither errorDither = ErrorDither.FloydSteinberg; OrderedDither orderedDither = OrderedDither.Bayer2x2; TPixel pixel = default; using (var image = new ImageFrame <TPixel>(Configuration.Default, 1, 1)) { errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0); orderedDither.Dither(pixel, 0, 0, 0, 0); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { var intersect = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); Configuration configuration = this.Configuration; TPixel upper = this.definition.Upper.ToPixel <TPixel>(); TPixel lower = this.definition.Lower.ToPixel <TPixel>(); float thresholdLimit = this.definition.ThresholdLimit; int startY = intersect.Y; int endY = intersect.Bottom; int startX = intersect.X; int endX = intersect.Right; int width = intersect.Width; int height = intersect.Height; // ClusterSize defines the size of cluster to used to check for average. Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' byte clusterSize = (byte)Math.Truncate((width / 16f) - 1); Buffer2D <TPixel> sourceBuffer = source.PixelBuffer; // Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data. using (Buffer2D <ulong> intImage = this.Configuration.MemoryAllocator.Allocate2D <ulong>(width, height)) { Rgba32 rgb = default; for (int x = startX; x < endX; x++) { ulong sum = 0; for (int y = startY; y < endY; y++) { Span <TPixel> row = sourceBuffer.DangerousGetRowSpan(y); ref TPixel rowRef = ref MemoryMarshal.GetReference(row); ref TPixel color = ref Unsafe.Add(ref rowRef, x); color.ToRgba32(ref rgb); sum += (ulong)(rgb.R + rgb.G + rgb.B); if (x - startX != 0) { intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum; } else { intImage[x - startX, y - startY] = sum; } } }
/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); Configuration configuration = this.Configuration; using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); ParallelRowIterator.IterateRowIntervals( configuration, interest, in operation); }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { float threshold = this.Threshold * 255F; var rgba = default(Rgba32); bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startY = interest.Y; int endY = interest.Bottom; int startX = interest.X; int endX = interest.Right; // Collect the values before looping so we can reduce our calculation count for identical sibling pixels TPixel sourcePixel = source[startX, startY]; TPixel previousPixel = sourcePixel; PixelPair <TPixel> pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); sourcePixel.ToRgba32(ref rgba); // Convert to grayscale using ITU-R Recommendation BT.709 if required byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); for (int y = startY; y < endY; y++) { Span <TPixel> row = source.GetPixelRowSpan(y); for (int x = startX; x < endX; x++) { sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. if (!previousPixel.Equals(sourcePixel)) { pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); sourcePixel.ToRgba32(ref rgba); luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); // Setup the previous pointer previousPixel = sourcePixel; } TPixel transformedPixel = luminance >= threshold ? pair.First : pair.Second; this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); } } }