mirror of https://github.com/SixLabors/ImageSharp
9 changed files with 557 additions and 753 deletions
@ -1,77 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png; |
|||
|
|||
/// <summary>
|
|||
/// The options available for manipulating the encoder pipeline.
|
|||
/// </summary>
|
|||
internal interface IPngEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the number of bits per sample or per palette index (not per pixel).
|
|||
/// Not all values are allowed for all <see cref="ColorType"/> values.
|
|||
/// </summary>
|
|||
PngBitDepth? BitDepth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the color type.
|
|||
/// </summary>
|
|||
PngColorType? ColorType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the filter method.
|
|||
/// </summary>
|
|||
PngFilterMethod? FilterMethod { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the compression level 1-9.
|
|||
/// <remarks>Defaults to <see cref="PngCompressionLevel.DefaultCompression"/>.</remarks>
|
|||
/// </summary>
|
|||
PngCompressionLevel CompressionLevel { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the threshold of characters in text metadata, when compression should be used.
|
|||
/// </summary>
|
|||
int TextCompressionThreshold { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the gamma value, that will be written the image.
|
|||
/// </summary>
|
|||
/// <value>The gamma value of the image.</value>
|
|||
float? Gamma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the quantizer for reducing the color count.
|
|||
/// </summary>
|
|||
IQuantizer Quantizer { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transparency threshold.
|
|||
/// </summary>
|
|||
byte Threshold { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this instance should write an Adam7 interlaced image.
|
|||
/// </summary>
|
|||
PngInterlaceMode? InterlaceMethod { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the metadata should be ignored when the image is being encoded.
|
|||
/// When set to true, all ancillary chunks will be skipped.
|
|||
/// </summary>
|
|||
bool IgnoreMetadata { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chunk filter method. This allows to filter ancillary chunks.
|
|||
/// </summary>
|
|||
PngChunkFilter? ChunkFilter { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether fully transparent pixels that may contain R, G, B values which are not 0,
|
|||
/// should be converted to transparent black, which can yield in better compression in some cases.
|
|||
/// </summary>
|
|||
PngTransparentColorMode TransparentColorMode { get; } |
|||
} |
|||
@ -1,68 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png; |
|||
|
|||
/// <summary>
|
|||
/// The options structure for the <see cref="PngEncoderCore"/>.
|
|||
/// </summary>
|
|||
internal class PngEncoderOptions : IPngEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PngEncoderOptions"/> class.
|
|||
/// </summary>
|
|||
/// <param name="source">The source.</param>
|
|||
public PngEncoderOptions(IPngEncoderOptions source) |
|||
{ |
|||
this.BitDepth = source.BitDepth; |
|||
this.ColorType = source.ColorType; |
|||
this.FilterMethod = source.FilterMethod; |
|||
this.CompressionLevel = source.CompressionLevel; |
|||
this.TextCompressionThreshold = source.TextCompressionThreshold; |
|||
this.Gamma = source.Gamma; |
|||
this.Quantizer = source.Quantizer; |
|||
this.Threshold = source.Threshold; |
|||
this.InterlaceMethod = source.InterlaceMethod; |
|||
this.ChunkFilter = source.ChunkFilter; |
|||
this.IgnoreMetadata = source.IgnoreMetadata; |
|||
this.TransparentColorMode = source.TransparentColorMode; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngBitDepth? BitDepth { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngColorType? ColorType { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngFilterMethod? FilterMethod { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression; |
|||
|
|||
/// <inheritdoc/>
|
|||
public int TextCompressionThreshold { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public float? Gamma { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public IQuantizer Quantizer { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public byte Threshold { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngInterlaceMode? InterlaceMethod { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngChunkFilter? ChunkFilter { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool IgnoreMetadata { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public PngTransparentColorMode TransparentColorMode { get; set; } |
|||
} |
|||
@ -1,214 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Png; |
|||
|
|||
/// <summary>
|
|||
/// The helper methods for the PNG encoder options.
|
|||
/// </summary>
|
|||
internal static class PngEncoderOptionsHelpers |
|||
{ |
|||
/// <summary>
|
|||
/// Adjusts the options based upon the given metadata.
|
|||
/// </summary>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <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>
|
|||
public static void AdjustOptions<TPixel>( |
|||
PngEncoderOptions options, |
|||
PngMetadata pngMetadata, |
|||
out bool use16Bit, |
|||
out int bytesPerPixel) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
// Always take the encoder options over the metadata values.
|
|||
options.Gamma ??= pngMetadata.Gamma; |
|||
|
|||
// Use options, then check metadata, if nothing set there then we suggest
|
|||
// a sensible default based upon the pixel format.
|
|||
options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType<TPixel>(); |
|||
options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth<TPixel>(); |
|||
if (!options.FilterMethod.HasValue) |
|||
{ |
|||
// Specification recommends default filter method None for paletted images and Paeth for others.
|
|||
if (options.ColorType == PngColorType.Palette) |
|||
{ |
|||
options.FilterMethod = PngFilterMethod.None; |
|||
} |
|||
else |
|||
{ |
|||
options.FilterMethod = PngFilterMethod.Paeth; |
|||
} |
|||
} |
|||
|
|||
// Ensure bit depth and color type are a supported combination.
|
|||
// Bit8 is the only bit depth supported by all color types.
|
|||
byte bits = (byte)options.BitDepth; |
|||
byte[] validBitDepths = PngConstants.ColorTypes[options.ColorType.Value]; |
|||
if (Array.IndexOf(validBitDepths, bits) == -1) |
|||
{ |
|||
options.BitDepth = PngBitDepth.Bit8; |
|||
} |
|||
|
|||
options.InterlaceMethod ??= pngMetadata.InterlaceMethod; |
|||
|
|||
use16Bit = options.BitDepth == PngBitDepth.Bit16; |
|||
bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit); |
|||
|
|||
if (options.IgnoreMetadata) |
|||
{ |
|||
options.ChunkFilter = PngChunkFilter.ExcludeAll; |
|||
} |
|||
} |
|||
|
|||
/// <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 = ColorNumerics.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()); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the bit depth value.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="quantizedFrame">The quantized frame.</param>
|
|||
public static byte CalculateBitDepth<TPixel>( |
|||
PngEncoderOptions options, |
|||
IndexedImageFrame<TPixel> quantizedFrame) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
byte bitDepth; |
|||
if (options.ColorType == PngColorType.Palette) |
|||
{ |
|||
byte quantizedBits = (byte)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(quantizedFrame.Palette.Length), 1, 8); |
|||
byte bits = Math.Max((byte)options.BitDepth, quantizedBits); |
|||
|
|||
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
|
|||
// We check again for the bit depth as the bit depth of the color palette from a given quantizer might not
|
|||
// be within the acceptable range.
|
|||
if (bits == 3) |
|||
{ |
|||
bits = 4; |
|||
} |
|||
else if (bits >= 5 && bits <= 7) |
|||
{ |
|||
bits = 8; |
|||
} |
|||
|
|||
bitDepth = bits; |
|||
} |
|||
else |
|||
{ |
|||
bitDepth = (byte)options.BitDepth; |
|||
} |
|||
|
|||
if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bitDepth) == -1) |
|||
{ |
|||
throw new NotSupportedException("Bit depth is not supported or not valid."); |
|||
} |
|||
|
|||
return bitDepth; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the correct number of bytes per pixel for the given color type.
|
|||
/// </summary>
|
|||
/// <returns>Bytes per pixel.</returns>
|
|||
private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit) |
|||
{ |
|||
return pngColorType switch |
|||
{ |
|||
PngColorType.Grayscale => use16Bit ? 2 : 1, |
|||
PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2, |
|||
PngColorType.Palette => 1, |
|||
PngColorType.Rgb => use16Bit ? 6 : 3, |
|||
|
|||
// PngColorType.RgbWithAlpha
|
|||
_ => use16Bit ? 8 : 4, |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a suggested <see cref="PngColorType"/> for the given <typeparamref name="TPixel"/>
|
|||
/// This is not exhaustive but covers many common pixel formats.
|
|||
/// </summary>
|
|||
private static PngColorType SuggestColorType<TPixel>() |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
return typeof(TPixel) switch |
|||
{ |
|||
Type t when t == typeof(A8) => PngColorType.GrayscaleWithAlpha, |
|||
Type t when t == typeof(Argb32) => PngColorType.RgbWithAlpha, |
|||
Type t when t == typeof(Bgr24) => PngColorType.Rgb, |
|||
Type t when t == typeof(Bgra32) => PngColorType.RgbWithAlpha, |
|||
Type t when t == typeof(L8) => PngColorType.Grayscale, |
|||
Type t when t == typeof(L16) => PngColorType.Grayscale, |
|||
Type t when t == typeof(La16) => PngColorType.GrayscaleWithAlpha, |
|||
Type t when t == typeof(La32) => PngColorType.GrayscaleWithAlpha, |
|||
Type t when t == typeof(Rgb24) => PngColorType.Rgb, |
|||
Type t when t == typeof(Rgba32) => PngColorType.RgbWithAlpha, |
|||
Type t when t == typeof(Rgb48) => PngColorType.Rgb, |
|||
Type t when t == typeof(Rgba64) => PngColorType.RgbWithAlpha, |
|||
Type t when t == typeof(RgbaVector) => PngColorType.RgbWithAlpha, |
|||
_ => PngColorType.RgbWithAlpha |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a suggested <see cref="PngBitDepth"/> for the given <typeparamref name="TPixel"/>
|
|||
/// This is not exhaustive but covers many common pixel formats.
|
|||
/// </summary>
|
|||
private static PngBitDepth SuggestBitDepth<TPixel>() |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
return typeof(TPixel) switch |
|||
{ |
|||
Type t when t == typeof(A8) => PngBitDepth.Bit8, |
|||
Type t when t == typeof(Argb32) => PngBitDepth.Bit8, |
|||
Type t when t == typeof(Bgr24) => PngBitDepth.Bit8, |
|||
Type t when t == typeof(Bgra32) => PngBitDepth.Bit8, |
|||
Type t when t == typeof(L8) => PngBitDepth.Bit8, |
|||
Type t when t == typeof(L16) => PngBitDepth.Bit16, |
|||
Type t when t == typeof(La16) => PngBitDepth.Bit8, |
|||
Type t when t == typeof(La32) => PngBitDepth.Bit16, |
|||
Type t when t == typeof(Rgb24) => PngBitDepth.Bit8, |
|||
Type t when t == typeof(Rgba32) => PngBitDepth.Bit8, |
|||
Type t when t == typeof(Rgb48) => PngBitDepth.Bit16, |
|||
Type t when t == typeof(Rgba64) => PngBitDepth.Bit16, |
|||
Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16, |
|||
_ => PngBitDepth.Bit8 |
|||
}; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue