diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 66ef146e44..cfea24ad99 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -103,6 +103,22 @@ namespace ImageSharp.Formats Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount); } + /// + /// Convert salars to byte-s and copy to dest + /// + /// Pointer to block + /// Destination + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan dest) + { + float* fPtr = (float*)blockPtr; + for (int i = 0; i < ScalarCount; i++) + { + dest[i] = (byte) *fPtr; + fPtr++; + } + } + /// /// Load raw 32bit floating point data from source /// @@ -128,7 +144,7 @@ namespace ImageSharp.Formats Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount); } } - + /// /// Copy raw 32bit floating point data to dest /// diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs b/src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs new file mode 100644 index 0000000000..ff2a888a47 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/HuffIndex.cs @@ -0,0 +1,32 @@ +namespace ImageSharp.Formats.Jpg.Components +{ + /// + /// Enumerates the Huffman tables + /// + internal enum HuffIndex + { + /// + /// The DC luminance huffman table index + /// + LuminanceDC = 0, + + // ReSharper disable UnusedMember.Local + + /// + /// The AC luminance huffman table index + /// + LuminanceAC = 1, + + /// + /// The DC chrominance huffman table index + /// + ChrominanceDC = 2, + + /// + /// The AC chrominance huffman table index + /// + ChrominanceAC = 3, + + // ReSharper restore UnusedMember.Local + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs b/src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs new file mode 100644 index 0000000000..a53ebf61bf --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/HuffmanLut.cs @@ -0,0 +1,68 @@ +namespace ImageSharp.Formats.Jpg.Components +{ + /// + /// A compiled look-up table representation of a huffmanSpec. + /// Each value maps to a uint32 of which the 8 most significant bits hold the + /// codeword size in bits and the 24 least significant bits hold the codeword. + /// The maximum codeword size is 16 bits. + /// + internal struct HuffmanLut + { + /// + /// Initializes a new instance of the class. + /// + /// The encoding specifications. + public HuffmanLut(HuffmanSpec spec) + { + int maxValue = 0; + + foreach (byte v in spec.Values) + { + if (v > maxValue) + { + maxValue = v; + } + } + + this.Values = new uint[maxValue + 1]; + + int code = 0; + int k = 0; + + for (int i = 0; i < spec.Count.Length; i++) + { + int bits = (i + 1) << 24; + for (int j = 0; j < spec.Count[i]; j++) + { + this.Values[spec.Values[k]] = (uint)(bits | code); + code++; + k++; + } + + code <<= 1; + } + } + + /// + /// Initialize static members + /// + static HuffmanLut() + { + // Initialize the Huffman tables + for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++) + { + HuffmanLut.TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); + } + } + + /// + /// Gets the collection of huffman values. + /// + public uint[] Values { get; } + + /// + /// The compiled representations of theHuffmanSpec. + /// + public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs new file mode 100644 index 0000000000..909d7da031 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/HuffmanSpec.cs @@ -0,0 +1,112 @@ +namespace ImageSharp.Formats.Jpg.Components +{ + /// + /// The Huffman encoding specifications. + /// + internal struct HuffmanSpec + { + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; + + /// + /// Gets value[i] - The decoded value of the codeword at the given index. + /// + public readonly byte[] Values; + + /// + /// Initializes a new instance of the struct. + /// + /// The number of codes. + /// The decoded values. + public HuffmanSpec(byte[] count, byte[] values) + { + this.Count = count; + this.Values = values; + } + +#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines + /// + /// The Huffman encoding specifications. + /// This encoder uses the same Huffman encoding for all images. + /// + public static readonly HuffmanSpec[] TheHuffmanSpecs = + { + // Luminance DC. + new HuffmanSpec( + new byte[] + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }), + new HuffmanSpec( + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }), + + // Chrominance AC. + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, + 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }) + }; +#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs b/src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs new file mode 100644 index 0000000000..11a82f831e --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/Components/QuantIndex.cs @@ -0,0 +1,18 @@ +namespace ImageSharp.Formats.Jpg.Components +{ + /// + /// Enumerates the quantization tables + /// + internal enum QuantIndex + { + /// + /// The luminance quantization table index + /// + Luminance = 0, + + /// + /// The chrominance quantization table index + /// + Chrominance = 1, + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index 8c5ed8b90e..75c38ae5aa 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -2,14 +2,16 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp.Formats { using System; + using System.Buffers; using System.IO; using System.Numerics; using System.Runtime.CompilerServices; + using ImageSharp.Formats.Jpg.Components; + /// /// Image encoder for writing an image to a stream as a jpeg. /// @@ -20,95 +22,6 @@ namespace ImageSharp.Formats /// private const int QuantizationTableCount = 2; -#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines - - /// - /// The Huffman encoding specifications. - /// This encoder uses the same Huffman encoding for all images. - /// - private static readonly HuffmanSpec[] TheHuffmanSpecs = - { - // Luminance DC. - new HuffmanSpec( - new byte[] - { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), - new HuffmanSpec( - new byte[] - { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 - }, - new byte[] - { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, - 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, - 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, - 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, - 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, - 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, - 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, - 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, - 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, - 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, - 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa - }), - new HuffmanSpec( - new byte[] - { - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), - - // Chrominance AC. - new HuffmanSpec( - new byte[] - { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 - }, - new byte[] - { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, - 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, - 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, - 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, - 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, - 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, - 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, - 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, - 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, - 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa - }) - }; -#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines - - /// - /// The compiled representations of theHuffmanSpec. - /// - private static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; - /// /// Counts the number of bits needed to hold an integer. /// @@ -129,32 +42,6 @@ namespace ImageSharp.Formats 8, 8, 8, }; - /// - /// The unscaled quantization tables in zig-zag order. Each - /// encoder copies and scales the tables according to its quality parameter. - /// The values are derived from section K.1 after converting from natural to - /// zig-zag order. - /// - private static readonly byte[,] UnscaledQuant = - { - { - // Luminance. - 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, - 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, - 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, - 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, - 100, 120, 92, 101, 103, 99, - }, - { - // Chrominance. - 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - } - }; - /// /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: /// - the marker length "\x00\x0c", @@ -168,7 +55,9 @@ namespace ImageSharp.Formats /// private static readonly byte[] SosHeaderYCbCr = { - JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, // Marker + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, + + // Marker 0x00, 0x0c, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) @@ -181,7 +70,35 @@ namespace ImageSharp.Formats 0x11, // DC/AC Huffman table 0x00, // Ss - Start of spectral selection. 0x3f, // Se - End of spectral selection. - 0x00 // Ah + Ah (Successive approximation bit position high + low) + 0x00 + + // Ah + Ah (Successive approximation bit position high + low) + }; + + /// + /// The unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + private static readonly byte[,] UnscaledQuant = + { + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, + 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, + 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, + 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, + 100, 120, 92, 101, 103, 99, + }, + { + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + } }; /// @@ -199,16 +116,6 @@ namespace ImageSharp.Formats /// private readonly byte[] huffmanBuffer = new byte[179]; - /// - /// The scaled luminance table, in zig-zag order. - /// - private Block8x8F luminanceQuantTable; - - /// - /// The scaled chrominance table, in zig-zag order. - /// - private Block8x8F chrominanceQuantTable; - /// /// The accumulated bits to write to the stream. /// @@ -220,72 +127,24 @@ namespace ImageSharp.Formats private uint bitCount; /// - /// The output stream. All attempted writes after the first error become no-ops. - /// - private Stream outputStream; - - /// - /// The subsampling method to use. + /// The scaled chrominance table, in zig-zag order. /// - private JpegSubsample subsample; + private Block8x8F chrominanceQuantTable; /// - /// Initializes static members of the class. + /// The scaled luminance table, in zig-zag order. /// - static JpegEncoderCore() - { - // Initialize the Huffman tables - for (int i = 0; i < TheHuffmanSpecs.Length; i++) - { - TheHuffmanLut[i] = new HuffmanLut(TheHuffmanSpecs[i]); - } - } + private Block8x8F luminanceQuantTable; /// - /// Enumerates the Huffman tables + /// The output stream. All attempted writes after the first error become no-ops. /// - private enum HuffIndex - { - /// - /// The DC luminance huffman table index - /// - LuminanceDC = 0, - - // ReSharper disable UnusedMember.Local - - /// - /// The AC luminance huffman table index - /// - LuminanceAC = 1, - - /// - /// The DC chrominance huffman table index - /// - ChrominanceDC = 2, - - /// - /// The AC chrominance huffman table index - /// - ChrominanceAC = 3, - - // ReSharper restore UnusedMember.Local - } + private Stream outputStream; /// - /// Enumerates the quantization tables + /// The subsampling method to use. /// - private enum QuantIndex - { - /// - /// The luminance quantization table index - /// - Luminance = 0, - - /// - /// The chrominance quantization table index - /// - Chrominance = 1, - } + private JpegSubsample subsample; /// /// Encode writes the image to the jpeg baseline format with the given options. @@ -403,8 +262,7 @@ namespace ImageSharp.Formats Block8x8F* yBlock, Block8x8F* cbBlock, Block8x8F* crBlock, - PixelArea rgbBytes) - where TColor : struct, IPackedPixel, IEquatable + PixelArea rgbBytes) where TColor : struct, IPackedPixel, IEquatable { float* yBlockRaw = (float*)yBlock; float* cbBlockRaw = (float*)cbBlock; @@ -438,6 +296,18 @@ namespace ImageSharp.Formats } } +#pragma warning disable SA1204 + private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) + { + dqt[offset++] = (byte)i; + for (int j = 0; j < Block8x8F.ScalarCount; j++) + { + dqt[offset++] = (byte)q[j]; + } + } + +#pragma warning restore SA1204 + /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits < 1<<nBits && nBits <= 16. @@ -486,7 +356,7 @@ namespace ImageSharp.Formats [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EmitHuff(HuffIndex index, int value) { - uint x = TheHuffmanLut[(int)index].Values[value]; + uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value]; this.Emit(x & ((1 << 24) - 1), x >> 24); } @@ -524,6 +394,107 @@ namespace ImageSharp.Formats } } + /// + /// Encodes the image with no subsampling. + /// + /// The pixel format. + /// The pixel accessor providing access to the image pixels. + private void Encode444(PixelAccessor pixels) + where TColor : struct, IPackedPixel, IEquatable + { + // TODO: Need a JpegEncoderScanImpl struct to encapsulate all this mess: + Block8x8F b = default(Block8x8F); + Block8x8F cb = default(Block8x8F); + Block8x8F cr = default(Block8x8F); + + Block8x8F temp1 = default(Block8x8F); + Block8x8F temp2 = default(Block8x8F); + + Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; + Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; + + UnzigData unzig = UnzigData.Create(); + + // ReSharper disable once InconsistentNaming + float prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ, true)) + { + for (int y = 0; y < pixels.Height; y += 8) + { + for (int x = 0; x < pixels.Width; x += 8) + { + ToYCbCr(pixels, x, y, &b, &cb, &cr, rgbBytes); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &b, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &cb, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &cr, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + } + } + } + } + + /// + /// Writes the application header containing the JFIF identifier plus extra data. + /// + /// The resolution of the image in the x- direction. + /// The resolution of the image in the y- direction. + private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) + { + // Write the start of image marker. Markers are always prefixed with with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.SOI; + + // Write the JFIF headers + this.buffer[2] = JpegConstants.Markers.XFF; + this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker + this.buffer[4] = 0x00; + this.buffer[5] = 0x10; + this.buffer[6] = 0x4a; // J + this.buffer[7] = 0x46; // F + this.buffer[8] = 0x49; // I + this.buffer[9] = 0x46; // F + this.buffer[10] = 0x00; // = "JFIF",'\0' + this.buffer[11] = 0x01; // versionhi + this.buffer[12] = 0x01; // versionlo + this.buffer[13] = 0x01; // xyunits as dpi + + // No thumbnail + this.buffer[14] = 0x00; // Thumbnail width + this.buffer[15] = 0x00; // Thumbnail height + + this.outputStream.Write(this.buffer, 0, 16); + + // Resolution. Big Endian + this.buffer[0] = (byte)(horizontalResolution >> 8); + this.buffer[1] = (byte)horizontalResolution; + this.buffer[2] = (byte)(verticalResolution >> 8); + this.buffer[3] = (byte)verticalResolution; + + this.outputStream.Write(this.buffer, 0, 4); + } + /// /// Writes a block of pixel data using the given quantization table, /// returning the post-quantized DC value of the DCT-transformed block. @@ -591,54 +562,74 @@ namespace ImageSharp.Formats } /// - /// Writes the application header containing the JFIF identifier plus extra data. + /// Writes the Define Huffman Table marker and tables. /// - /// The resolution of the image in the x- direction. - /// The resolution of the image in the y- direction. - private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) + /// The number of components to write. + private void WriteDefineHuffmanTables(int componentCount) { - // Write the start of image marker. Markers are always prefixed with with 0xff. - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.SOI; + // Table identifiers. + byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; + int markerlen = 2; + HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs; - // Write the JFIF headers - this.buffer[2] = JpegConstants.Markers.XFF; - this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker - this.buffer[4] = 0x00; - this.buffer[5] = 0x10; - this.buffer[6] = 0x4a; // J - this.buffer[7] = 0x46; // F - this.buffer[8] = 0x49; // I - this.buffer[9] = 0x46; // F - this.buffer[10] = 0x00; // = "JFIF",'\0' - this.buffer[11] = 0x01; // versionhi - this.buffer[12] = 0x01; // versionlo - this.buffer[13] = 0x01; // xyunits as dpi + if (componentCount == 1) + { + // Drop the Chrominance tables. + specs = new[] { HuffmanSpec.TheHuffmanSpecs[0], HuffmanSpec.TheHuffmanSpecs[1] }; + } - // No thumbnail - this.buffer[14] = 0x00; // Thumbnail width - this.buffer[15] = 0x00; // Thumbnail height + foreach (HuffmanSpec s in specs) + { + markerlen += 1 + 16 + s.Values.Length; + } - this.outputStream.Write(this.buffer, 0, 16); + this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); + for (int i = 0; i < specs.Length; i++) + { + HuffmanSpec spec = specs[i]; + int len = 0; - // Resolution. Big Endian - this.buffer[0] = (byte)(horizontalResolution >> 8); - this.buffer[1] = (byte)horizontalResolution; - this.buffer[2] = (byte)(verticalResolution >> 8); - this.buffer[3] = (byte)verticalResolution; + fixed (byte* huffman = this.huffmanBuffer) + fixed (byte* count = spec.Count) + fixed (byte* values = spec.Values) + { + huffman[len++] = headers[i]; - this.outputStream.Write(this.buffer, 0, 4); + for (int c = 0; c < spec.Count.Length; c++) + { + huffman[len++] = count[c]; + } + + for (int v = 0; v < spec.Values.Length; v++) + { + huffman[len++] = values[v]; + } + } + + this.outputStream.Write(this.huffmanBuffer, 0, len); + } } /// - /// Writes the metadata profiles to the image. + /// Writes the Define Quantization Marker and tables. /// - /// The image. - /// The pixel format. - private void WriteProfiles(Image image) - where TColor : struct, IPackedPixel, IEquatable + private void WriteDefineQuantizationTables() { - this.WriteProfile(image.ExifProfile); + // Marker + quantization table lengths + int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.ScalarCount)); + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); + + // Loop through and collect the tables as one array. + // This allows us to reduce the number of writes to the stream. + int dqtCount = (QuantizationTableCount * Block8x8F.ScalarCount) + QuantizationTableCount; + byte[] dqt = ArrayPool.Shared.Rent(dqtCount); + int offset = 0; + + WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); + WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); + + this.outputStream.Write(dqt, 0, dqtCount); + ArrayPool.Shared.Return(dqt); } /// @@ -674,35 +665,14 @@ namespace ImageSharp.Formats } /// - /// Writes the Define Quantization Marker and tables. + /// Writes the metadata profiles to the image. /// - private void WriteDefineQuantizationTables() - { - // Marker + quantization table lengths - int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.ScalarCount)); - this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); - - // Loop through and collect the tables as one array. - // This allows us to reduce the number of writes to the stream. - byte[] dqt = new byte[(QuantizationTableCount * Block8x8F.ScalarCount) + QuantizationTableCount]; - int offset = 0; - - WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); - WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); - - this.outputStream.Write(dqt, 0, dqt.Length); - } - -#pragma warning disable SA1204 - private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q) + /// The image. + /// The pixel format. + private void WriteProfiles(Image image) where TColor : struct, IPackedPixel, IEquatable { - dqt[offset++] = (byte)i; - for (int j = 0; j < Block8x8F.ScalarCount; j++) - { - dqt[offset++] = (byte)q[j]; - } + this.WriteProfile(image.ExifProfile); } -#pragma warning restore SA1204 /// /// Writes the Start Of Frame (Baseline) marker @@ -760,55 +730,6 @@ namespace ImageSharp.Formats this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); } - /// - /// Writes the Define Huffman Table marker and tables. - /// - /// The number of components to write. - private void WriteDefineHuffmanTables(int componentCount) - { - // Table identifiers. - byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; - int markerlen = 2; - HuffmanSpec[] specs = TheHuffmanSpecs; - - if (componentCount == 1) - { - // Drop the Chrominance tables. - specs = new[] { TheHuffmanSpecs[0], TheHuffmanSpecs[1] }; - } - - foreach (HuffmanSpec s in specs) - { - markerlen += 1 + 16 + s.Values.Length; - } - - this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); - for (int i = 0; i < specs.Length; i++) - { - HuffmanSpec spec = specs[i]; - int len = 0; - - fixed (byte* huffman = this.huffmanBuffer) - fixed (byte* count = spec.Count) - fixed (byte* values = spec.Values) - { - huffman[len++] = headers[i]; - - for (int c = 0; c < spec.Count.Length; c++) - { - huffman[len++] = count[c]; - } - - for (int v = 0; v < spec.Values.Length; v++) - { - huffman[len++] = values[v]; - } - } - - this.outputStream.Write(this.huffmanBuffer, 0, len); - } - } - /// /// Writes the StartOfScan marker. /// @@ -819,6 +740,7 @@ namespace ImageSharp.Formats private void WriteStartOfScan(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { + // TODO: This method should be the entry point for a JpegEncoderScanImpl struct // TODO: We should allow grayscale writing. this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length); @@ -836,67 +758,6 @@ namespace ImageSharp.Formats this.Emit(0x7f, 7); } - /// - /// Encodes the image with no subsampling. - /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - private void Encode444(PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable - { - // TODO: Need a JpegEncoderCoreCore class or struct to hold all this mess: - Block8x8F b = default(Block8x8F); - Block8x8F cb = default(Block8x8F); - Block8x8F cr = default(Block8x8F); - - Block8x8F temp1 = default(Block8x8F); - Block8x8F temp2 = default(Block8x8F); - - Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; - Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - - UnzigData unzig = UnzigData.Create(); - - // ReSharper disable once InconsistentNaming - float prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ, true)) - { - for (int y = 0; y < pixels.Height; y += 8) - { - for (int x = 0; x < pixels.Width; x += 8) - { - ToYCbCr(pixels, x, y, &b, &cb, &cr, rgbBytes); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - &b, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - &cb, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - &cr, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - } - } - } - } - #pragma warning disable SA1201 // MethodShouldNotFollowAStruct /// @@ -916,7 +777,7 @@ namespace ImageSharp.Formats private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - // TODO: Need a JpegEncoderCoreCore class or struct to hold all this mess: + // TODO: Need a JpegEncoderScanImpl struct to encapsulate all this mess: Block8x8F b = default(Block8x8F); BlockQuad cb = default(BlockQuad); @@ -997,81 +858,5 @@ namespace ImageSharp.Formats this.buffer[3] = (byte)(length & 0xff); this.outputStream.Write(this.buffer, 0, 4); } - - /// - /// The Huffman encoding specifications. - /// - private struct HuffmanSpec - { - /// - /// Gets count[i] - The number of codes of length i bits. - /// - public readonly byte[] Count; - - /// - /// Gets value[i] - The decoded value of the codeword at the given index. - /// - public readonly byte[] Values; - - /// - /// Initializes a new instance of the struct. - /// - /// The number of codes. - /// The decoded values. - public HuffmanSpec(byte[] count, byte[] values) - { - this.Count = count; - this.Values = values; - } - } - - /// - /// A compiled look-up table representation of a huffmanSpec. - /// Each value maps to a uint32 of which the 8 most significant bits hold the - /// codeword size in bits and the 24 least significant bits hold the codeword. - /// The maximum codeword size is 16 bits. - /// - private class HuffmanLut - { - /// - /// Initializes a new instance of the class. - /// - /// The encoding specifications. - public HuffmanLut(HuffmanSpec spec) - { - int maxValue = 0; - - foreach (byte v in spec.Values) - { - if (v > maxValue) - { - maxValue = v; - } - } - - this.Values = new uint[maxValue + 1]; - - int code = 0; - int k = 0; - - for (int i = 0; i < spec.Count.Length; i++) - { - int bits = (i + 1) << 24; - for (int j = 0; j < spec.Count[i]; j++) - { - this.Values[spec.Values[k]] = (uint)(bits | code); - code++; - k++; - } - - code <<= 1; - } - } - - /// - /// Gets the collection of huffman values. - /// - public uint[] Values { get; } - } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 48d5454f6e..a84c64abae 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -72,7 +72,7 @@ namespace ImageSharp.Tests //PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb; [Theory( - //Skip = "Benchmark, enable manually!" + Skip = "Benchmark, enable manually!" )] [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio420, 75)] [WithFileCollection(nameof(BenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio444, 75)] diff --git a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs deleted file mode 100644 index e30baddfc8..0000000000 --- a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs +++ /dev/null @@ -1,145 +0,0 @@ -// ReSharper disable InconsistentNaming -namespace ImageSharp.Tests -{ - using System.Numerics; - using ImageSharp.Formats; - - using Xunit; - using Xunit.Abstractions; - - public class ReferenceImplementationsTests : UtilityTestClassBase - { - public ReferenceImplementationsTests(ITestOutputHelper output) - : base(output) - { - } - - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Idct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) - { - MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); - MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); - - ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(intData); - - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.iDCT2D_llm(floatSrc, dest, temp); - - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void IntegerDCT_ForwardThenInverse(int seed, int startAt) - { - MutableSpan original = Create8x8RandomIntData(-200, 200, seed); - - var block = original.AddScalarToAllValues(128); - - ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block); - - for (int i = 0; i < 64; i++) - { - block[i] /= 8; - } - - ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(block); - - for (int i = startAt; i < 64; i++) - { - float expected = original[i]; - float actual = (float)block[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); - } - - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt) - { - var data = Create8x8RandomIntData(-200, 200, seed); - MutableSpan src = new MutableSpan(data).ConvertToFloat32MutableSpan(); - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(src, dest, temp, true); - ReferenceImplementations.iDCT2D_llm(dest, src, temp); - - for (int i = startAt; i < 64; i++) - { - float expected = data[i]; - float actual = (float)src[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(2f)); - } - } - - [Fact] - public void HowMuchIsTheFish() - { - Output.WriteLine(Vector.Count.ToString()); - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Fdct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) - { - MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); - MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); - - ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(intData); - - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); - - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Fdct_SimdReferenceImplementation_IsEquivalentToFloatingPointReferenceImplementation(int seed) - { - Block classic = new Block() { Data = Create8x8RandomIntData(-200, 200, seed) }; - MutableSpan src = new MutableSpan(classic.Data).ConvertToFloat32MutableSpan(); - - MutableSpan dest1 = new MutableSpan(64); - MutableSpan dest2 = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(src, dest1, temp, downscaleBy8: true, offsetSourceByNeg128: false); - ReferenceImplementations.fDCT8x8_llm_sse(src, dest2, temp); - - Assert.Equal(dest1.Data, dest2.Data, new ApproximateFloatComparer(1f)); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj index 2f03fa24c2..d53ee3d9c7 100644 --- a/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj +++ b/tests/ImageSharp.Tests46/ImageSharp.Tests46.csproj @@ -82,6 +82,9 @@ Formats\Jpg\ReferenceImplementations.cs + + Formats\Jpg\ReferenceImplementationsTests.cs + Formats\Jpg\UtilityTestClassBase.cs @@ -157,7 +160,6 @@ TestUtilities\TestUtilityExtensions.cs -