Browse Source

Make dither parallel and add benchmarks.

af/octree-no-pixelmap
James Jackson-South 6 years ago
parent
commit
94f69b67f9
  1. 64
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  2. 38
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs
  3. 33
      tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
  4. 3
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  5. 0
      tests/Images/Input/Png/bike-small.png

64
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<TPixel> 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);
}
/// <inheritdoc/>
@ -112,5 +107,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.isDisposed = true;
base.Dispose(disposing);
}
private readonly struct DitherRowIntervalOperation : IRowIntervalOperation
{
private readonly ImageFrame<TPixel> source;
private readonly Rectangle bounds;
private readonly EuclideanPixelMap<TPixel> pixelMap;
private readonly IDither dither;
private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)]
public DitherRowIntervalOperation(
ImageFrame<TPixel> source,
Rectangle bounds,
EuclideanPixelMap<TPixel> 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<TPixel> 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;
}
}
}
}
}
}

38
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs

@ -148,25 +148,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
}
/// <summary>
/// Returns the index and color from the quantized palette corresponding to the give to the given color.
/// </summary>
/// <param name="color">The color to match.</param>
/// <param name="palette">The output color palette.</param>
/// <param name="match">The matched color.</param>
/// <returns>The <see cref="byte"/> index.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
protected virtual byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
=> this.pixelMap.GetClosestColor(color, out match);
/// <summary>
/// Generates the palette for the quantized image.
/// </summary>
/// <returns>
/// <see cref="ReadOnlyMemory{TPixel}"/>
/// </returns>
protected abstract ReadOnlyMemory<TPixel> GenerateQuantizedPalette();
/// <summary>
/// Execute a second pass through the image to assign the pixels to a palette entry.
/// </summary>
@ -226,6 +207,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
in ditherOperation);
}
/// <summary>
/// Returns the index and color from the quantized palette corresponding to the give to the given color.
/// </summary>
/// <param name="color">The color to match.</param>
/// <param name="palette">The output color palette.</param>
/// <param name="match">The matched color.</param>
/// <returns>The <see cref="byte"/> index.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
protected virtual byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
=> this.pixelMap.GetClosestColor(color, out match);
/// <summary>
/// Generates the palette for the quantized image.
/// </summary>
/// <returns>
/// <see cref="ReadOnlyMemory{TPixel}"/>
/// </returns>
protected abstract ReadOnlyMemory<TPixel> GenerateQuantizedPalette();
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly ImageFrame<TPixel> source;

33
tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs

@ -12,6 +12,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
{
[Benchmark]
public Size DoDiffuse()
{
using (var image = new Image<Rgba32>(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<Rgba32>(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 |

3
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<object>());
}
[Fact]

0
tests/Images/Input/Png/CalliphoraPartial2.png → tests/Images/Input/Png/bike-small.png

Loading…
Cancel
Save