diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 041404f39..bdcc9e6b8 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -3,7 +3,9 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Dithering @@ -64,19 +66,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return; } - // TODO: This can be parallel. - // Ordered dithering. We are only operating on a single pixel. - for (int y = interest.Top; y < interest.Bottom; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = interest.Left; x < interest.Right; x++) - { - TPixel dithered = this.dither.Dither(source, interest, row[x], default, x, y, this.bitDepth); - this.pixelMap.GetClosestColor(dithered, out TPixel transformed); - row[x] = transformed; - } - } + // Ordered dithering. We are only operating on a single pixel so we can work in parallel. + var ditherOperation = new DitherRowIntervalOperation(source, interest, this.pixelMap, this.dither, this.bitDepth); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in ditherOperation); } /// @@ -112,5 +107,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.isDisposed = true; base.Dispose(disposing); } + + private readonly struct DitherRowIntervalOperation : IRowIntervalOperation + { + private readonly ImageFrame source; + private readonly Rectangle bounds; + private readonly EuclideanPixelMap pixelMap; + private readonly IDither dither; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public DitherRowIntervalOperation( + ImageFrame source, + Rectangle bounds, + EuclideanPixelMap pixelMap, + IDither dither, + int bitDepth) + { + this.source = source; + this.bounds = bounds; + this.pixelMap = pixelMap; + this.dither = dither; + this.bitDepth = bitDepth; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + IDither dither = this.dither; + TPixel transformed = default; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = this.source.GetPixelRowSpan(y); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + TPixel dithered = dither.Dither(this.source, this.bounds, row[x], transformed, x, y, this.bitDepth); + this.pixelMap.GetClosestColor(dithered, out transformed); + row[x] = transformed; + } + } + } + } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs index 63d6875d8..f8ae64d95 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs @@ -148,25 +148,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { } - /// - /// Returns the index and color from the quantized palette corresponding to the give to the given color. - /// - /// The color to match. - /// The output color palette. - /// The matched color. - /// The index. - [MethodImpl(InliningOptions.ShortMethod)] - protected virtual byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) - => this.pixelMap.GetClosestColor(color, out match); - - /// - /// Generates the palette for the quantized image. - /// - /// - /// - /// - protected abstract ReadOnlyMemory GenerateQuantizedPalette(); - /// /// Execute a second pass through the image to assign the pixels to a palette entry. /// @@ -226,6 +207,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization in ditherOperation); } + /// + /// Returns the index and color from the quantized palette corresponding to the give to the given color. + /// + /// The color to match. + /// The output color palette. + /// The matched color. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + protected virtual byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + => this.pixelMap.GetClosestColor(color, out match); + + /// + /// Generates the palette for the quantized image. + /// + /// + /// + /// + protected abstract ReadOnlyMemory GenerateQuantizedPalette(); + private readonly struct RowIntervalOperation : IRowIntervalOperation { private readonly ImageFrame source; diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs index 35a05b801..feb447501 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -12,6 +12,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers { [Benchmark] public Size DoDiffuse() + { + using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) + { + image.Mutate(x => x.Dither(KnownDitherers.FloydSteinberg)); + + return image.Size(); + } + } + + [Benchmark] + public Size DoDither() { using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) { @@ -48,3 +59,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers // |---------- |----- |-------- |----------:|----------:|----------:|------:|------:|------:|----------:| // | DoDiffuse | Clr | Clr | 124.93 ms | 33.297 ms | 1.8251 ms | - | - | - | 2 KB | // | DoDiffuse | Core | Core | 89.63 ms | 9.895 ms | 0.5424 ms | - | - | - | 1.91 KB | + +// #### 15th February 2020 #### +// +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-OJKYBT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-RZWLFP : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-NUYUQV : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:| +// | DoDiffuse | .NET 4.7.2 | 46.50 ms | 13.734 ms | 0.753 ms | - | - | - | 26.72 KB | +// | DoDither | .NET 4.7.2 | 17.79 ms | 7.705 ms | 0.422 ms | - | - | - | 31 KB | +// | DoDiffuse | .NET Core 2.1 | 26.45 ms | 1.463 ms | 0.080 ms | - | - | - | 26.03 KB | +// | DoDither | .NET Core 2.1 | 10.86 ms | 2.074 ms | 0.114 ms | - | - | - | 29.29 KB | +// | DoDiffuse | .NET Core 3.1 | 28.44 ms | 84.907 ms | 4.654 ms | - | - | - | 26.01 KB | +// | DoDither | .NET Core 3.1 | 10.50 ms | 5.698 ms | 0.312 ms | - | - | - | 30.94 KB | diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 95389511b..ff91c0e82 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Linq; using System.Reflection; @@ -90,7 +91,7 @@ namespace SixLabors.ImageSharp.Tests private static IQuantizer GetQuantizer(string name) { PropertyInfo property = typeof(KnownQuantizers).GetTypeInfo().GetProperty(name); - return (IQuantizer)property.GetMethod.Invoke(null, new object[0]); + return (IQuantizer)property.GetMethod.Invoke(null, Array.Empty()); } [Fact] diff --git a/tests/Images/Input/Png/CalliphoraPartial2.png b/tests/Images/Input/Png/bike-small.png similarity index 100% rename from tests/Images/Input/Png/CalliphoraPartial2.png rename to tests/Images/Input/Png/bike-small.png