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
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/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/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);
}
///
diff --git a/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs b/BCnEnc.Net/Encoder/Bptc/BptcEncodingHelpers.cs
index 3f19504..0eb201d 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)
@@ -50,9 +54,20 @@ 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.
+ #if NETSTANDARD2_0
+ var indices = new int[16];
+ reducedIndicesBlock.AsSpan.CopyTo(indices);
+ #endif
int CalculatePartitionError(int partitionIndex)
- {
+ {
+ #if NETSTANDARD2_1
+ var indices = reducedIndicesBlock.AsSpan;
+ #endif
+
var error = 0;
ReadOnlySpan partitionTable = Bc7Block.Subsets2PartitionTable[partitionIndex];
Span subset0 = stackalloc int[numDistinctClusters];
@@ -60,12 +75,12 @@ 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)
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset0[r]++;
var count = subset0[r];
if (count > subset0[max0Idx])
@@ -75,7 +90,7 @@ int CalculatePartitionError(int partitionIndex)
}
else
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset1[r]++;
var count = subset1[r];
if (count > subset1[max1Idx])
@@ -90,11 +105,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++;
}
}
@@ -110,8 +125,20 @@ 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.
+ #if NETSTANDARD2_0
+ var indices = new int[16];
+ reducedIndicesBlock.AsSpan.CopyTo(indices);
+ #endif
+
int CalculatePartitionError(int partitionIndex)
{
+ #if NETSTANDARD2_1
+ var indices = reducedIndicesBlock.AsSpan;
+ #endif
+
var error = 0;
ReadOnlySpan partitionTable = Bc7Block.Subsets3PartitionTable[partitionIndex];
@@ -122,12 +149,12 @@ 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)
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset0[r]++;
var count = subset0[r];
if (count > subset0[max0Idx])
@@ -137,7 +164,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])
@@ -147,7 +174,7 @@ int CalculatePartitionError(int partitionIndex)
}
else
{
- var r = reducedIndicesBlock[i];
+ var r = indices[i];
subset2[r]++;
var count = subset2[r];
if (count > subset2[max2Idx])
@@ -162,15 +189,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/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/MipMapper.cs b/BCnEnc.Net/Shared/MipMapper.cs
index e44dbf0..ddbcea7 100644
--- a/BCnEnc.Net/Shared/MipMapper.cs
+++ b/BCnEnc.Net/Shared/MipMapper.cs
@@ -164,15 +164,14 @@ 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];
+ 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 +185,21 @@ 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];
+ 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 +213,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/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()
{
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/BCnEncTests.Framework.csproj b/BCnEncTests.Framework/BCnEncTests.Framework.csproj
new file mode 100644
index 0000000..c928b90
--- /dev/null
+++ b/BCnEncTests.Framework/BCnEncTests.Framework.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net481
+ false
+ true
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BCnEncTests.Framework/HdrImageTests.cs b/BCnEncTests.Framework/HdrImageTests.cs
new file mode 100644
index 0000000..75e9c8e
--- /dev/null
+++ b/BCnEncTests.Framework/HdrImageTests.cs
@@ -0,0 +1,63 @@
+using System;
+using System.IO;
+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 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);
+
+ var rgba = new ColorRgba32[hdrImg.pixels.Length];
+ for (var i = 0; i < hdrImg.pixels.Length; i++)
+ {
+ var p = hdrImg.pixels[i];
+ rgba[i] = new ColorRgba32(
+ (byte)(Math.Max(0, Math.Min(1, p.r)) * 255 + 0.5f),
+ (byte)(Math.Max(0, Math.Min(1, p.g)) * 255 + 0.5f),
+ (byte)(Math.Max(0, Math.Min(1, p.b)) * 255 + 0.5f),
+ 255);
+ }
+ var converted = new Memory2D(rgba, hdrImg.height, hdrImg.width);
+ var reference = ImageLoader.LoadTestImage("../../../../BCnEncTests/testImages/test_hdr_kiara.png");
+ TestHelper.AssertImagesEqual(reference, converted, CompressionQuality.BestQuality);
+ }
+ }
+
+ [Fact]
+ public async Task DecodeAllMipMapsHdrStreamAsync()
+ {
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Format = CompressionFormat.Bc6U;
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ encoder.OutputOptions.GenerateMipMaps = true;
+
+ var decoder = new BcDecoder();
+ var input = HdrLoader.TestHdrKiara;
+ var ktxWithMips = encoder.EncodeToKtxHdr(new Memory2D(input.pixels, input.height, input.width));
+ using (var ms = new MemoryStream())
+ {
+ ktxWithMips.Write(ms);
+ ms.Position = 0;
+
+ var images = await decoder.DecodeAllMipMapsHdr2DAsync(ms);
+ Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length);
+ Assert.True(images.Length > 1);
+ }
+ }
+ }
+}
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/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..0a90454
--- /dev/null
+++ b/BCnEncTests.Framework/Support/ImageLoader.cs
@@ -0,0 +1,104 @@
+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..ba9cc8c
--- /dev/null
+++ b/BCnEncTests.Framework/Support/TestHelper.cs
@@ -0,0 +1,284 @@
+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);
+ }
+ }
+
+ 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/AtcTests.cs b/BCnEncTests.Shared/AtcTests.cs
similarity index 79%
rename from BCnEncTests/AtcTests.cs
rename to BCnEncTests.Shared/AtcTests.cs
index d02da3e..eff342c 100644
--- a/BCnEncTests/AtcTests.cs
+++ b/BCnEncTests.Shared/AtcTests.cs
@@ -2,8 +2,8 @@
using BCnEncoder.Encoder;
using BCnEncoder.Shared;
using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
using Xunit;
-using BCnEncoder.ImageSharp;
namespace BCnEncTests
{
@@ -12,96 +12,78 @@ 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);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(dds);
- // Assert
TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
}
}
diff --git a/BCnEncTests/BC1Tests.cs b/BCnEncTests.Shared/BC1Tests.cs
similarity index 91%
rename from BCnEncTests/BC1Tests.cs
rename to BCnEncTests.Shared/BC1Tests.cs
index bfc7320..314c284 100644
--- a/BCnEncTests/BC1Tests.cs
+++ b/BCnEncTests.Shared/BC1Tests.cs
@@ -1,15 +1,13 @@
using BCnEncoder.Shared;
-using SixLabors.ImageSharp.PixelFormats;
using Xunit;
-using BCnEncoder.ImageSharp;
namespace BCnEncTests
{
public class Bc1Tests
{
-
[Fact]
- public void Decode() {
+ public void Decode()
+ {
var block = new Bc1Block
{
color0 = new ColorRgb565(255, 255, 255),
@@ -60,7 +58,8 @@ public void Decode() {
}
[Fact]
- public void DecodeBlack() {
+ public void DecodeBlack()
+ {
var block = new Bc1Block
{
color0 = new ColorRgb565(200, 200, 200),
@@ -111,7 +110,8 @@ public void DecodeBlack() {
}
[Fact]
- public void DecodeAlpha() {
+ public void DecodeAlpha()
+ {
var block = new Bc1Block
{
color0 = new ColorRgb565(200, 200, 200),
@@ -145,10 +145,10 @@ public void DecodeAlpha() {
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(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);
diff --git a/BCnEncTests.Shared/BCnEncTests.Shared.projitems b/BCnEncTests.Shared/BCnEncTests.Shared.projitems
new file mode 100644
index 0000000..a221845
--- /dev/null
+++ b/BCnEncTests.Shared/BCnEncTests.Shared.projitems
@@ -0,0 +1,34 @@
+
+
+
+ $(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/Bc5Tests.cs b/BCnEncTests.Shared/Bc5Tests.cs
similarity index 94%
rename from BCnEncTests/Bc5Tests.cs
rename to BCnEncTests.Shared/Bc5Tests.cs
index 7eb37ff..4e361ef 100644
--- a/BCnEncTests/Bc5Tests.cs
+++ b/BCnEncTests.Shared/Bc5Tests.cs
@@ -1,5 +1,4 @@
using BCnEncoder.Decoder;
-using BCnEncoder.ImageSharp;
using BCnEncoder.Shared;
using BCnEncTests.Support;
using Xunit;
@@ -33,8 +32,8 @@ public void Bc5Indices()
public void Bc5DdsDecode()
{
var reference = ImageLoader.TestDecodingBc5Reference;
- var decoded = new BcDecoder().DecodeToImageRgba32(DdsLoader.TestDecompressBc5);
-
+ var decoded = new BcDecoder().Decode2D(DdsLoader.TestDecompressBc5);
+
var refSpan = TestHelper.GetSinglePixelArrayAsColors(reference);
var decSpan = TestHelper.GetSinglePixelArrayAsColors(decoded);
diff --git a/BCnEncTests/Bc6EncoderTests.cs b/BCnEncTests.Shared/Bc6EncoderTests.cs
similarity index 96%
rename from BCnEncTests/Bc6EncoderTests.cs
rename to BCnEncTests.Shared/Bc6EncoderTests.cs
index 7352f86..ced39c6 100644
--- a/BCnEncTests/Bc6EncoderTests.cs
+++ b/BCnEncTests.Shared/Bc6EncoderTests.cs
@@ -1,8 +1,5 @@
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;
@@ -40,7 +37,7 @@ public void Quantize(int initialQuantizedValue, int endpointBits, bool signed)
}
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);
@@ -95,13 +92,11 @@ public void RgbBoundingBox()
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,
@@ -113,7 +108,7 @@ public void PackMode3()
var indices = new byte[16];
for (var i = 1; i < indices.Length; i++)
{
- indices[i] = (byte) random.Next(1 << 4);
+ indices[i] = (byte)random.Next(1 << 4);
}
var block = Bc6Block.PackType3(ep0, ep1, indices);
@@ -133,7 +128,6 @@ public void PackMode3()
}
}
-
[Theory]
[InlineData(Bc6BlockType.Type0)]
[InlineData(Bc6BlockType.Type1)]
@@ -173,7 +167,7 @@ internal void EncodeAllModesUnsigned(Bc6BlockType type)
Bc6Block encoded;
var badTransform = false;
- if(type.HasSubsets())
+ if (type.HasSubsets())
{
var indexBlock = Bc6Encoder.CreateClusterIndexBlock(testBlock, out var numClusters, 2);
var best2SubsetPartitions = BptcEncodingHelpers.Rank2SubsetPartitions(indexBlock, numClusters, true);
@@ -271,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++)
{
@@ -281,7 +275,6 @@ public void Encode()
var error = decoded.CalculateError(blocks[i]);
if (error > 0.06 || i == 14749)
{
- Debugger.Break();
encoded = Bc6Encoder.Bc6EncoderBalanced.EncodeBlock(blocks[i], signed);
}
}
@@ -337,7 +330,7 @@ public void EncodeProbeSignedFast()
}
[Fact]
- public void EncodeProbSignedBalanced()
+ public void EncodeProbeSignedBalanced()
{
TestHelper.ExecuteHdrEncodingTest(HdrLoader.TestHdrProbe, CompressionFormat.Bc6S, CompressionQuality.Balanced,
"encoding_bc6_probe_signed_balanced.ktx", output);
@@ -360,8 +353,10 @@ public void EncodeToKtx()
var ktx = encoder.EncodeToKtxHdr(HdrLoader.TestHdrKiara.PixelMemory);
- using var fs = File.OpenWrite("encoding_bc6_ktx.ktx");
- ktx.Write(fs);
+ using (var fs = File.OpenWrite("encoding_bc6_ktx.ktx"))
+ {
+ ktx.Write(fs);
+ }
}
[Fact]
@@ -374,8 +369,10 @@ public void EncodeToDds()
var dds = encoder.EncodeToDdsHdr(HdrLoader.TestHdrKiara.PixelMemory);
- using var fs = File.OpenWrite("encoding_bc6_dds.dds");
- dds.Write(fs);
+ using (var fs = File.OpenWrite("encoding_bc6_dds.dds"))
+ {
+ dds.Write(fs);
+ }
}
[Fact]
diff --git a/BCnEncTests/Bc6HDecoderTests.cs b/BCnEncTests.Shared/Bc6HDecoderTests.cs
similarity index 59%
rename from BCnEncTests/Bc6HDecoderTests.cs
rename to BCnEncTests.Shared/Bc6HDecoderTests.cs
index 60a99fa..2cbb609 100644
--- a/BCnEncTests/Bc6HDecoderTests.cs
+++ b/BCnEncTests.Shared/Bc6HDecoderTests.cs
@@ -6,8 +6,6 @@
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;
@@ -26,28 +24,22 @@ 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);
+ 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);
hdr.pixels = decoded;
- using var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr");
- hdr.Write(sfs);
+ using (var sfs = File.OpenWrite("decoding_test_dds_bc6h.hdr"))
+ {
+ hdr.Write(sfs);
+ }
- img.SaveAsPng("decoding_test_dds_bc6h.png");
+ 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);
}
@@ -58,28 +50,22 @@ 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);
+ 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);
hdr.pixels = decoded;
- using var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr");
- hdr.Write(sfs);
+ using (var sfs = File.OpenWrite("decoding_test_ktx_bc6h.hdr"))
+ {
+ hdr.Write(sfs);
+ }
- img.SaveAsPng("decoding_test_ktx_bc6h.png");
+ 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);
}
@@ -93,24 +79,32 @@ 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++)
+ Stream fs;
+#if NETCOREAPP
+ fs = File.OpenRead("../../../testImages/test_hdr_kiara_dds_float16_data.bin");
+#else
+ fs = File.OpenRead("../../../../BCnEncTests/testImages/test_hdr_kiara_dds_float16_data.bin");
+#endif
+ using (fs)
+ using (var ms = new MemoryStream())
{
- 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);
+ 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);
+ }
}
}
@@ -129,7 +123,7 @@ public void DecodeModes()
{
var block = blocks[i];
var mode = block.Type;
- modes[(int) mode]++;
+ modes[(int)mode]++;
}
for (var i = 0; i < modes.Length; i++)
@@ -163,16 +157,17 @@ public void DecodeErrorBlock()
var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc6U) * width * height;
var buffer = new byte[bufferSize];
- Random r = new Random(44);
+ 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);
+ using (var fs = File.OpenWrite("test_decode_bc6h_error.hdr"))
+ {
+ image.Write(fs);
+ }
}
-
}
}
diff --git a/BCnEncTests/Bc7BlockTests.cs b/BCnEncTests.Shared/Bc7BlockTests.cs
similarity index 90%
rename from BCnEncTests/Bc7BlockTests.cs
rename to BCnEncTests.Shared/Bc7BlockTests.cs
index c05fdc6..8ac34ea 100644
--- a/BCnEncTests/Bc7BlockTests.cs
+++ b/BCnEncTests.Shared/Bc7BlockTests.cs
@@ -3,12 +3,13 @@
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 BCnEncTests.Support;
using Xunit;
+using CommunityToolkit.HighPerformance;
+
namespace BCnEncTests
{
public class Bc7BlockTests
@@ -79,15 +80,17 @@ public void DecodeErrorBlock()
var bufferSize = decoder.GetBlockSize(CompressionFormat.Bc7) * width * height;
var buffer = new byte[bufferSize];
- Random r = new Random(50);
+ var 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);
+ var decoded = new Memory2D(pixels, height * 4, width * 4);
+ using (var fs = File.OpenWrite("test_decode_bc7_error.png"))
+ {
+ TestHelper.SaveAsPng(decoded, fs);
+ }
}
#region Type Packs
@@ -105,9 +108,7 @@ private void Type0Pack(Span output)
new byte[]{0, 0, 0b1111},
new byte[]{0, 0, 0b1000}
};
- var pBits = new byte[] {
- 1, 0, 1, 0, 1, 1
- };
+ var pBits = new byte[] { 1, 0, 1, 0, 1, 1 };
var indices = new byte[] {
0, 1, 2, 3,
0, 1, 2, 3,
@@ -121,16 +122,13 @@ private void Type0Pack(Span output)
Assert.Equal(i, output[i].PartitionSetId);
}
- pBits = new byte[] {
- 0, 0, 0, 0, 0, 0
- };
+ 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);
@@ -138,16 +136,13 @@ private void Type0Pack(Span output)
Assert.Equal(i, output[i].PartitionSetId);
}
- pBits = new byte[] {
- 1, 1, 1, 1, 1, 1
- };
+ 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);
@@ -155,16 +150,13 @@ private void Type0Pack(Span output)
Assert.Equal(i, output[i].PartitionSetId);
}
- pBits = new byte[] {
- 0, 1, 0, 1, 0, 0
- };
+ 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);
@@ -183,9 +175,7 @@ private void Type1Pack(Span output)
new byte[]{0, 0, 0b111111},
new byte[]{0, 0, 0b1010}
};
- var pBits = new byte[] {
- 1, 1
- };
+ var pBits = new byte[] { 1, 1 };
var indices = new byte[] {
0, 1, 2, 3,
0, 1, 2, 3,
@@ -237,9 +227,7 @@ private void Type3Pack(Span output)
new byte[]{0, 0, 0b1111111},
new byte[]{0, 0, 0b11010}
};
- var pBits = new byte[] {
- 1, 0, 0, 1
- };
+ var pBits = new byte[] { 1, 0, 0, 1 };
var indices = new byte[] {
0, 1, 2, 3,
0, 1, 2, 3,
@@ -261,17 +249,13 @@ private void Type4Pack(Span output)
new byte[]{0b11111, 0, 0b0100},
new byte[]{0, 0b11111, 0b0100}
};
- var alphaEndPoints = new byte[]{
- 0b111111,
- 0
- };
+ 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,
@@ -295,13 +279,10 @@ private void Type5Pack(Span output)
{
var colorEndpoints = new[] {
//subset 1
- new byte[]{0b1111111, 0b0100, 0},
+ new byte[]{0b1111111, 0b0100, 0},
new byte[]{0, 0, 0b1111111}
};
- var alphaEndPoints = new byte[]{
- 255,
- 100
- };
+ var alphaEndPoints = new byte[] { 255, 100 };
var colorIndices = new byte[] {
0, 1, 2, 3,
0, 1, 2, 3,
@@ -328,12 +309,10 @@ private void Type6Pack(Span output)
{
var colorEndpoints = new[] {
//subset 1
- new byte[]{0b1111111, 0b0100, 0, 0b1111111},
+ new byte[]{0b1111111, 0b0100, 0, 0b1111111},
new byte[]{0, 0, 0b1111111, 0b1111}
};
- var pBits = new byte[] {
- 1, 0
- };
+ var pBits = new byte[] { 1, 0 };
var colorIndices = new byte[] {
0, 1, 2, 3,
4, 5, 6, 7,
@@ -351,15 +330,13 @@ private void Type7Pack(Span output)
{
var colorEndpoints = new[] {
//subset 1
- new byte[]{0b11111, 0b0100, 0, 0b11111},
+ new byte[]{0b11111, 0b0100, 0, 0b11111},
new byte[]{0, 0b11111, 0, 0b111},
//subset 2
- new byte[]{0b11111, 0b0100, 0, 0b1111},
+ new byte[]{0b11111, 0b0100, 0, 0b1111},
new byte[]{0b10100, 0, 0b11111, 0b11111}
};
- var pBits = new byte[] {
- 1, 0, 1, 0
- };
+ var pBits = new byte[] { 1, 0, 1, 0 };
var indices = new byte[] {
0, 1, 2, 3,
0, 1, 2, 3,
diff --git a/BCnEncTests/BgraTests.cs b/BCnEncTests.Shared/BgraTests.cs
similarity index 79%
rename from BCnEncTests/BgraTests.cs
rename to BCnEncTests.Shared/BgraTests.cs
index 7ae666e..6f0e8bf 100644
--- a/BCnEncTests/BgraTests.cs
+++ b/BCnEncTests.Shared/BgraTests.cs
@@ -1,6 +1,5 @@
using BCnEncoder.Decoder;
using BCnEncoder.Encoder;
-using BCnEncoder.ImageSharp;
using BCnEncoder.Shared;
using BCnEncTests.Support;
using Xunit;
@@ -12,66 +11,52 @@ 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);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(ktx);
- // Assert
TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
}
}
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.Shared/CancellationTests.cs b/BCnEncTests.Shared/CancellationTests.cs
new file mode 100644
index 0000000..9932c6c
--- /dev/null
+++ b/BCnEncTests.Shared/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.Shared/ClusterTests.cs b/BCnEncTests.Shared/ClusterTests.cs
new file mode 100644
index 0000000..a30ab52
--- /dev/null
+++ b/BCnEncTests.Shared/ClusterTests.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+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 = new Memory2D(pixels, height, width);
+ using (var fs = File.OpenWrite("test_cluster.png"))
+ {
+ TestHelper.SaveAsPng(result, fs);
+ }
+ }
+ }
+}
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/DdsReadTests.cs b/BCnEncTests.Shared/DdsReadTests.cs
similarity index 70%
rename from BCnEncTests/DdsReadTests.cs
rename to BCnEncTests.Shared/DdsReadTests.cs
index 9a5ba2e..3c2a35e 100644
--- a/BCnEncTests/DdsReadTests.cs
+++ b/BCnEncTests.Shared/DdsReadTests.cs
@@ -1,10 +1,8 @@
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
@@ -38,16 +36,18 @@ public void ReadBc7()
[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 fs = File.OpenRead(DdsLoader.TestDecompressBc1Name))
{
- using var outFs = File.OpenWrite($"decoding_test_dds_stream_bc1_mip{i}.png");
- images[i].SaveAsPng(outFs);
- images[i].Dispose();
+ 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/DdsWritingTests.cs b/BCnEncTests.Shared/DdsWritingTests.cs
similarity index 100%
rename from BCnEncTests/DdsWritingTests.cs
rename to BCnEncTests.Shared/DdsWritingTests.cs
diff --git a/BCnEncTests/DecodingAsyncTests.cs b/BCnEncTests.Shared/DecodingAsyncTests.cs
similarity index 63%
rename from BCnEncTests/DecodingAsyncTests.cs
rename to BCnEncTests.Shared/DecodingAsyncTests.cs
index 04cfa29..cba775b 100644
--- a/BCnEncTests/DecodingAsyncTests.cs
+++ b/BCnEncTests.Shared/DecodingAsyncTests.cs
@@ -1,11 +1,11 @@
-using System.ComponentModel.DataAnnotations;
+using System;
using System.IO;
using System.Threading.Tasks;
using BCnEncoder.Decoder;
using BCnEncoder.Encoder;
-using BCnEncoder.ImageSharp;
using BCnEncoder.Shared;
using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
using Xunit;
namespace BCnEncTests
@@ -20,10 +20,9 @@ public async Task DecodeAsync()
var original = ImageLoader.TestGradient1;
var file = encoder.EncodeToKtx(original);
- var image = await decoder.DecodeToImageRgba32Async(file);
+ var image = await decoder.Decode2DAsync(file);
- TestHelper.AssertImagesEqual(original, image,encoder.OutputOptions.Quality);
- image.Dispose();
+ TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
}
[Fact]
@@ -34,11 +33,9 @@ public async Task DecodeAllMipMapsAsync()
var original = ImageLoader.TestGradient1;
var file = encoder.EncodeToKtx(original);
- var images = await decoder.DecodeAllMipMapsToImageRgba32Async(file);
+ var images = await decoder.DecodeAllMipMaps2DAsync(file);
TestHelper.AssertImagesEqual(original, images[0], encoder.OutputOptions.Quality);
- foreach(var img in images)
- img.Dispose();
}
[Fact]
@@ -50,13 +47,17 @@ public async Task DecodeRawAsync()
var file = encoder.EncodeToKtx(original);
- var ms = new MemoryStream(file.MipMaps[0].Faces[0].Data);
+ var rawBytes = file.MipMaps[0].Faces[0].Data;
+ var mipWidth = (int)file.MipMaps[0].Width;
+ var mipHeight = (int)file.MipMaps[0].Height;
- var image = await decoder.DecodeRawToImageRgba32Async(ms,
- (int) file.MipMaps[0].Width, (int) file.MipMaps[0].Height, CompressionFormat.Bc1);
+ var decoded = await Task.Run(() =>
+ {
+ var pixels = decoder.DecodeRaw(rawBytes, mipWidth, mipHeight, CompressionFormat.Bc1);
+ return new Memory2D(pixels, mipHeight, mipWidth);
+ });
- TestHelper.AssertImagesEqual(original, image, encoder.OutputOptions.Quality);
- image.Dispose();
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
}
}
}
diff --git a/BCnEncTests/DecodingTests.cs b/BCnEncTests.Shared/DecodingTests.cs
similarity index 100%
rename from BCnEncTests/DecodingTests.cs
rename to BCnEncTests.Shared/DecodingTests.cs
diff --git a/BCnEncTests/EncoderOptionsTests.cs b/BCnEncTests.Shared/EncoderOptionsTests.cs
similarity index 92%
rename from BCnEncTests/EncoderOptionsTests.cs
rename to BCnEncTests.Shared/EncoderOptionsTests.cs
index 677fce4..f73ecd3 100644
--- a/BCnEncTests/EncoderOptionsTests.cs
+++ b/BCnEncTests.Shared/EncoderOptionsTests.cs
@@ -1,16 +1,11 @@
-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)]
@@ -28,7 +23,7 @@ public void MaxMipMaps(int requestedMipMaps)
}
};
- Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage));
+ Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height));
var ktx = encoder.EncodeToKtx(testImage);
@@ -55,7 +50,7 @@ public void GenerateMipMaps()
}
};
- Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage));
+ Assert.Equal(requestedMipMaps, encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height));
var ktx = encoder.EncodeToKtx(testImage);
diff --git a/BCnEncTests/EncodingAsyncTest.cs b/BCnEncTests.Shared/EncodingAsyncTest.cs
similarity index 57%
rename from BCnEncTests/EncodingAsyncTest.cs
rename to BCnEncTests.Shared/EncodingAsyncTest.cs
index 6d3e9ff..88cfb3f 100644
--- a/BCnEncTests/EncodingAsyncTest.cs
+++ b/BCnEncTests.Shared/EncodingAsyncTest.cs
@@ -1,11 +1,10 @@
+using System;
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 CommunityToolkit.HighPerformance;
using Xunit;
namespace BCnEncTests
@@ -14,8 +13,8 @@ public class EncodingAsyncTest
{
private readonly BcEncoder encoder;
private readonly BcDecoder decoder;
- private readonly Image originalImage;
- private readonly Image[] originalCubeMap;
+ private readonly Memory2D originalImage;
+ private readonly Memory2D[] originalCubeMap;
public EncodingAsyncTest()
{
@@ -29,20 +28,18 @@ public EncodingAsyncTest()
public async Task EncodeToDdsAsync()
{
var file = await encoder.EncodeToDdsAsync(originalImage);
- var image = decoder.DecodeToImageRgba32(file);
+ var image = decoder.Decode2D(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);
+ var image = decoder.Decode2D(file);
TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
- image.Dispose();
}
[Fact]
@@ -53,10 +50,11 @@ public async Task EncodeCubemapToDdsAsync()
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);
+ var rawPixels = decoder.DecodeRaw(file.Faces[i].MipMaps[0].Data,
+ (int)file.Faces[i].Width, (int)file.Faces[i].Height, CompressionFormat.Bc1);
+ var decodedImage = new Memory2D(rawPixels, (int)file.Faces[i].Height, (int)file.Faces[i].Width);
- TestHelper.AssertImagesEqual(originalCubeMap[i], image, encoder.OutputOptions.Quality);
- image.Dispose();
+ TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality);
}
}
@@ -68,10 +66,12 @@ public async Task EncodeCubemapToKtxAsync()
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);
+ 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 = new Memory2D(rawPixels,
+ (int)file.MipMaps[0].Faces[i].Height, (int)file.MipMaps[0].Faces[i].Width);
- TestHelper.AssertImagesEqual(originalCubeMap[i], image, encoder.OutputOptions.Quality);
- image.Dispose();
+ TestHelper.AssertImagesEqual(originalCubeMap[i], decodedImage, encoder.OutputOptions.Quality);
}
}
@@ -79,10 +79,10 @@ public async Task EncodeCubemapToKtxAsync()
public async Task EncodeToRawBytesAsync()
{
var data = await encoder.EncodeToRawBytesAsync(originalImage);
- var image = decoder.DecodeRawToImageRgba32(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1);
+ var rawPixels = decoder.DecodeRaw(data[0], originalImage.Width, originalImage.Height, CompressionFormat.Bc1);
+ var image = new Memory2D(rawPixels, originalImage.Height, originalImage.Width);
TestHelper.AssertImagesEqual(originalImage, image, encoder.OutputOptions.Quality);
- image.Dispose();
}
}
}
diff --git a/BCnEncTests.Shared/EncodingTest.cs b/BCnEncTests.Shared/EncodingTest.cs
new file mode 100644
index 0000000..e4f3edd
--- /dev/null
+++ b/BCnEncTests.Shared/EncodingTest.cs
@@ -0,0 +1,317 @@
+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/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/ProgressTests.cs b/BCnEncTests.Shared/ProgressTests.cs
similarity index 92%
rename from BCnEncTests/ProgressTests.cs
rename to BCnEncTests.Shared/ProgressTests.cs
index 1a58c31..83dab93 100644
--- a/BCnEncTests/ProgressTests.cs
+++ b/BCnEncTests.Shared/ProgressTests.cs
@@ -3,12 +3,10 @@
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 CommunityToolkit.HighPerformance;
using Xunit;
using Xunit.Abstractions;
@@ -20,7 +18,7 @@ public class ProgressTests
public ProgressTests(ITestOutputHelper output) => this.output = output;
- private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Image testImage, int expectedTotalBlocks)
+ private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Memory2D testImage, int expectedTotalBlocks)
{
var lastProgress = new ProgressElement(0, 1);
@@ -33,8 +31,10 @@ private async Task ExecuteEncodeProgressReport(BcEncoder encoder, Image
lastProgress = element;
});
- await using var ms = new MemoryStream();
- await encoder.EncodeToStreamAsync(testImage, ms);
+ using (var ms = new MemoryStream())
+ {
+ await encoder.EncodeToStreamAsync(testImage, ms);
+ }
output.WriteLine("LastProgress = " + lastProgress);
@@ -242,9 +242,9 @@ public async Task EncodeProgressReportParallelKtx()
var expectedTotal = 0;
- for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++)
+ for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++)
{
- encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH);
+ encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH);
expectedTotal += encoder.GetBlockCount(mW, mH);
}
@@ -263,9 +263,9 @@ public async Task EncodeProgressReportNonParallelKtx()
var expectedTotal = 0;
- for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++)
+ for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++)
{
- encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH);
+ encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH);
expectedTotal += encoder.GetBlockCount(mW, mH);
}
@@ -314,9 +314,9 @@ public async Task EncodeProgressReportParallelDds()
var expectedTotal = 0;
- for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++)
+ for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++)
{
- encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH);
+ encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH);
expectedTotal += encoder.GetBlockCount(mW, mH);
}
@@ -335,9 +335,9 @@ public async Task EncodeProgressReportNonParallelDds()
var expectedTotal = 0;
- for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage); i++)
+ for (var i = 0; i < encoder.CalculateNumberOfMipLevels(testImage.Width, testImage.Height); i++)
{
- encoder.CalculateMipMapSize(testImage, i, out var mW, out var mH);
+ encoder.CalculateMipMapSize(testImage.Width, testImage.Height, i, out var mW, out var mH);
expectedTotal += encoder.GetBlockCount(mW, mH);
}
@@ -375,7 +375,7 @@ public async Task EncodeProgressReportNonParallelOneMipDds()
}
}
- class SynchronousProgress : IProgress
+ internal class SynchronousProgress : IProgress
{
private readonly Action handler;
diff --git a/BCnEncTests.Shared/RawTests.cs b/BCnEncTests.Shared/RawTests.cs
new file mode 100644
index 0000000..ebd71e2
--- /dev/null
+++ b/BCnEncTests.Shared/RawTests.cs
@@ -0,0 +1,123 @@
+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 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 = new Memory2D(decodedPixels, 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 = new Memory2D(rawPixels, 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);
+ 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 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 = new Memory2D(rawPixels, mipHeight, mipWidth);
+
+ var originalColors = TestHelper.GetSinglePixelArrayAsColors(resized[i]);
+ 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[] ResizeImageToMips(Memory2D image)
+ {
+ int numMips = 0;
+ var chain = MipMapper.GenerateMipChain(image, ref numMips);
+ return chain;
+ }
+ }
+}
diff --git a/BCnEncTests/SingleBlockTests.cs b/BCnEncTests.Shared/SingleBlockTests.cs
similarity index 59%
rename from BCnEncTests/SingleBlockTests.cs
rename to BCnEncTests.Shared/SingleBlockTests.cs
index 89b0778..74691ed 100644
--- a/BCnEncTests/SingleBlockTests.cs
+++ b/BCnEncTests.Shared/SingleBlockTests.cs
@@ -1,22 +1,16 @@
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)]
@@ -26,6 +20,8 @@ public class SingleBlockTests
public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQuality quality)
{
var testImage = ImageLoader.TestAlpha1;
+ int height = testImage.Height;
+ int width = testImage.Width;
var encoder = new BcEncoder()
{
@@ -36,39 +32,37 @@ public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQ
}
};
- var pixels = TestHelper.GetSinglePixelArray(testImage);
- var colors = MemoryMarshal.Cast(pixels)
- .AsSpan2D(testImage.Height, testImage.Width);
+ var colors = testImage.Span;
var ms = new MemoryStream();
- for (var y = 0; y < testImage.Height; y+=4)
+ for (var y = 0; y < height; y += 4)
{
- for (var x = 0; x < testImage.Width; x+=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(testImage.Width, testImage.Height));
+ Assert.Equal(ms.Position, encoder.GetBlockSize() * encoder.GetBlockCount(width, height));
ms.Position = 0;
var decoder = new BcDecoder();
- var decoded = new ColorRgba32[testImage.Height, testImage.Width];
+ var decoded = new ColorRgba32[height, width];
- for (var y = 0; y < testImage.Height; y += 4)
+ for (var y = 0; y < height; y += 4)
{
- for (var x = 0; x < testImage.Width; x += 4)
+ for (var x = 0; x < 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 oPixels = TestHelper.GetSinglePixelArrayAsColors(testImage);
+ var dPixels = FlattenDecoded(decoded, height, width);
var psnr = ImageQuality.PeakSignalToNoiseRatio(oPixels, dPixels,
format != CompressionFormat.Bc1);
@@ -84,6 +78,8 @@ public void SingleBlockEncodeDecodeStream(CompressionFormat format, CompressionQ
public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality quality)
{
var testImage = ImageLoader.TestAlpha1;
+ int height = testImage.Height;
+ int width = testImage.Width;
var encoder = new BcEncoder()
{
@@ -94,50 +90,54 @@ public void SingleBlockEncodeDecode(CompressionFormat format, CompressionQuality
}
};
- var pixels = TestHelper.GetSinglePixelArray(testImage);
- var colors = MemoryMarshal.Cast(pixels)
- .AsSpan2D(testImage.Height, testImage.Width);
+ var colors = testImage.Span;
- Span buffer = new byte[encoder.GetBlockSize() * encoder.GetBlockCount(testImage.Width, testImage.Height)];
+ var encMs = new MemoryStream();
- var blockIndex = 0;
- for (var y = 0; y < testImage.Height; y += 4)
+ for (var y = 0; y < height; y += 4)
{
- for (var x = 0; x < testImage.Width; x += 4)
+ for (var x = 0; x < width; x += 4)
{
- var bytes = encoder.EncodeBlock(colors.Slice(y, x, 4, 4));
- bytes.CopyTo(buffer.Slice(blockIndex * encoder.GetBlockSize()));
- blockIndex++;
+ encoder.EncodeBlock(colors.Slice(y, x, 4, 4), encMs);
}
}
+ var buffer = encMs.ToArray();
+
var decoder = new BcDecoder();
- var decoded = new ColorRgba32[testImage.Height, testImage.Width];
+ var decoded = new ColorRgba32[height, width];
- blockIndex = 0;
- for (var y = 0; y < testImage.Height; y += 4)
+ var blockIndex = 0;
+ for (var y = 0; y < height; y += 4)
{
- for (var x = 0; x < testImage.Width; x += 4)
+ for (var x = 0; x < width; x += 4)
{
-
decoder.DecodeBlock(
- buffer.Slice(
+ new Span(buffer,
blockIndex * decoder.GetBlockSize(format),
- 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 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/BCnEncTests.csproj b/BCnEncTests/BCnEncTests.csproj
index 7dab56d..74aa454 100644
--- a/BCnEncTests/BCnEncTests.csproj
+++ b/BCnEncTests/BCnEncTests.csproj
@@ -7,6 +7,7 @@
+
@@ -24,4 +25,6 @@
+
+
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/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/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/HdrImageTests.cs b/BCnEncTests/HdrImageTests.cs
index 9e05ab2..14dc3d1 100644
--- a/BCnEncTests/HdrImageTests.cs
+++ b/BCnEncTests/HdrImageTests.cs
@@ -2,9 +2,12 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
+using System.Threading.Tasks;
+using BCnEncoder.Decoder;
using BCnEncoder.Encoder;
using BCnEncoder.Shared;
using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@@ -39,5 +42,25 @@ public void LoadHdr()
TestHelper.AssertImagesEqual(HdrLoader.ReferenceKiara, img2, CompressionQuality.BestQuality);
}
+
+ [Fact]
+ public async Task DecodeAllMipMapsHdrStreamAsync()
+ {
+ var encoder = new BcEncoder();
+ encoder.OutputOptions.Format = CompressionFormat.Bc6U;
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ encoder.OutputOptions.GenerateMipMaps = true;
+
+ var decoder = new BcDecoder();
+ var input = HdrLoader.TestHdrKiara;
+ var ktxWithMips = encoder.EncodeToKtxHdr(new Memory2D(input.pixels, input.height, input.width));
+ using var ms = new MemoryStream();
+ ktxWithMips.Write(ms);
+ ms.Position = 0;
+
+ var images = await decoder.DecodeAllMipMapsHdr2DAsync(ms);
+ Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length);
+ Assert.True(images.Length > 1);
+ }
}
}
diff --git a/BCnEncTests/ImageSharpTests.cs b/BCnEncTests/ImageSharpTests.cs
new file mode 100644
index 0000000..855a051
--- /dev/null
+++ b/BCnEncTests/ImageSharpTests.cs
@@ -0,0 +1,388 @@
+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;
+
+namespace BCnEncTests
+{
+ public class ImageSharpEncodingTests
+ {
+ private readonly BcEncoder encoder;
+ private readonly BcDecoder decoder;
+ private readonly Image original;
+ private readonly Image[] cubemap;
+
+ public ImageSharpEncodingTests()
+ {
+ encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ decoder = new BcDecoder();
+ original = ImageLoader.LoadTestImageSharp("../../../testImages/test_gradient_1_512.jpg");
+ cubemap = new[]
+ {
+ ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/right.png"),
+ ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/left.png"),
+ ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/top.png"),
+ ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/bottom.png"),
+ ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/back.png"),
+ ImageLoader.LoadTestImageSharp("../../../testImages/cubemap/forward.png"),
+ };
+ }
+
+ [Fact]
+ public void EncodeToKtx()
+ {
+ var ktx = encoder.EncodeToKtx(original);
+ using var decoded = decoder.DecodeToImageRgba32(ktx);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void EncodeToDds()
+ {
+ var dds = encoder.EncodeToDds(original);
+ using var decoded = decoder.DecodeToImageRgba32(dds);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void EncodeToStreamKtx()
+ {
+ encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx;
+ using var ms = new MemoryStream();
+ encoder.EncodeToStream(original, ms);
+ ms.Position = 0;
+ using var decoded = decoder.DecodeToImageRgba32(ms);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void EncodeToStreamDds()
+ {
+ encoder.OutputOptions.FileFormat = OutputFileFormat.Dds;
+ using var ms = new MemoryStream();
+ encoder.EncodeToStream(original, ms);
+ ms.Position = 0;
+ using var decoded = decoder.DecodeToImageRgba32(ms);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void EncodeToRawBytes()
+ {
+ var rawBytes = encoder.EncodeToRawBytes(original);
+ using var decoded = decoder.DecodeRawToImageRgba32(rawBytes[0], original.Width, original.Height, CompressionFormat.Bc1);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void EncodeToRawBytesSingleMip()
+ {
+ var mip0 = encoder.EncodeToRawBytes(original, 0, out var mipWidth, out var mipHeight);
+ Assert.Equal(original.Width, mipWidth);
+ Assert.Equal(original.Height, mipHeight);
+ Assert.True(mip0.Length > 0);
+ }
+
+ [Fact]
+ public void CalculateMipLevelCount()
+ {
+ var fromImage = encoder.CalculateNumberOfMipLevels(original);
+ var fromDims = encoder.CalculateNumberOfMipLevels(original.Width, original.Height);
+ Assert.Equal(fromDims, fromImage);
+ }
+
+ [Fact]
+ public void CalculateMipMapSize()
+ {
+ encoder.CalculateMipMapSize(original, 0, out var w0, out var h0);
+ Assert.Equal(original.Width, w0);
+ Assert.Equal(original.Height, h0);
+
+ encoder.CalculateMipMapSize(original, 1, out var w1, out var h1);
+ Assert.Equal(original.Width / 2, w1);
+ Assert.Equal(original.Height / 2, h1);
+ }
+
+ [Fact]
+ public void EncodeCubeMapToKtx()
+ {
+ var ktx = encoder.EncodeCubeMapToKtx(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5]);
+ Assert.Equal(6, ktx.MipMaps[0].Faces.Length);
+ for (var i = 0; i < 6; i++)
+ {
+ var face = ktx.MipMaps[0].Faces[i];
+ using var decoded = decoder.DecodeRawToImageRgba32(face.Data, (int)face.Width, (int)face.Height, CompressionFormat.Bc1);
+ TestHelper.AssertImagesEqual(cubemap[i], decoded, encoder.OutputOptions.Quality);
+ }
+ }
+
+ [Fact]
+ public void EncodeCubeMapToDds()
+ {
+ var dds = encoder.EncodeCubeMapToDds(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5]);
+ Assert.Equal(6, dds.Faces.Count);
+ for (var i = 0; i < 6; i++)
+ {
+ var face = dds.Faces[i].MipMaps[0];
+ using var decoded = decoder.DecodeRawToImageRgba32(face.Data, (int)face.Width, (int)face.Height, CompressionFormat.Bc1);
+ TestHelper.AssertImagesEqual(cubemap[i], decoded, encoder.OutputOptions.Quality);
+ }
+ }
+
+ [Fact]
+ public void EncodeCubeMapToStream()
+ {
+ encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx;
+ using var ms = new MemoryStream();
+ encoder.EncodeCubeMapToStream(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5], ms);
+ ms.Position = 0;
+ var ktx = KtxFile.Load(ms);
+ Assert.Equal(6, ktx.MipMaps[0].Faces.Length);
+ }
+
+ [Fact]
+ public async Task EncodeToKtxAsync()
+ {
+ var ktx = await encoder.EncodeToKtxAsync(original);
+ using var decoded = decoder.DecodeToImageRgba32(ktx);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task EncodeToDdsAsync()
+ {
+ var dds = await encoder.EncodeToDdsAsync(original);
+ using var decoded = decoder.DecodeToImageRgba32(dds);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task EncodeToStreamAsync()
+ {
+ encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx;
+ using var ms = new MemoryStream();
+ await encoder.EncodeToStreamAsync(original, ms);
+ ms.Position = 0;
+ using var decoded = decoder.DecodeToImageRgba32(ms);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task EncodeToRawBytesAsync()
+ {
+ var rawBytes = await encoder.EncodeToRawBytesAsync(original);
+ using var decoded = decoder.DecodeRawToImageRgba32(rawBytes[0], original.Width, original.Height, CompressionFormat.Bc1);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task EncodeCubeMapToKtxAsync()
+ {
+ var ktx = await encoder.EncodeCubeMapToKtxAsync(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5]);
+ Assert.Equal(6, ktx.MipMaps[0].Faces.Length);
+ }
+
+ [Fact]
+ public async Task EncodeCubeMapToDdsAsync()
+ {
+ var dds = await encoder.EncodeCubeMapToDdsAsync(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5]);
+ Assert.Equal(6, dds.Faces.Count);
+ }
+
+ [Fact]
+ public async Task EncodeCubeMapToStreamAsync()
+ {
+ encoder.OutputOptions.FileFormat = OutputFileFormat.Ktx;
+ using var ms = new MemoryStream();
+ await encoder.EncodeCubeMapToStreamAsync(cubemap[0], cubemap[1], cubemap[2], cubemap[3], cubemap[4], cubemap[5], ms);
+ ms.Position = 0;
+ var ktx = KtxFile.Load(ms);
+ Assert.Equal(6, ktx.MipMaps[0].Faces.Length);
+ }
+ }
+
+ public class ImageSharpDecodingTests
+ {
+ private readonly BcEncoder encoder;
+ private readonly BcDecoder decoder;
+ private readonly Image original;
+ private readonly KtxFile encodedKtx;
+ private readonly DdsFile encodedDds;
+ private readonly byte[] rawEncoded;
+
+ public ImageSharpDecodingTests()
+ {
+ encoder = new BcEncoder();
+ encoder.OutputOptions.Quality = CompressionQuality.Fast;
+ decoder = new BcDecoder();
+ original = ImageLoader.LoadTestImageSharp("../../../testImages/test_gradient_1_512.jpg");
+ encodedKtx = encoder.EncodeToKtx(ImageLoader.TestGradient1);
+ encodedDds = encoder.EncodeToDds(ImageLoader.TestGradient1);
+ rawEncoded = encoder.EncodeToRawBytes(ImageLoader.TestGradient1)[0];
+ }
+
+ [Fact]
+ public void DecodeKtxFile()
+ {
+ using var decoded = decoder.DecodeToImageRgba32(encodedKtx);
+ Assert.Equal(original.Width, decoded.Width);
+ Assert.Equal(original.Height, decoded.Height);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void DecodeDdsFile()
+ {
+ using var decoded = decoder.DecodeToImageRgba32(encodedDds);
+ Assert.Equal(original.Width, decoded.Width);
+ Assert.Equal(original.Height, decoded.Height);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void DecodeStream()
+ {
+ using var ms = new MemoryStream();
+ encodedKtx.Write(ms);
+ ms.Position = 0;
+ using var decoded = decoder.DecodeToImageRgba32(ms);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void DecodeAllMipMapsKtx()
+ {
+ encoder.OutputOptions.GenerateMipMaps = true;
+ var ktxWithMips = encoder.EncodeToKtx(ImageLoader.TestGradient1);
+ var images = decoder.DecodeAllMipMapsToImageRgba32(ktxWithMips);
+
+ Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length);
+ Assert.True(images.Length > 1);
+ TestHelper.AssertImagesEqual(original, images[0], encoder.OutputOptions.Quality);
+
+ foreach (var img in images) img.Dispose();
+ }
+
+ [Fact]
+ public void DecodeAllMipMapsDds()
+ {
+ encoder.OutputOptions.GenerateMipMaps = true;
+ var ddsWithMips = encoder.EncodeToDds(ImageLoader.TestGradient1);
+ var images = decoder.DecodeAllMipMapsToImageRgba32(ddsWithMips);
+
+ Assert.Equal((int)ddsWithMips.header.dwMipMapCount, images.Length);
+ Assert.True(images.Length > 1);
+
+ foreach (var img in images) img.Dispose();
+ }
+
+ [Fact]
+ public void DecodeAllMipMapsStream()
+ {
+ encoder.OutputOptions.GenerateMipMaps = true;
+ var ktxWithMips = encoder.EncodeToKtx(ImageLoader.TestGradient1);
+ using var ms = new MemoryStream();
+ ktxWithMips.Write(ms);
+ ms.Position = 0;
+
+ var images = decoder.DecodeAllMipMapsToImageRgba32(ms);
+ Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length);
+
+ foreach (var img in images) img.Dispose();
+ }
+
+ [Fact]
+ public void DecodeRaw()
+ {
+ using var decoded = decoder.DecodeRawToImageRgba32(rawEncoded, original.Width, original.Height, CompressionFormat.Bc1);
+ Assert.Equal(original.Width, decoded.Width);
+ Assert.Equal(original.Height, decoded.Height);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public void DecodeRawStream()
+ {
+ using var ms = new MemoryStream(rawEncoded);
+ using var decoded = decoder.DecodeRawToImageRgba32(ms, original.Width, original.Height, CompressionFormat.Bc1);
+ Assert.Equal(original.Width, decoded.Width);
+ Assert.Equal(original.Height, decoded.Height);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task DecodeKtxFileAsync()
+ {
+ using var decoded = await decoder.DecodeToImageRgba32Async(encodedKtx);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task DecodeDdsFileAsync()
+ {
+ using var decoded = await decoder.DecodeToImageRgba32Async(encodedDds);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task DecodeStreamAsync()
+ {
+ using var ms = new MemoryStream();
+ encodedKtx.Write(ms);
+ ms.Position = 0;
+ using var decoded = await decoder.DecodeToImageRgba32Async(ms);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task DecodeAllMipMapsKtxAsync()
+ {
+ encoder.OutputOptions.GenerateMipMaps = true;
+ var ktxWithMips = encoder.EncodeToKtx(ImageLoader.TestGradient1);
+ var images = await decoder.DecodeAllMipMapsToImageRgba32Async(ktxWithMips);
+
+ Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length);
+ Assert.True(images.Length > 1);
+
+ foreach (var img in images) img.Dispose();
+ }
+
+ [Fact]
+ public async Task DecodeAllMipMapsStreamAsync()
+ {
+ encoder.OutputOptions.GenerateMipMaps = true;
+ var ktxWithMips = encoder.EncodeToKtx(ImageLoader.TestGradient1);
+ using var ms = new MemoryStream();
+ ktxWithMips.Write(ms);
+ ms.Position = 0;
+
+ var images = await decoder.DecodeAllMipMapsToImageRgba32Async(ms);
+ Assert.Equal((int)ktxWithMips.header.NumberOfMipmapLevels, images.Length);
+
+ foreach (var img in images) img.Dispose();
+ }
+
+ [Fact]
+ public async Task DecodeRawAsync()
+ {
+ using var decoded = await decoder.DecodeRawToImageRgba32Async(rawEncoded, original.Width, original.Height, CompressionFormat.Bc1);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+
+ [Fact]
+ public async Task DecodeRawStreamAsync()
+ {
+ using var ms = new MemoryStream(rawEncoded);
+ using var decoded = await decoder.DecodeRawToImageRgba32Async(ms, original.Width, original.Height, CompressionFormat.Bc1);
+ TestHelper.AssertImagesEqual(original, decoded, encoder.OutputOptions.Quality);
+ }
+ }
+}
diff --git a/BCnEncTests/MipMapperTests.cs b/BCnEncTests/MipMapperTests.cs
new file mode 100644
index 0000000..5da9c7e
--- /dev/null
+++ b/BCnEncTests/MipMapperTests.cs
@@ -0,0 +1,79 @@
+using System;
+using BCnEncoder.Shared;
+using BCnEncTests.Support;
+using CommunityToolkit.HighPerformance;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Processing;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace BCnEncTests
+{
+ public class MipMapperTests
+ {
+ private readonly ITestOutputHelper output;
+
+ public MipMapperTests(ITestOutputHelper output) => this.output = output;
+
+ [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);
+ }
+
+ ///
+ /// Compares each mip level produced by MipMapper against the equivalent
+ /// ImageSharp Box-filter resize (Compand=false so both operate in sRGB
+ /// byte space without gamma correction). The PSNR should be very high
+ /// because both algorithms perform the same 2x2 box average.
+ ///
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(3)]
+ [InlineData(4)]
+ public void MipLevelMatchesImageSharpBoxFilter(int mipLevel)
+ {
+ var image = ImageLoader.TestGradient1;
+ var numMips = mipLevel + 1;
+ var chain = MipMapper.GenerateMipChain(image, ref numMips);
+
+ var mipW = chain[mipLevel].Width;
+ var mipH = chain[mipLevel].Height;
+
+ // ImageSharp Box sampler + Compand=false: no gamma correction,
+ // averages pixel values in sRGB space — matches MipMapper exactly.
+ using var imageSharp = ImageLoader.LoadTestImageSharp("../../../testImages/test_gradient_1_512.jpg");
+ using var reference = imageSharp.Clone(x => x.Resize(new ResizeOptions
+ {
+ Size = new Size(mipW, mipH),
+ Sampler = KnownResamplers.Box,
+ Compand = false
+ }));
+
+ var mipColors = TestHelper.GetSinglePixelArrayAsColors(chain[mipLevel]);
+ var refColors = TestHelper.GetSinglePixelArrayAsColors(reference);
+
+ var psnr = ImageQuality.PeakSignalToNoiseRatio(mipColors, refColors);
+ output.WriteLine($"Mip level {mipLevel} ({mipW}x{mipH}): PSNR vs ImageSharp Box = {psnr:F2} dB");
+
+ Assert.True(psnr > 40,
+ $"Mip level {mipLevel}: PSNR vs ImageSharp Box was {psnr:F2} dB (expected > 40)");
+ }
+ }
+}
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/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 ToMemory2D(Image image)
+ {
+ var pixels = new ColorRgba32[image.Width * image.Height];
+ for (var y = 0; y < image.Height; y++)
+ for (var x = 0; x < image.Width; x++)
+ {
+ var p = image[x, y];
+ pixels[y * image.Width + x] = new ColorRgba32(p.R, p.G, p.B, p.A);
+ }
+ return new Memory2D(pixels, image.Height, image.Width);
+ }
}
public static class DdsLoader
diff --git a/BCnEncTests/Support/TestHelper.cs b/BCnEncTests/Support/TestHelper.cs
index 40bc50c..9d6349a 100644
--- a/BCnEncTests/Support/TestHelper.cs
+++ b/BCnEncTests/Support/TestHelper.cs
@@ -20,17 +20,15 @@ public static class TestHelper
{
#region Assertions
- public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality)
+ public static void AssertPixelsEqual(Span originalPixels, Span pixels, CompressionQuality quality, ITestOutputHelper output = null)
{
- var psnr = ImageQuality.PeakSignalToNoiseRatio(
- MemoryMarshal.Cast(originalPixels),
- MemoryMarshal.Cast(pixels));
- AssertPSNR(psnr, quality);
+ 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);
+ var rmse = ImageQuality.CalculateLogRMSE(originalPixels, pixels);
AssertRMSE(rmse, quality, output);
}
@@ -40,6 +38,12 @@ public static void AssertImagesEqual(Image original, Image image
AssertPSNR(psnr, quality);
}
+ 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
@@ -50,23 +54,23 @@ public static void ExecuteDecodingTest(KtxFile file, string outputFile)
Assert.Equal((uint)1, file.header.NumberOfFaces);
var decoder = new BcDecoder();
- using var image = decoder.DecodeToImageRgba32(file);
+ var pixels = decoder.Decode2D(file);
- Assert.Equal((uint)image.Width, file.header.PixelWidth);
- Assert.Equal((uint)image.Height, file.header.PixelHeight);
+ Assert.Equal((uint)pixels.Width, file.header.PixelWidth);
+ Assert.Equal((uint)pixels.Height, file.header.PixelHeight);
using var outFs = File.OpenWrite(outputFile);
- image.SaveAsPng(outFs);
+ SaveAsPng(pixels, outFs);
}
#region Dds
- public static void ExecuteDdsWritingTest(Image image, CompressionFormat format, string outputFile)
+ public static void ExecuteDdsWritingTest(Memory2D image, CompressionFormat format, string outputFile)
{
ExecuteDdsWritingTest(new[] { image }, format, outputFile);
}
- public static void ExecuteDdsWritingTest(Image[] images, CompressionFormat format, string outputFile)
+ public static void ExecuteDdsWritingTest(Memory2D[] images, CompressionFormat format, string outputFile)
{
var encoder = new BcEncoder();
encoder.OutputOptions.Quality = CompressionQuality.Fast;
@@ -93,7 +97,7 @@ public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string
var decoder = new BcDecoder();
decoder.InputOptions.DdsBc1ExpectAlpha = assertAlpha;
- var images = decoder.DecodeAllMipMapsToImageRgba32(file);
+ var images = decoder.DecodeAllMipMaps2D(file);
Assert.Equal((uint)images[0].Width, file.header.dwWidth);
Assert.Equal((uint)images[0].Height, file.header.dwHeight);
@@ -107,8 +111,7 @@ public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string
}
using var outFs = File.OpenWrite(string.Format(outputFile, i));
- images[i].SaveAsPng(outFs);
- images[i].Dispose();
+ SaveAsPng(images[i], outFs);
}
}
@@ -116,7 +119,7 @@ public static void ExecuteDdsReadingTest(DdsFile file, DxgiFormat format, string
#region Cancellation
- public static async Task ExecuteCancellationTest(Image image, bool isParallel)
+ public static async Task ExecuteCancellationTest(Memory2D image, bool isParallel)
{
var encoder = new BcEncoder(CompressionFormat.Bc7);
encoder.OutputOptions.Quality = CompressionQuality.Fast;
@@ -124,14 +127,14 @@ public static async Task ExecuteCancellationTest(Image image, bool isPar
var source = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
await Assert.ThrowsAnyAsync(() =>
- encoder.EncodeToRawBytesAsync(image, 0, source.Token));
+ encoder.EncodeToRawBytesAsync(image, source.Token));
}
#endregion
#endregion
- public static float DecodeKtxCheckPSNR(string filename, Image original)
+ public static float DecodeKtxCheckPSNR(string filename, Memory2D original)
{
using var fs = File.OpenRead(filename);
var ktx = KtxFile.Load(fs);
@@ -139,9 +142,9 @@ public static float DecodeKtxCheckPSNR(string filename, Image original)
{
OutputOptions = { Bc4Component = ColorComponent.Luminance }
};
- using var img = decoder.DecodeToImageRgba32(ktx);
+ var decoded = decoder.Decode2D(ktx);
- return CalculatePSNR(original, img);
+ return CalculatePSNR(original, decoded);
}
public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original)
@@ -157,8 +160,7 @@ public static float DecodeKtxCheckRMSEHdr(string filename, HdrImage original)
return ImageQuality.CalculateLogRMSE(original.pixels, decoded);
}
-
- public static void ExecuteEncodingTest(Image image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output)
+ public static void ExecuteEncodingTest(Memory2D image, CompressionFormat format, CompressionQuality quality, string filename, ITestOutputHelper output)
{
var encoder = new BcEncoder();
encoder.OutputOptions.Quality = quality;
@@ -182,7 +184,7 @@ public static void ExecuteHdrEncodingTest(HdrImage image, CompressionFormat form
encoder.OutputOptions.Format = format;
var fs = File.OpenWrite(filename);
- encoder.EncodeToStreamHdr(image.pixels.AsMemory().AsMemory2D(image.height, image.width), fs);
+ encoder.EncodeToStreamHdr(new Memory2D(image.pixels, image.height, image.width), fs);
fs.Close();
var rmse = DecodeKtxCheckRMSEHdr(filename, image);
@@ -198,15 +200,24 @@ private static float CalculatePSNR(Image original, Image decoded
return ImageQuality.PeakSignalToNoiseRatio(pixels, pixels2, countAlpha);
}
- public static void AssertPSNR(float psnr, CompressionQuality 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);
+ Assert.True(psnr > 25, $"PSNR was less than 25: {psnr} , quality: {quality}");
}
else
{
- Assert.True(psnr > 30);
+ Assert.True(psnr > 30, $"PSNR was less than 30: {psnr} , quality: {quality}");
}
}
@@ -237,6 +248,20 @@ public static ColorRgba32[] GetSinglePixelArrayAsColors(Image original)
return pixels;
}
+ 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 T[] GetSinglePixelArray(Image original) where T : unmanaged, IPixel
{
T[] pixels = new T[original.Width * original.Height];
@@ -261,5 +286,35 @@ public static void SetSinglePixelArray(Image dest, T[] pixels) where T : u
}
}
}
+
+ public static void SaveAsPng(Memory2D img, Stream fs)
+ {
+ var imageRgba = new Image(img.Width, img.Height);
+ var span = img.Span;
+ for (var y = 0; y < img.Height; y++)
+ {
+ for (var x = 0; x < img.Width; x++)
+ {
+ var col = span[y, x];
+ imageRgba[x, y] = new Rgba32(col.r, col.g, col.b, col.a);
+ }
+ }
+ imageRgba.SaveAsPng(fs);
+ }
+
+ 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);
+ }
}
}