Browse Source

Convert TiffEncoder, Use sampling strategy for local palette building

pull/2269/head
James Jackson-South 4 years ago
parent
commit
1ac9de53ff
  1. 15
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 5
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  4. 4
      src/ImageSharp/Formats/IEncoderOptions.cs
  5. 2
      src/ImageSharp/Formats/ImageEncoder.cs
  6. 4
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  7. 6
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  8. 46
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  9. 61
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  10. 21
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  11. 3
      src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs
  12. 34
      src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs
  13. 50
      src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs
  14. 7
      src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs
  15. 13
      src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs
  16. 32
      src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs
  17. 2
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  18. 23
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs

15
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -57,6 +57,9 @@ internal static class AotCompilerTools
/// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the
/// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!!
/// </remarks>
/// <exception cref="InvalidOperationException">
/// This method is used for AOT code generation only. Do not call it at runtime.
/// </exception>
[Preserve]
private static void SeedPixelFormats()
{
@ -487,8 +490,10 @@ internal static class AotCompilerTools
private static void AotCompilePixelSamplingStrategys<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions<TPixel>(default);
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions<TPixel>(default);
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(Image<TPixel>));
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame<TPixel>));
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(Image<TPixel>));
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame<TPixel>));
}
/// <summary>
@ -513,13 +518,13 @@ internal static class AotCompilerTools
where TPixel : unmanaged, IPixel<TPixel>
where TDither : struct, IDither
{
var octree = default(OctreeQuantizer<TPixel>);
OctreeQuantizer<TPixel> octree = default;
default(TDither).ApplyQuantizationDither<OctreeQuantizer<TPixel>, TPixel>(ref octree, default, default, default);
var palette = default(PaletteQuantizer<TPixel>);
PaletteQuantizer<TPixel> palette = default;
default(TDither).ApplyQuantizationDither<PaletteQuantizer<TPixel>, TPixel>(ref palette, default, default, default);
var wu = default(WuQuantizer<TPixel>);
WuQuantizer<TPixel> wu = default;
default(TDither).ApplyQuantizationDither<WuQuantizer<TPixel>, TPixel>(ref wu, default, default, default);
default(TDither).ApplyPaletteDither<PaletteDitherProcessor<TPixel>.DitherProcessor, TPixel>(default, default, default);
}

2
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -106,7 +106,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = encoder.BitsPerPixel;
this.quantizer = encoder.Quantizer;
this.pixelSamplingStrategy = encoder.GlobalPixelSamplingStrategy;
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
}

5
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -70,7 +70,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
this.skipMetadata = encoder.SkipMetadata;
this.quantizer = encoder.Quantizer;
this.colorTableMode = encoder.ColorTableMode;
this.pixelSamplingStrategy = encoder.GlobalPixelSamplingStrategy;
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
}
/// <summary>
@ -103,7 +103,8 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
}
else
{
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds());
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image.Frames.RootFrame);
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
}
}

4
src/ImageSharp/Formats/IEncoderOptions.cs

@ -27,7 +27,7 @@ public interface IQuantizingEncoderOptions : IEncoderOptions
IQuantizer Quantizer { get; init; }
/// <summary>
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building a global color palette.
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building color palettes.
/// </summary>
IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; init; }
IPixelSamplingStrategy PixelSamplingStrategy { get; init; }
}

2
src/ImageSharp/Formats/ImageEncoder.cs

@ -33,5 +33,5 @@ public abstract class QuantizingImageEncoder : ImageEncoder, IQuantizingEncoderO
public IQuantizer Quantizer { get; init; } = KnownQuantizers.Octree;
/// <inheritdoc/>
public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy();
public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy();
}

4
src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs

@ -65,7 +65,7 @@ internal sealed class PbmEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
this.DeduceOptions(image);
this.SanitizeAndSetEncoderOptions(image);
byte signature = this.DeduceSignature();
this.WriteHeader(stream, signature, image.Size());
@ -75,7 +75,7 @@ internal sealed class PbmEncoderCore : IImageEncoderInternals
stream.Flush();
}
private void DeduceOptions<TPixel>(Image<TPixel> image)
private void SanitizeAndSetEncoderOptions<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
this.configuration = image.GetConfiguration();

6
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -149,7 +149,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
this.DeduceOptions<TPixel>(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
this.SanitizeAndSetEncoderOptions<TPixel>(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
Image<TPixel> clonedImage = null;
bool clearTransparency = this.encoder.TransparentColorMode == PngTransparentColorMode.Clear;
if (clearTransparency)
@ -1228,7 +1228,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// <param name="pngMetadata">The PNG metadata.</param>
/// <param name="use16Bit">if set to <c>true</c> [use16 bit].</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
private void DeduceOptions<TPixel>(
private void SanitizeAndSetEncoderOptions<TPixel>(
PngEncoder encoder,
PngMetadata pngMetadata,
out bool use16Bit,
@ -1298,7 +1298,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
// Create quantized frame returning the palette and set the bit depth.
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(image.GetConfiguration());
frameQuantizer.BuildPalette(encoder.GlobalPixelSamplingStrategy, image);
frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, image);
return frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
}

46
src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs

@ -1,46 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Tiff;
/// <summary>
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
/// </summary>
internal interface ITiffEncoderOptions
{
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
TiffBitsPerPixel? BitsPerPixel { get; }
/// <summary>
/// Gets the compression type to use.
/// </summary>
TiffCompression? Compression { get; }
/// <summary>
/// Gets the compression level 1-9 for the deflate compression mode.
/// <remarks>Defaults to <see cref="DeflateCompressionLevel.DefaultCompression"/>.</remarks>
/// </summary>
DeflateCompressionLevel? CompressionLevel { get; }
/// <summary>
/// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor.
/// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used.
/// </summary>
TiffPhotometricInterpretation? PhotometricInterpretation { get; }
/// <summary>
/// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression.
/// </summary>
TiffPredictor? HorizontalPredictor { get; }
/// <summary>
/// Gets the quantizer for creating a color palette image.
/// </summary>
IQuantizer Quantizer { get; }
}

61
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -4,47 +4,52 @@
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Tiff;
/// <summary>
/// Encoder for writing the data image to a stream in TIFF format.
/// </summary>
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
public class TiffEncoder : QuantizingImageEncoder
{
/// <inheritdoc/>
public TiffBitsPerPixel? BitsPerPixel { get; set; }
/// <inheritdoc/>
public TiffCompression? Compression { get; set; }
/// <inheritdoc/>
public DeflateCompressionLevel? CompressionLevel { get; set; }
/// <inheritdoc/>
public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; }
/// <inheritdoc/>
public TiffPredictor? HorizontalPredictor { get; set; }
/// <inheritdoc/>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
public TiffBitsPerPixel? BitsPerPixel { get; init; }
/// <summary>
/// Gets the compression type to use.
/// </summary>
public TiffCompression? Compression { get; init; }
/// <summary>
/// Gets the compression level 1-9 for the deflate compression mode.
/// <remarks>Defaults to <see cref="DeflateCompressionLevel.DefaultCompression" />.</remarks>
/// </summary>
public DeflateCompressionLevel? CompressionLevel { get; init; }
/// <summary>
/// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor.
/// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used.
/// </summary>
public TiffPhotometricInterpretation? PhotometricInterpretation { get; init; }
/// <summary>
/// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression.
/// </summary>
public TiffPredictor? HorizontalPredictor { get; init; }
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
{
var encode = new TiffEncoderCore(this, image.GetMemoryAllocator());
TiffEncoderCore encode = new(this, image.GetMemoryAllocator());
encode.Encode(image, stream);
}
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
var encoder = new TiffEncoderCore(this, image.GetMemoryAllocator());
TiffEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
}
}

21
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -10,7 +10,6 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Tiff;
@ -40,10 +39,15 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
private Configuration configuration;
/// <summary>
/// The quantizer for creating color palette image.
/// The quantizer for creating color palette images.
/// </summary>
private readonly IQuantizer quantizer;
/// <summary>
/// The pixel sampling strategy for quantization.
/// </summary>
private readonly IPixelSamplingStrategy pixelSamplingStrategy;
/// <summary>
/// Sets the deflate compression level.
/// </summary>
@ -76,11 +80,12 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator)
public TiffEncoderCore(TiffEncoder options, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
this.PhotometricInterpretation = options.PhotometricInterpretation;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.quantizer = options.Quantizer;
this.pixelSamplingStrategy = options.PixelSamplingStrategy;
this.BitsPerPixel = options.BitsPerPixel;
this.HorizontalPredictor = options.HorizontalPredictor;
this.CompressionType = options.Compression;
@ -215,6 +220,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
this.PhotometricInterpretation,
frame,
this.quantizer,
this.pixelSamplingStrategy,
this.memoryAllocator,
this.configuration,
entriesCollector,
@ -331,7 +337,12 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
return nextIfdMarker;
}
private void SanitizeAndSetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor)
private void SanitizeAndSetEncoderOptions(
TiffBitsPerPixel? bitsPerPixel,
int inputBitsPerPixel,
TiffPhotometricInterpretation? photometricInterpretation,
TiffCompression compression,
TiffPredictor predictor)
{
// BitsPerPixel should be the primary source of truth for the encoder options.
if (bitsPerPixel.HasValue)

3
src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs

@ -14,6 +14,7 @@ internal static class TiffColorWriterFactory
TiffPhotometricInterpretation? photometricInterpretation,
ImageFrame<TPixel> image,
IQuantizer quantizer,
IPixelSamplingStrategy pixelSamplingStrategy,
MemoryAllocator memoryAllocator,
Configuration configuration,
TiffEncoderEntriesCollector entriesCollector,
@ -23,7 +24,7 @@ internal static class TiffColorWriterFactory
switch (photometricInterpretation)
{
case TiffPhotometricInterpretation.PaletteColor:
return new TiffPaletteWriter<TPixel>(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel);
return new TiffPaletteWriter<TPixel>(image, quantizer, pixelSamplingStrategy, memoryAllocator, configuration, entriesCollector, bitsPerPixel);
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
if (bitsPerPixel == 1)

34
src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs

@ -17,19 +17,21 @@ internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
private readonly int maxColors;
private readonly int colorPaletteSize;
private readonly int colorPaletteBytes;
private readonly IndexedImageFrame<TPixel> quantizedImage;
private readonly IndexedImageFrame<TPixel> quantizedFrame;
private IMemoryOwner<byte> indexedPixelsBuffer;
public TiffPaletteWriter(
ImageFrame<TPixel> image,
ImageFrame<TPixel> frame,
IQuantizer quantizer,
IPixelSamplingStrategy pixelSamplingStrategy,
MemoryAllocator memoryAllocator,
Configuration configuration,
TiffEncoderEntriesCollector entriesCollector,
int bitsPerPixel)
: base(image, memoryAllocator, configuration, entriesCollector)
: base(frame, memoryAllocator, configuration, entriesCollector)
{
DebugGuard.NotNull(quantizer, nameof(quantizer));
DebugGuard.NotNull(quantizer, nameof(pixelSamplingStrategy));
DebugGuard.NotNull(configuration, nameof(configuration));
DebugGuard.NotNull(entriesCollector, nameof(entriesCollector));
DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel));
@ -38,11 +40,15 @@ internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
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());
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(
this.Configuration,
new QuantizerOptions()
{
MaxColors = this.maxColors
});
frameQuantizer.BuildPalette(pixelSamplingStrategy, frame);
this.quantizedFrame = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.AddColorMapTag();
}
@ -66,7 +72,7 @@ internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
int lastRow = y + height;
for (int row = y; row < lastRow; row++)
{
ReadOnlySpan<byte> indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row);
ReadOnlySpan<byte> indexedPixelRow = this.quantizedFrame.DangerousGetRowSpan(row);
int idxPixels = 0;
for (int x = 0; x < halfWidth; x++)
{
@ -93,7 +99,7 @@ internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
int indexedPixelsRowIdx = 0;
for (int row = y; row < lastRow; row++)
{
ReadOnlySpan<byte> indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row);
ReadOnlySpan<byte> indexedPixelRow = this.quantizedFrame.DangerousGetRowSpan(row);
indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width));
indexedPixelsRowIdx++;
}
@ -105,7 +111,7 @@ internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
this.quantizedImage?.Dispose();
this.quantizedFrame?.Dispose();
this.indexedPixelsBuffer?.Dispose();
}
@ -114,7 +120,7 @@ internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
using IMemoryOwner<byte> colorPaletteBuffer = this.MemoryAllocator.Allocate<byte>(this.colorPaletteBytes);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColors = this.quantizedImage.Palette.Span;
ReadOnlySpan<TPixel> quantizedColors = this.quantizedFrame.Palette.Span;
int quantizedColorBytes = quantizedColors.Length * 3 * 2;
// In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535.
@ -126,7 +132,7 @@ internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
// In a TIFF ColorMap, all the Red values come first, followed by the Green values,
// then the Blue values. Convert the quantized palette to this format.
var palette = new ushort[this.colorPaletteSize];
ushort[] palette = new ushort[this.colorPaletteSize];
int paletteIdx = 0;
for (int i = 0; i < quantizedColors.Length; i++)
{
@ -147,7 +153,7 @@ internal sealed class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
palette[paletteIdx++] = quantizedColorRgb48[i].B;
}
var colorMap = new ExifShortArray(ExifTagValue.ColorMap)
ExifShortArray colorMap = new(ExifTagValue.ColorMap)
{
Value = palette
};

50
src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs

@ -101,4 +101,54 @@ public class DefaultPixelSamplingStrategy : IPixelSamplingStrategy
}
}
}
/// <inheritdoc />
public IEnumerable<Buffer2DRegion<TPixel>> EnumeratePixelRegions<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>
{
long maximumPixels = Math.Min(this.MaximumPixels, (long)frame.Width * frame.Height);
long maxNumberOfRows = maximumPixels / frame.Width;
long totalNumberOfRows = frame.Height;
if (totalNumberOfRows <= maxNumberOfRows)
{
yield return frame.PixelBuffer.GetRegion();
}
else
{
double r = maxNumberOfRows / (double)totalNumberOfRows;
// Use a rough approximation to make sure we don't leave out large contiguous regions:
if (maxNumberOfRows > 200)
{
r = Math.Round(r, 2);
}
else
{
r = Math.Round(r, 1);
}
r = Math.Max(this.MinimumScanRatio, r); // always visit the minimum defined portion of the image.
Rational ratio = new(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);
}
}
Buffer2DRegion<TPixel> GetRow(int pos)
{
int y = pos % frame.Height;
return frame.PixelBuffer.GetRegion(0, y, frame.Width, 1);
}
}
}
}

7
src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs

@ -20,4 +20,11 @@ public class ExtensivePixelSamplingStrategy : IPixelSamplingStrategy
yield return frame.PixelBuffer.GetRegion();
}
}
/// <inheritdoc/>
public IEnumerable<Buffer2DRegion<TPixel>> EnumeratePixelRegions<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>
{
yield return frame.PixelBuffer.GetRegion();
}
}

13
src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs

@ -7,16 +7,25 @@ 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}"/>.
/// Provides an abstraction to enumerate pixel regions for sampling within <see cref="Image{TPixel}"/>.
/// </summary>
public interface IPixelSamplingStrategy
{
/// <summary>
/// Enumerates pixel regions within the image as <see cref="Buffer2DRegion{T}"/>.
/// Enumerates pixel regions for all frames within the image as <see cref="Buffer2DRegion{T}"/>.
/// </summary>
/// <param name="image">The image.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>An enumeration of pixel regions.</returns>
IEnumerable<Buffer2DRegion<TPixel>> EnumeratePixelRegions<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Enumerates pixel regions within a single image frame as <see cref="Buffer2DRegion{T}"/>.
/// </summary>
/// <param name="frame">The image frame.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>An enumeration of pixel regions.</returns>
IEnumerable<Buffer2DRegion<TPixel>> EnumeratePixelRegions<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>;
}

32
src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs

@ -99,13 +99,39 @@ public static class QuantizerUtilities
return destination;
}
internal static void BuildPalette<TPixel>(
/// <summary>
/// Adds colors to the quantized palette from the given pixel regions.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="quantizer">The pixel specific quantizer.</param>
/// <param name="pixelSamplingStrategy">The pixel sampling strategy.</param>
/// <param name="source">The source image to sample from.</param>
public static void BuildPalette<TPixel>(
this IQuantizer<TPixel> quantizer,
IPixelSamplingStrategy pixelSamplingStrategy,
Image<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (Buffer2DRegion<TPixel> region in pixelSamplingStrategy.EnumeratePixelRegions(source))
{
quantizer.AddPaletteColors(region);
}
}
/// <summary>
/// Adds colors to the quantized palette from the given pixel regions.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="quantizer">The pixel specific quantizer.</param>
/// <param name="pixelSamplingStrategy">The pixel sampling strategy.</param>
/// <param name="source">The source image frame to sample from.</param>
public static void BuildPalette<TPixel>(
this IQuantizer<TPixel> quantizer,
IPixelSamplingStrategy pixelSamplingStrategy,
Image<TPixel> image)
ImageFrame<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (Buffer2DRegion<TPixel> region in pixelSamplingStrategy.EnumeratePixelRegions(image))
foreach (Buffer2DRegion<TPixel> region in pixelSamplingStrategy.EnumeratePixelRegions(source))
{
quantizer.AddPaletteColors(region);
}

2
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -144,7 +144,7 @@ public class GifEncoderTests
GifEncoder encoder = new()
{
ColorTableMode = GifColorTableMode.Global,
GlobalPixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio)
PixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio)
};
string testOutputFile = provider.Utility.SaveTestOutputFile(

23
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs

@ -3,24 +3,21 @@
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Writers;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff;
[Trait("Format", "Tiff")]
public class TiffEncoderHeaderTests
{
private static readonly MemoryAllocator MemoryAllocator = MemoryAllocator.Create();
private static readonly Configuration Configuration = Configuration.Default;
private static readonly ITiffEncoderOptions Options = new TiffEncoder();
private static readonly TiffEncoder Encoder = new();
[Fact]
public void WriteHeader_WritesValidHeader()
{
using var stream = new MemoryStream();
var encoder = new TiffEncoderCore(Options, MemoryAllocator);
using MemoryStream stream = new();
TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator);
using (var writer = new TiffStreamWriter(stream))
using (TiffStreamWriter writer = new(stream))
{
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer);
}
@ -31,13 +28,11 @@ public class TiffEncoderHeaderTests
[Fact]
public void WriteHeader_ReturnsFirstIfdMarker()
{
using var stream = new MemoryStream();
var encoder = new TiffEncoderCore(Options, MemoryAllocator);
using MemoryStream stream = new();
TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator);
using (var writer = new TiffStreamWriter(stream))
{
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer);
Assert.Equal(4, firstIfdMarker);
}
using TiffStreamWriter writer = new(stream);
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer);
Assert.Equal(4, firstIfdMarker);
}
}

Loading…
Cancel
Save