Browse Source

Merge branch 'master' into patch-1

pull/1200/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
a85348dd11
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  2. 44
      src/ImageSharp/Formats/Png/PngChunkFilter.cs
  3. 14
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  4. 13
      src/ImageSharp/Formats/Png/PngEncoder.cs
  5. 110
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  6. 12
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  7. 7
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  8. 22
      src/ImageSharp/Formats/Png/PngTransparentColorMode.cs
  9. 326
      src/ImageSharp/Formats/Png/Zlib/Adler32.cs
  10. 70
      src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs
  11. 295
      src/ImageSharp/Formats/Png/Zlib/Crc32.cs
  12. 43
      src/ImageSharp/Formats/Png/Zlib/IChecksum.cs
  13. 10
      src/ImageSharp/Formats/Png/Zlib/README.md
  14. 8
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
  15. BIN
      src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
  16. 1
      tests/Directory.Build.targets
  17. 72
      tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs
  18. 72
      tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs
  19. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  20. 50
      tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs
  21. 50
      tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs
  22. 18
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  23. 328
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
  24. 226
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  25. 1
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj

17
src/ImageSharp/Formats/Png/IPngEncoderOptions.cs

@ -57,5 +57,22 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets a value indicating whether this instance should write an Adam7 interlaced image. /// Gets a value indicating whether this instance should write an Adam7 interlaced image.
/// </summary> /// </summary>
PngInterlaceMode? InterlaceMethod { get; } 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; }
} }
} }

44
src/ImageSharp/Formats/Png/PngChunkFilter.cs

@ -0,0 +1,44 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Provides enumeration of available PNG optimization methods.
/// </summary>
[Flags]
public enum PngChunkFilter
{
/// <summary>
/// With the None filter, all chunks will be written.
/// </summary>
None = 0,
/// <summary>
/// Excludes the physical dimension information chunk from encoding.
/// </summary>
ExcludePhysicalChunk = 1 << 0,
/// <summary>
/// Excludes the gamma information chunk from encoding.
/// </summary>
ExcludeGammaChunk = 1 << 1,
/// <summary>
/// Excludes the eXIf chunk from encoding.
/// </summary>
ExcludeExifChunk = 1 << 2,
/// <summary>
/// Excludes the tTXt, iTXt or zTXt chunk from encoding.
/// </summary>
ExcludeTextChunks = 1 << 3,
/// <summary>
/// All ancillary chunks will be excluded.
/// </summary>
ExcludeAll = ~None
}
}

14
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -30,11 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[4]; private readonly byte[] buffer = new byte[4];
/// <summary>
/// Reusable CRC for validating chunks.
/// </summary>
private readonly Crc32 crc = new Crc32();
/// <summary> /// <summary>
/// The global configuration. /// The global configuration.
/// </summary> /// </summary>
@ -1159,18 +1154,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="chunk">The <see cref="PngChunk"/>.</param> /// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk) private void ValidateChunk(in PngChunk chunk)
{ {
uint crc = this.ReadChunkCrc(); uint inputCrc = this.ReadChunkCrc();
if (chunk.IsCritical) if (chunk.IsCritical)
{ {
Span<byte> chunkType = stackalloc byte[4]; Span<byte> chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
this.crc.Reset(); uint validCrc = Crc32.Calculate(chunkType);
this.crc.Update(chunkType); validCrc = Crc32.Calculate(validCrc, chunk.Data.GetSpan());
this.crc.Update(chunk.Data.GetSpan());
if (this.crc.Value != crc) if (validCrc != inputCrc)
{ {
string chunkTypeName = Encoding.ASCII.GetString(chunkType); string chunkTypeName = Encoding.ASCII.GetString(chunkType);
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);

13
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -34,14 +34,21 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <inheritdoc/> /// <inheritdoc/>
public IQuantizer Quantizer { get; set; } public IQuantizer Quantizer { get; set; }
/// <summary> /// <inheritdoc/>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = byte.MaxValue; public byte Threshold { get; set; } = byte.MaxValue;
/// <inheritdoc/> /// <inheritdoc/>
public PngInterlaceMode? InterlaceMethod { get; set; } public PngInterlaceMode? InterlaceMethod { get; set; }
/// <inheritdoc/>
public PngChunkFilter? ChunkFilter { get; set; }
/// <inheritdoc/>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public PngTransparentColorMode TransparentColorMode { get; set; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>

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

@ -7,7 +7,7 @@ using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Formats.Png.Zlib;
@ -47,11 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
private readonly byte[] chunkDataBuffer = new byte[16]; private readonly byte[] chunkDataBuffer = new byte[16];
/// <summary>
/// Reusable CRC for validating chunks.
/// </summary>
private readonly Crc32 crc = new Crc32();
/// <summary> /// <summary>
/// The encoder options /// The encoder options
/// </summary> /// </summary>
@ -141,10 +136,18 @@ namespace SixLabors.ImageSharp.Formats.Png
this.height = image.Height; this.height = image.Height;
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetPngMetadata();
PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
PngEncoderOptionsHelpers.AdjustOptions<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); PngEncoderOptionsHelpers.AdjustOptions<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
IndexedImageFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); Image<TPixel> clonedImage = null;
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized); bool clearTransparency = this.options.TransparentColorMode == PngTransparentColorMode.Clear;
if (clearTransparency)
{
clonedImage = image.Clone();
ClearTransparentPixels(clonedImage);
}
IndexedImageFrame<TPixel> quantized = this.CreateQuantizedImage(image, clonedImage);
stream.Write(PngConstants.HeaderBytes); stream.Write(PngConstants.HeaderBytes);
@ -155,11 +158,13 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePhysicalChunk(stream, metadata); this.WritePhysicalChunk(stream, metadata);
this.WriteExifChunk(stream, metadata); this.WriteExifChunk(stream, metadata);
this.WriteTextChunks(stream, pngMetadata); this.WriteTextChunks(stream, pngMetadata);
this.WriteDataChunks(image.Frames.RootFrame, quantized, stream); this.WriteDataChunks(clearTransparency ? clonedImage : image, quantized, stream);
this.WriteEndChunk(stream); this.WriteEndChunk(stream);
stream.Flush(); stream.Flush();
quantized?.Dispose(); quantized?.Dispose();
clonedImage?.Dispose();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -180,6 +185,55 @@ namespace SixLabors.ImageSharp.Formats.Png
this.filterBuffer = null; this.filterBuffer = null;
} }
/// <summary>
/// Convert transparent pixels, to transparent black pixels, which can yield to better compression in some cases.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="image">The cloned image where the transparent pixels will be changed.</param>
private static void ClearTransparentPixels<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 rgba32 = default;
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> span = image.GetPixelRowSpan(y);
for (int x = 0; x < image.Width; x++)
{
span[x].ToRgba32(ref rgba32);
if (rgba32.A == 0)
{
span[x].FromRgba32(Color.Transparent);
}
}
}
}
/// <summary>
/// Creates the quantized image and sets calculates and sets the bit depth.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="image">The image to quantize.</param>
/// <param name="clonedImage">Cloned image with transparent pixels are changed to black.</param>
/// <returns>The quantized image.</returns>
private IndexedImageFrame<TPixel> CreateQuantizedImage<TPixel>(Image<TPixel> image, Image<TPixel> clonedImage)
where TPixel : unmanaged, IPixel<TPixel>
{
IndexedImageFrame<TPixel> quantized;
if (this.options.TransparentColorMode == PngTransparentColorMode.Clear)
{
quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, clonedImage);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized);
}
else
{
quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized);
}
return quantized;
}
/// <summary>Collects a row of grayscale pixels.</summary> /// <summary>Collects a row of grayscale pixels.</summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The image row span.</param> /// <param name="rowSpan">The image row span.</param>
@ -602,6 +656,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="meta">The image metadata.</param> /// <param name="meta">The image metadata.</param>
private void WritePhysicalChunk(Stream stream, ImageMetadata meta) private void WritePhysicalChunk(Stream stream, ImageMetadata meta)
{ {
if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludePhysicalChunk) == PngChunkFilter.ExcludePhysicalChunk)
{
return;
}
PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer); PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer);
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size); this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size);
@ -614,6 +673,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="meta">The image metadata.</param> /// <param name="meta">The image metadata.</param>
private void WriteExifChunk(Stream stream, ImageMetadata meta) private void WriteExifChunk(Stream stream, ImageMetadata meta)
{ {
if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeExifChunk) == PngChunkFilter.ExcludeExifChunk)
{
return;
}
if (meta.ExifProfile is null || meta.ExifProfile.Values.Count == 0) if (meta.ExifProfile is null || meta.ExifProfile.Values.Count == 0)
{ {
return; return;
@ -631,6 +695,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="meta">The image metadata.</param> /// <param name="meta">The image metadata.</param>
private void WriteTextChunks(Stream stream, PngMetadata meta) private void WriteTextChunks(Stream stream, PngMetadata meta)
{ {
if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks)
{
return;
}
const int MaxLatinCode = 255; const int MaxLatinCode = 255;
for (int i = 0; i < meta.TextData.Count; i++) for (int i = 0; i < meta.TextData.Count; i++)
{ {
@ -723,6 +792,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteGammaChunk(Stream stream) private void WriteGammaChunk(Stream stream)
{ {
if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeGammaChunk) == PngChunkFilter.ExcludeGammaChunk)
{
return;
}
if (this.options.Gamma > 0) if (this.options.Gamma > 0)
{ {
// 4-byte unsigned integer of gamma * 100,000. // 4-byte unsigned integer of gamma * 100,000.
@ -792,7 +866,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The image.</param> /// <param name="pixels">The image.</param>
/// <param name="quantized">The quantized pixel data. Can be null.</param> /// <param name="quantized">The quantized pixel data. Can be null.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, Stream stream) private void WriteDataChunks<TPixel>(Image<TPixel> pixels, IndexedImageFrame<TPixel> quantized, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
byte[] buffer; byte[] buffer;
@ -890,8 +964,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The pixels.</param> /// <param name="pixels">The pixels.</param>
/// <param name="quantized">The quantized pixels span.</param> /// <param name="quantized">The quantized pixels span.</param>
/// <param name="deflateStream">The deflate stream.</param> /// <param name="deflateStream">The deflate stream.</param>
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream) private void EncodePixels<TPixel>(Image<TPixel> pixels, IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int bytesPerScanline = this.CalculateScanlineLength(this.width); int bytesPerScanline = this.CalculateScanlineLength(this.width);
int resultLength = bytesPerScanline + 1; int resultLength = bytesPerScanline + 1;
@ -914,7 +988,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="pixels">The pixels.</param> /// <param name="pixels">The pixels.</param>
/// <param name="deflateStream">The deflate stream.</param> /// <param name="deflateStream">The deflate stream.</param>
private void EncodeAdam7Pixels<TPixel>(ImageFrame<TPixel> pixels, ZlibDeflateStream deflateStream) private void EncodeAdam7Pixels<TPixel>(Image<TPixel> pixels, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = pixels.Width; int width = pixels.Width;
@ -1041,18 +1115,16 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(this.buffer, 0, 8); stream.Write(this.buffer, 0, 8);
this.crc.Reset(); uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer
this.crc.Update(this.buffer.AsSpan(4, 4)); // Write the type buffer
if (data != null && length > 0) if (data != null && length > 0)
{ {
stream.Write(data, offset, length); stream.Write(data, offset, length);
this.crc.Update(data.AsSpan(offset, length)); crc = Crc32.Calculate(crc, data.AsSpan(offset, length));
} }
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, (uint)this.crc.Value); BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc);
stream.Write(this.buffer, 0, 4); // write the crc stream.Write(this.buffer, 0, 4); // write the crc
} }

12
src/ImageSharp/Formats/Png/PngEncoderOptions.cs

@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Formats.Png
this.Quantizer = source.Quantizer; this.Quantizer = source.Quantizer;
this.Threshold = source.Threshold; this.Threshold = source.Threshold;
this.InterlaceMethod = source.InterlaceMethod; this.InterlaceMethod = source.InterlaceMethod;
this.ChunkFilter = source.ChunkFilter;
this.IgnoreMetadata = source.IgnoreMetadata;
this.TransparentColorMode = source.TransparentColorMode;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -57,5 +60,14 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <inheritdoc/> /// <inheritdoc/>
public PngInterlaceMode? InterlaceMethod { get; set; } public PngInterlaceMode? InterlaceMethod { get; set; }
/// <inheritdoc/>
public PngChunkFilter? ChunkFilter { get; set; }
/// <inheritdoc/>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public PngTransparentColorMode TransparentColorMode { get; set; }
} }
} }

7
src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

@ -40,6 +40,11 @@ namespace SixLabors.ImageSharp.Formats.Png
use16Bit = options.BitDepth == PngBitDepth.Bit16; use16Bit = options.BitDepth == PngBitDepth.Bit16;
bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit); bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit);
if (options.IgnoreMetadata)
{
options.ChunkFilter = PngChunkFilter.ExcludeAll;
}
// Ensure we are not allowing impossible combinations. // Ensure we are not allowing impossible combinations.
if (!PngConstants.ColorTypes.ContainsKey(options.ColorType.Value)) if (!PngConstants.ColorTypes.ContainsKey(options.ColorType.Value))
{ {
@ -89,11 +94,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="image">The image.</param>
/// <param name="quantizedFrame">The quantized frame.</param> /// <param name="quantizedFrame">The quantized frame.</param>
public static byte CalculateBitDepth<TPixel>( public static byte CalculateBitDepth<TPixel>(
PngEncoderOptions options, PngEncoderOptions options,
Image<TPixel> image,
IndexedImageFrame<TPixel> quantizedFrame) IndexedImageFrame<TPixel> quantizedFrame)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {

22
src/ImageSharp/Formats/Png/PngTransparentColorMode.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Enum indicating how the transparency should be handled on encoding.
/// </summary>
public enum PngTransparentColorMode
{
/// <summary>
/// The transparency will be kept as is.
/// </summary>
Preserve = 0,
/// <summary>
/// Converts fully transparent pixels that may contain R, G, B values which are not 0,
/// to transparent black, which can yield in better compression in some cases.
/// </summary>
Clear = 1,
}
}

326
src/ImageSharp/Formats/Png/Zlib/Adler32.cs

@ -3,146 +3,258 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
#pragma warning disable IDE0007 // Use implicit type
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
/// <summary> /// <summary>
/// Computes Adler32 checksum for a stream of data. An Adler32 /// Calculates the 32 bit Adler checksum of a given buffer according to
/// checksum is not as reliable as a CRC32 checksum, but a lot faster to /// RFC 1950. ZLIB Compressed Data Format Specification version 3.3)
/// compute.
/// </summary> /// </summary>
/// <remarks> internal static class Adler32
/// The specification for Adler32 may be found in RFC 1950.
/// ZLIB Compressed Data Format Specification version 3.3)
///
///
/// From that document:
///
/// "ADLER32 (Adler-32 checksum)
/// This contains a checksum value of the uncompressed data
/// (excluding any dictionary data) computed according to Adler-32
/// algorithm. This algorithm is a 32-bit extension and improvement
/// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073
/// standard.
///
/// Adler-32 is composed of two sums accumulated per byte: s1 is
/// the sum of all bytes, s2 is the sum of all s1 values. Both sums
/// are done modulo 65521. s1 is initialized to 1, s2 to zero. The
/// Adler-32 checksum is stored as s2*65536 + s1 in most-
/// significant-byte first (network) order."
///
/// "8.2. The Adler-32 algorithm
///
/// The Adler-32 algorithm is much faster than the CRC32 algorithm yet
/// still provides an extremely low probability of undetected errors.
///
/// The modulo on unsigned long accumulators can be delayed for 5552
/// bytes, so the modulo operation time is negligible. If the bytes
/// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position
/// and order sensitive, unlike the first sum, which is just a
/// checksum. That 65521 is prime is important to avoid a possible
/// large class of two-byte errors that leave the check unchanged.
/// (The Fletcher checksum uses 255, which is not prime and which also
/// makes the Fletcher check insensitive to single byte changes 0 -
/// 255.)
///
/// The sum s1 is initialized to 1 instead of zero to make the length
/// of the sequence part of s2, so that the length does not have to be
/// checked separately. (Any sequence of zeroes has a Fletcher
/// checksum of zero.)"
/// </remarks>
/// <see cref="ZlibInflateStream"/>
/// <see cref="ZlibDeflateStream"/>
internal sealed class Adler32 : IChecksum
{ {
/// <summary> /// <summary>
/// largest prime smaller than 65536 /// The default initial seed value of a Adler32 checksum calculation.
/// </summary> /// </summary>
private const uint Base = 65521; public const uint SeedValue = 1U;
/// <summary> // Largest prime smaller than 65536
/// The checksum calculated to far. private const uint BASE = 65521;
/// </summary>
private uint checksum;
/// <summary> // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
/// Initializes a new instance of the <see cref="Adler32"/> class. private const uint NMAX = 5552;
/// The checksum starts off with a value of 1.
/// </summary>
public Adler32()
{
this.Reset();
}
/// <inheritdoc/> #if SUPPORTS_RUNTIME_INTRINSICS
public long Value private const int MinBufferSize = 64;
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.checksum;
}
/// <inheritdoc/> // The C# compiler emits this as a compile-time constant embedded in the PE file.
[MethodImpl(MethodImplOptions.AggressiveInlining)] private static ReadOnlySpan<byte> Tap1Tap2 => new byte[]
public void Reset()
{ {
this.checksum = 1; 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, // tap1
} 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 // tap2
};
#endif
/// <summary>
/// Calculates the Adler32 checksum with the bytes taken from the span.
/// </summary>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(ReadOnlySpan<byte> buffer)
=> Calculate(SeedValue, buffer);
/// <summary> /// <summary>
/// Updates the checksum with a byte value. /// Calculates the Adler32 checksum with the bytes taken from the span and seed.
/// </summary> /// </summary>
/// <param name="value"> /// <param name="adler">The input Adler32 value.</param>
/// The data value to add. The high byte of the int is ignored. /// <param name="buffer">The readonly span of bytes.</param>
/// </param> /// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
public void Update(int value) public static uint Calculate(uint adler, ReadOnlySpan<byte> buffer)
{ {
// We could make a length 1 byte array and call update again, but I if (buffer.IsEmpty)
// would rather not have that overhead {
uint s1 = this.checksum & 0xFFFF; return adler;
uint s2 = this.checksum >> 16; }
s1 = (s1 + ((uint)value & 0xFF)) % Base; #if SUPPORTS_RUNTIME_INTRINSICS
s2 = (s1 + s2) % Base; if (Sse3.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateSse(adler, buffer);
}
this.checksum = (s2 << 16) + s1; return CalculateScalar(adler, buffer);
#else
return CalculateScalar(adler, buffer);
#endif
} }
/// <inheritdoc/> // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
[MethodImpl(MethodImplOptions.AggressiveInlining)] #if SUPPORTS_RUNTIME_INTRINSICS
public void Update(ReadOnlySpan<byte> data) [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateSse(uint adler, ReadOnlySpan<byte> buffer)
{ {
ref byte dataRef = ref MemoryMarshal.GetReference(data); uint s1 = adler & 0xFFFF;
uint s1 = this.checksum & 0xFFFF; uint s2 = (adler >> 16) & 0xFFFF;
uint s2 = this.checksum >> 16;
int count = data.Length; // Process the data in blocks.
int offset = 0; const int BLOCK_SIZE = 1 << 5;
while (count > 0) uint length = (uint)buffer.Length;
uint blocks = length / BLOCK_SIZE;
length -= blocks * BLOCK_SIZE;
int index = 0;
fixed (byte* bufferPtr = buffer)
fixed (byte* tapPtr = Tap1Tap2)
{ {
// We can defer the modulo operation: index += (int)blocks * BLOCK_SIZE;
// s1 maximally grows from 65521 to 65521 + 255 * 3800 var localBufferPtr = bufferPtr;
// s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31
int n = 3800; // _mm_setr_epi8 on x86
if (n > count) Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
Vector128<sbyte> tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
Vector128<byte> zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
while (blocks > 0)
{ {
n = count; uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */
if (n > blocks)
{
n = blocks;
}
blocks -= n;
// Process n blocks of data. At most NMAX data bytes can be
// processed before s2 must be reduced modulo BASE.
Vector128<uint> v_ps = Vector128.CreateScalar(s1 * n);
Vector128<uint> v_s2 = Vector128.CreateScalar(s2);
Vector128<uint> v_s1 = Vector128<uint>.Zero;
do
{
// Load 32 input bytes.
Vector128<byte> bytes1 = Sse3.LoadDquVector128(localBufferPtr);
Vector128<byte> bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
// Add previous block byte sum to v_ps.
v_ps = Sse2.Add(v_ps, v_s1);
// Horizontally add the bytes for s1, multiply-adds the
// bytes by [ 32, 31, 30, ... ] for s2.
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32());
Vector128<short> mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32());
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32());
Vector128<short> mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
localBufferPtr += BLOCK_SIZE;
}
while (--n > 0);
v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5));
// Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
const byte S2301 = 0b1011_0001; // A B C D -> B A D C
const byte S1032 = 0b0100_1110; // A B C D -> C D A B
v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032));
s1 += v_s1.ToScalar();
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301));
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032));
s2 = v_s2.ToScalar();
// Reduce.
s1 %= BASE;
s2 %= BASE;
} }
count -= n; if (length > 0)
while (--n >= 0)
{ {
s1 += Unsafe.Add(ref dataRef, offset++); if (length >= 16)
s2 += s1; {
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
length -= 16;
}
while (length-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
if (s1 >= BASE)
{
s1 -= BASE;
}
s2 %= BASE;
} }
s1 %= Base; return s1 | (s2 << 16);
s2 %= Base;
} }
}
#endif
this.checksum = (s2 << 16) | s1; [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan<byte> buffer)
{
uint s1 = adler & 0xFFFF;
uint s2 = (adler >> 16) & 0xFFFF;
uint k;
fixed (byte* bufferPtr = buffer)
{
var localBufferPtr = bufferPtr;
uint length = (uint)buffer.Length;
while (length > 0)
{
k = length < NMAX ? length : NMAX;
length -= k;
while (k >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
k -= 16;
}
while (k-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
s1 %= BASE;
s2 %= BASE;
}
return (s2 << 16) | s1;
}
} }
} }
} }

70
src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs

@ -0,0 +1,70 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <content>
/// Contains precalulated tables for scalar calculations.
/// </content>
internal static partial class Crc32
{
/// <summary>
/// The table of all possible eight bit values for fast scalar lookup.
/// </summary>
private static readonly uint[] CrcTable =
{
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419,
0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07,
0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
0x2D02EF8D
};
}
}

295
src/ImageSharp/Formats/Png/Zlib/Crc32.cs

@ -4,151 +4,212 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
/// <summary> /// <summary>
/// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: /// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer
/// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. /// according to the IEEE 802.3 specification.
/// </summary> /// </summary>
/// <remarks> internal static partial class Crc32
/// <para>
/// Polynomials over GF(2) are represented in binary, one bit per coefficient,
/// with the lowest powers in the most significant bit. Then adding polynomials
/// is just exclusive-or, and multiplying a polynomial by x is a right shift by
/// one. If we call the above polynomial p, and represent a byte as the
/// polynomial q, also with the lowest power in the most significant bit (so the
/// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,
/// where a mod b means the remainder after dividing a by b.
/// </para>
/// <para>
/// This calculation is done using the shift-register method of multiplying and
/// taking the remainder. The register is initialized to zero, and for each
/// incoming bit, x^32 is added mod p to the register if the bit is a one (where
/// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by
/// x (which is shifting right by one and adding x^32 mod p if the bit shifted
/// out is a one). We start with the highest power (least significant bit) of
/// q and repeat for all eight bits of q.
/// </para>
/// <para>
/// The table is simply the CRC of all possible eight bit values. This is all
/// the information needed to generate CRC's on data a byte at a time for all
/// combinations of CRC register values and incoming bytes.
/// </para>
/// </remarks>
internal sealed class Crc32 : IChecksum
{ {
/// <summary> /// <summary>
/// The cycle redundancy check seed /// The default initial seed value of a Crc32 checksum calculation.
/// </summary> /// </summary>
private const uint CrcSeed = 0xFFFFFFFF; public const uint SeedValue = 0U;
/// <summary> #if SUPPORTS_RUNTIME_INTRINSICS
/// The table of all possible eight bit values for fast lookup. private const int MinBufferSize = 64;
/// </summary> private const int ChunksizeMask = 15;
private static readonly uint[] CrcTable =
// Definitions of the bit-reflected domain constants k1, k2, k3, etc and
// the CRC32+Barrett polynomials given at the end of the paper.
private static readonly ulong[] K05Poly =
{ {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x0154442bd4, 0x01c6e41596, // k1, k2
0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0x01751997d0, 0x00ccaa009e, // k3, k4
0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x0163cd6124, 0x0000000000, // k5, k0
0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x01db710641, 0x01f7011641 // polynomial
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
0x2D02EF8D
}; };
#endif
/// <summary> /// <summary>
/// The data checksum so far. /// Calculates the CRC checksum with the bytes taken from the span.
/// </summary> /// </summary>
private uint crc; /// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(ReadOnlySpan<byte> buffer)
=> Calculate(SeedValue, buffer);
/// <inheritdoc/> /// <summary>
public long Value /// Calculates the CRC checksum with the bytes taken from the span and seed.
/// </summary>
/// <param name="crc">The input CRC value.</param>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(uint crc, ReadOnlySpan<byte> buffer)
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] if (buffer.IsEmpty)
get => this.crc; {
return crc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] #if SUPPORTS_RUNTIME_INTRINSICS
set => this.crc = (uint)value; if (Sse41.IsSupported && Pclmulqdq.IsSupported && buffer.Length >= MinBufferSize)
{
return ~CalculateSse(~crc, buffer);
}
else
{
return ~CalculateScalar(~crc, buffer);
}
#else
return ~CalculateScalar(~crc, buffer);
#endif
} }
/// <inheritdoc/> #if SUPPORTS_RUNTIME_INTRINSICS
[MethodImpl(MethodImplOptions.AggressiveInlining)] // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/crc32_simd.c
public void Reset() [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateSse(uint crc, ReadOnlySpan<byte> buffer)
{ {
this.crc = 0; int chunksize = buffer.Length & ~ChunksizeMask;
} int length = chunksize;
/// <summary> fixed (byte* bufferPtr = buffer)
/// Updates the checksum with the given value. fixed (ulong* k05PolyPtr = K05Poly)
/// </summary> {
/// <param name="value">The byte is taken as the lower 8 bits of value.</param> byte* localBufferPtr = bufferPtr;
[MethodImpl(MethodImplOptions.AggressiveInlining)] ulong* localK05PolyPtr = k05PolyPtr;
public void Update(int value)
{ // There's at least one block of 64.
this.crc ^= CrcSeed; Vector128<ulong> x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
this.crc = CrcTable[(this.crc ^ value) & 0xFF] ^ (this.crc >> 8); Vector128<ulong> x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
this.crc ^= CrcSeed; Vector128<ulong> x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
Vector128<ulong> x5;
x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64());
// k1, k2
Vector128<ulong> x0 = Sse2.LoadVector128(localK05PolyPtr + 0x0);
localBufferPtr += 64;
length -= 64;
// Parallel fold blocks of 64, if any.
while (length >= 64)
{
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
Vector128<ulong> x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
Vector128<ulong> x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00);
Vector128<ulong> x8 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x11);
x3 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x11);
x4 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x11);
Vector128<ulong> y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
Vector128<ulong> y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
Vector128<ulong> y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> y8 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
x1 = Sse2.Xor(x1, x5);
x2 = Sse2.Xor(x2, x6);
x3 = Sse2.Xor(x3, x7);
x4 = Sse2.Xor(x4, x8);
x1 = Sse2.Xor(x1, y5);
x2 = Sse2.Xor(x2, y6);
x3 = Sse2.Xor(x3, y7);
x4 = Sse2.Xor(x4, y8);
localBufferPtr += 64;
length -= 64;
}
// Fold into 128-bits.
// k3, k4
x0 = Sse2.LoadVector128(k05PolyPtr + 0x2);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x2);
x1 = Sse2.Xor(x1, x5);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x3);
x1 = Sse2.Xor(x1, x5);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x4);
x1 = Sse2.Xor(x1, x5);
// Single fold blocks of 16, if any.
while (length >= 16)
{
x2 = Sse2.LoadVector128((ulong*)localBufferPtr);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x2);
x1 = Sse2.Xor(x1, x5);
localBufferPtr += 16;
length -= 16;
}
// Fold 128 - bits to 64 - bits.
x2 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x10);
x3 = Vector128.Create(~0, 0, ~0, 0).AsUInt64(); // _mm_setr_epi32 on x86
x1 = Sse2.ShiftRightLogical128BitLane(x1, 8);
x1 = Sse2.Xor(x1, x2);
// k5, k0
x0 = Sse2.LoadScalarVector128(localK05PolyPtr + 0x4);
x2 = Sse2.ShiftRightLogical128BitLane(x1, 4);
x1 = Sse2.And(x1, x3);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
// Barret reduce to 32-bits.
// polynomial
x0 = Sse2.LoadVector128(localK05PolyPtr + 0x6);
x2 = Sse2.And(x1, x3);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x10);
x2 = Sse2.And(x2, x3);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
crc = (uint)Sse41.Extract(x1.AsInt32(), 1);
return buffer.Length - chunksize == 0 ? crc : CalculateScalar(crc, buffer.Slice(chunksize));
}
} }
#endif
/// <inheritdoc/> [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint CalculateScalar(uint crc, ReadOnlySpan<byte> buffer)
public void Update(ReadOnlySpan<byte> data)
{ {
this.crc ^= CrcSeed;
ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan()); ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan());
for (int i = 0; i < data.Length; i++) ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
for (int i = 0; i < buffer.Length; i++)
{ {
this.crc = Unsafe.Add(ref crcTableRef, (int)((this.crc ^ data[i]) & 0xFF)) ^ (this.crc >> 8); crc = Unsafe.Add(ref crcTableRef, (int)((crc ^ Unsafe.Add(ref bufferRef, i)) & 0xFF)) ^ (crc >> 8);
} }
this.crc ^= CrcSeed; return crc;
} }
} }
} }

43
src/ImageSharp/Formats/Png/Zlib/IChecksum.cs

@ -1,43 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// Interface to compute a data checksum used by checked input/output streams.
/// A data checksum can be updated by one byte or with a byte array. After each
/// update the value of the current checksum can be returned by calling
/// <code>Value</code>. The complete checksum object can also be reset
/// so it can be used again with new data.
/// </summary>
internal interface IChecksum
{
/// <summary>
/// Gets the data checksum computed so far.
/// </summary>
long Value { get; }
/// <summary>
/// Resets the data checksum as if no update was ever called.
/// </summary>
void Reset();
/// <summary>
/// Adds one byte to the data checksum.
/// </summary>
/// <param name = "value">
/// The data value to add. The high byte of the integer is ignored.
/// </param>
void Update(int value);
/// <summary>
/// Updates the data checksum with the bytes taken from the span.
/// </summary>
/// <param name="data">
/// buffer an array of bytes
/// </param>
void Update(ReadOnlySpan<byte> data);
}
}

10
src/ImageSharp/Formats/Png/Zlib/README.md

@ -1,5 +1,11 @@
Deflatestream implementation adapted from DeflateStream implementation adapted from
https://github.com/icsharpcode/SharpZipLib https://github.com/icsharpcode/SharpZipLib
LIcensed under MIT Licensed under MIT
Crc32 and Adler32 SIMD implementation adapted from
https://github.com/chromium/chromium
Licensed under BSD 3-Clause "New" or "Revised" License

8
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Formats.Png.Zlib
@ -20,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary> /// <summary>
/// Computes the checksum for the data stream. /// Computes the checksum for the data stream.
/// </summary> /// </summary>
private readonly Adler32 adler32 = new Adler32(); private uint adler = Adler32.SeedValue;
/// <summary> /// <summary>
/// A value indicating whether this instance of the given entity has been disposed. /// A value indicating whether this instance of the given entity has been disposed.
@ -133,10 +134,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override void SetLength(long value) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException();
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)
{ {
this.deflateStream.Write(buffer, offset, count); this.deflateStream.Write(buffer, offset, count);
this.adler32.Update(buffer.AsSpan(offset, count)); this.adler = Adler32.Calculate(this.adler, buffer.AsSpan(offset, count));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -153,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.deflateStream.Dispose(); this.deflateStream.Dispose();
// Add the crc // Add the crc
uint crc = (uint)this.adler32.Value; uint crc = this.adler;
this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF));
this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF));
this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF));

BIN
src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf

Binary file not shown.

1
tests/Directory.Build.targets

@ -34,6 +34,7 @@
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.5.0-preview-20200116-01" /> <PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.5.0-preview-20200116-01" />
<PackageReference Update="Moq" Version="4.10.0" /> <PackageReference Update="Moq" Version="4.10.0" />
<PackageReference Update="Pfim" Version="0.9.1" /> <PackageReference Update="Pfim" Version="0.9.1" />
<PackageReference Update="SharpZipLib" Version="1.2.0" />
<PackageReference Update="System.Drawing.Common" Version="4.7.0" /> <PackageReference Update="System.Drawing.Common" Version="4.7.0" />
<PackageReference Update="xunit" Version="2.4.1" /> <PackageReference Update="xunit" Version="2.4.1" />
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Update="xunit.runner.visualstudio" Version="2.4.1" />

72
tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
namespace SixLabors.ImageSharp.Benchmarks.General
{
[Config(typeof(Config.ShortClr))]
public class Adler32Benchmark
{
private byte[] data;
private readonly SharpAdler32 adler = new SharpAdler32();
[Params(1024, 2048, 4096)]
public int Count { get; set; }
[GlobalSetup]
public void SetUp()
{
this.data = new byte[this.Count];
new Random(1).NextBytes(this.data);
}
[Benchmark(Baseline = true)]
public long SharpZipLibCalculate()
{
this.adler.Reset();
this.adler.Update(this.data);
return this.adler.Value;
}
[Benchmark]
public uint SixLaborsCalculate()
{
return Adler32.Calculate(this.data);
}
}
// ########## 17/05/2020 ##########
//
// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------------------- |-------------- |------ |------------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:|
// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 793.18 ns | 775.66 ns | 42.516 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 384.86 ns | 15.64 ns | 0.857 ns | 0.49 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 790.31 ns | 353.34 ns | 19.368 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 465.28 ns | 652.41 ns | 35.761 ns | 0.59 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 877.25 ns | 97.89 ns | 5.365 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 45.60 ns | 13.28 ns | 0.728 ns | 0.05 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 1,537.04 ns | 428.44 ns | 23.484 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 849.76 ns | 1,066.34 ns | 58.450 ns | 0.55 | 0.04 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 1,616.97 ns | 276.70 ns | 15.167 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 790.77 ns | 691.71 ns | 37.915 ns | 0.49 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 1,735.11 ns | 1,374.22 ns | 75.325 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 87.80 ns | 56.84 ns | 3.116 ns | 0.05 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 3,054.53 ns | 796.41 ns | 43.654 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 1,538.90 ns | 487.02 ns | 26.695 ns | 0.50 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 3,223.48 ns | 32.32 ns | 1.771 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 1,547.60 ns | 309.72 ns | 16.977 ns | 0.48 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 3,672.33 ns | 1,095.81 ns | 60.065 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 159.44 ns | 36.31 ns | 1.990 ns | 0.04 | 0.00 | - | - | - | - |
}

72
tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32;
namespace SixLabors.ImageSharp.Benchmarks.General
{
[Config(typeof(Config.ShortClr))]
public class Crc32Benchmark
{
private byte[] data;
private readonly SharpCrc32 crc = new SharpCrc32();
[Params(1024, 2048, 4096)]
public int Count { get; set; }
[GlobalSetup]
public void SetUp()
{
this.data = new byte[this.Count];
new Random(1).NextBytes(this.data);
}
[Benchmark(Baseline = true)]
public long SharpZipLibCalculate()
{
this.crc.Reset();
this.crc.Update(this.data);
return this.crc.Value;
}
[Benchmark]
public long SixLaborsCalculate()
{
return Crc32.Calculate(this.data);
}
}
// ########## 17/05/2020 ##########
//
// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------------------- |-------------- |------ |-------------:|-------------:|-----------:|------:|--------:|------:|------:|------:|----------:|
// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 2,797.77 ns | 278.697 ns | 15.276 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 2,275.56 ns | 216.100 ns | 11.845 ns | 0.81 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 2,923.43 ns | 2,656.882 ns | 145.633 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 2,257.79 ns | 75.081 ns | 4.115 ns | 0.77 | 0.04 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 2,764.14 ns | 86.281 ns | 4.729 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 49.32 ns | 1.813 ns | 0.099 ns | 0.02 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 5,603.71 ns | 427.240 ns | 23.418 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 4,525.02 ns | 33.931 ns | 1.860 ns | 0.81 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 5,563.32 ns | 49.337 ns | 2.704 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 4,519.61 ns | 29.837 ns | 1.635 ns | 0.81 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 5,543.37 ns | 518.551 ns | 28.424 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 89.07 ns | 3.312 ns | 0.182 ns | 0.02 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 11,396.95 ns | 373.450 ns | 20.470 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 9,070.35 ns | 271.083 ns | 14.859 ns | 0.80 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 11,127.81 ns | 239.177 ns | 13.110 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 9,050.46 ns | 230.916 ns | 12.657 ns | 0.81 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 11,098.62 ns | 687.978 ns | 37.710 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 168.11 ns | 3.633 ns | 0.199 ns | 0.02 | 0.00 | - | - | - | - |
}

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -25,6 +25,7 @@
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Condition="'$(OS)' == 'Windows_NT'" /> <PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Include="Colourful" /> <PackageReference Include="Colourful" />
<PackageReference Include="Pfim" /> <PackageReference Include="Pfim" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="System.Drawing.Common" /> <PackageReference Include="System.Drawing.Common" />
</ItemGroup> </ItemGroup>

50
tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using Xunit;
using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public class Adler32Tests
{
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public void ReturnsCorrectWhenEmpty(uint input)
{
Assert.Equal(input, Adler32.Calculate(input, default));
}
[Theory]
[InlineData(0)]
[InlineData(8)]
[InlineData(215)]
[InlineData(1024)]
[InlineData(1024 + 15)]
[InlineData(2034)]
[InlineData(4096)]
public void MatchesReference(int length)
{
var data = GetBuffer(length);
var adler = new SharpAdler32();
adler.Update(data);
long expected = adler.Value;
long actual = Adler32.Calculate(data);
Assert.Equal(expected, actual);
}
private static byte[] GetBuffer(int length)
{
var data = new byte[length];
new Random(1).NextBytes(data);
return data;
}
}
}

50
tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using Xunit;
using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public class Crc32Tests
{
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public void ReturnsCorrectWhenEmpty(uint input)
{
Assert.Equal(input, Crc32.Calculate(input, default));
}
[Theory]
[InlineData(0)]
[InlineData(8)]
[InlineData(215)]
[InlineData(1024)]
[InlineData(1024 + 15)]
[InlineData(2034)]
[InlineData(4096)]
public void MatchesReference(int length)
{
var data = GetBuffer(length);
var crc = new SharpCrc32();
crc.Update(data);
long expected = crc.Value;
long actual = Crc32.Calculate(data);
Assert.Equal(expected, actual);
}
private static byte[] GetBuffer(int length)
{
var data = new byte[length];
new Random(1).NextBytes(data);
return data;
}
}
}

18
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3. // Licensed under the GNU Affero General Public License, Version 3.
using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Text; using System.Text;
@ -16,6 +17,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
{ {
public partial class PngDecoderTests public partial class PngDecoderTests
{ {
// Represents ASCII string of "123456789"
private readonly byte[] check = { 49, 50, 51, 52, 53, 54, 55, 56, 57 };
// Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel. // Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel.
private static readonly byte[] Raw1X1PngIhdrAndpHYs = private static readonly byte[] Raw1X1PngIhdrAndpHYs =
{ {
@ -79,20 +83,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
} }
} }
[Fact]
public void CalculateCrc_Works()
{
// arrange
var data = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
var crc = new Crc32();
// act
crc.Update(data);
// assert
Assert.Equal(0x88AA689F, crc.Value);
}
private static string GetChunkTypeName(uint value) private static string GetChunkTypeName(uint value)
{ {
var data = new byte[4]; var data = new byte[4];

328
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs

@ -0,0 +1,328 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public partial class PngEncoderTests
{
[Fact]
public void HeaderChunk_ComesFirst()
{
// arrange
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
// act
input.Save(memStream, PngEncoder);
// assert
memStream.Position = 0;
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.Equal(PngChunkType.Header, type);
}
[Fact]
public void EndChunk_IsLast()
{
// arrange
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
// act
input.Save(memStream, PngEncoder);
// assert
memStream.Position = 0;
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
bool endChunkFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.False(endChunkFound);
if (type == PngChunkType.End)
{
endChunkFound = true;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Theory]
[InlineData(PngChunkType.Gamma)]
[InlineData(PngChunkType.Chroma)]
[InlineData(PngChunkType.EmbeddedColorProfile)]
[InlineData(PngChunkType.SignificantBits)]
[InlineData(PngChunkType.StandardRgbColourSpace)]
public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj)
{
// arrange
var chunkType = (PngChunkType)chunkTypeObj;
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
// act
input.Save(memStream, PngEncoder);
// assert
memStream.Position = 0;
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
bool palFound = false;
bool dataFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
if (chunkType == type)
{
Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk");
}
switch (type)
{
case PngChunkType.Data:
dataFound = true;
break;
case PngChunkType.Palette:
palFound = true;
break;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Theory]
[InlineData(PngChunkType.Physical)]
[InlineData(PngChunkType.SuggestedPalette)]
public void Chunk_ComesBeforeIDat(object chunkTypeObj)
{
// arrange
var chunkType = (PngChunkType)chunkTypeObj;
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
// act
input.Save(memStream, PngEncoder);
// assert
memStream.Position = 0;
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
bool dataFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
if (chunkType == type)
{
Assert.False(dataFound, $"{chunkType} chunk should come before data chunk");
}
if (type == PngChunkType.Data)
{
dataFound = true;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Fact]
public void IgnoreMetadata_WillExcludeAllAncillaryChunks()
{
// arrange
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
var encoder = new PngEncoder() { IgnoreMetadata = true, TextCompressionThreshold = 8 };
var expectedChunkTypes = new Dictionary<PngChunkType, bool>()
{
{ PngChunkType.Header, false },
{ PngChunkType.Palette, false },
{ PngChunkType.Data, false },
{ PngChunkType.End, false }
};
var excludedChunkTypes = new List<PngChunkType>()
{
PngChunkType.Gamma,
PngChunkType.Exif,
PngChunkType.Physical,
PngChunkType.Text,
PngChunkType.InternationalText,
PngChunkType.CompressedText,
};
// act
input.Save(memStream, encoder);
// assert
Assert.True(excludedChunkTypes.Count > 0);
memStream.Position = 0;
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded");
if (expectedChunkTypes.ContainsKey(chunkType))
{
expectedChunkTypes[chunkType] = true;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
// all expected chunk types should have been seen at least once.
foreach (PngChunkType chunkType in expectedChunkTypes.Keys)
{
Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once");
}
}
[Theory]
[InlineData(PngChunkFilter.ExcludeGammaChunk)]
[InlineData(PngChunkFilter.ExcludeExifChunk)]
[InlineData(PngChunkFilter.ExcludePhysicalChunk)]
[InlineData(PngChunkFilter.ExcludeTextChunks)]
[InlineData(PngChunkFilter.ExcludeAll)]
public void ExcludeFilter_Works(object filterObj)
{
// arrange
var chunkFilter = (PngChunkFilter)filterObj;
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
var encoder = new PngEncoder() { ChunkFilter = chunkFilter, TextCompressionThreshold = 8 };
var expectedChunkTypes = new Dictionary<PngChunkType, bool>()
{
{ PngChunkType.Header, false },
{ PngChunkType.Gamma, false },
{ PngChunkType.Palette, false },
{ PngChunkType.InternationalText, false },
{ PngChunkType.Text, false },
{ PngChunkType.CompressedText, false },
{ PngChunkType.Exif, false },
{ PngChunkType.Physical, false },
{ PngChunkType.Data, false },
{ PngChunkType.End, false }
};
var excludedChunkTypes = new List<PngChunkType>();
switch (chunkFilter)
{
case PngChunkFilter.ExcludeGammaChunk:
excludedChunkTypes.Add(PngChunkType.Gamma);
expectedChunkTypes.Remove(PngChunkType.Gamma);
break;
case PngChunkFilter.ExcludeExifChunk:
excludedChunkTypes.Add(PngChunkType.Exif);
expectedChunkTypes.Remove(PngChunkType.Exif);
break;
case PngChunkFilter.ExcludePhysicalChunk:
excludedChunkTypes.Add(PngChunkType.Physical);
expectedChunkTypes.Remove(PngChunkType.Physical);
break;
case PngChunkFilter.ExcludeTextChunks:
excludedChunkTypes.Add(PngChunkType.Text);
excludedChunkTypes.Add(PngChunkType.InternationalText);
excludedChunkTypes.Add(PngChunkType.CompressedText);
expectedChunkTypes.Remove(PngChunkType.Text);
expectedChunkTypes.Remove(PngChunkType.InternationalText);
expectedChunkTypes.Remove(PngChunkType.CompressedText);
break;
case PngChunkFilter.ExcludeAll:
excludedChunkTypes.Add(PngChunkType.Gamma);
excludedChunkTypes.Add(PngChunkType.Exif);
excludedChunkTypes.Add(PngChunkType.Physical);
excludedChunkTypes.Add(PngChunkType.Text);
excludedChunkTypes.Add(PngChunkType.InternationalText);
excludedChunkTypes.Add(PngChunkType.CompressedText);
expectedChunkTypes.Remove(PngChunkType.Gamma);
expectedChunkTypes.Remove(PngChunkType.Exif);
expectedChunkTypes.Remove(PngChunkType.Physical);
expectedChunkTypes.Remove(PngChunkType.Text);
expectedChunkTypes.Remove(PngChunkType.InternationalText);
expectedChunkTypes.Remove(PngChunkType.CompressedText);
break;
}
// act
input.Save(memStream, encoder);
// assert
Assert.True(excludedChunkTypes.Count > 0);
memStream.Position = 0;
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded");
if (expectedChunkTypes.ContainsKey(chunkType))
{
expectedChunkTypes[chunkType] = true;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
// all expected chunk types should have been seen at least once.
foreach (PngChunkType chunkType in expectedChunkTypes.Keys)
{
Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once");
}
}
[Fact]
public void ExcludeFilter_WithNone_DoesNotExcludeChunks()
{
// arrange
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
var encoder = new PngEncoder() { ChunkFilter = PngChunkFilter.None, TextCompressionThreshold = 8 };
var expectedChunkTypes = new List<PngChunkType>()
{
PngChunkType.Header,
PngChunkType.Gamma,
PngChunkType.Palette,
PngChunkType.InternationalText,
PngChunkType.Text,
PngChunkType.CompressedText,
PngChunkType.Exif,
PngChunkType.Physical,
PngChunkType.Data,
PngChunkType.End,
};
// act
input.Save(memStream, encoder);
memStream.Position = 0;
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.True(expectedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been present");
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
}
}

226
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -2,8 +2,6 @@
// Licensed under the GNU Affero General Public License, Version 3. // Licensed under the GNU Affero General Public License, Version 3.
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System;
using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -18,7 +16,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png namespace SixLabors.ImageSharp.Tests.Formats.Png
{ {
public class PngEncoderTests public partial class PngEncoderTests
{ {
private static PngEncoder PngEncoder => new PngEncoder(); private static PngEncoder PngEncoder => new PngEncoder();
@ -215,6 +213,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
} }
} }
[Theory]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)]
public void WorksWithAllBitDepthsAndExcludeAllFilter<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
pngColorType,
PngFilterMethod.Adaptive,
pngBitDepth,
interlaceMode,
appendPngColorType: true,
appendPixelType: true,
appendPngBitDepth: true,
optimizeMethod: PngChunkFilter.ExcludeAll);
}
}
[Theory] [Theory]
[WithBlankImages(1, 1, PixelTypes.A8, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] [WithBlankImages(1, 1, PixelTypes.A8, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
[WithBlankImages(1, 1, PixelTypes.Argb32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] [WithBlankImages(1, 1, PixelTypes.Argb32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
@ -358,6 +390,66 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
} }
} }
[Theory]
[InlineData(PngColorType.Palette)]
[InlineData(PngColorType.RgbWithAlpha)]
[InlineData(PngColorType.GrayscaleWithAlpha)]
public void Encode_WithPngTransparentColorBehaviorClear_Works(PngColorType colorType)
{
// arrange
var image = new Image<Rgba32>(50, 50);
var encoder = new PngEncoder()
{
TransparentColorMode = PngTransparentColorMode.Clear,
ColorType = colorType
};
Rgba32 rgba32 = Color.Blue;
for (int y = 0; y < image.Height; y++)
{
System.Span<Rgba32> rowSpan = image.GetPixelRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
{
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x].FromRgba32(rgba32);
}
}
// act
using var memStream = new MemoryStream();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
using var actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue;
if (colorType == PngColorType.Grayscale || colorType == PngColorType.GrayscaleWithAlpha)
{
var luminance = ImageMaths.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B);
expectedColor = new Rgba32(luminance, luminance, luminance);
}
for (int y = 0; y < actual.Height; y++)
{
System.Span<Rgba32> rowSpan = actual.GetPixelRowSpan(y);
if (y > 25)
{
expectedColor = Color.Transparent;
}
for (int x = 0; x < actual.Width; x++)
{
Assert.Equal(expectedColor, rowSpan[x]);
}
}
}
[Theory] [Theory]
[MemberData(nameof(PngTrnsFiles))] [MemberData(nameof(PngTrnsFiles))]
public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType)
@ -411,126 +503,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
} }
} }
[Fact]
public void HeaderChunk_ComesFirst()
{
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
// Skip header.
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8);
BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.Equal(PngChunkType.Header, type);
}
[Fact]
public void EndChunk_IsLast()
{
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
// Skip header.
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8);
bool endChunkFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
Assert.False(endChunkFound);
if (type == PngChunkType.End)
{
endChunkFound = true;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Theory]
[InlineData(PngChunkType.Gamma)]
[InlineData(PngChunkType.Chroma)]
[InlineData(PngChunkType.EmbeddedColorProfile)]
[InlineData(PngChunkType.SignificantBits)]
[InlineData(PngChunkType.StandardRgbColourSpace)]
public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj)
{
var chunkType = (PngChunkType)chunkTypeObj;
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
// Skip header.
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8);
bool palFound = false;
bool dataFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
if (chunkType == type)
{
Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk");
}
switch (type)
{
case PngChunkType.Data:
dataFound = true;
break;
case PngChunkType.Palette:
palFound = true;
break;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Theory]
[InlineData(PngChunkType.Physical)]
[InlineData(PngChunkType.SuggestedPalette)]
public void Chunk_ComesBeforeIDat(object chunkTypeObj)
{
var chunkType = (PngChunkType)chunkTypeObj;
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
// Skip header.
Span<byte> bytesSpan = memStream.ToArray().AsSpan(8);
bool dataFound = false;
while (bytesSpan.Length > 0)
{
int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
if (chunkType == type)
{
Assert.False(dataFound, $"{chunkType} chunk should come before data chunk");
}
if (type == PngChunkType.Data)
{
dataFound = true;
}
bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
}
}
[Theory] [Theory]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32)] [WithTestPatternImages(587, 821, PixelTypes.Rgba32)]
[WithTestPatternImages(677, 683, PixelTypes.Rgba32)] [WithTestPatternImages(677, 683, PixelTypes.Rgba32)]
@ -564,8 +536,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
bool appendPixelType = false, bool appendPixelType = false,
bool appendCompressionLevel = false, bool appendCompressionLevel = false,
bool appendPaletteSize = false, bool appendPaletteSize = false,
bool appendPngBitDepth = false) bool appendPngBitDepth = false,
where TPixel : unmanaged, IPixel<TPixel> PngChunkFilter optimizeMethod = PngChunkFilter.None)
where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
@ -576,7 +549,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
CompressionLevel = compressionLevel, CompressionLevel = compressionLevel,
BitDepth = bitDepth, BitDepth = bitDepth,
Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }),
InterlaceMethod = interlaceMode InterlaceMethod = interlaceMode,
ChunkFilter = optimizeMethod,
}; };
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;

1
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -21,6 +21,7 @@
<PackageReference Include="Magick.NET-Q16-AnyCPU" /> <PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="Microsoft.DotNet.RemoteExecutor" /> <PackageReference Include="Microsoft.DotNet.RemoteExecutor" />
<PackageReference Include="Moq" /> <PackageReference Include="Moq" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="System.Drawing.Common" /> <PackageReference Include="System.Drawing.Common" />
</ItemGroup> </ItemGroup>

Loading…
Cancel
Save