Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 109 additions & 23 deletions optimizations/JPEG.Benchmarks/Benchmarks/JpegProcessorBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Drawing;
using BenchmarkDotNet.Attributes;
using JPEG.Images;
using JPEG.Processor;

namespace JPEG.Benchmarks.Benchmarks;
Expand All @@ -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<float> dctCopy = stackalloc float[Size];
dctData.AsSpan().CopyTo(dctCopy);

DCT.DCT2D(dctCopy);
}

[Benchmark]
public void IDCT2D()
{
Span<float> idctCopy = stackalloc float[Size];
dctData.AsSpan().CopyTo(idctCopy);

DCT.IDCT2D(idctCopy);
}

[Benchmark]
public void DCT2DAndIDCT2D()
{
Span<float> 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;
}
}
2 changes: 1 addition & 1 deletion optimizations/JPEG/CompressedImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BitsWithLength, byte>(decodeTableSize, new BitsWithLength.Comparer());
result.DecodeTable = new Dictionary<BitsWithLength, byte>(decodeTableSize);

for(int i = 0; i < decodeTableSize; i++)
{
Expand Down
166 changes: 103 additions & 63 deletions optimizations/JPEG/DCT.cs
Original file line number Diff line number Diff line change
@@ -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<float> data)
{
Span<float> coeffs = stackalloc float[BlockSize];
DCT2D(data, coeffs);
coeffs.CopyTo(data);
}

public static void DCT2D(ReadOnlySpan<float> input, Span<float> coeffs)
{
Span<float> 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<float> data)
{
Span<float> output = stackalloc float[BlockSize];
IDCT2D(data, output);
output.CopyTo(data);
}

public static void IDCT2D(ReadOnlySpan<float> coeffs, Span<float> output)
{
Span<float> 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;
}
}
}
}
Loading