From 834c2546510b34320412ba6a1852110a01ebb555 Mon Sep 17 00:00:00 2001 From: alex_the_nugget <2005kan@mail.ru> Date: Sun, 15 Mar 2026 22:42:37 +0500 Subject: [PATCH] Changes --- .../Benchmarks/JpegProcessorBenchmark.cs | 132 ++++- optimizations/JPEG/CompressedImage.cs | 2 +- optimizations/JPEG/DCT.cs | 166 +++--- optimizations/JPEG/HuffmanCodec.cs | 393 +++++++------ optimizations/JPEG/Images/Matrix.cs | 186 +++++-- optimizations/JPEG/Images/Pixel.cs | 48 -- optimizations/JPEG/Images/PixelFormat.cs | 50 -- optimizations/JPEG/JPEG.csproj | 1 + optimizations/JPEG/Processor/JpegProcessor.cs | 518 ++++++++++-------- .../JPEG/Utilities/IEnumerableExtensions.cs | 23 - optimizations/JPEG/Utilities/MathEx.cs | 20 - 11 files changed, 836 insertions(+), 703 deletions(-) delete mode 100644 optimizations/JPEG/Images/Pixel.cs delete mode 100644 optimizations/JPEG/Images/PixelFormat.cs delete mode 100644 optimizations/JPEG/Utilities/IEnumerableExtensions.cs delete mode 100644 optimizations/JPEG/Utilities/MathEx.cs diff --git a/optimizations/JPEG.Benchmarks/Benchmarks/JpegProcessorBenchmark.cs b/optimizations/JPEG.Benchmarks/Benchmarks/JpegProcessorBenchmark.cs index 8ebeef7..909dab6 100644 --- a/optimizations/JPEG.Benchmarks/Benchmarks/JpegProcessorBenchmark.cs +++ b/optimizations/JPEG.Benchmarks/Benchmarks/JpegProcessorBenchmark.cs @@ -1,4 +1,7 @@ +using System; +using System.Drawing; using BenchmarkDotNet.Attributes; +using JPEG.Images; using JPEG.Processor; namespace JPEG.Benchmarks.Benchmarks; @@ -7,27 +10,110 @@ namespace JPEG.Benchmarks.Benchmarks; [SimpleJob(warmupCount: 2, iterationCount: 3)] public class JpegProcessorBenchmark { - private IJpegProcessor jpegProcessor; - private static readonly string imagePath = @"sample.bmp"; - private static readonly string compressedImagePath = imagePath + ".compressed." + JpegProcessor.CompressionQuality; - private static readonly string uncompressedImagePath = - imagePath + ".uncompressed." + JpegProcessor.CompressionQuality + ".bmp"; - - [GlobalSetup] - public void SetUp() - { - jpegProcessor = JpegProcessor.Init; - } - - [Benchmark] - public void Compress() - { - jpegProcessor.Compress(imagePath, compressedImagePath); - } - - [Benchmark] - public void Uncompress() - { - jpegProcessor.Uncompress(compressedImagePath, uncompressedImagePath); - } + private IJpegProcessor jpegProcessor = null!; + private static readonly string imagePath = @"sample.bmp"; + private static readonly string compressedImagePath = imagePath + ".compressed." + JpegProcessor.CompressionQuality; + private static readonly string uncompressedImagePath = + imagePath + ".uncompressed." + JpegProcessor.CompressionQuality + ".bmp"; + + private Bitmap bmp = null!; + private Matrix matrix = null!; + private const int Size = 64; + private float[] dctData = null!; + + [GlobalSetup] + public void SetUp() + { + var random = new Random(100); + dctData = new float[Size]; + for (var i = 0; i < Size; i++) + { + dctData[i] = random.Next(-128, 128); + } + + jpegProcessor = JpegProcessor.Init; + bmp = new Bitmap(imagePath); + matrix = (Matrix)bmp; + jpegProcessor.Compress(imagePath, compressedImagePath); + } + + [GlobalCleanup] + public void Cleanup() + { + matrix.Dispose(); + bmp.Dispose(); + } + + [Benchmark] + public void Compress() + { + jpegProcessor.Compress(imagePath, compressedImagePath); + } + + [Benchmark] + public void Uncompress() + { + jpegProcessor.Uncompress(compressedImagePath, uncompressedImagePath); + } + + [Benchmark] + public void CompressAndUncompress() + { + jpegProcessor.Compress(imagePath, compressedImagePath); + jpegProcessor.Uncompress(compressedImagePath, uncompressedImagePath); + } + + [Benchmark] + public void DCT2D() + { + Span dctCopy = stackalloc float[Size]; + dctData.AsSpan().CopyTo(dctCopy); + + DCT.DCT2D(dctCopy); + } + + [Benchmark] + public void IDCT2D() + { + Span idctCopy = stackalloc float[Size]; + dctData.AsSpan().CopyTo(idctCopy); + + DCT.IDCT2D(idctCopy); + } + + [Benchmark] + public void DCT2DAndIDCT2D() + { + Span block = stackalloc float[Size]; + dctData.AsSpan().CopyTo(block); + + DCT.DCT2D(block); + DCT.IDCT2D(block); + } + + [Benchmark] + public void BitmapToMatrix() + { + using var result = (Matrix)bmp; + } + + [Benchmark] + public void MatrixToBitmap() + { + using var result = (Bitmap)matrix; + } + + [Benchmark] + public void BitmapToMatrixToBitmap() + { + using var matrixResult = (Matrix)bmp; + using var bitmapResult = (Bitmap)matrixResult; + } + + [Benchmark] + public void MatrixToBitmapToMatrix() + { + using var bitmapResult = (Bitmap)matrix; + using var matrixResult = (Matrix)bitmapResult; + } } \ No newline at end of file diff --git a/optimizations/JPEG/CompressedImage.cs b/optimizations/JPEG/CompressedImage.cs index 7acb771..18ffefa 100644 --- a/optimizations/JPEG/CompressedImage.cs +++ b/optimizations/JPEG/CompressedImage.cs @@ -76,7 +76,7 @@ public static CompressedImage Load(string path) sr.Read(buffer, 0, 4); var decodeTableSize = BitConverter.ToInt32(buffer, 0); - result.DecodeTable = new Dictionary(decodeTableSize, new BitsWithLength.Comparer()); + result.DecodeTable = new Dictionary(decodeTableSize); for(int i = 0; i < decodeTableSize; i++) { diff --git a/optimizations/JPEG/DCT.cs b/optimizations/JPEG/DCT.cs index b9f0f52..7111e59 100644 --- a/optimizations/JPEG/DCT.cs +++ b/optimizations/JPEG/DCT.cs @@ -1,69 +1,109 @@ using System; -using JPEG.Utilities; namespace JPEG; -public class DCT +public static class DCT { - public static double[,] DCT2D(double[,] input) - { - var height = input.GetLength(0); - var width = input.GetLength(1); - var coeffs = new double[width, height]; - - MathEx.LoopByTwoVariables( - 0, width, - 0, height, - (u, v) => - { - var sum = MathEx - .SumByTwoVariables( - 0, width, - 0, height, - (x, y) => BasisFunction(input[x, y], u, v, x, y, height, width)); - - coeffs[u, v] = sum * Beta(height, width) * Alpha(u) * Alpha(v); - }); - - return coeffs; - } - - public static void IDCT2D(double[,] coeffs, double[,] output) - { - for (var x = 0; x < coeffs.GetLength(1); x++) - { - for (var y = 0; y < coeffs.GetLength(0); y++) - { - var sum = MathEx - .SumByTwoVariables( - 0, coeffs.GetLength(1), - 0, coeffs.GetLength(0), - (u, v) => - BasisFunction(coeffs[u, v], u, v, x, y, coeffs.GetLength(0), coeffs.GetLength(1)) * - Alpha(u) * Alpha(v)); - - output[x, y] = sum * Beta(coeffs.GetLength(0), coeffs.GetLength(1)); - } - } - } - - public static double BasisFunction(double a, double u, double v, double x, double y, int height, int width) - { - var b = Math.Cos(((2d * x + 1d) * u * Math.PI) / (2 * width)); - var c = Math.Cos(((2d * y + 1d) * v * Math.PI) / (2 * height)); - - return a * b * c; - } - - private static double Alpha(int u) - { - if (u == 0) - return 1 / Math.Sqrt(2); - return 1; - } - - private static double Beta(int height, int width) - { - return 1d / width + 1d / height; - } + private const int Size = 8; + private const int BlockSize = Size * Size; + private const float Beta = 0.25f; + + private static readonly float[] AlphaCos = new float[BlockSize]; + + static DCT() + { + for (var u = 0; u < Size; u++) + { + var alpha = u == 0 ? (float)(1.0 / Math.Sqrt(2.0)) : 1.0f; + for (var x = 0; x < Size; x++) + { + AlphaCos[u * Size + x] = + alpha * (float)Math.Cos(((2.0 * x + 1.0) * u * Math.PI) / 16.0); + } + } + } + + public static void DCT2D(Span data) + { + Span coeffs = stackalloc float[BlockSize]; + DCT2D(data, coeffs); + coeffs.CopyTo(data); + } + + public static void DCT2D(ReadOnlySpan input, Span coeffs) + { + Span tmp = stackalloc float[BlockSize]; + + for (var y = 0; y < Size; y++) + { + var rowOffset = y * Size; + for (var u = 0; u < Size; u++) + { + var acOffset = u * Size; + var sum = 0.0f; + for (var x = 0; x < Size; x++) + { + sum += input[rowOffset + x] * AlphaCos[acOffset + x]; + } + + tmp[rowOffset + u] = sum; + } + } + + for (var v = 0; v < Size; v++) + { + var acOffset = v * Size; + for (var u = 0; u < Size; u++) + { + var sum = 0.0f; + for (var y = 0; y < Size; y++) + { + sum += tmp[y * Size + u] * AlphaCos[acOffset + y]; + } + + coeffs[v * Size + u] = sum * Beta; + } + } + } + + public static void IDCT2D(Span data) + { + Span output = stackalloc float[BlockSize]; + IDCT2D(data, output); + output.CopyTo(data); + } + + public static void IDCT2D(ReadOnlySpan coeffs, Span output) + { + Span tmp = stackalloc float[BlockSize]; + + for (var v = 0; v < Size; v++) + { + var rowOffset = v * Size; + for (var x = 0; x < Size; x++) + { + var sum = 0.0f; + for (var u = 0; u < Size; u++) + { + sum += coeffs[rowOffset + u] * AlphaCos[u * Size + x]; + } + + tmp[rowOffset + x] = sum; + } + } + + for (var y = 0; y < Size; y++) + { + for (var x = 0; x < Size; x++) + { + var sum = 0.0f; + for (var v = 0; v < Size; v++) + { + sum += tmp[v * Size + x] * AlphaCos[v * Size + y]; + } + + output[y * Size + x] = sum * Beta; + } + } + } } \ No newline at end of file diff --git a/optimizations/JPEG/HuffmanCodec.cs b/optimizations/JPEG/HuffmanCodec.cs index c7a5b22..da297fc 100644 --- a/optimizations/JPEG/HuffmanCodec.cs +++ b/optimizations/JPEG/HuffmanCodec.cs @@ -1,191 +1,236 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using JPEG.Utilities; +using System; +using System.Collections.Generic; namespace JPEG; class HuffmanNode { - public byte? LeafLabel { get; set; } - public int Frequency { get; set; } - public HuffmanNode Left { get; set; } - public HuffmanNode Right { get; set; } + public byte? LeafLabel { get; set; } + public int Frequency { get; set; } + public HuffmanNode Left { get; set; } + public HuffmanNode Right { get; set; } } -public class BitsWithLength +public struct BitsWithLength : IEquatable { - public int Bits { get; set; } - public int BitsCount { get; set; } - - public class Comparer : IEqualityComparer - { - public bool Equals(BitsWithLength x, BitsWithLength y) - { - if (x == y) return true; - if (x == null || y == null) - return false; - return x.BitsCount == y.BitsCount && x.Bits == y.Bits; - } - - public int GetHashCode(BitsWithLength obj) - { - if (obj == null) - return 0; - return ((397 * obj.Bits) << 5) ^ (17 * obj.BitsCount); - } - } + public int Bits { get; set; } + public int BitsCount { get; set; } + + public bool Equals(BitsWithLength x) + { + return BitsCount == x.BitsCount && Bits == x.Bits; + } + + public override bool Equals(object obj) + { + return obj is BitsWithLength other && Equals(other); + } + + public override int GetHashCode() + { + return ((397 * Bits) << 5) ^ (17 * BitsCount); + } } class BitsBuffer { - private List buffer = new List(); - private BitsWithLength unfinishedBits = new BitsWithLength(); - - public void Add(BitsWithLength bitsWithLength) - { - var bitsCount = bitsWithLength.BitsCount; - var bits = bitsWithLength.Bits; - - int neededBits = 8 - unfinishedBits.BitsCount; - while (bitsCount >= neededBits) - { - bitsCount -= neededBits; - buffer.Add((byte)((unfinishedBits.Bits << neededBits) + (bits >> bitsCount))); - - bits = bits & ((1 << bitsCount) - 1); - - unfinishedBits.Bits = 0; - unfinishedBits.BitsCount = 0; - - neededBits = 8; - } - - unfinishedBits.BitsCount += bitsCount; - unfinishedBits.Bits = (unfinishedBits.Bits << bitsCount) + bits; - } - - public byte[] ToArray(out long bitsCount) - { - bitsCount = buffer.Count * 8L + unfinishedBits.BitsCount; - var result = new byte[bitsCount / 8 + (bitsCount % 8 > 0 ? 1 : 0)]; - buffer.CopyTo(result); - if (unfinishedBits.BitsCount > 0) - result[buffer.Count] = (byte)(unfinishedBits.Bits << (8 - unfinishedBits.BitsCount)); - return result; - } + private readonly List buffer; + private BitsWithLength unfinishedBits = new BitsWithLength(); + + public BitsBuffer(int capacity = 0) + { + buffer = capacity > 0 ? new List(capacity) : new List(); + } + + public void Add(BitsWithLength bitsWithLength) + { + var bitsCount = bitsWithLength.BitsCount; + var bits = bitsWithLength.Bits; + + var neededBits = 8 - unfinishedBits.BitsCount; + while (bitsCount >= neededBits) + { + bitsCount -= neededBits; + buffer.Add((byte)((unfinishedBits.Bits << neededBits) + (bits >> bitsCount))); + + bits = bits & ((1 << bitsCount) - 1); + + unfinishedBits.Bits = 0; + unfinishedBits.BitsCount = 0; + + neededBits = 8; + } + + unfinishedBits.BitsCount += bitsCount; + unfinishedBits.Bits = (unfinishedBits.Bits << bitsCount) + bits; + } + + public byte[] ToArray(out long bitsCount) + { + bitsCount = buffer.Count * 8L + unfinishedBits.BitsCount; + var result = new byte[bitsCount / 8 + (bitsCount % 8 > 0 ? 1 : 0)]; + buffer.CopyTo(result); + if (unfinishedBits.BitsCount > 0) + result[buffer.Count] = (byte)(unfinishedBits.Bits << (8 - unfinishedBits.BitsCount)); + return result; + } } class HuffmanCodec { - public static byte[] Encode(IEnumerable data, out Dictionary decodeTable, - out long bitsCount) - { - var frequences = CalcFrequences(data); - - var root = BuildHuffmanTree(frequences); - - var encodeTable = new BitsWithLength[byte.MaxValue + 1]; - FillEncodeTable(root, encodeTable); - - var bitsBuffer = new BitsBuffer(); - foreach (var b in data) - bitsBuffer.Add(encodeTable[b]); - - decodeTable = CreateDecodeTable(encodeTable); - - return bitsBuffer.ToArray(out bitsCount); - } - - public static byte[] Decode(byte[] encodedData, Dictionary decodeTable, long bitsCount) - { - var result = new List(); - - byte decodedByte; - var sample = new BitsWithLength { Bits = 0, BitsCount = 0 }; - for (var byteNum = 0; byteNum < encodedData.Length; byteNum++) - { - var b = encodedData[byteNum]; - for (var bitNum = 0; bitNum < 8 && byteNum * 8 + bitNum < bitsCount; bitNum++) - { - sample.Bits = (sample.Bits << 1) + ((b & (1 << (8 - bitNum - 1))) != 0 ? 1 : 0); - sample.BitsCount++; - - if (decodeTable.TryGetValue(sample, out decodedByte)) - { - result.Add(decodedByte); - - sample.BitsCount = 0; - sample.Bits = 0; - } - } - } - - return result.ToArray(); - } - - private static Dictionary CreateDecodeTable(BitsWithLength[] encodeTable) - { - var result = new Dictionary(new BitsWithLength.Comparer()); - for (int b = 0; b < encodeTable.Length; b++) - { - var bitsWithLength = encodeTable[b]; - if (bitsWithLength == null) - continue; - - result[bitsWithLength] = (byte)b; - } - - return result; - } - - private static void FillEncodeTable(HuffmanNode node, BitsWithLength[] encodeSubstitutionTable, - int bitvector = 0, int depth = 0) - { - if (node.LeafLabel != null) - encodeSubstitutionTable[node.LeafLabel.Value] = - new BitsWithLength { Bits = bitvector, BitsCount = depth }; - else - { - if (node.Left != null) - { - FillEncodeTable(node.Left, encodeSubstitutionTable, (bitvector << 1) + 1, depth + 1); - FillEncodeTable(node.Right, encodeSubstitutionTable, (bitvector << 1) + 0, depth + 1); - } - } - } - - private static HuffmanNode BuildHuffmanTree(int[] frequences) - { - var nodes = GetNodes(frequences); - - while (nodes.Count() > 1) - { - var firstMin = nodes.MinOrDefault(node => node.Frequency); - nodes = nodes.Without(firstMin); - var secondMin = nodes.MinOrDefault(node => node.Frequency); - nodes = nodes.Without(secondMin); - nodes = nodes.Concat(new HuffmanNode - { Frequency = firstMin.Frequency + secondMin.Frequency, Left = secondMin, Right = firstMin } - .ToEnumerable()); - } - - return nodes.First(); - } - - private static IEnumerable GetNodes(int[] frequences) - { - return Enumerable.Range(0, byte.MaxValue + 1) - .Select(num => new HuffmanNode { Frequency = frequences[num], LeafLabel = (byte)num }) - .Where(node => node.Frequency > 0) - .ToArray(); - } - - private static int[] CalcFrequences(IEnumerable data) - { - var result = new int[byte.MaxValue + 1]; - Parallel.ForEach(data, b => Interlocked.Increment(ref result[b])); - return result; - } + public static byte[] Encode(IReadOnlyList data, out Dictionary decodeTable, out long bitsCount) + { + if (data.Count == 0) + { + decodeTable = new Dictionary(); + bitsCount = 0; + return Array.Empty(); + } + + var frequences = CalcFrequences(data); + var root = BuildHuffmanTree(frequences); + + var encodeTable = new BitsWithLength[byte.MaxValue + 1]; + FillEncodeTable(root, encodeTable); + + var bitsBuffer = new BitsBuffer(data.Count); + for (var i = 0; i < data.Count; i++) + { + bitsBuffer.Add(encodeTable[data[i]]); + } + + decodeTable = CreateDecodeTable(encodeTable); + return bitsBuffer.ToArray(out bitsCount); + } + + public static byte[] Decode(byte[] encodedData, Dictionary decodeTable, long bitsCount, int size) + { + var result = new byte[size]; + var arrayIndex = 0; + + byte decodedByte; + var sample = new BitsWithLength { Bits = 0, BitsCount = 0 }; + for (var byteNum = 0; byteNum < encodedData.Length; byteNum++) + { + var b = encodedData[byteNum]; + for (var bitNum = 0; bitNum < 8 && byteNum * 8 + bitNum < bitsCount; bitNum++) + { + sample.Bits = (sample.Bits << 1) + ((b & (1 << (8 - bitNum - 1))) != 0 ? 1 : 0); + sample.BitsCount++; + + if (decodeTable.TryGetValue(sample, out decodedByte)) + { + if (arrayIndex >= result.Length) + return result; + + result[arrayIndex] = decodedByte; + arrayIndex++; + sample.BitsCount = 0; + sample.Bits = 0; + } + } + } + + return result; + } + + private static Dictionary CreateDecodeTable(BitsWithLength[] encodeTable) + { + var result = new Dictionary(); + for (int b = 0; b < encodeTable.Length; b++) + { + var bitsWithLength = encodeTable[b]; + if (bitsWithLength.BitsCount > 0) + { + result[bitsWithLength] = (byte)b; + } + } + return result; + } + + private static void FillEncodeTable(HuffmanNode node, BitsWithLength[] encodeSubstitutionTable, int bitvector = 0, int depth = 0) + { + if (node.LeafLabel != null) + { + encodeSubstitutionTable[node.LeafLabel.Value] = new BitsWithLength + { + Bits = bitvector, + BitsCount = depth == 0 ? 1 : depth + }; + } + else + { + if (node.Left != null) + { + FillEncodeTable(node.Left, encodeSubstitutionTable, (bitvector << 1) + 1, depth + 1); + FillEncodeTable(node.Right, encodeSubstitutionTable, (bitvector << 1) + 0, depth + 1); + } + } + } + + private static HuffmanNode BuildHuffmanTree(int[] frequences) + { + var nodes = GetNodes(frequences); + if (nodes.Count == 0) + throw new InvalidOperationException(); + if (nodes.Count == 1) + return nodes[0]; + + while (nodes.Count > 1) + { + var minIndex1 = 0; + for (var i = 0; i < nodes.Count; i++) + { + if (nodes[i].Frequency < nodes[minIndex1].Frequency) + minIndex1 = i; + } + var firstMin = nodes[minIndex1]; + nodes.RemoveAt(minIndex1); + + var minIndex2 = 0; + for (var j = 0; j < nodes.Count; j++) + { + if (nodes[j].Frequency < nodes[minIndex2].Frequency) + minIndex2 = j; + } + var secondMin = nodes[minIndex2]; + nodes.RemoveAt(minIndex2); + + nodes.Add(new HuffmanNode + { + Frequency = firstMin.Frequency + secondMin.Frequency, + Left = secondMin, + Right = firstMin + }); + } + + return nodes[0]; + } + + private static List GetNodes(int[] frequences) + { + var nodes = new List(byte.MaxValue + 1); + for (var i = 0; i < byte.MaxValue + 1; i++) + { + if (frequences[i] > 0) + { + nodes.Add(new HuffmanNode + { + Frequency = frequences[i], + LeafLabel = (byte)i + }); + } + } + return nodes; + } + + private static int[] CalcFrequences(IReadOnlyList data) + { + var result = new int[byte.MaxValue + 1]; + for (var i = 0; i < data.Count; i++) + { + result[data[i]]++; + } + return result; + } } \ No newline at end of file diff --git a/optimizations/JPEG/Images/Matrix.cs b/optimizations/JPEG/Images/Matrix.cs index 4b23e32..585a470 100644 --- a/optimizations/JPEG/Images/Matrix.cs +++ b/optimizations/JPEG/Images/Matrix.cs @@ -1,65 +1,131 @@ -using System.Drawing; +using System; +using System.Buffers; +using System.Drawing; +using System.Drawing.Imaging; +using System.Threading.Tasks; namespace JPEG.Images; -class Matrix +public class Matrix : IDisposable { - public readonly Pixel[,] Pixels; - public readonly int Height; - public readonly int Width; - - public Matrix(int height, int width) - { - Height = height; - Width = width; - - Pixels = new Pixel[height, width]; - for (var i = 0; i < height; ++i) - for (var j = 0; j < width; ++j) - Pixels[i, j] = new Pixel(0, 0, 0, PixelFormat.RGB); - } - - public static explicit operator Matrix(Bitmap bmp) - { - var height = bmp.Height - bmp.Height % 8; - var width = bmp.Width - bmp.Width % 8; - var matrix = new Matrix(height, width); - - for (var j = 0; j < height; j++) - { - for (var i = 0; i < width; i++) - { - var pixel = bmp.GetPixel(i, j); - matrix.Pixels[j, i] = new Pixel(pixel.R, pixel.G, pixel.B, PixelFormat.RGB); - } - } - - return matrix; - } - - public static explicit operator Bitmap(Matrix matrix) - { - var bmp = new Bitmap(matrix.Width, matrix.Height); - - for (var j = 0; j < bmp.Height; j++) - { - for (var i = 0; i < bmp.Width; i++) - { - var pixel = matrix.Pixels[j, i]; - bmp.SetPixel(i, j, Color.FromArgb(ToByte(pixel.R), ToByte(pixel.G), ToByte(pixel.B))); - } - } - - return bmp; - } - - public static int ToByte(double d) - { - var val = (int)d; - if (val > byte.MaxValue) - return byte.MaxValue; - if (val < byte.MinValue) - return byte.MinValue; - return val; - } + public readonly int Height; + public readonly int Width; + + public readonly float[] Y; + public readonly float[] Cb; + public readonly float[] Cr; + + public Matrix(int height, int width) + { + Height = height; + Width = width; + + Y = ArrayPool.Shared.Rent(height * width); + Cb = ArrayPool.Shared.Rent(height * width); + Cr = ArrayPool.Shared.Rent(height * width); + } + + public static explicit operator Matrix(Bitmap bmp) + { + var height = bmp.Height - bmp.Height % 8; + var width = bmp.Width - bmp.Width % 8; + + var matrix = new Matrix(height, width); + + var rect = new Rectangle(0, 0, bmp.Width, bmp.Height); + var bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); + + var stride = bmpData.Stride; + + unsafe + { + var ptr = (byte*)bmpData.Scan0; + Parallel.For(0, height, j => + { + var start = ptr + j * stride; + var rowOffset = j * width; + + for (var i = 0; i < width; i++) + { + var curr = start + i * 3; + var index = rowOffset + i; + + var b = curr[0]; + var g = curr[1]; + var r = curr[2]; + + var _y = 16.0f + (65.738f * r + 129.057f * g + 24.064f * b) / 256.0f; + var cb = 128.0f + (-37.945f * r - 74.494f * g + 112.439f * b) / 256.0f; + var cr = 128.0f + (112.439f * r - 94.154f * g - 18.285f * b) / 256.0f; + + matrix.Y[index] = _y; + matrix.Cb[index] = cb; + matrix.Cr[index] = cr; + } + }); + } + + bmp.UnlockBits(bmpData); + return matrix; + } + + public static explicit operator Bitmap(Matrix matrix) + { + var bmp = new Bitmap(matrix.Width, matrix.Height, PixelFormat.Format24bppRgb); + + var rect = new Rectangle(0, 0, bmp.Width, bmp.Height); + var bmpData = bmp.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); + + var matrixHeight = matrix.Height; + var matrixWidth = matrix.Width; + var stride = bmpData.Stride; + + unsafe + { + var ptr = (byte*)bmpData.Scan0; + Parallel.For(0, matrixHeight, j => + { + var start = ptr + j * stride; + var rowOffset = j * matrixWidth; + + for (var i = 0; i < matrixWidth; i++) + { + var curr = start + i * 3; + var index = rowOffset + i; + + var _y = matrix.Y[index]; + var cb = matrix.Cb[index]; + var cr = matrix.Cr[index]; + + var r = (298.082f * _y + 408.583f * cr) / 256.0f - 222.921f; + var g = (298.082f * _y - 100.291f * cb - 208.120f * cr) / 256.0f + 135.576f; + var b = (298.082f * _y + 516.412f * cb) / 256.0f - 276.836f; + + curr[0] = ToByte(b); + curr[1] = ToByte(g); + curr[2] = ToByte(r); + } + }); + } + + bmp.UnlockBits(bmpData); + return bmp; + } + + public void Dispose() + { + ArrayPool.Shared.Return(Y); + ArrayPool.Shared.Return(Cb); + ArrayPool.Shared.Return(Cr); + } + + private static byte ToByte(float d) + { + var val = (int)Math.Round(d); + if (val > byte.MaxValue) + return byte.MaxValue; + if (val < byte.MinValue) + return byte.MinValue; + return (byte)val; + } } \ No newline at end of file diff --git a/optimizations/JPEG/Images/Pixel.cs b/optimizations/JPEG/Images/Pixel.cs deleted file mode 100644 index 6be42d5..0000000 --- a/optimizations/JPEG/Images/Pixel.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Linq; - -namespace JPEG.Images; - -public class Pixel -{ - private readonly PixelFormat format; - - public Pixel(double firstComponent, double secondComponent, double thirdComponent, PixelFormat pixelFormat) - { - if (!new[] { PixelFormat.RGB, PixelFormat.YCbCr }.Contains(pixelFormat)) - throw new FormatException("Unknown pixel format: " + pixelFormat); - format = pixelFormat; - if (pixelFormat == PixelFormat.RGB) - { - r = firstComponent; - g = secondComponent; - b = thirdComponent; - } - - if (pixelFormat == PixelFormat.YCbCr) - { - y = firstComponent; - cb = secondComponent; - cr = thirdComponent; - } - } - - private readonly double r; - private readonly double g; - private readonly double b; - - private readonly double y; - private readonly double cb; - private readonly double cr; - - public double R => format == PixelFormat.RGB ? r : (298.082 * y + 408.583 * Cr) / 256.0 - 222.921; - - public double G => - format == PixelFormat.RGB ? g : (298.082 * Y - 100.291 * Cb - 208.120 * Cr) / 256.0 + 135.576; - - public double B => format == PixelFormat.RGB ? b : (298.082 * Y + 516.412 * Cb) / 256.0 - 276.836; - - public double Y => format == PixelFormat.YCbCr ? y : 16.0 + (65.738 * R + 129.057 * G + 24.064 * B) / 256.0; - public double Cb => format == PixelFormat.YCbCr ? cb : 128.0 + (-37.945 * R - 74.494 * G + 112.439 * B) / 256.0; - public double Cr => format == PixelFormat.YCbCr ? cr : 128.0 + (112.439 * R - 94.154 * G - 18.285 * B) / 256.0; -} \ No newline at end of file diff --git a/optimizations/JPEG/Images/PixelFormat.cs b/optimizations/JPEG/Images/PixelFormat.cs deleted file mode 100644 index 4330447..0000000 --- a/optimizations/JPEG/Images/PixelFormat.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace JPEG.Images; - -public class PixelFormat -{ - private string Format; - - private PixelFormat(string format) - { - Format = format; - } - - public static PixelFormat RGB => new PixelFormat(nameof(RGB)); - public static PixelFormat YCbCr => new PixelFormat(nameof(YCbCr)); - - protected bool Equals(PixelFormat other) - { - return string.Equals(Format, other.Format); - } - - public override bool Equals(object obj) - { - if (obj.GetType() != this.GetType()) return false; - return Equals((PixelFormat)obj); - } - - public override int GetHashCode() - { - return (Format != null ? Format.GetHashCode() : 0); - } - - public static bool operator ==(PixelFormat a, PixelFormat b) - { - return a.Equals(b); - } - - public static bool operator !=(PixelFormat a, PixelFormat b) - { - return !a.Equals(b); - } - - public override string ToString() - { - return Format; - } - - ~PixelFormat() - { - Format = null; - } -} \ No newline at end of file diff --git a/optimizations/JPEG/JPEG.csproj b/optimizations/JPEG/JPEG.csproj index fd2e0e5..1462224 100644 --- a/optimizations/JPEG/JPEG.csproj +++ b/optimizations/JPEG/JPEG.csproj @@ -4,6 +4,7 @@ Exe net7.0 latest + true diff --git a/optimizations/JPEG/Processor/JpegProcessor.cs b/optimizations/JPEG/Processor/JpegProcessor.cs index 09cb73f..b214b28 100644 --- a/optimizations/JPEG/Processor/JpegProcessor.cs +++ b/optimizations/JPEG/Processor/JpegProcessor.cs @@ -1,253 +1,289 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; +using System.Threading.Tasks; using JPEG.Images; -using PixelFormat = JPEG.Images.PixelFormat; namespace JPEG.Processor; public class JpegProcessor : IJpegProcessor { - public static readonly JpegProcessor Init = new(); - public const int CompressionQuality = 70; - private const int DCTSize = 8; - - public void Compress(string imagePath, string compressedImagePath) - { - using var fileStream = File.OpenRead(imagePath); - using var bmp = (Bitmap)Image.FromStream(fileStream, false, false); - var imageMatrix = (Matrix)bmp; - //Console.WriteLine($"{bmp.Width}x{bmp.Height} - {fileStream.Length / (1024.0 * 1024):F2} MB"); - var compressionResult = Compress(imageMatrix, CompressionQuality); - compressionResult.Save(compressedImagePath); - } - - public void Uncompress(string compressedImagePath, string uncompressedImagePath) - { - var compressedImage = CompressedImage.Load(compressedImagePath); - var uncompressedImage = Uncompress(compressedImage); - var resultBmp = (Bitmap)uncompressedImage; - resultBmp.Save(uncompressedImagePath, ImageFormat.Bmp); - } - - private static CompressedImage Compress(Matrix matrix, int quality = 50) - { - var allQuantizedBytes = new List(); - - for (var y = 0; y < matrix.Height; y += DCTSize) - { - for (var x = 0; x < matrix.Width; x += DCTSize) - { - foreach (var selector in new Func[] { p => p.Y, p => p.Cb, p => p.Cr }) - { - var subMatrix = GetSubMatrix(matrix, y, DCTSize, x, DCTSize, selector); - ShiftMatrixValues(subMatrix, -128); - var channelFreqs = DCT.DCT2D(subMatrix); - var quantizedFreqs = Quantize(channelFreqs, quality); - var quantizedBytes = ZigZagScan(quantizedFreqs); - allQuantizedBytes.AddRange(quantizedBytes); - } - } - } - - long bitsCount; - Dictionary decodeTable; - var compressedBytes = HuffmanCodec.Encode(allQuantizedBytes, out decodeTable, out bitsCount); - - return new CompressedImage - { - Quality = quality, CompressedBytes = compressedBytes, BitsCount = bitsCount, DecodeTable = decodeTable, - Height = matrix.Height, Width = matrix.Width - }; - } - - private static Matrix Uncompress(CompressedImage image) - { - var result = new Matrix(image.Height, image.Width); - using (var allQuantizedBytes = - new MemoryStream(HuffmanCodec.Decode(image.CompressedBytes, image.DecodeTable, image.BitsCount))) - { - for (var y = 0; y < image.Height; y += DCTSize) - { - for (var x = 0; x < image.Width; x += DCTSize) - { - var _y = new double[DCTSize, DCTSize]; - var cb = new double[DCTSize, DCTSize]; - var cr = new double[DCTSize, DCTSize]; - foreach (var channel in new[] { _y, cb, cr }) - { - var quantizedBytes = new byte[DCTSize * DCTSize]; - allQuantizedBytes.ReadAsync(quantizedBytes, 0, quantizedBytes.Length).Wait(); - var quantizedFreqs = ZigZagUnScan(quantizedBytes); - var channelFreqs = DeQuantize(quantizedFreqs, image.Quality); - DCT.IDCT2D(channelFreqs, channel); - ShiftMatrixValues(channel, 128); - } - - SetPixels(result, _y, cb, cr, PixelFormat.YCbCr, y, x); - } - } - } - - return result; - } - - private static void ShiftMatrixValues(double[,] subMatrix, int shiftValue) - { - var height = subMatrix.GetLength(0); - var width = subMatrix.GetLength(1); - - for (var y = 0; y < height; y++) - for (var x = 0; x < width; x++) - subMatrix[y, x] = subMatrix[y, x] + shiftValue; - } - - private static void SetPixels(Matrix matrix, double[,] a, double[,] b, double[,] c, PixelFormat format, - int yOffset, int xOffset) - { - var height = a.GetLength(0); - var width = a.GetLength(1); - - for (var y = 0; y < height; y++) - for (var x = 0; x < width; x++) - matrix.Pixels[yOffset + y, xOffset + x] = new Pixel(a[y, x], b[y, x], c[y, x], format); - } - - private static double[,] GetSubMatrix(Matrix matrix, int yOffset, int yLength, int xOffset, int xLength, - Func componentSelector) - { - var result = new double[yLength, xLength]; - for (var j = 0; j < yLength; j++) - for (var i = 0; i < xLength; i++) - result[j, i] = componentSelector(matrix.Pixels[yOffset + j, xOffset + i]); - return result; - } - - private static IEnumerable ZigZagScan(byte[,] channelFreqs) - { - return new[] - { - channelFreqs[0, 0], channelFreqs[0, 1], channelFreqs[1, 0], channelFreqs[2, 0], channelFreqs[1, 1], - channelFreqs[0, 2], channelFreqs[0, 3], channelFreqs[1, 2], - channelFreqs[2, 1], channelFreqs[3, 0], channelFreqs[4, 0], channelFreqs[3, 1], channelFreqs[2, 2], - channelFreqs[1, 3], channelFreqs[0, 4], channelFreqs[0, 5], - channelFreqs[1, 4], channelFreqs[2, 3], channelFreqs[3, 2], channelFreqs[4, 1], channelFreqs[5, 0], - channelFreqs[6, 0], channelFreqs[5, 1], channelFreqs[4, 2], - channelFreqs[3, 3], channelFreqs[2, 4], channelFreqs[1, 5], channelFreqs[0, 6], channelFreqs[0, 7], - channelFreqs[1, 6], channelFreqs[2, 5], channelFreqs[3, 4], - channelFreqs[4, 3], channelFreqs[5, 2], channelFreqs[6, 1], channelFreqs[7, 0], channelFreqs[7, 1], - channelFreqs[6, 2], channelFreqs[5, 3], channelFreqs[4, 4], - channelFreqs[3, 5], channelFreqs[2, 6], channelFreqs[1, 7], channelFreqs[2, 7], channelFreqs[3, 6], - channelFreqs[4, 5], channelFreqs[5, 4], channelFreqs[6, 3], - channelFreqs[7, 2], channelFreqs[7, 3], channelFreqs[6, 4], channelFreqs[5, 5], channelFreqs[4, 6], - channelFreqs[3, 7], channelFreqs[4, 7], channelFreqs[5, 6], - channelFreqs[6, 5], channelFreqs[7, 4], channelFreqs[7, 5], channelFreqs[6, 6], channelFreqs[5, 7], - channelFreqs[6, 7], channelFreqs[7, 6], channelFreqs[7, 7] - }; - } - - private static byte[,] ZigZagUnScan(IReadOnlyList quantizedBytes) - { - return new[,] - { - { - quantizedBytes[0], quantizedBytes[1], quantizedBytes[5], quantizedBytes[6], quantizedBytes[14], - quantizedBytes[15], quantizedBytes[27], quantizedBytes[28] - }, - { - quantizedBytes[2], quantizedBytes[4], quantizedBytes[7], quantizedBytes[13], quantizedBytes[16], - quantizedBytes[26], quantizedBytes[29], quantizedBytes[42] - }, - { - quantizedBytes[3], quantizedBytes[8], quantizedBytes[12], quantizedBytes[17], quantizedBytes[25], - quantizedBytes[30], quantizedBytes[41], quantizedBytes[43] - }, - { - quantizedBytes[9], quantizedBytes[11], quantizedBytes[18], quantizedBytes[24], quantizedBytes[31], - quantizedBytes[40], quantizedBytes[44], quantizedBytes[53] - }, - { - quantizedBytes[10], quantizedBytes[19], quantizedBytes[23], quantizedBytes[32], quantizedBytes[39], - quantizedBytes[45], quantizedBytes[52], quantizedBytes[54] - }, - { - quantizedBytes[20], quantizedBytes[22], quantizedBytes[33], quantizedBytes[38], quantizedBytes[46], - quantizedBytes[51], quantizedBytes[55], quantizedBytes[60] - }, - { - quantizedBytes[21], quantizedBytes[34], quantizedBytes[37], quantizedBytes[47], quantizedBytes[50], - quantizedBytes[56], quantizedBytes[59], quantizedBytes[61] - }, - { - quantizedBytes[35], quantizedBytes[36], quantizedBytes[48], quantizedBytes[49], quantizedBytes[57], - quantizedBytes[58], quantizedBytes[62], quantizedBytes[63] - } - }; - } - - private static byte[,] Quantize(double[,] channelFreqs, int quality) - { - var result = new byte[channelFreqs.GetLength(0), channelFreqs.GetLength(1)]; - - var quantizationMatrix = GetQuantizationMatrix(quality); - for (int y = 0; y < channelFreqs.GetLength(0); y++) - { - for (int x = 0; x < channelFreqs.GetLength(1); x++) - { - result[y, x] = (byte)(channelFreqs[y, x] / quantizationMatrix[y, x]); - } - } - - return result; - } - - private static double[,] DeQuantize(byte[,] quantizedBytes, int quality) - { - var result = new double[quantizedBytes.GetLength(0), quantizedBytes.GetLength(1)]; - var quantizationMatrix = GetQuantizationMatrix(quality); - - for (int y = 0; y < quantizedBytes.GetLength(0); y++) - { - for (int x = 0; x < quantizedBytes.GetLength(1); x++) - { - result[y, x] = - ((sbyte)quantizedBytes[y, x]) * - quantizationMatrix[y, x]; //NOTE cast to sbyte not to loose negative numbers - } - } - - return result; - } - - private static int[,] GetQuantizationMatrix(int quality) - { - if (quality < 1 || quality > 99) - throw new ArgumentException("quality must be in [1,99] interval"); - - var multiplier = quality < 50 ? 5000 / quality : 200 - 2 * quality; - - var result = new[,] - { - { 16, 11, 10, 16, 24, 40, 51, 61 }, - { 12, 12, 14, 19, 26, 58, 60, 55 }, - { 14, 13, 16, 24, 40, 57, 69, 56 }, - { 14, 17, 22, 29, 51, 87, 80, 62 }, - { 18, 22, 37, 56, 68, 109, 103, 77 }, - { 24, 35, 55, 64, 81, 104, 113, 92 }, - { 49, 64, 78, 87, 103, 121, 120, 101 }, - { 72, 92, 95, 98, 112, 100, 103, 99 } - }; - - for (int y = 0; y < result.GetLength(0); y++) - { - for (int x = 0; x < result.GetLength(1); x++) - { - result[y, x] = (multiplier * result[y, x] + 50) / 100; - } - } - - return result; - } + public static readonly JpegProcessor Init = new(); + public const int CompressionQuality = 70; + private const int DCTSize = 8; + private const int TotalSize = DCTSize * DCTSize; + + private static readonly int[] ZigZagMap = new[] + { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 + }; + + private static readonly int[] QuantizationMatrix = new[] + { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99 + }; + + public void Compress(string imagePath, string compressedImagePath) + { + using var fileStream = File.OpenRead(imagePath); + using var bmp = (Bitmap)Image.FromStream(fileStream, false, false); + using var imageMatrix = (Matrix)bmp; + var compressionResult = Compress(imageMatrix, CompressionQuality); + compressionResult.Save(compressedImagePath); + } + + public void Uncompress(string compressedImagePath, string uncompressedImagePath) + { + var compressedImage = CompressedImage.Load(compressedImagePath); + using var uncompressedImage = Uncompress(compressedImage); + using var resultBmp = (Bitmap)uncompressedImage; + resultBmp.Save(uncompressedImagePath, ImageFormat.Bmp); + } + + private static CompressedImage Compress(Matrix matrix, int quality = 50) + { + var height = matrix.Height; + var width = matrix.Width; + var maxSize = height * width * 3; + var blocksPerRow = width / DCTSize; + var blockPerHeight = height / DCTSize; + var allQuantizedBytes = ArrayPool.Shared.Rent(maxSize); + + try + { + var quantizationMatrix = GetQuantizationMatrix(quality); + Parallel.For(0, blockPerHeight, blockRow => + { + EncodeBlockRow(matrix, blockRow, blocksPerRow, quantizationMatrix, allQuantizedBytes); + }); + + var data = new ArraySegment(allQuantizedBytes, 0, maxSize); + long bitsCount; + Dictionary decodeTable; + var compressedBytes = HuffmanCodec.Encode(data, out decodeTable, out bitsCount); + + return new CompressedImage + { + Quality = quality, + CompressedBytes = compressedBytes, + BitsCount = bitsCount, + DecodeTable = decodeTable, + Height = matrix.Height, + Width = matrix.Width + }; + } + finally + { + ArrayPool.Shared.Return(allQuantizedBytes); + } + } + + private static void EncodeBlockRow( + Matrix matrix, + int blockRow, + int blocksPerRow, + int[] quantizationMatrix, + byte[] output) + { + var y = blockRow * DCTSize; + Span subMatrix = stackalloc float[TotalSize]; + Span quantizedFreqs = stackalloc byte[TotalSize]; + Span quantizedBytes = stackalloc byte[TotalSize]; + + for (var blockColumn = 0; blockColumn < blocksPerRow; blockColumn++) + { + var x = blockColumn * DCTSize; + var outputOffset = (blockRow * blocksPerRow + blockColumn) * 3 * TotalSize; + + EncodeChannel(matrix, y, x, matrix.Y, quantizationMatrix, subMatrix, quantizedFreqs, quantizedBytes, output, outputOffset); + EncodeChannel(matrix, y, x, matrix.Cb, quantizationMatrix, subMatrix, quantizedFreqs, quantizedBytes, output, outputOffset + TotalSize); + EncodeChannel(matrix, y, x, matrix.Cr, quantizationMatrix, subMatrix, quantizedFreqs, quantizedBytes, output, outputOffset + 2 * TotalSize); + } + } + + private static void EncodeChannel( + Matrix matrix, + int yOffset, + int xOffset, + float[] channel, + int[] quantizationMatrix, + Span subMatrix, + Span quantizedFreqs, + Span quantizedBytes, + byte[] output, + int outputOffset) + { + GetSubMatrix(matrix, yOffset, xOffset, channel, subMatrix); + ShiftMatrixValues(subMatrix, -128); + DCT.DCT2D(subMatrix); + Quantize(subMatrix, quantizationMatrix, quantizedFreqs); + ZigZagScan(quantizedFreqs, quantizedBytes); + quantizedBytes.CopyTo(output.AsSpan(outputOffset, TotalSize)); + } + + private static Matrix Uncompress(CompressedImage image) + { + var result = new Matrix(image.Height, image.Width); + var quantizationMatrix = GetQuantizationMatrix(image.Quality); + var blocksPerRow = image.Width / DCTSize; + var blockPerHeight = image.Height / DCTSize; + + var decodedData = HuffmanCodec.Decode( + image.CompressedBytes, + image.DecodeTable, + image.BitsCount, + image.Height * image.Width * 3); + + Parallel.For(0, blockPerHeight, blockRow => + { + DecodeBlockRow(decodedData, result, blockRow, blocksPerRow, quantizationMatrix); + }); + + return result; + } + + private static void DecodeBlockRow( + byte[] source, + Matrix result, + int blockRow, + int blocksPerRow, + int[] quantizationMatrix) + { + var y = blockRow * DCTSize; + Span _y = stackalloc float[TotalSize]; + Span cb = stackalloc float[TotalSize]; + Span cr = stackalloc float[TotalSize]; + Span quantizedFreqs = stackalloc byte[TotalSize]; + + for (var blockColumn = 0; blockColumn < blocksPerRow; blockColumn++) + { + var x = blockColumn * DCTSize; + var sourceOffset = (blockRow * blocksPerRow + blockColumn) * 3 * TotalSize; + + sourceOffset = DecodeChannel(source, sourceOffset, quantizationMatrix, _y, quantizedFreqs); + sourceOffset = DecodeChannel(source, sourceOffset, quantizationMatrix, cb, quantizedFreqs); + DecodeChannel(source, sourceOffset, quantizationMatrix, cr, quantizedFreqs); + + SetPixels(result, _y, cb, cr, y, x); + } + } + + private static int DecodeChannel( + ReadOnlySpan source, + int sourceOffset, + int[] quantizationMatrix, + Span channelData, + Span quantizedFreqs) + { + var quantizedBytes = source.Slice(sourceOffset, TotalSize); + + ZigZagUnScan(quantizedBytes, quantizedFreqs); + DeQuantize(quantizedFreqs, quantizationMatrix, channelData); + DCT.IDCT2D(channelData); + ShiftMatrixValues(channelData, 128); + + return sourceOffset + TotalSize; + } + + private static void ShiftMatrixValues(Span subMatrix, int shiftValue) + { + for (var i = 0; i < TotalSize; i++) + { + subMatrix[i] += shiftValue; + } + } + + private static void SetPixels(Matrix matrix, Span _y, Span cb, Span cr, int yOffset, int xOffset) + { + for (var y = 0; y < DCTSize; y++) + { + for (var x = 0; x < DCTSize; x++) + { + var imageIndex = (yOffset + y) * matrix.Width + xOffset + x; + var fragmentIndex = y * DCTSize + x; + matrix.Y[imageIndex] = _y[fragmentIndex]; + matrix.Cb[imageIndex] = cb[fragmentIndex]; + matrix.Cr[imageIndex] = cr[fragmentIndex]; + } + } + } + + private static void GetSubMatrix(Matrix matrix, int yOffset, int xOffset, float[] channel, Span result) + { + var width = matrix.Width; + for (var j = 0; j < DCTSize; j++) + { + for (var i = 0; i < DCTSize; i++) + { + var imageIndex = (yOffset + j) * width + xOffset + i; + var fragmentIndex = j * DCTSize + i; + result[fragmentIndex] = channel[imageIndex]; + } + } + } + + private static void ZigZagScan(ReadOnlySpan channelFreqs, Span result) + { + for (var i = 0; i < TotalSize; i++) + { + result[i] = channelFreqs[ZigZagMap[i]]; + } + } + + private static void ZigZagUnScan(ReadOnlySpan quantizedBytes, Span result) + { + for (var i = 0; i < TotalSize; i++) + { + result[ZigZagMap[i]] = quantizedBytes[i]; + } + } + + private static void Quantize(ReadOnlySpan channelFreqs, int[] quantizationMatrix, Span result) + { + for (var i = 0; i < TotalSize; i++) + { + result[i] = (byte)(channelFreqs[i] / quantizationMatrix[i]); + } + } + + private static void DeQuantize(ReadOnlySpan quantizedBytes, int[] quantizationMatrix, Span result) + { + for (var i = 0; i < TotalSize; i++) + { + result[i] = ((sbyte)quantizedBytes[i]) * quantizationMatrix[i]; + } + } + + private static int[] GetQuantizationMatrix(int quality) + { + if (quality < 1 || quality > 99) + throw new ArgumentException("quality must be in [1,99] interval"); + + var multiplier = quality < 50 ? 5000 / quality : 200 - 2 * quality; + var quantizationMatrix = new int[TotalSize]; + for (var i = 0; i < TotalSize; i++) + { + var quantized = (multiplier * QuantizationMatrix[i] + 50) / 100; + quantizationMatrix[i] = Math.Max(1, quantized); + } + + return quantizationMatrix; + } } \ No newline at end of file diff --git a/optimizations/JPEG/Utilities/IEnumerableExtensions.cs b/optimizations/JPEG/Utilities/IEnumerableExtensions.cs deleted file mode 100644 index ab48bb9..0000000 --- a/optimizations/JPEG/Utilities/IEnumerableExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace JPEG.Utilities; - -static class IEnumerableExtensions -{ - public static T MinOrDefault(this IEnumerable enumerable, Func selector) - { - return enumerable.OrderBy(selector).FirstOrDefault(); - } - - public static IEnumerable Without(this IEnumerable enumerable, params T[] elements) - { - return enumerable.Where(x => !elements.Contains(x)); - } - - public static IEnumerable ToEnumerable(this T element) - { - yield return element; - } -} \ No newline at end of file diff --git a/optimizations/JPEG/Utilities/MathEx.cs b/optimizations/JPEG/Utilities/MathEx.cs deleted file mode 100644 index 5070d74..0000000 --- a/optimizations/JPEG/Utilities/MathEx.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Linq; - -namespace JPEG.Utilities; - -public static class MathEx -{ - public static double Sum(int from, int to, Func function) - => Enumerable.Range(from, to - from).Sum(function); - - public static double SumByTwoVariables(int from1, int to1, int from2, int to2, Func function) - => Sum(from1, to1, x => Sum(from2, to2, y => function(x, y))); - - public static double LoopByTwoVariables(int from1, int to1, int from2, int to2, Action function) - => Sum(from1, to1, x => Sum(from2, to2, y => - { - function(x, y); - return 0; - })); -} \ No newline at end of file