From 0215b91a3cc179369067e34509a3dda8829240e3 Mon Sep 17 00:00:00 2001
From: Nominom <7089998+Nominom@users.noreply.github.com>
Date: Mon, 2 Mar 2026 18:50:08 +0200
Subject: [PATCH 01/17] Bump ImageSharp version
---
BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj b/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj
index 4d7c077..ef8673c 100644
--- a/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj
+++ b/BCnEncoder.NET.ImageSharp/BCnEncoder.NET.ImageSharp.csproj
@@ -17,8 +17,8 @@
https://github.com/Nominom/BCnEncoder.NET
https://github.com/Nominom/BCnEncoder.NET
git
- Update to ImageSharp 3.1.7 and CommunityToolkit.HighPerformance 8.4.0
- 1.1.2
+ Update to ImageSharp 3.1.12
+ 1.1.3
@@ -27,7 +27,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
From 9ed649f2b912f79f9488ba78b0871b238b3485b9 Mon Sep 17 00:00:00 2001
From: Nominom <7089998+Nominom@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:28:22 +0200
Subject: [PATCH 02/17] Add back netstandard 2.0 support
Co-authored-by: Crsky
---
BCnEnc.Net/BCnEncoder.csproj | 10 +-
.../Encoder/Bptc/BptcEncodingHelpers.cs | 12 +-
BCnEnc.Net/Encoder/LeastSquares.cs | 4 +
BCnEnc.Net/Shared/Colors.cs | 2 +-
BCnEnc.Net/Shared/HdrImage.cs | 2 +-
BCnEnc.Net/Shared/LinearClustering.cs | 8 +-
BCnEnc.Net/Shared/NetStdPolyfills.cs | 155 ++++++++++++++++++
BCnEnc.Net/Shared/RawBlocks.cs | 12 +-
8 files changed, 192 insertions(+), 13 deletions(-)
create mode 100644 BCnEnc.Net/Shared/NetStdPolyfills.cs
diff --git a/BCnEnc.Net/BCnEncoder.csproj b/BCnEnc.Net/BCnEncoder.csproj
index 7b60090..84f5a2b 100644
--- a/BCnEnc.Net/BCnEncoder.csproj
+++ b/BCnEnc.Net/BCnEncoder.csproj
@@ -1,7 +1,8 @@
- netstandard2.1
+ netstandard2.1;netstandard2.0
+ 8.0
true
true
snupkg
@@ -49,5 +50,12 @@ Supported formats are:
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs
index 3f19504..df19f58 100644
--- a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs
+++ b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs
@@ -5,6 +5,10 @@
using System.Text;
using BCnEncoder.Shared;
+#if NETSTANDARD2_0
+using MemoryMarshal = BCnEncoder.Shared.MemoryMarshalPolyfills;
+#endif
+
namespace BCnEncoder.Encoder.Bptc
{
internal static class BptcEncodingHelpers
@@ -20,7 +24,7 @@ public static int InterpolateInt(int e0, int e1, int index, int indexPrecision)
var aWeights2 = ColorInterpolationWeights2;
var aWeights3 = ColorInterpolationWeights3;
var aWeights4 = ColorInterpolationWeights4;
-
+
if (indexPrecision == 2)
return (((64 - aWeights2[index]) * e0 + aWeights2[index] * e1 + 32) >> 6);
if (indexPrecision == 3)
@@ -52,7 +56,7 @@ public static int[] Rank2SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock,
int CalculatePartitionError(int partitionIndex)
- {
+ {
var error = 0;
ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[partitionIndex];
Span subset0 = stackalloc int[numDistinctClusters];
@@ -60,7 +64,7 @@ int CalculatePartitionError(int partitionIndex)
var max0Idx = 0;
var max1Idx = 0;
- //Calculate largest cluster index for each subset
+ //Calculate largest cluster index for each subset
for (var i = 0; i < 16; i++)
{
if (partitionTable[i] == 0)
@@ -122,7 +126,7 @@ int CalculatePartitionError(int partitionIndex)
var max1Idx = 0;
var max2Idx = 0;
- //Calculate largest cluster index for each subset
+ //Calculate largest cluster index for each subset
for (var i = 0; i < 16; i++)
{
if (partitionTable[i] == 0)
diff --git a/BCnEnc.Net/Encoder/LeastSquares.cs b/BCnEnc.Net/Encoder/LeastSquares.cs
index 1c9bf68..bafdb61 100644
--- a/BCnEnc.Net/Encoder/LeastSquares.cs
+++ b/BCnEnc.Net/Encoder/LeastSquares.cs
@@ -5,6 +5,10 @@
using BCnEncoder.Encoder.Bptc;
using BCnEncoder.Shared;
+#if NETSTANDARD2_0
+using Math = BCnEncoder.Shared.MathPolyfills;
+#endif
+
namespace BCnEncoder.Encoder
{
///
diff --git a/BCnEnc.Net/Shared/Colors.cs b/BCnEnc.Net/Shared/Colors.cs
index b44645b..488d0f6 100644
--- a/BCnEnc.Net/Shared/Colors.cs
+++ b/BCnEnc.Net/Shared/Colors.cs
@@ -1241,7 +1241,7 @@ public static ColorLab XyzToLab(ColorXyz xyz)
private static float PivotXyz(float n)
{
- var i = MathF.Cbrt(n);
+ var i = MathCbrt.Cbrt(n);
return n > 0.008856f ? i : 7.787f * n + 16 / 116f;
}
}
diff --git a/BCnEnc.Net/Shared/HdrImage.cs b/BCnEnc.Net/Shared/HdrImage.cs
index 0dd33ec..a4143de 100644
--- a/BCnEnc.Net/Shared/HdrImage.cs
+++ b/BCnEnc.Net/Shared/HdrImage.cs
@@ -73,7 +73,7 @@ private static string ReadFromStream(Stream stream)
c = (char)b;
buffer[i++] = c;
} while (c != (char)10);
- return new string(buffer.AsSpan().Slice(0, i)).Trim();
+ return new string(buffer, 0, i).Trim();
}
private static void WriteLineToStream(BinaryWriter br, string s)
diff --git a/BCnEnc.Net/Shared/LinearClustering.cs b/BCnEnc.Net/Shared/LinearClustering.cs
index baf10aa..7e23f5b 100644
--- a/BCnEnc.Net/Shared/LinearClustering.cs
+++ b/BCnEnc.Net/Shared/LinearClustering.cs
@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
+#if NETSTANDARD2_0
+using Array = BCnEncoder.Shared.ArrayPolyfills;
+#endif
+
namespace BCnEncoder.Shared
{
///
@@ -194,7 +198,7 @@ public static int[] ClusterPixels(ReadOnlySpan pixels, int width, i
if (enforceConnectivity) {
clusterIndices = EnforceConnectivity(clusterIndices, width, height, clusters);
}
-
+
return clusterIndices;
}
@@ -427,7 +431,7 @@ private static int[] EnforceConnectivity(int[] oldLabels, int width, int height,
{
ReadOnlySpan neighborX = new[] { -1, 0, 1, 0 };
ReadOnlySpan neighborY = new[] { 0, -1, 0, 1 };
-
+
var sSquared = width * height / clusters;
var clusterX = new List(sSquared);
diff --git a/BCnEnc.Net/Shared/NetStdPolyfills.cs b/BCnEnc.Net/Shared/NetStdPolyfills.cs
new file mode 100644
index 0000000..3936f96
--- /dev/null
+++ b/BCnEnc.Net/Shared/NetStdPolyfills.cs
@@ -0,0 +1,155 @@
+
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using CommunityToolkit.HighPerformance;
+
+
+namespace BCnEncoder.Shared
+{
+#if NETSTANDARD2_0
+ internal static class MemoryPolyfills
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory2D AsMemory2D(this Memory memory, int height, int width)
+ {
+ if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment))
+ {
+ T[] array = segment.Array;
+ ref T value = ref array.DangerousGetReference();
+ return Memory2D.DangerousCreate(array, ref value, height, width, 0);
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlyMemory2D AsMemory2D(this ReadOnlyMemory memory, int height, int width)
+ {
+ if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment))
+ {
+ T[] array = segment.Array;
+ ref T value = ref array.DangerousGetReference();
+ return ReadOnlyMemory2D.DangerousCreate(array, ref value, height, width, 0);
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+
+ internal static class SpanPolyfills
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe ReadOnlySpan2D AsSpan2D(this ReadOnlySpan span, int height, int width)
+ {
+ ref T value = ref span.DangerousGetReference();
+ void* pointer = Unsafe.AsPointer(ref value);
+ return new ReadOnlySpan2D(pointer, height, width, 0);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe Span GetRowSpan(this Span2D span, int row)
+ {
+ ref T value = ref span.DangerousGetReferenceAt(row, 0);
+ void* pointer = Unsafe.AsPointer(ref value);
+ return new Span(pointer, span.Width);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe ReadOnlySpan GetRowSpan(this ReadOnlySpan2D span, int row)
+ {
+ ref T value = ref span.DangerousGetReferenceAt(row, 0);
+ void* pointer = Unsafe.AsPointer(ref value);
+ return new Span(pointer, span.Width);
+ }
+ }
+
+ internal static class BinaryWriterPolyfills
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Write(this BinaryWriter writer, ReadOnlySpan buffer)
+ {
+ writer.BaseStream.Write(buffer);
+ }
+ }
+
+ internal static class BinaryReaderPolyfills
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Read(this BinaryReader reader, Span buffer)
+ {
+ return reader.BaseStream.Read(buffer);
+ }
+ }
+
+ internal static class EncodingPolyfills
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe string GetString(this Encoding encoding, ReadOnlySpan buffer)
+ {
+ ref byte value = ref buffer.DangerousGetReference();
+ byte* pointer = (byte*)Unsafe.AsPointer(ref value);
+ return encoding.GetString(pointer, buffer.Length);
+ }
+ }
+
+ internal static class MemoryMarshalPolyfills
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe Span CreateSpan(ref T reference, int length)
+ {
+ return new Span(Unsafe.AsPointer(ref reference), length);
+ }
+ }
+
+ internal static class ArrayPolyfills
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Fill(T[] array, T value)
+ {
+ array.AsSpan().Fill(value);
+ }
+ }
+
+ internal static class MathPolyfills
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Clamp(float value, float min, float max)
+ {
+ if (min > max)
+ {
+ throw new ArgumentException();
+ }
+
+ if (value < min)
+ {
+ return min;
+ }
+ else if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+ }
+#endif
+
+ internal static class MathCbrt
+ {
+ public static float Cbrt(float f)
+ {
+ #if NETSTANDARD2_0
+ return MathF.Pow(f, 1 / 3.0f);
+ #else
+ return MathF.Cbrt(f);
+ #endif
+ }
+ }
+}
diff --git a/BCnEnc.Net/Shared/RawBlocks.cs b/BCnEnc.Net/Shared/RawBlocks.cs
index c78e5d8..dadb4cc 100644
--- a/BCnEnc.Net/Shared/RawBlocks.cs
+++ b/BCnEnc.Net/Shared/RawBlocks.cs
@@ -2,6 +2,10 @@
using System.Runtime.InteropServices;
using BCnEncoder.Encoder.Bptc;
+#if NETSTANDARD2_0
+using MemoryMarshal = BCnEncoder.Shared.MemoryMarshalPolyfills;
+#endif
+
namespace BCnEncoder.Shared
{
@@ -19,7 +23,7 @@ public RawBlock4X4Rgba32(ColorRgba32 fillColor)
p20 = p21 = p22 = p23 =
p30 = p31 = p32 = p33 = fillColor;
}
-
+
public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16);
public ColorRgba32 this[int x, int y]
@@ -156,7 +160,7 @@ public RawBlock4X4RgbFloat(ColorRgbFloat fillColor)
p20 = p21 = p22 = p23 =
p30 = p31 = p32 = p33 = fillColor;
}
-
+
public Span AsSpan => MemoryMarshal.CreateSpan(ref p00, 16);
public ColorRgbFloat this[int x, int y]
@@ -184,7 +188,7 @@ internal float CalculateError(RawBlock4X4RgbFloat other)
var re = Math.Sign(col1.r) * MathF.Log( 1 + MathF.Abs(col1.r)) - Math.Sign(col2.r) * MathF.Log( 1 + MathF.Abs(col2.r));
var ge = Math.Sign(col1.g) * MathF.Log( 1 + MathF.Abs(col1.g)) - Math.Sign(col2.g) * MathF.Log( 1 + MathF.Abs(col2.g));
var be = Math.Sign(col1.b) * MathF.Log( 1 + MathF.Abs(col1.b)) - Math.Sign(col2.b) * MathF.Log( 1 + MathF.Abs(col2.b));
-
+
error += re * re;
error += ge * ge;
error += be * be;
@@ -222,7 +226,7 @@ internal float CalculateYCbCrError(RawBlock4X4RgbFloat other)
return error;
}
-
+
internal RawBlock4X4Ycbcr ToRawBlockYcbcr()
{
From 0eee44ebcb672603c2f74632577e46dfa8102b5d Mon Sep 17 00:00:00 2001
From: Nominom <7089998+Nominom@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:29:10 +0200
Subject: [PATCH 03/17] Add dotnet framework tests
---
BCnEnc.Net/AssemblyInfo.cs | 1 +
.../Encoder/Bptc/BptcEncodingHelpers.cs | 31 +-
BCnEnc.Net/Shared/MipMapper.cs | 20 +-
BCnEncNet.sln | 8 +
BCnEncTests.Framework/AtcTests.cs | 90 ++++
BCnEncTests.Framework/BC1Tests.cs | 164 ++++++++
.../BCnEncTests.Framework.csproj | 34 ++
BCnEncTests.Framework/Bc5Tests.cs | 83 ++++
BCnEncTests.Framework/Bc6EncoderTests.cs | 398 ++++++++++++++++++
BCnEncTests.Framework/Bc6HDecoderTests.cs | 147 +++++++
BCnEncTests.Framework/Bc7BlockTests.cs | 348 +++++++++++++++
BCnEncTests.Framework/BgraTests.cs | 63 +++
BCnEncTests.Framework/CancellationTests.cs | 21 +
BCnEncTests.Framework/ClusterTests.cs | 52 +++
BCnEncTests.Framework/DdsReadTests.cs | 51 +++
BCnEncTests.Framework/DdsWritingTests.cs | 57 +++
BCnEncTests.Framework/DecodingAsyncTests.cs | 62 +++
BCnEncTests.Framework/DecodingTests.cs | 62 +++
BCnEncTests.Framework/EncoderOptionsTests.cs | 66 +++
BCnEncTests.Framework/EncodingAsyncTest.cs | 88 ++++
BCnEncTests.Framework/EncodingTest.cs | 315 ++++++++++++++
BCnEncTests.Framework/HdrImageTests.cs | 26 ++
BCnEncTests.Framework/ProgressTests.cs | 390 +++++++++++++++++
BCnEncTests.Framework/RawTests.cs | 153 +++++++
BCnEncTests.Framework/SingleBlockTests.cs | 143 +++++++
BCnEncTests.Framework/Support/HdrLoader.cs | 15 +
BCnEncTests.Framework/Support/ImageLoader.cs | 98 +++++
BCnEncTests.Framework/Support/TestHelper.cs | 258 ++++++++++++
.../BCnEncTests.Shared.projitems | 15 +
BCnEncTests.Shared/BCnEncTests.Shared.shproj | 10 +
.../BlockTests.cs | 34 +-
.../ColorTest.cs | 0
.../IntHelperTests.cs | 0
.../MathHelperTests.cs | 0
.../PcaTests.cs | 0
BCnEncTests/BCnEncTests.csproj | 2 +
36 files changed, 3269 insertions(+), 36 deletions(-)
create mode 100644 BCnEncTests.Framework/AtcTests.cs
create mode 100644 BCnEncTests.Framework/BC1Tests.cs
create mode 100644 BCnEncTests.Framework/BCnEncTests.Framework.csproj
create mode 100644 BCnEncTests.Framework/Bc5Tests.cs
create mode 100644 BCnEncTests.Framework/Bc6EncoderTests.cs
create mode 100644 BCnEncTests.Framework/Bc6HDecoderTests.cs
create mode 100644 BCnEncTests.Framework/Bc7BlockTests.cs
create mode 100644 BCnEncTests.Framework/BgraTests.cs
create mode 100644 BCnEncTests.Framework/CancellationTests.cs
create mode 100644 BCnEncTests.Framework/ClusterTests.cs
create mode 100644 BCnEncTests.Framework/DdsReadTests.cs
create mode 100644 BCnEncTests.Framework/DdsWritingTests.cs
create mode 100644 BCnEncTests.Framework/DecodingAsyncTests.cs
create mode 100644 BCnEncTests.Framework/DecodingTests.cs
create mode 100644 BCnEncTests.Framework/EncoderOptionsTests.cs
create mode 100644 BCnEncTests.Framework/EncodingAsyncTest.cs
create mode 100644 BCnEncTests.Framework/EncodingTest.cs
create mode 100644 BCnEncTests.Framework/HdrImageTests.cs
create mode 100644 BCnEncTests.Framework/ProgressTests.cs
create mode 100644 BCnEncTests.Framework/RawTests.cs
create mode 100644 BCnEncTests.Framework/SingleBlockTests.cs
create mode 100644 BCnEncTests.Framework/Support/HdrLoader.cs
create mode 100644 BCnEncTests.Framework/Support/ImageLoader.cs
create mode 100644 BCnEncTests.Framework/Support/TestHelper.cs
create mode 100644 BCnEncTests.Shared/BCnEncTests.Shared.projitems
create mode 100644 BCnEncTests.Shared/BCnEncTests.Shared.shproj
rename {BCnEncTests => BCnEncTests.Shared}/BlockTests.cs (79%)
rename {BCnEncTests => BCnEncTests.Shared}/ColorTest.cs (100%)
rename {BCnEncTests => BCnEncTests.Shared}/IntHelperTests.cs (100%)
rename {BCnEncTests => BCnEncTests.Shared}/MathHelperTests.cs (100%)
rename {BCnEncTests => BCnEncTests.Shared}/PcaTests.cs (100%)
diff --git a/BCnEnc.Net/AssemblyInfo.cs b/BCnEnc.Net/AssemblyInfo.cs
index 6d23fdb..0bce899 100644
--- a/BCnEnc.Net/AssemblyInfo.cs
+++ b/BCnEnc.Net/AssemblyInfo.cs
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("BCnEncTests")]
+[assembly: InternalsVisibleTo("BCnEncTests.Framework")]
diff --git a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs
index df19f58..7c9c911 100644
--- a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs
+++ b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs
@@ -54,6 +54,11 @@ public static int[] Rank2SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock,
{
var output = Enumerable.Range(0, smallIndex ? 32 : 64).ToArray();
+ // Copy struct to array before the closure so that reducedIndicesBlock is not
+ // heap-captured. On .NET Framework, MemoryMarshalPolyfills.CreateSpan uses
+ // Unsafe.AsPointer, which is only GC-safe for stack-allocated structs.
+ var indices = new int[16];
+ for (var k = 0; k < 16; k++) indices[k] = reducedIndicesBlock[k];
int CalculatePartitionError(int partitionIndex)
{
@@ -69,7 +74,7 @@ int CalculatePartitionError(int partitionIndex)
{
if (partitionTable[i] == 0)
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset0[r]++;
var count = subset0[r];
if (count > subset0[max0Idx])
@@ -79,7 +84,7 @@ int CalculatePartitionError(int partitionIndex)
}
else
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset1[r]++;
var count = subset1[r];
if (count > subset1[max1Idx])
@@ -94,11 +99,11 @@ int CalculatePartitionError(int partitionIndex)
{
if (partitionTable[i] == 0)
{
- if (reducedIndicesBlock[i] != max0Idx) error++;
+ if (indices[i] != max0Idx) error++;
}
else
{
- if (reducedIndicesBlock[i] != max1Idx) error++;
+ if (indices[i] != max1Idx) error++;
}
}
@@ -114,6 +119,12 @@ public static int[] Rank3SubsetPartitions(ClusterIndices4X4 reducedIndicesBlock,
{
var output = Enumerable.Range(0, 64).ToArray();
+ // Copy struct to array before the closure so that reducedIndicesBlock is not
+ // heap-captured. On .NET Framework, MemoryMarshalPolyfills.CreateSpan uses
+ // Unsafe.AsPointer, which is only GC-safe for stack-allocated structs.
+ var indices = new int[16];
+ for (var k = 0; k < 16; k++) indices[k] = reducedIndicesBlock[k];
+
int CalculatePartitionError(int partitionIndex)
{
var error = 0;
@@ -131,7 +142,7 @@ int CalculatePartitionError(int partitionIndex)
{
if (partitionTable[i] == 0)
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset0[r]++;
var count = subset0[r];
if (count > subset0[max0Idx])
@@ -141,7 +152,7 @@ int CalculatePartitionError(int partitionIndex)
}
else if (partitionTable[i] == 1)
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset1[r]++;
var count = subset1[r];
if (count > subset1[max1Idx])
@@ -151,7 +162,7 @@ int CalculatePartitionError(int partitionIndex)
}
else
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset2[r]++;
var count = subset2[r];
if (count > subset2[max2Idx])
@@ -166,15 +177,15 @@ int CalculatePartitionError(int partitionIndex)
{
if (partitionTable[i] == 0)
{
- if (reducedIndicesBlock[i] != max0Idx) error++;
+ if (indices[i] != max0Idx) error++;
}
else if (partitionTable[i] == 1)
{
- if (reducedIndicesBlock[i] != max1Idx) error++;
+ if (indices[i] != max1Idx) error++;
}
else
{
- if (reducedIndicesBlock[i] != max2Idx) error++;
+ if (indices[i] != max2Idx) error++;
}
}
diff --git a/BCnEnc.Net/Shared/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs
index e44dbf0..746ac29 100644
--- a/BCnEnc.Net/Shared/MipMapper.cs
+++ b/BCnEnc.Net/Shared/MipMapper.cs
@@ -164,15 +164,15 @@ public static void CalculateMipLevelSize(int width, int height, int mipIdx, out
mipHeight = Math.Max(1, height >> mipIdx);
}
- private static ColorRgba32[,] ResizeToHalf(ReadOnlySpan2D pixelsRgba)
+ private static Memory2D ResizeToHalf(ReadOnlySpan2D pixelsRgba)
{
-
var oldWidth = pixelsRgba.Width;
var oldHeight = pixelsRgba.Height;
var newWidth = Math.Max(1, oldWidth >> 1);
var newHeight = Math.Max(1, oldHeight >> 1);
- var result = new ColorRgba32[newHeight, newWidth];
+ // Use a 1D backing array so TryGetMemory() succeeds on all target frameworks
+ var result = new ColorRgba32[newHeight * newWidth];
int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x));
int ClampH(int y) => Math.Max(0, Math.Min(oldHeight - 1, y));
@@ -186,22 +186,22 @@ public static void CalculateMipLevelSize(int width, int height, int mipIdx, out
var ll = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2)].ToFloat();
var lr = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2 + 1)].ToFloat();
- result[y2, x2] = ((ul + ur + ll + lr) / 4).ToRgba32();
+ result[y2 * newWidth + x2] = ((ul + ur + ll + lr) / 4).ToRgba32();
}
}
- return result;
+ return ((Memory)result).AsMemory2D(newHeight, newWidth);
}
- private static ColorRgbFloat[,] ResizeToHalf(ReadOnlySpan2D pixelsRgba)
+ private static Memory2D ResizeToHalf(ReadOnlySpan2D pixelsRgba)
{
-
var oldWidth = pixelsRgba.Width;
var oldHeight = pixelsRgba.Height;
var newWidth = Math.Max(1, oldWidth >> 1);
var newHeight = Math.Max(1, oldHeight >> 1);
- var result = new ColorRgbFloat[newHeight, newWidth];
+ // Use a 1D backing array so TryGetMemory() succeeds on all target frameworks
+ var result = new ColorRgbFloat[newHeight * newWidth];
int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x));
int ClampH(int y) => Math.Max(0, Math.Min(oldHeight - 1, y));
@@ -215,11 +215,11 @@ public static void CalculateMipLevelSize(int width, int height, int mipIdx, out
var ll = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2)];
var lr = pixelsRgba[ClampH(y2 * 2 + 1), ClampW(x2 * 2 + 1)];
- result[y2, x2] = ((ul + ur + ll + lr) / 4);
+ result[y2 * newWidth + x2] = ((ul + ur + ll + lr) / 4);
}
}
- return result;
+ return ((Memory)result).AsMemory2D(newHeight, newWidth);
}
}
}
diff --git a/BCnEncNet.sln b/BCnEncNet.sln
index 6e3c4d8..dca5cf7 100644
--- a/BCnEncNet.sln
+++ b/BCnEncNet.sln
@@ -14,6 +14,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCnEncoder.NET.ImageSharp", "BCnEncoder.NET.ImageSharp\BCnEncoder.NET.ImageSharp.csproj", "{7D884C56-B982-4B8C-907E-68F231CF3D89}"
EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "BCnEncTests.Shared", "BCnEncTests.Shared\BCnEncTests.Shared.shproj", "{D954291E-2A0B-460D-934E-DC6B0785DB48}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCnEncTests.Framework", "BCnEncTests.Framework\BCnEncTests.Framework.csproj", "{CA12ED85-B4E8-4D62-9B03-0D02F742B00C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -32,6 +36,10 @@ Global
{7D884C56-B982-4B8C-907E-68F231CF3D89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D884C56-B982-4B8C-907E-68F231CF3D89}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7D884C56-B982-4B8C-907E-68F231CF3D89}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA12ED85-B4E8-4D62-9B03-0D02F742B00C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA12ED85-B4E8-4D62-9B03-0D02F742B00C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA12ED85-B4E8-4D62-9B03-0D02F742B00C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA12ED85-B4E8-4D62-9B03-0D02F742B00C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/BCnEncTests.Framework/AtcTests.cs b/BCnEncTests.Framework/AtcTests.cs
new file mode 100644
index 0000000..eff342c
--- /dev/null
+++ b/BCnEncTests.Framework/AtcTests.cs
@@ -0,0 +1,90 @@
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class AtcTests
+ {
+ [Fact]
+ public void AtcKtxDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.Atc);
+ var original = ImageLoader.TestLenna;
+
+ var ktx = encoder.EncodeToKtx(original);
+ var image = decoder.Decode2D(ktx);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void AtcDdsDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.Atc);
+ var original = ImageLoader.TestLenna;
+
+ var dds = encoder.EncodeToDds(original);
+ var image = decoder.Decode2D(dds);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void AtcExplicitKtxDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha);
+ var original = ImageLoader.TestAlphaGradient1;
+
+ var ktx = encoder.EncodeToKtx(original);
+ var image = decoder.Decode2D(ktx);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void AtcExplicitDdsDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha);
+ var original = ImageLoader.TestAlphaGradient1;
+
+ var dds = encoder.EncodeToDds(original);
+ var image = decoder.Decode2D(dds);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void AtcInterpolatedKtxDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha);
+ var original = ImageLoader.TestAlphaGradient1;
+
+ var ktx = encoder.EncodeToKtx(original);
+ var image = decoder.Decode2D(ktx);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void AtcInterpolatedDdsDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha);
+ var original = ImageLoader.TestAlphaGradient1;
+
+ var dds = encoder.EncodeToDds(original);
+ var image = decoder.Decode2D(dds);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/BC1Tests.cs b/BCnEncTests.Framework/BC1Tests.cs
new file mode 100644
index 0000000..314c284
--- /dev/null
+++ b/BCnEncTests.Framework/BC1Tests.cs
@@ -0,0 +1,164 @@
+using BCnEncoder.Shared;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class Bc1Tests
+ {
+ [Fact]
+ public void Decode()
+ {
+ var block = new Bc1Block
+ {
+ color0 = new ColorRgb565(255, 255, 255),
+ color1 = new ColorRgb565(0, 0, 0)
+ };
+
+ Assert.False(block.HasAlphaOrBlack);
+ block[0] = 1;
+ block[1] = 1;
+ block[2] = 1;
+ block[3] = 1;
+
+ block[4] = 3;
+ block[5] = 3;
+ block[6] = 3;
+ block[7] = 3;
+
+ block[8] = 2;
+ block[9] = 2;
+ block[10] = 2;
+ block[11] = 2;
+
+ block[12] = 0;
+ block[13] = 0;
+ block[14] = 0;
+ block[15] = 0;
+
+ var raw = block.Decode(false);
+ Assert.Equal(new ColorRgba32(0, 0, 0), raw.p00);
+ Assert.Equal(new ColorRgba32(0, 0, 0), raw.p10);
+ Assert.Equal(new ColorRgba32(0, 0, 0), raw.p20);
+ Assert.Equal(new ColorRgba32(0, 0, 0), raw.p30);
+
+ Assert.Equal(new ColorRgba32(85, 85, 85), raw.p01);
+ Assert.Equal(new ColorRgba32(85, 85, 85), raw.p11);
+ Assert.Equal(new ColorRgba32(85, 85, 85), raw.p21);
+ Assert.Equal(new ColorRgba32(85, 85, 85), raw.p31);
+
+ Assert.Equal(new ColorRgba32(170, 170, 170), raw.p02);
+ Assert.Equal(new ColorRgba32(170, 170, 170), raw.p12);
+ Assert.Equal(new ColorRgba32(170, 170, 170), raw.p22);
+ Assert.Equal(new ColorRgba32(170, 170, 170), raw.p32);
+
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33);
+ }
+
+ [Fact]
+ public void DecodeBlack()
+ {
+ var block = new Bc1Block
+ {
+ color0 = new ColorRgb565(200, 200, 200),
+ color1 = new ColorRgb565(255, 255, 255)
+ };
+
+ Assert.True(block.HasAlphaOrBlack);
+ block[0] = 0;
+ block[1] = 0;
+ block[2] = 0;
+ block[3] = 0;
+
+ block[4] = 3;
+ block[5] = 3;
+ block[6] = 3;
+ block[7] = 3;
+
+ block[8] = 2;
+ block[9] = 2;
+ block[10] = 2;
+ block[11] = 2;
+
+ block[12] = 1;
+ block[13] = 1;
+ block[14] = 1;
+ block[15] = 1;
+
+ var raw = block.Decode(false);
+ Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00);
+ Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10);
+ Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20);
+ Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30);
+
+ Assert.Equal(new ColorRgba32(0, 0, 0), raw.p01);
+ Assert.Equal(new ColorRgba32(0, 0, 0), raw.p11);
+ Assert.Equal(new ColorRgba32(0, 0, 0), raw.p21);
+ Assert.Equal(new ColorRgba32(0, 0, 0), raw.p31);
+
+ Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02);
+ Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12);
+ Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22);
+ Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32);
+
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33);
+ }
+
+ [Fact]
+ public void DecodeAlpha()
+ {
+ var block = new Bc1Block
+ {
+ color0 = new ColorRgb565(200, 200, 200),
+ color1 = new ColorRgb565(255, 255, 255)
+ };
+
+ Assert.True(block.HasAlphaOrBlack);
+ block[0] = 0;
+ block[1] = 0;
+ block[2] = 0;
+ block[3] = 0;
+
+ block[4] = 3;
+ block[5] = 3;
+ block[6] = 3;
+ block[7] = 3;
+
+ block[8] = 2;
+ block[9] = 2;
+ block[10] = 2;
+ block[11] = 2;
+
+ block[12] = 1;
+ block[13] = 1;
+ block[14] = 1;
+ block[15] = 1;
+
+ var raw = block.Decode(true);
+ Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00);
+ Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10);
+ Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20);
+ Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30);
+
+ Assert.Equal(new ColorRgba32(0, 0, 0, 0), raw.p01);
+ Assert.Equal(new ColorRgba32(0, 0, 0, 0), raw.p11);
+ Assert.Equal(new ColorRgba32(0, 0, 0, 0), raw.p21);
+ Assert.Equal(new ColorRgba32(0, 0, 0, 0), raw.p31);
+
+ Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02);
+ Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12);
+ Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22);
+ Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32);
+
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23);
+ Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/BCnEncTests.Framework.csproj b/BCnEncTests.Framework/BCnEncTests.Framework.csproj
new file mode 100644
index 0000000..e14d3cc
--- /dev/null
+++ b/BCnEncTests.Framework/BCnEncTests.Framework.csproj
@@ -0,0 +1,34 @@
+
+
+
+ net481
+ 8.0
+ false
+ true
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BCnEncTests.Framework/Bc5Tests.cs b/BCnEncTests.Framework/Bc5Tests.cs
new file mode 100644
index 0000000..4e361ef
--- /dev/null
+++ b/BCnEncTests.Framework/Bc5Tests.cs
@@ -0,0 +1,83 @@
+using BCnEncoder.Decoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class Bc5Tests
+ {
+ [Fact]
+ public void Bc5Indices()
+ {
+ var block = new Bc5Block();
+
+ for (var i = 0; i < 16; i++)
+ {
+ block.SetRedIndex(i, (byte)(i % 8));
+ block.SetGreenIndex(i, (byte)((i + 3) % 8));
+ }
+
+ for (var i = 0; i < 16; i++)
+ {
+ int rI = block.GetRedIndex(i);
+ int gI = block.GetGreenIndex(i);
+
+ Assert.Equal((byte)(i % 8), rI);
+ Assert.Equal((byte)((i + 3) % 8), gI);
+ }
+ }
+
+ [Fact]
+ public void Bc5DdsDecode()
+ {
+ var reference = ImageLoader.TestDecodingBc5Reference;
+ var decoded = new BcDecoder().Decode2D(DdsLoader.TestDecompressBc5);
+
+ var refSpan = TestHelper.GetSinglePixelArrayAsColors(reference);
+ var decSpan = TestHelper.GetSinglePixelArrayAsColors(decoded);
+
+ Assert.Equal(reference.Width, decoded.Width);
+ Assert.Equal(reference.Height, decoded.Height);
+
+ // Exactly equal
+ for (var i = 0; i < reference.Width * reference.Height; i++)
+ Assert.Equal(refSpan[i], decSpan[i]);
+ }
+
+ [Fact]
+ public void Bc5BlockDecode()
+ {
+ var block = new Bc5Block()
+ {
+ greenBlock = new Bc4ComponentBlock() { componentBlock = 0x91824260008935ee },
+ redBlock = new Bc4ComponentBlock() { componentBlock = 0x6d900f66d3c0a70d }
+ };
+
+ var referenceBlock = new RawBlock4X4Rgba32
+ {
+ p00 = new ColorRgba32(13, 53, 0, 255),
+ p01 = new ColorRgba32(136, 238, 0, 255),
+ p02 = new ColorRgba32(255, 212, 0, 255),
+ p03 = new ColorRgba32(167, 238, 0, 255),
+ p10 = new ColorRgba32(13, 53, 0, 255),
+ p11 = new ColorRgba32(136, 238, 0, 255),
+ p12 = new ColorRgba32(167, 238, 0, 255),
+ p13 = new ColorRgba32(75, 185, 0, 255),
+ p20 = new ColorRgba32(255, 212, 0, 255),
+ p21 = new ColorRgba32(167, 238, 0, 255),
+ p22 = new ColorRgba32(13, 53, 0, 255),
+ p23 = new ColorRgba32(75, 159, 0, 255),
+ p30 = new ColorRgba32(167, 238, 0, 255),
+ p31 = new ColorRgba32(75, 185, 0, 255),
+ p32 = new ColorRgba32(13, 53, 0, 255),
+ p33 = new ColorRgba32(75, 159, 0, 255)
+ };
+
+ var decodedBlock = block.Decode();
+
+ for (var i = 0; i < 16; i++)
+ Assert.Equal(referenceBlock[i], decodedBlock[i]);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/Bc6EncoderTests.cs b/BCnEncTests.Framework/Bc6EncoderTests.cs
new file mode 100644
index 0000000..5fb1f5d
--- /dev/null
+++ b/BCnEncTests.Framework/Bc6EncoderTests.cs
@@ -0,0 +1,398 @@
+using System;
+using System.IO;
+using BCnEncoder.Encoder;
+using BCnEncoder.Encoder.Bptc;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace BCnEncTests
+{
+ public class Bc6EncoderTests
+ {
+ private ITestOutputHelper output;
+
+ public Bc6EncoderTests(ITestOutputHelper output) => this.output = output;
+
+ [Theory]
+ [InlineData(0x7F, 8, false)]
+ [InlineData(0xFF, 8, false)]
+ [InlineData(0x7F, 8, true)]
+ [InlineData(0xFF, 8, true)]
+ [InlineData(0b111_1100_1101, 11, true)]
+ [InlineData(0b111_1100_1101, 11, false)]
+ [InlineData(0b011_1100_1101, 11, true)]
+ [InlineData(0b011_1100_1101, 11, false)]
+ [InlineData(0b0_1001, 5, true)]
+ [InlineData(0b0_1001, 5, false)]
+ [InlineData(0b1_1100, 5, true)]
+ [InlineData(0b1_1100, 5, false)]
+ public void Quantize(int initialQuantizedValue, int endpointBits, bool signed)
+ {
+ if (signed)
+ {
+ initialQuantizedValue = IntHelper.SignExtend(initialQuantizedValue, endpointBits);
+ }
+ var unquantized = Bc6Block.UnQuantize(initialQuantizedValue, endpointBits, signed);
+ var finishedUnquantized = Bc6Block.FinishUnQuantize(unquantized, signed);
+
+ var prequantized = Bc6EncodingHelpers.PreQuantize(finishedUnquantized, signed);
+ var quantized = Bc6EncodingHelpers.Quantize(prequantized, endpointBits, signed);
+
+ Assert.InRange(prequantized, unquantized - 2, unquantized + 2);
+ Assert.InRange(quantized, initialQuantizedValue - 1, initialQuantizedValue + 1);
+ }
+
+ [Fact]
+ public void PreQuantizeRawEndpoint()
+ {
+ var ep0 = new ColorRgbFloat(1, 0.001f, 0.2f);
+
+ var preQuantized = Bc6EncodingHelpers.PreQuantizeRawEndpoint(ep0, false);
+ var unQu = Bc6Block.FinishUnQuantize(preQuantized, false);
+
+ Assert.InRange(unQu.Item1, ep0.r - 0.001f, ep0.r + 0.001f);
+ Assert.InRange(unQu.Item2, ep0.g - 0.001f, ep0.g + 0.001f);
+ Assert.InRange(unQu.Item3, ep0.b - 0.001f, ep0.b + 0.001f);
+ }
+
+ [Fact]
+ public void RgbBoundingBox()
+ {
+ var testBlock = new RawBlock4X4RgbFloat()
+ {
+ p00 = new ColorRgbFloat(0.01f, -0.05f, 0.02f),
+ p01 = new ColorRgbFloat(0.02f, 0.02f, 0.03f),
+ p02 = new ColorRgbFloat(0.02f, 0.02f, 0.02f),
+ p03 = new ColorRgbFloat(0.01f, 0.02f, 0.02f),
+ p10 = new ColorRgbFloat(0.02f, 0.02f, 0.02f),
+ p11 = new ColorRgbFloat(0.045f, 0.02f, 0.02f),
+ p12 = new ColorRgbFloat(0.04f, 0.02f, 0.02f),
+ p13 = new ColorRgbFloat(0.04f, 0.02f, 0.02f),
+ p20 = new ColorRgbFloat(0.21f, 0.02f, 0.02f),
+ p21 = new ColorRgbFloat(0.01f, 0.02f, 0.02f),
+ p22 = new ColorRgbFloat(0.01f, 0.32f, 0.02f),
+ p23 = new ColorRgbFloat(1.01f, 0.22f, 0.02f),
+ p30 = new ColorRgbFloat(0.01f, 0.12f, 0.02f),
+ p31 = new ColorRgbFloat(0.01f, 0.02f, 0.02f),
+ p32 = new ColorRgbFloat(0.01f, 0.62f, 0.7f),
+ p33 = new ColorRgbFloat(0.01f, 0.02f, 0.02f)
+ };
+
+ BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max);
+
+ Assert.InRange(min.r, 0.01, 0.02);
+ Assert.InRange(min.g, -0.05, -0.03);
+ Assert.InRange(min.b, 0.02, 0.03);
+
+ Assert.InRange(max.r, 0.9, 1.01);
+ Assert.InRange(max.g, 0.59, 0.62);
+ Assert.InRange(max.b, 0.60, 0.7);
+ }
+
+ [Fact]
+ public void PackMode3()
+ {
+ const int endpointPrecision = 10;
+ var random = new Random();
+
+ var ep0 = (random.Next() & (1 << endpointPrecision) - 1,
+ random.Next() & (1 << endpointPrecision) - 1,
+ random.Next() & (1 << endpointPrecision) - 1);
+ var ep1 = (random.Next() & (1 << endpointPrecision) - 1,
+ random.Next() & (1 << endpointPrecision) - 1,
+ random.Next() & (1 << endpointPrecision) - 1);
+
+ var indices = new byte[16];
+ for (var i = 1; i < indices.Length; i++)
+ {
+ indices[i] = (byte)random.Next(1 << 4);
+ }
+
+ var block = Bc6Block.PackType3(ep0, ep1, indices);
+
+ Assert.Equal(Bc6BlockType.Type3, block.Type);
+
+ var extracted0 = block.ExtractEp0();
+ var extracted1 = block.ExtractEp1();
+
+ Assert.Equal(ep0, extracted0);
+ Assert.Equal(ep1, extracted1);
+
+ for (var i = 0; i < indices.Length; i++)
+ {
+ var idx = block.GetColorIndex(1, 0, 4, i);
+ Assert.Equal(indices[i], idx);
+ }
+ }
+
+ [Theory]
+ [InlineData(Bc6BlockType.Type0)]
+ [InlineData(Bc6BlockType.Type1)]
+ [InlineData(Bc6BlockType.Type2)]
+ [InlineData(Bc6BlockType.Type6)]
+ [InlineData(Bc6BlockType.Type10)]
+ [InlineData(Bc6BlockType.Type14)]
+ [InlineData(Bc6BlockType.Type18)]
+ [InlineData(Bc6BlockType.Type22)]
+ [InlineData(Bc6BlockType.Type26)]
+ [InlineData(Bc6BlockType.Type30)]
+ [InlineData(Bc6BlockType.Type3)]
+ [InlineData(Bc6BlockType.Type7)]
+ [InlineData(Bc6BlockType.Type11)]
+ //[InlineData(Bc6BlockType.Type15)] //deltabits too small to encode the testblock
+ internal void EncodeAllModesUnsigned(Bc6BlockType type)
+ {
+ var testBlock = new RawBlock4X4RgbFloat()
+ {
+ p00 = new ColorRgbFloat(1.011f, 10.01f, 2.01f),
+ p01 = new ColorRgbFloat(1.01f, 10.014f, 2.012f),
+ p02 = new ColorRgbFloat(1.005f, 10.012f, 2.02f),
+ p03 = new ColorRgbFloat(1.01f, 10.013f, 2.023f),
+ p10 = new ColorRgbFloat(1.011f, 10.01f, 2.01f),
+ p11 = new ColorRgbFloat(1.01f, 10.014f, 2.012f),
+ p12 = new ColorRgbFloat(1.005f, 10.012f, 2.02f),
+ p13 = new ColorRgbFloat(1.01f, 10.013f, 2.023f),
+ p20 = new ColorRgbFloat(1.011f, 10.01f, 2.01f),
+ p21 = new ColorRgbFloat(1.01f, 10.014f, 2.012f),
+ p22 = new ColorRgbFloat(1.005f, 10.012f, 2.02f),
+ p23 = new ColorRgbFloat(1.01f, 10.013f, 2.023f),
+ p30 = new ColorRgbFloat(1.011f, 10.01f, 2.01f),
+ p31 = new ColorRgbFloat(1.01f, 10.014f, 2.012f),
+ p32 = new ColorRgbFloat(1.005f, 10.012f, 2.02f),
+ p33 = new ColorRgbFloat(1.01f, 10.013f, 2.023f)
+ };
+ Bc6Block encoded;
+ var badTransform = false;
+
+ if (type.HasSubsets())
+ {
+ var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2);
+ var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true);
+
+ var bestPartition = best2SubsetPartitions[0];
+
+ Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0);
+ Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1);
+
+ encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition,
+ false, out badTransform);
+ }
+ else
+ {
+ BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max);
+ encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, false,
+ out badTransform);
+ }
+
+ Assert.False(badTransform);
+ Assert.Equal(type, encoded.Type);
+ var decoded = encoded.Decode(false);
+ var error = testBlock.CalculateError(decoded);
+ Assert.InRange(error, 0, 0.3);
+ }
+
+ [Theory]
+ [InlineData(Bc6BlockType.Type0)]
+ [InlineData(Bc6BlockType.Type1)]
+ [InlineData(Bc6BlockType.Type2)]
+ [InlineData(Bc6BlockType.Type6)]
+ [InlineData(Bc6BlockType.Type10)]
+ [InlineData(Bc6BlockType.Type14)]
+ [InlineData(Bc6BlockType.Type18)]
+ [InlineData(Bc6BlockType.Type22)]
+ [InlineData(Bc6BlockType.Type26)]
+ [InlineData(Bc6BlockType.Type30)]
+ [InlineData(Bc6BlockType.Type3)]
+ [InlineData(Bc6BlockType.Type7)]
+ [InlineData(Bc6BlockType.Type11)]
+ [InlineData(Bc6BlockType.Type15)]
+ internal void EncodeAllModesSigned(Bc6BlockType type)
+ {
+ var testBlock = new RawBlock4X4RgbFloat()
+ {
+ p00 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f),
+ p01 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f),
+ p02 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f),
+ p03 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f),
+ p10 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f),
+ p11 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f),
+ p12 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f),
+ p13 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f),
+ p20 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f),
+ p21 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f),
+ p22 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f),
+ p23 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f),
+ p30 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f),
+ p31 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f),
+ p32 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f),
+ p33 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f)
+ };
+ Bc6Block encoded;
+ var badTransform = false;
+
+ if (type.HasSubsets())
+ {
+ var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2);
+ var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true);
+
+ var bestPartition = best2SubsetPartitions[0];
+
+ Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0);
+ Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1);
+
+ encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition,
+ true, out badTransform);
+ }
+ else
+ {
+ BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max);
+ encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, true,
+ out badTransform);
+ }
+
+ Assert.False(badTransform);
+ Assert.Equal(type, encoded.Type);
+ var decoded = encoded.Decode(true);
+ var error = testBlock.CalculateError(decoded);
+ Assert.InRange(error, 0, 0.5);
+ }
+
+ [Fact]
+ public void Encode()
+ {
+ var signed = true;
+ var image = HdrLoader.TestHdrKiara;
+ var blocks = ImageToBlocks.ImageTo4X4(image.pixels.AsMemory().AsMemory2D(image.height, image.width), out var bW, out var bH);
+
+ for (var i = 0; i < blocks.Length; i++)
+ {
+ var encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed);
+ var decoded = encoded.Decode(signed);
+
+ var error = decoded.CalculateError(blocks[i]);
+ if (error > 0.06 || i == 14749)
+ {
+ encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed);
+ }
+ }
+ }
+
+ [Fact]
+ public void EncodeFast()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Fast,
+ "encoding_bc6_kiara_fast.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeBalanced()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Balanced,
+ "encoding_bc6_kiara_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeBestQuality()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.BestQuality,
+ "encoding_bc6_kiara_bestquality.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeProbeFast()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Fast,
+ "encoding_bc6_probe_fast.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeProbeBalanced()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Balanced,
+ "encoding_bc6_probe_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeProbeBestQuality()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.BestQuality,
+ "encoding_bc6_probe_bestquality.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeProbeSignedFast()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Fast,
+ "encoding_bc6_probe_signed_fast.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeProbeSignedBalanced()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Balanced,
+ "encoding_bc6_probe_signed_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeProbeSignedBestQuality()
+ {
+ TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.BestQuality,
+ "encoding_bc6_probe_signed_bestquality.ktx", output);
+ }
+
+ [Fact]
+ public void EncodeToKtx()
+ {
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ encoder.OutputOptions.GenerateMipMaps = true;
+ encoder.OutputOptions.Format = CompressionFormat.Bc6U;
+
+ var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory);
+
+ using var fs = File.OpenWrite("encoding_bc6_ktx.ktx");
+ ktx.Write(fs);
+ }
+
+ [Fact]
+ public void EncodeToDds()
+ {
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ encoder.OutputOptions.GenerateMipMaps = true;
+ encoder.OutputOptions.Format = CompressionFormat.Bc6U;
+
+ var dds = encoder.EncodeToDdsHdr(HdrLoader.TestHdrKiara.PixelMemory);
+
+ using var fs = File.OpenWrite("encoding_bc6_dds.dds");
+ dds.Write(fs);
+ }
+
+ [Fact]
+ public void EncodeToRaw()
+ {
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ encoder.OutputOptions.GenerateMipMaps = true;
+ encoder.OutputOptions.Format = CompressionFormat.Bc6U;
+
+ var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory);
+
+ var allMips = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory);
+
+ Assert.True(allMips.Length > 1);
+ Assert.True(allMips.Length == ktx.MipMaps.Count);
+
+ for (var i = 0; i < allMips.Length; i++)
+ {
+ var single = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory, i, out var mW, out var mH);
+
+ Assert.Equal(ktx.MipMaps[i].Faces[0].Data, single);
+ Assert.Equal(allMips[i], single);
+ }
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/Bc6HDecoderTests.cs b/BCnEncTests.Framework/Bc6HDecoderTests.cs
new file mode 100644
index 0000000..0453379
--- /dev/null
+++ b/BCnEncTests.Framework/Bc6HDecoderTests.cs
@@ -0,0 +1,147 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+using Xunit.Abstractions;
+using Half = BCnEncoder.Shared.Half;
+
+namespace BCnEncTests
+{
+ public class Bc6HDecoderTests
+ {
+ private ITestOutputHelper output;
+
+ public Bc6HDecoderTests(ITestOutputHelper output) => this.output = output;
+
+ [Fact]
+ public void DecodeDds()
+ {
+ var decoder = new BcDecoder();
+ var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds);
+
+ var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraDds.header.dwWidth,
+ (int)HdrLoader.TestHdrKiaraDds.header.dwHeight);
+
+ Assert.Equal(hdr.pixels.Length, decoded.Length);
+
+ hdr.pixels = decoded;
+ using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr");
+ hdr.Write(sfs);
+
+ TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output);
+ }
+
+ [Fact]
+ public void DecodeKtx()
+ {
+ var decoder = new BcDecoder();
+ var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraKtx);
+
+ var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth,
+ (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight);
+
+ Assert.Equal(hdr.pixels.Length, decoded.Length);
+
+ hdr.pixels = decoded;
+ using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr");
+ hdr.Write(sfs);
+
+ TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.BestQuality, output);
+ }
+
+ // TestHdrKiaraDds includes some blocks with all modes.
+ // The test_hdr_kiara_dds_float16_data.bin file contains Half-float values decoded with a reference decoder.
+ // This test ensures that decoding is bit-exact.
+ [Fact]
+ public void AllBlocksDecodesExact()
+ {
+ var decoder = new BcDecoder();
+ var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds);
+
+ using var fs = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin");
+ using var ms = new MemoryStream();
+ fs.CopyTo(ms);
+ var length = (int)ms.Position;
+
+ var bytes = ms.GetBuffer().AsSpan(0, length);
+ var halfs = MemoryMarshal.Cast(bytes);
+ Assert.Equal(halfs.Length / 4, decoded.Length);
+
+ for (var i = 0; i < decoded.Length; i++)
+ {
+ float r = halfs[i * 4 + 0];
+ float g = halfs[i * 4 + 1];
+ float b = halfs[i * 4 + 2];
+
+ Assert.Equal(r, decoded[i].r);
+ Assert.Equal(g, decoded[i].g);
+ Assert.Equal(b, decoded[i].b);
+ }
+ }
+
+ // Test that above statement holds true. And that modes are recognized correctly
+ [Fact]
+ public void DecodeModes()
+ {
+ var modes = new int[31];
+
+ var hdr = HdrLoader.TestHdrKiaraDds;
+
+ var bytes = hdr.Faces[0].MipMaps[0].Data;
+ var blocks = MemoryMarshal.Cast(bytes);
+
+ for (var i = 0; i < blocks.Length; i++)
+ {
+ var block = blocks[i];
+ var mode = block.Type;
+ modes[(int)mode]++;
+ }
+
+ for (var i = 0; i < modes.Length; i++)
+ {
+ output.WriteLine($"Mode {i}: {modes[i]}");
+ }
+
+ Assert.True(modes[0] > 0);
+ Assert.True(modes[1] > 0);
+ Assert.True(modes[2] > 0);
+ Assert.True(modes[6] > 0);
+ Assert.True(modes[10] > 0);
+ Assert.True(modes[14] > 0);
+ Assert.True(modes[18] > 0);
+ Assert.True(modes[22] > 0);
+ Assert.True(modes[26] > 0);
+ Assert.True(modes[30] > 0);
+ Assert.True(modes[3] > 0);
+ Assert.True(modes[7] > 0);
+ Assert.True(modes[11] > 0);
+ Assert.True(modes[15] > 0);
+ }
+
+ [Fact]
+ public void DecodeErrorBlock()
+ {
+ var decoder = new BcDecoder();
+
+ var width = 16;
+ var height = 16;
+ var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc6U) * width * height;
+
+ var buffer = new byte[bufferSize];
+ var r = new System.Random(44);
+ r.NextBytes(buffer);
+
+ var decoded = decoder.DecodeRawHdr(buffer, width * 4, height * 4, CompressionFormat.Bc6U);
+ Assert.Contains(new ColorRgbFloat(1, 0, 1), decoded);
+
+ HdrImage image = new HdrImage(new Span2D(decoded, height * 4, width * 4));
+ using var fs = File.OpenWrite("test_decode_bc6h_error.hdr");
+ image.Write(fs);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/Bc7BlockTests.cs b/BCnEncTests.Framework/Bc7BlockTests.cs
new file mode 100644
index 0000000..c356fb7
--- /dev/null
+++ b/BCnEncTests.Framework/Bc7BlockTests.cs
@@ -0,0 +1,348 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using BCnEncoder.Decoder;
+using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class Bc7BlockTests
+ {
+ [Fact]
+ public void PackTypes()
+ {
+ var numWidthBlocks = 4;
+ var numHeightBlocks = 2;
+ var outputBlocks = new Bc7Block[64 * numWidthBlocks * numHeightBlocks];
+ var encoded = new byte[64 * numWidthBlocks * numHeightBlocks * Unsafe.SizeOf()];
+
+ var output = new KtxFile(
+ KtxHeader.InitializeCompressed(numWidthBlocks * 8 * 4, numHeightBlocks * 8 * 4,
+ GlInternalFormat.GlCompressedRgbaBptcUnormArb,
+ GlFormat.GlRgba));
+
+ var type0 = new Span(outputBlocks, 0, 64);
+ Type0Pack(type0);
+ PlaceBlock(0, 0, type0, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
+
+ var type1 = new Span(outputBlocks, 64 * 1, 64);
+ Type1Pack(type1);
+ PlaceBlock(1, 0, type1, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
+
+ var type2 = new Span(outputBlocks, 64 * 2, 64);
+ Type2Pack(type2);
+ PlaceBlock(2, 0, type2, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
+
+ var type3 = new Span(outputBlocks, 64 * 3, 64);
+ Type3Pack(type3);
+ PlaceBlock(3, 0, type3, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
+
+ var type4 = new Span(outputBlocks, 64 * 4, 64);
+ Type4Pack(type4);
+ PlaceBlock(0, 1, type4, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
+
+ var type5 = new Span(outputBlocks, 64 * 5, 64);
+ Type5Pack(type5);
+ PlaceBlock(1, 1, type5, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
+
+ var type6 = new Span(outputBlocks, 64 * 6, 64);
+ Type6Pack(type6);
+ PlaceBlock(2, 1, type6, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
+
+ var type7 = new Span(outputBlocks, 64 * 7, 64);
+ Type7Pack(type7);
+ PlaceBlock(3, 1, type7, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
+
+ output.MipMaps.Add(new KtxMipmap((uint)encoded.Length,
+ (uint)(8 * 4 * numWidthBlocks),
+ (uint)(8 * 4 * numHeightBlocks), 1));
+ output.MipMaps[0].Faces[0] = new KtxMipFace(encoded,
+ (uint)(8 * 4 * numWidthBlocks),
+ (uint)(8 * 4 * numHeightBlocks));
+
+ var fs = File.OpenWrite("bc7_blocktests.ktx");
+ output.Write(fs);
+ }
+
+ [Fact]
+ public void DecodeErrorBlock()
+ {
+ var decoder = new BcDecoder();
+
+ var width = 32;
+ var height = 32;
+ var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc7) * width * height;
+
+ var buffer = new byte[bufferSize];
+ var r = new Random(50);
+ r.NextBytes(buffer);
+
+ var pixels = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc7);
+ Assert.Contains(new ColorRgba32(255, 0, 255), pixels);
+
+ var decoded = pixels.AsMemory().AsMemory2D(height * 4, width * 4);
+ using var fs = File.OpenWrite("test_decode_bc7_error.png");
+ TestHelper.SaveAsPng(decoded, fs);
+ }
+
+ #region Type Packs
+
+ private void Type0Pack(Span output)
+ {
+ var subsetEndpoints = new[] {
+ new byte[]{0b1111, 0, 0},
+ new byte[]{0b1000, 0, 0},
+ new byte[]{0, 0b1111, 0},
+ new byte[]{0, 0b1000, 0},
+ new byte[]{0, 0, 0b1111},
+ new byte[]{0, 0, 0b1000}
+ };
+ var pBits = new byte[] { 1, 0, 1, 0, 1, 1 };
+ var indices = new byte[] {
+ 0, 1, 2, 3,
+ 0, 1, 2, 3,
+ 3, 2, 1, 0,
+ 3, 2, 1, 0
+ };
+ for (var i = 0; i < 16; i++)
+ {
+ output[i].PackType0(i, subsetEndpoints, pBits, indices);
+ Assert.Equal(Bc7BlockType.Type0, output[i].Type);
+ Assert.Equal(i, output[i].PartitionSetId);
+ }
+
+ pBits = new byte[] { 0, 0, 0, 0, 0, 0 };
+ indices = new byte[] {
+ 0, 1, 0, 3,
+ 1, 1, 1, 3,
+ 2, 2, 2, 0,
+ 3, 2, 3, 0
+ };
+ for (var i = 0; i < 16; i++)
+ {
+ output[i + 16].PackType0(i, subsetEndpoints, pBits, indices);
+ Assert.Equal(Bc7BlockType.Type0, output[i].Type);
+ Assert.Equal(i, output[i].PartitionSetId);
+ }
+
+ pBits = new byte[] { 1, 1, 1, 1, 1, 1 };
+ indices = new byte[] {
+ 1, 0, 3, 3,
+ 2, 1, 2, 3,
+ 3, 2, 1, 0,
+ 0, 3, 0, 0
+ };
+ for (var i = 0; i < 16; i++)
+ {
+ output[i + 32].PackType0(i, subsetEndpoints, pBits, indices);
+ Assert.Equal(Bc7BlockType.Type0, output[i].Type);
+ Assert.Equal(i, output[i].PartitionSetId);
+ }
+
+ pBits = new byte[] { 0, 1, 0, 1, 0, 0 };
+ indices = new byte[] {
+ 3, 2, 1, 0,
+ 0, 1, 2, 3,
+ 3, 2, 1, 0,
+ 0, 1, 2, 3
+ };
+ for (var i = 0; i < 16; i++)
+ {
+ output[i + 48].PackType0(i, subsetEndpoints, pBits, indices);
+ Assert.Equal(Bc7BlockType.Type0, output[i].Type);
+ Assert.Equal(i, output[i].PartitionSetId);
+ }
+ }
+
+ private void Type1Pack(Span output)
+ {
+ var subsetEndpoints = new[] {
+ new byte[]{0b111111, 0b0100, 0},
+ new byte[]{0b0100, 0b111111, 0},
+ new byte[]{0, 0, 0b111111},
+ new byte[]{0, 0, 0b1010}
+ };
+ var pBits = new byte[] { 1, 1 };
+ var indices = new byte[] {
+ 0, 1, 2, 3,
+ 0, 1, 2, 3,
+ 3, 2, 1, 0,
+ 3, 2, 1, 0
+ };
+ for (var i = 0; i < 64; i++)
+ {
+ output[i].PackType1(i, subsetEndpoints, pBits, indices);
+ Assert.Equal(Bc7BlockType.Type1, output[i].Type);
+ Assert.Equal(i, output[i].PartitionSetId);
+ }
+ }
+
+ private void Type2Pack(Span output)
+ {
+ var subsetEndpoints = new[] {
+ new byte[]{0b11111, 0, 0},
+ new byte[]{0b01000, 0, 0},
+ new byte[]{0, 0b11111, 0},
+ new byte[]{0, 0b01000, 0},
+ new byte[]{0, 0, 0b11111},
+ new byte[]{0, 0, 0b01000}
+ };
+ var indices = new byte[] {
+ 0, 1, 2, 3,
+ 0, 1, 2, 3,
+ 3, 2, 1, 0,
+ 3, 2, 1, 0
+ };
+ for (var i = 0; i < 64; i++)
+ {
+ output[i].PackType2(i, subsetEndpoints, indices);
+ Assert.Equal(Bc7BlockType.Type2, output[i].Type);
+ Assert.Equal(i, output[i].PartitionSetId);
+ }
+ }
+
+ private void Type3Pack(Span output)
+ {
+ var subsetEndpoints = new[] {
+ new byte[]{0b1111111, 0b10100, 0},
+ new byte[]{0b10100, 0b1111111, 0},
+ new byte[]{0, 0, 0b1111111},
+ new byte[]{0, 0, 0b11010}
+ };
+ var pBits = new byte[] { 1, 0, 0, 1 };
+ var indices = new byte[] {
+ 0, 1, 2, 3,
+ 0, 1, 2, 3,
+ 3, 2, 1, 0,
+ 3, 2, 1, 0
+ };
+ for (var i = 0; i < 64; i++)
+ {
+ output[i].PackType3(i, subsetEndpoints, pBits, indices);
+ Assert.Equal(Bc7BlockType.Type3, output[i].Type);
+ Assert.Equal(i, output[i].PartitionSetId);
+ }
+ }
+
+ private void Type4Pack(Span output)
+ {
+ var colorEndpoints = new[] {
+ new byte[]{0b11111, 0, 0b0100},
+ new byte[]{0, 0b11111, 0b0100}
+ };
+ var alphaEndPoints = new byte[] { 0b111111, 0 };
+ var indices2Bit = new byte[] {
+ 0, 1, 2, 3,
+ 0, 1, 2, 3,
+ 3, 2, 1, 0,
+ 3, 2, 1, 0
+ };
+ var indices3Bit = new byte[] {
+ 0, 1, 2, 3,
+ 7, 6, 5, 4,
+ 7, 6, 5, 4,
+ 3, 2, 1, 0
+ };
+ var rotation = 0;
+ byte idxMode = 0;
+ for (var i = 0; i < 64; i++)
+ {
+ rotation = (rotation + 1) % 4;
+ idxMode = (byte)((idxMode + 1) % 2);
+ output[i].PackType4(rotation, idxMode, colorEndpoints, alphaEndPoints, indices2Bit, indices3Bit);
+ Assert.Equal(Bc7BlockType.Type4, output[i].Type);
+ Assert.Equal(rotation, output[i].RotationBits);
+ Assert.Equal(idxMode, output[i].Type4IndexMode);
+ }
+ }
+
+ private void Type5Pack(Span output)
+ {
+ var colorEndpoints = new[] {
+ new byte[]{0b1111111, 0b0100, 0},
+ new byte[]{0, 0, 0b1111111}
+ };
+ var alphaEndPoints = new byte[] { 255, 100 };
+ var colorIndices = new byte[] {
+ 0, 1, 2, 3,
+ 0, 1, 2, 3,
+ 3, 2, 1, 0,
+ 3, 2, 1, 0
+ };
+ var alphaIndices = new byte[] {
+ 1, 2, 3, 0,
+ 0, 1, 2, 3,
+ 2, 3, 0, 1,
+ 3, 2, 1, 0
+ };
+ var rotation = 0;
+ for (var i = 0; i < 64; i++)
+ {
+ rotation = (rotation + 1) % 4;
+ output[i].PackType5(rotation, colorEndpoints, alphaEndPoints, colorIndices, alphaIndices);
+ Assert.Equal(Bc7BlockType.Type5, output[i].Type);
+ Assert.Equal(rotation, output[i].RotationBits);
+ }
+ }
+
+ private void Type6Pack(Span output)
+ {
+ var colorEndpoints = new[] {
+ new byte[]{0b1111111, 0b0100, 0, 0b1111111},
+ new byte[]{0, 0, 0b1111111, 0b1111}
+ };
+ var pBits = new byte[] { 1, 0 };
+ var colorIndices = new byte[] {
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 12, 13, 14, 15
+ };
+ for (var i = 0; i < 64; i++)
+ {
+ output[i].PackType6(colorEndpoints, pBits, colorIndices);
+ Assert.Equal(Bc7BlockType.Type6, output[i].Type);
+ }
+ }
+
+ private void Type7Pack(Span output)
+ {
+ var colorEndpoints = new[] {
+ new byte[]{0b11111, 0b0100, 0, 0b11111},
+ new byte[]{0, 0b11111, 0, 0b111},
+ new byte[]{0b11111, 0b0100, 0, 0b1111},
+ new byte[]{0b10100, 0, 0b11111, 0b11111}
+ };
+ var pBits = new byte[] { 1, 0, 1, 0 };
+ var indices = new byte[] {
+ 0, 1, 2, 3,
+ 0, 1, 2, 3,
+ 3, 2, 1, 0,
+ 3, 2, 1, 0
+ };
+ for (var i = 0; i < 64; i++)
+ {
+ output[i].PackType7(i, colorEndpoints, pBits, indices);
+ Assert.Equal(Bc7BlockType.Type7, output[i].Type);
+ Assert.Equal(i, output[i].PartitionSetId);
+ }
+ }
+
+ private void PlaceBlock(int x, int y, Span data, Span destination, int destBlockWidth)
+ {
+ for (var i = 0; i < 8; i++)
+ {
+ var xStart = x * 8 + (y * 8 + i) * 8 * destBlockWidth;
+ var row = data.Slice(i * 8, 8);
+ row.CopyTo(destination.Slice(xStart, 8));
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/BCnEncTests.Framework/BgraTests.cs b/BCnEncTests.Framework/BgraTests.cs
new file mode 100644
index 0000000..6f0e8bf
--- /dev/null
+++ b/BCnEncTests.Framework/BgraTests.cs
@@ -0,0 +1,63 @@
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class BgraTests
+ {
+ [Fact]
+ public void BgraDdsDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.Bgra);
+ var original = ImageLoader.TestLenna;
+
+ var dds = encoder.EncodeToDds(original);
+ var image = decoder.Decode2D(dds);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void BgraAlphaDdsDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.Bgra);
+ var original = ImageLoader.TestAlphaGradient1;
+
+ var dds = encoder.EncodeToDds(original);
+ var image = decoder.Decode2D(dds);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void BgraKtxDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.Bgra);
+ var original = ImageLoader.TestLenna;
+
+ var ktx = encoder.EncodeToKtx(original);
+ var image = decoder.Decode2D(ktx);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void BgraAlphaKtxDecode()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder(CompressionFormat.Bgra);
+ var original = ImageLoader.TestAlphaGradient1;
+
+ var ktx = encoder.EncodeToKtx(original);
+ var image = decoder.Decode2D(ktx);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/CancellationTests.cs b/BCnEncTests.Framework/CancellationTests.cs
new file mode 100644
index 0000000..9932c6c
--- /dev/null
+++ b/BCnEncTests.Framework/CancellationTests.cs
@@ -0,0 +1,21 @@
+using System.Threading.Tasks;
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class CancellationTests
+ {
+ [Fact]
+ public async Task EncodeParallelCancellation()
+ {
+ await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, true);
+ }
+
+ [Fact]
+ public async Task EncodeNonParallelCancellation()
+ {
+ await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, false);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/ClusterTests.cs b/BCnEncTests.Framework/ClusterTests.cs
new file mode 100644
index 0000000..c141928
--- /dev/null
+++ b/BCnEncTests.Framework/ClusterTests.cs
@@ -0,0 +1,52 @@
+using System;
+using System.IO;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class ClusterTests
+ {
+ [Fact]
+ public void Clusterize()
+ {
+ var original = ImageLoader.TestBlur1;
+ int height = original.Height;
+ int width = original.Width;
+
+ // Copy pixels from the source image
+ var pixels = new ColorRgba32[height * width];
+ var srcSpan = original.Span;
+ for (int y = 0; y < height; y++)
+ for (int x = 0; x < width; x++)
+ pixels[y * width + x] = srcSpan[y, x];
+
+ var numClusters = (width / 32) * (height / 32);
+ var clusters = LinearClustering.ClusterPixels(pixels, width, height, numClusters, 10, 10);
+
+ var pixC = new ColorYCbCr[numClusters];
+ var counts = new int[numClusters];
+
+ for (var i = 0; i < pixels.Length; i++)
+ {
+ pixC[clusters[i]] += new ColorYCbCr(pixels[i]);
+ counts[clusters[i]]++;
+ }
+
+ for (var i = 0; i < numClusters; i++)
+ {
+ pixC[i] /= counts[i];
+ }
+
+ for (var i = 0; i < pixels.Length; i++)
+ {
+ pixels[i] = pixC[clusters[i]].ToColorRgba32();
+ }
+
+ var result = pixels.AsMemory().AsMemory2D(height, width);
+ using var fs = File.OpenWrite("test_cluster.png");
+ TestHelper.SaveAsPng(result, fs);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/DdsReadTests.cs b/BCnEncTests.Framework/DdsReadTests.cs
new file mode 100644
index 0000000..90c9279
--- /dev/null
+++ b/BCnEncTests.Framework/DdsReadTests.cs
@@ -0,0 +1,51 @@
+using System.IO;
+using BCnEncoder.Decoder;
+using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class DdsReadTests
+ {
+ [Fact]
+ public void ReadRgba()
+ {
+ TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressRgba, DxgiFormat.DxgiFormatR8G8B8A8Unorm, "decoding_test_dds_rgba_mip{0}.png");
+ }
+
+ [Fact]
+ public void ReadBc1()
+ {
+ TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1_mip{0}.png");
+ }
+
+ [Fact]
+ public void ReadBc1A()
+ {
+ TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1A, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1a_mip{0}.png");
+ }
+
+ [Fact]
+ public void ReadBc7()
+ {
+ TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc7, DxgiFormat.DxgiFormatUnknown, "decoding_test_dds_bc7_mip{0}.png");
+ }
+
+ [Fact]
+ public void ReadFromStream()
+ {
+ using var fs = File.OpenRead(DdsLoader.TestDecompressBc1Name);
+
+ var decoder = new BcDecoder();
+ var images = decoder.DecodeAllMipMaps2D(fs);
+
+ for (var i = 0; i < images.Length; i++)
+ {
+ using var outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png");
+ TestHelper.SaveAsPng(images[i], outFs);
+ }
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/DdsWritingTests.cs b/BCnEncTests.Framework/DdsWritingTests.cs
new file mode 100644
index 0000000..44a0616
--- /dev/null
+++ b/BCnEncTests.Framework/DdsWritingTests.cs
@@ -0,0 +1,57 @@
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class DdsWritingTests
+ {
+ [Fact]
+ public void DdsWriteRgba()
+ {
+ TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Rgba, "encoding_dds_rgba.dds");
+ }
+
+ [Fact]
+ public void DdsWriteBc1()
+ {
+ TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc1, "encoding_dds_bc1.dds");
+ }
+
+ [Fact]
+ public void DdsWriteBc2()
+ {
+ TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc2, "encoding_dds_bc2.dds");
+ }
+
+ [Fact]
+ public void DdsWriteBc3()
+ {
+ TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc3, "encoding_dds_bc3.dds");
+ }
+
+ [Fact]
+ public void DdsWriteBc4()
+ {
+ TestHelper.ExecuteDdsWritingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, "encoding_dds_bc4.dds");
+ }
+
+ [Fact]
+ public void DdsWriteBc5()
+ {
+ TestHelper.ExecuteDdsWritingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, "encoding_dds_bc5.dds");
+ }
+
+ [Fact]
+ public void DdsWriteBc7()
+ {
+ TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc7, "encoding_dds_bc7.dds");
+ }
+
+ [Fact]
+ public void DdsWriteCubemap()
+ {
+ TestHelper.ExecuteDdsWritingTest(ImageLoader.TestCubemap, CompressionFormat.Bc1, "encoding_dds_cubemap_bc1.dds");
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/DecodingAsyncTests.cs b/BCnEncTests.Framework/DecodingAsyncTests.cs
new file mode 100644
index 0000000..da61e5e
--- /dev/null
+++ b/BCnEncTests.Framework/DecodingAsyncTests.cs
@@ -0,0 +1,62 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class DecodingAsyncTests
+ {
+ [Fact]
+ public async Task DecodeAsync()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder();
+ var original = ImageLoader.TestGradient1;
+
+ var file = encoder.EncodeToKtx(original);
+ var image = await decoder.Decode2DAsync(file);
+
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task DecodeAllMipMapsAsync()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder();
+ var original = ImageLoader.TestGradient1;
+
+ var file = encoder.EncodeToKtx(original);
+ var images = await decoder.DecodeAllMipMaps2DAsync(file);
+
+ TestHelper.AssertImagesEqual(original, images[0], encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task DecodeRawAsync()
+ {
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder();
+ var original = ImageLoader.TestGradient1;
+
+ var file = encoder.EncodeToKtx(original);
+
+ var rawBytes = file.MipMaps[0].Faces[0].Data;
+ var mipWidth = (int)file.MipMaps[0].Width;
+ var mipHeight = (int)file.MipMaps[0].Height;
+
+ var decoded = await Task.Run(() =>
+ {
+ var pixels = decoder.DecodeRaw(rawBytes, mipWidth, mipHeight, CompressionFormat.Bc1);
+ return pixels.AsMemory().AsMemory2D(mipHeight, mipWidth);
+ });
+
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/DecodingTests.cs b/BCnEncTests.Framework/DecodingTests.cs
new file mode 100644
index 0000000..c2fc08c
--- /dev/null
+++ b/BCnEncTests.Framework/DecodingTests.cs
@@ -0,0 +1,62 @@
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class DecodingTests
+ {
+ [Fact]
+ public void Bc1Decode()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1, "decoding_test_bc1.png");
+ }
+
+ [Fact]
+ public void Bc1AlphaDecode()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1A, "decoding_test_bc1a.png");
+ }
+
+ [Fact]
+ public void Bc2Decode()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc2, "decoding_test_bc2.png");
+ }
+
+ [Fact]
+ public void Bc3Decode()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc3, "decoding_test_bc3.png");
+ }
+
+ [Fact]
+ public void Bc4Decode()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc4Unorm, "decoding_test_bc4.png");
+ }
+
+ [Fact]
+ public void Bc5Decode()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc5Unorm, "decoding_test_bc5.png");
+ }
+
+ [Fact]
+ public void Bc7DecodeRgb()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Rgb, "decoding_test_bc7_rgb.png");
+ }
+
+ [Fact]
+ public void Bc7DecodeUnorm()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Unorm, "decoding_test_bc7_unorm.png");
+ }
+
+ [Fact]
+ public void Bc7DecodeEveryBlockType()
+ {
+ TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Types, "decoding_test_bc7_types.png");
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/EncoderOptionsTests.cs b/BCnEncTests.Framework/EncoderOptionsTests.cs
new file mode 100644
index 0000000..f73ecd3
--- /dev/null
+++ b/BCnEncTests.Framework/EncoderOptionsTests.cs
@@ -0,0 +1,66 @@
+using BCnEncoder.Encoder;
+using BCnEncTests.Support;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class EncoderOptionsTests
+ {
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(5)]
+ [InlineData(10)]
+ public void MaxMipMaps(int requestedMipMaps)
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder()
+ {
+ OutputOptions =
+ {
+ GenerateMipMaps = true,
+ MaxMipMapLevel = requestedMipMaps
+ }
+ };
+
+ Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height));
+
+ var ktx = encoder.EncodeToKtx(testImage);
+
+ Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels);
+ Assert.Equal(requestedMipMaps, ktx.MipMaps.Count);
+
+ var dds = encoder.EncodeToDds(testImage);
+
+ Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount);
+ Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length);
+ }
+
+ [Fact]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.", Justification = "")]
+ public void GenerateMipMaps()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ const int requestedMipMaps = 1;
+ var encoder = new BcEncoder()
+ {
+ OutputOptions =
+ {
+ GenerateMipMaps = false
+ }
+ };
+
+ Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height));
+
+ var ktx = encoder.EncodeToKtx(testImage);
+
+ Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels);
+ Assert.Equal(requestedMipMaps, (int)ktx.MipMaps.Count);
+
+ var dds = encoder.EncodeToDds(testImage);
+
+ Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount);
+ Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/EncodingAsyncTest.cs b/BCnEncTests.Framework/EncodingAsyncTest.cs
new file mode 100644
index 0000000..a936b7d
--- /dev/null
+++ b/BCnEncTests.Framework/EncodingAsyncTest.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Threading.Tasks;
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class EncodingAsyncTest
+ {
+ private readonly BcEncoder encoder;
+ private readonly BcDecoder decoder;
+ private readonly Memory2D originalImage;
+ private readonly Memory2D[] originalCubeMap;
+
+ public EncodingAsyncTest()
+ {
+ encoder = new BcEncoder();
+ decoder = new BcDecoder();
+ originalImage = ImageLoader.TestGradient1;
+ originalCubeMap = ImageLoader.TestCubemap;
+ }
+
+ [Fact]
+ public async Task EncodeToDdsAsync()
+ {
+ var file = await encoder.EncodeToDdsAsync(originalImage);
+ var image = decoder.Decode2D(file);
+
+ TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task EncodeToKtxAsync()
+ {
+ var file = await encoder.EncodeToKtxAsync(originalImage);
+ var image = decoder.Decode2D(file);
+
+ TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task EncodeCubemapToDdsAsync()
+ {
+ var file = await encoder.EncodeCubeMapToDdsAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2],
+ originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]);
+
+ for (var i = 0; i < 6; i++)
+ {
+ var rawPixels = decoder.DecodeRaw(file.Faces[i].MipMaps[0].Data,
+ (int)file.Faces[i].Width, (int)file.Faces[i].Height, CompressionFormat.Bc1);
+ var decodedImage = rawPixels.AsMemory().AsMemory2D((int)file.Faces[i].Height, (int)file.Faces[i].Width);
+
+ TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality);
+ }
+ }
+
+ [Fact]
+ public async Task EncodeCubemapToKtxAsync()
+ {
+ var file = await encoder.EncodeCubeMapToKtxAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2],
+ originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]);
+
+ for (var i = 0; i < 6; i++)
+ {
+ var rawPixels = decoder.DecodeRaw(file.MipMaps[0].Faces[i].Data,
+ (int)file.MipMaps[0].Faces[i].Width, (int)file.MipMaps[0].Faces[i].Height, CompressionFormat.Bc1);
+ var decodedImage = rawPixels.AsMemory().AsMemory2D(
+ (int)file.MipMaps[0].Faces[i].Height, (int)file.MipMaps[0].Faces[i].Width);
+
+ TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality);
+ }
+ }
+
+ [Fact]
+ public async Task EncodeToRawBytesAsync()
+ {
+ var data = await encoder.EncodeToRawBytesAsync(originalImage);
+ var rawPixels = decoder.DecodeRaw(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1);
+ var image = rawPixels.AsMemory().AsMemory2D(originalImage.Height, originalImage.Width);
+
+ TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/EncodingTest.cs b/BCnEncTests.Framework/EncodingTest.cs
new file mode 100644
index 0000000..055de03
--- /dev/null
+++ b/BCnEncTests.Framework/EncodingTest.cs
@@ -0,0 +1,315 @@
+using System.IO;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace BCnEncTests
+{
+ public class Bc1GradientTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc1GradientTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc1GradientBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestGradient1, CompressionFormat.Bc1, CompressionQuality.BestQuality, "encoding_bc1_gradient_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc1GradientBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestGradient1, CompressionFormat.Bc1, CompressionQuality.Balanced, "encoding_bc1_gradient_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc1GradientFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestGradient1, CompressionFormat.Bc1, CompressionQuality.Fast, "encoding_bc1_gradient_fast.ktx", output);
+ }
+ }
+
+ public class Bc1DiffuseTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc1DiffuseTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc1DiffuseBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestDiffuse1, CompressionFormat.Bc1, CompressionQuality.BestQuality, "encoding_bc1_diffuse_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc1DiffuseBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestDiffuse1, CompressionFormat.Bc1, CompressionQuality.Balanced, "encoding_bc1_diffuse_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc1DiffuseFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestDiffuse1, CompressionFormat.Bc1, CompressionQuality.Fast, "encoding_bc1_diffuse_fast.ktx", output);
+ }
+ }
+
+ public class Bc1BlurryTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc1BlurryTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc1BlurBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestBlur1, CompressionFormat.Bc1, CompressionQuality.BestQuality, "encoding_bc1_blur_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc1BlurBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestBlur1, CompressionFormat.Bc1, CompressionQuality.Balanced, "encoding_bc1_blur_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc1BlurFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestBlur1, CompressionFormat.Bc1, CompressionQuality.Fast, "encoding_bc1_blur_fast.ktx", output);
+ }
+ }
+
+ public class Bc1ASpriteTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc1ASpriteTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc1ASpriteBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestTransparentSprite1, CompressionFormat.Bc1WithAlpha, CompressionQuality.BestQuality, "encoding_bc1a_sprite_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc1ASpriteBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestTransparentSprite1, CompressionFormat.Bc1WithAlpha, CompressionQuality.Balanced, "encoding_bc1a_sprite_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc1ASpriteFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestTransparentSprite1, CompressionFormat.Bc1WithAlpha, CompressionQuality.Fast, "encoding_bc1a_sprite_fast.ktx", output);
+ }
+ }
+
+ public class Bc2GradientTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc2GradientTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc2GradientBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc2, CompressionQuality.BestQuality, "encoding_bc2_gradient_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc2GradientBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc2, CompressionQuality.Balanced, "encoding_bc2_gradient_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc2GradientFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc2, CompressionQuality.Fast, "encoding_bc2_gradient_fast.ktx", output);
+ }
+ }
+
+ public class Bc3GradientTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc3GradientTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc3GradientBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc3, CompressionQuality.BestQuality, "encoding_bc3_gradient_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc3GradientBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc3, CompressionQuality.Balanced, "encoding_bc3_gradient_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc3GradientFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlphaGradient1, CompressionFormat.Bc3, CompressionQuality.Fast, "encoding_bc3_gradient_fast.ktx", output);
+ }
+ }
+
+ public class Bc4RedTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc4RedTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc4RedBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, CompressionQuality.BestQuality, "encoding_bc4_red_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc4RedBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, CompressionQuality.Balanced, "encoding_bc4_red_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc4RedFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, CompressionQuality.Fast, "encoding_bc4_red_fast.ktx", output);
+ }
+ }
+
+ public class Bc5RedGreenTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc5RedGreenTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc5RedGreenBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, CompressionQuality.BestQuality, "encoding_bc5_red_green_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc5RedGreenBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, CompressionQuality.Balanced, "encoding_bc5_red_green_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc5RedGreenFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, CompressionQuality.Fast, "encoding_bc5_red_green_fast.ktx", output);
+ }
+ }
+
+ public class Bc7RgbTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc7RgbTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc7RgbBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestRgbHard1, CompressionFormat.Bc7, CompressionQuality.BestQuality, "encoding_bc7_rgb_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc7RgbBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestRgbHard1, CompressionFormat.Bc7, CompressionQuality.Balanced, "encoding_bc7_rgb_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc7LennaBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestLenna, CompressionFormat.Bc7, CompressionQuality.Balanced, "encoding_bc7_lenna_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc7RgbFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestRgbHard1, CompressionFormat.Bc7, CompressionQuality.Fast, "encoding_bc7_rgb_fast.ktx", output);
+ }
+ }
+
+ public class Bc7RgbaTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public Bc7RgbaTest(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public void Bc7RgbaBestQuality()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc7, CompressionQuality.BestQuality, "encoding_bc7_rgba_bestQuality.ktx", output);
+ }
+
+ [Fact]
+ public void Bc7RgbaBalanced()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc7, CompressionQuality.Balanced, "encoding_bc7_rgba_balanced.ktx", output);
+ }
+
+ [Fact]
+ public void Bc7RgbaFast()
+ {
+ TestHelper.ExecuteEncodingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc7, CompressionQuality.Fast, "encoding_bc7_rgba_fast.ktx", output);
+ }
+ }
+
+ public class CubemapTest
+ {
+ [Fact]
+ public void WriteCubeMapFile()
+ {
+ var images = ImageLoader.TestCubemap;
+
+ var filename = "encoding_bc1_cubemap.ktx";
+
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ encoder.OutputOptions.GenerateMipMaps = true;
+ encoder.OutputOptions.Format = CompressionFormat.Bc1;
+
+ using var fs = File.OpenWrite(filename);
+ encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/HdrImageTests.cs b/BCnEncTests.Framework/HdrImageTests.cs
new file mode 100644
index 0000000..eda8e56
--- /dev/null
+++ b/BCnEncTests.Framework/HdrImageTests.cs
@@ -0,0 +1,26 @@
+using System.IO;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class HdrImageTests
+ {
+ [Fact]
+ public void LoadHdr()
+ {
+ using var stream = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara.hdr");
+ var hdrImg = HdrImage.Read(stream);
+ Assert.True(hdrImg.width > 0);
+ Assert.True(hdrImg.height > 0);
+ Assert.True(hdrImg.pixels.Length == hdrImg.width * hdrImg.height);
+
+ // Save as HDR to verify round-trip
+ var hdrOut = new HdrImage(new Span2D(hdrImg.pixels, hdrImg.height, hdrImg.width));
+ using var outStream = File.OpenWrite("test_hdr_load.hdr");
+ hdrOut.Write(outStream);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/ProgressTests.cs b/BCnEncTests.Framework/ProgressTests.cs
new file mode 100644
index 0000000..c82777b
--- /dev/null
+++ b/BCnEncTests.Framework/ProgressTests.cs
@@ -0,0 +1,390 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace BCnEncTests
+{
+ public class ProgressTests
+ {
+ private ITestOutputHelper output;
+
+ public ProgressTests(ITestOutputHelper output) => this.output = output;
+
+ private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Memory2D testImage, int expectedTotalBlocks)
+ {
+ var lastProgress = new ProgressElement(0, 1);
+
+ var processedBlocks = 0;
+ encoder.Options.Progress = new SynchronousProgress(element =>
+ {
+ output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}");
+
+ Assert.Equal(++processedBlocks, element.CurrentBlock);
+ lastProgress = element;
+ });
+
+ using var ms = new MemoryStream();
+ await encoder.EncodeToStreamAsync(testImage, ms);
+
+ output.WriteLine("LastProgress = " + lastProgress);
+
+ Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks);
+ Assert.Equal(1, lastProgress.Percentage);
+ }
+
+ private async Task ExecuteDecodeProgressReport(BcDecoder decoder, KtxFile image, int expectedTotalBlocks, bool singleMip)
+ {
+ var lastProgress = new ProgressElement(0, 1);
+
+ var processedBlocks = 0;
+ decoder.Options.Progress = new SynchronousProgress(element =>
+ {
+ output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}");
+
+ Assert.Equal(++processedBlocks, element.CurrentBlock);
+ lastProgress = element;
+ });
+
+ if (singleMip)
+ {
+ await decoder.DecodeAsync(image);
+ }
+ else
+ {
+ await decoder.DecodeAllMipMapsAsync(image);
+ }
+
+ output.WriteLine("LastProgress = " + lastProgress);
+
+ Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks);
+ Assert.Equal(1, lastProgress.Percentage);
+ }
+
+ private async Task ExecuteDecodeProgressReport(BcDecoder decoder, DdsFile image, int expectedTotalBlocks, bool singleMip)
+ {
+ var lastProgress = new ProgressElement(0, 1);
+
+ var processedBlocks = 0;
+ decoder.Options.Progress = new SynchronousProgress(element =>
+ {
+ output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}");
+
+ Assert.Equal(++processedBlocks, element.CurrentBlock);
+ lastProgress = element;
+ });
+
+ if (singleMip)
+ {
+ await decoder.DecodeAsync(image);
+ }
+ else
+ {
+ await decoder.DecodeAllMipMapsAsync(image);
+ }
+
+ output.WriteLine("LastProgress = " + lastProgress);
+
+ Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks);
+ Assert.Equal(1, lastProgress.Percentage);
+ }
+
+ [Fact]
+ public async Task DecodeProgressReportParallel()
+ {
+ var testImage = KtxLoader.TestDecompressBc1;
+ var decoder = new BcDecoder
+ {
+ Options = { IsParallel = true }
+ };
+
+ var expectedTotal = 0;
+
+ for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++)
+ {
+ expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height);
+ }
+
+ await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false);
+ }
+
+ [Fact]
+ public async Task DecodeProgressReportNonParallel()
+ {
+ var testImage = KtxLoader.TestDecompressBc1;
+ var decoder = new BcDecoder
+ {
+ Options = { IsParallel = false }
+ };
+
+ var expectedTotal = 0;
+
+ for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++)
+ {
+ expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height);
+ }
+
+ await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false);
+ }
+
+ [Fact]
+ public async Task DecodeProgressReportParallelOneMip()
+ {
+ var testImage = KtxLoader.TestDecompressBc1;
+ var decoder = new BcDecoder
+ {
+ Options = { IsParallel = true }
+ };
+
+ var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height);
+
+ await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true);
+ }
+
+ [Fact]
+ public async Task DecodeProgressReportNonParallelOneMip()
+ {
+ var testImage = KtxLoader.TestDecompressBc1;
+ var decoder = new BcDecoder
+ {
+ Options = { IsParallel = false }
+ };
+
+ var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height);
+
+ await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true);
+ }
+
+ [Fact]
+ public async Task DecodeProgressReportParallelDds()
+ {
+ var testImage = DdsLoader.TestDecompressBc1;
+ var decoder = new BcDecoder
+ {
+ Options = { IsParallel = true }
+ };
+
+ var expectedTotal = 0;
+
+ for (var i = 0; i < testImage.header.dwMipMapCount; i++)
+ {
+ expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height);
+ }
+
+ await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false);
+ }
+
+ [Fact]
+ public async Task DecodeProgressReportNonParallelDds()
+ {
+ var testImage = DdsLoader.TestDecompressBc1;
+ var decoder = new BcDecoder
+ {
+ Options = { IsParallel = false }
+ };
+
+ var expectedTotal = 0;
+
+ for (var i = 0; i < testImage.header.dwMipMapCount; i++)
+ {
+ expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height);
+ }
+
+ await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false);
+ }
+
+ [Fact]
+ public async Task DecodeProgressReportParallelOneMipDds()
+ {
+ var testImage = DdsLoader.TestDecompressBc1;
+ var decoder = new BcDecoder
+ {
+ Options = { IsParallel = true }
+ };
+
+ var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height);
+
+ await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true);
+ }
+
+ [Fact]
+ public async Task DecodeProgressReportNonParallelOneMipDds()
+ {
+ var testImage = DdsLoader.TestDecompressBc1;
+ var decoder = new BcDecoder
+ {
+ Options = { IsParallel = false }
+ };
+
+ var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height);
+
+ await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true);
+ }
+
+ [Fact]
+ public async Task EncodeProgressReportParallelKtx()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder
+ {
+ Options = { IsParallel = true },
+ OutputOptions = { FileFormat = OutputFileFormat.Ktx }
+ };
+
+ var expectedTotal = 0;
+
+ for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++)
+ {
+ encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH);
+ expectedTotal += encoder.GetBlockCount(mW, mH);
+ }
+
+ await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
+ }
+
+ [Fact]
+ public async Task EncodeProgressReportNonParallelKtx()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder
+ {
+ Options = { IsParallel = false },
+ OutputOptions = { FileFormat = OutputFileFormat.Ktx }
+ };
+
+ var expectedTotal = 0;
+
+ for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++)
+ {
+ encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH);
+ expectedTotal += encoder.GetBlockCount(mW, mH);
+ }
+
+ await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
+ }
+
+ [Fact]
+ public async Task EncodeProgressReportParallelOneMipKtx()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder
+ {
+ Options = { IsParallel = true },
+ OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx }
+ };
+
+ var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height);
+
+ await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
+ }
+
+ [Fact]
+ public async Task EncodeProgressReportNonParallelOneMipKtx()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder
+ {
+ Options = { IsParallel = false },
+ OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx }
+ };
+
+ var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height);
+
+ await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
+ }
+
+ [Fact]
+ public async Task EncodeProgressReportParallelDds()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder
+ {
+ Options = { IsParallel = true },
+ OutputOptions = { FileFormat = OutputFileFormat.Dds }
+ };
+
+ var expectedTotal = 0;
+
+ for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++)
+ {
+ encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH);
+ expectedTotal += encoder.GetBlockCount(mW, mH);
+ }
+
+ await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
+ }
+
+ [Fact]
+ public async Task EncodeProgressReportNonParallelDds()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder
+ {
+ Options = { IsParallel = false },
+ OutputOptions = { FileFormat = OutputFileFormat.Dds }
+ };
+
+ var expectedTotal = 0;
+
+ for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++)
+ {
+ encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH);
+ expectedTotal += encoder.GetBlockCount(mW, mH);
+ }
+
+ await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
+ }
+
+ [Fact]
+ public async Task EncodeProgressReportParallelOneMipDds()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder
+ {
+ Options = { IsParallel = true },
+ OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds }
+ };
+
+ var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height);
+
+ await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
+ }
+
+ [Fact]
+ public async Task EncodeProgressReportNonParallelOneMipDds()
+ {
+ var testImage = ImageLoader.TestBlur1;
+ var encoder = new BcEncoder
+ {
+ Options = { IsParallel = false },
+ OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds }
+ };
+
+ var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height);
+
+ await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
+ }
+ }
+
+ internal class SynchronousProgress : IProgress
+ {
+ private readonly Action handler;
+
+ public SynchronousProgress(Action handler)
+ {
+ this.handler = handler;
+ }
+
+ public void Report(T value)
+ {
+ handler(value);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/RawTests.cs b/BCnEncTests.Framework/RawTests.cs
new file mode 100644
index 0000000..d2a572a
--- /dev/null
+++ b/BCnEncTests.Framework/RawTests.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class RawTests
+ {
+ [Fact]
+ public void EncodeDecode()
+ {
+ var inputImage = ImageLoader.TestGradient1;
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder
+ {
+ OutputOptions = { Quality = CompressionQuality.BestQuality }
+ };
+
+ var encodedRawBytes = encoder.EncodeToRawBytes(inputImage);
+ var decodedPixels = decoder.DecodeRaw(encodedRawBytes[0], inputImage.Width, inputImage.Height, CompressionFormat.Bc1);
+ var decodedImage = decodedPixels.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width);
+
+ var originalColors = TestHelper.GetSinglePixelArrayAsColors(inputImage);
+ var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage);
+
+ TestHelper.AssertPixelsEqual(originalColors, decodedColors, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void EncodeDecodeStream()
+ {
+ var inputImage = ImageLoader.TestGradient1;
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder
+ {
+ OutputOptions = { Quality = CompressionQuality.BestQuality }
+ };
+
+ var encodedRawBytes = encoder.EncodeToRawBytes(inputImage);
+
+ using var ms = new MemoryStream(encodedRawBytes[0]);
+
+ Assert.Equal(0, ms.Position);
+
+ var rawPixels = decoder.DecodeRaw(ms, inputImage.Width, inputImage.Height, CompressionFormat.Bc1);
+ var decodedImage = rawPixels.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width);
+
+ var originalColors = TestHelper.GetSinglePixelArrayAsColors(inputImage);
+ var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage);
+
+ TestHelper.AssertPixelsEqual(originalColors, decodedColors, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void EncodeDecodeAllMipMapsStream()
+ {
+ var inputImage = ImageLoader.TestGradient1;
+ var decoder = new BcDecoder();
+ var encoder = new BcEncoder
+ {
+ OutputOptions =
+ {
+ Quality = CompressionQuality.BestQuality,
+ GenerateMipMaps = true,
+ MaxMipMapLevel = 0
+ }
+ };
+
+ using var ms = new MemoryStream();
+
+ var encodedRawBytes = encoder.EncodeToRawBytes(inputImage);
+
+ var mipLevels = encoder.CalculateNumberOfMipLevels(inputImage.Width, inputImage.Height);
+ Assert.True(mipLevels > 1);
+
+ for (var i = 0; i < mipLevels; i++)
+ {
+ ms.Write(encodedRawBytes[i], 0, encodedRawBytes[i].Length);
+ }
+
+ ms.Position = 0;
+ Assert.Equal(0, ms.Position);
+
+ for (var i = 0; i < mipLevels; i++)
+ {
+ encoder.CalculateMipMapSize(inputImage.Width, inputImage.Height, i, out var mipWidth, out var mipHeight);
+ var resized = ResizeImage(inputImage, mipWidth, mipHeight);
+
+ var blockSize = decoder.GetBlockSize(CompressionFormat.Bc1);
+ var blockCount = decoder.GetBlockCount(mipWidth, mipHeight);
+ var buffer = new byte[blockSize * blockCount];
+ ms.Read(buffer, 0, buffer.Length);
+
+ var rawPixels = decoder.DecodeRaw(buffer, mipWidth, mipHeight, CompressionFormat.Bc1);
+ var decodedImage = rawPixels.AsMemory().AsMemory2D(mipHeight, mipWidth);
+
+ var originalColors = TestHelper.GetSinglePixelArrayAsColors(resized);
+ var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage);
+
+ TestHelper.AssertPixelsEqual(originalColors, decodedColors, encoder.OutputOptions.Quality);
+ }
+
+ encoder.CalculateMipMapSize(inputImage.Width, inputImage.Height, mipLevels - 1, out var lastMWidth, out var lastMHeight);
+ Assert.Equal(1, lastMWidth);
+ Assert.Equal(1, lastMHeight);
+ }
+
+ private static ReadOnlyMemory2D ResizeImage(Memory2D image, int newWidth, int newHeight)
+ {
+ int numMips = 0;
+ var chain = MipMapper.GenerateMipChain(image, ref numMips);
+
+ foreach (var memory2D in chain)
+ {
+ if (memory2D.Width == newWidth && memory2D.Height == newHeight)
+ {
+ return memory2D;
+ }
+ }
+ throw new InvalidOperationException("Cannot resize image to non-mip dimensions");
+ }
+
+ private static unsafe Bitmap ToBitmap(Memory2D image)
+ {
+ var bmp = new Bitmap(image.Width, image.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+ var data = bmp.LockBits(new Rectangle(0, 0, image.Width, image.Height),
+ ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+ byte* ptr = (byte*)data.Scan0;
+ var span = image.Span;
+ for (int y = 0; y < image.Height; y++)
+ {
+ for (int x = 0; x < image.Width; x++)
+ {
+ var c = span[y, x];
+ ptr[0] = c.b;
+ ptr[1] = c.g;
+ ptr[2] = c.r;
+ ptr[3] = c.a;
+ ptr += 4;
+ }
+ }
+ bmp.UnlockBits(data);
+ return bmp;
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/SingleBlockTests.cs b/BCnEncTests.Framework/SingleBlockTests.cs
new file mode 100644
index 0000000..74691ed
--- /dev/null
+++ b/BCnEncTests.Framework/SingleBlockTests.cs
@@ -0,0 +1,143 @@
+using System;
+using System.IO;
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class SingleBlockTests
+ {
+ [Theory]
+ [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)]
+ [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)]
+ [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)]
+ [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)]
+ [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)]
+ public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQuality quality)
+ {
+ var testImage = ImageLoader.TestAlpha1;
+ int height = testImage.Height;
+ int width = testImage.Width;
+
+ var encoder = new BcEncoder()
+ {
+ OutputOptions =
+ {
+ Format = format,
+ Quality = quality
+ }
+ };
+
+ var colors = testImage.Span;
+
+ var ms = new MemoryStream();
+
+ for (var y = 0; y < height; y += 4)
+ {
+ for (var x = 0; x < width; x += 4)
+ {
+ encoder.EncodeBlock(colors.Slice(y, x, 4, 4), ms);
+ }
+ }
+
+ Assert.Equal(ms.Position, encoder.GetBlockSize() * encoder.GetBlockCount(width, height));
+
+ ms.Position = 0;
+
+ var decoder = new BcDecoder();
+
+ var decoded = new ColorRgba32[height, width];
+
+ for (var y = 0; y < height; y += 4)
+ {
+ for (var x = 0; x < width; x += 4)
+ {
+ decoder.DecodeBlock(ms, format,
+ decoded.AsSpan2D().Slice(y, x, 4, 4));
+ }
+ }
+
+ var oPixels = TestHelper.GetSinglePixelArrayAsColors(testImage);
+ var dPixels = FlattenDecoded(decoded, height, width);
+ var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels,
+ format != CompressionFormat.Bc1);
+
+ TestHelper.AssertPSNR(psnr, quality);
+ }
+
+ [Theory]
+ [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)]
+ [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)]
+ [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)]
+ [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)]
+ [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)]
+ public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality quality)
+ {
+ var testImage = ImageLoader.TestAlpha1;
+ int height = testImage.Height;
+ int width = testImage.Width;
+
+ var encoder = new BcEncoder()
+ {
+ OutputOptions =
+ {
+ Format = format,
+ Quality = quality
+ }
+ };
+
+ var colors = testImage.Span;
+
+ var encMs = new MemoryStream();
+
+ for (var y = 0; y < height; y += 4)
+ {
+ for (var x = 0; x < width; x += 4)
+ {
+ encoder.EncodeBlock(colors.Slice(y, x, 4, 4), encMs);
+ }
+ }
+
+ var buffer = encMs.ToArray();
+
+ var decoder = new BcDecoder();
+
+ var decoded = new ColorRgba32[height, width];
+
+ var blockIndex = 0;
+ for (var y = 0; y < height; y += 4)
+ {
+ for (var x = 0; x < width; x += 4)
+ {
+ decoder.DecodeBlock(
+ new Span(buffer,
+ blockIndex * decoder.GetBlockSize(format),
+ decoder.GetBlockSize(format)),
+ format,
+ decoded.AsSpan2D().Slice(y, x, 4, 4));
+ blockIndex++;
+ }
+ }
+
+ var oPixels = TestHelper.GetSinglePixelArrayAsColors(testImage);
+ var dPixels = FlattenDecoded(decoded, height, width);
+ var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels,
+ format != CompressionFormat.Bc1);
+
+ TestHelper.AssertPSNR(psnr, quality);
+ }
+
+ private static ColorRgba32[] FlattenDecoded(ColorRgba32[,] decoded, int height, int width)
+ {
+ var result = new ColorRgba32[height * width];
+ for (var y = 0; y < height; y++)
+ for (var x = 0; x < width; x++)
+ result[y * width + x] = decoded[y, x];
+ return result;
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/Support/HdrLoader.cs b/BCnEncTests.Framework/Support/HdrLoader.cs
new file mode 100644
index 0000000..51f7d5b
--- /dev/null
+++ b/BCnEncTests.Framework/Support/HdrLoader.cs
@@ -0,0 +1,15 @@
+using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
+
+namespace BCnEncTests.Support
+{
+ public static class HdrLoader
+ {
+ public static HdrImage TestHdrKiara { get; } = HdrImage.Read("../../../../BCnEncTests/testImages/test_hdr_kiara.hdr");
+ public static HdrImage TestHdrProbe { get; } = HdrImage.Read("../../../../BCnEncTests/testImages/test_hdr_probe.hdr");
+ public static DdsFile TestHdrKiaraDds { get; } =
+ DdsLoader.LoadDdsFile("../../../../BCnEncTests/testImages/test_hdr_kiara_bc6h.dds");
+ public static KtxFile TestHdrKiaraKtx { get; } =
+ KtxLoader.LoadKtxFile("../../../../BCnEncTests/testImages/test_hdr_kiara_bc6h_ktx.ktx");
+ }
+}
diff --git a/BCnEncTests.Framework/Support/ImageLoader.cs b/BCnEncTests.Framework/Support/ImageLoader.cs
new file mode 100644
index 0000000..2dc9958
--- /dev/null
+++ b/BCnEncTests.Framework/Support/ImageLoader.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
+using CommunityToolkit.HighPerformance;
+
+namespace BCnEncTests.Support
+{
+ public static class ImageLoader
+ {
+ public static Memory2D TestDiffuse1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_diffuse_1_512.jpg");
+ public static Memory2D TestBlur1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_blur_1_512.jpg");
+ public static Memory2D TestNormal1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_normal_1_512.jpg");
+ public static Memory2D TestHeight1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_height_1_512.jpg");
+ public static Memory2D TestGradient1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_gradient_1_512.jpg");
+
+ public static Memory2D TestTransparentSprite1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_transparent.png");
+ public static Memory2D TestAlphaGradient1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_alphagradient_1_512.png");
+ public static Memory2D TestAlpha1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_alpha_1_512.png");
+ public static Memory2D TestRedGreen1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_red_green_1_64.png");
+ public static Memory2D TestRgbHard1 { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_rgb_hard_1.png");
+ public static Memory2D TestLenna { get; } = LoadTestImage("../../../../BCnEncTests/testImages/test_lenna_512.png");
+ public static Memory2D TestDecodingBc5Reference { get; } = LoadTestImage("../../../../BCnEncTests/testImages/decoding_dds_bc5_reference.png");
+
+ public static Memory2D[] TestCubemap { get; } = {
+ LoadTestImage("../../../../BCnEncTests/testImages/cubemap/right.png"),
+ LoadTestImage("../../../../BCnEncTests/testImages/cubemap/left.png"),
+ LoadTestImage("../../../../BCnEncTests/testImages/cubemap/top.png"),
+ LoadTestImage("../../../../BCnEncTests/testImages/cubemap/bottom.png"),
+ LoadTestImage("../../../../BCnEncTests/testImages/cubemap/back.png"),
+ LoadTestImage("../../../../BCnEncTests/testImages/cubemap/forward.png")
+ };
+
+ internal static Memory2D LoadTestImage(string filename)
+ {
+ using var bmp = new Bitmap(filename);
+ return FromBitmap(bmp);
+ }
+
+ internal static unsafe Memory2D FromBitmap(Bitmap bmp)
+ {
+ var pixels = new ColorRgba32[bmp.Width * bmp.Height];
+ var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
+ ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+ byte* ptr = (byte*)data.Scan0;
+ for (int i = 0; i < pixels.Length; i++)
+ {
+ // GDI+ Format32bppArgb memory order: B,G,R,A
+ pixels[i] = new ColorRgba32(ptr[2], ptr[1], ptr[0], ptr[3]);
+ ptr += 4;
+ }
+ bmp.UnlockBits(data);
+ return pixels.AsMemory().AsMemory2D(bmp.Height, bmp.Width);
+ }
+ }
+
+ public static class DdsLoader
+ {
+ public const string TestDecompressBc1Name = "../../../../BCnEncTests/testImages/test_decompress_bc1.dds";
+ public const string TestDecompressBc1AName = "../../../../BCnEncTests/testImages/test_decompress_bc1a.dds";
+ public const string TestDecompressBc7Name = "../../../../BCnEncTests/testImages/test_decompress_bc7.dds";
+ public const string TestDecompressBc5Name = "../../../../BCnEncTests/testImages/decoding_dds_bc5.dds";
+ public const string TestDecompressRgbaName = "../../../../BCnEncTests/testImages/test_decompress_rgba.dds";
+
+ public static DdsFile TestDecompressBc1 { get; } = LoadDdsFile(TestDecompressBc1Name);
+ public static DdsFile TestDecompressBc1A { get; } = LoadDdsFile(TestDecompressBc1AName);
+ public static DdsFile TestDecompressBc5 { get; } = LoadDdsFile(TestDecompressBc5Name);
+ public static DdsFile TestDecompressBc7 { get; } = LoadDdsFile(TestDecompressBc7Name);
+ public static DdsFile TestDecompressRgba { get; } = LoadDdsFile(TestDecompressRgbaName);
+
+ internal static DdsFile LoadDdsFile(string filename)
+ {
+ using var fs = File.OpenRead(filename);
+ return DdsFile.Load(fs);
+ }
+ }
+
+ public static class KtxLoader
+ {
+ public static KtxFile TestDecompressBc1 { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc1.ktx");
+ public static KtxFile TestDecompressBc1A { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc1a.ktx");
+ public static KtxFile TestDecompressBc2 { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc2.ktx");
+ public static KtxFile TestDecompressBc3 { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc3.ktx");
+ public static KtxFile TestDecompressBc4Unorm { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc4_unorm.ktx");
+ public static KtxFile TestDecompressBc5Unorm { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc5_unorm.ktx");
+ public static KtxFile TestDecompressBc7Rgb { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc7_rgb.ktx");
+ public static KtxFile TestDecompressBc7Types { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc7_types.ktx");
+ public static KtxFile TestDecompressBc7Unorm { get; } = LoadKtxFile("../../../../BCnEncTests/testImages/test_decompress_bc7_unorm.ktx");
+
+ internal static KtxFile LoadKtxFile(string filename)
+ {
+ using var fs = File.OpenRead(filename);
+ return KtxFile.Load(fs);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/Support/TestHelper.cs b/BCnEncTests.Framework/Support/TestHelper.cs
new file mode 100644
index 0000000..0a9bcd1
--- /dev/null
+++ b/BCnEncTests.Framework/Support/TestHelper.cs
@@ -0,0 +1,258 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using BCnEncoder.Decoder;
+using BCnEncoder.Encoder;
+using BCnEncoder.Shared;
+using BCnEncoder.Shared.ImageFiles;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace BCnEncTests.Support
+{
+ public static class TestHelper
+ {
+ #region Assertions
+
+ public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null)
+ {
+ var psnr = ImageQuality.PeakSignalToNoiseRatio(originalPixels, pixels);
+ AssertPSNR(psnr, quality, output);
+ }
+
+ public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null)
+ {
+ var rmse = ImageQuality.CalculateLogRMSE(originalPixels, pixels);
+ AssertRMSE(rmse, quality, output);
+ }
+
+ public static void AssertImagesEqual(Memory2D original, Memory2D image, CompressionQuality quality, bool countAlpha = true)
+ {
+ var psnr = CalculatePSNR(original, image, countAlpha);
+ AssertPSNR(psnr, quality);
+ }
+
+ #endregion
+
+ #region Execute methods
+
+ public static void ExecuteDecodingTest(KtxFile file, string outputFile)
+ {
+ Assert.True(file.header.VerifyHeader());
+ Assert.Equal((uint)1, file.header.NumberOfFaces);
+
+ var decoder = new BcDecoder();
+ var pixels = decoder.Decode2D(file);
+
+ Assert.Equal((uint)pixels.Width, file.header.PixelWidth);
+ Assert.Equal((uint)pixels.Height, file.header.PixelHeight);
+
+ using var outFs = File.OpenWrite(outputFile);
+ SaveAsPng(pixels, outFs);
+ }
+
+ #region Dds
+
+ public static void ExecuteDdsWritingTest(Memory2D image, CompressionFormat format, string outputFile)
+ {
+ ExecuteDdsWritingTest(new[] { image }, format, outputFile);
+ }
+
+ public static void ExecuteDdsWritingTest(Memory2D[] images, CompressionFormat format, string outputFile)
+ {
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ encoder.OutputOptions.GenerateMipMaps = true;
+ encoder.OutputOptions.Format = format;
+ encoder.OutputOptions.FileFormat = OutputFileFormat.Dds;
+
+ using var fs = File.OpenWrite(outputFile);
+
+ if (images.Length == 1)
+ {
+ encoder.EncodeToStream(images[0], fs);
+ }
+ else
+ {
+ encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs);
+ }
+ }
+
+ public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string outputFile, bool assertAlpha = false)
+ {
+ Assert.Equal(format, file.header.ddsPixelFormat.DxgiFormat);
+ Assert.Equal(file.header.dwMipMapCount, (uint)file.Faces[0].MipMaps.Length);
+
+ var decoder = new BcDecoder();
+ decoder.InputOptions.DdsBc1ExpectAlpha = assertAlpha;
+ var images = decoder.DecodeAllMipMaps2D(file);
+
+ Assert.Equal((uint)images[0].Width, file.header.dwWidth);
+ Assert.Equal((uint)images[0].Height, file.header.dwHeight);
+
+ for (var i = 0; i < images.Length; i++)
+ {
+ if (assertAlpha)
+ {
+ var pixels = GetSinglePixelArrayAsColors(images[0]);
+ Assert.Contains(pixels, x => x.a == 0);
+ }
+
+ using var outFs = File.OpenWrite(string.Format(outputFile, i));
+ SaveAsPng(images[i], outFs);
+ }
+ }
+
+ #endregion
+
+ #region Cancellation
+
+ public static async Task ExecuteCancellationTest(Memory2D image, bool isParallel)
+ {
+ var encoder = new BcEncoder(CompressionFormat.Bc7);
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ encoder.Options.IsParallel = isParallel;
+
+ var source = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
+ await Assert.ThrowsAnyAsync(() =>
+ encoder.EncodeToRawBytesAsync(image, source.Token));
+ }
+
+ #endregion
+
+ #endregion
+
+ public static float DecodeKtxCheckPSNR(string filename, Memory2D original)
+ {
+ using var fs = File.OpenRead(filename);
+ var ktx = KtxFile.Load(fs);
+ var decoder = new BcDecoder()
+ {
+ OutputOptions = { Bc4Component = ColorComponent.Luminance }
+ };
+ var decoded = decoder.Decode2D(ktx);
+
+ return CalculatePSNR(original, decoded);
+ }
+
+ public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original)
+ {
+ using var fs = File.OpenRead(filename);
+ var ktx = KtxFile.Load(fs);
+ var decoder = new BcDecoder();
+
+ var decoded = decoder.DecodeHdr(ktx);
+
+ return ImageQuality.CalculateLogRMSE(original.pixels, decoded);
+ }
+
+ public static void ExecuteEncodingTest(Memory2D image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output)
+ {
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = quality;
+ encoder.OutputOptions.GenerateMipMaps = true;
+ encoder.OutputOptions.Format = format;
+
+ var fs = File.OpenWrite(filename);
+ encoder.EncodeToStream(image, fs);
+ fs.Close();
+
+ var psnr = DecodeKtxCheckPSNR(filename, image);
+ output.WriteLine("RGBA PSNR: " + psnr + "db");
+ AssertPSNR(psnr, encoder.OutputOptions.Quality);
+ }
+
+ public static void ExecuteHdrEncodingTest(HdrImage image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output)
+ {
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = quality;
+ encoder.OutputOptions.GenerateMipMaps = true;
+ encoder.OutputOptions.Format = format;
+
+ var fs = File.OpenWrite(filename);
+ encoder.EncodeToStreamHdr(image.pixels.AsMemory().AsMemory2D(image.height, image.width), fs);
+ fs.Close();
+
+ var rmse = DecodeKtxCheckRMSEHdr(filename, image);
+ output.WriteLine("RGBFloat RMSE: " + rmse);
+ AssertRMSE(rmse, encoder.OutputOptions.Quality);
+ }
+
+ private static float CalculatePSNR(Memory2D original, Memory2D decoded, bool countAlpha = true)
+ {
+ var pixels = GetSinglePixelArrayAsColors(original);
+ var pixels2 = GetSinglePixelArrayAsColors(decoded);
+
+ return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, countAlpha);
+ }
+
+ public static void AssertPSNR(float psnr, CompressionQuality quality, ITestOutputHelper output = null)
+ {
+ output?.WriteLine($"PSNR: {psnr} , quality: {quality}");
+ if (quality == CompressionQuality.Fast)
+ {
+ Assert.True(psnr > 25, $"PSNR was less than 25: {psnr} , quality: {quality}");
+ }
+ else
+ {
+ Assert.True(psnr > 30, $"PSNR was less than 30: {psnr} , quality: {quality}");
+ }
+ }
+
+ public static void AssertRMSE(float rmse, CompressionQuality quality, ITestOutputHelper output = null)
+ {
+ output?.WriteLine($"RMSE: {rmse} , quality: {quality}");
+ if (quality == CompressionQuality.Fast)
+ {
+ Assert.True(rmse < 0.1);
+ }
+ else
+ {
+ Assert.True(rmse < 0.04);
+ }
+ }
+
+ public static ColorRgba32[] GetSinglePixelArrayAsColors(ReadOnlyMemory2D image)
+ {
+ var pixels = new ColorRgba32[image.Width * image.Height];
+ var span = image.Span;
+ for (var y = 0; y < image.Height; y++)
+ {
+ for (var x = 0; x < image.Width; x++)
+ {
+ pixels[y * image.Width + x] = span[y, x];
+ }
+ }
+ return pixels;
+ }
+
+ public static unsafe void SaveAsPng(Memory2D image, Stream stream)
+ {
+ int width = image.Width;
+ int height = image.Height;
+ using var bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+ var data = bmp.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+ byte* ptr = (byte*)data.Scan0;
+ var span = image.Span;
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ var c = span[y, x];
+ ptr[0] = c.b;
+ ptr[1] = c.g;
+ ptr[2] = c.r;
+ ptr[3] = c.a;
+ ptr += 4;
+ }
+ }
+ bmp.UnlockBits(data);
+ bmp.Save(stream, ImageFormat.Png);
+ }
+ }
+}
diff --git a/BCnEncTests.Shared/BCnEncTests.Shared.projitems b/BCnEncTests.Shared/BCnEncTests.Shared.projitems
new file mode 100644
index 0000000..f25f88b
--- /dev/null
+++ b/BCnEncTests.Shared/BCnEncTests.Shared.projitems
@@ -0,0 +1,15 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ D954291E-2A0B-460D-934E-DC6B0785DB48
+
+
+
+
+
+
+
+
+
diff --git a/BCnEncTests.Shared/BCnEncTests.Shared.shproj b/BCnEncTests.Shared/BCnEncTests.Shared.shproj
new file mode 100644
index 0000000..be4f3fb
--- /dev/null
+++ b/BCnEncTests.Shared/BCnEncTests.Shared.shproj
@@ -0,0 +1,10 @@
+
+
+
+ {D954291E-2A0B-460D-934E-DC6B0785DB48}
+ 14.0
+
+
+
+
+
diff --git a/BCnEncTests/BlockTests.cs b/BCnEncTests.Shared/BlockTests.cs
similarity index 79%
rename from BCnEncTests/BlockTests.cs
rename to BCnEncTests.Shared/BlockTests.cs
index 98ffc59..14d6621 100644
--- a/BCnEncTests/BlockTests.cs
+++ b/BCnEncTests.Shared/BlockTests.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using BCnEncoder.Shared;
using CommunityToolkit.HighPerformance;
using Xunit;
@@ -36,10 +36,10 @@ public void PaddingColor()
{
var testImage = new ColorRgba32[15, 11];
- var pixels = testImage.AsSpan();
-
- for (var i = 0; i < pixels.Length; i++) {
- pixels[i] = new ColorRgba32(0, 125, 125);
+ for (var y = 0; y < testImage.GetLength(0); y++) {
+ for (var x = 0; x < testImage.GetLength(1); x++) {
+ testImage[y, x] = new ColorRgba32(0, 125, 125);
+ }
}
var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight);
@@ -63,14 +63,14 @@ public void BlocksToImage()
var r = new Random(0);
var testImage = new ColorRgba32[16, 16];
- var pixels = testImage.AsSpan();
-
- for (var i = 0; i < pixels.Length; i++) {
- pixels[i] = new ColorRgba32(
- (byte)r.Next(255),
- (byte)r.Next(255),
- (byte)r.Next(255),
- (byte)r.Next(255));
+ for (var y = 0; y < testImage.GetLength(0); y++) {
+ for (var x = 0; x < testImage.GetLength(1); x++) {
+ testImage[y, x] = new ColorRgba32(
+ (byte)r.Next(255),
+ (byte)r.Next(255),
+ (byte)r.Next(255),
+ (byte)r.Next(255));
+ }
}
var blocks = ImageToBlocks.ImageTo4X4(testImage, out var blocksWidth, out var blocksHeight);
@@ -83,9 +83,11 @@ public void BlocksToImage()
var pixels2 = output.AsSpan();
- Assert.Equal(pixels.Length, pixels2.Length);
- for (var i = 0; i < pixels.Length; i++) {
- Assert.Equal(pixels[i], pixels2[i]);
+ Assert.Equal(testImage.Length, pixels2.Length);
+ for (var y = 0; y < 16; y++) {
+ for (var x = 0; x < 16; x++) {
+ Assert.Equal(testImage[y, x], pixels2[y * 16 + x]);
+ }
}
}
diff --git a/BCnEncTests/ColorTest.cs b/BCnEncTests.Shared/ColorTest.cs
similarity index 100%
rename from BCnEncTests/ColorTest.cs
rename to BCnEncTests.Shared/ColorTest.cs
diff --git a/BCnEncTests/IntHelperTests.cs b/BCnEncTests.Shared/IntHelperTests.cs
similarity index 100%
rename from BCnEncTests/IntHelperTests.cs
rename to BCnEncTests.Shared/IntHelperTests.cs
diff --git a/BCnEncTests/MathHelperTests.cs b/BCnEncTests.Shared/MathHelperTests.cs
similarity index 100%
rename from BCnEncTests/MathHelperTests.cs
rename to BCnEncTests.Shared/MathHelperTests.cs
diff --git a/BCnEncTests/PcaTests.cs b/BCnEncTests.Shared/PcaTests.cs
similarity index 100%
rename from BCnEncTests/PcaTests.cs
rename to BCnEncTests.Shared/PcaTests.cs
diff --git a/BCnEncTests/BCnEncTests.csproj b/BCnEncTests/BCnEncTests.csproj
index 7dab56d..fabc4d1 100644
--- a/BCnEncTests/BCnEncTests.csproj
+++ b/BCnEncTests/BCnEncTests.csproj
@@ -24,4 +24,6 @@
+
+
From 339582ac14db22468bee2ad3d5447e614185a262 Mon Sep 17 00:00:00 2001
From: Nominom <7089998+Nominom@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:49:35 +0200
Subject: [PATCH 04/17] Change test runner to windows
---
.github/workflows/dotnetcore.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml
index 6ac8ac1..96d29b4 100644
--- a/.github/workflows/dotnetcore.yml
+++ b/.github/workflows/dotnetcore.yml
@@ -16,7 +16,7 @@ jobs:
build-and-test:
name: build-and-test
- runs-on: ubuntu-latest
+ runs-on: windows-latest
steps:
- uses: actions/checkout@v3
From 476d4267de19c7e311b6007206bdc017f99487d0 Mon Sep 17 00:00:00 2001
From: Nominom <7089998+Nominom@users.noreply.github.com>
Date: Tue, 3 Mar 2026 13:43:18 +0200
Subject: [PATCH 05/17] Fix bug with decoder not decoding all mipmaps on some
methods
---
BCnEnc.Net/Decoder/BcDecoder.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/BCnEnc.Net/Decoder/BcDecoder.cs b/BCnEnc.Net/Decoder/BcDecoder.cs
index 446c83a..c11ec62 100644
--- a/BCnEnc.Net/Decoder/BcDecoder.cs
+++ b/BCnEnc.Net/Decoder/BcDecoder.cs
@@ -166,7 +166,7 @@ public Task> Decode2DAsync(Stream inputStream, Cancellatio
/// The awaitable operation to retrieve the decoded image.
public Task[]> DecodeAllMipMaps2DAsync(Stream inputStream, CancellationToken token = default)
{
- return Task.Run(() => DecodeFromStreamInternal2D(inputStream, false, token), token);
+ return Task.Run(() => DecodeFromStreamInternal2D(inputStream, true, token), token);
}
///
@@ -675,7 +675,7 @@ public Task> DecodeHdr2DAsync(Stream inputStream, Cancel
/// The awaitable operation to retrieve the decoded image.
public Task[]> DecodeAllMipMapsHdr2DAsync(Stream inputStream, CancellationToken token = default)
{
- return Task.Run(() => DecodeFromStreamInternalHdr2D(inputStream, false, token), token);
+ return Task.Run(() => DecodeFromStreamInternalHdr2D(inputStream, true, token), token);
}
///
From ae862fbf17bdadafd0665a8312590de77b7f07ca Mon Sep 17 00:00:00 2001
From: Nominom <7089998+Nominom@users.noreply.github.com>
Date: Tue, 3 Mar 2026 13:43:27 +0200
Subject: [PATCH 06/17] Remove extra comment
---
BCnEnc.Net/Shared/MipMapper.cs | 2 --
1 file changed, 2 deletions(-)
diff --git a/BCnEnc.Net/Shared/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs
index 746ac29..ddbcea7 100644
--- a/BCnEnc.Net/Shared/MipMapper.cs
+++ b/BCnEnc.Net/Shared/MipMapper.cs
@@ -171,7 +171,6 @@ private static Memory2D ResizeToHalf(ReadOnlySpan2D pi
var newWidth = Math.Max(1, oldWidth >> 1);
var newHeight = Math.Max(1, oldHeight >> 1);
- // Use a 1D backing array so TryGetMemory() succeeds on all target frameworks
var result = new ColorRgba32[newHeight * newWidth];
int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x));
@@ -200,7 +199,6 @@ private static Memory2D ResizeToHalf(ReadOnlySpan2D> 1);
var newHeight = Math.Max(1, oldHeight >> 1);
- // Use a 1D backing array so TryGetMemory() succeeds on all target frameworks
var result = new ColorRgbFloat[newHeight * newWidth];
int ClampW(int x) => Math.Max(0, Math.Min(oldWidth - 1, x));
From 6eb5f4b0b9611cb8dc935a5aefb4d0e36bf59cc0 Mon Sep 17 00:00:00 2001
From: Nominom <7089998+Nominom@users.noreply.github.com>
Date: Tue, 3 Mar 2026 14:10:50 +0200
Subject: [PATCH 07/17] Move most tests to shared test project
---
BCnEncTests.Framework/MipMapperTests.cs | 83 +++
BCnEncTests.Framework/Support/TestHelper.cs | 15 +
.../AtcTests.cs | 0
.../BC1Tests.cs | 0
.../BCnEncTests.Shared.projitems | 19 +
.../Bc5Tests.cs | 0
.../Bc6EncoderTests.cs | 2 +-
.../Bc6HDecoderTests.cs | 20 +-
.../Bc7BlockTests.cs | 5 +-
.../BgraTests.cs | 0
.../CancellationTests.cs | 0
.../ClusterTests.cs | 3 +-
.../DdsReadTests.cs | 0
.../DdsWritingTests.cs | 0
.../DecodingAsyncTests.cs | 3 +-
.../DecodingTests.cs | 0
.../EncoderOptionsTests.cs | 0
.../EncodingAsyncTest.cs | 6 +-
.../EncodingTest.cs | 0
.../ProgressTests.cs | 0
.../RawTests.cs | 48 +-
.../SingleBlockTests.cs | 0
BCnEncTests/AtcTests.cs | 108 ----
BCnEncTests/BC1Tests.cs | 164 ------
BCnEncTests/BCnEncTests.csproj | 1 +
BCnEncTests/Bc5Tests.cs | 84 ---
BCnEncTests/Bc6EncoderTests.cs | 405 --------------
BCnEncTests/Bc6HDecoderTests.cs | 178 -------
BCnEncTests/Bc7BlockTests.cs | 389 --------------
BCnEncTests/BgraTests.cs | 78 ---
BCnEncTests/CancellationTests.cs | 28 -
BCnEncTests/ClusterTests.cs | 52 --
BCnEncTests/DdsReadTests.cs | 54 --
BCnEncTests/DdsWritingTests.cs | 57 --
BCnEncTests/DecodingAsyncTests.cs | 62 ---
BCnEncTests/DecodingTests.cs | 62 ---
BCnEncTests/EncodeByteOrderTests.cs | 2 +-
BCnEncTests/EncoderOptionsTests.cs | 71 ---
BCnEncTests/EncodingAsyncTest.cs | 88 ---
BCnEncTests/EncodingTest.cs | 501 ------------------
BCnEncTests/ProgressTests.cs | 392 --------------
BCnEncTests/RawTests.cs | 106 ----
BCnEncTests/SingleBlockTests.cs | 143 -----
BCnEncTests/Support/HdrLoader.cs | 2 +-
BCnEncTests/Support/ImageLoader.cs | 47 +-
BCnEncTests/Support/TestHelper.cs | 107 +++-
46 files changed, 269 insertions(+), 3116 deletions(-)
create mode 100644 BCnEncTests.Framework/MipMapperTests.cs
rename {BCnEncTests.Framework => BCnEncTests.Shared}/AtcTests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/BC1Tests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/Bc5Tests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/Bc6EncoderTests.cs (98%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/Bc6HDecoderTests.cs (84%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/Bc7BlockTests.cs (99%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/BgraTests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/CancellationTests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/ClusterTests.cs (91%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/DdsReadTests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/DdsWritingTests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/DecodingAsyncTests.cs (93%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/DecodingTests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/EncoderOptionsTests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/EncodingAsyncTest.cs (90%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/EncodingTest.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/ProgressTests.cs (100%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/RawTests.cs (70%)
rename {BCnEncTests.Framework => BCnEncTests.Shared}/SingleBlockTests.cs (100%)
delete mode 100644 BCnEncTests/AtcTests.cs
delete mode 100644 BCnEncTests/BC1Tests.cs
delete mode 100644 BCnEncTests/Bc5Tests.cs
delete mode 100644 BCnEncTests/Bc6EncoderTests.cs
delete mode 100644 BCnEncTests/Bc6HDecoderTests.cs
delete mode 100644 BCnEncTests/Bc7BlockTests.cs
delete mode 100644 BCnEncTests/BgraTests.cs
delete mode 100644 BCnEncTests/CancellationTests.cs
delete mode 100644 BCnEncTests/ClusterTests.cs
delete mode 100644 BCnEncTests/DdsReadTests.cs
delete mode 100644 BCnEncTests/DdsWritingTests.cs
delete mode 100644 BCnEncTests/DecodingAsyncTests.cs
delete mode 100644 BCnEncTests/DecodingTests.cs
delete mode 100644 BCnEncTests/EncoderOptionsTests.cs
delete mode 100644 BCnEncTests/EncodingAsyncTest.cs
delete mode 100644 BCnEncTests/EncodingTest.cs
delete mode 100644 BCnEncTests/ProgressTests.cs
delete mode 100644 BCnEncTests/RawTests.cs
delete mode 100644 BCnEncTests/SingleBlockTests.cs
diff --git a/BCnEncTests.Framework/MipMapperTests.cs b/BCnEncTests.Framework/MipMapperTests.cs
new file mode 100644
index 0000000..8b9e292
--- /dev/null
+++ b/BCnEncTests.Framework/MipMapperTests.cs
@@ -0,0 +1,83 @@
+using System;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using Xunit;
+
+namespace BCnEncTests
+{
+ public class MipMapperTests
+ {
+ [Fact]
+ public void MipChainHasCorrectDimensions()
+ {
+ var image = ImageLoader.TestGradient1; // 512x416
+ var numMips = 0;
+ var chain = MipMapper.GenerateMipChain(image, ref numMips);
+
+ Assert.Equal(chain.Length, numMips);
+ Assert.True(numMips > 1);
+
+ for (var i = 0; i < numMips; i++)
+ {
+ Assert.Equal(Math.Max(1, image.Width >> i), chain[i].Width);
+ Assert.Equal(Math.Max(1, image.Height >> i), chain[i].Height);
+ }
+
+ // Last level must be 1x1
+ Assert.Equal(1, chain[numMips - 1].Width);
+ Assert.Equal(1, chain[numMips - 1].Height);
+ }
+
+ ///
+ /// A solid-color image must downsample to exactly the same color at
+ /// every mip level, regardless of how many times the box filter is applied.
+ ///
+ [Fact]
+ public void SolidColorMipChainIsExact()
+ {
+ var color = new ColorRgba32(200, 100, 50, 255);
+ var pixels = new ColorRgba32[16 * 16];
+ for (var i = 0; i < pixels.Length; i++) pixels[i] = color;
+
+ var image = new Memory2D(pixels, 16, 16);
+ var numMips = 0;
+ var chain = MipMapper.GenerateMipChain(image, ref numMips);
+
+ // 16x16 -> 8x8 -> 4x4 -> 2x2 -> 1x1 = 5 levels
+ Assert.Equal(5, numMips);
+
+ for (var level = 0; level < numMips; level++)
+ {
+ var span = chain[level].Span;
+ for (var y = 0; y < chain[level].Height; y++)
+ for (var x = 0; x < chain[level].Width; x++)
+ Assert.Equal(color, span[y, x]);
+ }
+ }
+
+ ///
+ /// Verifies the exact 2x2 box-filter arithmetic with a known input.
+ /// A 2x2 image of two values repeated both rows should average exactly.
+ ///
+ [Fact]
+ public void KnownPatternDownsamplesCorrectly()
+ {
+ // 2-wide, 2-tall: left column = 100 red, right column = 200 red.
+ // Expected 1x1 average: r = (100+200+100+200)/4 = 150, g=b=0, a=255.
+ var pixels = new[]
+ {
+ new ColorRgba32(100, 0, 0, 255), new ColorRgba32(200, 0, 0, 255),
+ new ColorRgba32(100, 0, 0, 255), new ColorRgba32(200, 0, 0, 255),
+ };
+ var image = new Memory2D(pixels, 2, 2);
+ var numMips = 0;
+ var chain = MipMapper.GenerateMipChain(image, ref numMips);
+
+ Assert.Equal(2, numMips); // 2x2 -> 1x1
+ Assert.Equal(1, chain[1].Width);
+ Assert.Equal(1, chain[1].Height);
+ Assert.Equal(new ColorRgba32(150, 0, 0, 255), chain[1].Span[0, 0]);
+ }
+ }
+}
diff --git a/BCnEncTests.Framework/Support/TestHelper.cs b/BCnEncTests.Framework/Support/TestHelper.cs
index 0a9bcd1..e5e2280 100644
--- a/BCnEncTests.Framework/Support/TestHelper.cs
+++ b/BCnEncTests.Framework/Support/TestHelper.cs
@@ -254,5 +254,20 @@ public static unsafe void SaveAsPng(Memory2D image, Stream stream)
bmp.UnlockBits(data);
bmp.Save(stream, ImageFormat.Png);
}
+
+ public static void SaveAsPng(ColorRgbFloat[] pixels, int width, int height, Stream stream)
+ {
+ var rgba = new ColorRgba32[pixels.Length];
+ for (var i = 0; i < pixels.Length; i++)
+ {
+ var p = pixels[i];
+ byte r = (byte)(Math.Max(0, Math.Min(1, p.r)) * 255 + 0.5f);
+ byte g = (byte)(Math.Max(0, Math.Min(1, p.g)) * 255 + 0.5f);
+ byte b = (byte)(Math.Max(0, Math.Min(1, p.b)) * 255 + 0.5f);
+ rgba[i] = new ColorRgba32(r, g, b, 255);
+ }
+ var mem = new Memory2D(rgba, height, width);
+ SaveAsPng(mem, stream);
+ }
}
}
diff --git a/BCnEncTests.Framework/AtcTests.cs b/BCnEncTests.Shared/AtcTests.cs
similarity index 100%
rename from BCnEncTests.Framework/AtcTests.cs
rename to BCnEncTests.Shared/AtcTests.cs
diff --git a/BCnEncTests.Framework/BC1Tests.cs b/BCnEncTests.Shared/BC1Tests.cs
similarity index 100%
rename from BCnEncTests.Framework/BC1Tests.cs
rename to BCnEncTests.Shared/BC1Tests.cs
diff --git a/BCnEncTests.Shared/BCnEncTests.Shared.projitems b/BCnEncTests.Shared/BCnEncTests.Shared.projitems
index f25f88b..a221845 100644
--- a/BCnEncTests.Shared/BCnEncTests.Shared.projitems
+++ b/BCnEncTests.Shared/BCnEncTests.Shared.projitems
@@ -6,10 +6,29 @@
D954291E-2A0B-460D-934E-DC6B0785DB48
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BCnEncTests.Framework/Bc5Tests.cs b/BCnEncTests.Shared/Bc5Tests.cs
similarity index 100%
rename from BCnEncTests.Framework/Bc5Tests.cs
rename to BCnEncTests.Shared/Bc5Tests.cs
diff --git a/BCnEncTests.Framework/Bc6EncoderTests.cs b/BCnEncTests.Shared/Bc6EncoderTests.cs
similarity index 98%
rename from BCnEncTests.Framework/Bc6EncoderTests.cs
rename to BCnEncTests.Shared/Bc6EncoderTests.cs
index 5fb1f5d..6a5bcad 100644
--- a/BCnEncTests.Framework/Bc6EncoderTests.cs
+++ b/BCnEncTests.Shared/Bc6EncoderTests.cs
@@ -265,7 +265,7 @@ public void Encode()
{
var signed = true;
var image = HdrLoader.TestHdrKiara;
- var blocks = ImageToBlocks.ImageTo4X4(image.pixels.AsMemory().AsMemory2D(image.height, image.width), out var bW, out var bH);
+ var blocks = ImageToBlocks.ImageTo4X4(new Memory2D(image.pixels, image.height, image.width), out var bW, out var bH);
for (var i = 0; i < blocks.Length; i++)
{
diff --git a/BCnEncTests.Framework/Bc6HDecoderTests.cs b/BCnEncTests.Shared/Bc6HDecoderTests.cs
similarity index 84%
rename from BCnEncTests.Framework/Bc6HDecoderTests.cs
rename to BCnEncTests.Shared/Bc6HDecoderTests.cs
index 0453379..75aa7c3 100644
--- a/BCnEncTests.Framework/Bc6HDecoderTests.cs
+++ b/BCnEncTests.Shared/Bc6HDecoderTests.cs
@@ -24,8 +24,9 @@ public void DecodeDds()
var decoder = new BcDecoder();
var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds);
- var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraDds.header.dwWidth,
- (int)HdrLoader.TestHdrKiaraDds.header.dwHeight);
+ var width = (int)HdrLoader.TestHdrKiaraDds.header.dwWidth;
+ var height = (int)HdrLoader.TestHdrKiaraDds.header.dwHeight;
+ var hdr = new HdrImage(width, height);
Assert.Equal(hdr.pixels.Length, decoded.Length);
@@ -33,6 +34,9 @@ public void DecodeDds()
using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr");
hdr.Write(sfs);
+ using var pngFs = File.OpenWrite("decoding_test_dds_bc6h.png");
+ TestHelper.SaveAsPng(decoded, width, height, pngFs);
+
TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output);
}
@@ -42,8 +46,9 @@ public void DecodeKtx()
var decoder = new BcDecoder();
var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraKtx);
- var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth,
- (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight);
+ var width = (int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth;
+ var height = (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight;
+ var hdr = new HdrImage(width, height);
Assert.Equal(hdr.pixels.Length, decoded.Length);
@@ -51,6 +56,9 @@ public void DecodeKtx()
using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr");
hdr.Write(sfs);
+ using var pngFs = File.OpenWrite("decoding_test_ktx_bc6h.png");
+ TestHelper.SaveAsPng(decoded, width, height, pngFs);
+
TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.BestQuality, output);
}
@@ -63,7 +71,11 @@ public void AllBlocksDecodesExact()
var decoder = new BcDecoder();
var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds);
+#if NETCOREAPP
+ using var fs = File.OpenRead("../../../testImages/test_hdr_kiara_dds_float16_data.bin");
+#else
using var fs = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin");
+#endif
using var ms = new MemoryStream();
fs.CopyTo(ms);
var length = (int)ms.Position;
diff --git a/BCnEncTests.Framework/Bc7BlockTests.cs b/BCnEncTests.Shared/Bc7BlockTests.cs
similarity index 99%
rename from BCnEncTests.Framework/Bc7BlockTests.cs
rename to BCnEncTests.Shared/Bc7BlockTests.cs
index c356fb7..a2904da 100644
--- a/BCnEncTests.Framework/Bc7BlockTests.cs
+++ b/BCnEncTests.Shared/Bc7BlockTests.cs
@@ -6,9 +6,10 @@
using BCnEncoder.Shared;
using BCnEncoder.Shared.ImageFiles;
using BCnEncTests.Support;
-using CommunityToolkit.HighPerformance;
using Xunit;
+using CommunityToolkit.HighPerformance;
+
namespace BCnEncTests
{
public class Bc7BlockTests
@@ -85,7 +86,7 @@ public void DecodeErrorBlock()
var pixels = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc7);
Assert.Contains(new ColorRgba32(255, 0, 255), pixels);
- var decoded = pixels.AsMemory().AsMemory2D(height * 4, width * 4);
+ var decoded = new Memory2D(pixels, height * 4, width * 4);
using var fs = File.OpenWrite("test_decode_bc7_error.png");
TestHelper.SaveAsPng(decoded, fs);
}
diff --git a/BCnEncTests.Framework/BgraTests.cs b/BCnEncTests.Shared/BgraTests.cs
similarity index 100%
rename from BCnEncTests.Framework/BgraTests.cs
rename to BCnEncTests.Shared/BgraTests.cs
diff --git a/BCnEncTests.Framework/CancellationTests.cs b/BCnEncTests.Shared/CancellationTests.cs
similarity index 100%
rename from BCnEncTests.Framework/CancellationTests.cs
rename to BCnEncTests.Shared/CancellationTests.cs
diff --git a/BCnEncTests.Framework/ClusterTests.cs b/BCnEncTests.Shared/ClusterTests.cs
similarity index 91%
rename from BCnEncTests.Framework/ClusterTests.cs
rename to BCnEncTests.Shared/ClusterTests.cs
index c141928..d8c20b8 100644
--- a/BCnEncTests.Framework/ClusterTests.cs
+++ b/BCnEncTests.Shared/ClusterTests.cs
@@ -2,6 +2,7 @@
using System.IO;
using BCnEncoder.Shared;
using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
using Xunit;
namespace BCnEncTests
@@ -44,7 +45,7 @@ public void Clusterize()
pixels[i] = pixC[clusters[i]].ToColorRgba32();
}
- var result = pixels.AsMemory().AsMemory2D(height, width);
+ var result = new Memory2D(pixels, height, width);
using var fs = File.OpenWrite("test_cluster.png");
TestHelper.SaveAsPng(result, fs);
}
diff --git a/BCnEncTests.Framework/DdsReadTests.cs b/BCnEncTests.Shared/DdsReadTests.cs
similarity index 100%
rename from BCnEncTests.Framework/DdsReadTests.cs
rename to BCnEncTests.Shared/DdsReadTests.cs
diff --git a/BCnEncTests.Framework/DdsWritingTests.cs b/BCnEncTests.Shared/DdsWritingTests.cs
similarity index 100%
rename from BCnEncTests.Framework/DdsWritingTests.cs
rename to BCnEncTests.Shared/DdsWritingTests.cs
diff --git a/BCnEncTests.Framework/DecodingAsyncTests.cs b/BCnEncTests.Shared/DecodingAsyncTests.cs
similarity index 93%
rename from BCnEncTests.Framework/DecodingAsyncTests.cs
rename to BCnEncTests.Shared/DecodingAsyncTests.cs
index da61e5e..cba775b 100644
--- a/BCnEncTests.Framework/DecodingAsyncTests.cs
+++ b/BCnEncTests.Shared/DecodingAsyncTests.cs
@@ -5,6 +5,7 @@
using BCnEncoder.Encoder;
using BCnEncoder.Shared;
using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
using Xunit;
namespace BCnEncTests
@@ -53,7 +54,7 @@ public async Task DecodeRawAsync()
var decoded = await Task.Run(() =>
{
var pixels = decoder.DecodeRaw(rawBytes, mipWidth, mipHeight, CompressionFormat.Bc1);
- return pixels.AsMemory().AsMemory2D(mipHeight, mipWidth);
+ return new Memory2D(pixels, mipHeight, mipWidth);
});
TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
diff --git a/BCnEncTests.Framework/DecodingTests.cs b/BCnEncTests.Shared/DecodingTests.cs
similarity index 100%
rename from BCnEncTests.Framework/DecodingTests.cs
rename to BCnEncTests.Shared/DecodingTests.cs
diff --git a/BCnEncTests.Framework/EncoderOptionsTests.cs b/BCnEncTests.Shared/EncoderOptionsTests.cs
similarity index 100%
rename from BCnEncTests.Framework/EncoderOptionsTests.cs
rename to BCnEncTests.Shared/EncoderOptionsTests.cs
diff --git a/BCnEncTests.Framework/EncodingAsyncTest.cs b/BCnEncTests.Shared/EncodingAsyncTest.cs
similarity index 90%
rename from BCnEncTests.Framework/EncodingAsyncTest.cs
rename to BCnEncTests.Shared/EncodingAsyncTest.cs
index a936b7d..88cfb3f 100644
--- a/BCnEncTests.Framework/EncodingAsyncTest.cs
+++ b/BCnEncTests.Shared/EncodingAsyncTest.cs
@@ -52,7 +52,7 @@ public async Task EncodeCubemapToDdsAsync()
{
var rawPixels = decoder.DecodeRaw(file.Faces[i].MipMaps[0].Data,
(int)file.Faces[i].Width, (int)file.Faces[i].Height, CompressionFormat.Bc1);
- var decodedImage = rawPixels.AsMemory().AsMemory2D((int)file.Faces[i].Height, (int)file.Faces[i].Width);
+ var decodedImage = new Memory2D(rawPixels, (int)file.Faces[i].Height, (int)file.Faces[i].Width);
TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality);
}
@@ -68,7 +68,7 @@ public async Task EncodeCubemapToKtxAsync()
{
var rawPixels = decoder.DecodeRaw(file.MipMaps[0].Faces[i].Data,
(int)file.MipMaps[0].Faces[i].Width, (int)file.MipMaps[0].Faces[i].Height, CompressionFormat.Bc1);
- var decodedImage = rawPixels.AsMemory().AsMemory2D(
+ var decodedImage = new Memory2D(rawPixels,
(int)file.MipMaps[0].Faces[i].Height, (int)file.MipMaps[0].Faces[i].Width);
TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality);
@@ -80,7 +80,7 @@ public async Task EncodeToRawBytesAsync()
{
var data = await encoder.EncodeToRawBytesAsync(originalImage);
var rawPixels = decoder.DecodeRaw(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1);
- var image = rawPixels.AsMemory().AsMemory2D(originalImage.Height, originalImage.Width);
+ var image = new Memory2D(rawPixels, originalImage.Height, originalImage.Width);
TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
}
diff --git a/BCnEncTests.Framework/EncodingTest.cs b/BCnEncTests.Shared/EncodingTest.cs
similarity index 100%
rename from BCnEncTests.Framework/EncodingTest.cs
rename to BCnEncTests.Shared/EncodingTest.cs
diff --git a/BCnEncTests.Framework/ProgressTests.cs b/BCnEncTests.Shared/ProgressTests.cs
similarity index 100%
rename from BCnEncTests.Framework/ProgressTests.cs
rename to BCnEncTests.Shared/ProgressTests.cs
diff --git a/BCnEncTests.Framework/RawTests.cs b/BCnEncTests.Shared/RawTests.cs
similarity index 70%
rename from BCnEncTests.Framework/RawTests.cs
rename to BCnEncTests.Shared/RawTests.cs
index d2a572a..e19d3f9 100644
--- a/BCnEncTests.Framework/RawTests.cs
+++ b/BCnEncTests.Shared/RawTests.cs
@@ -1,6 +1,4 @@
using System;
-using System.Drawing;
-using System.Drawing.Imaging;
using System.IO;
using BCnEncoder.Decoder;
using BCnEncoder.Encoder;
@@ -25,7 +23,7 @@ public void EncodeDecode()
var encodedRawBytes = encoder.EncodeToRawBytes(inputImage);
var decodedPixels = decoder.DecodeRaw(encodedRawBytes[0], inputImage.Width, inputImage.Height, CompressionFormat.Bc1);
- var decodedImage = decodedPixels.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width);
+ var decodedImage = new Memory2D(decodedPixels, inputImage.Height, inputImage.Width);
var originalColors = TestHelper.GetSinglePixelArrayAsColors(inputImage);
var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage);
@@ -50,7 +48,7 @@ public void EncodeDecodeStream()
Assert.Equal(0, ms.Position);
var rawPixels = decoder.DecodeRaw(ms, inputImage.Width, inputImage.Height, CompressionFormat.Bc1);
- var decodedImage = rawPixels.AsMemory().AsMemory2D(inputImage.Height, inputImage.Width);
+ var decodedImage = new Memory2D(rawPixels, inputImage.Height, inputImage.Width);
var originalColors = TestHelper.GetSinglePixelArrayAsColors(inputImage);
var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage);
@@ -87,11 +85,12 @@ public void EncodeDecodeAllMipMapsStream()
ms.Position = 0;
Assert.Equal(0, ms.Position);
+ var resized = ResizeImageToMips(inputImage);
+
for (var i = 0; i < mipLevels; i++)
{
encoder.CalculateMipMapSize(inputImage.Width, inputImage.Height, i, out var mipWidth, out var mipHeight);
- var resized = ResizeImage(inputImage, mipWidth, mipHeight);
var blockSize = decoder.GetBlockSize(CompressionFormat.Bc1);
var blockCount = decoder.GetBlockCount(mipWidth, mipHeight);
@@ -99,9 +98,9 @@ public void EncodeDecodeAllMipMapsStream()
ms.Read(buffer, 0, buffer.Length);
var rawPixels = decoder.DecodeRaw(buffer, mipWidth, mipHeight, CompressionFormat.Bc1);
- var decodedImage = rawPixels.AsMemory().AsMemory2D(mipHeight, mipWidth);
+ var decodedImage = new Memory2D(rawPixels, mipHeight, mipWidth);
- var originalColors = TestHelper.GetSinglePixelArrayAsColors(resized);
+ var originalColors = TestHelper.GetSinglePixelArrayAsColors(resized[i]);
var decodedColors = TestHelper.GetSinglePixelArrayAsColors(decodedImage);
TestHelper.AssertPixelsEqual(originalColors, decodedColors, encoder.OutputOptions.Quality);
@@ -112,42 +111,11 @@ public void EncodeDecodeAllMipMapsStream()
Assert.Equal(1, lastMHeight);
}
- private static ReadOnlyMemory2D ResizeImage(Memory2D image, int newWidth, int newHeight)
+ private static ReadOnlyMemory2D[] ResizeImageToMips(Memory2D image)
{
int numMips = 0;
var chain = MipMapper.GenerateMipChain(image, ref numMips);
-
- foreach (var memory2D in chain)
- {
- if (memory2D.Width == newWidth && memory2D.Height == newHeight)
- {
- return memory2D;
- }
- }
- throw new InvalidOperationException("Cannot resize image to non-mip dimensions");
- }
-
- private static unsafe Bitmap ToBitmap(Memory2D image)
- {
- var bmp = new Bitmap(image.Width, image.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
- var data = bmp.LockBits(new Rectangle(0, 0, image.Width, image.Height),
- ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
- byte* ptr = (byte*)data.Scan0;
- var span = image.Span;
- for (int y = 0; y < image.Height; y++)
- {
- for (int x = 0; x < image.Width; x++)
- {
- var c = span[y, x];
- ptr[0] = c.b;
- ptr[1] = c.g;
- ptr[2] = c.r;
- ptr[3] = c.a;
- ptr += 4;
- }
- }
- bmp.UnlockBits(data);
- return bmp;
+ return chain;
}
}
}
diff --git a/BCnEncTests.Framework/SingleBlockTests.cs b/BCnEncTests.Shared/SingleBlockTests.cs
similarity index 100%
rename from BCnEncTests.Framework/SingleBlockTests.cs
rename to BCnEncTests.Shared/SingleBlockTests.cs
diff --git a/BCnEncTests/AtcTests.cs b/BCnEncTests/AtcTests.cs
deleted file mode 100644
index d02da3e..0000000
--- a/BCnEncTests/AtcTests.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using BCnEncoder.Decoder;
-using BCnEncoder.Encoder;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using Xunit;
-using BCnEncoder.ImageSharp;
-
-namespace BCnEncTests
-{
- public class AtcTests
- {
- [Fact]
- public void AtcKtxDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.Atc);
- var original = ImageLoader.TestLenna;
-
- // Act
- var ktx = encoder.EncodeToKtx(original);
- var image = decoder.DecodeToImageRgba32(ktx);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void AtcDdsDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.Atc);
- var original = ImageLoader.TestLenna;
-
- // Act
- var dds = encoder.EncodeToDds(original);
- var image = decoder.DecodeToImageRgba32(dds);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void AtcExplicitKtxDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha);
- var original = ImageLoader.TestAlphaGradient1;
-
- // Act
- var ktx = encoder.EncodeToKtx(original);
- var image = decoder.DecodeToImageRgba32(ktx);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void AtcExplicitDdsDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.AtcExplicitAlpha);
- var original = ImageLoader.TestAlphaGradient1;
-
- // Act
- var dds = encoder.EncodeToDds(original);
- var image = decoder.DecodeToImageRgba32(dds);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void AtcInterpolatedKtxDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha);
- var original = ImageLoader.TestAlphaGradient1;
-
- // Act
- var ktx = encoder.EncodeToKtx(original);
- var image = decoder.DecodeToImageRgba32(ktx);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void AtcInterpolatedDdsDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.AtcInterpolatedAlpha);
- var original = ImageLoader.TestAlphaGradient1;
-
- // Act
- var dds = encoder.EncodeToDds(original);
- var image = decoder.DecodeToImageRgba32(dds);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
- }
-}
diff --git a/BCnEncTests/BC1Tests.cs b/BCnEncTests/BC1Tests.cs
deleted file mode 100644
index bfc7320..0000000
--- a/BCnEncTests/BC1Tests.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-using BCnEncoder.Shared;
-using SixLabors.ImageSharp.PixelFormats;
-using Xunit;
-using BCnEncoder.ImageSharp;
-
-namespace BCnEncTests
-{
- public class Bc1Tests
- {
-
- [Fact]
- public void Decode() {
- var block = new Bc1Block
- {
- color0 = new ColorRgb565(255, 255, 255),
- color1 = new ColorRgb565(0, 0, 0)
- };
-
- Assert.False(block.HasAlphaOrBlack);
- block[0] = 1;
- block[1] = 1;
- block[2] = 1;
- block[3] = 1;
-
- block[4] = 3;
- block[5] = 3;
- block[6] = 3;
- block[7] = 3;
-
- block[8] = 2;
- block[9] = 2;
- block[10] = 2;
- block[11] = 2;
-
- block[12] = 0;
- block[13] = 0;
- block[14] = 0;
- block[15] = 0;
-
- var raw = block.Decode(false);
- Assert.Equal(new ColorRgba32(0, 0, 0), raw.p00);
- Assert.Equal(new ColorRgba32(0, 0, 0), raw.p10);
- Assert.Equal(new ColorRgba32(0, 0, 0), raw.p20);
- Assert.Equal(new ColorRgba32(0, 0, 0), raw.p30);
-
- Assert.Equal(new ColorRgba32(85, 85, 85), raw.p01);
- Assert.Equal(new ColorRgba32(85, 85, 85), raw.p11);
- Assert.Equal(new ColorRgba32(85, 85, 85), raw.p21);
- Assert.Equal(new ColorRgba32(85, 85, 85), raw.p31);
-
- Assert.Equal(new ColorRgba32(170, 170, 170), raw.p02);
- Assert.Equal(new ColorRgba32(170, 170, 170), raw.p12);
- Assert.Equal(new ColorRgba32(170, 170, 170), raw.p22);
- Assert.Equal(new ColorRgba32(170, 170, 170), raw.p32);
-
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33);
- }
-
- [Fact]
- public void DecodeBlack() {
- var block = new Bc1Block
- {
- color0 = new ColorRgb565(200, 200, 200),
- color1 = new ColorRgb565(255, 255, 255)
- };
-
- Assert.True(block.HasAlphaOrBlack);
- block[0] = 0;
- block[1] = 0;
- block[2] = 0;
- block[3] = 0;
-
- block[4] = 3;
- block[5] = 3;
- block[6] = 3;
- block[7] = 3;
-
- block[8] = 2;
- block[9] = 2;
- block[10] = 2;
- block[11] = 2;
-
- block[12] = 1;
- block[13] = 1;
- block[14] = 1;
- block[15] = 1;
-
- var raw = block.Decode(false);
- Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00);
- Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10);
- Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20);
- Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30);
-
- Assert.Equal(new ColorRgba32(0, 0, 0), raw.p01);
- Assert.Equal(new ColorRgba32(0, 0, 0), raw.p11);
- Assert.Equal(new ColorRgba32(0, 0, 0), raw.p21);
- Assert.Equal(new ColorRgba32(0, 0, 0), raw.p31);
-
- Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02);
- Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12);
- Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22);
- Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32);
-
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33);
- }
-
- [Fact]
- public void DecodeAlpha() {
- var block = new Bc1Block
- {
- color0 = new ColorRgb565(200, 200, 200),
- color1 = new ColorRgb565(255, 255, 255)
- };
-
- Assert.True(block.HasAlphaOrBlack);
- block[0] = 0;
- block[1] = 0;
- block[2] = 0;
- block[3] = 0;
-
- block[4] = 3;
- block[5] = 3;
- block[6] = 3;
- block[7] = 3;
-
- block[8] = 2;
- block[9] = 2;
- block[10] = 2;
- block[11] = 2;
-
- block[12] = 1;
- block[13] = 1;
- block[14] = 1;
- block[15] = 1;
-
- var raw = block.Decode(true);
- Assert.Equal(new ColorRgba32(206, 203, 206), raw.p00);
- Assert.Equal(new ColorRgba32(206, 203, 206), raw.p10);
- Assert.Equal(new ColorRgba32(206, 203, 206), raw.p20);
- Assert.Equal(new ColorRgba32(206, 203, 206), raw.p30);
-
- Assert.Equal(new ColorRgba32(0,0,0,0), raw.p01);
- Assert.Equal(new ColorRgba32(0,0,0,0), raw.p11);
- Assert.Equal(new ColorRgba32(0,0,0,0), raw.p21);
- Assert.Equal(new ColorRgba32(0,0,0,0), raw.p31);
-
- Assert.Equal(new ColorRgba32(230, 229, 230), raw.p02);
- Assert.Equal(new ColorRgba32(230, 229, 230), raw.p12);
- Assert.Equal(new ColorRgba32(230, 229, 230), raw.p22);
- Assert.Equal(new ColorRgba32(230, 229, 230), raw.p32);
-
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p03);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p13);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p23);
- Assert.Equal(new ColorRgba32(255, 255, 255), raw.p33);
- }
- }
-}
diff --git a/BCnEncTests/BCnEncTests.csproj b/BCnEncTests/BCnEncTests.csproj
index fabc4d1..74aa454 100644
--- a/BCnEncTests/BCnEncTests.csproj
+++ b/BCnEncTests/BCnEncTests.csproj
@@ -7,6 +7,7 @@
+
diff --git a/BCnEncTests/Bc5Tests.cs b/BCnEncTests/Bc5Tests.cs
deleted file mode 100644
index 7eb37ff..0000000
--- a/BCnEncTests/Bc5Tests.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using BCnEncoder.Decoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class Bc5Tests
- {
- [Fact]
- public void Bc5Indices()
- {
- var block = new Bc5Block();
-
- for (var i = 0; i < 16; i++)
- {
- block.SetRedIndex(i, (byte)(i % 8));
- block.SetGreenIndex(i, (byte)((i + 3) % 8));
- }
-
- for (var i = 0; i < 16; i++)
- {
- int rI = block.GetRedIndex(i);
- int gI = block.GetGreenIndex(i);
-
- Assert.Equal((byte)(i % 8), rI);
- Assert.Equal((byte)((i + 3) % 8), gI);
- }
- }
-
- [Fact]
- public void Bc5DdsDecode()
- {
- var reference = ImageLoader.TestDecodingBc5Reference;
- var decoded = new BcDecoder().DecodeToImageRgba32(DdsLoader.TestDecompressBc5);
-
- var refSpan = TestHelper.GetSinglePixelArrayAsColors(reference);
- var decSpan = TestHelper.GetSinglePixelArrayAsColors(decoded);
-
- Assert.Equal(reference.Width, decoded.Width);
- Assert.Equal(reference.Height, decoded.Height);
-
- // Exactly equal
- for (var i = 0; i < reference.Width * reference.Height; i++)
- Assert.Equal(refSpan[i], decSpan[i]);
- }
-
- [Fact]
- public void Bc5BlockDecode()
- {
- var block = new Bc5Block()
- {
- greenBlock = new Bc4ComponentBlock() { componentBlock = 0x91824260008935ee },
- redBlock = new Bc4ComponentBlock() { componentBlock = 0x6d900f66d3c0a70d }
- };
-
- var referenceBlock = new RawBlock4X4Rgba32
- {
- p00 = new ColorRgba32(13, 53, 0, 255),
- p01 = new ColorRgba32(136, 238, 0, 255),
- p02 = new ColorRgba32(255, 212, 0, 255),
- p03 = new ColorRgba32(167, 238, 0, 255),
- p10 = new ColorRgba32(13, 53, 0, 255),
- p11 = new ColorRgba32(136, 238, 0, 255),
- p12 = new ColorRgba32(167, 238, 0, 255),
- p13 = new ColorRgba32(75, 185, 0, 255),
- p20 = new ColorRgba32(255, 212, 0, 255),
- p21 = new ColorRgba32(167, 238, 0, 255),
- p22 = new ColorRgba32(13, 53, 0, 255),
- p23 = new ColorRgba32(75, 159, 0, 255),
- p30 = new ColorRgba32(167, 238, 0, 255),
- p31 = new ColorRgba32(75, 185, 0, 255),
- p32 = new ColorRgba32(13, 53, 0, 255),
- p33 = new ColorRgba32(75, 159, 0, 255)
- };
-
- var decodedBlock = block.Decode();
-
- for (var i = 0; i < 16; i++)
- Assert.Equal(referenceBlock[i], decodedBlock[i]);
- }
- }
-}
diff --git a/BCnEncTests/Bc6EncoderTests.cs b/BCnEncTests/Bc6EncoderTests.cs
deleted file mode 100644
index 7352f86..0000000
--- a/BCnEncTests/Bc6EncoderTests.cs
+++ /dev/null
@@ -1,405 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Text;
-using BCnEncoder.Encoder;
-using BCnEncoder.Encoder.Bptc;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using CommunityToolkit.HighPerformance;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace BCnEncTests
-{
- public class Bc6EncoderTests
- {
- private ITestOutputHelper output;
-
- public Bc6EncoderTests(ITestOutputHelper output) => this.output = output;
-
- [Theory]
- [InlineData(0x7F, 8, false)]
- [InlineData(0xFF, 8, false)]
- [InlineData(0x7F, 8, true)]
- [InlineData(0xFF, 8, true)]
- [InlineData(0b111_1100_1101, 11, true)]
- [InlineData(0b111_1100_1101, 11, false)]
- [InlineData(0b011_1100_1101, 11, true)]
- [InlineData(0b011_1100_1101, 11, false)]
- [InlineData(0b0_1001, 5, true)]
- [InlineData(0b0_1001, 5, false)]
- [InlineData(0b1_1100, 5, true)]
- [InlineData(0b1_1100, 5, false)]
- public void Quantize(int initialQuantizedValue, int endpointBits, bool signed)
- {
- if (signed)
- {
- initialQuantizedValue = IntHelper.SignExtend(initialQuantizedValue, endpointBits);
- }
- var unquantized = Bc6Block.UnQuantize(initialQuantizedValue, endpointBits, signed);
- var finishedUnquantized = Bc6Block.FinishUnQuantize(unquantized, signed);
-
- var prequantized = Bc6EncodingHelpers.PreQuantize(finishedUnquantized, signed);
- var quantized = Bc6EncodingHelpers.Quantize(prequantized, endpointBits, signed);
-
- Assert.InRange(prequantized, unquantized - 2, unquantized + 2);
- Assert.InRange(quantized, initialQuantizedValue - 1, initialQuantizedValue + 1);
- }
-
- [Fact]
- public void PreQuantizeRawEndpoint()
- {
- var ep0 = new ColorRgbFloat(1, 0.001f, 0.2f);
-
- var preQuantized = Bc6EncodingHelpers.PreQuantizeRawEndpoint(ep0, false);
- var unQu = Bc6Block.FinishUnQuantize(preQuantized, false);
-
- Assert.InRange(unQu.Item1, ep0.r - 0.001f, ep0.r + 0.001f);
- Assert.InRange(unQu.Item2, ep0.g - 0.001f, ep0.g + 0.001f);
- Assert.InRange(unQu.Item3, ep0.b - 0.001f, ep0.b + 0.001f);
- }
-
- [Fact]
- public void RgbBoundingBox()
- {
- var testBlock = new RawBlock4X4RgbFloat()
- {
- p00 = new ColorRgbFloat(0.01f, -0.05f, 0.02f),
- p01 = new ColorRgbFloat(0.02f, 0.02f, 0.03f),
- p02 = new ColorRgbFloat(0.02f, 0.02f, 0.02f),
- p03 = new ColorRgbFloat(0.01f, 0.02f, 0.02f),
- p10 = new ColorRgbFloat(0.02f, 0.02f, 0.02f),
- p11 = new ColorRgbFloat(0.045f, 0.02f, 0.02f),
- p12 = new ColorRgbFloat(0.04f, 0.02f, 0.02f),
- p13 = new ColorRgbFloat(0.04f, 0.02f, 0.02f),
- p20 = new ColorRgbFloat(0.21f, 0.02f, 0.02f),
- p21 = new ColorRgbFloat(0.01f, 0.02f, 0.02f),
- p22 = new ColorRgbFloat(0.01f, 0.32f, 0.02f),
- p23 = new ColorRgbFloat(1.01f, 0.22f, 0.02f),
- p30 = new ColorRgbFloat(0.01f, 0.12f, 0.02f),
- p31 = new ColorRgbFloat(0.01f, 0.02f, 0.02f),
- p32 = new ColorRgbFloat(0.01f, 0.62f, 0.7f),
- p33 = new ColorRgbFloat(0.01f, 0.02f, 0.02f)
- };
-
- BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max);
-
- Assert.InRange(min.r, 0.01, 0.02);
- Assert.InRange(min.g, -0.05, -0.03);
- Assert.InRange(min.b, 0.02, 0.03);
-
- Assert.InRange(max.r, 0.9, 1.01);
- Assert.InRange(max.g, 0.59, 0.62);
- Assert.InRange(max.b, 0.60, 0.7);
- }
-
-
- [Fact]
- public void PackMode3()
- {
- const int endpointPrecision = 10;
- var random = new Random();
- var rand = random.Next();
-
- var ep0 = (random.Next() & (1 << endpointPrecision) - 1,
- random.Next() & (1 << endpointPrecision) - 1,
- random.Next() & (1 << endpointPrecision) - 1);
- var ep1 = (random.Next() & (1 << endpointPrecision) - 1,
- random.Next() & (1 << endpointPrecision) - 1,
- random.Next() & (1 << endpointPrecision) - 1);
-
- var indices = new byte[16];
- for (var i = 1; i < indices.Length; i++)
- {
- indices[i] = (byte) random.Next(1 << 4);
- }
-
- var block = Bc6Block.PackType3(ep0, ep1, indices);
-
- Assert.Equal(Bc6BlockType.Type3, block.Type);
-
- var extracted0 = block.ExtractEp0();
- var extracted1 = block.ExtractEp1();
-
- Assert.Equal(ep0, extracted0);
- Assert.Equal(ep1, extracted1);
-
- for (var i = 0; i < indices.Length; i++)
- {
- var idx = block.GetColorIndex(1, 0, 4, i);
- Assert.Equal(indices[i], idx);
- }
- }
-
-
- [Theory]
- [InlineData(Bc6BlockType.Type0)]
- [InlineData(Bc6BlockType.Type1)]
- [InlineData(Bc6BlockType.Type2)]
- [InlineData(Bc6BlockType.Type6)]
- [InlineData(Bc6BlockType.Type10)]
- [InlineData(Bc6BlockType.Type14)]
- [InlineData(Bc6BlockType.Type18)]
- [InlineData(Bc6BlockType.Type22)]
- [InlineData(Bc6BlockType.Type26)]
- [InlineData(Bc6BlockType.Type30)]
- [InlineData(Bc6BlockType.Type3)]
- [InlineData(Bc6BlockType.Type7)]
- [InlineData(Bc6BlockType.Type11)]
- //[InlineData(Bc6BlockType.Type15)] //deltabits too small to encode the testblock
- internal void EncodeAllModesUnsigned(Bc6BlockType type)
- {
- var testBlock = new RawBlock4X4RgbFloat()
- {
- p00 = new ColorRgbFloat(1.011f, 10.01f, 2.01f),
- p01 = new ColorRgbFloat(1.01f, 10.014f, 2.012f),
- p02 = new ColorRgbFloat(1.005f, 10.012f, 2.02f),
- p03 = new ColorRgbFloat(1.01f, 10.013f, 2.023f),
- p10 = new ColorRgbFloat(1.011f, 10.01f, 2.01f),
- p11 = new ColorRgbFloat(1.01f, 10.014f, 2.012f),
- p12 = new ColorRgbFloat(1.005f, 10.012f, 2.02f),
- p13 = new ColorRgbFloat(1.01f, 10.013f, 2.023f),
- p20 = new ColorRgbFloat(1.011f, 10.01f, 2.01f),
- p21 = new ColorRgbFloat(1.01f, 10.014f, 2.012f),
- p22 = new ColorRgbFloat(1.005f, 10.012f, 2.02f),
- p23 = new ColorRgbFloat(1.01f, 10.013f, 2.023f),
- p30 = new ColorRgbFloat(1.011f, 10.01f, 2.01f),
- p31 = new ColorRgbFloat(1.01f, 10.014f, 2.012f),
- p32 = new ColorRgbFloat(1.005f, 10.012f, 2.02f),
- p33 = new ColorRgbFloat(1.01f, 10.013f, 2.023f)
- };
- Bc6Block encoded;
- var badTransform = false;
-
- if(type.HasSubsets())
- {
- var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2);
- var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true);
-
- var bestPartition = best2SubsetPartitions[0];
-
- Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0);
- Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1);
-
- encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition,
- false, out badTransform);
- }
- else
- {
- BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max);
- encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, false,
- out badTransform);
- }
-
- Assert.False(badTransform);
- Assert.Equal(type, encoded.Type);
- var decoded = encoded.Decode(false);
- var error = testBlock.CalculateError(decoded);
- Assert.InRange(error, 0, 0.3);
- }
-
- [Theory]
- [InlineData(Bc6BlockType.Type0)]
- [InlineData(Bc6BlockType.Type1)]
- [InlineData(Bc6BlockType.Type2)]
- [InlineData(Bc6BlockType.Type6)]
- [InlineData(Bc6BlockType.Type10)]
- [InlineData(Bc6BlockType.Type14)]
- [InlineData(Bc6BlockType.Type18)]
- [InlineData(Bc6BlockType.Type22)]
- [InlineData(Bc6BlockType.Type26)]
- [InlineData(Bc6BlockType.Type30)]
- [InlineData(Bc6BlockType.Type3)]
- [InlineData(Bc6BlockType.Type7)]
- [InlineData(Bc6BlockType.Type11)]
- [InlineData(Bc6BlockType.Type15)]
- internal void EncodeAllModesSigned(Bc6BlockType type)
- {
- var testBlock = new RawBlock4X4RgbFloat()
- {
- p00 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f),
- p01 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f),
- p02 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f),
- p03 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f),
- p10 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f),
- p11 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f),
- p12 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f),
- p13 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f),
- p20 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f),
- p21 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f),
- p22 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f),
- p23 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f),
- p30 = new ColorRgbFloat(-1.011f, 10.01f, 2.01f),
- p31 = new ColorRgbFloat(-1.01f, 10.014f, 2.012f),
- p32 = new ColorRgbFloat(-1.005f, 10.012f, 2.02f),
- p33 = new ColorRgbFloat(-1.01f, 10.013f, 2.023f)
- };
- Bc6Block encoded;
- var badTransform = false;
-
- if (type.HasSubsets())
- {
- var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2);
- var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true);
-
- var bestPartition = best2SubsetPartitions[0];
-
- Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep0, out var ep1, bestPartition, 0);
- Bc6EncodingHelpers.GetInitialUnscaledEndpointsForSubset(testBlock, out var ep2, out var ep3, bestPartition, 1);
-
- encoded = Bc6ModeEncoder.EncodeBlock2Sub(type, testBlock, ep0, ep1, ep2, ep3, bestPartition,
- true, out badTransform);
- }
- else
- {
- BCnEncoder.Shared.RgbBoundingBox.CreateFloat(testBlock.AsSpan, out var min, out var max);
- encoded = Bc6ModeEncoder.EncodeBlock1Sub(type, testBlock, min, max, true,
- out badTransform);
- }
-
- Assert.False(badTransform);
- Assert.Equal(type, encoded.Type);
- var decoded = encoded.Decode(true);
- var error = testBlock.CalculateError(decoded);
- Assert.InRange(error, 0, 0.5);
- }
-
- [Fact]
- public void Encode()
- {
- var signed = true;
- var image = HdrLoader.TestHdrKiara;
- var blocks = ImageToBlocks.ImageTo4X4(image.pixels.AsMemory().AsMemory2D(image.height, image.width), out var bW, out var bH);
-
- for (var i = 0; i < blocks.Length; i++)
- {
- var encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed);
- var decoded = encoded.Decode(signed);
-
- var error = decoded.CalculateError(blocks[i]);
- if (error > 0.06 || i == 14749)
- {
- Debugger.Break();
- encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed);
- }
- }
- }
-
- [Fact]
- public void EncodeFast()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Fast,
- "encoding_bc6_kiara_fast.ktx", output);
- }
-
- [Fact]
- public void EncodeBalanced()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.Balanced,
- "encoding_bc6_kiara_balanced.ktx", output);
- }
-
- [Fact]
- public void EncodeBestQuality()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrKiara, CompressionFormat.Bc6U, CompressionQuality.BestQuality,
- "encoding_bc6_kiara_bestquality.ktx", output);
- }
-
- [Fact]
- public void EncodeProbeFast()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Fast,
- "encoding_bc6_probe_fast.ktx", output);
- }
-
- [Fact]
- public void EncodeProbeBalanced()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.Balanced,
- "encoding_bc6_probe_balanced.ktx", output);
- }
-
- [Fact]
- public void EncodeProbeBestQuality()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6U, CompressionQuality.BestQuality,
- "encoding_bc6_probe_bestquality.ktx", output);
- }
-
- [Fact]
- public void EncodeProbeSignedFast()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Fast,
- "encoding_bc6_probe_signed_fast.ktx", output);
- }
-
- [Fact]
- public void EncodeProbSignedBalanced()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Balanced,
- "encoding_bc6_probe_signed_balanced.ktx", output);
- }
-
- [Fact]
- public void EncodeProbeSignedBestQuality()
- {
- TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.BestQuality,
- "encoding_bc6_probe_signed_bestquality.ktx", output);
- }
-
- [Fact]
- public void EncodeToKtx()
- {
- var encoder = new BcEncoder();
- encoder.OutputOptions.Quality = CompressionQuality.Fast;
- encoder.OutputOptions.GenerateMipMaps = true;
- encoder.OutputOptions.Format = CompressionFormat.Bc6U;
-
- var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory);
-
- using var fs = File.OpenWrite("encoding_bc6_ktx.ktx");
- ktx.Write(fs);
- }
-
- [Fact]
- public void EncodeToDds()
- {
- var encoder = new BcEncoder();
- encoder.OutputOptions.Quality = CompressionQuality.Fast;
- encoder.OutputOptions.GenerateMipMaps = true;
- encoder.OutputOptions.Format = CompressionFormat.Bc6U;
-
- var dds = encoder.EncodeToDdsHdr(HdrLoader.TestHdrKiara.PixelMemory);
-
- using var fs = File.OpenWrite("encoding_bc6_dds.dds");
- dds.Write(fs);
- }
-
- [Fact]
- public void EncodeToRaw()
- {
- var encoder = new BcEncoder();
- encoder.OutputOptions.Quality = CompressionQuality.Fast;
- encoder.OutputOptions.GenerateMipMaps = true;
- encoder.OutputOptions.Format = CompressionFormat.Bc6U;
-
- var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory);
-
- var allMips = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory);
-
- Assert.True(allMips.Length > 1);
- Assert.True(allMips.Length == ktx.MipMaps.Count);
-
- for (var i = 0; i < allMips.Length; i++)
- {
- var single = encoder.EncodeToRawBytesHdr(HdrLoader.TestHdrKiara.PixelMemory, i, out var mW, out var mH);
-
- Assert.Equal(ktx.MipMaps[i].Faces[0].Data, single);
- Assert.Equal(allMips[i], single);
- }
- }
- }
-}
diff --git a/BCnEncTests/Bc6HDecoderTests.cs b/BCnEncTests/Bc6HDecoderTests.cs
deleted file mode 100644
index 60a99fa..0000000
--- a/BCnEncTests/Bc6HDecoderTests.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
-using BCnEncoder.Decoder;
-using BCnEncoder.Encoder;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using CommunityToolkit.HighPerformance;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
-using Xunit;
-using Xunit.Abstractions;
-using Half = BCnEncoder.Shared.Half;
-
-namespace BCnEncTests
-{
- public class Bc6HDecoderTests
- {
- private ITestOutputHelper output;
-
- public Bc6HDecoderTests(ITestOutputHelper output) => this.output = output;
-
- [Fact]
- public void DecodeDds()
- {
- var decoder = new BcDecoder();
- var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds);
-
- var img = new Image((int)HdrLoader.TestHdrKiaraDds.header.dwWidth,
- (int)HdrLoader.TestHdrKiaraDds.header.dwHeight);
-
- var pixels = TestHelper.GetSinglePixelArray(img);
-
- for (var i = 0; i < decoded.Length; i++)
- {
- pixels[i] = new RgbaVector(decoded[i].r, decoded[i].g, decoded[i].b);
- }
-
- TestHelper.SetSinglePixelArray(img, pixels);
-
- var hdr = new HdrImage((int) HdrLoader.TestHdrKiaraDds.header.dwWidth,
- (int) HdrLoader.TestHdrKiaraDds.header.dwHeight);
-
- Assert.Equal(hdr.pixels.Length, decoded.Length);
-
- hdr.pixels = decoded;
- using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr");
- hdr.Write(sfs);
-
- img.SaveAsPng("decoding_test_dds_bc6h.png");
-
- TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.Fast, output);
- }
-
- [Fact]
- public void DecodeKtx()
- {
- var decoder = new BcDecoder();
- var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraKtx);
-
- var img = new Image((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth,
- (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight);
-
- var pixels = TestHelper.GetSinglePixelArray(img);
-
- for (var i = 0; i < decoded.Length; i++)
- {
- pixels[i] = new RgbaVector(decoded[i].r, decoded[i].g, decoded[i].b);
- }
-
- TestHelper.SetSinglePixelArray(img, pixels);
-
- var hdr = new HdrImage((int)HdrLoader.TestHdrKiaraKtx.header.PixelWidth,
- (int)HdrLoader.TestHdrKiaraKtx.header.PixelHeight);
-
- Assert.Equal(hdr.pixels.Length, decoded.Length);
-
- hdr.pixels = decoded;
- using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr");
- hdr.Write(sfs);
-
- img.SaveAsPng("decoding_test_ktx_bc6h.png");
-
- TestHelper.AssertPixelsEqual(HdrLoader.TestHdrKiara.pixels, decoded, CompressionQuality.BestQuality, output);
- }
-
- // TestHdrKiaraDds includes some blocks with all modes.
- // The test_hdr_kiara_dds_float16_data.bin file contains Half-float values decoded with a reference decoder.
- // This test ensures that decoding is bit-exact.
- [Fact]
- public void AllBlocksDecodesExact()
- {
- var decoder = new BcDecoder();
- var decoded = decoder.DecodeHdr(HdrLoader.TestHdrKiaraDds);
-
- using var fs = File.OpenRead("../../../testImages/test_hdr_kiara_dds_float16_data.bin");
- using var ms = new MemoryStream();
- fs.CopyTo(ms);
- var length = (int)ms.Position;
-
- var bytes = ms.GetBuffer().AsSpan(0, length);
- var halfs = MemoryMarshal.Cast(bytes);
- Assert.Equal(halfs.Length / 4, decoded.Length);
-
- for (var i = 0; i < decoded.Length; i++)
- {
- float r = halfs[i * 4 + 0];
- float g = halfs[i * 4 + 1];
- float b = halfs[i * 4 + 2];
-
- Assert.Equal(r, decoded[i].r);
- Assert.Equal(g, decoded[i].g);
- Assert.Equal(b, decoded[i].b);
- }
- }
-
- // Test that above statement holds true. And that modes are recognized correctly
- [Fact]
- public void DecodeModes()
- {
- var modes = new int[31];
-
- var hdr = HdrLoader.TestHdrKiaraDds;
-
- var bytes = hdr.Faces[0].MipMaps[0].Data;
- var blocks = MemoryMarshal.Cast(bytes);
-
- for (var i = 0; i < blocks.Length; i++)
- {
- var block = blocks[i];
- var mode = block.Type;
- modes[(int) mode]++;
- }
-
- for (var i = 0; i < modes.Length; i++)
- {
- output.WriteLine($"Mode {i}: {modes[i]}");
- }
-
- Assert.True(modes[0] > 0);
- Assert.True(modes[1] > 0);
- Assert.True(modes[2] > 0);
- Assert.True(modes[6] > 0);
- Assert.True(modes[10] > 0);
- Assert.True(modes[14] > 0);
- Assert.True(modes[18] > 0);
- Assert.True(modes[22] > 0);
- Assert.True(modes[26] > 0);
- Assert.True(modes[30] > 0);
- Assert.True(modes[3] > 0);
- Assert.True(modes[7] > 0);
- Assert.True(modes[11] > 0);
- Assert.True(modes[15] > 0);
- }
-
- [Fact]
- public void DecodeErrorBlock()
- {
- var decoder = new BcDecoder();
-
- var width = 16;
- var height = 16;
- var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc6U) * width * height;
-
- var buffer = new byte[bufferSize];
- Random r = new Random(44);
- r.NextBytes(buffer);
-
- var decoded = decoder.DecodeRawHdr(buffer, width * 4, height * 4, CompressionFormat.Bc6U);
- Assert.Contains(new ColorRgbFloat(1, 0, 1), decoded);
-
- HdrImage image = new HdrImage(new Span2D(decoded, height * 4, width * 4));
- using var fs = File.OpenWrite("test_decode_bc6h_error.hdr");
- image.Write(fs);
- }
-
- }
-}
diff --git a/BCnEncTests/Bc7BlockTests.cs b/BCnEncTests/Bc7BlockTests.cs
deleted file mode 100644
index c05fdc6..0000000
--- a/BCnEncTests/Bc7BlockTests.cs
+++ /dev/null
@@ -1,389 +0,0 @@
-using System;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using BCnEncoder.Decoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncoder.Shared.ImageFiles;
-using SixLabors.ImageSharp;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class Bc7BlockTests
- {
- [Fact]
- public void PackTypes()
- {
- var numWidthBlocks = 4;
- var numHeightBlocks = 2;
- var outputBlocks = new Bc7Block[64 * numWidthBlocks * numHeightBlocks];
- var encoded = new byte[64 * numWidthBlocks * numHeightBlocks * Unsafe.SizeOf()];
-
- var output = new KtxFile(
- KtxHeader.InitializeCompressed(numWidthBlocks * 8 * 4, numHeightBlocks * 8 * 4,
- GlInternalFormat.GlCompressedRgbaBptcUnormArb,
- GlFormat.GlRgba));
-
- var type0 = new Span(outputBlocks, 0, 64);
- Type0Pack(type0);
- PlaceBlock(0, 0, type0, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
-
- var type1 = new Span(outputBlocks, 64 * 1, 64);
- Type1Pack(type1);
- PlaceBlock(1, 0, type1, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
-
- var type2 = new Span(outputBlocks, 64 * 2, 64);
- Type2Pack(type2);
- PlaceBlock(2, 0, type2, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
-
- var type3 = new Span(outputBlocks, 64 * 3, 64);
- Type3Pack(type3);
- PlaceBlock(3, 0, type3, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
-
- var type4 = new Span(outputBlocks, 64 * 4, 64);
- Type4Pack(type4);
- PlaceBlock(0, 1, type4, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
-
- var type5 = new Span(outputBlocks, 64 * 5, 64);
- Type5Pack(type5);
- PlaceBlock(1, 1, type5, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
-
- var type6 = new Span(outputBlocks, 64 * 6, 64);
- Type6Pack(type6);
- PlaceBlock(2, 1, type6, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
-
- var type7 = new Span(outputBlocks, 64 * 7, 64);
- Type7Pack(type7);
- PlaceBlock(3, 1, type7, MemoryMarshal.Cast(new Span(encoded)), numWidthBlocks);
-
- output.MipMaps.Add(new KtxMipmap((uint)encoded.Length,
- (uint)(8 * 4 * numWidthBlocks),
- (uint)(8 * 4 * numHeightBlocks), 1));
- output.MipMaps[0].Faces[0] = new KtxMipFace(encoded,
- (uint)(8 * 4 * numWidthBlocks),
- (uint)(8 * 4 * numHeightBlocks));
-
- var fs = File.OpenWrite("bc7_blocktests.ktx");
- output.Write(fs);
- }
-
- [Fact]
- public void DecodeErrorBlock()
- {
- var decoder = new BcDecoder();
-
- var width = 32;
- var height = 32;
- var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc7) * width * height;
-
- var buffer = new byte[bufferSize];
- Random r = new Random(50);
- r.NextBytes(buffer);
-
- var pixels = decoder.DecodeRaw(buffer, width * 4, height * 4, CompressionFormat.Bc7);
- var decoded = decoder.DecodeRawToImageRgba32(buffer, width * 4, height * 4, CompressionFormat.Bc7);
- Assert.Contains(new ColorRgba32(255, 0, 255), pixels);
-
- using var fs = File.OpenWrite("test_decode_bc7_error.png");
- decoded.SaveAsPng(fs);
- }
-
- #region Type Packs
-
- private void Type0Pack(Span output)
- {
- var subsetEndpoints = new[] {
- //subset 1
- new byte[]{0b1111, 0, 0},
- new byte[]{0b1000, 0, 0},
- // subset 2
- new byte[]{0, 0b1111, 0},
- new byte[]{0, 0b1000, 0},
- //subset 3
- new byte[]{0, 0, 0b1111},
- new byte[]{0, 0, 0b1000}
- };
- var pBits = new byte[] {
- 1, 0, 1, 0, 1, 1
- };
- var indices = new byte[] {
- 0, 1, 2, 3,
- 0, 1, 2, 3,
- 3, 2, 1, 0,
- 3, 2, 1, 0
- };
- for (var i = 0; i < 16; i++)
- {
- output[i].PackType0(i, subsetEndpoints, pBits, indices);
- Assert.Equal(Bc7BlockType.Type0, output[i].Type);
- Assert.Equal(i, output[i].PartitionSetId);
- }
-
- pBits = new byte[] {
- 0, 0, 0, 0, 0, 0
- };
- indices = new byte[] {
- 0, 1, 0, 3,
- 1, 1, 1, 3,
- 2, 2, 2, 0,
- 3, 2, 3, 0
- };
-
- for (var i = 0; i < 16; i++)
- {
- output[i + 16].PackType0(i, subsetEndpoints, pBits, indices);
- Assert.Equal(Bc7BlockType.Type0, output[i].Type);
- Assert.Equal(i, output[i].PartitionSetId);
- }
-
- pBits = new byte[] {
- 1, 1, 1, 1, 1, 1
- };
- indices = new byte[] {
- 1, 0, 3, 3,
- 2, 1, 2, 3,
- 3, 2, 1, 0,
- 0, 3, 0, 0
- };
-
- for (var i = 0; i < 16; i++)
- {
- output[i + 32].PackType0(i, subsetEndpoints, pBits, indices);
- Assert.Equal(Bc7BlockType.Type0, output[i].Type);
- Assert.Equal(i, output[i].PartitionSetId);
- }
-
- pBits = new byte[] {
- 0, 1, 0, 1, 0, 0
- };
- indices = new byte[] {
- 3, 2, 1, 0,
- 0, 1, 2, 3,
- 3, 2, 1, 0,
- 0, 1, 2, 3
- };
-
- for (var i = 0; i < 16; i++)
- {
- output[i + 48].PackType0(i, subsetEndpoints, pBits, indices);
- Assert.Equal(Bc7BlockType.Type0, output[i].Type);
- Assert.Equal(i, output[i].PartitionSetId);
- }
- }
-
- private void Type1Pack(Span output)
- {
- var subsetEndpoints = new[] {
- //subset 1
- new byte[]{0b111111, 0b0100, 0},
- new byte[]{0b0100, 0b111111, 0},
- // subset 2
- new byte[]{0, 0, 0b111111},
- new byte[]{0, 0, 0b1010}
- };
- var pBits = new byte[] {
- 1, 1
- };
- var indices = new byte[] {
- 0, 1, 2, 3,
- 0, 1, 2, 3,
- 3, 2, 1, 0,
- 3, 2, 1, 0
- };
- for (var i = 0; i < 64; i++)
- {
- output[i].PackType1(i, subsetEndpoints, pBits, indices);
- Assert.Equal(Bc7BlockType.Type1, output[i].Type);
- Assert.Equal(i, output[i].PartitionSetId);
- }
- }
-
- private void Type2Pack(Span output)
- {
- var subsetEndpoints = new[] {
- //subset 1
- new byte[]{0b11111, 0, 0},
- new byte[]{0b01000, 0, 0},
- // subset 2
- new byte[]{0, 0b11111, 0},
- new byte[]{0, 0b01000, 0},
- //subset 3
- new byte[]{0, 0, 0b11111},
- new byte[]{0, 0, 0b01000}
- };
- var indices = new byte[] {
- 0, 1, 2, 3,
- 0, 1, 2, 3,
- 3, 2, 1, 0,
- 3, 2, 1, 0
- };
- for (var i = 0; i < 64; i++)
- {
- output[i].PackType2(i, subsetEndpoints, indices);
- Assert.Equal(Bc7BlockType.Type2, output[i].Type);
- Assert.Equal(i, output[i].PartitionSetId);
- }
- }
-
- private void Type3Pack(Span output)
- {
- var subsetEndpoints = new[] {
- //subset 1
- new byte[]{0b1111111, 0b10100, 0},
- new byte[]{0b10100, 0b1111111, 0},
- // subset 2
- new byte[]{0, 0, 0b1111111},
- new byte[]{0, 0, 0b11010}
- };
- var pBits = new byte[] {
- 1, 0, 0, 1
- };
- var indices = new byte[] {
- 0, 1, 2, 3,
- 0, 1, 2, 3,
- 3, 2, 1, 0,
- 3, 2, 1, 0
- };
- for (var i = 0; i < 64; i++)
- {
- output[i].PackType3(i, subsetEndpoints, pBits, indices);
- Assert.Equal(Bc7BlockType.Type3, output[i].Type);
- Assert.Equal(i, output[i].PartitionSetId);
- }
- }
-
- private void Type4Pack(Span output)
- {
- var colorEndpoints = new[] {
- //subset 1
- new byte[]{0b11111, 0, 0b0100},
- new byte[]{0, 0b11111, 0b0100}
- };
- var alphaEndPoints = new byte[]{
- 0b111111,
- 0
- };
- var indices2Bit = new byte[] {
- 0, 1, 2, 3,
- 0, 1, 2, 3,
- 3, 2, 1, 0,
- 3, 2, 1, 0
- };
-
- var indices3Bit = new byte[] {
- 0, 1, 2, 3,
- 7, 6, 5, 4,
- 7, 6, 5, 4,
- 3, 2, 1, 0
- };
- var rotation = 0;
- byte idxMode = 0;
- for (var i = 0; i < 64; i++)
- {
- rotation = (rotation + 1) % 4;
- idxMode = (byte)((idxMode + 1) % 2);
- output[i].PackType4(rotation, idxMode, colorEndpoints, alphaEndPoints, indices2Bit, indices3Bit);
- Assert.Equal(Bc7BlockType.Type4, output[i].Type);
- Assert.Equal(rotation, output[i].RotationBits);
- Assert.Equal(idxMode, output[i].Type4IndexMode);
- }
- }
-
- private void Type5Pack(Span output)
- {
- var colorEndpoints = new[] {
- //subset 1
- new byte[]{0b1111111, 0b0100, 0},
- new byte[]{0, 0, 0b1111111}
- };
- var alphaEndPoints = new byte[]{
- 255,
- 100
- };
- var colorIndices = new byte[] {
- 0, 1, 2, 3,
- 0, 1, 2, 3,
- 3, 2, 1, 0,
- 3, 2, 1, 0
- };
- var alphaIndices = new byte[] {
- 1, 2, 3, 0,
- 0, 1, 2, 3,
- 2, 3, 0, 1,
- 3, 2, 1, 0
- };
- var rotation = 0;
- for (var i = 0; i < 64; i++)
- {
- rotation = (rotation + 1) % 4;
- output[i].PackType5(rotation, colorEndpoints, alphaEndPoints, colorIndices, alphaIndices);
- Assert.Equal(Bc7BlockType.Type5, output[i].Type);
- Assert.Equal(rotation, output[i].RotationBits);
- }
- }
-
- private void Type6Pack(Span output)
- {
- var colorEndpoints = new[] {
- //subset 1
- new byte[]{0b1111111, 0b0100, 0, 0b1111111},
- new byte[]{0, 0, 0b1111111, 0b1111}
- };
- var pBits = new byte[] {
- 1, 0
- };
- var colorIndices = new byte[] {
- 0, 1, 2, 3,
- 4, 5, 6, 7,
- 8, 9, 10, 11,
- 12, 13, 14, 15
- };
- for (var i = 0; i < 64; i++)
- {
- output[i].PackType6(colorEndpoints, pBits, colorIndices);
- Assert.Equal(Bc7BlockType.Type6, output[i].Type);
- }
- }
-
- private void Type7Pack(Span output)
- {
- var colorEndpoints = new[] {
- //subset 1
- new byte[]{0b11111, 0b0100, 0, 0b11111},
- new byte[]{0, 0b11111, 0, 0b111},
- //subset 2
- new byte[]{0b11111, 0b0100, 0, 0b1111},
- new byte[]{0b10100, 0, 0b11111, 0b11111}
- };
- var pBits = new byte[] {
- 1, 0, 1, 0
- };
- var indices = new byte[] {
- 0, 1, 2, 3,
- 0, 1, 2, 3,
- 3, 2, 1, 0,
- 3, 2, 1, 0
- };
- for (var i = 0; i < 64; i++)
- {
- output[i].PackType7(i, colorEndpoints, pBits, indices);
- Assert.Equal(Bc7BlockType.Type7, output[i].Type);
- Assert.Equal(i, output[i].PartitionSetId);
- }
- }
-
- private void PlaceBlock(int x, int y, Span data, Span destination, int destBlockWidth)
- {
- for (var i = 0; i < 8; i++)
- {
- var xStart = x * 8 + (y * 8 + i) * 8 * destBlockWidth;
- var row = data.Slice(i * 8, 8);
- row.CopyTo(destination.Slice(xStart, 8));
- }
- }
-
- #endregion
- }
-}
diff --git a/BCnEncTests/BgraTests.cs b/BCnEncTests/BgraTests.cs
deleted file mode 100644
index 7ae666e..0000000
--- a/BCnEncTests/BgraTests.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using BCnEncoder.Decoder;
-using BCnEncoder.Encoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class BgraTests
- {
- [Fact]
- public void BgraDdsDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.Bgra);
- var original = ImageLoader.TestLenna;
-
- // Act
- var dds = encoder.EncodeToDds(original);
- var image = decoder.DecodeToImageRgba32(dds);
-
-
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void BgraAlphaDdsDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.Bgra);
- var original = ImageLoader.TestAlphaGradient1;
-
- // Act
- var dds = encoder.EncodeToDds(original);
- var image = decoder.DecodeToImageRgba32(dds);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void BgraKtxDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.Bgra);
- var original = ImageLoader.TestLenna;
-
- // Act
- var ktx = encoder.EncodeToKtx(original);
- var image = decoder.DecodeToImageRgba32(ktx);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void BgraAlphaKtxDecode()
- {
- // Arrange
- var decoder = new BcDecoder();
- var encoder = new BcEncoder(CompressionFormat.Bgra);
- var original = ImageLoader.TestAlphaGradient1;
-
- // Act
- var ktx = encoder.EncodeToKtx(original);
- var image = decoder.DecodeToImageRgba32(ktx);
-
- // Assert
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- }
- }
-}
diff --git a/BCnEncTests/CancellationTests.cs b/BCnEncTests/CancellationTests.cs
deleted file mode 100644
index ca85a9a..0000000
--- a/BCnEncTests/CancellationTests.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using BCnEncoder.Decoder;
-using BCnEncTests.Support;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class CancellationTests
- {
- [Fact]
- public async Task EncodeParallelCancellation()
- {
- await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, true);
- }
-
- [Fact]
- public async Task EncodeNonParallelCancellation()
- {
- await TestHelper.ExecuteCancellationTest(ImageLoader.TestAlphaGradient1, false);
- }
-
- // HINT: Decoding in general is too fast to be cancelled.
- // HINT: For parallel decoding even with TimeSpan.FromTicks(1) the test never successfully threw an exception when executed in bulk with other tests.
- // HINT: For non parallel decoding the test was partially successful, due to fluctuations in how much time the decoding needed and when the cancellation was introduced.
- }
-}
diff --git a/BCnEncTests/ClusterTests.cs b/BCnEncTests/ClusterTests.cs
deleted file mode 100644
index cee9ccd..0000000
--- a/BCnEncTests/ClusterTests.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class ClusterTests
- {
- [Fact]
- public void Clusterize()
- {
- var testImage = ImageLoader.TestBlur1.Clone();
-
- var pix = TestHelper.GetSinglePixelArray(testImage);
-
- var pixels = MemoryMarshal.Cast(pix).ToArray();
-
- var numClusters = (testImage.Width / 32) * (testImage.Height / 32);
- var clusters = LinearClustering.ClusterPixels(pixels, testImage.Width, testImage.Height, numClusters, 10, 10);
-
- var pixC = new ColorYCbCr[numClusters];
- var counts = new int[numClusters];
-
- for (var i = 0; i < pixels.Length; i++)
- {
- pixC[clusters[i]] += new ColorYCbCr(pixels[i]);
- counts[clusters[i]]++;
- }
-
- for (var i = 0; i < numClusters; i++)
- {
- pixC[i] /= counts[i];
- }
-
- for (var i = 0; i < pixels.Length; i++)
- {
- pixels[i] = pixC[clusters[i]].ToColorRgba32();
- pix[i] = new Rgba32(pixels[i].r, pixels[i].g, pixels[i].b, pixels[i].a);
- }
-
- TestHelper.SetSinglePixelArray(testImage, pix);
-
- using var fs = File.OpenWrite("test_cluster.png");
- testImage.SaveAsPng(fs);
- }
- }
-}
diff --git a/BCnEncTests/DdsReadTests.cs b/BCnEncTests/DdsReadTests.cs
deleted file mode 100644
index 9a5ba2e..0000000
--- a/BCnEncTests/DdsReadTests.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using System.IO;
-using BCnEncoder.Decoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncoder.Shared.ImageFiles;
-using BCnEncTests.Support;
-using SixLabors.ImageSharp;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class DdsReadTests
- {
- [Fact]
- public void ReadRgba()
- {
- TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressRgba, DxgiFormat.DxgiFormatR8G8B8A8Unorm, "decoding_test_dds_rgba_mip{0}.png");
- }
-
- [Fact]
- public void ReadBc1()
- {
- TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1_mip{0}.png");
- }
-
- [Fact]
- public void ReadBc1A()
- {
- TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc1A, DxgiFormat.DxgiFormatBc1Unorm, "decoding_test_dds_bc1a_mip{0}.png");
- }
-
- [Fact]
- public void ReadBc7()
- {
- TestHelper.ExecuteDdsReadingTest(DdsLoader.TestDecompressBc7, DxgiFormat.DxgiFormatUnknown, "decoding_test_dds_bc7_mip{0}.png");
- }
-
- [Fact]
- public void ReadFromStream()
- {
- using var fs = File.OpenRead(DdsLoader.TestDecompressBc1Name);
-
- var decoder = new BcDecoder();
- var images = decoder.DecodeAllMipMapsToImageRgba32(fs);
-
- for (var i = 0; i < images.Length; i++)
- {
- using var outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png");
- images[i].SaveAsPng(outFs);
- images[i].Dispose();
- }
- }
- }
-}
diff --git a/BCnEncTests/DdsWritingTests.cs b/BCnEncTests/DdsWritingTests.cs
deleted file mode 100644
index 44a0616..0000000
--- a/BCnEncTests/DdsWritingTests.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class DdsWritingTests
- {
- [Fact]
- public void DdsWriteRgba()
- {
- TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Rgba, "encoding_dds_rgba.dds");
- }
-
- [Fact]
- public void DdsWriteBc1()
- {
- TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc1, "encoding_dds_bc1.dds");
- }
-
- [Fact]
- public void DdsWriteBc2()
- {
- TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc2, "encoding_dds_bc2.dds");
- }
-
- [Fact]
- public void DdsWriteBc3()
- {
- TestHelper.ExecuteDdsWritingTest(ImageLoader.TestAlpha1, CompressionFormat.Bc3, "encoding_dds_bc3.dds");
- }
-
- [Fact]
- public void DdsWriteBc4()
- {
- TestHelper.ExecuteDdsWritingTest(ImageLoader.TestHeight1, CompressionFormat.Bc4, "encoding_dds_bc4.dds");
- }
-
- [Fact]
- public void DdsWriteBc5()
- {
- TestHelper.ExecuteDdsWritingTest(ImageLoader.TestRedGreen1, CompressionFormat.Bc5, "encoding_dds_bc5.dds");
- }
-
- [Fact]
- public void DdsWriteBc7()
- {
- TestHelper.ExecuteDdsWritingTest(ImageLoader.TestLenna, CompressionFormat.Bc7, "encoding_dds_bc7.dds");
- }
-
- [Fact]
- public void DdsWriteCubemap()
- {
- TestHelper.ExecuteDdsWritingTest(ImageLoader.TestCubemap, CompressionFormat.Bc1, "encoding_dds_cubemap_bc1.dds");
- }
- }
-}
diff --git a/BCnEncTests/DecodingAsyncTests.cs b/BCnEncTests/DecodingAsyncTests.cs
deleted file mode 100644
index 04cfa29..0000000
--- a/BCnEncTests/DecodingAsyncTests.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.IO;
-using System.Threading.Tasks;
-using BCnEncoder.Decoder;
-using BCnEncoder.Encoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class DecodingAsyncTests
- {
- [Fact]
- public async Task DecodeAsync()
- {
- var decoder = new BcDecoder();
- var encoder = new BcEncoder();
- var original = ImageLoader.TestGradient1;
-
- var file = encoder.EncodeToKtx(original);
- var image = await decoder.DecodeToImageRgba32Async(file);
-
- TestHelper.AssertImagesEqual(original, image,encoder.OutputOptions.Quality);
- image.Dispose();
- }
-
- [Fact]
- public async Task DecodeAllMipMapsAsync()
- {
- var decoder = new BcDecoder();
- var encoder = new BcEncoder();
- var original = ImageLoader.TestGradient1;
-
- var file = encoder.EncodeToKtx(original);
- var images = await decoder.DecodeAllMipMapsToImageRgba32Async(file);
-
- TestHelper.AssertImagesEqual(original, images[0], encoder.OutputOptions.Quality);
- foreach(var img in images)
- img.Dispose();
- }
-
- [Fact]
- public async Task DecodeRawAsync()
- {
- var decoder = new BcDecoder();
- var encoder = new BcEncoder();
- var original = ImageLoader.TestGradient1;
-
- var file = encoder.EncodeToKtx(original);
-
- var ms = new MemoryStream(file.MipMaps[0].Faces[0].Data);
-
- var image = await decoder.DecodeRawToImageRgba32Async(ms,
- (int) file.MipMaps[0].Width, (int) file.MipMaps[0].Height, CompressionFormat.Bc1);
-
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- image.Dispose();
- }
- }
-}
diff --git a/BCnEncTests/DecodingTests.cs b/BCnEncTests/DecodingTests.cs
deleted file mode 100644
index c2fc08c..0000000
--- a/BCnEncTests/DecodingTests.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using BCnEncTests.Support;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class DecodingTests
- {
- [Fact]
- public void Bc1Decode()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1, "decoding_test_bc1.png");
- }
-
- [Fact]
- public void Bc1AlphaDecode()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc1A, "decoding_test_bc1a.png");
- }
-
- [Fact]
- public void Bc2Decode()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc2, "decoding_test_bc2.png");
- }
-
- [Fact]
- public void Bc3Decode()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc3, "decoding_test_bc3.png");
- }
-
- [Fact]
- public void Bc4Decode()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc4Unorm, "decoding_test_bc4.png");
- }
-
- [Fact]
- public void Bc5Decode()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc5Unorm, "decoding_test_bc5.png");
- }
-
- [Fact]
- public void Bc7DecodeRgb()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Rgb, "decoding_test_bc7_rgb.png");
- }
-
- [Fact]
- public void Bc7DecodeUnorm()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Unorm, "decoding_test_bc7_unorm.png");
- }
-
- [Fact]
- public void Bc7DecodeEveryBlockType()
- {
- TestHelper.ExecuteDecodingTest(KtxLoader.TestDecompressBc7Types, "decoding_test_bc7_types.png");
- }
- }
-}
diff --git a/BCnEncTests/EncodeByteOrderTests.cs b/BCnEncTests/EncodeByteOrderTests.cs
index 928fbc9..9b1d042 100644
--- a/BCnEncTests/EncodeByteOrderTests.cs
+++ b/BCnEncTests/EncodeByteOrderTests.cs
@@ -19,7 +19,7 @@ public class EncodeByteOrderTests
private void EncodeByteOrderTest(PixelFormat pixelFormat) where T : unmanaged, IPixel
{
- var imageOrig = ImageLoader.TestAlpha1;
+ using var imageOrig = ImageLoader.LoadTestImageSharp("../../../testImages/test_alpha_1_512.png");
var testImage = imageOrig.CloneAs();
var pixels = testImage.GetPixelMemoryGroup()[0];
diff --git a/BCnEncTests/EncoderOptionsTests.cs b/BCnEncTests/EncoderOptionsTests.cs
deleted file mode 100644
index 677fce4..0000000
--- a/BCnEncTests/EncoderOptionsTests.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using BCnEncoder.Encoder;
-using BCnEncTests.Support;
-using Xunit;
-using BCnEncoder.ImageSharp;
-
-namespace BCnEncTests
-{
- public class EncoderOptionsTests
- {
-
- [Theory]
- [InlineData(1)]
- [InlineData(2)]
- [InlineData(5)]
- [InlineData(10)]
- public void MaxMipMaps(int requestedMipMaps)
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder()
- {
- OutputOptions =
- {
- GenerateMipMaps = true,
- MaxMipMapLevel = requestedMipMaps
- }
- };
-
- Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage));
-
- var ktx = encoder.EncodeToKtx(testImage);
-
- Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels);
- Assert.Equal(requestedMipMaps, ktx.MipMaps.Count);
-
- var dds = encoder.EncodeToDds(testImage);
-
- Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount);
- Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length);
- }
-
- [Fact]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.", Justification = "")]
- public void GenerateMipMaps()
- {
- var testImage = ImageLoader.TestBlur1;
- const int requestedMipMaps = 1;
- var encoder = new BcEncoder()
- {
- OutputOptions =
- {
- GenerateMipMaps = false
- }
- };
-
- Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage));
-
- var ktx = encoder.EncodeToKtx(testImage);
-
- Assert.Equal(requestedMipMaps, (int)ktx.header.NumberOfMipmapLevels);
- Assert.Equal(requestedMipMaps, (int)ktx.MipMaps.Count);
-
- var dds = encoder.EncodeToDds(testImage);
-
- Assert.Equal(requestedMipMaps, (int)dds.header.dwMipMapCount);
- Assert.Equal(requestedMipMaps, dds.Faces[0].MipMaps.Length);
- }
- }
-}
diff --git a/BCnEncTests/EncodingAsyncTest.cs b/BCnEncTests/EncodingAsyncTest.cs
deleted file mode 100644
index 6d3e9ff..0000000
--- a/BCnEncTests/EncodingAsyncTest.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System.Threading.Tasks;
-using BCnEncoder.Decoder;
-using BCnEncoder.Encoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class EncodingAsyncTest
- {
- private readonly BcEncoder encoder;
- private readonly BcDecoder decoder;
- private readonly Image originalImage;
- private readonly Image[] originalCubeMap;
-
- public EncodingAsyncTest()
- {
- encoder = new BcEncoder();
- decoder = new BcDecoder();
- originalImage = ImageLoader.TestGradient1;
- originalCubeMap = ImageLoader.TestCubemap;
- }
-
- [Fact]
- public async Task EncodeToDdsAsync()
- {
- var file = await encoder.EncodeToDdsAsync(originalImage);
- var image = decoder.DecodeToImageRgba32(file);
-
- TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
- image.Dispose();
- }
-
- [Fact]
- public async Task EncodeToKtxAsync()
- {
- var file = await encoder.EncodeToKtxAsync(originalImage);
- var image = decoder.DecodeToImageRgba32(file);
-
- TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
- image.Dispose();
- }
-
- [Fact]
- public async Task EncodeCubemapToDdsAsync()
- {
- var file = await encoder.EncodeCubeMapToDdsAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2],
- originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]);
-
- for (var i = 0; i < 6; i++)
- {
- var image = decoder.DecodeRawToImageRgba32(file.Faces[i].MipMaps[0].Data, (int)file.Faces[i].Width, (int)file.Faces[i].Height, CompressionFormat.Bc1);
-
- TestHelper.AssertImagesEqual(originalCubeMap[i], image, encoder.OutputOptions.Quality);
- image.Dispose();
- }
- }
-
- [Fact]
- public async Task EncodeCubemapToKtxAsync()
- {
- var file = await encoder.EncodeCubeMapToKtxAsync(originalCubeMap[0], originalCubeMap[1], originalCubeMap[2],
- originalCubeMap[3], originalCubeMap[4], originalCubeMap[5]);
-
- for (var i = 0; i < 6; i++)
- {
- var image = decoder.DecodeRawToImageRgba32(file.MipMaps[0].Faces[i].Data, (int)file.MipMaps[0].Faces[i].Width, (int)file.MipMaps[0].Faces[i].Height, CompressionFormat.Bc1);
-
- TestHelper.AssertImagesEqual(originalCubeMap[i], image, encoder.OutputOptions.Quality);
- image.Dispose();
- }
- }
-
- [Fact]
- public async Task EncodeToRawBytesAsync()
- {
- var data = await encoder.EncodeToRawBytesAsync(originalImage);
- var image = decoder.DecodeRawToImageRgba32(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1);
-
- TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
- image.Dispose();
- }
- }
-}
diff --git a/BCnEncTests/EncodingTest.cs b/BCnEncTests/EncodingTest.cs
deleted file mode 100644
index 00e46f2..0000000
--- a/BCnEncTests/EncodingTest.cs
+++ /dev/null
@@ -1,501 +0,0 @@
-using System.IO;
-using BCnEncoder.Encoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace BCnEncTests
-{
- public class Bc1GradientTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc1GradientTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc1GradientBestQuality()
- {
- var image = ImageLoader.TestGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.BestQuality,
- "encoding_bc1_gradient_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc1GradientBalanced()
- {
- var image = ImageLoader.TestGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.Balanced,
- "encoding_bc1_gradient_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc1GradientFast()
- {
- var image = ImageLoader.TestGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.Fast,
- "encoding_bc1_gradient_fast.ktx",
- output);
- }
- }
-
- public class Bc1DiffuseTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc1DiffuseTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc1DiffuseBestQuality()
- {
- var image = ImageLoader.TestDiffuse1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.BestQuality,
- "encoding_bc1_diffuse_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc1DiffuseBalanced()
- {
- var image = ImageLoader.TestDiffuse1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.Balanced,
- "encoding_bc1_diffuse_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc1DiffuseFast()
- {
- var image = ImageLoader.TestDiffuse1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.Fast,
- "encoding_bc1_diffuse_fast.ktx",
- output);
- }
- }
-
- public class Bc1BlurryTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc1BlurryTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc1BlurBestQuality()
- {
- var image = ImageLoader.TestBlur1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.BestQuality,
- "encoding_bc1_blur_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc1BlurBalanced()
- {
- var image = ImageLoader.TestBlur1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.Balanced,
- "encoding_bc1_blur_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc1BlurFast()
- {
- var image = ImageLoader.TestBlur1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1,
- CompressionQuality.Fast,
- "encoding_bc1_blur_fast.ktx",
- output);
- }
- }
-
- public class Bc1ASpriteTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc1ASpriteTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc1ASpriteBestQuality()
- {
- var image = ImageLoader.TestTransparentSprite1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1WithAlpha,
- CompressionQuality.BestQuality,
- "encoding_bc1a_sprite_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc1ASpriteBalanced()
- {
- var image = ImageLoader.TestTransparentSprite1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1WithAlpha,
- CompressionQuality.Balanced,
- "encoding_bc1a_sprite_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc1ASpriteFast()
- {
- var image = ImageLoader.TestTransparentSprite1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc1WithAlpha,
- CompressionQuality.Fast,
- "encoding_bc1a_sprite_fast.ktx",
- output);
- }
- }
-
- public class Bc2GradientTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc2GradientTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc2GradientBestQuality()
- {
- var image = ImageLoader.TestAlphaGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc2,
- CompressionQuality.BestQuality,
- "encoding_bc2_gradient_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc2GradientBalanced()
- {
- var image = ImageLoader.TestAlphaGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc2,
- CompressionQuality.Balanced,
- "encoding_bc2_gradient_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc2GradientFast()
- {
- var image = ImageLoader.TestAlphaGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc2,
- CompressionQuality.Fast,
- "encoding_bc2_gradient_fast.ktx",
- output);
- }
- }
-
- public class Bc3GradientTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc3GradientTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc3GradientBestQuality()
- {
- var image = ImageLoader.TestAlphaGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc3,
- CompressionQuality.BestQuality,
- "encoding_bc3_gradient_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc3GradientBalanced()
- {
- var image = ImageLoader.TestAlphaGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc3,
- CompressionQuality.Balanced,
- "encoding_bc3_gradient_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc3GradientFast()
- {
- var image = ImageLoader.TestAlphaGradient1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc3,
- CompressionQuality.Fast,
- "encoding_bc3_gradient_fast.ktx",
- output);
- }
- }
-
- public class Bc4RedTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc4RedTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc4RedBestQuality()
- {
- var image = ImageLoader.TestHeight1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc4,
- CompressionQuality.BestQuality,
- "encoding_bc4_red_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc4RedBalanced()
- {
- var image = ImageLoader.TestHeight1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc4,
- CompressionQuality.Balanced,
- "encoding_bc4_red_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc4RedFast()
- {
- var image = ImageLoader.TestHeight1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc4,
- CompressionQuality.Fast,
- "encoding_bc4_red_fast.ktx",
- output);
- }
- }
-
- public class Bc5RedGreenTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc5RedGreenTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc5RedGreenBestQuality()
- {
- var image = ImageLoader.TestRedGreen1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc5,
- CompressionQuality.BestQuality,
- "encoding_bc5_red_green_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc5RedGreenBalanced()
- {
- var image = ImageLoader.TestRedGreen1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc5,
- CompressionQuality.Balanced,
- "encoding_bc5_red_green_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc5RedGreenFast()
- {
- var image = ImageLoader.TestRedGreen1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc5,
- CompressionQuality.Fast,
- "encoding_bc5_red_green_fast.ktx",
- output);
- }
- }
-
- public class Bc7RgbTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc7RgbTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc7RgbBestQuality()
- {
- var image = ImageLoader.TestRgbHard1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc7,
- CompressionQuality.BestQuality,
- "encoding_bc7_rgb_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc7RgbBalanced()
- {
- var image = ImageLoader.TestRgbHard1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc7,
- CompressionQuality.Balanced,
- "encoding_bc7_rgb_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc7LennaBalanced()
- {
- var image = ImageLoader.TestLenna;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc7,
- CompressionQuality.Balanced,
- "encoding_bc7_lenna_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc7RgbFast()
- {
- var image = ImageLoader.TestRgbHard1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc7,
- CompressionQuality.Fast,
- "encoding_bc7_rgb_fast.ktx",
- output);
- }
- }
-
- public class Bc7RgbaTest
- {
- private readonly ITestOutputHelper output;
-
- public Bc7RgbaTest(ITestOutputHelper output)
- {
- this.output = output;
- }
-
- [Fact]
- public void Bc7RgbaBestQuality()
- {
- var image = ImageLoader.TestAlpha1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc7,
- CompressionQuality.BestQuality,
- "encoding_bc7_rgba_bestQuality.ktx",
- output);
- }
-
- [Fact]
- public void Bc7RgbaBalanced()
- {
- var image = ImageLoader.TestAlpha1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc7,
- CompressionQuality.Balanced,
- "encoding_bc7_rgba_balanced.ktx",
- output);
- }
-
- [Fact]
- public void Bc7RgbaFast()
- {
- var image = ImageLoader.TestAlpha1;
-
- TestHelper.ExecuteEncodingTest(image,
- CompressionFormat.Bc7,
- CompressionQuality.Fast,
- "encoding_bc7_rgba_fast.ktx",
- output);
- }
- }
-
- public class CubemapTest
- {
- [Fact]
- public void WriteCubeMapFile()
- {
- var images = ImageLoader.TestCubemap;
-
- var filename = "encoding_bc1_cubemap.ktx";
-
- var encoder = new BcEncoder();
- encoder.OutputOptions.Quality = CompressionQuality.Fast;
- encoder.OutputOptions.GenerateMipMaps = true;
- encoder.OutputOptions.Format = CompressionFormat.Bc1;
-
- using var fs = File.OpenWrite(filename);
- encoder.EncodeCubeMapToStream(images[0], images[1], images[2], images[3], images[4], images[5], fs);
- }
- }
-}
diff --git a/BCnEncTests/ProgressTests.cs b/BCnEncTests/ProgressTests.cs
deleted file mode 100644
index 1a58c31..0000000
--- a/BCnEncTests/ProgressTests.cs
+++ /dev/null
@@ -1,392 +0,0 @@
-using System;
-using System.IO;
-using System.Threading.Tasks;
-using BCnEncoder.Decoder;
-using BCnEncoder.Encoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncoder.Shared.ImageFiles;
-using BCnEncTests.Support;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace BCnEncTests
-{
- public class ProgressTests
- {
- private ITestOutputHelper output;
-
- public ProgressTests(ITestOutputHelper output) => this.output = output;
-
- private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Image testImage, int expectedTotalBlocks)
- {
- var lastProgress = new ProgressElement(0, 1);
-
- var processedBlocks = 0;
- encoder.Options.Progress = new SynchronousProgress(element =>
- {
- output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}");
-
- Assert.Equal(++processedBlocks, element.CurrentBlock);
- lastProgress = element;
- });
-
- await using var ms = new MemoryStream();
- await encoder.EncodeToStreamAsync(testImage, ms);
-
- output.WriteLine("LastProgress = " + lastProgress);
-
- Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks);
- Assert.Equal(1, lastProgress.Percentage);
- }
-
- private async Task ExecuteDecodeProgressReport(BcDecoder decoder, KtxFile image, int expectedTotalBlocks, bool singleMip)
- {
- var lastProgress = new ProgressElement(0, 1);
-
- var processedBlocks = 0;
- decoder.Options.Progress = new SynchronousProgress(element =>
- {
- output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}");
-
- Assert.Equal(++processedBlocks, element.CurrentBlock);
- lastProgress = element;
- });
-
- if (singleMip)
- {
- await decoder.DecodeAsync(image);
- }
- else
- {
- await decoder.DecodeAllMipMapsAsync(image);
- }
-
- output.WriteLine("LastProgress = " + lastProgress);
-
- Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks);
- Assert.Equal(1, lastProgress.Percentage);
- }
-
- private async Task ExecuteDecodeProgressReport(BcDecoder decoder, DdsFile image, int expectedTotalBlocks, bool singleMip)
- {
- var lastProgress = new ProgressElement(0, 1);
-
- var processedBlocks = 0;
- decoder.Options.Progress = new SynchronousProgress(element =>
- {
- output.WriteLine($"Progress = {element.CurrentBlock} / {element.TotalBlocks}");
-
- Assert.Equal(++processedBlocks, element.CurrentBlock);
- lastProgress = element;
- });
-
- if (singleMip)
- {
- await decoder.DecodeAsync(image);
- }
- else
- {
- await decoder.DecodeAllMipMapsAsync(image);
- }
-
- output.WriteLine("LastProgress = " + lastProgress);
-
- Assert.Equal(expectedTotalBlocks, lastProgress.TotalBlocks);
- Assert.Equal(1, lastProgress.Percentage);
- }
-
- [Fact]
- public async Task DecodeProgressReportParallel()
- {
- var testImage = KtxLoader.TestDecompressBc1;
- var decoder = new BcDecoder
- {
- Options = { IsParallel = true }
- };
-
- var expectedTotal = 0;
-
- for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++)
- {
- expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height);
- }
-
- await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false);
- }
-
- [Fact]
- public async Task DecodeProgressReportNonParallel()
- {
- var testImage = KtxLoader.TestDecompressBc1;
- var decoder = new BcDecoder
- {
- Options = { IsParallel = false }
- };
-
- var expectedTotal = 0;
-
- for (var i = 0; i < testImage.header.NumberOfMipmapLevels; i++)
- {
- expectedTotal += decoder.GetBlockCount((int)testImage.MipMaps[i].Width, (int)testImage.MipMaps[i].Height);
- }
-
- await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false);
- }
-
- [Fact]
- public async Task DecodeProgressReportParallelOneMip()
- {
- var testImage = KtxLoader.TestDecompressBc1;
- var decoder = new BcDecoder
- {
- Options = { IsParallel = true }
- };
-
- var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height);
-
- await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true);
- }
-
- [Fact]
- public async Task DecodeProgressReportNonParallelOneMip()
- {
- var testImage = KtxLoader.TestDecompressBc1;
- var decoder = new BcDecoder
- {
- Options = { IsParallel = false }
- };
-
- var expectedTotal = decoder.GetBlockCount((int)testImage.MipMaps[0].Width, (int)testImage.MipMaps[0].Height);
-
- await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true);
- }
-
- [Fact]
- public async Task DecodeProgressReportParallelDds()
- {
- var testImage = DdsLoader.TestDecompressBc1;
- var decoder = new BcDecoder
- {
- Options = { IsParallel = true }
- };
-
- var expectedTotal = 0;
-
- for (var i = 0; i < testImage.header.dwMipMapCount; i++)
- {
- expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height);
- }
-
- await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false);
- }
-
- [Fact]
- public async Task DecodeProgressReportNonParallelDds()
- {
- var testImage = DdsLoader.TestDecompressBc1;
- var decoder = new BcDecoder
- {
- Options = { IsParallel = false }
- };
-
- var expectedTotal = 0;
-
- for (var i = 0; i < testImage.header.dwMipMapCount; i++)
- {
- expectedTotal += decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[i].Width, (int)testImage.Faces[0].MipMaps[i].Height);
- }
-
- await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, false);
- }
-
- [Fact]
- public async Task DecodeProgressReportParallelOneMipDds()
- {
- var testImage = DdsLoader.TestDecompressBc1;
- var decoder = new BcDecoder
- {
- Options = { IsParallel = true }
- };
-
- var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height);
-
- await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true);
- }
-
- [Fact]
- public async Task DecodeProgressReportNonParallelOneMipDds()
- {
- var testImage = DdsLoader.TestDecompressBc1;
- var decoder = new BcDecoder
- {
- Options = { IsParallel = false }
- };
-
- var expectedTotal = decoder.GetBlockCount((int)testImage.Faces[0].MipMaps[0].Width, (int)testImage.Faces[0].MipMaps[0].Height);
-
- await ExecuteDecodeProgressReport(decoder, testImage, expectedTotal, true);
- }
-
- [Fact]
- public async Task EncodeProgressReportParallelKtx()
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder
- {
- Options = { IsParallel = true },
- OutputOptions = { FileFormat = OutputFileFormat.Ktx }
- };
-
- var expectedTotal = 0;
-
- for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++)
- {
- encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH);
- expectedTotal += encoder.GetBlockCount(mW, mH);
- }
-
- await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
- }
-
- [Fact]
- public async Task EncodeProgressReportNonParallelKtx()
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder
- {
- Options = { IsParallel = false },
- OutputOptions = { FileFormat = OutputFileFormat.Ktx }
- };
-
- var expectedTotal = 0;
-
- for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++)
- {
- encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH);
- expectedTotal += encoder.GetBlockCount(mW, mH);
- }
-
- await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
- }
-
- [Fact]
- public async Task EncodeProgressReportParallelOneMipKtx()
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder
- {
- Options = { IsParallel = true },
- OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx }
- };
-
- var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height);
-
- await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
- }
-
- [Fact]
- public async Task EncodeProgressReportNonParallelOneMipKtx()
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder
- {
- Options = { IsParallel = false },
- OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Ktx }
- };
-
- var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height);
-
- await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
- }
-
- [Fact]
- public async Task EncodeProgressReportParallelDds()
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder
- {
- Options = { IsParallel = true },
- OutputOptions = { FileFormat = OutputFileFormat.Dds }
- };
-
- var expectedTotal = 0;
-
- for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++)
- {
- encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH);
- expectedTotal += encoder.GetBlockCount(mW, mH);
- }
-
- await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
- }
-
- [Fact]
- public async Task EncodeProgressReportNonParallelDds()
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder
- {
- Options = { IsParallel = false },
- OutputOptions = { FileFormat = OutputFileFormat.Dds }
- };
-
- var expectedTotal = 0;
-
- for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++)
- {
- encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH);
- expectedTotal += encoder.GetBlockCount(mW, mH);
- }
-
- await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
- }
-
- [Fact]
- public async Task EncodeProgressReportParallelOneMipDds()
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder
- {
- Options = { IsParallel = true },
- OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds }
- };
-
- var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height);
-
- await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
- }
-
- [Fact]
- public async Task EncodeProgressReportNonParallelOneMipDds()
- {
- var testImage = ImageLoader.TestBlur1;
- var encoder = new BcEncoder
- {
- Options = { IsParallel = false },
- OutputOptions = { MaxMipMapLevel = 1, FileFormat = OutputFileFormat.Dds }
- };
-
- var expectedTotal = encoder.GetBlockCount(testImage.Width, testImage.Height);
-
- await ExecuteEncodeProgressReport(encoder, testImage, expectedTotal);
- }
- }
-
- class SynchronousProgress : IProgress
- {
- private readonly Action handler;
-
- public SynchronousProgress(Action handler)
- {
- this.handler = handler;
- }
-
- public void Report(T value)
- {
- handler(value);
- }
- }
-}
diff --git a/BCnEncTests/RawTests.cs b/BCnEncTests/RawTests.cs
deleted file mode 100644
index 8bfc6f8..0000000
--- a/BCnEncTests/RawTests.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-using System.IO;
-using BCnEncoder.Decoder;
-using BCnEncoder.Encoder;
-using BCnEncoder.ImageSharp;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using SixLabors.ImageSharp.Processing;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class RawTests
- {
- [Fact]
- public void EncodeDecode()
- {
- var inputImage = ImageLoader.TestGradient1;
- var decoder = new BcDecoder();
- var encoder = new BcEncoder
- {
- OutputOptions = { Quality = CompressionQuality.BestQuality }
- };
-
- var encodedRawBytes = encoder.EncodeToRawBytes(inputImage);
- var decodedImage = decoder.DecodeRawToImageRgba32(encodedRawBytes[0], inputImage.Width, inputImage.Height, CompressionFormat.Bc1);
-
- var originalPixels = TestHelper.GetSinglePixelArray(ImageLoader.TestGradient1);
- var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage);
-
- TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality);
- }
-
- [Fact]
- public void EncodeDecodeStream()
- {
- var inputImage = ImageLoader.TestGradient1;
- var decoder = new BcDecoder();
- var encoder = new BcEncoder
- {
- OutputOptions = { Quality = CompressionQuality.BestQuality }
- };
-
- var encodedRawBytes = encoder.EncodeToRawBytes(inputImage);
-
- using var ms = new MemoryStream(encodedRawBytes[0]);
-
- Assert.Equal(0, ms.Position);
-
- var decodedImage = decoder.DecodeRawToImageRgba32(ms, inputImage.Width, inputImage.Height, CompressionFormat.Bc1);
-
- var originalPixels = TestHelper.GetSinglePixelArray(inputImage);
- var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage);
-
- TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality);
- }
-
-
- [Fact]
- public void EncodeDecodeAllMipMapsStream()
- {
- var inputImage = ImageLoader.TestGradient1;
- var decoder = new BcDecoder();
- var encoder = new BcEncoder
- {
- OutputOptions =
- {
- Quality = CompressionQuality.BestQuality,
- GenerateMipMaps = true,
- MaxMipMapLevel = 0
- }
- };
-
- using var ms = new MemoryStream();
-
- var encodedRawBytes = encoder.EncodeToRawBytes(inputImage);
-
- var mipLevels = encoder.CalculateNumberOfMipLevels(inputImage);
- Assert.True(mipLevels > 1);
-
- for (var i = 0; i < mipLevels; i++)
- {
- ms.Write(encodedRawBytes[i]);
- }
-
- ms.Position = 0;
- Assert.Equal(0, ms.Position);
-
- for (var i = 0; i < mipLevels; i++)
- {
- encoder.CalculateMipMapSize(inputImage, i, out var mipWidth, out var mipHeight);
- using var resized = inputImage.Clone(x => x.Resize(mipWidth, mipHeight));
-
- var decodedImage = decoder.DecodeRawToImageRgba32(ms, mipWidth, mipHeight, CompressionFormat.Bc1);
-
- var originalPixels = TestHelper.GetSinglePixelArray(resized);
- var decodedPixels = TestHelper.GetSinglePixelArray(decodedImage);
-
- TestHelper.AssertPixelsEqual(originalPixels, decodedPixels, encoder.OutputOptions.Quality);
- }
-
- encoder.CalculateMipMapSize(inputImage, mipLevels - 1, out var lastMWidth, out var lastMHeight);
- Assert.Equal(1, lastMWidth);
- Assert.Equal(1, lastMHeight);
- }
- }
-}
diff --git a/BCnEncTests/SingleBlockTests.cs b/BCnEncTests/SingleBlockTests.cs
deleted file mode 100644
index 89b0778..0000000
--- a/BCnEncTests/SingleBlockTests.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Runtime.InteropServices;
-using System.Text;
-using BCnEncoder.Decoder;
-using BCnEncoder.Encoder;
-using BCnEncoder.Shared;
-using BCnEncTests.Support;
-using CommunityToolkit.HighPerformance;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-using Xunit;
-
-namespace BCnEncTests
-{
- public class SingleBlockTests
- {
-
- [Theory]
- [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)]
- [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)]
- [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)]
- [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)]
- [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)]
- public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQuality quality)
- {
- var testImage = ImageLoader.TestAlpha1;
-
- var encoder = new BcEncoder()
- {
- OutputOptions =
- {
- Format = format,
- Quality = quality
- }
- };
-
- var pixels = TestHelper.GetSinglePixelArray(testImage);
- var colors = MemoryMarshal.Cast(pixels)
- .AsSpan2D(testImage.Height, testImage.Width);
-
- var ms = new MemoryStream();
-
- for (var y = 0; y < testImage.Height; y+=4)
- {
- for (var x = 0; x < testImage.Width; x+=4)
- {
- encoder.EncodeBlock(colors.Slice(y, x, 4, 4), ms);
- }
- }
-
- Assert.Equal(ms.Position, encoder.GetBlockSize() * encoder.GetBlockCount(testImage.Width, testImage.Height));
-
- ms.Position = 0;
-
- var decoder = new BcDecoder();
-
- var decoded = new ColorRgba32[testImage.Height, testImage.Width];
-
- for (var y = 0; y < testImage.Height; y += 4)
- {
- for (var x = 0; x < testImage.Width; x += 4)
- {
- decoder.DecodeBlock(ms, format,
- decoded.AsSpan2D().Slice(y, x, 4, 4));
- }
- }
-
- colors.TryGetSpan(out var oPixels);
- decoded.AsSpan2D().TryGetSpan(out var dPixels);
- var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels,
- format != CompressionFormat.Bc1);
-
- TestHelper.AssertPSNR(psnr, quality);
- }
-
- [Theory]
- [InlineData(CompressionFormat.Bc1, CompressionQuality.Fast)]
- [InlineData(CompressionFormat.Bc1, CompressionQuality.Balanced)]
- [InlineData(CompressionFormat.Bc3, CompressionQuality.Fast)]
- [InlineData(CompressionFormat.Bc3, CompressionQuality.Balanced)]
- [InlineData(CompressionFormat.Bc7, CompressionQuality.Fast)]
- public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality quality)
- {
- var testImage = ImageLoader.TestAlpha1;
-
- var encoder = new BcEncoder()
- {
- OutputOptions =
- {
- Format = format,
- Quality = quality
- }
- };
-
- var pixels = TestHelper.GetSinglePixelArray(testImage);
- var colors = MemoryMarshal.Cast(pixels)
- .AsSpan2D(testImage.Height, testImage.Width);
-
- Span buffer = new byte[encoder.GetBlockSize() * encoder.GetBlockCount(testImage.Width, testImage.Height)];
-
- var blockIndex = 0;
- for (var y = 0; y < testImage.Height; y += 4)
- {
- for (var x = 0; x < testImage.Width; x += 4)
- {
- var bytes = encoder.EncodeBlock(colors.Slice(y, x, 4, 4));
- bytes.CopyTo(buffer.Slice(blockIndex * encoder.GetBlockSize()));
- blockIndex++;
- }
- }
-
- var decoder = new BcDecoder();
-
- var decoded = new ColorRgba32[testImage.Height, testImage.Width];
-
- blockIndex = 0;
- for (var y = 0; y < testImage.Height; y += 4)
- {
- for (var x = 0; x < testImage.Width; x += 4)
- {
-
- decoder.DecodeBlock(
- buffer.Slice(
- blockIndex * decoder.GetBlockSize(format),
- decoder.GetBlockSize(format)
- ),
- format,
- decoded.AsSpan2D().Slice(y, x, 4, 4));
- blockIndex++;
- }
- }
-
- colors.TryGetSpan(out var oPixels);
- decoded.AsSpan2D().TryGetSpan(out var dPixels);
- var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels,
- format != CompressionFormat.Bc1);
-
- TestHelper.AssertPSNR(psnr, quality);
- }
- }
-}
diff --git a/BCnEncTests/Support/HdrLoader.cs b/BCnEncTests/Support/HdrLoader.cs
index 519f02e..6080cf4 100644
--- a/BCnEncTests/Support/HdrLoader.cs
+++ b/BCnEncTests/Support/HdrLoader.cs
@@ -12,7 +12,7 @@ public static class HdrLoader
{
public static HdrImage TestHdrKiara { get; } = HdrImage.Read("../../../testImages/test_hdr_kiara.hdr");
public static HdrImage TestHdrProbe { get; } = HdrImage.Read("../../../testImages/test_hdr_probe.hdr");
- public static Image ReferenceKiara { get; } = ImageLoader.LoadTestImage("../../../testImages/test_hdr_kiara.png");
+ public static Image ReferenceKiara { get; } = ImageLoader.LoadTestImageSharp("../../../testImages/test_hdr_kiara.png");
public static DdsFile TestHdrKiaraDds { get; } =
DdsLoader.LoadDdsFile("../../../testImages/test_hdr_kiara_bc6h.dds");
public static KtxFile TestHdrKiaraKtx { get; } =
diff --git a/BCnEncTests/Support/ImageLoader.cs b/BCnEncTests/Support/ImageLoader.cs
index 3a825d2..37d410b 100644
--- a/BCnEncTests/Support/ImageLoader.cs
+++ b/BCnEncTests/Support/ImageLoader.cs
@@ -1,6 +1,7 @@
using System.IO;
using BCnEncoder.Shared;
using BCnEncoder.Shared.ImageFiles;
+using CommunityToolkit.HighPerformance;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -8,21 +9,21 @@ namespace BCnEncTests.Support
{
public static class ImageLoader
{
- public static Image TestDiffuse1 { get; } = LoadTestImage("../../../testImages/test_diffuse_1_512.jpg");
- public static Image TestBlur1 { get; } = LoadTestImage("../../../testImages/test_blur_1_512.jpg");
- public static Image TestNormal1 { get; } = LoadTestImage("../../../testImages/test_normal_1_512.jpg");
- public static Image TestHeight1 { get; } = LoadTestImage("../../../testImages/test_height_1_512.jpg");
- public static Image TestGradient1 { get; } = LoadTestImage("../../../testImages/test_gradient_1_512.jpg");
+ public static Memory2D TestDiffuse1 { get; } = LoadTestImage("../../../testImages/test_diffuse_1_512.jpg");
+ public static Memory2D TestBlur1 { get; } = LoadTestImage("../../../testImages/test_blur_1_512.jpg");
+ public static Memory2D TestNormal1 { get; } = LoadTestImage("../../../testImages/test_normal_1_512.jpg");
+ public static Memory2D TestHeight1 { get; } = LoadTestImage("../../../testImages/test_height_1_512.jpg");
+ public static Memory2D TestGradient1 { get; } = LoadTestImage("../../../testImages/test_gradient_1_512.jpg");
- public static Image TestTransparentSprite1 { get; } = LoadTestImage("../../../testImages/test_transparent.png");
- public static Image TestAlphaGradient1 { get; } = LoadTestImage("../../../testImages/test_alphagradient_1_512.png");
- public static Image TestAlpha1 { get; } = LoadTestImage("../../../testImages/test_alpha_1_512.png");
- public static Image TestRedGreen1 { get; } = LoadTestImage("../../../testImages/test_red_green_1_64.png");
- public static Image TestRgbHard1 { get; } = LoadTestImage("../../../testImages/test_rgb_hard_1.png");
- public static Image TestLenna { get; } = LoadTestImage("../../../testImages/test_lenna_512.png");
- public static Image TestDecodingBc5Reference { get; } = LoadTestImage("../../../testImages/decoding_dds_bc5_reference.png");
+ public static Memory2D TestTransparentSprite1 { get; } = LoadTestImage("../../../testImages/test_transparent.png");
+ public static Memory2D TestAlphaGradient1 { get; } = LoadTestImage("../../../testImages/test_alphagradient_1_512.png");
+ public static Memory2D TestAlpha1 { get; } = LoadTestImage("../../../testImages/test_alpha_1_512.png");
+ public static Memory2D TestRedGreen1 { get; } = LoadTestImage("../../../testImages/test_red_green_1_64.png");
+ public static Memory2D TestRgbHard1 { get; } = LoadTestImage("../../../testImages/test_rgb_hard_1.png");
+ public static Memory2D TestLenna { get; } = LoadTestImage("../../../testImages/test_lenna_512.png");
+ public static Memory2D TestDecodingBc5Reference { get; } = LoadTestImage("../../../testImages/decoding_dds_bc5_reference.png");
- public static Image[] TestCubemap { get; } = {
+ public static Memory2D[] TestCubemap { get; } = {
LoadTestImage("../../../testImages/cubemap/right.png"),
LoadTestImage("../../../testImages/cubemap/left.png"),
LoadTestImage("../../../testImages/cubemap/top.png"),
@@ -31,10 +32,28 @@ public static class ImageLoader
LoadTestImage("../../../testImages/cubemap/forward.png")
};
- internal static Image LoadTestImage(string filename)
+ internal static Memory2D LoadTestImage(string filename)
+ {
+ using var image = Image.Load(filename);
+ return ToMemory2D(image);
+ }
+
+ internal static Image LoadTestImageSharp(string filename)
{
return Image.Load(filename);
}
+
+ private static Memory2D