diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index 44b39dfd71..6e17762c72 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -31,6 +31,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; + public static readonly HuffmanLut[] DcHuffmanLut = new HuffmanLut[2]; + public static readonly HuffmanLut[] AcHuffmanLut = new HuffmanLut[2]; + /// /// Initializes static members of the struct. /// @@ -41,6 +44,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); } + + // TODO: REWRITE THIS + DcHuffmanLut[0] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[0]); + DcHuffmanLut[1] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[2]); + AcHuffmanLut[0] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[1]); + AcHuffmanLut[1] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[3]); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index bc765742e9..9803bcf2a4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -132,8 +132,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP - this.huffmanTables = HuffmanLut.TheHuffmanLut; - frame.Init(1, 1); frame.AllocateComponents(fullScan: false); @@ -161,8 +159,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { JpegComponent component = frame.Components[k]; - ref HuffmanLut dcHuffmanTable = ref this.huffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.huffmanTables[component.AcTableId]; + ref HuffmanLut dcHuffmanTable = ref HuffmanLut.DcHuffmanLut[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref HuffmanLut.AcHuffmanLut[component.AcTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 7323a30baf..87fd218eb8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public JpegComponentConfig[] Components { get; } - public JpegFrameConfig PopulateComponent(int index, int id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) + public JpegFrameConfig PopulateComponent(int index, byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) { this.Components[index] = new JpegComponentConfig { @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public class JpegComponentConfig { - public int Id { get; set; } + public byte Id { get; set; } public int HorizontalSampleFactor { get; set; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 065d9c6019..082ab83ccf 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -89,7 +89,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Compute number of components based on color type in options. int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3; - ReadOnlySpan componentIds = this.GetComponentIds(); // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that // Initialize the quantization tables. @@ -117,13 +116,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable); // Write the image dimensions. - this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds); + this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig.Components); // Write the Huffman tables. this.WriteDefineHuffmanTables(componentCount); // Write the scan header. - this.WriteStartOfScan(componentCount, componentIds); + this.WriteStartOfScan(componentCount, this.frameConfig.Components); var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; @@ -200,15 +199,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg || colorType == JpegColorType.Luminance || colorType == JpegColorType.Rgb; - /// - /// Gets the component ids. - /// For color space RGB this will be RGB as ASCII, otherwise 1, 2, 3. - /// - /// The component Ids. - private ReadOnlySpan GetComponentIds() => this.colorType == JpegColorType.Rgb - ? new ReadOnlySpan(new byte[] { 82, 71, 66 }) - : new ReadOnlySpan(new byte[] { 1, 2, 3 }); - /// /// Writes data to "Define Quantization Tables" block for QuantIndex. /// @@ -646,91 +636,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The height of the image. /// The number of components in a pixel. /// The component Id's. - private void WriteStartOfFrame(int width, int height, int componentCount, ReadOnlySpan componentIds) + private void WriteStartOfFrame(int width, int height, JpegComponentConfig[] components) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - // "default" to 4:2:0 - ReadOnlySpan subsamples = new byte[] - { - 0x22, - 0x11, - 0x11 - }; - - ReadOnlySpan chroma = new byte[] - { - 0x00, - 0x01, - 0x01 - }; - - if (this.colorType == JpegColorType.Luminance) - { - subsamples = new byte[] - { - 0x11, - 0x00, - 0x00 - }; - } - else - { - switch (this.colorType) - { - case JpegColorType.YCbCrRatio444: - case JpegColorType.Rgb: - subsamples = new byte[] - { - 0x11, - 0x11, - 0x11 - }; - - if (this.colorType == JpegColorType.Rgb) - { - chroma = new byte[] - { - 0x00, - 0x00, - 0x00 - }; - } - - break; - case JpegColorType.YCbCrRatio420: - subsamples = new byte[] - { - 0x22, - 0x11, - 0x11 - }; - break; - } - } - // Length (high byte, low byte), 8 + components * 3. - int markerlen = 8 + (3 * componentCount); + int markerlen = 8 + (3 * components.Length); this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported this.buffer[1] = (byte)(height >> 8); this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported this.buffer[3] = (byte)(width >> 8); this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported - this.buffer[5] = (byte)componentCount; + this.buffer[5] = (byte)components.Length; - for (int i = 0; i < componentCount; i++) + // Components data + for (int i = 0; i < components.Length; i++) { int i3 = 3 * i; - - // Component ID. Span bufferSpan = this.buffer.AsSpan(i3 + 6, 3); - bufferSpan[2] = chroma[i]; - bufferSpan[1] = subsamples[i]; - bufferSpan[0] = componentIds[i]; + + // Quantization table selector + bufferSpan[2] = (byte)components[i].QuantizatioTableIndex; + + // Sampling factors + // 4 bits + int samplingFactors = components[i].HorizontalSampleFactor | (components[i].VerticalSampleFactor << 4); + bufferSpan[1] = (byte)samplingFactors; + + // Id + bufferSpan[0] = components[i].Id; } - this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); + this.outputStream.Write(this.buffer, 0, (3 * (components.Length - 1)) + 9); } /// @@ -738,28 +674,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The number of components in a pixel. /// The componentId's. - private void WriteStartOfScan(int componentCount, ReadOnlySpan componentIds) + private void WriteStartOfScan(int componentCount, JpegComponentConfig[] components) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - ReadOnlySpan huffmanId = new byte[] - { - 0x00, - 0x11, - 0x11 - }; - - // Use the same DC/AC tables for all channels for RGB. - if (this.colorType == JpegColorType.Rgb) - { - huffmanId = new byte[] - { - 0x00, - 0x00, - 0x00 - }; - } - // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: // - the marker length "\x00\x0c", // - the number of components "\x03", @@ -777,11 +693,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.buffer[2] = 0x00; this.buffer[3] = (byte)sosSize; this.buffer[4] = (byte)componentCount; // Number of components in a scan + + // Components data for (int i = 0; i < componentCount; i++) { int i2 = 2 * i; - this.buffer[i2 + 5] = componentIds[i]; // Component Id - this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table + + // Id + this.buffer[i2 + 5] = components[i].Id; + + // Table selectors + int tableSelectors = (components[i].dcTableSelector << 4) | (components[i].acTableSelector); + this.buffer[i2 + 6] = (byte)tableSelectors; } this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection.