mirror of https://github.com/SixLabors/ImageSharp
16 changed files with 362 additions and 99 deletions
@ -0,0 +1,90 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Quantization |
|||
{ |
|||
/// <summary>
|
|||
/// A pixel sampling strategy that enumerates a limited amount of rows from different frames,
|
|||
/// if the total number of pixels is over a threshold.
|
|||
/// </summary>
|
|||
public class DefaultPixelSamplingStrategy : IPixelSamplingStrategy |
|||
{ |
|||
// TODO: This value shall be determined by benchmarking.
|
|||
// (Maximum quality while decoding time is still tolerable.)
|
|||
private const int DefaultMaximumPixels = 8192 * 8192; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DefaultPixelSamplingStrategy"/> class.
|
|||
/// </summary>
|
|||
public DefaultPixelSamplingStrategy() |
|||
: this(DefaultMaximumPixels) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DefaultPixelSamplingStrategy"/> class.
|
|||
/// </summary>
|
|||
/// <param name="maximumPixels">The maximum number of pixels to process.</param>
|
|||
public DefaultPixelSamplingStrategy(int maximumPixels) |
|||
{ |
|||
Guard.MustBeGreaterThan(maximumPixels, 0, nameof(maximumPixels)); |
|||
this.MaximumPixels = maximumPixels; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum number of pixels to process. (The threshold.)
|
|||
/// </summary>
|
|||
public long MaximumPixels { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public IEnumerable<BufferRegion<TPixel>> EnumeratePixelRegions<TPixel>(Image<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
long maximumPixels = Math.Min(MaximumPixels, (long)image.Width * image.Height * image.Frames.Count); |
|||
long maxNumberOfRows = maximumPixels / image.Width; |
|||
long totalNumberOfRows = (long)image.Height * image.Frames.Count; |
|||
|
|||
if (totalNumberOfRows <= maxNumberOfRows) |
|||
{ |
|||
// Enumerate all pixels
|
|||
foreach (ImageFrame<TPixel> frame in image.Frames) |
|||
{ |
|||
yield return frame.PixelBuffer.GetRegion(); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
double r = maxNumberOfRows / (double)totalNumberOfRows; |
|||
|
|||
r = Math.Round(r, 1); // Use a rough approximation to make sure we don't leave out large contiguous regions:
|
|||
r = Math.Max(0.1, r); // always visit at least 10% of the image
|
|||
|
|||
var ratio = new Rational(r); |
|||
|
|||
int denom = (int)ratio.Denominator; |
|||
int num = (int)ratio.Numerator; |
|||
|
|||
for (int pos = 0; pos < totalNumberOfRows; pos++) |
|||
{ |
|||
int subPos = pos % denom; |
|||
if (subPos < num) |
|||
{ |
|||
yield return GetRow(pos); |
|||
} |
|||
} |
|||
|
|||
BufferRegion<TPixel> GetRow(int pos) |
|||
{ |
|||
int frameIdx = pos / image.Height; |
|||
int y = pos % image.Height; |
|||
return image.Frames[frameIdx].PixelBuffer.GetRegion(0, y, image.Width, 1); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Quantization |
|||
{ |
|||
/// <summary>
|
|||
/// A pixel sampling strategy that enumerates all pixels.
|
|||
/// </summary>
|
|||
public class ExtensivePixelSamplingStrategy : IPixelSamplingStrategy |
|||
{ |
|||
/// <inheritdoc />
|
|||
public IEnumerable<BufferRegion<TPixel>> EnumeratePixelRegions<TPixel>(Image<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
foreach (ImageFrame<TPixel> frame in image.Frames) |
|||
{ |
|||
yield return frame.PixelBuffer.GetRegion(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Quantization |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an abstraction to enumerate pixel regions within a multi-framed <see cref="Image{TPixel}"/>.
|
|||
/// </summary>
|
|||
public interface IPixelSamplingStrategy |
|||
{ |
|||
/// <summary>
|
|||
/// Enumerates pixel regions within the image as <see cref="BufferRegion{T}"/>.
|
|||
/// </summary>
|
|||
/// <param name="image">The image.</param>
|
|||
/// <typeparam name="TPixel">The pixel type.</typeparam>
|
|||
/// <returns>An enumeration of pixel regions.</returns>
|
|||
IEnumerable<BufferRegion<TPixel>> EnumeratePixelRegions<TPixel>(Image<TPixel> image) |
|||
where TPixel : unmanaged, IPixel<TPixel>; |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Quantization |
|||
{ |
|||
public class PixelSamplingStrategyTests |
|||
{ |
|||
public static readonly TheoryData<int, int, int, int> DefaultPixelSamplingStrategy_Data = new TheoryData<int, int, int, int>() |
|||
{ |
|||
{ 100, 100, 1, 10000 }, |
|||
{ 100, 100, 1, 5000 }, |
|||
{ 100, 100, 10, 50000 }, |
|||
{ 99, 100, 11, 30000 }, |
|||
{ 97, 99, 11, 80000 }, |
|||
{ 99, 100, 11, 20000 }, |
|||
{ 99, 501, 20, 100000 }, |
|||
{ 97, 500, 20, 10000 }, |
|||
{ 103, 501, 20, 1000 }, |
|||
}; |
|||
|
|||
[Fact] |
|||
public void ExtensivePixelSamplingStrategy_EnumeratesAll() |
|||
{ |
|||
using Image<L8> image = CreateTestImage(100, 100, 100); |
|||
var strategy = new ExtensivePixelSamplingStrategy(); |
|||
|
|||
foreach (BufferRegion<L8> region in strategy.EnumeratePixelRegions(image)) |
|||
{ |
|||
PaintWhite(region); |
|||
} |
|||
|
|||
using Image<L8> expected = CreateTestImage(100, 100, 100, true); |
|||
|
|||
ImageComparer.Exact.VerifySimilarity(expected, image); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(DefaultPixelSamplingStrategy_Data), 1, 1, PixelTypes.L8)] |
|||
public void DefaultPixelSamplingStrategy_IsFair(TestImageProvider<L8> dummyProvider, int width, int height, int noOfFrames, int maximumNumberOfPixels) |
|||
{ |
|||
using Image<L8> image = CreateTestImage(width, height, noOfFrames); |
|||
|
|||
var strategy = new DefaultPixelSamplingStrategy(maximumNumberOfPixels); |
|||
|
|||
long visitedPixels = 0; |
|||
foreach (BufferRegion<L8> region in strategy.EnumeratePixelRegions(image)) |
|||
{ |
|||
PaintWhite(region); |
|||
visitedPixels += region.Width * region.Height; |
|||
} |
|||
|
|||
image.DebugSaveMultiFrame( |
|||
dummyProvider, |
|||
$"W{width}_H{height}_noOfFrames_{noOfFrames}_maximumNumberOfPixels_{maximumNumberOfPixels}", |
|||
appendPixelTypeToFileName: false); |
|||
|
|||
int maximumPixels = image.Width * image.Height * image.Frames.Count / 10; |
|||
maximumPixels = Math.Max(maximumPixels, (int)strategy.MaximumPixels); |
|||
|
|||
// allow some inaccuracy:
|
|||
double visitRatio = visitedPixels / (double)maximumPixels; |
|||
Assert.True(visitRatio <= 1.1, $"{visitedPixels}>{maximumPixels}"); |
|||
} |
|||
|
|||
static void PaintWhite(BufferRegion<L8> region) |
|||
{ |
|||
var white = new L8(255); |
|||
for (int y = 0; y < region.Height; y++) |
|||
{ |
|||
region.GetRowSpan(y).Fill(white); |
|||
} |
|||
} |
|||
|
|||
private Image<L8> CreateTestImage(int width, int height, int noOfFrames, bool paintWhite = false) |
|||
{ |
|||
L8 bg = paintWhite ? new L8(255) : default; |
|||
var image = new Image<L8>(width, height, bg); |
|||
|
|||
for (int i = 1; i < noOfFrames; i++) |
|||
{ |
|||
ImageFrame<L8> f = image.Frames.CreateFrame(); |
|||
if (paintWhite) |
|||
{ |
|||
f.PixelBuffer.MemoryGroup.Fill(bg); |
|||
} |
|||
} |
|||
|
|||
return image; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue