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