From 8c184feee964b0f73e1582e05e53d3509a98e3c5 Mon Sep 17 00:00:00 2001 From: Julius Friedman Date: Wed, 23 Oct 2024 20:42:28 -0400 Subject: [PATCH] Start to quantize entropy. --- Codecs/Image/Jpeg/Classes/HuffmanTable.cs | 41 ++++++ Codecs/Image/Jpeg/Classes/JpegState.cs | 58 +++++++++ Codecs/Image/Jpeg/JpegCodec.cs | 149 ++++------------------ Codecs/Image/Jpeg/JpegImage.cs | 93 +++++++++----- 4 files changed, 188 insertions(+), 153 deletions(-) create mode 100644 Codecs/Image/Jpeg/Classes/HuffmanTable.cs diff --git a/Codecs/Image/Jpeg/Classes/HuffmanTable.cs b/Codecs/Image/Jpeg/Classes/HuffmanTable.cs new file mode 100644 index 00000000..8c727a9d --- /dev/null +++ b/Codecs/Image/Jpeg/Classes/HuffmanTable.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace Codec.Jpeg.Classes; + +internal class HuffmanTable +{ + public byte Id; + + public int[] MinCode { get; set; } + public int[] MaxCode { get; set; } + public int[] ValPtr { get; set; } + public byte[] Values { get; set; } + public Dictionary CodeTable { get; set; } + + public HuffmanTable() + { + MinCode = new int[16]; + MaxCode = new int[16]; + ValPtr = new int[16]; + Values = new byte[256]; + CodeTable = new Dictionary(); + } + + public (int code, int length) GetCode(int value) + { + if (CodeTable.TryGetValue(value, out var codeInfo)) + { + return codeInfo; + } + return (0, 0); + } + + public int GetCodeLength(int value) + { + if (CodeTable.TryGetValue(value, out var codeInfo)) + { + return codeInfo.length; + } + return 0; + } +} diff --git a/Codecs/Image/Jpeg/Classes/JpegState.cs b/Codecs/Image/Jpeg/Classes/JpegState.cs index 4810f37f..1d93e636 100644 --- a/Codecs/Image/Jpeg/Classes/JpegState.cs +++ b/Codecs/Image/Jpeg/Classes/JpegState.cs @@ -1,5 +1,7 @@ using Codec.Jpeg.Markers; using System; +using static Media.Codec.Jpeg.JpegCodec; +using System.Collections.Generic; namespace Codec.Jpeg.Classes; @@ -33,6 +35,62 @@ internal sealed class JpegState : IEquatable /// public byte Al; + public HuffmanTable DcTable = new HuffmanTable + { + Id = 0, + MinCode = [0, 1, 5, 6, 14, 30, 62, 126, 254, 510, 1022, 2046, 4094, 8190, 16382, 32766], + MaxCode = [0, 1, 5, 6, 14, 30, 62, 126, 254, 510, 1022, 2046, 4094, 8190, 16382, 32766], + ValPtr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + Values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + CodeTable = new Dictionary + { + { 0, (0b00, 2) }, + { 1, (0b01, 2) }, + { 2, (0b100, 3) }, + { 3, (0b101, 3) }, + { 4, (0b1100, 4) }, + { 5, (0b1101, 4) }, + { 6, (0b11100, 5) }, + { 7, (0b11101, 5) }, + { 8, (0b111100, 6) }, + { 9, (0b111101, 6) }, + { 10, (0b1111100, 7) }, + { 11, (0b1111101, 7) }, + { 12, (0b11111100, 8) }, + { 13, (0b11111101, 8) }, + { 14, (0b111111100, 9) }, + { 15, (0b111111101, 9) } + } + }; + + public HuffmanTable AcTable = new HuffmanTable + { + Id = 1, + MinCode = [0, 1, 5, 6, 14, 30, 62, 126, 254, 510, 1022, 2046, 4094, 8190, 16382, 32766], + MaxCode = [0, 1, 5, 6, 14, 30, 62, 126, 254, 510, 1022, 2046, 4094, 8190, 16382, 32766], + ValPtr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + Values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + CodeTable = new Dictionary + { + { 0, (0b00, 2) }, + { 1, (0b01, 2) }, + { 2, (0b100, 3) }, + { 3, (0b101, 3) }, + { 4, (0b1100, 4) }, + { 5, (0b1101, 4) }, + { 6, (0b11100, 5) }, + { 7, (0b11101, 5) }, + { 8, (0b111100, 6) }, + { 9, (0b111101, 6) }, + { 10, (0b1111100, 7) }, + { 11, (0b1111101, 7) }, + { 12, (0b11111100, 8) }, + { 13, (0b11111101, 8) }, + { 14, (0b111111100, 9) }, + { 15, (0b111111101, 9) } + } + }; + /// /// Constructors a /// diff --git a/Codecs/Image/Jpeg/JpegCodec.cs b/Codecs/Image/Jpeg/JpegCodec.cs index d12ee80f..afa1417a 100644 --- a/Codecs/Image/Jpeg/JpegCodec.cs +++ b/Codecs/Image/Jpeg/JpegCodec.cs @@ -117,7 +117,7 @@ private static void InverseQuantize(short[] block, short[] quantizationTable) //Decompress - internal static void VIDCT(short[] block, double[] output) + internal static void VIDCT(Span block, Span output) { const int BlockSize = 8; double SqrtHalf = 1.0 / System.Math.Sqrt(2.0); @@ -155,7 +155,7 @@ internal static void VIDCT(short[] block, double[] output) } } - internal static void IDCT(short[] block, double[] output) + internal static void IDCT(Span block, Span output) { for (int y = 0; y < BlockSize; y++) { @@ -183,7 +183,7 @@ internal static void IDCT(short[] block, double[] output) //Compress - internal static void FDCT(double[] input, double[] output) + internal static void FDCT(Span input, Span output) { for (int u = 0; u < BlockSize; u++) { @@ -206,7 +206,7 @@ internal static void FDCT(double[] input, double[] output) } } - internal static void VFDCT(double[] input, double[] output) + internal static void VFDCT(Span input, Span output) { for (int u = 0; u < BlockSize; u++) { @@ -217,7 +217,7 @@ internal static void VFDCT(double[] input, double[] output) { for (int y = 0; y < BlockSize; y += Vector.Count) { - var inputVector = new Vector(input, y * BlockSize + x); + var inputVector = new Vector(input); var cosX = new Vector(System.Math.Cos((2 * x + 1) * u * System.Math.PI / 16)); var cosY = new Vector(System.Math.Cos((2 * y + 1) * v * System.Math.PI / 16)); sum += inputVector * cosX * cosY; @@ -230,7 +230,7 @@ internal static void VFDCT(double[] input, double[] output) } } - private static void HuffmanEncode(short[] block, HuffmanTable dcTable, HuffmanTable acTable, BitWriter writer) + internal static void HuffmanEncode(Span block, HuffmanTable dcTable, HuffmanTable acTable, BitWriter writer) { // DC coefficient encoding int dcValue = block[0]; @@ -287,7 +287,7 @@ private static int GetBitSize(int value) return size; } - internal static void VQuantize(double[] block, short[] quantizationTable, short[] output) + internal static void VQuantize(Span block, Span quantizationTable, Span output) { int VectorSize = Vector.Count; int i = 0; @@ -295,7 +295,7 @@ internal static void VQuantize(double[] block, short[] quantizationTable, short[ // Process in chunks of VectorSize for (; i <= block.Length - VectorSize; i += VectorSize) { - var blockVector = new Vector(block, i); + var blockVector = new Vector(block); var quantizationVector = new Vector(MemoryMarshal.Cast(quantizationTable)); var resultVector = blockVector / quantizationVector; @@ -313,7 +313,7 @@ internal static void VQuantize(double[] block, short[] quantizationTable, short[ } } - internal static void Quantize(double[] block, short[] quantizationTable, short[] output) + internal static void Quantize(Span block, Span quantizationTable, Span output) { for (int i = 0; i < BlockSize * BlockSize; i++) { @@ -321,44 +321,6 @@ internal static void Quantize(double[] block, short[] quantizationTable, short[] } } - internal class HuffmanTable - { - public byte Id; - - public int[] MinCode { get; set; } - public int[] MaxCode { get; set; } - public int[] ValPtr { get; set; } - public byte[] Values { get; set; } - public Dictionary CodeTable { get; set; } - - public HuffmanTable() - { - MinCode = new int[16]; - MaxCode = new int[16]; - ValPtr = new int[16]; - Values = new byte[256]; - CodeTable = new Dictionary(); - } - - public (int code, int length) GetCode(int value) - { - if (CodeTable.TryGetValue(value, out var codeInfo)) - { - return codeInfo; - } - return (0, 0); - } - - public int GetCodeLength(int value) - { - if (CodeTable.TryGetValue(value, out var codeInfo)) - { - return codeInfo.length; - } - return 0; - } - } - private static int DecodeHuffman(BitReader stream, HuffmanTable table) { int code = 0; @@ -416,72 +378,15 @@ public static void Compress(JpegImage jpegImage, Stream outputStream, int qualit using (var reader = new BitReader(inputStream)) using (var writer = new BitWriter(outputStream)) { + var blockSizeSquared = BlockSize * BlockSize; + // Span // Example quantization table (you need to initialize this properly) - short[] quantizationTable = new short[BlockSize * BlockSize]; - - // Example Huffman tables (you need to initialize these properly) - HuffmanTable dcTable = new HuffmanTable - { - Id = 0, - MinCode = [0, 1, 5, 6, 14, 30, 62, 126, 254, 510, 1022, 2046, 4094, 8190, 16382, 32766], - MaxCode = [0, 1, 5, 6, 14, 30, 62, 126, 254, 510, 1022, 2046, 4094, 8190, 16382, 32766], - ValPtr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - Values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - CodeTable = new Dictionary - { - { 0, (0b00, 2) }, - { 1, (0b01, 2) }, - { 2, (0b100, 3) }, - { 3, (0b101, 3) }, - { 4, (0b1100, 4) }, - { 5, (0b1101, 4) }, - { 6, (0b11100, 5) }, - { 7, (0b11101, 5) }, - { 8, (0b111100, 6) }, - { 9, (0b111101, 6) }, - { 10, (0b1111100, 7) }, - { 11, (0b1111101, 7) }, - { 12, (0b11111100, 8) }, - { 13, (0b11111101, 8) }, - { 14, (0b111111100, 9) }, - { 15, (0b111111101, 9) } - } - }; - - HuffmanTable acTable = new HuffmanTable - { - Id = 1, - MinCode = [0, 1, 5, 6, 14, 30, 62, 126, 254, 510, 1022, 2046, 4094, 8190, 16382, 32766], - MaxCode = [0, 1, 5, 6, 14, 30, 62, 126, 254, 510, 1022, 2046, 4094, 8190, 16382, 32766], - ValPtr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - Values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - CodeTable = new Dictionary - { - { 0, (0b00, 2) }, - { 1, (0b01, 2) }, - { 2, (0b100, 3) }, - { 3, (0b101, 3) }, - { 4, (0b1100, 4) }, - { 5, (0b1101, 4) }, - { 6, (0b11100, 5) }, - { 7, (0b11101, 5) }, - { 8, (0b111100, 6) }, - { 9, (0b111101, 6) }, - { 10, (0b1111100, 7) }, - { 11, (0b1111101, 7) }, - { 12, (0b11111100, 8) }, - { 13, (0b11111101, 8) }, - { 14, (0b111111100, 9) }, - { 15, (0b111111101, 9) } - } - }; - - WriteHuffmanTableMarkers(outputStream, dcTable, acTable); + Span quantizationTable = stackalloc short[blockSizeSquared]; // Span // Read the input data (this part is simplified for illustration purposes) - double[] inputBlock = new double[BlockSize * BlockSize]; + Span inputBlock = stackalloc double[blockSizeSquared]; for (int i = 0; i < BlockSize * BlockSize; i++) { @@ -490,24 +395,22 @@ public static void Compress(JpegImage jpegImage, Stream outputStream, int qualit // Span // Perform forward DCT and quantization - double[] dctBlock = new double[BlockSize * BlockSize]; - short[] quantizedBlock = new short[BlockSize * BlockSize]; + Span dctBlock = stackalloc double[blockSizeSquared]; + Span quantizedBlock = stackalloc short[blockSizeSquared]; if (Vector.IsHardwareAccelerated) { VFDCT(inputBlock, dctBlock); VQuantize(dctBlock, quantizationTable, quantizedBlock); - WriteQuantizationTableMarker(outputStream, quality); // Write the quantization table } else { FDCT(inputBlock, dctBlock); Quantize(dctBlock, quantizationTable, quantizedBlock); - WriteQuantizationTableMarker(outputStream, quality); // Write the quantization table } // Perform Huffman encoding - HuffmanEncode(quantizedBlock, dcTable, acTable, writer); + HuffmanEncode(quantizedBlock, jpegImage.JpegState.DcTable, jpegImage.JpegState.AcTable, writer); } // Write the SOS marker @@ -581,15 +484,17 @@ internal static void WriteInformationMarker(byte functionCode, Stream stream) stream.WriteByte(functionCode); } - private static void WriteQuantizationTableMarker(Stream stream, int quality) + internal static void WriteQuantizationTableMarker(Stream stream, int quality) { const int QuantizationTableLength = 64; // Each quantization table has 64 values // Calculate the quantization table based on the quality short[] quantizationTable = GetQuantizationTable(quality); - // Write the DQT marker - WriteMarker(stream, new Marker(Markers.QuantizationTable, Marker.LengthBytes + 1 + QuantizationTableLength)); + var outputMarker = new Marker(Markers.QuantizationTable, Marker.LengthBytes + 1 + QuantizationTableLength); + + using var outputStream = outputMarker.ToMemoryStream(); + outputStream.Seek(outputMarker.DataOffset, SeekOrigin.Begin); // Write the precision and identifier (assuming 8-bit precision and identifier 0) byte precisionAndIdentifier = 0; // 0 for 8-bit precision and identifier 0 @@ -598,11 +503,14 @@ private static void WriteQuantizationTableMarker(Stream stream, int quality) // Write the quantization table values foreach (short value in quantizationTable) { - stream.WriteByte((byte)value); + outputStream.WriteByte((byte)value); } + + // Write the DQT marker + WriteMarker(stream, outputMarker); } - private static short[] GetQuantizationTable(int quality) + internal static short[] GetQuantizationTable(int quality) { // Standard JPEG quantization table for luminance short[] baseTable = @@ -630,7 +538,7 @@ private static short[] GetQuantizationTable(int quality) return quantizationTable; } - private static void WriteHuffmanTableMarkers(Stream stream, params HuffmanTable[] huffmanTables) + internal static void WriteHuffmanTableMarkers(Stream stream, params HuffmanTable[] huffmanTables) { foreach (var huffmanTable in huffmanTables) { @@ -643,11 +551,10 @@ private static void WriteHuffmanTableMarkers(Stream stream, params HuffmanTable[ using var memoryStream = slice.ToMemoryStream(); // Write the Huffman table data to the stream - WriteHuffmanTableData(stream, huffmanTable); + WriteHuffmanTableData(memoryStream, huffmanTable); // Write the marker to the stream WriteMarker(stream, huffmanTableMarker); - } } diff --git a/Codecs/Image/Jpeg/JpegImage.cs b/Codecs/Image/Jpeg/JpegImage.cs index 4da1dacf..86e70e84 100644 --- a/Codecs/Image/Jpeg/JpegImage.cs +++ b/Codecs/Image/Jpeg/JpegImage.cs @@ -7,6 +7,9 @@ using Media.Common.Collections.Generic; using Codec.Jpeg.Markers; using Codec.Jpeg.Classes; +using System.Runtime.InteropServices; +using System.ComponentModel; +using Media.Common.Interfaces; namespace Media.Codec.Jpeg; @@ -142,11 +145,17 @@ public static JpegImage FromStream(Stream stream) } var dataSegmentSize = CalculateSize(imageFormat, width, height); + dataSegment = new MemorySegment(Math.Abs(dataSegmentSize)); + var read = stream.Read(dataSegment.Array, dataSegment.Offset, dataSegment.Count); + if (read < dataSegment.Count) dataSegment = dataSegment.Slice(0, read); + using var bitStream = new BitReader(dataSegment.Array, Binary.BitOrder.MostSignificant, 0, 0, true, Environment.ProcessorCount * Environment.ProcessorCount); + JpegCodec.Decompress(bitStream); + break; } case Jpeg.Markers.AppFirst: @@ -177,11 +186,7 @@ public static JpegImage FromStream(Stream stream) markers.Add(marker.FunctionCode, marker); continue; } - } - - using var bitStream = new BitReader(dataSegment.Array, Binary.BitOrder.MostSignificant, 0, 0, true, Environment.ProcessorCount * Environment.ProcessorCount); - - JpegCodec.Decompress(bitStream); + } if (imageFormat == null || dataSegment == null && thumbnailData == null) throw new InvalidDataException("The provided stream does not contain valid JPEG image data."); @@ -220,23 +225,35 @@ public void Save(Stream stream, int quality = 99) markerBuffer.Remove(marker.FunctionCode); } } - else - { - //// Step 4: Perform the compression (Example) - //foreach (var component in ImageFormat.Components) - //{ - // // Step 4.1: Perform Forward Discrete Cosine Transform (FDCT) - // double[] dctCoefficients = new double[BlockSize]; - // JpegCodec.FDCT(component.Data, dctCoefficients); - - // // Step 4.2: Quantize the DCT coefficients - // Span quantizedCoefficients = [BlockSize]; - // JpegCodec.Quantize(dctCoefficients, GetQuantizationTable(quality), quantizedCoefficients); - - // // Step 4.3: Huffman encode the quantized coefficients - // JpegCodec.HuffmanEncode(quantizedCoefficients, GetHuffmanTable(component.Id, true), GetHuffmanTable(component.Id, false), writer); - //} - } + //else + //{ + // Span coefficients = stackalloc double[JpegCodec.BlockSize]; + + // Span quantizedCoefficients = stackalloc short[JpegCodec.BlockSize]; + // var quantizationTable = JpegCodec.GetQuantizationTable(quality); + + // using var writer = new BitWriter(stream, Environment.ProcessorCount * Environment.ProcessorCount, true); + + // Span bytes = stackalloc byte[Vector.Count]; + + // // Iterate over each block of the image + // for (int y = 0; y < Height; y += JpegCodec.BlockSize) + // { + // for (int x = 0; x < Width; x += JpegCodec.BlockSize) + // { + // foreach(var mediaComponent in ImageFormat.Components) + // { + // // Step 4.0: Get the pixel data for the media component + // var componentData = GetComponentVector(x, y, mediaComponent.Id); // Y component + + // componentData.CopyTo(bytes); + + // // Process each component separately + // ProcessComponent(bytes, quantizationTable, coefficients, quantizedCoefficients, writer); + // } + // } + // } + //} if (markerBuffer != null) { @@ -246,10 +263,7 @@ public void Save(Stream stream, int quality = 99) } markerBuffer.Remove(Jpeg.Markers.HuffmanTable); - } - if (markerBuffer != null) - { foreach (var marker in markerBuffer.Values) { JpegCodec.WriteMarker(stream, marker); @@ -258,17 +272,14 @@ public void Save(Stream stream, int quality = 99) } else { - //// Step 5: Write the compressed image data to the stream - //// Write the DQT marker - //JpegCodec.WriteMarker(stream, JpegCodec.GetQuantizationTableMarker(this, quality)); - //// Write the DHT marker - //JpegCodec.WriteMarker(stream, JpegCodec.GetHuffmanTableMarker(this, true)); - //JpegCodec.WriteMarker(stream, JpegCodec.GetHuffmanTableMarker(this, false)); + JpegCodec.WriteQuantizationTableMarker(stream, quality); + JpegCodec.WriteHuffmanTableMarkers(stream, JpegState.DcTable, JpegState.AcTable); } // Write the SOF marker JpegCodec.WriteStartOfFrame(this, stream); + //Write the SOS marker JpegCodec.WriteStartOfScan(this, stream); if (markerBuffer != null) @@ -286,7 +297,25 @@ public void Save(Stream stream, int quality = 99) // Write the EOI marker JpegCodec.WriteInformationMarker(Jpeg.Markers.EndOfInformation, stream); } - } + } + + + private void ProcessComponent(Span span, Span quantizationTable, Span coefficients, Span quantizedCoefficients, BitWriter writer) + { + // Step 4.1: Perform Forward Discrete Cosine Transform (FDCT) + ref Span dctCoefficients = ref coefficients; + + if (Vector.IsHardwareAccelerated) + JpegCodec.VFDCT(MemoryMarshal.Cast(span), dctCoefficients); + else + JpegCodec.FDCT(MemoryMarshal.Cast(span), dctCoefficients); + + // Step 4.2: Quantize the DCT coefficients + JpegCodec.Quantize(dctCoefficients, quantizationTable, quantizedCoefficients); + + // Step 4.3: Huffman encode the quantized coefficients + JpegCodec.HuffmanEncode(quantizedCoefficients, JpegState.DcTable, JpegState.AcTable, writer); + } public MemorySegment GetPixelDataAt(int x, int y) {